Skip to content

Commit

Permalink
Merge pull request #363 from stone-lyl/main
Browse files Browse the repository at this point in the history
feat: Add Zod schema for request method validation in `WorkspaceApiClient`
  • Loading branch information
stone-lyl authored Jan 10, 2025
2 parents 861c1e2 + ddb8b93 commit df2480b
Show file tree
Hide file tree
Showing 34 changed files with 398 additions and 151 deletions.
5 changes: 3 additions & 2 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"watch:test": "yarn run -T vitest",
"build:esm": "vite build --config vite.config.esm.ts",
"build:cjs": "tsc -p tsconfig.build.json",
"watch": "yarn build:esm --watch",
"watch": "yarn build:esm --watch && yarn build:cjs --watch",
"build": "yarn build:esm && yarn build:cjs && yarn build-vite",
"release": "yarn run -T release-it"
},
Expand All @@ -56,7 +56,8 @@
"axios": "^1.3.4",
"dotenv": "^16.0.3",
"nanoid": "3",
"rxjs": "^7.8.1"
"rxjs": "^7.8.1",
"zod": "^3.24.1"
},
"devDependencies": {
"@types/node": "18.14.2",
Expand Down
7 changes: 4 additions & 3 deletions packages/core/src/InputObserverController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ItemValue } from './types/ItemValue';
import { RequestObserverType } from './types/InputObserveConfig';
import {
CancelObservation,
NodesStatusInfo,
ObserveLinkCounts,
ObserveLinkItems,
ObserveLinkUpdate,
Expand All @@ -10,7 +11,7 @@ import {
import { bufferTime, Subject, Subscription } from 'rxjs';
import { filter, map, tap } from 'rxjs/operators';
import { LinkId } from './types/Link';
import { GetDataFromStorage, LinkItems } from './types/GetDataFromStorage';
import { GetDataFromStorageParams, LinkItems } from './types/GetDataFromStorageParams';
import { NodeStatus } from './Executor';
import { NodeId } from './types/Node';
import { ObserverStorage } from './types/ObserverStorage';
Expand Down Expand Up @@ -55,7 +56,7 @@ export class InputObserverController {
linkId,
limit = 100,
offset = 0
}: GetDataFromStorage): Promise<LinkItems> {
}: GetDataFromStorageParams): Promise<LinkItems> {
const items: LinkItems = {};
const currentItems = await this.storage.getLinkItems({linkId, offset, limit}) ?? [];
items[linkId] = currentItems;
Expand Down Expand Up @@ -137,7 +138,7 @@ export class InputObserverController {
}
}));
observer.onReceive({
nodes: nodes as {nodeId: NodeId, status: NodeStatus}[]
nodes: nodes as NodesStatusInfo[]
});
})
).subscribe();
Expand Down
10 changes: 7 additions & 3 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ export { multiline } from './utils/multiline'
export { pascalToSentenceCase } from './utils/pascalToSentenceCase'
export { flattenObjectOneLevel } from './utils/flattenObjectOneLevel'
export { createDataStoryId } from './utils/createDataStoryId'
export type { NodeDescription } from './types/NodeDescription'
export type { NodeDescription, NodeDescriptionRequest, NodeDescriptionResponse } from './types/NodeDescription'
export { NodeDescriptionResponseSchema, NodeDescriptionRequestSchema} from './types/NodeDescription';
export type { Port, AbstractPort } from './types/Port'
export type { Param, ParamValue } from './Param'
export { Application } from './Application'
Expand Down Expand Up @@ -44,8 +45,10 @@ export { jsExpressionEvaluation } from './Param/evaluations/jsExpressionEvaluati
export { numberCast } from './Param/casts/numberCast'
export { stringCast } from './Param/casts/stringCast'
export { core } from './core'
export type { LinkCountInfo, ObserveLinkCounts, ExecutionObserver, ObserveLinkItems, ObserveNodeStatus, CancelObservation, ObserveLinkUpdate} from './types/ExecutionObserver'
export type { GetDataFromStorage } from './types/GetDataFromStorage'
export type { LinkCountInfo, ObserveLinkCounts, ExecutionObserver, ObserveLinkItems, ObserveNodeStatus, CancelObservation, ObserveLinkUpdate, NodesStatusInfo} from './types/ExecutionObserver'
export {LinkCountInfoSchema, ObserveLinkCountsSchema, ObserveLinkItemsSchema, ObserveNodeStatusSchema, CancelObservationSchema, ObserveLinkUpdateSchema, NodesStatusInfoSchema as NodesStatusSchema } from './types/ExecutionObserver';
export type { GetDataFromStorageParams, LinkItems, GetDataFromStorageResponse } from './types/GetDataFromStorageParams'
export { GetDataFromStorageParamsSchema, GetDataFromStorageResponseSchema, LinkItemsSchema } from './types/GetDataFromStorageParams'
export * as nodes from './computers'
export * from './Param'
export { Registry } from './Registry'
Expand All @@ -55,4 +58,5 @@ export type { NodeStatus } from './Executor';
export type { ObserverStorage, GetLinkItemsParams } from './types/ObserverStorage'
export { DiagramObserverStorage } from './storage/diagramObserverStorage'
export type { LinkItemsParam } from './types/LinkItemsParam'
export { LinkItemsParamSchema } from './types/LinkItemsParam'
export type { LinksCountParam } from './types/LinksCountParam'
230 changes: 171 additions & 59 deletions packages/core/src/types/ExecutionObserver.ts
Original file line number Diff line number Diff line change
@@ -1,62 +1,174 @@
import { RequestObserverType } from './InputObserveConfig';
import { NotifyObserversCallback } from './NotifyObserversCallback';
import { LinkCount, LinkId } from './Link';
import { NodeStatus } from '../Executor';
import { NotifyObserversCallback, NotifyObserversCallbackSchema } from './NotifyObserversCallback';
import { LinkId } from './Link';
import { NodeId } from './Node';
import { z } from 'zod';

