cogsbox-shape 0.5.188 → 0.5.190

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.
@@ -50,8 +50,9 @@ function extractTableMeta(entry) {
50
50
  clientToDbName.set(key, dbName);
51
51
  if (sqlConfig.pk)
52
52
  pkFields.push(dbName);
53
- if (sqlConfig.isClientPk)
53
+ if (sqlConfig.isClientPk && !sqlConfig.isForeignKey) {
54
54
  clientPkFields.push(key);
55
+ }
55
56
  if (sqlConfig.sqlOnly) {
56
57
  sqlOnlyFields.add(dbName);
57
58
  sqlOnlyClientFields.add(key);
@@ -96,6 +97,84 @@ function enhanceTable(entry, meta, db) {
96
97
  },
97
98
  });
98
99
  }
100
+ function registryKeyForTableName(registry, tableName) {
101
+ return Object.keys(registry).find((key) => registry[key]?.rawSchema?._tableName === tableName);
102
+ }
103
+ function dbNameForClientKey(meta, clientKey) {
104
+ return meta.clientToDbName.get(clientKey) ?? clientKey;
105
+ }
106
+ function clientKeyForRelationTarget(targetField, targetDefinition) {
107
+ const metaKey = targetField?.__meta?._key;
108
+ if (typeof metaKey === "string")
109
+ return metaKey;
110
+ for (const [key, field] of Object.entries(targetDefinition)) {
111
+ if (field === targetField)
112
+ return key;
113
+ }
114
+ return undefined;
115
+ }
116
+ function createViewHydrator(db, registry, baseRegistryKey, selection) {
117
+ const metas = new Map();
118
+ const getMeta = (registryKey) => {
119
+ let meta = metas.get(registryKey);
120
+ if (!meta) {
121
+ const entry = registry[registryKey];
122
+ meta = extractTableMeta({
123
+ definition: entry?.rawSchema,
124
+ deriveDependencies: entry?.deriveDependencies,
125
+ });
126
+ metas.set(registryKey, meta);
127
+ }
128
+ return meta;
129
+ };
130
+ const hydrate = async (row, currentRegistryKey, currentSelection) => {
131
+ if (!row || typeof currentSelection !== "object")
132
+ return row;
133
+ const currentEntry = registry[currentRegistryKey];
134
+ if (!currentEntry)
135
+ return row;
136
+ const currentMeta = getMeta(currentRegistryKey);
137
+ const hydrated = { ...row };
138
+ for (const [relationKey, relationSelection] of Object.entries(currentSelection)) {
139
+ if (!relationSelection)
140
+ continue;
141
+ const relationField = currentEntry.rawSchema?.[relationKey];
142
+ const relationConfig = relationField?.config?.sql;
143
+ if (!relationConfig?.schema)
144
+ continue;
145
+ const targetTableName = relationConfig.schema()._tableName;
146
+ const targetRegistryKey = registryKeyForTableName(registry, targetTableName);
147
+ if (!targetRegistryKey)
148
+ continue;
149
+ const targetEntry = registry[targetRegistryKey];
150
+ const targetMeta = getMeta(targetRegistryKey);
151
+ const fromDbName = dbNameForClientKey(currentMeta, relationConfig.fromKey);
152
+ const targetClientKey = clientKeyForRelationTarget(relationConfig.toKey?.(), targetEntry.rawSchema);
153
+ if (!targetClientKey)
154
+ continue;
155
+ const targetDbName = dbNameForClientKey(targetMeta, targetClientKey);
156
+ const fromValue = row[fromDbName];
157
+ if (fromValue === undefined || fromValue === null) {
158
+ hydrated[relationKey] = ["hasMany", "manyToMany"].includes(relationConfig.type)
159
+ ? []
160
+ : null;
161
+ continue;
162
+ }
163
+ const qb = db;
164
+ const relatedRows = (await qb
165
+ .selectFrom(targetMeta.tableName)
166
+ .selectAll()
167
+ .where(targetDbName, "=", fromValue)
168
+ .execute());
169
+ const hydratedRelated = await Promise.all(relatedRows.map((relatedRow) => hydrate(relatedRow, targetRegistryKey, relationSelection)));
170
+ hydrated[relationKey] = ["hasMany", "manyToMany"].includes(relationConfig.type)
171
+ ? hydratedRelated
172
+ : hydratedRelated[0] ?? null;
173
+ }
174
+ return hydrated;
175
+ };
176
+ return (row) => hydrate(row, baseRegistryKey, selection);
177
+ }
99
178
  export function connect(box, db) {
100
179
  const result = {};
101
180
  for (const key of Object.keys(box)) {
@@ -114,13 +193,19 @@ export function connect(box, db) {
114
193
  const viewMeta = { ...meta };
115
194
  const viewTransforms = view.transforms ?? {};
116
195
  const reconcile = view.reconcile;
196
+ const registry = view.__registry;
197
+ const baseTable = view.baseTable;
198
+ const viewSelection = view.viewSelection;
199
+ const hydrateRow = registry && baseTable && viewSelection
200
+ ? createViewHydrator(db, registry, baseTable, viewSelection)
201
+ : undefined;
117
202
  const viewDb = new TableDB(db, viewMeta, {
118
203
  toClient: viewTransforms.toClient ?? ((r) => r),
119
204
  toDb: viewTransforms.toDb ?? ((r) => r),
120
205
  parseForDb: viewTransforms.parseForDb ?? ((r) => r),
121
206
  parsePatchForDb: viewTransforms.parsePatchForDb ?? viewTransforms.toDb ?? ((r) => r),
122
207
  parseFromDb: viewTransforms.parseFromDb ?? ((r) => r),
123
- }, reconcile);
208
+ }, reconcile, hydrateRow);
124
209
  return new Proxy(view, {
125
210
  get(target, prop, receiver) {
126
211
  if (prop === "db")
@@ -10,6 +10,7 @@ export declare class TableDB<TClient extends Record<string, unknown>, TCreate, T
10
10
  private meta;
11
11
  private transforms;
12
12
  private reconcile?;
13
+ private hydrateRow?;
13
14
  constructor(db: Kysely<unknown>, meta: TableMeta, transforms: {
14
15
  toClient: (row: Record<string, unknown>) => TClient;
15
16
  toDb: (row: Record<string, unknown>) => Record<string, unknown>;
@@ -18,7 +19,7 @@ export declare class TableDB<TClient extends Record<string, unknown>, TCreate, T
18
19
  parseFromDb: (data: Record<string, unknown>) => TClient;
19
20
  }, reconcile?: ((clientData: unknown) => {
20
21
  withServer: (serverData: unknown) => unknown;
21
- }) | undefined);
22
+ }) | undefined, hydrateRow?: ((row: Record<string, unknown>) => Promise<Record<string, unknown>>) | undefined);
22
23
  findMany(opts?: FindManyOpts<TClient>): Promise<TClient[]>;
23
24
  findById(id: unknown): Promise<TClient | null>;
24
25
  insert(data: TCreate, ...args: InsertDbOnlyArgs<TDbOnly>): {
@@ -36,6 +37,7 @@ export declare class TableDB<TClient extends Record<string, unknown>, TCreate, T
36
37
  private missingDeriveDependencies;
37
38
  private fetchClientFieldsById;
38
39
  private pickDbPatchFields;
40
+ private isWritableDbColumn;
39
41
  private parseDbOnlyData;
40
42
  reconcileIds(clientData: unknown, ids: unknown): unknown;
41
43
  private reconcileFlatIds;
@@ -6,11 +6,13 @@ export class TableDB {
6
6
  meta;
7
7
  transforms;
8
8
  reconcile;
9
- constructor(db, meta, transforms, reconcile) {
9
+ hydrateRow;
10
+ constructor(db, meta, transforms, reconcile, hydrateRow) {
10
11
  this.db = db;
11
12
  this.meta = meta;
12
13
  this.transforms = transforms;
13
14
  this.reconcile = reconcile;
15
+ this.hydrateRow = hydrateRow;
14
16
  }
15
17
  async findMany(opts) {
16
18
  const qb = this.db;
@@ -36,7 +38,10 @@ export class TableDB {
36
38
  query = query.offset(opts.offset);
37
39
  }
38
40
  const rows = (await query.execute());
39
- return rows.map((r) => this.transforms.parseFromDb(r));
41
+ const hydratedRows = this.hydrateRow
42
+ ? await Promise.all(rows.map((row) => this.hydrateRow(row)))
43
+ : rows;
44
+ return hydratedRows.map((r) => this.transforms.parseFromDb(r));
40
45
  }
41
46
  async findById(id) {
42
47
  const pkValues = Array.isArray(id) ? id : [id];
@@ -54,7 +59,8 @@ export class TableDB {
54
59
  const row = rows[0] ?? null;
55
60
  if (!row)
56
61
  return null;
57
- return this.transforms.parseFromDb(row);
62
+ const hydratedRow = this.hydrateRow ? await this.hydrateRow(row) : row;
63
+ return this.transforms.parseFromDb(hydratedRow);
58
64
  }
59
65
  insert(data, ...args) {
60
66
  const dbOnlyData = args[0];
@@ -82,7 +88,7 @@ export class TableDB {
82
88
  }));
83
89
  const insertData = {};
84
90
  for (const key of Object.keys(dbData)) {
85
- if (!pkDbNames.has(key)) {
91
+ if (!pkDbNames.has(key) && this.isWritableDbColumn(key)) {
86
92
  insertData[key] = dbData[key];
87
93
  }
88
94
  }
@@ -200,13 +206,22 @@ export class TableDB {
200
206
  pickDbPatchFields(dbData, clientKeys) {
201
207
  const picked = {};
202
208
  for (const clientKey of clientKeys) {
203
- const dbName = this.meta.clientToDbName.get(clientKey) ?? clientKey;
209
+ const dbName = this.meta.clientToDbName.get(clientKey);
210
+ if (!dbName)
211
+ continue;
204
212
  if (dbData[dbName] !== undefined) {
205
213
  picked[dbName] = dbData[dbName];
206
214
  }
207
215
  }
208
216
  return picked;
209
217
  }
218
+ isWritableDbColumn(dbName) {
219
+ for (const field of this.meta.dbFields.values()) {
220
+ if (field.dbName === dbName)
221
+ return true;
222
+ }
223
+ return false;
224
+ }
210
225
  parseDbOnlyData(dbOnlyData, opts = { requireRequired: false }) {
211
226
  if (opts.requireRequired) {
212
227
  for (const requiredKey of this.meta.sqlOnlyRequiredClientFields) {
package/dist/schema.js CHANGED
@@ -760,7 +760,11 @@ export function createSchemaBox(schemas, resolutions) {
760
760
  if (targetField && targetField.config) {
761
761
  const newConfig = {
762
762
  ...targetField.config,
763
- sql: { ...targetField.config.sql, isForeignKey: true },
763
+ sql: {
764
+ ...targetField.config.sql,
765
+ field: fieldName,
766
+ isForeignKey: true,
767
+ },
764
768
  };
765
769
  resolvedSchemas[tableName][fieldName] = {
766
770
  ...targetField,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cogsbox-shape",
3
- "version": "0.5.188",
3
+ "version": "0.5.190",
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",