@kubb/plugin-zod 5.4.0-canary.20260925T211808 → 5.4.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/index.js CHANGED
@@ -811,13 +811,38 @@ function shouldCoerce(coercion, type) {
811
811
  if (coercion === true) return true;
812
812
  return !!coercion[type];
813
813
  }
814
+ function hasPropertyNames(node) {
815
+ return "propertyNames" in node && Boolean(node.propertyNames);
816
+ }
817
+ /**
818
+ * Whether the schema node represents a closed or finite set of keys (e.g. enum, literal,
819
+ * or union of enums/literals).
820
+ *
821
+ * In Zod v4, passing a closed key schema to `z.record()` enforces exhaustiveness (all keys
822
+ * must exist in the input). OpenAPI 3.1 `propertyNames` only validates present keys without
823
+ * requiring all keys to exist, so closed key schemas use `z.partialRecord()` instead.
824
+ */
825
+ function isClosedKeySchema(node) {
826
+ if (!node) return false;
827
+ const resolved = syncSchemaRef(node);
828
+ if (resolved.type === "enum") return true;
829
+ if (resolved.type === "union") {
830
+ const members = resolved.members ?? [];
831
+ return members.length > 0 && members.every(isClosedKeySchema);
832
+ }
833
+ return false;
834
+ }
814
835
  /**
815
836
  * Whether the node is a plain inline object whose shape can be lifted into an `.extend({ … })`
816
837
  * argument. A catchall, `patternProperties`, or a nullable/optional wrapper cannot, so those stay
817
838
  * on `.and(…)`.
818
839
  */
819
840
  function isPlainInlineObject(node) {
820
- return node.type === "object" && !node.nullable && !node.optional && !node.nullish && node.additionalProperties === void 0 && !node.patternProperties;
841
+ if (node.type !== "object") return false;
842
+ if (node.nullable || node.optional || node.nullish) return false;
843
+ const hasAdditionalProperties = node.additionalProperties !== void 0;
844
+ const hasPatternProperties = Boolean(node.patternProperties);
845
+ return !hasAdditionalProperties && !hasPatternProperties && !hasPropertyNames(node);
821
846
  }
822
847
  /**
823
848
  * Whether a node renders as a bare Zod object — one that accepts `.extend(…)` and can be a
@@ -828,7 +853,12 @@ function isPlainInlineObject(node) {
828
853
  */
