cogsbox-shape 0.5.195 → 0.5.196
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 +30 -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,16 @@ 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 = {
|
|
219
|
+
(layers: RefineLayer | RefineLayer[], check: (row: any) => RefinementError | RefinementError[] | undefined | null): RefineEntry;
|
|
220
|
+
(layers: RefineLayer | RefineLayer[], check: (row: any) => RefinementError | RefinementError[] | undefined | null, deps: string | string[]): RefineEntry;
|
|
221
|
+
};
|
|
213
222
|
type PickPrimaryKeys<T extends ShapeSchema> = {
|
|
214
223
|
[K in keyof T as T[K] extends {
|
|
215
224
|
config: {
|
|
@@ -242,10 +251,7 @@ type SchemaBuilder<T extends ShapeSchema> = Prettify<EnrichFields<T>> & {
|
|
|
242
251
|
forClient?: Record<string, (row: any) => any>;
|
|
243
252
|
forDb?: Record<string, (row: any) => any>;
|
|
244
253
|
};
|
|
245
|
-
|
|
246
|
-
server?: RefinementFn<InferClientRow<T>>;
|
|
247
|
-
client?: RefinementFn<z.infer<z.ZodObject<Prettify<DeriveSchemaByKey<EnrichFields<T>, "zodClientInputSchema">>>>>;
|
|
248
|
-
};
|
|
254
|
+
__refines?: RefineEntry[];
|
|
249
255
|
primaryKeySQL: (definer: (pkFields: PickPrimaryKeys<T>) => string) => SchemaBuilder<T>;
|
|
250
256
|
derive: (derivers: {
|
|
251
257
|
forClient?: {
|
|
@@ -255,10 +261,7 @@ type SchemaBuilder<T extends ShapeSchema> = Prettify<EnrichFields<T>> & {
|
|
|
255
261
|
[K in PickDbFieldKeys<T>]?: (row: InferClientRow<T>) => any;
|
|
256
262
|
};
|
|
257
263
|
}) => SchemaBuilder<T>;
|
|
258
|
-
refine: (
|
|
259
|
-
server?: RefinementFn<InferClientRow<T>>;
|
|
260
|
-
client?: RefinementFn<z.infer<z.ZodObject<Prettify<DeriveSchemaByKey<EnrichFields<T>, "zodClientInputSchema">>>>>;
|
|
261
|
-
}) => SchemaBuilder<T>;
|
|
264
|
+
refine: (fn: (r: RefineHelper) => RefineEntry[]) => SchemaBuilder<T>;
|
|
262
265
|
};
|
|
263
266
|
export declare function schema<T extends string, U extends ShapeSchema<T>>(schema: U): SchemaBuilder<U>;
|
|
264
267
|
export type RelationType = "hasMany" | "hasOne" | "manyToMany";
|
|
@@ -298,9 +301,9 @@ export declare function createSchema<T extends {
|
|
|
298
301
|
pk: string[] | null;
|
|
299
302
|
clientPk: string[] | null;
|
|
300
303
|
deriveDependencies: Record<string, string[]>;
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
+
refineInfo: {
|
|
305
|
+
groups: RefineEntry[];
|
|
306
|
+
fieldToGroup: Record<string, number[]>;
|
|
304
307
|
};
|
|
305
308
|
isClientRecord: (record: any) => boolean;
|
|
306
309
|
sqlSchema: z.ZodObject<Prettify<DeriveSchemaByKey<TActualSchema, "zodSqlSchema">>>;
|
|
@@ -383,9 +386,9 @@ type ResolvedRegistryWithSchemas<S extends Record<string, SchemaWithPlaceholders
|
|
|
383
386
|
};
|
|
384
387
|
pk: string[] | null;
|
|
385
388
|
clientPk: string[] | null;
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
+
refineInfo: {
|
|
390
|
+
groups: RefineEntry[];
|
|
391
|
+
fieldToGroup: Record<string, number[]>;
|
|
389
392
|
};
|
|
390
393
|
isClientRecord: (record: any) => boolean;
|
|
391
394
|
generateDefaults: () => Prettify<DeriveDefaults<ResolveSchema<S[K], K extends keyof R ? (R[K] extends object ? R[K] : {}) : {}>>>;
|
|
@@ -533,9 +536,9 @@ type RegistryShape = Record<string, {
|
|
|
533
536
|
defaultValues: any;
|
|
534
537
|
stateType: any;
|
|
535
538
|
deriveDependencies: Record<string, string[]>;
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
+
refineInfo: {
|
|
540
|
+
groups: RefineEntry[];
|
|
541
|
+
fieldToGroup: Record<string, number[]>;
|
|
539
542
|
};
|
|
540
543
|
};
|
|
541
544
|
transforms: {
|
|
@@ -548,9 +551,9 @@ type RegistryShape = Record<string, {
|
|
|
548
551
|
pk: string[] | null;
|
|
549
552
|
clientPk: string[] | null;
|
|
550
553
|
deriveDependencies: Record<string, string[]>;
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
+
refineInfo: {
|
|
555
|
+
groups: RefineEntry[];
|
|
556
|
+
fieldToGroup: Record<string, number[]>;
|
|
554
557
|
};
|
|
555
558
|
isClientRecord: (record: any) => boolean;
|
|
556
559
|
generateDefaults: () => any;
|
|
@@ -579,9 +582,9 @@ type CreateSchemaBoxReturn<S extends Record<string, SchemaWithPlaceholders>, R e
|
|
|
579
582
|
pk: string[] | null;
|
|
580
583
|
clientPk: string[] | null;
|
|
581
584
|
deriveDependencies: Record<string, string[]>;
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
+
refineInfo: {
|
|
586
|
+
groups: RefineEntry[];
|
|
587
|
+
fieldToGroup: Record<string, number[]>;
|
|
585
588
|
};
|
|
586
589
|
isClientRecord: (record: any) => boolean;
|
|
587
590
|
nav: NavigationProxy<K & string, Resolved>;
|
|
@@ -608,7 +611,7 @@ type GetDbKey<K, Field> = Field extends Reference<infer TGetter> ? ReturnType<TG
|
|
|
608
611
|
};
|
|
609
612
|
} ? string extends F ? K : F : K;
|
|
610
613
|
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" | "
|
|
614
|
+
[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
615
|
config: {
|
|
613
616
|
sql: {
|
|
614
617
|
sqlOnly: true;
|
|
@@ -635,7 +638,7 @@ type DeriveSchemaByKey<T, Key extends "zodSqlSchema" | "zodClientInputSchema" |
|
|
|
635
638
|
} ? ZodSchema : never;
|
|
636
639
|
};
|
|
637
640
|
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" | "
|
|
641
|
+
[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
642
|
config: {
|
|
640
643
|
sql: {
|
|
641
644
|
sqlOnly: true;
|
|
@@ -658,7 +661,7 @@ type DeriveDefaults<T, Depth extends any[] = []> = Prettify<Depth["length"] exte
|
|
|
658
661
|
} ? z.infer<TClient> : never;
|
|
659
662
|
}>;
|
|
660
663
|
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" | "
|
|
664
|
+
[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
665
|
config: {
|
|
663
666
|
sql: {
|
|
664
667
|
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
|
-
|
|
1073
|
+
}).refine((r) => [
|
|
1074
|
+
r("clientInput", (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
|
-
|
|
12
|
+
}).refine((r) => [
|
|
13
|
+
r("clientInput", (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.196",
|
|
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",
|