@stratum-hq/lib 1.2.0 → 1.3.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 (54) hide show
  1. package/dist/crypto.d.ts.map +1 -1
  2. package/dist/crypto.js +7 -1
  3. package/dist/crypto.js.map +1 -1
  4. package/dist/index.d.ts +2 -2
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +3 -1
  7. package/dist/index.js.map +1 -1
  8. package/dist/migrate-schemas.d.ts.map +1 -1
  9. package/dist/migrate-schemas.js +19 -8
  10. package/dist/migrate-schemas.js.map +1 -1
  11. package/dist/migrations/011_demo_bootstrap.sql +3 -3
  12. package/dist/migrations/022_roles_name_per_tenant.sql +10 -0
  13. package/dist/migrations/024_propagate_ancestry_ltree.sql +42 -0
  14. package/dist/migrations/025_redact_sensitive_config_audit.sql +19 -0
  15. package/dist/migrations/026_redact_webhook_secret_audit.sql +28 -0
  16. package/dist/migrations/027_tenant_status_pending.sql +8 -0
  17. package/dist/services/abac-service.d.ts.map +1 -1
  18. package/dist/services/abac-service.js +49 -37
  19. package/dist/services/abac-service.js.map +1 -1
  20. package/dist/services/api-key-service.d.ts.map +1 -1
  21. package/dist/services/api-key-service.js +55 -33
  22. package/dist/services/api-key-service.js.map +1 -1
  23. package/dist/services/config-service.d.ts +2 -7
  24. package/dist/services/config-service.d.ts.map +1 -1
  25. package/dist/services/config-service.js +14 -6
  26. package/dist/services/config-service.js.map +1 -1
  27. package/dist/services/event-service.d.ts +12 -0
  28. package/dist/services/event-service.d.ts.map +1 -1
  29. package/dist/services/event-service.js +240 -79
  30. package/dist/services/event-service.js.map +1 -1
  31. package/dist/services/permission-service.d.ts.map +1 -1
  32. package/dist/services/permission-service.js +30 -13
  33. package/dist/services/permission-service.js.map +1 -1
  34. package/dist/services/retention-service.d.ts +7 -1
  35. package/dist/services/retention-service.d.ts.map +1 -1
  36. package/dist/services/retention-service.js +47 -7
  37. package/dist/services/retention-service.js.map +1 -1
  38. package/dist/services/role-service.d.ts +7 -0
  39. package/dist/services/role-service.d.ts.map +1 -1
  40. package/dist/services/role-service.js +25 -11
  41. package/dist/services/role-service.js.map +1 -1
  42. package/dist/services/tenant-service.d.ts +12 -3
  43. package/dist/services/tenant-service.d.ts.map +1 -1
  44. package/dist/services/tenant-service.js +120 -53
  45. package/dist/services/tenant-service.js.map +1 -1
  46. package/dist/services/webhook-service.d.ts +5 -0
  47. package/dist/services/webhook-service.d.ts.map +1 -1
  48. package/dist/services/webhook-service.js +14 -0
  49. package/dist/services/webhook-service.js.map +1 -1
  50. package/dist/stratum.d.ts +26 -18
  51. package/dist/stratum.d.ts.map +1 -1
  52. package/dist/stratum.js +105 -41
  53. package/dist/stratum.js.map +1 -1
  54. package/package.json +2 -2
@@ -4,6 +4,7 @@ exports.purgeExpiredData = purgeExpiredData;
4
4
  exports.purgeTenant = purgeTenant;
5
5
  exports.exportTenantData = exportTenantData;
6
6
  const core_1 = require("@stratum-hq/core");
7
+ const db_adapters_1 = require("@stratum-hq/db-adapters");
7
8
  const pool_helpers_js_1 = require("../pool-helpers.js");
8
9
  const DEFAULT_RETENTION_DAYS = 90;
