typespec-http-zod 0.27.0 → 0.29.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/dist/src/zod.js CHANGED
@@ -1,6 +1,7 @@
1
- import { getDiscriminatedUnion, getDiscriminator, getEncode, resolveEncodedName, isNeverType, isTemplateDeclaration, } from "@typespec/compiler";
2
- import { createMetadataInfo, isQueryParam, Visibility } from "@typespec/http";
1
+ import { getDiscriminatedUnion, getDiscriminator, getDoc, getEncode, resolveEncodedName, isNeverType, isTemplateDeclaration, } from "@typespec/compiler";
2
+ import { createMetadataInfo, getCookieParamOptions, getHeaderFieldName, getPathParamName, getQueryParamName, isCookieParam, isHeader, isPathParam, isQueryParam, Visibility, } from "@typespec/http";
3
3
  import { applyConstraints } from "./constraints.js";
4
+ import { literal } from "./literal.js";
4
5
  import { reportDiagnostic } from "./lib.js";
5
6
  /**
6
7
  * What a walk returns after reporting a refusal.
@@ -406,10 +407,57 @@ const SCALARS = {
406
407
  url: "z.url()",
407
408
  };
408
409
  function scalarToZod(program, scalar) {
410
+ return described(scalarSchemaOf(program, scalar), scalarDocOf(program, scalar));
411
+ }
412
+ /**
413
+ * **A doc becomes `.describe()`, which is the one place Zod keeps a description.**
414
+ *
415
+ * `@typespec/openapi3` writes every `getDoc` as a JSON Schema `description`, and nothing carried it
416
+ * into the validators: a surface publishing its input schema FROM them - an MCP server's
417
+ * `tools/list` - gave a model calling a tool bare patterns and enums with no word on what a field is
418
+ * for. Measured on one consumer: a tool refused 369 calls and accepted 98, most refusals coming from
419
+ * the client checking that published schema before it called.
420
+ *
421
+ * `.describe()` records the text in `z.globalRegistry`, which is a `globalThis` singleton shared by
422
+ * every copy of Zod in a process, and both `z.toJSONSchema` and Zod's Standard Schema converter read
423
+ * it by default. It returns a clone and changes nothing a parse decides.
424
+ *
425
+ * **It is always the OUTERMOST call at a position, and never on a bare reference.** Inside
426
+ * `.nullable()` the text lands in `anyOf[0]` rather than on the property; on an identifier inside
427
+ * `z.array()` or a union it would clone a declaration that other code finds by identity. So it is
428
+ * applied where a declaration's body, a property's value or a scalar's schema is finished, and
429
+ * nowhere else.
430
+ *
431
+ * Gated on `undefined` rather than on truthiness, as openapi3's `applyConstraint` is: an empty
432
+ * `@doc("")` is published as `""`, and so is it here.
433
+ */
434
+ export function described(expression, doc) {
435
+ return doc === undefined ? expression : `${expression}.describe(${literal(doc)})`;
436
+ }
437
+ /**
438
+ * The description a declared scalar publishes: its own doc, else the nearest one up its `extends`
439
+ * chain, and never a standard type's.
440
+ *
441
+ * Both halves are openapi3's, from `#getSchemaForScalar`: a derived scalar starts from its base's
442
+ * schema, description included, and overwrites what it declares itself - so `scalar B extends A`
443
+ * with no doc publishes A's. A standard scalar's description is deleted, because "standard types are
444
+ * going to be inlined in the spec and we don't want the description of the scalar to show up".
445
+ * Without that, `string`'s own "A sequence of textual characters." would sit on every `z.string()`.
446
+ */
447
+ export function scalarDocOf(program, scalar) {
448
+ for (let current = scalar; current !== undefined && !program.checker.isStdType(current); current = current.baseScalar) {
449
+ const doc = getDoc(program, current);
450
+ if (doc !== undefined)
451
+ return doc;
452
+ }
453
+ return undefined;
454
+ }
455
+ /** A scalar's schema without its description, for a position that publishes its own. */
456
+ function scalarSchemaOf(program, scalar) {
409
457
  // `scalar unixTimestamp32 is utcDateTime;` with `@encode(int32)` on the SCALAR, not on each use.
410
458
  const encoded = encodedTypeOf(program, scalar);
411
459
  if (encoded !== undefined && encoded !== scalar) {
412
- return applyConstraints(program, scalarToZod(program, encoded), scalar);
460
+ return applyConstraints(program, scalarSchemaOf(program, encoded), scalar);
413
461
  }
414
462
  let current = scalar;
415
463
  // Walk the `extends` chain so a named scalar (`scalar chNumber extends string`) resolves to the
@@ -454,7 +502,7 @@ function scalarToZod(program, scalar) {
454
502
  function enumToZod(_program, target) {
455
503
  const values = [...target.members.values()].map((member) => member.value ?? member.name);
456
504
  if (values.some((value) => typeof value !== "string")) {
457
- return `z.union([${values.map((value) => `z.literal(${JSON.stringify(value)})`).join(", ")}])`;
505
+ return `z.union([${values.map((value) => `z.literal(${literal(value)})`).join(", ")}])`;
458
506
  }
459
507
  /**
460
508
  * Every string vocabulary resolves through the generated constant rather than being restated.
@@ -475,7 +523,7 @@ function enumToZod(_program, target) {
475
523
  */
476
524
  const shared = contractsPackage();
477
525
  if (shared === undefined) {
478
- return `z.enum([${values.map((value) => JSON.stringify(value)).join(", ")}])`;
526
+ return `z.enum([${values.map((value) => literal(value)).join(", ")}])`;
479
527
  }
480
528
  noteExternalImport(shared, "SPEC_VOCABULARIES");
481
529
  return `z.enum(SPEC_VOCABULARIES.${target.name})`;
@@ -534,7 +582,7 @@ function declaresDiscriminator(program, variant, property) {
534
582
  */
535
583
  function discriminatedVariant(program, union, variantName, variant, options) {
536
584
  const inner = typeToZod(program, variant);
537
- const tag = `${objectKey(options.discriminatorPropertyName)}: z.literal(${JSON.stringify(variantName)})`;
585
+ const tag = `${objectKey(options.discriminatorPropertyName)}: z.literal(${literal(variantName)})`;
538
586
  // The variant already carries the discriminator, so the document publishes it and there is
539
587
  // nothing to add.
540
588
  if (options.envelope === "none")
@@ -587,7 +635,7 @@ function unionToZod(program, union) {
587
635
  }
588
636
  const members = [...discriminated.variants.entries()].map(([name, variant]) => discriminatedVariant(program, union, name, variant, options));
589
637
  const property = options.discriminatorPropertyName;
590
- return `z.discriminatedUnion(${JSON.stringify(property)}, [${members.join(", ")}])`;
638
+ return `z.discriminatedUnion(${literal(property)}, [${members.join(", ")}])`;
591
639
  }
592
640
  const variants = [...union.variants.values()].map((variant) => variant.type);
593
641
  const nullable = variants.some((type) => type.kind === "Intrinsic" && type.name === "null");
@@ -602,7 +650,7 @@ function unionToZod(program, union) {
602
650
  }
603
651
  const suffix = nullable ? ".nullable()" : "";
604
652
  if (real.every((type) => type.kind === "String")) {
605
- const literals = real.map((type) => JSON.stringify(type.value));
653
+ const literals = real.map((type) => literal(type.value));
606
654
  return `z.enum([${literals.join(", ")}])${suffix}`;
607
655
  }
608
656
  if (real.length === 1)
@@ -698,9 +746,10 @@ function modelToZod(program, model) {
698
746
  */
699
747
  const subtypes = discriminatedSubtypes(program, model);
700
748
  if (subtypes.length > 0) {
701
- const discriminator = getDiscriminator(program, model);
749
+ // `discriminatedSubtypes` is empty unless the model has a discriminator.
750
+ const discriminator = getDiscriminator(program, model)?.propertyName ?? "";
702
751
  const options = subtypes.map((derived) => typeToZod(program, derived));
703
- return `z.discriminatedUnion(${JSON.stringify(discriminator?.propertyName)}, [${options.join(", ")}])`;
752
+ return `z.discriminatedUnion(${literal(discriminator)}, [${options.join(", ")}])`;
704
753
  }
705
754
  const indexer = effectiveIndexer(model);
706
755
  const indexerKey = indexer?.key.name;
@@ -945,7 +994,16 @@ export function wireKindOf(program, type) {
945
994
  * Exported so there is one answer to this question. There were two sites and only one of them knew.
946
995
  */
947
996
  export function objectKey(name) {
948
- return /^[A-Za-z_$][\w$]*$/.test(name) ? name : JSON.stringify(name);
997
+ /**
998
+ * **`__proto__` is the one name a key cannot spell plainly.** In an object literal, `__proto__:`
999
+ * - quoted or not - sets the object's prototype rather than declaring a key, so the emitted shape
1000
+ * had no such property: it vanished from the published JSON Schema, and under
1001
+ * `seal-object-schemas` every body carrying it was refused as an unrecognised key. A computed
1002
+ * key declares it, in a value and in a type alike. See `unenforceable-property-name`.
1003
+ */
1004
+ if (name === "__proto__")
1005
+ return `[${literal(name)}]`;
1006
+ return /^[A-Za-z_$][\w$]*$/.test(name) ? name : literal(name);
949
1007
  }
950
1008
  /**
951
1009
  * A doc string as a JSDoc comment, indented, or `""` when there is none.
@@ -1063,9 +1121,56 @@ const PARAMETER_STYLE_ENCODINGS = new Set([
1063
1121
  "ArrayEncoding.commaDelimited",
1064
1122
  "ArrayEncoding.newlineDelimited",
1065
1123
  ]);
1066
- function encodedTypeOf(program, target) {
1124
+ /**
1125
+ * **A date-time header with no `@encode` is an RFC 7231 HTTP-date, as if it said
1126
+ * `@encode(DateTimeKnownEncoding.rfc7231)`.**
1127
+ *
1128
+ * That is TypeSpec's wire form for one, and `@typespec/openapi3` publishes it as
1129
+ * `format: http-date` from `applyEncoding`'s `isDateTimeHeader` branch, which this emitter did not
1130
+ * follow. The header fell through to the scalar's default, `z.iso.datetime({ offset: true })`, so a
1131
+ * request sending it as the document describes was refused with 400, and a handler setting it as an
1132
+ * HTTP-date failed its own response contract with 500 (issue #4). The explicit encoding already
1133
+ * emitted `z.string()`, and this is that.
1134
+ *
1135
+ * **The predicate is openapi3's: the header's own schema carries `format: date-time`.** That is
1136
+ * the standard `utcDateTime` or `offsetDateTime` itself. A declared scalar extending one is
1137
+ * published as a `$ref`, a list carries the format on its items, and `T | null` is an `anyOf`
1138
+ * under 3.1 and 3.2 - the versions this package grades against - so none of those is an HTTP-date
1139
+ * in the document, and none is here. (3.0 publishes the nullable one as `nullable: true` beside an
1140
+ * `http-date` format; the graded documents do not.)
1141
+ */
1142
+ function isDateTimeHeader(program, property) {
1143
+ const type = property.type;
1144
+ return isHeader(program, property) && type.kind === "Scalar" && isStdDateTime(program, type);
1145
+ }
1146
+ function isStdDateTime(program, scalar) {
1147
+ const { checker } = program;
1148
+ return (scalar === checker.getStdType("utcDateTime") || scalar === checker.getStdType("offsetDateTime"));
1149
+ }
1150
+ /** Whether a property's value, or a scalar, is a date-time: one of the two, or built on one. */
1151
+ function isDateTime(program, target) {
1152
+ const type = target.kind === "ModelProperty" ? (nullableInnerOf(target.type) ?? target.type) : target;
1153
+ for (let current = type.kind === "Scalar" ? type : undefined; current !== undefined; current = current.baseScalar) {
1154
+ if (isStdDateTime(program, current))
1155
+ return true;
1156
+ }
1157
+ return false;
1158
+ }
1159
+ export function encodedTypeOf(program, target) {
1067
1160
  const encoded = getEncode(program, target);
1068
- if (encoded === undefined)
1161
+ if (encoded === undefined) {
1162
+ return target.kind === "ModelProperty" && isDateTimeHeader(program, target)
1163
+ ? program.checker.getStdType("string")
1164
+ : undefined;
1165
+ }
1166
+ /**
1167
+ * **`rfc3339` on a date-time is the form it already has, so it changes nothing.** openapi3's
1168
+ * `mergeFormatAndEncoding` keeps `format: date-time` for it, and the type it reports, `string`,
1169
+ * emitted `z.string()`: the explicit spelling of the default dropped the date-time check the
1170
+ * document still states. Only the encodings that change the wire form - `rfc7231`,
1171
+ * `unixTimestamp` - change the type.
1172
+ */
1173
+ if (encoded.encoding === "rfc3339" && isDateTime(program, target))
1069
1174
  return undefined;
1070
1175
  if (target.kind === "ModelProperty" &&
1071
1176
  isQueryParam(program, target) &&
@@ -1091,9 +1196,26 @@ export function propertyToZod(program, property) {
1091
1196
  // wrapper is kept: `@encode` changes what the value IS, not whether it may be absent.
1092
1197
  const encoded = encodedTypeOf(program, property);
1093
1198
  const declared = encoded ?? nullable ?? property.type;
1094
- const base = nullable === undefined || encoded !== undefined
1095
- ? applyConstraints(program, typeToZod(program, declared), property)
1096
- : `${applyConstraints(program, typeToZod(program, declared), property)}.nullable()`;
1199
+ const bare = nullable === undefined || encoded !== undefined;
1200
+ /**
1201
+ * **One description per position, and a property's own doc wins over its scalar's.**
1202
+ *
1203
+ * openapi3 publishes `subject: Subject` as `{ $ref: Subject, description: <the property's doc> }`,
1204
+ * and a reader takes the sibling over what the reference carries. A scalar here is inlined rather
1205
+ * than referenced, so where the property's value IS the scalar, its schema is taken without its
1206
+ * description and the one the position publishes is decided once: the property's doc, else the
1207
+ * scalar's. Otherwise a documented property of a documented scalar was written
1208
+ * `.describe(a).describe(b)`, which says the same thing in a way no one would write.
1209
+ *
1210
+ * Under `.nullable()` the scalar keeps its own, inside the wrapper: openapi3 publishes
1211
+ * `anyOf: [{ $ref: Subject }, { type: "null" }]` there, so the scalar's description belongs to the
1212
+ * branch and only the property's belongs to the property.
1213
+ */
1214
+ const inlinedScalar = bare && declared.kind === "Scalar";
1215
+ const value = inlinedScalar ? scalarSchemaOf(program, declared) : typeToZod(program, declared);
1216
+ const constrained = applyConstraints(program, value, property);
1217
+ const base = bare ? constrained : `${constrained}.nullable()`;
1218
+ const description = getDoc(program, property) ?? (inlinedScalar ? scalarDocOf(program, declared) : undefined);
1097
1219
  /**
1098
1220
  * **Optionality is a function of the position too, not only of the `?` in the spec.**
1099
1221
  *
@@ -1104,6 +1226,7 @@ export function propertyToZod(program, property) {
1104
1226
  * predicate `@typespec/openapi3` uses to decide the `required` list.
1105
1227
  */
1106
1228
  const optional = property.optional || isOptionalAt(program, property);
1229
+ reportInheritedName(program, property, optional);
1107
1230
  /**
1108
1231
  * **A default only applies where the document says the property may be absent.**
1109
1232
  *
@@ -1125,7 +1248,7 @@ export function propertyToZod(program, property) {
1125
1248
  target: property,
1126
1249
  format: { name: property.name },
1127
1250
  });
1128
- return base;
1251
+ return described(base, description);
1129
1252
  }
1130
1253
  /**
1131
1254
  * A TypeSpec default (`basis?: string = "s1159"`) becomes `.default(...)`, which in Zod also makes
@@ -1136,7 +1259,7 @@ export function propertyToZod(program, property) {
1136
1259
  */
1137
1260
  const defaulted = defaultOf(program, property);
1138
1261
  if (defaulted !== undefined)
1139
- return `${base}.default(${defaulted})`;
1262
+ return described(`${base}.default(${defaulted})`, description);
1140
1263
  /**
1141
1264
  * **`.exactOptional()`, because the document says the KEY may be absent and nothing more.**
1142
1265
  *
@@ -1155,7 +1278,66 @@ export function propertyToZod(program, property) {
1155
1278
  * differential's serialisation axis is untouched, and `def.type` is still `"optional"`, so the
1156
1279
  * shape describers read it unchanged.
1157
1280
  */
1158
- return optional ? `${base}.exactOptional()` : base;
1281
+ return described(optional ? `${base}.exactOptional()` : base, description);
1282
+ }
1283
+ /**
1284
+ * The names every JavaScript object inherits, which Zod reads in place of an absent property.
1285
+ * `__proto__` is among them and is the different case: Zod skips it outright.
1286
+ */
1287
+ const INHERITED_NAMES = new Set(Object.getOwnPropertyNames(Object.prototype));
1288
+ /** Properties already reported, by declaration: one property renders once per visibility. */
1289
+ const reportedNames = new WeakMap();
1290
+ /**
1291
+ * **A wire name the validator cannot treat as an ordinary key is reported where it bites.** See
1292
+ * `unenforceable-property-name` and `optional-prototype-name` in `lib.ts` for why each is a
1293
+ * warning and what each costs.
1294
+ *
1295
+ * Reported here rather than from a walk over the program because `optional` is a fact about the
1296
+ * POSITION: a PATCH makes a required property optional, and only there does an inherited name
1297
+ * refuse its omission. Once per declaration, by its syntax node, because a visibility projection
1298
+ * hands this a different `ModelProperty` for the same one.
1299
+ */
1300
+ function reportInheritedName(program, property, optional) {
1301
+ const name = wireNameOf(program, property);
1302
+ if (!INHERITED_NAMES.has(name))
1303
+ return;
1304
+ const unenforceable = name === "__proto__";
1305
+ if (!unenforceable && !optional)
1306
+ return;
1307
+ const declaration = property.node ?? property;
1308
+ let reported = reportedNames.get(program);
1309
+ if (reported === undefined)
1310
+ reportedNames.set(program, (reported = new WeakSet()));
1311
+ if (reported.has(declaration))
1312
+ return;
1313
+ reported.add(declaration);
1314
+ if (unenforceable) {
1315
+ reportDiagnostic(program, {
1316
+ code: "unenforceable-property-name",
1317
+ target: property,
1318
+ format: { name },
1319
+ });
1320
+ }
1321
+ else {
1322
+ reportDiagnostic(program, {
1323
+ code: "optional-prototype-name",
1324
+ target: property,
1325
+ format: { name },
1326
+ });
1327
+ }
1328
+ }
1329
+ /** The key a property is validated under: its parameter name, or its name in a JSON body. */
1330
+ function wireNameOf(program, property) {
1331
+ if (isHeader(program, property))
1332
+ return getHeaderFieldName(program, property);
1333
+ if (isQueryParam(program, property))
1334
+ return getQueryParamName(program, property) ?? property.name;
1335
+ if (isPathParam(program, property))
1336
+ return getPathParamName(program, property) ?? property.name;
1337
+ if (isCookieParam(program, property)) {
1338
+ return getCookieParamOptions(program, property)?.name ?? property.name;
1339
+ }
1340
+ return rawPropertyKey(program, property);
1159
1341
  }
1160
1342
  /**
1161
1343
  * A default value as a JS literal, or `undefined` where this emitter cannot render one.
@@ -1180,7 +1362,7 @@ export function propertyToZod(program, property) {
1180
1362
  */
1181
1363
  function renderValue(value) {
1182
1364
  if (value.valueKind === "StringValue")
1183
- return JSON.stringify(value.value);
1365
+ return literal(value.value);
1184
1366
  if (value.valueKind === "NumericValue")
1185
1367
  return String(value.value.asNumber());
1186
1368
  if (value.valueKind === "BooleanValue")
@@ -1189,7 +1371,7 @@ function renderValue(value) {
1189
1371
  return "null";
1190
1372
  if (value.valueKind === "EnumValue") {
1191
1373
  const member = value.value;
1192
- return JSON.stringify(member.value ?? member.name);
1374
+ return literal(member.value ?? member.name);
1193
1375
  }
1194
1376
  if (value.valueKind === "ArrayValue") {
1195
1377
  const rendered = value.values.map(renderValue);
@@ -1203,12 +1385,30 @@ function renderValue(value) {
1203
1385
  const rendered = renderValue(descriptor.value);
1204
1386
  if (rendered === undefined)
1205
1387
  return undefined;
1206
- entries.push(`${JSON.stringify(name)}: ${rendered}`);
1388
+ entries.push(`${literal(name)}: ${rendered}`);
1207
1389
  }
1208
1390
  return `{${entries.join(", ")}}`;
1209
1391
  }
1210
1392
  return undefined;
1211
1393
  }
1394
+ /**
1395
+ * **Whether `propertyToZod` emits `.default(...)` for this property, which is exactly whether its
1396
+ * INPUT admits an explicit `undefined`.**
1397
+ *
1398
+ * `.exactOptional()` refuses `{ p: undefined }`; `.default(v)` accepts it and fills `v`. The contract
1399
+ * types in `requests.gen.ts` state the input, so they have to know which of the two this emitted -
1400
+ * and asking this function rather than restating its rule is what keeps the two walks agreeing.
1401
+ * A default on a REQUIRED property emits neither (`default-on-required-property`), and one whose
1402
+ * value cannot be rendered emits no `.default()` (`unsupported-default`); both fall out here.
1403
+ *
1404
+ * Pure: it reports nothing, so the type walk can ask it without duplicating a diagnostic.
1405
+ */
1406
+ export function emitsDefault(program, property) {
1407
+ const optional = property.optional || isOptionalAt(program, property);
1408
+ return (optional &&
1409
+ property.defaultValue !== undefined &&
1410
+ renderValue(property.defaultValue) !== undefined);
1411
+ }
1212
1412
  /** A property's declared default, as a JS literal, or `undefined` when it has none. */
1213
1413
  function defaultOf(program, property) {
1214
1414
  const value = property.defaultValue;
@@ -1312,13 +1512,13 @@ export function typeToZodBody(program, type) {
1312
1512
  case "Union":
1313
1513
  return unionToZod(program, type);
1314
1514
  case "String":
1315
- return `z.literal(${JSON.stringify(type.value)})`;
1515
+ return `z.literal(${literal(type.value)})`;
1316
1516
  case "Number":
1317
1517
  return `z.literal(${type.value})`;
1318
1518
  case "Boolean":
1319
1519
  return `z.literal(${String(type.value)})`;
1320
1520
  case "EnumMember":
1321
- return `z.literal(${JSON.stringify(type.value ?? type.name)})`;
1521
+ return `z.literal(${literal(type.value ?? type.name)})`;
1322
1522
  /**
1323
1523
  * **A named union VARIANT is a type you can refer to, and it is not the union.**
1324
1524
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "typespec-http-zod",
3
- "version": "0.27.0",
3
+ "version": "0.29.0",
4
4
  "description": "TypeSpec emitter and library: turn an HTTP service definition into Zod validators and framework-free contract types that agree, keyword for keyword, with the OpenAPI document @typespec/openapi3 publishes from the same source.",
5
5
  "keywords": [
6
6
  "codegen",
@@ -47,17 +47,17 @@
47
47
  },
48
48
  "devDependencies": {
49
49
  "@types/node": "^26.5.1",
50
- "@typespec/compiler": "1.16.0",
51
- "@typespec/events": "0.86.0",
52
- "@typespec/http": "1.16.0",
53
- "@typespec/http-specs": "0.1.0-alpha.41",
54
- "@typespec/openapi": "1.16.0",
55
- "@typespec/openapi3": "1.16.0",
56
- "@typespec/rest": "0.86.0",
57
- "@typespec/sse": "0.86.0",
58
- "@typespec/streams": "0.86.0",
59
- "@typespec/versioning": "0.86.0",
60
- "@typespec/xml": "0.86.0",
50
+ "@typespec/compiler": "1.17.0",
51
+ "@typespec/events": "0.87.0",
52
+ "@typespec/http": "1.17.0",
53
+ "@typespec/http-specs": "0.1.0-alpha.44",
54
+ "@typespec/openapi": "1.17.0",
55
+ "@typespec/openapi3": "1.17.0",
56
+ "@typespec/rest": "0.87.0",
57
+ "@typespec/sse": "0.87.0",
58
+ "@typespec/streams": "0.87.0",
59
+ "@typespec/versioning": "0.87.0",
60
+ "@typespec/xml": "0.87.0",
61
61
  "ajv": "^8.20.0",
62
62
  "oxfmt": "^0.67.0",
63
63
  "oxlint": "^1.82.0",
@@ -71,8 +71,8 @@
71
71
  "@typespec/compiler": "^1.15.0",
72
72
  "@typespec/http": "^1.15.0",
73
73
  "@typespec/openapi": "^1.15.0",
74
- "@typespec/streams": "^0.85.0 || ^0.86.0",
75
- "@typespec/versioning": "^0.85.0 || ^0.86.0",
74
+ "@typespec/streams": "^0.85.0 || ^0.86.0 || ^0.87.0",
75
+ "@typespec/versioning": "^0.85.0 || ^0.86.0 || ^0.87.0",
76
76
  "zod": "^4.5.0"
77
77
  },
78
78
  "peerDependenciesMeta": {