ont-run 0.0.5 → 0.0.6

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.
@@ -38,7 +38,7 @@ import type { OntologyConfig, FunctionDefinition, AccessGroupConfig, Environment
38
38
  * });
39
39
  * ```
40
40
  */
41
- export declare function defineOntology<TGroups extends string, TEntities extends string, TFunctions extends Record<string, FunctionDefinition<TGroups, TEntities>>>(config: {
41
+ export declare function defineOntology<TGroups extends string, TEntities extends string, TFunctions extends Record<string, FunctionDefinition<TGroups, TEntities, any, any>>>(config: {
42
42
  name: string;
43
43
  environments: Record<string, EnvironmentConfig>;
44
44
  auth: AuthFunction;
@@ -5,60 +5,30 @@ import type { OntologyConfig } from "./types.js";
5
5
  */
6
6
  export declare const EnvironmentConfigSchema: z.ZodObject<{
7
7
  debug: z.ZodOptional<z.ZodBoolean>;
8
- }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
9
- debug: z.ZodOptional<z.ZodBoolean>;
10
- }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
11
- debug: z.ZodOptional<z.ZodBoolean>;
12
- }, z.ZodTypeAny, "passthrough">>;
8
+ }, z.core.$loose>;
13
9
  /**
14
10
  * Schema for access group configuration
15
11
  */
16
12
  export declare const AccessGroupConfigSchema: z.ZodObject<{
17
13
  description: z.ZodString;
18
- }, "strip", z.ZodTypeAny, {
19
- description: string;
20
- }, {
21
- description: string;
22
- }>;
14
+ }, z.core.$strip>;
23
15
  /**
24
16
  * Schema for entity definition
25
17
  */
26
18
  export declare const EntityDefinitionSchema: z.ZodObject<{
27
19
  description: z.ZodString;
28
- }, "strip", z.ZodTypeAny, {
29
- description: string;
30
- }, {
31
- description: string;
32
- }>;
20
+ }, z.core.$strip>;
33
21
  /**
34
22
  * Schema for function definition
35
23
  */
36
24
  export declare const FunctionDefinitionSchema: z.ZodObject<{
37
25
  description: z.ZodString;
38
- access: z.ZodArray<z.ZodString, "many">;
39
- entities: z.ZodArray<z.ZodString, "many">;
40
- inputs: z.ZodType<z.ZodType<any, z.ZodTypeDef, any>, z.ZodTypeDef, z.ZodType<any, z.ZodTypeDef, any>>;
41
- outputs: z.ZodOptional<z.ZodType<z.ZodType<any, z.ZodTypeDef, any>, z.ZodTypeDef, z.ZodType<any, z.ZodTypeDef, any>>>;
42
- resolver: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodUnknown>;
43
- }, "strip", z.ZodTypeAny, {
44
- description: string;
45
- access: string[];
46
- entities: string[];
47
- inputs: z.ZodType<any, z.ZodTypeDef, any>;
48
- resolver: (...args: unknown[]) => unknown;
49
- outputs?: z.ZodType<any, z.ZodTypeDef, any> | undefined;
50
- }, {
51
- description: string;
52
- access: string[];
53
- entities: string[];
54
- inputs: z.ZodType<any, z.ZodTypeDef, any>;
55
- resolver: (...args: unknown[]) => unknown;
56
- outputs?: z.ZodType<any, z.ZodTypeDef, any> | undefined;
57
- }>;
58
- /**
59
- * Schema for auth function
60
- */
61
- export declare const AuthFunctionSchema: z.ZodFunction<z.ZodTuple<[z.ZodType<Request, z.ZodTypeDef, Request>], z.ZodUnknown>, z.ZodUnion<[z.ZodArray<z.ZodString, "many">, z.ZodPromise<z.ZodArray<z.ZodString, "many">>]>>;
26
+ access: z.ZodArray<z.ZodString>;
27
+ entities: z.ZodArray<z.ZodString>;
28
+ inputs: z.ZodCustom<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
29
+ outputs: z.ZodOptional<z.ZodCustom<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>>;
30
+ resolver: z.ZodCustom<(...args: unknown[]) => unknown, (...args: unknown[]) => unknown>;
31
+ }, z.core.$strip>;
62
32
  /**
63
33
  * Schema for the full ontology configuration
64
34
  */
@@ -66,89 +36,23 @@ export declare const OntologyConfigSchema: z.ZodObject<{
66
36
  name: z.ZodString;
67
37
  environments: z.ZodRecord<z.ZodString, z.ZodObject<{
68
38
  debug: z.ZodOptional<z.ZodBoolean>;
69
- }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
70
- debug: z.ZodOptional<z.ZodBoolean>;
71
- }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
72
- debug: z.ZodOptional<z.ZodBoolean>;
73
- }, z.ZodTypeAny, "passthrough">>>;
74
- auth: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodUnknown>;
39
+ }, z.core.$loose>>;
40
+ auth: z.ZodCustom<(req: Request) => unknown, (req: Request) => unknown>;
75
41
  accessGroups: z.ZodRecord<z.ZodString, z.ZodObject<{
76
42
  description: z.ZodString;
77
- }, "strip", z.ZodTypeAny, {
78
- description: string;
79
- }, {
80
- description: string;
81
- }>>;
43
+ }, z.core.$strip>>;
82
44
  entities: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
83
45
  description: z.ZodString;
84
- }, "strip", z.ZodTypeAny, {
85
- description: string;
86
- }, {
87
- description: string;
88
- }>>>;
46
+ }, z.core.$strip>>>;
89
47
  functions: z.ZodRecord<z.ZodString, z.ZodObject<{
90
48
  description: z.ZodString;
91
- access: z.ZodArray<z.ZodString, "many">;
92
- entities: z.ZodArray<z.ZodString, "many">;
93
- inputs: z.ZodType<z.ZodType<any, z.ZodTypeDef, any>, z.ZodTypeDef, z.ZodType<any, z.ZodTypeDef, any>>;
94
- outputs: z.ZodOptional<z.ZodType<z.ZodType<any, z.ZodTypeDef, any>, z.ZodTypeDef, z.ZodType<any, z.ZodTypeDef, any>>>;
95
- resolver: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodUnknown>;
96
- }, "strip", z.ZodTypeAny, {
97
- description: string;
98
- access: string[];
99
- entities: string[];
100
- inputs: z.ZodType<any, z.ZodTypeDef, any>;
101
- resolver: (...args: unknown[]) => unknown;
102
- outputs?: z.ZodType<any, z.ZodTypeDef, any> | undefined;
103
- }, {
104
- description: string;
105
- access: string[];
106
- entities: string[];
107
- inputs: z.ZodType<any, z.ZodTypeDef, any>;
108
- resolver: (...args: unknown[]) => unknown;
109
- outputs?: z.ZodType<any, z.ZodTypeDef, any> | undefined;
110
- }>>;
111
- }, "strip", z.ZodTypeAny, {
112
- name: string;
113
- environments: Record<string, z.objectOutputType<{
114
- debug: z.ZodOptional<z.ZodBoolean>;
115
- }, z.ZodTypeAny, "passthrough">>;
116
- auth: (...args: unknown[]) => unknown;
117
- accessGroups: Record<string, {
118
- description: string;
119
- }>;
120
- functions: Record<string, {
121
- description: string;
122
- access: string[];
123
- entities: string[];
124
- inputs: z.ZodType<any, z.ZodTypeDef, any>;
125
- resolver: (...args: unknown[]) => unknown;
126
- outputs?: z.ZodType<any, z.ZodTypeDef, any> | undefined;
127
- }>;
128
- entities?: Record<string, {
129
- description: string;
130
- }> | undefined;
131
- }, {
132
- name: string;
133
- environments: Record<string, z.objectInputType<{
134
- debug: z.ZodOptional<z.ZodBoolean>;
135
- }, z.ZodTypeAny, "passthrough">>;
136
- auth: (...args: unknown[]) => unknown;
137
- accessGroups: Record<string, {
138
- description: string;
139
- }>;
140
- functions: Record<string, {
141
- description: string;
142
- access: string[];
143
- entities: string[];
144
- inputs: z.ZodType<any, z.ZodTypeDef, any>;
145
- resolver: (...args: unknown[]) => unknown;
146
- outputs?: z.ZodType<any, z.ZodTypeDef, any> | undefined;
147
- }>;
148
- entities?: Record<string, {
149
- description: string;
150
- }> | undefined;
151
- }>;
49
+ access: z.ZodArray<z.ZodString>;
50
+ entities: z.ZodArray<z.ZodString>;
51
+ inputs: z.ZodCustom<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
52
+ outputs: z.ZodOptional<z.ZodCustom<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>, z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>>;
53
+ resolver: z.ZodCustom<(...args: unknown[]) => unknown, (...args: unknown[]) => unknown>;
54
+ }, z.core.$strip>>;
55
+ }, z.core.$strip>;
152
56
  /**
153
57
  * Validate that all function access groups exist in accessGroups
154
58
  */
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Utility functions for inspecting Zod schemas.
3
+ * These use duck typing to work across Zod 3 and Zod 4.
4
+ */
5
+ /**
6
+ * Check if a value is a Zod schema (duck typing to work across bundle boundaries and Zod versions)
7
+ */
8
+ export declare function isZodSchema(val: unknown): boolean;
9
+ /**
10
+ * Get the Zod type name from a schema (works with both Zod 3 and 4)
11
+ */
12
+ export declare function getZodTypeName(schema: unknown): string | undefined;
13
+ /**
14
+ * Check if schema is a ZodObject (works with both Zod 3 and 4)
15
+ */
16
+ export declare function isZodObject(schema: unknown): boolean;
17
+ /**
18
+ * Check if schema is a ZodOptional (works with both Zod 3 and 4)
19
+ */
20
+ export declare function isZodOptional(schema: unknown): boolean;
21
+ /**
22
+ * Check if schema is a ZodNullable (works with both Zod 3 and 4)
23
+ */
24
+ export declare function isZodNullable(schema: unknown): boolean;
25
+ /**
26
+ * Check if schema is a ZodArray (works with both Zod 3 and 4)
27
+ */
28
+ export declare function isZodArray(schema: unknown): boolean;
29
+ /**
30
+ * Check if schema is a ZodDefault (works with both Zod 3 and 4)
31
+ */
32
+ export declare function isZodDefault(schema: unknown): boolean;
33
+ /**
34
+ * Get the shape of a ZodObject schema (works with both Zod 3 and 4)
35
+ */
36
+ export declare function getObjectShape(schema: unknown): Record<string, unknown> | undefined;
37
+ /**
38
+ * Get the inner schema from Optional/Nullable/Default (works with both Zod 3 and 4)
39
+ */
40
+ export declare function getInnerSchema(schema: unknown): unknown;
41
+ /**
42
+ * Get the element schema from a ZodArray (works with both Zod 3 and 4)
43
+ */
44
+ export declare function getArrayElement(schema: unknown): unknown;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ont-run",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "description": "Ontology-enforced API framework for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -35,8 +35,7 @@
35
35
  "consola": "^3.2.0",
36
36
  "hono": "^4.6.0",
37
37
  "open": "^10.0.0",
38
- "zod": "^3.24.0",
39
- "zod-to-json-schema": "^3.23.0"
38
+ "zod": "4"
40
39
  },
41
40
  "devDependencies": {
42
41
  "@types/bun": "latest",
@@ -1,8 +1,17 @@
1
1
  import { z } from "zod";
2
- import { zodToJsonSchema } from "zod-to-json-schema";
3
2
  import type { OntologyConfig } from "../config/types.js";
4
3
  import type { OntologyDiff, FunctionChange } from "../lockfile/types.js";
5
4
  import { getFieldFromMetadata, getUserContextFields } from "../config/categorical.js";
5
+ import {
6
+ isZodObject,
7
+ isZodOptional,
8
+ isZodNullable,
9
+ isZodArray,
10
+ isZodDefault,
11
+ getObjectShape,
12
+ getInnerSchema,
13
+ getArrayElement,
14
+ } from "../config/zod-utils.js";
6
15
 
7
16
  export type NodeType = "entity" | "function" | "accessGroup";
8
17
  export type EdgeType = "operates-on" | "requires-access" | "depends-on";
@@ -80,30 +89,44 @@ function extractFieldReferences(
80
89
  });
81
90
  }
82
91
 
83
- if (schema instanceof z.ZodObject) {
84
- const shape = schema.shape;
85
- for (const [key, value] of Object.entries(shape)) {
86
- const fieldPath = path ? `${path}.${key}` : key;
87
- results.push(
88
- ...extractFieldReferences(value as z.ZodType<unknown>, fieldPath)
89
- );
92
+ if (isZodObject(schema)) {
93
+ const shape = getObjectShape(schema);
94
+ if (shape) {
95
+ for (const [key, value] of Object.entries(shape)) {
96
+ const fieldPath = path ? `${path}.${key}` : key;
97
+ results.push(
98
+ ...extractFieldReferences(value as z.ZodType<unknown>, fieldPath)
99
+ );
100
+ }
90
101
  }
91
102
  }
92
103
 
93
- if (schema instanceof z.ZodOptional) {
94
- results.push(...extractFieldReferences(schema.unwrap(), path));
104
+ if (isZodOptional(schema)) {
105
+ const inner = getInnerSchema(schema);
106
+ if (inner) {
107
+ results.push(...extractFieldReferences(inner as z.ZodType<unknown>, path));
108
+ }
95
109
  }
96
110
 
97
- if (schema instanceof z.ZodNullable) {
98
- results.push(...extractFieldReferences(schema.unwrap(), path));
111
+ if (isZodNullable(schema)) {
112
+ const inner = getInnerSchema(schema);
113
+ if (inner) {
114
+ results.push(...extractFieldReferences(inner as z.ZodType<unknown>, path));
115
+ }
99
116
  }
100
117
 
101
- if (schema instanceof z.ZodArray) {
102
- results.push(...extractFieldReferences(schema.element, `${path}[]`));
118
+ if (isZodArray(schema)) {
119
+ const element = getArrayElement(schema);
120
+ if (element) {
121
+ results.push(...extractFieldReferences(element as z.ZodType<unknown>, `${path}[]`));
122
+ }
103
123
  }
104
124
 
105
- if (schema instanceof z.ZodDefault) {
106
- results.push(...extractFieldReferences(schema._def.innerType, path));
125
+ if (isZodDefault(schema)) {
126
+ const inner = getInnerSchema(schema);
127
+ if (inner) {
128
+ results.push(...extractFieldReferences(inner as z.ZodType<unknown>, path));
129
+ }
107
130
  }
108
131
 
109
132
  return results;
@@ -114,7 +137,7 @@ function extractFieldReferences(
114
137
  */
115
138
  function safeZodToJsonSchema(schema: z.ZodTypeAny): Record<string, unknown> | undefined {
116
139
  try {
117
- const result = zodToJsonSchema(schema, { $refStrategy: "none" }) as Record<string, unknown>;
140
+ const result = z.toJSONSchema(schema, { reused: "inline", unrepresentable: "any" }) as Record<string, unknown>;
118
141
  delete result.$schema;
119
142
  return result;
120
143
  } catch {
@@ -54,6 +54,11 @@ export const initCommand = defineCommand({
54
54
  const configTemplate = `import { defineOntology, userContext } from 'ont-run';
