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 +23 -13
- package/cogsbox-shape-db/dist/connect.d.ts +1 -1
- package/dist/schema.d.ts +37 -27
- package/dist/schema.js +74 -76
- package/dist/vitest/fullSchema.test.js +23 -23
- package/dist/vitest/refineRuntime.test.js +6 -6
- package/package.json +1 -1
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
|
|
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()`
|
|
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
|
-
|
|
|
277
|
-
|
|
278
|
-
| `server` | `parseForDb()` | Cross-field validation before DB writes |
|
|
279
|
-
| `client` | `
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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.
|
|
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" | "
|
|
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
|
-
|
|
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
|
-
|
|
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: (
|
|
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
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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
|
-
|
|
387
|
-
|
|
388
|
-
|
|
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
|
-
|
|
537
|
-
|
|
538
|
-
|
|
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
|
-
|
|
552
|
-
|
|
553
|
-
|
|
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
|
-
|
|
583
|
-
|
|
584
|
-
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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.
|
|
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 (
|
|
379
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
const
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
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
|
-
|
|
743
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
1117
|
-
expect(box.items.
|
|
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
|
|
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.
|
|
1146
|
-
|
|
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
|
|
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
|
|
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.
|
|
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",
|