9
10
  /**
@@ -28,18 +29,54 @@ async function purgeExpiredData(pool, retentionDays = DEFAULT_RETENTION_DAYS) {
28
29
  return { deleted_count: totalDeleted };
29
30
  });
30
31
  }
32
+ // Guard: reject if tenant has children (FK RESTRICT would crash otherwise)
33
+ async function assertNoChildren(client, tenantId) {
34
+ const childCheck = await client.query(`SELECT COUNT(*)::int AS count FROM tenants WHERE parent_id = $1`, [tenantId]);
35
+ const childCount = childCheck.rows[0].count;
36
+ if (childCount > 0) {
37
+ throw new core_1.StratumError(core_1.ErrorCode.TENANT_HAS_CHILDREN, `Cannot purge tenant ${tenantId}: has ${childCount} child tenant(s). Purge children first.`, 409, { tenant_id: tenantId, child_count: childCount });
38
+ }
39
+ }
40
+ /**
41
+ * Whether purge drops this tenant's schema or database. Only a tenant that was
42
+ * activated owns storage: a pending tenant's provisioning never completed, and
43
+ * may have failed because something else already held the name. The storage
44
+ * name is derived by the same db-adapters helpers provisioning uses.
45
+ */
46
+ function ownsStorage(tenant, strategy) {
47
+ return tenant !== undefined && tenant.status !== "pending" && tenant.isolation_strategy === strategy;
48
+ }
31
49
  /**
32
50
  * GDPR Article 17 — Right to Erasure.
33
- * Hard-delete ALL data belonging to a specific tenant, in correct FK order.
51
+ * Hard-delete ALL data belonging to a specific tenant, in correct FK order,
52
+ * including its own schema (SCHEMA_PER_TENANT) or database (DB_PER_TENANT).
53
+ *
54
+ * The schema is dropped in the same transaction as the rows, so both go or
55
+ * neither does. DROP DATABASE cannot run in a transaction, so the database is
56
+ * dropped first: if that fails nothing has changed, and if removing the rows
57
+ * then fails, purging again completes (the drop is IF EXISTS).
34
58
  */