export type ObserveLinkItems = {
type: RequestObserverType.observeLinkItems,
linkIds: LinkId[],
onReceive: NotifyObserversCallback,
observerId: string,
direction?: 'pull' | 'push',
onlyFirstNItems?: number,
throttleMs?: number,
msgId?: string;
}

export interface LinkCountInfo {
count: LinkCount;
linkId: LinkId;
}

export type ObserveLinkCounts = {
type: RequestObserverType.observeLinkCounts,
linkIds: LinkId[],
observerId: string,
throttleMs?: number,
msgId?: string,
onReceive: (params: {
links: LinkCountInfo[],
}) => void,
}

export type ObserveNodeStatus = {
type: RequestObserverType.observeNodeStatus,
nodeIds: NodeId[],
observerId: string,
throttleMs?: number,
msgId?: string,
onReceive: (data: {
nodes: {nodeId: NodeId, status: Omit<NodeStatus, 'AVAILABLE'>}[],
}) => void,
}

export type ObserveLinkUpdate = {
type: RequestObserverType.observeLinkUpdate,
linkIds: LinkId[],
observerId: string,
onReceive: (linkIds: LinkId[]) => void,
throttleMs?: number,
msgId?: string,
limit?: number,
offset?: number,
}

export type CancelObservation = {
type: RequestObserverType.cancelObservation,
observerId: string,
msgId?: string
}

export type ExecutionObserver = ObserveLinkItems | ObserveLinkCounts | CancelObservation | ObserveLinkUpdate | ObserveNodeStatus;
export const LinkCountInfoSchema = z.object({
count: z.number({
required_error: 'count is required',
invalid_type_error: 'count must be a number'
}),
linkId: z.string({
required_error: 'linkId is required',
invalid_type_error: 'linkId must be a string'
}) as z.ZodType<LinkId>,
})

export type LinkCountInfo = z.input<typeof LinkCountInfoSchema>;

export const NodesStatusInfoSchema = z.object({
nodeId: z.string({
required_error: 'nodeId is required',
invalid_type_error: 'nodeId must be a string'
}),
status: z.enum(['BUSY', 'COMPLETE'], {
required_error: 'status is required',
invalid_type_error: 'status must be either BUSY or COMPLETE'
}),
})

export type NodesStatusInfo = z.input<typeof NodesStatusInfoSchema>;

export const ObserveLinkItemsSchema = z.object({
linkIds: z.array(z.string({
required_error: 'linkIds is required',
invalid_type_error: 'linkIds must be an array'
}) as z.ZodType<LinkId>),
type: z.literal(RequestObserverType.observeLinkItems, {
required_error: 'type is required',
invalid_type_error: 'type must be observeLinkItems'
}),
observerId: z.string({
required_error: 'observerId is required',
invalid_type_error: 'observerId must be a string'
}),
direction: z.enum(['pull', 'push'], {
invalid_type_error: 'direction must be either pull or push'
}).optional(),
throttleMs: z.number({
invalid_type_error: 'throttleMs must be a number'
}).optional(),
msgId: z.string({
invalid_type_error: 'msgId must be a string'
}).optional(),
onReceive: NotifyObserversCallbackSchema as z.ZodType<NotifyObserversCallback>,
})

export type ObserveLinkItems = z.input<typeof ObserveLinkItemsSchema>;

export const ObserveLinkCountsSchema = z.object({
type: z.literal(RequestObserverType.observeLinkCounts, {
required_error: 'type is required',
invalid_type_error: 'type must be observeLinkCounts'
}),
linkIds: z.array(z.string({
required_error: 'linkIds is required',
invalid_type_error: 'linkIds must be a string'
}) as z.ZodType<LinkId>),
observerId: z.string({
required_error: 'observerId is required',
invalid_type_error: 'observerId must be a string'
}),
throttleMs: z.number({
invalid_type_error: 'throttleMs must be a number'
}).optional(),
msgId: z.string({
invalid_type_error: 'msgId must be a string'
}).optional(),
onReceive: z.function()
.args(z.object({
links: z.array(LinkCountInfoSchema)
}))
.returns(z.void()),
});

export type ObserveLinkCounts = z.input<typeof ObserveLinkCountsSchema>;

