@apifuse/provider-sdk 2.2.0-beta.26 → 2.2.0-beta.27
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 +4 -0
- package/dist/contract-serialization.d.ts +1 -20
- package/dist/contract-serialization.js +8 -587
- package/dist/contract.d.ts +0 -2
- package/dist/contract.js +5 -9
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/provider.d.ts +1 -1
- package/dist/provider.js +1 -1
- package/dist/schema.d.ts +0 -63
- package/dist/schema.js +8 -808
- package/package.json +1 -1
- package/src/contract-serialization.ts +8 -859
- package/src/contract.ts +5 -16
- package/src/index.ts +0 -11
- package/src/provider.ts +0 -10
- package/src/schema.ts +9 -1060
package/CHANGELOG.md
CHANGED
|
@@ -1,23 +1,4 @@
|
|
|
1
1
|
import { type JsonValue } from "./contract-json.js";
|
|
2
2
|
import type { SchemaLike } from "./types.js";
|
|
3
|
-
|
|
4
|
-
readonly eventName?: string;
|
|
5
|
-
readonly operationId?: string;
|
|
6
|
-
readonly outputTextTrust?: boolean;
|
|
7
|
-
}
|
|
8
|
-
export declare class OutputTextTrustProjectionError extends Error {
|
|
9
|
-
readonly schemaPath: string;
|
|
10
|
-
readonly operationId?: string | undefined;
|
|
11
|
-
readonly eventName?: string | undefined;
|
|
12
|
-
readonly classification: "untrusted";
|
|
13
|
-
readonly code = "output_text_trust_projection_failed";
|
|
14
|
-
constructor(schemaPath: string, cause: unknown, operationId?: string | undefined, eventName?: string | undefined);
|
|
15
|
-
}
|
|
16
|
-
export declare class OutputTextTrustProjectionMarkerError extends TypeError {
|
|
17
|
-
readonly schemaPath: string;
|
|
18
|
-
readonly code = "invalid_output_text_trust_projection_marker";
|
|
19
|
-
constructor(schemaPath: string);
|
|
20
|
-
}
|
|
21
|
-
export declare function describeSchema(schema: SchemaLike, options?: DescribeSchemaOptions): JsonValue;
|
|
3
|
+
export declare function describeSchema(schema: SchemaLike): JsonValue;
|
|
22
4
|
export declare function serializeSmsMatcher(value: Record<string, unknown>): Record<string, unknown>;
|
|
23
|
-
export {};
|
|
@@ -1,43 +1,9 @@
|
|
|
1
|
-
import { createHash
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { canonicalJson, compactObject, isRecord, toJsonValue, } from "./contract-json.js";
|
|
4
|
-
|
|
5
|
-
const OUTPUT_TEXT_TRUST_PROJECTION_MARKER_KEY = "x-apifuse-internal-text-trust-projection";
|
|
6
|
-
export class OutputTextTrustProjectionError extends Error {
|
|
7
|
-
schemaPath;
|
|
8
|
-
operationId;
|
|
9
|
-
eventName;
|
|
10
|
-
classification = "untrusted";
|
|
11
|
-
code = "output_text_trust_projection_failed";
|
|
12
|
-
constructor(schemaPath, cause, operationId, eventName) {
|
|
13
|
-
const context = [
|
|
14
|
-
operationId === undefined ? undefined : `operation "${operationId}"`,
|
|
15
|
-
eventName === undefined ? undefined : `event "${eventName}"`,
|
|
16
|
-
]
|
|
17
|
-
.filter((value) => value !== undefined)
|
|
18
|
-
.join(", ");
|
|
19
|
-
super(context.length === 0
|
|
20
|
-
? `Output text-trust projection failed at schema path ${schemaPath}.`
|
|
21
|
-
: `Output text-trust projection failed for ${context} at schema path ${schemaPath}.`);
|
|
22
|
-
this.schemaPath = schemaPath;
|
|
23
|
-
this.operationId = operationId;
|
|
24
|
-
this.eventName = eventName;
|
|
25
|
-
this.name = "OutputTextTrustProjectionError";
|
|
26
|
-
this.cause = cause;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
export class OutputTextTrustProjectionMarkerError extends TypeError {
|
|
30
|
-
schemaPath;
|
|
31
|
-
code = "invalid_output_text_trust_projection_marker";
|
|
32
|
-
constructor(schemaPath) {
|
|
33
|
-
super(`Invalid internal output text-trust projection marker at schema path ${schemaPath}.`);
|
|
34
|
-
this.schemaPath = schemaPath;
|
|
35
|
-
this.name = "OutputTextTrustProjectionMarkerError";
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
export function describeSchema(schema, options = {}) {
|
|
4
|
+
export function describeSchema(schema) {
|
|
39
5
|
if (isZodSchema(schema)) {
|
|
40
|
-
const jsonSchema = zodJsonSchema(schema
|
|
6
|
+
const jsonSchema = zodJsonSchema(schema);
|
|
41
7
|
return compactObject({
|
|
42
8
|
kind: "schema",
|
|
43
9
|
vendor: "zod",
|
|
@@ -48,9 +14,6 @@ export function describeSchema(schema, options = {}) {
|
|
|
48
14
|
}
|
|
49
15
|
const standard = isRecord(schema) ? schema["~standard"] : undefined;
|
|
50
16
|
if (isRecord(standard)) {
|
|
51
|
-
if (options.outputTextTrust) {
|
|
52
|
-
throw new OutputTextTrustProjectionError("$", new TypeError("Standard Schema output cannot be projected to JSON Schema with text-trust classifications."), options.operationId, options.eventName);
|
|
53
|
-
}
|
|
54
17
|
return compactObject({
|
|
55
18
|
kind: "schema",
|
|
56
19
|
standard: "standard-schema-v1",
|
|
@@ -90,554 +53,12 @@ function digest(value) {
|
|
|
90
53
|
function isZodSchema(schema) {
|
|
91
54
|
return schema instanceof z.ZodType;
|
|
92
55
|
}
|
|
93
|
-
function zodJsonSchema(schema
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const expectedHonestTextContainerMarkers = new Map();
|
|
98
|
-
try {
|
|
99
|
-
const jsonSchemaOptions = {
|
|
100
|
-
unrepresentable: options.outputTextTrust ? "any" : "throw",
|
|
101
|
-
override: ({ zodSchema, jsonSchema: projectedSchema, path }) => {
|
|
102
|
-
if (!options.outputTextTrust) {
|
|
103
|
-
delete projectedSchema[OUTPUT_TEXT_TRUST_PROJECTION_MARKER_KEY];
|
|
104
|
-
delete projectedSchema[APIFUSE_TEXT_TRUST_META_KEY];
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
try {
|
|
108
|
-
const unrepresentable = unrepresentableZodOutputCause(zodSchema);
|
|
109
|
-
if (unrepresentable)
|
|
110
|
-
throw unrepresentable;
|
|
111
|
-
const resolved = resolveOutputTextTrustProjection(zodSchema);
|
|
112
|
-
delete projectedSchema[APIFUSE_TEXT_TRUST_META_KEY];
|
|
113
|
-
if (hasDynamicOutputFallback(zodSchema))
|
|
114
|
-
delete projectedSchema.default;
|
|
115
|
-
if (resolved !== undefined) {
|
|
116
|
-
const markerId = writeOutputTextTrustProjectionMarker(projectedSchema, projectionMarkers, {
|
|
117
|
-
inherited: resolved.inherited,
|
|
118
|
-
kind: "leaf",
|
|
119
|
-
local: resolved.local,
|
|
120
|
-
});
|
|
121
|
-
expectedLeafMarkers.set(projectedJsonSchemaPath(path), markerId);
|
|
122
|
-
}
|
|
123
|
-
else if (invalidatesDescendantOutputTextAutoTrust(zodSchema)) {
|
|
124
|
-
if (projectedJsonSchemaIsProvablyNonText(projectedSchema) &&
|
|
125
|
-
mutatorReturnIsProvablyNonText(zodSchema)) {
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
if (mutatorCanUseProjectedTextItems(zodSchema, projectedSchema)) {
|
|
129
|
-
writeOutputTextTrustProjectionMarker(projectedSchema, projectionMarkers, {
|
|
130
|
-
kind: "container-untrusted",
|
|
131
|
-
});
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
admitPossibleRuntimeText(projectedSchema, projectionMarkers, expectedLeafMarkers, expectedUnsafeContainerMarkers, expectedHonestTextContainerMarkers, projectedJsonSchemaPath(path));
|
|
135
|
-
const containerMarkerId = writeOutputTextTrustProjectionMarker(projectedSchema, projectionMarkers, { kind: "container-unsafe" });
|
|
136
|
-
expectedUnsafeContainerMarkers.set(projectedJsonSchemaPath(path), containerMarkerId);
|
|
137
|
-
expectedHonestTextContainerMarkers.set(projectedJsonSchemaPath(path), containerMarkerId);
|
|
138
|
-
}
|
|
139
|
-
else if (inheritsUntrustedOutputTextTrust(zodSchema)) {
|
|
140
|
-
writeOutputTextTrustProjectionMarker(projectedSchema, projectionMarkers, {
|
|
141
|
-
kind: "container-untrusted",
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
if (hasOpenObjectCatchall(zodSchema)) {
|
|
145
|
-
const containerMarkerId = writeOutputTextTrustProjectionMarker(projectedSchema, projectionMarkers, { kind: "container-unsafe" });
|
|
146
|
-
expectedUnsafeContainerMarkers.set(projectedJsonSchemaPath(path), containerMarkerId);
|
|
147
|
-
}
|
|
148
|
-
if (zodSchema._zod.def.type === "record" &&
|
|
149
|
-
projectedSchema.additionalProperties === false) {
|
|
150
|
-
const projectedPath = projectedJsonSchemaPath(path);
|
|
151
|
-
expectedLeafMarkers.delete(appendJsonSchemaPath(projectedPath, "additionalProperties"));
|
|
152
|
-
const containerMarkerId = writeOutputTextTrustProjectionMarker(projectedSchema, projectionMarkers, { kind: "container-unsafe" });
|
|
153
|
-
expectedUnsafeContainerMarkers.set(projectedPath, containerMarkerId);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
catch (error) {
|
|
157
|
-
throw new OutputTextTrustProjectionError(jsonSchemaPath(path), error, options.operationId, options.eventName);
|
|
158
|
-
}
|
|
159
|
-
},
|
|
160
|
-
};
|
|
161
|
-
const projectJsonSchema = () => z.toJSONSchema(schema, jsonSchemaOptions);
|
|
162
|
-
const jsonSchema = options.outputTextTrust
|
|
163
|
-
? suppressDynamicOutputFallbacksDuring(projectJsonSchema)
|
|
164
|
-
: projectJsonSchema();
|
|
165
|
-
if (options.outputTextTrust) {
|
|
166
|
-
finalizeProjectedTextTrust(jsonSchema, projectionMarkers, expectedLeafMarkers, expectedUnsafeContainerMarkers, expectedHonestTextContainerMarkers, options);
|
|
167
|
-
}
|
|
168
|
-
const projected = toJsonValue(jsonSchema);
|
|
169
|
-
if (projected === undefined) {
|
|
170
|
-
throw new TypeError("z.toJSONSchema() returned a non-JSON value");
|
|
171
|
-
}
|
|
172
|
-
return projected;
|
|
173
|
-
}
|
|
174
|
-
catch (error) {
|
|
175
|
-
if (error instanceof OutputTextTrustProjectionError)
|
|
176
|
-
throw error;
|
|
177
|
-
if (!options.outputTextTrust)
|
|
178
|
-
throw error;
|
|
179
|
-
throw new OutputTextTrustProjectionError("$", error, options.operationId, options.eventName);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
function writeOutputTextTrustProjectionMarker(schema, markers, marker) {
|
|
183
|
-
const existingId = schema[OUTPUT_TEXT_TRUST_PROJECTION_MARKER_KEY];
|
|
184
|
-
if (typeof existingId === "string") {
|
|
185
|
-
const existingMarker = markers.get(existingId);
|
|
186
|
-
if (existingMarker) {
|
|
187
|
-
markers.set(existingId, mergeOutputTextTrustProjectionMarkers(existingMarker, marker));
|
|
188
|
-
return existingId;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
const id = randomUUID();
|
|
192
|
-
markers.set(id, marker);
|
|
193
|
-
schema[OUTPUT_TEXT_TRUST_PROJECTION_MARKER_KEY] = id;
|
|
194
|
-
return id;
|
|
195
|
-
}
|
|
196
|
-
function mergeOutputTextTrustProjectionMarkers(existing, next) {
|
|
197
|
-
if (existing.kind === "container-unsafe" && next.kind === "container-untrusted") {
|
|
198
|
-
return existing;
|
|
199
|
-
}
|
|
200
|
-
return next;
|
|
201
|
-
}
|
|
202
|
-
function hasOpenObjectCatchall(schema) {
|
|
203
|
-
const def = schema._zod.def;
|
|
204
|
-
if (def.type !== "object")
|
|
205
|
-
return false;
|
|
206
|
-
const catchall = Reflect.get(def, "catchall");
|
|
207
|
-
if (!catchall || typeof catchall !== "object")
|
|
208
|
-
return false;
|
|
209
|
-
const internals = Reflect.get(catchall, "_zod");
|
|
210
|
-
if (!internals || typeof internals !== "object")
|
|
211
|
-
return false;
|
|
212
|
-
const catchallDef = Reflect.get(internals, "def");
|
|
213
|
-
return (!catchallDef || typeof catchallDef !== "object" || Reflect.get(catchallDef, "type") !== "never");
|
|
214
|
-
}
|
|
215
|
-
function unrepresentableZodOutputCause(schema) {
|
|
216
|
-
const def = schema._zod.def;
|
|
217
|
-
switch (def.type) {
|
|
218
|
-
case "bigint":
|
|
219
|
-
return new Error("BigInt cannot be represented in JSON Schema");
|
|
220
|
-
case "symbol":
|
|
221
|
-
return new Error("Symbols cannot be represented in JSON Schema");
|
|
222
|
-
case "undefined":
|
|
223
|
-
return new Error("Undefined cannot be represented in JSON Schema");
|
|
224
|
-
case "void":
|
|
225
|
-
return new Error("Void cannot be represented in JSON Schema");
|
|
226
|
-
case "date":
|
|
227
|
-
return new Error("Date cannot be represented in JSON Schema");
|
|
228
|
-
case "nan":
|
|
229
|
-
return new Error("NaN cannot be represented in JSON Schema");
|
|
230
|
-
case "custom":
|
|
231
|
-
return new Error("Custom types cannot be represented in JSON Schema");
|
|
232
|
-
case "function":
|
|
233
|
-
return new Error("Function types cannot be represented in JSON Schema");
|
|
234
|
-
case "transform":
|
|
235
|
-
return new Error("Transforms cannot be represented in JSON Schema");
|
|
236
|
-
case "map":
|
|
237
|
-
return new Error("Map cannot be represented in JSON Schema");
|
|
238
|
-
case "set":
|
|
239
|
-
return new Error("Set cannot be represented in JSON Schema");
|
|
240
|
-
case "literal": {
|
|
241
|
-
const values = Reflect.get(def, "values");
|
|
242
|
-
if (!Array.isArray(values))
|
|
243
|
-
return undefined;
|
|
244
|
-
if (values.some((value) => value === undefined)) {
|
|
245
|
-
return new Error("Literal `undefined` cannot be represented in JSON Schema");
|
|
246
|
-
}
|
|
247
|
-
if (values.some((value) => typeof value === "bigint")) {
|
|
248
|
-
return new Error("BigInt literals cannot be represented in JSON Schema");
|
|
249
|
-
}
|
|
250
|
-
return undefined;
|
|
251
|
-
}
|
|
252
|
-
default:
|
|
253
|
-
return undefined;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
const SINGLE_SCHEMA_KEYWORDS = [
|
|
257
|
-
"additionalProperties",
|
|
258
|
-
"contains",
|
|
259
|
-
"contentSchema",
|
|
260
|
-
"else",
|
|
261
|
-
"if",
|
|
262
|
-
"items",
|
|
263
|
-
"not",
|
|
264
|
-
"propertyNames",
|
|
265
|
-
"then",
|
|
266
|
-
"unevaluatedItems",
|
|
267
|
-
"unevaluatedProperties",
|
|
268
|
-
];
|
|
269
|
-
const ARRAY_SCHEMA_KEYWORDS = ["allOf", "anyOf", "oneOf", "prefixItems"];
|
|
270
|
-
const MAP_SCHEMA_KEYWORDS = [
|
|
271
|
-
"$defs",
|
|
272
|
-
"definitions",
|
|
273
|
-
"dependentSchemas",
|
|
274
|
-
"patternProperties",
|
|
275
|
-
"properties",
|
|
276
|
-
];
|
|
277
|
-
function finalizeProjectedTextTrust(value, projectionMarkers, expectedLeafMarkers, expectedUnsafeContainerMarkers, expectedHonestTextContainerMarkers, options) {
|
|
278
|
-
if (!isJsonSchemaNode(value)) {
|
|
279
|
-
throw new OutputTextTrustProjectionError("#", new TypeError("Projected output schema is not a JSON Schema object."), options.operationId, options.eventName);
|
|
280
|
-
}
|
|
281
|
-
const root = value;
|
|
282
|
-
const nodes = collectProjectedJsonSchemaNodes(root);
|
|
283
|
-
const markers = new WeakMap();
|
|
284
|
-
for (const { path, schema } of nodes) {
|
|
285
|
-
const marker = readOutputTextTrustProjectionMarker(schema[OUTPUT_TEXT_TRUST_PROJECTION_MARKER_KEY], projectionMarkers, path, options);
|
|
286
|
-
if (marker)
|
|
287
|
-
markers.set(schema, marker);
|
|
288
|
-
}
|
|
289
|
-
for (const [expectedPath, expectedMarkerId] of expectedLeafMarkers) {
|
|
290
|
-
const authenticatedLeaf = resolveExpectedProjectedSchema(root, expectedPath, expectedMarkerId, markers, options);
|
|
291
|
-
if (authenticatedLeaf === undefined || markers.get(authenticatedLeaf)?.kind !== "leaf") {
|
|
292
|
-
throw new OutputTextTrustProjectionError(expectedPath, new TypeError("A projected output text leaf lost its authenticated trust marker."), options.operationId, options.eventName);
|
|
293
|
-
}
|
|
294
|
-
if (!projectedJsonSchemaCanAllowString(authenticatedLeaf)) {
|
|
295
|
-
throw new OutputTextTrustProjectionError(expectedPath, new TypeError("A projected output text leaf was mutated to a non-text type."), options.operationId, options.eventName);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
for (const [expectedPath, expectedMarkerId] of expectedUnsafeContainerMarkers) {
|
|
299
|
-
const authenticatedContainer = resolveExpectedProjectedSchema(root, expectedPath, expectedMarkerId, markers, options);
|
|
300
|
-
if (authenticatedContainer === undefined ||
|
|
301
|
-
markers.get(authenticatedContainer)?.kind !== "container-unsafe") {
|
|
302
|
-
throw new OutputTextTrustProjectionError(expectedPath, new TypeError("A type-mutating output container lost its authenticated trust marker."), options.operationId, options.eventName);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
for (const [expectedPath, expectedMarkerId] of expectedHonestTextContainerMarkers) {
|
|
306
|
-
const authenticatedContainer = resolveExpectedProjectedSchema(root, expectedPath, expectedMarkerId, markers, options);
|
|
307
|
-
const branches = authenticatedContainer?.anyOf;
|
|
308
|
-
if (!Array.isArray(branches) ||
|
|
309
|
-
branches.length !== 2 ||
|
|
310
|
-
!branches.every((branch) => isJsonSchemaNode(branch) && markers.get(branch)?.kind === "container-unsafe") ||
|
|
311
|
-
!isJsonSchemaNode(branches[1]) ||
|
|
312
|
-
!projectedJsonSchemaAllowsString(branches[1])) {
|
|
313
|
-
throw new OutputTextTrustProjectionError(expectedPath, new TypeError("A type-mutating output container lost its authenticated runtime text shape."), options.operationId, options.eventName);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
for (const { schema } of nodes) {
|
|
317
|
-
if (!markers.has(schema) && projectedJsonSchemaAllowsString(schema)) {
|
|
318
|
-
markers.set(schema, { inherited: "untrusted", kind: "leaf", local: "untrusted" });
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
const desiredTrust = new WeakMap();
|
|
322
|
-
const desiredContainerCarriers = new WeakSet();
|
|
323
|
-
const visitedStates = new WeakMap();
|
|
324
|
-
const project = (schema, inheritedUntrusted, autoTrustAllowed, runtimeMutationUntrusted) => {
|
|
325
|
-
const state = 1 <<
|
|
326
|
-
((inheritedUntrusted ? 1 : 0) +
|
|
327
|
-
(autoTrustAllowed ? 0 : 2) +
|
|
328
|
-
(runtimeMutationUntrusted ? 4 : 0));
|
|
329
|
-
const visited = visitedStates.get(schema) ?? 0;
|
|
330
|
-
if ((visited & state) !== 0)
|
|
331
|
-
return;
|
|
332
|
-
visitedStates.set(schema, visited | state);
|
|
333
|
-
const marker = markers.get(schema);
|
|
334
|
-
if (marker?.kind === "leaf") {
|
|
335
|
-
const trust = autoTrustAllowed
|
|
336
|
-
? inheritedUntrusted
|
|
337
|
-
? marker.inherited
|
|
338
|
-
: marker.local
|
|
339
|
-
: "untrusted";
|
|
340
|
-
if (desiredTrust.get(schema) !== "untrusted")
|
|
341
|
-
desiredTrust.set(schema, trust);
|
|
342
|
-
return;
|
|
343
|
-
}
|
|
344
|
-
if (runtimeMutationUntrusted)
|
|
345
|
-
desiredTrust.set(schema, "untrusted");
|
|
346
|
-
if (marker?.kind === "container-unsafe") {
|
|
347
|
-
desiredTrust.set(schema, "untrusted");
|
|
348
|
-
desiredContainerCarriers.add(schema);
|
|
349
|
-
}
|
|
350
|
-
const descendantUntrusted = inheritedUntrusted || marker?.kind === "container-untrusted";
|
|
351
|
-
const descendantAutoTrustAllowed = autoTrustAllowed && marker?.kind !== "container-unsafe";
|
|
352
|
-
const descendantRuntimeMutationUntrusted = runtimeMutationUntrusted || marker?.kind === "container-unsafe";
|
|
353
|
-
const reference = schema.$ref;
|
|
354
|
-
if (reference !== undefined) {
|
|
355
|
-
if (typeof reference !== "string") {
|
|
356
|
-
throw new OutputTextTrustProjectionError(requireNodePath(nodes, schema, options), new TypeError("Projected JSON Schema $ref must be a string."), options.operationId, options.eventName);
|
|
357
|
-
}
|
|
358
|
-
project(resolveLocalJsonSchemaReference(root, reference, requireNodePath(nodes, schema, options), options), descendantUntrusted, descendantAutoTrustAllowed, descendantRuntimeMutationUntrusted);
|
|
359
|
-
}
|
|
360
|
-
for (const child of projectedJsonSchemaChildren(schema, false)) {
|
|
361
|
-
project(child, descendantUntrusted, descendantAutoTrustAllowed, descendantRuntimeMutationUntrusted);
|
|
362
|
-
}
|
|
363
|
-
};
|
|
364
|
-
project(root, false, true, false);
|
|
365
|
-
for (const { schema } of nodes) {
|
|
366
|
-
delete schema[OUTPUT_TEXT_TRUST_PROJECTION_MARKER_KEY];
|
|
367
|
-
delete schema[APIFUSE_TEXT_TRUST_META_KEY];
|
|
368
|
-
}
|
|
369
|
-
for (const { schema } of nodes) {
|
|
370
|
-
const trust = desiredTrust.get(schema);
|
|
371
|
-
if (trust !== undefined) {
|
|
372
|
-
schema[APIFUSE_TEXT_TRUST_META_KEY] = desiredContainerCarriers.has(schema)
|
|
373
|
-
? { v: 1, trust, carrier: "container" }
|
|
374
|
-
: { v: 1, trust };
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
function projectedJsonSchemaAllowsString(schema) {
|
|
379
|
-
return schema.type === "string" || (Array.isArray(schema.type) && schema.type.includes("string"));
|
|
380
|
-
}
|
|
381
|
-
function admitPossibleRuntimeText(schema, projectionMarkers, expectedLeafMarkers, expectedUnsafeContainerMarkers, expectedHonestTextContainerMarkers, projectedPath) {
|
|
382
|
-
const existingMarkerId = schema[OUTPUT_TEXT_TRUST_PROJECTION_MARKER_KEY];
|
|
383
|
-
if (typeof existingMarkerId === "string" &&
|
|
384
|
-
projectionMarkers.get(existingMarkerId)?.kind === "container-unsafe") {
|
|
385
|
-
return;
|
|
386
|
-
}
|
|
387
|
-
const originalShape = { ...schema };
|
|
388
|
-
for (const key of Object.keys(schema))
|
|
389
|
-
delete schema[key];
|
|
390
|
-
const textShape = { type: "string" };
|
|
391
|
-
schema.anyOf = [originalShape, textShape];
|
|
392
|
-
const originalPath = appendJsonSchemaPath(appendJsonSchemaPath(projectedPath, "anyOf"), 0);
|
|
393
|
-
rebaseExpectedProjectionPaths(expectedLeafMarkers, projectedPath, originalPath);
|
|
394
|
-
expectedUnsafeContainerMarkers.delete(projectedPath);
|
|
395
|
-
rebaseExpectedProjectionPaths(expectedUnsafeContainerMarkers, projectedPath, originalPath);
|
|
396
|
-
expectedHonestTextContainerMarkers.delete(projectedPath);
|
|
397
|
-
rebaseExpectedProjectionPaths(expectedHonestTextContainerMarkers, projectedPath, originalPath);
|
|
398
|
-
writeOutputTextTrustProjectionMarker(originalShape, projectionMarkers, {
|
|
399
|
-
kind: "container-unsafe",
|
|
400
|
-
});
|
|
401
|
-
writeOutputTextTrustProjectionMarker(textShape, projectionMarkers, {
|
|
402
|
-
kind: "container-unsafe",
|
|
403
|
-
});
|
|
404
|
-
}
|
|
405
|
-
function rebaseExpectedProjectionPaths(expectedMarkers, fromPath, toPath) {
|
|
406
|
-
for (const [path, markerId] of [...expectedMarkers]) {
|
|
407
|
-
if (path !== fromPath && !path.startsWith(`${fromPath}/`))
|
|
408
|
-
continue;
|
|
409
|
-
expectedMarkers.delete(path);
|
|
410
|
-
expectedMarkers.set(`${toPath}${path.slice(fromPath.length)}`, markerId);
|
|
56
|
+
function zodJsonSchema(schema) {
|
|
57
|
+
const jsonSchema = toJsonValue(z.toJSONSchema(schema));
|
|
58
|
+
if (jsonSchema === undefined) {
|
|
59
|
+
throw new TypeError("z.toJSONSchema() returned a non-JSON value");
|
|
411
60
|
}
|
|
412
|
-
|
|
413
|
-
function projectedJsonSchemaCanAllowString(schema) {
|
|
414
|
-
return schema.type === undefined || projectedJsonSchemaAllowsString(schema);
|
|
415
|
-
}
|
|
416
|
-
const PROVABLY_NON_TEXT_JSON_SCHEMA_TYPES = new Set(["boolean", "integer", "null", "number"]);
|
|
417
|
-
function mutatorCanUseProjectedTextItems(zodSchema, projectedSchema) {
|
|
418
|
-
return (projectedJsonSchemaAllowsArray(projectedSchema) &&
|
|
419
|
-
mutatorUsesStaticArrayFallback(zodSchema) &&
|
|
420
|
-
isJsonSchemaNode(projectedSchema.items) &&
|
|
421
|
-
projectedJsonSchemaCanAllowString(projectedSchema.items));
|
|
422
|
-
}
|
|
423
|
-
function projectedJsonSchemaIsProvablyNonText(schema, visited = new WeakSet()) {
|
|
424
|
-
if (visited.has(schema))
|
|
425
|
-
return false;
|
|
426
|
-
visited.add(schema);
|
|
427
|
-
try {
|
|
428
|
-
const types = typeof schema.type === "string" ? [schema.type] : schema.type;
|
|
429
|
-
if (Array.isArray(types) && types.length > 0) {
|
|
430
|
-
return types.every((type) => typeof type === "string" &&
|
|
431
|
-
(PROVABLY_NON_TEXT_JSON_SCHEMA_TYPES.has(type) ||
|
|
432
|
-
(type === "array" &&
|
|
433
|
-
isJsonSchemaNode(schema.items) &&
|
|
434
|
-
projectedJsonSchemaIsProvablyNonText(schema.items, visited))));
|
|
435
|
-
}
|
|
436
|
-
for (const keyword of ["anyOf", "oneOf"]) {
|
|
437
|
-
const branches = schema[keyword];
|
|
438
|
-
if (!Array.isArray(branches) || branches.length === 0)
|
|
439
|
-
continue;
|
|
440
|
-
return branches.every((branch) => isJsonSchemaNode(branch) && projectedJsonSchemaIsProvablyNonText(branch, visited));
|
|
441
|
-
}
|
|
442
|
-
return false;
|
|
443
|
-
}
|
|
444
|
-
finally {
|
|
445
|
-
visited.delete(schema);
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
function projectedJsonSchemaAllowsArray(schema) {
|
|
449
|
-
return schema.type === "array" || (Array.isArray(schema.type) && schema.type.includes("array"));
|
|
450
|
-
}
|
|
451
|
-
function readOutputTextTrustProjectionMarker(value, projectionMarkers, schemaPath, options) {
|
|
452
|
-
if (value === undefined)
|
|
453
|
-
return undefined;
|
|
454
|
-
if (typeof value !== "string")
|
|
455
|
-
throw invalidProjectionMarker(schemaPath, options);
|
|
456
|
-
const marker = projectionMarkers.get(value);
|
|
457
|
-
if (!marker)
|
|
458
|
-
throw invalidProjectionMarker(schemaPath, options);
|
|
459
|
-
return marker;
|
|
460
|
-
}
|
|
461
|
-
function invalidProjectionMarker(schemaPath, options) {
|
|
462
|
-
return new OutputTextTrustProjectionError(schemaPath, new OutputTextTrustProjectionMarkerError(schemaPath), options.operationId, options.eventName);
|
|
463
|
-
}
|
|
464
|
-
function resolveExpectedProjectedSchema(root, expectedPath, expectedMarkerId, markers, options) {
|
|
465
|
-
let target = root;
|
|
466
|
-
let finalContainer;
|
|
467
|
-
let finalSegment;
|
|
468
|
-
let finalArrayKeyword;
|
|
469
|
-
if (expectedPath !== "#") {
|
|
470
|
-
if (!expectedPath.startsWith("#/")) {
|
|
471
|
-
throw new OutputTextTrustProjectionError(expectedPath, new TypeError("Expected projection path is not a local JSON Pointer."), options.operationId, options.eventName);
|
|
472
|
-
}
|
|
473
|
-
const encodedSegments = expectedPath.slice(2).split("/");
|
|
474
|
-
const decodedSegments = encodedSegments.map((segment) => segment.replaceAll("~1", "/").replaceAll("~0", "~"));
|
|
475
|
-
finalArrayKeyword = decodedSegments.at(-2);
|
|
476
|
-
for (const segment of decodedSegments) {
|
|
477
|
-
const visitedReferences = new WeakSet();
|
|
478
|
-
while (isJsonSchemaNode(target) && !Object.hasOwn(target, segment)) {
|
|
479
|
-
if (visitedReferences.has(target) || typeof target.$ref !== "string")
|
|
480
|
-
return undefined;
|
|
481
|
-
visitedReferences.add(target);
|
|
482
|
-
target = resolveLocalJsonSchemaReference(root, target.$ref, expectedPath, options);
|
|
483
|
-
}
|
|
484
|
-
if (!target || typeof target !== "object" || !Object.hasOwn(target, segment))
|
|
485
|
-
return undefined;
|
|
486
|
-
finalContainer = target;
|
|
487
|
-
finalSegment = segment;
|
|
488
|
-
target = Reflect.get(target, segment);
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
if (!isJsonSchemaNode(target))
|
|
492
|
-
return undefined;
|
|
493
|
-
const visited = new WeakSet();
|
|
494
|
-
const findMarker = (candidate) => {
|
|
495
|
-
if (visited.has(candidate))
|
|
496
|
-
return undefined;
|
|
497
|
-
visited.add(candidate);
|
|
498
|
-
const markerId = candidate[OUTPUT_TEXT_TRUST_PROJECTION_MARKER_KEY];
|
|
499
|
-
if (markerId === expectedMarkerId && markers.has(candidate))
|
|
500
|
-
return candidate;
|
|
501
|
-
if (typeof candidate.$ref === "string") {
|
|
502
|
-
const referenced = findMarker(resolveLocalJsonSchemaReference(root, candidate.$ref, expectedPath, options));
|
|
503
|
-
if (referenced)
|
|
504
|
-
return referenced;
|
|
505
|
-
}
|
|
506
|
-
return undefined;
|
|
507
|
-
};
|
|
508
|
-
const exact = findMarker(target);
|
|
509
|
-
if (exact)
|
|
510
|
-
return exact;
|
|
511
|
-
if (finalSegment !== undefined &&
|
|
512
|
-
/^[0-9]+$/.test(finalSegment) &&
|
|
513
|
-
finalArrayKeyword !== undefined &&
|
|
514
|
-
Array.isArray(finalContainer)) {
|
|
515
|
-
const index = Number(finalSegment);
|
|
516
|
-
const findNestedProjectedIndex = (candidate) => {
|
|
517
|
-
const nestedBranches = candidate[finalArrayKeyword];
|
|
518
|
-
if (Array.isArray(nestedBranches) && isJsonSchemaNode(nestedBranches[index])) {
|
|
519
|
-
const nested = findMarker(nestedBranches[index]);
|
|
520
|
-
if (nested)
|
|
521
|
-
return nested;
|
|
522
|
-
}
|
|
523
|
-
for (const keyword of ARRAY_SCHEMA_KEYWORDS) {
|
|
524
|
-
const wrappers = candidate[keyword];
|
|
525
|
-
if (!Array.isArray(wrappers))
|
|
526
|
-
continue;
|
|
527
|
-
for (const wrapper of wrappers) {
|
|
528
|
-
if (!isJsonSchemaNode(wrapper))
|
|
529
|
-
continue;
|
|
530
|
-
const nested = findNestedProjectedIndex(wrapper);
|
|
531
|
-
if (nested)
|
|
532
|
-
return nested;
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
return undefined;
|
|
536
|
-
};
|
|
537
|
-
for (const wrapper of finalContainer) {
|
|
538
|
-
if (!isJsonSchemaNode(wrapper))
|
|
539
|
-
continue;
|
|
540
|
-
const nested = findNestedProjectedIndex(wrapper);
|
|
541
|
-
if (nested)
|
|
542
|
-
return nested;
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
return undefined;
|
|
546
|
-
}
|
|
547
|
-
function collectProjectedJsonSchemaNodes(root) {
|
|
548
|
-
const nodes = [];
|
|
549
|
-
const visited = new WeakSet();
|
|
550
|
-
const collect = (schema, path) => {
|
|
551
|
-
if (visited.has(schema))
|
|
552
|
-
return;
|
|
553
|
-
visited.add(schema);
|
|
554
|
-
nodes.push({ path, schema });
|
|
555
|
-
for (const [childPath, child] of projectedJsonSchemaChildEntries(schema, path, true)) {
|
|
556
|
-
collect(child, childPath);
|
|
557
|
-
}
|
|
558
|
-
};
|
|
559
|
-
collect(root, "#");
|
|
560
|
-
return nodes;
|
|
561
|
-
}
|
|
562
|
-
function projectedJsonSchemaChildren(schema, includeDefinitions) {
|
|
563
|
-
return [...projectedJsonSchemaChildEntries(schema, "#", includeDefinitions)].map(([, child]) => child);
|
|
564
|
-
}
|
|
565
|
-
function* projectedJsonSchemaChildEntries(schema, path, includeDefinitions) {
|
|
566
|
-
for (const keyword of SINGLE_SCHEMA_KEYWORDS) {
|
|
567
|
-
const child = schema[keyword];
|
|
568
|
-
if (isJsonSchemaNode(child))
|
|
569
|
-
yield [appendJsonSchemaPath(path, keyword), child];
|
|
570
|
-
if (keyword === "items" && Array.isArray(child)) {
|
|
571
|
-
for (const [index, item] of child.entries()) {
|
|
572
|
-
if (isJsonSchemaNode(item)) {
|
|
573
|
-
yield [appendJsonSchemaPath(appendJsonSchemaPath(path, keyword), index), item];
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
for (const keyword of ARRAY_SCHEMA_KEYWORDS) {
|
|
579
|
-
const children = schema[keyword];
|
|
580
|
-
if (!Array.isArray(children))
|
|
581
|
-
continue;
|
|
582
|
-
for (const [index, child] of children.entries()) {
|
|
583
|
-
if (isJsonSchemaNode(child)) {
|
|
584
|
-
yield [appendJsonSchemaPath(appendJsonSchemaPath(path, keyword), index), child];
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
for (const keyword of MAP_SCHEMA_KEYWORDS) {
|
|
589
|
-
if (!includeDefinitions && (keyword === "$defs" || keyword === "definitions"))
|
|
590
|
-
continue;
|
|
591
|
-
const children = schema[keyword];
|
|
592
|
-
if (!isJsonSchemaNode(children))
|
|
593
|
-
continue;
|
|
594
|
-
for (const [key, child] of Object.entries(children)) {
|
|
595
|
-
if (isJsonSchemaNode(child)) {
|
|
596
|
-
yield [appendJsonSchemaPath(appendJsonSchemaPath(path, keyword), key), child];
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
function resolveLocalJsonSchemaReference(root, reference, schemaPath, options) {
|
|
602
|
-
let target = root;
|
|
603
|
-
if (reference !== "#") {
|
|
604
|
-
if (!reference.startsWith("#/")) {
|
|
605
|
-
throw new OutputTextTrustProjectionError(schemaPath, new TypeError(`Unsupported non-local JSON Schema reference: ${reference}`), options.operationId, options.eventName);
|
|
606
|
-
}
|
|
607
|
-
for (const encodedSegment of reference.slice(2).split("/")) {
|
|
608
|
-
const segment = encodedSegment.replaceAll("~1", "/").replaceAll("~0", "~");
|
|
609
|
-
if (!target || typeof target !== "object" || !Object.hasOwn(target, segment)) {
|
|
610
|
-
throw new OutputTextTrustProjectionError(schemaPath, new TypeError(`Unresolvable local JSON Schema reference: ${reference}`), options.operationId, options.eventName);
|
|
611
|
-
}
|
|
612
|
-
target = Reflect.get(target, segment);
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
if (!isJsonSchemaNode(target)) {
|
|
616
|
-
throw new OutputTextTrustProjectionError(schemaPath, new TypeError(`JSON Schema reference does not resolve to a schema object: ${reference}`), options.operationId, options.eventName);
|
|
617
|
-
}
|
|
618
|
-
return target;
|
|
619
|
-
}
|
|
620
|
-
function isJsonSchemaNode(value) {
|
|
621
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
622
|
-
}
|
|
623
|
-
function requireNodePath(nodes, target, options) {
|
|
624
|
-
const path = nodes.find(({ schema }) => schema === target)?.path;
|
|
625
|
-
if (path !== undefined)
|
|
626
|
-
return path;
|
|
627
|
-
throw new OutputTextTrustProjectionError("#", new TypeError("Projected JSON Schema node is missing from the collected node index."), options.operationId, options.eventName);
|
|
628
|
-
}
|
|
629
|
-
function appendJsonSchemaPath(path, segment) {
|
|
630
|
-
return `${path}/${String(segment).replaceAll("~", "~0").replaceAll("/", "~1")}`;
|
|
631
|
-
}
|
|
632
|
-
function jsonSchemaPath(path) {
|
|
633
|
-
if (path.length === 0)
|
|
634
|
-
return "$";
|
|
635
|
-
return `#/${path
|
|
636
|
-
.map((segment) => String(segment).replaceAll("~", "~0").replaceAll("/", "~1"))
|
|
637
|
-
.join("/")}`;
|
|
638
|
-
}
|
|
639
|
-
function projectedJsonSchemaPath(path) {
|
|
640
|
-
return path.length === 0 ? "#" : jsonSchemaPath(path);
|
|
61
|
+
return jsonSchema;
|
|
641
62
|
}
|
|
642
63
|
function getSchemaTypeName(schema) {
|
|
643
64
|
if (!isRecord(schema))
|
package/dist/contract.d.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { canonicalJson, type JsonPrimitive, type JsonValue } from "./contract-json.js";
|
|
2
|
-
import { OutputTextTrustProjectionError } from "./contract-serialization.js";
|
|
3
2
|
import { PROVIDER_CONTRACT_SCHEMA_VERSION, type ProviderContractOperation, type ProviderContractSnapshot } from "./contract-types.js";
|
|
4
3
|
import type { ProviderDefinition } from "./types.js";
|
|
5
4
|
export { canonicalJson, type JsonPrimitive, type JsonValue, PROVIDER_CONTRACT_SCHEMA_VERSION, type ProviderContractOperation, type ProviderContractSnapshot, };
|
|
6
|
-
export { OutputTextTrustProjectionError };
|
|
7
5
|
export declare function extractProviderContract(provider: ProviderDefinition): ProviderContractSnapshot;
|
|
8
6
|
export declare function digestProviderContract(snapshot: ProviderContractSnapshot): string;
|