hierarchical-approval 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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/errors.ts","../../src/adapters/MemoryAdapter.ts"],"names":[],"mappings":"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","file":"MemoryAdapter.js","sourcesContent":["export class ApprovalError extends Error {\n constructor(\n message: string,\n public readonly code: string,\n ) {\n super(message);\n this.name = 'ApprovalError';\n }\n\n toJSON(): { code: string; message: string; name: string } {\n return { code: this.code, message: this.message, name: this.name };\n }\n\n toHttpStatus(): number {\n const map: Record<string, number> = {\n NOT_FOUND: 404,\n CONFLICT: 409,\n FORBIDDEN: 403,\n VALIDATION: 422,\n TEMPLATE_NOT_FOUND: 404,\n };\n return map[this.code] ?? 500;\n }\n}\n\nexport class ApprovalNotFoundError extends ApprovalError {\n constructor(resource: string, id: string) {\n super(`${resource} \"${id}\" not found.`, 'NOT_FOUND');\n this.name = 'ApprovalNotFoundError';\n }\n}\n\nexport class ApprovalConflictError extends ApprovalError {\n constructor(instanceId: string) {\n super(\n `Concurrent modification detected on instance \"${instanceId}\". The record was updated by another process. Please retry.`,\n 'CONFLICT',\n );\n this.name = 'ApprovalConflictError';\n }\n}\n\nexport class ApprovalForbiddenError extends ApprovalError {\n constructor(message: string) {\n super(message, 'FORBIDDEN');\n this.name = 'ApprovalForbiddenError';\n }\n}\n\nexport class ApprovalValidationError extends ApprovalError {\n constructor(\n message: string,\n public readonly cause?: unknown,\n ) {\n super(message, 'VALIDATION');\n this.name = 'ApprovalValidationError';\n }\n}\n\nexport class ApprovalTemplateNotFoundError extends ApprovalError {\n constructor(name: string) {\n super(`Template \"${name}\" not found.`, 'TEMPLATE_NOT_FOUND');\n this.name = 'ApprovalTemplateNotFoundError';\n }\n}\n","import type {\n IStorageAdapter,\n PaginationOpts,\n PaginatedResult,\n InstanceFilter,\n CursorPaginationOpts,\n CursorPaginatedResult,\n} from './IStorageAdapter.js';\nimport type { ApprovalTemplate, ApprovalInstance, AuditEntry } from '../types/index.js';\nimport { ApprovalConflictError } from '../errors.js';\n\nfunction deepClone<T>(value: T): T {\n return JSON.parse(JSON.stringify(value)) as T;\n}\n\nfunction reviveDates(instance: ApprovalInstance): ApprovalInstance {\n return {\n ...instance,\n createdAt: new Date(instance.createdAt),\n updatedAt: new Date(instance.updatedAt),\n expiresAt: instance.expiresAt ? new Date(instance.expiresAt) : undefined,\n slaDeadlineAt: instance.slaDeadlineAt ? new Date(instance.slaDeadlineAt) : undefined,\n slaBreachedAt: instance.slaBreachedAt ? new Date(instance.slaBreachedAt) : undefined,\n auditLog: instance.auditLog.map((e) => ({ ...e, timestamp: new Date(e.timestamp) })),\n levels: instance.levels.map((l) => {\n const level: typeof l = { ...l };\n if (l.escalationDueAt) level.escalationDueAt = new Date(l.escalationDueAt);\n if (l.delegatedUntil) level.delegatedUntil = new Date(l.delegatedUntil);\n return level;\n }),\n };\n}\n\nfunction applyFilter(instance: ApprovalInstance, filter: InstanceFilter): boolean {\n if (filter.status && instance.status !== filter.status) return false;\n if (filter.documentType && instance.documentType !== filter.documentType) return false;\n if (filter.submittedBy && instance.submittedBy !== filter.submittedBy) return false;\n if (filter.fromDate && instance.createdAt < filter.fromDate) return false;\n if (filter.toDate && instance.createdAt > filter.toDate) return false;\n return true;\n}\n\nexport class MemoryAdapter implements IStorageAdapter {\n // keyed by `${tenantId}:${template.name}`\n private templates = new Map<string, ApprovalTemplate>();\n // keyed by `${tenantId}:${instance.id}`\n private instances = new Map<string, ApprovalInstance>();\n\n async saveTemplate(template: ApprovalTemplate): Promise<void> {\n this.templates.set(`${template.tenantId}:${template.name}`, deepClone(template));\n }\n\n async getTemplate(tenantId: string, name: string): Promise<ApprovalTemplate | null> {\n return deepClone(this.templates.get(`${tenantId}:${name}`) ?? null);\n }\n\n async listTemplates(tenantId: string): Promise<ApprovalTemplate[]> {\n const result: ApprovalTemplate[] = [];\n for (const [key, template] of this.templates) {\n if (key.startsWith(`${tenantId}:`)) {\n result.push(deepClone(template));\n }\n }\n return result;\n }\n\n async saveInstance(instance: ApprovalInstance): Promise<void> {\n this.instances.set(`${instance.tenantId}:${instance.id}`, deepClone(instance));\n }\n\n async updateInstance(instance: ApprovalInstance, expectedVersion: number): Promise<void> {\n const key = `${instance.tenantId}:${instance.id}`;\n const stored = this.instances.get(key);\n if (!stored) throw new ApprovalConflictError(instance.id);\n if (stored.version !== expectedVersion) throw new ApprovalConflictError(instance.id);\n const updated = deepClone(instance);\n updated.version = expectedVersion + 1;\n this.instances.set(key, updated);\n }\n\n async getInstance(tenantId: string, id: string): Promise<ApprovalInstance | null> {\n const raw = this.instances.get(`${tenantId}:${id}`);\n if (!raw) return null;\n return reviveDates(deepClone(raw));\n }\n\n async getInstancesByApprover(\n tenantId: string,\n approverId: string,\n opts?: PaginationOpts,\n ): Promise<PaginatedResult<ApprovalInstance>> {\n const all = [...this.instances.values()].filter((i) => {\n if (i.tenantId !== tenantId || i.status !== 'pending') return false;\n // Use .find() by level number, not array index (level numbers may not be consecutive)\n const currentLevel = i.levels.find((l) => l.level === i.currentLevel);\n return currentLevel?.approverIds.includes(approverId) ?? false;\n });\n return paginate(all.map((i) => reviveDates(deepClone(i))), opts);\n }\n\n async getInstancesByFilter(\n tenantId: string,\n filter: InstanceFilter,\n opts?: PaginationOpts,\n ): Promise<PaginatedResult<ApprovalInstance>> {\n const all = [...this.instances.values()].filter(\n (i) => i.tenantId === tenantId && applyFilter(i, filter),\n );\n return paginate(all.map((i) => reviveDates(deepClone(i))), opts);\n }\n\n async getOverdueInstances(tenantId: string, asOf: Date): Promise<ApprovalInstance[]> {\n return [...this.instances.values()]\n .filter((i) => {\n if (i.tenantId !== tenantId || i.status !== 'pending') return false;\n // Escalation overdue\n const currentLevel = i.levels.find((l) => l.level === i.currentLevel);\n const hasOverdueEscalation =\n currentLevel?.escalationDueAt != null && new Date(currentLevel.escalationDueAt) <= asOf;\n // Instance deadline expired\n const isExpired = i.expiresAt != null && new Date(i.expiresAt) <= asOf;\n // SLA breach (not yet recorded)\n const hasSLABreach =\n i.slaDeadlineAt != null && new Date(i.slaDeadlineAt) <= asOf && !i.slaBreachedAt;\n // Delegation expiry on any pending level\n const hasDelegationExpiry = i.levels.some(\n (l) =>\n l.status === 'pending' &&\n l.delegatedUntil != null &&\n new Date(l.delegatedUntil) <= asOf &&\n l.delegatedFrom != null,\n );\n return hasOverdueEscalation || isExpired || hasSLABreach || hasDelegationExpiry;\n })\n .map((i) => reviveDates(deepClone(i)));\n }\n\n async getInstancesByCursor(\n tenantId: string,\n filter: InstanceFilter,\n opts: CursorPaginationOpts,\n ): Promise<CursorPaginatedResult<ApprovalInstance>> {\n const all = [...this.instances.values()]\n .filter((i) => i.tenantId === tenantId && applyFilter(i, filter))\n .map((i) => reviveDates(deepClone(i)))\n .sort((a, b) => {\n const ta = a.updatedAt.getTime();\n const tb = b.updatedAt.getTime();\n return ta !== tb ? ta - tb : a.id.localeCompare(b.id);\n });\n\n const { cursor, limit, direction = 'forward' } = opts;\n let startIdx = 0;\n\n if (cursor) {\n const [ts, id] = decodeCursor(cursor);\n const idx = all.findIndex(\n (i) => i.updatedAt.getTime() > ts || (i.updatedAt.getTime() === ts && i.id > id),\n );\n startIdx = idx === -1 ? all.length : idx;\n }\n\n if (direction === 'backward' && startIdx > 0) {\n startIdx = Math.max(0, startIdx - limit - 1);\n }\n\n const slice = all.slice(startIdx, startIdx + limit);\n const hasMore = startIdx + limit < all.length;\n const nextCursor = hasMore ? encodeCursor(slice[slice.length - 1]!) : undefined;\n const prevCursor = startIdx > 0 ? encodeCursor(all[startIdx - 1]!) : undefined;\n\n return { items: slice, nextCursor, prevCursor, hasMore };\n }\n\n async getIdempotentInstance(\n tenantId: string,\n idempotencyKey: string,\n ): Promise<ApprovalInstance | null> {\n for (const instance of this.instances.values()) {\n if (instance.tenantId === tenantId && instance.idempotencyKey === idempotencyKey) {\n return reviveDates(deepClone(instance));\n }\n }\n return null;\n }\n\n async appendAuditEntry(tenantId: string, instanceId: string, entry: AuditEntry): Promise<void> {\n const key = `${tenantId}:${instanceId}`;\n const instance = this.instances.get(key);\n if (!instance) return;\n instance.auditLog.push(deepClone(entry));\n instance.updatedAt = new Date(entry.timestamp);\n }\n\n /** Test helper — total stored instances across all tenants. */\n get size(): number {\n return this.instances.size;\n }\n}\n\nfunction paginate<T>(items: T[], opts?: PaginationOpts): PaginatedResult<T> {\n const total = items.length;\n if (!opts) return { items, total };\n return { items: items.slice(opts.offset, opts.offset + opts.limit), total };\n}\n\nfunction encodeCursor(instance: ApprovalInstance): string {\n return Buffer.from(`${instance.updatedAt.getTime()}|${instance.id}`).toString('base64');\n}\n\nfunction decodeCursor(cursor: string): [number, string] {\n const decoded = Buffer.from(cursor, 'base64').toString('utf8');\n const pipeIdx = decoded.indexOf('|');\n return [Number(decoded.slice(0, pipeIdx)), decoded.slice(pipeIdx + 1)];\n}\n"]}
@@ -0,0 +1,468 @@
1
+ 'use strict';
2
+
3
+ // src/errors.ts
4
+ var ApprovalError = class extends Error {
5
+ constructor(message, code) {
6
+ super(message);
7
+ this.code = code;
8
+ this.name = "ApprovalError";
9
+ }
10
+ toJSON() {
11
+ return { code: this.code, message: this.message, name: this.name };
12
+ }
13
+ toHttpStatus() {
14
+ const map = {
15
+ NOT_FOUND: 404,
16
+ CONFLICT: 409,
17
+ FORBIDDEN: 403,
18
+ VALIDATION: 422,
19
+ TEMPLATE_NOT_FOUND: 404
20
+ };
21
+ return map[this.code] ?? 500;
22
+ }
23
+ };
24
+ var ApprovalConflictError = class extends ApprovalError {
25
+ constructor(instanceId) {
26
+ super(
27
+ `Concurrent modification detected on instance "${instanceId}". The record was updated by another process. Please retry.`,
28
+ "CONFLICT"
29
+ );
30
+ this.name = "ApprovalConflictError";
31
+ }
32
+ };
33
+ var ApprovalValidationError = class extends ApprovalError {
34
+ constructor(message, cause) {
35
+ super(message, "VALIDATION");
36
+ this.cause = cause;
37
+ this.name = "ApprovalValidationError";
38
+ }
39
+ };
40
+
41
+ // src/adapters/PostgresAdapter.ts
42
+ var TABLE_PREFIX_RE = /^[a-z][a-z0-9_]*$/;
43
+ var PostgresAdapter = class {
44
+ constructor(opts) {
45
+ this.opts = opts;
46
+ this._pool = null;
47
+ const prefix = opts.tablePrefix ?? "ha";
48
+ if (!TABLE_PREFIX_RE.test(prefix)) {
49
+ throw new ApprovalValidationError(
50
+ `PostgresAdapter: tablePrefix "${prefix}" is invalid. Must match /^[a-z][a-z0-9_]*$/. Only lowercase letters, digits, and underscores are allowed.`
51
+ );
52
+ }
53
+ this.prefix = prefix;
54
+ this.schema = opts.schema ?? "public";
55
+ this.statementTimeoutMs = opts.statementTimeoutMs;
56
+ this.externalPool = opts.pool;
57
+ }
58
+ async getPool() {
59
+ if (this.externalPool) return this.externalPool;
60
+ if (this._pool) return this._pool;
61
+ const { default: pg } = await import('pg');
62
+ this._pool = new pg.Pool({
63
+ connectionString: this.opts.connectionString,
64
+ ssl: this.opts.ssl
65
+ });
66
+ return this._pool;
67
+ }
68
+ get p() {
69
+ return `${this.schema}.${this.prefix}`;
70
+ }
71
+ async migrate() {
72
+ const pool = await this.getPool();
73
+ await pool.query(`
74
+ CREATE TABLE IF NOT EXISTS ${this.p}_templates (
75
+ tenant_id TEXT NOT NULL,
76
+ name TEXT NOT NULL,
77
+ data JSONB NOT NULL,
78
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
79
+ PRIMARY KEY (tenant_id, name)
80
+ );
81
+
82
+ CREATE TABLE IF NOT EXISTS ${this.p}_instances (
83
+ id TEXT NOT NULL,
84
+ tenant_id TEXT NOT NULL,
85
+ template_name TEXT NOT NULL,
86
+ document_id TEXT NOT NULL,
87
+ document_type TEXT NOT NULL,
88
+ submitted_by TEXT NOT NULL,
89
+ status TEXT NOT NULL,
90
+ current_level INT NOT NULL,
91
+ version INT NOT NULL DEFAULT 1,
92
+ idempotency_key TEXT,
93
+ data JSONB NOT NULL DEFAULT '{}',
94
+ metadata JSONB NOT NULL DEFAULT '{}',
95
+ levels JSONB NOT NULL DEFAULT '[]',
96
+ parent_instance_id TEXT,
97
+ expires_at TIMESTAMPTZ,
98
+ deadline_action TEXT,
99
+ sla_deadline_at TIMESTAMPTZ,
100
+ sla_breached_at TIMESTAMPTZ,
101
+ template_snapshot JSONB,
102
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
103
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
104
+ PRIMARY KEY (tenant_id, id),
105
+ UNIQUE (tenant_id, idempotency_key)
106
+ );
107
+
108
+ CREATE TABLE IF NOT EXISTS ${this.p}_audit_log (
109
+ id BIGSERIAL PRIMARY KEY,
110
+ tenant_id TEXT NOT NULL,
111
+ instance_id TEXT NOT NULL,
112
+ action TEXT NOT NULL,
113
+ actor_id TEXT NOT NULL,
114
+ actor_role TEXT,
115
+ actor_ip TEXT,
116
+ actor_user_agent TEXT,
117
+ trace_id TEXT,
118
+ level INT NOT NULL,
119
+ timestamp TIMESTAMPTZ NOT NULL,
120
+ old_value JSONB,
121
+ new_value JSONB,
122
+ comment TEXT,
123
+ reason TEXT,
124
+ delegate_to TEXT
125
+ );
126
+
127
+ CREATE INDEX IF NOT EXISTS ${this.p}_instances_tenant_status
128
+ ON ${this.p}_instances (tenant_id, status);
129
+
130
+ CREATE INDEX IF NOT EXISTS ${this.p}_instances_tenant_updated
131
+ ON ${this.p}_instances (tenant_id, updated_at);
132
+
133
+ CREATE INDEX IF NOT EXISTS ${this.p}_audit_log_instance
134
+ ON ${this.p}_audit_log (tenant_id, instance_id);
135
+
136
+ -- Add new columns to existing tables (idempotent for upgrades)
137
+ ALTER TABLE IF EXISTS ${this.p}_instances ADD COLUMN IF NOT EXISTS parent_instance_id TEXT;
138
+ ALTER TABLE IF EXISTS ${this.p}_instances ADD COLUMN IF NOT EXISTS expires_at TIMESTAMPTZ;
139
+ ALTER TABLE IF EXISTS ${this.p}_instances ADD COLUMN IF NOT EXISTS deadline_action TEXT;
140
+ ALTER TABLE IF EXISTS ${this.p}_instances ADD COLUMN IF NOT EXISTS sla_deadline_at TIMESTAMPTZ;
141
+ ALTER TABLE IF EXISTS ${this.p}_instances ADD COLUMN IF NOT EXISTS sla_breached_at TIMESTAMPTZ;
142
+ ALTER TABLE IF EXISTS ${this.p}_instances ADD COLUMN IF NOT EXISTS template_snapshot JSONB;
143
+ `);
144
+ }
145
+ /** Enable PostgreSQL Row Level Security for the instances and audit_log tables. */
146
+ async enableRLS() {
147
+ const pool = await this.getPool();
148
+ await pool.query(`
149
+ ALTER TABLE ${this.p}_instances ENABLE ROW LEVEL SECURITY;
150
+ ALTER TABLE ${this.p}_audit_log ENABLE ROW LEVEL SECURITY;
151
+
152
+ DROP POLICY IF EXISTS ${this.p}_tenant_isolation ON ${this.p}_instances;
153
+ CREATE POLICY ${this.p}_tenant_isolation ON ${this.p}_instances
154
+ USING (tenant_id = current_setting('rls.tenant_id', TRUE));
155
+
156
+ DROP POLICY IF EXISTS ${this.p}_audit_tenant_isolation ON ${this.p}_audit_log;
157
+ CREATE POLICY ${this.p}_audit_tenant_isolation ON ${this.p}_audit_log
158
+ USING (tenant_id = current_setting('rls.tenant_id', TRUE));
159
+ `);
160
+ }
161
+ // ─── Templates ────────────────────────────────────────────────────────────
162
+ async saveTemplate(template) {
163
+ const pool = await this.getPool();
164
+ await pool.query(
165
+ `INSERT INTO ${this.p}_templates (tenant_id, name, data, created_at)
166
+ VALUES ($1, $2, $3, $4)
167
+ ON CONFLICT (tenant_id, name) DO UPDATE SET data = EXCLUDED.data`,
168
+ [template.tenantId, template.name, JSON.stringify(template), template.createdAt.toISOString()]
169
+ );
170
+ }
171
+ async getTemplate(tenantId, name) {
172
+ const pool = await this.getPool();
173
+ const result = await pool.query(
174
+ `SELECT data FROM ${this.p}_templates WHERE tenant_id = $1 AND name = $2`,
175
+ [tenantId, name]
176
+ );
177
+ return result.rows[0]?.data ?? null;
178
+ }
179
+ async listTemplates(tenantId) {
180
+ const pool = await this.getPool();
181
+ const result = await pool.query(
182
+ `SELECT data FROM ${this.p}_templates WHERE tenant_id = $1 ORDER BY created_at ASC`,
183
+ [tenantId]
184
+ );
185
+ return result.rows.map((r) => r.data);
186
+ }
187
+ // ─── Instances ────────────────────────────────────────────────────────────
188
+ async saveInstance(instance) {
189
+ const pool = await this.getPool();
190
+ await pool.query(
191
+ `INSERT INTO ${this.p}_instances
192
+ (id, tenant_id, template_name, document_id, document_type, submitted_by,
193
+ status, current_level, version, idempotency_key,
194
+ data, metadata, levels,
195
+ parent_instance_id, expires_at, deadline_action,
196
+ sla_deadline_at, sla_breached_at, template_snapshot,
197
+ created_at, updated_at)
198
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21)
199
+ ON CONFLICT (tenant_id, id) DO NOTHING`,
200
+ [
201
+ instance.id,
202
+ instance.tenantId,
203
+ instance.templateName,
204
+ instance.documentId,
205
+ instance.documentType,
206
+ instance.submittedBy,
207
+ instance.status,
208
+ instance.currentLevel,
209
+ instance.version,
210
+ instance.idempotencyKey ?? null,
211
+ JSON.stringify(instance.data),
212
+ JSON.stringify(instance.metadata),
213
+ JSON.stringify(instance.levels),
214
+ instance.parentInstanceId ?? null,
215
+ instance.expiresAt?.toISOString() ?? null,
216
+ instance.deadlineAction ?? null,
217
+ instance.slaDeadlineAt?.toISOString() ?? null,
218
+ instance.slaBreachedAt?.toISOString() ?? null,
219
+ instance.templateSnapshot ? JSON.stringify(instance.templateSnapshot) : null,
220
+ instance.createdAt.toISOString(),
221
+ instance.updatedAt.toISOString()
222
+ ]
223
+ );
224
+ }
225
+ async updateInstance(instance, expectedVersion) {
226
+ const pool = await this.getPool();
227
+ const result = await pool.query(
228
+ `UPDATE ${this.p}_instances SET
229
+ status = $4,
230
+ current_level = $5,
231
+ version = $6 + 1,
232
+ levels = $7,
233
+ sla_breached_at = $8,
234
+ updated_at = $9
235
+ WHERE tenant_id = $1 AND id = $2 AND version = $3
236
+ RETURNING id`,
237
+ [
238
+ instance.tenantId,
239
+ instance.id,
240
+ expectedVersion,
241
+ instance.status,
242
+ instance.currentLevel,
243
+ expectedVersion,
244
+ JSON.stringify(instance.levels),
245
+ instance.slaBreachedAt?.toISOString() ?? null,
246
+ instance.updatedAt.toISOString()
247
+ ]
248
+ );
249
+ if (result.rowCount === 0) throw new ApprovalConflictError(instance.id);
250
+ }
251
+ async getInstance(tenantId, id) {
252
+ const pool = await this.getPool();
253
+ const result = await pool.query(
254
+ `SELECT * FROM ${this.p}_instances WHERE tenant_id = $1 AND id = $2`,
255
+ [tenantId, id]
256
+ );
257
+ const row = result.rows[0];
258
+ if (!row) return null;
259
+ return this.rowToInstance(row);
260
+ }
261
+ async getInstancesByApprover(tenantId, approverId, opts) {
262
+ const pool = await this.getPool();
263
+ const offset = opts?.offset ?? 0;
264
+ const limit = opts?.limit ?? 50;
265
+ const result = await pool.query(
266
+ `SELECT *, COUNT(*) OVER() AS total_count
267
+ FROM ${this.p}_instances
268
+ WHERE tenant_id = $1
269
+ AND status = 'pending'
270
+ AND EXISTS (
271
+ SELECT 1 FROM jsonb_array_elements(levels) AS lvl
272
+ WHERE lvl->>'status' = 'pending'
273
+ AND lvl->'approverIds' @> $2::jsonb
274
+ )
275
+ ORDER BY updated_at ASC
276
+ LIMIT $3 OFFSET $4`,
277
+ [tenantId, JSON.stringify([approverId]), limit, offset]
278
+ );
279
+ const total = result.rows[0] ? Number(result.rows[0]["total_count"]) : 0;
280
+ return {
281
+ items: result.rows.map((r) => this.rowToInstance(r)),
282
+ total
283
+ };
284
+ }
285
+ async getInstancesByFilter(tenantId, filter, opts) {
286
+ const pool = await this.getPool();
287
+ const conditions = ["tenant_id = $1"];
288
+ const params = [tenantId];
289
+ let idx = 2;
290
+ if (filter.status) {
291
+ conditions.push(`status = $${idx++}`);
292
+ params.push(filter.status);
293
+ }
294
+ if (filter.documentType) {
295
+ conditions.push(`document_type = $${idx++}`);
296
+ params.push(filter.documentType);
297
+ }
298
+ if (filter.submittedBy) {
299
+ conditions.push(`submitted_by = $${idx++}`);
300
+ params.push(filter.submittedBy);
301
+ }
302
+ if (filter.fromDate) {
303
+ conditions.push(`created_at >= $${idx++}`);
304
+ params.push(filter.fromDate.toISOString());
305
+ }
306
+ if (filter.toDate) {
307
+ conditions.push(`created_at <= $${idx++}`);
308
+ params.push(filter.toDate.toISOString());
309
+ }
310
+ const offset = opts?.offset ?? 0;
311
+ const limit = opts?.limit ?? 50;
312
+ params.push(limit, offset);
313
+ const sql = `SELECT *, COUNT(*) OVER() AS total_count
314
+ FROM ${this.p}_instances
315
+ WHERE ${conditions.join(" AND ")}
316
+ ORDER BY created_at DESC
317
+ LIMIT $${idx++} OFFSET $${idx}`;
318
+ const result = await pool.query(sql, params);
319
+ const total = result.rows[0] ? Number(result.rows[0]["total_count"]) : 0;
320
+ return {
321
+ items: result.rows.map((r) => this.rowToInstance(r)),
322
+ total
323
+ };
324
+ }
325
+ async getOverdueInstances(tenantId, asOf) {
326
+ const pool = await this.getPool();
327
+ const result = await pool.query(
328
+ `SELECT * FROM ${this.p}_instances
329
+ WHERE tenant_id = $1
330
+ AND status = 'pending'
331
+ AND (
332
+ EXISTS (
333
+ SELECT 1 FROM jsonb_array_elements(levels) AS lvl
334
+ WHERE (lvl->>'escalationDueAt') IS NOT NULL
335
+ AND (lvl->>'escalationDueAt')::timestamptz <= $2
336
+ )
337
+ OR (expires_at IS NOT NULL AND expires_at <= $2)
338
+ OR (sla_deadline_at IS NOT NULL AND sla_deadline_at <= $2 AND sla_breached_at IS NULL)
339
+ OR EXISTS (
340
+ SELECT 1 FROM jsonb_array_elements(levels) AS lvl
341
+ WHERE lvl->>'status' = 'pending'
342
+ AND (lvl->>'delegatedUntil') IS NOT NULL
343
+ AND (lvl->>'delegatedUntil')::timestamptz <= $2
344
+ AND (lvl->>'delegatedFrom') IS NOT NULL
345
+ )
346
+ )`,
347
+ [tenantId, asOf.toISOString()]
348
+ );
349
+ return result.rows.map((r) => this.rowToInstance(r));
350
+ }
351
+ async getIdempotentInstance(tenantId, idempotencyKey) {
352
+ const pool = await this.getPool();
353
+ const result = await pool.query(
354
+ `SELECT * FROM ${this.p}_instances WHERE tenant_id = $1 AND idempotency_key = $2`,
355
+ [tenantId, idempotencyKey]
356
+ );
357
+ const row = result.rows[0];
358
+ if (!row) return null;
359
+ return this.rowToInstance(row);
360
+ }
361
+ // ─── Audit (append-only) ──────────────────────────────────────────────────
362
+ async appendAuditEntry(tenantId, instanceId, entry) {
363
+ const pool = await this.getPool();
364
+ await pool.query(
365
+ `INSERT INTO ${this.p}_audit_log
366
+ (tenant_id, instance_id, action, actor_id, actor_role, actor_ip,
367
+ actor_user_agent, trace_id, level, timestamp,
368
+ old_value, new_value, comment, reason, delegate_to)
369
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15)`,
370
+ [
371
+ tenantId,
372
+ instanceId,
373
+ entry.action,
374
+ entry.actorId,
375
+ entry.actorRole ?? null,
376
+ entry.actorIp ?? null,
377
+ entry.actorUserAgent ?? null,
378
+ entry.traceId ?? null,
379
+ entry.level,
380
+ entry.timestamp.toISOString(),
381
+ entry.oldValue ? JSON.stringify(entry.oldValue) : null,
382
+ entry.newValue ? JSON.stringify(entry.newValue) : null,
383
+ entry.comment ?? null,
384
+ entry.reason ?? null,
385
+ entry.delegateTo ?? null
386
+ ]
387
+ );
388
+ }
389
+ async getInstancesByCursor(tenantId, filter, opts) {
390
+ const pool = await this.getPool();
391
+ const conditions = ["tenant_id = $1"];
392
+ const params = [tenantId];
393
+ let idx = 2;
394
+ if (filter.status) {
395
+ conditions.push(`status = $${idx++}`);
396
+ params.push(filter.status);
397
+ }
398
+ if (filter.documentType) {
399
+ conditions.push(`document_type = $${idx++}`);
400
+ params.push(filter.documentType);
401
+ }
402
+ if (filter.submittedBy) {
403
+ conditions.push(`submitted_by = $${idx++}`);
404
+ params.push(filter.submittedBy);
405
+ }
406
+ const { cursor, limit, direction = "forward" } = opts;
407
+ if (cursor) {
408
+ const decoded = Buffer.from(cursor, "base64").toString("utf8");
409
+ const colonIdx = decoded.indexOf(":");
410
+ const ts = decoded.slice(0, colonIdx);
411
+ const id = decoded.slice(colonIdx + 1);
412
+ if (direction === "forward") {
413
+ conditions.push(`(updated_at, id) > ($${idx++}::timestamptz, $${idx++})`);
414
+ params.push(ts, id);
415
+ } else {
416
+ conditions.push(`(updated_at, id) < ($${idx++}::timestamptz, $${idx++})`);
417
+ params.push(ts, id);
418
+ }
419
+ }
420
+ params.push(limit + 1);
421
+ const order = direction === "backward" ? "DESC" : "ASC";
422
+ const sql = `SELECT * FROM ${this.p}_instances
423
+ WHERE ${conditions.join(" AND ")}
424
+ ORDER BY updated_at ${order}, id ${order}
425
+ LIMIT $${idx}`;
426
+ const result = await pool.query(sql, params);
427
+ const hasMore = result.rows.length > limit;
428
+ const rows = result.rows.slice(0, limit);
429
+ const items = rows.map((r) => this.rowToInstance(r));
430
+ const nextCursor = hasMore ? Buffer.from(`${rows[rows.length - 1]["updated_at"]}:${rows[rows.length - 1]["id"]}`).toString("base64") : void 0;
431
+ return { items, nextCursor, hasMore };
432
+ }
433
+ async end() {
434
+ await this._pool?.end();
435
+ this._pool = null;
436
+ }
437
+ rowToInstance(row) {
438
+ return {
439
+ id: row["id"],
440
+ tenantId: row["tenant_id"],
441
+ templateId: "",
442
+ templateName: row["template_name"],
443
+ documentId: row["document_id"],
444
+ documentType: row["document_type"],
445
+ submittedBy: row["submitted_by"],
446
+ status: row["status"],
447
+ currentLevel: row["current_level"],
448
+ version: row["version"],
449
+ idempotencyKey: row["idempotency_key"] ?? void 0,
450
+ data: row["data"],
451
+ metadata: row["metadata"],
452
+ levels: row["levels"],
453
+ auditLog: [],
454
+ parentInstanceId: row["parent_instance_id"] ?? void 0,
455
+ expiresAt: row["expires_at"] ? new Date(row["expires_at"]) : void 0,
456
+ deadlineAction: row["deadline_action"] ?? void 0,
457
+ slaDeadlineAt: row["sla_deadline_at"] ? new Date(row["sla_deadline_at"]) : void 0,
458
+ slaBreachedAt: row["sla_breached_at"] ? new Date(row["sla_breached_at"]) : void 0,
459
+ templateSnapshot: row["template_snapshot"] ?? void 0,
460
+ createdAt: new Date(row["created_at"]),
461
+ updatedAt: new Date(row["updated_at"])
462
+ };
463
+ }
464
+ };
465
+
466
+ exports.PostgresAdapter = PostgresAdapter;
467
+ //# sourceMappingURL=PostgresAdapter.cjs.map
468
+ //# sourceMappingURL=PostgresAdapter.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/errors.ts","../../src/adapters/PostgresAdapter.ts"],"names":[],"mappings":"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","file":"PostgresAdapter.cjs","sourcesContent":["export class ApprovalError extends Error {\n constructor(\n message: string,\n public readonly code: string,\n ) {\n super(message);\n this.name = 'ApprovalError';\n }\n\n toJSON(): { code: string; message: string; name: string } {\n return { code: this.code, message: this.message, name: this.name };\n }\n\n toHttpStatus(): number {\n const map: Record<string, number> = {\n NOT_FOUND: 404,\n CONFLICT: 409,\n FORBIDDEN: 403,\n VALIDATION: 422,\n TEMPLATE_NOT_FOUND: 404,\n };\n return map[this.code] ?? 500;\n }\n}\n\nexport class ApprovalNotFoundError extends ApprovalError {\n constructor(resource: string, id: string) {\n super(`${resource} \"${id}\" not found.`, 'NOT_FOUND');\n this.name = 'ApprovalNotFoundError';\n }\n}\n\nexport class ApprovalConflictError extends ApprovalError {\n constructor(instanceId: string) {\n super(\n `Concurrent modification detected on instance \"${instanceId}\". The record was updated by another process. Please retry.`,\n 'CONFLICT',\n );\n this.name = 'ApprovalConflictError';\n }\n}\n\nexport class ApprovalForbiddenError extends ApprovalError {\n constructor(message: string) {\n super(message, 'FORBIDDEN');\n this.name = 'ApprovalForbiddenError';\n }\n}\n\nexport class ApprovalValidationError extends ApprovalError {\n constructor(\n message: string,\n public readonly cause?: unknown,\n ) {\n super(message, 'VALIDATION');\n this.name = 'ApprovalValidationError';\n }\n}\n\nexport class ApprovalTemplateNotFoundError extends ApprovalError {\n constructor(name: string) {\n super(`Template \"${name}\" not found.`, 'TEMPLATE_NOT_FOUND');\n this.name = 'ApprovalTemplateNotFoundError';\n }\n}\n","/**\n * PostgreSQL adapter for hierarchical-approval.\n *\n * Peer dependency: `pg` >= 8\n *\n * Run the bundled migration before using:\n * const adapter = new PostgresAdapter({ connectionString: '...' });\n * await adapter.migrate();\n *\n * For PostgreSQL Row Level Security, call:\n * await adapter.enableRLS();\n */\nimport type {\n IStorageAdapter,\n PaginationOpts,\n PaginatedResult,\n InstanceFilter,\n CursorPaginationOpts,\n CursorPaginatedResult,\n} from './IStorageAdapter.js';\nimport type { ApprovalTemplate, ApprovalInstance, AuditEntry } from '../types/index.js';\nimport { ApprovalConflictError, ApprovalValidationError } from '../errors.js';\n\nexport interface PostgresAdapterOptions {\n connectionString?: string;\n /** Bring your own pre-configured pg.Pool instead of a connectionString. */\n pool?: import('pg').Pool;\n tablePrefix?: string;\n /** PostgreSQL schema to use (default: 'public'). */\n schema?: string;\n /** Statement timeout in milliseconds applied to every query (default: none). */\n statementTimeoutMs?: number;\n ssl?: import('tls').ConnectionOptions;\n}\n\nconst TABLE_PREFIX_RE = /^[a-z][a-z0-9_]*$/;\n\nexport class PostgresAdapter implements IStorageAdapter {\n private _pool: import('pg').Pool | null = null;\n private readonly prefix: string;\n private readonly schema: string;\n private readonly statementTimeoutMs?: number;\n private readonly externalPool: import('pg').Pool | undefined;\n\n constructor(private readonly opts: PostgresAdapterOptions) {\n const prefix = opts.tablePrefix ?? 'ha';\n if (!TABLE_PREFIX_RE.test(prefix)) {\n throw new ApprovalValidationError(\n `PostgresAdapter: tablePrefix \"${prefix}\" is invalid. Must match /^[a-z][a-z0-9_]*$/. Only lowercase letters, digits, and underscores are allowed.`,\n );\n }\n this.prefix = prefix;\n this.schema = opts.schema ?? 'public';\n this.statementTimeoutMs = opts.statementTimeoutMs;\n this.externalPool = opts.pool;\n }\n\n private async getPool(): Promise<import('pg').Pool> {\n if (this.externalPool) return this.externalPool;\n if (this._pool) return this._pool;\n const { default: pg } = await import('pg');\n this._pool = new pg.Pool({\n connectionString: this.opts.connectionString,\n ssl: this.opts.ssl,\n });\n return this._pool;\n }\n\n private get p() {\n return `${this.schema}.${this.prefix}`;\n }\n\n async migrate(): Promise<void> {\n const pool = await this.getPool();\n await pool.query(`\n CREATE TABLE IF NOT EXISTS ${this.p}_templates (\n tenant_id TEXT NOT NULL,\n name TEXT NOT NULL,\n data JSONB NOT NULL,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n PRIMARY KEY (tenant_id, name)\n );\n\n CREATE TABLE IF NOT EXISTS ${this.p}_instances (\n id TEXT NOT NULL,\n tenant_id TEXT NOT NULL,\n template_name TEXT NOT NULL,\n document_id TEXT NOT NULL,\n document_type TEXT NOT NULL,\n submitted_by TEXT NOT NULL,\n status TEXT NOT NULL,\n current_level INT NOT NULL,\n version INT NOT NULL DEFAULT 1,\n idempotency_key TEXT,\n data JSONB NOT NULL DEFAULT '{}',\n metadata JSONB NOT NULL DEFAULT '{}',\n levels JSONB NOT NULL DEFAULT '[]',\n parent_instance_id TEXT,\n expires_at TIMESTAMPTZ,\n deadline_action TEXT,\n sla_deadline_at TIMESTAMPTZ,\n sla_breached_at TIMESTAMPTZ,\n template_snapshot JSONB,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n PRIMARY KEY (tenant_id, id),\n UNIQUE (tenant_id, idempotency_key)\n );\n\n CREATE TABLE IF NOT EXISTS ${this.p}_audit_log (\n id BIGSERIAL PRIMARY KEY,\n tenant_id TEXT NOT NULL,\n instance_id TEXT NOT NULL,\n action TEXT NOT NULL,\n actor_id TEXT NOT NULL,\n actor_role TEXT,\n actor_ip TEXT,\n actor_user_agent TEXT,\n trace_id TEXT,\n level INT NOT NULL,\n timestamp TIMESTAMPTZ NOT NULL,\n old_value JSONB,\n new_value JSONB,\n comment TEXT,\n reason TEXT,\n delegate_to TEXT\n );\n\n CREATE INDEX IF NOT EXISTS ${this.p}_instances_tenant_status\n ON ${this.p}_instances (tenant_id, status);\n\n CREATE INDEX IF NOT EXISTS ${this.p}_instances_tenant_updated\n ON ${this.p}_instances (tenant_id, updated_at);\n\n CREATE INDEX IF NOT EXISTS ${this.p}_audit_log_instance\n ON ${this.p}_audit_log (tenant_id, instance_id);\n\n -- Add new columns to existing tables (idempotent for upgrades)\n ALTER TABLE IF EXISTS ${this.p}_instances ADD COLUMN IF NOT EXISTS parent_instance_id TEXT;\n ALTER TABLE IF EXISTS ${this.p}_instances ADD COLUMN IF NOT EXISTS expires_at TIMESTAMPTZ;\n ALTER TABLE IF EXISTS ${this.p}_instances ADD COLUMN IF NOT EXISTS deadline_action TEXT;\n ALTER TABLE IF EXISTS ${this.p}_instances ADD COLUMN IF NOT EXISTS sla_deadline_at TIMESTAMPTZ;\n ALTER TABLE IF EXISTS ${this.p}_instances ADD COLUMN IF NOT EXISTS sla_breached_at TIMESTAMPTZ;\n ALTER TABLE IF EXISTS ${this.p}_instances ADD COLUMN IF NOT EXISTS template_snapshot JSONB;\n `);\n }\n\n /** Enable PostgreSQL Row Level Security for the instances and audit_log tables. */\n async enableRLS(): Promise<void> {\n const pool = await this.getPool();\n await pool.query(`\n ALTER TABLE ${this.p}_instances ENABLE ROW LEVEL SECURITY;\n ALTER TABLE ${this.p}_audit_log ENABLE ROW LEVEL SECURITY;\n\n DROP POLICY IF EXISTS ${this.p}_tenant_isolation ON ${this.p}_instances;\n CREATE POLICY ${this.p}_tenant_isolation ON ${this.p}_instances\n USING (tenant_id = current_setting('rls.tenant_id', TRUE));\n\n DROP POLICY IF EXISTS ${this.p}_audit_tenant_isolation ON ${this.p}_audit_log;\n CREATE POLICY ${this.p}_audit_tenant_isolation ON ${this.p}_audit_log\n USING (tenant_id = current_setting('rls.tenant_id', TRUE));\n `);\n }\n\n // ─── Templates ────────────────────────────────────────────────────────────\n\n async saveTemplate(template: ApprovalTemplate): Promise<void> {\n const pool = await this.getPool();\n await pool.query(\n `INSERT INTO ${this.p}_templates (tenant_id, name, data, created_at)\n VALUES ($1, $2, $3, $4)\n ON CONFLICT (tenant_id, name) DO UPDATE SET data = EXCLUDED.data`,\n [template.tenantId, template.name, JSON.stringify(template), template.createdAt.toISOString()],\n );\n }\n\n async getTemplate(tenantId: string, name: string): Promise<ApprovalTemplate | null> {\n const pool = await this.getPool();\n const result = await pool.query<{ data: ApprovalTemplate }>(\n `SELECT data FROM ${this.p}_templates WHERE tenant_id = $1 AND name = $2`,\n [tenantId, name],\n );\n return result.rows[0]?.data ?? null;\n }\n\n async listTemplates(tenantId: string): Promise<ApprovalTemplate[]> {\n const pool = await this.getPool();\n const result = await pool.query<{ data: ApprovalTemplate }>(\n `SELECT data FROM ${this.p}_templates WHERE tenant_id = $1 ORDER BY created_at ASC`,\n [tenantId],\n );\n return result.rows.map((r) => r.data);\n }\n\n // ─── Instances ────────────────────────────────────────────────────────────\n\n async saveInstance(instance: ApprovalInstance): Promise<void> {\n const pool = await this.getPool();\n await pool.query(\n `INSERT INTO ${this.p}_instances\n (id, tenant_id, template_name, document_id, document_type, submitted_by,\n status, current_level, version, idempotency_key,\n data, metadata, levels,\n parent_instance_id, expires_at, deadline_action,\n sla_deadline_at, sla_breached_at, template_snapshot,\n created_at, updated_at)\n VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21)\n ON CONFLICT (tenant_id, id) DO NOTHING`,\n [\n instance.id,\n instance.tenantId,\n instance.templateName,\n instance.documentId,\n instance.documentType,\n instance.submittedBy,\n instance.status,\n instance.currentLevel,\n instance.version,\n instance.idempotencyKey ?? null,\n JSON.stringify(instance.data),\n JSON.stringify(instance.metadata),\n JSON.stringify(instance.levels),\n instance.parentInstanceId ?? null,\n instance.expiresAt?.toISOString() ?? null,\n instance.deadlineAction ?? null,\n instance.slaDeadlineAt?.toISOString() ?? null,\n instance.slaBreachedAt?.toISOString() ?? null,\n instance.templateSnapshot ? JSON.stringify(instance.templateSnapshot) : null,\n instance.createdAt.toISOString(),\n instance.updatedAt.toISOString(),\n ],\n );\n }\n\n async updateInstance(instance: ApprovalInstance, expectedVersion: number): Promise<void> {\n const pool = await this.getPool();\n const result = await pool.query(\n `UPDATE ${this.p}_instances SET\n status = $4,\n current_level = $5,\n version = $6 + 1,\n levels = $7,\n sla_breached_at = $8,\n updated_at = $9\n WHERE tenant_id = $1 AND id = $2 AND version = $3\n RETURNING id`,\n [\n instance.tenantId,\n instance.id,\n expectedVersion,\n instance.status,\n instance.currentLevel,\n expectedVersion,\n JSON.stringify(instance.levels),\n instance.slaBreachedAt?.toISOString() ?? null,\n instance.updatedAt.toISOString(),\n ],\n );\n if (result.rowCount === 0) throw new ApprovalConflictError(instance.id);\n }\n\n async getInstance(tenantId: string, id: string): Promise<ApprovalInstance | null> {\n const pool = await this.getPool();\n const result = await pool.query(\n `SELECT * FROM ${this.p}_instances WHERE tenant_id = $1 AND id = $2`,\n [tenantId, id],\n );\n const row = result.rows[0];\n if (!row) return null;\n return this.rowToInstance(row as Record<string, unknown>);\n }\n\n async getInstancesByApprover(\n tenantId: string,\n approverId: string,\n opts?: PaginationOpts,\n ): Promise<PaginatedResult<ApprovalInstance>> {\n const pool = await this.getPool();\n const offset = opts?.offset ?? 0;\n const limit = opts?.limit ?? 50;\n\n const result = await pool.query(\n `SELECT *, COUNT(*) OVER() AS total_count\n FROM ${this.p}_instances\n WHERE tenant_id = $1\n AND status = 'pending'\n AND EXISTS (\n SELECT 1 FROM jsonb_array_elements(levels) AS lvl\n WHERE lvl->>'status' = 'pending'\n AND lvl->'approverIds' @> $2::jsonb\n )\n ORDER BY updated_at ASC\n LIMIT $3 OFFSET $4`,\n [tenantId, JSON.stringify([approverId]), limit, offset],\n );\n\n const total = result.rows[0] ? Number((result.rows[0] as Record<string, unknown>)['total_count']) : 0;\n return {\n items: result.rows.map((r) => this.rowToInstance(r as Record<string, unknown>)),\n total,\n };\n }\n\n async getInstancesByFilter(\n tenantId: string,\n filter: InstanceFilter,\n opts?: PaginationOpts,\n ): Promise<PaginatedResult<ApprovalInstance>> {\n const pool = await this.getPool();\n const conditions: string[] = ['tenant_id = $1'];\n const params: unknown[] = [tenantId];\n let idx = 2;\n\n if (filter.status) { conditions.push(`status = $${idx++}`); params.push(filter.status); }\n if (filter.documentType) { conditions.push(`document_type = $${idx++}`); params.push(filter.documentType); }\n if (filter.submittedBy) { conditions.push(`submitted_by = $${idx++}`); params.push(filter.submittedBy); }\n if (filter.fromDate) { conditions.push(`created_at >= $${idx++}`); params.push(filter.fromDate.toISOString()); }\n if (filter.toDate) { conditions.push(`created_at <= $${idx++}`); params.push(filter.toDate.toISOString()); }\n\n const offset = opts?.offset ?? 0;\n const limit = opts?.limit ?? 50;\n params.push(limit, offset);\n\n const sql = `SELECT *, COUNT(*) OVER() AS total_count\n FROM ${this.p}_instances\n WHERE ${conditions.join(' AND ')}\n ORDER BY created_at DESC\n LIMIT $${idx++} OFFSET $${idx}`;\n\n const result = await pool.query(sql, params);\n const total = result.rows[0] ? Number((result.rows[0] as Record<string, unknown>)['total_count']) : 0;\n return {\n items: result.rows.map((r) => this.rowToInstance(r as Record<string, unknown>)),\n total,\n };\n }\n\n async getOverdueInstances(tenantId: string, asOf: Date): Promise<ApprovalInstance[]> {\n const pool = await this.getPool();\n const result = await pool.query(\n `SELECT * FROM ${this.p}_instances\n WHERE tenant_id = $1\n AND status = 'pending'\n AND (\n EXISTS (\n SELECT 1 FROM jsonb_array_elements(levels) AS lvl\n WHERE (lvl->>'escalationDueAt') IS NOT NULL\n AND (lvl->>'escalationDueAt')::timestamptz <= $2\n )\n OR (expires_at IS NOT NULL AND expires_at <= $2)\n OR (sla_deadline_at IS NOT NULL AND sla_deadline_at <= $2 AND sla_breached_at IS NULL)\n OR EXISTS (\n SELECT 1 FROM jsonb_array_elements(levels) AS lvl\n WHERE lvl->>'status' = 'pending'\n AND (lvl->>'delegatedUntil') IS NOT NULL\n AND (lvl->>'delegatedUntil')::timestamptz <= $2\n AND (lvl->>'delegatedFrom') IS NOT NULL\n )\n )`,\n [tenantId, asOf.toISOString()],\n );\n return result.rows.map((r) => this.rowToInstance(r as Record<string, unknown>));\n }\n\n async getIdempotentInstance(\n tenantId: string,\n idempotencyKey: string,\n ): Promise<ApprovalInstance | null> {\n const pool = await this.getPool();\n const result = await pool.query(\n `SELECT * FROM ${this.p}_instances WHERE tenant_id = $1 AND idempotency_key = $2`,\n [tenantId, idempotencyKey],\n );\n const row = result.rows[0];\n if (!row) return null;\n return this.rowToInstance(row as Record<string, unknown>);\n }\n\n // ─── Audit (append-only) ──────────────────────────────────────────────────\n\n async appendAuditEntry(tenantId: string, instanceId: string, entry: AuditEntry): Promise<void> {\n const pool = await this.getPool();\n await pool.query(\n `INSERT INTO ${this.p}_audit_log\n (tenant_id, instance_id, action, actor_id, actor_role, actor_ip,\n actor_user_agent, trace_id, level, timestamp,\n old_value, new_value, comment, reason, delegate_to)\n VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15)`,\n [\n tenantId,\n instanceId,\n entry.action,\n entry.actorId,\n entry.actorRole ?? null,\n entry.actorIp ?? null,\n entry.actorUserAgent ?? null,\n entry.traceId ?? null,\n entry.level,\n entry.timestamp.toISOString(),\n entry.oldValue ? JSON.stringify(entry.oldValue) : null,\n entry.newValue ? JSON.stringify(entry.newValue) : null,\n entry.comment ?? null,\n entry.reason ?? null,\n entry.delegateTo ?? null,\n ],\n );\n }\n\n async getInstancesByCursor(\n tenantId: string,\n filter: InstanceFilter,\n opts: CursorPaginationOpts,\n ): Promise<CursorPaginatedResult<ApprovalInstance>> {\n const pool = await this.getPool();\n const conditions: string[] = ['tenant_id = $1'];\n const params: unknown[] = [tenantId];\n let idx = 2;\n\n if (filter.status) { conditions.push(`status = $${idx++}`); params.push(filter.status); }\n if (filter.documentType) { conditions.push(`document_type = $${idx++}`); params.push(filter.documentType); }\n if (filter.submittedBy) { conditions.push(`submitted_by = $${idx++}`); params.push(filter.submittedBy); }\n\n const { cursor, limit, direction = 'forward' } = opts;\n\n if (cursor) {\n const decoded = Buffer.from(cursor, 'base64').toString('utf8');\n const colonIdx = decoded.indexOf(':');\n const ts = decoded.slice(0, colonIdx);\n const id = decoded.slice(colonIdx + 1);\n if (direction === 'forward') {\n conditions.push(`(updated_at, id) > ($${idx++}::timestamptz, $${idx++})`);\n params.push(ts, id);\n } else {\n conditions.push(`(updated_at, id) < ($${idx++}::timestamptz, $${idx++})`);\n params.push(ts, id);\n }\n }\n\n params.push(limit + 1);\n const order = direction === 'backward' ? 'DESC' : 'ASC';\n const sql = `SELECT * FROM ${this.p}_instances\n WHERE ${conditions.join(' AND ')}\n ORDER BY updated_at ${order}, id ${order}\n LIMIT $${idx}`;\n\n const result = await pool.query(sql, params);\n const hasMore = result.rows.length > limit;\n const rows = result.rows.slice(0, limit);\n const items = rows.map((r) => this.rowToInstance(r as Record<string, unknown>));\n\n const nextCursor = hasMore\n ? Buffer.from(`${rows[rows.length - 1]!['updated_at']}:${rows[rows.length - 1]!['id']}`).toString('base64')\n : undefined;\n\n return { items, nextCursor, hasMore };\n }\n\n async end(): Promise<void> {\n await this._pool?.end();\n this._pool = null;\n }\n\n private rowToInstance(row: Record<string, unknown>): ApprovalInstance {\n return {\n id: row['id'] as string,\n tenantId: row['tenant_id'] as string,\n templateId: '',\n templateName: row['template_name'] as string,\n documentId: row['document_id'] as string,\n documentType: row['document_type'] as string,\n submittedBy: row['submitted_by'] as string,\n status: row['status'] as ApprovalInstance['status'],\n currentLevel: row['current_level'] as number,\n version: row['version'] as number,\n idempotencyKey: (row['idempotency_key'] as string | null) ?? undefined,\n data: row['data'] as Record<string, unknown>,\n metadata: row['metadata'] as Record<string, unknown>,\n levels: row['levels'] as ApprovalInstance['levels'],\n auditLog: [],\n parentInstanceId: (row['parent_instance_id'] as string | null) ?? undefined,\n expiresAt: row['expires_at'] ? new Date(row['expires_at'] as string) : undefined,\n deadlineAction: (row['deadline_action'] as 'cancel' | 'reject' | null) ?? undefined,\n slaDeadlineAt: row['sla_deadline_at'] ? new Date(row['sla_deadline_at'] as string) : undefined,\n slaBreachedAt: row['sla_breached_at'] ? new Date(row['sla_breached_at'] as string) : undefined,\n templateSnapshot: (row['template_snapshot'] as ApprovalInstance['templateSnapshot'] | null) ?? undefined,\n createdAt: new Date(row['created_at'] as string),\n updatedAt: new Date(row['updated_at'] as string),\n };\n }\n}\n"]}
@@ -0,0 +1,45 @@
1
+ import * as tls from 'tls';
2
+ import * as pg from 'pg';
3
+ import { I as IStorageAdapter, A as ApprovalTemplate, a as ApprovalInstance, P as PaginationOpts, b as PaginatedResult, c as InstanceFilter, d as AuditEntry, C as CursorPaginationOpts, e as CursorPaginatedResult } from '../IStorageAdapter-RAiLF8bc.cjs';
4
+
5
+ interface PostgresAdapterOptions {
6
+ connectionString?: string;
7
+ /** Bring your own pre-configured pg.Pool instead of a connectionString. */
8
+ pool?: pg.Pool;
9
+ tablePrefix?: string;
10
+ /** PostgreSQL schema to use (default: 'public'). */
11
+ schema?: string;
12
+ /** Statement timeout in milliseconds applied to every query (default: none). */
13
+ statementTimeoutMs?: number;
14
+ ssl?: tls.ConnectionOptions;
15
+ }
16
+ declare class PostgresAdapter implements IStorageAdapter {
17
+ private readonly opts;
18
+ private _pool;
19
+ private readonly prefix;
20
+ private readonly schema;
21
+ private readonly statementTimeoutMs?;
22
+ private readonly externalPool;
23
+ constructor(opts: PostgresAdapterOptions);
24
+ private getPool;
25
+ private get p();
26
+ migrate(): Promise<void>;
27
+ /** Enable PostgreSQL Row Level Security for the instances and audit_log tables. */
28
+ enableRLS(): Promise<void>;
29
+ saveTemplate(template: ApprovalTemplate): Promise<void>;
30
+ getTemplate(tenantId: string, name: string): Promise<ApprovalTemplate | null>;
31
+ listTemplates(tenantId: string): Promise<ApprovalTemplate[]>;
32
+ saveInstance(instance: ApprovalInstance): Promise<void>;
33
+ updateInstance(instance: ApprovalInstance, expectedVersion: number): Promise<void>;
34
+ getInstance(tenantId: string, id: string): Promise<ApprovalInstance | null>;
35
+ getInstancesByApprover(tenantId: string, approverId: string, opts?: PaginationOpts): Promise<PaginatedResult<ApprovalInstance>>;
36
+ getInstancesByFilter(tenantId: string, filter: InstanceFilter, opts?: PaginationOpts): Promise<PaginatedResult<ApprovalInstance>>;
37
+ getOverdueInstances(tenantId: string, asOf: Date): Promise<ApprovalInstance[]>;
38
+ getIdempotentInstance(tenantId: string, idempotencyKey: string): Promise<ApprovalInstance | null>;
39
+ appendAuditEntry(tenantId: string, instanceId: string, entry: AuditEntry): Promise<void>;
40
+ getInstancesByCursor(tenantId: string, filter: InstanceFilter, opts: CursorPaginationOpts): Promise<CursorPaginatedResult<ApprovalInstance>>;
41
+ end(): Promise<void>;
42
+ private rowToInstance;
43
+ }
44
+
45
+ export { PostgresAdapter, type PostgresAdapterOptions };