toolcraft-openapi 0.0.123 → 0.0.124

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.
Files changed (39) hide show
  1. package/dist/composition.json +6 -1
  2. package/node_modules/toolcraft-schema/LICENSE +21 -0
  3. package/node_modules/toolcraft-schema/README.md +89 -0
  4. package/node_modules/toolcraft-schema/dist/index.compile-check.d.ts +1 -0
  5. package/node_modules/toolcraft-schema/dist/index.compile-check.js +17 -0
  6. package/node_modules/toolcraft-schema/dist/index.d.ts +184 -0
  7. package/node_modules/toolcraft-schema/dist/index.js +295 -0
  8. package/node_modules/toolcraft-schema/dist/json-schema/compiler.d.ts +11 -0
  9. package/node_modules/toolcraft-schema/dist/json-schema/compiler.js +390 -0
  10. package/node_modules/toolcraft-schema/dist/json-schema/evaluate.d.ts +3 -0
  11. package/node_modules/toolcraft-schema/dist/json-schema/evaluate.js +442 -0
  12. package/node_modules/toolcraft-schema/dist/json-schema/index.d.ts +5 -0
  13. package/node_modules/toolcraft-schema/dist/json-schema/index.js +19 -0
  14. package/node_modules/toolcraft-schema/dist/json-schema/types.d.ts +33 -0
  15. package/node_modules/toolcraft-schema/dist/json-schema/types.js +1 -0
  16. package/node_modules/toolcraft-schema/dist/json-schema/utils.d.ts +19 -0
  17. package/node_modules/toolcraft-schema/dist/json-schema/utils.js +171 -0
  18. package/node_modules/toolcraft-schema/dist/json-schema-document.d.ts +14 -0
  19. package/node_modules/toolcraft-schema/dist/json-schema-document.js +17 -0
  20. package/node_modules/toolcraft-schema/dist/json.compile-check.d.ts +1 -0
  21. package/node_modules/toolcraft-schema/dist/json.compile-check.js +2 -0
  22. package/node_modules/toolcraft-schema/dist/json.d.ts +10 -0
  23. package/node_modules/toolcraft-schema/dist/json.js +5 -0
  24. package/node_modules/toolcraft-schema/dist/oneof.compile-check.d.ts +1 -0
  25. package/node_modules/toolcraft-schema/dist/oneof.compile-check.js +12 -0
  26. package/node_modules/toolcraft-schema/dist/oneof.d.ts +15 -0
  27. package/node_modules/toolcraft-schema/dist/oneof.js +18 -0
  28. package/node_modules/toolcraft-schema/dist/record.compile-check.d.ts +1 -0
  29. package/node_modules/toolcraft-schema/dist/record.compile-check.js +2 -0
  30. package/node_modules/toolcraft-schema/dist/record.d.ts +5 -0
  31. package/node_modules/toolcraft-schema/dist/record.js +6 -0
  32. package/node_modules/toolcraft-schema/dist/union.compile-check.d.ts +1 -0
  33. package/node_modules/toolcraft-schema/dist/union.compile-check.js +9 -0
  34. package/node_modules/toolcraft-schema/dist/union.d.ts +8 -0
  35. package/node_modules/toolcraft-schema/dist/union.js +45 -0
  36. package/node_modules/toolcraft-schema/dist/validate.d.ts +17 -0
  37. package/node_modules/toolcraft-schema/dist/validate.js +379 -0
  38. package/node_modules/toolcraft-schema/package.json +33 -0
  39. package/package.json +6 -4
