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 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 on read and default generation. Because you define the fields first, derived fields can be either standard DB fields or Client-only fields.
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
- // 1. Defined purely as a client field
192
- fullName: s.clientInput(""),
193
- // 2. Defined as a DB field
194
- searchIndex: s.sql({ type: "varchar" }),
195
- }).derive({
196
- // Computes on toClient() and generateDefaults()
197
- fullName: (row) => `${row.firstName} ${row.lastName}`,
198
- searchIndex: (row) => `${row.firstName} ${row.lastName}`.toLowerCase(),
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
- ### 5. Nested Defaults and Form Definitions (`defaultsDefinition`)
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
- if (!dbOnlyData || dbOnlyData[requiredKey] === undefined) {
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 PickSqlOnlyKeys<T extends ShapeSchema> = {
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
- } ? K : never;
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 PickSqlOnlyKeys<T>]?: (row: InferClientRow<T>) => any;
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] || 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
- if (derives?.forClient) {
649
+ const trackDeriveDependencies = (deriveGroup) => {
650
+ if (!deriveGroup)
651
+ return;
648
652
  const trackingSeed = { ...defaultValues };
649
- for (const key in derives.forClient) {
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
- derives.forClient[key]?.(trackingRow);
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.191",
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",