export const ObserveNodeStatusSchema = z.object({
type: z.literal(RequestObserverType.observeNodeStatus, {
required_error: 'type is required',
invalid_type_error: 'type must be observeNodeStatus'
}),
nodeIds: z.array(z.string({
required_error: 'nodeIds is required',
invalid_type_error: 'nodeIds must be a string'
}).transform(nodeId => nodeId as NodeId)),
observerId: z.string({
required_error: 'observerId is required',
invalid_type_error: 'observerId must be a string'
}),
throttleMs: z.number({
invalid_type_error: 'throttleMs must be a number'
}).optional(),
msgId: z.string({
invalid_type_error: 'msgId must be a string'
}).optional(),
onReceive: z.function()
.args(z.object({
nodes: z.array(NodesStatusInfoSchema)
}))
.returns(z.void()),
});

export type ObserveNodeStatus = z.input<typeof ObserveNodeStatusSchema>;

export const ObserveLinkUpdateSchema = z.object({
type: z.literal(RequestObserverType.observeLinkUpdate, {
required_error: 'type is required',
invalid_type_error: 'type must be observeLinkUpdate'
}),
linkIds: z.array(z.string({
required_error: 'linkIds is required',
invalid_type_error: 'linkIds must be a string'
}) as z.ZodType<LinkId>),
observerId: z.string({
required_error: 'observerId is required',
invalid_type_error: 'observerId must be a string'
}),
throttleMs: z.number({
invalid_type_error: 'throttleMs must be a number'
}).optional(),
msgId: z.string({
invalid_type_error: 'msgId must be a string'
}).optional(),
limit: z.number({
invalid_type_error: 'limit must be a number'
}).optional(),
offset: z.number({
invalid_type_error: 'offset must be a number'
}).optional(),
onReceive: z.function()
.args(z.array(
z.string().transform(linkId => linkId as LinkId).optional()
))
.returns(z.void())
});

export type ObserveLinkUpdate = z.input<typeof ObserveLinkUpdateSchema>;

export const CancelObservationSchema = z.object({
type: z.literal(RequestObserverType.cancelObservation, {
required_error: 'type is required',
invalid_type_error: 'type must be cancelObservation'
}),
observerId: z.string({
required_error: 'observerId is required',
invalid_type_error: 'observerId must be a string'
}),
msgId: z.string({
invalid_type_error: 'msgId must be a string'
}).optional(),
});

export type CancelObservation = z.input<typeof CancelObservationSchema>;

export const ExecutionObserverSchema = z.discriminatedUnion('type', [
ObserveLinkItemsSchema,
ObserveLinkCountsSchema,
ObserveLinkUpdateSchema,
ObserveNodeStatusSchema,
CancelObservationSchema
]);

export type ExecutionObserver = z.input<typeof ExecutionObserverSchema>;
12 changes: 0 additions & 12 deletions packages/core/src/types/GetDataFromStorage.ts

This file was deleted.

38 changes: 38 additions & 0 deletions packages/core/src/types/GetDataFromStorageParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { ItemValue } from './ItemValue';
import { LinkId } from './Link';
import { z } from 'zod';

export type LinkItems = Record<LinkId, ItemValue[]>

export const LinkItemsSchema = z.record(z.string() as z.ZodType<LinkId>, z.array(z.object({}) as z.ZodType<ItemValue>));

/**
* @schema GetDataFromStorageParams
*/
export const GetDataFromStorageParamsSchema = z.object({
type: z.literal('getDataFromStorage', {
required_error: 'type is required',
invalid_type_error: 'type must be getDataFromStorage'
}),
linkId: z.string({
required_error: 'linkId is required',
invalid_type_error: 'linkId must be a string'
}) as z.ZodType<LinkId>,
msgId: z.string({
invalid_type_error: 'msgId must be a string'
}).optional(),
offset: z.number({
invalid_type_error: 'offset must be a number'
}).optional(),
limit: z.number({
invalid_type_error: 'limit must be a number'
}).optional(),
});

export type GetDataFromStorageParams = z.input<typeof GetDataFromStorageParamsSchema>;

export const GetDataFromStorageResponseSchema = GetDataFromStorageParamsSchema.extend({
data: LinkItemsSchema,
});

export type GetDataFromStorageResponse = z.input<typeof GetDataFromStorageResponseSchema>;
20 changes: 14 additions & 6 deletions packages/core/src/types/LinkItemsParam.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import { z } from 'zod';
import { RequestObserverType } from './InputObserveConfig';
import { ItemValue } from './ItemValue';
import { LinkId } from './Link';

export type LinkItemsParam = {
type: RequestObserverType.observeLinkItems;
linkId: LinkId;
items: ItemValue[];
}
export const LinkItemsParamSchema = z.object({
type: z.literal(RequestObserverType.observeLinkItems, {
required_error: 'type is required',
invalid_type_error: 'type must be observeLinkItems'
}),
linkId: z.string({
required_error: 'linkId is required',
invalid_type_error: 'linkId must be a string'
}) as z.ZodType<string>,
items: z.array(z.object({}) as z.ZodType<ItemValue>),
});

export type LinkItemsParam = z.input<typeof LinkItemsParamSchema>;
Loading

0 comments on commit df2480b

Please sign in to comment.