@xemahq/biome-database-nest 7.3.0 → 8.0.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.
Files changed (86) hide show
  1. package/README.md +57 -0
  2. package/dist/index.d.ts +7 -2
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +22 -2
  5. package/dist/index.js.map +1 -1
  6. package/dist/lib/biome-migrations.d.ts +2 -0
  7. package/dist/lib/biome-migrations.d.ts.map +1 -1
  8. package/dist/lib/biome-migrations.js +3 -1
  9. package/dist/lib/biome-migrations.js.map +1 -1
  10. package/dist/lib/bootstrap.d.ts +2 -0
  11. package/dist/lib/bootstrap.d.ts.map +1 -1
  12. package/dist/lib/bootstrap.js +4 -3
  13. package/dist/lib/bootstrap.js.map +1 -1
  14. package/dist/lib/ledger-rows.d.ts +1 -0
  15. package/dist/lib/ledger-rows.d.ts.map +1 -1
  16. package/dist/lib/ledger-rows.js +9 -0
  17. package/dist/lib/ledger-rows.js.map +1 -1
  18. package/dist/lib/nest/biome-database.module.d.ts +2 -0
  19. package/dist/lib/nest/biome-database.module.d.ts.map +1 -1
  20. package/dist/lib/nest/biome-database.module.js +5 -0
  21. package/dist/lib/nest/biome-database.module.js.map +1 -1
  22. package/dist/lib/nest/org-erasure.module.d.ts.map +1 -1
  23. package/dist/lib/nest/org-erasure.module.js +2 -0
  24. package/dist/lib/nest/org-erasure.module.js.map +1 -1
  25. package/dist/lib/nest/prisma-service-factory.d.ts.map +1 -1
  26. package/dist/lib/nest/prisma-service-factory.js +23 -13
  27. package/dist/lib/nest/prisma-service-factory.js.map +1 -1
  28. package/dist/lib/org-quiesce/org-erasure-isolation-error.d.ts +6 -0
  29. package/dist/lib/org-quiesce/org-erasure-isolation-error.d.ts.map +1 -0
  30. package/dist/lib/org-quiesce/org-erasure-isolation-error.js +16 -0
  31. package/dist/lib/org-quiesce/org-erasure-isolation-error.js.map +1 -0
  32. package/dist/lib/org-quiesce/org-erasure-transaction.d.ts +2 -0
  33. package/dist/lib/org-quiesce/org-erasure-transaction.d.ts.map +1 -0
  34. package/dist/lib/org-quiesce/org-erasure-transaction.js +16 -0
  35. package/dist/lib/org-quiesce/org-erasure-transaction.js.map +1 -0
  36. package/dist/lib/org-quiesce/org-quiesce-marks.d.ts +2 -2
  37. package/dist/lib/org-quiesce/org-quiesce-marks.d.ts.map +1 -1
  38. package/dist/lib/org-quiesce/org-quiesce-marks.js +13 -9
  39. package/dist/lib/org-quiesce/org-quiesce-marks.js.map +1 -1
  40. package/dist/lib/org-quiesce/org-quiesce-marks.migrations.d.ts +1 -0
  41. package/dist/lib/org-quiesce/org-quiesce-marks.migrations.d.ts.map +1 -1
  42. package/dist/lib/org-quiesce/org-quiesce-marks.migrations.js +26 -3
  43. package/dist/lib/org-quiesce/org-quiesce-marks.migrations.js.map +1 -1
  44. package/dist/lib/org-quiesce/org-quiesce-refusal-mapping.d.ts +13 -0
  45. package/dist/lib/org-quiesce/org-quiesce-refusal-mapping.d.ts.map +1 -0
  46. package/dist/lib/org-quiesce/org-quiesce-refusal-mapping.js +63 -0
  47. package/dist/lib/org-quiesce/org-quiesce-refusal-mapping.js.map +1 -0
  48. package/dist/lib/org-quiesce/org-quiesce-refusal.d.ts +26 -1
  49. package/dist/lib/org-quiesce/org-quiesce-refusal.d.ts.map +1 -1
  50. package/dist/lib/org-quiesce/org-quiesce-refusal.js +34 -3
  51. package/dist/lib/org-quiesce/org-quiesce-refusal.js.map +1 -1
  52. package/dist/lib/org-quiesce/org-write-fence-apply.d.ts +10 -0
  53. package/dist/lib/org-quiesce/org-write-fence-apply.d.ts.map +1 -0
  54. package/dist/lib/org-quiesce/org-write-fence-apply.js +55 -0
  55. package/dist/lib/org-quiesce/org-write-fence-apply.js.map +1 -0
  56. package/dist/lib/org-quiesce/org-write-fence-coverage.d.ts +18 -0
  57. package/dist/lib/org-quiesce/org-write-fence-coverage.d.ts.map +1 -0
  58. package/dist/lib/org-quiesce/org-write-fence-coverage.js +51 -0
  59. package/dist/lib/org-quiesce/org-write-fence-coverage.js.map +1 -0
  60. package/dist/lib/org-quiesce/org-write-fence-token.sql.d.ts +2 -0
  61. package/dist/lib/org-quiesce/org-write-fence-token.sql.d.ts.map +1 -0
  62. package/dist/lib/org-quiesce/org-write-fence-token.sql.js +80 -0
  63. package/dist/lib/org-quiesce/org-write-fence-token.sql.js.map +1 -0
  64. package/dist/lib/org-quiesce/org-write-fence-triggers.d.ts +48 -0
  65. package/dist/lib/org-quiesce/org-write-fence-triggers.d.ts.map +1 -0
  66. package/dist/lib/org-quiesce/org-write-fence-triggers.js +171 -0
  67. package/dist/lib/org-quiesce/org-write-fence-triggers.js.map +1 -0
  68. package/dist/lib/org-quiesce/org-write-fence.sql.d.ts +2 -0
  69. package/dist/lib/org-quiesce/org-write-fence.sql.d.ts.map +1 -0
  70. package/dist/lib/org-quiesce/org-write-fence.sql.js +132 -0
  71. package/dist/lib/org-quiesce/org-write-fence.sql.js.map +1 -0
  72. package/dist/lib/sdk-owned-migrations.d.ts.map +1 -1
  73. package/dist/lib/sdk-owned-migrations.js +5 -1
  74. package/dist/lib/sdk-owned-migrations.js.map +1 -1
  75. package/dist/lib/service-migration-scan.d.ts +2 -1
  76. package/dist/lib/service-migration-scan.d.ts.map +1 -1
  77. package/dist/lib/service-migration-scan.js +7 -2
  78. package/dist/lib/service-migration-scan.js.map +1 -1
  79. package/dist/lib/tenant-isolation/authored-resource-org-erasure.d.ts.map +1 -1
  80. package/dist/lib/tenant-isolation/authored-resource-org-erasure.js +22 -19
  81. package/dist/lib/tenant-isolation/authored-resource-org-erasure.js.map +1 -1
  82. package/dist/lib/tenant-isolation/org-erasure.d.ts.map +1 -1
  83. package/dist/lib/tenant-isolation/org-erasure.js +8 -7
  84. package/dist/lib/tenant-isolation/org-erasure.js.map +1 -1
  85. package/package.json +7 -6
  86. package/xema-peer-facts.json +3 -3
