@apifuse/provider-sdk 2.2.0-beta.26 → 2.2.0-beta.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/bin/apifuse-dev.ts +2 -2
  3. package/bin/apifuse-pack-smoke.ts +1 -1
  4. package/bin/apifuse-pack-types.ts +2 -1
  5. package/bin/apifuse-perf.ts +2 -4
  6. package/bin/apifuse-record.ts +1 -1
  7. package/dist/auth-turn/index.d.ts +1 -1
  8. package/dist/auth-turn/index.js +1 -1
  9. package/dist/ceremonies/index.js +52 -11
  10. package/dist/config/loader.d.ts +1 -1
  11. package/dist/config/loader.js +4 -2
  12. package/dist/contract-serialization.d.ts +1 -20
  13. package/dist/contract-serialization.js +8 -587
  14. package/dist/contract.d.ts +0 -2
  15. package/dist/contract.js +5 -9
  16. package/dist/index.d.ts +9 -8
  17. package/dist/index.js +6 -8
  18. package/dist/provider.d.ts +3 -2
  19. package/dist/provider.js +2 -2
  20. package/dist/runtime/auth-flow.js +1 -1
  21. package/dist/runtime/http.d.ts +1 -0
  22. package/dist/runtime/http.js +135 -12
  23. package/dist/runtime/instrumentation.js +1 -1
  24. package/dist/runtime/native-network-errors.d.ts +33 -0
  25. package/dist/runtime/native-network-errors.js +69 -0
  26. package/dist/runtime/native-network.d.ts +2 -33
  27. package/dist/runtime/native-network.js +2 -68
  28. package/dist/runtime/redis.d.ts +1 -1
  29. package/dist/runtime/redis.js +4 -2
  30. package/dist/runtime/resolver-config.d.ts +6 -0
  31. package/dist/runtime/resolver-config.js +6 -0
  32. package/dist/runtime/resolver-shared.d.ts +3 -0
  33. package/dist/runtime/resolver-shared.js +12 -0
  34. package/dist/runtime/resolver-vendors/twocaptcha.d.ts +2 -1
  35. package/dist/runtime/resolver-vendors/twocaptcha.js +80 -43
  36. package/dist/runtime/resolver-vendors/types.d.ts +1 -1
  37. package/dist/runtime/resolver.d.ts +6 -10
  38. package/dist/runtime/resolver.js +19 -18
  39. package/dist/runtime/state.js +5 -115
  40. package/dist/runtime/stealth-cookies.d.ts +20 -0
  41. package/dist/runtime/stealth-cookies.js +111 -0
  42. package/dist/runtime/stealth.js +7 -130
  43. package/dist/schema.d.ts +0 -63
  44. package/dist/schema.js +8 -808
  45. package/dist/serve.d.ts +1 -1
  46. package/dist/serve.js +1 -1
  47. package/dist/server/index.d.ts +1 -1
  48. package/dist/server/index.js +1 -1
  49. package/dist/server/self-test.d.ts +13 -0
  50. package/dist/server/self-test.js +124 -46
  51. package/dist/server/serve-implementation.d.ts +199 -0
  52. package/dist/server/serve-implementation.js +2054 -0
  53. package/dist/server/serve.d.ts +1 -187
  54. package/dist/server/serve.js +1 -1827
  55. package/dist/stateful/errors.d.ts +5 -0
  56. package/dist/stateful/errors.js +10 -0
  57. package/dist/stateful/stateful-provider-session-routing.d.ts +1 -5
  58. package/dist/stateful/stateful-provider-session-routing.js +2 -10
  59. package/dist/stream.js +7 -1
  60. package/package.json +27 -2
  61. package/src/auth-turn/index.ts +1 -1
  62. package/src/ceremonies/index.ts +68 -18
  63. package/src/config/loader.ts +5 -2
  64. package/src/contract-serialization.ts +8 -859
  65. package/src/contract.ts +5 -16
  66. package/src/index.ts +18 -34
  67. package/src/provider.ts +12 -24
  68. package/src/runtime/auth-flow.ts +1 -1
  69. package/src/runtime/http.ts +155 -11
  70. package/src/runtime/instrumentation.ts +1 -1
  71. package/src/runtime/native-network-errors.ts +99 -0
  72. package/src/runtime/native-network.ts +16 -97
  73. package/src/runtime/redis.ts +7 -2
  74. package/src/runtime/resolver-config.ts +6 -0
  75. package/src/runtime/resolver-shared.ts +17 -0
  76. package/src/runtime/resolver-vendors/twocaptcha.ts +100 -49
  77. package/src/runtime/resolver-vendors/types.ts +1 -0
  78. package/src/runtime/resolver.ts +47 -22
  79. package/src/runtime/state.ts +5 -144
  80. package/src/runtime/stealth-cookies.ts +132 -0
  81. package/src/runtime/stealth.ts +14 -157
  82. package/src/schema.ts +9 -1060
  83. package/src/serve.ts +6 -1
  84. package/src/server/index.ts +1 -0
  85. package/src/server/self-test.ts +184 -59
  86. package/src/server/serve-implementation.ts +3024 -0
  87. package/src/server/serve.ts +1 -2661
  88. package/src/stateful/errors.ts +12 -0
  89. package/src/stateful/stateful-provider-session-routing.ts +2 -11
  90. package/src/stream.ts +8 -1
