@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.cjs CHANGED
@@ -821,13 +821,38 @@ function shouldCoerce(coercion, type) {
821
821
  if (coercion === true) return true;
822
822
  return !!coercion[type];
823
823
  }
824
+ function hasPropertyNames(node) {
825
+ return "propertyNames" in node && Boolean(node.propertyNames);
826
+ }
827
+ /**
828
+ * Whether the schema node represents a closed or finite set of keys (e.g. enum, literal,
829
+ * or union of enums/literals).
830
+ *
831
+ * In Zod v4, passing a closed key schema to `z.record()` enforces exhaustiveness (all keys
832
+ * must exist in the input). OpenAPI 3.1 `propertyNames` only validates present keys without
833
+ * requiring all keys to exist, so closed key schemas use `z.partialRecord()` instead.
834
+ */
835
+ function isClosedKeySchema(node) {
836
+ if (!node) return false;
837
+ const resolved = (0, kubb_kit.syncSchemaRef)(node);
838
+ if (resolved.type === "enum") return true;
839
+ if (resolved.type === "union") {
840
+ const members = resolved.members ?? [];
841
+ return members.length > 0 && members.every(isClosedKeySchema);
842
+ }
843
+ return false;
844
+ }
824
845
  /**
825
846
  * Whether the node is a plain inline object whose shape can be lifted into an `.extend({ … })`
826
847
  * argument. A catchall, `patternProperties`, or a nullable/optional wrapper cannot, so those stay
827
848
  * on `.and(…)`.
828
849
  */
829
850
  function isPlainInlineObject(node) {
830
- return node.type === "object" && !node.nullable && !node.optional && !node.nullish && node.additionalProperties === void 0 && !node.patternProperties;
851
+ if (node.type !== "object") return false;
852
+ if (node.nullable || node.optional || node.nullish) return false;
853
+ const hasAdditionalProperties = node.additionalProperties !== void 0;
854
+ const hasPatternProperties = Boolean(node.patternProperties);
855
+ return !hasAdditionalProperties && !hasPatternProperties && !hasPropertyNames(node);
831
856
  }
832
857
  /**
833
858
  * Whether a node renders as a bare Zod object — one that accepts `.extend(…)` and can be a
@@ -838,7 +863,12 @@ function isPlainInlineObject(node) {
838
863
  */
