@siglo/postgres 0.0.0-stage → 1.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.
- package/LICENSE +21 -0
- package/README.md +259 -2
- package/dist/adapter.d.ts +47 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +380 -0
- package/dist/adapter.js.map +1 -0
- package/dist/idempotency.d.ts +17 -0
- package/dist/idempotency.d.ts.map +1 -0
- package/dist/idempotency.js +109 -0
- package/dist/idempotency.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +20 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +59 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2017 Jerico de guzman
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,260 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @siglo/postgres Adapter Guide
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`@siglo/postgres` provides an enterprise ACID-compliant persistence adapter for `Siglo` workflows using PostgreSQL. It offers durable execution checkpointing, immutable context mutation audit event logging, automated table initialization, schema indexing, retention policy cleanup, and distributed atomic idempotency claim locking.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Overview & Architecture
|
|
8
|
+
|
|
9
|
+
When workflows coordinate mission-critical financial, compliance, or enterprise transactions, state persistence must survive node failures and network partitions:
|
|
10
|
+
|
|
11
|
+
```mermaid
|
|
12
|
+
flowchart TD
|
|
13
|
+
Engine["Siglo Engine"]
|
|
14
|
+
|
|
15
|
+
subgraph Adapters["@siglo/postgres"]
|
|
16
|
+
direction LR
|
|
17
|
+
SA["PostgresStorageAdapter<br/>• saveCheckpoint()<br/>• getCheckpoint()<br/>• onContextChange()<br/>• cleanupOldCheckpoints()"]
|
|
18
|
+
IS["PostgresIdempotencyStore<br/>• claim()<br/>• complete()<br/>• fail()<br/>• cleanupExpired()"]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
subgraph DB["PostgreSQL Database"]
|
|
22
|
+
direction LR
|
|
23
|
+
T1["siglo_checkpoints<br/>• stack_id (PK)<br/>• namespace<br/>• state (JSONB)<br/>• created_at / updated"]
|
|
24
|
+
T2["siglo_context_events<br/>• id (BIGSERIAL PK)<br/>• stack_id, step_id<br/>• namespace, field<br/>• value (JSONB), created_at"]
|
|
25
|
+
T3["siglo_idempotency<br/>• key (VARCHAR(512) PK)<br/>• status, result (JSONB)<br/>• expires_at, created_at"]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
Engine --> SA
|
|
29
|
+
Engine --> IS
|
|
30
|
+
SA --> T1
|
|
31
|
+
SA --> T2
|
|
32
|
+
IS --> T3
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 2. Installation & Requirements
|
|
38
|
+
|
|
39
|
+
Install `@siglo/postgres` and `pg`:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm install @siglo/core @siglo/postgres pg
|
|
43
|
+
npm install --save-dev @types/pg
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Peer dependency matrix:
|
|
47
|
+
- `Siglo`: `*` (v1.0.0+)
|
|
48
|
+
- `pg`: `^8.0.0`
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 3. Database Schema
|
|
53
|
+
|
|
54
|
+
Both `PostgresStorageAdapter` and `PostgresIdempotencyStore` support automated table creation via `ensureTables()` / `ensureTable()`.
|
|
55
|
+
|
|
56
|
+
### DDL Definitions:
|
|
57
|
+
|
|
58
|
+
```sql
|
|
59
|
+
-- 1. Checkpoint Storage Table
|
|
60
|
+
CREATE TABLE IF NOT EXISTS siglo_checkpoints (
|
|
61
|
+
stack_id VARCHAR(255) PRIMARY KEY,
|
|
62
|
+
namespace VARCHAR(255),
|
|
63
|
+
state JSONB NOT NULL,
|
|
64
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
65
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
66
|
+
);
|
|
67
|
+
CREATE INDEX IF NOT EXISTS idx_siglo_checkpoints_namespace ON siglo_checkpoints(namespace);
|
|
68
|
+
|
|
69
|
+
-- 2. Context Mutation Audit Log
|
|
70
|
+
CREATE TABLE IF NOT EXISTS siglo_context_events (
|
|
71
|
+
id BIGSERIAL PRIMARY KEY,
|
|
72
|
+
stack_id VARCHAR(255) NOT NULL,
|
|
73
|
+
step_id VARCHAR(255) NOT NULL,
|
|
74
|
+
namespace VARCHAR(255),
|
|
75
|
+
field VARCHAR(255) NOT NULL,
|
|
76
|
+
value JSONB,
|
|
77
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
78
|
+
);
|
|
79
|
+
CREATE INDEX IF NOT EXISTS idx_siglo_context_events_stack ON siglo_context_events(stack_id);
|
|
80
|
+
|
|
81
|
+
-- 3. Distributed Idempotency Table
|
|
82
|
+
CREATE TABLE IF NOT EXISTS siglo_idempotency (
|
|
83
|
+
key VARCHAR(512) PRIMARY KEY,
|
|
84
|
+
status VARCHAR(50) NOT NULL,
|
|
85
|
+
result JSONB,
|
|
86
|
+
expires_at TIMESTAMPTZ NOT NULL,
|
|
87
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
88
|
+
);
|
|
89
|
+
CREATE INDEX IF NOT EXISTS idx_siglo_idempotency_expires ON siglo_idempotency(expires_at);
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## 4. `PostgresStorageAdapter` API Reference
|
|
95
|
+
|
|
96
|
+
Implements the standard `StorageAdapter` interface.
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
import { PostgresStorageAdapter } from '@siglo/postgres';
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Constructor Options (`PostgresStorageAdapterOptions`)
|
|
103
|
+
|
|
104
|
+
| Option | Type | Default | Description |
|
|
105
|
+
| :--- | :--- | :--- | :--- |
|
|
106
|
+
| `client` | `PostgresClientInterface \| Pool` | *Required* | Active PostgreSQL `Pool` or `Client` instance. |
|
|
107
|
+
| `checkpointsTable` | `string` | `'siglo_checkpoints'` | Name of checkpoints table. Must match `/^[a-zA-Z_][a-zA-Z0-9_]*$/` (up to 63 chars) for SQL injection protection. |
|
|
108
|
+
| `eventsTable` | `string` | `'siglo_context_events'` | Name of context change audit table. Must match `/^[a-zA-Z_][a-zA-Z0-9_]*$/` (up to 63 chars) for SQL injection protection. |
|
|
109
|
+
| `recordContextChanges` | `boolean` | `true` | If `true`, writes all step context mutations into `eventsTable`. |
|
|
110
|
+
|
|
111
|
+
### Methods
|
|
112
|
+
|
|
113
|
+
#### `ensureTables(): Promise<void>`
|
|
114
|
+
Executes DDL statements to create tables and indexes if they do not already exist.
|
|
115
|
+
|
|
116
|
+
#### `saveCheckpoint(checkpoint: SerializedExecutionStack): Promise<void>`
|
|
117
|
+
Upserts a serialized execution stack by `stackId`, updating the `state` JSONB and `updated_at` timestamp.
|
|
118
|
+
|
|
119
|
+
#### `getCheckpoint(stackId: string): Promise<SerializedExecutionStack | undefined>`
|
|
120
|
+
Fetches the checkpoint by `stackId`. Returns `undefined` if no checkpoint is found.
|
|
121
|
+
|
|
122
|
+
#### `onContextChange(entry): Promise<void>`
|
|
123
|
+
Streams a field modification into `siglo_context_events` with step ID, field name, JSONB value, and high-resolution timestamp.
|
|
124
|
+
|
|
125
|
+
#### `cleanupOldCheckpoints(retentionDays?: number): Promise<number>`
|
|
126
|
+
Deletes checkpoints older than `retentionDays` (default `30` days). Returns the count of deleted rows.
|
|
127
|
+
|
|
128
|
+
#### `cleanupOldEvents(retentionDays?: number): Promise<number>`
|
|
129
|
+
Deletes audit records older than `retentionDays` (default `30` days). Returns the count of deleted rows.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## 5. `PostgresIdempotencyStore` API Reference
|
|
134
|
+
|
|
135
|
+
Implements the standard `IdempotencyStore` interface.
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
import { PostgresIdempotencyStore } from '@siglo/postgres';
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Constructor Options (`PostgresIdempotencyOptions`)
|
|
142
|
+
|
|
143
|
+
| Option | Type | Default | Description |
|
|
144
|
+
| :--- | :--- | :--- | :--- |
|
|
145
|
+
| `client` | `PostgresClientInterface \| Pool` | *Required* | Active PostgreSQL `Pool` or `Client` instance. |
|
|
146
|
+
| `tableName` | `string` | `'siglo_idempotency'` | Name of idempotency table. Must match `/^[a-zA-Z_][a-zA-Z0-9_]*$/` (up to 63 chars) for SQL injection protection. |
|
|
147
|
+
| `defaultTtlMs` | `number` | `86400000` (24h) | Fallback expiration TTL in milliseconds. |
|
|
148
|
+
|
|
149
|
+
### Methods
|
|
150
|
+
|
|
151
|
+
#### `ensureTable(): Promise<void>`
|
|
152
|
+
Initializes the idempotency schema and expiration index.
|
|
153
|
+
|
|
154
|
+
#### `claim(key: string, ttlMs?: number): Promise<'claimed' | 'completed' | 'in-progress'>`
|
|
155
|
+
Atomically attempts to insert an `'in-progress'` record. If the key exists:
|
|
156
|
+
- If expired, reclaims the lock and returns `'claimed'`.
|
|
157
|
+
- If completed, returns `'completed'`.
|
|
158
|
+
- If currently running, returns `'in-progress'`.
|
|
159
|
+
|
|
160
|
+
#### `complete(key: string, result: unknown, ttlMs?: number): Promise<void>`
|
|
161
|
+
Transitions key status to `'completed'`, serializes the result into JSONB, and sets expiration.
|
|
162
|
+
|
|
163
|
+
#### `fail(key: string, error: unknown): Promise<void>`
|
|
164
|
+
Deletes in-progress claims so subsequent retries can proceed.
|
|
165
|
+
|
|
166
|
+
#### `getResult(key: string): Promise<unknown | undefined>`
|
|
167
|
+
Retrieves the stored JSONB result for completed keys.
|
|
168
|
+
|
|
169
|
+
#### `cleanupExpired(): Promise<number>`
|
|
170
|
+
Prunes expired idempotency records (`WHERE expires_at < NOW()`).
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## 6. Complete Production Example
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
import { Pool } from 'pg';
|
|
178
|
+
import { Step, flow } from '@siglo/core';
|
|
179
|
+
import { PostgresStorageAdapter, PostgresIdempotencyStore } from '@siglo/postgres';
|
|
180
|
+
|
|
181
|
+
// 1. Initialize PostgreSQL Connection Pool
|
|
182
|
+
const pool = new Pool({
|
|
183
|
+
connectionString: process.env.DATABASE_URL || 'postgresql://postgres:postgres@localhost:5432/orders_db',
|
|
184
|
+
max: 20,
|
|
185
|
+
idleTimeoutMillis: 30000,
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// 2. Initialize and Register Storage Adapter
|
|
189
|
+
const storageAdapter = new PostgresStorageAdapter({
|
|
190
|
+
client: pool,
|
|
191
|
+
recordContextChanges: true,
|
|
192
|
+
});
|
|
193
|
+
await storageAdapter.ensureTables();
|
|
194
|
+
Step.useStorage(storageAdapter);
|
|
195
|
+
|
|
196
|
+
// 3. Initialize Idempotency Store
|
|
197
|
+
const idempotencyStore = new PostgresIdempotencyStore({
|
|
198
|
+
client: pool,
|
|
199
|
+
defaultTtlMs: 86400000, // 24 hours
|
|
200
|
+
});
|
|
201
|
+
await idempotencyStore.ensureTable();
|
|
202
|
+
|
|
203
|
+
// 4. Define Payment Settlement Workflow
|
|
204
|
+
interface SettlementInput {
|
|
205
|
+
settlementId: string;
|
|
206
|
+
accountId: string;
|
|
207
|
+
amount: number;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const debitSender = Step.create('debitSender', async (param: SettlementInput) => {
|
|
211
|
+
// Perform database debit operation within pool
|
|
212
|
+
return { debitReference: `dr_${param.settlementId}`, debited: true };
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
const creditReceiver = Step.create('creditReceiver', async (param: any) => {
|
|
216
|
+
// Perform receiver credit
|
|
217
|
+
return { creditReference: `cr_${param.settlementId}`, credited: true };
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
const settlementFlow = flow(debitSender, creditReceiver);
|
|
221
|
+
|
|
222
|
+
// 5. Execute with Distributed Idempotency Guard
|
|
223
|
+
async function runIdempotentSettlement(input: SettlementInput) {
|
|
224
|
+
const idempKey = `settle:${input.settlementId}`;
|
|
225
|
+
const claimStatus = await idempotencyStore.claim(idempKey, 30000); // 30s claim lock
|
|
226
|
+
|
|
227
|
+
if (claimStatus === 'completed') {
|
|
228
|
+
console.log('Settlement already processed. Returning cached result.');
|
|
229
|
+
return await idempotencyStore.getResult(idempKey);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (claimStatus === 'in-progress') {
|
|
233
|
+
throw new Error(`Settlement ${input.settlementId} is already running concurrently.`);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
const result = await settlementFlow.run(input, {
|
|
238
|
+
namespace: 'finance-prod',
|
|
239
|
+
});
|
|
240
|
+
const clean = {
|
|
241
|
+
settlementId: input.settlementId,
|
|
242
|
+
debitReference: result.debitReference(),
|
|
243
|
+
creditReference: result.creditReference(),
|
|
244
|
+
};
|
|
245
|
+
await idempotencyStore.complete(idempKey, clean);
|
|
246
|
+
return clean;
|
|
247
|
+
} catch (err) {
|
|
248
|
+
await idempotencyStore.fail(idempKey, err);
|
|
249
|
+
throw err;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// 6. Maintenance Routine: Automated Data Retention
|
|
254
|
+
async function runRetentionCleanup() {
|
|
255
|
+
const prunedCheckpoints = await storageAdapter.cleanupOldCheckpoints(14); // Retain 14 days
|
|
256
|
+
const prunedEvents = await storageAdapter.cleanupOldEvents(30); // Retain 30 days
|
|
257
|
+
const prunedIdemp = await idempotencyStore.cleanupExpired();
|
|
258
|
+
console.log(`Pruned: ${prunedCheckpoints} checkpoints, ${prunedEvents} events, ${prunedIdemp} idemp keys`);
|
|
259
|
+
}
|
|
260
|
+
```
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { StorageAdapter, SerializedExecutionStack } from '@siglo/core';
|
|
2
|
+
import { PostgresStorageAdapterOptions } from './types';
|
|
3
|
+
export declare class PostgresStorageAdapter implements StorageAdapter {
|
|
4
|
+
readonly name = "postgres";
|
|
5
|
+
private baseClient;
|
|
6
|
+
private scope;
|
|
7
|
+
private directClientBusy;
|
|
8
|
+
private get client();
|
|
9
|
+
private checkpointsTable;
|
|
10
|
+
private eventsTable;
|
|
11
|
+
private waitRecordsTable;
|
|
12
|
+
private recordContextChanges;
|
|
13
|
+
constructor(options: PostgresStorageAdapterOptions);
|
|
14
|
+
/**
|
|
15
|
+
* Initializes the necessary PostgreSQL schema and tables if they do not exist.
|
|
16
|
+
*/
|
|
17
|
+
ensureTables(): Promise<void>;
|
|
18
|
+
private trackWrite;
|
|
19
|
+
saveCheckpoint(checkpoint: SerializedExecutionStack, expectedVersion?: number): Promise<void>;
|
|
20
|
+
private persistCheckpoint;
|
|
21
|
+
getCheckpoint(stackId: string): Promise<SerializedExecutionStack | undefined>;
|
|
22
|
+
onContextChange(entry: {
|
|
23
|
+
namespace?: string;
|
|
24
|
+
stackId: string;
|
|
25
|
+
stepId: string;
|
|
26
|
+
field: string;
|
|
27
|
+
value: any;
|
|
28
|
+
timestamp: number;
|
|
29
|
+
}): Promise<void>;
|
|
30
|
+
cleanExpiredCheckpoints(): Promise<number>;
|
|
31
|
+
saveWaitRecord(record: any): Promise<void>;
|
|
32
|
+
private persistWaitRecord;
|
|
33
|
+
/** A database-session lock serializes continuation and compensation. Process death releases it. */
|
|
34
|
+
withWorkflowLock<T>(stackId: string, action: () => Promise<T>): Promise<T>;
|
|
35
|
+
cancelWaitRecord(correlationKey: string): Promise<void>;
|
|
36
|
+
cancelWorkflow(stackId: string): Promise<void>;
|
|
37
|
+
completeWaitRecord(correlationKey: string, claimantId: string): Promise<void>;
|
|
38
|
+
claimWaitRecord(correlationKey: string, claimantId?: string, options?: {
|
|
39
|
+
allowExpired?: boolean;
|
|
40
|
+
eventPayload?: any;
|
|
41
|
+
}): Promise<any>;
|
|
42
|
+
getWaitRecord(correlationKeyOrStackId: string): Promise<any | undefined>;
|
|
43
|
+
cleanupOldCheckpoints(retentionDays?: number): Promise<number>;
|
|
44
|
+
cleanupOldEvents(retentionDays?: number): Promise<number>;
|
|
45
|
+
cleanupOldWaitRecords(retentionDays?: number): Promise<number>;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,wBAAwB,EAA0E,MAAM,aAAa,CAAC;AAC/I,OAAO,EAAE,6BAA6B,EAA2B,MAAM,SAAS,CAAC;AASjF,qBAAa,sBAAuB,YAAW,cAAc;IACzD,SAAgB,IAAI,cAAc;IAClC,OAAO,CAAC,UAAU,CAA0B;IAC5C,OAAO,CAAC,KAAK,CAA0H;IACvI,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,KAAK,MAAM,GAAuH;IACzI,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,oBAAoB,CAAU;gBAE1B,OAAO,EAAE,6BAA6B;IAWlD;;OAEG;IACG,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IA+CnC,OAAO,CAAC,UAAU;IASlB,cAAc,CAAC,UAAU,EAAE,wBAAwB,EAAE,eAAe,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAI/E,iBAAiB;IA8EzB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,wBAAwB,GAAG,SAAS,CAAC;IAqB7E,eAAe,CAAC,KAAK,EAAE;QACzB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,GAAG,CAAC;QACX,SAAS,EAAE,MAAM,CAAC;KACrB,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBX,uBAAuB,IAAI,OAAO,CAAC,MAAM,CAAC;IAShD,cAAc,CAAC,MAAM,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;YAI5B,iBAAiB;IAgD/B,mGAAmG;IAC7F,gBAAgB,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAyB1E,gBAAgB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvD,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAO9C,kBAAkB,CAAC,cAAc,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAM7E,eAAe,CAAC,cAAc,EAAE,MAAM,EAAE,UAAU,GAAE,MAAiB,EAAE,OAAO,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,OAAO,CAAC;QAAC,YAAY,CAAC,EAAE,GAAG,CAAA;KAAE,GAAG,OAAO,CAAC,GAAG,CAAC;IAuC9I,aAAa,CAAC,uBAAuB,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,GAAG,SAAS,CAAC;IA2CxE,qBAAqB,CAAC,aAAa,GAAE,MAAW,GAAG,OAAO,CAAC,MAAM,CAAC;IAQlE,gBAAgB,CAAC,aAAa,GAAE,MAAW,GAAG,OAAO,CAAC,MAAM,CAAC;IAQ7D,qBAAqB,CAAC,aAAa,GAAE,MAAW,GAAG,OAAO,CAAC,MAAM,CAAC;CAO3E"}
|
package/dist/adapter.js
ADDED
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PostgresStorageAdapter = void 0;
|
|
4
|
+
const node_async_hooks_1 = require("node:async_hooks");
|
|
5
|
+
const core_1 = require("@siglo/core");
|
|
6
|
+
function validateIdentifier(name, paramName) {
|
|
7
|
+
if (!name || typeof name !== 'string' || !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name) || name.length > 63) {
|
|
8
|
+
throw new Error(`Invalid SQL identifier for ${paramName}: "${name}". Must be 1-63 alphanumeric characters or underscores, starting with a letter or underscore.`);
|
|
9
|
+
}
|
|
10
|
+
return name;
|
|
11
|
+
}
|
|
12
|
+
class PostgresStorageAdapter {
|
|
13
|
+
name = 'postgres';
|
|
14
|
+
baseClient;
|
|
15
|
+
scope = new node_async_hooks_1.AsyncLocalStorage();
|
|
16
|
+
directClientBusy = false;
|
|
17
|
+
get client() { return this.scope.getStore()?.active ? this.scope.getStore().client : this.baseClient; }
|
|
18
|
+
checkpointsTable;
|
|
19
|
+
eventsTable;
|
|
20
|
+
waitRecordsTable;
|
|
21
|
+
recordContextChanges;
|
|
22
|
+
constructor(options) {
|
|
23
|
+
if (!options || !options.client) {
|
|
24
|
+
throw new Error('PostgresStorageAdapter requires a valid Postgres client or pool instance.');
|
|
25
|
+
}
|
|
26
|
+
this.baseClient = options.client;
|
|
27
|
+
this.checkpointsTable = validateIdentifier(options.checkpointsTable || 'siglo_checkpoints', 'checkpointsTable');
|
|
28
|
+
this.eventsTable = validateIdentifier(options.eventsTable || 'siglo_context_events', 'eventsTable');
|
|
29
|
+
this.waitRecordsTable = validateIdentifier(options.waitRecordsTable || 'siglo_wait_records', 'waitRecordsTable');
|
|
30
|
+
this.recordContextChanges = options.recordContextChanges ?? true;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Initializes the necessary PostgreSQL schema and tables if they do not exist.
|
|
34
|
+
*/
|
|
35
|
+
async ensureTables() {
|
|
36
|
+
await this.client.query(`
|
|
37
|
+
CREATE TABLE IF NOT EXISTS ${this.checkpointsTable} (
|
|
38
|
+
stack_id VARCHAR(255) PRIMARY KEY,
|
|
39
|
+
namespace VARCHAR(255),
|
|
40
|
+
state JSONB NOT NULL,
|
|
41
|
+
version BIGINT NOT NULL DEFAULT 1,
|
|
42
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
43
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
44
|
+
);
|
|
45
|
+
ALTER TABLE ${this.checkpointsTable} ADD COLUMN IF NOT EXISTS version BIGINT NOT NULL DEFAULT 1;
|
|
46
|
+
CREATE INDEX IF NOT EXISTS idx_${this.checkpointsTable}_namespace ON ${this.checkpointsTable}(namespace);
|
|
47
|
+
|
|
48
|
+
CREATE TABLE IF NOT EXISTS ${this.eventsTable} (
|
|
49
|
+
id BIGSERIAL PRIMARY KEY,
|
|
50
|
+
stack_id VARCHAR(255) NOT NULL,
|
|
51
|
+
step_id VARCHAR(255) NOT NULL,
|
|
52
|
+
namespace VARCHAR(255),
|
|
53
|
+
field VARCHAR(255) NOT NULL,
|
|
54
|
+
value JSONB,
|
|
55
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
56
|
+
);
|
|
57
|
+
CREATE INDEX IF NOT EXISTS idx_${this.eventsTable}_stack ON ${this.eventsTable}(stack_id);
|
|
58
|
+
|
|
59
|
+
CREATE TABLE IF NOT EXISTS ${this.waitRecordsTable} (
|
|
60
|
+
correlation_key VARCHAR(255) PRIMARY KEY,
|
|
61
|
+
stack_id VARCHAR(255) NOT NULL,
|
|
62
|
+
namespace VARCHAR(255),
|
|
63
|
+
event_name VARCHAR(255),
|
|
64
|
+
status VARCHAR(50) NOT NULL DEFAULT 'WAITING',
|
|
65
|
+
continuation_step_index INT NOT NULL DEFAULT 0,
|
|
66
|
+
version VARCHAR(50),
|
|
67
|
+
data JSONB,
|
|
68
|
+
resumed_by VARCHAR(255),
|
|
69
|
+
resumed_at TIMESTAMPTZ,
|
|
70
|
+
expires_at TIMESTAMPTZ,
|
|
71
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
72
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
73
|
+
);
|
|
74
|
+
ALTER TABLE ${this.waitRecordsTable} ADD COLUMN IF NOT EXISTS resume_payload JSONB;
|
|
75
|
+
ALTER TABLE ${this.waitRecordsTable} ADD COLUMN IF NOT EXISTS locked_by TEXT;
|
|
76
|
+
ALTER TABLE ${this.waitRecordsTable} ADD COLUMN IF NOT EXISTS locked_at TIMESTAMPTZ;
|
|
77
|
+
CREATE INDEX IF NOT EXISTS idx_${this.waitRecordsTable}_stack ON ${this.waitRecordsTable}(stack_id);
|
|
78
|
+
CREATE INDEX IF NOT EXISTS idx_${this.waitRecordsTable}_status ON ${this.waitRecordsTable}(status);
|
|
79
|
+
`);
|
|
80
|
+
}
|
|
81
|
+
trackWrite(action) {
|
|
82
|
+
const scope = this.scope.getStore();
|
|
83
|
+
if (!scope?.active)
|
|
84
|
+
return action();
|
|
85
|
+
const pending = scope.tail.then(action);
|
|
86
|
+
scope.tail = pending;
|
|
87
|
+
pending.catch(() => { });
|
|
88
|
+
return pending;
|
|
89
|
+
}
|
|
90
|
+
saveCheckpoint(checkpoint, expectedVersion) {
|
|
91
|
+
return this.trackWrite(() => this.persistCheckpoint(checkpoint, expectedVersion));
|
|
92
|
+
}
|
|
93
|
+
async persistCheckpoint(checkpoint, expectedVersion) {
|
|
94
|
+
if (!checkpoint || !checkpoint.stackId) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const stateJson = JSON.stringify(checkpoint);
|
|
98
|
+
const namespace = checkpoint.namespace || null;
|
|
99
|
+
const versionToExpect = expectedVersion !== undefined
|
|
100
|
+
? expectedVersion
|
|
101
|
+
: (checkpoint.occVersion !== undefined ? checkpoint.occVersion : undefined);
|
|
102
|
+
if (versionToExpect !== undefined) {
|
|
103
|
+
const updateRes = await this.client.query(`UPDATE ${this.checkpointsTable}
|
|
104
|
+
SET state = CASE WHEN state->>'status' = 'cancelled' THEN jsonb_set($1::jsonb, '{status}', '"cancelled"') ELSE $1::jsonb END, namespace = $2, version = version + 1, updated_at = NOW()
|
|
105
|
+
WHERE stack_id = $3 AND version = $4
|
|
106
|
+
RETURNING version`, [stateJson, namespace, checkpoint.stackId, versionToExpect]);
|
|
107
|
+
if ((updateRes.rowCount || 0) === 0) {
|
|
108
|
+
const existingRes = await this.client.query(`SELECT version FROM ${this.checkpointsTable} WHERE stack_id = $1`, [checkpoint.stackId]);
|
|
109
|
+
const currentVersion = existingRes.rows && existingRes.rows.length > 0
|
|
110
|
+
? Number(existingRes.rows[0].version)
|
|
111
|
+
: undefined;
|
|
112
|
+
throw new core_1.OptimisticLockException(checkpoint.stackId, currentVersion, versionToExpect);
|
|
113
|
+
}
|
|
114
|
+
if (updateRes.rows && updateRes.rows.length > 0) {
|
|
115
|
+
checkpoint.occVersion = Number(updateRes.rows[0].version);
|
|
116
|
+
checkpoint.revision = checkpoint.occVersion;
|
|
117
|
+
}
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const insertRes = await this.client.query(`INSERT INTO ${this.checkpointsTable} (stack_id, namespace, state, version, updated_at)
|
|
121
|
+
VALUES ($1, $2, $3::jsonb, 1, NOW())
|
|
122
|
+
ON CONFLICT (stack_id) DO NOTHING`, [checkpoint.stackId, namespace, stateJson]);
|
|
123
|
+
if ((insertRes.rowCount || 0) > 0) {
|
|
124
|
+
checkpoint.occVersion = 1;
|
|
125
|
+
checkpoint.revision = 1;
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const existingRes = await this.client.query(`SELECT version FROM ${this.checkpointsTable} WHERE stack_id = $1`, [checkpoint.stackId]);
|
|
129
|
+
if (existingRes.rows && existingRes.rows.length > 0) {
|
|
130
|
+
const curr = Number(existingRes.rows[0].version);
|
|
131
|
+
const updateRes = await this.client.query(`UPDATE ${this.checkpointsTable}
|
|
132
|
+
SET state = CASE WHEN state->>'status' = 'cancelled' THEN jsonb_set($1::jsonb, '{status}', '"cancelled"') ELSE $1::jsonb END, namespace = $2, version = version + 1, updated_at = NOW()
|
|
133
|
+
WHERE stack_id = $3 AND version = $4
|
|
134
|
+
RETURNING version`, [stateJson, namespace, checkpoint.stackId, curr]);
|
|
135
|
+
if ((updateRes.rowCount || 0) === 0) {
|
|
136
|
+
const refreshed = await this.client.query(`SELECT version FROM ${this.checkpointsTable} WHERE stack_id = $1`, [checkpoint.stackId]);
|
|
137
|
+
const actual = refreshed.rows && refreshed.rows.length > 0 ? Number(refreshed.rows[0].version) : undefined;
|
|
138
|
+
throw new core_1.OptimisticLockException(checkpoint.stackId, actual, curr);
|
|
139
|
+
}
|
|
140
|
+
if (updateRes.rows && updateRes.rows.length > 0) {
|
|
141
|
+
checkpoint.occVersion = Number(updateRes.rows[0].version);
|
|
142
|
+
checkpoint.revision = checkpoint.occVersion;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
async getCheckpoint(stackId) {
|
|
147
|
+
if (!stackId) {
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
const res = await this.client.query(`SELECT state, version FROM ${this.checkpointsTable} WHERE stack_id = $1`, [stackId]);
|
|
151
|
+
if (!res.rows || res.rows.length === 0) {
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
154
|
+
const row = res.rows[0];
|
|
155
|
+
const rowState = row.state;
|
|
156
|
+
const checkpoint = typeof rowState === 'string' ? JSON.parse(rowState) : rowState;
|
|
157
|
+
if (checkpoint && row.version !== undefined) {
|
|
158
|
+
checkpoint.occVersion = Number(row.version);
|
|
159
|
+
checkpoint.revision = Number(row.version);
|
|
160
|
+
}
|
|
161
|
+
return checkpoint;
|
|
162
|
+
}
|
|
163
|
+
async onContextChange(entry) {
|
|
164
|
+
if (!this.recordContextChanges) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
try {
|
|
168
|
+
const valJson = typeof entry.value === 'object' && entry.value !== null
|
|
169
|
+
? JSON.stringify(entry.value)
|
|
170
|
+
: JSON.stringify({ value: entry.value });
|
|
171
|
+
await this.client.query(`INSERT INTO ${this.eventsTable} (stack_id, step_id, namespace, field, value, created_at)
|
|
172
|
+
VALUES ($1, $2, $3, $4, $5::jsonb, to_timestamp($6 / 1000.0))`, [entry.stackId, entry.stepId, entry.namespace || null, entry.field, valJson, entry.timestamp]);
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
// Non-blocking telemetry stream
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
async cleanExpiredCheckpoints() {
|
|
179
|
+
const res = await this.client.query(`DELETE FROM ${this.checkpointsTable}
|
|
180
|
+
WHERE (state->>'expiresAt') IS NOT NULL
|
|
181
|
+
AND (state->>'expiresAt')::bigint <= (EXTRACT(EPOCH FROM NOW()) * 1000)::bigint`);
|
|
182
|
+
return res.rowCount || 0;
|
|
183
|
+
}
|
|
184
|
+
saveWaitRecord(record) {
|
|
185
|
+
return this.trackWrite(() => this.persistWaitRecord(record));
|
|
186
|
+
}
|
|
187
|
+
async persistWaitRecord(record) {
|
|
188
|
+
if (!record || (!record.correlationKey && !record.stackId)) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const corrKey = record.correlationKey || record.stackId;
|
|
192
|
+
const stackId = record.stackId || record.correlationKey;
|
|
193
|
+
const dataJson = record.data ? JSON.stringify(record.data) : null;
|
|
194
|
+
const expiresAt = record.expiresAt ? new Date(record.expiresAt) : null;
|
|
195
|
+
const status = (record.status || 'WAITING').toUpperCase();
|
|
196
|
+
await this.client.query(`INSERT INTO ${this.waitRecordsTable}
|
|
197
|
+
(correlation_key, stack_id, namespace, event_name, status, continuation_step_index, version, data, expires_at, created_at, updated_at)
|
|
198
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8::jsonb, $9, NOW(), NOW())
|
|
199
|
+
ON CONFLICT (correlation_key)
|
|
200
|
+
DO UPDATE SET
|
|
201
|
+
stack_id = EXCLUDED.stack_id,
|
|
202
|
+
namespace = EXCLUDED.namespace,
|
|
203
|
+
event_name = EXCLUDED.event_name,
|
|
204
|
+
status = EXCLUDED.status,
|
|
205
|
+
continuation_step_index = EXCLUDED.continuation_step_index,
|
|
206
|
+
version = EXCLUDED.version,
|
|
207
|
+
data = EXCLUDED.data,
|
|
208
|
+
expires_at = EXCLUDED.expires_at,
|
|
209
|
+
updated_at = NOW()
|
|
210
|
+
WHERE ${this.waitRecordsTable}.status = 'WAITING' AND ${this.waitRecordsTable}.stack_id = EXCLUDED.stack_id`, [
|
|
211
|
+
corrKey,
|
|
212
|
+
stackId,
|
|
213
|
+
record.namespace || null,
|
|
214
|
+
record.eventName || null,
|
|
215
|
+
status,
|
|
216
|
+
record.continuationStepIndex || 0,
|
|
217
|
+
record.version !== undefined ? String(record.version) : null,
|
|
218
|
+
dataJson,
|
|
219
|
+
expiresAt
|
|
220
|
+
]);
|
|
221
|
+
if (stackId) {
|
|
222
|
+
const cp = await this.getCheckpoint(stackId);
|
|
223
|
+
if (cp) {
|
|
224
|
+
cp.metadata = { ...(cp.metadata || {}), waitRecord: record };
|
|
225
|
+
await this.persistCheckpoint(cp);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
/** A database-session lock serializes continuation and compensation. Process death releases it. */
|
|
230
|
+
async withWorkflowLock(stackId, action) {
|
|
231
|
+
if (this.scope.getStore()?.active && this.scope.getStore()?.stackId === stackId)
|
|
232
|
+
return action();
|
|
233
|
+
const pool = this.baseClient;
|
|
234
|
+
const pooled = 'totalCount' in pool;
|
|
235
|
+
if (!pooled && this.directClientBusy)
|
|
236
|
+
throw new core_1.DuplicateEventException(stackId);
|
|
237
|
+
if (!pooled)
|
|
238
|
+
this.directClientBusy = true;
|
|
239
|
+
let client;
|
|
240
|
+
let locked = false;
|
|
241
|
+
try {
|
|
242
|
+
client = pooled ? await pool.connect() : pool;
|
|
243
|
+
const result = await client.query('SELECT pg_try_advisory_lock(hashtextextended($1, 0)) AS locked', [`siglo:workflow:${stackId}`]);
|
|
244
|
+
locked = result.rows[0]?.locked === true;
|
|
245
|
+
if (!locked)
|
|
246
|
+
throw new core_1.DuplicateEventException(stackId);
|
|
247
|
+
const scope = { stackId, client, tail: Promise.resolve(), active: true };
|
|
248
|
+
return await this.scope.run(scope, async () => {
|
|
249
|
+
try {
|
|
250
|
+
return await action();
|
|
251
|
+
}
|
|
252
|
+
finally {
|
|
253
|
+
try {
|
|
254
|
+
await scope.tail;
|
|
255
|
+
}
|
|
256
|
+
finally {
|
|
257
|
+
scope.active = false;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
finally {
|
|
263
|
+
try {
|
|
264
|
+
if (locked)
|
|
265
|
+
await client.query('SELECT pg_advisory_unlock(hashtextextended($1, 0))', [`siglo:workflow:${stackId}`]);
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
if (pooled) {
|
|
269
|
+
client?.release(error);
|
|
270
|
+
client = undefined;
|
|
271
|
+
}
|
|
272
|
+
throw error;
|
|
273
|
+
}
|
|
274
|
+
finally {
|
|
275
|
+
if (pooled)
|
|
276
|
+
client?.release();
|
|
277
|
+
else
|
|
278
|
+
this.directClientBusy = false;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
async cancelWaitRecord(correlationKey) {
|
|
283
|
+
await this.client.query(`UPDATE ${this.waitRecordsTable} SET status = 'CANCELLED', updated_at = NOW() WHERE correlation_key = $1`, [correlationKey]);
|
|
284
|
+
}
|
|
285
|
+
async cancelWorkflow(stackId) {
|
|
286
|
+
// Drain earlier checkpoint/wait writes before setting the cancellation barrier.
|
|
287
|
+
await this.scope.getStore()?.tail;
|
|
288
|
+
await this.client.query(`UPDATE ${this.waitRecordsTable} SET status = 'CANCELLED', updated_at = NOW() WHERE stack_id = $1`, [stackId]);
|
|
289
|
+
await this.client.query(`UPDATE ${this.checkpointsTable} SET state = jsonb_set(state, '{status}', '"cancelled"'), version = version + 1 WHERE stack_id = $1`, [stackId]);
|
|
290
|
+
}
|
|
291
|
+
async completeWaitRecord(correlationKey, claimantId) {
|
|
292
|
+
await this.scope.getStore()?.tail;
|
|
293
|
+
await this.client.query(`UPDATE ${this.waitRecordsTable} SET status = 'RESUMED', resumed_at = NOW(), resumed_by = $1, updated_at = NOW()
|
|
294
|
+
WHERE correlation_key = $2 AND status = 'RESUMING' AND locked_by = $1`, [claimantId, correlationKey]);
|
|
295
|
+
}
|
|
296
|
+
async claimWaitRecord(correlationKey, claimantId = 'worker', options) {
|
|
297
|
+
if (!correlationKey) {
|
|
298
|
+
throw new Error('claimWaitRecord requires a valid correlationKey.');
|
|
299
|
+
}
|
|
300
|
+
const res = await this.client.query(`UPDATE ${this.waitRecordsTable}
|
|
301
|
+
SET status = 'RESUMING', locked_at = NOW(), locked_by = $1,
|
|
302
|
+
resume_payload = COALESCE(resume_payload, $3::jsonb), updated_at = NOW()
|
|
303
|
+
WHERE correlation_key = $2
|
|
304
|
+
AND (status = 'WAITING' OR (status = 'RESUMING' AND $5))
|
|
305
|
+
AND (resume_payload IS NOT NULL OR $4 OR expires_at IS NULL OR expires_at > NOW())
|
|
306
|
+
RETURNING *`, [claimantId, correlationKey, JSON.stringify(options?.eventPayload ?? {}), options?.allowExpired === true, this.scope.getStore()?.active === true]);
|
|
307
|
+
if (!res.rows || res.rows.length === 0) {
|
|
308
|
+
const record = await this.getWaitRecord(correlationKey);
|
|
309
|
+
if (record?.status === 'WAITING' && record.expiresAt !== undefined && record.expiresAt <= Date.now()) {
|
|
310
|
+
throw new core_1.ExpiredWaitException(correlationKey, record.expiresAt, record.eventName);
|
|
311
|
+
}
|
|
312
|
+
throw new core_1.DuplicateEventException(correlationKey);
|
|
313
|
+
}
|
|
314
|
+
const row = res.rows[0];
|
|
315
|
+
return {
|
|
316
|
+
stackId: row.stack_id,
|
|
317
|
+
correlationKey: row.correlation_key,
|
|
318
|
+
namespace: row.namespace,
|
|
319
|
+
eventName: row.event_name,
|
|
320
|
+
status: row.status,
|
|
321
|
+
continuationStepIndex: row.continuation_step_index !== undefined ? Number(row.continuation_step_index) : 0,
|
|
322
|
+
version: row.version,
|
|
323
|
+
data: typeof row.data === 'string' ? JSON.parse(row.data) : row.data,
|
|
324
|
+
resumePayload: typeof row.resume_payload === 'string' ? JSON.parse(row.resume_payload) : row.resume_payload,
|
|
325
|
+
expiresAt: row.expires_at ? new Date(row.expires_at).getTime() : undefined,
|
|
326
|
+
resumedBy: row.resumed_by,
|
|
327
|
+
resumedAt: row.resumed_at ? new Date(row.resumed_at).toISOString() : undefined,
|
|
328
|
+
createdAt: row.created_at ? new Date(row.created_at).getTime() : undefined
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
async getWaitRecord(correlationKeyOrStackId) {
|
|
332
|
+
if (!correlationKeyOrStackId) {
|
|
333
|
+
return undefined;
|
|
334
|
+
}
|
|
335
|
+
const res = await this.client.query(`SELECT * FROM ${this.waitRecordsTable}
|
|
336
|
+
WHERE correlation_key = $1 OR stack_id = $1 LIMIT 1`, [correlationKeyOrStackId]);
|
|
337
|
+
if (res.rows && res.rows.length > 0) {
|
|
338
|
+
const row = res.rows[0];
|
|
339
|
+
return {
|
|
340
|
+
stackId: row.stack_id,
|
|
341
|
+
correlationKey: row.correlation_key,
|
|
342
|
+
namespace: row.namespace,
|
|
343
|
+
eventName: row.event_name,
|
|
344
|
+
status: row.status,
|
|
345
|
+
continuationStepIndex: row.continuation_step_index !== undefined ? Number(row.continuation_step_index) : 0,
|
|
346
|
+
version: row.version,
|
|
347
|
+
data: typeof row.data === 'string' ? JSON.parse(row.data) : row.data,
|
|
348
|
+
resumePayload: typeof row.resume_payload === 'string' ? JSON.parse(row.resume_payload) : row.resume_payload,
|
|
349
|
+
resumedBy: row.resumed_by,
|
|
350
|
+
resumedAt: row.resumed_at ? new Date(row.resumed_at).toISOString() : undefined,
|
|
351
|
+
expiresAt: row.expires_at ? new Date(row.expires_at).getTime() : undefined,
|
|
352
|
+
createdAt: row.created_at ? new Date(row.created_at).getTime() : undefined
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
const cpRes = await this.client.query(`SELECT state FROM ${this.checkpointsTable}
|
|
356
|
+
WHERE stack_id = $1
|
|
357
|
+
OR state->'metadata'->'waitRecord'->>'correlationKey' = $1
|
|
358
|
+
OR state->'metadata'->'waitRecord'->>'stackId' = $1 LIMIT 1`, [correlationKeyOrStackId]);
|
|
359
|
+
if (!cpRes.rows || cpRes.rows.length === 0) {
|
|
360
|
+
return undefined;
|
|
361
|
+
}
|
|
362
|
+
const rowState = cpRes.rows[0].state;
|
|
363
|
+
const checkpoint = typeof rowState === 'string' ? JSON.parse(rowState) : rowState;
|
|
364
|
+
return checkpoint?.metadata?.waitRecord;
|
|
365
|
+
}
|
|
366
|
+
async cleanupOldCheckpoints(retentionDays = 30) {
|
|
367
|
+
const res = await this.client.query(`DELETE FROM ${this.checkpointsTable} WHERE updated_at < NOW() - make_interval(days => $1)`, [retentionDays]);
|
|
368
|
+
return res.rowCount || 0;
|
|
369
|
+
}
|
|
370
|
+
async cleanupOldEvents(retentionDays = 30) {
|
|
371
|
+
const res = await this.client.query(`DELETE FROM ${this.eventsTable} WHERE created_at < NOW() - make_interval(days => $1)`, [retentionDays]);
|
|
372
|
+
return res.rowCount || 0;
|
|
373
|
+
}
|
|
374
|
+
async cleanupOldWaitRecords(retentionDays = 30) {
|
|
375
|
+
const res = await this.client.query(`DELETE FROM ${this.waitRecordsTable} WHERE updated_at < NOW() - make_interval(days => $1)`, [retentionDays]);
|
|
376
|
+
return res.rowCount || 0;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
exports.PostgresStorageAdapter = PostgresStorageAdapter;
|
|
380
|
+
//# sourceMappingURL=adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { IdempotencyStore } from '@siglo/core';
|
|
2
|
+
import { PostgresIdempotencyOptions } from './types';
|
|
3
|
+
export declare class PostgresIdempotencyStore implements IdempotencyStore {
|
|
4
|
+
private client;
|
|
5
|
+
private tableName;
|
|
6
|
+
private defaultTtlMs;
|
|
7
|
+
constructor(options: PostgresIdempotencyOptions);
|
|
8
|
+
ensureTable(): Promise<void>;
|
|
9
|
+
claim(key: string, ttlMs?: number): Promise<'claimed' | 'completed' | 'in-progress'>;
|
|
10
|
+
complete(key: string, result: unknown, ttlMs?: number): Promise<void>;
|
|
11
|
+
fail(key: string, _error: unknown): Promise<void>;
|
|
12
|
+
getResult(key: string): Promise<unknown | undefined>;
|
|
13
|
+
get(key: string): Promise<unknown | undefined>;
|
|
14
|
+
getRecord(key: string): Promise<any | undefined>;
|
|
15
|
+
cleanupExpired(): Promise<number>;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=idempotency.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idempotency.d.ts","sourceRoot":"","sources":["../src/idempotency.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,0BAA0B,EAA2B,MAAM,SAAS,CAAC;AAS9E,qBAAa,wBAAyB,YAAW,gBAAgB;IAC7D,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,YAAY,CAAS;gBAEjB,OAAO,EAAE,0BAA0B;IASzC,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAa5B,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,WAAW,GAAG,aAAa,CAAC;IAkDpF,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAarE,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAQjD,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC;IAYpD,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC;IAI9C,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,GAAG,SAAS,CAAC;IAkBhD,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC;CAM1C"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PostgresIdempotencyStore = void 0;
|
|
4
|
+
function validateIdentifier(name, paramName = 'tableName') {
|
|
5
|
+
if (!name || typeof name !== 'string' || !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name) || name.length > 63) {
|
|
6
|
+
throw new Error(`Invalid SQL identifier for ${paramName}: "${name}". Must be 1-63 alphanumeric characters or underscores, starting with a letter or underscore.`);
|
|
7
|
+
}
|
|
8
|
+
return name;
|
|
9
|
+
}
|
|
10
|
+
class PostgresIdempotencyStore {
|
|
11
|
+
client;
|
|
12
|
+
tableName;
|
|
13
|
+
defaultTtlMs;
|
|
14
|
+
constructor(options) {
|
|
15
|
+
if (!options || !options.client) {
|
|
16
|
+
throw new Error('PostgresIdempotencyStore requires a valid Postgres client or pool instance.');
|
|
17
|
+
}
|
|
18
|
+
this.client = options.client;
|
|
19
|
+
this.tableName = validateIdentifier(options.tableName || 'siglo_idempotency', 'tableName');
|
|
20
|
+
this.defaultTtlMs = options.defaultTtlMs || 86400000; // 24 hours
|
|
21
|
+
}
|
|
22
|
+
async ensureTable() {
|
|
23
|
+
await this.client.query(`
|
|
24
|
+
CREATE TABLE IF NOT EXISTS ${this.tableName} (
|
|
25
|
+
key VARCHAR(512) PRIMARY KEY,
|
|
26
|
+
status VARCHAR(50) NOT NULL,
|
|
27
|
+
result JSONB,
|
|
28
|
+
expires_at TIMESTAMPTZ NOT NULL,
|
|
29
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
30
|
+
);
|
|
31
|
+
CREATE INDEX IF NOT EXISTS idx_${this.tableName}_expires ON ${this.tableName}(expires_at);
|
|
32
|
+
`);
|
|
33
|
+
}
|
|
34
|
+
async claim(key, ttlMs) {
|
|
35
|
+
const effectiveTtl = ttlMs || this.defaultTtlMs;
|
|
36
|
+
const now = Date.now();
|
|
37
|
+
const expiresAt = new Date(now + effectiveTtl);
|
|
38
|
+
// Attempt initial insert
|
|
39
|
+
const insertRes = await this.client.query(`INSERT INTO ${this.tableName} (key, status, expires_at, created_at)
|
|
40
|
+
VALUES ($1, 'in-progress', $2, NOW())
|
|
41
|
+
ON CONFLICT (key) DO NOTHING`, [key, expiresAt]);
|
|
42
|
+
if ((insertRes.rowCount || 0) > 0) {
|
|
43
|
+
return 'claimed';
|
|
44
|
+
}
|
|
45
|
+
// Key exists, inspect current status and expiration
|
|
46
|
+
const selectRes = await this.client.query(`SELECT status, result, expires_at FROM ${this.tableName} WHERE key = $1`, [key]);
|
|
47
|
+
if (!selectRes.rows || selectRes.rows.length === 0) {
|
|
48
|
+
return 'claimed';
|
|
49
|
+
}
|
|
50
|
+
const row = selectRes.rows[0];
|
|
51
|
+
const rowExpiresAt = new Date(row.expires_at).getTime();
|
|
52
|
+
// If previous claim expired, reclaim it
|
|
53
|
+
if (rowExpiresAt <= now) {
|
|
54
|
+
const updateRes = await this.client.query(`UPDATE ${this.tableName}
|
|
55
|
+
SET status = 'in-progress', expires_at = $2, result = NULL
|
|
56
|
+
WHERE key = $1 AND expires_at <= NOW()`, [key, expiresAt]);
|
|
57
|
+
if ((updateRes.rowCount || 0) > 0) {
|
|
58
|
+
return 'claimed';
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (row.status === 'completed') {
|
|
62
|
+
return 'completed';
|
|
63
|
+
}
|
|
64
|
+
return 'in-progress';
|
|
65
|
+
}
|
|
66
|
+
async complete(key, result, ttlMs) {
|
|
67
|
+
const effectiveTtl = ttlMs || this.defaultTtlMs;
|
|
68
|
+
const expiresAt = new Date(Date.now() + effectiveTtl);
|
|
69
|
+
const resultJson = JSON.stringify(result ?? null);
|
|
70
|
+
await this.client.query(`UPDATE ${this.tableName}
|
|
71
|
+
SET status = 'completed', result = $2::jsonb, expires_at = $3
|
|
72
|
+
WHERE key = $1`, [key, resultJson, expiresAt]);
|
|
73
|
+
}
|
|
74
|
+
async fail(key, _error) {
|
|
75
|
+
// Clear in-flight claim to permit retry
|
|
76
|
+
await this.client.query(`DELETE FROM ${this.tableName} WHERE key = $1 AND status = 'in-progress'`, [key]);
|
|
77
|
+
}
|
|
78
|
+
async getResult(key) {
|
|
79
|
+
const res = await this.client.query(`SELECT result FROM ${this.tableName} WHERE key = $1 AND status = 'completed'`, [key]);
|
|
80
|
+
if (!res.rows || res.rows.length === 0) {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
const resData = res.rows[0].result;
|
|
84
|
+
return typeof resData === 'string' ? JSON.parse(resData) : resData;
|
|
85
|
+
}
|
|
86
|
+
async get(key) {
|
|
87
|
+
return this.getResult(key);
|
|
88
|
+
}
|
|
89
|
+
async getRecord(key) {
|
|
90
|
+
const res = await this.client.query(`SELECT status, result, expires_at, created_at FROM ${this.tableName} WHERE key = $1`, [key]);
|
|
91
|
+
if (!res.rows || res.rows.length === 0) {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
const row = res.rows[0];
|
|
95
|
+
const resData = typeof row.result === 'string' ? JSON.parse(row.result) : row.result;
|
|
96
|
+
return {
|
|
97
|
+
status: row.status,
|
|
98
|
+
result: resData,
|
|
99
|
+
expiresAt: row.expires_at ? new Date(row.expires_at).getTime() : undefined,
|
|
100
|
+
claimedAt: row.created_at ? new Date(row.created_at).getTime() : Date.now()
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
async cleanupExpired() {
|
|
104
|
+
const res = await this.client.query(`DELETE FROM ${this.tableName} WHERE expires_at < NOW()`);
|
|
105
|
+
return res.rowCount || 0;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
exports.PostgresIdempotencyStore = PostgresIdempotencyStore;
|
|
109
|
+
//# sourceMappingURL=idempotency.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idempotency.js","sourceRoot":"","sources":["../src/idempotency.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./types"), exports);
|
|
18
|
+
__exportStar(require("./adapter"), exports);
|
|
19
|
+
__exportStar(require("./idempotency"), exports);
|
|
20
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,0CAAwB;AACxB,4CAA0B;AAC1B,gDAA8B"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface PostgresQueryResult<T = any> {
|
|
2
|
+
rows: T[];
|
|
3
|
+
rowCount: number | null;
|
|
4
|
+
}
|
|
5
|
+
export interface PostgresClientInterface {
|
|
6
|
+
query<T = any>(queryText: string, values?: any[]): Promise<PostgresQueryResult<T>>;
|
|
7
|
+
}
|
|
8
|
+
export interface PostgresStorageAdapterOptions {
|
|
9
|
+
client: PostgresClientInterface;
|
|
10
|
+
checkpointsTable?: string;
|
|
11
|
+
eventsTable?: string;
|
|
12
|
+
waitRecordsTable?: string;
|
|
13
|
+
recordContextChanges?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface PostgresIdempotencyOptions {
|
|
16
|
+
client: PostgresClientInterface;
|
|
17
|
+
tableName?: string;
|
|
18
|
+
defaultTtlMs?: number;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB,CAAC,CAAC,GAAG,GAAG;IACxC,IAAI,EAAE,CAAC,EAAE,CAAC;IACV,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,uBAAuB;IACpC,KAAK,CAAC,CAAC,GAAG,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC;CACtF;AAED,MAAM,WAAW,6BAA6B;IAC1C,MAAM,EAAE,uBAAuB,CAAC;IAChC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAClC;AAED,MAAM,WAAW,0BAA0B;IACvC,MAAM,EAAE,uBAAuB,CAAC;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACzB"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
CHANGED
|
@@ -1,6 +1,61 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@siglo/postgres",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Production PostgreSQL durable checkpoint, event audit, and idempotency adapter for Siglo workflows",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"require": "./dist/index.js",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./package.json": "./package.json"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public",
|
|
23
|
+
"registry": "https://registry.npmjs.org/"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsc -p tsconfig.json",
|
|
27
|
+
"prepublishOnly": "npm run build",
|
|
28
|
+
"test": "npm run build && mocha -r tsx 'src/**/*.test.ts'"
|
|
29
|
+
},
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/jsoftgem/siglo.git",
|
|
33
|
+
"directory": "packages/postgres"
|
|
34
|
+
},
|
|
35
|
+
"author": "Jerico de Guzman",
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/jsoftgem/siglo/issues"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://github.com/jsoftgem/siglo#readme",
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@siglo/core": "^1.0.0"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"pg": "^8.0.0"
|
|
46
|
+
},
|
|
47
|
+
"peerDependenciesMeta": {
|
|
48
|
+
"pg": {
|
|
49
|
+
"optional": true
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@types/chai": "^5.2.3",
|
|
54
|
+
"@types/mocha": "^10.0.10",
|
|
55
|
+
"@types/node": "^26.6.5",
|
|
56
|
+
"chai": "^3.5.0",
|
|
57
|
+
"mocha": "^12.0.3",
|
|
58
|
+
"tsx": "^4.23.15",
|
|
59
|
+
"typescript": "~5.7.0"
|
|
60
|
+
}
|
|
61
|
+
}
|