@guisao-llc/gambit-cascade 0.1.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/dist/index.d.ts +57 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/package.json +39 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { ClientSession, Types } from "mongoose";
|
|
2
|
+
/**
|
|
3
|
+
* @guisao-llc/gambit-cascade
|
|
4
|
+
*
|
|
5
|
+
* Cascading deletion, minus the deletions.
|
|
6
|
+
*
|
|
7
|
+
* Mongo has no foreign keys, so "deleting this takes its dependents with it" is
|
|
8
|
+
* application code in every app that needs it. One app here spreads roughly 700
|
|
9
|
+
* lines of that across eight files; another does the same job in schema hooks.
|
|
10
|
+
* What those have in common is not the deletions — those are entirely domain —
|
|
11
|
+
* but the small amount of machinery around them: an operation interface, and a
|
|
12
|
+
* runner that executes a list of them in order.
|
|
13
|
+
*
|
|
14
|
+
* That is all this package is. It never touches a collection itself.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* One step of a cascade: delete what this subject owns in one collection.
|
|
18
|
+
*
|
|
19
|
+
* Generic over its arguments, because cascades are not all keyed the same way.
|
|
20
|
+
* Deleting a person runs on `(id, session)`. Deleting a ROLE runs on the role's
|
|
21
|
+
* NAME, because the accounts holding it store a name string rather than a
|
|
22
|
+
* reference — and that difference had produced a second, near-identical copy of
|
|
23
|
+
* the runner before this was generalized.
|
|
24
|
+
*
|
|
25
|
+
* The default parameter keeps the common case free of type noise.
|
|
26
|
+
*/
|
|
27
|
+
export interface CascadeOperation<TArgs extends unknown[] = [Types.ObjectId, ClientSession]> {
|
|
28
|
+
execute(...args: TArgs): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Runs operations in order, inside the caller's transaction.
|
|
32
|
+
*
|
|
33
|
+
* Order is the caller's to decide and it matters: a leaf that RESOLVES a
|
|
34
|
+
* reference — reading `person.accountId` before the account is deleted — has to
|
|
35
|
+
* run before whatever removes the document it reads from.
|
|
36
|
+
*
|
|
37
|
+
* ── Why there is no per-operation catch ──────────────────────────────────────
|
|
38
|
+
*
|
|
39
|
+
* Deliberately none, and this is the load-bearing decision in the file.
|
|
40
|
+
*
|
|
41
|
+
* A cascade runs inside `session.withTransaction(...)`, so a throw from any
|
|
42
|
+
* leaf must abort the whole thing. Catching and continuing would let a PARTIAL
|
|
43
|
+
* delete commit: rows orphaned against a parent that no longer exists, silently,
|
|
44
|
+
* with no error anywhere. That is strictly worse than the failure it would be
|
|
45
|
+
* hiding, because the failure is loud and the orphans are not.
|
|
46
|
+
*
|
|
47
|
+
* If a step is genuinely allowed to fail, it catches its own error and resolves.
|
|
48
|
+
* Making that the exception rather than the default keeps it a visible decision.
|
|
49
|
+
*
|
|
50
|
+
* Composable: this is itself a `CascadeOperation`, so cascades nest.
|
|
51
|
+
*/
|
|
52
|
+
export declare class SequentialCascade<TArgs extends unknown[] = [Types.ObjectId, ClientSession]> implements CascadeOperation<TArgs> {
|
|
53
|
+
private readonly operations;
|
|
54
|
+
constructor(operations: CascadeOperation<TArgs>[]);
|
|
55
|
+
execute(...args: TArgs): Promise<void>;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAErD;;;;;;;;;;;;;GAaG;AAEH;;;;;;;;;;GAUG;AACH,MAAM,WAAW,gBAAgB,CAC/B,KAAK,SAAS,OAAO,EAAE,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,aAAa,CAAC;IAEzD,OAAO,CAAC,GAAG,IAAI,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,iBAAiB,CAC5B,KAAK,SAAS,OAAO,EAAE,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,aAAa,CAAC,CACzD,YAAW,gBAAgB,CAAC,KAAK,CAAC;IAEtB,OAAO,CAAC,QAAQ,CAAC,UAAU;gBAAV,UAAU,EAAE,gBAAgB,CAAC,KAAK,CAAC,EAAE;IAE5D,OAAO,CAAC,GAAG,IAAI,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC;CAK7C"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SequentialCascade = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Runs operations in order, inside the caller's transaction.
|
|
6
|
+
*
|
|
7
|
+
* Order is the caller's to decide and it matters: a leaf that RESOLVES a
|
|
8
|
+
* reference — reading `person.accountId` before the account is deleted — has to
|
|
9
|
+
* run before whatever removes the document it reads from.
|
|
10
|
+
*
|
|
11
|
+
* ── Why there is no per-operation catch ──────────────────────────────────────
|
|
12
|
+
*
|
|
13
|
+
* Deliberately none, and this is the load-bearing decision in the file.
|
|
14
|
+
*
|
|
15
|
+
* A cascade runs inside `session.withTransaction(...)`, so a throw from any
|
|
16
|
+
* leaf must abort the whole thing. Catching and continuing would let a PARTIAL
|
|
17
|
+
* delete commit: rows orphaned against a parent that no longer exists, silently,
|
|
18
|
+
* with no error anywhere. That is strictly worse than the failure it would be
|
|
19
|
+
* hiding, because the failure is loud and the orphans are not.
|
|
20
|
+
*
|
|
21
|
+
* If a step is genuinely allowed to fail, it catches its own error and resolves.
|
|
22
|
+
* Making that the exception rather than the default keeps it a visible decision.
|
|
23
|
+
*
|
|
24
|
+
* Composable: this is itself a `CascadeOperation`, so cascades nest.
|
|
25
|
+
*/
|
|
26
|
+
class SequentialCascade {
|
|
27
|
+
constructor(operations) {
|
|
28
|
+
this.operations = operations;
|
|
29
|
+
}
|
|
30
|
+
async execute(...args) {
|
|
31
|
+
for (const op of this.operations) {
|
|
32
|
+
await op.execute(...args);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
exports.SequentialCascade = SequentialCascade;
|
|
37
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAkCA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAa,iBAAiB;IAI5B,YAA6B,UAAqC;QAArC,eAAU,GAAV,UAAU,CAA2B;IAAG,CAAC;IAEtE,KAAK,CAAC,OAAO,CAAC,GAAG,IAAW;QAC1B,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACjC,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC;CACF;AAXD,8CAWC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@guisao-llc/gambit-cascade",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Sequential cascading deletion for databases without foreign keys.",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/Guisao-LLC/Gambit.git",
|
|
9
|
+
"directory": "packages/gambit-cascade"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/Guisao-LLC/Gambit/tree/main/packages/gambit-cascade",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"mongodb",
|
|
14
|
+
"mongoose",
|
|
15
|
+
"cascade",
|
|
16
|
+
"delete",
|
|
17
|
+
"referential-integrity"
|
|
18
|
+
],
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=20 <23"
|
|
21
|
+
},
|
|
22
|
+
"main": "dist/index.js",
|
|
23
|
+
"types": "dist/index.d.ts",
|
|
24
|
+
"files": [
|
|
25
|
+
"dist"
|
|
26
|
+
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsc -b",
|
|
29
|
+
"clean": "tsc -b --clean",
|
|
30
|
+
"test": "node --test test/*.test.js",
|
|
31
|
+
"prepublishOnly": "npm run build && npm test && node ../../scripts/prepublish-check.mjs"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"mongoose": ">=8"
|
|
35
|
+
},
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
}
|
|
39
|
+
}
|