@proteinjs/db 1.55.0 → 1.55.1
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 +11 -0
- package/dist/generated/test/index.d.ts.map +1 -1
- package/dist/generated/test/index.js +9 -1
- package/dist/generated/test/index.js.map +1 -1
- package/dist/src/Columns.d.ts +20 -2
- package/dist/src/Columns.d.ts.map +1 -1
- package/dist/src/Columns.js +46 -45
- package/dist/src/Columns.js.map +1 -1
- package/dist/src/Db.d.ts +8 -0
- package/dist/src/Db.d.ts.map +1 -1
- package/dist/src/Db.js +9 -1
- package/dist/src/Db.js.map +1 -1
- package/dist/test/reusable/CascadeDeleteTests.d.ts.map +1 -1
- package/dist/test/reusable/CascadeDeleteTests.js +132 -0
- package/dist/test/reusable/CascadeDeleteTests.js.map +1 -1
- package/dist/test/util/tables/cascadeDeleteTestTables.d.ts +38 -0
- package/dist/test/util/tables/cascadeDeleteTestTables.d.ts.map +1 -1
- package/dist/test/util/tables/cascadeDeleteTestTables.js +119 -1
- package/dist/test/util/tables/cascadeDeleteTestTables.js.map +1 -1
- package/generated/test/index.ts +9 -1
- package/package.json +2 -2
- package/src/Columns.ts +50 -27
- package/src/Db.ts +9 -1
- package/test/reusable/CascadeDeleteTests.ts +90 -0
- package/test/util/tables/cascadeDeleteTestTables.ts +85 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@proteinjs/db",
|
|
3
|
-
"version": "1.55.
|
|
3
|
+
"version": "1.55.1",
|
|
4
4
|
"main": "./dist/generated/index.js",
|
|
5
5
|
"types": "./dist/generated/index.d.ts",
|
|
6
6
|
"exports": {
|
|
@@ -66,5 +66,5 @@
|
|
|
66
66
|
"ts-jest": "29.1.1",
|
|
67
67
|
"typescript": "5.2.2"
|
|
68
68
|
},
|
|
69
|
-
"gitHead": "
|
|
69
|
+
"gitHead": "195e4d8a6aae026ad6c6b8b0fbfa831a57ecb501"
|
|
70
70
|
}
|
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, getColumnPropertyName, Table
|
|
3
|
+
import { Column, ColumnOptions, getColumnPropertyName, Table } from './Table';
|
|
4
4
|
import { Record } from './Record';
|
|
5
5
|
import { ReferenceArray } from './reference/ReferenceArray';
|
|
6
6
|
import { Db } from './Db';
|
|
@@ -263,17 +263,29 @@ export class ReferenceArrayColumn<T extends Record> extends ObjectColumn<Referen
|
|
|
263
263
|
return new ReferenceArray(this.referenceTable, ids);
|
|
264
264
|
}
|
|
265
265
|
|
|
266
|
-
|
|
266
|
+
/**
|
|
267
|
+
* Cascade: the referenced records go with the holder. Runs through the Db the firing write
|
|
268
|
+
* hands it — its driver, its transaction context, its authority — and off the ids the column
|
|
269
|
+
* stores; nothing here opens a Db of its own or re-reads the referenced rows, either of which
|
|
270
|
+
* would run as the session's user rather than as the write (a system delete's cascade must stay
|
|
271
|
+
* system: in a background context there is no session user at all).
|
|
272
|
+
*/
|
|
273
|
+
async beforeDelete(
|
|
274
|
+
table: Table<any>,
|
|
275
|
+
columnPropertyName: string,
|
|
276
|
+
records: any[],
|
|
277
|
+
getTable?: (tableName: string) => Table<any>,
|
|
278
|
+
db?: Db
|
|
279
|
+
): Promise<void> {
|
|
267
280
|
if (!this.cascadeDelete) {
|
|
268
281
|
return;
|
|
269
282
|
}
|
|
270
283
|
|
|
271
284
|
const recordIdsToDelete: string[] = [];
|
|
272
285
|
for (const record of records) {
|
|
273
|
-
const referenceArray = record[columnPropertyName] as ReferenceArray<Record
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
recordIdsToDelete.push(referenceRecord.id);
|
|
286
|
+
const referenceArray = record[columnPropertyName] as ReferenceArray<Record> | null | undefined;
|
|
287
|
+
if (referenceArray) {
|
|
288
|
+
recordIdsToDelete.push(...referenceArray._ids);
|
|
277
289
|
}
|
|
278
290
|
}
|
|
279
291
|
|
|
@@ -281,11 +293,12 @@ export class ReferenceArrayColumn<T extends Record> extends ObjectColumn<Referen
|
|
|
281
293
|
return;
|
|
282
294
|
}
|
|
283
295
|
|
|
284
|
-
|
|
296
|
+
// Optional in the hook contract for implementers that need neither; the Db hands both to every hook.
|
|
297
|
+
const referenceTable = getTable!(this.referenceTable);
|
|
285
298
|
const qb = new QueryBuilderFactory()
|
|
286
299
|
.getQueryBuilder(referenceTable)
|
|
287
300
|
.condition({ field: 'id', operator: 'IN', value: recordIdsToDelete });
|
|
288
|
-
await
|
|
301
|
+
await db!.delete(referenceTable, qb);
|
|
289
302
|
}
|
|
290
303
|
}
|
|
291
304
|
|
|
@@ -331,7 +344,18 @@ export class ReferenceColumn<T extends Record> extends StringColumn<Reference<T>
|
|
|
331
344
|
return reference as Reference<T>;
|
|
332
345
|
}
|
|
333
346
|
|
|
334
|
-
|
|
347
|
+
/**
|
|
348
|
+
* Cascade: the referenced record goes with the holder. Runs through the Db the firing write
|
|
349
|
+
* hands it (its driver, transaction context and authority) off the stored id — see
|
|
350
|
+
* `ReferenceArrayColumn.beforeDelete` for why the hook never opens a Db of its own.
|
|
351
|
+
*/
|
|
352
|
+
async beforeDelete(
|
|
353
|
+
table: Table<any>,
|
|
354
|
+
columnPropertyName: string,
|
|
355
|
+
records: any[],
|
|
356
|
+
getTable?: (tableName: string) => Table<any>,
|
|
357
|
+
db?: Db
|
|
358
|
+
): Promise<void> {
|
|
335
359
|
if (!this.cascadeDelete) {
|
|
336
360
|
return;
|
|
337
361
|
}
|
|
@@ -348,11 +372,12 @@ export class ReferenceColumn<T extends Record> extends StringColumn<Reference<T>
|
|
|
348
372
|
return;
|
|
349
373
|
}
|
|
350
374
|
|
|
351
|
-
|
|
375
|
+
// Optional in the hook contract for implementers that need neither; the Db hands both to every hook.
|
|
376
|
+
const referenceTable = getTable!(this.referenceTable);
|
|
352
377
|
const qb = new QueryBuilderFactory()
|
|
353
378
|
.getQueryBuilder(referenceTable)
|
|
354
379
|
.condition({ field: 'id', operator: 'IN', value: recordIdsToDelete });
|
|
355
|
-
await
|
|
380
|
+
await db!.delete(referenceTable, qb);
|
|
356
381
|
}
|
|
357
382
|
}
|
|
358
383
|
|
|
@@ -508,6 +533,12 @@ export class DynamicReferenceColumn<T extends Record> extends StringColumn<Refer
|
|
|
508
533
|
return new Reference<T>(tableName, serializedFieldValue);
|
|
509
534
|
}
|
|
510
535
|
|
|
536
|
+
/**
|
|
537
|
+
* Cascade: the referenced record goes with the holder, whichever table the row names. Runs
|
|
538
|
+
* through the Db the firing write hands it (its driver, transaction context and authority) off
|
|
539
|
+
* the stored ids — see `ReferenceArrayColumn.beforeDelete` for why the hook never opens a Db of
|
|
540
|
+
* its own.
|
|
541
|
+
*/
|
|
511
542
|
async beforeDelete(
|
|
512
543
|
table: Table<any>,
|
|
513
544
|
columnPropertyName: string,
|
|
@@ -519,12 +550,8 @@ export class DynamicReferenceColumn<T extends Record> extends StringColumn<Refer
|
|
|
519
550
|
return;
|
|
520
551
|
}
|
|
521
552
|
|
|
522
|
-
|
|
523
|
-
const dbInstance = db ? db : new Db();
|
|
524
|
-
|
|
525
|
-
// Get all referenced record IDs grouped by table
|
|
553
|
+
// The referenced ids, grouped by the table each row names
|
|
526
554
|
const recordsToDelete = new Map<string, string[]>();
|
|
527
|
-
|
|
528
555
|
for (const record of records) {
|
|
529
556
|
const reference = record[columnPropertyName] as Reference<Record>;
|
|
530
557
|
if (!reference?._id || !reference._table) {
|
|
@@ -537,17 +564,13 @@ export class DynamicReferenceColumn<T extends Record> extends StringColumn<Refer
|
|
|
537
564
|
recordsToDelete.get(reference._table)!.push(reference._id);
|
|
538
565
|
}
|
|
539
566
|
|
|
540
|
-
//
|
|
541
|
-
const
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
.getQueryBuilder(referenceTable)
|
|
548
|
-
.condition({ field: 'id', operator: 'IN', value: ids });
|
|
549
|
-
await dbInstance.delete(referenceTable, qb);
|
|
550
|
-
}
|
|
567
|
+
// Optional in the hook contract for implementers that need neither; the Db hands both to every hook.
|
|
568
|
+
for (const [tableName, ids] of Array.from(recordsToDelete.entries())) {
|
|
569
|
+
const referenceTable = getTable!(tableName);
|
|
570
|
+
const qb = new QueryBuilderFactory()
|
|
571
|
+
.getQueryBuilder(referenceTable)
|
|
572
|
+
.condition({ field: 'id', operator: 'IN', value: ids });
|
|
573
|
+
await db!.delete(referenceTable, qb);
|
|
551
574
|
}
|
|
552
575
|
}
|
|
553
576
|
}
|
package/src/Db.ts
CHANGED
|
@@ -551,6 +551,14 @@ export class Db<R extends Record = Record> implements DbService<R> {
|
|
|
551
551
|
return recordDeleteCount;
|
|
552
552
|
}
|
|
553
553
|
|
|
554
|
+
/**
|
|
555
|
+
* A column's `beforeDelete` hook runs with the authority of the write that fired it: the Db it
|
|
556
|
+
* is handed rides this instance's driver, tables and transaction context AND its system mode.
|
|
557
|
+
* The hooks cascade a delete through reference columns, and a system delete's cascade must stay
|
|
558
|
+
* system — handed a default (non-system) Db, the cascade ran as whoever the session held, which
|
|
559
|
+
* narrowed it to that user's rows inside a request and refused it outright in a background
|
|
560
|
+
* context, where a scoped table finds no user to scope the query to.
|
|
561
|
+
*/
|
|
554
562
|
private async runColumnBeforeDeletes(table: Table<any>, recordsToDelete: Record[]) {
|
|
555
563
|
for (const columnPropertyName in table.columns) {
|
|
556
564
|
const column = (table.columns as any)[columnPropertyName] as Column<any, any>;
|
|
@@ -560,7 +568,7 @@ export class Db<R extends Record = Record> implements DbService<R> {
|
|
|
560
568
|
columnPropertyName,
|
|
561
569
|
recordsToDelete,
|
|
562
570
|
this.getTable,
|
|
563
|
-
new Db(this.dbDriver, this.getTable, this.transactionContextFactory)
|
|
571
|
+
new Db(this.dbDriver, this.getTable, this.transactionContextFactory, this.runAsSystem)
|
|
564
572
|
);
|
|
565
573
|
}
|
|
566
574
|
}
|
|
@@ -6,6 +6,10 @@ import {
|
|
|
6
6
|
GroupArr,
|
|
7
7
|
GroupDyn,
|
|
8
8
|
GroupRef,
|
|
9
|
+
GuardedHolderArr,
|
|
10
|
+
GuardedHolderDyn,
|
|
11
|
+
GuardedHolderRef,
|
|
12
|
+
GuardedMember,
|
|
9
13
|
MemberArr,
|
|
10
14
|
MemberArrRev,
|
|
11
15
|
MemberDyn,
|
|
@@ -224,5 +228,91 @@ export const cascadeDeleteTests = (
|
|
|
224
228
|
expect(missionsAfterPilot.length).toBe(0);
|
|
225
229
|
});
|
|
226
230
|
});
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* -------------------- Cascade Delete as system (holder → guarded referenced) --------------------
|
|
234
|
+
* A system delete's cascade runs with the authority of the write that fired it. The children's
|
|
235
|
+
* table refuses every non-system query (a scoped table's shape), and the delete runs in a fresh
|
|
236
|
+
* async context with no session — a background sweep's shape. Before the fix the cascade hooks
|
|
237
|
+
* ran as a non-system Db, and every case below failed with the guarded table's refusal.
|
|
238
|
+
*/
|
|
239
|
+
describe('Cascade Delete as system', () => {
|
|
240
|
+
const systemDb = new Db(driver, undefined, transactionContextFactory, true);
|
|
241
|
+
const memberTable = cascadeDeleteTestTables.GuardedMember;
|
|
242
|
+
|
|
243
|
+
/** A fresh async context with nothing inherited from the test's own — a background sweep's shape. */
|
|
244
|
+
const inFreshContext = <T>(fn: () => Promise<T>): Promise<T> =>
|
|
245
|
+
new Promise<T>((resolve, reject) => {
|
|
246
|
+
setImmediate(() => {
|
|
247
|
+
fn().then(resolve, reject);
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test('ReferenceColumn: a system delete of the holder deletes the guarded referenced record', async () => {
|
|
252
|
+
const holderTable = cascadeDeleteTestTables.GuardedHolderRef;
|
|
253
|
+
|
|
254
|
+
const m = await systemDb.insert(memberTable, { name: 'Gail' });
|
|
255
|
+
const h = await systemDb.insert(holderTable, {
|
|
256
|
+
holderName: 'H-Ref',
|
|
257
|
+
memberRef: new Reference<GuardedMember>(memberTable.name, m.id),
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
const delQb = new QueryBuilder<GuardedHolderRef>(holderTable.name).condition({
|
|
261
|
+
field: 'id',
|
|
262
|
+
operator: '=',
|
|
263
|
+
value: h.id,
|
|
264
|
+
});
|
|
265
|
+
const deleted = await inFreshContext(() => systemDb.delete(holderTable, delQb));
|
|
266
|
+
expect(deleted).toBe(1);
|
|
267
|
+
|
|
268
|
+
expect(await systemDb.query(memberTable, { id: m.id })).toHaveLength(0);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test('ReferenceArrayColumn: a system delete of the holder deletes every guarded referenced record', async () => {
|
|
272
|
+
const holderTable = cascadeDeleteTestTables.GuardedHolderArr;
|
|
273
|
+
|
|
274
|
+
const m1 = await systemDb.insert(memberTable, { name: 'Gus' });
|
|
275
|
+
const m2 = await systemDb.insert(memberTable, { name: 'Greta' });
|
|
276
|
+
const m3 = await systemDb.insert(memberTable, { name: 'Glen' });
|
|
277
|
+
const h = await systemDb.insert(holderTable, {
|
|
278
|
+
holderName: 'H-Arr',
|
|
279
|
+
memberRefs: new ReferenceArray<GuardedMember>(memberTable.name, [m1.id, m2.id]),
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
const delQb = new QueryBuilder<GuardedHolderArr>(holderTable.name).condition({
|
|
283
|
+
field: 'id',
|
|
284
|
+
operator: '=',
|
|
285
|
+
value: h.id,
|
|
286
|
+
});
|
|
287
|
+
const deleted = await inFreshContext(() => systemDb.delete(holderTable, delQb));
|
|
288
|
+
expect(deleted).toBe(1);
|
|
289
|
+
|
|
290
|
+
expect(await systemDb.query(memberTable, { id: m1.id })).toHaveLength(0);
|
|
291
|
+
expect(await systemDb.query(memberTable, { id: m2.id })).toHaveLength(0);
|
|
292
|
+
// The unreferenced member stands: the cascade is the stored ids, nothing wider.
|
|
293
|
+
expect(await systemDb.query(memberTable, { id: m3.id })).toHaveLength(1);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
test('DynamicReferenceColumn: a system delete of the holder deletes the guarded dynamically referenced record', async () => {
|
|
297
|
+
const holderTable = cascadeDeleteTestTables.GuardedHolderDyn;
|
|
298
|
+
|
|
299
|
+
const m = await systemDb.insert(memberTable, { name: 'Gwen' });
|
|
300
|
+
const h = await systemDb.insert(holderTable, {
|
|
301
|
+
holderName: 'H-Dyn',
|
|
302
|
+
memberDynTableName: memberTable.name,
|
|
303
|
+
memberDynRef: new Reference<GuardedMember>(memberTable.name, m.id),
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
const delQb = new QueryBuilder<GuardedHolderDyn>(holderTable.name).condition({
|
|
307
|
+
field: 'id',
|
|
308
|
+
operator: '=',
|
|
309
|
+
value: h.id,
|
|
310
|
+
});
|
|
311
|
+
const deleted = await inFreshContext(() => systemDb.delete(holderTable, delQb));
|
|
312
|
+
expect(deleted).toBe(1);
|
|
313
|
+
|
|
314
|
+
expect(await systemDb.query(memberTable, { id: m.id })).toHaveLength(0);
|
|
315
|
+
});
|
|
316
|
+
});
|
|
227
317
|
};
|
|
228
318
|
};
|
|
@@ -80,6 +80,28 @@ export interface Mission extends Record {
|
|
|
80
80
|
assigneeRef?: Reference<Pilot | Robot> | null;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
// --- Cascade under the write's authority: a guarded child (GuardedHolder* -> GuardedMember)
|
|
84
|
+
// The child's table refuses every non-system query, the shape of a scoped table (its column adds
|
|
85
|
+
// the caller's scope to each query and throws when the context carries no user). A holder deleted
|
|
86
|
+
// AS SYSTEM must take its children with it: the cascade runs with the authority of the write that
|
|
87
|
+
// fired it, never as whoever happens to be in the session.
|
|
88
|
+
export interface GuardedMember extends Record {
|
|
89
|
+
name: string;
|
|
90
|
+
}
|
|
91
|
+
export interface GuardedHolderRef extends Record {
|
|
92
|
+
holderName: string;
|
|
93
|
+
memberRef?: Reference<GuardedMember> | null;
|
|
94
|
+
}
|
|
95
|
+
export interface GuardedHolderArr extends Record {
|
|
96
|
+
holderName: string;
|
|
97
|
+
memberRefs?: ReferenceArray<GuardedMember> | null;
|
|
98
|
+
}
|
|
99
|
+
export interface GuardedHolderDyn extends Record {
|
|
100
|
+
holderName: string;
|
|
101
|
+
memberDynTableName?: string | null;
|
|
102
|
+
memberDynRef?: Reference<GuardedMember> | null;
|
|
103
|
+
}
|
|
104
|
+
|
|
83
105
|
/**
|
|
84
106
|
* ---------- Table Names ----------
|
|
85
107
|
*/
|
|
@@ -112,6 +134,15 @@ const PILOT_TABLE = 'db_test_cd_pilots';
|
|
|
112
134
|
const ROBOT_TABLE = 'db_test_cd_robots';
|
|
113
135
|
const MISSION_TABLE = 'db_test_cd_missions';
|
|
114
136
|
|
|
137
|
+
// Cascade under the write's authority (guarded child)
|
|
138
|
+
const GUARDED_MEMBER_TABLE = 'db_test_cd_guarded_members';
|
|
139
|
+
const GUARDED_HOLDER_REF_TABLE = 'db_test_cd_guarded_holders_ref';
|
|
140
|
+
const GUARDED_HOLDER_ARR_TABLE = 'db_test_cd_guarded_holders_arr';
|
|
141
|
+
const GUARDED_HOLDER_DYN_TABLE = 'db_test_cd_guarded_holders_dyn';
|
|
142
|
+
|
|
143
|
+
/** The guarded table's refusal of a non-system query — the words a suite asserts on. */
|
|
144
|
+
export const GUARDED_QUERY_REFUSAL = 'no user in scope';
|
|
145
|
+
|
|
115
146
|
/**
|
|
116
147
|
* ---------- Table Classes ----------
|
|
117
148
|
*/
|
|
@@ -261,6 +292,56 @@ export class MissionTable extends Table<Mission> {
|
|
|
261
292
|
});
|
|
262
293
|
}
|
|
263
294
|
|
|
295
|
+
// Cascade under the write's authority: the guarded child and its three holders
|
|
296
|
+
export class GuardedMemberTable extends Table<GuardedMember> {
|
|
297
|
+
name = GUARDED_MEMBER_TABLE;
|
|
298
|
+
columns: Table<GuardedMember>['columns'] = withRecordColumns<GuardedMember>({
|
|
299
|
+
name: new StringColumn('name', {
|
|
300
|
+
// The scoped-table contract without a dependency on it: a non-system query of this table
|
|
301
|
+
// would need the context's user to scope it to, and there is none — refuse it by name.
|
|
302
|
+
addToQuery: async (qb, runAsSystem) => {
|
|
303
|
+
if (!runAsSystem) {
|
|
304
|
+
throw new Error(GUARDED_QUERY_REFUSAL);
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
}),
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
export class GuardedHolderRefTable extends Table<GuardedHolderRef> {
|
|
311
|
+
name = GUARDED_HOLDER_REF_TABLE;
|
|
312
|
+
columns: Table<GuardedHolderRef>['columns'] = withRecordColumns<GuardedHolderRef>({
|
|
313
|
+
holderName: new StringColumn('holder_name'),
|
|
314
|
+
memberRef: new ReferenceColumn<GuardedMember>(
|
|
315
|
+
'member_id',
|
|
316
|
+
GUARDED_MEMBER_TABLE,
|
|
317
|
+
true // cascade: deleting the holder deletes the referenced GuardedMember
|
|
318
|
+
),
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
export class GuardedHolderArrTable extends Table<GuardedHolderArr> {
|
|
322
|
+
name = GUARDED_HOLDER_ARR_TABLE;
|
|
323
|
+
columns: Table<GuardedHolderArr>['columns'] = withRecordColumns<GuardedHolderArr>({
|
|
324
|
+
holderName: new StringColumn('holder_name'),
|
|
325
|
+
memberRefs: new ReferenceArrayColumn<GuardedMember>(
|
|
326
|
+
'member_ids',
|
|
327
|
+
GUARDED_MEMBER_TABLE,
|
|
328
|
+
true // cascade: deleting the holder deletes every referenced GuardedMember
|
|
329
|
+
),
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
export class GuardedHolderDynTable extends Table<GuardedHolderDyn> {
|
|
333
|
+
name = GUARDED_HOLDER_DYN_TABLE;
|
|
334
|
+
columns: Table<GuardedHolderDyn>['columns'] = withRecordColumns<GuardedHolderDyn>({
|
|
335
|
+
holderName: new StringColumn('holder_name'),
|
|
336
|
+
memberDynTableName: new DynamicReferenceTableNameColumn('member_dyn_table_name', 'member_dyn_ref'),
|
|
337
|
+
memberDynRef: new DynamicReferenceColumn<GuardedMember>(
|
|
338
|
+
'member_dyn_ref',
|
|
339
|
+
'member_dyn_table_name',
|
|
340
|
+
true // cascade: deleting the holder deletes the dynamically referenced GuardedMember
|
|
341
|
+
),
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
264
345
|
export const cascadeDeleteTestTables = {
|
|
265
346
|
MemberRef: new MemberRefTable() as Table<MemberRef>,
|
|
266
347
|
GroupRef: new GroupRefTable() as Table<GroupRef>,
|
|
@@ -277,4 +358,8 @@ export const cascadeDeleteTestTables = {
|
|
|
277
358
|
Pilot: new PilotTable() as Table<Pilot>,
|
|
278
359
|
Robot: new RobotTable() as Table<Robot>,
|
|
279
360
|
Mission: new MissionTable() as Table<Mission>,
|
|
361
|
+
GuardedMember: new GuardedMemberTable() as Table<GuardedMember>,
|
|
362
|
+
GuardedHolderRef: new GuardedHolderRefTable() as Table<GuardedHolderRef>,
|
|
363
|
+
GuardedHolderArr: new GuardedHolderArrTable() as Table<GuardedHolderArr>,
|
|
364
|
+
GuardedHolderDyn: new GuardedHolderDynTable() as Table<GuardedHolderDyn>,
|
|
280
365
|
};
|