@@ -0,0 +1,18 @@
1
+ import type { OrgWriteFenceTable } from './org-write-fence-triggers';
2
+ import type { OrgErasureGraph } from '../org-erasure-coverage/org-erasure-graph';
3
+ export interface OrgWriteFenceExemption {
4
+ readonly table: string;
5
+ readonly reason: string;
6
+ }
7
+ export interface OrgWriteFenceCoverageOptions {
8
+ readonly orgField?: string;
9
+ readonly exemptModels?: readonly string[];
10
+ readonly tableOfModel: (model: string) => string;
11
+ readonly extraTables?: readonly string[];
12
+ }
13
+ export declare class OrgWriteFenceCoverageError extends Error {
14
+ readonly problems: readonly string[];
15
+ constructor(problems: readonly string[]);
16
+ }
17
+ export declare function assertOrgWriteFenceCoversErasureGraph(graph: OrgErasureGraph, orgWriteFenceTables: ReadonlyArray<OrgWriteFenceTable>, exempt: ReadonlyArray<OrgWriteFenceExemption>, options: OrgWriteFenceCoverageOptions): void;
18
+ //# sourceMappingURL=org-write-fence-coverage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"org-write-fence-coverage.d.ts","sourceRoot":"","sources":["../../../src/lib/org-quiesce/org-write-fence-coverage.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2CAA2C,CAAC;AAGjF,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,4BAA4B;IAE3C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAE3B,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAK1C,QAAQ,CAAC,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAMjD,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1C;AAGD,qBAAa,0BAA2B,SAAQ,KAAK;aAChB,QAAQ,EAAE,SAAS,MAAM,EAAE;gBAA3B,QAAQ,EAAE,SAAS,MAAM,EAAE;CAM/D;AAeD,wBAAgB,qCAAqC,CACnD,KAAK,EAAE,eAAe,EACtB,mBAAmB,EAAE,aAAa,CAAC,kBAAkB,CAAC,EACtD,MAAM,EAAE,aAAa,CAAC,sBAAsB,CAAC,EAC7C,OAAO,EAAE,4BAA4B,GACpC,IAAI,CA4CN"}
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OrgWriteFenceCoverageError = void 0;
4
+ exports.assertOrgWriteFenceCoversErasureGraph = assertOrgWriteFenceCoversErasureGraph;
5
+ const tenant_isolation_extension_1 = require("../tenant-isolation/tenant-isolation.extension");
6
+ class OrgWriteFenceCoverageError extends Error {
7
+ problems;
8
+ constructor(problems) {
9
+ super(`[org-write-fence] the org write fence does not cover the erasure graph: ${problems.join('; ')}.`);
10
+ this.problems = problems;
11
+ this.name = 'OrgWriteFenceCoverageError';
12
+ }
13
+ }
14
+ exports.OrgWriteFenceCoverageError = OrgWriteFenceCoverageError;
15
+ function assertOrgWriteFenceCoversErasureGraph(graph, orgWriteFenceTables, exempt, options) {
16
+ const orgField = options.orgField ?? tenant_isolation_extension_1.DEFAULT_ORG_FIELD;
17
+ const skipped = new Set(options.exemptModels ?? []);
18
+ const declared = new Map(orgWriteFenceTables.map((entry) => [entry.table, entry]));
19
+ const exemptTables = new Set(exempt.map((entry) => entry.table));
20
+ const swept = Object.entries(graph.models)
21
+ .filter(([name, model]) => !skipped.has(name) && model.fields.includes(orgField))
22
+ .map(([name]) => ({ model: name, table: options.tableOfModel(name) }))
23
+ .sort((a, b) => (a.table < b.table ? -1 : 1));
24
+ const sweptTables = new Set(swept.map((entry) => entry.table));
25
+ const extras = new Set(options.extraTables ?? []);
26
+ const problems = [];
27
+ for (const entry of exempt) {
28
+ if (entry.reason.trim().length === 0) {
29
+ problems.push(`exempt table "${entry.table}" has no reason`);
30
+ }
31
+ if (declared.has(entry.table)) {
32
+ problems.push(`table "${entry.table}" is both declared and exempt`);
33
+ }
34
+ }
35
+ for (const { model, table } of swept) {
36
+ if (!declared.has(table) && !exemptTables.has(table)) {
37
+ problems.push(`swept model ${model} (table "${table}", org column "${orgField}") is neither fenced nor exempt`);
38
+ }
39
+ }
40
+ for (const entry of orgWriteFenceTables) {
41
+ if (entry.parent === undefined &&
42
+ !sweptTables.has(entry.table) &&
43
+ !extras.has(entry.table)) {
44
+ problems.push(`declared table "${entry.table}" is not a swept model's table, a parent-form table or an extra`);
45
+ }
46
+ }
47
+ if (problems.length > 0) {
48
+ throw new OrgWriteFenceCoverageError(problems);
49
+ }
50
+ }
51
+ //# sourceMappingURL=org-write-fence-coverage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"org-write-fence-coverage.js","sourceRoot":"","sources":["../../../src/lib/org-quiesce/org-write-fence-coverage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare const ORG_WRITE_FENCE_TOKEN_SQL = "\nCREATE TABLE \"org_write_fence_rows\" (\n \"org_id\" TEXT NOT NULL,\n \"version\" BIGINT NOT NULL DEFAULT 0,\n CONSTRAINT \"org_write_fence_rows_pkey\" PRIMARY KEY (\"org_id\")\n);\n\nCREATE FUNCTION xema_org_is_erasing(p_org text) RETURNS boolean\nLANGUAGE plpgsql VOLATILE SET search_path FROM CURRENT AS $fn$\nDECLARE\n tag text := p_org || ':' || txid_current()::text;\nBEGIN\n -- Already verified in THIS transaction (the tag carries the txid): skip\n -- pg_locks. This cache is TRUSTED WITHOUT a pg_locks check, and a client can\n -- set it: a client running arbitrary SQL in its own transaction can forge it\n -- and write for a marked org. By design: the fence orders concurrent\n -- APPLICATION writers against an erasure; it does not defend against such a\n -- client, who could equally delete the marks or drop the triggers. The\n -- declaration below, by contrast, is always checked against pg_locks.\n IF current_setting('xema.org_erasure_verified', true) = tag THEN\n RETURN true;\n END IF;\n -- Declared by xema_org_begin_erasure for this org and this transaction: trust\n -- it only after ONE check against pg_locks that this backend really holds the\n -- exclusive lock, and remember that check for the rest of the transaction.\n IF current_setting('xema.org_erasure', true) = tag THEN\n IF xema_org_holds_erasure_lock(p_org) THEN\n PERFORM set_config('xema.org_erasure_verified', tag, true);\n RETURN true;\n END IF;\n RETURN false;\n END IF;\n -- No declaration (a biome-owned eraser that took the lock itself, a stale or\n -- foreign value): the lock in pg_locks is the authority, checked every time.\n RETURN xema_org_holds_erasure_lock(p_org);\nEND\n$fn$;\n\nCREATE FUNCTION xema_org_begin_erasure(p_org text) RETURNS void\nLANGUAGE plpgsql VOLATILE SET search_path FROM CURRENT AS $fn$\nBEGIN\n PERFORM xema_org_quiesce_mark(p_org);\n PERFORM set_config('xema.org_erasure', p_org || ':' || txid_current()::text, true);\nEND\n$fn$;\n\nCREATE OR REPLACE FUNCTION xema_org_is_quiescing(p_org text) RETURNS boolean\nLANGUAGE plpgsql VOLATILE SET search_path FROM CURRENT AS $fn$\nBEGIN\n -- The hot path: one shared advisory lock and one indexed read. The pg_locks\n -- scan (which takes every lock-manager partition lock) runs ONLY for a\n -- marked org, never per ordinary row write.\n PERFORM pg_advisory_xact_lock_shared(xema_org_quiesce_lock_key(p_org));\n -- A read-only transaction cannot write org rows, so it needs no token (and\n -- cannot INSERT one: that would raise 25006).\n IF current_setting('transaction_isolation') <> 'read committed'\n AND current_setting('transaction_read_only') = 'off' THEN\n INSERT INTO \"org_write_fence_rows\" (\"org_id\") VALUES (p_org) ON CONFLICT (\"org_id\") DO NOTHING;\n END IF;\n IF EXISTS (SELECT 1 FROM \"org_quiesce_marks\" AS m WHERE m.\"org_id\" = p_org) THEN\n RETURN NOT xema_org_is_erasing(p_org);\n END IF;\n RETURN false;\nEND\n$fn$;\n\nCREATE OR REPLACE FUNCTION xema_org_quiesce_mark(p_org text) RETURNS void\nLANGUAGE plpgsql VOLATILE SET search_path FROM CURRENT AS $fn$\nBEGIN\n PERFORM pg_advisory_xact_lock(xema_org_quiesce_lock_key(p_org));\n INSERT INTO \"org_write_fence_rows\" (\"org_id\", \"version\") VALUES (p_org, 1)\n ON CONFLICT (\"org_id\") DO UPDATE SET \"version\" = \"org_write_fence_rows\".\"version\" + 1;\n INSERT INTO \"org_quiesce_marks\" (\"org_id\") VALUES (p_org) ON CONFLICT (\"org_id\") DO NOTHING;\nEND\n$fn$;\n";
2
+ //# sourceMappingURL=org-write-fence-token.sql.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"org-write-fence-token.sql.d.ts","sourceRoot":"","sources":["../../../src/lib/org-quiesce/org-write-fence-token.sql.ts"],"names":[],"mappings":"AAmEA,eAAO,MAAM,yBAAyB,46GA2ErC,CAAC"}
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ORG_WRITE_FENCE_TOKEN_SQL = void 0;
4
+ exports.ORG_WRITE_FENCE_TOKEN_SQL = `
5
+ CREATE TABLE "org_write_fence_rows" (
6
+ "org_id" TEXT NOT NULL,
7
+ "version" BIGINT NOT NULL DEFAULT 0,
8
+ CONSTRAINT "org_write_fence_rows_pkey" PRIMARY KEY ("org_id")
9
+ );
10
+
11
+ CREATE FUNCTION xema_org_is_erasing(p_org text) RETURNS boolean
12
+ LANGUAGE plpgsql VOLATILE SET search_path FROM CURRENT AS $fn$
13
+ DECLARE
14
+ tag text := p_org || ':' || txid_current()::text;
15
+ BEGIN
16
+ -- Already verified in THIS transaction (the tag carries the txid): skip
17
+ -- pg_locks. This cache is TRUSTED WITHOUT a pg_locks check, and a client can
18
+ -- set it: a client running arbitrary SQL in its own transaction can forge it
19
+ -- and write for a marked org. By design: the fence orders concurrent
20
+ -- APPLICATION writers against an erasure; it does not defend against such a
21
+ -- client, who could equally delete the marks or drop the triggers. The
22
+ -- declaration below, by contrast, is always checked against pg_locks.
23
+ IF current_setting('xema.org_erasure_verified', true) = tag THEN
24
+ RETURN true;
25
+ END IF;
26
+ -- Declared by xema_org_begin_erasure for this org and this transaction: trust
27
+ -- it only after ONE check against pg_locks that this backend really holds the
28
+ -- exclusive lock, and remember that check for the rest of the transaction.
29
+ IF current_setting('xema.org_erasure', true) = tag THEN
30
+ IF xema_org_holds_erasure_lock(p_org) THEN
31
+ PERFORM set_config('xema.org_erasure_verified', tag, true);
32
+ RETURN true;
33
+ END IF;
34
+ RETURN false;
35
+ END IF;
36
+ -- No declaration (a biome-owned eraser that took the lock itself, a stale or
37
+ -- foreign value): the lock in pg_locks is the authority, checked every time.
38
+ RETURN xema_org_holds_erasure_lock(p_org);
39
+ END
40
+ $fn$;
41
+
42
+ CREATE FUNCTION xema_org_begin_erasure(p_org text) RETURNS void
43
+ LANGUAGE plpgsql VOLATILE SET search_path FROM CURRENT AS $fn$
44
+ BEGIN
45
+ PERFORM xema_org_quiesce_mark(p_org);
46
+ PERFORM set_config('xema.org_erasure', p_org || ':' || txid_current()::text, true);
47
+ END
48
+ $fn$;
49
+
50
+ CREATE OR REPLACE FUNCTION xema_org_is_quiescing(p_org text) RETURNS boolean
51
+ LANGUAGE plpgsql VOLATILE SET search_path FROM CURRENT AS $fn$
52
+ BEGIN
53
+ -- The hot path: one shared advisory lock and one indexed read. The pg_locks
54
+ -- scan (which takes every lock-manager partition lock) runs ONLY for a
55
+ -- marked org, never per ordinary row write.
56
+ PERFORM pg_advisory_xact_lock_shared(xema_org_quiesce_lock_key(p_org));
57
+ -- A read-only transaction cannot write org rows, so it needs no token (and
58
+ -- cannot INSERT one: that would raise 25006).
59
+ IF current_setting('transaction_isolation') <> 'read committed'
60
+ AND current_setting('transaction_read_only') = 'off' THEN
61
+ INSERT INTO "org_write_fence_rows" ("org_id") VALUES (p_org) ON CONFLICT ("org_id") DO NOTHING;
62
+ END IF;
63
+ IF EXISTS (SELECT 1 FROM "org_quiesce_marks" AS m WHERE m."org_id" = p_org) THEN
64
+ RETURN NOT xema_org_is_erasing(p_org);
65
+ END IF;
66
+ RETURN false;
67
+ END
68
+ $fn$;
69
+
70
+ CREATE OR REPLACE FUNCTION xema_org_quiesce_mark(p_org text) RETURNS void
71
+ LANGUAGE plpgsql VOLATILE SET search_path FROM CURRENT AS $fn$
72
+ BEGIN
73
+ PERFORM pg_advisory_xact_lock(xema_org_quiesce_lock_key(p_org));
74
+ INSERT INTO "org_write_fence_rows" ("org_id", "version") VALUES (p_org, 1)
75
+ ON CONFLICT ("org_id") DO UPDATE SET "version" = "org_write_fence_rows"."version" + 1;
76
+ INSERT INTO "org_quiesce_marks" ("org_id") VALUES (p_org) ON CONFLICT ("org_id") DO NOTHING;
77
+ END
78
+ $fn$;
79
+ `;
80
+ //# sourceMappingURL=org-write-fence-token.sql.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"org-write-fence-token.sql.js","sourceRoot":"","sources":["../../../src/lib/org-quiesce/org-write-fence-token.sql.ts"],"names":[],"mappings":";;;AAmEa,QAAA,yBAAyB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2ExC,CAAC"}
@@ -0,0 +1,48 @@
1
+ import type { AdvisoryLockClient } from '../advisory-lock';
2
+ export declare const ORG_WRITE_FENCE_TRIGGER_NAME = "xema_org_write_fence";
3
+ export declare const ORG_WRITE_FENCE_MOVE_TRIGGER_NAME = "xema_org_write_fence_move";
4
+ export declare const ORG_WRITE_FENCE_TRUNCATE_TRIGGER_NAME = "xema_org_write_fence_truncate";
5
+ export type OrgWriteFenceEvents = 'insert' | 'all';
6
+ export interface OrgWriteFenceColumnTable {
7
+ readonly events?: OrgWriteFenceEvents;
8
+ readonly table: string;
9
+ readonly orgColumn: string;
10
+ readonly parent?: undefined;
11
+ }
12
+ export interface OrgWriteFenceParentTable {
13
+ readonly events?: OrgWriteFenceEvents;
14
+ readonly table: string;
15
+ readonly orgColumn?: undefined;
16
+ readonly parent: {
17
+ readonly fkColumn: string;
18
+ readonly table: string;
19
+ readonly key: string;
20
+ readonly orgColumn: string;
21
+ };
22
+ }
23
+ export type OrgWriteFenceTable = OrgWriteFenceColumnTable | OrgWriteFenceParentTable;
24
+ export declare class OrgWriteFenceIdentifierError extends Error {
25
+ constructor(what: string, value: string);
26
+ }
27
+ export declare class OrgWriteFenceDuplicateTableError extends Error {
28
+ constructor(table: string);
29
+ }
30
+ export declare class OrgWriteFenceTriggersMissingError extends Error {
31
+ readonly problems: readonly string[];
32
+ constructor(problems: readonly string[]);
33
+ }
34
+ export declare const ORG_WRITE_FENCE_TRIGGER_NAMES: readonly string[];
35
+ export declare function orgWriteFenceTriggerSql(tables: ReadonlyArray<OrgWriteFenceTable>): string;
36
+ export interface OrgWriteFenceTriggerRow {
37
+ readonly table_name: string;
38
+ readonly name: string;
39
+ readonly type: number;
40
+ readonly enabled: boolean;
41
+ readonly args: readonly string[];
42
+ readonly has_when: boolean;
43
+ }
44
+ export declare const ORG_WRITE_FENCE_TRIGGER_ROWS_SQL = "\n SELECT c.relname AS table_name,\n t.tgname AS name,\n t.tgtype::int AS type,\n (t.tgenabled = 'O') AS enabled,\n (t.tgqual IS NOT NULL) AS has_when,\n CASE WHEN t.tgnargs = 0 THEN ARRAY[]::text[]\n ELSE regexp_split_to_array(regexp_replace(encode(t.tgargs, 'escape'), '\\\\000$', ''), '\\\\000') END AS args\n FROM pg_trigger AS t\n JOIN pg_class AS c ON c.oid = t.tgrelid\n JOIN pg_namespace AS n ON n.oid = c.relnamespace\n JOIN pg_proc AS p ON p.oid = t.tgfoid\n WHERE n.nspname = current_schema()\n AND NOT t.tgisinternal\n AND ((t.tgname IN ('xema_org_write_fence', 'xema_org_write_fence_move') AND p.proname = 'xema_org_write_fence_trigger')\n OR (t.tgname = 'xema_org_write_fence_truncate' AND p.proname = 'xema_org_write_fence_truncate'))";
45
+ export declare function orgWriteFenceStaleRows(rows: readonly OrgWriteFenceTriggerRow[], tables: ReadonlyArray<OrgWriteFenceTable>): OrgWriteFenceTriggerRow[];
46
+ export declare function orgWriteFenceProblems(rows: readonly OrgWriteFenceTriggerRow[], tables: ReadonlyArray<OrgWriteFenceTable>): string[];
47
+ export declare function assertOrgWriteFenceTriggers(db: AdvisoryLockClient, tables: ReadonlyArray<OrgWriteFenceTable>): Promise<void>;
48
+ //# sourceMappingURL=org-write-fence-triggers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"org-write-fence-triggers.d.ts","sourceRoot":"","sources":["../../../src/lib/org-quiesce/org-write-fence-triggers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAG3D,eAAO,MAAM,4BAA4B,yBAAyB,CAAC;AAEnE,eAAO,MAAM,iCAAiC,8BAA8B,CAAC;AAE7E,eAAO,MAAM,qCAAqC,kCAAkC,CAAC;AA0BrF,MAAM,MAAM,mBAAmB,GAAG,QAAQ,GAAG,KAAK,CAAC;AAGnD,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,MAAM,CAAC,EAAE,mBAAmB,CAAC;IACtC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC;CAC7B;AAMD,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,MAAM,CAAC,EAAE,mBAAmB,CAAC;IACtC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE;QACf,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;KAC5B,CAAC;CACH;AAGD,MAAM,MAAM,kBAAkB,GAC1B,wBAAwB,GACxB,wBAAwB,CAAC;AAG7B,qBAAa,4BAA6B,SAAQ,KAAK;gBAClC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;CAM/C;AAGD,qBAAa,gCAAiC,SAAQ,KAAK;gBACtC,KAAK,EAAE,MAAM;CAIjC;AAGD,qBAAa,iCAAkC,SAAQ,KAAK;aACvB,QAAQ,EAAE,SAAS,MAAM,EAAE;gBAA3B,QAAQ,EAAE,SAAS,MAAM,EAAE;CAM/D;AA8ED,eAAO,MAAM,6BAA6B,EAAE,SAAS,MAAM,EAI1D,CAAC;AAYF,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,aAAa,CAAC,kBAAkB,CAAC,GACxC,MAAM,CAYR;AAGD,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAGD,eAAO,MAAM,gCAAgC,s1BAe8D,CAAC;AAG5G,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,SAAS,uBAAuB,EAAE,EACxC,MAAM,EAAE,aAAa,CAAC,kBAAkB,CAAC,GACxC,uBAAuB,EAAE,CAO3B;AAQD,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,SAAS,uBAAuB,EAAE,EACxC,MAAM,EAAE,aAAa,CAAC,kBAAkB,CAAC,GACxC,MAAM,EAAE,CAyBV;AAeD,wBAAsB,2BAA2B,CAC/C,EAAE,EAAE,kBAAkB,EACtB,MAAM,EAAE,aAAa,CAAC,kBAAkB,CAAC,GACxC,OAAO,CAAC,IAAI,CAAC,CAUf"}
@@ -0,0 +1,171 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ORG_WRITE_FENCE_TRIGGER_ROWS_SQL = exports.ORG_WRITE_FENCE_TRIGGER_NAMES = exports.OrgWriteFenceTriggersMissingError = exports.OrgWriteFenceDuplicateTableError = exports.OrgWriteFenceIdentifierError = exports.ORG_WRITE_FENCE_TRUNCATE_TRIGGER_NAME = exports.ORG_WRITE_FENCE_MOVE_TRIGGER_NAME = exports.ORG_WRITE_FENCE_TRIGGER_NAME = void 0;
4
+ exports.orgWriteFenceTriggerSql = orgWriteFenceTriggerSql;
5
+ exports.orgWriteFenceStaleRows = orgWriteFenceStaleRows;
6
+ exports.orgWriteFenceProblems = orgWriteFenceProblems;
7
+ exports.assertOrgWriteFenceTriggers = assertOrgWriteFenceTriggers;
8
+ exports.ORG_WRITE_FENCE_TRIGGER_NAME = 'xema_org_write_fence';
9
+ exports.ORG_WRITE_FENCE_MOVE_TRIGGER_NAME = 'xema_org_write_fence_move';
10
+ exports.ORG_WRITE_FENCE_TRUNCATE_TRIGGER_NAME = 'xema_org_write_fence_truncate';
11
+ const TRIGGER_NAME = exports.ORG_WRITE_FENCE_TRIGGER_NAME;
12
+ const TRIGGER_FUNCTION = 'xema_org_write_fence_trigger';
13
+ const TRUNCATE_FUNCTION = 'xema_org_write_fence_truncate';
14
+ const TRIGGER_TYPE_BEFORE_ROW_INSERT = 7;
15
+ const TRIGGER_TYPE_BEFORE_ROW_INSERT_DELETE_UPDATE = 31;
16
+ const TRIGGER_TYPE_BEFORE_ROW_UPDATE = 19;
17
+ const TRIGGER_TYPE_BEFORE_TRUNCATE = 34;
18
+ const IDENTIFIER_PATTERN = /^[a-z_][a-z0-9_]*$/u;
19
+ class OrgWriteFenceIdentifierError extends Error {
20
+ constructor(what, value) {
21
+ super(`[org-write-fence] ${what} "${value}" must match ${IDENTIFIER_PATTERN.source}.`);
22
+ this.name = 'OrgWriteFenceIdentifierError';
23
+ }
24
+ }
25
+ exports.OrgWriteFenceIdentifierError = OrgWriteFenceIdentifierError;
26
+ class OrgWriteFenceDuplicateTableError extends Error {
27
+ constructor(table) {
28
+ super(`[org-write-fence] table "${table}" is declared more than once.`);
29
+ this.name = 'OrgWriteFenceDuplicateTableError';
30
+ }
31
+ }
32
+ exports.OrgWriteFenceDuplicateTableError = OrgWriteFenceDuplicateTableError;
33
+ class OrgWriteFenceTriggersMissingError extends Error {
34
+ problems;
35
+ constructor(problems) {
36
+ super(`[org-write-fence] ${problems.length} table(s) are not guarded by the ${TRIGGER_NAME} trigger as declared: ${problems.join('; ')}.`);
37
+ this.problems = problems;
38
+ this.name = 'OrgWriteFenceTriggersMissingError';
39
+ }
40
+ }
41
+ exports.OrgWriteFenceTriggersMissingError = OrgWriteFenceTriggersMissingError;
42
+ function identifier(what, value) {
43
+ if (!IDENTIFIER_PATTERN.test(value)) {
44
+ throw new OrgWriteFenceIdentifierError(what, value);
45
+ }
46
+ return value;
47
+ }
48
+ function triggerArgs(entry) {
49
+ if (entry.parent === undefined) {
50
+ return [identifier('orgColumn', entry.orgColumn)];
51
+ }
52
+ const { fkColumn, table, key, orgColumn } = entry.parent;
53
+ return [
54
+ identifier('parent.fkColumn', fkColumn),
55
+ identifier('parent.table', table),
56
+ identifier('parent.key', key),
57
+ identifier('parent.orgColumn', orgColumn),
58
+ ];
59
+ }
60
+ function expectedTriggers(entry) {
61
+ const table = identifier('table', entry.table);
62
+ const args = triggerArgs(entry);
63
+ const argList = args.map((arg) => `'${arg}'`).join(', ');
64
+ const truncate = {
65
+ name: exports.ORG_WRITE_FENCE_TRUNCATE_TRIGGER_NAME,
66
+ type: TRIGGER_TYPE_BEFORE_TRUNCATE,
67
+ args: [],
68
+ hasWhen: false,
69
+ sql: `CREATE TRIGGER ${exports.ORG_WRITE_FENCE_TRUNCATE_TRIGGER_NAME} BEFORE TRUNCATE ON "${table}" FOR EACH STATEMENT EXECUTE FUNCTION ${TRUNCATE_FUNCTION}();`,
70
+ };
71
+ if (entry.events !== 'insert') {
72
+ return [
73
+ {
74
+ name: TRIGGER_NAME,
75
+ type: TRIGGER_TYPE_BEFORE_ROW_INSERT_DELETE_UPDATE,
76
+ args,
77
+ hasWhen: false,
78
+ sql: `CREATE TRIGGER ${TRIGGER_NAME} BEFORE INSERT OR UPDATE OR DELETE ON "${table}" FOR EACH ROW EXECUTE FUNCTION ${TRIGGER_FUNCTION}(${argList});`,
79
+ },
80
+ truncate,
81
+ ];
82
+ }
83
+ const moving = args[0];
84
+ return [
85
+ {
86
+ name: TRIGGER_NAME,
87
+ type: TRIGGER_TYPE_BEFORE_ROW_INSERT,
88
+ args,
89
+ hasWhen: false,
90
+ sql: `CREATE TRIGGER ${TRIGGER_NAME} BEFORE INSERT ON "${table}" FOR EACH ROW EXECUTE FUNCTION ${TRIGGER_FUNCTION}(${argList});`,
91
+ },
92
+ {
93
+ name: exports.ORG_WRITE_FENCE_MOVE_TRIGGER_NAME,
94
+ type: TRIGGER_TYPE_BEFORE_ROW_UPDATE,
95
+ args,
96
+ hasWhen: true,
97
+ sql: `CREATE TRIGGER ${exports.ORG_WRITE_FENCE_MOVE_TRIGGER_NAME} BEFORE UPDATE ON "${table}" FOR EACH ROW WHEN (OLD."${moving}" IS DISTINCT FROM NEW."${moving}") EXECUTE FUNCTION ${TRIGGER_FUNCTION}(${argList});`,
98
+ },
99
+ truncate,
100
+ ];
101
+ }
102
+ exports.ORG_WRITE_FENCE_TRIGGER_NAMES = [
103
+ TRIGGER_NAME,
104
+ exports.ORG_WRITE_FENCE_MOVE_TRIGGER_NAME,
105
+ exports.ORG_WRITE_FENCE_TRUNCATE_TRIGGER_NAME,
106
+ ];
107
+ function orgWriteFenceTriggerSql(tables) {
108
+ const seen = new Set();
109
+ return tables
110
+ .flatMap((entry) => {
111
+ const table = identifier('table', entry.table);
112
+ if (seen.has(table)) {
113
+ throw new OrgWriteFenceDuplicateTableError(table);
114
+ }
115
+ seen.add(table);
116
+ return expectedTriggers(entry).map((trigger) => trigger.sql);
117
+ })
118
+ .join('\n');
119
+ }
120
+ exports.ORG_WRITE_FENCE_TRIGGER_ROWS_SQL = `
121
+ SELECT c.relname AS table_name,
122
+ t.tgname AS name,
123
+ t.tgtype::int AS type,
124
+ (t.tgenabled = 'O') AS enabled,
125
+ (t.tgqual IS NOT NULL) AS has_when,
126
+ CASE WHEN t.tgnargs = 0 THEN ARRAY[]::text[]
127
+ ELSE regexp_split_to_array(regexp_replace(encode(t.tgargs, 'escape'), '\\\\000$', ''), '\\\\000') END AS args
128
+ FROM pg_trigger AS t
129
+ JOIN pg_class AS c ON c.oid = t.tgrelid
130
+ JOIN pg_namespace AS n ON n.oid = c.relnamespace
131
+ JOIN pg_proc AS p ON p.oid = t.tgfoid
132
+ WHERE n.nspname = current_schema()
133
+ AND NOT t.tgisinternal
134
+ AND ((t.tgname IN ('${TRIGGER_NAME}', '${exports.ORG_WRITE_FENCE_MOVE_TRIGGER_NAME}') AND p.proname = '${TRIGGER_FUNCTION}')
135
+ OR (t.tgname = '${exports.ORG_WRITE_FENCE_TRUNCATE_TRIGGER_NAME}' AND p.proname = '${TRUNCATE_FUNCTION}'))`;
136
+ function orgWriteFenceStaleRows(rows, tables) {
137
+ const expected = new Set(tables.flatMap((entry) => expectedTriggers(entry).map((trigger) => `${entry.table}\u0000${trigger.name}`)));
138
+ return rows.filter((row) => !expected.has(`${row.table_name}\u0000${row.name}`));
139
+ }
140
+ function orgWriteFenceProblems(rows, tables) {
141
+ const problems = [];
142
+ for (const entry of tables) {
143
+ for (const trigger of expectedTriggers(entry)) {
144
+ const found = rows.find((row) => row.table_name === entry.table && row.name === trigger.name);
145
+ if (found === undefined || !found.enabled) {
146
+ problems.push(`"${entry.table}" has no enabled ${trigger.name} trigger`);
147
+ }
148
+ else if (found.type !== trigger.type) {
149
+ problems.push(`"${entry.table}" ${trigger.name} has tgtype ${found.type}, expected ${trigger.type} (declared events '${entry.events ?? 'all'}')`);
150
+ }
151
+ else if (found.has_when !== trigger.hasWhen) {
152
+ problems.push(`"${entry.table}" ${trigger.name} ${trigger.hasWhen ? 'has no WHEN clause' : 'has an unexpected WHEN clause'}`);
153
+ }
154
+ else if (found.args.join('\u0000') !== trigger.args.join('\u0000')) {
155
+ problems.push(`"${entry.table}" ${trigger.name} is guarded with (${found.args.join(', ')}) but is declared (${trigger.args.join(', ')})`);
156
+ }
157
+ }
158
+ }
159
+ return problems;
160
+ }
161
+ async function assertOrgWriteFenceTriggers(db, tables) {
162
+ const query = Object.assign([exports.ORG_WRITE_FENCE_TRIGGER_ROWS_SQL], {
163
+ raw: [exports.ORG_WRITE_FENCE_TRIGGER_ROWS_SQL],
164
+ });
165
+ const rows = await db.$queryRaw(query);
166
+ const problems = orgWriteFenceProblems(rows, tables);
167
+ if (problems.length > 0) {
168
+ throw new OrgWriteFenceTriggersMissingError(problems);
169
+ }
170
+ }
171
+ //# sourceMappingURL=org-write-fence-triggers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"org-write-fence-triggers.js","sourceRoot":"","sources":["../../../src/lib/org-quiesce/org-write-fence-triggers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare const ORG_WRITE_FENCE_SQL = "\nCREATE FUNCTION xema_org_quiesce_lock_key(p_org text) RETURNS bigint\nLANGUAGE sql STABLE SET search_path FROM CURRENT AS $fn$\n SELECT hashtext(current_schema() || ':' || 'org-quiesce:' || p_org)::bigint\n$fn$;\n\nCREATE FUNCTION xema_org_holds_erasure_lock(p_org text) RETURNS boolean\nLANGUAGE sql VOLATILE SET search_path FROM CURRENT AS $fn$\n SELECT EXISTS (\n SELECT 1\n FROM pg_locks AS l, xema_org_quiesce_lock_key(p_org) AS k(key)\n WHERE l.locktype = 'advisory'\n AND l.database = (SELECT d.oid FROM pg_database AS d WHERE d.datname = current_database())\n AND l.pid = pg_backend_pid()\n AND l.mode = 'ExclusiveLock'\n AND l.granted\n AND l.objsubid = 1\n AND l.classid::bigint = (k.key >> 32) & 4294967295\n AND l.objid::bigint = k.key & 4294967295\n )\n$fn$;\n\nCREATE FUNCTION xema_org_is_quiescing(p_org text) RETURNS boolean\nLANGUAGE plpgsql VOLATILE SET search_path FROM CURRENT AS $fn$\nBEGIN\n -- The hot path: one shared advisory lock and one indexed read. The pg_locks\n -- scan (which takes every lock-manager partition lock) runs ONLY for a\n -- marked org, never per ordinary row write.\n PERFORM pg_advisory_xact_lock_shared(xema_org_quiesce_lock_key(p_org));\n IF EXISTS (SELECT 1 FROM \"org_quiesce_marks\" AS m WHERE m.\"org_id\" = p_org) THEN\n RETURN NOT xema_org_holds_erasure_lock(p_org);\n END IF;\n RETURN false;\nEND\n$fn$;\n\nCREATE FUNCTION xema_org_write_fence(p_org text) RETURNS void\nLANGUAGE plpgsql VOLATILE SET search_path FROM CURRENT AS $fn$\nBEGIN\n IF xema_org_is_quiescing(p_org) THEN\n RAISE EXCEPTION USING ERRCODE = 'XQ409', MESSAGE = 'ORG_QUIESCING', DETAIL = p_org;\n END IF;\nEND\n$fn$;\n\nCREATE FUNCTION xema_org_write_fence_org_of_row(p_row jsonb, p_is_new boolean, p_nargs integer, p_a0 text, p_a1 text, p_a2 text, p_a3 text) RETURNS text\nLANGUAGE plpgsql VOLATILE SET search_path FROM CURRENT AS $fn$\nDECLARE\n fk_value text;\n key_type text;\n parent_org text;\n found boolean;\nBEGIN\n IF p_nargs = 1 THEN\n RETURN p_row ->> p_a0;\n END IF;\n IF p_nargs <> 4 THEN\n RAISE EXCEPTION 'xema_org_write_fence_trigger takes (org_column) or (fk_column, parent_table, parent_key, parent_org_column), got % arguments', p_nargs;\n END IF;\n fk_value := p_row ->> p_a0;\n IF fk_value IS NULL THEN\n RETURN NULL;\n END IF;\n -- The parameter is cast to the parent key's own type, never the column to\n -- text, so the lookup uses the key's index (a uuid or int key included).\n SELECT format_type(a.atttypid, a.atttypmod) INTO key_type\n FROM pg_attribute AS a\n WHERE a.attrelid = to_regclass(quote_ident(p_a1)) AND a.attname = p_a2 AND NOT a.attisdropped;\n IF key_type IS NULL THEN\n RAISE EXCEPTION 'xema_org_write_fence_trigger: parent key %.% does not exist', p_a1, p_a2;\n END IF;\n EXECUTE format('SELECT true, %I::text FROM %I WHERE %I = $1::%s', p_a3, p_a1, p_a2, key_type)\n INTO found, parent_org USING fk_value;\n IF found IS NOT TRUE THEN\n -- A row that is written (NEW) must have its parent: without it the org is\n -- unknown, and an unknown org must never pass the fence.\n IF p_is_new THEN\n RAISE EXCEPTION USING ERRCODE = 'XQ404', MESSAGE = 'ORG_FENCE_PARENT_MISSING', DETAIL = fk_value;\n END IF;\n -- A row that is removed or replaced (OLD) with no parent passes: an ON\n -- DELETE CASCADE from the parent runs after the parent row is gone, and\n -- the parent's own delete was already fenced by its own trigger.\n RETURN NULL;\n END IF;\n RETURN parent_org;\nEND\n$fn$;\n\nCREATE FUNCTION xema_org_write_fence_trigger() RETURNS trigger\nLANGUAGE plpgsql VOLATILE SET search_path FROM CURRENT AS $fn$\nDECLARE\n new_org text;\n old_org text;\nBEGIN\n IF TG_OP IN ('INSERT', 'UPDATE') THEN\n new_org := xema_org_write_fence_org_of_row(to_jsonb(NEW), true, TG_NARGS, TG_ARGV[0], TG_ARGV[1], TG_ARGV[2], TG_ARGV[3]);\n IF new_org IS NOT NULL THEN\n PERFORM xema_org_write_fence(new_org);\n END IF;\n END IF;\n IF TG_OP IN ('UPDATE', 'DELETE') THEN\n old_org := xema_org_write_fence_org_of_row(to_jsonb(OLD), false, TG_NARGS, TG_ARGV[0], TG_ARGV[1], TG_ARGV[2], TG_ARGV[3]);\n IF old_org IS NOT NULL AND old_org IS DISTINCT FROM new_org THEN\n PERFORM xema_org_write_fence(old_org);\n END IF;\n END IF;\n IF TG_OP = 'DELETE' THEN\n RETURN OLD;\n END IF;\n RETURN NEW;\nEND\n$fn$;\n\nCREATE FUNCTION xema_org_write_fence_truncate() RETURNS trigger\nLANGUAGE plpgsql VOLATILE SET search_path FROM CURRENT AS $fn$\nBEGIN\n RAISE EXCEPTION USING ERRCODE = 'XQ410', MESSAGE = 'ORG_FENCE_TRUNCATE_REFUSED', DETAIL = TG_TABLE_NAME;\nEND\n$fn$;\n\nCREATE FUNCTION xema_org_quiesce_mark(p_org text) RETURNS void\nLANGUAGE plpgsql VOLATILE SET search_path FROM CURRENT AS $fn$\nBEGIN\n PERFORM pg_advisory_xact_lock(xema_org_quiesce_lock_key(p_org));\n INSERT INTO \"org_quiesce_marks\" (\"org_id\") VALUES (p_org) ON CONFLICT (\"org_id\") DO NOTHING;\nEND\n$fn$;\n";
2
+ //# sourceMappingURL=org-write-fence.sql.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"org-write-fence.sql.d.ts","sourceRoot":"","sources":["../../../src/lib/org-quiesce/org-write-fence.sql.ts"],"names":[],"mappings":"AAyCA,eAAO,MAAM,mBAAmB,64JA+H/B,CAAC"}
@@ -0,0 +1,132 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ORG_WRITE_FENCE_SQL = void 0;
4
+ exports.ORG_WRITE_FENCE_SQL = `
5
+ CREATE FUNCTION xema_org_quiesce_lock_key(p_org text) RETURNS bigint
6
+ LANGUAGE sql STABLE SET search_path FROM CURRENT AS $fn$
7
+ SELECT hashtext(current_schema() || ':' || 'org-quiesce:' || p_org)::bigint
8
+ $fn$;
9
+
10
+ CREATE FUNCTION xema_org_holds_erasure_lock(p_org text) RETURNS boolean
11
+ LANGUAGE sql VOLATILE SET search_path FROM CURRENT AS $fn$
12
+ SELECT EXISTS (
13
+ SELECT 1
14
+ FROM pg_locks AS l, xema_org_quiesce_lock_key(p_org) AS k(key)
15
+ WHERE l.locktype = 'advisory'
16
+ AND l.database = (SELECT d.oid FROM pg_database AS d WHERE d.datname = current_database())
17
+ AND l.pid = pg_backend_pid()
18
+ AND l.mode = 'ExclusiveLock'
19
+ AND l.granted
20
+ AND l.objsubid = 1
21
+ AND l.classid::bigint = (k.key >> 32) & 4294967295
22
+ AND l.objid::bigint = k.key & 4294967295
23
+ )
24
+ $fn$;
25
+
26
+ CREATE FUNCTION xema_org_is_quiescing(p_org text) RETURNS boolean
27
+ LANGUAGE plpgsql VOLATILE SET search_path FROM CURRENT AS $fn$
28
+ BEGIN
29
+ -- The hot path: one shared advisory lock and one indexed read. The pg_locks
30
+ -- scan (which takes every lock-manager partition lock) runs ONLY for a
31
+ -- marked org, never per ordinary row write.
32
+ PERFORM pg_advisory_xact_lock_shared(xema_org_quiesce_lock_key(p_org));
33
+ IF EXISTS (SELECT 1 FROM "org_quiesce_marks" AS m WHERE m."org_id" = p_org) THEN
34
+ RETURN NOT xema_org_holds_erasure_lock(p_org);
35
+ END IF;
36
+ RETURN false;
37
+ END
38
+ $fn$;
39
+
40
+ CREATE FUNCTION xema_org_write_fence(p_org text) RETURNS void
41
+ LANGUAGE plpgsql VOLATILE SET search_path FROM CURRENT AS $fn$
42
+ BEGIN
43
+ IF xema_org_is_quiescing(p_org) THEN
44
+ RAISE EXCEPTION USING ERRCODE = 'XQ409', MESSAGE = 'ORG_QUIESCING', DETAIL = p_org;
45
+ END IF;
46
+ END
47
+ $fn$;
48
+
49
+ CREATE FUNCTION xema_org_write_fence_org_of_row(p_row jsonb, p_is_new boolean, p_nargs integer, p_a0 text, p_a1 text, p_a2 text, p_a3 text) RETURNS text
50
+ LANGUAGE plpgsql VOLATILE SET search_path FROM CURRENT AS $fn$
51
+ DECLARE
52
+ fk_value text;
53
+ key_type text;
54
+ parent_org text;
55
+ found boolean;
56
+ BEGIN
57
+ IF p_nargs = 1 THEN
58
+ RETURN p_row ->> p_a0;
59
+ END IF;
60
+ IF p_nargs <> 4 THEN
61
+ RAISE EXCEPTION 'xema_org_write_fence_trigger takes (org_column) or (fk_column, parent_table, parent_key, parent_org_column), got % arguments', p_nargs;
62
+ END IF;
63
+ fk_value := p_row ->> p_a0;
64
+ IF fk_value IS NULL THEN
65
+ RETURN NULL;
66
+ END IF;
67
+ -- The parameter is cast to the parent key's own type, never the column to
68
+ -- text, so the lookup uses the key's index (a uuid or int key included).
69
+ SELECT format_type(a.atttypid, a.atttypmod) INTO key_type
70
+ FROM pg_attribute AS a
71
+ WHERE a.attrelid = to_regclass(quote_ident(p_a1)) AND a.attname = p_a2 AND NOT a.attisdropped;
72
+ IF key_type IS NULL THEN
73
+ RAISE EXCEPTION 'xema_org_write_fence_trigger: parent key %.% does not exist', p_a1, p_a2;
74
+ END IF;
75
+ EXECUTE format('SELECT true, %I::text FROM %I WHERE %I = $1::%s', p_a3, p_a1, p_a2, key_type)
76
+ INTO found, parent_org USING fk_value;
77
+ IF found IS NOT TRUE THEN
78
+ -- A row that is written (NEW) must have its parent: without it the org is
79
+ -- unknown, and an unknown org must never pass the fence.
80
+ IF p_is_new THEN
81
+ RAISE EXCEPTION USING ERRCODE = 'XQ404', MESSAGE = 'ORG_FENCE_PARENT_MISSING', DETAIL = fk_value;
82
+ END IF;
83
+ -- A row that is removed or replaced (OLD) with no parent passes: an ON
84
+ -- DELETE CASCADE from the parent runs after the parent row is gone, and
85
+ -- the parent's own delete was already fenced by its own trigger.
86
+ RETURN NULL;
87
+ END IF;
88
+ RETURN parent_org;
89
+ END
90
+ $fn$;
91
+
92
+ CREATE FUNCTION xema_org_write_fence_trigger() RETURNS trigger
93
+ LANGUAGE plpgsql VOLATILE SET search_path FROM CURRENT AS $fn$
94
+ DECLARE
95
+ new_org text;
96
+ old_org text;
97
+ BEGIN
98
+ IF TG_OP IN ('INSERT', 'UPDATE') THEN
99
+ new_org := xema_org_write_fence_org_of_row(to_jsonb(NEW), true, TG_NARGS, TG_ARGV[0], TG_ARGV[1], TG_ARGV[2], TG_ARGV[3]);
100
+ IF new_org IS NOT NULL THEN
101
+ PERFORM xema_org_write_fence(new_org);
102
+ END IF;
103
+ END IF;
104
+ IF TG_OP IN ('UPDATE', 'DELETE') THEN
105
+ old_org := xema_org_write_fence_org_of_row(to_jsonb(OLD), false, TG_NARGS, TG_ARGV[0], TG_ARGV[1], TG_ARGV[2], TG_ARGV[3]);
106
+ IF old_org IS NOT NULL AND old_org IS DISTINCT FROM new_org THEN
107
+ PERFORM xema_org_write_fence(old_org);
108
+ END IF;
109
+ END IF;
110
+ IF TG_OP = 'DELETE' THEN
111
+ RETURN OLD;
112
+ END IF;
113
+ RETURN NEW;
114
+ END
115
+ $fn$;
116
+
117
+ CREATE FUNCTION xema_org_write_fence_truncate() RETURNS trigger
118
+ LANGUAGE plpgsql VOLATILE SET search_path FROM CURRENT AS $fn$
119
+ BEGIN
120
+ RAISE EXCEPTION USING ERRCODE = 'XQ410', MESSAGE = 'ORG_FENCE_TRUNCATE_REFUSED', DETAIL = TG_TABLE_NAME;
121
+ END
122
+ $fn$;
123
+
124
+ CREATE FUNCTION xema_org_quiesce_mark(p_org text) RETURNS void
125
+ LANGUAGE plpgsql VOLATILE SET search_path FROM CURRENT AS $fn$
126
+ BEGIN
127
+ PERFORM pg_advisory_xact_lock(xema_org_quiesce_lock_key(p_org));
128
+ INSERT INTO "org_quiesce_marks" ("org_id") VALUES (p_org) ON CONFLICT ("org_id") DO NOTHING;
129
+ END
130
+ $fn$;
131
+ `;
132
+ //# sourceMappingURL=org-write-fence.sql.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"org-write-fence.sql.js","sourceRoot":"","sources":["../../../src/lib/org-quiesce/org-write-fence.sql.ts"],"names":[],"mappings":";;;AAyCa,QAAA,mBAAmB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+HlC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"sdk-owned-migrations.d.ts","sourceRoot":"","sources":["../../src/lib/sdk-owned-migrations.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,yBAAyB,EAC/B,MAAM,eAAe,CAAC;AAWvB,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,2BAA2B,CAAC;AAmBnC,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,SAAS,oBAAoB,EAAE,GACpC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAQrB;AA+CD,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,yBAAyB,EACnC,IAAI,EAAE,SAAS,oBAAoB,EAAE,GACpC,OAAO,CAAC,IAAI,CAAC,CAyCf"}
1
+ {"version":3,"file":"sdk-owned-migrations.d.ts","sourceRoot":"","sources":["../../src/lib/sdk-owned-migrations.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,yBAAyB,EAC/B,MAAM,eAAe,CAAC;AAYvB,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,2BAA2B,CAAC;AAmBnC,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,SAAS,oBAAoB,EAAE,GACpC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAQrB;AAiDD,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,yBAAyB,EACnC,IAAI,EAAE,SAAS,oBAAoB,EAAE,GACpC,OAAO,CAAC,IAAI,CAAC,CAyCf"}
@@ -5,6 +5,7 @@ exports.applySdkOwnedMigrations = applySdkOwnedMigrations;
5
5
  const ledger_rows_1 = require("./ledger-rows");
6
6
  const operator_migration_1 = require("./operator-migration");
7
7
  const operator_migration_ledger_1 = require("./operator-migration-ledger");
8
+ const org_write_fence_apply_1 = require("./org-quiesce/org-write-fence-apply");
8
9
  const owned_table_shape_1 = require("./owned-table-shape");
9
10
  const sdk_owned_errors_1 = require("./sdk-owned-errors");
10
11
  const sdk_owned_migration_set_1 = require("./sdk-owned-migration-set");
@@ -18,7 +19,10 @@ function sdkOwnedTableOwners(sets) {
18
19
  return new Map(all.flatMap((set) => set.tables.map((table) => [table.name, set.owner])));
19
20
  }
20
21
  async function assertLedgerKnownToImage(database, sets) {
21
- const known = new Set(migrationIdsOf([operator_migration_ledger_1.operatorMigrationLedgerMigrations, ...sets]));
22
+ const known = new Set([
23
+ ...migrationIdsOf([operator_migration_ledger_1.operatorMigrationLedgerMigrations, ...sets]),
24
+ org_write_fence_apply_1.ORG_WRITE_FENCE_DECLARATION_ID,
25
+ ]);
22
26
  const recorded = await database.query(`SELECT migration_id FROM "${operator_migration_ledger_1.OPERATOR_MIGRATION_LEDGER_TABLE}" WHERE phase = $1 ORDER BY migration_id`, [ledger_rows_1.OperatorMigrationPhase.Applied]);
23
27
  const unknown = recorded.rows
24
28
  .map((row) => row.migration_id)
@@ -1 +1 @@
1
- {"version":3,"file":"sdk-owned-migrations.js","sourceRoot":"","sources":["../../src/lib/sdk-owned-migrations.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sdk-owned-migrations.js","sourceRoot":"","sources":["../../src/lib/sdk-owned-migrations.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import type { SdkOwnedTablePriorCreator } from './sdk-owned-migration-set';
1
2
  export declare function sdkOwnedTableNamedByMigration(sql: string, tables: readonly string[]): string | undefined;
2
- export declare function assertServiceMigrationsLeaveSdkOwnedTablesAlone(workspaceDir: string, ownerOfTable: ReadonlyMap<string, string>): Promise<void>;
3
+ export declare function assertServiceMigrationsLeaveSdkOwnedTablesAlone(workspaceDir: string, ownerOfTable: ReadonlyMap<string, string>, priorCreators: readonly SdkOwnedTablePriorCreator[]): Promise<void>;
3
4
  //# sourceMappingURL=service-migration-scan.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"service-migration-scan.d.ts","sourceRoot":"","sources":["../../src/lib/service-migration-scan.ts"],"names":[],"mappings":"AAgKA,wBAAgB,6BAA6B,CAC3C,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,SAAS,MAAM,EAAE,GACxB,MAAM,GAAG,SAAS,CAuBpB;AAOD,wBAAsB,+CAA+C,CACnE,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GACxC,OAAO,CAAC,IAAI,CAAC,CAgBf"}
1
+ {"version":3,"file":"service-migration-scan.d.ts","sourceRoot":"","sources":["../../src/lib/service-migration-scan.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AA4J3E,wBAAgB,6BAA6B,CAC3C,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,SAAS,MAAM,EAAE,GACxB,MAAM,GAAG,SAAS,CAuBpB;AAeD,wBAAsB,+CAA+C,CACnE,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,EACzC,aAAa,EAAE,SAAS,yBAAyB,EAAE,GAClD,OAAO,CAAC,IAAI,CAAC,CA4Bf"}