webspresso 0.0.112 → 0.0.114
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/README.md +26 -0
- package/bin/commands/admin-password.js +1 -7
- package/bin/commands/api.js +1 -7
- package/bin/commands/new.js +1 -7
- package/bin/commands/page.js +1 -7
- package/bin/commands/seed.js +1 -7
- package/bin/commands/service.js +1 -7
- package/bin/utils/prompt.js +17 -0
- package/core/cache/tagged-lru.js +9 -3
- package/core/client/adapter/datastar.js +602 -26
- package/core/client/api.js +151 -7
- package/core/client/controller.js +172 -11
- package/core/client/devtool.js +59 -33
- package/core/client/forms.js +479 -29
- package/core/client/fragments.js +456 -27
- package/core/client/index.js +18 -1
- package/core/client/server-fragments.js +125 -40
- package/core/client/utils.js +72 -17
- package/core/console/commands.js +26 -5
- package/core/console/repl.js +7 -1
- package/core/http/pipeline.js +82 -1
- package/core/openapi/build-from-api-routes.js +5 -5
- package/core/orm/json-fields.js +7 -6
- package/core/orm/schema-helpers.js +18 -15
- package/core/orm/utils/nanoid.js +7 -8
- package/core/plugins/plugin-manager.js +155 -1
- package/core/services/validator.js +12 -1
- package/core/validation/apply.js +3 -0
- package/core/validation/index.js +25 -0
- package/core/validation/zod-introspect.js +187 -0
- package/index.d.ts +113 -1
- package/package.json +12 -11
- package/plugins/mcp/providers/services.js +2 -2
- package/src/api/define-api.js +2 -0
- package/src/api/validation-i18n.js +6 -2
- package/src/bundle/index.js +140 -6
- package/src/dev/api-typegen.js +79 -22
- package/src/dev/typegen.js +61 -35
- package/src/dev-reload/index.js +44 -2
- package/src/file-router.js +24 -6
- package/src/routing/mount-discovered-routes.js +24 -6
- package/src/server.js +59 -3
package/core/orm/json-fields.js
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* @module core/orm/json-fields
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
const { getZodDef, getZodInner, getZodTypeName } = require('../validation/zod-introspect');
|
|
7
|
+
|
|
6
8
|
/**
|
|
7
9
|
* Get JSON column names from model
|
|
8
10
|
* @param {import('./types').ModelDefinition} model - Model definition
|
|
@@ -31,16 +33,15 @@ function getJsonColumns(model) {
|
|
|
31
33
|
if (model && model.schema && model.schema.shape) {
|
|
32
34
|
for (const [name, fieldSchema] of Object.entries(model.schema.shape)) {
|
|
33
35
|
let current = fieldSchema;
|
|
34
|
-
while (current && current
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
current = current._def.schema;
|
|
36
|
+
while (current && getZodDef(current)) {
|
|
37
|
+
const inner = getZodInner(current);
|
|
38
|
+
if (inner) {
|
|
39
|
+
current = inner;
|
|
39
40
|
} else {
|
|
40
41
|
break;
|
|
41
42
|
}
|
|
42
43
|
}
|
|
43
|
-
const typeName = current
|
|
44
|
+
const typeName = getZodTypeName(current);
|
|
44
45
|
if (typeName === 'ZodRecord' || typeName === 'ZodObject' || typeName === 'ZodArray') {
|
|
45
46
|
jsonCols.add(name);
|
|
46
47
|
}
|
|
@@ -5,6 +5,13 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
const { safeJsonParse } = require('../utils/json');
|
|
8
|
+
const {
|
|
9
|
+
getZodDef,
|
|
10
|
+
getZodTypeName,
|
|
11
|
+
getZodInner,
|
|
12
|
+
getZodEnumValues,
|
|
13
|
+
isZodInt,
|
|
14
|
+
} = require('../validation/zod-introspect');
|
|
8
15
|
|
|
9
16
|
const METADATA_MARKER = '__wdb__';
|
|
10
17
|
const metaWeakMap = new WeakMap();
|
|
@@ -665,10 +672,7 @@ function createSchemaHelpers(z) {
|
|
|
665
672
|
*/
|
|
666
673
|
json(options = {}) {
|
|
667
674
|
const { nullable = false, ...rest } = options;
|
|
668
|
-
let schema = z.unknown();
|
|
669
|
-
if (nullable) {
|
|
670
|
-
schema = schema.nullable().optional();
|
|
671
|
-
}
|
|
675
|
+
let schema = nullable ? z.unknown().nullable().optional() : z.unknown().optional();
|
|
672
676
|
return createSchemaBuilder(schema, {
|
|
673
677
|
type: 'json',
|
|
674
678
|
nullable,
|
|
@@ -888,14 +892,14 @@ function extractColumnsFromSchema(schema) {
|
|
|
888
892
|
// Unwrap optional/nullable wrappers to get to the base schema
|
|
889
893
|
let current = schemaToCheck;
|
|
890
894
|
let isNullable = false;
|
|
891
|
-
while (current && current
|
|
892
|
-
|
|
895
|
+
while (current && getZodDef(current)) {
|
|
896
|
+
const currentType = getZodTypeName(current);
|
|
897
|
+
if (currentType === 'ZodNullable' || currentType === 'ZodOptional') {
|
|
893
898
|
isNullable = true;
|
|
894
899
|
}
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
current = current._def.schema;
|
|
900
|
+
const inner = getZodInner(current);
|
|
901
|
+
if (inner) {
|
|
902
|
+
current = inner;
|
|
899
903
|
} else {
|
|
900
904
|
break;
|
|
901
905
|
}
|
|
@@ -905,16 +909,15 @@ function extractColumnsFromSchema(schema) {
|
|
|
905
909
|
const meta = getColumnMeta(schemaToCheck) || (current ? getColumnMeta(current) : null);
|
|
906
910
|
if (meta) {
|
|
907
911
|
columns.set(key, meta);
|
|
908
|
-
} else if (current && current
|
|
909
|
-
const typeName = current
|
|
912
|
+
} else if (current && getZodTypeName(current)) {
|
|
913
|
+
const typeName = getZodTypeName(current);
|
|
910
914
|
let inferred = null;
|
|
911
915
|
switch (typeName) {
|
|
912
916
|
case 'ZodString':
|
|
913
917
|
inferred = { type: 'string', maxLength: 255, nullable: isNullable };
|
|
914
918
|
break;
|
|
915
919
|
case 'ZodNumber': {
|
|
916
|
-
|
|
917
|
-
inferred = { type: isInt ? 'integer' : 'float', nullable: isNullable };
|
|
920
|
+
inferred = { type: isZodInt(current) ? 'integer' : 'float', nullable: isNullable };
|
|
918
921
|
break;
|
|
919
922
|
}
|
|
920
923
|
case 'ZodBoolean':
|
|
@@ -924,7 +927,7 @@ function extractColumnsFromSchema(schema) {
|
|
|
924
927
|
inferred = { type: 'timestamp', nullable: isNullable };
|
|
925
928
|
break;
|
|
926
929
|
case 'ZodEnum':
|
|
927
|
-
inferred = { type: 'enum', enumValues: current
|
|
930
|
+
inferred = { type: 'enum', enumValues: getZodEnumValues(current), nullable: isNullable };
|
|
928
931
|
break;
|
|
929
932
|
case 'ZodArray':
|
|
930
933
|
inferred = { type: 'files', nullable: isNullable };
|
package/core/orm/utils/nanoid.js
CHANGED
|
@@ -63,14 +63,13 @@ function resolveNanoidSize(arg) {
|
|
|
63
63
|
* @param {typeof import('zod').z} zod
|
|
64
64
|
*/
|
|
65
65
|
function extendZ(zod) {
|
|
66
|
-
return
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
return
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
return Reflect.get(target, prop, receiver);
|
|
66
|
+
return Object.create(zod, {
|
|
67
|
+
nanoid: {
|
|
68
|
+
value: function nanoid(arg) {
|
|
69
|
+
return zodNanoid(zod, resolveNanoidSize(arg));
|
|
70
|
+
},
|
|
71
|
+
enumerable: true,
|
|
72
|
+
configurable: true,
|
|
74
73
|
},
|
|
75
74
|
});
|
|
76
75
|
}
|
|
@@ -147,6 +147,9 @@ class PluginManager {
|
|
|
147
147
|
this.routes = []; // Collected route metadata
|
|
148
148
|
this.customRoutes = []; // Routes added by plugins
|
|
149
149
|
this.cspDirectives = new Map(); // directive -> Set of sources (from plugins)
|
|
150
|
+
this.clientControllers = new Map(); // name -> { name, filePath, pluginName }
|
|
151
|
+
this.clientServices = new Map(); // name -> { name, filePath, pluginName }
|
|
152
|
+
this.registeredFragments = new Map(); // name -> { name, def, pluginName }
|
|
150
153
|
this.disposers = []; // Array<{ name: string, fn: Function }>
|
|
151
154
|
this._cachedHelpers = null; // Cached helpers object
|
|
152
155
|
this.pendingAsyncOperations = []; // Track background promises
|
|
@@ -271,7 +274,7 @@ class PluginManager {
|
|
|
271
274
|
|
|
272
275
|
// Build graph
|
|
273
276
|
for (const plugin of plugins) {
|
|
274
|
-
if (!plugin.name) {
|
|
277
|
+
if (!plugin || !plugin.name) {
|
|
275
278
|
console.warn('[plugin-manager] Skipping plugin without name:', plugin);
|
|
276
279
|
continue;
|
|
277
280
|
}
|
|
@@ -730,6 +733,33 @@ class PluginManager {
|
|
|
730
733
|
*/
|
|
731
734
|
getScriptInjector() {
|
|
732
735
|
return getCachedScriptInjector();
|
|
736
|
+
},
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* Register a client controller entry file (`.client.js` / `.client.ts`) for bundler discovery
|
|
740
|
+
* @param {string} name - Controller name used in `data-w-controller="<name>"`
|
|
741
|
+
* @param {string} filePath - Absolute or project-relative path to the client controller module
|
|
742
|
+
*/
|
|
743
|
+
registerClientController(name, filePath) {
|
|
744
|
+
self.registerClientController(name, filePath, pluginName);
|
|
745
|
+
},
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* Register a client service module (`defineClientService(...)`) for bundler discovery
|
|
749
|
+
* @param {string} name - Service name used in `ctx.services.get('<name>')`
|
|
750
|
+
* @param {string} filePath - Absolute or project-relative path to the client service module
|
|
751
|
+
*/
|
|
752
|
+
registerClientService(name, filePath) {
|
|
753
|
+
self.registerClientService(name, filePath, pluginName);
|
|
754
|
+
},
|
|
755
|
+
|
|
756
|
+
/**
|
|
757
|
+
* Register a reusable SSR fragment (`defineFragment(...)`) usable across pages
|
|
758
|
+
* @param {string} name - Fragment name used in `fsy.fragment('<name>', ...)`
|
|
759
|
+
* @param {Object} fragmentDef - Fragment definition created via `defineFragment(...)`
|
|
760
|
+
*/
|
|
761
|
+
registerFragment(name, fragmentDef) {
|
|
762
|
+
self.registerFragment(name, fragmentDef, pluginName);
|
|
733
763
|
}
|
|
734
764
|
};
|
|
735
765
|
}
|
|
@@ -808,6 +838,130 @@ class PluginManager {
|
|
|
808
838
|
}
|
|
809
839
|
}
|
|
810
840
|
|
|
841
|
+
/**
|
|
842
|
+
* Register a client controller module from a plugin.
|
|
843
|
+
* @param {string} name
|
|
844
|
+
* @param {string} filePath
|
|
845
|
+
* @param {string} [pluginName='unknown']
|
|
846
|
+
*/
|
|
847
|
+
registerClientController(name, filePath, pluginName = 'unknown') {
|
|
848
|
+
const cleanName = typeof name === 'string' ? name.trim() : '';
|
|
849
|
+
const cleanPath = typeof filePath === 'string' ? filePath.trim() : '';
|
|
850
|
+
if (!cleanName || !cleanPath) {
|
|
851
|
+
throw new TypeError(`[plugin-manager] registerClientController(name, filePath) requires non-empty strings (plugin "${pluginName}").`);
|
|
852
|
+
}
|
|
853
|
+
const existing = this.clientControllers.get(cleanName);
|
|
854
|
+
if (existing && existing.filePath !== cleanPath) {
|
|
855
|
+
const err = new Error(
|
|
856
|
+
`Client controller name collision: "${cleanName}" is registered by both plugin "${existing.pluginName}" (${existing.filePath}) and "${pluginName}" (${cleanPath}).`
|
|
857
|
+
);
|
|
858
|
+
err.code = 'PLUGIN_CLIENT_CONTROLLER_COLLISION';
|
|
859
|
+
throw err;
|
|
860
|
+
}
|
|
861
|
+
this.clientControllers.set(cleanName, {
|
|
862
|
+
name: cleanName,
|
|
863
|
+
filePath: cleanPath,
|
|
864
|
+
pluginName,
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
/**
|
|
869
|
+
* Register a client service module from a plugin.
|
|
870
|
+
* @param {string} name
|
|
871
|
+
* @param {string} filePath
|
|
872
|
+
* @param {string} [pluginName='unknown']
|
|
873
|
+
*/
|
|
874
|
+
registerClientService(name, filePath, pluginName = 'unknown') {
|
|
875
|
+
const cleanName = typeof name === 'string' ? name.trim() : '';
|
|
876
|
+
const cleanPath = typeof filePath === 'string' ? filePath.trim() : '';
|
|
877
|
+
if (!cleanName || !cleanPath) {
|
|
878
|
+
throw new TypeError(`[plugin-manager] registerClientService(name, filePath) requires non-empty strings (plugin "${pluginName}").`);
|
|
879
|
+
}
|
|
880
|
+
const existing = this.clientServices.get(cleanName);
|
|
881
|
+
if (existing && existing.filePath !== cleanPath) {
|
|
882
|
+
const err = new Error(
|
|
883
|
+
`Client service name collision: "${cleanName}" is registered by both plugin "${existing.pluginName}" (${existing.filePath}) and "${pluginName}" (${cleanPath}).`
|
|
884
|
+
);
|
|
885
|
+
err.code = 'PLUGIN_CLIENT_SERVICE_COLLISION';
|
|
886
|
+
throw err;
|
|
887
|
+
}
|
|
888
|
+
this.clientServices.set(cleanName, {
|
|
889
|
+
name: cleanName,
|
|
890
|
+
filePath: cleanPath,
|
|
891
|
+
pluginName,
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
/**
|
|
896
|
+
* Register a reusable SSR fragment definition from a plugin.
|
|
897
|
+
* @param {string} name
|
|
898
|
+
* @param {Object} fragmentDef
|
|
899
|
+
* @param {string} [pluginName='unknown']
|
|
900
|
+
*/
|
|
901
|
+
registerFragment(name, fragmentDef, pluginName = 'unknown') {
|
|
902
|
+
const cleanName = typeof name === 'string' ? name.trim() : '';
|
|
903
|
+
if (!cleanName || !fragmentDef || typeof fragmentDef !== 'object') {
|
|
904
|
+
throw new TypeError(`[plugin-manager] registerFragment(name, fragmentDef) requires a valid fragment name and definition (plugin "${pluginName}").`);
|
|
905
|
+
}
|
|
906
|
+
if (this.registeredFragments.has(cleanName)) {
|
|
907
|
+
console.warn(`[plugin-manager] Fragment "${cleanName}" is already registered (registered by "${pluginName}" overwrites existing fragment).`);
|
|
908
|
+
}
|
|
909
|
+
this.registeredFragments.set(cleanName, {
|
|
910
|
+
name: cleanName,
|
|
911
|
+
def: fragmentDef,
|
|
912
|
+
pluginName,
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
/**
|
|
917
|
+
* Get all client controllers registered by plugins.
|
|
918
|
+
* @returns {Array<{ name: string, filePath: string, pluginName: string }>}
|
|
919
|
+
*/
|
|
920
|
+
getClientControllers() {
|
|
921
|
+
return Array.from(this.clientControllers.values());
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
/**
|
|
925
|
+
* Get all client services registered by plugins.
|
|
926
|
+
* @returns {Array<{ name: string, filePath: string, pluginName: string }>}
|
|
927
|
+
*/
|
|
928
|
+
getClientServices() {
|
|
929
|
+
return Array.from(this.clientServices.values());
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
/**
|
|
933
|
+
* Look up a plugin-registered fragment definition by name.
|
|
934
|
+
* @param {string} name
|
|
935
|
+
* @returns {Object|null}
|
|
936
|
+
*/
|
|
937
|
+
getFragment(name) {
|
|
938
|
+
const entry = this.registeredFragments.get(name);
|
|
939
|
+
return entry ? entry.def : null;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
/**
|
|
943
|
+
* Get all plugin-registered fragments as a plain map `{ [name]: fragmentDef }`.
|
|
944
|
+
* @returns {Record<string, Object>}
|
|
945
|
+
*/
|
|
946
|
+
getFragments() {
|
|
947
|
+
const out = {};
|
|
948
|
+
for (const [name, entry] of this.registeredFragments) {
|
|
949
|
+
out[name] = entry.def;
|
|
950
|
+
}
|
|
951
|
+
return out;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
/**
|
|
955
|
+
* Get combined client manifest of controllers, services, and fragment names registered by plugins.
|
|
956
|
+
*/
|
|
957
|
+
getClientManifest() {
|
|
958
|
+
return {
|
|
959
|
+
controllers: this.getClientControllers(),
|
|
960
|
+
services: this.getClientServices(),
|
|
961
|
+
fragments: Array.from(this.registeredFragments.keys()),
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
|
|
811
965
|
/**
|
|
812
966
|
* Register a request-scoped helper provider (called on each SSR request)
|
|
813
967
|
* @param {Function} fn - (req) => Record<string, any>
|
|
@@ -293,7 +293,18 @@ function validateServiceInput(schema, input, serviceName, options = {}) {
|
|
|
293
293
|
// 1. Zod schema validation
|
|
294
294
|
if (compiled instanceof z.ZodType || typeof compiled.safeParse === 'function') {
|
|
295
295
|
try {
|
|
296
|
-
|
|
296
|
+
let result;
|
|
297
|
+
if (compiled._zod && typeof compiled._zod.run === 'function') {
|
|
298
|
+
const rawRun = compiled._zod.run({ value: sanitized, issues: [] }, { async: true });
|
|
299
|
+
if (rawRun instanceof Promise) {
|
|
300
|
+
// Attach no-op catch so async refinements rejected during sync preflight don't emit UnhandledRejection
|
|
301
|
+
rawRun.catch(() => {});
|
|
302
|
+
throw new Error('Encountered Promise during synchronous parse');
|
|
303
|
+
}
|
|
304
|
+
result = rawRun.issues.length ? compiled.safeParse(sanitized) : { success: true, data: rawRun.value };
|
|
305
|
+
} else {
|
|
306
|
+
result = compiled.safeParse(sanitized);
|
|
307
|
+
}
|
|
297
308
|
if (!result.success) {
|
|
298
309
|
const { message, fields, fieldErrors } = formatZodErrors(result.error, serviceName);
|
|
299
310
|
throw createServiceValidationError(message, {
|
package/core/validation/apply.js
CHANGED
package/core/validation/index.js
CHANGED
|
@@ -28,6 +28,31 @@ if (z.ZodType && z.ZodType.prototype && typeof z.ZodType.prototype._refinement =
|
|
|
28
28
|
};
|
|
29
29
|
wrappedRefinement.__webspresso_wrapped = true;
|
|
30
30
|
z.ZodType.prototype._refinement = wrappedRefinement;
|
|
31
|
+
} else if (z.ZodType && z.ZodType.prototype && typeof z.ZodType.prototype.refine === 'function' && !z.ZodType.prototype.refine.__webspresso_wrapped) {
|
|
32
|
+
const origRefine = z.ZodType.prototype.refine;
|
|
33
|
+
const wrappedRefine = function (check, params) {
|
|
34
|
+
const wrappedCheck = typeof check === 'function'
|
|
35
|
+
? (val) => {
|
|
36
|
+
const res = check(val);
|
|
37
|
+
if (res && typeof res.catch === 'function') {
|
|
38
|
+
res.catch(() => {});
|
|
39
|
+
}
|
|
40
|
+
return res;
|
|
41
|
+
}
|
|
42
|
+
: check;
|
|
43
|
+
return origRefine.call(this, wrappedCheck, params);
|
|
44
|
+
};
|
|
45
|
+
wrappedRefine.__webspresso_wrapped = true;
|
|
46
|
+
z.ZodType.prototype.refine = wrappedRefine;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (z.ZodError && z.ZodError.prototype && !Object.getOwnPropertyDescriptor(z.ZodError.prototype, 'errors')) {
|
|
50
|
+
Object.defineProperty(z.ZodError.prototype, 'errors', {
|
|
51
|
+
get() {
|
|
52
|
+
return this.issues;
|
|
53
|
+
},
|
|
54
|
+
configurable: true,
|
|
55
|
+
});
|
|
31
56
|
}
|
|
32
57
|
|
|
33
58
|
/**
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod v3 & v4 Introspection Helpers
|
|
3
|
+
* Provides a unified abstraction over Zod v3 (`_def.typeName`) and Zod v4 (`_zod.def.type`)
|
|
4
|
+
* for ORM column inference, TypeGen, API TypeGen, and OpenAPI/MCP JSON Schema generation.
|
|
5
|
+
* @module core/validation/zod-introspect
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const z = require('zod');
|
|
9
|
+
|
|
10
|
+
const V4_TYPE_MAP = Object.freeze({
|
|
11
|
+
string: 'ZodString',
|
|
12
|
+
number: 'ZodNumber',
|
|
13
|
+
boolean: 'ZodBoolean',
|
|
14
|
+
date: 'ZodDate',
|
|
15
|
+
bigint: 'ZodBigInt',
|
|
16
|
+
any: 'ZodAny',
|
|
17
|
+
unknown: 'ZodUnknown',
|
|
18
|
+
never: 'ZodNever',
|
|
19
|
+
undefined: 'ZodUndefined',
|
|
20
|
+
void: 'ZodVoid',
|
|
21
|
+
null: 'ZodNull',
|
|
22
|
+
literal: 'ZodLiteral',
|
|
23
|
+
enum: 'ZodEnum',
|
|
24
|
+
array: 'ZodArray',
|
|
25
|
+
optional: 'ZodOptional',
|
|
26
|
+
nullable: 'ZodNullable',
|
|
27
|
+
default: 'ZodDefault',
|
|
28
|
+
prefault: 'ZodDefault',
|
|
29
|
+
transform: 'ZodEffects',
|
|
30
|
+
pipe: 'ZodPipeline',
|
|
31
|
+
union: 'ZodUnion',
|
|
32
|
+
object: 'ZodObject',
|
|
33
|
+
record: 'ZodRecord',
|
|
34
|
+
tuple: 'ZodTuple',
|
|
35
|
+
set: 'ZodSet',
|
|
36
|
+
map: 'ZodMap',
|
|
37
|
+
nonoptional: 'ZodNonOptional',
|
|
38
|
+
readonly: 'ZodReadonly',
|
|
39
|
+
catch: 'ZodCatch',
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
function getZodDef(schema) {
|
|
43
|
+
if (!schema || typeof schema !== 'object') return null;
|
|
44
|
+
return (schema._zod && schema._zod.def) || schema._def || null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getZodTypeName(schema) {
|
|
48
|
+
const def = getZodDef(schema);
|
|
49
|
+
if (!def) return null;
|
|
50
|
+
if (def.typeName) return def.typeName;
|
|
51
|
+
if (typeof def.type === 'string') {
|
|
52
|
+
return V4_TYPE_MAP[def.type] || null;
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function isZodSchema(val) {
|
|
58
|
+
return Boolean(val && typeof val === 'object' && (val._zod || val._def) && typeof val.safeParse === 'function');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function getZodInner(schema) {
|
|
62
|
+
const def = getZodDef(schema);
|
|
63
|
+
if (!def) return null;
|
|
64
|
+
return def.innerType || def.schema || def.in || null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function getZodPipeOut(schema) {
|
|
68
|
+
const def = getZodDef(schema);
|
|
69
|
+
if (!def) return null;
|
|
70
|
+
return def.out || null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function getZodArrayElement(schema) {
|
|
74
|
+
const def = getZodDef(schema);
|
|
75
|
+
if (!def) return null;
|
|
76
|
+
if (def.element) return def.element;
|
|
77
|
+
if (def.type && typeof def.type === 'object') return def.type;
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function getZodObjectShape(schema) {
|
|
82
|
+
if (!schema || typeof schema !== 'object') return {};
|
|
83
|
+
if (typeof schema.shape === 'object' && schema.shape !== null) return schema.shape;
|
|
84
|
+
const def = getZodDef(schema);
|
|
85
|
+
if (!def) return {};
|
|
86
|
+
if (typeof def.shape === 'function') return def.shape() || {};
|
|
87
|
+
if (typeof def.shape === 'object' && def.shape !== null) return def.shape;
|
|
88
|
+
return {};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function getZodEnumValues(schema) {
|
|
92
|
+
const def = getZodDef(schema);
|
|
93
|
+
if (!def) return [];
|
|
94
|
+
if (Array.isArray(def.values)) return def.values;
|
|
95
|
+
if (def.entries && typeof def.entries === 'object') return Object.values(def.entries);
|
|
96
|
+
if (def.values && typeof def.values === 'object') return Object.values(def.values);
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function getZodLiteralValue(schema) {
|
|
101
|
+
const def = getZodDef(schema);
|
|
102
|
+
if (!def) return undefined;
|
|
103
|
+
if (def.value !== undefined) return def.value;
|
|
104
|
+
if (Array.isArray(def.values)) return def.values[0];
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function isZodInt(schema) {
|
|
109
|
+
const def = getZodDef(schema);
|
|
110
|
+
if (!def) return false;
|
|
111
|
+
if (def.format === 'safeint' || def.format === 'int32' || def.format === 'uint32') return true;
|
|
112
|
+
if (!Array.isArray(def.checks)) return false;
|
|
113
|
+
return def.checks.some((c) => {
|
|
114
|
+
if (!c) return false;
|
|
115
|
+
if (c.kind === 'int') return true;
|
|
116
|
+
const cDef = c._zod && c._zod.def;
|
|
117
|
+
return Boolean(cDef && cDef.check === 'number_format' && (cDef.format === 'safeint' || cDef.format === 'int32'));
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function isZodCoerced(schema) {
|
|
122
|
+
const def = getZodDef(schema);
|
|
123
|
+
return Boolean(def && def.coerce);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function zodToJsonSchemaCompat(schema, options = {}) {
|
|
127
|
+
if (typeof z.toJSONSchema === 'function') {
|
|
128
|
+
try {
|
|
129
|
+
const target = options.target === 'openApi3' ? 'openapi-3.0' : (options.target || 'draft-7');
|
|
130
|
+
const jsonSchema = z.toJSONSchema(schema, {
|
|
131
|
+
target,
|
|
132
|
+
unrepresentable: 'any',
|
|
133
|
+
});
|
|
134
|
+
if (jsonSchema && typeof jsonSchema === 'object') {
|
|
135
|
+
delete jsonSchema.$schema;
|
|
136
|
+
}
|
|
137
|
+
return jsonSchema;
|
|
138
|
+
} catch (_) {
|
|
139
|
+
// Fall through to legacy helper if needed
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const { zodToJsonSchema } = require('zod-to-json-schema');
|
|
143
|
+
return zodToJsonSchema(schema, options);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Apply Zod v3 compatibility shims onto Zod v4 prototypes (idempotent).
|
|
148
|
+
* - Exposes `ZodError.prototype.errors` -> `this.issues`
|
|
149
|
+
*/
|
|
150
|
+
function ensureZodCompat(zodModule = z) {
|
|
151
|
+
if (!zodModule) return;
|
|
152
|
+
|
|
153
|
+
const sampleError = typeof zodModule.string === 'function'
|
|
154
|
+
? zodModule.string().safeParse(123)?.error
|
|
155
|
+
: null;
|
|
156
|
+
const errProtos = [
|
|
157
|
+
sampleError ? Object.getPrototypeOf(sampleError) : null,
|
|
158
|
+
zodModule.ZodError?.prototype,
|
|
159
|
+
];
|
|
160
|
+
for (const errProto of errProtos) {
|
|
161
|
+
if (errProto && !Object.getOwnPropertyDescriptor(errProto, 'errors')) {
|
|
162
|
+
Object.defineProperty(errProto, 'errors', {
|
|
163
|
+
get() {
|
|
164
|
+
return this.issues;
|
|
165
|
+
},
|
|
166
|
+
configurable: true,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
module.exports = {
|
|
173
|
+
getZodDef,
|
|
174
|
+
getZodTypeName,
|
|
175
|
+
isZodSchema,
|
|
176
|
+
getZodInner,
|
|
177
|
+
getZodPipeOut,
|
|
178
|
+
getZodArrayElement,
|
|
179
|
+
getZodObjectShape,
|
|
180
|
+
getZodEnumValues,
|
|
181
|
+
getZodLiteralValue,
|
|
182
|
+
isZodInt,
|
|
183
|
+
isZodCoerced,
|
|
184
|
+
zodToJsonSchemaCompat,
|
|
185
|
+
ensureZodCompat,
|
|
186
|
+
};
|
|
187
|
+
|