@proteinjs/db 1.11.0 → 1.12.0
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/CHANGELOG.md +16 -0
- package/dist/generated/index.js +1 -1
- package/dist/generated/index.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/src/Columns.d.ts +30 -0
- package/dist/src/Columns.d.ts.map +1 -1
- package/dist/src/Columns.js +203 -1
- package/dist/src/Columns.js.map +1 -1
- package/dist/src/Db.d.ts +2 -1
- package/dist/src/Db.d.ts.map +1 -1
- package/dist/src/Db.js +8 -7
- package/dist/src/Db.js.map +1 -1
- package/dist/src/Record.d.ts +1 -1
- package/dist/src/Record.d.ts.map +1 -1
- package/dist/src/Record.js +3 -3
- package/dist/src/Record.js.map +1 -1
- package/dist/src/Table.d.ts +5 -4
- package/dist/src/Table.d.ts.map +1 -1
- package/dist/src/Table.js +13 -12
- package/dist/src/Table.js.map +1 -1
- package/dist/src/schema/TableManager.d.ts +1 -0
- package/dist/src/schema/TableManager.d.ts.map +1 -1
- package/dist/src/schema/TableManager.js +48 -1
- package/dist/src/schema/TableManager.js.map +1 -1
- package/dist/src/transaction/TransactionRunner.d.ts +5 -0
- package/dist/src/transaction/TransactionRunner.d.ts.map +1 -1
- package/dist/src/transaction/TransactionRunner.js +5 -0
- package/dist/src/transaction/TransactionRunner.js.map +1 -1
- package/dist/test/reusable/DynamicReferenceColumn.d.ts +77 -0
- package/dist/test/reusable/DynamicReferenceColumn.d.ts.map +1 -0
- package/dist/test/reusable/DynamicReferenceColumn.js +656 -0
- package/dist/test/reusable/DynamicReferenceColumn.js.map +1 -0
- package/generated/index.ts +1 -1
- package/index.ts +1 -0
- package/package.json +3 -3
- package/src/Columns.ts +190 -1
- package/src/Db.ts +12 -6
- package/src/Record.ts +7 -3
- package/src/Table.ts +17 -7
- package/src/schema/TableManager.ts +61 -0
- package/src/transaction/TransactionRunner.ts +6 -0
- package/test/reusable/DynamicReferenceColumn.ts +487 -0
package/src/Columns.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import moment from 'moment';
|
|
2
2
|
import { v1 as uuidv1 } from 'uuid';
|
|
3
|
-
import { Column, ColumnOptions, Table, tableByName } from './Table';
|
|
3
|
+
import { Column, ColumnOptions, getColumnPropertyName, Table, tableByName } from './Table';
|
|
4
4
|
import { Record } from './Record';
|
|
5
5
|
import { ReferenceArray } from './reference/ReferenceArray';
|
|
6
6
|
import { Db } from './Db';
|
|
@@ -350,3 +350,192 @@ export class ReferenceColumn<T extends Record> extends StringColumn<Reference<T>
|
|
|
350
350
|
await new Db().delete(referenceTable, qb);
|
|
351
351
|
}
|
|
352
352
|
}
|
|
353
|
+
|
|
354
|
+
/** Column type for storing table names that links to a DynamicReferenceColumn */
|
|
355
|
+
export class DynamicReferenceTableNameColumn extends StringColumn<string> {
|
|
356
|
+
constructor(
|
|
357
|
+
name: string,
|
|
358
|
+
public referenceColumnName: string,
|
|
359
|
+
options?: ColumnOptions
|
|
360
|
+
) {
|
|
361
|
+
const enhancedOptions = {
|
|
362
|
+
...options,
|
|
363
|
+
defaultValue: async (table: Table<any>, record: any) => {
|
|
364
|
+
const colPropertyName = getColumnPropertyName(table, name);
|
|
365
|
+
const refColPropertyName = getColumnPropertyName(table, referenceColumnName);
|
|
366
|
+
if (!colPropertyName) {
|
|
367
|
+
throw new Error(`Column ${name} in table ${table.name} not found when setting default value`);
|
|
368
|
+
}
|
|
369
|
+
if (!refColPropertyName) {
|
|
370
|
+
throw new Error(`Column ${referenceColumnName} in table ${table.name} not found when setting default value`);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// No reference is being set, so we can return early
|
|
374
|
+
if (!record[refColPropertyName]) {
|
|
375
|
+
return options?.defaultValue ? await options.defaultValue(table, record) : record[colPropertyName] ?? null;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Get the table name from the reference column
|
|
379
|
+
const { _table: referenceTableName } = record[refColPropertyName];
|
|
380
|
+
|
|
381
|
+
if (!referenceTableName) {
|
|
382
|
+
throw new Error(
|
|
383
|
+
`When inserting, table name must be set in Reference object for DynamicReferenceColumn ${referenceColumnName}`
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Assign the table name and return it, unless an defaultValue function is provided via options
|
|
388
|
+
record[colPropertyName] = referenceTableName;
|
|
389
|
+
return options?.defaultValue ? await options.defaultValue(table, record) : referenceTableName;
|
|
390
|
+
},
|
|
391
|
+
updateValue: async (table: Table<any>, updateObj: any) => {
|
|
392
|
+
const colPropertyName = getColumnPropertyName(table, name);
|
|
393
|
+
const refColPropertyName = getColumnPropertyName(table, referenceColumnName);
|
|
394
|
+
if (!colPropertyName) {
|
|
395
|
+
throw new Error(`Column ${name} in table ${table.name} not found when setting default value`);
|
|
396
|
+
}
|
|
397
|
+
if (!refColPropertyName) {
|
|
398
|
+
throw new Error(`Column ${referenceColumnName} in table ${table.name} not found when setting default value`);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// The reference column is not being updated, so we can return early
|
|
402
|
+
if (!updateObj[refColPropertyName]) {
|
|
403
|
+
return options?.updateValue?.(table, updateObj) ?? updateObj[colPropertyName] ?? undefined;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Get the table name from the new reference column
|
|
407
|
+
const { _table: newTableName } = updateObj[refColPropertyName];
|
|
408
|
+
|
|
409
|
+
if (!newTableName) {
|
|
410
|
+
throw new Error(
|
|
411
|
+
`When inserting, table name must be set in Reference object for DynamicReferenceColumn ${referenceColumnName}`
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Assign the new table name and return it, unless an updateValue function is provided via options
|
|
416
|
+
updateObj[colPropertyName] = newTableName;
|
|
417
|
+
return options?.updateValue?.(table, updateObj) ?? newTableName;
|
|
418
|
+
},
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
super(
|
|
422
|
+
name,
|
|
423
|
+
Object.assign(
|
|
424
|
+
{
|
|
425
|
+
ui: {
|
|
426
|
+
hidden: true,
|
|
427
|
+
},
|
|
428
|
+
},
|
|
429
|
+
enhancedOptions
|
|
430
|
+
)
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Creates a dynamic reference column that can link to records in any table
|
|
437
|
+
*
|
|
438
|
+
* The reference is stored as two columns:
|
|
439
|
+
* 1. A `DynamicReferenceTableNameColumn` storing the reference's table name, which is managed internally and should not be set or updated
|
|
440
|
+
* 2. A `DynamicReferenceColumn` which is a reference to a record
|
|
441
|
+
*
|
|
442
|
+
* @example
|
|
443
|
+
* {
|
|
444
|
+
* referenceTableName: new DynamicReferenceTableNameColumn('reference_table_name', 'dynamic_reference'),
|
|
445
|
+
* dynamicReference: new DynamicReferenceColumn<EntityType>(
|
|
446
|
+
* 'dynamic_reference',
|
|
447
|
+
* 'reference_table_name', // Name of column containing table name
|
|
448
|
+
* )
|
|
449
|
+
* }
|
|
450
|
+
*/
|
|
451
|
+
|
|
452
|
+
export class DynamicReferenceColumn<T extends Record> extends StringColumn<Reference<T>> {
|
|
453
|
+
constructor(
|
|
454
|
+
name: string,
|
|
455
|
+
public dynamicRefTableColName: string,
|
|
456
|
+
public cascadeDelete: boolean = false,
|
|
457
|
+
options?: ColumnOptions
|
|
458
|
+
) {
|
|
459
|
+
super(
|
|
460
|
+
name,
|
|
461
|
+
Object.assign(
|
|
462
|
+
{
|
|
463
|
+
ui: {
|
|
464
|
+
hidden: true,
|
|
465
|
+
},
|
|
466
|
+
},
|
|
467
|
+
options
|
|
468
|
+
),
|
|
469
|
+
36
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
async serialize(fieldValue: Reference<T> | null | undefined): Promise<string | null> {
|
|
474
|
+
if (fieldValue === undefined || fieldValue == null || !fieldValue._id) {
|
|
475
|
+
return null;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if (!fieldValue._table || fieldValue._table.trim() === '') {
|
|
479
|
+
throw new Error(`Table name must be provided for DynamicReferenceColumn ${this.name}`);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
return fieldValue._id;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
async deserialize(serializedFieldValue: string, serializedRecord: any): Promise<Reference<T> | null> {
|
|
486
|
+
const reference = new Reference('', serializedFieldValue);
|
|
487
|
+
if (reference._id === null) {
|
|
488
|
+
return null;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const tableName = serializedRecord[this.dynamicRefTableColName];
|
|
492
|
+
if (!tableName) {
|
|
493
|
+
throw new Error(`Table name not found in column ${this.dynamicRefTableColName} for reference ${this.name}`);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
return new Reference<T>(tableName, serializedFieldValue);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
async beforeDelete(
|
|
500
|
+
table: Table<any>,
|
|
501
|
+
columnPropertyName: string,
|
|
502
|
+
records: any[],
|
|
503
|
+
getTable?: (tableName: string) => Table<any>,
|
|
504
|
+
db?: Db
|
|
505
|
+
): Promise<void> {
|
|
506
|
+
if (!this.cascadeDelete) {
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
const getTableFn = getTable ? getTable : tableByName;
|
|
511
|
+
const dbInstance = db ? db : new Db();
|
|
512
|
+
|
|
513
|
+
// Get all referenced record IDs grouped by table
|
|
514
|
+
const recordsToDelete = new Map<string, string[]>();
|
|
515
|
+
|
|
516
|
+
for (const record of records) {
|
|
517
|
+
const reference = record[columnPropertyName] as Reference<Record>;
|
|
518
|
+
if (!reference?._id || !reference._table) {
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
if (!recordsToDelete.has(reference._table)) {
|
|
523
|
+
recordsToDelete.set(reference._table, []);
|
|
524
|
+
}
|
|
525
|
+
recordsToDelete.get(reference._table)!.push(reference._id);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Delete records from each referenced table using Promise.all to properly await all deletions
|
|
529
|
+
const entries = Array.from(recordsToDelete.entries());
|
|
530
|
+
console.log(`entries to delete: ${JSON.stringify(entries)}`);
|
|
531
|
+
for (const [tableName, ids] of entries) {
|
|
532
|
+
if (ids.length > 0) {
|
|
533
|
+
const referenceTable = getTableFn(tableName);
|
|
534
|
+
const qb = new QueryBuilderFactory()
|
|
535
|
+
.getQueryBuilder(referenceTable)
|
|
536
|
+
.condition({ field: 'id', operator: 'IN', value: ids });
|
|
537
|
+
await dbInstance.delete(referenceTable, qb);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
package/src/Db.ts
CHANGED
|
@@ -75,7 +75,7 @@ export class Db<R extends Record = Record> implements DbService<R> {
|
|
|
75
75
|
|
|
76
76
|
constructor(
|
|
77
77
|
dbDriver?: DbDriver,
|
|
78
|
-
getTable?: (tableName: string) => Table<any>,
|
|
78
|
+
private getTable?: (tableName: string) => Table<any>,
|
|
79
79
|
transactionContextFactory?: DefaultTransactionContextFactory,
|
|
80
80
|
private runAsSystem: boolean = false
|
|
81
81
|
) {
|
|
@@ -169,8 +169,8 @@ export class Db<R extends Record = Record> implements DbService<R> {
|
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
recordCopy = await this.tableWatcherRunner.runBeforeUpdateTableWatchers(table, recordCopy, qb);
|
|
172
|
-
const
|
|
173
|
-
const serializedRecord = await
|
|
172
|
+
const recordSerializer = new RecordSerializer<T>(table);
|
|
173
|
+
const serializedRecord = await recordSerializer.serialize(recordCopy);
|
|
174
174
|
delete serializedRecord['id'];
|
|
175
175
|
const generateUpdate = (config: DbDriverDmlStatementConfig) =>
|
|
176
176
|
new StatementFactory<T>().update(
|
|
@@ -212,7 +212,13 @@ export class Db<R extends Record = Record> implements DbService<R> {
|
|
|
212
212
|
for (const columnPropertyName in table.columns) {
|
|
213
213
|
const column = (table.columns as any)[columnPropertyName] as Column<any, any>;
|
|
214
214
|
if (typeof column.beforeDelete !== 'undefined') {
|
|
215
|
-
await column.beforeDelete(
|
|
215
|
+
await column.beforeDelete(
|
|
216
|
+
table,
|
|
217
|
+
columnPropertyName,
|
|
218
|
+
recordsToDelete,
|
|
219
|
+
this.getTable,
|
|
220
|
+
new Db(this.dbDriver, this.getTable, this.transactionContextFactory)
|
|
221
|
+
);
|
|
216
222
|
}
|
|
217
223
|
}
|
|
218
224
|
}
|
|
@@ -257,9 +263,9 @@ export class Db<R extends Record = Record> implements DbService<R> {
|
|
|
257
263
|
const generateQuery = (config: DbDriverQueryStatementConfig) =>
|
|
258
264
|
qb.toSql(this.statementConfigFactory.getStatementConfig(config));
|
|
259
265
|
const serializedRecords = await this.dbDriver.runQuery(generateQuery, this.currentTransaction);
|
|
260
|
-
const
|
|
266
|
+
const recordSerializer = new RecordSerializer(table);
|
|
261
267
|
return await Promise.all(
|
|
262
|
-
serializedRecords.map(async (serializedRecord) =>
|
|
268
|
+
serializedRecords.map(async (serializedRecord) => recordSerializer.deserialize(serializedRecord))
|
|
263
269
|
);
|
|
264
270
|
}
|
|
265
271
|
|
package/src/Record.ts
CHANGED
|
@@ -87,7 +87,11 @@ export class RecordSerializer<T extends Record> {
|
|
|
87
87
|
for (const columnName in serializedRecord) {
|
|
88
88
|
const serializedFieldValue = serializedRecord[columnName];
|
|
89
89
|
try {
|
|
90
|
-
const { fieldPropertyName, fieldValue } = await fieldSerializer.deserialize(
|
|
90
|
+
const { fieldPropertyName, fieldValue } = await fieldSerializer.deserialize(
|
|
91
|
+
columnName,
|
|
92
|
+
serializedFieldValue,
|
|
93
|
+
serializedRecord
|
|
94
|
+
);
|
|
91
95
|
deserialized[fieldPropertyName] = fieldValue;
|
|
92
96
|
} catch (MissingFieldError) {
|
|
93
97
|
omittedFields.push(columnName);
|
|
@@ -121,7 +125,7 @@ export class FieldSerializer<T extends Record> {
|
|
|
121
125
|
return { columnName: column.name, serializedFieldValue };
|
|
122
126
|
}
|
|
123
127
|
|
|
124
|
-
async deserialize(columnName: string, serializedFieldValue: any) {
|
|
128
|
+
async deserialize(columnName: string, serializedFieldValue: any, serializedRecord: SerializedRecord) {
|
|
125
129
|
const columns: { [prop: string]: Column<any, any> } = this.table.columns;
|
|
126
130
|
let fieldPropertyName = columnName;
|
|
127
131
|
let column = columns[columnName]; // the scenario that the column name is the same as the property name
|
|
@@ -143,7 +147,7 @@ export class FieldSerializer<T extends Record> {
|
|
|
143
147
|
|
|
144
148
|
let fieldValue = serializedFieldValue;
|
|
145
149
|
if (column.deserialize) {
|
|
146
|
-
fieldValue = await column.deserialize(serializedFieldValue);
|
|
150
|
+
fieldValue = await column.deserialize(serializedFieldValue, serializedRecord);
|
|
147
151
|
}
|
|
148
152
|
|
|
149
153
|
return { fieldPropertyName, fieldValue };
|
package/src/Table.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Loadable, SourceRepository } from '@proteinjs/reflection';
|
|
2
2
|
import { CustomSerializableObject } from '@proteinjs/serializer';
|
|
3
|
-
import { Record } from './Record';
|
|
3
|
+
import { Record, RecordSerializer } from './Record';
|
|
4
4
|
import { TableSerializerId } from './serializers/TableSerializer';
|
|
5
5
|
import { QueryBuilder } from '@proteinjs/db-query';
|
|
6
6
|
import { Identity, TableOperationsAuth } from './auth/TableAuth';
|
|
7
|
+
import { Db } from './Db';
|
|
7
8
|
|
|
8
9
|
export const isTable = (obj: any) => obj.__serializerId === TableSerializerId;
|
|
9
10
|
|
|
@@ -51,7 +52,7 @@ export const addDefaultFieldValues = async (table: Table<any>, record: any, runA
|
|
|
51
52
|
column.options?.forceDefaultValue === true ||
|
|
52
53
|
(typeof column.options?.forceDefaultValue === 'function' && column.options.forceDefaultValue(runAsSystem)))
|
|
53
54
|
) {
|
|
54
|
-
record[columnPropertyName] = await column.options.defaultValue(record);
|
|
55
|
+
record[columnPropertyName] = await column.options.defaultValue(table, record);
|
|
55
56
|
}
|
|
56
57
|
}
|
|
57
58
|
};
|
|
@@ -60,7 +61,10 @@ export const addUpdateFieldValues = async (table: Table<any>, record: any) => {
|
|
|
60
61
|
for (const columnPropertyName in table.columns) {
|
|
61
62
|
const column = (table.columns as any)[columnPropertyName] as Column<any, any>;
|
|
62
63
|
if (column.options?.updateValue) {
|
|
63
|
-
|
|
64
|
+
const value = await column.options.updateValue(table, record);
|
|
65
|
+
if (value !== undefined) {
|
|
66
|
+
record[columnPropertyName] = value;
|
|
67
|
+
}
|
|
64
68
|
}
|
|
65
69
|
}
|
|
66
70
|
};
|
|
@@ -120,8 +124,14 @@ export type Column<T, Serialized> = {
|
|
|
120
124
|
oldName?: string;
|
|
121
125
|
options?: ColumnOptions;
|
|
122
126
|
serialize?: (fieldValue: T | null | undefined) => Promise<Serialized | null | undefined>;
|
|
123
|
-
deserialize?: (serializedFieldValue: Serialized | null) => Promise<T | null | void>;
|
|
124
|
-
beforeDelete?: (
|
|
127
|
+
deserialize?: (serializedFieldValue: Serialized | null, serializedRecord: any) => Promise<T | null | void>;
|
|
128
|
+
beforeDelete?: (
|
|
129
|
+
table: Table<any>,
|
|
130
|
+
columnPropertyName: string,
|
|
131
|
+
records: any[],
|
|
132
|
+
getTable?: (tableName: string) => Table<any>,
|
|
133
|
+
db?: Db
|
|
134
|
+
) => Promise<void>;
|
|
125
135
|
};
|
|
126
136
|
|
|
127
137
|
export type ColumnOptions = {
|
|
@@ -134,11 +144,11 @@ export type ColumnOptions = {
|
|
|
134
144
|
references?: { table: string };
|
|
135
145
|
nullable?: boolean;
|
|
136
146
|
/** Value stored on insert */
|
|
137
|
-
defaultValue?: (insertObj: any) => Promise<any>;
|
|
147
|
+
defaultValue?: (table: Table<any>, insertObj: any) => Promise<any>;
|
|
138
148
|
/** If true, the `defaultValue` function will always provide the value and override any existing value */
|
|
139
149
|
forceDefaultValue?: boolean | ((runAsSystem: boolean) => boolean);
|
|
140
150
|
/** Value stored on update */
|
|
141
|
-
updateValue?: (updateObj: any) => Promise<any>;
|
|
151
|
+
updateValue?: (table: Table<any>, updateObj: any) => Promise<any>;
|
|
142
152
|
/** Add conditions to query; called on every query of this table */
|
|
143
153
|
addToQuery?: (qb: QueryBuilder, runAsSystem: boolean) => Promise<void>;
|
|
144
154
|
ui?: {
|
|
@@ -3,6 +3,7 @@ import { Column, Table, getTables } from '../Table';
|
|
|
3
3
|
import { SchemaOperations, TableChanges } from './SchemaOperations';
|
|
4
4
|
import { SchemaMetadata } from './SchemaMetadata';
|
|
5
5
|
import { DbDriver } from '../Db';
|
|
6
|
+
import { DynamicReferenceColumn, DynamicReferenceTableNameColumn } from '../Columns';
|
|
6
7
|
|
|
7
8
|
export interface ColumnTypeFactory {
|
|
8
9
|
getType(column: Column<any, any>): string;
|
|
@@ -37,6 +38,8 @@ export class TableManager {
|
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
async loadTable(table: Table<any>): Promise<void> {
|
|
41
|
+
this.validateDynamicReferenceColumns(table);
|
|
42
|
+
|
|
40
43
|
if (await this.tableExists(table)) {
|
|
41
44
|
const tableChanges = await this.getTableChanges(table);
|
|
42
45
|
if (this.shouldAlterTable(tableChanges)) {
|
|
@@ -51,6 +54,64 @@ export class TableManager {
|
|
|
51
54
|
}
|
|
52
55
|
}
|
|
53
56
|
|
|
57
|
+
private validateDynamicReferenceColumns(table: Table<any>): void {
|
|
58
|
+
const isDynamicRefColumn = (column: any): column is DynamicReferenceColumn<any> =>
|
|
59
|
+
typeof column.dynamicRefTableColName === 'string';
|
|
60
|
+
|
|
61
|
+
const isDynamicRefTableNameColumn = (column: any): column is DynamicReferenceTableNameColumn =>
|
|
62
|
+
typeof column.referenceColumnName === 'string';
|
|
63
|
+
|
|
64
|
+
// Quick check if there are any dynamic reference columns
|
|
65
|
+
const hasDynamicColumns = Object.values(table.columns).some(
|
|
66
|
+
(column) => isDynamicRefColumn(column) || isDynamicRefTableNameColumn(column)
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
if (!hasDynamicColumns) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
interface DynamicRefColumnInfo {
|
|
74
|
+
columnName: string;
|
|
75
|
+
tableColumnName: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const dynamicRefColumns: DynamicRefColumnInfo[] = [];
|
|
79
|
+
const dynamicRefTableNameColumns: string[] = [];
|
|
80
|
+
|
|
81
|
+
// First pass: collect all dynamic reference columns
|
|
82
|
+
Object.entries(table.columns).forEach(([propertyName, column]) => {
|
|
83
|
+
if (isDynamicRefColumn(column)) {
|
|
84
|
+
dynamicRefColumns.push({
|
|
85
|
+
columnName: column.name,
|
|
86
|
+
tableColumnName: column.dynamicRefTableColName,
|
|
87
|
+
});
|
|
88
|
+
} else if (isDynamicRefTableNameColumn(column)) {
|
|
89
|
+
dynamicRefTableNameColumns.push(column.name);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Second pass: validate that each DynamicReferenceColumn has its required table name column
|
|
94
|
+
dynamicRefColumns.forEach(({ columnName, tableColumnName }) => {
|
|
95
|
+
if (!dynamicRefTableNameColumns.includes(tableColumnName)) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
`Table ${table.name} has a DynamicReferenceColumn '${columnName}' but is missing its required DynamicReferenceTableNameColumn '${tableColumnName}'`
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Third pass: validate that each DynamicReferenceTableNameColumn is used by a DynamicReferenceColumn
|
|
103
|
+
dynamicRefTableNameColumns.forEach((tableNameColumnName) => {
|
|
104
|
+
const hasMatchingRefColumn = dynamicRefColumns.some(
|
|
105
|
+
({ tableColumnName }) => tableColumnName === tableNameColumnName
|
|
106
|
+
);
|
|
107
|
+
if (!hasMatchingRefColumn) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
`Table ${table.name} has a DynamicReferenceTableNameColumn '${tableNameColumnName}' but no DynamicReferenceColumn references it`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
54
115
|
private shouldAlterTable(tableChanges: TableChanges) {
|
|
55
116
|
if (
|
|
56
117
|
tableChanges.columnsToCreate.length == 0 &&
|
|
@@ -6,6 +6,12 @@ export const getTransactionRunner = () =>
|
|
|
6
6
|
typeof self === 'undefined' ? new TransactionRunner() : (getTransactionRunnerService() as TransactionRunner);
|
|
7
7
|
|
|
8
8
|
export class TransactionRunner implements TransactionRunnerService {
|
|
9
|
+
public serviceMetadata = {
|
|
10
|
+
auth: {
|
|
11
|
+
allUsers: true,
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
|
|
9
15
|
async run(ops: Operation<any>[]): Promise<void> {
|
|
10
16
|
const db = getDb();
|
|
11
17
|
|