@buildnbuzz/buzzform 0.1.0 → 0.1.1

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.
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { F as Field, V as ValidationContext, K as FieldType, A as ArrayHelpers } from './adapter-BT9v2OVg.js';
2
+ import { F as Field, T as TextField, E as EmailField, P as PasswordField, o as TextareaField, N as NumberField, D as DateField, p as DatetimeField, r as SelectField, s as CheckboxField, t as SwitchField, u as RadioField, w as TagsField, x as UploadField, G as GroupField, y as ArrayField, z as RowField, I as TabsField, J as CollapsibleField, V as ValidationContext, K as FieldType, i as ValidationFn, A as ArrayHelpers } from './adapter-nQW28cyO.mjs';
3
3
 
4
4
  /**
5
5
  * Maps a BuzzForm field definition to its corresponding Zod type.
@@ -105,85 +105,98 @@ type SchemaBuilderMap = {
105
105
  type InferSchema<T extends z.ZodTypeAny> = z.infer<T>;
106
106
 
107
107
  /**
108
- * Creates a Zod schema from an array of field definitions.
109
- *
110
- * Provides full intellisense when writing field definitions inline.
111
- * The returned schema has `.fields` attached for use in rendering.
108
+ * Strict field typing utilities for compile-time validation.
109
+ */
110
+
111
+ /**
112
+ * Maps field type literals to their corresponding interface.
113
+ */
114
+ interface FieldTypeMap {
115
+ text: TextField;
116
+ email: EmailField;
117
+ password: PasswordField;
118
+ textarea: TextareaField;
119
+ number: NumberField;
120
+ date: DateField;
121
+ datetime: DatetimeField;
122
+ select: SelectField;
123
+ checkbox: CheckboxField;
124
+ switch: SwitchField;
125
+ radio: RadioField;
126
+ tags: TagsField;
127
+ upload: UploadField;
128
+ group: GroupField;
129
+ array: ArrayField;
130
+ row: RowField;
131
+ tabs: TabsField;
132
+ collapsible: CollapsibleField;
133
+ }
134
+ type FieldTypeLiteral = keyof FieldTypeMap;
135
+ /**
136
+ * Resolves a field object to its strict interface based on the `type` property.
137
+ */
138
+ type StrictFieldByType<T> = T extends {
139
+ type: infer Type extends FieldTypeLiteral;
140
+ } ? FieldTypeMap[Type] : T extends Field ? T : never;
141
+ /**
142
+ * Validates an array of fields, ensuring each matches its declared type's interface.
143
+ */
144
+ type StrictFieldArray<T extends readonly unknown[]> = {
145
+ [K in keyof T]: StrictFieldByType<T[K]>;
146
+ };
147
+
148
+ /**
149
+ * Creates a Zod schema from field definitions with strict type validation.
112
150
  *
113
151
  * @example
114
- * const loginSchema = createSchema([
115
- * { type: 'email', name: 'email', required: true }, // ← Full intellisense!
152
+ * const schema = createSchema([
153
+ * { type: 'email', name: 'email', required: true },
116
154
  * { type: 'password', name: 'password', minLength: 8 },
117
155
  * ]);
118
156
  *
119
- * type LoginData = z.infer<typeof loginSchema>;
120
- * // { email: string; password: string }
121
- *
122
- * // Use with useForm:
123
- * const form = useForm({ schema: loginSchema });
124
- *
125
- * // Access fields for rendering:
126
- * <FormRenderer fields={loginSchema.fields} />
157
+ * type FormData = z.infer<typeof schema>;
127
158
  */
