@tsed/cli-mcp 7.0.0-rc.1 → 7.0.0-rc.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/esm/constants/constants.js +5 -0
- package/lib/esm/decorators/prompt.js +12 -0
- package/lib/esm/decorators/resource.js +15 -0
- package/lib/esm/decorators/tool.js +12 -0
- package/lib/esm/fn/definePrompt.js +31 -49
- package/lib/esm/fn/defineResource.js +28 -31
- package/lib/esm/fn/defineTool.js +58 -76
- package/lib/esm/services/McpServerFactory.js +24 -12
- package/lib/esm/utils/json-schema-to-zod/Types.js +1 -0
- package/lib/esm/utils/json-schema-to-zod/cli.js +74 -0
- package/lib/esm/utils/json-schema-to-zod/index.js +25 -0
- package/lib/esm/utils/json-schema-to-zod/jsonSchemaToZod.js +43 -0
- package/lib/esm/utils/json-schema-to-zod/parsers/parseAllOf.js +37 -0
- package/lib/esm/utils/json-schema-to-zod/parsers/parseAnyOf.js +11 -0
- package/lib/esm/utils/json-schema-to-zod/parsers/parseArray.js +16 -0
- package/lib/esm/utils/json-schema-to-zod/parsers/parseBoolean.js +3 -0
- package/lib/esm/utils/json-schema-to-zod/parsers/parseConst.js +3 -0
- package/lib/esm/utils/json-schema-to-zod/parsers/parseDefault.js +3 -0
- package/lib/esm/utils/json-schema-to-zod/parsers/parseEnum.js +15 -0
- package/lib/esm/utils/json-schema-to-zod/parsers/parseIfThenElse.js +20 -0
- package/lib/esm/utils/json-schema-to-zod/parsers/parseMultipleType.js +4 -0
- package/lib/esm/utils/json-schema-to-zod/parsers/parseNot.js +7 -0
- package/lib/esm/utils/json-schema-to-zod/parsers/parseNull.js +3 -0
- package/lib/esm/utils/json-schema-to-zod/parsers/parseNullable.js +8 -0
- package/lib/esm/utils/json-schema-to-zod/parsers/parseNumber.js +46 -0
- package/lib/esm/utils/json-schema-to-zod/parsers/parseObject.js +171 -0
- package/lib/esm/utils/json-schema-to-zod/parsers/parseOneOf.js +34 -0
- package/lib/esm/utils/json-schema-to-zod/parsers/parseSchema.js +138 -0
- package/lib/esm/utils/json-schema-to-zod/parsers/parseString.js +57 -0
- package/lib/esm/utils/json-schema-to-zod/utils/cliTools.js +99 -0
- package/lib/esm/utils/json-schema-to-zod/utils/half.js +3 -0
- package/lib/esm/utils/json-schema-to-zod/utils/jsdocs.js +12 -0
- package/lib/esm/utils/json-schema-to-zod/utils/omit.js +6 -0
- package/lib/esm/utils/json-schema-to-zod/utils/withMessage.js +19 -0
- package/lib/esm/utils/toZod.js +2 -2
- package/lib/tsconfig.esm.tsbuildinfo +1 -1
- package/lib/types/constants/constants.d.ts +5 -0
- package/lib/types/decorators/prompt.d.ts +3 -0
- package/lib/types/decorators/resource.d.ts +4 -0
- package/lib/types/decorators/tool.d.ts +2 -0
- package/lib/types/fn/definePrompt.d.ts +42 -38
- package/lib/types/fn/defineResource.d.ts +2 -29
- package/lib/types/fn/defineTool.d.ts +128 -40
- package/lib/types/utils/json-schema-to-zod/Types.d.ts +68 -0
- package/lib/types/utils/json-schema-to-zod/cli.d.ts +2 -0
- package/lib/types/utils/json-schema-to-zod/index.d.ts +25 -0
- package/lib/types/utils/json-schema-to-zod/jsonSchemaToZod.d.ts +2 -0
- package/lib/types/utils/json-schema-to-zod/parsers/parseAllOf.d.ts +4 -0
- package/lib/types/utils/json-schema-to-zod/parsers/parseAnyOf.d.ts +4 -0
- package/lib/types/utils/json-schema-to-zod/parsers/parseArray.d.ts +4 -0
- package/lib/types/utils/json-schema-to-zod/parsers/parseBoolean.d.ts +4 -0
- package/lib/types/utils/json-schema-to-zod/parsers/parseConst.d.ts +4 -0
- package/lib/types/utils/json-schema-to-zod/parsers/parseDefault.d.ts +2 -0
- package/lib/types/utils/json-schema-to-zod/parsers/parseEnum.d.ts +4 -0
- package/lib/types/utils/json-schema-to-zod/parsers/parseIfThenElse.d.ts +6 -0
- package/lib/types/utils/json-schema-to-zod/parsers/parseMultipleType.d.ts +4 -0
- package/lib/types/utils/json-schema-to-zod/parsers/parseNot.d.ts +4 -0
- package/lib/types/utils/json-schema-to-zod/parsers/parseNull.d.ts +4 -0
- package/lib/types/utils/json-schema-to-zod/parsers/parseNullable.d.ts +7 -0
- package/lib/types/utils/json-schema-to-zod/parsers/parseNumber.d.ts +4 -0
- package/lib/types/utils/json-schema-to-zod/parsers/parseObject.d.ts +4 -0
- package/lib/types/utils/json-schema-to-zod/parsers/parseOneOf.d.ts +4 -0
- package/lib/types/utils/json-schema-to-zod/parsers/parseSchema.d.ts +46 -0
- package/lib/types/utils/json-schema-to-zod/parsers/parseString.d.ts +4 -0
- package/lib/types/utils/json-schema-to-zod/utils/cliTools.d.ts +28 -0
- package/lib/types/utils/json-schema-to-zod/utils/half.d.ts +1 -0
- package/lib/types/utils/json-schema-to-zod/utils/jsdocs.d.ts +3 -0
- package/lib/types/utils/json-schema-to-zod/utils/omit.d.ts +1 -0
- package/lib/types/utils/json-schema-to-zod/utils/withMessage.d.ts +10 -0
- package/lib/types/utils/toZod.d.ts +2 -2
- package/package.json +5 -5
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { classOf } from "@tsed/core";
|
|
2
|
+
import { definePrompt } from "../fn/definePrompt.js";
|
|
3
|
+
export function Prompt(options) {
|
|
4
|
+
return (target, propertyKey, _) => {
|
|
5
|
+
definePrompt({
|
|
6
|
+
...options,
|
|
7
|
+
name: options?.name || String(propertyKey),
|
|
8
|
+
token: classOf(target),
|
|
9
|
+
propertyKey: propertyKey
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { classOf } from "@tsed/core";
|
|
2
|
+
import { isString } from "@tsed/core/utils/isString.js";
|
|
3
|
+
import { defineResource } from "../fn/defineResource.js";
|
|
4
|
+
export function Resource(uriOrTemplate, options) {
|
|
5
|
+
return (target, propertyKey, _) => {
|
|
6
|
+
defineResource({
|
|
7
|
+
...options,
|
|
8
|
+
name: options?.name || String(propertyKey),
|
|
9
|
+
token: classOf(target),
|
|
10
|
+
propertyKey,
|
|
11
|
+
uri: isString(uriOrTemplate) ? uriOrTemplate : undefined,
|
|
12
|
+
template: !isString(uriOrTemplate) ? uriOrTemplate : undefined
|
|
13
|
+
});
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { classOf } from "@tsed/core";
|
|
2
|
+
import { defineTool } from "../fn/defineTool.js";
|
|
3
|
+
export function Tool(name, options = {}) {
|
|
4
|
+
return (target, propertyKey, _) => {
|
|
5
|
+
defineTool({
|
|
6
|
+
...options,
|
|
7
|
+
name: name || String(propertyKey),
|
|
8
|
+
token: classOf(target),
|
|
9
|
+
propertyKey
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
}
|
|
@@ -1,52 +1,34 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
* text: `Please review this code:\n\n${code}`
|
|
23
|
-
* }
|
|
24
|
-
* }
|
|
25
|
-
* ]
|
|
26
|
-
* })
|
|
27
|
-
* });
|
|
28
|
-
* ```
|
|
29
|
-
*
|
|
30
|
-
* @param options {PromptProps}
|
|
31
|
-
*/
|
|
32
|
-
export function definePrompt(options) {
|
|
33
|
-
const provider = injectable(options.token || Symbol.for(`MCP:RESOURCE:${options.name}`))
|
|
34
|
-
.type("CLI_MCP_RESOURCES")
|
|
35
|
-
.factory(() => ({
|
|
1
|
+
import { isArrowFn } from "@tsed/core";
|
|
2
|
+
import { inject, injectable } from "@tsed/di";
|
|
3
|
+
import { JsonEntityStore, JsonSchema } from "@tsed/schema";
|
|
4
|
+
import { MCP_PROVIDER_TYPES } from "../constants/constants.js";
|
|
5
|
+
import { toZod } from "../utils/toZod.js";
|
|
6
|
+
function mapOptions(options) {
|
|
7
|
+
let handler = undefined;
|
|
8
|
+
if ("handler" in options) {
|
|
9
|
+
handler = options.handler;
|
|
10
|
+
}
|
|
11
|
+
if ("propertyKey" in options && options.propertyKey) {
|
|
12
|
+
const { token, propertyKey } = options;
|
|
13
|
+
handler = ((...args) => {
|
|
14
|
+
const instance = inject(options.token);
|
|
15
|
+
return instance[propertyKey](...args);
|
|
16
|
+
});
|
|
17
|
+
const methodStore = JsonEntityStore.fromMethod(token, propertyKey);
|
|
18
|
+
options.description = options.description || methodStore.operation.get("description");
|
|
19
|
+
options.title = options.title || methodStore.schema.get("title");
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
36
22
|
...options,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
return options.handler(...args);
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
}));
|
|
23
|
+
argsSchema: toZod(isArrowFn(options.argsSchema) ? options.argsSchema() : options.argsSchema),
|
|
24
|
+
handler: handler
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export function definePrompt(options) {
|
|
28
|
+
const provider = injectable(Symbol.for(`MCP:PROMPT:${options.name}`))
|
|
29
|
+
.type(MCP_PROVIDER_TYPES.PROMPT)
|
|
30
|
+
.factory(() => {
|
|
31
|
+
return mapOptions(options);
|
|
32
|
+
});
|
|
51
33
|
return provider.token();
|
|
52
34
|
}
|
|
@@ -1,34 +1,31 @@
|
|
|
1
|
-
import { injectable } from "@tsed/
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import { inject, injectable } from "@tsed/di";
|
|
2
|
+
import { JsonEntityStore } from "@tsed/schema";
|
|
3
|
+
import { MCP_PROVIDER_TYPES } from "../constants/constants.js";
|
|
4
|
+
function mapOptions(options) {
|
|
5
|
+
let handler;
|
|
6
|
+
if ("propertyKey" in options && options.propertyKey) {
|
|
7
|
+
const { token, propertyKey } = options;
|
|
8
|
+
handler = (...args) => {
|
|
9
|
+
const instance = inject(options.token);
|
|
10
|
+
return instance[propertyKey](...args);
|
|
11
|
+
};
|
|
12
|
+
const methodStore = JsonEntityStore.fromMethod(token, propertyKey);
|
|
13
|
+
options.description = options.description || methodStore.operation.get("description");
|
|
14
|
+
options.title = options.title || methodStore.schema.get("title");
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
handler = options.handler;
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
8
20
|
...options,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
try {
|
|
19
|
-
return await runInContext($ctx, () => {
|
|
20
|
-
return options.handler(...args);
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
finally {
|
|
24
|
-
try {
|
|
25
|
-
await $ctx.destroy();
|
|
26
|
-
}
|
|
27
|
-
catch {
|
|
28
|
-
// ignore
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}));
|
|
21
|
+
handler
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export function defineResource(options) {
|
|
25
|
+
const provider = injectable(Symbol.for(`MCP:RESOURCE:${options.name}`))
|
|
26
|
+
.type(MCP_PROVIDER_TYPES.RESOURCE)
|
|
27
|
+
.factory(() => {
|
|
28
|
+
return mapOptions(options);
|
|
29
|
+
});
|
|
33
30
|
return provider.token();
|
|
34
31
|
}
|
package/lib/esm/fn/defineTool.js
CHANGED
|
@@ -1,85 +1,67 @@
|
|
|
1
|
-
import { injectable } from "@tsed/cli-core";
|
|
2
1
|
import { isArrowFn } from "@tsed/core";
|
|
3
|
-
import {
|
|
4
|
-
import { JsonSchema } from "@tsed/schema";
|
|
5
|
-
import {
|
|
2
|
+
import { inject, injectable, logger } from "@tsed/di";
|
|
3
|
+
import { JsonEntityStore, JsonMethodStore, JsonSchema } from "@tsed/schema";
|
|
4
|
+
import { MCP_PROVIDER_TYPES } from "../constants/constants.js";
|
|
6
5
|
import { toZod } from "../utils/toZod.js";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
* }
|
|
32
|
-
* });
|
|
33
|
-
* ```
|
|
34
|
-
*
|
|
35
|
-
* @param options
|
|
36
|
-
*/
|
|
37
|
-
export function defineTool(options) {
|
|
38
|
-
const provider = injectable(options.token || Symbol.for(`MCP:TOOL:${options.name}`))
|
|
39
|
-
.type("CLI_MCP_TOOLS")
|
|
40
|
-
.factory(() => ({
|
|
6
|
+
function getOutputSchema(methodStore) {
|
|
7
|
+
const schema = methodStore.operation.getResponseOf(200)?.getMedia("application/json")?.get("schema");
|
|
8
|
+
return schema?.itemSchema();
|
|
9
|
+
}
|
|
10
|
+
function getInputSchema(token, propertyKey) {
|
|
11
|
+
return JsonEntityStore.from(token, propertyKey, 0).schema?.itemSchema();
|
|
12
|
+
}
|
|
13
|
+
function mapOptions(options) {
|
|
14
|
+
let handler;
|
|
15
|
+
if ("propertyKey" in options) {
|
|
16
|
+
const { token, propertyKey } = options;
|
|
17
|
+
handler = (args, extra) => {
|
|
18
|
+
const instance = inject(options.token);
|
|
19
|
+
return instance[options.propertyKey](args, extra);
|
|
20
|
+
};
|
|
21
|
+
const methodStore = JsonEntityStore.fromMethod(token, propertyKey);
|
|
22
|
+
options.description = options.description || methodStore.operation.get("description");
|
|
23
|
+
options.inputSchema = options.inputSchema || getInputSchema(token, propertyKey);
|
|
24
|
+
options.outputSchema = options.outputSchema || getOutputSchema(methodStore);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
handler = options.handler;
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
41
30
|
...options,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
catch (er) {
|
|
59
|
-
$ctx.logger.error({
|
|
60
|
-
event: "MCP_TOOL_ERROR",
|
|
61
|
-
tool: options.name,
|
|
62
|
-
error_message: er.message,
|
|
63
|
-
stack: er.stack
|
|
64
|
-
});
|
|
65
|
-
return {
|
|
66
|
-
content: [],
|
|
67
|
-
structuredContent: {
|
|
68
|
-
code: "E_MCP_TOOL_ERROR",
|
|
69
|
-
message: er.message
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
finally {
|
|
74
|
-
// Ensure per-invocation context is destroyed to avoid leaks
|
|
31
|
+
handler
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export function defineTool(options) {
|
|
35
|
+
const provider = injectable(Symbol.for(`MCP:TOOL:${options.name}`))
|
|
36
|
+
.type(MCP_PROVIDER_TYPES.TOOL)
|
|
37
|
+
.factory(() => {
|
|
38
|
+
let { handler, ...opts } = mapOptions(options);
|
|
39
|
+
return {
|
|
40
|
+
...opts,
|
|
41
|
+
name: opts.name,
|
|
42
|
+
inputSchema: toZod(isArrowFn(opts.inputSchema) ? opts.inputSchema() : opts.inputSchema),
|
|
43
|
+
outputSchema: toZod(opts.outputSchema),
|
|
44
|
+
async handler(args, extra) {
|
|
75
45
|
try {
|
|
76
|
-
await
|
|
46
|
+
return await handler(args, extra);
|
|
77
47
|
}
|
|
78
|
-
catch {
|
|
79
|
-
|
|
48
|
+
catch (er) {
|
|
49
|
+
logger().error({
|
|
50
|
+
event: "MCP_TOOL_ERROR",
|
|
51
|
+
tool: opts.name,
|
|
52
|
+
error_message: er?.message,
|
|
53
|
+
stack: er?.stack
|
|
54
|
+
});
|
|
55
|
+
return {
|
|
56
|
+
content: [],
|
|
57
|
+
structuredContent: {
|
|
58
|
+
code: "E_MCP_TOOL_ERROR",
|
|
59
|
+
message: er?.message
|
|
60
|
+
}
|
|
61
|
+
};
|
|
80
62
|
}
|
|
81
63
|
}
|
|
82
|
-
}
|
|
83
|
-
})
|
|
64
|
+
};
|
|
65
|
+
});
|
|
84
66
|
return provider.token();
|
|
85
67
|
}
|
|
@@ -1,28 +1,40 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
-
import { constant, inject, injectable, logger } from "@tsed/di";
|
|
2
|
+
import { constant, inject, injectable, injector, logger } from "@tsed/di";
|
|
3
|
+
import { MCP_PROVIDER_TYPES } from "../constants/constants.js";
|
|
3
4
|
import { mcpStdioServer } from "./McpStdioServer.js";
|
|
4
5
|
import { mcpStreamableServer } from "./McpStreamableServer.js";
|
|
6
|
+
function collectTokens(type, configured = []) {
|
|
7
|
+
const tokens = new Set(configured);
|
|
8
|
+
injector()
|
|
9
|
+
.getProviders(type)
|
|
10
|
+
.forEach((provider) => tokens.add(provider.token));
|
|
11
|
+
return [...tokens];
|
|
12
|
+
}
|
|
5
13
|
export const MCP_SERVER = injectable(McpServer)
|
|
6
14
|
.factory(() => {
|
|
7
15
|
const defaultMode = constant("mcp.mode");
|
|
8
|
-
const name = constant("name");
|
|
16
|
+
const name = constant("name", "tsed-mcp");
|
|
17
|
+
const version = constant("version", "0.0.0");
|
|
9
18
|
const server = new McpServer({
|
|
10
19
|
name,
|
|
11
|
-
version
|
|
20
|
+
version
|
|
12
21
|
});
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
const
|
|
22
|
+
const toolTokens = collectTokens(MCP_PROVIDER_TYPES.TOOL, constant("tools", []));
|
|
23
|
+
toolTokens.forEach((token) => {
|
|
24
|
+
const definition = inject(token);
|
|
25
|
+
const { name, handler, ...opts } = definition;
|
|
16
26
|
server.registerTool(name, opts, handler);
|
|
17
27
|
});
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
const
|
|
28
|
+
const resourceTokens = collectTokens(MCP_PROVIDER_TYPES.RESOURCE, constant("resources", []));
|
|
29
|
+
resourceTokens.forEach((token) => {
|
|
30
|
+
const definition = inject(token);
|
|
31
|
+
const { name, handler, uri, template, ...opts } = definition;
|
|
21
32
|
server.registerResource(name, (uri || template), opts, handler);
|
|
22
33
|
});
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
const
|
|
34
|
+
const promptTokens = collectTokens(MCP_PROVIDER_TYPES.PROMPT, constant("prompts", []));
|
|
35
|
+
promptTokens.forEach((token) => {
|
|
36
|
+
const definition = inject(token);
|
|
37
|
+
const { name, handler, ...opts } = definition;
|
|
26
38
|
server.registerPrompt(name, opts, handler);
|
|
27
39
|
});
|
|
28
40
|
return {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { mkdirSync, writeFileSync } from "fs";
|
|
3
|
+
import { dirname } from "path";
|
|
4
|
+
import { jsonSchemaToZod } from "./jsonSchemaToZod.js";
|
|
5
|
+
import { parseArgs, parseOrReadJSON, readPipe } from "./utils/cliTools.js";
|
|
6
|
+
const params = {
|
|
7
|
+
input: {
|
|
8
|
+
shorthand: "i",
|
|
9
|
+
value: "string",
|
|
10
|
+
required: process.stdin.isTTY && "input is required when no JSON or file path is piped",
|
|
11
|
+
description: "JSON or a source file path. Required if no data is piped."
|
|
12
|
+
},
|
|
13
|
+
output: {
|
|
14
|
+
shorthand: "o",
|
|
15
|
+
value: "string",
|
|
16
|
+
description: "A file path to write to. If not supplied stdout will be used."
|
|
17
|
+
},
|
|
18
|
+
name: {
|
|
19
|
+
shorthand: "n",
|
|
20
|
+
value: "string",
|
|
21
|
+
description: "The name of the schema in the output."
|
|
22
|
+
},
|
|
23
|
+
depth: {
|
|
24
|
+
shorthand: "d",
|
|
25
|
+
value: "number",
|
|
26
|
+
description: "Maximum depth of recursion before falling back to z.any(). Defaults to 0."
|
|
27
|
+
},
|
|
28
|
+
module: {
|
|
29
|
+
shorthand: "m",
|
|
30
|
+
value: ["esm", "cjs", "none"],
|
|
31
|
+
description: "Module syntax; 'esm', 'cjs' or 'none'. Defaults to 'esm'."
|
|
32
|
+
},
|
|
33
|
+
type: {
|
|
34
|
+
shorthand: "t",
|
|
35
|
+
value: "string",
|
|
36
|
+
description: "The name of the (optional) inferred type export."
|
|
37
|
+
},
|
|
38
|
+
noImport: {
|
|
39
|
+
shorthand: "ni",
|
|
40
|
+
description: "Removes the `import { z } from 'zod';` or equivalent from the output."
|
|
41
|
+
},
|
|
42
|
+
withJsdocs: {
|
|
43
|
+
shorthand: "wj",
|
|
44
|
+
description: "Generate jsdocs off of the description property."
|
|
45
|
+
},
|
|
46
|
+
zodVersion: {
|
|
47
|
+
shorthand: "zv",
|
|
48
|
+
value: "number",
|
|
49
|
+
description: "Target Zod version: 3 or 4. Defaults to 4."
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
async function main() {
|
|
53
|
+
const args = parseArgs(params, process.argv, true);
|
|
54
|
+
const input = args.input || (await readPipe());
|
|
55
|
+
const jsonSchema = parseOrReadJSON(input);
|
|
56
|
+
const zodVersion = (args.zodVersion === 3 ? 3 : 4);
|
|
57
|
+
const zodSchema = jsonSchemaToZod(jsonSchema, {
|
|
58
|
+
name: args.name,
|
|
59
|
+
depth: args.depth,
|
|
60
|
+
module: args.module || "esm",
|
|
61
|
+
noImport: args.noImport,
|
|
62
|
+
type: args.type,
|
|
63
|
+
withJsdocs: args.withJsdocs,
|
|
64
|
+
zodVersion
|
|
65
|
+
});
|
|
66
|
+
if (args.output) {
|
|
67
|
+
mkdirSync(dirname(args.output), { recursive: true });
|
|
68
|
+
writeFileSync(args.output, zodSchema);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
console.log(zodSchema);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
void main();
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export * from "./jsonSchemaToZod.js";
|
|
2
|
+
export * from "./parsers/parseAllOf.js";
|
|
3
|
+
export * from "./parsers/parseAnyOf.js";
|
|
4
|
+
export * from "./parsers/parseArray.js";
|
|
5
|
+
export * from "./parsers/parseBoolean.js";
|
|
6
|
+
export * from "./parsers/parseConst.js";
|
|
7
|
+
export * from "./parsers/parseDefault.js";
|
|
8
|
+
export * from "./parsers/parseEnum.js";
|
|
9
|
+
export * from "./parsers/parseIfThenElse.js";
|
|
10
|
+
export * from "./parsers/parseMultipleType.js";
|
|
11
|
+
export * from "./parsers/parseNot.js";
|
|
12
|
+
export * from "./parsers/parseNull.js";
|
|
13
|
+
export * from "./parsers/parseNullable.js";
|
|
14
|
+
export * from "./parsers/parseNumber.js";
|
|
15
|
+
export * from "./parsers/parseObject.js";
|
|
16
|
+
export * from "./parsers/parseOneOf.js";
|
|
17
|
+
export * from "./parsers/parseSchema.js";
|
|
18
|
+
export * from "./parsers/parseString.js";
|
|
19
|
+
export * from "./Types.js";
|
|
20
|
+
export * from "./utils/half.js";
|
|
21
|
+
export * from "./utils/jsdocs.js";
|
|
22
|
+
export * from "./utils/omit.js";
|
|
23
|
+
export * from "./utils/withMessage.js";
|
|
24
|
+
import { jsonSchemaToZod } from "./jsonSchemaToZod.js";
|
|
25
|
+
export default jsonSchemaToZod;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { parseSchema } from "./parsers/parseSchema.js";
|
|
2
|
+
import { expandJsdocs } from "./utils/jsdocs.js";
|
|
3
|
+
export const jsonSchemaToZod = (schema, { module, name, type, noImport, zodVersion = 4, ...rest } = {}) => {
|
|
4
|
+
if (type && (!name || module !== "esm")) {
|
|
5
|
+
throw new Error("Option `type` requires `name` to be set and `module` to be `esm`");
|
|
6
|
+
}
|
|
7
|
+
let result = parseSchema(schema, {
|
|
8
|
+
module,
|
|
9
|
+
name,
|
|
10
|
+
path: [],
|
|
11
|
+
seen: new Map(),
|
|
12
|
+
zodVersion,
|
|
13
|
+
...rest
|
|
14
|
+
});
|
|
15
|
+
const jsdocs = rest.withJsdocs && typeof schema !== "boolean" && schema.description ? expandJsdocs(schema.description) : "";
|
|
16
|
+
if (module === "cjs") {
|
|
17
|
+
result = `${jsdocs}module.exports = ${name ? `{ ${JSON.stringify(name)}: ${result} }` : result}
|
|
18
|
+
`;
|
|
19
|
+
if (!noImport) {
|
|
20
|
+
result = `${jsdocs}const { z } = require("zod")
|
|
21
|
+
|
|
22
|
+
${result}`;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
else if (module === "esm") {
|
|
26
|
+
result = `${jsdocs}export ${name ? `const ${name} =` : `default`} ${result}
|
|
27
|
+
`;
|
|
28
|
+
if (!noImport) {
|
|
29
|
+
result = `import { z } from "zod"
|
|
30
|
+
|
|
31
|
+
${result}`;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
else if (name) {
|
|
35
|
+
result = `${jsdocs}const ${name} = ${result}`;
|
|
36
|
+
}
|
|
37
|
+
if (type && name) {
|
|
38
|
+
let typeName = typeof type === "string" ? type : `${name[0].toUpperCase()}${name.substring(1)}`;
|
|
39
|
+
result += `export type ${typeName} = z.infer<typeof ${name}>
|
|
40
|
+
`;
|
|
41
|
+
}
|
|
42
|
+
return result;
|
|
43
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { half } from "../utils/half.js";
|
|
2
|
+
import { parseSchema } from "./parseSchema.js";
|
|
3
|
+
const originalIndex = Symbol("Original index");
|
|
4
|
+
const ensureOriginalIndex = (arr) => {
|
|
5
|
+
let newArr = [];
|
|
6
|
+
for (let i = 0; i < arr.length; i++) {
|
|
7
|
+
const item = arr[i];
|
|
8
|
+
if (typeof item === "boolean") {
|
|
9
|
+
newArr.push(item ? { [originalIndex]: i } : { [originalIndex]: i, not: {} });
|
|
10
|
+
}
|
|
11
|
+
else if (originalIndex in item) {
|
|
12
|
+
return arr;
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
newArr.push({ ...item, [originalIndex]: i });
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return newArr;
|
|
19
|
+
};
|
|
20
|
+
export function parseAllOf(schema, refs) {
|
|
21
|
+
if (schema.allOf.length === 0) {
|
|
22
|
+
return "z.never()";
|
|
23
|
+
}
|
|
24
|
+
else if (schema.allOf.length === 1) {
|
|
25
|
+
const item = schema.allOf[0];
|
|
26
|
+
return parseSchema(item, {
|
|
27
|
+
...refs,
|
|
28
|
+
path: [...refs.path, "allOf", item[originalIndex]]
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
const [left, right] = half(ensureOriginalIndex(schema.allOf));
|
|
33
|
+
return `z.intersection(${parseAllOf({ allOf: left }, refs)}, ${parseAllOf({
|
|
34
|
+
allOf: right
|
|
35
|
+
}, refs)})`;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { parseSchema } from "./parseSchema.js";
|
|
2
|
+
export const parseAnyOf = (schema, refs) => {
|
|
3
|
+
return schema.anyOf.length
|
|
4
|
+
? schema.anyOf.length === 1
|
|
5
|
+
? parseSchema(schema.anyOf[0], {
|
|
6
|
+
...refs,
|
|
7
|
+
path: [...refs.path, "anyOf", 0]
|
|
8
|
+
})
|
|
9
|
+
: `z.union([${schema.anyOf.map((schema, i) => parseSchema(schema, { ...refs, path: [...refs.path, "anyOf", i] })).join(", ")}])`
|
|
10
|
+
: `z.any()`;
|
|
11
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { withMessage } from "../utils/withMessage.js";
|
|
2
|
+
import { parseSchema } from "./parseSchema.js";
|
|
3
|
+
export const parseArray = (schema, refs) => {
|
|
4
|
+
if (Array.isArray(schema.items)) {
|
|
5
|
+
return `z.tuple([${schema.items.map((v, i) => parseSchema(v, { ...refs, path: [...refs.path, "items", i] }))}])`;
|
|
6
|
+
}
|
|
7
|
+
let r = !schema.items
|
|
8
|
+
? "z.array(z.any())"
|
|
9
|
+
: `z.array(${parseSchema(schema.items, {
|
|
10
|
+
...refs,
|
|
11
|
+
path: [...refs.path, "items"]
|
|
12
|
+
})})`;
|
|
13
|
+
r += withMessage(schema, "minItems", ({ json }) => [`.min(${json}`, ", ", ")"]);
|
|
14
|
+
r += withMessage(schema, "maxItems", ({ json }) => [`.max(${json}`, ", ", ")"]);
|
|
15
|
+
return r;
|
|
16
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const parseEnum = (schema) => {
|
|
2
|
+
if (schema.enum.length === 0) {
|
|
3
|
+
return "z.never()";
|
|
4
|
+
}
|
|
5
|
+
else if (schema.enum.length === 1) {
|
|
6
|
+
// union does not work when there is only one element
|
|
7
|
+
return `z.literal(${JSON.stringify(schema.enum[0])})`;
|
|
8
|
+
}
|
|
9
|
+
else if (schema.enum.every((x) => typeof x === "string")) {
|
|
10
|
+
return `z.enum([${schema.enum.map((x) => JSON.stringify(x))}])`;
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
return `z.union([${schema.enum.map((x) => `z.literal(${JSON.stringify(x)})`).join(", ")}])`;
|
|
14
|
+
}
|
|
15
|
+
};
|