@pikku/inspector 0.12.67 → 0.12.68
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/CHANGELOG.md +10 -0
- package/dist/error-codes.d.ts +1 -0
- package/dist/error-codes.js +2 -0
- package/dist/utils/find-zod-transform.d.ts +21 -0
- package/dist/utils/find-zod-transform.js +93 -0
- package/dist/utils/schema-generator.js +11 -0
- package/package.json +3 -3
- package/src/error-codes.ts +3 -0
- package/src/utils/find-zod-transform.test.ts +70 -0
- package/src/utils/find-zod-transform.ts +103 -0
- package/src/utils/schema-generator.test.ts +47 -0
- package/src/utils/schema-generator.ts +14 -0
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
## 0.12.68
|
|
2
|
+
|
|
3
|
+
### Patch Changes
|
|
4
|
+
|
|
5
|
+
- ee9da9e: A `.transform()` in a schema used as a function's `input` now reports PKU491, naming the schema and the path to the transform, instead of surfacing downstream as PKU724 — the bad `#pikku/*` import error.
|
|
6
|
+
- Updated dependencies [ee9da9e]
|
|
7
|
+
- Updated dependencies [7a15c9c]
|
|
8
|
+
- Updated dependencies [ee9da9e]
|
|
9
|
+
- @pikku/core@0.12.99
|
|
10
|
+
|
|
1
11
|
## 0.12.67
|
|
2
12
|
|
|
3
13
|
### Patch Changes
|
package/dist/error-codes.d.ts
CHANGED
|
@@ -65,6 +65,7 @@ export declare enum ErrorCode {
|
|
|
65
65
|
AGENT_TOOL_MISSING_DESCRIPTION = "PKU154",
|
|
66
66
|
AGENT_SCORER_NOT_FOUND = "PKU155",
|
|
67
67
|
SCHEMA_AND_WIRING_COLOCATED = "PKU490",
|
|
68
|
+
SCHEMA_TRANSFORM_NOT_SUPPORTED = "PKU491",
|
|
68
69
|
SERVICES_NOT_DESTRUCTURED = "PKU410",
|
|
69
70
|
WIRES_NOT_DESTRUCTURED = "PKU411",
|
|
70
71
|
FUNCTION_DYNAMIC_IMPORT = "PKU498",
|
package/dist/error-codes.js
CHANGED
|
@@ -84,6 +84,8 @@ export var ErrorCode;
|
|
|
84
84
|
ErrorCode["AGENT_SCORER_NOT_FOUND"] = "PKU155";
|
|
85
85
|
// File structure errors
|
|
86
86
|
ErrorCode["SCHEMA_AND_WIRING_COLOCATED"] = "PKU490";
|
|
87
|
+
// Schema authoring errors
|
|
88
|
+
ErrorCode["SCHEMA_TRANSFORM_NOT_SUPPORTED"] = "PKU491";
|
|
87
89
|
// Optimization diagnostics
|
|
88
90
|
ErrorCode["SERVICES_NOT_DESTRUCTURED"] = "PKU410";
|
|
89
91
|
ErrorCode["WIRES_NOT_DESTRUCTURED"] = "PKU411";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Locate a `.transform()` anywhere inside a Zod schema.
|
|
3
|
+
*
|
|
4
|
+
* A transform is why a schema cannot be a function's `input`: the value the
|
|
5
|
+
* caller sends and the value the function receives are different, and a wire
|
|
6
|
+
* contract has to describe one shape. Zod says so twice, in two different
|
|
7
|
+
* places, and neither says it usefully on its own — `toJSONSchema` with
|
|
8
|
+
* `unrepresentable: 'any'` quietly yields `{}`, a schema that accepts
|
|
9
|
+
* everything, and `zodToTs` throws about TypeScript rather than about the
|
|
10
|
+
* wiring. Finding it up front lets the diagnostic name the schema, the path,
|
|
11
|
+
* and the call.
|
|
12
|
+
*
|
|
13
|
+
* Zod 4 models `x.transform(fn)` as a `pipe` whose `out` is a `transform`, so
|
|
14
|
+
* the walk looks for that node rather than for the method.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* The path to the first `.transform()` found — `''` when the transform is the
|
|
18
|
+
* schema itself, `'.user.email'` when it is nested — or `undefined` when there
|
|
19
|
+
* is none.
|
|
20
|
+
*/
|
|
21
|
+
export declare function findZodTransform(schema: unknown): string | undefined;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Locate a `.transform()` anywhere inside a Zod schema.
|
|
3
|
+
*
|
|
4
|
+
* A transform is why a schema cannot be a function's `input`: the value the
|
|
5
|
+
* caller sends and the value the function receives are different, and a wire
|
|
6
|
+
* contract has to describe one shape. Zod says so twice, in two different
|
|
7
|
+
* places, and neither says it usefully on its own — `toJSONSchema` with
|
|
8
|
+
* `unrepresentable: 'any'` quietly yields `{}`, a schema that accepts
|
|
9
|
+
* everything, and `zodToTs` throws about TypeScript rather than about the
|
|
10
|
+
* wiring. Finding it up front lets the diagnostic name the schema, the path,
|
|
11
|
+
* and the call.
|
|
12
|
+
*
|
|
13
|
+
* Zod 4 models `x.transform(fn)` as a `pipe` whose `out` is a `transform`, so
|
|
14
|
+
* the walk looks for that node rather than for the method.
|
|
15
|
+
*/
|
|
16
|
+
/** Max nodes visited, so a pathological or cyclic schema cannot hang the walk. */
|
|
17
|
+
const MAX_NODES = 10_000;
|
|
18
|
+
const isNode = (value) => typeof value === 'object' &&
|
|
19
|
+
value !== null &&
|
|
20
|
+
typeof value._zod?.def?.type === 'string';
|
|
21
|
+
/**
|
|
22
|
+
* The path to the first `.transform()` found — `''` when the transform is the
|
|
23
|
+
* schema itself, `'.user.email'` when it is nested — or `undefined` when there
|
|
24
|
+
* is none.
|
|
25
|
+
*/
|
|
26
|
+
export function findZodTransform(schema) {
|
|
27
|
+
const seen = new Set();
|
|
28
|
+
let budget = MAX_NODES;
|
|
29
|
+
const walk = (node, path) => {
|
|
30
|
+
if (budget-- <= 0 || !isNode(node) || seen.has(node))
|
|
31
|
+
return undefined;
|
|
32
|
+
seen.add(node);
|
|
33
|
+
const def = node._zod.def;
|
|
34
|
+
if (def.type === 'transform')
|
|
35
|
+
return path;
|
|
36
|
+
const children = [];
|
|
37
|
+
switch (def.type) {
|
|
38
|
+
case 'object':
|
|
39
|
+
case 'interface':
|
|
40
|
+
for (const [key, child] of Object.entries(def.shape ?? {})) {
|
|
41
|
+
children.push([child, `${path}.${key}`]);
|
|
42
|
+
}
|
|
43
|
+
break;
|
|
44
|
+
case 'array':
|
|
45
|
+
case 'set':
|
|
46
|
+
children.push([def.element, `${path}[]`]);
|
|
47
|
+
break;
|
|
48
|
+
case 'tuple':
|
|
49
|
+
for (const [i, child] of (def.items ?? []).entries()) {
|
|
50
|
+
children.push([child, `${path}[${i}]`]);
|
|
51
|
+
}
|
|
52
|
+
if (def.rest)
|
|
53
|
+
children.push([def.rest, `${path}[]`]);
|
|
54
|
+
break;
|
|
55
|
+
case 'union':
|
|
56
|
+
for (const [i, child] of (def.options ?? []).entries()) {
|
|
57
|
+
children.push([child, `${path}|${i}`]);
|
|
58
|
+
}
|
|
59
|
+
break;
|
|
60
|
+
case 'intersection':
|
|
61
|
+
children.push([def.left, path], [def.right, path]);
|
|
62
|
+
break;
|
|
63
|
+
case 'record':
|
|
64
|
+
case 'map':
|
|
65
|
+
children.push([def.keyType, `${path}[key]`], [def.valueType, `${path}[]`]);
|
|
66
|
+
break;
|
|
67
|
+
case 'pipe':
|
|
68
|
+
// `.transform()` itself: report the pipe's own path, not `out`'s.
|
|
69
|
+
children.push([def.in, path], [def.out, path]);
|
|
70
|
+
break;
|
|
71
|
+
default:
|
|
72
|
+
// optional, nullable, default, prefault, nonoptional, readonly, catch,
|
|
73
|
+
// promise, lazy, success — every wrapper Zod spells `innerType`.
|
|
74
|
+
if (def.innerType)
|
|
75
|
+
children.push([def.innerType, path]);
|
|
76
|
+
if (def.getter) {
|
|
77
|
+
try {
|
|
78
|
+
children.push([def.getter(), path]);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// A lazy schema that cannot be forced tells us nothing; skip it.
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
for (const [child, childPath] of children) {
|
|
86
|
+
const found = walk(child, childPath);
|
|
87
|
+
if (found !== undefined)
|
|
88
|
+
return found;
|
|
89
|
+
}
|
|
90
|
+
return undefined;
|
|
91
|
+
};
|
|
92
|
+
return walk(schema, '');
|
|
93
|
+
}
|
|
@@ -6,6 +6,7 @@ import { createGenerator, RootlessError } from 'ts-json-schema-generator';
|
|
|
6
6
|
import { register } from 'tsx/esm/api';
|
|
7
7
|
import * as z from 'zod';
|
|
8
8
|
import { zodToTs, createAuxiliaryTypeStore } from 'zod-to-ts';
|
|
9
|
+
import { findZodTransform } from './find-zod-transform.js';
|
|
9
10
|
import { ErrorCode } from '../error-codes.js';
|
|
10
11
|
import { generateCustomTypes } from './custom-types-generator.js';
|
|
11
12
|
const PRIMITIVE_TYPES = new Set([
|
|
@@ -382,6 +383,16 @@ async function importWithRegister(sourceFile) {
|
|
|
382
383
|
}
|
|
383
384
|
}
|
|
384
385
|
function processZodSchema(schemaName, zodSchema, schemas, typesMap, auxiliaryTypeStore, printer, fakeSourceFile, logger) {
|
|
386
|
+
const transformPath = findZodTransform(zodSchema);
|
|
387
|
+
if (transformPath !== undefined) {
|
|
388
|
+
const where = transformPath === '' ? 'the schema itself' : transformPath;
|
|
389
|
+
throw new Error(`[${ErrorCode.SCHEMA_TRANSFORM_NOT_SUPPORTED}] '${schemaName}' contains a .transform() at ${where}.\n` +
|
|
390
|
+
'A transform makes the value sent and the value received two different ' +
|
|
391
|
+
'shapes, and a wire contract describes one: it cannot be expressed in ' +
|
|
392
|
+
'JSON Schema or in the generated TypeScript.\n' +
|
|
393
|
+
'Validate with the plain schema and apply the transform inside the ' +
|
|
394
|
+
'function, or move it to the output type.');
|
|
395
|
+
}
|
|
385
396
|
const schema = z.toJSONSchema(zodSchema, {
|
|
386
397
|
unrepresentable: 'any',
|
|
387
398
|
override: ({ zodSchema, jsonSchema }) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/inspector",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.68",
|
|
4
4
|
"description": "TypeScript inspector for Pikku — discovers functions and wirings to drive code generation",
|
|
5
5
|
"author": "yasser.fadl@gmail.com",
|
|
6
6
|
"license": "BUSL-1.1",
|
|
@@ -45,10 +45,10 @@
|
|
|
45
45
|
"zod-to-ts": "^2.1.0"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
|
-
"@pikku/core": "^0.12.
|
|
48
|
+
"@pikku/core": "^0.12.99"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@pikku/core": "^0.12.
|
|
51
|
+
"@pikku/core": "^0.12.99",
|
|
52
52
|
"@types/node": "^24.13.3"
|
|
53
53
|
},
|
|
54
54
|
"engines": {
|
package/src/error-codes.ts
CHANGED
|
@@ -98,6 +98,9 @@ export enum ErrorCode {
|
|
|
98
98
|
// File structure errors
|
|
99
99
|
SCHEMA_AND_WIRING_COLOCATED = 'PKU490',
|
|
100
100
|
|
|
101
|
+
// Schema authoring errors
|
|
102
|
+
SCHEMA_TRANSFORM_NOT_SUPPORTED = 'PKU491',
|
|
103
|
+
|
|
101
104
|
// Optimization diagnostics
|
|
102
105
|
SERVICES_NOT_DESTRUCTURED = 'PKU410',
|
|
103
106
|
WIRES_NOT_DESTRUCTURED = 'PKU411',
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { test } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import * as z from 'zod'
|
|
4
|
+
import { findZodTransform } from './find-zod-transform.js'
|
|
5
|
+
|
|
6
|
+
test('a schema with no transform is reported as clean', () => {
|
|
7
|
+
assert.equal(findZodTransform(z.object({ id: z.string() })), undefined)
|
|
8
|
+
assert.equal(findZodTransform(z.string().min(1).trim()), undefined)
|
|
9
|
+
// a refinement narrows the value, it does not change its shape
|
|
10
|
+
assert.equal(
|
|
11
|
+
findZodTransform(z.object({ id: z.string() }).refine(() => true)),
|
|
12
|
+
undefined
|
|
13
|
+
)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
test('a transform on the schema itself is found at the root', () => {
|
|
17
|
+
assert.equal(
|
|
18
|
+
findZodTransform(z.object({ id: z.string() }).transform((v) => v)),
|
|
19
|
+
''
|
|
20
|
+
)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
test('a transform on a field is found by path', () => {
|
|
24
|
+
assert.equal(
|
|
25
|
+
findZodTransform(
|
|
26
|
+
z.object({
|
|
27
|
+
id: z.string(),
|
|
28
|
+
when: z.string().transform((s) => new Date(s)),
|
|
29
|
+
})
|
|
30
|
+
),
|
|
31
|
+
'.when'
|
|
32
|
+
)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test('a transform is found through wrappers, arrays and unions', () => {
|
|
36
|
+
assert.equal(
|
|
37
|
+
findZodTransform(
|
|
38
|
+
z.object({ tags: z.array(z.string().transform((s) => s.trim())) })
|
|
39
|
+
),
|
|
40
|
+
'.tags[]'
|
|
41
|
+
)
|
|
42
|
+
assert.equal(
|
|
43
|
+
findZodTransform(z.object({ n: z.string().transform(Number).optional() })),
|
|
44
|
+
'.n'
|
|
45
|
+
)
|
|
46
|
+
assert.equal(
|
|
47
|
+
findZodTransform(
|
|
48
|
+
z.object({ v: z.union([z.string(), z.number().transform(String)]) })
|
|
49
|
+
),
|
|
50
|
+
'.v|1'
|
|
51
|
+
)
|
|
52
|
+
assert.equal(
|
|
53
|
+
findZodTransform(
|
|
54
|
+
z.object({
|
|
55
|
+
m: z.record(
|
|
56
|
+
z.string(),
|
|
57
|
+
z.string().transform((s) => s)
|
|
58
|
+
),
|
|
59
|
+
})
|
|
60
|
+
),
|
|
61
|
+
'.m[]'
|
|
62
|
+
)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test('a self-referencing schema terminates instead of recursing forever', () => {
|
|
66
|
+
const node: z.ZodType = z.lazy(() =>
|
|
67
|
+
z.object({ name: z.string(), children: z.array(node) })
|
|
68
|
+
)
|
|
69
|
+
assert.equal(findZodTransform(node), undefined)
|
|
70
|
+
})
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Locate a `.transform()` anywhere inside a Zod schema.
|
|
3
|
+
*
|
|
4
|
+
* A transform is why a schema cannot be a function's `input`: the value the
|
|
5
|
+
* caller sends and the value the function receives are different, and a wire
|
|
6
|
+
* contract has to describe one shape. Zod says so twice, in two different
|
|
7
|
+
* places, and neither says it usefully on its own — `toJSONSchema` with
|
|
8
|
+
* `unrepresentable: 'any'` quietly yields `{}`, a schema that accepts
|
|
9
|
+
* everything, and `zodToTs` throws about TypeScript rather than about the
|
|
10
|
+
* wiring. Finding it up front lets the diagnostic name the schema, the path,
|
|
11
|
+
* and the call.
|
|
12
|
+
*
|
|
13
|
+
* Zod 4 models `x.transform(fn)` as a `pipe` whose `out` is a `transform`, so
|
|
14
|
+
* the walk looks for that node rather than for the method.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** Max nodes visited, so a pathological or cyclic schema cannot hang the walk. */
|
|
18
|
+
const MAX_NODES = 10_000
|
|
19
|
+
|
|
20
|
+
interface ZodNode {
|
|
21
|
+
_zod?: { def?: Record<string, any> }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const isNode = (value: unknown): value is ZodNode =>
|
|
25
|
+
typeof value === 'object' &&
|
|
26
|
+
value !== null &&
|
|
27
|
+
typeof (value as ZodNode)._zod?.def?.type === 'string'
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The path to the first `.transform()` found — `''` when the transform is the
|
|
31
|
+
* schema itself, `'.user.email'` when it is nested — or `undefined` when there
|
|
32
|
+
* is none.
|
|
33
|
+
*/
|
|
34
|
+
export function findZodTransform(schema: unknown): string | undefined {
|
|
35
|
+
const seen = new Set<unknown>()
|
|
36
|
+
let budget = MAX_NODES
|
|
37
|
+
|
|
38
|
+
const walk = (node: unknown, path: string): string | undefined => {
|
|
39
|
+
if (budget-- <= 0 || !isNode(node) || seen.has(node)) return undefined
|
|
40
|
+
seen.add(node)
|
|
41
|
+
|
|
42
|
+
const def = node._zod!.def!
|
|
43
|
+
if (def.type === 'transform') return path
|
|
44
|
+
|
|
45
|
+
const children: Array<[unknown, string]> = []
|
|
46
|
+
switch (def.type) {
|
|
47
|
+
case 'object':
|
|
48
|
+
case 'interface':
|
|
49
|
+
for (const [key, child] of Object.entries(def.shape ?? {})) {
|
|
50
|
+
children.push([child, `${path}.${key}`])
|
|
51
|
+
}
|
|
52
|
+
break
|
|
53
|
+
case 'array':
|
|
54
|
+
case 'set':
|
|
55
|
+
children.push([def.element, `${path}[]`])
|
|
56
|
+
break
|
|
57
|
+
case 'tuple':
|
|
58
|
+
for (const [i, child] of (def.items ?? []).entries()) {
|
|
59
|
+
children.push([child, `${path}[${i}]`])
|
|
60
|
+
}
|
|
61
|
+
if (def.rest) children.push([def.rest, `${path}[]`])
|
|
62
|
+
break
|
|
63
|
+
case 'union':
|
|
64
|
+
for (const [i, child] of (def.options ?? []).entries()) {
|
|
65
|
+
children.push([child, `${path}|${i}`])
|
|
66
|
+
}
|
|
67
|
+
break
|
|
68
|
+
case 'intersection':
|
|
69
|
+
children.push([def.left, path], [def.right, path])
|
|
70
|
+
break
|
|
71
|
+
case 'record':
|
|
72
|
+
case 'map':
|
|
73
|
+
children.push(
|
|
74
|
+
[def.keyType, `${path}[key]`],
|
|
75
|
+
[def.valueType, `${path}[]`]
|
|
76
|
+
)
|
|
77
|
+
break
|
|
78
|
+
case 'pipe':
|
|
79
|
+
// `.transform()` itself: report the pipe's own path, not `out`'s.
|
|
80
|
+
children.push([def.in, path], [def.out, path])
|
|
81
|
+
break
|
|
82
|
+
default:
|
|
83
|
+
// optional, nullable, default, prefault, nonoptional, readonly, catch,
|
|
84
|
+
// promise, lazy, success — every wrapper Zod spells `innerType`.
|
|
85
|
+
if (def.innerType) children.push([def.innerType, path])
|
|
86
|
+
if (def.getter) {
|
|
87
|
+
try {
|
|
88
|
+
children.push([def.getter(), path])
|
|
89
|
+
} catch {
|
|
90
|
+
// A lazy schema that cannot be forced tells us nothing; skip it.
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
for (const [child, childPath] of children) {
|
|
96
|
+
const found = walk(child, childPath)
|
|
97
|
+
if (found !== undefined) return found
|
|
98
|
+
}
|
|
99
|
+
return undefined
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return walk(schema, '')
|
|
103
|
+
}
|
|
@@ -3,6 +3,7 @@ import assert from 'node:assert/strict'
|
|
|
3
3
|
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'fs'
|
|
4
4
|
import { tmpdir } from 'os'
|
|
5
5
|
import { join } from 'path'
|
|
6
|
+
import { fileURLToPath } from 'url'
|
|
6
7
|
|
|
7
8
|
import { getInitialInspectorState } from '../inspector.js'
|
|
8
9
|
import { generateAllSchemas, schemaRuntimeFile } from './schema-generator.js'
|
|
@@ -223,3 +224,49 @@ describe('schemaRuntimeFile', () => {
|
|
|
223
224
|
assert.equal(schemaRuntimeFile(orphan), orphan)
|
|
224
225
|
})
|
|
225
226
|
})
|
|
227
|
+
|
|
228
|
+
describe('a Zod schema that cannot be a wire contract', () => {
|
|
229
|
+
test('names .transform() and its own error code, not a downstream one', async () => {
|
|
230
|
+
// A transform used to surface as PKU724 — the *bad `#pikku/*` import* error
|
|
231
|
+
// — because the schema failed to convert, its generated leaf never got
|
|
232
|
+
// written, and the type that leaf feeds resolved to an error type. The
|
|
233
|
+
// message sent you to an import block that was fine.
|
|
234
|
+
// Inside the package, so the fixture's `import 'zod'` resolves the same
|
|
235
|
+
// way a project's would; a tmpdir has no node_modules above it.
|
|
236
|
+
const dir = mkdtempSync(
|
|
237
|
+
join(fileURLToPath(new URL('../../', import.meta.url)), '.zod-transform-')
|
|
238
|
+
)
|
|
239
|
+
try {
|
|
240
|
+
const file = join(dir, 'schema.ts')
|
|
241
|
+
writeFileSync(
|
|
242
|
+
file,
|
|
243
|
+
`import * as z from 'zod'\n` +
|
|
244
|
+
`export const CreateAssessment = z.object({ patientId: z.string(), when: z.string().transform((s) => new Date(s)) })\n`
|
|
245
|
+
)
|
|
246
|
+
const state = getInitialInspectorState(dir) as any
|
|
247
|
+
state.schemaLookup.set('CreateAssessmentInput', {
|
|
248
|
+
variableName: 'CreateAssessment',
|
|
249
|
+
sourceFile: file,
|
|
250
|
+
vendor: 'zod',
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
const errors: string[] = []
|
|
254
|
+
await assert.rejects(() =>
|
|
255
|
+
generateAllSchemas(
|
|
256
|
+
{ ...logger, error: (m: string) => errors.push(m) },
|
|
257
|
+
{ tsconfig: join(projectDir, 'tsconfig.json') },
|
|
258
|
+
state
|
|
259
|
+
)
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
const reported = errors.join('\n')
|
|
263
|
+
assert.match(reported, /PKU491/)
|
|
264
|
+
assert.match(reported, /CreateAssessmentInput/)
|
|
265
|
+
assert.match(reported, /\.transform\(\)/)
|
|
266
|
+
assert.match(reported, /\.when/)
|
|
267
|
+
assert.doesNotMatch(reported, /PKU724/)
|
|
268
|
+
} finally {
|
|
269
|
+
rmSync(dir, { recursive: true, force: true })
|
|
270
|
+
}
|
|
271
|
+
})
|
|
272
|
+
})
|
|
@@ -12,6 +12,7 @@ import { createGenerator, RootlessError } from 'ts-json-schema-generator'
|
|
|
12
12
|
import { register } from 'tsx/esm/api'
|
|
13
13
|
import * as z from 'zod'
|
|
14
14
|
import { zodToTs, createAuxiliaryTypeStore } from 'zod-to-ts'
|
|
15
|
+
import { findZodTransform } from './find-zod-transform.js'
|
|
15
16
|
import type { FunctionsMeta } from '@pikku/core/services'
|
|
16
17
|
import type { JSONValue } from '@pikku/core/utils'
|
|
17
18
|
import type { HTTPWiringsMeta } from '@pikku/core/http'
|
|
@@ -508,6 +509,19 @@ function processZodSchema(
|
|
|
508
509
|
fakeSourceFile: ts.SourceFile,
|
|
509
510
|
logger: InspectorLogger
|
|
510
511
|
): void {
|
|
512
|
+
const transformPath = findZodTransform(zodSchema)
|
|
513
|
+
if (transformPath !== undefined) {
|
|
514
|
+
const where = transformPath === '' ? 'the schema itself' : transformPath
|
|
515
|
+
throw new Error(
|
|
516
|
+
`[${ErrorCode.SCHEMA_TRANSFORM_NOT_SUPPORTED}] '${schemaName}' contains a .transform() at ${where}.\n` +
|
|
517
|
+
'A transform makes the value sent and the value received two different ' +
|
|
518
|
+
'shapes, and a wire contract describes one: it cannot be expressed in ' +
|
|
519
|
+
'JSON Schema or in the generated TypeScript.\n' +
|
|
520
|
+
'Validate with the plain schema and apply the transform inside the ' +
|
|
521
|
+
'function, or move it to the output type.'
|
|
522
|
+
)
|
|
523
|
+
}
|
|
524
|
+
|
|
511
525
|
const schema = z.toJSONSchema(zodSchema, {
|
|
512
526
|
unrepresentable: 'any',
|
|
513
527
|
override: ({ zodSchema, jsonSchema }) => {
|