cogsbox-shape 0.5.191 → 0.5.192
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 +47 -19
- package/cogsbox-shape-db/dist/table-db.js +6 -1
- package/dist/schema.d.ts +6 -6
- package/dist/schema.js +12 -10
- package/package.json +9 -5
package/README.md
CHANGED
|
@@ -178,9 +178,12 @@ const products = schema({
|
|
|
178
178
|
});
|
|
179
179
|
```
|
|
180
180
|
|
|
181
|
-
#### Derived Fields (`.derive()`)
|
|
182
|
-
|
|
183
|
-
`.derive()` populates _existing fields_ dynamically
|
|
181
|
+
#### Derived Fields (`.derive()`)
|
|
182
|
+
|
|
183
|
+
`.derive()` populates _existing fields_ dynamically. Define the target field first, then choose where the derivation runs:
|
|
184
|
+
|
|
185
|
+
- `forClient` computes client-only fields during `generateDefaults()` and `toClient()`.
|
|
186
|
+
- `forDb` computes DB-backed fields during `toDb()`, `parseForDb()`, and ORM writes. Use `sqlOnly: true` when the computed column should stay hidden from the client.
|
|
184
187
|
|
|
185
188
|
```typescript
|
|
186
189
|
const users = schema({
|
|
@@ -188,16 +191,22 @@ const users = schema({
|
|
|
188
191
|
firstName: s.sql({ type: "varchar" }).clientInput({ value: "John" }),
|
|
189
192
|
lastName: s.sql({ type: "varchar" }).clientInput({ value: "Doe" }),
|
|
190
193
|
|
|
191
|
-
//
|
|
192
|
-
fullName: s.clientInput(""),
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}
|
|
200
|
-
|
|
194
|
+
// Virtual field. It exists in app/view state, not SQL.
|
|
195
|
+
fullName: s.clientInput(""),
|
|
196
|
+
|
|
197
|
+
// Hidden DB column. It is written to SQL, but not sent to the client.
|
|
198
|
+
searchIndex: s.sql({ type: "varchar", sqlOnly: true }),
|
|
199
|
+
}).derive({
|
|
200
|
+
forClient: {
|
|
201
|
+
fullName: (row) => `${row.firstName} ${row.lastName}`,
|
|
202
|
+
},
|
|
203
|
+
forDb: {
|
|
204
|
+
searchIndex: (row) => `${row.firstName} ${row.lastName}`.toLowerCase(),
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
During partial ORM updates, DB-backed derivations fetch only missing dependency fields they actually read, then recompute the affected `forDb` fields. Client-only derived fields are ignored by SQL writes.
|
|
201
210
|
|
|
202
211
|
### Schema Object Structure
|
|
203
212
|
|
|
@@ -337,12 +346,31 @@ type UserWithPosts = z.infer<typeof userWithPosts.schemas.client>;
|
|
|
337
346
|
// posts: { id: number; title: string; authorId: number; }[]
|
|
338
347
|
// }
|
|
339
348
|
|
|
340
|
-
// Views also have transforms for the selected fields
|
|
341
|
-
const { defaults, transforms } = userWithPosts;
|
|
342
|
-
// transforms.toClient() handles nested relation transforms automatically
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
|
|
349
|
+
// Views also have transforms for the selected fields
|
|
350
|
+
const { defaults, transforms } = userWithPosts;
|
|
351
|
+
// transforms.toClient() handles nested relation transforms automatically
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
When a box is connected to the ORM, view reads hydrate the selected relation tree before parsing:
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
import { connect } from "cogsbox-shape/db";
|
|
358
|
+
import { createSqliteDb } from "cogsbox-shape/db/sqlite";
|
|
359
|
+
|
|
360
|
+
const db = createSqliteDb("app.sqlite");
|
|
361
|
+
const bx = connect(box, db);
|
|
362
|
+
|
|
363
|
+
const userView = bx.users.createView({
|
|
364
|
+
posts: true,
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
const user = await userView.db.findById(1);
|
|
368
|
+
// user.posts is loaded and validated as part of the view shape
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
Use `insert(data).ids()` when you only need the database identity, or `insert(data).full()` when you want optimistic client IDs reconciled back into the submitted client object. `create()` is kept as an alias for older code; prefer `insert()` in new code.
|
|
372
|
+
|
|
373
|
+
### 5. Nested Defaults and Form Definitions (`defaultsDefinition`)
|
|
346
374
|
|
|
347
375
|
When working with forms and nested array relations (like `hasMany`), you often need the default state for a _single new item_ to add to a form array.
|
|
348
376
|
|
|
@@ -80,6 +80,7 @@ export class TableDB {
|
|
|
80
80
|
const dbData = this.transforms.parseForDb(data);
|
|
81
81
|
const parsedDbOnlyData = this.parseDbOnlyData(dbOnlyData, {
|
|
82
82
|
requireRequired: true,
|
|
83
|
+
presentDbData: dbData,
|
|
83
84
|
});
|
|
84
85
|
const clientPkClientKeys = this.meta.clientPkFields;
|
|
85
86
|
const pkDbNames = new Set(clientPkClientKeys.map((k) => {
|
|
@@ -225,7 +226,11 @@ export class TableDB {
|
|
|
225
226
|
parseDbOnlyData(dbOnlyData, opts = { requireRequired: false }) {
|
|
226
227
|
if (opts.requireRequired) {
|
|
227
228
|
for (const requiredKey of this.meta.sqlOnlyRequiredClientFields) {
|
|
228
|
-
|
|
229
|
+
const field = this.meta.dbFields.get(requiredKey);
|
|
230
|
+
const dbName = field?.dbName ?? requiredKey;
|
|
231
|
+
const alreadyPresent = opts.presentDbData?.[dbName] !== undefined;
|
|
232
|
+
if (!alreadyPresent &&
|
|
233
|
+
(!dbOnlyData || dbOnlyData[requiredKey] === undefined)) {
|
|
229
234
|
throw new Error(`Missing required sqlOnly field "${requiredKey}" for "${this.meta.tableName}".`);
|
|
230
235
|
}
|
|
231
236
|
}
|
package/dist/schema.d.ts
CHANGED
|
@@ -201,14 +201,14 @@ type PickClientOnlyKeys<T extends ShapeSchema> = {
|
|
|
201
201
|
};
|
|
202
202
|
} ? K : never;
|
|
203
203
|
}[keyof T];
|
|
204
|
-
type
|
|
204
|
+
type PickDbFieldKeys<T extends ShapeSchema> = {
|
|
205
205
|
[K in keyof T]: T[K] extends {
|
|
206
206
|
config: {
|
|
207
|
-
sql:
|
|
208
|
-
sqlOnly: true;
|
|
209
|
-
};
|
|
207
|
+
sql: infer TSql;
|
|
210
208
|
};
|
|
211
|
-
} ?
|
|
209
|
+
} ? TSql extends null ? never : TSql extends {
|
|
210
|
+
type: "hasMany" | "hasOne" | "belongsTo" | "manyToMany";
|
|
211
|
+
} ? never : K : never;
|
|
212
212
|
}[keyof T];
|
|
213
213
|
type InferClientRow<T extends ShapeSchema> = Prettify<z.infer<z.ZodObject<Prettify<DeriveSchemaByKey<T, "zodClientSchema">>>>>;
|
|
214
214
|
type SchemaBuilder<T extends ShapeSchema> = Prettify<EnrichFields<T>> & {
|
|
@@ -223,7 +223,7 @@ type SchemaBuilder<T extends ShapeSchema> = Prettify<EnrichFields<T>> & {
|
|
|
223
223
|
[K in PickClientOnlyKeys<T>]?: (row: InferClientRow<T>) => any;
|
|
224
224
|
};
|
|
225
225
|
forDb?: {
|
|
226
|
-
[K in
|
|
226
|
+
[K in PickDbFieldKeys<T>]?: (row: InferClientRow<T>) => any;
|
|
227
227
|
};
|
|
228
228
|
}) => SchemaBuilder<T>;
|
|
229
229
|
};
|
package/dist/schema.js
CHANGED
|
@@ -622,7 +622,9 @@ export function createSchema(schema, relations) {
|
|
|
622
622
|
for (const clientKey in clientObject) {
|
|
623
623
|
if (clientObject[clientKey] === undefined)
|
|
624
624
|
continue;
|
|
625
|
-
const dbKey = clientToDbKeys[clientKey]
|
|
625
|
+
const dbKey = clientToDbKeys[clientKey];
|
|
626
|
+
if (!dbKey)
|
|
627
|
+
continue;
|
|
626
628
|
const transform = fieldTransforms[clientKey]?.toDb;
|
|
627
629
|
dbObject[dbKey] = transform
|
|
628
630
|
? transform(clientObject[clientKey])
|
|
@@ -644,9 +646,11 @@ export function createSchema(schema, relations) {
|
|
|
644
646
|
const finalClientSchema = z.object(clientFields);
|
|
645
647
|
const finalValidationSchema = z.object(serverFields);
|
|
646
648
|
const deriveDependencies = {};
|
|
647
|
-
|
|
649
|
+
const trackDeriveDependencies = (deriveGroup) => {
|
|
650
|
+
if (!deriveGroup)
|
|
651
|
+
return;
|
|
648
652
|
const trackingSeed = { ...defaultValues };
|
|
649
|
-
for (const key in
|
|
653
|
+
for (const key in deriveGroup) {
|
|
650
654
|
const accessed = new Set();
|
|
651
655
|
const trackingRow = new Proxy(trackingSeed, {
|
|
652
656
|
get(target, prop, receiver) {
|
|
@@ -657,12 +661,14 @@ export function createSchema(schema, relations) {
|
|
|
657
661
|
},
|
|
658
662
|
});
|
|
659
663
|
try {
|
|
660
|
-
|
|
664
|
+
deriveGroup[key]?.(trackingRow);
|
|
661
665
|
}
|
|
662
666
|
catch (e) { }
|
|
663
|
-
deriveDependencies[key] = Array.from(accessed);
|
|
667
|
+
deriveDependencies[key] = Array.from(new Set([...(deriveDependencies[key] ?? []), ...accessed]));
|
|
664
668
|
}
|
|
665
|
-
}
|
|
669
|
+
};
|
|
670
|
+
trackDeriveDependencies(derives?.forClient);
|
|
671
|
+
trackDeriveDependencies(derives?.forDb);
|
|
666
672
|
return {
|
|
667
673
|
pk: pkKeys.length ? pkKeys : null,
|
|
668
674
|
clientPk: clientPkKeys.length ? clientPkKeys : null,
|
|
@@ -706,10 +712,6 @@ function createViewObject(initialRegistryKey, selection, registry, tableNameToRe
|
|
|
706
712
|
registryEntry.zodSchemas.clientPk.length > 0);
|
|
707
713
|
checkedTables[currentRegistryKey] = hasPks;
|
|
708
714
|
if (!hasPks) {
|
|
709
|
-
console.log(`Table ${currentRegistryKey} missing pk/clientPk:`, {
|
|
710
|
-
pk: registryEntry.zodSchemas?.pk,
|
|
711
|
-
clientPk: registryEntry.zodSchemas?.clientPk,
|
|
712
|
-
});
|
|
713
715
|
allTablesSupportsReconciliation = false;
|
|
714
716
|
}
|
|
715
717
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cogsbox-shape",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.192",
|
|
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",
|
|
@@ -22,16 +22,20 @@
|
|
|
22
22
|
"exports": {
|
|
23
23
|
".": {
|
|
24
24
|
"types": "./dist/index.d.ts",
|
|
25
|
-
"import": "./dist/index.js"
|
|
25
|
+
"import": "./dist/index.js",
|
|
26
|
+
"default": "./dist/index.js"
|
|
26
27
|
},
|
|
27
28
|
"./db": {
|
|
28
29
|
"types": "./cogsbox-shape-db/dist/index.d.ts",
|
|
29
|
-
"import": "./cogsbox-shape-db/dist/index.js"
|
|
30
|
+
"import": "./cogsbox-shape-db/dist/index.js",
|
|
31
|
+
"default": "./cogsbox-shape-db/dist/index.js"
|
|
30
32
|
},
|
|
31
33
|
"./db/sqlite": {
|
|
32
34
|
"types": "./cogsbox-shape-db/dist/sqlite/index.d.ts",
|
|
33
|
-
"import": "./cogsbox-shape-db/dist/sqlite/index.js"
|
|
34
|
-
|
|
35
|
+
"import": "./cogsbox-shape-db/dist/sqlite/index.js",
|
|
36
|
+
"default": "./cogsbox-shape-db/dist/sqlite/index.js"
|
|
37
|
+
},
|
|
38
|
+
"./package.json": "./package.json"
|
|
35
39
|
},
|
|
36
40
|
"keywords": [
|
|
37
41
|
"typescript",
|