package/src/schema.ts CHANGED
@@ -83,42 +83,8 @@ export function safeParseSchemaSync(
83
83
  export const APIFUSE_SENSITIVE_META_KEY = "x-apifuse-sensitive";
84
84
  export const APIFUSE_SENSITIVE_KIND_META_KEY = "x-apifuse-sensitive-kind";
85
85
  export const APIFUSE_DESCRIPTION_KEY_META_KEY = "x-apifuse-description-key";
86
- export const APIFUSE_TEXT_TRUST_META_KEY = "x-apifuse-text-trust";
87
86
  export const APIFUSE_REDACTION_MARKER = "<redacted>";
88
87
 
89
- export type TextTrust = "trusted" | "untrusted";
90
-
91
- export interface TextTrustMetadata {
92
- readonly v: 1;
93
- readonly trust: TextTrust;
94
- readonly carrier?: "container";
95
- }
96
-
97
- export type OutputTextTrustMap = Readonly<Record<string, TextTrust>>;
98
-
99
- /**
100
- * Auto-trusted output leaves are string-valued `z.literal()` / `z.enum()`,
101
- * patterns whose every top-level alternative is anchored at both ends and
102
- * whose bodies use only finite quantifiers, have a maximum match length of 32,
103
- * and contain only digits and safe punctuation in positive whitespace-free
104
- * character classes, literals/escapes, and ordinary or non-capturing groups,
105
- * plus the structurally constrained formats listed here. A brand is trusted
106
- * only when its underlying schema meets one of those rules. Letter-bearing
107
- * identifiers, length-only strings, and other formats (including email, URL,
108
- * base64, and JWT) are not trusted.
109
- */
110
- export const AUTO_TRUSTED_ZOD_STRING_FORMATS = [
111
- "cidrv4",
112
- "cidrv6",
113
- "date",
114
- "datetime",
115
- "duration",
116
- "e164",
117
- "ipv4",
118
- "ipv6",
119
- "time",
120
- ] as const;
121
-
122
88
  export type SensitivePathSegment = string | "*";
123
89
  export type SensitivePath = readonly SensitivePathSegment[];
124
90
  export type SensitiveFieldKind =
@@ -161,19 +127,9 @@ export function describeKey<TSchema extends ZodType>(
161
127
  });
162
128
  }
163
129
 
164
- /** Attach output text-trust authoring metadata without changing validation. */
165
- export function textTrust<TSchema extends ZodType>(schema: TSchema, trust: TextTrust): TSchema {
166
- const metadata = schema.meta() ?? {};
167
- return schema.meta({
168
- ...metadata,
169
- [APIFUSE_TEXT_TRUST_META_KEY]: { v: 1, trust } satisfies TextTrustMetadata,
170
- });
171
- }
172
-
173
130
  declare module "zod" {
174
131
  interface ZodType {
175
132
  describeKey(key: ProviderLocaleKey | string): this;
176
- textTrust(trust: TextTrust): this;
177
133
  }
178
134
  }
179
135
 
@@ -184,101 +140,16 @@ const describeKeyMethod = function <TSchema extends ZodType>(
184
140
  return describeKey(this, key);
185
141
  };
186
142
 
