@proteinjs/db 1.34.3 → 1.34.4
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/index.js +1 -1
- package/dist/generated/index.js.map +1 -1
- package/dist/generated/test/index.d.ts.map +1 -1
- package/dist/generated/test/index.js +3 -1
- package/dist/generated/test/index.js.map +1 -1
- package/dist/src/source/SourceRecord.d.ts +35 -2
- package/dist/src/source/SourceRecord.d.ts.map +1 -1
- package/dist/src/source/SourceRecord.js +8 -1
- package/dist/src/source/SourceRecord.js.map +1 -1
- package/dist/src/source/SourceRecordLoader.d.ts +98 -7
- package/dist/src/source/SourceRecordLoader.d.ts.map +1 -1
- package/dist/src/source/SourceRecordLoader.js +348 -93
- package/dist/src/source/SourceRecordLoader.js.map +1 -1
- package/dist/src/tables/MigrationTable.d.ts +2 -0
- package/dist/src/tables/MigrationTable.d.ts.map +1 -1
- package/dist/test/reusable/SourceRecordSyncTests.d.ts.map +1 -1
- package/dist/test/reusable/SourceRecordSyncTests.js +803 -27
- package/dist/test/reusable/SourceRecordSyncTests.js.map +1 -1
- package/dist/test/util/tables/sourceRecordSyncTestTables.d.ts +16 -0
- package/dist/test/util/tables/sourceRecordSyncTestTables.d.ts.map +1 -1
- package/dist/test/util/tables/sourceRecordSyncTestTables.js +23 -1
- package/dist/test/util/tables/sourceRecordSyncTestTables.js.map +1 -1
- package/generated/index.ts +1 -1
- package/generated/test/index.ts +3 -1
- package/package.json +4 -4
- package/src/source/SourceRecord.ts +42 -4
- package/src/source/SourceRecordLoader.ts +333 -48
- package/test/reusable/SourceRecordSyncTests.ts +462 -11
- package/test/util/tables/sourceRecordSyncTestTables.ts +20 -0
|
@@ -105,20 +105,56 @@ var sourceRecordSyncTests = function (driver, transactionContextFactory, dropTab
|
|
|
105
105
|
var machineTable = sourceRecordSyncTestTables_1.sourceRecordSyncTestTables.SyncMachineAccount;
|
|
106
106
|
var defaultPolicyTable = sourceRecordSyncTestTables_1.sourceRecordSyncTestTables.SyncDefaultPolicy;
|
|
107
107
|
var objectCache = function () { return reflection_1.SourceRepository.get().objectCache; };
|
|
108
|
+
var namedObjectCache = function () { return reflection_1.SourceRepository.get().namedObjectCache; };
|
|
108
109
|
var originalWatchers;
|
|
109
|
-
/**
|
|
110
|
-
var
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
110
|
+
/** The single source most of the suite boots as (multi-source tests name their own). */
|
|
111
|
+
var DEFAULT_TEST_SOURCE = '@test/source-a';
|
|
112
|
+
/**
|
|
113
|
+
* One boot of the loader leg as a BUILD: each declaration owned by its declaring package
|
|
114
|
+
* (`source`), seeded through the named-object cache the loader resolves declarations (and
|
|
115
|
+
* their owning packages) from. `versions` pins each package's version for the boot (the
|
|
116
|
+
* loader's version resolution is overridden — a package absent from the map is versionless,
|
|
117
|
+
* matching a build whose package.json could not be resolved).
|
|
118
|
+
*/
|
|
119
|
+
var bootBuild = function (declarations, versions) {
|
|
120
|
+
if (versions === void 0) { versions = {}; }
|
|
121
|
+
return __awaiter(void 0, void 0, void 0, function () {
|
|
122
|
+
var loader;
|
|
123
|
+
return __generator(this, function (_a) {
|
|
124
|
+
switch (_a.label) {
|
|
125
|
+
case 0:
|
|
126
|
+
namedObjectCache()['@proteinjs/db/SourceRecordLoader'] = declarations.map(function (declaration, i) { return ({
|
|
127
|
+
qualifiedName: "".concat(declaration.source, "/SeededLoader").concat(i),
|
|
128
|
+
packageName: declaration.source,
|
|
129
|
+
object: { table: declaration.table, record: declaration.record },
|
|
130
|
+
}); });
|
|
131
|
+
loader = new SourceRecordLoader_1.SourceRecordLoader();
|
|
132
|
+
loader.resolveSourceVersion = function (source) {
|
|
133
|
+
return Object.prototype.hasOwnProperty.call(versions, source) ? versions[source] : undefined;
|
|
134
|
+
};
|
|
135
|
+
return [4 /*yield*/, loader.load()];
|
|
136
|
+
case 1: return [2 /*return*/, _a.sent()];
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
};
|
|
141
|
+
/** One boot of the loader leg as a build declaring everything from a single package. */
|
|
142
|
+
var bootAsSource = function (source, declarations, version) { return __awaiter(void 0, void 0, void 0, function () {
|
|
143
|
+
var _a;
|
|
144
|
+
return __generator(this, function (_b) {
|
|
145
|
+
switch (_b.label) {
|
|
146
|
+
case 0: return [4 /*yield*/, bootBuild(declarations.map(function (declaration) { return (__assign({ source: source }, declaration)); }), version === undefined ? {} : (_a = {}, _a[source] = version, _a))];
|
|
147
|
+
case 1: return [2 /*return*/, _b.sent()];
|
|
119
148
|
}
|
|
120
149
|
});
|
|
121
150
|
}); };
|
|
151
|
+
/** Seed the boot declarations and run the sync — one boot of `Db.init`'s loader leg. */
|
|
152
|
+
var boot = function (declarations) { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
153
|
+
switch (_a.label) {
|
|
154
|
+
case 0: return [4 /*yield*/, bootAsSource(DEFAULT_TEST_SOURCE, declarations)];
|
|
155
|
+
case 1: return [2 /*return*/, _a.sent()];
|
|
156
|
+
}
|
|
157
|
+
}); }); };
|
|
122
158
|
var machineDeclaration = function (record) { return ({
|
|
123
159
|
table: machineTable,
|
|
124
160
|
record: __assign({ status: 'active' }, record),
|
|
@@ -160,6 +196,7 @@ var sourceRecordSyncTests = function (driver, transactionContextFactory, dropTab
|
|
|
160
196
|
case 0: return [4 /*yield*/, testEnv.afterAll()];
|
|
161
197
|
case 1:
|
|
162
198
|
_a.sent();
|
|
199
|
+
delete namedObjectCache()['@proteinjs/db/SourceRecordLoader'];
|
|
163
200
|
delete objectCache()['@proteinjs/db/SourceRecordLoader'];
|
|
164
201
|
delete objectCache()['@proteinjs/db/DefaultDbDriverFactory'];
|
|
165
202
|
delete objectCache()['@proteinjs/db/DefaultTransactionContextFactory'];
|
|
@@ -186,6 +223,9 @@ var sourceRecordSyncTests = function (driver, transactionContextFactory, dropTab
|
|
|
186
223
|
_a.sent();
|
|
187
224
|
return [4 /*yield*/, db.delete(defaultPolicyTable, {})];
|
|
188
225
|
case 2:
|
|
226
|
+
_a.sent();
|
|
227
|
+
return [4 /*yield*/, db.delete(sourceRecordSyncTestTables_1.sourceRecordSyncTestTables.InheritedStamp, {})];
|
|
228
|
+
case 3:
|
|
189
229
|
_a.sent();
|
|
190
230
|
RecordingMachineAccountWatcher.updates = [];
|
|
191
231
|
return [2 /*return*/];
|
|
@@ -315,7 +355,7 @@ var sourceRecordSyncTests = function (driver, transactionContextFactory, dropTab
|
|
|
315
355
|
});
|
|
316
356
|
}); });
|
|
317
357
|
test('onSourceRemoved update: removed rows are flagged through Db.update (watchers fire), never deleted; re-declaring reverts', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
318
|
-
var db, human, removed, _a, _b;
|
|
358
|
+
var db, human, keeper, removed, _a, _b;
|
|
319
359
|
return __generator(this, function (_c) {
|
|
320
360
|
switch (_c.label) {
|
|
321
361
|
case 0:
|
|
@@ -323,11 +363,14 @@ var sourceRecordSyncTests = function (driver, transactionContextFactory, dropTab
|
|
|
323
363
|
return [4 /*yield*/, db.insert(machineTable, { email: 'human@test.local', displayName: 'A human' })];
|
|
324
364
|
case 1:
|
|
325
365
|
human = _c.sent();
|
|
326
|
-
|
|
366
|
+
keeper = machineDeclaration({ id: 'machine-keep', email: 'keeper@test.local' });
|
|
367
|
+
return [4 /*yield*/, boot([machineDeclaration({ id: 'machine-1', email: 'machine@test.local' }), keeper])];
|
|
327
368
|
case 2:
|
|
328
369
|
_c.sent();
|
|
370
|
+
// The source's next build drops machine-1 (while still declaring from this table —
|
|
371
|
+
// ownership is per source, and a source only reconciles what it still speaks for).
|
|
329
372
|
RecordingMachineAccountWatcher.updates = [];
|
|
330
|
-
return [4 /*yield*/, boot([])];
|
|
373
|
+
return [4 /*yield*/, boot([keeper])];
|
|
331
374
|
case 3:
|
|
332
375
|
_c.sent();
|
|
333
376
|
return [4 /*yield*/, db.get(machineTable, { id: 'machine-1' })];
|
|
@@ -347,12 +390,12 @@ var sourceRecordSyncTests = function (driver, transactionContextFactory, dropTab
|
|
|
347
390
|
_a.apply(void 0, [(_c.sent()).status]).toBeFalsy();
|
|
348
391
|
// Idempotent: an already-flagged row is not re-written on the next boot.
|
|
349
392
|
RecordingMachineAccountWatcher.updates = [];
|
|
350
|
-
return [4 /*yield*/, boot([])];
|
|
393
|
+
return [4 /*yield*/, boot([keeper])];
|
|
351
394
|
case 6:
|
|
352
395
|
_c.sent();
|
|
353
396
|
expect(RecordingMachineAccountWatcher.updates).toHaveLength(0);
|
|
354
397
|
// Removal is reversible in source: re-declaring reverts the patch via drift reversion.
|
|
355
|
-
return [4 /*yield*/, boot([machineDeclaration({ id: 'machine-1', email: 'machine@test.local' })])];
|
|
398
|
+
return [4 /*yield*/, boot([machineDeclaration({ id: 'machine-1', email: 'machine@test.local' }), keeper])];
|
|
356
399
|
case 7:
|
|
357
400
|
// Removal is reversible in source: re-declaring reverts the patch via drift reversion.
|
|
358
401
|
_c.sent();
|
|
@@ -365,36 +408,734 @@ var sourceRecordSyncTests = function (driver, transactionContextFactory, dropTab
|
|
|
365
408
|
});
|
|
366
409
|
}); });
|
|
367
410
|
test("onSourceRemoved default: removed source rows are deleted; human rows survive", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
368
|
-
var db, human, _a, _b, _c;
|
|
369
|
-
return __generator(this, function (
|
|
370
|
-
switch (
|
|
411
|
+
var db, human, keeper, _a, _b, _c, _d;
|
|
412
|
+
return __generator(this, function (_e) {
|
|
413
|
+
switch (_e.label) {
|
|
371
414
|
case 0:
|
|
372
415
|
db = (0, db_1.getDbAsSystem)();
|
|
373
416
|
return [4 /*yield*/, db.insert(defaultPolicyTable, { email: 'human@test.local' })];
|
|
374
417
|
case 1:
|
|
375
|
-
human =
|
|
376
|
-
|
|
418
|
+
human = _e.sent();
|
|
419
|
+
keeper = { table: defaultPolicyTable, record: { id: 'default-keep', email: 'keeper@test.local' } };
|
|
420
|
+
return [4 /*yield*/, boot([{ table: defaultPolicyTable, record: { id: 'default-1', email: 'temp@test.local' } }, keeper])];
|
|
377
421
|
case 2:
|
|
378
|
-
|
|
422
|
+
_e.sent();
|
|
379
423
|
_a = expect;
|
|
380
424
|
return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'default-1' })];
|
|
381
425
|
case 3:
|
|
382
|
-
_a.apply(void 0, [
|
|
383
|
-
return [4 /*yield*/, boot([])];
|
|
426
|
+
_a.apply(void 0, [_e.sent()]).toBeDefined();
|
|
427
|
+
return [4 /*yield*/, boot([keeper])];
|
|
384
428
|
case 4:
|
|
385
|
-
|
|
429
|
+
_e.sent();
|
|
386
430
|
_b = expect;
|
|
387
431
|
return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'default-1' })];
|
|
388
432
|
case 5:
|
|
389
|
-
_b.apply(void 0, [
|
|
433
|
+
_b.apply(void 0, [_e.sent()]).toBeUndefined();
|
|
390
434
|
_c = expect;
|
|
391
|
-
return [4 /*yield*/, db.get(defaultPolicyTable, { id:
|
|
435
|
+
return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'default-keep' })];
|
|
392
436
|
case 6:
|
|
393
|
-
_c.apply(void 0, [
|
|
437
|
+
_c.apply(void 0, [_e.sent()]).toBeDefined();
|
|
438
|
+
_d = expect;
|
|
439
|
+
return [4 /*yield*/, db.get(defaultPolicyTable, { id: human.id })];
|
|
440
|
+
case 7:
|
|
441
|
+
_d.apply(void 0, [_e.sent()]).toBeDefined();
|
|
394
442
|
return [2 /*return*/];
|
|
395
443
|
}
|
|
396
444
|
});
|
|
397
445
|
}); });
|
|
446
|
+
/**
|
|
447
|
+
* Source-scoped pruning: multiple servers running DIFFERENT builds against ONE shared
|
|
448
|
+
* database (the thought_type dev topology — two dev servers on one Spanner database).
|
|
449
|
+
* Ownership grain is the declaring package: each declaration's source is the package that
|
|
450
|
+
* compiled it into the build. The removed-reconcile must stay authoritative WITHIN a source
|
|
451
|
+
* (a source still deletes declarations it itself removed) and be a strict no-op ACROSS
|
|
452
|
+
* sources (a boot never deletes rows owned by a package absent from its build).
|
|
453
|
+
*/
|
|
454
|
+
describe('source-scoped pruning (shared DB, per-package ownership)', function () {
|
|
455
|
+
var decl = function (id, email) { return ({ table: defaultPolicyTable, record: { id: id, email: email } }); };
|
|
456
|
+
test("one source's sync never deletes rows owned by a different source", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
457
|
+
var db, _a, _b, _c, _d, _e, _f, _g, _h;
|
|
458
|
+
return __generator(this, function (_j) {
|
|
459
|
+
switch (_j.label) {
|
|
460
|
+
case 0:
|
|
461
|
+
db = (0, db_1.getDbAsSystem)();
|
|
462
|
+
// Build A (e.g. this checkout's flow types) declares {a-1, a-2}; build B (the other
|
|
463
|
+
// checkout's media types) declares a disjoint set {b-1}.
|
|
464
|
+
return [4 /*yield*/, bootAsSource('@test/pkg-a', [decl('a-1', 'a1@test.local'), decl('a-2', 'a2@test.local')])];
|
|
465
|
+
case 1:
|
|
466
|
+
// Build A (e.g. this checkout's flow types) declares {a-1, a-2}; build B (the other
|
|
467
|
+
// checkout's media types) declares a disjoint set {b-1}.
|
|
468
|
+
_j.sent();
|
|
469
|
+
return [4 /*yield*/, bootAsSource('@test/pkg-b', [decl('b-1', 'b1@test.local')])];
|
|
470
|
+
case 2:
|
|
471
|
+
_j.sent();
|
|
472
|
+
// B's boot must NOT have pruned A's rows — this is the cross-server nuke.
|
|
473
|
+
_a = expect;
|
|
474
|
+
return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'a-1' })];
|
|
475
|
+
case 3:
|
|
476
|
+
// B's boot must NOT have pruned A's rows — this is the cross-server nuke.
|
|
477
|
+
_a.apply(void 0, [_j.sent()]).toBeDefined();
|
|
478
|
+
_b = expect;
|
|
479
|
+
return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'a-2' })];
|
|
480
|
+
case 4:
|
|
481
|
+
_b.apply(void 0, [_j.sent()]).toBeDefined();
|
|
482
|
+
_c = expect;
|
|
483
|
+
return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'b-1' })];
|
|
484
|
+
case 5:
|
|
485
|
+
_c.apply(void 0, [_j.sent()]).toBeDefined();
|
|
486
|
+
// Each row is stamped with its owning package — the scoping the prune runs on.
|
|
487
|
+
_d = expect;
|
|
488
|
+
return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'a-1' })];
|
|
489
|
+
case 6:
|
|
490
|
+
// Each row is stamped with its owning package — the scoping the prune runs on.
|
|
491
|
+
_d.apply(void 0, [(_j.sent()).sourcePackage]).toBe('@test/pkg-a');
|
|
492
|
+
_e = expect;
|
|
493
|
+
return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'b-1' })];
|
|
494
|
+
case 7:
|
|
495
|
+
_e.apply(void 0, [(_j.sent()).sourcePackage]).toBe('@test/pkg-b');
|
|
496
|
+
// And symmetrically: A re-booting must not prune B's row.
|
|
497
|
+
return [4 /*yield*/, bootAsSource('@test/pkg-a', [decl('a-1', 'a1@test.local'), decl('a-2', 'a2@test.local')])];
|
|
498
|
+
case 8:
|
|
499
|
+
// And symmetrically: A re-booting must not prune B's row.
|
|
500
|
+
_j.sent();
|
|
501
|
+
_f = expect;
|
|
502
|
+
return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'b-1' })];
|
|
503
|
+
case 9:
|
|
504
|
+
_f.apply(void 0, [_j.sent()]).toBeDefined();
|
|
505
|
+
_g = expect;
|
|
506
|
+
return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'a-1' })];
|
|
507
|
+
case 10:
|
|
508
|
+
_g.apply(void 0, [_j.sent()]).toBeDefined();
|
|
509
|
+
_h = expect;
|
|
510
|
+
return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'a-2' })];
|
|
511
|
+
case 11:
|
|
512
|
+
_h.apply(void 0, [_j.sent()]).toBeDefined();
|
|
513
|
+
return [2 /*return*/];
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
}); });
|
|
517
|
+
test('a source still prunes its OWN removed declarations without touching other sources', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
518
|
+
var db, _a, _b, _c;
|
|
519
|
+
return __generator(this, function (_d) {
|
|
520
|
+
switch (_d.label) {
|
|
521
|
+
case 0:
|
|
522
|
+
db = (0, db_1.getDbAsSystem)();
|
|
523
|
+
return [4 /*yield*/, bootAsSource('@test/pkg-a', [decl('a-1', 'a1@test.local'), decl('a-2', 'a2@test.local')])];
|
|
524
|
+
case 1:
|
|
525
|
+
_d.sent();
|
|
526
|
+
return [4 /*yield*/, bootAsSource('@test/pkg-b', [decl('b-1', 'b1@test.local')])];
|
|
527
|
+
case 2:
|
|
528
|
+
_d.sent();
|
|
529
|
+
// A's next build drops a-2: A's own removed declaration is pruned...
|
|
530
|
+
return [4 /*yield*/, bootAsSource('@test/pkg-a', [decl('a-1', 'a1@test.local')])];
|
|
531
|
+
case 3:
|
|
532
|
+
// A's next build drops a-2: A's own removed declaration is pruned...
|
|
533
|
+
_d.sent();
|
|
534
|
+
_a = expect;
|
|
535
|
+
return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'a-2' })];
|
|
536
|
+
case 4:
|
|
537
|
+
_a.apply(void 0, [_d.sent()]).toBeUndefined();
|
|
538
|
+
// ...while A's still-declared row and B's row both survive.
|
|
539
|
+
_b = expect;
|
|
540
|
+
return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'a-1' })];
|
|
541
|
+
case 5:
|
|
542
|
+
// ...while A's still-declared row and B's row both survive.
|
|
543
|
+
_b.apply(void 0, [_d.sent()]).toBeDefined();
|
|
544
|
+
_c = expect;
|
|
545
|
+
return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'b-1' })];
|
|
546
|
+
case 6:
|
|
547
|
+
_c.apply(void 0, [_d.sent()]).toBeDefined();
|
|
548
|
+
return [2 /*return*/];
|
|
549
|
+
}
|
|
550
|
+
});
|
|
551
|
+
}); });
|
|
552
|
+
test('a build declaring nothing for a table prunes nothing', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
553
|
+
var db, _a;
|
|
554
|
+
return __generator(this, function (_b) {
|
|
555
|
+
switch (_b.label) {
|
|
556
|
+
case 0:
|
|
557
|
+
db = (0, db_1.getDbAsSystem)();
|
|
558
|
+
return [4 /*yield*/, bootAsSource('@test/pkg-a', [decl('a-1', 'a1@test.local')])];
|
|
559
|
+
case 1:
|
|
560
|
+
_b.sent();
|
|
561
|
+
// A build whose declaring packages are all absent (or declare nothing for this table)
|
|
562
|
+
// makes no claim about it — pre-fix this boot deleted EVERY source-loaded row.
|
|
563
|
+
return [4 /*yield*/, bootAsSource('@test/pkg-b', [])];
|
|
564
|
+
case 2:
|
|
565
|
+
// A build whose declaring packages are all absent (or declare nothing for this table)
|
|
566
|
+
// makes no claim about it — pre-fix this boot deleted EVERY source-loaded row.
|
|
567
|
+
_b.sent();
|
|
568
|
+
_a = expect;
|
|
569
|
+
return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'a-1' })];
|
|
570
|
+
case 3:
|
|
571
|
+
_a.apply(void 0, [_b.sent()]).toBeDefined();
|
|
572
|
+
return [2 /*return*/];
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
}); });
|
|
576
|
+
test("onSourceRemoved update is source-scoped too: a foreign boot does not flag another source's rows", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
577
|
+
var db, _a, _b, _c, _d;
|
|
578
|
+
return __generator(this, function (_e) {
|
|
579
|
+
switch (_e.label) {
|
|
580
|
+
case 0:
|
|
581
|
+
db = (0, db_1.getDbAsSystem)();
|
|
582
|
+
return [4 /*yield*/, bootAsSource('@test/pkg-a', [
|
|
583
|
+
{ table: machineTable, record: { id: 'm-a', email: 'ma@test.local', status: 'active' } },
|
|
584
|
+
])];
|
|
585
|
+
case 1:
|
|
586
|
+
_e.sent();
|
|
587
|
+
return [4 /*yield*/, bootAsSource('@test/pkg-b', [
|
|
588
|
+
{ table: machineTable, record: { id: 'm-b', email: 'mb@test.local', status: 'active' } },
|
|
589
|
+
])];
|
|
590
|
+
case 2:
|
|
591
|
+
_e.sent();
|
|
592
|
+
// B's boot must not have treated A's machine account as removed.
|
|
593
|
+
_a = expect;
|
|
594
|
+
return [4 /*yield*/, db.get(machineTable, { id: 'm-a' })];
|
|
595
|
+
case 3:
|
|
596
|
+
// B's boot must not have treated A's machine account as removed.
|
|
597
|
+
_a.apply(void 0, [(_e.sent()).status]).toBe('active');
|
|
598
|
+
_b = expect;
|
|
599
|
+
return [4 /*yield*/, db.get(machineTable, { id: 'm-b' })];
|
|
600
|
+
case 4:
|
|
601
|
+
_b.apply(void 0, [(_e.sent()).status]).toBe('active');
|
|
602
|
+
// A dropping its own declaration still deactivates it — and leaves B's untouched.
|
|
603
|
+
return [4 /*yield*/, bootAsSource('@test/pkg-a', [
|
|
604
|
+
{ table: machineTable, record: { id: 'm-a2', email: 'ma2@test.local', status: 'active' } },
|
|
605
|
+
])];
|
|
606
|
+
case 5:
|
|
607
|
+
// A dropping its own declaration still deactivates it — and leaves B's untouched.
|
|
608
|
+
_e.sent();
|
|
609
|
+
_c = expect;
|
|
610
|
+
return [4 /*yield*/, db.get(machineTable, { id: 'm-a' })];
|
|
611
|
+
case 6:
|
|
612
|
+
_c.apply(void 0, [(_e.sent()).status]).toBe('deactivated');
|
|
613
|
+
_d = expect;
|
|
614
|
+
return [4 /*yield*/, db.get(machineTable, { id: 'm-b' })];
|
|
615
|
+
case 7:
|
|
616
|
+
_d.apply(void 0, [(_e.sent()).status]).toBe('active');
|
|
617
|
+
return [2 /*return*/];
|
|
618
|
+
}
|
|
619
|
+
});
|
|
620
|
+
}); });
|
|
621
|
+
test('a declaration migrating to a new source is re-owned, not pruned by its old source', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
622
|
+
var db, _a, _b;
|
|
623
|
+
return __generator(this, function (_c) {
|
|
624
|
+
switch (_c.label) {
|
|
625
|
+
case 0:
|
|
626
|
+
db = (0, db_1.getDbAsSystem)();
|
|
627
|
+
// k-1 starts life declared by pkg-a, then moves to pkg-b (package rename/move).
|
|
628
|
+
return [4 /*yield*/, bootAsSource('@test/pkg-a', [decl('k-1', 'k1@test.local')])];
|
|
629
|
+
case 1:
|
|
630
|
+
// k-1 starts life declared by pkg-a, then moves to pkg-b (package rename/move).
|
|
631
|
+
_c.sent();
|
|
632
|
+
return [4 /*yield*/, bootAsSource('@test/pkg-b', [decl('k-1', 'k1@test.local')])];
|
|
633
|
+
case 2:
|
|
634
|
+
_c.sent();
|
|
635
|
+
// The later declaration re-owns the row in place (adoption keeps the id).
|
|
636
|
+
_a = expect;
|
|
637
|
+
return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'k-1' })];
|
|
638
|
+
case 3:
|
|
639
|
+
// The later declaration re-owns the row in place (adoption keeps the id).
|
|
640
|
+
_a.apply(void 0, [(_c.sent()).sourcePackage]).toBe('@test/pkg-b');
|
|
641
|
+
// pkg-a's next build no longer carries k-1 — but the row now belongs to pkg-b.
|
|
642
|
+
return [4 /*yield*/, bootAsSource('@test/pkg-a', [decl('a-x', 'ax@test.local')])];
|
|
643
|
+
case 4:
|
|
644
|
+
// pkg-a's next build no longer carries k-1 — but the row now belongs to pkg-b.
|
|
645
|
+
_c.sent();
|
|
646
|
+
_b = expect;
|
|
647
|
+
return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'k-1' })];
|
|
648
|
+
case 5:
|
|
649
|
+
_b.apply(void 0, [_c.sent()]).toBeDefined();
|
|
650
|
+
return [2 /*return*/];
|
|
651
|
+
}
|
|
652
|
+
});
|
|
653
|
+
}); });
|
|
654
|
+
test('legacy rows (source_package NULL) are never pruned; still-declared ones are adopted and stamped', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
655
|
+
var db, legacyDeclared, legacyForeign, _a, _b;
|
|
656
|
+
return __generator(this, function (_c) {
|
|
657
|
+
switch (_c.label) {
|
|
658
|
+
case 0:
|
|
659
|
+
db = (0, db_1.getDbAsSystem)();
|
|
660
|
+
return [4 /*yield*/, db.insert(defaultPolicyTable, {
|
|
661
|
+
email: 'l1@test.local',
|
|
662
|
+
isLoadedFromSource: true,
|
|
663
|
+
})];
|
|
664
|
+
case 1:
|
|
665
|
+
legacyDeclared = _c.sent();
|
|
666
|
+
return [4 /*yield*/, db.insert(defaultPolicyTable, {
|
|
667
|
+
email: 'l2@test.local',
|
|
668
|
+
isLoadedFromSource: true,
|
|
669
|
+
})];
|
|
670
|
+
case 2:
|
|
671
|
+
legacyForeign = _c.sent();
|
|
672
|
+
return [4 /*yield*/, bootAsSource('@test/pkg-a', [decl(legacyDeclared.id, 'l1@test.local')])];
|
|
673
|
+
case 3:
|
|
674
|
+
_c.sent();
|
|
675
|
+
// The declared legacy row converges: adopted by its declaring package and stamped.
|
|
676
|
+
_a = expect;
|
|
677
|
+
return [4 /*yield*/, db.get(defaultPolicyTable, { id: legacyDeclared.id })];
|
|
678
|
+
case 4:
|
|
679
|
+
// The declared legacy row converges: adopted by its declaring package and stamped.
|
|
680
|
+
_a.apply(void 0, [(_c.sent()).sourcePackage]).toBe('@test/pkg-a');
|
|
681
|
+
// The undeclared legacy row is out of prune scope — it may belong to a build that has
|
|
682
|
+
// not rebooted onto the stamped world yet; its owner's next boot will claim it.
|
|
683
|
+
_b = expect;
|
|
684
|
+
return [4 /*yield*/, db.get(defaultPolicyTable, { id: legacyForeign.id })];
|
|
685
|
+
case 5:
|
|
686
|
+
// The undeclared legacy row is out of prune scope — it may belong to a build that has
|
|
687
|
+
// not rebooted onto the stamped world yet; its owner's next boot will claim it.
|
|
688
|
+
_b.apply(void 0, [_c.sent()]).toBeDefined();
|
|
689
|
+
return [2 /*return*/];
|
|
690
|
+
}
|
|
691
|
+
});
|
|
692
|
+
}); });
|
|
693
|
+
test('a declaration moving between packages WITHIN one build is re-owned in place — never deleted and re-inserted', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
694
|
+
var db, before, after, _a;
|
|
695
|
+
return __generator(this, function (_b) {
|
|
696
|
+
switch (_b.label) {
|
|
697
|
+
case 0:
|
|
698
|
+
db = (0, db_1.getDbAsSystem)();
|
|
699
|
+
// k-1 and a-other both start under pkg-a.
|
|
700
|
+
return [4 /*yield*/, bootAsSource('@test/pkg-a', [decl('k-1', 'k1@test.local'), decl('a-other', 'aother@test.local')])];
|
|
701
|
+
case 1:
|
|
702
|
+
// k-1 and a-other both start under pkg-a.
|
|
703
|
+
_b.sent();
|
|
704
|
+
return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'k-1' })];
|
|
705
|
+
case 2:
|
|
706
|
+
before = _b.sent();
|
|
707
|
+
// The next build moves k-1 to pkg-b while pkg-a still declares a-other — ONE boot.
|
|
708
|
+
// pkg-a's prune scope must not treat k-1 as removed (it is declared elsewhere in the
|
|
709
|
+
// build): the row survives in place, then the stamp leg re-owns it.
|
|
710
|
+
return [4 /*yield*/, bootBuild([
|
|
711
|
+
__assign({ source: '@test/pkg-a' }, decl('a-other', 'aother@test.local')),
|
|
712
|
+
__assign({ source: '@test/pkg-b' }, decl('k-1', 'k1@test.local')),
|
|
713
|
+
])];
|
|
714
|
+
case 3:
|
|
715
|
+
// The next build moves k-1 to pkg-b while pkg-a still declares a-other — ONE boot.
|
|
716
|
+
// pkg-a's prune scope must not treat k-1 as removed (it is declared elsewhere in the
|
|
717
|
+
// build): the row survives in place, then the stamp leg re-owns it.
|
|
718
|
+
_b.sent();
|
|
719
|
+
return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'k-1' })];
|
|
720
|
+
case 4:
|
|
721
|
+
after = _b.sent();
|
|
722
|
+
expect(after).toBeDefined();
|
|
723
|
+
// Row continuity is the outcome: created unchanged means no delete+re-insert cycle
|
|
724
|
+
// (a re-insert restamps created), so watchers saw no delete and references held.
|
|
725
|
+
expect(after.created.valueOf()).toBe(before.created.valueOf());
|
|
726
|
+
expect(after.sourcePackage).toBe('@test/pkg-b');
|
|
727
|
+
_a = expect;
|
|
728
|
+
return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'a-other' })];
|
|
729
|
+
case 5:
|
|
730
|
+
_a.apply(void 0, [_b.sent()]).toBeDefined();
|
|
731
|
+
return [2 /*return*/];
|
|
732
|
+
}
|
|
733
|
+
});
|
|
734
|
+
}); });
|
|
735
|
+
test('a within-build package move on an update-policy table never passes through the removed patch', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
736
|
+
var db, moved, deactivations;
|
|
737
|
+
return __generator(this, function (_a) {
|
|
738
|
+
switch (_a.label) {
|
|
739
|
+
case 0:
|
|
740
|
+
db = (0, db_1.getDbAsSystem)();
|
|
741
|
+
return [4 /*yield*/, bootAsSource('@test/pkg-a', [
|
|
742
|
+
{ table: machineTable, record: { id: 'm-move', email: 'mmove@test.local', status: 'active' } },
|
|
743
|
+
{ table: machineTable, record: { id: 'm-stay', email: 'mstay@test.local', status: 'active' } },
|
|
744
|
+
])];
|
|
745
|
+
case 1:
|
|
746
|
+
_a.sent();
|
|
747
|
+
// Runtime-owned state that must survive the move untouched (the credential stand-in).
|
|
748
|
+
return [4 /*yield*/, db.update(machineTable, { id: 'm-move', runtimeNote: 'provisioned' })];
|
|
749
|
+
case 2:
|
|
750
|
+
// Runtime-owned state that must survive the move untouched (the credential stand-in).
|
|
751
|
+
_a.sent();
|
|
752
|
+
RecordingMachineAccountWatcher.updates = [];
|
|
753
|
+
return [4 /*yield*/, bootBuild([
|
|
754
|
+
{
|
|
755
|
+
source: '@test/pkg-a',
|
|
756
|
+
table: machineTable,
|
|
757
|
+
record: { id: 'm-stay', email: 'mstay@test.local', status: 'active' },
|
|
758
|
+
},
|
|
759
|
+
{
|
|
760
|
+
source: '@test/pkg-b',
|
|
761
|
+
table: machineTable,
|
|
762
|
+
record: { id: 'm-move', email: 'mmove@test.local', status: 'active' },
|
|
763
|
+
},
|
|
764
|
+
])];
|
|
765
|
+
case 3:
|
|
766
|
+
_a.sent();
|
|
767
|
+
return [4 /*yield*/, db.get(machineTable, { id: 'm-move' })];
|
|
768
|
+
case 4:
|
|
769
|
+
moved = _a.sent();
|
|
770
|
+
expect(moved).toMatchObject({ status: 'active', runtimeNote: 'provisioned', sourcePackage: '@test/pkg-b' });
|
|
771
|
+
deactivations = RecordingMachineAccountWatcher.updates.filter(function (u) { return u.status === 'deactivated'; });
|
|
772
|
+
expect(deactivations).toHaveLength(0);
|
|
773
|
+
return [2 /*return*/];
|
|
774
|
+
}
|
|
775
|
+
});
|
|
776
|
+
}); });
|
|
777
|
+
test('cross-source boots leave a natural-key-adopted row in place — adopted id preserved', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
778
|
+
var db, handMade, _a, adopted, foreignTouches;
|
|
779
|
+
return __generator(this, function (_b) {
|
|
780
|
+
switch (_b.label) {
|
|
781
|
+
case 0:
|
|
782
|
+
db = (0, db_1.getDbAsSystem)();
|
|
783
|
+
return [4 /*yield*/, db.insert(machineTable, { email: 'bridge2@test.local', runtimeNote: 'cred' })];
|
|
784
|
+
case 1:
|
|
785
|
+
handMade = _b.sent();
|
|
786
|
+
return [4 /*yield*/, bootAsSource('@test/pkg-b', [
|
|
787
|
+
{ table: machineTable, record: { id: 'declared-bridge2', email: 'bridge2@test.local', status: 'active' } },
|
|
788
|
+
])];
|
|
789
|
+
case 2:
|
|
790
|
+
_b.sent();
|
|
791
|
+
_a = expect;
|
|
792
|
+
return [4 /*yield*/, db.get(machineTable, { email: 'bridge2@test.local' })];
|
|
793
|
+
case 3:
|
|
794
|
+
_a.apply(void 0, [(_b.sent()).id]).toBe(handMade.id);
|
|
795
|
+
// A DIFFERENT source booting (with its own machine declaration) must not prune, flag,
|
|
796
|
+
// or disturb the adopted row — same id, same runtime state, still active.
|
|
797
|
+
RecordingMachineAccountWatcher.updates = [];
|
|
798
|
+
return [4 /*yield*/, bootAsSource('@test/pkg-a', [
|
|
799
|
+
{ table: machineTable, record: { id: 'm-a3', email: 'ma3@test.local', status: 'active' } },
|
|
800
|
+
])];
|
|
801
|
+
case 4:
|
|
802
|
+
_b.sent();
|
|
803
|
+
return [4 /*yield*/, db.get(machineTable, { email: 'bridge2@test.local' })];
|
|
804
|
+
case 5:
|
|
805
|
+
adopted = _b.sent();
|
|
806
|
+
expect(adopted).toMatchObject({ id: handMade.id, status: 'active', runtimeNote: 'cred' });
|
|
807
|
+
foreignTouches = RecordingMachineAccountWatcher.updates.filter(function (u) { return u.id === handMade.id; });
|
|
808
|
+
expect(foreignTouches).toHaveLength(0);
|
|
809
|
+
return [2 /*return*/];
|
|
810
|
+
}
|
|
811
|
+
});
|
|
812
|
+
}); });
|
|
813
|
+
test('legacy rows (source_package NULL) are excluded from the update-policy leg too', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
814
|
+
var db, legacy, _a, legacyTouches;
|
|
815
|
+
return __generator(this, function (_b) {
|
|
816
|
+
switch (_b.label) {
|
|
817
|
+
case 0:
|
|
818
|
+
db = (0, db_1.getDbAsSystem)();
|
|
819
|
+
return [4 /*yield*/, db.insert(machineTable, {
|
|
820
|
+
email: 'legacy@test.local',
|
|
821
|
+
status: 'active',
|
|
822
|
+
isLoadedFromSource: true,
|
|
823
|
+
})];
|
|
824
|
+
case 1:
|
|
825
|
+
legacy = _b.sent();
|
|
826
|
+
RecordingMachineAccountWatcher.updates = [];
|
|
827
|
+
return [4 /*yield*/, bootAsSource('@test/pkg-a', [
|
|
828
|
+
{ table: machineTable, record: { id: 'm-a4', email: 'ma4@test.local', status: 'active' } },
|
|
829
|
+
])];
|
|
830
|
+
case 2:
|
|
831
|
+
_b.sent();
|
|
832
|
+
// NULL-stamped rows are out of BOTH reconcile legs — not deleted (covered above) and
|
|
833
|
+
// not flagged: the legacy machine account stays active, unobserved by watchers.
|
|
834
|
+
_a = expect;
|
|
835
|
+
return [4 /*yield*/, db.get(machineTable, { id: legacy.id })];
|
|
836
|
+
case 3:
|
|
837
|
+
// NULL-stamped rows are out of BOTH reconcile legs — not deleted (covered above) and
|
|
838
|
+
// not flagged: the legacy machine account stays active, unobserved by watchers.
|
|
839
|
+
_a.apply(void 0, [(_b.sent()).status]).toBe('active');
|
|
840
|
+
legacyTouches = RecordingMachineAccountWatcher.updates.filter(function (u) { return u.id === legacy.id; });
|
|
841
|
+
expect(legacyTouches).toHaveLength(0);
|
|
842
|
+
return [2 /*return*/];
|
|
843
|
+
}
|
|
844
|
+
});
|
|
845
|
+
}); });
|
|
846
|
+
test('a package in the build that no longer declares anything for a table prunes its rows there — dropping the last declaration is a removal', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
847
|
+
var db, _a, _b, _c;
|
|
848
|
+
return __generator(this, function (_d) {
|
|
849
|
+
switch (_d.label) {
|
|
850
|
+
case 0:
|
|
851
|
+
db = (0, db_1.getDbAsSystem)();
|
|
852
|
+
// pkg-a declares a-1 on the default-policy table and m-1 on the machine table; pkg-b owns b-1.
|
|
853
|
+
return [4 /*yield*/, bootBuild([
|
|
854
|
+
__assign({ source: '@test/pkg-a' }, decl('a-1', 'a1@test.local')),
|
|
855
|
+
{
|
|
856
|
+
source: '@test/pkg-a',
|
|
857
|
+
table: machineTable,
|
|
858
|
+
record: { id: 'm-1', email: 'm1@test.local', status: 'active' },
|
|
859
|
+
},
|
|
860
|
+
__assign({ source: '@test/pkg-b' }, decl('b-1', 'b1@test.local')),
|
|
861
|
+
])];
|
|
862
|
+
case 1:
|
|
863
|
+
// pkg-a declares a-1 on the default-policy table and m-1 on the machine table; pkg-b owns b-1.
|
|
864
|
+
_d.sent();
|
|
865
|
+
// pkg-a's next build keeps m-1 but drops its LAST default-policy declaration. The package is
|
|
866
|
+
// still in the build, so it is authoritative for its own rows on every table: a-1 is removed.
|
|
867
|
+
return [4 /*yield*/, bootBuild([
|
|
868
|
+
{
|
|
869
|
+
source: '@test/pkg-a',
|
|
870
|
+
table: machineTable,
|
|
871
|
+
record: { id: 'm-1', email: 'm1@test.local', status: 'active' },
|
|
872
|
+
},
|
|
873
|
+
])];
|
|
874
|
+
case 2:
|
|
875
|
+
// pkg-a's next build keeps m-1 but drops its LAST default-policy declaration. The package is
|
|
876
|
+
// still in the build, so it is authoritative for its own rows on every table: a-1 is removed.
|
|
877
|
+
_d.sent();
|
|
878
|
+
_a = expect;
|
|
879
|
+
return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'a-1' })];
|
|
880
|
+
case 3:
|
|
881
|
+
_a.apply(void 0, [_d.sent()]).toBeUndefined();
|
|
882
|
+
// pkg-b's row on that table is untouched, and pkg-a's still-declared machine row survives.
|
|
883
|
+
_b = expect;
|
|
884
|
+
return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'b-1' })];
|
|
885
|
+
case 4:
|
|
886
|
+
// pkg-b's row on that table is untouched, and pkg-a's still-declared machine row survives.
|
|
887
|
+
_b.apply(void 0, [_d.sent()]).toBeDefined();
|
|
888
|
+
_c = expect;
|
|
889
|
+
return [4 /*yield*/, db.get(machineTable, { id: 'm-1' })];
|
|
890
|
+
case 5:
|
|
891
|
+
_c.apply(void 0, [(_d.sent()).status]).toBe('active');
|
|
892
|
+
return [2 /*return*/];
|
|
893
|
+
}
|
|
894
|
+
});
|
|
895
|
+
}); });
|
|
896
|
+
test('unowned legacy rows no declaration in the build claims are counted, never pruned', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
897
|
+
var db, unowned, summary, _a, claimed, _b;
|
|
898
|
+
return __generator(this, function (_c) {
|
|
899
|
+
switch (_c.label) {
|
|
900
|
+
case 0:
|
|
901
|
+
db = (0, db_1.getDbAsSystem)();
|
|
902
|
+
return [4 /*yield*/, db.insert(defaultPolicyTable, { email: 'u@test.local', isLoadedFromSource: true })];
|
|
903
|
+
case 1:
|
|
904
|
+
unowned = _c.sent();
|
|
905
|
+
return [4 /*yield*/, bootAsSource('@test/pkg-a', [decl('a-1', 'a1@test.local')])];
|
|
906
|
+
case 2:
|
|
907
|
+
summary = _c.sent();
|
|
908
|
+
expect(summary[defaultPolicyTable.name].unowned).toBe(1);
|
|
909
|
+
_a = expect;
|
|
910
|
+
return [4 /*yield*/, db.get(defaultPolicyTable, { id: unowned.id })];
|
|
911
|
+
case 3:
|
|
912
|
+
_a.apply(void 0, [_c.sent()]).toBeDefined();
|
|
913
|
+
return [4 /*yield*/, bootAsSource('@test/pkg-a', [
|
|
914
|
+
decl('a-1', 'a1@test.local'),
|
|
915
|
+
decl(unowned.id, 'u@test.local'),
|
|
916
|
+
])];
|
|
917
|
+
case 4:
|
|
918
|
+
claimed = _c.sent();
|
|
919
|
+
expect(claimed[defaultPolicyTable.name].unowned).toBe(0);
|
|
920
|
+
_b = expect;
|
|
921
|
+
return [4 /*yield*/, db.get(defaultPolicyTable, { id: unowned.id })];
|
|
922
|
+
case 5:
|
|
923
|
+
_b.apply(void 0, [(_c.sent()).sourcePackage]).toBe('@test/pkg-a');
|
|
924
|
+
return [2 /*return*/];
|
|
925
|
+
}
|
|
926
|
+
});
|
|
927
|
+
}); });
|
|
928
|
+
});
|
|
929
|
+
/**
|
|
930
|
+
* Version-scoped pruning WITHIN one package: two servers running different VERSIONS of the
|
|
931
|
+
* same package against one shared database (the actual brent-dev-2 incident — the main app
|
|
932
|
+
* at thought-common 3.29.0 and a feature worktree at 3.32.0, whose added thought types the
|
|
933
|
+
* older build deleted on every boot). Every row carries the declaring package's version;
|
|
934
|
+
* a boot never prunes (or flags) a row stamped by a NEWER version of the same package.
|
|
935
|
+
* Within the same or an older stamped version, the package stays authoritative:
|
|
936
|
+
* removals still land, and equal-version skew is last-writer-wins by design.
|
|
937
|
+
*/
|
|
938
|
+
describe('version-scoped pruning (same package, different builds)', function () {
|
|
939
|
+
var decl = function (id, email) { return ({ table: defaultPolicyTable, record: { id: id, email: email } }); };
|
|
940
|
+
test('an older build of a package never prunes rows a newer build of the same package declared (the incident)', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
941
|
+
var db, _a, _b, _c, _d;
|
|
942
|
+
return __generator(this, function (_e) {
|
|
943
|
+
switch (_e.label) {
|
|
944
|
+
case 0:
|
|
945
|
+
db = (0, db_1.getDbAsSystem)();
|
|
946
|
+
// The newer build (e.g. thought-common 3.32.0) declares an added type k-2.
|
|
947
|
+
return [4 /*yield*/, bootAsSource('@test/pkg-a', [decl('k-1', 'k1@test.local'), decl('k-2', 'k2@test.local')], '3.32.0')];
|
|
948
|
+
case 1:
|
|
949
|
+
// The newer build (e.g. thought-common 3.32.0) declares an added type k-2.
|
|
950
|
+
_e.sent();
|
|
951
|
+
// The older build (3.29.0) boots without k-2 — pre-fix this deleted k-2 every boot.
|
|
952
|
+
return [4 /*yield*/, bootAsSource('@test/pkg-a', [decl('k-1', 'k1@test.local')], '3.29.0')];
|
|
953
|
+
case 2:
|
|
954
|
+
// The older build (3.29.0) boots without k-2 — pre-fix this deleted k-2 every boot.
|
|
955
|
+
_e.sent();
|
|
956
|
+
_a = expect;
|
|
957
|
+
return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'k-2' })];
|
|
958
|
+
case 3:
|
|
959
|
+
_a.apply(void 0, [_e.sent()]).toBeDefined();
|
|
960
|
+
_b = expect;
|
|
961
|
+
return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'k-1' })];
|
|
962
|
+
case 4:
|
|
963
|
+
_b.apply(void 0, [_e.sent()]).toBeDefined();
|
|
964
|
+
// The NEWER build remains authoritative for genuine removals: it drops k-2 for real.
|
|
965
|
+
return [4 /*yield*/, bootAsSource('@test/pkg-a', [decl('k-1', 'k1@test.local')], '3.32.0')];
|
|
966
|
+
case 5:
|
|
967
|
+
// The NEWER build remains authoritative for genuine removals: it drops k-2 for real.
|
|
968
|
+
_e.sent();
|
|
969
|
+
_c = expect;
|
|
970
|
+
return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'k-2' })];
|
|
971
|
+
case 6:
|
|
972
|
+
_c.apply(void 0, [_e.sent()]).toBeUndefined();
|
|
973
|
+
_d = expect;
|
|
974
|
+
return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'k-1' })];
|
|
975
|
+
case 7:
|
|
976
|
+
_d.apply(void 0, [_e.sent()]).toBeDefined();
|
|
977
|
+
return [2 /*return*/];
|
|
978
|
+
}
|
|
979
|
+
});
|
|
980
|
+
}); });
|
|
981
|
+
test('an older build never writes over a row a newer build of the same package stamped — content and stamp survive', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
982
|
+
var db, k1, summary, row, _a;
|
|
983
|
+
return __generator(this, function (_b) {
|
|
984
|
+
switch (_b.label) {
|
|
985
|
+
case 0:
|
|
986
|
+
db = (0, db_1.getDbAsSystem)();
|
|
987
|
+
k1 = function (displayName) { return ({
|
|
988
|
+
table: defaultPolicyTable,
|
|
989
|
+
record: { id: 'k-1', email: 'k1@test.local', displayName: displayName },
|
|
990
|
+
}); };
|
|
991
|
+
// The newer build (3.32.0) declares k-1 with its current definition.
|
|
992
|
+
return [4 /*yield*/, bootAsSource('@test/pkg-a', [k1('newer')], '3.32.0')];
|
|
993
|
+
case 1:
|
|
994
|
+
// The newer build (3.32.0) declares k-1 with its current definition.
|
|
995
|
+
_b.sent();
|
|
996
|
+
return [4 /*yield*/, bootAsSource('@test/pkg-a', [k1('older')], '3.29.0')];
|
|
997
|
+
case 2:
|
|
998
|
+
summary = _b.sent();
|
|
999
|
+
return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'k-1' })];
|
|
1000
|
+
case 3:
|
|
1001
|
+
row = _b.sent();
|
|
1002
|
+
expect(row.displayName).toBe('newer');
|
|
1003
|
+
expect(row.sourcePackageVersion).toBe('3.32.0');
|
|
1004
|
+
expect(summary[defaultPolicyTable.name].skippedNewer).toBe(1);
|
|
1005
|
+
expect(summary[defaultPolicyTable.name].updates).toBe(0);
|
|
1006
|
+
// The newer build remains authoritative over its own rows: its next definition lands.
|
|
1007
|
+
return [4 /*yield*/, bootAsSource('@test/pkg-a', [k1('newest')], '3.32.0')];
|
|
1008
|
+
case 4:
|
|
1009
|
+
// The newer build remains authoritative over its own rows: its next definition lands.
|
|
1010
|
+
_b.sent();
|
|
1011
|
+
_a = expect;
|
|
1012
|
+
return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'k-1' })];
|
|
1013
|
+
case 5:
|
|
1014
|
+
_a.apply(void 0, [(_b.sent()).displayName]).toBe('newest');
|
|
1015
|
+
return [2 /*return*/];
|
|
1016
|
+
}
|
|
1017
|
+
});
|
|
1018
|
+
}); });
|
|
1019
|
+
test('version skew on an update-policy table: an older build does not flag newer rows', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1020
|
+
var db, _a, deactivations;
|
|
1021
|
+
return __generator(this, function (_b) {
|
|
1022
|
+
switch (_b.label) {
|
|
1023
|
+
case 0:
|
|
1024
|
+
db = (0, db_1.getDbAsSystem)();
|
|
1025
|
+
return [4 /*yield*/, bootAsSource('@test/pkg-a', [
|
|
1026
|
+
{ table: machineTable, record: { id: 'mv-1', email: 'mv1@test.local', status: 'active' } },
|
|
1027
|
+
{ table: machineTable, record: { id: 'mv-2', email: 'mv2@test.local', status: 'active' } },
|
|
1028
|
+
], '2.0.0')];
|
|
1029
|
+
case 1:
|
|
1030
|
+
_b.sent();
|
|
1031
|
+
RecordingMachineAccountWatcher.updates = [];
|
|
1032
|
+
return [4 /*yield*/, bootAsSource('@test/pkg-a', [{ table: machineTable, record: { id: 'mv-1', email: 'mv1@test.local', status: 'active' } }], '1.0.0')];
|
|
1033
|
+
case 2:
|
|
1034
|
+
_b.sent();
|
|
1035
|
+
// The newer build's machine account is not deactivated by the older build's boot.
|
|
1036
|
+
_a = expect;
|
|
1037
|
+
return [4 /*yield*/, db.get(machineTable, { id: 'mv-2' })];
|
|
1038
|
+
case 3:
|
|
1039
|
+
// The newer build's machine account is not deactivated by the older build's boot.
|
|
1040
|
+
_a.apply(void 0, [(_b.sent()).status]).toBe('active');
|
|
1041
|
+
deactivations = RecordingMachineAccountWatcher.updates.filter(function (u) { return u.status === 'deactivated'; });
|
|
1042
|
+
expect(deactivations).toHaveLength(0);
|
|
1043
|
+
return [2 /*return*/];
|
|
1044
|
+
}
|
|
1045
|
+
});
|
|
1046
|
+
}); });
|
|
1047
|
+
test('equal versions with differing sets remain last-writer-wins — the deliberate residual', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1048
|
+
var db, _a, _b;
|
|
1049
|
+
return __generator(this, function (_c) {
|
|
1050
|
+
switch (_c.label) {
|
|
1051
|
+
case 0:
|
|
1052
|
+
db = (0, db_1.getDbAsSystem)();
|
|
1053
|
+
// Two builds at the SAME version (uncommitted local skew) cannot be ordered; the
|
|
1054
|
+
// package stays authoritative within its own version: the later boot's set wins.
|
|
1055
|
+
return [4 /*yield*/, bootAsSource('@test/pkg-a', [decl('e-1', 'e1@test.local'), decl('e-2', 'e2@test.local')], '1.0.0')];
|
|
1056
|
+
case 1:
|
|
1057
|
+
// Two builds at the SAME version (uncommitted local skew) cannot be ordered; the
|
|
1058
|
+
// package stays authoritative within its own version: the later boot's set wins.
|
|
1059
|
+
_c.sent();
|
|
1060
|
+
return [4 /*yield*/, bootAsSource('@test/pkg-a', [decl('e-1', 'e1@test.local')], '1.0.0')];
|
|
1061
|
+
case 2:
|
|
1062
|
+
_c.sent();
|
|
1063
|
+
_a = expect;
|
|
1064
|
+
return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'e-2' })];
|
|
1065
|
+
case 3:
|
|
1066
|
+
_a.apply(void 0, [_c.sent()]).toBeUndefined();
|
|
1067
|
+
_b = expect;
|
|
1068
|
+
return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'e-1' })];
|
|
1069
|
+
case 4:
|
|
1070
|
+
_b.apply(void 0, [_c.sent()]).toBeDefined();
|
|
1071
|
+
return [2 /*return*/];
|
|
1072
|
+
}
|
|
1073
|
+
});
|
|
1074
|
+
}); });
|
|
1075
|
+
test('a build without a resolvable version never prunes version-stamped rows; unversioned stamps stay last-writer-wins', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1076
|
+
var db, _a, _b, _c, _d;
|
|
1077
|
+
return __generator(this, function (_e) {
|
|
1078
|
+
switch (_e.label) {
|
|
1079
|
+
case 0:
|
|
1080
|
+
db = (0, db_1.getDbAsSystem)();
|
|
1081
|
+
// Unversioned stamps carry no ordering: prunable by any build of the package —
|
|
1082
|
+
// versionless-to-versionless keeps the pre-version last-writer-wins behavior.
|
|
1083
|
+
return [4 /*yield*/, bootAsSource('@test/pkg-a', [decl('n-1', 'n1@test.local'), decl('n-2', 'n2@test.local')])];
|
|
1084
|
+
case 1:
|
|
1085
|
+
// Unversioned stamps carry no ordering: prunable by any build of the package —
|
|
1086
|
+
// versionless-to-versionless keeps the pre-version last-writer-wins behavior.
|
|
1087
|
+
_e.sent();
|
|
1088
|
+
return [4 /*yield*/, bootAsSource('@test/pkg-a', [decl('n-1', 'n1@test.local')])];
|
|
1089
|
+
case 2:
|
|
1090
|
+
_e.sent();
|
|
1091
|
+
_a = expect;
|
|
1092
|
+
return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'n-2' })];
|
|
1093
|
+
case 3:
|
|
1094
|
+
_a.apply(void 0, [_e.sent()]).toBeUndefined();
|
|
1095
|
+
// Versioned rows survive an unorderable (versionless) build's boot: it cannot place
|
|
1096
|
+
// itself before or after the stamp, so it must not delete what might be newer.
|
|
1097
|
+
return [4 /*yield*/, bootAsSource('@test/pkg-a', [decl('u-1', 'u1@test.local'), decl('u-2', 'u2@test.local')], '1.0.0')];
|
|
1098
|
+
case 4:
|
|
1099
|
+
// Versioned rows survive an unorderable (versionless) build's boot: it cannot place
|
|
1100
|
+
// itself before or after the stamp, so it must not delete what might be newer.
|
|
1101
|
+
_e.sent();
|
|
1102
|
+
return [4 /*yield*/, bootAsSource('@test/pkg-a', [decl('u-1', 'u1@test.local')])];
|
|
1103
|
+
case 5:
|
|
1104
|
+
_e.sent();
|
|
1105
|
+
_b = expect;
|
|
1106
|
+
return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'u-2' })];
|
|
1107
|
+
case 6:
|
|
1108
|
+
_b.apply(void 0, [_e.sent()]).toBeDefined();
|
|
1109
|
+
// A versioned build prunes older-or-equal stamps as usual (1.0.0 <= 1.0.1).
|
|
1110
|
+
return [4 /*yield*/, bootAsSource('@test/pkg-a', [decl('u-1', 'u1@test.local')], '1.0.1')];
|
|
1111
|
+
case 7:
|
|
1112
|
+
// A versioned build prunes older-or-equal stamps as usual (1.0.0 <= 1.0.1).
|
|
1113
|
+
_e.sent();
|
|
1114
|
+
_c = expect;
|
|
1115
|
+
return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'u-2' })];
|
|
1116
|
+
case 8:
|
|
1117
|
+
_c.apply(void 0, [_e.sent()]).toBeUndefined();
|
|
1118
|
+
_d = expect;
|
|
1119
|
+
return [4 /*yield*/, db.get(defaultPolicyTable, { id: 'u-1' })];
|
|
1120
|
+
case 9:
|
|
1121
|
+
_d.apply(void 0, [_e.sent()]).toBeDefined();
|
|
1122
|
+
return [2 /*return*/];
|
|
1123
|
+
}
|
|
1124
|
+
});
|
|
1125
|
+
}); });
|
|
1126
|
+
test("the version resolver reads the declaring package's real package.json", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1127
|
+
var internals;
|
|
1128
|
+
return __generator(this, function (_a) {
|
|
1129
|
+
internals = new SourceRecordLoader_1.SourceRecordLoader();
|
|
1130
|
+
// A real dependency of this build resolves to its actual version (entry resolution +
|
|
1131
|
+
// walk-up works for exports-mapped packages, where `<pkg>/package.json` is not requireable).
|
|
1132
|
+
expect(internals.resolveSourceVersion('@proteinjs/db-query')).toMatch(/^\d+\.\d+\.\d+/);
|
|
1133
|
+
// Resolution failure degrades to versionless, never throws.
|
|
1134
|
+
expect(internals.resolveSourceVersion('@test/no-such-package')).toBeUndefined();
|
|
1135
|
+
return [2 /*return*/];
|
|
1136
|
+
});
|
|
1137
|
+
}); });
|
|
1138
|
+
});
|
|
398
1139
|
test('a natural key without a unique guarantee fails boot loudly', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
399
1140
|
var MisdeclaredNaturalKeyTable;
|
|
400
1141
|
return __generator(this, function (_a) {
|
|
@@ -477,6 +1218,41 @@ var sourceRecordSyncTests = function (driver, transactionContextFactory, dropTab
|
|
|
477
1218
|
}
|
|
478
1219
|
});
|
|
479
1220
|
}); });
|
|
1221
|
+
test('a new source-record table inherits the ownership stamp columns from SourceRecord — never declared per table', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1222
|
+
var table, row;
|
|
1223
|
+
var _a, _b;
|
|
1224
|
+
return __generator(this, function (_c) {
|
|
1225
|
+
switch (_c.label) {
|
|
1226
|
+
case 0:
|
|
1227
|
+
table = sourceRecordSyncTestTables_1.sourceRecordSyncTestTables.InheritedStamp;
|
|
1228
|
+
// Type layer: the minted table carries the stamp columns, at their physical names,
|
|
1229
|
+
// without declaring them.
|
|
1230
|
+
expect((_a = table.columns.sourcePackage) === null || _a === void 0 ? void 0 : _a.name).toBe('source_package');
|
|
1231
|
+
expect((_b = table.columns.sourcePackageVersion) === null || _b === void 0 ? void 0 : _b.name).toBe('source_package_version');
|
|
1232
|
+
expect((0, db_1.isSourceRecordTable)(table)).toBe(true);
|
|
1233
|
+
// Schema layer: the harness minted the physical table through TableManager, which derives
|
|
1234
|
+
// DDL from the type — so the stamp columns must have been emitted without the fixture
|
|
1235
|
+
// declaring them. One boot of the sync stamps a row through them; the write itself fails
|
|
1236
|
+
// if the physical columns were not emitted.
|
|
1237
|
+
return [4 /*yield*/, bootAsSource('@test/pkg-a', [{ table: table, record: { id: 's-1', email: 's1@test.local' } }], '1.0.0')];
|
|
1238
|
+
case 1:
|
|
1239
|
+
// Schema layer: the harness minted the physical table through TableManager, which derives
|
|
1240
|
+
// DDL from the type — so the stamp columns must have been emitted without the fixture
|
|
1241
|
+
// declaring them. One boot of the sync stamps a row through them; the write itself fails
|
|
1242
|
+
// if the physical columns were not emitted.
|
|
1243
|
+
_c.sent();
|
|
1244
|
+
return [4 /*yield*/, (0, db_1.getDbAsSystem)().get(table, { id: 's-1' })];
|
|
1245
|
+
case 2:
|
|
1246
|
+
row = _c.sent();
|
|
1247
|
+
expect(row).toMatchObject({
|
|
1248
|
+
sourcePackage: '@test/pkg-a',
|
|
1249
|
+
sourcePackageVersion: '1.0.0',
|
|
1250
|
+
isLoadedFromSource: true,
|
|
1251
|
+
});
|
|
1252
|
+
return [2 /*return*/];
|
|
1253
|
+
}
|
|
1254
|
+
});
|
|
1255
|
+
}); });
|
|
480
1256
|
};
|
|
481
1257
|
};
|
|
482
1258
|
exports.sourceRecordSyncTests = sourceRecordSyncTests;
|