cogsbox-shape 0.5.207 → 0.5.208
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 +31 -5
- package/cogsbox-shape-db/dist/table-db.js +3 -2
- package/dist/schema.d.ts +30 -0
- package/dist/schema.js +22 -4
- package/dist/vitest/fullSchema.test.js +3 -3
- package/dist/vitest/refineRuntime.test.js +16 -34
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -307,6 +307,28 @@ schema({ ... })
|
|
|
307
307
|
|
|
308
308
|
**Note**: `parsePatchForDb` uses the base schema (without refinement) since partial data may not satisfy cross-field rules.
|
|
309
309
|
|
|
310
|
+
### Schemas vs Validators
|
|
311
|
+
|
|
312
|
+
Each box entry exposes two sets of Zod schemas:
|
|
313
|
+
|
|
314
|
+
- **`schemas`** — plain `ZodObject` shapes. Always composable with `.pick()`, `.omit()`, `.partial()`, etc. Use these for form field extraction, type inference, and partial validation.
|
|
315
|
+
- **`validators`** — schema + refinements. These are `ZodEffects` when `.refine()` is used, otherwise the same `ZodObject`. Use these for full validation that enforces cross-field rules.
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
const box = createSchemaBox({ events }, { events: {} });
|
|
319
|
+
|
|
320
|
+
// Base schema — always a ZodObject, always composable
|
|
321
|
+
box.events.schemas.client.pick({ startDate: true, endDate: true }); // works!
|
|
322
|
+
|
|
323
|
+
// Validator — enforces refine rules
|
|
324
|
+
box.events.validators.client.safeParse(data); // runs cross-field checks
|
|
325
|
+
|
|
326
|
+
// Internal transforms use validators automatically
|
|
327
|
+
box.events.transforms.parseForDb(data); // uses validator.server
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
Why the split? After `.refine()`, Zod wraps the schema in `ZodEffects`, which loses `.shape`, `.pick()`, `.omit()`, and `.partial()`. By keeping the base schema separate from refinements, you can always compose the shape while still enforcing cross-field rules when needed.
|
|
331
|
+
|
|
310
332
|
### Schema Object Structure
|
|
311
333
|
|
|
312
334
|
The returned schema object has a clear separation of concerns:
|
|
@@ -314,16 +336,17 @@ The returned schema object has a clear separation of concerns:
|
|
|
314
336
|
```typescript
|
|
315
337
|
const schema = createSchema(mySchema);
|
|
316
338
|
|
|
317
|
-
schema.schemas; // { sql, client, clientChecked, server } —
|
|
339
|
+
schema.schemas; // { sql, client, clientChecked, server } — ZodObject shapes (composable)
|
|
340
|
+
schema.validators; // { sql, client, clientChecked, server } — with refinements enforced
|
|
318
341
|
schema.transforms; // { toClient, toDb, parseForDb, parseFromDb } — transformations
|
|
319
342
|
schema.defaults; // Default values for forms
|
|
320
|
-
schema.generateDefaults; // Function to generate fresh client defaults (executes randomizers)
|
|
343
|
+
schema.generateDefaults; // Function to generate fresh client defaults (executes randomizers)
|
|
321
344
|
schema.pk; // Primary key field names
|
|
322
345
|
schema.clientPk; // Client-side primary key field names
|
|
323
346
|
schema.isClientRecord; // Function to check if a record is client-created
|
|
324
347
|
schema.deriveDependencies; // Derive function dependencies ({ [field]: string[] })
|
|
325
348
|
schema.refineInfo; // Refinement info ({ groups: RefineEntry[], fieldToGroup: Record<string, number[]> })
|
|
326
|
-
```
|
|
349
|
+
```
|
|
327
350
|
|
|
328
351
|
## Using Schemas
|
|
329
352
|
|
|
@@ -364,8 +387,11 @@ const { toClient, toDb, parseForDb, parseFromDb } = schema.transforms;
|
|
|
364
387
|
const [data, setData] = useState(generateDefaults());
|
|
365
388
|
// { id: "new_a1b2c3d4", name: "", email: "", isActive: true }
|
|
366
389
|
|
|
367
|
-
// Validate explicitly
|
|
368
|
-
const result = server.safeParse(data);
|
|
390
|
+
// Validate explicitly (use validators for refinement enforcement)
|
|
391
|
+
const result = schema.validators.server.safeParse(data);
|
|
392
|
+
|
|
393
|
+
// Or use the base schema for shape operations
|
|
394
|
+
const pickedSchema = schema.schemas.server.pick({ email: true, age: true });
|
|
369
395
|
|
|
370
396
|
// Or handle validation & transformation in a single step!
|
|
371
397
|
const safeDbRow = parseForDb(data);
|
|
@@ -35,8 +35,9 @@ export class TableDB {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
if (opts?.limit !== undefined) {
|
|
39
|
+
query = query.limit(opts.limit);
|
|
40
|
+
}
|
|
40
41
|
if (opts?.offset !== undefined) {
|
|
41
42
|
query = query.offset(opts.offset);
|
|
42
43
|
}
|
package/dist/schema.d.ts
CHANGED
|
@@ -336,6 +336,12 @@ export declare function createSchema<T extends {
|
|
|
336
336
|
clientSchema: z.ZodObject<Prettify<DeriveSchemaByKey<TActualSchema, "zodClientSchema">>>;
|
|
337
337
|
clientCheckedSchema: z.ZodObject<Prettify<DeriveSchemaByKey<TActualSchema, "zodClientCheckedSchema">>>;
|
|
338
338
|
serverSchema: z.ZodObject<Prettify<DeriveSchemaByKey<TActualSchema, "zodValidationSchema">>>;
|
|
339
|
+
validators: {
|
|
340
|
+
sql: z.ZodTypeAny;
|
|
341
|
+
client: z.ZodTypeAny;
|
|
342
|
+
clientChecked: z.ZodTypeAny;
|
|
343
|
+
server: z.ZodTypeAny;
|
|
344
|
+
};
|
|
339
345
|
defaultValues: Prettify<DeriveDefaults<TActualSchema>>;
|
|
340
346
|
stateType: Prettify<DeriveStateType<TActualSchema>>;
|
|
341
347
|
generateDefaults: () => Prettify<DeriveDefaults<TActualSchema>>;
|
|
@@ -400,6 +406,12 @@ type ResolvedRegistryWithSchemas<S extends Record<string, SchemaWithPlaceholders
|
|
|
400
406
|
clientSchema: z.ZodObject<Prettify<DeriveSchemaByKey<ResolveSchema<S[K], K extends keyof R ? (R[K] extends object ? R[K] : {}) : {}>, "zodClientSchema">>>;
|
|
401
407
|
clientCheckedSchema: z.ZodObject<Prettify<DeriveSchemaByKey<ResolveSchema<S[K], K extends keyof R ? (R[K] extends object ? R[K] : {}) : {}>, "zodClientCheckedSchema">>>;
|
|
402
408
|
serverSchema: z.ZodObject<Prettify<DeriveSchemaByKey<ResolveSchema<S[K], K extends keyof R ? (R[K] extends object ? R[K] : {}) : {}>, "zodValidationSchema">>>;
|
|
409
|
+
validators: {
|
|
410
|
+
sql: z.ZodTypeAny;
|
|
411
|
+
client: z.ZodTypeAny;
|
|
412
|
+
clientChecked: z.ZodTypeAny;
|
|
413
|
+
server: z.ZodTypeAny;
|
|
414
|
+
};
|
|
403
415
|
defaultValues: Prettify<DeriveDefaults<ResolveSchema<S[K], K extends keyof R ? (R[K] extends object ? R[K] : {}) : {}>>>;
|
|
404
416
|
stateType: Prettify<DeriveStateType<ResolveSchema<S[K], K extends keyof R ? (R[K] extends object ? R[K] : {}) : {}>>>;
|
|
405
417
|
};
|
|
@@ -511,6 +523,12 @@ export type DeriveViewResult<TTableName extends keyof TRegistry, TSelection, TRe
|
|
|
511
523
|
clientChecked: z.ZodObject<_DeriveViewShape<TTableName, TSelection, TRegistry, "clientCheckedSchema">>;
|
|
512
524
|
server: z.ZodObject<_DeriveViewShape<TTableName, TSelection, TRegistry, "serverSchema">>;
|
|
513
525
|
};
|
|
526
|
+
validators: {
|
|
527
|
+
sql: TRegistry[TTableName]["zodSchemas"]["validators"]["sql"];
|
|
528
|
+
client: TRegistry[TTableName]["zodSchemas"]["validators"]["client"];
|
|
529
|
+
clientChecked: TRegistry[TTableName]["zodSchemas"]["validators"]["clientChecked"];
|
|
530
|
+
server: TRegistry[TTableName]["zodSchemas"]["validators"]["server"];
|
|
531
|
+
};
|
|
514
532
|
transforms: {
|
|
515
533
|
toClient: TRegistry[TTableName]["transforms"]["toClient"];
|
|
516
534
|
toDb: TRegistry[TTableName]["transforms"]["toDb"];
|
|
@@ -560,6 +578,12 @@ type RegistryShape = Record<string, {
|
|
|
560
578
|
clientSchema: z.ZodObject<any>;
|
|
561
579
|
clientCheckedSchema: z.ZodObject<any>;
|
|
562
580
|
serverSchema: z.ZodObject<any>;
|
|
581
|
+
validators: {
|
|
582
|
+
sql: z.ZodTypeAny;
|
|
583
|
+
client: z.ZodTypeAny;
|
|
584
|
+
clientChecked: z.ZodTypeAny;
|
|
585
|
+
server: z.ZodTypeAny;
|
|
586
|
+
};
|
|
563
587
|
defaultValues: any;
|
|
564
588
|
stateType: any;
|
|
565
589
|
deriveDependencies: Record<string, string[]>;
|
|
@@ -595,6 +619,12 @@ type CreateSchemaBoxReturn<S extends Record<string, SchemaWithPlaceholders>, R e
|
|
|
595
619
|
clientChecked: Resolved[K]["zodSchemas"]["clientCheckedSchema"];
|
|
596
620
|
server: Resolved[K]["zodSchemas"]["serverSchema"];
|
|
597
621
|
};
|
|
622
|
+
validators: {
|
|
623
|
+
sql: Resolved[K]["zodSchemas"]["validators"]["sql"];
|
|
624
|
+
client: Resolved[K]["zodSchemas"]["validators"]["client"];
|
|
625
|
+
clientChecked: Resolved[K]["zodSchemas"]["validators"]["clientChecked"];
|
|
626
|
+
server: Resolved[K]["zodSchemas"]["validators"]["server"];
|
|
627
|
+
};
|
|
598
628
|
transforms: {
|
|
599
629
|
toClient: (dbData: z.infer<Resolved[K]["zodSchemas"]["sqlSchema"]>) => z.infer<Resolved[K]["zodSchemas"]["clientSchema"]>;
|
|
600
630
|
toDb: (clientData: z.infer<Resolved[K]["zodSchemas"]["clientSchema"]>) => z.infer<Resolved[K]["zodSchemas"]["sqlSchema"]>;
|
package/dist/schema.js
CHANGED
|
@@ -787,10 +787,16 @@ export function createSchema(schema, relations) {
|
|
|
787
787
|
deriveDependencies,
|
|
788
788
|
refineInfo: { groups: refineGroups ?? [], fieldToGroup },
|
|
789
789
|
isClientRecord,
|
|
790
|
-
sqlSchema:
|
|
791
|
-
clientSchema:
|
|
792
|
-
clientCheckedSchema:
|
|
793
|
-
serverSchema:
|
|
790
|
+
sqlSchema: finalSqlSchema,
|
|
791
|
+
clientSchema: finalClientSchema,
|
|
792
|
+
clientCheckedSchema: finalClientCheckedSchema,
|
|
793
|
+
serverSchema: finalValidationSchema,
|
|
794
|
+
validators: {
|
|
795
|
+
sql: refinedSqlSchema,
|
|
796
|
+
client: refinedClientSchema,
|
|
797
|
+
clientChecked: refinedClientCheckedSchema,
|
|
798
|
+
server: refinedValidationSchema,
|
|
799
|
+
},
|
|
794
800
|
defaultValues: defaultValues,
|
|
795
801
|
stateType: {},
|
|
796
802
|
generateDefaults,
|
|
@@ -1028,6 +1034,12 @@ export function createSchemaBox(schemas, resolutions) {
|
|
|
1028
1034
|
clientChecked: entry.zodSchemas.clientCheckedSchema,
|
|
1029
1035
|
server: entry.zodSchemas.serverSchema,
|
|
1030
1036
|
},
|
|
1037
|
+
validators: {
|
|
1038
|
+
sql: entry.zodSchemas.validators.sql,
|
|
1039
|
+
client: entry.zodSchemas.validators.client,
|
|
1040
|
+
clientChecked: entry.zodSchemas.validators.clientChecked,
|
|
1041
|
+
server: entry.zodSchemas.validators.server,
|
|
1042
|
+
},
|
|
1031
1043
|
transforms: {
|
|
1032
1044
|
toClient: entry.transforms.toClient,
|
|
1033
1045
|
toDb: entry.transforms.toDb,
|
|
@@ -1172,6 +1184,12 @@ export function createSchemaBox(schemas, resolutions) {
|
|
|
1172
1184
|
clientChecked: view.clientChecked,
|
|
1173
1185
|
server: view.server,
|
|
1174
1186
|
},
|
|
1187
|
+
validators: {
|
|
1188
|
+
sql: view.sql,
|
|
1189
|
+
client: view.client,
|
|
1190
|
+
clientChecked: view.clientChecked,
|
|
1191
|
+
server: view.server,
|
|
1192
|
+
},
|
|
1175
1193
|
transforms: {
|
|
1176
1194
|
toClient: viewToClient,
|
|
1177
1195
|
toDb: viewToDb,
|
|
@@ -368,7 +368,7 @@ describe("client vs clientChecked schema divergence after .clientCheck()", () =>
|
|
|
368
368
|
id: 1, password: "abc", confirmPassword: "def",
|
|
369
369
|
});
|
|
370
370
|
expect(clientResult.success).toBe(true);
|
|
371
|
-
const clientCheckedResult = box.forms.
|
|
371
|
+
const clientCheckedResult = box.forms.validators.clientChecked.safeParse({
|
|
372
372
|
id: 1, password: "abc", confirmPassword: "def",
|
|
373
373
|
});
|
|
374
374
|
expect(clientCheckedResult.success).toBe(false);
|
|
@@ -1204,7 +1204,7 @@ describe("refine", () => {
|
|
|
1204
1204
|
}),
|
|
1205
1205
|
]);
|
|
1206
1206
|
const box = createSchemaBox({ forms }, { forms: {} });
|
|
1207
|
-
const result = box.forms.
|
|
1207
|
+
const result = box.forms.validators.client.safeParse({
|
|
1208
1208
|
id: 1,
|
|
1209
1209
|
password: "secret",
|
|
1210
1210
|
confirmPassword: "different",
|
|
@@ -1213,7 +1213,7 @@ describe("refine", () => {
|
|
|
1213
1213
|
if (!result.success) {
|
|
1214
1214
|
expect(result.error.issues[0].message).toBe("Passwords must match");
|
|
1215
1215
|
}
|
|
1216
|
-
const validResult = box.forms.
|
|
1216
|
+
const validResult = box.forms.validators.client.safeParse({
|
|
1217
1217
|
id: 1,
|
|
1218
1218
|
password: "secret",
|
|
1219
1219
|
confirmPassword: "secret",
|
|
@@ -32,16 +32,16 @@ describe("refine runtime behavior", () => {
|
|
|
32
32
|
]);
|
|
33
33
|
return createSchemaBox({ rules }, { rules: {} });
|
|
34
34
|
}
|
|
35
|
-
it("
|
|
35
|
+
it("validators.client catches client refine", () => {
|
|
36
36
|
const box = makeRefinedBox();
|
|
37
|
-
const good = box.rules.
|
|
37
|
+
const good = box.rules.validators.client.safeParse({
|
|
38
38
|
id: 1,
|
|
39
39
|
min: 1,
|
|
40
40
|
max: 10,
|
|
41
41
|
label: "x",
|
|
42
42
|
});
|
|
43
43
|
expect(good.success).toBe(true);
|
|
44
|
-
const bad = box.rules.
|
|
44
|
+
const bad = box.rules.validators.client.safeParse({
|
|
45
45
|
id: 1,
|
|
46
46
|
min: 10,
|
|
47
47
|
max: 1,
|
|
@@ -52,9 +52,9 @@ describe("refine runtime behavior", () => {
|
|
|
52
52
|
expect(bad.error.issues[0].message).toBe("Max must be > min");
|
|
53
53
|
}
|
|
54
54
|
});
|
|
55
|
-
it("
|
|
55
|
+
it("validators.clientChecked catches client refine", () => {
|
|
56
56
|
const box = makeRefinedBox();
|
|
57
|
-
const bad = box.rules.
|
|
57
|
+
const bad = box.rules.validators.clientChecked.safeParse({
|
|
58
58
|
id: 1,
|
|
59
59
|
min: 10,
|
|
60
60
|
max: 1,
|
|
@@ -65,9 +65,9 @@ describe("refine runtime behavior", () => {
|
|
|
65
65
|
expect(bad.error.issues[0].message).toBe("Max must be > min");
|
|
66
66
|
}
|
|
67
67
|
});
|
|
68
|
-
it("
|
|
68
|
+
it("validators.server catches server refine", () => {
|
|
69
69
|
const box = makeRefinedBox();
|
|
70
|
-
const bad = box.rules.
|
|
70
|
+
const bad = box.rules.validators.server.safeParse({
|
|
71
71
|
id: 1,
|
|
72
72
|
min: 1,
|
|
73
73
|
max: 10,
|
|
@@ -78,9 +78,9 @@ describe("refine runtime behavior", () => {
|
|
|
78
78
|
expect(bad.error.issues[0].message).toBe("Label required");
|
|
79
79
|
}
|
|
80
80
|
});
|
|
81
|
-
it("
|
|
81
|
+
it("validators.sql catches server refine", () => {
|
|
82
82
|
const box = makeRefinedBox();
|
|
83
|
-
const bad = box.rules.
|
|
83
|
+
const bad = box.rules.validators.sql.safeParse({
|
|
84
84
|
id: 1,
|
|
85
85
|
min: 1,
|
|
86
86
|
max: 10,
|
|
@@ -122,36 +122,18 @@ describe("refine runtime behavior", () => {
|
|
|
122
122
|
});
|
|
123
123
|
expect(good.success).toBe(true);
|
|
124
124
|
});
|
|
125
|
-
it("
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
id: s.sqlite({ type: "int", pk: true }),
|
|
129
|
-
min: s
|
|
130
|
-
.sqlite({ type: "int", field: "minField" })
|
|
131
|
-
.client({ value: 0 }),
|
|
132
|
-
max: s.sqlite({ type: "int" }).client({ value: 0 }),
|
|
133
|
-
}).refine((r) => [
|
|
134
|
-
r("clientCheck", (row) => row.min >= row.max
|
|
135
|
-
? { path: ["max"], message: "Max must be > min" }
|
|
136
|
-
: undefined, ["min", "max"]),
|
|
137
|
-
]);
|
|
138
|
-
const journal = schema({
|
|
139
|
-
_tableName: "journal",
|
|
140
|
-
id: s.sqlite({ type: "int", pk: true }),
|
|
141
|
-
rules: s.hasOne(true),
|
|
142
|
-
});
|
|
143
|
-
const box = createSchemaBox({ rules, journal }, {
|
|
144
|
-
journal: { rules: { fromKey: "id", toKey: rules.id } },
|
|
145
|
-
});
|
|
146
|
-
const view = box.journal.createView({ rules: true });
|
|
147
|
-
const bad = view.schemas.clientChecked.safeParse({
|
|
125
|
+
it("validators enforce refine on direct table schemas", () => {
|
|
126
|
+
const box = makeRefinedBox();
|
|
127
|
+
const bad = box.rules.validators.clientChecked.safeParse({
|
|
148
128
|
id: 1,
|
|
149
|
-
|
|
129
|
+
min: 10,
|
|
130
|
+
max: 1,
|
|
131
|
+
label: "x",
|
|
150
132
|
});
|
|
151
133
|
expect(bad.success).toBe(false);
|
|
152
134
|
if (!bad.success) {
|
|
153
135
|
expect(bad.error.issues[0]).toMatchObject({
|
|
154
|
-
path: ["
|
|
136
|
+
path: ["max"],
|
|
155
137
|
message: "Max must be > min",
|
|
156
138
|
});
|
|
157
139
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cogsbox-shape",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.208",
|
|
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",
|