829
854
  function isObjectSchemaNode(node, cyclicSchemas) {
830
855
  if (node.nullable || node.optional || node.nullish) return false;
831
- if (node.type === "object") return true;
856
+ if (node.type === "object") {
857
+ const hasProperties = Boolean(node.properties?.length);
858
+ const hasTypedAdditionalProperties = Boolean(node.additionalProperties && node.additionalProperties !== true);
859
+ const hasPatternProperties = Boolean(node.patternProperties);
860
+ return !(!hasProperties && (hasTypedAdditionalProperties || hasPatternProperties || hasPropertyNames(node)));
861
+ }
832
862
  if (node.type === "ref") {
833
863
  const refName = ast.resolveRefName(node);
834
864
  if (refName && cyclicSchemas?.has(refName)) return false;
@@ -1202,6 +1232,7 @@ function containsDirectionalNode({ node, printerOptions, seen = /* @__PURE__ */
1202
1232
  if ("items" in node && node.items) children.push(...node.items);
1203
1233
  if ("members" in node && node.members) children.push(...node.members);
1204
1234
  if ("additionalProperties" in node && node.additionalProperties && node.additionalProperties !== true) children.push(node.additionalProperties);
1235
+ if ("propertyNames" in node && node.propertyNames) children.push(node.propertyNames);
1205
1236
  return children.some((child) => containsDirectionalNode({
1206
1237
  node: child,
1207
1238
  printerOptions,
@@ -1366,14 +1397,23 @@ const printerZod = ast.createPrinter((options) => {
1366
1397
  object(node) {
1367
1398
  const entries = node.properties ?? [];
1368
1399
  const patterns = node.patternProperties ? Object.entries(node.patternProperties) : [];
1400
+ const propertyNamesNode = "propertyNames" in node ? node.propertyNames : void 0;
1401
+ const propertyNamesKeySchema = propertyNamesNode ? this.transform(propertyNamesNode) : null;
1369
1402
  const isStrict = node.additionalProperties === false && patterns.length === 0;
1370
- const objectBase = `${isStrict ? "z.strictObject" : "z.object"}(${buildZodObjectShape(this, node)})`;
1403
+ const isLoose = node.additionalProperties === true;
1404
+ const objectBase = `${isStrict ? "z.strictObject" : isLoose ? "z.looseObject" : "z.object"}(${buildZodObjectShape(this, node)})`;
1371
1405
  return (() => {
1406
+ const recordFn = isClosedKeySchema(propertyNamesNode) ? "z.partialRecord" : "z.record";
1372
1407
  if (node.additionalProperties && node.additionalProperties !== true) {
1373
1408
  const catchallType = this.transform(node.additionalProperties);
1409
+ if (entries.length === 0) return catchallType ? `${recordFn}(${propertyNamesKeySchema || "z.string()"}, ${catchallType})` : objectBase;
1374
1410
  return catchallType ? `${objectBase}.catchall(${catchallType})` : objectBase;
1375
1411
  }
1376
- if (node.additionalProperties === true) return `${objectBase}.catchall(${this.transform(ast.factory.createSchema({ type: "unknown" }))})`;
1412
+ if (entries.length === 0 && patterns.length === 0 && propertyNamesKeySchema && !isStrict) {
1413
+ const unknownType = this.transform(ast.factory.createSchema({ type: "unknown" }));
1414
+ return `${recordFn}(${propertyNamesKeySchema}, ${unknownType})`;
1415
+ }
1416
+ if (node.additionalProperties === true) return objectBase;
1377
1417
  if (isStrict) return objectBase;
1378
1418
  if (patterns.length > 0) {
1379
1419
  const values = patterns.map(([, valueSchema]) => {
@@ -1629,13 +1669,19 @@ const printerZodMini = ast.createPrinter((options) => {
1629
1669
  object(node) {
1630
1670
  const entries = node.properties ?? [];
1631
1671
  const patterns = node.patternProperties ? Object.entries(node.patternProperties) : [];
1672
+ const propertyNamesNode = "propertyNames" in node ? node.propertyNames : void 0;
1673
+ const propertyNamesKeySchema = propertyNamesNode ? this.transform(propertyNamesNode) : null;
1632
1674
  const isStrict = node.additionalProperties === false && patterns.length === 0;
1633
- const objectBase = `${isStrict ? "z.strictObject" : "z.object"}(${buildZodMiniObjectShape(this, node)})`;
1675
+ const isLoose = node.additionalProperties === true;
1676
+ const objectBase = `${isStrict ? "z.strictObject" : isLoose ? "z.looseObject" : "z.object"}(${buildZodMiniObjectShape(this, node)})`;
1677
+ const recordFn = isClosedKeySchema(propertyNamesNode) ? "z.partialRecord" : "z.record";
1634
1678
  if (node.additionalProperties && node.additionalProperties !== true) {
1635
1679
  const catchallType = this.transform(node.additionalProperties);
1680
+ if (entries.length === 0) return catchallType ? `${recordFn}(${propertyNamesKeySchema || "z.string()"}, ${catchallType})` : objectBase;
1636
1681
  return catchallType ? `z.catchall(${objectBase}, ${catchallType})` : objectBase;
1637
1682
  }
1638
- if (node.additionalProperties === true) return `z.catchall(${objectBase}, ${this.transform(ast.factory.createSchema({ type: "unknown" }))})`;
1683
+ if (entries.length === 0 && patterns.length === 0 && propertyNamesKeySchema && !isStrict) return `${recordFn}(${propertyNamesKeySchema}, ${this.transform(ast.factory.createSchema({ type: "unknown" }))})`;
1684
+ if (node.additionalProperties === true) return objectBase;
1639
1685
  if (isStrict) return objectBase;
1640
1686
  if (patterns.length > 0) {
1641
1687
  const values = patterns.map(([, valueSchema]) => {