@ultimat3/schema 2.0.0 → 3.0.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/package.json +1 -1
- package/src/coerce.ts +14 -4
- package/src/provider.ts +4 -2
package/package.json
CHANGED
package/src/coerce.ts
CHANGED
|
@@ -81,7 +81,10 @@ export function coerceNode(node: SchemaNode, raw: unknown): unknown {
|
|
|
81
81
|
const source = raw as Record<string, unknown>;
|
|
82
82
|
const out: Record<string, unknown> = { ...source };
|
|
83
83
|
for (const [key, child] of Object.entries(node.properties)) {
|
|
84
|
-
|
|
84
|
+
// `Object.hasOwn`, never `key in source`: `{ ...source }` above already dropped what a
|
|
85
|
+
// prototype carries, so an `in` check put it back — a schema field named `toString` or
|
|
86
|
+
// `valueOf` was coerced from the INHERITED member and handed validation a function.
|
|
87
|
+
if (Object.hasOwn(source, key)) out[key] = coerceNode(child, source[key]);
|
|
85
88
|
}
|
|
86
89
|
return out;
|
|
87
90
|
}
|
|
@@ -111,11 +114,17 @@ export function coerceNode(node: SchemaNode, raw: unknown): unknown {
|
|
|
111
114
|
}
|
|
112
115
|
}
|
|
113
116
|
|
|
117
|
+
/**
|
|
118
|
+
* `Object.create(null)`, for the reason `@ultimat3/http`'s `parseQuery` already uses one: this
|
|
119
|
+
* record is built from caller-controlled keys. On a `{}` literal `out['__proto__'] = …` hits the
|
|
120
|
+
* prototype accessor instead of declaring a key, and every member of `Object.prototype` reads
|
|
121
|
+
* back as present when the client sent nothing.
|
|
122
|
+
*/
|
|
114
123
|
function toRecord(source: QuerySource): Record<string, string | readonly string[] | undefined> {
|
|
124
|
+
const out = Object.create(null) as Record<string, string | readonly string[] | undefined>;
|
|
115
125
|
if (!(source instanceof URLSearchParams)) {
|
|
116
|
-
return
|
|
126
|
+
return Object.assign(out, source);
|
|
117
127
|
}
|
|
118
|
-
const out: Record<string, string | readonly string[]> = {};
|
|
119
128
|
for (const key of new Set(source.keys())) {
|
|
120
129
|
const all = source.getAll(key);
|
|
121
130
|
out[key] = all.length > 1 ? all : (all[0] as string);
|
|
@@ -134,7 +143,8 @@ export function coerceQuery(schema: unknown, source: QuerySource): Record<string
|
|
|
134
143
|
|
|
135
144
|
const out: Record<string, unknown> = { ...record };
|
|
136
145
|
for (const [key, child] of Object.entries(node.properties)) {
|
|
137
|
-
|
|
146
|
+
// See `toRecord`: a declared property is coerced only when the caller actually sent it.
|
|
147
|
+
if (!Object.hasOwn(record, key)) continue;
|
|
138
148
|
const raw = record[key];
|
|
139
149
|
out[key] = coerceNode(child, child.kind === 'array' ? (raw ?? []) : normaliseSingle(raw));
|
|
140
150
|
}
|
package/src/provider.ts
CHANGED
|
@@ -11,8 +11,10 @@ export interface SchemaProvider {
|
|
|
11
11
|
readonly vendor: string;
|
|
12
12
|
readonly t: TNamespace;
|
|
13
13
|
/**
|
|
14
|
-
* Return the IR for one of this provider's schemas. Required for OpenAPI
|
|
15
|
-
*
|
|
14
|
+
* Return the IR for one of this provider's schemas. Required for OpenAPI, MCP tool schemas
|
|
15
|
+
* and the admin form generator: `toJsonSchema()` calls it unconditionally and throws
|
|
16
|
+
* `X_SCHEMA_UNSUPPORTED` without it. There is no second way to describe a schema — the IR is
|
|
17
|
+
* the one projection surface, and every generator reads it.
|
|
16
18
|
*/
|
|
17
19
|
introspect?(schema: unknown): SchemaNode | undefined;
|
|
18
20
|
}
|