128
- declare function createSchema<const T extends readonly Field[]>(fields: [...{
129
- [K in keyof T]: T[K] extends Field ? T[K] : Field;
130
- }]): z.ZodObject<FieldsToShape<T>> & {
159
+ declare function createSchema<const T extends readonly Field[]>(fields: StrictFieldArray<T> & T): z.ZodObject<FieldsToShape<T>> & {
131
160
  fields: T;
132
161
  };
133
162
 
134
163
  /**
135
- * Converts an array of field definitions to a Zod object schema.
136
- * Handles all field types including layouts and nested structures.
164
+ * Converts field definitions to a Zod schema.
165
+ *
166
+ * Note: Custom validation (field.validate) is handled by the zodResolver,
167
+ * not at the schema level. This ensures custom validators run even when
168
+ * other fields have errors.
137
169
  */
138
170
  declare function fieldsToZodSchema<T extends readonly Field[]>(fields: T): z.ZodObject<FieldsToShape<T>>;
139
171
 
140
- /**
141
- * A validation function that can be extracted from a field.
142
- */
143
172
  type ExtractableValidationFn = (value: unknown, context: ValidationContext) => true | string | Promise<true | string>;
144
173
  interface ExtractedValidationConfig {
145
- /** The validation function (if any) */
146
174
  fn?: ExtractableValidationFn;
147
- /** Whether this is a live validation */
148
175
  isLive: boolean;
149
- /** Debounce milliseconds for live validation */
150
176
  debounceMs?: number;
151
177
  }
152
- /**
153
- * Extracts validation function and config from the unified validate property.
154
- */
155
178
  declare function extractValidationConfig(validate?: unknown): ExtractedValidationConfig;
179
+ interface FieldValidator {
180
+ path: string;
181
+ fn: ValidationFn;
182
+ }
156
183
  /**
157
- * Applies custom validation from field.validate to a Zod schema.
158
- * Standardizes the pattern across all field types.
184
+ * Recursively collects all field validators from a field array.
159
185
  */
160
- declare function applyCustomValidation(schema: z.ZodTypeAny, field: Field, fieldPath?: string): z.ZodTypeAny;
186
+ declare function collectFieldValidators(fields: readonly Field[], basePath?: string): FieldValidator[];
161
187
  /**
162
- * Makes a schema optional based on field type.
163
- * Different field types have different "empty" representations.
188
+ * Gets the parent object containing the field at the given path.
164
189
  */
165
- declare function makeOptional(schema: z.ZodTypeAny, fieldType: FieldType): z.ZodTypeAny;
190
+ declare function getSiblingData(data: Record<string, unknown>, path: string): Record<string, unknown>;
166
191
  /**
167
- * Coerces a value to a number.
168
- * Empty/null/undefined → undefined, otherwise Number().
192
+ * Gets a value at a dot-notation path.
169
193
  */
194
+ declare function getValueByPath(data: Record<string, unknown>, path: string): unknown;
195
+ declare function makeOptional(schema: z.ZodTypeAny, fieldType: FieldType): z.ZodTypeAny;
170
196
  declare function coerceToNumber(val: unknown): number | undefined;
171
- /**
172
- * Coerces a value to a Date.
173
- * Handles strings, numbers, and Date objects.
174
- */
175
197
  declare function coerceToDate(val: unknown): Date | undefined;
176
- /**
177
- * Gets a human-readable error message for a regex pattern.
178
- */
179
198
  declare function getPatternErrorMessage(pattern: string | RegExp): string;
180
- /**
181
- * Checks if a value is a File-like object.
182
- */
183
199
  declare function isFileLike(value: unknown): value is File;
184
- /**
185
- * Validates file type against accept pattern.
186
- */
187
200
  declare function isFileTypeAccepted(file: File, accept: string): boolean;
188
201
 
189
202
  /**
@@ -230,4 +243,4 @@ declare function setNestedValue<T extends Record<string, unknown>>(obj: T, path:
230
243
  */
231
244
  declare function formatBytes(bytes: number, decimals?: number): string;
232
245
 
233
- export { type FieldToZod as F, type InferSchema as I, type SchemaBuilder as S, type FieldsToShape as a, type SchemaBuilderMap as b, createSchema as c, applyCustomValidation as d, extractValidationConfig as e, fieldsToZodSchema as f, coerceToNumber as g, coerceToDate as h, getPatternErrorMessage as i, isFileLike as j, isFileTypeAccepted as k, createArrayHelpers as l, makeOptional as m, generateFieldId as n, getNestedValue as o, formatBytes as p, setNestedValue as s };
246
+ export { type FieldToZod as F, type InferSchema as I, type SchemaBuilder as S, type FieldsToShape as a, type SchemaBuilderMap as b, createSchema as c, coerceToNumber as d, extractValidationConfig as e, fieldsToZodSchema as f, coerceToDate as g, getPatternErrorMessage as h, isFileLike as i, isFileTypeAccepted as j, createArrayHelpers as k, generateFieldId as l, makeOptional as m, getNestedValue as n, formatBytes as o, collectFieldValidators as p, getSiblingData as q, getValueByPath as r, setNestedValue as s, type FieldValidator as t };
package/dist/zod.d.mts CHANGED
@@ -1,32 +1,14 @@
1
- import { ZodSchema } from 'zod';
2
- export { ZodSchema, z } from 'zod';
3
- import { e as Resolver } from './adapter-BT9v2OVg.mjs';
1
+ import { z } from 'zod';
2
+ export { ZodType as ZodSchema, z } from 'zod';
3
+ import { e as Resolver } from './adapter-nQW28cyO.mjs';
4
4
  import 'react';
5
5
 
6
6
  /**
7
7
  * Creates a validation resolver from a Zod schema.
8
8
  *
9
- * The resolver validates form values against the schema and returns:
10
- * - `{ values }` if validation passes (with transformed/parsed data)
11
- * - `{ errors }` if validation fails (with field-level error messages)
12
- *
13
- * @param schema - A Zod schema to validate against
14
- * @returns A Resolver function compatible with BuzzForm adapters
15
- *
16
- * @example
17
- * import { z } from '@buildnbuzz/buzzform/zod';
18
- * import { zodResolver } from '@buildnbuzz/buzzform';
19
- *
20
- * const schema = z.object({
21
- * email: z.string().email('Invalid email'),
22
- * age: z.number().min(18, 'Must be at least 18'),
23
- * });
24
- *
25
- * const form = useRhf({
26
- * resolver: zodResolver(schema),
27
- * defaultValues: { email: '', age: 0 },
28
- * });
9
+ * Custom field validators (field.validate) are run separately from the base schema
10
+ * to ensure they execute even when other fields have errors.
29
11
  */
30
- declare function zodResolver<TData>(schema: ZodSchema<TData>): Resolver<TData>;
12
+ declare function zodResolver<TData>(schema: z.ZodType<TData>): Resolver<TData>;
31
13
 
32
14
  export { zodResolver };
package/dist/zod.d.ts CHANGED
@@ -1,32 +1,14 @@
1
- import { ZodSchema } from 'zod';
2
- export { ZodSchema, z } from 'zod';
3
- import { e as Resolver } from './adapter-BT9v2OVg.js';
1
+ import { z } from 'zod';
2
+ export { ZodType as ZodSchema, z } from 'zod';
3
+ import { e as Resolver } from './adapter-nQW28cyO.js';
4
4
  import 'react';
5
5
 
6
6
  /**
7
7
  * Creates a validation resolver from a Zod schema.
8
8
  *
9
- * The resolver validates form values against the schema and returns:
10
- * - `{ values }` if validation passes (with transformed/parsed data)
11
- * - `{ errors }` if validation fails (with field-level error messages)
12
- *
13
- * @param schema - A Zod schema to validate against
14
- * @returns A Resolver function compatible with BuzzForm adapters
15
- *
16
- * @example
17
- * import { z } from '@buildnbuzz/buzzform/zod';
18
- * import { zodResolver } from '@buildnbuzz/buzzform';
19
- *
20
- * const schema = z.object({
21
- * email: z.string().email('Invalid email'),
22
- * age: z.number().min(18, 'Must be at least 18'),
23
- * });
24
- *
25
- * const form = useRhf({
26
- * resolver: zodResolver(schema),
27
- * defaultValues: { email: '', age: 0 },
28
- * });
9
+ * Custom field validators (field.validate) are run separately from the base schema
10
+ * to ensure they execute even when other fields have errors.
29
11
  */
30
- declare function zodResolver<TData>(schema: ZodSchema<TData>): Resolver<TData>;
12
+ declare function zodResolver<TData>(schema: z.ZodType<TData>): Resolver<TData>;
31
13
 
32
14
  export { zodResolver };
package/dist/zod.js CHANGED
@@ -20,30 +20,158 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/resolvers/zod.ts
21
21
  var zod_exports = {};
22
22
  __export(zod_exports, {
23
- z: () => import_zod.z,
23
+ z: () => import_zod2.z,
24
24
  zodResolver: () => zodResolver
25
25
  });
26
26
  module.exports = __toCommonJS(zod_exports);
27
+
28
+ // src/schema/helpers.ts
27
29
  var import_zod = require("zod");
30
+ function extractValidationConfig(validate) {
31
+ if (!validate) {
32
+ return { fn: void 0, isLive: false };
33
+ }
34
+ if (typeof validate === "function") {
35
+ return { fn: validate, isLive: false };
36
+ }
37
+ if (typeof validate === "object" && "fn" in validate) {
38
+ const obj = validate;
39
+ const fn = typeof obj.fn === "function" ? obj.fn : void 0;
40
+ if (!obj.live) {
41
+ return { fn, isLive: false };
42
+ }
43
+ const debounceMs = typeof obj.live === "object" ? obj.live.debounceMs : void 0;
44
+ return { fn, isLive: true, debounceMs };
45
+ }
46
+ return { fn: void 0, isLive: false };
47
+ }
48
+ function collectFieldValidators(fields, basePath = "") {
49
+ const validators = [];
50
+ for (const field of fields) {
51
+ if ("name" in field && field.name) {
52
+ const fieldPath = basePath ? `${basePath}.${field.name}` : field.name;
53
+ if ("validate" in field && field.validate) {
54
+ const config = extractValidationConfig(field.validate);
55
+ if (config.fn) {
56
+ validators.push({
57
+ path: fieldPath,
58
+ fn: config.fn
59
+ });
60
+ }
61
+ }
62
+ if (field.type === "group" && "fields" in field) {
63
+ validators.push(...collectFieldValidators(field.fields, fieldPath));
64
+ }
65
+ }
66
+ if (field.type === "row" && "fields" in field) {
67
+ validators.push(...collectFieldValidators(field.fields, basePath));
68
+ }
69
+ if (field.type === "collapsible" && "fields" in field) {
70
+ validators.push(...collectFieldValidators(field.fields, basePath));
71
+ }
72
+ if (field.type === "tabs" && "tabs" in field) {
73
+ for (const tab of field.tabs) {
74
+ const tabPath = tab.name ? basePath ? `${basePath}.${tab.name}` : tab.name : basePath;
75
+ validators.push(...collectFieldValidators(tab.fields, tabPath));
76
+ }
77
+ }
78
+ }
79
+ return validators;
80
+ }
81
+ function getSiblingData(data, path) {
82
+ const parts = path.split(".");
83
+ if (parts.length <= 1) {
84
+ return data;
85
+ }
86
+ const parentParts = parts.slice(0, -1);
87
+ let current = data;
88
+ for (const part of parentParts) {
89
+ if (current && typeof current === "object" && current !== null) {
90
+ current = current[part];
91
+ } else {
92
+ return {};
93
+ }
94
+ }
95
+ if (current && typeof current === "object" && current !== null) {
96
+ return current;
97
+ }
98
+ return {};
99
+ }
100
+ function getValueByPath(data, path) {
101
+ const parts = path.split(".");
102
+ let current = data;
103
+ for (const part of parts) {
104
+ if (current && typeof current === "object" && current !== null) {
105
+ current = current[part];
106
+ } else {
107
+ return void 0;
108
+ }
109
+ }
110
+ return current;
111
+ }
112
+
113
+ // src/resolvers/zod.ts
114
+ var import_zod2 = require("zod");
28
115
  function zodResolver(schema) {
116
+ const schemaWithFields = schema;
117
+ const fieldValidators = schemaWithFields.fields ? collectFieldValidators(schemaWithFields.fields) : [];
29
118
  return async (values) => {
119
+ const errors = {};
120
+ let parsedValues;
30
121
  try {
31
- const parsed = await schema.parseAsync(values);
32
- return {
33
- values: parsed,
34
- errors: {}
35
- };
122
+ parsedValues = await schema.parseAsync(values);
36
123
  } catch (error) {
37
124
  if (isZodError(error)) {
38
- return {
39
- values: {},
40
- errors: mapZodErrors(error)
41
- };
125
+ Object.assign(errors, mapZodErrors(error));
126
+ } else {
127
+ throw error;
42
128
  }
43
- throw error;
44
129
  }
130
+ if (fieldValidators.length > 0) {
131
+ const customErrors = await runFieldValidators(
132
+ fieldValidators,
133
+ values
134
+ );
135
+ for (const [path, error] of Object.entries(customErrors)) {
136
+ if (!errors[path]) {
137
+ errors[path] = error;
138
+ }
139
+ }
140
+ }
141
+ if (Object.keys(errors).length === 0 && parsedValues !== void 0) {
142
+ return { values: parsedValues, errors: {} };
143
+ }
144
+ return { values: {}, errors };
45
145
  };
46
146
  }
147
+ async function runFieldValidators(validators, data) {
148
+ const errors = {};
149
+ await Promise.all(
150
+ validators.map(async ({ path, fn }) => {
151
+ const value = getValueByPath(data, path);
152
+ const siblingData = getSiblingData(data, path);
153
+ try {
154
+ const result = await fn(value, {
155
+ data,
156
+ siblingData,
157
+ path: path.split(".")
158
+ });
159
+ if (result !== true) {
160
+ errors[path] = {
161
+ type: "custom",
162
+ message: typeof result === "string" ? result : "Validation failed"
163
+ };
164
+ }
165
+ } catch (error) {
166
+ errors[path] = {
167
+ type: "custom",
168
+ message: error instanceof Error ? error.message : "Validation error"
169
+ };
170
+ }
171
+ })
172
+ );
173
+ return errors;
174
+ }
47
175
  function mapZodErrors(error) {
48
176
  const errors = {};
49
177
  for (const issue of error.issues) {
@@ -63,18 +191,24 @@ function issuePath(issue) {
63
191
  function issueType(issue) {
64
192
  switch (issue.code) {
65
193
  case "invalid_type":
66
- if (issue.received === "undefined") return "required";
194
+ if ("received" in issue && issue.received === "undefined") return "required";
67
195
  return "type";
68
196
  case "too_small":
69
- return issue.type === "string" ? "minLength" : "min";
197
+ if ("origin" in issue && issue.origin === "string") return "minLength";
198
+ return "min";
70
199
  case "too_big":
71
- return issue.type === "string" ? "maxLength" : "max";
72
- case "invalid_string":
73
- return issue.validation?.toString() || "pattern";
200
+ if ("origin" in issue && issue.origin === "string") return "maxLength";
201
+ return "max";
202
+ case "invalid_format": {
203
+ const formatIssue = issue;
204
+ if (formatIssue.format === "regex") return "pattern";
205
+ if (typeof formatIssue.format === "string") return formatIssue.format;
206
+ return "pattern";
207
+ }
74
208
  case "custom":
75
209
  return "custom";
76
210
  default:
77
- return issue.code;
211
+ return issue.code ?? "validation";
78
212
  }
79
213
  }
80
214
  function isZodError(error) {
package/dist/zod.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/resolvers/zod.ts"],"sourcesContent":["import type { ZodSchema, ZodError, ZodIssue } from 'zod';\r\nimport type { Resolver, FieldError, ResolverResult } from '../types';\r\n\r\n// =============================================================================\r\n// ZOD RESOLVER\r\n// =============================================================================\r\n\r\n/**\r\n * Creates a validation resolver from a Zod schema.\r\n * \r\n * The resolver validates form values against the schema and returns:\r\n * - `{ values }` if validation passes (with transformed/parsed data)\r\n * - `{ errors }` if validation fails (with field-level error messages)\r\n * \r\n * @param schema - A Zod schema to validate against\r\n * @returns A Resolver function compatible with BuzzForm adapters\r\n * \r\n * @example\r\n * import { z } from '@buildnbuzz/buzzform/zod';\r\n * import { zodResolver } from '@buildnbuzz/buzzform';\r\n * \r\n * const schema = z.object({\r\n * email: z.string().email('Invalid email'),\r\n * age: z.number().min(18, 'Must be at least 18'),\r\n * });\r\n * \r\n * const form = useRhf({\r\n * resolver: zodResolver(schema),\r\n * defaultValues: { email: '', age: 0 },\r\n * });\r\n */\r\nexport function zodResolver<TData>(\r\n schema: ZodSchema<TData>\r\n): Resolver<TData> {\r\n return async (values: TData): Promise<ResolverResult<TData>> => {\r\n try {\r\n // Parse and validate - this also transforms the data\r\n const parsed = await schema.parseAsync(values);\r\n\r\n return {\r\n values: parsed,\r\n errors: {},\r\n };\r\n } catch (error) {\r\n // Handle Zod validation errors\r\n if (isZodError(error)) {\r\n return {\r\n values: {} as TData,\r\n errors: mapZodErrors(error),\r\n };\r\n }\r\n\r\n // Re-throw unexpected errors\r\n throw error;\r\n }\r\n };\r\n}\r\n\r\n// =============================================================================\r\n// ERROR MAPPING\r\n// =============================================================================\r\n\r\n/**\r\n * Maps Zod validation errors to our FieldError format.\r\n * Handles nested paths (e.g., \"address.city\", \"items.0.name\").\r\n */\r\nfunction mapZodErrors(error: ZodError): Record<string, FieldError> {\r\n const errors: Record<string, FieldError> = {};\r\n\r\n for (const issue of error.issues) {\r\n const path = issuePath(issue);\r\n\r\n // Only set the first error for each path\r\n if (!errors[path]) {\r\n errors[path] = {\r\n type: issueType(issue),\r\n message: issue.message,\r\n };\r\n }\r\n }\r\n\r\n return errors;\r\n}\r\n\r\n/**\r\n * Convert Zod issue path to dot-notation string.\r\n * ['address', 'city'] → 'address.city'\r\n * ['items', 0, 'name'] → 'items.0.name'\r\n */\r\nfunction issuePath(issue: ZodIssue): string {\r\n return issue.path.map(String).join('.');\r\n}\r\n\r\n/**\r\n * Map Zod issue code to a simpler type string.\r\n */\r\nfunction issueType(issue: ZodIssue): string {\r\n switch (issue.code) {\r\n case 'invalid_type':\r\n if (issue.received === 'undefined') return 'required';\r\n return 'type';\r\n case 'too_small':\r\n return issue.type === 'string' ? 'minLength' : 'min';\r\n case 'too_big':\r\n return issue.type === 'string' ? 'maxLength' : 'max';\r\n case 'invalid_string':\r\n return issue.validation?.toString() || 'pattern';\r\n case 'custom':\r\n return 'custom';\r\n default:\r\n return issue.code;\r\n }\r\n}\r\n\r\n/**\r\n * Type guard to check if an error is a ZodError.\r\n */\r\nfunction isZodError(error: unknown): error is ZodError {\r\n return (\r\n typeof error === 'object' &&\r\n error !== null &&\r\n 'issues' in error &&\r\n Array.isArray((error as ZodError).issues)\r\n );\r\n}\r\n\r\n// =============================================================================\r\n// RE-EXPORTS FOR CONVENIENCE\r\n// =============================================================================\r\n\r\nexport type { ZodSchema } from 'zod';\r\nexport { z } from 'zod';"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmIA,iBAAkB;AApGX,SAAS,YACZ,QACe;AACf,SAAO,OAAO,WAAkD;AAC5D,QAAI;AAEA,YAAM,SAAS,MAAM,OAAO,WAAW,MAAM;AAE7C,aAAO;AAAA,QACH,QAAQ;AAAA,QACR,QAAQ,CAAC;AAAA,MACb;AAAA,IACJ,SAAS,OAAO;AAEZ,UAAI,WAAW,KAAK,GAAG;AACnB,eAAO;AAAA,UACH,QAAQ,CAAC;AAAA,UACT,QAAQ,aAAa,KAAK;AAAA,QAC9B;AAAA,MACJ;AAGA,YAAM;AAAA,IACV;AAAA,EACJ;AACJ;AAUA,SAAS,aAAa,OAA6C;AAC/D,QAAM,SAAqC,CAAC;AAE5C,aAAW,SAAS,MAAM,QAAQ;AAC9B,UAAM,OAAO,UAAU,KAAK;AAG5B,QAAI,CAAC,OAAO,IAAI,GAAG;AACf,aAAO,IAAI,IAAI;AAAA,QACX,MAAM,UAAU,KAAK;AAAA,QACrB,SAAS,MAAM;AAAA,MACnB;AAAA,IACJ;AAAA,EACJ;AAEA,SAAO;AACX;AAOA,SAAS,UAAU,OAAyB;AACxC,SAAO,MAAM,KAAK,IAAI,MAAM,EAAE,KAAK,GAAG;AAC1C;AAKA,SAAS,UAAU,OAAyB;AACxC,UAAQ,MAAM,MAAM;AAAA,IAChB,KAAK;AACD,UAAI,MAAM,aAAa,YAAa,QAAO;AAC3C,aAAO;AAAA,IACX,KAAK;AACD,aAAO,MAAM,SAAS,WAAW,cAAc;AAAA,IACnD,KAAK;AACD,aAAO,MAAM,SAAS,WAAW,cAAc;AAAA,IACnD,KAAK;AACD,aAAO,MAAM,YAAY,SAAS,KAAK;AAAA,IAC3C,KAAK;AACD,aAAO;AAAA,IACX;AACI,aAAO,MAAM;AAAA,EACrB;AACJ;AAKA,SAAS,WAAW,OAAmC;AACnD,SACI,OAAO,UAAU,YACjB,UAAU,QACV,YAAY,SACZ,MAAM,QAAS,MAAmB,MAAM;AAEhD;","names":[]}
1
+ {"version":3,"sources":["../src/resolvers/zod.ts","../src/schema/helpers.ts"],"sourcesContent":["import { type z } from 'zod';\nimport type { Resolver, FieldError, ResolverResult, Field } from '../types';\nimport { collectFieldValidators, getSiblingData, getValueByPath, type FieldValidator } from '../schema/helpers';\n\n// =============================================================================\n// ZOD RESOLVER\n// =============================================================================\n\ntype ZodIssue = z.core.$ZodIssue;\ntype ZodError = z.ZodError;\n\n// Schema with attached fields from createSchema()\ntype SchemaWithFields = z.ZodType & { fields?: readonly Field[] };\n\n/**\n * Creates a validation resolver from a Zod schema.\n * \n * Custom field validators (field.validate) are run separately from the base schema\n * to ensure they execute even when other fields have errors.\n */\nexport function zodResolver<TData>(\n schema: z.ZodType<TData>\n): Resolver<TData> {\n // Extract field validators from schema.fields (if present)\n const schemaWithFields = schema as SchemaWithFields;\n const fieldValidators = schemaWithFields.fields\n ? collectFieldValidators(schemaWithFields.fields)\n : [];\n\n return async (values: TData): Promise<ResolverResult<TData>> => {\n const errors: Record<string, FieldError> = {};\n let parsedValues: TData | undefined;\n\n // Phase 1: Run base schema validation\n try {\n parsedValues = await schema.parseAsync(values);\n } catch (error) {\n if (isZodError(error)) {\n Object.assign(errors, mapZodErrors(error));\n } else {\n throw error;\n }\n }\n\n // Phase 2: Run custom field validators (always runs, even if base fails)\n if (fieldValidators.length > 0) {\n const customErrors = await runFieldValidators(\n fieldValidators,\n values as Record<string, unknown>\n );\n // Merge, but don't overwrite existing errors\n for (const [path, error] of Object.entries(customErrors)) {\n if (!errors[path]) {\n errors[path] = error;\n }\n }\n }\n\n // Return result\n if (Object.keys(errors).length === 0 && parsedValues !== undefined) {\n return { values: parsedValues, errors: {} };\n }\n\n return { values: {} as TData, errors };\n };\n}\n\n/**\n * Run all field validators and collect errors.\n */\nasync function runFieldValidators(\n validators: FieldValidator[],\n data: Record<string, unknown>\n): Promise<Record<string, FieldError>> {\n const errors: Record<string, FieldError> = {};\n\n await Promise.all(\n validators.map(async ({ path, fn }) => {\n const value = getValueByPath(data, path);\n const siblingData = getSiblingData(data, path);\n\n try {\n const result = await fn(value, {\n data,\n siblingData,\n path: path.split('.'),\n });\n\n if (result !== true) {\n errors[path] = {\n type: 'custom',\n message: typeof result === 'string' ? result : 'Validation failed',\n };\n }\n } catch (error) {\n errors[path] = {\n type: 'custom',\n message: error instanceof Error ? error.message : 'Validation error',\n };\n }\n })\n );\n\n return errors;\n}\n\n// =============================================================================\n// ERROR MAPPING\n// =============================================================================\n\nfunction mapZodErrors(error: ZodError): Record<string, FieldError> {\n const errors: Record<string, FieldError> = {};\n\n for (const issue of error.issues) {\n const path = issuePath(issue);\n if (!errors[path]) {\n errors[path] = {\n type: issueType(issue),\n message: issue.message,\n };\n }\n }\n\n return errors;\n}\n\nfunction issuePath(issue: ZodIssue): string {\n return issue.path.map(String).join('.');\n}\n\nfunction issueType(issue: ZodIssue): string {\n switch (issue.code) {\n case 'invalid_type':\n if ('received' in issue && issue.received === 'undefined') return 'required';\n return 'type';\n case 'too_small':\n if ('origin' in issue && issue.origin === 'string') return 'minLength';\n return 'min';\n case 'too_big':\n if ('origin' in issue && issue.origin === 'string') return 'maxLength';\n return 'max';\n case 'invalid_format': {\n const formatIssue = issue as { format?: string };\n if (formatIssue.format === 'regex') return 'pattern';\n if (typeof formatIssue.format === 'string') return formatIssue.format;\n return 'pattern';\n }\n case 'custom':\n return 'custom';\n default:\n return issue.code ?? 'validation';\n }\n}\n\nfunction isZodError(error: unknown): error is ZodError {\n return (\n typeof error === 'object' &&\n error !== null &&\n 'issues' in error &&\n Array.isArray((error as ZodError).issues)\n );\n}\n\n// =============================================================================\n// RE-EXPORTS\n// =============================================================================\n\nexport type { ZodType as ZodSchema } from 'zod';\nexport { z } from 'zod';","import { z } from 'zod';\nimport type { Field, FieldType, ValidationContext, ValidationFn } from '../types';\n\n// =============================================================================\n// VALIDATION CONFIG EXTRACTION\n// =============================================================================\n\ntype ExtractableValidationFn = (\n value: unknown,\n context: ValidationContext\n) => true | string | Promise<true | string>;\n\nexport interface ExtractedValidationConfig {\n fn?: ExtractableValidationFn;\n isLive: boolean;\n debounceMs?: number;\n}\n\nexport function extractValidationConfig(\n validate?: unknown\n): ExtractedValidationConfig {\n if (!validate) {\n return { fn: undefined, isLive: false };\n }\n\n if (typeof validate === 'function') {\n return { fn: validate as ExtractableValidationFn, isLive: false };\n }\n\n if (typeof validate === 'object' && 'fn' in validate) {\n const obj = validate as { fn?: unknown; live?: boolean | { debounceMs?: number } };\n const fn = typeof obj.fn === 'function' ? obj.fn as ExtractableValidationFn : undefined;\n\n if (!obj.live) {\n return { fn, isLive: false };\n }\n\n const debounceMs = typeof obj.live === 'object' ? obj.live.debounceMs : undefined;\n return { fn, isLive: true, debounceMs };\n }\n\n return { fn: undefined, isLive: false };\n}\n\n// =============================================================================\n// FIELD VALIDATOR COLLECTION\n// =============================================================================\n\nexport interface FieldValidator {\n path: string;\n fn: ValidationFn;\n}\n\n/**\n * Recursively collects all field validators from a field array.\n */\nexport function collectFieldValidators(\n fields: readonly Field[],\n basePath: string = ''\n): FieldValidator[] {\n const validators: FieldValidator[] = [];\n\n for (const field of fields) {\n if ('name' in field && field.name) {\n const fieldPath = basePath ? `${basePath}.${field.name}` : field.name;\n\n if ('validate' in field && field.validate) {\n const config = extractValidationConfig(field.validate);\n if (config.fn) {\n validators.push({\n path: fieldPath,\n fn: config.fn as ValidationFn,\n });\n }\n }\n\n if (field.type === 'group' && 'fields' in field) {\n validators.push(...collectFieldValidators(field.fields, fieldPath));\n }\n }\n\n // Layout fields pass through without adding to path\n if (field.type === 'row' && 'fields' in field) {\n validators.push(...collectFieldValidators(field.fields, basePath));\n }\n if (field.type === 'collapsible' && 'fields' in field) {\n validators.push(...collectFieldValidators(field.fields, basePath));\n }\n if (field.type === 'tabs' && 'tabs' in field) {\n for (const tab of field.tabs) {\n const tabPath = tab.name\n ? (basePath ? `${basePath}.${tab.name}` : tab.name)\n : basePath;\n validators.push(...collectFieldValidators(tab.fields, tabPath));\n }\n }\n }\n\n return validators;\n}\n\n// =============================================================================\n// SIBLING DATA EXTRACTION\n// =============================================================================\n\n/**\n * Gets the parent object containing the field at the given path.\n */\nexport function getSiblingData(\n data: Record<string, unknown>,\n path: string\n): Record<string, unknown> {\n const parts = path.split('.');\n\n if (parts.length <= 1) {\n return data;\n }\n\n const parentParts = parts.slice(0, -1);\n let current: unknown = data;\n\n for (const part of parentParts) {\n if (current && typeof current === 'object' && current !== null) {\n current = (current as Record<string, unknown>)[part];\n } else {\n return {};\n }\n }\n\n if (current && typeof current === 'object' && current !== null) {\n return current as Record<string, unknown>;\n }\n\n return {};\n}\n\n/**\n * Gets a value at a dot-notation path.\n */\nexport function getValueByPath(\n data: Record<string, unknown>,\n path: string\n): unknown {\n const parts = path.split('.');\n let current: unknown = data;\n\n for (const part of parts) {\n if (current && typeof current === 'object' && current !== null) {\n current = (current as Record<string, unknown>)[part];\n } else {\n return undefined;\n }\n }\n\n return current;\n}\n\n/**\n * Creates a superRefine that runs all field validators with full form context.\n */\nexport function createRootValidationRefinement(\n validators: FieldValidator[]\n): (data: Record<string, unknown>, ctx: z.RefinementCtx) => Promise<void> {\n return async (data, ctx) => {\n const validationPromises = validators.map(async ({ path, fn }) => {\n const value = getValueByPath(data, path);\n const siblingData = getSiblingData(data, path);\n\n try {\n const result = await fn(value, {\n data,\n siblingData,\n path: path.split('.'),\n });\n\n if (result !== true) {\n ctx.addIssue({\n code: 'custom',\n path: path.split('.'),\n message: typeof result === 'string' ? result : 'Validation failed',\n });\n }\n } catch (error) {\n ctx.addIssue({\n code: 'custom',\n path: path.split('.'),\n message: error instanceof Error ? error.message : 'Validation error',\n });\n }\n });\n\n await Promise.all(validationPromises);\n };\n}\n\n// =============================================================================\n// OPTIONAL HANDLING\n// =============================================================================\n\nexport function makeOptional(\n schema: z.ZodTypeAny,\n fieldType: FieldType\n): z.ZodTypeAny {\n switch (fieldType) {\n case 'text':\n case 'textarea':\n case 'email':\n case 'password':\n return schema.optional().or(z.literal(''));\n\n case 'number':\n case 'date':\n case 'select':\n case 'radio':\n return schema.optional().nullable();\n\n case 'checkbox':\n case 'switch':\n return schema;\n\n case 'tags':\n case 'array':\n return schema.optional().default([]);\n\n case 'upload':\n return schema.optional().nullable();\n\n default:\n return schema.optional();\n }\n}\n\n// =============================================================================\n// COERCION HELPERS\n// =============================================================================\n\nexport function coerceToNumber(val: unknown): number | undefined {\n if (val === '' || val === null || val === undefined) {\n return undefined;\n }\n const num = Number(val);\n return isNaN(num) ? undefined : num;\n}\n\nexport function coerceToDate(val: unknown): Date | undefined {\n if (val === '' || val === null || val === undefined) {\n return undefined;\n }\n if (val instanceof Date) {\n return isNaN(val.getTime()) ? undefined : val;\n }\n if (typeof val === 'string' || typeof val === 'number') {\n const d = new Date(val);\n return isNaN(d.getTime()) ? undefined : d;\n }\n return undefined;\n}\n\n// =============================================================================\n// PATTERN VALIDATION\n// =============================================================================\n\nconst PATTERN_MESSAGES: Record<string, string> = {\n '^[a-zA-Z0-9_]+$': 'Only letters, numbers, and underscores allowed',\n '^[a-z0-9-]+$': 'Only lowercase letters, numbers, and hyphens allowed',\n '^\\\\S+@\\\\S+\\\\.\\\\S+$': 'Invalid email format',\n '^https?://': 'Must start with http:// or https://',\n};\n\nexport function getPatternErrorMessage(pattern: string | RegExp): string {\n const patternStr = typeof pattern === 'string' ? pattern : pattern.source;\n return PATTERN_MESSAGES[patternStr] || `Must match pattern: ${patternStr}`;\n}\n\n// =============================================================================\n// FILE VALIDATION HELPERS\n// =============================================================================\n\nexport function isFileLike(value: unknown): value is File {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'name' in value &&\n 'size' in value &&\n 'type' in value\n );\n}\n\nexport function isFileTypeAccepted(\n file: File,\n accept: string\n): boolean {\n if (accept === '*' || !accept) return true;\n\n const acceptTypes = accept.split(',').map(t => t.trim().toLowerCase());\n const fileType = file.type.toLowerCase();\n const fileName = file.name.toLowerCase();\n\n return acceptTypes.some(acceptType => {\n if (acceptType.endsWith('/*')) {\n const category = acceptType.replace('/*', '');\n return fileType.startsWith(category + '/');\n }\n if (acceptType.startsWith('.')) {\n return fileName.endsWith(acceptType);\n }\n return fileType === acceptType;\n });\n}\n"],"mappings":"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","names":["import_zod"]}
package/dist/zod.mjs CHANGED
@@ -1,24 +1,71 @@
1
+ import {
2
+ collectFieldValidators,
3
+ getSiblingData,
4
+ getValueByPath
5
+ } from "./chunk-63LF7K4O.mjs";
6
+
1
7
  // src/resolvers/zod.ts
2
8
  import { z } from "zod";
3
9
  function zodResolver(schema) {
10
+ const schemaWithFields = schema;
11
+ const fieldValidators = schemaWithFields.fields ? collectFieldValidators(schemaWithFields.fields) : [];
4
12
  return async (values) => {
13
+ const errors = {};
14
+ let parsedValues;
5
15
  try {
6
- const parsed = await schema.parseAsync(values);
7
- return {
8
- values: parsed,
9
- errors: {}
10
- };
16
+ parsedValues = await schema.parseAsync(values);
11
17
  } catch (error) {
12
18
  if (isZodError(error)) {
13
- return {
14
- values: {},
15
- errors: mapZodErrors(error)
16
- };
19
+ Object.assign(errors, mapZodErrors(error));
20
+ } else {
21
+ throw error;
22
+ }
23
+ }
24
+ if (fieldValidators.length > 0) {
25
+ const customErrors = await runFieldValidators(
26
+ fieldValidators,
27
+ values
28
+ );
29
+ for (const [path, error] of Object.entries(customErrors)) {
30
+ if (!errors[path]) {
31
+ errors[path] = error;
32
+ }
17
33
  }
18
- throw error;
19
34
  }
35
+ if (Object.keys(errors).length === 0 && parsedValues !== void 0) {
36
+ return { values: parsedValues, errors: {} };
37
+ }
38
+ return { values: {}, errors };
20
39
  };
21
40
  }
41
+ async function runFieldValidators(validators, data) {
42
+ const errors = {};
43
+ await Promise.all(
44
+ validators.map(async ({ path, fn }) => {
45
+ const value = getValueByPath(data, path);
46
+ const siblingData = getSiblingData(data, path);
47
+ try {
48
+ const result = await fn(value, {
49
+ data,
50
+ siblingData,
51
+ path: path.split(".")
52
+ });
53
+ if (result !== true) {
54
+ errors[path] = {
55
+ type: "custom",
56
+ message: typeof result === "string" ? result : "Validation failed"
57
+ };
58
+ }
59
+ } catch (error) {
60
+ errors[path] = {
61
+ type: "custom",
62
+ message: error instanceof Error ? error.message : "Validation error"
63
+ };
64
+ }
65
+ })
66
+ );
67
+ return errors;
68
+ }
22
69
  function mapZodErrors(error) {
23
70
  const errors = {};
24
71
  for (const issue of error.issues) {
@@ -38,18 +85,24 @@ function issuePath(issue) {
38
85
  function issueType(issue) {
39
86
  switch (issue.code) {
40
87
  case "invalid_type":
41
- if (issue.received === "undefined") return "required";
88
+ if ("received" in issue && issue.received === "undefined") return "required";
42
89
  return "type";
43
90
  case "too_small":
44
- return issue.type === "string" ? "minLength" : "min";
91
+ if ("origin" in issue && issue.origin === "string") return "minLength";
92
+ return "min";
45
93
  case "too_big":
46
- return issue.type === "string" ? "maxLength" : "max";
47
- case "invalid_string":
48
- return issue.validation?.toString() || "pattern";
94
+ if ("origin" in issue && issue.origin === "string") return "maxLength";
95
+ return "max";
96
+ case "invalid_format": {
97
+ const formatIssue = issue;
98
+ if (formatIssue.format === "regex") return "pattern";
99
+ if (typeof formatIssue.format === "string") return formatIssue.format;
100
+ return "pattern";
101
+ }
49
102
  case "custom":
50
103
  return "custom";
51
104
  default:
52
- return issue.code;
105
+ return issue.code ?? "validation";
53
106
  }
54
107
  }
55
108
  function isZodError(error) {