clyops-api 0.0.0-stage → 0.2.0
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/README.md +162 -2
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +62 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +4 -0
- package/dist/server.d.ts +75 -0
- package/dist/server.js +518 -0
- package/dist/zod.d.ts +28 -0
- package/dist/zod.js +75 -0
- package/package.json +58 -4
- package/src/cli.ts +64 -0
- package/src/index.ts +4 -0
- package/src/server.ts +590 -0
- package/src/zod.ts +79 -0
package/src/zod.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// The JSON Schema from clyops-tools' toJsonSchema as a zod schema, so
|
|
2
|
+
// plus-express validates request bodies and documents them in OpenAPI from
|
|
3
|
+
// the same source the MCP server uses.
|
|
4
|
+
import type { JsonSchema } from 'clyops-tools';
|
|
5
|
+
import { z } from 'plus-express';
|
|
6
|
+
|
|
7
|
+
type Js = JsonSchema & {
|
|
8
|
+
type?: string; enum?: string[]; pattern?: string; items?: Js; description?: string; default?: unknown;
|
|
9
|
+
minimum?: number; maximum?: number; minLength?: number; maxLength?: number;
|
|
10
|
+
properties?: Record<string, Js>; required?: string[];
|
|
11
|
+
allOf?: { not?: { required?: string[]; properties?: Record<string, Js & { const?: unknown; not?: unknown }> }; description?: string }[];
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/** Whether `value` matches one of the "given" shapes toJsonSchema uses for exclusive options. */
|
|
15
|
+
function given(value: unknown, shape: { const?: unknown; type?: string; minItems?: number; not?: unknown } | undefined): boolean {
|
|
16
|
+
if (value === undefined || value === null) return false;
|
|
17
|
+
if (!shape) return true;
|
|
18
|
+
if ('const' in shape) return value === shape.const;
|
|
19
|
+
if (shape.type === 'array') return Array.isArray(value) && value.length >= (shape.minItems ?? 0);
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function toZod(schema: Js): z.ZodType {
|
|
24
|
+
let out: z.ZodType;
|
|
25
|
+
switch (schema.type) {
|
|
26
|
+
case 'integer':
|
|
27
|
+
case 'number': {
|
|
28
|
+
let n = schema.type === 'integer' ? z.number().int() : z.number();
|
|
29
|
+
if (schema.minimum !== undefined) n = n.min(schema.minimum);
|
|
30
|
+
if (schema.maximum !== undefined) n = n.max(schema.maximum);
|
|
31
|
+
out = n;
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
case 'boolean':
|
|
35
|
+
out = z.boolean();
|
|
36
|
+
break;
|
|
37
|
+
case 'array':
|
|
38
|
+
out = z.array(toZod(schema.items ?? {}));
|
|
39
|
+
break;
|
|
40
|
+
case 'object': {
|
|
41
|
+
const required = new Set(schema.required ?? []);
|
|
42
|
+
const shape = Object.fromEntries(
|
|
43
|
+
Object.entries(schema.properties ?? {}).map(([key, prop]) => [key, required.has(key) ? toZod(prop) : toZod(prop).optional()]),
|
|
44
|
+
);
|
|
45
|
+
out = z.strictObject(shape);
|
|
46
|
+
// Exclusive options (allOf: [{ not: { required: [a, b] } }]) can't both be given.
|
|
47
|
+
const exclusive = (schema.allOf ?? []).filter((c) => c.not?.required?.length);
|
|
48
|
+
if (exclusive.length) {
|
|
49
|
+
out = out.superRefine((value, ctx) => {
|
|
50
|
+
const input = value as Record<string, unknown>;
|
|
51
|
+
for (const c of exclusive) {
|
|
52
|
+
const keys = c.not?.required ?? [];
|
|
53
|
+
if (keys.every((k) => given(input[k], c.not?.properties?.[k]))) {
|
|
54
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: [keys[keys.length - 1]], message: c.description ?? `${keys.join(' and ')} cannot be used together` });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
default: {
|
|
62
|
+
if (schema.enum) {
|
|
63
|
+
out = z.enum(schema.enum as [string, ...string[]]);
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
let s = z.string();
|
|
67
|
+
if (schema.minLength !== undefined) s = s.min(schema.minLength);
|
|
68
|
+
if (schema.maxLength !== undefined) s = s.max(schema.maxLength);
|
|
69
|
+
if (schema.pattern) s = s.regex(new RegExp(schema.pattern));
|
|
70
|
+
out = s;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Defaults are documentation only: the tool applies its own, so a value it
|
|
74
|
+
// would get from its config file or environment is not overridden.
|
|
75
|
+
const meta: Record<string, unknown> = {};
|
|
76
|
+
if (schema.description) meta.description = schema.description;
|
|
77
|
+
if (schema.default !== undefined) meta.default = schema.default;
|
|
78
|
+
return Object.keys(meta).length ? out.openapi(meta) : out;
|
|
79
|
+
}
|