cogsbox-shape 0.5.195 → 0.5.197

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 CHANGED
@@ -248,8 +248,8 @@ const events = schema({
248
248
  schema: z.string().nullable(),
249
249
  }),
250
250
  isPublished: s.sqlite({ type: "boolean" }).clientInput({ value: false }),
251
- }).refine({
252
- server: (row) => {
251
+ }).refine((r) => [
252
+ r("server", (row) => {
253
253
  const errors: { path: string[]; message: string }[] = [];
254
254
  if (row.startDate && row.endDate && row.startDate > row.endDate) {
255
255
  errors.push({ path: ["endDate"], message: "End date must be after start date" });
@@ -258,8 +258,8 @@ const events = schema({
258
258
  errors.push({ path: ["content"], message: "Published events must have content" });
259
259
  }
260
260
  return errors.length > 0 ? errors : undefined;
261
- },
262
- });
261
+ }),
262
+ ]);
263
263
 
264
264
  const box = createSchemaBox({ events }, { events: {} });
265
265
 
@@ -271,26 +271,36 @@ box.events.transforms.parseForDb({
271
271
  // Throws: "End date must be after start date"
272
272
  ```
273
273
 
274
- The `refine()` config accepts two optional callbacks:
274
+ The `refine()` method takes a callback that receives an `r` helper function. Each call to `r(layer, check, deps?)` creates a refine entry:
275
275
 
276
- | Callback | Runs on | Purpose |
277
- |----------|---------|---------|
278
- | `server` | `parseForDb()` | Cross-field validation before DB writes |
279
- | `client` | `clientInput` schema | Cross-field validation on client input |
276
+ | Layer | Applies to | Purpose |
277
+ |-------|-----------|---------|
278
+ | `"server"` | `parseForDb()`, `server` schema | Cross-field validation before DB writes |
279
+ | `"client"` | `client` schema | Cross-field validation on client output |
280
+ | `"clientInput"` | `clientInput` schema | Cross-field validation on raw client input |
281
+ | `"sql"` | `parseFromDb()`, `sql` schema | Cross-field validation on DB reads |
282
+ | `"all"` | all of the above | Universal cross-field validation |
283
+ | `string[]` | specified layers | Apply to multiple layers at once |
280
284
 
281
- Each callback receives the full row and returns:
285
+ The check function receives the full row and returns:
282
286
  - `undefined` or `null` — validation passes
283
287
  - A single `{ path: string[]; message: string }` — one error
284
288
  - An array of `{ path: string[]; message: string }` — multiple errors
285
289
 
286
- **Dependency tracking**: Same proxy-based approach as `derive()` the library tracks which fields the refine function reads. This is used by the ORM to know which fields to include during partial updates. The same caveat applies: conditional branches with falsy defaults can hide dependencies.
290
+ Optional third argument `deps` specifies explicit dependency fields as a string or string array. If omitted, the library uses proxy-based tracking (same caveat as `derive()` conditional branches with falsy defaults can hide dependencies).
291
+
292
+ **Dependency tracking**: Dependencies are exposed as `refineInfo` on the box entry:
293
+ ```typescript
294
+ box.events.refineInfo;
295
+ // { groups: RefineEntry[], fieldToGroup: Record<string, number[]> }
296
+ ```
287
297
 
288
298
  **Chaining**: `refine()` can be chained after `derive()`:
289
299
 
290
300
  ```typescript
291
301
  schema({ ... })
292
302
  .derive({ forDb: { fullName: (row) => `${row.firstName} ${row.lastName}` } })
293
- .refine({ server: (row) => { ... } });
303
+ .refine((r) => [r("server", (row) => { ... })]);
294
304
  ```
295
305
 
296
306
  **Note**: `parsePatchForDb` uses the base schema (without refinement) since partial data may not satisfy cross-field rules.
@@ -310,7 +320,7 @@ schema.pk; // Primary key field names
310
320
  schema.clientPk; // Client-side primary key field names
311
321
  schema.isClientRecord; // Function to check if a record is client-created
312
322
  schema.deriveDependencies; // Derive function dependencies ({ [field]: string[] })
313
- schema.refineDependencies; // Refinement dependencies ({ server: string[], client: string[] })
323
+ schema.refineInfo; // Refinement info ({ groups: RefineEntry[], fieldToGroup: Record<string, number[]> })
314
324
  ```
315
325
 
316
326
  ## Using Schemas
@@ -6,7 +6,7 @@ type Row<T> = T extends readonly (infer TItem)[] ? TItem : T;
6
6
  type Prettify<T> = {
7
7
  [K in keyof T]: T[K];
8
8
  } & {};
9
- type SchemaMetaKey = "_tableName" | "__primaryKeySQL" | "__derives" | "__refinements" | "primaryKeySQL" | "derive" | "refine";
9
+ type SchemaMetaKey = "_tableName" | "__primaryKeySQL" | "__derives" | "__refines" | "primaryKeySQL" | "derive" | "refine";
10
10
  type SqlConfigOf<TField> = TField extends {
11
11
  config: {
12
12
  sql: infer TSql;
package/dist/schema.d.ts CHANGED
@@ -209,7 +209,20 @@ export type RefinementError = {
209
209
  path: string[];
210
210
  message: string;
211
211
  };
212
- export type RefinementFn<T> = (row: T) => RefinementError | RefinementError[] | undefined | null;
212
+ type RefineLayer = "client" | "server" | "sql" | "clientInput" | "all";
213
+ type RefineEntry = {
214
+ layers: RefineLayer[];
215
+ deps: string[] | null;
216
+ check: (row: any) => RefinementError | RefinementError[] | undefined | null;
217
+ };
218
+ type RefineHelper<T extends ShapeSchema> = {
219
+ (layer: "client", check: (row: InferClientRow<T>) => RefinementError | RefinementError[] | undefined | null, deps?: string | string[]): RefineEntry;
220
+ (layer: "clientInput", check: (row: InferClientInputRow<T>) => RefinementError | RefinementError[] | undefined | null, deps?: string | string[]): RefineEntry;
221
+ (layer: "server", check: (row: InferValidationRow<T>) => RefinementError | RefinementError[] | undefined | null, deps?: string | string[]): RefineEntry;
222
+ (layer: "sql", check: (row: InferSqlRow<T>) => RefinementError | RefinementError[] | undefined | null, deps?: string | string[]): RefineEntry;
223
+ (layer: "all", check: (row: InferClientRow<T>) => RefinementError | RefinementError[] | undefined | null, deps?: string | string[]): RefineEntry;
224
+ (layer: RefineLayer[], check: (row: InferClientRow<T>) => RefinementError | RefinementError[] | undefined | null, deps?: string | string[]): RefineEntry;
225
+ };
213
226
  type PickPrimaryKeys<T extends ShapeSchema> = {
214
227
  [K in keyof T as T[K] extends {
215
228
  config: {
@@ -236,16 +249,16 @@ type PickDbFieldKeys<T extends ShapeSchema> = {
236
249
  } ? never : K : never;
237
250
  }[keyof T];
238
251
  type InferClientRow<T extends ShapeSchema> = Prettify<z.infer<z.ZodObject<Prettify<DeriveSchemaByKey<T, "zodClientSchema">>>>>;
252
+ type InferClientInputRow<T extends ShapeSchema> = Prettify<z.infer<z.ZodObject<Prettify<DeriveSchemaByKey<T, "zodClientInputSchema">>>>>;
253
+ type InferValidationRow<T extends ShapeSchema> = Prettify<z.infer<z.ZodObject<Prettify<DeriveSchemaByKey<T, "zodValidationSchema">>>>>;
254
+ type InferSqlRow<T extends ShapeSchema> = Prettify<z.infer<z.ZodObject<Prettify<DeriveSchemaByKey<T, "zodSqlSchema">>>>>;
239
255
  type SchemaBuilder<T extends ShapeSchema> = Prettify<EnrichFields<T>> & {
240
256
  __primaryKeySQL?: string;
241
257
  __derives?: {
242
258
  forClient?: Record<string, (row: any) => any>;
243
259
  forDb?: Record<string, (row: any) => any>;
244
260
  };
245
- __refinements?: {
246
- server?: RefinementFn<InferClientRow<T>>;
247
- client?: RefinementFn<z.infer<z.ZodObject<Prettify<DeriveSchemaByKey<EnrichFields<T>, "zodClientInputSchema">>>>>;
248
- };
261
+ __refines?: RefineEntry[];
249
262
  primaryKeySQL: (definer: (pkFields: PickPrimaryKeys<T>) => string) => SchemaBuilder<T>;
250
263
  derive: (derivers: {
251
264
  forClient?: {
@@ -255,10 +268,7 @@ type SchemaBuilder<T extends ShapeSchema> = Prettify<EnrichFields<T>> & {
255
268
  [K in PickDbFieldKeys<T>]?: (row: InferClientRow<T>) => any;
256
269
  };
257
270
  }) => SchemaBuilder<T>;
258
- refine: (refinements: {
259
- server?: RefinementFn<InferClientRow<T>>;
260
- client?: RefinementFn<z.infer<z.ZodObject<Prettify<DeriveSchemaByKey<EnrichFields<T>, "zodClientInputSchema">>>>>;
261
- }) => SchemaBuilder<T>;
271
+ refine: (fn: (r: RefineHelper<T>) => RefineEntry[]) => SchemaBuilder<T>;
262
272
  };
263
273
  export declare function schema<T extends string, U extends ShapeSchema<T>>(schema: U): SchemaBuilder<U>;
264
274
  export type RelationType = "hasMany" | "hasOne" | "manyToMany";
@@ -298,9 +308,9 @@ export declare function createSchema<T extends {
298
308
  pk: string[] | null;
299
309
  clientPk: string[] | null;
300
310
  deriveDependencies: Record<string, string[]>;
301
- refineDependencies: {
302
- server: string[];
303
- client: string[];
311
+ refineInfo: {
312
+ groups: RefineEntry[];
313
+ fieldToGroup: Record<string, number[]>;
304
314
  };
305
315
  isClientRecord: (record: any) => boolean;
306
316
  sqlSchema: z.ZodObject<Prettify<DeriveSchemaByKey<TActualSchema, "zodSqlSchema">>>;
@@ -383,9 +393,9 @@ type ResolvedRegistryWithSchemas<S extends Record<string, SchemaWithPlaceholders
383
393
  };
384
394
  pk: string[] | null;
385
395
  clientPk: string[] | null;
386
- refineDependencies: {
387
- server: string[];
388
- client: string[];
396
+ refineInfo: {
397
+ groups: RefineEntry[];
398
+ fieldToGroup: Record<string, number[]>;
389
399
  };
390
400
  isClientRecord: (record: any) => boolean;
391
401
  generateDefaults: () => Prettify<DeriveDefaults<ResolveSchema<S[K], K extends keyof R ? (R[K] extends object ? R[K] : {}) : {}>>>;
@@ -533,9 +543,9 @@ type RegistryShape = Record<string, {
533
543
  defaultValues: any;
534
544
  stateType: any;
535
545
  deriveDependencies: Record<string, string[]>;
536
- refineDependencies: {
537
- server: string[];
538
- client: string[];
546
+ refineInfo: {
547
+ groups: RefineEntry[];
548
+ fieldToGroup: Record<string, number[]>;
539
549
  };
540
550
  };
541
551
  transforms: {
@@ -548,9 +558,9 @@ type RegistryShape = Record<string, {
548
558
  pk: string[] | null;
549
559
  clientPk: string[] | null;
550
560
  deriveDependencies: Record<string, string[]>;
551
- refineDependencies: {
552
- server: string[];
553
- client: string[];
561
+ refineInfo: {
562
+ groups: RefineEntry[];
563
+ fieldToGroup: Record<string, number[]>;
554
564
  };
555
565
  isClientRecord: (record: any) => boolean;
556
566
  generateDefaults: () => any;
@@ -579,9 +589,9 @@ type CreateSchemaBoxReturn<S extends Record<string, SchemaWithPlaceholders>, R e
579
589
  pk: string[] | null;
580
590
  clientPk: string[] | null;
581
591
  deriveDependencies: Record<string, string[]>;
582
- refineDependencies: {
583
- server: string[];
584
- client: string[];
592
+ refineInfo: {
593
+ groups: RefineEntry[];
594
+ fieldToGroup: Record<string, number[]>;
585
595
  };
586
596
  isClientRecord: (record: any) => boolean;
587
597
  nav: NavigationProxy<K & string, Resolved>;
@@ -608,7 +618,7 @@ type GetDbKey<K, Field> = Field extends Reference<infer TGetter> ? ReturnType<TG
608
618
  };
609
619
  } ? string extends F ? K : F : K;
610
620
  type DeriveSchemaByKey<T, Key extends "zodSqlSchema" | "zodClientInputSchema" | "zodClientSchema" | "zodValidationSchema", Depth extends any[] = []> = Depth["length"] extends 10 ? any : {
611
- [K in keyof T as K extends "_tableName" | typeof SchemaWrapperBrand | "__primaryKeySQL" | "primaryKeySQL" | "derive" | "__derives" | "refine" | "__refinements" ? never : K extends keyof T ? T[K] extends {
621
+ [K in keyof T as K extends "_tableName" | typeof SchemaWrapperBrand | "__primaryKeySQL" | "primaryKeySQL" | "derive" | "__derives" | "refine" | "__refines" ? never : K extends keyof T ? T[K] extends {
612
622
  config: {
613
623
  sql: {
614
624
  sqlOnly: true;
@@ -635,7 +645,7 @@ type DeriveSchemaByKey<T, Key extends "zodSqlSchema" | "zodClientInputSchema" |
635
645
  } ? ZodSchema : never;
636
646
  };
637
647
  type DeriveDefaults<T, Depth extends any[] = []> = Prettify<Depth["length"] extends 10 ? any : {
638
- [K in keyof T as K extends "_tableName" | typeof SchemaWrapperBrand | "__primaryKeySQL" | "primaryKeySQL" | "derive" | "__derives" | "refine" | "__refinements" ? never : K extends keyof T ? T[K] extends {
648
+ [K in keyof T as K extends "_tableName" | typeof SchemaWrapperBrand | "__primaryKeySQL" | "primaryKeySQL" | "derive" | "__derives" | "refine" | "__refines" ? never : K extends keyof T ? T[K] extends {
639
649
  config: {
640
650
  sql: {
641
651
  sqlOnly: true;
@@ -658,7 +668,7 @@ type DeriveDefaults<T, Depth extends any[] = []> = Prettify<Depth["length"] exte
658
668
  } ? z.infer<TClient> : never;
659
669
  }>;
660
670
  type DeriveStateType<T, Depth extends any[] = []> = Prettify<Depth["length"] extends 10 ? any : {
661
- [K in keyof T as K extends "_tableName" | typeof SchemaWrapperBrand | "__primaryKeySQL" | "primaryKeySQL" | "derive" | "__derives" | "refine" | "__refinements" ? never : K extends keyof T ? T[K] extends {
671
+ [K in keyof T as K extends "_tableName" | typeof SchemaWrapperBrand | "__primaryKeySQL" | "primaryKeySQL" | "derive" | "__derives" | "refine" | "__refines" ? never : K extends keyof T ? T[K] extends {
662
672
  config: {
663
673
  sql: {
664
674
  sqlOnly: true;
package/dist/schema.js CHANGED
@@ -357,7 +357,7 @@ export function schema(schema) {
357
357
  enrichedSchema[SchemaWrapperBrand] = true;
358
358
  enrichedSchema.__primaryKeySQL = undefined;
359
359
  enrichedSchema.__derives = undefined;
360
- enrichedSchema.__refinements = undefined;
360
+ enrichedSchema.__refines = undefined;
361
361
  enrichedSchema.primaryKeySQL = function (definer) {
362
362
  const pkFieldsOnly = {};
363
363
  for (const key in schema) {
@@ -375,8 +375,16 @@ export function schema(schema) {
375
375
  enrichedSchema.__derives = derivers;
376
376
  return enrichedSchema;
377
377
  };
378
- enrichedSchema.refine = function (refinements) {
379
- enrichedSchema.__refinements = refinements;
378
+ enrichedSchema.refine = function (fn) {
379
+ const r = (layers, check, deps) => {
380
+ const layerArr = Array.isArray(layers) ? layers : [layers];
381
+ return {
382
+ layers: layerArr,
383
+ deps: deps ? (Array.isArray(deps) ? deps : [deps]) : null,
384
+ check,
385
+ };
386
+ };
387
+ enrichedSchema.__refines = fn(r);
380
388
  return enrichedSchema;
381
389
  };
382
390
  return enrichedSchema;
@@ -445,7 +453,7 @@ export function createSchema(schema, relations) {
445
453
  let pkKeys = [];
446
454
  let clientPkKeys = [];
447
455
  const derives = schema.__derives;
448
- const refinements = schema.__refinements;
456
+ const refineGroups = schema.__refines;
449
457
  for (const key in fullSchema) {
450
458
  const value = fullSchema[key];
451
459
  if (key === "_tableName" ||
@@ -684,86 +692,76 @@ export function createSchema(schema, relations) {
684
692
  };
685
693
  trackDeriveDependencies(derives?.forClient);
686
694
  trackDeriveDependencies(derives?.forDb);
687
- const refineDependencies = { server: [], client: [] };
688
- const trackRefinementDependencies = (fn, key) => {
689
- if (!fn)
690
- return;
691
- const accessed = new Set();
692
- const trackingRow = new Proxy(defaultValues, {
693
- get(target, prop, receiver) {
694
- if (typeof prop === "string") {
695
- accessed.add(prop);
696
- }
697
- return Reflect.get(target, prop, receiver);
698
- },
699
- });
700
- try {
701
- fn(trackingRow);
702
- }
703
- catch (e) { }
704
- refineDependencies[key] = Array.from(accessed);
705
- };
706
- trackRefinementDependencies(refinements?.server, "server");
707
- trackRefinementDependencies(refinements?.client, "client");
708
- let refinedValidationSchema = finalValidationSchema;
709
- if (refinements?.server) {
710
- const serverRefine = refinements.server;
711
- refinedValidationSchema = finalValidationSchema.superRefine((data, ctx) => {
712
- const result = serverRefine(data);
713
- if (!result)
714
- return;
715
- const errors = Array.isArray(result) ? result : [result];
716
- for (const err of errors) {
717
- ctx.addIssue({
718
- code: z.ZodIssueCode.custom,
719
- message: err.message,
720
- path: err.path,
721
- });
722
- }
723
- });
724
- }
695
+ let refinedSqlSchema = finalSqlSchema;
725
696
  let refinedClientInputSchema = finalClientInputSchema;
726
697
  let refinedClientSchema = finalClientSchema;
727
- if (refinements?.client) {
728
- const clientRefine = refinements.client;
729
- const refineFn = (data, ctx) => {
730
- const result = clientRefine(data);
731
- if (!result)
732
- return;
733
- const errors = Array.isArray(result) ? result : [result];
734
- for (const err of errors) {
735
- ctx.addIssue({
736
- code: z.ZodIssueCode.custom,
737
- message: err.message,
738
- path: err.path,
698
+ let refinedValidationSchema = finalValidationSchema;
699
+ const fieldToGroup = {};
700
+ if (refineGroups) {
701
+ for (let i = 0; i < refineGroups.length; i++) {
702
+ const entry = refineGroups[i];
703
+ let { layers, deps, check } = entry;
704
+ const applyTo = layers.includes("all")
705
+ ? ["sql", "clientInput", "client", "server"]
706
+ : layers;
707
+ // Track deps from proxy if not provided explicitly
708
+ if (!deps) {
709
+ const accessed = new Set();
710
+ const trackingRow = new Proxy(defaultValues, {
711
+ get(target, prop, receiver) {
712
+ if (typeof prop === "string")
713
+ accessed.add(prop);
714
+ return Reflect.get(target, prop, receiver);
715
+ },
739
716
  });
717
+ try {
718
+ check(trackingRow);
719
+ }
720
+ catch (e) { }
721
+ deps = Array.from(accessed);
722
+ entry.deps = deps;
740
723
  }
741
- };
742
- refinedClientInputSchema = finalClientInputSchema.superRefine(refineFn);
743
- refinedClientSchema = finalClientSchema.superRefine(refineFn);
744
- }
745
- let refinedSqlSchema = finalSqlSchema;
746
- if (refinements?.server) {
747
- const serverRefine = refinements.server;
748
- refinedSqlSchema = finalSqlSchema.superRefine((data, ctx) => {
749
- const result = serverRefine(data);
750
- if (!result)
751
- return;
752
- const errors = Array.isArray(result) ? result : [result];
753
- for (const err of errors) {
754
- ctx.addIssue({
755
- code: z.ZodIssueCode.custom,
756
- message: err.message,
757
- path: err.path,
758
- });
724
+ for (const dep of deps) {
725
+ if (!fieldToGroup[dep])
726
+ fieldToGroup[dep] = [];
727
+ fieldToGroup[dep].push(i);
759
728
  }
760
- });
729
+ const refineFn = (data, ctx) => {
730
+ const result = check(data);
731
+ if (!result)
732
+ return;
733
+ const errors = Array.isArray(result) ? result : [result];
734
+ for (const err of errors) {
735
+ ctx.addIssue({
736
+ code: z.ZodIssueCode.custom,
737
+ message: err.message,
738
+ path: err.path,
739
+ });
740
+ }
741
+ };
742
+ for (const layer of applyTo) {
743
+ switch (layer) {
744
+ case "sql":
745
+ refinedSqlSchema = refinedSqlSchema.superRefine(refineFn);
746
+ break;
747
+ case "clientInput":
748
+ refinedClientInputSchema = refinedClientInputSchema.superRefine(refineFn);
749
+ break;
750
+ case "client":
751
+ refinedClientSchema = refinedClientSchema.superRefine(refineFn);
752
+ break;
753
+ case "server":
754
+ refinedValidationSchema = refinedValidationSchema.superRefine(refineFn);
755
+ break;
756
+ }
757
+ }
758
+ }
761
759
  }
762
760
  return {
763
761
  pk: pkKeys.length ? pkKeys : null,
764
762
  clientPk: clientPkKeys.length ? clientPkKeys : null,
765
763
  deriveDependencies,
766
- refineDependencies,
764
+ refineInfo: { groups: refineGroups ?? [], fieldToGroup },
767
765
  isClientRecord,
768
766
  sqlSchema: refinedSqlSchema,
769
767
  clientInputSchema: refinedClientInputSchema,
@@ -931,7 +929,7 @@ export function createSchemaBox(schemas, resolutions) {
931
929
  pk: zodSchemas.pk,
932
930
  clientPk: zodSchemas.clientPk,
933
931
  deriveDependencies: zodSchemas.deriveDependencies,
934
- refineDependencies: zodSchemas.refineDependencies,
932
+ refineInfo: zodSchemas.refineInfo,
935
933
  isClientRecord: zodSchemas.isClientRecord,
936
934
  generateDefaults: zodSchemas.generateDefaults,
937
935
  };
@@ -1017,7 +1015,7 @@ export function createSchemaBox(schemas, resolutions) {
1017
1015
  pk: entry.pk,
1018
1016
  clientPk: entry.clientPk,
1019
1017
  deriveDependencies: entry.deriveDependencies,
1020
- refineDependencies: entry.refineDependencies,
1018
+ refineInfo: entry.refineInfo,
1021
1019
  isClientRecord: entry.isClientRecord,
1022
1020
  nav: createNavProxy(tableName, finalRegistry),
1023
1021
  createView: (selection) => {
@@ -1017,8 +1017,8 @@ describe("refine", () => {
1017
1017
  schema: z.string().nullable(),
1018
1018
  }),
1019
1019
  isPublished: s.sqlite({ type: "boolean" }).clientInput({ value: false }),
1020
- }).refine({
1021
- server: (row) => {
1020
+ }).refine((r) => [
1021
+ r("server", (row) => {
1022
1022
  const errors = [];
1023
1023
  if (row.startDate && row.endDate && row.startDate > row.endDate) {
1024
1024
  errors.push({
@@ -1033,8 +1033,8 @@ describe("refine", () => {
1033
1033
  });
1034
1034
  }
1035
1035
  return errors.length > 0 ? errors : undefined;
1036
- },
1037
- });
1036
+ }),
1037
+ ]);
1038
1038
  const box = createSchemaBox({ events }, { events: {} });
1039
1039
  expect(() => box.events.transforms.parseForDb({
1040
1040
  id: 1,
@@ -1070,8 +1070,8 @@ describe("refine", () => {
1070
1070
  id: s.sqlite({ type: "int", pk: true }),
1071
1071
  password: s.sqlite({ type: "varchar" }).clientInput({ value: "" }),
1072
1072
  confirmPassword: s.sqlite({ type: "varchar" }).clientInput({ value: "" }),
1073
- }).refine({
1074
- client: (row) => {
1073
+ }).refine((r) => [
1074
+ r(["clientInput", "client"], (row) => {
1075
1075
  if (row.password !== row.confirmPassword) {
1076
1076
  return {
1077
1077
  path: ["confirmPassword"],
@@ -1079,8 +1079,8 @@ describe("refine", () => {
1079
1079
  };
1080
1080
  }
1081
1081
  return undefined;
1082
- },
1083
- });
1082
+ }),
1083
+ ]);
1084
1084
  const box = createSchemaBox({ forms }, { forms: {} });
1085
1085
  const result = box.forms.schemas.clientInput.safeParse({
1086
1086
  id: 1,
@@ -1098,23 +1098,25 @@ describe("refine", () => {
1098
1098
  });
1099
1099
  expect(validResult.success).toBe(true);
1100
1100
  });
1101
- it("should track refinement dependencies", () => {
1101
+ it("should track refinement dependencies via fieldToGroup", () => {
1102
1102
  const items = schema({
1103
1103
  _tableName: "items",
1104
1104
  id: s.sqlite({ type: "int", pk: true }),
1105
1105
  min: s.sqlite({ type: "int" }).clientInput({ value: 0 }),
1106
1106
  max: s.sqlite({ type: "int" }).clientInput({ value: 100 }),
1107
- }).refine({
1108
- server: (row) => {
1107
+ }).refine((r) => [
1108
+ r("server", (row) => {
1109
1109
  if (row.min > row.max) {
1110
1110
  return { path: ["max"], message: "Max must be >= min" };
1111
1111
  }
1112
1112
  return undefined;
1113
- },
1114
- });
1113
+ }),
1114
+ ]);
1115
1115
  const box = createSchemaBox({ items }, { items: {} });
1116
- expect(box.items.refineDependencies.server).toEqual(["min", "max"]);
1117
- expect(box.items.refineDependencies.client).toEqual([]);
1116
+ expect(box.items.refineInfo.fieldToGroup).toHaveProperty("min");
1117
+ expect(box.items.refineInfo.fieldToGroup).toHaveProperty("max");
1118
+ expect(box.items.refineInfo.groups[0].deps).toContain("min");
1119
+ expect(box.items.refineInfo.groups[0].deps).toContain("max");
1118
1120
  });
1119
1121
  it("should support chaining derive and refine", () => {
1120
1122
  const records = schema({
@@ -1129,23 +1131,21 @@ describe("refine", () => {
1129
1131
  fullName: (row) => `${row.firstName} ${row.lastName}`.trim(),
1130
1132
  },
1131
1133
  })
1132
- .refine({
1133
- server: (row) => {
1134
+ .refine((r) => [
1135
+ r("server", (row) => {
1134
1136
  if (!row.firstName && !row.lastName) {
1135
1137
  return { path: ["firstName"], message: "Name is required" };
1136
1138
  }
1137
1139
  return undefined;
1138
- },
1139
- });
1140
+ }),
1141
+ ]);
1140
1142
  const box = createSchemaBox({ records }, { records: {} });
1141
1143
  expect(box.records.deriveDependencies.fullName).toEqual([
1142
1144
  "firstName",
1143
1145
  "lastName",
1144
1146
  ]);
1145
- expect(box.records.refineDependencies.server).toEqual([
1146
- "firstName",
1147
- "lastName",
1148
- ]);
1147
+ expect(box.records.refineInfo.fieldToGroup).toHaveProperty("firstName");
1148
+ expect(box.records.refineInfo.fieldToGroup).toHaveProperty("lastName");
1149
1149
  const result = box.records.transforms.parseForDb({
1150
1150
  id: 1,
1151
1151
  firstName: "John",
@@ -9,14 +9,14 @@ describe("refine runtime behavior", () => {
9
9
  min: s.sqlite({ type: "int", nullable: true }).clientInput({ value: null, schema: z.number().nullable() }),
10
10
  max: s.sqlite({ type: "int", nullable: true }).clientInput({ value: null, schema: z.number().nullable() }),
11
11
  label: s.sqlite({ type: "varchar" }).clientInput({ value: "" }),
12
- }).refine({
13
- client: (row) => {
12
+ }).refine((r) => [
13
+ r(["clientInput", "client"], (row) => {
14
14
  if (row.min !== null && row.max !== null && row.min >= row.max) {
15
15
  return { path: ["max"], message: "Max must be > min" };
16
16
  }
17
17
  return undefined;
18
- },
19
- server: (row) => {
18
+ }),
19
+ r(["server", "sql"], (row) => {
20
20
  if (row.min !== null && row.max !== null && row.min >= row.max) {
21
21
  return { path: ["max"], message: "Max must be > min" };
22
22
  }
@@ -24,8 +24,8 @@ describe("refine runtime behavior", () => {
24
24
  return { path: ["label"], message: "Label required" };
25
25
  }
26
26
  return undefined;
27
- },
28
- });
27
+ }),
28
+ ]);
29
29
  return createSchemaBox({ rules }, { rules: {} });
30
30
  }
31
31
  it("schemas.clientInput catches client refine", () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cogsbox-shape",
3
- "version": "0.5.195",
3
+ "version": "0.5.197",
4
4
  "description": "A TypeScript library for creating type-safe database schemas with Zod validation, SQL type definitions, and automatic client/server transformations. Unifies client, server, and database types through a single schema definition, with built-in support for relationships and serialization.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",