35
59
  async function purgeTenant(pool, tenantId) {
36
- await (0, pool_helpers_js_1.withTransaction)(pool, async (client) => {
37
- // Guard: reject if tenant has children (FK RESTRICT would crash otherwise)
38
- const childCheck = await client.query(`SELECT COUNT(*)::int AS count FROM tenants WHERE parent_id = $1`, [tenantId]);
39
- const childCount = childCheck.rows[0].count;
40
- if (childCount > 0) {
41
- throw new core_1.StratumError(core_1.ErrorCode.TENANT_HAS_CHILDREN, `Cannot purge tenant ${tenantId}: has ${childCount} child tenant(s). Purge children first.`, 409, { tenant_id: tenantId, child_count: childCount });
60
+ const before = await (0, pool_helpers_js_1.withClient)(pool, async (client) => {
61
+ await assertNoChildren(client, tenantId);
62
+ const res = await client.query(`SELECT slug, isolation_strategy, status FROM tenants WHERE id = $1`, [tenantId]);
63
+ return res.rows[0];
64
+ });
65
+ if (ownsStorage(before, "DB_PER_TENANT")) {
66
+ const client = await pool.connect();
67
+ try {
68
+ await (0, db_adapters_1.dropDatabase)(client, before.slug);
69
+ }
70
+ finally {
71
+ client.release();
42
72
  }
73
+ }
74
+ await (0, pool_helpers_js_1.withTransaction)(pool, async (client) => {
75
+ // Lock the row first so a concurrent create under this tenant (which holds
76
+ // it FOR SHARE) finishes and is counted, rather than failing the delete.
77
+ const res = await client.query(`SELECT slug, isolation_strategy, status FROM tenants WHERE id = $1 FOR UPDATE`, [tenantId]);
78
+ const tenant = res.rows[0];
79
+ await assertNoChildren(client, tenantId);
43
80
  // Delete in FK-safe order (children before parents)
44
81
  await client.query(`DELETE FROM config_entries WHERE tenant_id = $1`, [tenantId]);
45
82
  await client.query(`DELETE FROM permission_policies WHERE tenant_id = $1`, [tenantId]);
@@ -56,6 +93,9 @@ async function purgeTenant(pool, tenantId) {
56
93
  await client.query(`DELETE FROM audit_logs WHERE tenant_id = $1 OR (resource_type = 'tenant' AND resource_id = $1::text)`, [tenantId]);
57
94
  // Finally, the tenant itself
58
95
  await client.query(`DELETE FROM tenants WHERE id = $1`, [tenantId]);
96
+ if (ownsStorage(tenant, "SCHEMA_PER_TENANT")) {
97
+ await (0, db_adapters_1.dropSchema)(client, tenant.slug);
98
+ }
59
99
  });
60
100
  }
61
101
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"retention-service.js","sourceRoot":"","sources":["../../src/services/retention-service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"retention-service.js","sourceRoot":"","sources":["../../src/services/retention-service.ts"],"names":[],"mappings":"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"}
@@ -37,6 +37,11 @@ export declare function removeRoleFromKey(pool: pg.Pool, keyId: string): Promise
37
37
  * asks. Assigning a role therefore governs authorization everywhere.
38
38
  */
39
39
  export declare function resolveEffectiveScopes(pool: pg.Pool, keyId: string): Promise<string[]>;
40
+ /**
41
+ * {@link resolveEffectiveScopes} on a client the caller already holds, so a
42
+ * caller inside withClient does not need a second pool connection.
43
+ */
44
+ export declare function resolveEffectiveScopesOnClient(client: pg.PoolClient, keyId: string): Promise<string[]>;
40
45
  /**
41
46
  * Resolve effective scopes for an API key. Thin alias over
42
47
  * {@link resolveEffectiveScopes}, kept as the public facade name.
@@ -59,6 +64,8 @@ export declare function resolveKeyScopes(pool: pg.Pool, keyId: string): Promise<
59
64
  * authorizing the assignment.
60
65
  */
61
66
  export declare function assignRole(pool: pg.Pool, principalType: string, principalId: string, roleId: string, tenantId?: string): Promise<boolean>;
67
+ /** The role currently assigned to a principal, or null when it has none. */
68
+ export declare function getPrincipalRoleId(pool: pg.Pool, principalType: string, principalId: string): Promise<string | null>;
62
69
  /** Remove a principal's role assignment. Returns true if one was removed. */
63
70
  export declare function removeRole(pool: pg.Pool, principalType: string, principalId: string): Promise<boolean>;
64
71
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"role-service.d.ts","sourceRoot":"","sources":["../../src/services/role-service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"role-service.d.ts","sourceRoot":"","sources":["../../src/services/role-service.ts"],"names":[],"mappings":"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"}
@@ -8,8 +8,10 @@ exports.deleteRole = deleteRole;
8
8
  exports.assignRoleToKey = assignRoleToKey;
9
9
  exports.removeRoleFromKey = removeRoleFromKey;
10
10
  exports.resolveEffectiveScopes = resolveEffectiveScopes;
11
+ exports.resolveEffectiveScopesOnClient = resolveEffectiveScopesOnClient;
11
12
  exports.resolveKeyScopes = resolveKeyScopes;
12
13
  exports.assignRole = assignRole;
14
+ exports.getPrincipalRoleId = getPrincipalRoleId;
13
15
  exports.removeRole = removeRole;
14
16
  exports.resolvePrincipalScopes = resolvePrincipalScopes;
15
17
  const pool_helpers_js_1 = require("../pool-helpers.js");
@@ -94,17 +96,22 @@ async function removeRoleFromKey(pool, keyId) {
94
96
  * asks. Assigning a role therefore governs authorization everywhere.
95
97
  */
96
98
  async function resolveEffectiveScopes(pool, keyId) {
97
- return (0, pool_helpers_js_1.withClient)(pool, async (client) => {
98
- const res = await client.query(`SELECT ak.scopes, r.scopes as role_scopes
99
- FROM api_keys ak
100
- LEFT JOIN roles r ON r.id = ak.role_id
101
- WHERE ak.id = $1 AND ak.revoked_at IS NULL`, [keyId]);
102
- if (res.rows.length === 0)
103
- return ["read"];
104
- const row = res.rows[0];
105
- // Role scopes override key scopes when a role is assigned.
106
- return row.role_scopes ?? row.scopes ?? ["read"];
107
- });
99
+ return (0, pool_helpers_js_1.withClient)(pool, (client) => resolveEffectiveScopesOnClient(client, keyId));
100
+ }
101
+ /**
102
+ * {@link resolveEffectiveScopes} on a client the caller already holds, so a
103
+ * caller inside withClient does not need a second pool connection.
104
+ */
105
+ async function resolveEffectiveScopesOnClient(client, keyId) {
106
+ const res = await client.query(`SELECT ak.scopes, r.scopes as role_scopes
107
+ FROM api_keys ak
108
+ LEFT JOIN roles r ON r.id = ak.role_id
109
+ WHERE ak.id = $1 AND ak.revoked_at IS NULL`, [keyId]);
110
+ if (res.rows.length === 0)
111
+ return ["read"];
112
+ const row = res.rows[0];
113
+ // Role scopes override key scopes when a role is assigned.
114
+ return row.role_scopes ?? row.scopes ?? ["read"];
108
115
  }
109
116
  /**
110
117
  * Resolve effective scopes for an API key. Thin alias over
@@ -144,6 +151,13 @@ async function assignRole(pool, principalType, principalId, roleId, tenantId) {
144
151
  return res.rows.length > 0;
145
152
  });
146
153
  }
154
+ /** The role currently assigned to a principal, or null when it has none. */
155
+ async function getPrincipalRoleId(pool, principalType, principalId) {
156
+ return (0, pool_helpers_js_1.withClient)(pool, async (client) => {
157
+ const res = await client.query(`SELECT role_id FROM principal_roles WHERE principal_type = $1 AND principal_id = $2`, [principalType, principalId]);
158
+ return res.rows[0]?.role_id ?? null;
159
+ });
160
+ }
147
161
  /** Remove a principal's role assignment. Returns true if one was removed. */
148
162
  async function removeRole(pool, principalType, principalId) {
149
163
  return (0, pool_helpers_js_1.withClient)(pool, async (client) => {
@@ -1 +1 @@
1
- {"version":3,"file":"role-service.js","sourceRoot":"","sources":["../../src/services/role-service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"role-service.js","sourceRoot":"","sources":["../../src/services/role-service.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import pg from "pg";
2
- import { type TenantNode, type CreateTenantInput, type UpdateTenantInput, type PaginationInput, type PaginatedResult } from "@stratum-hq/core";
2
+ import { type TenantNode, type CreateTenantInput, type UpdateTenantInput, type PaginationInput, type PaginatedResult, type TenantStatus } from "@stratum-hq/core";
3
3
  export declare function createTenant(pool: pg.Pool, input: CreateTenantInput): Promise<TenantNode>;
4
4
  export declare function getTenant(pool: pg.Pool, id: string, includeArchived?: boolean): Promise<TenantNode>;
5
5
  /**
@@ -10,11 +10,15 @@ export declare function getTenant(pool: pg.Pool, id: string, includeArchived?: b
10
10
  *
11
11
  * Mirrors getTenant's not-found and state contract: throws TenantNotFoundError
12
12
  * when no row matches, and — unless `includeArchived` is set — TenantArchivedError
13
- * / TenantSuspendedError for a non-active row. `includeArchived` is the "give me
13
+ * / TenantSuspendedError / TenantPendingError for a non-active row. `includeArchived` is the "give me
14
14
  * the row whatever its state" escape hatch and bypasses both non-active states.
15
15
  */
16
16
  export declare function getTenantBySlug(pool: pg.Pool, slug: string, includeArchived?: boolean): Promise<TenantNode>;
17
- export declare function listTenants(pool: pg.Pool, pagination: PaginationInput): Promise<PaginatedResult<TenantNode>>;
17
+ /**
18
+ * List tenants in one status, `active` by default. Pending, suspended and
19
+ * archived tenants appear only when that status is asked for.
20
+ */
21
+ export declare function listTenants(pool: pg.Pool, pagination: PaginationInput, status?: TenantStatus): Promise<PaginatedResult<TenantNode>>;
18
22
  export declare function updateTenant(pool: pg.Pool, id: string, patch: UpdateTenantInput): Promise<TenantNode>;
19
23
  /**
20
24
  * Suspend an active tenant: reversible block on access. Rejects if the tenant
@@ -32,6 +36,11 @@ export declare function archiveTenant(pool: pg.Pool, id: string): Promise<Tenant
32
36
  * its parent is not active. Resuming is top-down.
33
37
  */
34
38
  export declare function resumeTenant(pool: pg.Pool, id: string): Promise<TenantNode>;
39
+ /**
40
+ * Activate a pending tenant once its schema or database has been provisioned.
41
+ * Rejects if the tenant is not pending, or if its parent is not active.
42
+ */
43
+ export declare function activateTenant(pool: pg.Pool, id: string): Promise<TenantNode>;
35
44
  /**
36
45
  * Soft-delete a tenant.
37
46
  *
@@ -1 +1 @@
1
- {"version":3,"file":"tenant-service.d.ts","sourceRoot":"","sources":["../../src/services/tenant-service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tenant-service.d.ts","sourceRoot":"","sources":["../../src/services/tenant-service.ts"],"names":[],"mappings":"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"}
@@ -8,6 +8,7 @@ exports.updateTenant = updateTenant;
8
8
  exports.suspendTenant = suspendTenant;
9
9
  exports.archiveTenant = archiveTenant;
10
10
  exports.resumeTenant = resumeTenant;
11
+ exports.activateTenant = activateTenant;
11
12
  exports.deleteTenant = deleteTenant;
12
13
  exports.moveTenant = moveTenant;
13
14
  exports.batchCreateTenants = batchCreateTenants;
@@ -18,30 +19,62 @@ exports.getChildren = getChildren;
18
19
  exports.reorderTenant = reorderTenant;
19
20
  const pool_helpers_js_1 = require("../pool-helpers.js");
20
21
  const core_1 = require("@stratum-hq/core");
22
+ /**
23
+ * Serialize changes to the tree's shape on one advisory key. A move takes it
24
+ * exclusively: it checks for cycles against the destination's ancestry and
25
+ * rewrites the paths of a whole subtree, so nothing else may read or write
26
+ * ancestry while it runs. Creates and lifecycle transitions take it shared, so
27
+ * they still run alongside each other (row locks order those) but never read a
28
+ * parent path that an in-flight move is about to rewrite. Take it before
29
+ * reading any row the change depends on.
30
+ */
31
+ async function lockTree(client, mode) {
32
+ const fn = mode === "exclusive" ? "pg_advisory_xact_lock" : "pg_advisory_xact_lock_shared";
33
+ await client.query(`SELECT ${fn}(('x' || substr(md5('stratum.tenant_tree'), 1, 16))::bit(64)::bigint)`);
34
+ }
35
+ /**
36
+ * Load the parent for a tenant being created or moved, and require it to be
37
+ * active: a suspended or archived subtree must not grow, so an active tenant
38
+ * always has an active parent. FOR SHARE holds the parent row until commit, so
39
+ * a concurrent suspend or archive of the parent (which locks it FOR UPDATE)
40
+ * waits for this transaction and then sees the new child.
41
+ */
42
+ async function loadActiveParent(client, parentId) {
43
+ const res = await client.query(`SELECT * FROM tenants WHERE id = $1 FOR SHARE`, [parentId]);
44
+ if (res.rows.length === 0) {
45
+ throw new core_1.TenantNotFoundError(parentId);
46
+ }
47
+ const parent = res.rows[0];
48
+ if (parent.status === "archived") {
49
+ throw new core_1.TenantArchivedError(parentId);
50
+ }
51
+ if (parent.status === "suspended") {
52
+ throw new core_1.TenantSuspendedError(parentId);
53
+ }
54
+ if (parent.status === "pending") {
55
+ throw new core_1.TenantPendingError(parentId);
56
+ }
57
+ return parent;
58
+ }
59
+ /**
60
+ * A tenant with its own schema or database starts `pending` and is activated
61
+ * (activateTenant) once that storage has been provisioned, so it is never
62
+ * usable without it. SHARED_RLS tenants need no storage and start `active`.
63
+ */
64
+ function initialStatus(input) {
65
+ const strategy = input.isolation_strategy ?? "SHARED_RLS";
66
+ return strategy === "SCHEMA_PER_TENANT" || strategy === "DB_PER_TENANT" ? "pending" : "active";
67
+ }
21
68
  async function createTenant(pool, input) {
22
69
  return (0, pool_helpers_js_1.withTransaction)(pool, async (client) => {
23
- // Advisory lock on parent to serialize sibling inserts
24
70
  if (input.parent_id) {
25
- await client.query(`SELECT pg_advisory_xact_lock(('x' || substr(md5($1::text), 1, 16))::bit(64)::bigint)`, [input.parent_id]);
26
- // Verify parent exists and is active
27
- const parentRes = await client.query(`SELECT * FROM tenants WHERE id = $1`, [input.parent_id]);
28
- if (parentRes.rows.length === 0) {
29
- throw new core_1.TenantNotFoundError(input.parent_id);
30
- }
31
- if (parentRes.rows[0].status === "archived") {
32
- throw new core_1.TenantArchivedError(input.parent_id);
33
- }
34
- // A suspended or archived subtree must not grow: an active tenant always
35
- // has an active parent. See suspendTenant/resumeTenant for the inverse.
36
- if (parentRes.rows[0].status === "suspended") {
37
- throw new core_1.TenantSuspendedError(input.parent_id);
38
- }
39
- const parent = parentRes.rows[0];
71
+ await lockTree(client, "shared");
72
+ const parent = await loadActiveParent(client, input.parent_id);
40
73
  const ancestry_path = (0, core_1.appendToPath)(parent.ancestry_path, parent.id);
41
74
  // Inherit region_id from parent if not explicitly provided
42
75
  const regionId = input.region_id ?? parent.region_id ?? null;
43
76
  const res = await client.query(`INSERT INTO tenants (parent_id, ancestry_path, depth, name, slug, config, metadata, isolation_strategy, status, region_id)
44
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, 'active', $9)
77
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
45
78
  RETURNING *`, [
46
79
  input.parent_id,
47
80
  ancestry_path,
@@ -51,6 +84,7 @@ async function createTenant(pool, input) {
51
84
  JSON.stringify(input.config ?? {}),
52
85
  JSON.stringify(input.metadata ?? {}),
53
86
  input.isolation_strategy ?? "SHARED_RLS",
87
+ initialStatus(input),
54
88
  regionId,
55
89
  ]);
56
90
  return res.rows[0];
@@ -59,13 +93,14 @@ async function createTenant(pool, input) {
59
93
  // Root tenant — no parent lock needed
60
94
  const rootRegionId = input.region_id ?? null;
61
95
  const res = await client.query(`INSERT INTO tenants (parent_id, ancestry_path, depth, name, slug, config, metadata, isolation_strategy, status, region_id)
62
- VALUES (NULL, '/', 0, $1, $2, $3, $4, $5, 'active', $6)
96
+ VALUES (NULL, '/', 0, $1, $2, $3, $4, $5, $6, $7)
63
97
  RETURNING *`, [
64
98
  input.name,
65
99
  input.slug,
66
100
  JSON.stringify(input.config ?? {}),
67
101
  JSON.stringify(input.metadata ?? {}),
68
102
  input.isolation_strategy ?? "SHARED_RLS",
103
+ initialStatus(input),
69
104
  rootRegionId,
70
105
  ]);
71
106
  return res.rows[0];
@@ -90,12 +125,15 @@ async function getTenant(pool, id, includeArchived = false) {
90
125
  if (!includeArchived && tenant.status === "archived") {
91
126
  throw new core_1.TenantArchivedError(id);
92
127
  }
93
- // Suspended tenants are blocked from normal access too. `includeArchived`
94
- // is the "give me the row whatever its state" escape hatch and bypasses
95
- // both non-active states.
128
+ // Suspended and pending tenants are blocked from normal access too.
129
+ // `includeArchived` is the "give me the row whatever its state" escape
130
+ // hatch and bypasses every non-active state.
96
131
  if (!includeArchived && tenant.status === "suspended") {
97
132
  throw new core_1.TenantSuspendedError(id);
98
133
  }
134
+ if (!includeArchived && tenant.status === "pending") {
135
+ throw new core_1.TenantPendingError(id);
136
+ }
99
137
  return tenant;
100
138
  });
101
139
  }
@@ -107,7 +145,7 @@ async function getTenant(pool, id, includeArchived = false) {
107
145
  *
108
146
  * Mirrors getTenant's not-found and state contract: throws TenantNotFoundError
109
147
  * when no row matches, and — unless `includeArchived` is set — TenantArchivedError
110
- * / TenantSuspendedError for a non-active row. `includeArchived` is the "give me
148
+ * / TenantSuspendedError / TenantPendingError for a non-active row. `includeArchived` is the "give me
111
149
  * the row whatever its state" escape hatch and bypasses both non-active states.
112
150
  */
113
151
  async function getTenantBySlug(pool, slug, includeArchived = false) {
@@ -124,24 +162,31 @@ async function getTenantBySlug(pool, slug, includeArchived = false) {
124
162
  if (!includeArchived && tenant.status === "suspended") {
125
163
  throw new core_1.TenantSuspendedError(tenant.id);
126
164
  }
165
+ if (!includeArchived && tenant.status === "pending") {
166
+ throw new core_1.TenantPendingError(tenant.id);
167
+ }
127
168
  return tenant;
128
169
  });
129
170
  }
130
- async function listTenants(pool, pagination) {
171
+ /**
172
+ * List tenants in one status, `active` by default. Pending, suspended and
173
+ * archived tenants appear only when that status is asked for.
174
+ */
175
+ async function listTenants(pool, pagination, status = "active") {
131
176
  return (0, pool_helpers_js_1.withClient)(pool, async (client) => {
132
177
  const limit = pagination.limit ?? 50;
133
178
  let res;
134
179
  if (pagination.cursor) {
135
180
  res = await client.query(`SELECT * FROM tenants
136
- WHERE status = 'active' AND id > $1
181
+ WHERE status = $3 AND id > $1
137
182
  ORDER BY id ASC
138
- LIMIT $2`, [pagination.cursor, limit + 1]);
183
+ LIMIT $2`, [pagination.cursor, limit + 1, status]);
139
184
  }
140
185
  else {
141
186
  res = await client.query(`SELECT * FROM tenants
142
- WHERE status = 'active'
187
+ WHERE status = $2
143
188
  ORDER BY id ASC
144
- LIMIT $1`, [limit + 1]);
189
+ LIMIT $1`, [limit + 1, status]);
145
190
  }
146
191
  const rows = res.rows;
147
192
  const has_more = rows.length > limit;
@@ -164,6 +209,14 @@ async function updateTenant(pool, id, patch) {
164
209
  values.push(patch.name);
165
210
  }
166
211
  if (patch.slug !== undefined) {
212
+ // Schema- and database-per-tenant storage is named from the slug, so
213
+ // changing it would detach the tenant from its storage and free the old
214
+ // name for another tenant.
215
+ const strategy = existing.rows[0].isolation_strategy;
216
+ if (patch.slug !== existing.rows[0].slug &&
217
+ (strategy === "SCHEMA_PER_TENANT" || strategy === "DB_PER_TENANT")) {
218
+ throw new core_1.ValidationError(`Slug cannot be changed for a ${strategy} tenant`, { tenant_id: id, isolation_strategy: strategy });
219
+ }
167
220
  sets.push(`slug = $${idx++}`);
168
221
  values.push(patch.slug);
169
222
  }
@@ -194,8 +247,10 @@ async function updateTenant(pool, id, patch) {
194
247
  // ---------------------------------------------------------------------------
195
248
  // Tenant lifecycle
196
249
  //
197
- // States: active -> {suspended, archived} -> (purged). Transitions:
198
- // createTenant (none) -> active
250
+ // States: [pending ->] active -> {suspended, archived} -> (purged). Transitions:
251
+ // createTenant (none) -> active, or pending for a tenant with
252
+ // its own schema or database
253
+ // activateTenant pending -> active (storage provisioned)
199
254
  // suspendTenant active -> suspended (reversible, blocks access)
200
255
  // resumeTenant suspended | archived -> active (reverses suspend/archive)
201
256
  // archiveTenant active | suspended -> archived (soft delete, reversible)
@@ -208,8 +263,8 @@ async function updateTenant(pool, id, patch) {
208
263
  // Descendant rules, tested against real Postgres in packages/integration-tests:
209
264
  // * suspend / archive block when the tenant has an ACTIVE child
210
265
  // (TenantHasChildrenError). They act leaf-first and never cascade.
211
- // * resume / create require the parent to be ACTIVE, so the invariant "an
212
- // active tenant's parent is active" always holds.
266
+ // * resume / activate / create / move require the parent to be ACTIVE, so
267
+ // the invariant "an active tenant's parent is active" always holds.
213
268
  // * purge requires an empty subtree (no children of ANY status).
214
269
  // ---------------------------------------------------------------------------
215
270
  /**
@@ -218,7 +273,11 @@ async function updateTenant(pool, id, patch) {
218
273
  * suspended rows, because lifecycle operations act on exactly those states.
219
274
  */
220
275
  async function loadForTransition(client, id) {
221
- const res = await client.query(`SELECT * FROM tenants WHERE id = $1`, [id]);
276
+ // Transitions read the tree (children, parent) to keep "an active tenant's
277
+ // parent is active", so they must not interleave with a move. FOR UPDATE
278
+ // then orders them against creates and other transitions on this tenant.
279
+ await lockTree(client, "shared");
280
+ const res = await client.query(`SELECT * FROM tenants WHERE id = $1 FOR UPDATE`, [id]);
222
281
  if (res.rows.length === 0) {
223
282
  throw new core_1.TenantNotFoundError(id);
224
283
  }
@@ -278,7 +337,9 @@ async function resumeTenant(pool, id) {
278
337
  throw new core_1.InvalidTenantStateError(id, tenant.status, "resume", ["suspended", "archived"]);
279
338
  }
280
339
  if (tenant.parent_id) {
281
- const parentRes = await client.query(`SELECT status FROM tenants WHERE id = $1`, [tenant.parent_id]);
340
+ // FOR SHARE: a concurrent suspend or archive of the parent waits for
341
+ // this resume and then sees an active child.
342
+ const parentRes = await client.query(`SELECT status FROM tenants WHERE id = $1 FOR SHARE`, [tenant.parent_id]);
282
343
  const parentStatus = parentRes.rows[0]?.status;
283
344
  if (parentStatus === "archived") {
284
345
  throw new core_1.TenantArchivedError(tenant.parent_id);
@@ -291,6 +352,23 @@ async function resumeTenant(pool, id) {
291
352
  return res.rows[0];
292
353
  });
293
354
  }
355
+ /**
356
+ * Activate a pending tenant once its schema or database has been provisioned.
357
+ * Rejects if the tenant is not pending, or if its parent is not active.
358
+ */
359
+ async function activateTenant(pool, id) {
360
+ return (0, pool_helpers_js_1.withTransaction)(pool, async (client) => {
361
+ const tenant = await loadForTransition(client, id);
362
+ if (tenant.status !== "pending") {
363
+ throw new core_1.InvalidTenantStateError(id, tenant.status, "activate", ["pending"]);
364
+ }
365
+ if (tenant.parent_id) {
366
+ await loadActiveParent(client, tenant.parent_id);
367
+ }
368
+ const res = await client.query(`UPDATE tenants SET status = 'active', updated_at = now() WHERE id = $1 RETURNING *`, [id]);
369
+ return res.rows[0];
370
+ });
371
+ }
294
372
  /**
295
373
  * Soft-delete a tenant.
296
374
  *
@@ -302,21 +380,14 @@ async function deleteTenant(pool, id) {
302
380
  }
303
381
  async function moveTenant(pool, id, newParentId) {
304
382
  return (0, pool_helpers_js_1.withTransaction)(pool, async (client) => {
305
- // Lock both old and new parents to prevent concurrent moves
306
- await client.query(`SELECT pg_advisory_xact_lock(('x' || substr(md5($1::text), 1, 16))::bit(64)::bigint)`, [id]);
307
- await client.query(`SELECT pg_advisory_xact_lock(('x' || substr(md5($1::text), 1, 16))::bit(64)::bigint)`, [newParentId]);
383
+ await lockTree(client, "exclusive");
308
384
  // Load tenant being moved
309
385
  const tenantRes = await client.query(`SELECT * FROM tenants WHERE id = $1 AND status != 'archived'`, [id]);
310
386
  if (tenantRes.rows.length === 0) {
311
387
  throw new core_1.TenantNotFoundError(id);
312
388
  }
313
389
  const tenant = tenantRes.rows[0];
314
- // Load new parent
315
- const newParentRes = await client.query(`SELECT * FROM tenants WHERE id = $1 AND status != 'archived'`, [newParentId]);
316
- if (newParentRes.rows.length === 0) {
317
- throw new core_1.TenantNotFoundError(newParentId);
318
- }
319
- const newParent = newParentRes.rows[0];
390
+ const newParent = await loadActiveParent(client, newParentId);
320
391
  // Cycle detection: newParent must not be a descendant of tenant.
321
392
  // Check if the moving tenant's ID appears in the new parent's ancestry_path
322
393
  // (which would mean the new parent is a descendant of the moving tenant).
@@ -373,11 +444,12 @@ async function batchCreateTenants(pool, inputs) {
373
444
  const errors = [];
374
445
  try {
375
446
  await (0, pool_helpers_js_1.withTransaction)(pool, async (client) => {
447
+ await lockTree(client, "shared");
376
448
  // Map of slug → created tenant for intra-batch parent references
377
449
  const slugMap = new Map();
378
450
  for (let i = 0; i < inputs.length; i++) {
379
451
  const input = inputs[i];
380
- let parentId = input.parent_id ?? null;
452
+ const parentId = input.parent_id ?? null;
381
453
  let parentNode = null;
382
454
  if (parentId) {
383
455
  // Check if parent was created earlier in this batch (by ID)
@@ -386,19 +458,12 @@ async function batchCreateTenants(pool, inputs) {
386
458
  parentNode = batchParent;
387
459
  }
388
460
  else {
389
- // Look up in DB
390
- const parentRes = await client.query(`SELECT * FROM tenants WHERE id = $1 AND status != 'archived'`, [parentId]);
391
- if (parentRes.rows.length === 0) {
392
- throw new core_1.TenantNotFoundError(parentId);
393
- }
394
- parentNode = parentRes.rows[0];
461
+ parentNode = await loadActiveParent(client, parentId);
395
462
  }
396
- // Advisory lock on parent
397
- await client.query(`SELECT pg_advisory_xact_lock(('x' || substr(md5($1::text), 1, 16))::bit(64)::bigint)`, [parentId]);
398
463
  const ancestry_path = (0, core_1.appendToPath)(parentNode.ancestry_path, parentNode.id);
399
464
  const regionId = input.region_id ?? parentNode.region_id ?? null;
400
465
  const res = await client.query(`INSERT INTO tenants (parent_id, ancestry_path, depth, name, slug, config, metadata, isolation_strategy, status, region_id)
401
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, 'active', $9)
466
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
402
467
  RETURNING *`, [
403
468
  parentId,
404
469
  ancestry_path,
@@ -408,6 +473,7 @@ async function batchCreateTenants(pool, inputs) {
408
473
  JSON.stringify(input.config ?? {}),
409
474
  JSON.stringify(input.metadata ?? {}),
410
475
  input.isolation_strategy ?? "SHARED_RLS",
476
+ initialStatus(input),
411
477
  regionId,
412
478
  ]);
413
479
  const tenant = res.rows[0];
@@ -418,13 +484,14 @@ async function batchCreateTenants(pool, inputs) {
418
484
  // Root tenant
419
485
  const rootRegionId = input.region_id ?? null;
420
486
  const res = await client.query(`INSERT INTO tenants (parent_id, ancestry_path, depth, name, slug, config, metadata, isolation_strategy, status, region_id)
421
- VALUES (NULL, '/', 0, $1, $2, $3, $4, $5, 'active', $6)
487
+ VALUES (NULL, '/', 0, $1, $2, $3, $4, $5, $6, $7)
422
488
  RETURNING *`, [
423
489
  input.name,
424
490
  input.slug,
425
491
  JSON.stringify(input.config ?? {}),
426
492
  JSON.stringify(input.metadata ?? {}),
427
493
  input.isolation_strategy ?? "SHARED_RLS",
494
+ initialStatus(input),
428
495
  rootRegionId,
429
496
  ]);
430
497
  const tenant = res.rows[0];