typespec-http-zod 0.29.0 → 0.31.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 +19 -0
- package/dist/src/api.d.ts +55 -0
- package/dist/src/api.js +1602 -369
- package/dist/src/constraints.d.ts +45 -1
- package/dist/src/constraints.js +242 -46
- package/dist/src/lib.d.ts +148 -7
- package/dist/src/lib.js +193 -6
- package/dist/src/published.d.ts +139 -0
- package/dist/src/published.js +798 -0
- package/dist/src/registry.d.ts +77 -45
- package/dist/src/registry.js +759 -198
- package/dist/src/types.d.ts +12 -2
- package/dist/src/types.js +276 -78
- package/dist/src/uri-template.d.ts +110 -8
- package/dist/src/uri-template.js +251 -46
- package/dist/src/zod.d.ts +732 -12
- package/dist/src/zod.js +4042 -411
- package/package.json +1 -1
|
@@ -37,6 +37,11 @@ export declare function routeTemplateOf(uriTemplate: string, parameters: readonl
|
|
|
37
37
|
};
|
|
38
38
|
/** What a value decodes into: one value, a list, or name/value pairs (a record or a model). */
|
|
39
39
|
export type ExpansionShape = "scalar" | "list" | "record";
|
|
40
|
+
/**
|
|
41
|
+
* What a decoder reads an expansion as: one of those, or `any` - a value of any type, which is the
|
|
42
|
+
* shape its expansion spells. See {@link URI_EXPANSION_SOURCE}.
|
|
43
|
+
*/
|
|
44
|
+
export type DecodedShape = ExpansionShape | "any";
|
|
40
45
|
/**
|
|
41
46
|
* Whether a path value needs decoding at all. **An ordinary whole-segment parameter does not**, and
|
|
42
47
|
* leaving its validator untouched keeps every spec without an RFC 6570 form byte-identical.
|
|
@@ -54,9 +59,73 @@ export declare function uriExpansionCall(spec: {
|
|
|
54
59
|
readonly operator: string;
|
|
55
60
|
readonly name: string;
|
|
56
61
|
readonly explode: boolean;
|
|
57
|
-
readonly shape:
|
|
58
|
-
readonly
|
|
62
|
+
readonly shape: DecodedShape;
|
|
63
|
+
readonly readers: ValueReaders;
|
|
59
64
|
}): string;
|
|
65
|
+
/**
|
|
66
|
+
* **The schemas a record's or a model's values are read against**, as Zod expressions: one for every
|
|
67
|
+
* value of a record, or one per key of a model. `null` where no value can be anything but text, so
|
|
68
|
+
* a service whose parameters are all strings declares no reader.
|
|
69
|
+
*/
|
|
70
|
+
export interface ValueReaders {
|
|
71
|
+
readonly values: string | null;
|
|
72
|
+
readonly fields: Readonly<Record<string, string>> | null;
|
|
73
|
+
/**
|
|
74
|
+
* **A discriminated base's readers per subtype**, chosen by the value's own discriminator: a header
|
|
75
|
+
* `Pet` carrying `kind,cat,x,5` reads `x` as `Cat` declares it, a string, where `Dog`'s `x` is a
|
|
76
|
+
* number. Read by one table for every subtype, the first subtype's reading won.
|
|
77
|
+
*/
|
|
78
|
+
readonly by?: {
|
|
79
|
+
readonly property: string;
|
|
80
|
+
readonly readers: Readonly<Record<string, ValueReaders>>;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* A reader for a list: a lone value boxed into the list it stands for, each element read against the
|
|
85
|
+
* element's schema - `?tags=1&tags=2` gathered as `["1", "2"]`, and `?tags=1` as `["1"]`.
|
|
86
|
+
*/
|
|
87
|
+
export declare function eachReader(element: string | readonly string[], one?: string, whole?: string): string;
|
|
88
|
+
export declare const NO_READERS: ValueReaders;
|
|
89
|
+
/**
|
|
90
|
+
* **Wire text read as the JSON value it spells, as its schema says it is.** A path, query, header,
|
|
91
|
+
* cookie or form value is always text, and the document's schema describes it once read: `"5"` is
|
|
92
|
+
* `5` where the schema is `int32`, `true` where it is `boolean`, and stays `"5"` where it is a
|
|
93
|
+
* string. The text is tried as a number, then as a boolean, then as itself, and the first the schema
|
|
94
|
+
* accepts is the value - so `limit: int32 | "all"` reads `"5"` as `5` and `"all"` as `"all"`, and
|
|
95
|
+
* `int32 | boolean` reads both. Where none is accepted the first reading is handed on, to fail with
|
|
96
|
+
* the error that reading earns.
|
|
97
|
+
*
|
|
98
|
+
* **A number is text in JSON's number grammar, and nothing looser.** Read by `Number()`, `"0x10"`,
|
|
99
|
+
* `"007"` and `" 5"` were numbers: `int32` accepted text no JSON number is written as, and `"007" |
|
|
100
|
+
* int32` turned the `"007"` it accepts into `7`.
|
|
101
|
+
*
|
|
102
|
+
* Read by a kind instead - number, boolean or text, decided from the type - a numeric enum, a union
|
|
103
|
+
* of a number and a boolean, and a number beside a string literal were never read at all.
|
|
104
|
+
*/
|
|
105
|
+
export declare const WIRE_READ_SOURCE = "\n/** Wire text as the JSON value it spells that the schema accepts: a number, a boolean, or the text. */\nfunction wireRead(raw: unknown, schema: z.ZodType): unknown {\n\tif (typeof raw !== \"string\") return raw;\n\tconst readings: unknown[] = [];\n\tif (/^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?$/.test(raw) && Number.isFinite(Number(raw))) {\n\t\treadings.push(Number(raw));\n\t}\n\tif (raw === \"true\" || raw === \"false\") readings.push(raw === \"true\");\n\tfor (const reading of [...readings, raw]) if (schema.safeParse(reading).success) return reading;\n\treturn readings[0] ?? raw;\n}\n";
|
|
106
|
+
/**
|
|
107
|
+
* How one value is read: against a schema, or as a list boxed and read element by element - a
|
|
108
|
+
* record's or a model's value, through the decoders below, and a multipart text part that may be one
|
|
109
|
+
* value or several. Declared beside its callers only.
|
|
110
|
+
*/
|
|
111
|
+
export declare const WIRE_READ_AS_SOURCE = "\n/** How one value is read: against a schema, or as a list of values each read against one. */\ntype WireReader =\n\t| z.ZodType\n\t| { readonly each: z.ZodType | readonly z.ZodType[]; readonly one?: z.ZodType; readonly whole?: z.ZodType };\n\n/** A value read against its reader: one value, or a list of them. */\nfunction wireReadAs(value: unknown, reader: WireReader | null | undefined): unknown {\n\tif (reader === null || reader === undefined) return value;\n\tif (\"each\" in reader) {\n\t\tconst accepted = (read: unknown): boolean => reader.whole === undefined || reader.whole.safeParse(read).success;\n\t\t// A value that may be one value or a list of them: a lone occurrence is the one value, where that is one.\n\t\tif (!Array.isArray(value) && reader.one !== undefined) {\n\t\t\tconst one = wireRead(value, reader.one);\n\t\t\tif (accepted(one)) return one;\n\t\t}\n\t\t// A list read as the first of its list members' elements the whole accepts: `int32[] | string[]`.\n\t\tconst items: unknown[] = Array.isArray(value) ? value : [value];\n\t\tconst lists: readonly z.ZodType[] = Array.isArray(reader.each) ? reader.each : [reader.each as z.ZodType];\n\t\tconst readings = lists.map((each) => items.map((item) => wireRead(item, each)));\n\t\treturn readings.find(accepted) ?? readings[0];\n\t}\n\treturn wireRead(value, reader);\n}\n";
|
|
112
|
+
/**
|
|
113
|
+
* How the RFC 6570 decoders read a record's or a model's values: by the reader for the key, looked
|
|
114
|
+
* up as the table's OWN property - a key named `constructor` found `Object` on the prototype and
|
|
115
|
+
* handed it to `wireRead`, which threw - each read by {@link WIRE_READ_AS_SOURCE}. Declared beside
|
|
116
|
+
* those decoders only, which are its only callers.
|
|
117
|
+
*/
|
|
118
|
+
export declare const WIRE_READERS_SOURCE = "\n/** A record's or a model's readers: every value's, each key's, and a discriminated base's per subtype. */\ntype WireReaders = {\n\treadonly values: WireReader | null;\n\treadonly fields: globalThis.Readonly<globalThis.Record<string, WireReader>> | null;\n\treadonly by?: { readonly property: string; readonly readers: globalThis.Readonly<globalThis.Record<string, WireReaders>> };\n};\n\n/** The readers for an object whose discriminator reads `kind`: the subtype it names, else the base's. */\nfunction wireReadersOf(spec: WireReaders, kind: unknown): WireReaders {\n\tif (spec.by === undefined || typeof kind !== \"string\") return spec;\n\treturn Object.prototype.hasOwnProperty.call(spec.by.readers, kind) ? (spec.by.readers[kind] ?? spec) : spec;\n}\n\n/** The value an object's discriminator holds, as its own property. */\nfunction wireKindOf(spec: WireReaders, raw: object): unknown {\n\treturn spec.by !== undefined && Object.prototype.hasOwnProperty.call(raw, spec.by.property)\n\t\t? (raw as globalThis.Record<string, unknown>)[spec.by.property]\n\t\t: undefined;\n}\n\n/** A model's reader for a key it declares, read as its own property only, or a record's for any. */\nfunction wireReaderOf(spec: WireReaders, key: string): WireReader | null {\n\tconst own = spec.fields !== null && Object.prototype.hasOwnProperty.call(spec.fields, key);\n\treturn (own ? spec.fields?.[key] : spec.values) ?? null;\n}\n";
|
|
119
|
+
/**
|
|
120
|
+
* **A gathered object read as one member of a union of models reads it.** A form-exploded query
|
|
121
|
+
* typed `A | B` arrives as one set of keys, gathered once under the parameter's name; each member then
|
|
122
|
+
* reads the values as its own properties say. Gathered once per member, the last member's gatherer
|
|
123
|
+
* claimed the name and every other member's keys stayed unread, so `?kind=a&x=1` was refused.
|
|
124
|
+
* Copied key by key as own properties, so a key named `__proto__` is a key and not the prototype.
|
|
125
|
+
*/
|
|
126
|
+
export declare const WIRE_READ_OBJECT_SOURCE = "\n/** A gathered object's values read against one member's readers. */\nfunction wireReadObject(raw: unknown, spec: WireReaders): unknown {\n\tif (raw === null || typeof raw !== \"object\" || Array.isArray(raw)) return raw;\n\tconst readers = wireReadersOf(spec, wireKindOf(spec, raw));\n\tconst read: globalThis.Record<string, unknown> = {};\n\tfor (const [key, value] of Object.entries(raw)) {\n\t\tObject.defineProperty(read, key, { value: wireReadAs(value, wireReaderOf(readers, key)), enumerable: true, writable: true, configurable: true });\n\t}\n\treturn read;\n}\n";
|
|
127
|
+
/** The call that reads a gathered object as one member of a union - see {@link WIRE_READ_OBJECT_SOURCE}. */
|
|
128
|
+
export declare function wireReadObjectCall(readers: ValueReaders): string;
|
|
60
129
|
/**
|
|
61
130
|
* **RFC 6570 section 3.2, run backwards.** A router hands over the text of the segment an expression
|
|
62
131
|
* sits in (or, for an exploding `/` expression, every segment after its literal), and this undoes
|
|
@@ -68,8 +137,16 @@ export declare function uriExpansionCall(spec: {
|
|
|
68
137
|
* the refusal to the schema admitted a request the route does not match. A list's element values
|
|
69
138
|
* are decoded by the element decoder inside, as a query list's are; a record's values are decoded
|
|
70
139
|
* here, because no schema expression reaches them one by one.
|
|
140
|
+
*
|
|
141
|
+
* **A value of any type is read as the shape its expansion spells** (`any`): `unknown`, a scalar
|
|
142
|
+
* with no standard base, or a union holding one, which openapi3 publishes as `{}` or with `{}` among
|
|
143
|
+
* its members. Under `{;v*}` a scalar is exactly one `v=` part, so `;v=1;v=2` - `[1, 2]` in the
|
|
144
|
+
* document - and `;a=1;b=2` - `{ a: 1, b: 2 }` - had no reading, and were refused as not the
|
|
145
|
+
* expansion the route declares. Several parts all named `v` are a list, any other an object, each
|
|
146
|
+
* value read as the text it spells; anywhere else, and as one `v=` part, every expansion is also a
|
|
147
|
+
* scalar's, which is the reading taken first, as `wireRead` takes the number first.
|
|
71
148
|
*/
|
|
72
|
-
export declare const URI_EXPANSION_SOURCE = "\n/** RFC 6570 section 3.2 in reverse: one expression's expansion, decoded into the value it expanded. */\nfunction uriExpansion(\n\traw: unknown,\n\tctx: { addIssue(issue: { code: \"custom\"; message: string }): void },\n\tspec: {\n\t\treadonly prefix: string;\n\t\treadonly suffix: string;\n\t\treadonly operator: string;\n\t\treadonly name: string;\n\t\treadonly explode: boolean;\n\t\treadonly shape: \"scalar\" | \"list\" | \"record\"
|
|
149
|
+
export declare const URI_EXPANSION_SOURCE = "\n/** RFC 6570 section 3.2 in reverse: one expression's expansion, decoded into the value it expanded. */\nfunction uriExpansion(\n\traw: unknown,\n\tctx: { addIssue(issue: { code: \"custom\"; message: string }): void },\n\tspec: WireReaders & {\n\t\treadonly prefix: string;\n\t\treadonly suffix: string;\n\t\treadonly operator: string;\n\t\treadonly name: string;\n\t\treadonly explode: boolean;\n\t\treadonly shape: \"scalar\" | \"list\" | \"record\" | \"any\";\n\t},\n): unknown {\n\tif (typeof raw !== \"string\") return raw;\n\tconst refuse = (): unknown => {\n\t\tctx.addIssue({ code: \"custom\", message: `Not the expansion of {${spec.operator}${spec.name}${spec.explode ? \"*\" : \"\"}} this route declares` });\n\t\treturn z.NEVER;\n\t};\n\tif (raw.length < spec.prefix.length + spec.suffix.length) return refuse();\n\tif (!raw.startsWith(spec.prefix) || !raw.endsWith(spec.suffix)) return refuse();\n\tlet text = raw.slice(spec.prefix.length, raw.length - spec.suffix.length);\n\tif (spec.operator === \".\" || spec.operator === \";\") {\n\t\tif (!text.startsWith(spec.operator)) return refuse();\n\t\ttext = text.slice(1);\n\t}\n\tconst parts = text.split(\";\");\n\tconst own = parts.filter((part) => part === spec.name || part.startsWith(`${spec.name}=`)).length;\n\t// A value of any type is the shape its expansion spells: only `{;v*}` spells a list or an object no scalar does.\n\tconst shape =\n\t\tspec.shape !== \"any\"\n\t\t\t? spec.shape\n\t\t\t: spec.operator !== \";\" || !spec.explode || text === \"\" || (parts.length === 1 && own === 1)\n\t\t\t\t? \"scalar\"\n\t\t\t\t: own === parts.length\n\t\t\t\t\t? \"list\"\n\t\t\t\t\t: \"record\";\n\tif (spec.operator === \";\" && !(shape === \"record\" && spec.explode)) {\n\t\tconst named = parts.map((part) => (part === spec.name ? \"\" : part.startsWith(`${spec.name}=`) ? part.slice(spec.name.length + 1) : undefined));\n\t\tif (named.some((part) => part === undefined)) return refuse();\n\t\tif (shape === \"list\" && spec.explode) {\n\t\t\t// Read here where no element decoder runs after: the elements of a value of any type.\n\t\t\treturn spec.shape === \"any\" ? named.map((item) => wireReadAs(item, spec.values)) : named;\n\t\t}\n\t\tif (named.length !== 1) return refuse();\n\t\ttext = named[0] ?? \"\";\n\t}\n\tif (shape === \"scalar\") return text;\n\tconst joiner = spec.explode && (spec.operator === \".\" || spec.operator === \"/\" || spec.operator === \";\") ? spec.operator : \",\";\n\tif (shape === \"list\") return text === \"\" ? [] : text.split(joiner);\n\tconst entries: [string, string][] = [];\n\tif (spec.explode) {\n\t\tfor (const piece of text === \"\" ? [] : text.split(joiner)) {\n\t\t\tconst at = piece.indexOf(\"=\");\n\t\t\tconst last = entries[entries.length - 1];\n\t\t\t// A value holding the joiner, which the expansion leaves as it is - `a=1.5` under `{.r*}` - goes on.\n\t\t\tif (at === -1 && last !== undefined) last[1] += `${joiner}${piece}`;\n\t\t\telse if (at === -1) return refuse();\n\t\t\telse entries.push([piece.slice(0, at), piece.slice(at + 1)]);\n\t\t}\n\t} else {\n\t\tconst items = text === \"\" ? [] : text.split(\",\");\n\t\tif (items.length % 2 !== 0) return refuse();\n\t\tfor (let index = 0; index < items.length; index += 2) {\n\t\t\tentries.push([items[index] ?? \"\", items[index + 1] ?? \"\"]);\n\t\t}\n\t}\n\tconst readers = wireReadersOf(spec, wireKindOf(spec, Object.fromEntries(entries)));\n\treturn Object.fromEntries(entries.map(([key, item]) => [key, wireReadAs(item, wireReaderOf(readers, key))]));\n}\n";
|
|
73
150
|
/**
|
|
74
151
|
* The call that gathers a form-exploded record or model back under its parameter name. The
|
|
75
152
|
* gatherer itself is {@link EXPLODED_QUERY_SOURCE}.
|
|
@@ -80,15 +157,40 @@ export declare function explodedQueryCall(spec: {
|
|
|
80
157
|
readonly others: readonly string[];
|
|
81
158
|
/** For a model, its own properties' wire names; `undefined` for a record. */
|
|
82
159
|
readonly keys: readonly string[] | undefined;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
160
|
+
/**
|
|
161
|
+
* Whether the parameter may also be sent as itself - a list or a scalar beside its records and
|
|
162
|
+
* models - so what arrived under its name is kept for those, see {@link WIRE_ITSELF_SOURCE}.
|
|
163
|
+
*/
|
|
164
|
+
readonly itself: boolean;
|
|
165
|
+
readonly readers: ValueReaders;
|
|
87
166
|
}): string;
|
|
88
167
|
/**
|
|
89
168
|
* **`{?param*}` over a record or a model expands to the object's own keys**, `?a=1&b=2` and
|
|
90
169
|
* `?field=status&value=active`, so there is no `param` key for a query validator to find. This
|
|
91
170
|
* gathers those keys back under the parameter's name before the query object is validated: a model
|
|
92
171
|
* takes exactly its declared properties, a record every key no other query parameter claims.
|
|
172
|
+
*
|
|
173
|
+
* **The parameter's own name is a key like any other, and only that.** `?f=5` beside `f?: F`, where
|
|
174
|
+
* `F` declares no `f`, is no key of the object and no other parameter's: the document reads it as a
|
|
175
|
+
* parameter it does not declare, and accepts it. Left under the name, it reached the model's schema
|
|
176
|
+
* as the string `"5"`, and was refused, where `?zzz=5` was accepted. Only a parameter that may also be
|
|
177
|
+
* sent as itself keeps it - see {@link WIRE_ITSELF_SOURCE}.
|
|
178
|
+
*/
|
|
179
|
+
export declare const EXPLODED_QUERY_SOURCE = "\n/** Each object a gatherer made for a parameter that may also be sent as itself, and what was: one value, or none. */\nconst wireSentAsItself = new WeakMap<object, readonly unknown[]>();\n\n/** RFC 6570 form expansion of an exploded record or model, gathered back under its parameter. */\nfunction explodedQuery(\n\traw: unknown,\n\tspec: WireReaders & {\n\t\treadonly name: string;\n\t\treadonly others: readonly string[];\n\t\treadonly keys: readonly string[] | null;\n\t\treadonly itself: boolean;\n\t},\n): unknown {\n\tif (raw === null || typeof raw !== \"object\" || Array.isArray(raw)) return raw;\n\tconst readers = wireReadersOf(spec, wireKindOf(spec, raw));\n\tconst gathered: globalThis.Record<string, unknown> = {};\n\tconst rest: globalThis.Record<string, unknown> = {};\n\tfor (const [key, value] of Object.entries(raw)) {\n\t\t// Already gathered only where an OBJECT sits under the name: a query value from the wire is a\n\t\t// string or a list of them, and `{ param: 0 }` expands to `?param=0`.\n\t\tif (key === spec.name && value !== null && typeof value === \"object\" && !Array.isArray(value)) return raw;\n\t\t/**\n\t\t * Left in place as well, for every other object gathered from the same query: `f: F1` and\n\t\t * `g: F2` both declaring `a` each read `?a=1`, as the document reads it, where the first\n\t\t * gatherer took `a` and the second refused a missing `g`. The query object strips it after.\n\t\t */\n\t\trest[key] = value;\n\t\t// A key another query parameter is named is that parameter's, the reading the document takes.\n\t\tconst own = !spec.others.includes(key) && (spec.keys === null || spec.keys.includes(key));\n\t\tif (own) gathered[key] = wireReadAs(value, wireReaderOf(readers, key));\n\t}\n\tconst sent = Object.prototype.hasOwnProperty.call(raw, spec.name);\n\tif (Object.keys(gathered).length > 0) {\n\t\tif (spec.itself) wireSentAsItself.set(gathered, sent ? [(raw as globalThis.Record<string, unknown>)[spec.name]] : []);\n\t\treturn { ...rest, [spec.name]: gathered };\n\t}\n\t// Nothing gathered: the name sent alone is the parameter itself, where it may be, or a key nothing declares.\n\tif (!sent || spec.itself) return raw;\n\tdelete rest[spec.name];\n\treturn rest;\n}\n";
|
|
180
|
+
/** The call that hands a list or a scalar form the value its parameter was sent as itself. */
|
|
181
|
+
export declare function wireItselfCall(): string;
|
|
182
|
+
/**
|
|
183
|
+
* **A parameter that may be an object or a list is read both ways from one query.** `x?: int32[] |
|
|
184
|
+
* Record<int32>` exploded is `?x=1&x=2` as the list and `?a=1&b=2` as the record - and `?x=5` as
|
|
185
|
+
* either, since `{ x: 5 }` expands to it too. The record's gatherer takes every key, the parameter's
|
|
186
|
+
* own name included, so the list member was handed `{ x: ["1", "2"] }` and refused it: `?x=1&x=2`,
|
|
187
|
+
* `[1, 2]` in the document, was refused for every such union, and `?x=007`, `["007"]`, for
|
|
188
|
+
* `string[] | Record<int32>`. The gatherer now records what arrived under the name beside the object
|
|
189
|
+
* it made, and each member that is not an object reads that, while each that is reads the object -
|
|
190
|
+
* so the union's first member the value satisfies is its reading, as everywhere else.
|
|
191
|
+
*
|
|
192
|
+
* **Only an object the gatherer made carries it.** One a caller built - the value already gathered,
|
|
193
|
+
* or `queryFields` given structured input - is the object it is, and a member that is not an object
|
|
194
|
+
* refuses it as before; the gatherer's own object, with nothing sent under the name, is refused here.
|
|
93
195
|
*/
|
|
94
|
-
export declare const
|
|
196
|
+
export declare const WIRE_ITSELF_SOURCE = "\n/** What a parameter was sent as itself, out of the object its gatherer made: refused where it was not. */\nfunction wireItself(raw: unknown, ctx: { addIssue(issue: { code: \"custom\"; message: string }): void }): unknown {\n\tif (raw === null || typeof raw !== \"object\") return raw;\n\tconst sent = wireSentAsItself.get(raw);\n\tif (sent === undefined) return raw;\n\tif (sent.length === 0) {\n\t\tctx.addIssue({ code: \"custom\", message: \"Sent as an object's keys, not as a value of its own\" });\n\t\treturn z.NEVER;\n\t}\n\treturn sent[0];\n}\n";
|
package/dist/src/uri-template.js
CHANGED
|
@@ -129,7 +129,153 @@ export function needsExpansionDecoding(segment, shape) {
|
|
|
129
129
|
* {@link URI_EXPANSION_SOURCE}, declared once in `schemas.gen.ts` when something calls it.
|
|
130
130
|
*/
|
|
131
131
|
export function uriExpansionCall(spec) {
|
|
132
|
-
return `(raw, ctx) => uriExpansion(raw, ctx, { prefix: ${literal(spec.prefix)}, suffix: ${literal(spec.suffix)}, operator: ${literal(spec.operator)}, name: ${literal(spec.name)}, explode: ${spec.explode}, shape: ${literal(spec.shape)},
|
|
132
|
+
return `(raw, ctx) => uriExpansion(raw, ctx, { prefix: ${literal(spec.prefix)}, suffix: ${literal(spec.suffix)}, operator: ${literal(spec.operator)}, name: ${literal(spec.name)}, explode: ${spec.explode}, shape: ${literal(spec.shape)}, ${readersOf(spec.readers)} })`;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* A reader for a list: a lone value boxed into the list it stands for, each element read against the
|
|
136
|
+
* element's schema - `?tags=1&tags=2` gathered as `["1", "2"]`, and `?tags=1` as `["1"]`.
|
|
137
|
+
*/
|
|
138
|
+
export function eachReader(element, one, whole) {
|
|
139
|
+
const each = typeof element === "string"
|
|
140
|
+
? element
|
|
141
|
+
: element.length === 1
|
|
142
|
+
? element[0]
|
|
143
|
+
: `[${element.join(", ")}]`;
|
|
144
|
+
return `{ each: ${each}${one === undefined ? "" : `, one: ${one}`}${whole === undefined ? "" : `, whole: ${whole}`} }`;
|
|
145
|
+
}
|
|
146
|
+
export const NO_READERS = { values: null, fields: null };
|
|
147
|
+
function readersOf(readers) {
|
|
148
|
+
const by = readers.by;
|
|
149
|
+
return by === undefined
|
|
150
|
+
? tableOf(readers)
|
|
151
|
+
: `${tableOf(readers)}, by: { property: ${literal(by.property)}, readers: { ${Object.entries(by.readers)
|
|
152
|
+
.map(([value, each]) => `[${literal(value)}]: { ${tableOf(each)} }`)
|
|
153
|
+
.join(", ")} } }`;
|
|
154
|
+
}
|
|
155
|
+
/** One table of readers: every value's, and each key's. */
|
|
156
|
+
function tableOf(readers) {
|
|
157
|
+
const fields = readers.fields === null
|
|
158
|
+
? "null"
|
|
159
|
+
: // Computed keys, so `__proto__` is a key like any other and not the object's prototype.
|
|
160
|
+
`{ ${Object.entries(readers.fields)
|
|
161
|
+
.map(([key, schema]) => `[${literal(key)}]: ${schema}`)
|
|
162
|
+
.join(", ")} }`;
|
|
163
|
+
return `values: ${readers.values ?? "null"}, fields: ${fields}`;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* **Wire text read as the JSON value it spells, as its schema says it is.** A path, query, header,
|
|
167
|
+
* cookie or form value is always text, and the document's schema describes it once read: `"5"` is
|
|
168
|
+
* `5` where the schema is `int32`, `true` where it is `boolean`, and stays `"5"` where it is a
|
|
169
|
+
* string. The text is tried as a number, then as a boolean, then as itself, and the first the schema
|
|
170
|
+
* accepts is the value - so `limit: int32 | "all"` reads `"5"` as `5` and `"all"` as `"all"`, and
|
|
171
|
+
* `int32 | boolean` reads both. Where none is accepted the first reading is handed on, to fail with
|
|
172
|
+
* the error that reading earns.
|
|
173
|
+
*
|
|
174
|
+
* **A number is text in JSON's number grammar, and nothing looser.** Read by `Number()`, `"0x10"`,
|
|
175
|
+
* `"007"` and `" 5"` were numbers: `int32` accepted text no JSON number is written as, and `"007" |
|
|
176
|
+
* int32` turned the `"007"` it accepts into `7`.
|
|
177
|
+
*
|
|
178
|
+
* Read by a kind instead - number, boolean or text, decided from the type - a numeric enum, a union
|
|
179
|
+
* of a number and a boolean, and a number beside a string literal were never read at all.
|
|
180
|
+
*/
|
|
181
|
+
export const WIRE_READ_SOURCE = `
|
|
182
|
+
/** Wire text as the JSON value it spells that the schema accepts: a number, a boolean, or the text. */
|
|
183
|
+
function wireRead(raw: unknown, schema: z.ZodType): unknown {
|
|
184
|
+
if (typeof raw !== "string") return raw;
|
|
185
|
+
const readings: unknown[] = [];
|
|
186
|
+
if (/^-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?$/.test(raw) && Number.isFinite(Number(raw))) {
|
|
187
|
+
readings.push(Number(raw));
|
|
188
|
+
}
|
|
189
|
+
if (raw === "true" || raw === "false") readings.push(raw === "true");
|
|
190
|
+
for (const reading of [...readings, raw]) if (schema.safeParse(reading).success) return reading;
|
|
191
|
+
return readings[0] ?? raw;
|
|
192
|
+
}
|
|
193
|
+
`;
|
|
194
|
+
/**
|
|
195
|
+
* How one value is read: against a schema, or as a list boxed and read element by element - a
|
|
196
|
+
* record's or a model's value, through the decoders below, and a multipart text part that may be one
|
|
197
|
+
* value or several. Declared beside its callers only.
|
|
198
|
+
*/
|
|
199
|
+
export const WIRE_READ_AS_SOURCE = `
|
|
200
|
+
/** How one value is read: against a schema, or as a list of values each read against one. */
|
|
201
|
+
type WireReader =
|
|
202
|
+
| z.ZodType
|
|
203
|
+
| { readonly each: z.ZodType | readonly z.ZodType[]; readonly one?: z.ZodType; readonly whole?: z.ZodType };
|
|
204
|
+
|
|
205
|
+
/** A value read against its reader: one value, or a list of them. */
|
|
206
|
+
function wireReadAs(value: unknown, reader: WireReader | null | undefined): unknown {
|
|
207
|
+
if (reader === null || reader === undefined) return value;
|
|
208
|
+
if ("each" in reader) {
|
|
209
|
+
const accepted = (read: unknown): boolean => reader.whole === undefined || reader.whole.safeParse(read).success;
|
|
210
|
+
// A value that may be one value or a list of them: a lone occurrence is the one value, where that is one.
|
|
211
|
+
if (!Array.isArray(value) && reader.one !== undefined) {
|
|
212
|
+
const one = wireRead(value, reader.one);
|
|
213
|
+
if (accepted(one)) return one;
|
|
214
|
+
}
|
|
215
|
+
// A list read as the first of its list members' elements the whole accepts: \`int32[] | string[]\`.
|
|
216
|
+
const items: unknown[] = Array.isArray(value) ? value : [value];
|
|
217
|
+
const lists: readonly z.ZodType[] = Array.isArray(reader.each) ? reader.each : [reader.each as z.ZodType];
|
|
218
|
+
const readings = lists.map((each) => items.map((item) => wireRead(item, each)));
|
|
219
|
+
return readings.find(accepted) ?? readings[0];
|
|
220
|
+
}
|
|
221
|
+
return wireRead(value, reader);
|
|
222
|
+
}
|
|
223
|
+
`;
|
|
224
|
+
/**
|
|
225
|
+
* How the RFC 6570 decoders read a record's or a model's values: by the reader for the key, looked
|
|
226
|
+
* up as the table's OWN property - a key named `constructor` found `Object` on the prototype and
|
|
227
|
+
* handed it to `wireRead`, which threw - each read by {@link WIRE_READ_AS_SOURCE}. Declared beside
|
|
228
|
+
* those decoders only, which are its only callers.
|
|
229
|
+
*/
|
|
230
|
+
export const WIRE_READERS_SOURCE = `
|
|
231
|
+
/** A record's or a model's readers: every value's, each key's, and a discriminated base's per subtype. */
|
|
232
|
+
type WireReaders = {
|
|
233
|
+
readonly values: WireReader | null;
|
|
234
|
+
readonly fields: globalThis.Readonly<globalThis.Record<string, WireReader>> | null;
|
|
235
|
+
readonly by?: { readonly property: string; readonly readers: globalThis.Readonly<globalThis.Record<string, WireReaders>> };
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
/** The readers for an object whose discriminator reads \`kind\`: the subtype it names, else the base's. */
|
|
239
|
+
function wireReadersOf(spec: WireReaders, kind: unknown): WireReaders {
|
|
240
|
+
if (spec.by === undefined || typeof kind !== "string") return spec;
|
|
241
|
+
return Object.prototype.hasOwnProperty.call(spec.by.readers, kind) ? (spec.by.readers[kind] ?? spec) : spec;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** The value an object's discriminator holds, as its own property. */
|
|
245
|
+
function wireKindOf(spec: WireReaders, raw: object): unknown {
|
|
246
|
+
return spec.by !== undefined && Object.prototype.hasOwnProperty.call(raw, spec.by.property)
|
|
247
|
+
? (raw as globalThis.Record<string, unknown>)[spec.by.property]
|
|
248
|
+
: undefined;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** A model's reader for a key it declares, read as its own property only, or a record's for any. */
|
|
252
|
+
function wireReaderOf(spec: WireReaders, key: string): WireReader | null {
|
|
253
|
+
const own = spec.fields !== null && Object.prototype.hasOwnProperty.call(spec.fields, key);
|
|
254
|
+
return (own ? spec.fields?.[key] : spec.values) ?? null;
|
|
255
|
+
}
|
|
256
|
+
`;
|
|
257
|
+
/**
|
|
258
|
+
* **A gathered object read as one member of a union of models reads it.** A form-exploded query
|
|
259
|
+
* typed `A | B` arrives as one set of keys, gathered once under the parameter's name; each member then
|
|
260
|
+
* reads the values as its own properties say. Gathered once per member, the last member's gatherer
|
|
261
|
+
* claimed the name and every other member's keys stayed unread, so `?kind=a&x=1` was refused.
|
|
262
|
+
* Copied key by key as own properties, so a key named `__proto__` is a key and not the prototype.
|
|
263
|
+
*/
|
|
264
|
+
export const WIRE_READ_OBJECT_SOURCE = `
|
|
265
|
+
/** A gathered object's values read against one member's readers. */
|
|
266
|
+
function wireReadObject(raw: unknown, spec: WireReaders): unknown {
|
|
267
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return raw;
|
|
268
|
+
const readers = wireReadersOf(spec, wireKindOf(spec, raw));
|
|
269
|
+
const read: globalThis.Record<string, unknown> = {};
|
|
270
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
271
|
+
Object.defineProperty(read, key, { value: wireReadAs(value, wireReaderOf(readers, key)), enumerable: true, writable: true, configurable: true });
|
|
272
|
+
}
|
|
273
|
+
return read;
|
|
274
|
+
}
|
|
275
|
+
`;
|
|
276
|
+
/** The call that reads a gathered object as one member of a union - see {@link WIRE_READ_OBJECT_SOURCE}. */
|
|
277
|
+
export function wireReadObjectCall(readers) {
|
|
278
|
+
return `(raw) => wireReadObject(raw, { ${readersOf(readers)} })`;
|
|
133
279
|
}
|
|
134
280
|
/**
|
|
135
281
|
* **RFC 6570 section 3.2, run backwards.** A router hands over the text of the segment an expression
|
|
@@ -142,20 +288,27 @@ export function uriExpansionCall(spec) {
|
|
|
142
288
|
* the refusal to the schema admitted a request the route does not match. A list's element values
|
|
143
289
|
* are decoded by the element decoder inside, as a query list's are; a record's values are decoded
|
|
144
290
|
* here, because no schema expression reaches them one by one.
|
|
291
|
+
*
|
|
292
|
+
* **A value of any type is read as the shape its expansion spells** (`any`): `unknown`, a scalar
|
|
293
|
+
* with no standard base, or a union holding one, which openapi3 publishes as `{}` or with `{}` among
|
|
294
|
+
* its members. Under `{;v*}` a scalar is exactly one `v=` part, so `;v=1;v=2` - `[1, 2]` in the
|
|
295
|
+
* document - and `;a=1;b=2` - `{ a: 1, b: 2 }` - had no reading, and were refused as not the
|
|
296
|
+
* expansion the route declares. Several parts all named `v` are a list, any other an object, each
|
|
297
|
+
* value read as the text it spells; anywhere else, and as one `v=` part, every expansion is also a
|
|
298
|
+
* scalar's, which is the reading taken first, as `wireRead` takes the number first.
|
|
145
299
|
*/
|
|
146
300
|
export const URI_EXPANSION_SOURCE = `
|
|
147
301
|
/** RFC 6570 section 3.2 in reverse: one expression's expansion, decoded into the value it expanded. */
|
|
148
302
|
function uriExpansion(
|
|
149
303
|
raw: unknown,
|
|
150
304
|
ctx: { addIssue(issue: { code: "custom"; message: string }): void },
|
|
151
|
-
spec: {
|
|
305
|
+
spec: WireReaders & {
|
|
152
306
|
readonly prefix: string;
|
|
153
307
|
readonly suffix: string;
|
|
154
308
|
readonly operator: string;
|
|
155
309
|
readonly name: string;
|
|
156
310
|
readonly explode: boolean;
|
|
157
|
-
readonly shape: "scalar" | "list" | "record";
|
|
158
|
-
readonly values: "number" | "boolean" | "";
|
|
311
|
+
readonly shape: "scalar" | "list" | "record" | "any";
|
|
159
312
|
},
|
|
160
313
|
): unknown {
|
|
161
314
|
if (typeof raw !== "string") return raw;
|
|
@@ -170,39 +323,49 @@ function uriExpansion(
|
|
|
170
323
|
if (!text.startsWith(spec.operator)) return refuse();
|
|
171
324
|
text = text.slice(1);
|
|
172
325
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
326
|
+
const parts = text.split(";");
|
|
327
|
+
const own = parts.filter((part) => part === spec.name || part.startsWith(\`\${spec.name}=\`)).length;
|
|
328
|
+
// A value of any type is the shape its expansion spells: only \`{;v*}\` spells a list or an object no scalar does.
|
|
329
|
+
const shape =
|
|
330
|
+
spec.shape !== "any"
|
|
331
|
+
? spec.shape
|
|
332
|
+
: spec.operator !== ";" || !spec.explode || text === "" || (parts.length === 1 && own === 1)
|
|
333
|
+
? "scalar"
|
|
334
|
+
: own === parts.length
|
|
335
|
+
? "list"
|
|
336
|
+
: "record";
|
|
337
|
+
if (spec.operator === ";" && !(shape === "record" && spec.explode)) {
|
|
338
|
+
const named = parts.map((part) => (part === spec.name ? "" : part.startsWith(\`\${spec.name}=\`) ? part.slice(spec.name.length + 1) : undefined));
|
|
177
339
|
if (named.some((part) => part === undefined)) return refuse();
|
|
178
|
-
if (
|
|
340
|
+
if (shape === "list" && spec.explode) {
|
|
341
|
+
// Read here where no element decoder runs after: the elements of a value of any type.
|
|
342
|
+
return spec.shape === "any" ? named.map((item) => wireReadAs(item, spec.values)) : named;
|
|
343
|
+
}
|
|
179
344
|
if (named.length !== 1) return refuse();
|
|
180
345
|
text = named[0] ?? "";
|
|
181
346
|
}
|
|
182
|
-
if (
|
|
347
|
+
if (shape === "scalar") return text;
|
|
183
348
|
const joiner = spec.explode && (spec.operator === "." || spec.operator === "/" || spec.operator === ";") ? spec.operator : ",";
|
|
184
|
-
if (
|
|
185
|
-
const
|
|
186
|
-
spec.values === "number" && item.trim() !== "" && Number.isFinite(Number(item))
|
|
187
|
-
? Number(item)
|
|
188
|
-
: spec.values === "boolean" && (item === "true" || item === "false")
|
|
189
|
-
? item === "true"
|
|
190
|
-
: item;
|
|
191
|
-
const entries: [string, unknown][] = [];
|
|
349
|
+
if (shape === "list") return text === "" ? [] : text.split(joiner);
|
|
350
|
+
const entries: [string, string][] = [];
|
|
192
351
|
if (spec.explode) {
|
|
193
|
-
for (const
|
|
194
|
-
const at =
|
|
195
|
-
|
|
196
|
-
|
|
352
|
+
for (const piece of text === "" ? [] : text.split(joiner)) {
|
|
353
|
+
const at = piece.indexOf("=");
|
|
354
|
+
const last = entries[entries.length - 1];
|
|
355
|
+
// A value holding the joiner, which the expansion leaves as it is - \`a=1.5\` under \`{.r*}\` - goes on.
|
|
356
|
+
if (at === -1 && last !== undefined) last[1] += \`\${joiner}\${piece}\`;
|
|
357
|
+
else if (at === -1) return refuse();
|
|
358
|
+
else entries.push([piece.slice(0, at), piece.slice(at + 1)]);
|
|
197
359
|
}
|
|
198
360
|
} else {
|
|
199
361
|
const items = text === "" ? [] : text.split(",");
|
|
200
362
|
if (items.length % 2 !== 0) return refuse();
|
|
201
363
|
for (let index = 0; index < items.length; index += 2) {
|
|
202
|
-
entries.push([items[index] ?? "",
|
|
364
|
+
entries.push([items[index] ?? "", items[index + 1] ?? ""]);
|
|
203
365
|
}
|
|
204
366
|
}
|
|
205
|
-
|
|
367
|
+
const readers = wireReadersOf(spec, wireKindOf(spec, Object.fromEntries(entries)));
|
|
368
|
+
return Object.fromEntries(entries.map(([key, item]) => [key, wireReadAs(item, wireReaderOf(readers, key))]));
|
|
206
369
|
}
|
|
207
370
|
`;
|
|
208
371
|
/**
|
|
@@ -210,49 +373,91 @@ function uriExpansion(
|
|
|
210
373
|
* gatherer itself is {@link EXPLODED_QUERY_SOURCE}.
|
|
211
374
|
*/
|
|
212
375
|
export function explodedQueryCall(spec) {
|
|
213
|
-
return `(raw) => explodedQuery(raw, { name: ${literal(spec.name)}, others: ${literal(spec.others)}, keys: ${literal(spec.keys ?? null)},
|
|
376
|
+
return `(raw) => explodedQuery(raw, { name: ${literal(spec.name)}, others: ${literal(spec.others)}, keys: ${literal(spec.keys ?? null)}, itself: ${spec.itself}, ${readersOf(spec.readers)} })`;
|
|
214
377
|
}
|
|
215
378
|
/**
|
|
216
379
|
* **`{?param*}` over a record or a model expands to the object's own keys**, `?a=1&b=2` and
|
|
217
380
|
* `?field=status&value=active`, so there is no `param` key for a query validator to find. This
|
|
218
381
|
* gathers those keys back under the parameter's name before the query object is validated: a model
|
|
219
382
|
* takes exactly its declared properties, a record every key no other query parameter claims.
|
|
383
|
+
*
|
|
384
|
+
* **The parameter's own name is a key like any other, and only that.** `?f=5` beside `f?: F`, where
|
|
385
|
+
* `F` declares no `f`, is no key of the object and no other parameter's: the document reads it as a
|
|
386
|
+
* parameter it does not declare, and accepts it. Left under the name, it reached the model's schema
|
|
387
|
+
* as the string `"5"`, and was refused, where `?zzz=5` was accepted. Only a parameter that may also be
|
|
388
|
+
* sent as itself keeps it - see {@link WIRE_ITSELF_SOURCE}.
|
|
220
389
|
*/
|
|
221
390
|
export const EXPLODED_QUERY_SOURCE = `
|
|
391
|
+
/** Each object a gatherer made for a parameter that may also be sent as itself, and what was: one value, or none. */
|
|
392
|
+
const wireSentAsItself = new WeakMap<object, readonly unknown[]>();
|
|
393
|
+
|
|
222
394
|
/** RFC 6570 form expansion of an exploded record or model, gathered back under its parameter. */
|
|
223
395
|
function explodedQuery(
|
|
224
396
|
raw: unknown,
|
|
225
|
-
spec: {
|
|
397
|
+
spec: WireReaders & {
|
|
226
398
|
readonly name: string;
|
|
227
399
|
readonly others: readonly string[];
|
|
228
400
|
readonly keys: readonly string[] | null;
|
|
229
|
-
readonly
|
|
230
|
-
readonly booleans: readonly string[];
|
|
231
|
-
readonly values: "number" | "boolean" | "";
|
|
401
|
+
readonly itself: boolean;
|
|
232
402
|
},
|
|
233
403
|
): unknown {
|
|
234
404
|
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return raw;
|
|
235
|
-
const
|
|
236
|
-
const
|
|
405
|
+
const readers = wireReadersOf(spec, wireKindOf(spec, raw));
|
|
406
|
+
const gathered: globalThis.Record<string, unknown> = {};
|
|
407
|
+
const rest: globalThis.Record<string, unknown> = {};
|
|
237
408
|
for (const [key, value] of Object.entries(raw)) {
|
|
238
409
|
// Already gathered only where an OBJECT sits under the name: a query value from the wire is a
|
|
239
410
|
// string or a list of them, and \`{ param: 0 }\` expands to \`?param=0\`.
|
|
240
411
|
if (key === spec.name && value !== null && typeof value === "object" && !Array.isArray(value)) return raw;
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
412
|
+
/**
|
|
413
|
+
* Left in place as well, for every other object gathered from the same query: \`f: F1\` and
|
|
414
|
+
* \`g: F2\` both declaring \`a\` each read \`?a=1\`, as the document reads it, where the first
|
|
415
|
+
* gatherer took \`a\` and the second refused a missing \`g\`. The query object strips it after.
|
|
416
|
+
*/
|
|
417
|
+
rest[key] = value;
|
|
418
|
+
// A key another query parameter is named is that parameter's, the reading the document takes.
|
|
419
|
+
const own = !spec.others.includes(key) && (spec.keys === null || spec.keys.includes(key));
|
|
420
|
+
if (own) gathered[key] = wireReadAs(value, wireReaderOf(readers, key));
|
|
421
|
+
}
|
|
422
|
+
const sent = Object.prototype.hasOwnProperty.call(raw, spec.name);
|
|
423
|
+
if (Object.keys(gathered).length > 0) {
|
|
424
|
+
if (spec.itself) wireSentAsItself.set(gathered, sent ? [(raw as globalThis.Record<string, unknown>)[spec.name]] : []);
|
|
425
|
+
return { ...rest, [spec.name]: gathered };
|
|
426
|
+
}
|
|
427
|
+
// Nothing gathered: the name sent alone is the parameter itself, where it may be, or a key nothing declares.
|
|
428
|
+
if (!sent || spec.itself) return raw;
|
|
429
|
+
delete rest[spec.name];
|
|
430
|
+
return rest;
|
|
431
|
+
}
|
|
432
|
+
`;
|
|
433
|
+
/** The call that hands a list or a scalar form the value its parameter was sent as itself. */
|
|
434
|
+
export function wireItselfCall() {
|
|
435
|
+
return `(raw, ctx) => wireItself(raw, ctx)`;
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* **A parameter that may be an object or a list is read both ways from one query.** `x?: int32[] |
|
|
439
|
+
* Record<int32>` exploded is `?x=1&x=2` as the list and `?a=1&b=2` as the record - and `?x=5` as
|
|
440
|
+
* either, since `{ x: 5 }` expands to it too. The record's gatherer takes every key, the parameter's
|
|
441
|
+
* own name included, so the list member was handed `{ x: ["1", "2"] }` and refused it: `?x=1&x=2`,
|
|
442
|
+
* `[1, 2]` in the document, was refused for every such union, and `?x=007`, `["007"]`, for
|
|
443
|
+
* `string[] | Record<int32>`. The gatherer now records what arrived under the name beside the object
|
|
444
|
+
* it made, and each member that is not an object reads that, while each that is reads the object -
|
|
445
|
+
* so the union's first member the value satisfies is its reading, as everywhere else.
|
|
446
|
+
*
|
|
447
|
+
* **Only an object the gatherer made carries it.** One a caller built - the value already gathered,
|
|
448
|
+
* or `queryFields` given structured input - is the object it is, and a member that is not an object
|
|
449
|
+
* refuses it as before; the gatherer's own object, with nothing sent under the name, is refused here.
|
|
450
|
+
*/
|
|
451
|
+
export const WIRE_ITSELF_SOURCE = `
|
|
452
|
+
/** What a parameter was sent as itself, out of the object its gatherer made: refused where it was not. */
|
|
453
|
+
function wireItself(raw: unknown, ctx: { addIssue(issue: { code: "custom"; message: string }): void }): unknown {
|
|
454
|
+
if (raw === null || typeof raw !== "object") return raw;
|
|
455
|
+
const sent = wireSentAsItself.get(raw);
|
|
456
|
+
if (sent === undefined) return raw;
|
|
457
|
+
if (sent.length === 0) {
|
|
458
|
+
ctx.addIssue({ code: "custom", message: "Sent as an object's keys, not as a value of its own" });
|
|
459
|
+
return z.NEVER;
|
|
255
460
|
}
|
|
256
|
-
return
|
|
461
|
+
return sent[0];
|
|
257
462
|
}
|
|
258
463
|
`;
|