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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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: {
|
|
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.
|
|
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",
|