839
864
  function isObjectSchemaNode(node, cyclicSchemas) {
840
865
  if (node.nullable || node.optional || node.nullish) return false;
841
- if (node.type === "object") return true;
866
+ if (node.type === "object") {
867
+ const hasProperties = Boolean(node.properties?.length);
868
+ const hasTypedAdditionalProperties = Boolean(node.additionalProperties && node.additionalProperties !== true);
869
+ const hasPatternProperties = Boolean(node.patternProperties);
870
+ return !(!hasProperties && (hasTypedAdditionalProperties || hasPatternProperties || hasPropertyNames(node)));
871
+ }
842
872
  if (node.type === "ref") {
843
873
  const refName = kubb_kit.ast.resolveRefName(node);
844
874
  if (refName && cyclicSchemas?.has(refName)) return false;
@@ -1212,6 +1242,7 @@ function containsDirectionalNode({ node, printerOptions, seen = /* @__PURE__ */
1212
1242
  if ("items" in node && node.items) children.push(...node.items);
1213
1243
  if ("members" in node && node.members) children.push(...node.members);
1214
1244
  if ("additionalProperties" in node && node.additionalProperties && node.additionalProperties !== true) children.push(node.additionalProperties);
1245
+ if ("propertyNames" in node && node.propertyNames) children.push(node.propertyNames);
1215
1246
  return children.some((child) => containsDirectionalNode({
1216
1247
  node: child,
1217
1248
  printerOptions,
@@ -1376,14 +1407,23 @@ const printerZod = kubb_kit.ast.createPrinter((options) => {
1376
1407
  object(node) {
1377
1408
  const entries = node.properties ?? [];
1378
1409
  const patterns = node.patternProperties ? Object.entries(node.patternProperties) : [];
1410
+ const propertyNamesNode = "propertyNames" in node ? node.propertyNames : void 0;
1411
+ const propertyNamesKeySchema = propertyNamesNode ? this.transform(propertyNamesNode) : null;
1379
1412
  const isStrict = node.additionalProperties === false && patterns.length === 0;
1380
- const objectBase = `${isStrict ? "z.strictObject" : "z.object"}(${buildZodObjectShape(this, node)})`;
1413
+ const isLoose = node.additionalProperties === true;
1414
+ const objectBase = `${isStrict ? "z.strictObject" : isLoose ? "z.looseObject" : "z.object"}(${buildZodObjectShape(this, node)})`;
1381
1415
  return (() => {
1416
+ const recordFn = isClosedKeySchema(propertyNamesNode) ? "z.partialRecord" : "z.record";
1382
1417
  if (node.additionalProperties && node.additionalProperties !== true) {
1383
1418
  const catchallType = this.transform(node.additionalProperties);
1419
+ if (entries.length === 0) return catchallType ? `${recordFn}(${propertyNamesKeySchema || "z.string()"}, ${catchallType})` : objectBase;
1384
1420
  return catchallType ? `${objectBase}.catchall(${catchallType})` : objectBase;
1385
1421
  }
1386
- if (node.additionalProperties === true) return `${objectBase}.catchall(${this.transform(kubb_kit.ast.factory.createSchema({ type: "unknown" }))})`;
1422
+ if (entries.length === 0 && patterns.length === 0 && propertyNamesKeySchema && !isStrict) {
1423
+ const unknownType = this.transform(kubb_kit.ast.factory.createSchema({ type: "unknown" }));
1424
+ return `${recordFn}(${propertyNamesKeySchema}, ${unknownType})`;
1425
+ }
1426
+ if (node.additionalProperties === true) return objectBase;
1387
1427
  if (isStrict) return objectBase;
1388
1428
  if (patterns.length > 0) {
1389
1429
  const values = patterns.map(([, valueSchema]) => {
@@ -1639,13 +1679,19 @@ const printerZodMini = kubb_kit.ast.createPrinter((options) => {
1639
1679
  object(node) {
1640
1680
  const entries = node.properties ?? [];
1641
1681
  const patterns = node.patternProperties ? Object.entries(node.patternProperties) : [];
1682
+ const propertyNamesNode = "propertyNames" in node ? node.propertyNames : void 0;
1683
+ const propertyNamesKeySchema = propertyNamesNode ? this.transform(propertyNamesNode) : null;
1642
1684
  const isStrict = node.additionalProperties === false && patterns.length === 0;
1643
- const objectBase = `${isStrict ? "z.strictObject" : "z.object"}(${buildZodMiniObjectShape(this, node)})`;
1685
+ const isLoose = node.additionalProperties === true;
1686
+ const objectBase = `${isStrict ? "z.strictObject" : isLoose ? "z.looseObject" : "z.object"}(${buildZodMiniObjectShape(this, node)})`;
1687
+ const recordFn = isClosedKeySchema(propertyNamesNode) ? "z.partialRecord" : "z.record";
1644
1688
  if (node.additionalProperties && node.additionalProperties !== true) {
1645
1689
  const catchallType = this.transform(node.additionalProperties);
1690
+ if (entries.length === 0) return catchallType ? `${recordFn}(${propertyNamesKeySchema || "z.string()"}, ${catchallType})` : objectBase;
1646
1691
  return catchallType ? `z.catchall(${objectBase}, ${catchallType})` : objectBase;
1647
1692
  }
1648
- if (node.additionalProperties === true) return `z.catchall(${objectBase}, ${this.transform(kubb_kit.ast.factory.createSchema({ type: "unknown" }))})`;
1693
+ if (entries.length === 0 && patterns.length === 0 && propertyNamesKeySchema && !isStrict) return `${recordFn}(${propertyNamesKeySchema}, ${this.transform(kubb_kit.ast.factory.createSchema({ type: "unknown" }))})`;
1694
+ if (node.additionalProperties === true) return objectBase;
1649
1695
  if (isStrict) return objectBase;
1650
1696
  if (patterns.length > 0) {
1651
1697
  const values = patterns.map(([, valueSchema]) => {