@@ -0,0 +1,379 @@
1
+ import { getRequiredKeyFingerprint } from "./union.js";
2
+ const missingValue = Symbol("missingValue");
3
+ export function validate(schema, value) {
4
+ const state = { issues: [] };
5
+ const result = walkSchema(schema, value, [], state);
6
+ if (state.issues.length > 0) {
7
+ return { ok: false, issues: state.issues };
8
+ }
9
+ return { ok: true, value: (result.present ? result.value : undefined) };
10
+ }
11
+ function walkSchema(schema, value, path, state) {
12
+ if (schema.kind === "optional") {
13
+ return walkOptional(schema, value, path, state);
14
+ }
15
+ if (value === missingValue) {
16
+ addIssue(state, path, expectedFor(schema), "missing", `Expected ${expectedFor(schema)} at ${formatPath(path)}`);
17
+ return { present: false };
18
+ }
19
+ if (value === null && schema.nullable === true) {
20
+ return { present: true, value };
21
+ }
22
+ switch (schema.kind) {
23
+ case "string":
24
+ return walkString(schema, value, path, state);
25
+ case "number":
26
+ return walkNumber(schema, value, path, state);
27
+ case "boolean":
28
+ return walkBoolean(value, path, state);
29
+ case "enum":
30
+ return walkEnum(schema, value, path, state);
31
+ case "array":
32
+ return walkArray(schema, value, path, state);
33
+ case "object":
34
+ return walkObject(schema, value, path, state);
35
+ case "oneOf":
36
+ return walkOneOf(schema, value, path, state);
37
+ case "union":
38
+ return walkUnion(schema, value, path, state);
39
+ case "record":
40
+ return walkRecord(schema, value, path, state);
41
+ case "json":
42
+ return walkJson(value, path, state);
43
+ }
44
+ }
45
+ function walkOptional(schema, value, path, state) {
46
+ if (value === missingValue || value === undefined) {
47
+ const defaultValue = getDefault(schema.inner);
48
+ if (defaultValue.present) {
49
+ return walkSchema(schema.inner, cloneDefault(defaultValue.value), path, state);
50
+ }
51
+ return { present: false };
52
+ }
53
+ return walkSchema(schema.inner, value, path, state);
54
+ }
55
+ function walkString(schema, value, path, state) {
56
+ if (typeof value !== "string") {
57
+ addExpectedIssue(state, path, "string", value);
58
+ return { present: true, value };
59
+ }
60
+ if (schema.minLength !== undefined && value.length < schema.minLength) {
61
+ const expected = `string with length at least ${schema.minLength}`;
62
+ addIssue(state, path, expected, `string with length ${value.length}`, `Expected ${expected} at ${formatPath(path)}`);
63
+ }
64
+ if (schema.maxLength !== undefined && value.length > schema.maxLength) {
65
+ const expected = `string with length at most ${schema.maxLength}`;
66
+ addIssue(state, path, expected, `string with length ${value.length}`, `Expected ${expected} at ${formatPath(path)}`);
67
+ }
68
+ if (schema.pattern !== undefined) {
69
+ const pattern = compilePattern(schema.pattern);
70
+ if (pattern === undefined || !pattern.test(value)) {
71
+ const expected = `string matching pattern ${schema.pattern}`;
72
+ addIssue(state, path, expected, value, `Expected ${expected} at ${formatPath(path)}`);
73
+ }
74
+ }
75
+ return { present: true, value };
76
+ }
77
+ function walkNumber(schema, value, path, state) {
78
+ if (typeof value !== "number" || !Number.isFinite(value)) {
79
+ addExpectedIssue(state, path, schema.jsonType === "integer" ? "integer" : "number", value);
80
+ return { present: true, value };
81
+ }
82
+ if (schema.jsonType === "integer" && !Number.isInteger(value)) {
83
+ addExpectedIssue(state, path, "integer", value);
84
+ }
85
+ if (schema.minimum !== undefined && value < schema.minimum) {
86
+ const expected = `number greater than or equal to ${schema.minimum}`;
87
+ addIssue(state, path, expected, String(value), `Expected ${expected} at ${formatPath(path)}`);
88
+ }
89
+ if (schema.maximum !== undefined && value > schema.maximum) {
90
+ const expected = `number less than or equal to ${schema.maximum}`;
91
+ addIssue(state, path, expected, String(value), `Expected ${expected} at ${formatPath(path)}`);
92
+ }
93
+ return { present: true, value };
94
+ }
95
+ function walkBoolean(value, path, state) {
96
+ if (typeof value !== "boolean") {
97
+ addExpectedIssue(state, path, "boolean", value);
98
+ }
99
+ return { present: true, value };
100
+ }
101
+ function walkEnum(schema, value, path, state) {
102
+ if (!schema.values.includes(value)) {
103
+ const expected = `one of ${schema.values.join(", ")}`;
104
+ addIssue(state, path, expected, receivedValue(value), `Expected ${expected} at ${formatPath(path)}`);
105
+ }
106
+ return { present: true, value };
107
+ }
108
+ function walkArray(schema, value, path, state) {
109
+ if (!Array.isArray(value)) {
110
+ addExpectedIssue(state, path, "array", value);
111
+ return { present: true, value };
112
+ }
113
+ if (schema.minItems !== undefined && value.length < schema.minItems) {
114
+ const expected = `array with at least ${schema.minItems} items`;
115
+ addIssue(state, path, expected, `array with ${value.length} items`, `Expected ${expected} at ${formatPath(path)}`);
116
+ }
117
+ if (schema.maxItems !== undefined && value.length > schema.maxItems) {
118
+ const expected = `array with at most ${schema.maxItems} items`;
119
+ addIssue(state, path, expected, `array with ${value.length} items`, `Expected ${expected} at ${formatPath(path)}`);
120
+ }
121
+ const nextValue = value.map((item, index) => {
122
+ const result = walkSchema(schema.item, item, [...path, String(index)], state);
123
+ return result.present ? result.value : item;
124
+ });
125
+ return { present: true, value: nextValue };
126
+ }
127
+ function walkObject(schema, value, path, state, injectedProperties = {}) {
128
+ if (!isPlainRecord(value)) {
129
+ addExpectedIssue(state, path, "object", value);
130
+ return { present: true, value };
131
+ }
132
+ const nextValue = {};
133
+ const allowedKeys = new Set([...Object.keys(schema.shape), ...Object.keys(injectedProperties)]);
134
+ for (const [key, propertySchema] of Object.entries(schema.shape)) {
135
+ const propertyValue = Object.hasOwn(value, key) ? value[key] : missingValue;
136
+ const result = walkSchema(propertySchema, propertyValue, [...path, key], state);
137
+ if (result.present) {
138
+ setOwnValue(nextValue, key, result.value);
139
+ }
140
+ }
141
+ for (const [key, injectedValue] of Object.entries(injectedProperties)) {
142
+ if (Object.hasOwn(value, key)) {
143
+ setOwnValue(nextValue, key, value[key]);
144
+ }
145
+ else {
146
+ setOwnValue(nextValue, key, injectedValue);
147
+ }
148
+ }
149
+ for (const [key, propertyValue] of Object.entries(value)) {
150
+ if (allowedKeys.has(key)) {
151
+ continue;
152
+ }
153
+ if (schema.additionalProperties === true) {
154
+ setOwnValue(nextValue, key, propertyValue);
155
+ }
156
+ else {
157
+ addUnexpectedPropertyIssue(state, [...path, key]);
158
+ }
159
+ }
160
+ return { present: true, value: nextValue };
161
+ }
162
+ function walkOneOf(schema, value, path, state) {
163
+ if (!isPlainRecord(value)) {
164
+ addExpectedIssue(state, path, "object", value);
165
+ return { present: true, value };
166
+ }
167
+ const discriminatorValue = value[schema.discriminator];
168
+ const discriminatorPath = [...path, schema.discriminator];
169
+ const branchValues = Object.keys(schema.branches);
170
+ const expected = `one of ${branchValues.join(", ")}`;
171
+ if (!Object.hasOwn(value, schema.discriminator)) {
172
+ addIssueWithMessage(state, discriminatorPath, expected, "missing", `Missing discriminator "${schema.discriminator}" at ${formatPath(path)}. Expected one of: ${branchValues.join(", ")}.`);
173
+ return { present: true, value };
174
+ }
175
+ if (typeof discriminatorValue !== "string" ||
176
+ !Object.hasOwn(schema.branches, discriminatorValue)) {
177
+ addIssueWithMessage(state, discriminatorPath, expected, receivedValue(discriminatorValue), `Expected ${expected} at ${formatPath(discriminatorPath)}, got ${formatReceivedDiscriminator(discriminatorValue)}`);
178
+ return { present: true, value };
179
+ }
180
+ return walkObject(schema.branches[discriminatorValue], value, path, state, {
181
+ [schema.discriminator]: discriminatorValue
182
+ });
183
+ }
184
+ function walkUnion(schema, value, path, state) {
185
+ if (isPlainRecord(value)) {
186
+ const candidateBranches = schema.branches.filter((branch) => hasRequiredKeys(branch, value));
187
+ if (candidateBranches.length === 1) {
188
+ return walkObject(candidateBranches[0], value, path, state);
189
+ }
190
+ }
191
+ const matches = [];
192
+ for (const branch of schema.branches) {
193
+ const branchState = { issues: [] };
194
+ const result = walkObject(branch, value, path, branchState);
195
+ if (branchState.issues.length === 0 && result.present) {
196
+ matches.push({ fingerprint: getRequiredKeyFingerprint(branch), value: result.value });
197
+ }
198
+ }
199
+ if (matches.length === 1) {
200
+ return { present: true, value: matches[0].value };
201
+ }
202
+ if (matches.length === 0) {
203
+ const branchDescriptions = schema.branches.map((branch) => getRequiredKeyFingerprint(branch));
204
+ addIssueWithMessage(state, path, "exactly one union branch", "0 matching branches", `No union branch matched at ${formatPath(path)}. Tried ${schema.branches.length} branches. Expected one of: ${branchDescriptions.join(" | ")}.`);
205
+ return { present: true, value };
206
+ }
207
+ addIssueWithMessage(state, path, "exactly one union branch", `${matches.length} matching branches`, `Expected exactly one union branch at ${formatPath(path)}, but matched more than one branch: ${matches.map((match) => match.fingerprint).join(" | ")}`);
208
+ return { present: true, value };
209
+ }
210
+ function hasRequiredKeys(schema, value) {
211
+ for (const [key, propertySchema] of Object.entries(schema.shape)) {
212
+ if (propertySchema.kind !== "optional" && !Object.hasOwn(value, key)) {
213
+ return false;
214
+ }
215
+ }
216
+ return true;
217
+ }
218
+ function walkRecord(schema, value, path, state) {
219
+ if (!isPlainRecord(value)) {
220
+ addExpectedIssue(state, path, "object", value);
221
+ return { present: true, value };
222
+ }
223
+ const nextValue = {};
224
+ for (const [key, propertyValue] of Object.entries(value)) {
225
+ const result = walkSchema(schema.value, propertyValue, [...path, key], state);
226
+ if (result.present) {
227
+ setOwnValue(nextValue, key, result.value);
228
+ }
229
+ }
230
+ return { present: true, value: nextValue };
231
+ }
232
+ function walkJson(value, path, state) {
233
+ if (isJsonValue(value)) {
234
+ return { present: true, value };
235
+ }
236
+ addExpectedIssue(state, path, "JSON value", value);
237
+ return { present: true, value };
238
+ }
239
+ function getDefault(schema) {
240
+ if (schema.default !== undefined) {
241
+ return { present: true, value: schema.default };
242
+ }
243
+ if (schema.kind === "optional") {
244
+ return getDefault(schema.inner);
245
+ }
246
+ return { present: false };
247
+ }
248
+ function isPlainRecord(value) {
249
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
250
+ return false;
251
+ }
252
+ const prototype = Object.getPrototypeOf(value);
253
+ return prototype === Object.prototype || prototype === null;
254
+ }
255
+ function isJsonValue(value, ancestors = new Set()) {
256
+ if (value === null ||
257
+ typeof value === "string" ||
258
+ typeof value === "number" ||
259
+ typeof value === "boolean") {
260
+ return typeof value !== "number" || Number.isFinite(value);
261
+ }
262
+ if (Array.isArray(value)) {
263
+ if (ancestors.has(value)) {
264
+ return false;
265
+ }
266
+ ancestors.add(value);
267
+ const result = value.every((item) => isJsonValue(item, ancestors));
268
+ ancestors.delete(value);
269
+ return result;
270
+ }
271
+ if (isPlainRecord(value)) {
272
+ if (ancestors.has(value)) {
273
+ return false;
274
+ }
275
+ ancestors.add(value);
276
+ const result = Object.values(value).every((item) => isJsonValue(item, ancestors));
277
+ ancestors.delete(value);
278
+ return result;
279
+ }
280
+ return false;
281
+ }
282
+ function cloneDefault(value) {
283
+ return structuredClone(value);
284
+ }
285
+ function setOwnValue(target, key, value) {
286
+ Object.defineProperty(target, key, {
287
+ configurable: true,
288
+ enumerable: true,
289
+ writable: true,
290
+ value
291
+ });
292
+ }
293
+ function expectedFor(schema) {
294
+ switch (schema.kind) {
295
+ case "string":
296
+ return "string";
297
+ case "number":
298
+ return schema.jsonType === "integer" ? "integer" : "number";
299
+ case "boolean":
300
+ return "boolean";
301
+ case "enum":
302
+ return `one of ${schema.values.join(", ")}`;
303
+ case "array":
304
+ return "array";
305
+ case "object":
306
+ case "oneOf":
307
+ case "record":
308
+ return "object";
309
+ case "union":
310
+ return "exactly one union branch";
311
+ case "json":
312
+ return "JSON value";
313
+ case "optional":
314
+ return expectedFor(schema.inner);
315
+ }
316
+ }
317
+ function addExpectedIssue(state, path, expected, value) {
318
+ addIssue(state, path, expected, receivedType(value), `Expected ${expected} at ${formatPath(path)}`);
319
+ }
320
+ function addUnexpectedPropertyIssue(state, path) {
321
+ addIssue(state, path, "no additional properties", "unknown property", `Unexpected property ${formatPath(path)}`);
322
+ }
323
+ function addIssue(state, path, expected, received, _message) {
324
+ state.issues.push({
325
+ path,
326
+ expected,
327
+ received,
328
+ message: formatIssueMessage(expected, path, received)
329
+ });
330
+ }
331
+ function addIssueWithMessage(state, path, expected, received, message) {
332
+ state.issues.push({
333
+ path,
334
+ expected,
335
+ received,
336
+ message
337
+ });
338
+ }
339
+ function formatIssueMessage(expected, path, received) {
340
+ return `Expected ${expected} at ${formatPath(path)}, got ${received}`;
341
+ }
342
+ function formatPath(path) {
343
+ return path.length === 0 ? "value" : path.join(".");
344
+ }
345
+ function compilePattern(pattern) {
346
+ try {
347
+ return new RegExp(pattern);
348
+ }
349
+ catch {
350
+ return undefined;
351
+ }
352
+ }
353
+ function receivedType(value) {
354
+ if (value === null) {
355
+ return "null";
356
+ }
357
+ if (Array.isArray(value)) {
358
+ return "array";
359
+ }
360
+ if (typeof value === "number" && Number.isInteger(value)) {
361
+ return "integer";
362
+ }
363
+ return typeof value;
364
+ }
365
+ function receivedValue(value) {
366
+ if (typeof value === "string") {
367
+ return value;
368
+ }
369
+ if (value === undefined) {
370
+ return "undefined";
371
+ }
372
+ return String(value);
373
+ }
374
+ function formatReceivedDiscriminator(value) {
375
+ if (typeof value === "string") {
376
+ return JSON.stringify(value);
377
+ }
378
+ return receivedValue(value);
379
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "toolcraft-schema",
3
+ "version": "0.0.124",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "default": "./dist/index.js"
13
+ }
14
+ },
15
+ "scripts": {
16
+ "build": "rm -rf dist && tsc",
17
+ "test": "cd ../.. && vitest run packages/toolcraft-schema/src",
18
+ "test:unit": "cd ../.. && vitest run packages/toolcraft-schema/src",
19
+ "lint": "cd ../.. && eslint packages/toolcraft-schema/src --ext ts && tsc -p packages/toolcraft-schema/tsconfig.json --noEmit"
20
+ },
21
+ "files": [
22
+ "LICENSE",
23
+ "dist"
24
+ ],
25
+ "engines": {
26
+ "node": ">=18.18"
27
+ },
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/poe-platform/poe-code.git",
31
+ "directory": "packages/toolcraft-schema"
32
+ }
33
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolcraft-openapi",
3
- "version": "0.0.123",
3
+ "version": "0.0.124",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -19,8 +19,8 @@
19
19
  "build": "rm -rf dist && tsc",