55
55
  import { z } from 'zod';
56
56
 
57
+ // Import resolver functions - TypeScript enforces return types match outputs
58
+ import healthCheck from './resolvers/healthCheck.js';
59
+ import getUser from './resolvers/getUser.js';
60
+ import deleteUser from './resolvers/deleteUser.js';
61
+
57
62
  export default defineOntology({
58
63
  name: 'my-api',
59
64
 
@@ -98,7 +103,7 @@ export default defineOntology({
98
103
  access: ['public', 'support', 'admin'],
99
104
  entities: [],
100
105
  inputs: z.object({}),
101
- resolver: './resolvers/healthCheck.ts',
106
+ resolver: healthCheck,
102
107
  },
103
108
 
104
109
  // Example: Restricted function with row-level access
@@ -114,7 +119,7 @@ export default defineOntology({
114
119
  email: z.string(),
115
120
  })),
116
121
  }),
117
- resolver: './resolvers/getUser.ts',
122
+ resolver: getUser,
118
123
  },
119
124
 
120
125
  // Example: Admin-only function
@@ -126,7 +131,7 @@ export default defineOntology({
126
131
  userId: z.string().uuid(),
127
132
  reason: z.string().optional(),
128
133
  }),
129
- resolver: './resolvers/deleteUser.ts',
134
+ resolver: deleteUser,
130
135
  },