187
- const textTrustMethod = function <TSchema extends ZodType>(
188
- this: TSchema,
189
- trust: TextTrust,
190
- ): TSchema {
191
- return textTrust(this, trust);
192
- };
193
-
194
- interface StaticOutputFallback {
195
- readonly value: unknown;
196
- }
197
-
198
- const staticOutputFallbacks = new WeakMap<object, StaticOutputFallback>();
199
- let suppressDynamicOutputFallbacks = 0;
200
-
201
- function trackOutputFallback(schema: unknown, fallback: unknown): void {
202
- if (!(schema instanceof z.ZodType)) return;
203
- const def = schema._zod.def;
204
- if (typeof fallback !== "function") {
205
- staticOutputFallbacks.set(def, { value: fallback });
206
- return;
207
- }
208
- if (def.type === "default") {
209
- const descriptor = Object.getOwnPropertyDescriptor(def, "defaultValue");
210
- if (!descriptor?.get) return;
211
- const originalGetter = descriptor.get;
212
- Object.defineProperty(def, "defaultValue", {
213
- ...descriptor,
214
- get(this: object) {
215
- if (suppressDynamicOutputFallbacks > 0) return null;
216
- return Reflect.apply(originalGetter, this, []);
217
- },
218
- });
143
+ function installDescribeKeyOnPrototype(prototype: unknown): void {
144
+ const target = prototype as (Record<string, unknown> & { describeKey?: unknown }) | null;
145
+ if (!target || typeof target.describeKey === "function") {
219
146
  return;
220
147
  }
221
- if (def.type === "catch") {
222
- const catchValue = Reflect.get(def, "catchValue");
223
- if (typeof catchValue !== "function") return;
224
- Reflect.set(def, "catchValue", function (this: unknown, ...args: unknown[]) {
225
- if (suppressDynamicOutputFallbacks > 0) return null as never;
226
- return Reflect.apply(catchValue, this, args) as never;
227
- });
228
- }
229
- }
230
-
231
- const trackedFallbackPrototypes = new WeakSet<object>();
232
-
233
- function installOutputFallbackTrackingOnPrototype(prototype: unknown): void {
234
- if (!prototype || typeof prototype !== "object" || trackedFallbackPrototypes.has(prototype)) {
235
- return;
236
- }
237
- trackedFallbackPrototypes.add(prototype);
238
- Reflect.apply(z.ZodType.init, z.ZodType, [Object.create(prototype), { type: "custom" }]);
239
- for (const methodName of ["default", "catch"] as const) {
240
- const descriptor = Object.getOwnPropertyDescriptor(prototype, methodName);
241
- if (!descriptor?.configurable || !descriptor.get) continue;
242
- const originalGetter = descriptor.get;
243
- Object.defineProperty(prototype, methodName, {
244
- ...descriptor,
245
- get(this: object) {
246
- const originalMethod = Reflect.apply(originalGetter, this, []);
247
- if (typeof originalMethod !== "function") return originalMethod;
248
- const trackedMethod = function (this: unknown, fallback: unknown) {
249
- const result = Reflect.apply(originalMethod, this, [fallback]);
250
- trackOutputFallback(result, fallback);
251
- return result;
252
- };
253
- Object.defineProperty(this, methodName, {
254
- configurable: true,
255
- enumerable: true,
256
- value: trackedMethod,
257
- writable: true,
258
- });
259
- return trackedMethod;
260
- },
261
- });
262
- }
263
- }
264
-
265
- function installSchemaMetadataMethodsOnPrototype(prototype: unknown): void {
266
- const target = prototype as Record<string, unknown> | null;
267
- if (!target) return;
268
- if (typeof target.describeKey !== "function") {
269
- Object.defineProperty(target, "describeKey", {
270
- configurable: true,
271
- value: describeKeyMethod,
272
- writable: true,
273
- });
274
- }
275
- if (typeof target.textTrust !== "function") {
276
- Object.defineProperty(target, "textTrust", {
277
- configurable: true,
278
- value: textTrustMethod,
279
- writable: true,
280
- });
281
- }
148
+ Object.defineProperty(target, "describeKey", {
149
+ configurable: true,
150
+ value: describeKeyMethod,
151
+ writable: true,
152
+ });
282
153
  }
283
154
 
284
155
  for (const [name, value] of Object.entries(z)) {
@@ -288,8 +159,7 @@ for (const [name, value] of Object.entries(z)) {
288
159
  if (typeof value !== "function") {
289
160
  continue;
290
161
  }
291
- installSchemaMetadataMethodsOnPrototype(value.prototype);
292
- installOutputFallbackTrackingOnPrototype(value.prototype);
162
+ installDescribeKeyOnPrototype(value.prototype);
293
163
  }
294
164
 
295
165
  const RESERVED_SENSITIVE_KEYS = new Set([
@@ -368,927 +238,6 @@ export const fields = {
368
238
  sensitiveString("token", "Provider access or refresh token.", options),
369
239
  } as const;
370
240
 
371
- type TextTrustDeclaration = TextTrust | "absent" | "invalid";
372
-
373
- type InternalZodSchema = z.core.$ZodTypes;
374
- type InternalZodDef = InternalZodSchema["_zod"]["def"];
375
-
376
- interface OutputTextLeaf {
377
- readonly classification: TextTrust;
378
- readonly classified: boolean;
379
- }
380
-
381
- interface OutputTextTrustCollection {
382
- readonly leaves: Array<OutputTextLeaf & { readonly path: string }>;
383
- readonly debtPaths: Set<string>;
384
- }
385
-
386
- export class OutputTextTrustSchemaError extends TypeError {
387
- readonly code = "invalid_output_text_trust_schema";
388
-
389
- constructor(value: unknown) {
390
- const receivedType = value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
391
- super(`Output text-trust collection requires a Zod schema; received ${receivedType}.`);
392
- this.name = "OutputTextTrustSchemaError";
393
- }
394
- }
395
-
396
- export class OutputTextTrustCollectionError extends Error {
397
- readonly classification = "untrusted" as const;
398
- readonly code = "output_text_trust_collection_failed";
399
-
400
- constructor(
401
- public readonly schemaPath: string,
402
- cause: unknown,
403
- ) {
404
- super(`Output text-trust collection failed at schema path ${schemaPath}.`);
405
- this.name = "OutputTextTrustCollectionError";
406
- this.cause = cause;
407
- }
408
- }
409
-
410
- /**
411
- * Collect every textual leaf in a Zod output schema as a deterministic
412
- * schema-path-to-classification object. The path grammar is rooted at `$`:
413
- * object keys are `["key"]`, arrays and record values are `[*]`, tuple slots
414
- * are `[n]`, union/intersection alternatives are `<union:n>` / `<intersection:n>`,
415
- * record keys are `<record-key>`, and one bounded cycle expansion is marked
416
- * `<recursive>`.
417
- */
418
- export function collectOutputTextTrust(schema: ZodType): OutputTextTrustMap {
419
- const { leaves } = collectOutputTextLeaves(requireOutputTextTrustSchema(schema));
420
- return Object.fromEntries(
421
- leaves
422
- .map(({ path, classification }) => [path, classification] as const)
423
- .sort(([left], [right]) => left.localeCompare(right)),
424
- );
425
- }
426
-
427
- /** Report textual output paths that lack a valid explicit or auto-derived classification. */
428
- export function findUnclassifiedOutputTextPaths(schema: ZodType): string[] {
429
- const { debtPaths, leaves } = collectOutputTextLeaves(requireOutputTextTrustSchema(schema));
430
- for (const { classified, path } of leaves) {
431
- if (!classified) debtPaths.add(path);
432
- }
433
- return [...debtPaths].sort((left, right) => left.localeCompare(right));
434
- }
435
-
436
- /** @internal Used by contract JSON Schema projection. */
437
- export function resolveOutputTextTrust(
438
- schema: z.core.$ZodType,
439
- inheritedUntrusted = false,
440
- ): TextTrust | undefined {
441
- const resolved = resolveOutputTextTrustProjection(schema);
442
- return resolved?.[inheritedUntrusted ? "inherited" : "local"];
443
- }
444
-
445
- /** @internal Resolve both projection states from one stable schema traversal. */
446
- export function resolveOutputTextTrustProjection(
447
- schema: z.core.$ZodType,
448
- ): { readonly inherited: TextTrust; readonly local: TextTrust } | undefined {
449
- const resolved = resolveOutputTextLeafPair(asInternalZodSchema(schema), [], true);
450
- return resolved
451
- ? {
452
- inherited: resolved.inherited.classification,
453
- local: resolved.local.classification,
454
- }
455
- : undefined;
456
- }
457
-
458
- /** @internal Whether this node can bypass or mutate descendant validation guarantees. */
459
- export function invalidatesDescendantOutputTextAutoTrust(schema: z.core.$ZodType): boolean {
460
- return invalidatesOutputTextAutoTrust(asInternalZodSchema(schema)._zod.def);
461
- }
462
-
463
- /** @internal Run output projection without evaluating dynamic fallback callbacks. */
464
- export function suppressDynamicOutputFallbacksDuring<T>(project: () => T): T {
465
- suppressDynamicOutputFallbacks += 1;
466
- try {
467
- return project();
468
- } finally {
469
- suppressDynamicOutputFallbacks -= 1;
470
- }
471
- }
472
-
473
- /** @internal Whether this mutator has a static fallback that cannot contain text. */
474
- export function mutatorReturnIsProvablyNonText(schema: z.core.$ZodType): boolean {
475
- const def = asInternalZodSchema(schema)._zod.def;
476
- if (def.type !== "default" && def.type !== "catch") return false;
477
- const fallback = staticOutputFallbacks.get(def);
478
- return fallback !== undefined && outputFallbackValueIsProvablyNonText(fallback.value);
479
- }
480
-
481
- /** @internal Whether this mutator has a static array fallback. */
482
- export function mutatorUsesStaticArrayFallback(schema: z.core.$ZodType): boolean {
483
- const def = asInternalZodSchema(schema)._zod.def;
484
- if (def.type !== "default" && def.type !== "catch") return false;
485
- const fallback = staticOutputFallbacks.get(def);
486
- return fallback !== undefined && Array.isArray(fallback.value);
487
- }
488
-
489
- /** @internal Whether this default or catch was authored with a callback. */
490
- export function hasDynamicOutputFallback(schema: z.core.$ZodType): boolean {
491
- const def = asInternalZodSchema(schema)._zod.def;
492
- return (def.type === "default" || def.type === "catch") && !staticOutputFallbacks.has(def);
493
- }
494
-
495
- /** @internal Used by contract JSON Schema projection. */
496
- export function inheritsUntrustedOutputTextTrust(schema: z.core.$ZodType): boolean {
497
- let current = asInternalZodSchema(schema);
498
- while (true) {
499
- if (readTextTrustDeclaration(current) === "untrusted") return true;
500
- const inner = flattenedOutputSchema(current._zod.def);
501
- if (!inner) return false;
502
- current = inner;
503
- }
504
- }
505
-
506
- function outputFallbackValueIsProvablyNonText(value: unknown): boolean {
507
- return (
508
- value === null ||
509
- typeof value === "boolean" ||
510
- typeof value === "number" ||
511
- (Array.isArray(value) && value.every(outputFallbackValueIsProvablyNonText))
512
- );
513
- }
514
-
515
- function collectOutputTextLeaves(schema: InternalZodSchema): OutputTextTrustCollection {
516
- const leaves: Array<OutputTextLeaf & { readonly path: string }> = [];
517
- const debtPaths = new Set<string>();
518
- const emittedPaths = new Set<string>();
519
- walkOutputSchema(schema, "$", leaves, debtPaths, emittedPaths, new Set(), false, false, true);
520
- return { leaves, debtPaths };
521
- }
522
-
523
- function walkOutputSchema(
524
- schema: InternalZodSchema,
525
- path: string,
526
- out: Array<OutputTextLeaf & { readonly path: string }>,
527
- debtPaths: Set<string>,
528
- emittedPaths: Set<string>,
529
- activeSchemas: Set<InternalZodSchema>,
530
- expandingCycle: boolean,
531
- inheritedUntrusted: boolean,
532
- autoTrustAllowed: boolean,
533
- ): void {
534
- try {
535
- walkOutputSchemaUnchecked(
536
- schema,
537
- path,
538
- out,
539
- debtPaths,
540
- emittedPaths,
541
- activeSchemas,
542
- expandingCycle,
543
- inheritedUntrusted,
544
- autoTrustAllowed,
545
- );
546
- } catch (error) {
547
- if (error instanceof OutputTextTrustCollectionError) throw error;
548
- throw new OutputTextTrustCollectionError(path, error);
549
- }
550
- }
551
-
552
- function walkOutputSchemaUnchecked(
553
- schema: InternalZodSchema,
554
- path: string,
555
- out: Array<OutputTextLeaf & { readonly path: string }>,
556
- debtPaths: Set<string>,
557
- emittedPaths: Set<string>,
558
- activeSchemas: Set<InternalZodSchema>,
559
- expandingCycle: boolean,
560
- inheritedUntrusted: boolean,
561
- autoTrustAllowed: boolean,
562
- ): void {
563
- const alreadyActive = activeSchemas.has(schema);
564
- if (alreadyActive) {
565
- if (!expandingCycle) {
566
- walkOutputSchema(
567
- schema,
568
- `${path}<recursive>`,
569
- out,
570
- debtPaths,
571
- emittedPaths,
572
- new Set(),
573
- true,
574
- inheritedUntrusted,
575
- autoTrustAllowed,
576
- );
577
- }
578
- return;
579
- }
580
-
581
- const leaf = resolveOutputTextLeaf(schema, [], autoTrustAllowed, inheritedUntrusted);
582
- if (leaf) {
583
- if (!emittedPaths.has(path)) {
584
- emittedPaths.add(path);
585
- out.push({ path, ...leaf });
586
- }
587
- return;
588
- }
589
-
590
- const def = schema._zod.def;
591
- const declaration = readTextTrustDeclaration(schema);
592
- if (declaration === "trusted" || declaration === "invalid") debtPaths.add(path);
593
- const descendantUntrusted = inheritedUntrusted || declaration === "untrusted";
594
- const descendantAutoTrustAllowed = autoTrustAllowed && !invalidatesOutputTextAutoTrust(def);
595
- if (
596
- invalidatesOutputTextAutoTrust(def) &&
597
- !mutatorReturnIsProvablyNonText(schema) &&
598
- !mutatorCanUseCollectedTextItems(schema) &&
599
- !emittedPaths.has(path)
600
- ) {
601
- emittedPaths.add(path);
602
- out.push({
603
- classification: "untrusted",
604
- classified: descendantUntrusted,
605
- path,
606
- });
607
- }
608
- if (!alreadyActive) activeSchemas.add(schema);
609
- try {
610
- switch (def.type) {
611
- case "object": {
612
- for (const [key, child] of Object.entries(def.shape).sort(([left], [right]) =>
613
- left.localeCompare(right),
614
- )) {
615
- walkOutputSchema(
616
- asInternalZodSchema(child),
617
- `${path}[${JSON.stringify(key)}]`,
618
- out,
619
- debtPaths,
620
- emittedPaths,
621
- activeSchemas,
622
- expandingCycle,
623
- descendantUntrusted,
624
- descendantAutoTrustAllowed,
625
- );
626
- }
627
- if (def.catchall) {
628
- if (asInternalZodSchema(def.catchall)._zod.def.type !== "never") {
629
- const catchallKeyPath = `${path}<catchall-key>`;
630
- if (!emittedPaths.has(catchallKeyPath)) {
631
- emittedPaths.add(catchallKeyPath);
632
- out.push({
633
- classification: "untrusted",
634
- classified: descendantUntrusted,
635
- path: catchallKeyPath,
636
- });
637
- }
638
- }
639
- walkOutputSchema(
640
- asInternalZodSchema(def.catchall),
641
- `${path}[*]`,
642
- out,
643
- debtPaths,
644
- emittedPaths,
645
- activeSchemas,
646
- expandingCycle,
647
- descendantUntrusted,
648
- descendantAutoTrustAllowed,
649
- );
650
- }
651
- break;
652
- }
653
- case "array":
654
- walkOutputSchema(
655
- asInternalZodSchema(def.element),
656
- `${path}[*]`,
657
- out,
658
- debtPaths,
659
- emittedPaths,
660
- activeSchemas,
661
- expandingCycle,
662
- descendantUntrusted,
663
- descendantAutoTrustAllowed,
664
- );
665
- break;
666
- case "tuple": {
667
- def.items.forEach((child, index) => {
668
- walkOutputSchema(
669
- asInternalZodSchema(child),
670
- `${path}[${index}]`,
671
- out,
672
- debtPaths,
673
- emittedPaths,
674
- activeSchemas,
675
- expandingCycle,
676
- descendantUntrusted,
677
- descendantAutoTrustAllowed,
678
- );
679
- });
680
- if (def.rest) {
681
- walkOutputSchema(
682
- asInternalZodSchema(def.rest),
683
- `${path}[*]`,
684
- out,
685
- debtPaths,
686
- emittedPaths,
687
- activeSchemas,
688
- expandingCycle,
689
- descendantUntrusted,
690
- descendantAutoTrustAllowed,
691
- );
692
- }
693
- break;
694
- }
695
- case "record":
696
- walkOutputSchema(
697
- asInternalZodSchema(def.keyType),
698
- `${path}<record-key>`,
699
- out,
700
- debtPaths,
701
- emittedPaths,
702
- activeSchemas,
703
- expandingCycle,
704
- descendantUntrusted,
705
- descendantAutoTrustAllowed,
706
- );
707
- walkOutputSchema(
708
- asInternalZodSchema(def.valueType),
709
- `${path}[*]`,
710
- out,
711
- debtPaths,
712
- emittedPaths,
713
- activeSchemas,
714
- expandingCycle,
715
- descendantUntrusted,
716
- descendantAutoTrustAllowed,
717
- );
718
- break;
719
- case "union":
720
- def.options.forEach((child, index) => {
721
- walkOutputSchema(
722
- asInternalZodSchema(child),
723
- `${path}<union:${index}>`,
724
- out,
725
- debtPaths,
726
- emittedPaths,
727
- activeSchemas,
728
- expandingCycle,
729
- descendantUntrusted,
730
- descendantAutoTrustAllowed,
731
- );
732
- });
733
- break;
734
- case "intersection":
735
- for (const [index, side] of [def.left, def.right].entries()) {
736
- walkOutputSchema(
737
- asInternalZodSchema(side),
738
- `${path}<intersection:${index}>`,
739
- out,
740
- debtPaths,
741
- emittedPaths,
742
- activeSchemas,
743
- expandingCycle,
744
- descendantUntrusted,
745
- descendantAutoTrustAllowed,
746
- );
747
- }
748
- break;
749
- case "optional":
750
- case "nullable":
751
- case "default":
752
- case "prefault":
753
- case "catch":
754
- case "readonly":
755
- case "nonoptional":
756
- case "promise":
757
- walkOutputSchema(
758
- asInternalZodSchema(def.innerType),
759
- path,
760
- out,
761
- debtPaths,
762
- emittedPaths,
763
- activeSchemas,
764
- expandingCycle,
765
- descendantUntrusted,
766
- descendantAutoTrustAllowed,
767
- );
768
- break;
769
- case "pipe":
770
- walkOutputSchema(
771
- asInternalZodSchema(def.out),
772
- path,
773
- out,
774
- debtPaths,
775
- emittedPaths,
776
- activeSchemas,
777
- expandingCycle,
778
- descendantUntrusted,
779
- descendantAutoTrustAllowed,
780
- );
781
- break;
782
- case "lazy":
783
- walkOutputSchema(
784
- asInternalZodSchema(def.getter()),
785
- path,
786
- out,
787
- debtPaths,
788
- emittedPaths,
789
- activeSchemas,
790
- expandingCycle,
791
- descendantUntrusted,
792
- descendantAutoTrustAllowed,
793
- );
794
- break;
795
- case "string":
796
- case "template_literal":
797
- case "literal":
798
- case "enum":
799
- case "number":
800
- case "bigint":
801
- case "boolean":
802
- case "date":
803
- case "symbol":
804
- case "undefined":
805
- case "null":
806
- case "any":
807
- case "unknown":
808
- case "never":
809
- case "void":
810
- case "map":
811
- case "set":
812
- case "function":
813
- case "custom":
814
- case "transform":
815
- case "nan":
816
- case "success":
817
- case "file":
818
- break;
819
- default:
820
- assertNeverZodDef(def);
821
- }
822
- } finally {
823
- if (!alreadyActive) activeSchemas.delete(schema);
824
- }
825
- }
826
-
827
- function mutatorCanUseCollectedTextItems(schema: InternalZodSchema): boolean {
828
- if (!mutatorUsesStaticArrayFallback(schema)) return false;
829
- const inner = flattenedOutputSchema(schema._zod.def);
830
- if (inner?._zod.def.type !== "array") return false;
831
- return collectOutputTextLeaves(asInternalZodSchema(inner._zod.def.element)).leaves.length > 0;
832
- }
833
-
834
- function resolveOutputTextLeaf(
835
- schema: InternalZodSchema,
836
- outerDeclarations: readonly TextTrustDeclaration[],
837
- autoTrustAllowed: boolean,
838
- inheritedUntrusted: boolean,
839
- ): OutputTextLeaf | undefined {
840
- const resolved = resolveOutputTextLeafPair(schema, outerDeclarations, autoTrustAllowed);
841
- return resolved?.[inheritedUntrusted ? "inherited" : "local"];
842
- }
843
-
844
- function resolveOutputTextLeafPair(
845
- schema: InternalZodSchema,
846
- outerDeclarations: readonly TextTrustDeclaration[],
847
- autoTrustAllowed: boolean,
848
- ): { readonly inherited: OutputTextLeaf; readonly local: OutputTextLeaf } | undefined {
849
- const declarations = [...outerDeclarations, readTextTrustDeclaration(schema)];
850
- const def = schema._zod.def;
851
- if (!isOutputTextLeafDef(def)) {
852
- const inner = flattenedOutputSchema(def);
853
- return inner
854
- ? resolveOutputTextLeafPair(
855
- inner,
856
- declarations,
857
- autoTrustAllowed && !invalidatesOutputTextAutoTrust(def),
858
- )
859
- : undefined;
860
- }
861
-
862
- const autoTrusted =
863
- autoTrustAllowed && !invalidatesOutputTextAutoTrust(def) && isAutoTrustedOutputTextLeaf(schema);
864
- const explicitTrustAllowed = autoTrustAllowed && !invalidatesOutputTextAutoTrust(def);
865
- return {
866
- inherited: classifyOutputTextLeaf(declarations, autoTrusted, explicitTrustAllowed, true),
867
- local: classifyOutputTextLeaf(declarations, autoTrusted, explicitTrustAllowed, false),
868
- };
869
- }
870
-
871
- function classifyOutputTextLeaf(
872
- declarations: readonly TextTrustDeclaration[],
873
- autoTrusted: boolean,
874
- explicitTrustAllowed: boolean,
875
- inheritedUntrusted: boolean,
876
- ): OutputTextLeaf {
877
- if (declarations.includes("invalid")) {
878
- return { classification: "untrusted", classified: false };
879
- }
880
- if (declarations.includes("untrusted")) {
881
- return { classification: "untrusted", classified: true };
882
- }
883
- if (declarations.includes("trusted")) {
884
- return {
885
- classification: explicitTrustAllowed ? "trusted" : "untrusted",
886
- classified: explicitTrustAllowed,
887
- };
888
- }
889
- if (inheritedUntrusted) {
890
- return { classification: "untrusted", classified: true };
891
- }
892
- return autoTrusted
893
- ? { classification: "trusted", classified: true }
894
- : { classification: "untrusted", classified: false };
895
- }
896
-
897
- function flattenedOutputSchema(def: InternalZodDef): InternalZodSchema | undefined {
898
- switch (def.type) {
899
- case "optional":
900
- case "nullable":
901
- case "default":
902
- case "prefault":
903
- case "catch":
904
- case "readonly":
905
- case "nonoptional":
906
- case "promise":
907
- return asInternalZodSchema(def.innerType);
908
- case "pipe":
909
- return asInternalZodSchema(def.out);
910
- case "lazy":
911
- return asInternalZodSchema(def.getter());
912
- case "string":
913
- case "template_literal":
914
- case "literal":
915
- case "enum":
916
- case "number":
917
- case "bigint":
918
- case "boolean":
919
- case "date":
920
- case "symbol":
921
- case "undefined":
922
- case "null":
923
- case "any":
924
- case "unknown":
925
- case "never":
926
- case "void":
927
- case "object":
928
- case "array":
929
- case "tuple":
930
- case "record":
931
- case "union":
932
- case "intersection":
933
- case "map":
934
- case "set":
935
- case "function":
936
- case "custom":
937
- case "transform":
938
- case "nan":
939
- case "success":
940
- case "file":
941
- return undefined;
942
- default:
943
- return assertNeverZodDef(def);
944
- }
945
- }
946
-
947
- function isOutputTextLeafDef(def: InternalZodDef): boolean {
948
- switch (def.type) {
949
- case "string":
950
- case "template_literal":
951
- return true;
952
- case "literal":
953
- return def.values.some((value) => typeof value === "string");
954
- case "enum":
955
- return Object.values(def.entries).some((value) => typeof value === "string");
956
- case "any":
957
- case "unknown":
958
- case "custom":
959
- case "transform":
960
- return true;
961
- default:
962
- return false;
963
- }
964
- }
965
-
966
- function isAutoTrustedOutputTextLeaf(schema: InternalZodSchema): boolean {
967
- const def = schema._zod.def;
968
- if (hasUnsafeOutputCheck(def)) return false;
969
- if (def.type === "literal" || def.type === "enum") return isOutputTextLeafDef(def);
970
- if (def.type !== "string" && def.type !== "template_literal") return false;
971
-
972
- const { bag, pattern } = schema._zod;
973
- if (hasSdkOwnedStringFormatValidator(schema)) {
974
- return true;
975
- }
976
-
977
- if (pattern instanceof RegExp && isRestrictiveAnchoredPattern(pattern)) return true;
978
- const patterns = bag.patterns;
979
- return (
980
- patterns instanceof Set &&
981
- patterns.size > 0 &&
982
- [...patterns].every(
983
- (candidate) => candidate instanceof RegExp && isRestrictiveAnchoredPattern(candidate),
984
- )
985
- );
986
- }
987
-
988
- function hasUnsafeOutputCheck(def: InternalZodDef): boolean {
989
- const checks = Reflect.get(def, "checks");
990
- return (
991
- Array.isArray(checks) &&
992
- checks.some((check) => {
993
- if (!check || typeof check !== "object") return false;
994
- const internals = Reflect.get(check, "_zod");
995
- if (!internals || typeof internals !== "object") return false;
996
- const checkDef = Reflect.get(internals, "def");
997
- if (!checkDef || typeof checkDef !== "object") return false;
998
- const kind = Reflect.get(checkDef, "check");
999
- return kind === "overwrite" || kind === "custom";
1000
- })
1001
- );
1002
- }
1003
-
1004
- function invalidatesOutputTextAutoTrust(def: InternalZodDef): boolean {
1005
- return (
1006
- def.type === "default" ||
1007
- def.type === "catch" ||
1008
- def.type === "transform" ||
1009
- hasUnsafeOutputCheck(def)
1010
- );
1011
- }
1012
-
1013
- interface OwnedStringFormatValidator {
1014
- readonly constructor: unknown;
1015
- readonly patternFlags: string;
1016
- readonly patternSource: string;
1017
- }
1018
-
1019
- const SDK_OWNED_STRING_FORMAT_VALIDATORS: readonly OwnedStringFormatValidator[] = [
1020
- z.cidrv4(),
1021
- z.cidrv6(),
1022
- z.iso.date(),
1023
- z.iso.datetime(),
1024
- z.iso.duration(),
1025
- z.e164(),
1026
- z.ipv4(),
1027
- z.ipv6(),
1028
- z.iso.time(),
1029
- ].map((validator) => {
1030
- const pattern = Reflect.get(validator._zod.def, "pattern");
1031
- if (!(pattern instanceof RegExp)) {
1032
- throw new TypeError("SDK-owned Zod string format validator is missing its pattern.");
1033
- }
1034
- return {
1035
- constructor: validator.constructor,
1036
- patternFlags: pattern.flags,
1037
- patternSource: pattern.source,
1038
- };
1039
- });
1040
-
1041
- function hasSdkOwnedStringFormatValidator(schema: InternalZodSchema): boolean {
1042
- const candidates: unknown[] = [schema];
1043
- const checks = Reflect.get(schema._zod.def, "checks");
1044
- if (Array.isArray(checks)) candidates.push(...checks);
1045
- return candidates.some((candidate) => {
1046
- if (!candidate || typeof candidate !== "object") return false;
1047
- const internals = Reflect.get(candidate, "_zod");
1048
- if (!internals || typeof internals !== "object") return false;
1049
- const def = Reflect.get(internals, "def");
1050
- if (!def || typeof def !== "object" || Reflect.has(def, "fn")) return false;
1051
- const pattern = Reflect.get(def, "pattern");
1052
- if (!(pattern instanceof RegExp)) return false;
1053
- return SDK_OWNED_STRING_FORMAT_VALIDATORS.some(
1054
- (owned) =>
1055
- Reflect.get(candidate, "constructor") === owned.constructor &&
1056
- pattern.source === owned.patternSource &&
1057
- pattern.flags === owned.patternFlags,
1058
- );
1059
- });
1060
- }
1061
-
1062
- function isRestrictiveAnchoredPattern(pattern: RegExp): boolean {
1063
- if (pattern.multiline) return false;
1064
- const alternatives = splitTopLevelRegexAlternatives(pattern.source);
1065
- if (alternatives.length === 0) return false;
1066
- let maximumLength = 0;
1067
- for (const alternative of alternatives) {
1068
- if (!alternative.startsWith("^") || !alternative.endsWith("$")) return false;
1069
- const analysis = analyzeProvablyRestrictedRegexBody(alternative.slice(1, -1));
1070
- if (!analysis) return false;
1071
- maximumLength = Math.max(maximumLength, analysis.maximumLength);
1072
- }
1073
- return maximumLength <= MAXIMUM_AUTO_TRUSTED_REGEX_LENGTH;
1074
- }
1075
-
1076
- function splitTopLevelRegexAlternatives(source: string): string[] {
1077
- const alternatives: string[] = [];
1078
- let start = 0;
1079
- let escaped = false;
1080
- let inCharacterClass = false;
1081
- let groupDepth = 0;
1082
- for (let index = 0; index < source.length; index += 1) {
1083
- const character = source[index];
1084
- if (escaped) {
1085
- escaped = false;
1086
- continue;
1087
- }
1088
- if (character === "\\") {
1089
- escaped = true;
1090
- continue;
1091
- }
1092
- if (character === "[") {
1093
- inCharacterClass = true;
1094
- continue;
1095
- }
1096
- if (character === "]") {
1097
- inCharacterClass = false;
1098
- continue;
1099
- }
1100
- if (inCharacterClass) continue;
1101
- if (character === "(") {
1102
- groupDepth += 1;
1103
- continue;
1104
- }
1105
- if (character === ")") {
1106
- groupDepth -= 1;
1107
- continue;
1108
- }
1109
- if (character === "|" && groupDepth === 0) {
1110
- alternatives.push(source.slice(start, index));
1111
- start = index + 1;
1112
- }
1113
- }
1114
- alternatives.push(source.slice(start));
1115
- return alternatives;
1116
- }
1117
-
1118
- interface RestrictedRegexAnalysis {
1119
- readonly maximumLength: number;
1120
- }
1121
-
1122
- interface SafeRegexCharacterClassAnalysis extends RestrictedRegexAnalysis {
1123
- readonly consumedLength: number;
1124
- }
1125
-
1126
- const MAXIMUM_AUTO_TRUSTED_REGEX_LENGTH = 32;
1127
-
1128
- function analyzeProvablyRestrictedRegexBody(source: string): RestrictedRegexAnalysis | undefined {
1129
- let index = 0;
1130
- const parseAlternatives = (): RestrictedRegexAnalysis | undefined => {
1131
- let maximumLength = 0;
1132
- while (true) {
1133
- const sequence = parseSequence();
1134
- if (!sequence) return undefined;
1135
- maximumLength = Math.max(maximumLength, sequence.maximumLength);
1136
- if (source[index] !== "|") break;
1137
- index += 1;
1138
- }
1139
- return { maximumLength };
1140
- };
1141
- const parseSequence = (): RestrictedRegexAnalysis | undefined => {
1142
- let maximumLength = 0;
1143
- while (index < source.length && source[index] !== "|" && source[index] !== ")") {
1144
- const atom = parseAtom();
1145
- if (!atom) return undefined;
1146
- let maximumRepetitions = 1;
1147
- if (source[index] === "?") {
1148
- index += 1;
1149
- } else if (source[index] === "{") {
1150
- const match = /^\{(\d+)(?:,(\d+))?\}/.exec(source.slice(index));
1151
- if (!match) return undefined;
1152
- const minimum = Number(match[1]);
1153
- maximumRepetitions = match[2] === undefined ? minimum : Number(match[2]);
1154
- if (
1155
- !Number.isSafeInteger(minimum) ||
1156
- !Number.isSafeInteger(maximumRepetitions) ||
1157
- maximumRepetitions < minimum
1158
- )
1159
- return undefined;
1160
- index += match[0].length;
1161
- }
1162
- if (source[index] === "?") index += 1;
1163
- maximumLength = addRestrictedRegexLengths(
1164
- maximumLength,
1165
- multiplyRestrictedRegexLength(atom.maximumLength, maximumRepetitions),
1166
- );
1167
- }
1168
- return { maximumLength };
1169
- };
1170
- const parseAtom = (): RestrictedRegexAnalysis | undefined => {
1171
- if (source[index] === "(") {
1172
- index += 1;
1173
- if (source[index] === "?") {
1174
- if (source.slice(index, index + 2) !== "?:") return undefined;
1175
- index += 2;
1176
- }
1177
- const group = parseAlternatives();
1178
- if (!group || source[index] !== ")") return undefined;
1179
- index += 1;
1180
- return group;
1181
- }
1182
- if (source[index] === "[") {
1183
- const characterClass = analyzeSafeRegexCharacterClass(source.slice(index));
1184
- if (!characterClass) return undefined;
1185
- index += characterClass.consumedLength;
1186
- return characterClass;
1187
- }
1188
- const atomLength = safeRegexAtomLength(source.slice(index));
1189
- if (atomLength === 0) return undefined;
1190
- index += atomLength;
1191
- return { maximumLength: 1 };
1192
- };
1193
-
1194
- const analysis = parseAlternatives();
1195
- return analysis && index === source.length ? analysis : undefined;
1196
- }
1197
-
1198
- function analyzeSafeRegexCharacterClass(
1199
- source: string,
1200
- ): SafeRegexCharacterClassAnalysis | undefined {
1201
- if (!source.startsWith("[") || source[1] === "^") return undefined;
1202
- for (let index = 1; index < source.length; index += 1) {
1203
- const character = source[index];
1204
- if (character === "]") {
1205
- return { consumedLength: index + 1, maximumLength: 1 };
1206
- }
1207
-
1208
- const atomLength = safeRegexAtomLength(source.slice(index));
1209
- if (atomLength === 0) return undefined;
1210
- const rangeSeparator = index + atomLength;
1211
- if (source[rangeSeparator] === "-" && source[rangeSeparator + 1] !== "]") {
1212
- const endIndex = rangeSeparator + 1;
1213
- const endLength = safeRegexAtomLength(source.slice(endIndex));
1214
- if (endLength === 0 || !isSafeRegexCharacterRange(source, index, endIndex)) return undefined;
1215
- index = endIndex + endLength - 1;
1216
- continue;
1217
- }
1218
- index += atomLength - 1;
1219
- }
1220
- return undefined;
1221
- }
1222
-
1223
- function addRestrictedRegexLengths(left: number, right: number): number {
1224
- return left > MAXIMUM_AUTO_TRUSTED_REGEX_LENGTH - right
1225
- ? MAXIMUM_AUTO_TRUSTED_REGEX_LENGTH + 1
1226
- : left + right;
1227
- }
1228
-
1229
- function multiplyRestrictedRegexLength(length: number, repetitions: number): number {
1230
- if (length === 0 || repetitions === 0) return 0;
1231
- return repetitions > Math.floor(MAXIMUM_AUTO_TRUSTED_REGEX_LENGTH / length)
1232
- ? MAXIMUM_AUTO_TRUSTED_REGEX_LENGTH + 1
1233
- : length * repetitions;
1234
- }
1235
-
1236
- function safeRegexAtomLength(source: string): number {
1237
- const character = source[0];
1238
- if (character === undefined) return 0;
1239
- if (/^[0-9]$/.test(character)) return 1;
1240
- if (character === "-") return 1;
1241
- if (character !== "\\") return 0;
1242
- const escaped = source[1];
1243
- if (escaped === "d") return 2;
1244
- return escaped !== undefined && SAFE_REGEX_ESCAPED_PUNCTUATION.includes(escaped) ? 2 : 0;
1245
- }
1246
-
1247
- function isSafeRegexCharacterRange(source: string, startIndex: number, endIndex: number): boolean {
1248
- const start = regexCharacterClassLiteralCodePoint(source, startIndex);
1249
- const end = regexCharacterClassLiteralCodePoint(source, endIndex);
1250
- if (start === undefined || end === undefined || start > end) return false;
1251
- const minimum = "0".codePointAt(0);
1252
- const maximum = "9".codePointAt(0);
1253
- return minimum !== undefined && maximum !== undefined && start >= minimum && end <= maximum;
1254
- }
1255
-
1256
- function regexCharacterClassLiteralCodePoint(source: string, index: number): number | undefined {
1257
- const character = source[index];
1258
- if (character !== "\\") return character?.codePointAt(0);
1259
- const escaped = source[index + 1];
1260
- if (escaped === undefined || escaped === "d" || escaped === "w") return undefined;
1261
- return escaped.codePointAt(0);
1262
- }
1263
-
1264
- const SAFE_REGEX_ESCAPED_PUNCTUATION = "\\.-^$*+?()[]{}|";
1265
-
1266
- function requireOutputTextTrustSchema(schema: unknown): InternalZodSchema {
1267
- if (!(schema instanceof z.ZodType)) throw new OutputTextTrustSchemaError(schema);
1268
- return asInternalZodSchema(schema);
1269
- }
1270
-
1271
- function asInternalZodSchema(schema: z.core.$ZodType): InternalZodSchema {
1272
- // Zod's child-definition fields use the base type and erase the concrete
1273
- // union member. This is the only cast boundary; all callers immediately
1274
- // narrow the returned discriminated definition by `def.type`.
1275
- return schema as InternalZodSchema;
1276
- }
1277
-
1278
- function assertNeverZodDef(def: never): never {
1279
- throw new Error(`Unsupported Zod definition: ${String((def as { type?: unknown }).type)}`);
1280
- }
1281
-
1282
- function readTextTrustDeclaration(schema: unknown): TextTrustDeclaration {
1283
- const metadata = readZodMetadata(schema);
1284
- if (!metadata || !Reflect.has(metadata, APIFUSE_TEXT_TRUST_META_KEY)) return "absent";
1285
- const value = Reflect.get(metadata, APIFUSE_TEXT_TRUST_META_KEY);
1286
- if (!value || typeof value !== "object") return "invalid";
1287
- if (Reflect.get(value, "v") !== 1) return "invalid";
1288
- const trust = Reflect.get(value, "trust");
1289
- return trust === "trusted" || trust === "untrusted" ? trust : "invalid";
1290
- }
1291
-
1292
241
  export function isSensitiveSchema(schema: unknown): boolean {
1293
242
  const metadata = readZodMetadata(schema);
1294
243
  return metadata !== undefined && Reflect.get(metadata, APIFUSE_SENSITIVE_META_KEY) === true;