@travetto/model-sql 7.0.0-rc.1 → 7.0.0-rc.3
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/package.json +7 -7
- package/src/config.ts +5 -5
- package/src/connection/base.ts +30 -30
- package/src/connection/decorator.ts +17 -17
- package/src/dialect/base.ts +225 -157
- package/src/service.ts +52 -59
- package/src/table-manager.ts +91 -61
- package/src/types.ts +1 -1
- package/src/util.ts +75 -75
- package/support/test/query.ts +2 -2
package/src/util.ts
CHANGED
|
@@ -8,9 +8,9 @@ import { TableSymbol, VisitStack } from './types.ts';
|
|
|
8
8
|
|
|
9
9
|
type FieldCacheEntry = {
|
|
10
10
|
local: SchemaFieldConfig[];
|
|
11
|
-
localMap: Record<string
|
|
11
|
+
localMap: Record<string, SchemaFieldConfig>;
|
|
12
12
|
foreign: SchemaFieldConfig[];
|
|
13
|
-
foreignMap: Record<string
|
|
13
|
+
foreignMap: Record<string, SchemaFieldConfig>;
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -30,22 +30,22 @@ export class SQLModelUtil {
|
|
|
30
30
|
/**
|
|
31
31
|
* Clean results from db, by dropping internal fields
|
|
32
32
|
*/
|
|
33
|
-
static cleanResults<T, U = T>(
|
|
34
|
-
static cleanResults<T, U = T>(
|
|
35
|
-
static cleanResults<T, U = T>(
|
|
36
|
-
if (Array.isArray(
|
|
37
|
-
return
|
|
38
|
-
} else if (!DataUtil.isSimpleValue(
|
|
39
|
-
for (const
|
|
40
|
-
if (
|
|
41
|
-
delete
|
|
33
|
+
static cleanResults<T, U = T>(state: DialectState, item: T[]): U[];
|
|
34
|
+
static cleanResults<T, U = T>(state: DialectState, item: T): U;
|
|
35
|
+
static cleanResults<T, U = T>(state: DialectState, item: T | T[]): U | U[] {
|
|
36
|
+
if (Array.isArray(item)) {
|
|
37
|
+
return item.filter(value => value !== null && value !== undefined).map(value => this.cleanResults(state, value));
|
|
38
|
+
} else if (!DataUtil.isSimpleValue(item)) {
|
|
39
|
+
for (const key of TypedObject.keys(item)) {
|
|
40
|
+
if (item[key] === null || item[key] === undefined || key === state.parentPathField.name || key === state.pathField.name || key === state.idxField.name) {
|
|
41
|
+
delete item[key];
|
|
42
42
|
} else {
|
|
43
|
-
|
|
43
|
+
item[key] = this.cleanResults(state, item[key]);
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
-
return castTo({ ...
|
|
46
|
+
return castTo({ ...item });
|
|
47
47
|
} else {
|
|
48
|
-
return castTo(
|
|
48
|
+
return castTo(item);
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
|
|
@@ -54,13 +54,13 @@ export class SQLModelUtil {
|
|
|
54
54
|
*/
|
|
55
55
|
static getFieldsByLocation(stack: VisitStack[]): FieldCacheEntry {
|
|
56
56
|
const top = stack.at(-1)!;
|
|
57
|
-
const
|
|
57
|
+
const config = SchemaRegistryIndex.getOptional(top.type)?.get();
|
|
58
58
|
|
|
59
|
-
if (
|
|
60
|
-
return this.#schemaFieldsCache.get(
|
|
59
|
+
if (config && this.#schemaFieldsCache.has(config.class)) {
|
|
60
|
+
return this.#schemaFieldsCache.get(config.class)!;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
if (!
|
|
63
|
+
if (!config) { // If a simple type, it is it's own field
|
|
64
64
|
const field: SchemaFieldConfig = castTo({ ...top });
|
|
65
65
|
return {
|
|
66
66
|
local: [field], localMap: { [field.name]: field },
|
|
@@ -68,15 +68,15 @@ export class SQLModelUtil {
|
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
const hasModel = ModelRegistryIndex.has(
|
|
72
|
-
const fields = Object.values(
|
|
71
|
+
const hasModel = ModelRegistryIndex.has(config.class)!;
|
|
72
|
+
const fields = Object.values(config.fields).map(field => ({ ...field }));
|
|
73
73
|
|
|
74
74
|
// Polymorphic
|
|
75
|
-
if (hasModel &&
|
|
76
|
-
const fieldMap = new Set(fields.map(
|
|
77
|
-
for (const type of SchemaRegistryIndex.getDiscriminatedClasses(
|
|
78
|
-
const
|
|
79
|
-
for (const [fieldName, field] of Object.entries<SchemaFieldConfig>(
|
|
75
|
+
if (hasModel && config.discriminatedBase) {
|
|
76
|
+
const fieldMap = new Set(fields.map(field => field.name));
|
|
77
|
+
for (const type of SchemaRegistryIndex.getDiscriminatedClasses(config.class)) {
|
|
78
|
+
const typeConfig = SchemaRegistryIndex.getConfig(type);
|
|
79
|
+
for (const [fieldName, field] of Object.entries<SchemaFieldConfig>(typeConfig.fields)) {
|
|
80
80
|
if (!fieldMap.has(fieldName)) {
|
|
81
81
|
fieldMap.add(fieldName);
|
|
82
82
|
fields.push({ ...field, required: { active: false } });
|
|
@@ -85,26 +85,26 @@ export class SQLModelUtil {
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
const
|
|
88
|
+
const entry: FieldCacheEntry = {
|
|
89
89
|
localMap: {},
|
|
90
90
|
foreignMap: {},
|
|
91
|
-
local: fields.filter(
|
|
92
|
-
foreign: fields.filter(
|
|
91
|
+
local: fields.filter(field => !SchemaRegistryIndex.has(field.type) && !field.array),
|
|
92
|
+
foreign: fields.filter(field => SchemaRegistryIndex.has(field.type) || field.array)
|
|
93
93
|
};
|
|
94
94
|
|
|
95
|
-
|
|
96
|
-
|
|
95
|
+
entry.local.reduce((map, field) => (map[field.name] = field) && map, entry.localMap);
|
|
96
|
+
entry.foreign.reduce((map, field) => (map[field.name] = field) && map, entry.foreignMap);
|
|
97
97
|
|
|
98
|
-
this.#schemaFieldsCache.set(
|
|
98
|
+
this.#schemaFieldsCache.set(config.class, entry);
|
|
99
99
|
|
|
100
|
-
return
|
|
100
|
+
return entry;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
/**
|
|
104
104
|
* Process a schema structure, synchronously
|
|
105
105
|
*/
|
|
106
106
|
static visitSchemaSync(config: SchemaClassConfig | SchemaFieldConfig, handler: VisitHandler<void>, state: VisitState = { path: [] }): void {
|
|
107
|
-
const path = '
|
|
107
|
+
const path = 'fields' in config ? this.classToStack(config.class) : [...state.path, config];
|
|
108
108
|
const { local: fields, foreign } = this.getFieldsByLocation(path);
|
|
109
109
|
|
|
110
110
|
const descend = (): void => {
|
|
@@ -121,7 +121,7 @@ export class SQLModelUtil {
|
|
|
121
121
|
}
|
|
122
122
|
};
|
|
123
123
|
|
|
124
|
-
if ('
|
|
124
|
+
if ('fields' in config) {
|
|
125
125
|
return handler.onRoot({ config, fields, descend, path });
|
|
126
126
|
} else {
|
|
127
127
|
return handler.onSub({ config, fields, descend, path });
|
|
@@ -132,7 +132,7 @@ export class SQLModelUtil {
|
|
|
132
132
|
* Visit a Schema structure
|
|
133
133
|
*/
|
|
134
134
|
static async visitSchema(config: SchemaClassConfig | SchemaFieldConfig, handler: VisitHandler<Promise<void>>, state: VisitState = { path: [] }): Promise<void> {
|
|
135
|
-
const path = '
|
|
135
|
+
const path = 'fields' in config ? this.classToStack(config.class) : [...state.path, config];
|
|
136
136
|
const { local: fields, foreign } = this.getFieldsByLocation(path);
|
|
137
137
|
|
|
138
138
|
const descend = async (): Promise<void> => {
|
|
@@ -149,7 +149,7 @@ export class SQLModelUtil {
|
|
|
149
149
|
}
|
|
150
150
|
};
|
|
151
151
|
|
|
152
|
-
if ('
|
|
152
|
+
if ('fields' in config) {
|
|
153
153
|
return handler.onRoot({ config, fields, descend, path });
|
|
154
154
|
} else {
|
|
155
155
|
return handler.onSub({ config, fields, descend, path });
|
|
@@ -160,7 +160,7 @@ export class SQLModelUtil {
|
|
|
160
160
|
* Process a schema instance by visiting it synchronously. This is synchronous to prevent concurrent calls from breaking
|
|
161
161
|
*/
|
|
162
162
|
static visitSchemaInstance<T extends ModelType>(cls: Class<T>, instance: T | OptionalId<T>, handler: VisitHandler<unknown, VisitInstanceNode<unknown>>): void {
|
|
163
|
-
const
|
|
163
|
+
const pathStack: unknown[] = [instance];
|
|
164
164
|
this.visitSchemaSync(SchemaRegistryIndex.getConfig(cls), {
|
|
165
165
|
onRoot: (config) => {
|
|
166
166
|
const { path } = config;
|
|
@@ -170,24 +170,24 @@ export class SQLModelUtil {
|
|
|
170
170
|
},
|
|
171
171
|
onSub: (config) => {
|
|
172
172
|
const { config: field } = config;
|
|
173
|
-
const
|
|
173
|
+
const topObject: Record<string, unknown> = castTo(pathStack.at(-1));
|
|
174
174
|
const top = config.path.at(-1)!;
|
|
175
175
|
|
|
176
|
-
if (field.name in
|
|
177
|
-
const
|
|
178
|
-
const values = Array.isArray(
|
|
176
|
+
if (field.name in topObject) {
|
|
177
|
+
const valuesInput = topObject[field.name];
|
|
178
|
+
const values = Array.isArray(valuesInput) ? valuesInput : [valuesInput];
|
|
179
179
|
|
|
180
180
|
let i = 0;
|
|
181
|
-
for (const
|
|
181
|
+
for (const value of values) {
|
|
182
182
|
try {
|
|
183
|
-
|
|
183
|
+
pathStack.push(value);
|
|
184
184
|
config.path[config.path.length - 1] = { ...top, index: i++ };
|
|
185
|
-
handler.onSub({ ...config, value
|
|
185
|
+
handler.onSub({ ...config, value });
|
|
186
186
|
if (!field.array) {
|
|
187
187
|
config.descend();
|
|
188
188
|
}
|
|
189
189
|
} finally {
|
|
190
|
-
|
|
190
|
+
pathStack.pop();
|
|
191
191
|
}
|
|
192
192
|
i += 1;
|
|
193
193
|
}
|
|
@@ -198,8 +198,8 @@ export class SQLModelUtil {
|
|
|
198
198
|
},
|
|
199
199
|
onSimple: (config) => {
|
|
200
200
|
const { config: field } = config;
|
|
201
|
-
const
|
|
202
|
-
const value =
|
|
201
|
+
const topObject: Record<string, unknown> = castTo(pathStack.at(-1));
|
|
202
|
+
const value = topObject[field.name];
|
|
203
203
|
return handler.onSimple({ ...config, value });
|
|
204
204
|
}
|
|
205
205
|
});
|
|
@@ -210,26 +210,26 @@ export class SQLModelUtil {
|
|
|
210
210
|
*/
|
|
211
211
|
static select<T>(cls: Class<T>, select?: SelectClause<T>): SchemaFieldConfig[] {
|
|
212
212
|
if (!select || Object.keys(select).length === 0) {
|
|
213
|
-
return [{ type: cls, name: '*',
|
|
213
|
+
return [{ type: cls, name: '*', class: cls, array: false }];
|
|
214
214
|
}
|
|
215
215
|
|
|
216
216
|
const { localMap } = this.getFieldsByLocation(this.classToStack(cls));
|
|
217
217
|
|
|
218
218
|
let toGet = new Set<string>();
|
|
219
219
|
|
|
220
|
-
for (const [
|
|
221
|
-
if (typeof
|
|
222
|
-
if (!
|
|
220
|
+
for (const [key, value] of TypedObject.entries(select)) {
|
|
221
|
+
if (typeof key === 'string' && !DataUtil.isPlainObject(select[key]) && localMap[key]) {
|
|
222
|
+
if (!value) {
|
|
223
223
|
if (toGet.size === 0) {
|
|
224
224
|
toGet = new Set(Object.keys(SchemaRegistryIndex.getConfig(cls).fields));
|
|
225
225
|
}
|
|
226
|
-
toGet.delete(
|
|
226
|
+
toGet.delete(key);
|
|
227
227
|
} else {
|
|
228
|
-
toGet.add(
|
|
228
|
+
toGet.add(key);
|
|
229
229
|
}
|
|
230
230
|
}
|
|
231
231
|
}
|
|
232
|
-
return [...toGet].map(
|
|
232
|
+
return [...toGet].map(field => localMap[field]);
|
|
233
233
|
}
|
|
234
234
|
|
|
235
235
|
/**
|
|
@@ -242,15 +242,15 @@ export class SQLModelUtil {
|
|
|
242
242
|
let found: OrderBy | undefined;
|
|
243
243
|
while (!found) {
|
|
244
244
|
const key = Object.keys(cl)[0];
|
|
245
|
-
const
|
|
245
|
+
const value = cl[key];
|
|
246
246
|
const field = { ...schema.fields[key] };
|
|
247
|
-
if (DataUtil.isPrimitive(
|
|
247
|
+
if (DataUtil.isPrimitive(value)) {
|
|
248
248
|
stack.push(field);
|
|
249
|
-
found = { stack, asc:
|
|
249
|
+
found = { stack, asc: value === 1 };
|
|
250
250
|
} else {
|
|
251
251
|
stack.push(field);
|
|
252
252
|
schema = SchemaRegistryIndex.getConfig(field.type);
|
|
253
|
-
cl = castTo(
|
|
253
|
+
cl = castTo(value);
|
|
254
254
|
}
|
|
255
255
|
}
|
|
256
256
|
return found;
|
|
@@ -260,30 +260,30 @@ export class SQLModelUtil {
|
|
|
260
260
|
/**
|
|
261
261
|
* Find all dependent fields via child tables
|
|
262
262
|
*/
|
|
263
|
-
static collectDependents<T>(
|
|
263
|
+
static collectDependents<T>(state: DialectState, parent: unknown, items: T[], field?: SchemaFieldConfig): Record<string, T> {
|
|
264
264
|
if (field) {
|
|
265
265
|
const isSimple = SchemaRegistryIndex.has(field.type);
|
|
266
|
-
for (const
|
|
267
|
-
const parentKey: string = castTo(
|
|
266
|
+
for (const item of items) {
|
|
267
|
+
const parentKey: string = castTo(item[castKey<T>(state.parentPathField.name)]);
|
|
268
268
|
const root = castTo<Record<string, Record<string, unknown>>>(parent)[parentKey];
|
|
269
269
|
const fieldKey = castKey<(typeof root) | T>(field.name);
|
|
270
270
|
if (field.array) {
|
|
271
271
|
if (!root[fieldKey]) {
|
|
272
|
-
root[fieldKey] = [isSimple ?
|
|
272
|
+
root[fieldKey] = [isSimple ? item : item[fieldKey]];
|
|
273
273
|
} else if (Array.isArray(root[fieldKey])) {
|
|
274
|
-
root[fieldKey].push(isSimple ?
|
|
274
|
+
root[fieldKey].push(isSimple ? item : item[fieldKey]);
|
|
275
275
|
}
|
|
276
276
|
} else {
|
|
277
|
-
root[fieldKey] = isSimple ?
|
|
277
|
+
root[fieldKey] = isSimple ? item : item[fieldKey];
|
|
278
278
|
}
|
|
279
279
|
}
|
|
280
280
|
}
|
|
281
281
|
|
|
282
282
|
const mapping: Record<string, T> = {};
|
|
283
|
-
for (const
|
|
284
|
-
const key =
|
|
283
|
+
for (const item of items) {
|
|
284
|
+
const key = item[castKey<T>(state.pathField.name)];
|
|
285
285
|
if (typeof key === 'string') {
|
|
286
|
-
mapping[key] =
|
|
286
|
+
mapping[key] = item;
|
|
287
287
|
}
|
|
288
288
|
}
|
|
289
289
|
return mapping;
|
|
@@ -295,7 +295,7 @@ export class SQLModelUtil {
|
|
|
295
295
|
static buildTable(list: VisitStack[]): string {
|
|
296
296
|
const top = list.at(-1)!;
|
|
297
297
|
if (!top[TableSymbol]) {
|
|
298
|
-
top[TableSymbol] = list.map((
|
|
298
|
+
top[TableSymbol] = list.map((item, i) => i === 0 ? ModelRegistryIndex.getStoreName(item.type) : item.name).join('_');
|
|
299
299
|
}
|
|
300
300
|
return top[TableSymbol]!;
|
|
301
301
|
}
|
|
@@ -304,22 +304,22 @@ export class SQLModelUtil {
|
|
|
304
304
|
* Build property path for a table/field given the current stack
|
|
305
305
|
*/
|
|
306
306
|
static buildPath(list: VisitStack[]): string {
|
|
307
|
-
return list.map((
|
|
307
|
+
return list.map((item) => `${item.name}${item.index ? `[${item.index}]` : ''}`).join('.');
|
|
308
308
|
}
|
|
309
309
|
|
|
310
310
|
/**
|
|
311
311
|
* Get insert statements for a given class, and its child tables
|
|
312
312
|
*/
|
|
313
|
-
static async getInserts<T extends ModelType>(cls: Class<T>,
|
|
314
|
-
const
|
|
313
|
+
static async getInserts<T extends ModelType>(cls: Class<T>, items: (T | OptionalId<T>)[]): Promise<InsertWrapper[]> {
|
|
314
|
+
const wrappers: Record<string, InsertWrapper> = {};
|
|
315
315
|
|
|
316
316
|
const track = (stack: VisitStack[], value: unknown): void => {
|
|
317
317
|
const key = this.buildTable(stack);
|
|
318
|
-
(
|
|
318
|
+
(wrappers[key] ??= { stack, records: [] }).records.push({ stack, value });
|
|
319
319
|
};
|
|
320
320
|
|
|
321
|
-
const all =
|
|
322
|
-
this.visitSchemaInstance(cls,
|
|
321
|
+
const all = items.map(item =>
|
|
322
|
+
this.visitSchemaInstance(cls, item, {
|
|
323
323
|
onRoot: ({ path, value }) => track(path, value),
|
|
324
324
|
onSub: ({ path, value }) => track(path, value),
|
|
325
325
|
onSimple: ({ path, value }) => track(path, value)
|
|
@@ -327,7 +327,7 @@ export class SQLModelUtil {
|
|
|
327
327
|
|
|
328
328
|
await Promise.all(all);
|
|
329
329
|
|
|
330
|
-
const result = [...Object.values(
|
|
330
|
+
const result = [...Object.values(wrappers)].toSorted((a, b) => a.stack.length - b.stack.length);
|
|
331
331
|
return result;
|
|
332
332
|
}
|
|
333
333
|
}
|
package/support/test/query.ts
CHANGED
|
@@ -69,7 +69,7 @@ export abstract class BaseSQLTest extends BaseModelSuite<SQLModelService> {
|
|
|
69
69
|
dct.resolveName = (stack: VisitStack[]) => {
|
|
70
70
|
const field: SchemaFieldConfig = castTo(stack.at(-1));
|
|
71
71
|
const parent: SchemaFieldConfig = castTo(stack.at(-2));
|
|
72
|
-
return `${field.
|
|
72
|
+
return `${field.class ? field.class.name.toString() : parent.name.toString()}.${field.name.toString()}`;
|
|
73
73
|
};
|
|
74
74
|
|
|
75
75
|
const qryStr = dct.getWhereGroupingSQL(WhereType, qry);
|
|
@@ -81,7 +81,7 @@ export abstract class BaseSQLTest extends BaseModelSuite<SQLModelService> {
|
|
|
81
81
|
const dct = await this.dialect;
|
|
82
82
|
dct.resolveName = (stack: VisitStack[]) => {
|
|
83
83
|
const field: SchemaFieldConfig = castTo(stack.at(-1));
|
|
84
|
-
return `${field.
|
|
84
|
+
return `${field.class?.name}.${field.name.toString()}`;
|
|
85
85
|
};
|
|
86
86
|
|
|
87
87
|
const out = dct.getWhereGroupingSQL(User, {
|