transactional-ai 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 omar
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 ADDED
@@ -0,0 +1,161 @@
1
+ # Transactional AI
2
+
3
+ **A reliability protocol for AI Agents.**
4
+ Implement the Saga Pattern with persistent rollback and state recovery for Long-Running Machine (LLM) operations.
5
+
6
+ [![npm version](https://img.shields.io/npm/v/transactional-ai.svg)](https://www.npmjs.com/package/transactional-ai)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
+
9
+ ## Why use this?
10
+ AI Agents are flaky. Steps fail, APIs time out, and hallucinations happen.
11
+ `transactional-ai` gives you:
12
+ 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.
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install transactional-ai
20
+ ```
21
+
22
+ ---
23
+
24
+ ## Quick Start
25
+
26
+ ### 1. The "Litmus Test" (Basic Usage)
27
+ Define a transaction where every action has a compensating rollback action.
28
+
29
+ ```typescript
30
+ import { Transaction } from 'transactional-ai';
31
+
32
+ // 1. Create a named transaction (required for resumability)
33
+ const agent = new Transaction('user-onboarding-123');
34
+
35
+ agent
36
+ .step({
37
+ name: 'create-file',
38
+ execute: async (ctx) => {
39
+ // Do the work
40
+ const file = await googleDrive.createFile('report.txt');
41
+ return file.id;
42
+ },
43
+ compensate: async (fileId) => {
44
+ // Undo the work
45
+ await googleDrive.deleteFile(fileId);
46
+ }
47
+ })
48
+ .step({
49
+ name: 'email-report',
50
+ execute: async (ctx) => {
51
+ // Use previous results via context or external state
52
+ await emailService.send(ctx.result);
53
+ },
54
+ compensate: async () => {
55
+ await emailService.recallLast();
56
+ }
57
+ });
58
+
59
+ // 2. Run it
60
+ await agent.run({ initialData: 'foo' });
61
+ ```
62
+
63
+ ### Adding Persistence (Redis)
64
+ To survive process crashes, simply provide a storage adapter.
65
+
66
+ ```typescript
67
+ import { Transaction, RedisStorage } from 'transactional-ai';
68
+
69
+ const storage = new RedisStorage('redis://localhost:6379');
70
+ const agent = new Transaction('workflow-id-555', storage);
71
+
72
+ // If the process crashes here, running this code again
73
+ // will automatically SKIP completed steps and resume at the failure point.
74
+ agent
75
+ .step({ /* ... */ })
76
+ .step({ /* ... */ });
77
+
78
+ await agent.run();
79
+ ```
80
+
81
+ ---
82
+
83
+ ## CLI Inspector
84
+ 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
+
86
+ ```bash
87
+ # Inspect a specific transaction ID (File Storage)
88
+ npx tai-inspect workflow-id-555
89
+
90
+ # Inspect using Redis
91
+ export REDIS_URL="redis://localhost:6379"
92
+ npx tai-inspect workflow-id-555
93
+ ```
94
+
95
+ Output:
96
+
97
+ ```
98
+ 🔍 Inspecting: workflow-id-555
99
+ Source: RedisStorage
100
+
101
+ STEP NAME | STATUS
102
+ ------------------------------------
103
+ ├── create-file | ✅ completed
104
+ │ └-> Result: "file_xyz123"
105
+ ├── email-report | ❌ failed
106
+ └── (comp) create-f..| ✅ completed
107
+ ```
108
+
109
+ ---
110
+
111
+ ## Advanced Usage
112
+
113
+ ### Audit Mode (Governance)
114
+ 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
+
116
+ ```typescript
117
+ const agent = new Transaction('id', storage, {
118
+ cleanupOnSuccess: false
119
+ });
120
+ ```
121
+
122
+ ### Manual Rollbacks
123
+ The library handles rollbacks automatically on error. You can trigger them manually by throwing an error inside any step:
124
+
125
+ ```typescript
126
+ // Define a step that throws an error to trigger rollback
127
+ agent.step({
128
+ name: 'check-balance',
129
+ execute: async (ctx) => {
130
+ const balance = await getBalance(ctx.userId);
131
+ if (balance < 10) {
132
+ // Throwing an error automatically triggers the compensation
133
+ // for all previous steps.
134
+ throw new Error("Insufficient funds");
135
+ }
136
+ },
137
+ compensate: async () => {
138
+ // No compensation needed for a read-only check
139
+ }
140
+ });
141
+ ```
142
+
143
+ ---
144
+
145
+ ## Roadmap
146
+ [x] Core Saga Engine (Execute/Compensate)
147
+
148
+ [x] Persistence Adapters (File, Redis)
149
+
150
+ [x] Resumability (Skip completed steps)
151
+
152
+ [x] CLI Inspector (tai-inspect)
153
+
154
+ [ ] Concurrent Transaction Locking
155
+
156
+ [ ] Postgres/SQL Storage Adapter
157
+
158
+ ---
159
+
160
+ ## License
161
+ MIT
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=inspect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inspect.d.ts","sourceRoot":"","sources":["../../bin/inspect.ts"],"names":[],"mappings":""}
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const RedisStorage_1 = require("../engine/RedisStorage");
5
+ const FileStorage_1 = require("../engine/FileStorage");
6
+ async function main() {
7
+ const [, , txId] = process.argv;
8
+ if (!txId) {
9
+ console.error("Usage: npx transactional-ai inspect <transaction-id>");
10
+ process.exit(1);
11
+ }
12
+ // Auto-detect storage based on Env Var
13
+ const storage = process.env.REDIS_URL
14
+ ? new RedisStorage_1.RedisStorage(process.env.REDIS_URL)
15
+ : new FileStorage_1.FileStorage();
16
+ console.log(`\n🔍 Inspecting Transaction: ${txId}`);
17
+ console.log(` Source: ${storage.constructor.name}\n`);
18
+ const history = await storage.load(txId);
19
+ if (!history) {
20
+ console.error("❌ Transaction not found.");
21
+ process.exit(1);
22
+ }
23
+ console.log(" STEP NAME | STATUS ");
24
+ console.log(" ------------------------------------");
25
+ history.forEach((step, index) => {
26
+ const isLast = index === history.length - 1;
27
+ // Simple ASCII Tree
28
+ const prefix = isLast ? "└──" : "├──";
29
+ // Status Icon
30
+ const icon = step.status === 'completed' ? '✅' : '⏳';
31
+ console.log(` ${prefix} ${step.name.padEnd(20)} | ${icon} ${step.status}`);
32
+ // If there is a result and it's small, show it
33
+ if (step.result && typeof step.result === 'string' && step.result.length < 50) {
34
+ console.log(` └-> Result: ${step.result}`);
35
+ }
36
+ });
37
+ console.log("\n");
38
+ if (storage instanceof RedisStorage_1.RedisStorage) {
39
+ await storage.disconnect();
40
+ }
41
+ }
42
+ main().catch(console.error);
43
+ //# sourceMappingURL=inspect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inspect.js","sourceRoot":"","sources":["../../bin/inspect.ts"],"names":[],"mappings":";;;AACA,yDAAsD;AACtD,uDAAoD;AAEpD,KAAK,UAAU,IAAI;IACjB,MAAM,CAAC,EAAC,EAAE,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAE/B,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,CAAC,KAAK,CAAC,sDAAsD,CAAC,CAAC;QACtE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,uCAAuC;IACvC,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS;QACnC,CAAC,CAAC,IAAI,2BAAY,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC;QACzC,CAAC,CAAC,IAAI,yBAAW,EAAE,CAAC;IAEtB,OAAO,CAAC,GAAG,CAAC,gCAAgC,IAAI,EAAE,CAAC,CAAC;IACpD,OAAO,CAAC,GAAG,CAAC,cAAc,OAAO,CAAC,WAAW,CAAC,IAAI,IAAI,CAAC,CAAC;IAExD,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEzC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC;QAC1C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC;IACjD,OAAO,CAAC,GAAG,CAAC,yCAAyC,CAAC,CAAC;IAEvD,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QAC9B,MAAM,MAAM,GAAG,KAAK,KAAK,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;QAC5C,oBAAoB;QACpB,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC;QAEtC,cAAc;QACd,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;QAErD,OAAO,CAAC,GAAG,CAAC,MAAM,MAAM,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,IAAI,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;QAE7E,+CAA+C;QAC/C,IAAI,IAAI,CAAC,MAAM,IAAI,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;YAC7E,OAAO,CAAC,GAAG,CAAC,sBAAsB,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;QACpD,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAElB,IAAI,OAAO,YAAY,2BAAY,EAAE,CAAC;QACpC,MAAM,OAAO,CAAC,UAAU,EAAE,CAAC;IAC7B,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC"}
@@ -0,0 +1,10 @@
1
+ import { StorageAdapter } from './Storage';
2
+ import { StepContext } from './Transaction';
3
+ export declare class FileStorage implements StorageAdapter {
4
+ private dir;
5
+ init(): Promise<void>;
6
+ save(txId: string, state: StepContext[]): Promise<void>;
7
+ load(txId: string): Promise<StepContext[] | null>;
8
+ clear(txId: string): Promise<void>;
9
+ }
10
+ //# sourceMappingURL=FileStorage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FileStorage.d.ts","sourceRoot":"","sources":["../../engine/FileStorage.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,qBAAa,WAAY,YAAW,cAAc;IAChD,OAAO,CAAC,GAAG,CAAiD;IAEtD,IAAI;IAIJ,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAKvD,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC;IASjD,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAGzC"}
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.FileStorage = void 0;
7
+ const promises_1 = __importDefault(require("fs/promises"));
8
+ const path_1 = __importDefault(require("path"));
9
+ class FileStorage {
10
+ dir = path_1.default.join(process.cwd(), '.transaction-logs');
11
+ async init() {
12
+ try {
13
+ await promises_1.default.mkdir(this.dir, { recursive: true });
14
+ }
15
+ catch { }
16
+ }
17
+ async save(txId, state) {
18
+ await this.init();
19
+ await promises_1.default.writeFile(path_1.default.join(this.dir, `${txId}.json`), JSON.stringify(state, null, 2));
20
+ }
21
+ async load(txId) {
22
+ try {
23
+ const data = await promises_1.default.readFile(path_1.default.join(this.dir, `${txId}.json`), 'utf-8');
24
+ return JSON.parse(data);
25
+ }
26
+ catch {
27
+ return null;
28
+ }
29
+ }
30
+ async clear(txId) {
31
+ try {
32
+ await promises_1.default.unlink(path_1.default.join(this.dir, `${txId}.json`));
33
+ }
34
+ catch { }
35
+ }
36
+ }
37
+ exports.FileStorage = FileStorage;
38
+ //# sourceMappingURL=FileStorage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FileStorage.js","sourceRoot":"","sources":["../../engine/FileStorage.ts"],"names":[],"mappings":";;;;;;AAAA,2DAA6B;AAC7B,gDAAwB;AAIxB,MAAa,WAAW;IACd,GAAG,GAAG,cAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,mBAAmB,CAAC,CAAC;IAE5D,KAAK,CAAC,IAAI;QACR,IAAI,CAAC;YAAC,MAAM,kBAAE,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;IACjE,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,IAAY,EAAE,KAAoB;QAC3C,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAClB,MAAM,kBAAE,CAAC,SAAS,CAAC,cAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,OAAO,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAC1F,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,IAAY;QACrB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,kBAAE,CAAC,QAAQ,CAAC,cAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC;YAC7E,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC1B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,IAAY;QACtB,IAAI,CAAC;YAAC,MAAM,kBAAE,CAAC,MAAM,CAAC,cAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,OAAO,CAAC,CAAC,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;IACxE,CAAC;CACF;AAxBD,kCAwBC"}
@@ -0,0 +1,16 @@
1
+ import { StorageAdapter } from './Storage';
2
+ import { StepContext } from './Transaction';
3
+ export declare class RedisStorage implements StorageAdapter {
4
+ private redis;
5
+ private ttlSeconds;
6
+ /**
7
+ * @param connectionString Redis connection URL (e.g., "redis://localhost:6379")
8
+ * @param ttlSeconds How long to keep logs (default: 1 hour)
9
+ */
10
+ constructor(connectionString: string, ttlSeconds?: number);
11
+ save(txId: string, state: StepContext[]): Promise<void>;
12
+ load(txId: string): Promise<StepContext[] | null>;
13
+ clear(txId: string): Promise<void>;
14
+ disconnect(): Promise<void>;
15
+ }
16
+ //# sourceMappingURL=RedisStorage.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.RedisStorage = void 0;
7
+ const ioredis_1 = __importDefault(require("ioredis"));
8
+ class RedisStorage {
9
+ redis;
10
+ ttlSeconds;
11
+ /**
12
+ * @param connectionString Redis connection URL (e.g., "redis://localhost:6379")
13
+ * @param ttlSeconds How long to keep logs (default: 1 hour)
14
+ */
15
+ constructor(connectionString, ttlSeconds = 3600) {
16
+ this.redis = new ioredis_1.default(connectionString);
17
+ this.ttlSeconds = ttlSeconds;
18
+ }
19
+ async save(txId, state) {
20
+ const key = `tx:${txId}`;
21
+ await this.redis.set(key, JSON.stringify(state));
22
+ // Reset expiration timer on every update so active transactions don't expire
23
+ await this.redis.expire(key, this.ttlSeconds);
24
+ }
25
+ async load(txId) {
26
+ const data = await this.redis.get(`tx:${txId}`);
27
+ return data ? JSON.parse(data) : null;
28
+ }
29
+ async clear(txId) {
30
+ await this.redis.del(`tx:${txId}`);
31
+ }
32
+ // Helper to close connection when app shuts down
33
+ async disconnect() {
34
+ await this.redis.quit();
35
+ }
36
+ }
37
+ exports.RedisStorage = RedisStorage;
38
+ //# sourceMappingURL=RedisStorage.js.map
@@ -0,0 +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"}
@@ -0,0 +1,7 @@
1
+ import { StepContext } from './Transaction';
2
+ export interface StorageAdapter {
3
+ save(transactionId: string, state: StepContext[]): Promise<void>;
4
+ load(transactionId: string): Promise<StepContext[] | null>;
5
+ clear(transactionId: string): Promise<void>;
6
+ }
7
+ //# sourceMappingURL=Storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Storage.d.ts","sourceRoot":"","sources":["../../engine/Storage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,aAAa,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,IAAI,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,CAAC;IAC3D,KAAK,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7C"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=Storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Storage.js","sourceRoot":"","sources":["../../engine/Storage.ts"],"names":[],"mappings":""}
@@ -0,0 +1,26 @@
1
+ import { StorageAdapter } from './Storage';
2
+ export type StepContext<T = any> = {
3
+ name: string;
4
+ result?: T;
5
+ status: 'completed' | 'pending';
6
+ };
7
+ export type StepDefinition<T> = {
8
+ do: () => Promise<T> | T;
9
+ undo: (result: T) => Promise<void> | void;
10
+ };
11
+ type TransactionOptions = {
12
+ cleanupOnSuccess?: boolean;
13
+ };
14
+ export declare class Transaction {
15
+ private stepStack;
16
+ private history;
17
+ private id;
18
+ private storage?;
19
+ private cleanupOnSuccess;
20
+ constructor(id: string, storage?: StorageAdapter, options?: TransactionOptions);
21
+ run(workflow: (tx: Transaction) => Promise<void>): Promise<void>;
22
+ step<T>(name: string, definition: StepDefinition<T>): Promise<T>;
23
+ private rollback;
24
+ }
25
+ export {};
26
+ //# sourceMappingURL=Transaction.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Transaction = void 0;
4
+ class Transaction {
5
+ stepStack = [];
6
+ history = new Map();
7
+ id;
8
+ storage;
9
+ cleanupOnSuccess;
10
+ constructor(id, storage, options = {}) {
11
+ this.id = id;
12
+ this.storage = storage;
13
+ // Default to TRUE (clean up) unless told otherwise
14
+ this.cleanupOnSuccess = options.cleanupOnSuccess ?? true;
15
+ }
16
+ 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
+ }
26
+ }
27
+ 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);
32
+ }
33
+ }
34
+ catch (error) {
35
+ console.error(`\n🔴 [Transaction Failed] Error: ${error instanceof Error ? error.message : error}`);
36
+ await this.rollback();
37
+ throw error;
38
+ }
39
+ }
40
+ async step(name, definition) {
41
+ // 1. Check if we already did this step (Resumability!)
42
+ if (this.history.has(name)) {
43
+ console.log(`⏩ [Skip: ${name}] Already completed.`);
44
+ 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
+ this.stepStack.push({ name, undo: () => definition.undo(result) });
48
+ return result;
49
+ }
50
+ // 2. If not, Execute
51
+ console.log(`➡️ [Do: ${name}] Executing...`);
52
+ const result = await definition.do();
53
+ // 3. Update State
54
+ this.stepStack.push({ name, undo: () => definition.undo(result) });
55
+ // 4. Persist
56
+ if (this.storage) {
57
+ const currentHistory = this.stepStack.map(s => ({
58
+ name: s.name,
59
+ result: s.name === name ? result : this.history.get(s.name), // simplified for demo
60
+ status: 'completed'
61
+ }));
62
+ // In a real app, map the full history properly
63
+ await this.storage.save(this.id, currentHistory);
64
+ }
65
+ return result;
66
+ }
67
+ async rollback() {
68
+ const stepsToUndo = [...this.stepStack].reverse();
69
+ for (const step of stepsToUndo) {
70
+ console.log(`⬅️ [Undo: ${step.name}] Reverting...`);
71
+ try {
72
+ await step.undo();
73
+ }
74
+ catch (err) {
75
+ console.error(` ❌ [CRITICAL] Undo failed for '${step.name}'`, err);
76
+ }
77
+ }
78
+ // Clear storage after rollback so we don't resume into a broken state
79
+ if (this.storage)
80
+ await this.storage.clear(this.id);
81
+ this.stepStack = [];
82
+ }
83
+ }
84
+ exports.Transaction = Transaction;
85
+ //# sourceMappingURL=Transaction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Transaction.js","sourceRoot":"","sources":["../../engine/Transaction.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export { Transaction } from './engine/Transaction';
2
+ export type { StepContext, StepDefinition } from './engine/Transaction';
3
+ export { StorageAdapter } from './engine/Storage';
4
+ export { FileStorage } from './engine/FileStorage';
5
+ export { RedisStorage } from './engine/RedisStorage';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +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"}
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RedisStorage = exports.FileStorage = exports.Transaction = void 0;
4
+ // Export the core engine
5
+ var Transaction_1 = require("./engine/Transaction");
6
+ Object.defineProperty(exports, "Transaction", { enumerable: true, get: function () { return Transaction_1.Transaction; } });
7
+ var FileStorage_1 = require("./engine/FileStorage");
8
+ Object.defineProperty(exports, "FileStorage", { enumerable: true, get: function () { return FileStorage_1.FileStorage; } });
9
+ var RedisStorage_1 = require("./engine/RedisStorage");
10
+ Object.defineProperty(exports, "RedisStorage", { enumerable: true, get: function () { return RedisStorage_1.RedisStorage; } });
11
+ // We do NOT export the internal 'examples' or 'bin' scripts.
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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"}
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "transactional-ai",
3
+ "version": "0.1.0",
4
+ "description": "A reliability protocol for AI Agents. Saga pattern with persistent rollback.",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "tai-inspect": "dist/bin/inspect.js"
8
+ },
9
+ "types": "dist/index.d.ts",
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsc",
15
+ "postbuild": "chmod +x dist/bin/inspect.js",
16
+ "test": "jest",
17
+ "prepublishOnly": "npm run build"
18
+ },
19
+ "keywords": [
20
+ "ai",
21
+ "agent",
22
+ "transaction",
23
+ "saga",
24
+ "reliability",
25
+ "llm"
26
+ ],
27
+ "author": "Your Name",
28
+ "license": "MIT",
29
+ "devDependencies": {
30
+ "@testing-library/jest-dom": "^6.9.1",
31
+ "@testing-library/react": "^16.3.1",
32
+ "@types/ioredis": "^5.0.0",
33
+ "@types/jest": "^30.0.0",
34
+ "jest": "^30.2.0",
35
+ "jest-environment-jsdom": "^30.2.0",
36
+ "react": "19.2.3",
37
+ "react-dom": "19.2.3",
38
+ "ts-jest": "^29.4.6",
39
+ "tsx": "^4.21.0",
40
+ "typescript": "^5.9.3"
41
+ },
42
+ "dependencies": {
43
+ "@prisma/client": "6.15.0",
44
+ "ioredis": "^5.9.1",
45
+ "node-fetch": "^3.3.2",
46
+ "openai": "^6.15.0",
47
+ "prisma": "6.15.0"
48
+ }
49
+ }