20
20
  "test": "cd ../.. && vitest run packages/toolcraft-openapi/src",
21
21
  "test:unit": "cd ../.. && vitest run packages/toolcraft-openapi/src",
22
- "prepack": "node ../../scripts/set-bin-executable.mjs && node ../../scripts/manage-bundled-workspace-deps.mjs prepare . toolcraft-design @poe-code/frontmatter auth-store fast-string-width fast-wrap-ansi sisteransi yaml",
23
- "postpack": "node ../../scripts/manage-bundled-workspace-deps.mjs cleanup . toolcraft-design @poe-code/frontmatter auth-store fast-string-width fast-wrap-ansi sisteransi yaml"
22
+ "prepack": "node ../../scripts/set-bin-executable.mjs && node ../../scripts/manage-bundled-workspace-deps.mjs prepare . toolcraft-schema toolcraft-design @poe-code/frontmatter auth-store fast-string-width fast-wrap-ansi sisteransi yaml",
23
+ "postpack": "node ../../scripts/manage-bundled-workspace-deps.mjs cleanup . toolcraft-schema toolcraft-design @poe-code/frontmatter auth-store fast-string-width fast-wrap-ansi sisteransi yaml"
24
24
  },
25
25
  "files": [
26
26
  "LICENSE",
@@ -30,7 +30,7 @@
30
30
  "toolcraft-openapi-generate": "dist/bin/generate.js"
31
31
  },
32
32
  "dependencies": {
33
- "toolcraft": "0.0.123",
33
+ "toolcraft": "0.0.124",
34
34
  "auth-store": "^0.0.1",
35
35
  "fast-string-width": "^3.0.2",
36
36
  "fast-wrap-ansi": "^0.2.0",
@@ -46,10 +46,12 @@
46
46
  "directory": "packages/toolcraft-openapi"
47
47
  },
48
48
  "optionalDependencies": {
49
+ "toolcraft-schema": "0.0.124",
49
50
  "toolcraft-design": "*",
50
51
  "@poe-code/frontmatter": "*"
51
52
  },
52
53
  "bundleDependencies": [
54
+ "toolcraft-schema",
53
55
  "toolcraft-design",
54
56
  "@poe-code/frontmatter",
55
57
  "auth-store",