transactional-ai 0.1.0 → 0.2.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/README.md CHANGED
@@ -5,13 +5,17 @@ Implement the Saga Pattern with persistent rollback and state recovery for Long-
5
5
 
6
6
  [![npm version](https://img.shields.io/npm/v/transactional-ai.svg)](https://www.npmjs.com/package/transactional-ai)
7
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
+ ![CI](https://github.com/Grafikui/transactional-ai-control-plane/actions/workflows/ci.yml/badge.svg)
8
9
 
9
10
  ## Why use this?
11
+
10
12
  AI Agents are flaky. Steps fail, APIs time out, and hallucinations happen.
11
13
  `transactional-ai` gives you:
14
+
12
15
  1. **Automatic Rollbacks**: If step 3 fails, steps 2 and 1 are compensated (undone) automatically.
13
- 2. **Persistence**: Transactions survive process crashes using Redis or File storage.
14
- 3. **Observability**: Inspect running transactions via the built-in CLI.
16
+ 2. **Concurrency Safety**: Distributed locking prevents race conditions when scaling workers.
17
+ 3. **Persistence**: Transactions survive process crashes using Redis, Postgres, or File storage.
18
+ 4. **Resilience**: Built-in retry policies for flaky LLM APIs.
15
19
 
16
20
  ## Installation
17
21
 
@@ -24,63 +28,146 @@ npm install transactional-ai
24
28
  ## Quick Start
25
29
 
26
30
  ### 1. The "Litmus Test" (Basic Usage)
31
+
27
32
  Define a transaction where every action has a compensating rollback action.
28
33
 
29
34
  ```typescript
30
- import { Transaction } from 'transactional-ai';
35
+ import { Transaction } from "transactional-ai";
31
36
 
32
37
  // 1. Create a named transaction (required for resumability)
33
- const agent = new Transaction('user-onboarding-123');
38
+ const agent = new Transaction("user-onboarding-123");
34
39
 
35
40
  agent
36
41
  .step({
37
- name: 'create-file',
42
+ name: "create-file",
38
43
  execute: async (ctx) => {
39
- // Do the work
40
- const file = await googleDrive.createFile('report.txt');
41
- return file.id;
44
+ // Do the work
45
+ const file = await googleDrive.createFile("report.txt");
46
+ return file.id;
42
47
  },
43
48
  compensate: async (fileId) => {
44
- // Undo the work
45
- await googleDrive.deleteFile(fileId);
46
- }
49
+ // Undo the work
50
+ await googleDrive.deleteFile(fileId);
51
+ },
47
52
  })
48
53
  .step({
49
- name: 'email-report',
54
+ name: "email-report",
50
55
  execute: async (ctx) => {
51
- // Use previous results via context or external state
52
- await emailService.send(ctx.result);
56
+ // Use previous results via context or external state
57
+ await emailService.send(ctx.result);
53
58
  },
54
59
  compensate: async () => {
55
- await emailService.recallLast();
56
- }
60
+ await emailService.recallLast();
61
+ },
57
62
  });
58
63
 
59
64
  // 2. Run it
60
- await agent.run({ initialData: 'foo' });
65
+ await agent.run({ initialData: "foo" });
61
66
  ```
62
67
 
63
68
  ### Adding Persistence (Redis)
69
+
64
70
  To survive process crashes, simply provide a storage adapter.
65
71
 
66
72
  ```typescript
67
- import { Transaction, RedisStorage } from 'transactional-ai';
73
+ import { Transaction, RedisStorage } from "transactional-ai";
68
74
 
69
- const storage = new RedisStorage('redis://localhost:6379');
70
- const agent = new Transaction('workflow-id-555', storage);
75
+ const storage = new RedisStorage("redis://localhost:6379");
76
+ const agent = new Transaction("workflow-id-555", storage);
71
77
 
72
78
  // If the process crashes here, running this code again
73
79
  // will automatically SKIP completed steps and resume at the failure point.
74
80
  agent
75
- .step({ /* ... */ })
76
- .step({ /* ... */ });
81
+ .step({
82
+ /* ... */
83
+ })
84
+ .step({
85
+ /* ... */
86
+ });
77
87
 
78
88
  await agent.run();
79
89
  ```
80
90
 
91
+ ## Enterprise Features (v0.2.0)
92
+
93
+ ### 1. Distributed Locking (Redis)
94
+
95
+ Prevent race conditions when running multiple agent workers on the same Transaction ID.
96
+
97
+ ```typeScript
98
+ import { Transaction, RedisStorage, RedisLock } from 'transactional-ai';
99
+
100
+ const connection = 'redis://localhost:6379';
101
+ const storage = new RedisStorage(connection);
102
+ const lock = new RedisLock(connection);
103
+
104
+ const agent = new Transaction('tx-unique-id', storage, {
105
+ lock: lock,
106
+ lockTTL: 30000 // Hold lock for 30s
107
+ });
108
+
109
+ // Safe to run across multiple processes/servers
110
+ await agent.run();
111
+ ```
112
+
113
+ ### 2. Postgres Storage (SQL)
114
+
115
+ Use Postgres for strict ACID compliance and auditability.
116
+
117
+ Prerequisite: Run schema.sql in your database.
118
+
119
+ ```typeScript
120
+ import { Transaction, PostgresStorage } from 'transactional-ai';
121
+
122
+ const storage = new PostgresStorage('postgresql://user:pass@localhost:5432/mydb');
123
+ const agent = new Transaction('tx-id', storage);
124
+ ```
125
+
126
+ #### Setting Up Postgres
127
+
128
+ Before using PostgresStorage, create the required table in your database:
129
+
130
+ **Option 1: Run the provided schema**
131
+ ```bash
132
+ # From project root
133
+ psql -U your_user -d your_database -f schema.sql
134
+ ```
135
+
136
+ **Option 2: Copy-paste this SQL**
137
+ ```sql
138
+ CREATE TABLE IF NOT EXISTS transactions (
139
+ id VARCHAR(255) PRIMARY KEY,
140
+ status VARCHAR(50) NOT NULL,
141
+ step_stack JSONB NOT NULL,
142
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
143
+ updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
144
+ );
145
+
146
+ CREATE INDEX IF NOT EXISTS idx_transactions_status ON transactions(status);
147
+ ```
148
+
149
+ **Important:** The table must exist before running your first transaction. It will NOT be auto-created.
150
+
151
+ ### 3. Retry Policies
152
+
153
+ Handle flaky APIs (e.g., OpenAI 500 errors) automatically without failing the transaction.
154
+
155
+ ```typeScript
156
+ agent.step({
157
+ name: 'generate-text',
158
+ execute: async () => await openai.createCompletion(...),
159
+ compensate: async () => { /* ... */ },
160
+ retry: {
161
+ attempts: 3, // Try 3 times
162
+ backoffMs: 1000 // Wait 1s between attempts
163
+ }
164
+ });
165
+ ```
166
+
81
167
  ---
82
168
 
83
169
  ## CLI Inspector
170
+
84
171
  You don't need a complex dashboard to see what your agents are doing. Use the included CLI to inspect transaction logs directly from your terminal.
85
172
 
86
173
  ```bash
@@ -111,15 +198,17 @@ Output:
111
198
  ## Advanced Usage
112
199
 
113
200
  ### Audit Mode (Governance)
201
+
114
202
  By default, logs are cleared upon success to save storage space. To keep a permanent audit trail for compliance (e.g., "Why did the agent do this?"), enable Audit Mode:
115
203
 
116
204
  ```typescript
117
- const agent = new Transaction('id', storage, {
118
- cleanupOnSuccess: false
205
+ const agent = new Transaction("id", storage, {
206
+ cleanupOnSuccess: false,
119
207
  });
120
208
  ```
121
209
 
122
210
  ### Manual Rollbacks
211
+
123
212
  The library handles rollbacks automatically on error. You can trigger them manually by throwing an error inside any step:
124
213
 
125
214
  ```typescript
@@ -143,6 +232,7 @@ agent.step({
143
232
  ---
144
233
 
145
234
  ## Roadmap
235
+
146
236
  [x] Core Saga Engine (Execute/Compensate)
147
237
 
148
238
  [x] Persistence Adapters (File, Redis)
@@ -151,11 +241,14 @@ agent.step({
151
241
 
152
242
  [x] CLI Inspector (tai-inspect)
153
243
 
154
- [ ] Concurrent Transaction Locking
244
+ [x] Concurrent Transaction Locking
245
+
246
+ [x] Postgres/SQL Storage Adapter
155
247
 
156
- [ ] Postgres/SQL Storage Adapter
248
+ [x] Step Retry Policies
157
249
 
158
250
  ---
159
251
 
160
252
  ## License
161
- MIT
253
+
254
+ MIT
@@ -0,0 +1,27 @@
1
+ export declare class TransactionLockedError extends Error {
2
+ constructor(message: string);
3
+ }
4
+ export interface LockAdapter {
5
+ /**
6
+ * Attempts to acquire a lock for the given transaction ID.
7
+ * @param id The transaction ID to lock
8
+ * @param ttlMs How long (in ms) to hold the lock before auto-expiring (safety)
9
+ * @returns A "lock token" if successful, or null if the lock is busy.
10
+ */
11
+ acquire(id: string, ttlMs: number): Promise<string | null>;
12
+ /**
13
+ * Releases the lock using the token received during acquire.
14
+ * @param id The transaction ID
15
+ * @param token The token received from acquire
16
+ */
17
+ release(id: string, token: string): Promise<void>;
18
+ }
19
+ /**
20
+ * Default "No Operation" lock for single-process usage.
21
+ * It never blocks.
22
+ */
23
+ export declare class NoOpLock implements LockAdapter {
24
+ acquire(id: string, ttlMs: number): Promise<string>;
25
+ release(id: string, token: string): Promise<void>;
26
+ }
27
+ //# sourceMappingURL=Lock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Lock.d.ts","sourceRoot":"","sources":["../../engine/Lock.ts"],"names":[],"mappings":"AAAA,qBAAa,sBAAuB,SAAQ,KAAK;gBACnC,OAAO,EAAE,MAAM;CAI5B;AAED,MAAM,WAAW,WAAW;IAC1B;;;;;OAKG;IACH,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAE3D;;;;OAIG;IACH,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnD;AAED;;;GAGG;AACH,qBAAa,QAAS,YAAW,WAAW;IAEpC,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAGnD,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAGxD"}
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NoOpLock = exports.TransactionLockedError = void 0;
4
+ class TransactionLockedError extends Error {
5
+ constructor(message) {
6
+ super(message);
7
+ this.name = 'TransactionLockedError';
8
+ }
9
+ }
10
+ exports.TransactionLockedError = TransactionLockedError;
11
+ /**
12
+ * Default "No Operation" lock for single-process usage.
13
+ * It never blocks.
14
+ */
15
+ class NoOpLock {
16
+ // FIXED: Added 'ttlMs' to match interface signature
17
+ async acquire(id, ttlMs) {
18
+ return 'dummy-token';
19
+ }
20
+ async release(id, token) {
21
+ // Do nothing
22
+ }
23
+ }
24
+ exports.NoOpLock = NoOpLock;
25
+ //# sourceMappingURL=Lock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Lock.js","sourceRoot":"","sources":["../../engine/Lock.ts"],"names":[],"mappings":";;;AAAA,MAAa,sBAAuB,SAAQ,KAAK;IAC/C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;IACvC,CAAC;CACF;AALD,wDAKC;AAmBD;;;GAGG;AACH,MAAa,QAAQ;IACnB,oDAAoD;IACpD,KAAK,CAAC,OAAO,CAAC,EAAU,EAAE,KAAa;QACrC,OAAO,aAAa,CAAC;IACvB,CAAC;IACD,KAAK,CAAC,OAAO,CAAC,EAAU,EAAE,KAAa;QACrC,aAAa;IACf,CAAC;CACF;AARD,4BAQC"}
@@ -0,0 +1,13 @@
1
+ import { Pool } from 'pg';
2
+ import { StorageAdapter } from './Storage';
3
+ import { StepContext } from './Transaction';
4
+ export declare class PostgresStorage implements StorageAdapter {
5
+ private pool;
6
+ private tableName;
7
+ constructor(connectionString: string, tableName?: string, pool?: Pool);
8
+ save(txId: string, state: StepContext[]): Promise<void>;
9
+ load(txId: string): Promise<StepContext[] | null>;
10
+ clear(txId: string): Promise<void>;
11
+ disconnect(): Promise<void>;
12
+ }
13
+ //# sourceMappingURL=PostgresStorage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PostgresStorage.d.ts","sourceRoot":"","sources":["../../engine/PostgresStorage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AAC1B,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,qBAAa,eAAgB,YAAW,cAAc;IACpD,OAAO,CAAC,IAAI,CAAO;IACnB,OAAO,CAAC,SAAS,CAAS;gBAGd,gBAAgB,EAAE,MAAM,EAAE,SAAS,SAAiB,EAAE,IAAI,CAAC,EAAE,IAAI;IAMvE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBvD,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC;IAQjD,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKlC,UAAU;CAGjB"}
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PostgresStorage = void 0;
4
+ const pg_1 = require("pg");
5
+ class PostgresStorage {
6
+ pool;
7
+ tableName;
8
+ // CHANGED: Accept optional 'pool' for testing/sharing
9
+ constructor(connectionString, tableName = 'transactions', pool) {
10
+ this.tableName = tableName;
11
+ // Use provided pool OR create a new one
12
+ this.pool = pool || new pg_1.Pool({ connectionString });
13
+ }
14
+ async save(txId, state) {
15
+ const status = state.length > 0 && state[state.length - 1].status === 'completed'
16
+ ? 'pending'
17
+ : 'pending';
18
+ const query = `
19
+ INSERT INTO ${this.tableName} (id, status, step_stack, updated_at)
20
+ VALUES ($1, $2, $3, NOW())
21
+ ON CONFLICT (id)
22
+ DO UPDATE SET
23
+ step_stack = EXCLUDED.step_stack,
24
+ updated_at = NOW(),
25
+ status = EXCLUDED.status;
26
+ `;
27
+ await this.pool.query(query, [txId, status, JSON.stringify(state)]);
28
+ }
29
+ async load(txId) {
30
+ const query = `SELECT step_stack FROM ${this.tableName} WHERE id = $1`;
31
+ const res = await this.pool.query(query, [txId]);
32
+ if (res.rows.length === 0)
33
+ return null;
34
+ return res.rows[0].step_stack;
35
+ }
36
+ async clear(txId) {
37
+ const query = `DELETE FROM ${this.tableName} WHERE id = $1`;
38
+ await this.pool.query(query, [txId]);
39
+ }
40
+ async disconnect() {
41
+ await this.pool.end();
42
+ }
43
+ }
44
+ exports.PostgresStorage = PostgresStorage;
45
+ //# sourceMappingURL=PostgresStorage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PostgresStorage.js","sourceRoot":"","sources":["../../engine/PostgresStorage.ts"],"names":[],"mappings":";;;AAAA,2BAA0B;AAI1B,MAAa,eAAe;IAClB,IAAI,CAAO;IACX,SAAS,CAAS;IAE1B,sDAAsD;IACtD,YAAY,gBAAwB,EAAE,SAAS,GAAG,cAAc,EAAE,IAAW;QAC3E,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,wCAAwC;QACxC,IAAI,CAAC,IAAI,GAAG,IAAI,IAAI,IAAI,SAAI,CAAC,EAAE,gBAAgB,EAAE,CAAC,CAAC;IACrD,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,IAAY,EAAE,KAAoB;QAC3C,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,MAAM,KAAK,WAAW;YAC/E,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,SAAS,CAAC;QAEd,MAAM,KAAK,GAAG;oBACE,IAAI,CAAC,SAAS;;;;;;;KAO7B,CAAC;QAEF,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACtE,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,IAAY;QACrB,MAAM,KAAK,GAAG,0BAA0B,IAAI,CAAC,SAAS,gBAAgB,CAAC;QACvE,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;QAEjD,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QACvC,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,UAA2B,CAAC;IACjD,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,IAAY;QACtB,MAAM,KAAK,GAAG,eAAe,IAAI,CAAC,SAAS,gBAAgB,CAAC;QAC5D,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IACvC,CAAC;IAED,KAAK,CAAC,UAAU;QACd,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;IACxB,CAAC;CACF;AA7CD,0CA6CC"}
@@ -1,5 +1,6 @@
1
1
  import { StorageAdapter } from './Storage';
2
2
  import { StepContext } from './Transaction';
3
+ import { LockAdapter } from './Lock';
3
4
  export declare class RedisStorage implements StorageAdapter {
4
5
  private redis;
5
6
  private ttlSeconds;
@@ -13,4 +14,14 @@ export declare class RedisStorage implements StorageAdapter {
13
14
  clear(txId: string): Promise<void>;
14
15
  disconnect(): Promise<void>;
15
16
  }
17
+ /**
18
+ * Distributed Lock implementation using Redis SET NX
19
+ */
20
+ export declare class RedisLock implements LockAdapter {
21
+ private redis;
22
+ constructor(connectionString: string);
23
+ acquire(id: string, ttlMs: number): Promise<string | null>;
24
+ release(id: string, token: string): Promise<void>;
25
+ disconnect(): Promise<void>;
26
+ }
16
27
  //# sourceMappingURL=RedisStorage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"RedisStorage.d.ts","sourceRoot":"","sources":["../../engine/RedisStorage.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,qBAAa,YAAa,YAAW,cAAc;IACjD,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,UAAU,CAAS;IAE3B;;;OAGG;gBACS,gBAAgB,EAAE,MAAM,EAAE,UAAU,SAAO;IAKjD,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAOvD,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC;IAKjD,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKlC,UAAU;CAGjB"}
1
+ {"version":3,"file":"RedisStorage.d.ts","sourceRoot":"","sources":["../../engine/RedisStorage.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAErC,qBAAa,YAAa,YAAW,cAAc;IACjD,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,UAAU,CAAS;IAE3B;;;OAGG;gBACS,gBAAgB,EAAE,MAAM,EAAE,UAAU,SAAO;IAKjD,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAOvD,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC;IAKjD,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKlC,UAAU;CAGjB;AAED;;GAEG;AACH,qBAAa,SAAU,YAAW,WAAW;IAC3C,OAAO,CAAC,KAAK,CAAQ;gBAET,gBAAgB,EAAE,MAAM;IAI9B,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAU1D,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBjD,UAAU;CAGjB"}
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.RedisStorage = void 0;
6
+ exports.RedisLock = exports.RedisStorage = void 0;
7
7
  const ioredis_1 = __importDefault(require("ioredis"));
8
8
  class RedisStorage {
9
9
  redis;
@@ -35,4 +35,37 @@ class RedisStorage {
35
35
  }
36
36
  }
37
37
  exports.RedisStorage = RedisStorage;
38
+ /**
39
+ * Distributed Lock implementation using Redis SET NX
40
+ */
41
+ class RedisLock {
42
+ redis;
43
+ constructor(connectionString) {
44
+ this.redis = new ioredis_1.default(connectionString);
45
+ }
46
+ async acquire(id, ttlMs) {
47
+ const token = Math.random().toString(36).substring(2);
48
+ const key = `lock:${id}`;
49
+ // FIXED: ioredis requires 'PX' (expiry) before 'NX' (condition)
50
+ const result = await this.redis.set(key, token, 'PX', ttlMs, 'NX');
51
+ return result === 'OK' ? token : null;
52
+ }
53
+ async release(id, token) {
54
+ const key = `lock:${id}`;
55
+ // Safety: Only delete the lock if *we* own it (check token).
56
+ // Lua script guarantees atomicity.
57
+ const script = `
58
+ if redis.call("get", KEYS[1]) == ARGV[1] then
59
+ return redis.call("del", KEYS[1])
60
+ else
61
+ return 0
62
+ end
63
+ `;
64
+ await this.redis.eval(script, 1, key, token);
65
+ }
66
+ async disconnect() {
67
+ await this.redis.quit();
68
+ }
69
+ }
70
+ exports.RedisLock = RedisLock;
38
71
  //# sourceMappingURL=RedisStorage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"RedisStorage.js","sourceRoot":"","sources":["../../engine/RedisStorage.ts"],"names":[],"mappings":";;;;;;AAAA,sDAA4B;AAI5B,MAAa,YAAY;IACf,KAAK,CAAQ;IACb,UAAU,CAAS;IAE3B;;;OAGG;IACH,YAAY,gBAAwB,EAAE,UAAU,GAAG,IAAI;QACrD,IAAI,CAAC,KAAK,GAAG,IAAI,iBAAK,CAAC,gBAAgB,CAAC,CAAC;QACzC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC/B,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,IAAY,EAAE,KAAoB;QAC3C,MAAM,GAAG,GAAG,MAAM,IAAI,EAAE,CAAC;QACzB,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;QACjD,6EAA6E;QAC7E,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IAChD,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,IAAY;QACrB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;QAChD,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACxC,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,IAAY;QACtB,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;IACrC,CAAC;IAED,iDAAiD;IACjD,KAAK,CAAC,UAAU;QACd,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;IAC1B,CAAC;CACF;AAjCD,oCAiCC"}
1
+ {"version":3,"file":"RedisStorage.js","sourceRoot":"","sources":["../../engine/RedisStorage.ts"],"names":[],"mappings":";;;;;;AAAA,sDAA4B;AAK5B,MAAa,YAAY;IACf,KAAK,CAAQ;IACb,UAAU,CAAS;IAE3B;;;OAGG;IACH,YAAY,gBAAwB,EAAE,UAAU,GAAG,IAAI;QACrD,IAAI,CAAC,KAAK,GAAG,IAAI,iBAAK,CAAC,gBAAgB,CAAC,CAAC;QACzC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC/B,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,IAAY,EAAE,KAAoB;QAC3C,MAAM,GAAG,GAAG,MAAM,IAAI,EAAE,CAAC;QACzB,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;QACjD,6EAA6E;QAC7E,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IAChD,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,IAAY;QACrB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;QAChD,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACxC,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,IAAY;QACtB,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;IACrC,CAAC;IAED,iDAAiD;IACjD,KAAK,CAAC,UAAU;QACd,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;IAC1B,CAAC;CACF;AAjCD,oCAiCC;AAED;;GAEG;AACH,MAAa,SAAS;IACZ,KAAK,CAAQ;IAErB,YAAY,gBAAwB;QAClC,IAAI,CAAC,KAAK,GAAG,IAAI,iBAAK,CAAC,gBAAgB,CAAC,CAAC;IAC3C,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,EAAU,EAAE,KAAa;QACrC,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;QACtD,MAAM,GAAG,GAAG,QAAQ,EAAE,EAAE,CAAC;QAEzB,gEAAgE;QAChE,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;QAEnE,OAAO,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;IACxC,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,EAAU,EAAE,KAAa;QACrC,MAAM,GAAG,GAAG,QAAQ,EAAE,EAAE,CAAC;QAEzB,6DAA6D;QAC7D,mCAAmC;QACnC,MAAM,MAAM,GAAG;;;;;;KAMd,CAAC;QAEF,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;IAC/C,CAAC;IAED,KAAK,CAAC,UAAU;QACd,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;IAC1B,CAAC;CACF;AApCD,8BAoCC"}
@@ -1,15 +1,23 @@
1
1
  import { StorageAdapter } from './Storage';
2
+ import { LockAdapter } from './Lock';
2
3
  export type StepContext<T = any> = {
3
4
  name: string;
4
5
  result?: T;
5
6
  status: 'completed' | 'pending';
6
7
  };
8
+ export type RetryPolicy = {
9
+ attempts: number;
10
+ backoffMs?: number;
11
+ };
7
12
  export type StepDefinition<T> = {
8
13
  do: () => Promise<T> | T;
9
14
  undo: (result: T) => Promise<void> | void;
15
+ retry?: RetryPolicy;
10
16
  };
11
17
  type TransactionOptions = {
12
18
  cleanupOnSuccess?: boolean;
19
+ lock?: LockAdapter;
20
+ lockTTL?: number;
13
21
  };
14
22
  export declare class Transaction {
15
23
  private stepStack;
@@ -17,9 +25,12 @@ export declare class Transaction {
17
25
  private id;
18
26
  private storage?;
19
27
  private cleanupOnSuccess;
28
+ private lock;
29
+ private lockTTL;
20
30
  constructor(id: string, storage?: StorageAdapter, options?: TransactionOptions);
21
31
  run(workflow: (tx: Transaction) => Promise<void>): Promise<void>;
22
32
  step<T>(name: string, definition: StepDefinition<T>): Promise<T>;
33
+ private executeWithRetry;
23
34
  private rollback;
24
35
  }
25
36
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"Transaction.d.ts","sourceRoot":"","sources":["../../engine/Transaction.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAE3C,MAAM,MAAM,WAAW,CAAC,CAAC,GAAG,GAAG,IAAI;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,CAAC,CAAC;IACX,MAAM,EAAE,WAAW,GAAG,SAAS,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI;IAC9B,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACzB,IAAI,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC3C,CAAC;AASF,KAAK,kBAAkB,GAAG;IACxB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,CAAC;AAEF,qBAAa,WAAW;IACtB,OAAO,CAAC,SAAS,CAAsB;IACvC,OAAO,CAAC,OAAO,CAA+B;IAC9C,OAAO,CAAC,EAAE,CAAS;IACnB,OAAO,CAAC,OAAO,CAAC,CAAiB;IACjC,OAAO,CAAC,gBAAgB,CAAU;gBAEtB,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,EAAE,OAAO,GAAE,kBAAuB;IAO5E,GAAG,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAyBhE,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;YAgCxD,QAAQ;CAWvB"}
1
+ {"version":3,"file":"Transaction.d.ts","sourceRoot":"","sources":["../../engine/Transaction.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAoC,MAAM,QAAQ,CAAC;AAEvE,MAAM,MAAM,WAAW,CAAC,CAAC,GAAG,GAAG,IAAI;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,CAAC,CAAC;IACX,MAAM,EAAE,WAAW,GAAG,SAAS,CAAC;CACjC,CAAC;AAGF,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI;IAC9B,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACzB,IAAI,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC1C,KAAK,CAAC,EAAE,WAAW,CAAC;CACrB,CAAC;AAQF,KAAK,kBAAkB,GAAG;IACxB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,qBAAa,WAAW;IACtB,OAAO,CAAC,SAAS,CAAsB;IACvC,OAAO,CAAC,OAAO,CAA+B;IAC9C,OAAO,CAAC,EAAE,CAAS;IACnB,OAAO,CAAC,OAAO,CAAC,CAAiB;IACjC,OAAO,CAAC,gBAAgB,CAAU;IAGlC,OAAO,CAAC,IAAI,CAAc;IAC1B,OAAO,CAAC,OAAO,CAAS;gBAEZ,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,EAAE,OAAO,GAAE,kBAAuB;IAU5E,GAAG,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAwChE,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;YAgCxD,gBAAgB;YAmBhB,QAAQ;CAUvB"}
@@ -1,69 +1,106 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Transaction = void 0;
4
+ const Lock_1 = require("./Lock");
4
5
  class Transaction {
5
6
  stepStack = [];
6
7
  history = new Map();
7
8
  id;
8
9
  storage;
9
10
  cleanupOnSuccess;
11
+ // Locking Props
12
+ lock;
13
+ lockTTL;
10
14
  constructor(id, storage, options = {}) {
11
15
  this.id = id;
12
16
  this.storage = storage;
13
- // Default to TRUE (clean up) unless told otherwise
14
17
  this.cleanupOnSuccess = options.cleanupOnSuccess ?? true;
18
+ // Default to NoOpLock
19
+ this.lock = options.lock ?? new Lock_1.NoOpLock();
20
+ this.lockTTL = options.lockTTL ?? 30000;
15
21
  }
16
22
  async run(workflow) {
17
- // 1. Load previous state if storage is provided
18
- if (this.storage) {
19
- const savedState = await this.storage.load(this.id);
20
- if (savedState) {
21
- console.log(`[Transaction] 🔄 Resuming ${this.id} with ${savedState.length} completed steps.`);
22
- savedState.forEach(s => this.history.set(s.name, s.result));
23
- // Note: We can't re-hydrate the 'undo' functions until the code runs again.
24
- // The 'step' method handles re-hydrating the stack.
25
- }
23
+ // 1. Acquire Lock
24
+ console.log(`🔒 [Lock] Acquiring lock for ${this.id}...`);
25
+ const lockToken = await this.lock.acquire(this.id, this.lockTTL);
26
+ if (!lockToken) {
27
+ throw new Lock_1.TransactionLockedError(`Transaction '${this.id}' is currently running elsewhere.`);
26
28
  }
27
29
  try {
28
- await workflow(this);
29
- // CHANGED: Only clear if flag is true
30
- if (this.storage && this.cleanupOnSuccess) {
31
- await this.storage.clear(this.id);
30
+ // 2. Load previous state
31
+ if (this.storage) {
32
+ const savedState = await this.storage.load(this.id);
33
+ if (savedState) {
34
+ console.log(`[Transaction] 🔄 Resuming ${this.id} with ${savedState.length} completed steps.`);
35
+ savedState.forEach(s => this.history.set(s.name, s.result));
36
+ }
37
+ }
38
+ // 3. Execute Workflow
39
+ try {
40
+ await workflow(this);
41
+ // 4. Cleanup on Success
42
+ if (this.storage && this.cleanupOnSuccess) {
43
+ await this.storage.clear(this.id);
44
+ }
45
+ }
46
+ catch (error) {
47
+ console.error(`\n🔴 [Transaction Failed] Error: ${error instanceof Error ? error.message : error}`);
48
+ await this.rollback();
49
+ throw error;
32
50
  }
33
51
  }
34
- catch (error) {
35
- console.error(`\n🔴 [Transaction Failed] Error: ${error instanceof Error ? error.message : error}`);
36
- await this.rollback();
37
- throw error;
52
+ finally {
53
+ // 5. ALWAYS Release Lock
54
+ console.log(`🔓 [Lock] Releasing...`);
55
+ await this.lock.release(this.id, lockToken);
38
56
  }
39
57
  }
40
58
  async step(name, definition) {
41
- // 1. Check if we already did this step (Resumability!)
59
+ // 1. Check Resumability
42
60
  if (this.history.has(name)) {
43
61
  console.log(`⏩ [Skip: ${name}] Already completed.`);
44
62
  const result = this.history.get(name);
45
- // CRITICAL: We still need to push the undo handler to the stack
46
- // so we can rollback if a LATER step fails.
47
63
  this.stepStack.push({ name, undo: () => definition.undo(result) });
48
64
  return result;
49
65
  }
50
- // 2. If not, Execute
51
66
  console.log(`➡️ [Do: ${name}] Executing...`);
52
- const result = await definition.do();
67
+ // 2. Execute with Retry Logic
68
+ // We replace the direct 'definition.do()' call with our new helper
69
+ const result = await this.executeWithRetry(name, definition);
53
70
  // 3. Update State
54
71
  this.stepStack.push({ name, undo: () => definition.undo(result) });
55
72
  // 4. Persist
56
73
  if (this.storage) {
57
74
  const currentHistory = this.stepStack.map(s => ({
58
75
  name: s.name,
59
- result: s.name === name ? result : this.history.get(s.name), // simplified for demo
76
+ result: s.name === name ? result : this.history.get(s.name),
60
77
  status: 'completed'
61
78
  }));
62
- // In a real app, map the full history properly
63
79
  await this.storage.save(this.id, currentHistory);
64
80
  }
65
81
  return result;
66
82
  }
83
+ // NEW: Retry Helper
84
+ async executeWithRetry(name, def) {
85
+ const attempts = def.retry?.attempts || 1;
86
+ const backoff = def.retry?.backoffMs || 0;
87
+ for (let i = 1; i <= attempts; i++) {
88
+ try {
89
+ return await def.do();
90
+ }
91
+ catch (err) {
92
+ if (i < attempts) {
93
+ console.warn(` ⚠️ [Retry ${i}/${attempts}] Step '${name}' failed. Retrying in ${backoff}ms...`);
94
+ if (backoff > 0)
95
+ await new Promise(r => setTimeout(r, backoff));
96
+ }
97
+ else {
98
+ throw err; // Final failure
99
+ }
100
+ }
101
+ }
102
+ throw new Error('Unreachable');
103
+ }
67
104
  async rollback() {
68
105
  const stepsToUndo = [...this.stepStack].reverse();
69
106
  for (const step of stepsToUndo) {
@@ -75,7 +112,6 @@ class Transaction {
75
112
  console.error(` ❌ [CRITICAL] Undo failed for '${step.name}'`, err);
76
113
  }
77
114
  }
78
- // Clear storage after rollback so we don't resume into a broken state
79
115
  if (this.storage)
80
116
  await this.storage.clear(this.id);
81
117
  this.stepStack = [];
@@ -1 +1 @@
1
- {"version":3,"file":"Transaction.js","sourceRoot":"","sources":["../../engine/Transaction.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Transaction.js","sourceRoot":"","sources":["../../engine/Transaction.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -2,5 +2,7 @@ export { Transaction } from './engine/Transaction';
2
2
  export type { StepContext, StepDefinition } from './engine/Transaction';
3
3
  export { StorageAdapter } from './engine/Storage';
4
4
  export { FileStorage } from './engine/FileStorage';
5
- export { RedisStorage } from './engine/RedisStorage';
5
+ export { RedisStorage, RedisLock } from './engine/RedisStorage';
6
+ export { PostgresStorage } from './engine/PostgresStorage';
7
+ export { LockAdapter, TransactionLockedError, NoOpLock } from './engine/Lock';
6
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,YAAY,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAGxE,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,YAAY,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAGxE,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAG3D,OAAO,EAAE,WAAW,EAAE,sBAAsB,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC"}
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RedisStorage = exports.FileStorage = exports.Transaction = void 0;
3
+ exports.NoOpLock = exports.TransactionLockedError = exports.PostgresStorage = exports.RedisLock = exports.RedisStorage = exports.FileStorage = exports.Transaction = void 0;
4
4
  // Export the core engine
5
5
  var Transaction_1 = require("./engine/Transaction");
6
6
  Object.defineProperty(exports, "Transaction", { enumerable: true, get: function () { return Transaction_1.Transaction; } });
@@ -8,5 +8,11 @@ var FileStorage_1 = require("./engine/FileStorage");
8
8
  Object.defineProperty(exports, "FileStorage", { enumerable: true, get: function () { return FileStorage_1.FileStorage; } });
9
9
  var RedisStorage_1 = require("./engine/RedisStorage");
10
10
  Object.defineProperty(exports, "RedisStorage", { enumerable: true, get: function () { return RedisStorage_1.RedisStorage; } });
11
- // We do NOT export the internal 'examples' or 'bin' scripts.
11
+ Object.defineProperty(exports, "RedisLock", { enumerable: true, get: function () { return RedisStorage_1.RedisLock; } });
12
+ var PostgresStorage_1 = require("./engine/PostgresStorage");
13
+ Object.defineProperty(exports, "PostgresStorage", { enumerable: true, get: function () { return PostgresStorage_1.PostgresStorage; } });
14
+ // Export Locking Primitives
15
+ var Lock_1 = require("./engine/Lock");
16
+ Object.defineProperty(exports, "TransactionLockedError", { enumerable: true, get: function () { return Lock_1.TransactionLockedError; } });
17
+ Object.defineProperty(exports, "NoOpLock", { enumerable: true, get: function () { return Lock_1.NoOpLock; } });
12
18
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":";;;AAAA,yBAAyB;AACzB,oDAAmD;AAA1C,0GAAA,WAAW,OAAA;AAKpB,oDAAmD;AAA1C,0GAAA,WAAW,OAAA;AACpB,sDAAqD;AAA5C,4GAAA,YAAY,OAAA;AAErB,6DAA6D"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":";;;AAAA,yBAAyB;AACzB,oDAAmD;AAA1C,0GAAA,WAAW,OAAA;AAKpB,oDAAmD;AAA1C,0GAAA,WAAW,OAAA;AACpB,sDAAgE;AAAvD,4GAAA,YAAY,OAAA;AAAE,yGAAA,SAAS,OAAA;AAChC,4DAA2D;AAAlD,kHAAA,eAAe,OAAA;AAExB,4BAA4B;AAC5B,sCAA8E;AAAxD,8GAAA,sBAAsB,OAAA;AAAE,gGAAA,QAAQ,OAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "transactional-ai",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "A reliability protocol for AI Agents. Saga pattern with persistent rollback.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -14,18 +14,36 @@
14
14
  "build": "tsc",
15
15
  "postbuild": "chmod +x dist/bin/inspect.js",
16
16
  "test": "jest",
17
- "prepublishOnly": "npm run build"
17
+ "prepublishOnly": "npm run build",
18
+ "example:failure": "tsx examples/demo-failure.ts",
19
+ "example:resume": "tsx examples/demo-resume.ts",
20
+ "example:openai": "tsx examples/demo-openai-stripe.ts"
18
21
  },
19
22
  "keywords": [
20
23
  "ai",
21
24
  "agent",
22
25
  "transaction",
23
26
  "saga",
27
+ "saga-pattern",
24
28
  "reliability",
25
- "llm"
29
+ "llm",
30
+ "openai",
31
+ "workflow",
32
+ "orchestration",
33
+ "rollback",
34
+ "compensation",
35
+ "typescript"
26
36
  ],
27
- "author": "Your Name",
37
+ "author": "GrafikUi <hello@grafikui.com>",
28
38
  "license": "MIT",
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "https://github.com/Grafikui/Transactional-ai.git"
42
+ },
43
+ "bugs": {
44
+ "url": "https://github.com/Grafikui/Transactional-ai/issues"
45
+ },
46
+ "homepage": "https://github.com/Grafikui/Transactional-ai#readme",
29
47
  "devDependencies": {
30
48
  "@testing-library/jest-dom": "^6.9.1",
31
49
  "@testing-library/react": "^16.3.1",
@@ -41,9 +59,11 @@
41
59
  },
42
60
  "dependencies": {
43
61
  "@prisma/client": "6.15.0",
62
+ "@types/pg": "^8.16.0",
44
63
  "ioredis": "^5.9.1",
45
64
  "node-fetch": "^3.3.2",
46
65
  "openai": "^6.15.0",
66
+ "pg": "^8.16.3",
47
67
  "prisma": "6.15.0"
48
68
  }
49
69
  }