131
136
  },
132
137
  });
@@ -138,7 +143,7 @@ export default defineOntology({
138
143
  // Write example resolvers
139
144
  const healthCheckResolver = `import type { ResolverContext } from 'ont-run';
140
145
 
141
- export default async function healthCheck(ctx: ResolverContext, args: {}) {
146
+ export default async function healthCheck(ctx: ResolverContext) {
142
147
  ctx.logger.info('Health check called');
143
148
 
144
149
  return {
@@ -237,7 +242,7 @@ await startOnt();
237
242
  packageJson.dependencies = {
238
243
  ...(packageJson.dependencies as Record<string, string> || {}),
239
244
  "ont-run": "latest",
240
- zod: "^3.24.0",
245
+ zod: "^4.0.0",
241
246
  };
242
247
 
243
248
  writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
@@ -56,7 +56,9 @@ import type {
56
56
  export function defineOntology<
57
57
  TGroups extends string,
58
58
  TEntities extends string,
59
- TFunctions extends Record<string, FunctionDefinition<TGroups, TEntities>>,
59
+ // Use `any` for input/output schema types to avoid contravariance issues with resolver functions.
60
+ // Without this, ResolverFunction<unknown, unknown> won't accept more specific resolver types.
61
+ TFunctions extends Record<string, FunctionDefinition<TGroups, TEntities, any, any>>,
60
62
  >(config: {
61
63
  name: string;
62
64
  environments: Record<string, EnvironmentConfig>;
@@ -1,5 +1,16 @@
1
1
  import { z } from "zod";
2
2
  import { getFieldFromMetadata, getUserContextFields } from "./categorical.js";
3
+ import {
4
+ isZodSchema,
5
+ isZodObject,
6
+ isZodOptional,
7
+ isZodNullable,
8
+ isZodArray,
9
+ isZodDefault,
10
+ getObjectShape,
11
+ getInnerSchema,
12
+ getArrayElement,
13
+ } from "./zod-utils.js";
3
14
  import type { OntologyConfig } from "./types.js";
4
15
 
5
16
  /**
@@ -26,16 +37,10 @@ export const EntityDefinitionSchema = z.object({
26
37
  });
27
38
 
28
39
  /**
29
- * Check if a value is a Zod schema (duck typing to work across bundle boundaries)
40
+ * Check if a value is a function
30
41
  */
31
- function isZodSchema(val: unknown): boolean {
32
- return (
33
- val !== null &&
34
- typeof val === "object" &&
35
- "_def" in val &&
36
- "safeParse" in val &&
37
- typeof (val as { safeParse: unknown }).safeParse === "function"
38
- );
42
+ function isFunction(val: unknown): val is (...args: unknown[]) => unknown {
43
+ return typeof val === "function";
39
44
  }
40
45
 
41
46
  /**
@@ -53,24 +58,20 @@ export const FunctionDefinitionSchema = z.object({
53
58
  message: "outputs must be a Zod schema",
54
59
  })
55
60
  .optional(),
56
- resolver: z.function(),
61
+ resolver: z.custom<(...args: unknown[]) => unknown>(isFunction, {
62
+ message: "resolver must be a function",
63
+ }),
57
64
  });
58
65
 
59
- /**
60
- * Schema for auth function
61
- */
62
- export const AuthFunctionSchema = z
63
- .function()
64
- .args(z.custom<Request>())
65
- .returns(z.union([z.array(z.string()), z.promise(z.array(z.string()))]));
66
-
67
66
  /**
68
67
  * Schema for the full ontology configuration
69
68
  */
70
69
  export const OntologyConfigSchema = z.object({
71
70
  name: z.string().min(1),
72
71
  environments: z.record(z.string(), EnvironmentConfigSchema),
73
- auth: z.function(),
72
+ auth: z.custom<(req: Request) => unknown>(isFunction, {
73
+ message: "auth must be a function",
74
+ }),
74
75
  accessGroups: z.record(z.string(), AccessGroupConfigSchema),
75
76
  entities: z.record(z.string(), EntityDefinitionSchema).optional(),
76
77
  functions: z.record(z.string(), FunctionDefinitionSchema),
@@ -141,34 +142,48 @@ function extractFieldFromRefs(
141
142
  }
142
143
 
143
144
  // Handle ZodObject - recurse into properties
144
- if (schema instanceof z.ZodObject) {
145
- const shape = schema.shape;
146
- for (const [key, value] of Object.entries(shape)) {
147
- const fieldPath = path ? `${path}.${key}` : key;
148
- results.push(
149
- ...extractFieldFromRefs(value as z.ZodType<unknown>, fieldPath)
150
- );
145
+ if (isZodObject(schema)) {
146
+ const shape = getObjectShape(schema);
147
+ if (shape) {
148
+ for (const [key, value] of Object.entries(shape)) {
149
+ const fieldPath = path ? `${path}.${key}` : key;
150
+ results.push(
151
+ ...extractFieldFromRefs(value as z.ZodType<unknown>, fieldPath)
152
+ );
153
+ }
151
154
  }
152
155
  }
153
156
 
154
157
  // Handle ZodOptional - unwrap
155
- if (schema instanceof z.ZodOptional) {
156
- results.push(...extractFieldFromRefs(schema.unwrap(), path));
158
+ if (isZodOptional(schema)) {
159
+ const inner = getInnerSchema(schema);
160
+ if (inner) {
161
+ results.push(...extractFieldFromRefs(inner as z.ZodType<unknown>, path));
162
+ }
157
163
  }
158
164
 
159
165
  // Handle ZodNullable - unwrap
160
- if (schema instanceof z.ZodNullable) {
161
- results.push(...extractFieldFromRefs(schema.unwrap(), path));
166
+ if (isZodNullable(schema)) {
167
+ const inner = getInnerSchema(schema);
168
+ if (inner) {
169
+ results.push(...extractFieldFromRefs(inner as z.ZodType<unknown>, path));
170
+ }
162
171
  }
163
172
 
164
173
  // Handle ZodArray - recurse into element
165
- if (schema instanceof z.ZodArray) {
166
- results.push(...extractFieldFromRefs(schema.element, `${path}[]`));
174
+ if (isZodArray(schema)) {
175
+ const element = getArrayElement(schema);
176
+ if (element) {
177
+ results.push(...extractFieldFromRefs(element as z.ZodType<unknown>, `${path}[]`));
178
+ }
167
179
  }
168
180
 
169
181
  // Handle ZodDefault - unwrap
170
- if (schema instanceof z.ZodDefault) {
171
- results.push(...extractFieldFromRefs(schema._def.innerType, path));
182
+ if (isZodDefault(schema)) {
183
+ const inner = getInnerSchema(schema);
184
+ if (inner) {
185
+ results.push(...extractFieldFromRefs(inner as z.ZodType<unknown>, path));
186
+ }
172
187
  }
173
188
 
174
189
  return results;
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Utility functions for inspecting Zod schemas.
3
+ * These use duck typing to work across Zod 3 and Zod 4.
4
+ */
5
+
6
+ /**
7
+ * Check if a value is a Zod schema (duck typing to work across bundle boundaries and Zod versions)
8
+ */
9
+ export function isZodSchema(val: unknown): boolean {
10
+ if (val === null || typeof val !== "object") return false;
11
+ // Zod 4 uses _zod property
12
+ if ("_zod" in val && "safeParse" in val) return true;
13
+ // Zod 3 uses _def property
14
+ if ("_def" in val && "safeParse" in val) return true;
15
+ return false;
16
+ }
17
+
18
+ /**
19
+ * Get the Zod type name from a schema (works with both Zod 3 and 4)
20
+ */
21
+ export function getZodTypeName(schema: unknown): string | undefined {
22
+ if (!isZodSchema(schema)) return undefined;
23
+ const s = schema as Record<string, unknown>;
24
+ // Zod 4: check _zod.def.typeName
25
+ if (s._zod && typeof s._zod === "object") {
26
+ const zod = s._zod as Record<string, unknown>;
27
+ if (zod.def && typeof zod.def === "object") {
28
+ const def = zod.def as Record<string, unknown>;
29
+ if (typeof def.typeName === "string") return def.typeName;
30
+ }
31
+ }
32
+ // Zod 3: check _def.typeName
33
+ if (s._def && typeof s._def === "object") {
34
+ const def = s._def as Record<string, unknown>;
35
+ if (typeof def.typeName === "string") return def.typeName;
36
+ }
37
+ return undefined;
38
+ }
39
+
40
+ /**
41
+ * Check if schema is a ZodObject (works with both Zod 3 and 4)
42
+ */
43
+ export function isZodObject(schema: unknown): boolean {
44
+ const typeName = getZodTypeName(schema);
45
+ return typeName === "ZodObject" || typeName === "object";
46
+ }
47
+
48
+ /**
49
+ * Check if schema is a ZodOptional (works with both Zod 3 and 4)
50
+ */
51
+ export function isZodOptional(schema: unknown): boolean {
52
+ const typeName = getZodTypeName(schema);
53
+ return typeName === "ZodOptional" || typeName === "optional";
54
+ }
55
+
56
+ /**
57
+ * Check if schema is a ZodNullable (works with both Zod 3 and 4)
58
+ */
59
+ export function isZodNullable(schema: unknown): boolean {
60
+ const typeName = getZodTypeName(schema);
61
+ return typeName === "ZodNullable" || typeName === "nullable";
62
+ }
63
+
64
+ /**
65
+ * Check if schema is a ZodArray (works with both Zod 3 and 4)
66
+ */
67
+ export function isZodArray(schema: unknown): boolean {
68
+ const typeName = getZodTypeName(schema);
69
+ return typeName === "ZodArray" || typeName === "array";
70
+ }
71
+
72
+ /**
73
+ * Check if schema is a ZodDefault (works with both Zod 3 and 4)
74
+ */
75
+ export function isZodDefault(schema: unknown): boolean {
76
+ const typeName = getZodTypeName(schema);
77
+ return typeName === "ZodDefault" || typeName === "default";
78
+ }
79
+
80
+ /**
81
+ * Get the shape of a ZodObject schema (works with both Zod 3 and 4)
82
+ */
83
+ export function getObjectShape(schema: unknown): Record<string, unknown> | undefined {
84
+ if (!isZodObject(schema)) return undefined;
85
+ const s = schema as Record<string, unknown>;
86
+ // Zod 4: shape is in _zod.def.shape
87
+ if (s._zod && typeof s._zod === "object") {
88
+ const zod = s._zod as Record<string, unknown>;
89
+ if (zod.def && typeof zod.def === "object") {
90
+ const def = zod.def as Record<string, unknown>;
91
+ if (def.shape && typeof def.shape === "object") {
92
+ return def.shape as Record<string, unknown>;
93
+ }
94
+ }
95
+ }
96
+ // Zod 3: shape property directly on schema
97
+ if (typeof s.shape === "object" && s.shape !== null) {
98
+ return s.shape as Record<string, unknown>;
99
+ }
100
+ return undefined;
101
+ }
102
+
103
+ /**
104
+ * Get the inner schema from Optional/Nullable/Default (works with both Zod 3 and 4)
105
+ */
106
+ export function getInnerSchema(schema: unknown): unknown {
107
+ const s = schema as Record<string, unknown>;
108
+ // Zod 4: innerType is in _zod.def.innerType
109
+ if (s._zod && typeof s._zod === "object") {
110
+ const zod = s._zod as Record<string, unknown>;
111
+ if (zod.def && typeof zod.def === "object") {
112
+ const def = zod.def as Record<string, unknown>;
113
+ if (def.innerType) return def.innerType;
114
+ }
115
+ }
116
+ // Zod 3: unwrap method or _def.innerType
117
+ if (typeof s.unwrap === "function") {
118
+ return (s.unwrap as () => unknown)();
119
+ }
120
+ if (s._def && typeof s._def === "object") {
121
+ const def = s._def as Record<string, unknown>;
122
+ if (def.innerType) return def.innerType;
123
+ }
124
+ return undefined;
125
+ }
126
+
127
+ /**
128
+ * Get the element schema from a ZodArray (works with both Zod 3 and 4)
129
+ */
130
+ export function getArrayElement(schema: unknown): unknown {
131
+ if (!isZodArray(schema)) return undefined;
132
+ const s = schema as Record<string, unknown>;
133
+ // Zod 4: element is in _zod.def.element
134
+ if (s._zod && typeof s._zod === "object") {
135
+ const zod = s._zod as Record<string, unknown>;
136
+ if (zod.def && typeof zod.def === "object") {
137
+ const def = zod.def as Record<string, unknown>;
138
+ if (def.element) return def.element;
139
+ }
140
+ }
141
+ // Zod 3: element property directly on schema
142
+ if (s.element) return s.element;
143
+ return undefined;
144
+ }