From b51bfb8d5952819ead5b3fbf5372dda04ee90a6b Mon Sep 17 00:00:00 2001 From: Aiden McClelland Date: Tue, 3 Mar 2026 11:42:02 -0700 Subject: [PATCH] fix: preserve z namespace types for sdk consumers --- sdk/Makefile | 21 +++++++- sdk/base/lib/index.ts | 100 +------------------------------------- sdk/base/lib/zExport.d.ts | 14 ++++++ sdk/base/lib/zExport.js | 92 +++++++++++++++++++++++++++++++++++ 4 files changed, 126 insertions(+), 101 deletions(-) create mode 100644 sdk/base/lib/zExport.d.ts create mode 100644 sdk/base/lib/zExport.js diff --git a/sdk/Makefile b/sdk/Makefile index 198510d54..9370ab372 100644 --- a/sdk/Makefile +++ b/sdk/Makefile @@ -27,16 +27,33 @@ bundle: baseDist dist | test fmt base/lib/exver/exver.ts: base/node_modules base/lib/exver/exver.pegjs cd base && npm run peggy -baseDist: $(PACKAGE_TS_FILES) $(BASE_TS_FILES) base/package.json base/node_modules base/README.md base/LICENSE +baseDist: $(PACKAGE_TS_FILES) $(BASE_TS_FILES) base/package.json base/node_modules base/README.md base/LICENSE (cd base && npm run tsc) + # Copy hand-written .js/.d.ts pairs (no corresponding .ts source) into the output. + cd base/lib && find . -name '*.js' | while read f; do \ + base="$${f%.js}"; \ + if [ -f "$$base.d.ts" ] && [ ! -f "$$base.ts" ]; then \ + mkdir -p "../../baseDist/$$(dirname "$$f")"; \ + cp "$$f" "../../baseDist/$$f"; \ + cp "$$base.d.ts" "../../baseDist/$$base.d.ts"; \ + fi; \ + done rsync -ac base/node_modules baseDist/ cp base/package.json baseDist/package.json cp base/README.md baseDist/README.md cp base/LICENSE baseDist/LICENSE touch baseDist -dist: $(PACKAGE_TS_FILES) $(BASE_TS_FILES) package/package.json package/.npmignore package/node_modules package/README.md package/LICENSE +dist: $(PACKAGE_TS_FILES) $(BASE_TS_FILES) package/package.json package/.npmignore package/node_modules package/README.md package/LICENSE (cd package && npm run tsc) + cd base/lib && find . -name '*.js' | while read f; do \ + base="$${f%.js}"; \ + if [ -f "$$base.d.ts" ] && [ ! -f "$$base.ts" ]; then \ + mkdir -p "../../dist/base/lib/$$(dirname "$$f")"; \ + cp "$$f" "../../dist/base/lib/$$f"; \ + cp "$$base.d.ts" "../../dist/base/lib/$$base.d.ts"; \ + fi; \ + done rsync -ac package/node_modules dist/ cp package/.npmignore dist/.npmignore cp package/package.json dist/package.json diff --git a/sdk/base/lib/index.ts b/sdk/base/lib/index.ts index cb48ea89c..bcf37fe03 100644 --- a/sdk/base/lib/index.ts +++ b/sdk/base/lib/index.ts @@ -8,104 +8,6 @@ export * as types from './types' export * as T from './types' export * as yaml from 'yaml' export * as inits from './inits' -import { z as _z } from 'zod' -import { zodDeepPartial } from 'zod-deep-partial' -import type { DeepPartial } from './types' - -type ZodDeepPartial = (a: _z.ZodType) => _z.ZodType> - -// Recursively make all ZodObjects in a schema loose (preserve extra keys at every nesting level). -// Uses _zod.def.type duck-typing instead of instanceof to avoid issues with mismatched zod versions. -function deepLoose(schema: S): S { - const def = (schema as any)._zod?.def - if (!def) return schema - let result: _z.ZodType - switch (def.type) { - case 'optional': - result = deepLoose(def.innerType).optional() - break - case 'nullable': - result = deepLoose(def.innerType).nullable() - break - case 'object': { - const newShape: Record = {} - for (const key in (schema as any).shape) { - newShape[key] = deepLoose((schema as any).shape[key]) - } - result = _z.looseObject(newShape) - break - } - case 'array': - result = _z.array(deepLoose(def.element)) - break - case 'union': - result = _z.union(def.options.map((o: _z.ZodType) => deepLoose(o))) - break - case 'intersection': - result = _z.intersection(deepLoose(def.left), deepLoose(def.right)) - break - case 'record': - result = _z.record(def.keyType, deepLoose(def.valueType)) - break - case 'tuple': - result = _z.tuple(def.items.map((i: _z.ZodType) => deepLoose(i))) - break - case 'lazy': - result = _z.lazy(() => deepLoose(def.getter())) - break - default: - return schema - } - return result as S -} - -type ZodDeepLoose = (a: _z.ZodType) => _z.ZodType - -// Add deepPartial and deepLoose to z at runtime -;(_z as any).deepPartial = (a: _z.ZodType) => deepLoose(zodDeepPartial(a)) -;(_z as any).deepLoose = deepLoose - -// Augment zod's z namespace so z.deepPartial and z.deepLoose are typed -declare module 'zod' { - namespace z { - const deepPartial: ZodDeepPartial - const deepLoose: ZodDeepLoose - } -} - -// Override z.object to produce loose objects by default (extra keys are preserved, not stripped). -const _origObject = _z.object -const _patchedObject = (...args: Parameters) => - _origObject(...args).loose() - -// In CJS (Node.js), patch the source module in require.cache where 'object' is a writable property; -// the CJS getter chain (index → external → schemas) then relays the patched version. -// We walk only the zod entry module's dependency tree and match by identity (=== origObject). -try { - const _zodModule = require.cache[require.resolve('zod')] - for (const child of _zodModule?.children ?? []) { - for (const grandchild of child.children ?? []) { - const desc = Object.getOwnPropertyDescriptor(grandchild.exports, 'object') - if (desc?.value === _origObject && desc.writable) { - grandchild.exports.object = _patchedObject - } - } - } -} catch (_) { - // Not in CJS/Node environment (e.g. browser) — require.cache unavailable -} - -// z.object is a non-configurable getter on the zod namespace, so we can't override it directly. -// Shadow it by exporting a new object with _z as prototype and our patched object on the instance. -const z: typeof _z = Object.create(_z, { - object: { - value: _patchedObject, - writable: true, - configurable: true, - enumerable: true, - }, -}) - -export { z } +export { z } from './zExport' export * as utils from './util' diff --git a/sdk/base/lib/zExport.d.ts b/sdk/base/lib/zExport.d.ts new file mode 100644 index 000000000..995cf7c9c --- /dev/null +++ b/sdk/base/lib/zExport.d.ts @@ -0,0 +1,14 @@ +import { z as _z } from 'zod' +import type { DeepPartial } from './types' + +type ZodDeepPartial = (a: _z.ZodType) => _z.ZodType> +type ZodDeepLoose = (a: _z.ZodType) => _z.ZodType + +declare module 'zod' { + namespace z { + const deepPartial: ZodDeepPartial + const deepLoose: ZodDeepLoose + } +} + +export { _z as z } diff --git a/sdk/base/lib/zExport.js b/sdk/base/lib/zExport.js new file mode 100644 index 000000000..c99abe324 --- /dev/null +++ b/sdk/base/lib/zExport.js @@ -0,0 +1,92 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); + +const zod_1 = require("zod"); +const zod_deep_partial_1 = require("zod-deep-partial"); + +// Recursively make all ZodObjects in a schema loose (preserve extra keys at every nesting level). +// Uses _zod.def.type duck-typing instead of instanceof to avoid issues with mismatched zod versions. +function deepLoose(schema) { + const def = schema._zod?.def; + if (!def) return schema; + let result; + switch (def.type) { + case "optional": + result = deepLoose(def.innerType).optional(); + break; + case "nullable": + result = deepLoose(def.innerType).nullable(); + break; + case "object": { + const newShape = {}; + for (const key in schema.shape) { + newShape[key] = deepLoose(schema.shape[key]); + } + result = zod_1.z.looseObject(newShape); + break; + } + case "array": + result = zod_1.z.array(deepLoose(def.element)); + break; + case "union": + result = zod_1.z.union(def.options.map((o) => deepLoose(o))); + break; + case "intersection": + result = zod_1.z.intersection(deepLoose(def.left), deepLoose(def.right)); + break; + case "record": + result = zod_1.z.record(def.keyType, deepLoose(def.valueType)); + break; + case "tuple": + result = zod_1.z.tuple(def.items.map((i) => deepLoose(i))); + break; + case "lazy": + result = zod_1.z.lazy(() => deepLoose(def.getter())); + break; + default: + return schema; + } + return result; +} + +// Add deepPartial and deepLoose to z at runtime +zod_1.z.deepPartial = (a) => + deepLoose((0, zod_deep_partial_1.zodDeepPartial)(a)); +zod_1.z.deepLoose = deepLoose; + +// Override z.object to produce loose objects by default (extra keys are preserved, not stripped). +const _origObject = zod_1.z.object; +const _patchedObject = (...args) => _origObject(...args).loose(); + +// In CJS (Node.js), patch the source module in require.cache where 'object' is a writable property; +// the CJS getter chain (index → external → schemas) then relays the patched version. +// We walk only the zod entry module's dependency tree and match by identity (=== origObject). +try { + const _zodModule = require.cache[require.resolve("zod")]; + for (const child of _zodModule?.children ?? []) { + for (const grandchild of child.children ?? []) { + const desc = Object.getOwnPropertyDescriptor( + grandchild.exports, + "object", + ); + if (desc?.value === _origObject && desc.writable) { + grandchild.exports.object = _patchedObject; + } + } + } +} catch (_) { + // Not in CJS/Node environment (e.g. browser) — require.cache unavailable +} + +// z.object is a non-configurable getter on the zod namespace, so we can't override it directly. +// Shadow it by exporting a new object with _z as prototype and our patched object on the instance. +const z = Object.create(zod_1.z, { + object: { + value: _patchedObject, + writable: true, + configurable: true, + enumerable: true, + }, +}); + +exports.z = z;