@ax-llm/ax-tools 23.0.0 → 23.0.2
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 +9 -4
- package/event/sqlite.cjs +117 -0
- package/event/sqlite.cjs.map +1 -0
- package/event/sqlite.d.cts +73 -0
- package/event/sqlite.d.ts +73 -0
- package/event/sqlite.js +117 -0
- package/event/sqlite.js.map +1 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -19,6 +19,12 @@ A transport for the Model Context Protocol (MCP) that communicates with MCP
|
|
|
19
19
|
servers via stdin/stdout. This enables running local MCP servers as child
|
|
20
20
|
processes.
|
|
21
21
|
|
|
22
|
+
### Persistent event runtime store
|
|
23
|
+
|
|
24
|
+
`@ax-llm/ax-tools/event/sqlite` exports `AxSQLiteEventStore` and
|
|
25
|
+
`AX_SQLITE_EVENT_STANDARD_RETENTION`. It supports cooperating Node processes
|
|
26
|
+
that share a local SQLite file. It is not intended for network filesystems.
|
|
27
|
+
|
|
22
28
|
### Function Tools
|
|
23
29
|
|
|
24
30
|
`AxJSRuntime` has moved to `@ax-llm/ax`.
|
|
@@ -34,7 +40,7 @@ npm install @ax-llm/ax-tools
|
|
|
34
40
|
### Basic MCP Stdio Transport
|
|
35
41
|
|
|
36
42
|
```typescript
|
|
37
|
-
import {
|
|
43
|
+
import { ax, AxMCPClient } from "@ax-llm/ax";
|
|
38
44
|
import { AxMCPStdioTransport } from "@ax-llm/ax-tools";
|
|
39
45
|
|
|
40
46
|
// Create a stdio transport for an MCP server
|
|
@@ -48,9 +54,8 @@ const transport = new AxMCPStdioTransport({
|
|
|
48
54
|
const client = new AxMCPClient(transport, { debug: true });
|
|
49
55
|
await client.init();
|
|
50
56
|
|
|
51
|
-
//
|
|
52
|
-
const
|
|
53
|
-
console.log(`Available functions: ${functions.map((f) => f.name).join(", ")}`);
|
|
57
|
+
// Attach the live client natively to AxGen, AxAgent, or AxFlow.
|
|
58
|
+
const program = ax('question:string -> answer:string', { mcp: client });
|
|
54
59
|
```
|
|
55
60
|
|
|
56
61
|
### With Factory Function
|
package/event/sqlite.cjs
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class;var _crypto = require('crypto');var _ax = require('@ax-llm/ax');var _bettersqlite3 = require('better-sqlite3'); var _bettersqlite32 = _interopRequireDefault(_bettersqlite3);var c=1,N="axevent-store-v1",d=["waiting_event","succeeded","failed","cancelled","dead_lettered","output_persistence_failed","outcome_unknown"],b= exports.AX_SQLITE_EVENT_STANDARD_RETENTION ={eventAndResultMs:7*24*60*60*1e3,runMetadataAndDeadLettersMs:30*24*60*60*1e3,completedContinuationsMs:7*24*60*60*1e3},_= exports.AxSQLiteEventStore = (_class =class{constructor(e){;_class.prototype.__init.call(this);this.options=e;if(!e.retention)throw new Error("AxSQLiteEventStore requires explicit retention");this.clock=_nullishCoalesce(e.clock, () => (new _ax.AxSystemEventClock)),this.maxPendingDeliveries=_nullishCoalesce(e.maxPendingDeliveries, () => (1e5)),this.maxPendingBytes=_nullishCoalesce(e.maxPendingBytes, () => (1024*1024*1024)),this.maxEventBytes=_nullishCoalesce(e.maxEventBytes, () => (16*1024*1024)),this.maxInlinePayloadBytes=_nullishCoalesce(e.maxInlinePayloadBytes, () => (16*1024*1024)),this.db=new (0, _bettersqlite32.default)(e.filename),this.db.pragma(`busy_timeout = ${_nullishCoalesce(e.busyTimeoutMs, () => (5e3))}`),this.db.pragma("journal_mode = WAL"),this.db.pragma("foreign_keys = ON"),this.migrate(),this.prune(this.clock.now())}__init() {this.capabilities={durability:"persistent",coordination:"multi-worker",leases:!0,transactions:!0,compareAndSet:!0,outputPersistence:!0,conformance:{multiWorker:N,schemaVersion:c}}}async enqueue(e,t){let n=Buffer.byteLength(JSON.stringify(e.ingress));if(n>this.maxEventBytes)throw new (0, _ax.AxEventBackpressureError)(`Event is ${n} bytes; maximum is ${this.maxEventBytes}`);let i=this.clock.now()+e.publishTimeoutMs;for(;;){if(_optionalChain([t, 'optionalAccess', _2 => _2.aborted]))throw t.reason;let s=this.tryEnqueue(e);if(s)return s;let r=i-this.clock.now();if(r<=0)throw new _ax.AxEventBackpressureError;await this.clock.sleep(Math.min(25,r),t)}}async claim(e,t,n=3e4){return this.db.transaction(()=>{let i=this.db.prepare(`SELECT d.* FROM event_deliveries d
|
|
2
|
+
WHERE (
|
|
3
|
+
(d.status = 'queued' AND d.available_at <= ?)
|
|
4
|
+
OR
|
|
5
|
+
(d.status IN ('claimed','running') AND d.lease_expires_at <= ?)
|
|
6
|
+
)
|
|
7
|
+
AND (
|
|
8
|
+
d.ordering_mode = 'relaxed'
|
|
9
|
+
OR NOT EXISTS (
|
|
10
|
+
SELECT 1 FROM event_deliveries earlier
|
|
11
|
+
WHERE earlier.sequence < d.sequence
|
|
12
|
+
AND COALESCE(earlier.target_id, '') = COALESCE(d.target_id, '')
|
|
13
|
+
AND earlier.instance_key = d.instance_key
|
|
14
|
+
AND earlier.status NOT IN (${d.map(()=>"?").join(",")})
|
|
15
|
+
)
|
|
16
|
+
)
|
|
17
|
+
ORDER BY d.sequence LIMIT 1`).get(t,t,...d);if(!i)return;let s=i.status!=="queued",r=i.fencing_token+1;if(this.db.prepare(`UPDATE event_deliveries
|
|
18
|
+
SET status='claimed', claimed_by=?, lease_expires_at=?, fencing_token=?
|
|
19
|
+
WHERE id=? AND fencing_token=?`).run(e,t+n,r,i.id,i.fencing_token).changes===1)return{...this.rowToDelivery({...i,status:"claimed",claimed_by:e,lease_expires_at:t+n,fencing_token:r}),...s?{recoveredFromExpiredLease:!0}:{}}})()}async renewClaim(e,t,n,i){if(this.db.prepare(`UPDATE event_deliveries SET lease_expires_at=?
|
|
20
|
+
WHERE id=? AND claimed_by=? AND fencing_token=?
|
|
21
|
+
AND status IN ('claimed','running')`).run(i,e,t,n).changes!==1)throw new Error(`Stale event claim for ${e}`)}async getDelivery(e){let t=this.db.prepare("SELECT * FROM event_deliveries WHERE id=?").get(e);return t?this.rowToDelivery(t):void 0}async saveDelivery(e){if(this.db.prepare(`UPDATE event_deliveries SET
|
|
22
|
+
status=?, attempt=?, available_at=?, claimed_by=?, run_id=?, error=?,
|
|
23
|
+
lease_expires_at=?, invocation_started=?, retry_safety=?, ordering_mode=?
|
|
24
|
+
WHERE id=? AND fencing_token=?`).run(e.status,e.attempt,e.availableAt,_nullishCoalesce(e.claimedBy, () => (null)),_nullishCoalesce(e.runId, () => (null)),_nullishCoalesce(e.error, () => (null)),_nullishCoalesce(e.leaseExpiresAt, () => (null)),e.invocationStarted?1:0,e.retrySafety,e.ordering,e.id,_nullishCoalesce(e.fencingToken, () => (-1))).changes!==1)throw new Error(`Stale fencing token for event delivery ${e.id}`)}async saveRun(e){this.assertFence(e.deliveryId,e.fencingToken);let t=structuredClone(e),n=e.output!==void 0||e.chunks!==void 0,i={output:e.output,chunks:e.chunks};if(n&&Buffer.byteLength(JSON.stringify(i))>this.maxInlinePayloadBytes){if(!this.options.payloadStore)throw new Error(`output_persistence_failed: run ${e.id} exceeded ${this.maxInlinePayloadBytes} inline bytes`);let s=await this.options.payloadStore.put(`event-run:${e.id}`,i);t={...t,output:void 0,chunks:void 0,outputRef:s}}this.db.prepare(`INSERT INTO event_runs(id, delivery_id, run_json, updated_at, finished_at)
|
|
25
|
+
VALUES(?,?,?,?,?)
|
|
26
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
27
|
+
run_json=excluded.run_json, updated_at=excluded.updated_at,
|
|
28
|
+
finished_at=excluded.finished_at`).run(e.id,e.deliveryId,JSON.stringify(t),this.clock.now(),_nullishCoalesce(e.finishedAt, () => (null)))}async getRun(e){let t=this.db.prepare("SELECT run_json FROM event_runs WHERE id=?").get(e);if(!t)return;let n=JSON.parse(t.run_json);if(n.outputRef&&this.options.payloadStore){let i=await this.options.payloadStore.get(n.outputRef);return{...n,output:i.output,chunks:i.chunks}}return n}async registerContinuation(e){this.db.transaction(()=>{this.db.prepare(`INSERT INTO event_continuations
|
|
29
|
+
(id, identity_scope, continuation_json, created_at, expires_at)
|
|
30
|
+
VALUES(?,?,?,?,?)`).run(e.id,e.identityScope,JSON.stringify(e),e.createdAt,_nullishCoalesce(e.expiresAt, () => (null)));let t=this.db.prepare("INSERT INTO event_continuation_keys(correlation_key, continuation_id) VALUES(?,?)");for(let n of e.correlation)t.run(_ax.axEventScopedCorrelationKey.call(void 0, e.identityScope,n.kind,n.value),e.id)})()}async findContinuation(e,t,n){let i=this.db.prepare(`SELECT c.continuation_json, c.id, c.expires_at
|
|
31
|
+
FROM event_continuation_keys k
|
|
32
|
+
JOIN event_continuations c ON c.id=k.continuation_id
|
|
33
|
+
WHERE k.correlation_key=? AND c.completed_at IS NULL`).get(_ax.axEventScopedCorrelationKey.call(void 0, e,t.kind,t.value));if(i){if(i.expires_at!==null&&i.expires_at<=n){await this.completeContinuation(i.id);return}return JSON.parse(i.continuation_json)}}async completeContinuation(e){this.db.transaction(()=>{this.db.prepare("DELETE FROM event_continuation_keys WHERE continuation_id=?").run(e),this.db.prepare("UPDATE event_continuations SET completed_at=? WHERE id=? AND completed_at IS NULL").run(this.clock.now(),e)})()}async addDeadLetter(e){this.db.prepare(`INSERT OR REPLACE INTO event_dead_letters
|
|
34
|
+
(id, delivery_id, dead_letter_json, created_at) VALUES(?,?,?,?)`).run(e.id,e.deliveryId,JSON.stringify(e),e.createdAt)}async getDeadLetter(e){let t=this.db.prepare("SELECT dead_letter_json FROM event_dead_letters WHERE id=?").get(e);return t?JSON.parse(t.dead_letter_json):void 0}async removeDeadLetter(e){this.db.prepare("DELETE FROM event_dead_letters WHERE id=?").run(e)}async listDeadLetters(){return this.db.prepare("SELECT dead_letter_json FROM event_dead_letters ORDER BY created_at").all().map(e=>JSON.parse(e.dead_letter_json))}async redriveDelivery(e,t){if(this.db.prepare(`UPDATE event_deliveries SET status='queued', attempt=0, available_at=?,
|
|
35
|
+
claimed_by=NULL, run_id=NULL, error=NULL, lease_expires_at=NULL,
|
|
36
|
+
invocation_started=0
|
|
37
|
+
WHERE id=? AND status IN (${d.map(()=>"?").join(",")})`).run(t,e,...d).changes!==1)throw new Error(`Event delivery ${e} is not terminal`)}async nextAvailableAt(e){let t=this.db.prepare(`SELECT MIN(CASE
|
|
38
|
+
WHEN status='queued' THEN available_at
|
|
39
|
+
ELSE lease_expires_at END) AS next
|
|
40
|
+
FROM event_deliveries
|
|
41
|
+
WHERE status='queued' OR status IN ('claimed','running')`).get();return t.next===null?void 0:Math.max(e,t.next)}async waitForWork(e){for(;!await this.isIdle();){if(_optionalChain([e, 'optionalAccess', _3 => _3.aborted]))throw e.reason;await this.clock.sleep(25,e);return}await this.clock.sleep(25,e)}async isIdle(){return this.db.prepare(`SELECT COUNT(*) AS count FROM event_deliveries
|
|
42
|
+
WHERE status NOT IN (${d.map(()=>"?").join(",")})`).get(...d).count===0}async load(e){let t=this.db.prepare("SELECT state_json FROM event_program_state WHERE state_key=?").get(e);return t?JSON.parse(t.state_json):void 0}async compareAndSet(e,t,n,i){return this.db.transaction(()=>{i&&this.assertFence(i.deliveryId,i.fencingToken);let s=this.db.prepare("SELECT revision FROM event_program_state WHERE state_key=?").get(e);if(_optionalChain([s, 'optionalAccess', _4 => _4.revision])!==t)throw new Error(`Program state compare-and-set failed for ${e}: expected ${String(t)}, current ${String(_optionalChain([s, 'optionalAccess', _5 => _5.revision]))}`);let r={...structuredClone(n),revision:(_nullishCoalesce(_optionalChain([s, 'optionalAccess', _6 => _6.revision]), () => (0)))+1};return this.db.prepare(`INSERT INTO event_program_state(state_key, revision, state_json, updated_at)
|
|
43
|
+
VALUES(?,?,?,?)
|
|
44
|
+
ON CONFLICT(state_key) DO UPDATE SET revision=excluded.revision,
|
|
45
|
+
state_json=excluded.state_json, updated_at=excluded.updated_at`).run(e,r.revision,JSON.stringify(r),r.updatedAt),r})()}async delete(e){this.db.prepare("DELETE FROM event_program_state WHERE state_key=?").run(e)}async close(){this.db.close()}tryEnqueue(e){return this.db.transaction(()=>{let t=_ax.axEventScopedDedupeKey.call(void 0, e.ingress),n=this.db.prepare("SELECT event_id, delivery_ids_json FROM event_dedupe WHERE dedupe_key=?").get(t);if(n)return{eventId:n.event_id,accepted:!0,duplicate:!0,durability:"persistent",deliveryIds:JSON.parse(n.delivery_ids_json)};let i=this.db.prepare(`SELECT COUNT(*) AS count, COALESCE(SUM(size_bytes),0) AS bytes
|
|
46
|
+
FROM event_deliveries WHERE status NOT IN (${d.map(()=>"?").join(",")})`).get(...d),s=e.deliveries.reduce((a,o)=>a+o.sizeBytes,0);if(i.count+e.deliveries.length>this.maxPendingDeliveries||i.bytes+s>this.maxPendingBytes)return;let r=[],u=this.db.prepare(`INSERT INTO event_deliveries(
|
|
47
|
+
id, ingress_json, identity_scope, route_id, action, target_id,
|
|
48
|
+
instance_key, status, attempt, available_at, accepted_at, size_bytes,
|
|
49
|
+
retry_safety, ordering_mode
|
|
50
|
+
) VALUES(?,?,?,?,?,?,?,'queued',0,?,?,?,?,?)`);for(let a of e.deliveries){let o=_crypto.randomUUID.call(void 0, );r.push(o),u.run(o,JSON.stringify(e.ingress),_ax.axEventIdentityScope.call(void 0, e.ingress.identity),a.routeId,a.action,_nullishCoalesce(a.targetId, () => (null)),a.instanceKey,_nullishCoalesce(a.availableAt, () => (e.acceptedAt)),e.acceptedAt,a.sizeBytes,_nullishCoalesce(a.retrySafety, () => ("unknown")),_nullishCoalesce(a.ordering, () => ("strict")))}return this.db.prepare("INSERT INTO event_dedupe(dedupe_key,event_id,delivery_ids_json,created_at) VALUES(?,?,?,?)").run(t,e.ingress.event.id,JSON.stringify(r),e.acceptedAt),{eventId:e.ingress.event.id,accepted:!0,duplicate:!1,durability:"persistent",deliveryIds:r}})()}rowToDelivery(e){return{id:e.id,sequence:e.sequence,ingress:JSON.parse(e.ingress_json),identityScope:e.identity_scope,routeId:e.route_id,action:e.action,...e.target_id?{targetId:e.target_id}:{},instanceKey:e.instance_key,status:e.status,attempt:e.attempt,availableAt:e.available_at,acceptedAt:e.accepted_at,...e.claimed_by?{claimedBy:e.claimed_by}:{},...e.run_id?{runId:e.run_id}:{},...e.error?{error:e.error}:{},sizeBytes:e.size_bytes,retrySafety:e.retry_safety,ordering:e.ordering_mode,...e.lease_expires_at!==null?{leaseExpiresAt:e.lease_expires_at}:{},fencingToken:e.fencing_token,invocationStarted:e.invocation_started===1}}assertFence(e,t){if(t===void 0)return;let n=this.db.prepare("SELECT fencing_token FROM event_deliveries WHERE id=?").get(e);if(!n||n.fencing_token!==t)throw new Error(`Stale fencing token for event delivery ${e}`)}migrate(){let e=this.db.pragma("user_version",{simple:!0});if(e>c)throw new Error(`Unsupported AxSQLiteEventStore schema ${e}`);e===0&&this.db.exec(`
|
|
51
|
+
CREATE TABLE event_deliveries (
|
|
52
|
+
sequence INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
53
|
+
id TEXT NOT NULL UNIQUE,
|
|
54
|
+
ingress_json TEXT NOT NULL,
|
|
55
|
+
identity_scope TEXT NOT NULL,
|
|
56
|
+
route_id TEXT NOT NULL,
|
|
57
|
+
action TEXT NOT NULL,
|
|
58
|
+
target_id TEXT,
|
|
59
|
+
instance_key TEXT NOT NULL,
|
|
60
|
+
status TEXT NOT NULL,
|
|
61
|
+
attempt INTEGER NOT NULL,
|
|
62
|
+
available_at INTEGER NOT NULL,
|
|
63
|
+
accepted_at INTEGER NOT NULL,
|
|
64
|
+
claimed_by TEXT,
|
|
65
|
+
run_id TEXT,
|
|
66
|
+
error TEXT,
|
|
67
|
+
size_bytes INTEGER NOT NULL,
|
|
68
|
+
retry_safety TEXT NOT NULL,
|
|
69
|
+
ordering_mode TEXT NOT NULL,
|
|
70
|
+
lease_expires_at INTEGER,
|
|
71
|
+
fencing_token INTEGER NOT NULL DEFAULT 0,
|
|
72
|
+
invocation_started INTEGER NOT NULL DEFAULT 0
|
|
73
|
+
);
|
|
74
|
+
CREATE INDEX event_delivery_claim ON event_deliveries(status, available_at, lease_expires_at, sequence);
|
|
75
|
+
CREATE TABLE event_dedupe (
|
|
76
|
+
dedupe_key TEXT PRIMARY KEY,
|
|
77
|
+
event_id TEXT NOT NULL,
|
|
78
|
+
delivery_ids_json TEXT NOT NULL,
|
|
79
|
+
created_at INTEGER NOT NULL
|
|
80
|
+
);
|
|
81
|
+
CREATE TABLE event_runs (
|
|
82
|
+
id TEXT PRIMARY KEY,
|
|
83
|
+
delivery_id TEXT NOT NULL,
|
|
84
|
+
run_json TEXT NOT NULL,
|
|
85
|
+
updated_at INTEGER NOT NULL,
|
|
86
|
+
finished_at INTEGER
|
|
87
|
+
);
|
|
88
|
+
CREATE TABLE event_continuations (
|
|
89
|
+
id TEXT PRIMARY KEY,
|
|
90
|
+
identity_scope TEXT NOT NULL,
|
|
91
|
+
continuation_json TEXT NOT NULL,
|
|
92
|
+
created_at INTEGER NOT NULL,
|
|
93
|
+
expires_at INTEGER,
|
|
94
|
+
completed_at INTEGER
|
|
95
|
+
);
|
|
96
|
+
CREATE TABLE event_continuation_keys (
|
|
97
|
+
correlation_key TEXT PRIMARY KEY,
|
|
98
|
+
continuation_id TEXT NOT NULL REFERENCES event_continuations(id) ON DELETE CASCADE
|
|
99
|
+
);
|
|
100
|
+
CREATE TABLE event_dead_letters (
|
|
101
|
+
id TEXT PRIMARY KEY,
|
|
102
|
+
delivery_id TEXT NOT NULL,
|
|
103
|
+
dead_letter_json TEXT NOT NULL,
|
|
104
|
+
created_at INTEGER NOT NULL
|
|
105
|
+
);
|
|
106
|
+
CREATE TABLE event_program_state (
|
|
107
|
+
state_key TEXT PRIMARY KEY,
|
|
108
|
+
revision INTEGER NOT NULL,
|
|
109
|
+
state_json TEXT NOT NULL,
|
|
110
|
+
updated_at INTEGER NOT NULL
|
|
111
|
+
);
|
|
112
|
+
PRAGMA user_version = 1;
|
|
113
|
+
`)}prune(e){let t=this.options.retention;this.db.transaction(()=>{let n=e-t.eventAndResultMs;this.db.prepare("DELETE FROM event_dedupe WHERE created_at < ?").run(n),this.db.prepare(`UPDATE event_runs
|
|
114
|
+
SET run_json=json_remove(run_json, '$.output', '$.chunks')
|
|
115
|
+
WHERE finished_at IS NOT NULL AND finished_at < ?`).run(n),this.db.prepare(`DELETE FROM event_deliveries
|
|
116
|
+
WHERE accepted_at < ? AND status IN (${d.map(()=>"?").join(",")})`).run(n,...d),this.db.prepare("DELETE FROM event_runs WHERE updated_at < ?").run(e-t.runMetadataAndDeadLettersMs),this.db.prepare("DELETE FROM event_dead_letters WHERE created_at < ?").run(e-t.runMetadataAndDeadLettersMs),this.db.prepare("DELETE FROM event_continuations WHERE completed_at IS NOT NULL AND completed_at < ?").run(e-t.completedContinuationsMs)})()}}, _class);exports.AX_SQLITE_EVENT_STANDARD_RETENTION = b; exports.AxSQLiteEventStore = _;
|
|
117
|
+
//# sourceMappingURL=sqlite.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../event/sqlite.ts"],"names":["SCHEMA_VERSION","MULTI_WORKER_CONFORMANCE","TERMINAL","AX_SQLITE_EVENT_STANDARD_RETENTION","AxSQLiteEventStore","options","AxSystemEventClock","Database"],"mappings":"AAAA,6zBAA2B,gCAmBpB,6GACc,IAEfA,CAAAA,CAAiB,CAAA,CACjBC,CAAAA,CAA2B,kBAAA,CAC3BC,CAAAA,CAAW,CACf,eAAA,CACA,WAAA,CACA,QAAA,CACA,WAAA,CACA,eAAA,CACA,2BAAA,CACA,iBACF,CAAA,CAQaC,CAAAA,8CACX,CACE,gBAAA,CAAkB,CAAA,CAAI,EAAA,CAAK,EAAA,CAAK,EAAA,CAAK,GAAA,CACrC,2BAAA,CAA6B,EAAA,CAAK,EAAA,CAAK,EAAA,CAAK,EAAA,CAAK,GAAA,CACjD,wBAAA,CAA0B,CAAA,CAAI,EAAA,CAAK,EAAA,CAAK,EAAA,CAAK,GAC/C,CAAA,CAsCWC,CAAAA,wCAAN,KAAsE,CAqB3E,WAAA,CAA6BC,CAAAA,CAA8C,qCAA9C,IAAA,CAAA,OAAA,CAAAA,CAAAA,CAC3B,EAAA,CAAI,CAACA,CAAAA,CAAQ,SAAA,CACX,MAAM,IAAI,KAAA,CAAM,gDAAgD,CAAA,CAElE,IAAA,CAAK,KAAA,kBAAQA,CAAAA,CAAQ,KAAA,SAAS,IAAIC,wBAAAA,CAClC,IAAA,CAAK,oBAAA,kBAAuBD,CAAAA,CAAQ,oBAAA,SAAwB,KAAA,CAC5D,IAAA,CAAK,eAAA,kBAAkBA,CAAAA,CAAQ,eAAA,SAAmB,IAAA,CAAO,IAAA,CAAO,MAAA,CAChE,IAAA,CAAK,aAAA,kBAAgBA,CAAAA,CAAQ,aAAA,SAAiB,EAAA,CAAK,IAAA,CAAO,MAAA,CAC1D,IAAA,CAAK,qBAAA,kBACHA,CAAAA,CAAQ,qBAAA,SAAyB,EAAA,CAAK,IAAA,CAAO,MAAA,CAC/C,IAAA,CAAK,EAAA,CAAK,IAAIE,4BAAAA,CAASF,CAAAA,CAAQ,QAAQ,CAAA,CACvC,IAAA,CAAK,EAAA,CAAG,MAAA,CAAO,CAAA,eAAA,mBAAkBA,CAAAA,CAAQ,aAAA,SAAiB,KAAK,CAAA,CAAA;AAoCzD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaqE,4CAAA;AAAA;AAAA;AAWrE,sCAAA;AAAA;AA2BF,yCAAA;AAAA;AAqBA,8CAAA;AAAA;AAAA;AAmDA,uCAAA;AAAA;AAAA;AAAA;AAqCE,2CAAA;AAAA;AAkCF,4BAAA;AAAA;AAAA;AAsCA,6DAAA;AAuCA,wEAAA;AAAA;AAAA;AAcA,mCAAA;AAAA;AAAA;AAAA;AAsBA,iEAAA;AAwCE,8BAAA;AAAA;AAAA;AA2CA,2EAAA;AAgBF,sDAAA;AAAA;AAAA;AAAA;AA2DiB,oDAAA;AA6BN;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA4ET,MAAA;AAAA;AAOA,4DAAA;AAiBV,gDAAA","file":"/home/runner/work/ax/ax/src/tools/dist/event/sqlite.cjs","sourcesContent":["import { randomUUID } from 'node:crypto';\nimport {\n AxEventBackpressureError,\n type AxEventClock,\n type AxEventContinuation,\n type AxEventCorrelationKey,\n type AxEventDeadLetter,\n type AxEventDelivery,\n type AxEventEnqueueRequest,\n type AxEventPayloadStore,\n type AxEventPublishReceipt,\n type AxEventRun,\n type AxEventStore,\n type AxProgramStateEnvelope,\n type AxProgramStateStore,\n AxSystemEventClock,\n axEventIdentityScope,\n axEventScopedCorrelationKey,\n axEventScopedDedupeKey,\n} from '@ax-llm/ax';\nimport Database from 'better-sqlite3';\n\nconst SCHEMA_VERSION = 1;\nconst MULTI_WORKER_CONFORMANCE = 'axevent-store-v1';\nconst TERMINAL = [\n 'waiting_event',\n 'succeeded',\n 'failed',\n 'cancelled',\n 'dead_lettered',\n 'output_persistence_failed',\n 'outcome_unknown',\n] as const;\n\nexport interface AxSQLiteEventRetention {\n eventAndResultMs: number;\n runMetadataAndDeadLettersMs: number;\n completedContinuationsMs: number;\n}\n\nexport const AX_SQLITE_EVENT_STANDARD_RETENTION: Readonly<AxSQLiteEventRetention> =\n {\n eventAndResultMs: 7 * 24 * 60 * 60 * 1_000,\n runMetadataAndDeadLettersMs: 30 * 24 * 60 * 60 * 1_000,\n completedContinuationsMs: 7 * 24 * 60 * 60 * 1_000,\n };\n\nexport interface AxSQLiteEventStoreOptions {\n filename: string;\n clock?: AxEventClock;\n busyTimeoutMs?: number;\n maxPendingDeliveries?: number;\n maxPendingBytes?: number;\n maxEventBytes?: number;\n maxInlinePayloadBytes?: number;\n payloadStore?: AxEventPayloadStore;\n retention: Readonly<AxSQLiteEventRetention>;\n}\n\ntype DeliveryRow = {\n id: string;\n sequence: number;\n ingress_json: string;\n identity_scope: string;\n route_id: string;\n action: AxEventDelivery['action'];\n target_id: string | null;\n instance_key: string;\n status: AxEventDelivery['status'];\n attempt: number;\n available_at: number;\n accepted_at: number;\n claimed_by: string | null;\n run_id: string | null;\n error: string | null;\n size_bytes: number;\n retry_safety: AxEventDelivery['retrySafety'];\n ordering_mode: AxEventDelivery['ordering'];\n lease_expires_at: number | null;\n fencing_token: number;\n invocation_started: number;\n};\n\nexport class AxSQLiteEventStore implements AxEventStore, AxProgramStateStore {\n readonly capabilities = {\n durability: 'persistent',\n coordination: 'multi-worker',\n leases: true,\n transactions: true,\n compareAndSet: true,\n outputPersistence: true,\n conformance: {\n multiWorker: MULTI_WORKER_CONFORMANCE,\n schemaVersion: SCHEMA_VERSION,\n },\n } as const;\n\n private readonly db: Database.Database;\n private readonly clock: AxEventClock;\n private readonly maxPendingDeliveries: number;\n private readonly maxPendingBytes: number;\n private readonly maxEventBytes: number;\n private readonly maxInlinePayloadBytes: number;\n\n constructor(private readonly options: Readonly<AxSQLiteEventStoreOptions>) {\n if (!options.retention) {\n throw new Error('AxSQLiteEventStore requires explicit retention');\n }\n this.clock = options.clock ?? new AxSystemEventClock();\n this.maxPendingDeliveries = options.maxPendingDeliveries ?? 100_000;\n this.maxPendingBytes = options.maxPendingBytes ?? 1024 * 1024 * 1024;\n this.maxEventBytes = options.maxEventBytes ?? 16 * 1024 * 1024;\n this.maxInlinePayloadBytes =\n options.maxInlinePayloadBytes ?? 16 * 1024 * 1024;\n this.db = new Database(options.filename);\n this.db.pragma(`busy_timeout = ${options.busyTimeoutMs ?? 5_000}`);\n this.db.pragma('journal_mode = WAL');\n this.db.pragma('foreign_keys = ON');\n this.migrate();\n this.prune(this.clock.now());\n }\n\n async enqueue(\n request: Readonly<AxEventEnqueueRequest>,\n signal?: AbortSignal\n ): Promise<AxEventPublishReceipt> {\n const eventBytes = Buffer.byteLength(JSON.stringify(request.ingress));\n if (eventBytes > this.maxEventBytes) {\n throw new AxEventBackpressureError(\n `Event is ${eventBytes} bytes; maximum is ${this.maxEventBytes}`\n );\n }\n const deadline = this.clock.now() + request.publishTimeoutMs;\n for (;;) {\n if (signal?.aborted) throw signal.reason;\n const result = this.tryEnqueue(request);\n if (result) return result;\n const remaining = deadline - this.clock.now();\n if (remaining <= 0) throw new AxEventBackpressureError();\n await this.clock.sleep(Math.min(25, remaining), signal);\n }\n }\n\n async claim(\n workerId: string,\n now: number,\n leaseMs = 30_000\n ): Promise<AxEventDelivery | undefined> {\n return this.db.transaction((): AxEventDelivery | undefined => {\n const row = this.db\n .prepare(\n `SELECT d.* FROM event_deliveries d\n WHERE (\n (d.status = 'queued' AND d.available_at <= ?)\n OR\n (d.status IN ('claimed','running') AND d.lease_expires_at <= ?)\n )\n AND (\n d.ordering_mode = 'relaxed'\n OR NOT EXISTS (\n SELECT 1 FROM event_deliveries earlier\n WHERE earlier.sequence < d.sequence\n AND COALESCE(earlier.target_id, '') = COALESCE(d.target_id, '')\n AND earlier.instance_key = d.instance_key\n AND earlier.status NOT IN (${TERMINAL.map(() => '?').join(',')})\n )\n )\n ORDER BY d.sequence LIMIT 1`\n )\n .get(now, now, ...TERMINAL) as DeliveryRow | undefined;\n if (!row) return;\n const recovered = row.status !== 'queued';\n const fencingToken = row.fencing_token + 1;\n const updated = this.db\n .prepare(\n `UPDATE event_deliveries\n SET status='claimed', claimed_by=?, lease_expires_at=?, fencing_token=?\n WHERE id=? AND fencing_token=?`\n )\n .run(workerId, now + leaseMs, fencingToken, row.id, row.fencing_token);\n if (updated.changes !== 1) return;\n return {\n ...this.rowToDelivery({\n ...row,\n status: 'claimed',\n claimed_by: workerId,\n lease_expires_at: now + leaseMs,\n fencing_token: fencingToken,\n }),\n ...(recovered ? { recoveredFromExpiredLease: true } : {}),\n };\n })();\n }\n\n async renewClaim(\n deliveryId: string,\n workerId: string,\n fencingToken: number,\n leaseExpiresAt: number\n ): Promise<void> {\n const result = this.db\n .prepare(\n `UPDATE event_deliveries SET lease_expires_at=?\n WHERE id=? AND claimed_by=? AND fencing_token=?\n AND status IN ('claimed','running')`\n )\n .run(leaseExpiresAt, deliveryId, workerId, fencingToken);\n if (result.changes !== 1)\n throw new Error(`Stale event claim for ${deliveryId}`);\n }\n\n async getDelivery(\n deliveryId: string\n ): Promise<Readonly<AxEventDelivery> | undefined> {\n const row = this.db\n .prepare('SELECT * FROM event_deliveries WHERE id=?')\n .get(deliveryId) as DeliveryRow | undefined;\n return row ? this.rowToDelivery(row) : undefined;\n }\n\n async saveDelivery(delivery: Readonly<AxEventDelivery>): Promise<void> {\n const result = this.db\n .prepare(\n `UPDATE event_deliveries SET\n status=?, attempt=?, available_at=?, claimed_by=?, run_id=?, error=?,\n lease_expires_at=?, invocation_started=?, retry_safety=?, ordering_mode=?\n WHERE id=? AND fencing_token=?`\n )\n .run(\n delivery.status,\n delivery.attempt,\n delivery.availableAt,\n delivery.claimedBy ?? null,\n delivery.runId ?? null,\n delivery.error ?? null,\n delivery.leaseExpiresAt ?? null,\n delivery.invocationStarted ? 1 : 0,\n delivery.retrySafety,\n delivery.ordering,\n delivery.id,\n delivery.fencingToken ?? -1\n );\n if (result.changes !== 1) {\n throw new Error(`Stale fencing token for event delivery ${delivery.id}`);\n }\n }\n\n async saveRun(run: Readonly<AxEventRun>): Promise<void> {\n this.assertFence(run.deliveryId, run.fencingToken);\n let stored: AxEventRun = structuredClone(run);\n const hasPayload = run.output !== undefined || run.chunks !== undefined;\n const payload = { output: run.output, chunks: run.chunks };\n if (\n hasPayload &&\n Buffer.byteLength(JSON.stringify(payload)) > this.maxInlinePayloadBytes\n ) {\n if (!this.options.payloadStore) {\n throw new Error(\n `output_persistence_failed: run ${run.id} exceeded ${this.maxInlinePayloadBytes} inline bytes`\n );\n }\n const outputRef = await this.options.payloadStore.put(\n `event-run:${run.id}`,\n payload\n );\n stored = {\n ...stored,\n output: undefined,\n chunks: undefined,\n outputRef,\n };\n }\n this.db\n .prepare(\n `INSERT INTO event_runs(id, delivery_id, run_json, updated_at, finished_at)\n VALUES(?,?,?,?,?)\n ON CONFLICT(id) DO UPDATE SET\n run_json=excluded.run_json, updated_at=excluded.updated_at,\n finished_at=excluded.finished_at`\n )\n .run(\n run.id,\n run.deliveryId,\n JSON.stringify(stored),\n this.clock.now(),\n run.finishedAt ?? null\n );\n }\n\n async getRun(runId: string): Promise<Readonly<AxEventRun> | undefined> {\n const row = this.db\n .prepare('SELECT run_json FROM event_runs WHERE id=?')\n .get(runId) as { run_json: string } | undefined;\n if (!row) return;\n const run = JSON.parse(row.run_json) as AxEventRun;\n if (run.outputRef && this.options.payloadStore) {\n const payload = (await this.options.payloadStore.get(run.outputRef)) as {\n output?: unknown;\n chunks?: AxEventRun['chunks'];\n };\n return { ...run, output: payload.output, chunks: payload.chunks };\n }\n return run;\n }\n\n async registerContinuation(\n continuation: Readonly<AxEventContinuation>\n ): Promise<void> {\n this.db.transaction(() => {\n this.db\n .prepare(\n `INSERT INTO event_continuations\n (id, identity_scope, continuation_json, created_at, expires_at)\n VALUES(?,?,?,?,?)`\n )\n .run(\n continuation.id,\n continuation.identityScope,\n JSON.stringify(continuation),\n continuation.createdAt,\n continuation.expiresAt ?? null\n );\n const insert = this.db.prepare(\n 'INSERT INTO event_continuation_keys(correlation_key, continuation_id) VALUES(?,?)'\n );\n for (const correlation of continuation.correlation) {\n insert.run(\n axEventScopedCorrelationKey(\n continuation.identityScope,\n correlation.kind,\n correlation.value\n ),\n continuation.id\n );\n }\n })();\n }\n\n async findContinuation(\n identityScope: string,\n correlation: Readonly<AxEventCorrelationKey>,\n now: number\n ): Promise<Readonly<AxEventContinuation> | undefined> {\n const row = this.db\n .prepare(\n `SELECT c.continuation_json, c.id, c.expires_at\n FROM event_continuation_keys k\n JOIN event_continuations c ON c.id=k.continuation_id\n WHERE k.correlation_key=? AND c.completed_at IS NULL`\n )\n .get(\n axEventScopedCorrelationKey(\n identityScope,\n correlation.kind,\n correlation.value\n )\n ) as\n | { continuation_json: string; id: string; expires_at: number | null }\n | undefined;\n if (!row) return;\n if (row.expires_at !== null && row.expires_at <= now) {\n await this.completeContinuation(row.id);\n return;\n }\n return JSON.parse(row.continuation_json) as AxEventContinuation;\n }\n\n async completeContinuation(id: string): Promise<void> {\n this.db.transaction(() => {\n this.db\n .prepare('DELETE FROM event_continuation_keys WHERE continuation_id=?')\n .run(id);\n this.db\n .prepare(\n 'UPDATE event_continuations SET completed_at=? WHERE id=? AND completed_at IS NULL'\n )\n .run(this.clock.now(), id);\n })();\n }\n\n async addDeadLetter(deadLetter: Readonly<AxEventDeadLetter>): Promise<void> {\n this.db\n .prepare(\n `INSERT OR REPLACE INTO event_dead_letters\n (id, delivery_id, dead_letter_json, created_at) VALUES(?,?,?,?)`\n )\n .run(\n deadLetter.id,\n deadLetter.deliveryId,\n JSON.stringify(deadLetter),\n deadLetter.createdAt\n );\n }\n\n async getDeadLetter(\n id: string\n ): Promise<Readonly<AxEventDeadLetter> | undefined> {\n const row = this.db\n .prepare('SELECT dead_letter_json FROM event_dead_letters WHERE id=?')\n .get(id) as { dead_letter_json: string } | undefined;\n return row\n ? (JSON.parse(row.dead_letter_json) as AxEventDeadLetter)\n : undefined;\n }\n\n async removeDeadLetter(id: string): Promise<void> {\n this.db.prepare('DELETE FROM event_dead_letters WHERE id=?').run(id);\n }\n\n async listDeadLetters(): Promise<readonly Readonly<AxEventDeadLetter>[]> {\n return (\n this.db\n .prepare(\n 'SELECT dead_letter_json FROM event_dead_letters ORDER BY created_at'\n )\n .all() as { dead_letter_json: string }[]\n ).map((row) => JSON.parse(row.dead_letter_json) as AxEventDeadLetter);\n }\n\n async redriveDelivery(deliveryId: string, now: number): Promise<void> {\n const result = this.db\n .prepare(\n `UPDATE event_deliveries SET status='queued', attempt=0, available_at=?,\n claimed_by=NULL, run_id=NULL, error=NULL, lease_expires_at=NULL,\n invocation_started=0\n WHERE id=? AND status IN (${TERMINAL.map(() => '?').join(',')})`\n )\n .run(now, deliveryId, ...TERMINAL);\n if (result.changes !== 1) {\n throw new Error(`Event delivery ${deliveryId} is not terminal`);\n }\n }\n\n async nextAvailableAt(now: number): Promise<number | undefined> {\n const row = this.db\n .prepare(\n `SELECT MIN(CASE\n WHEN status='queued' THEN available_at\n ELSE lease_expires_at END) AS next\n FROM event_deliveries\n WHERE status='queued' OR status IN ('claimed','running')`\n )\n .get() as { next: number | null };\n return row.next === null ? undefined : Math.max(now, row.next);\n }\n\n async waitForWork(signal?: AbortSignal): Promise<void> {\n while (!(await this.isIdle())) {\n if (signal?.aborted) throw signal.reason;\n await this.clock.sleep(25, signal);\n return;\n }\n await this.clock.sleep(25, signal);\n }\n\n async isIdle(): Promise<boolean> {\n const row = this.db\n .prepare(\n `SELECT COUNT(*) AS count FROM event_deliveries\n WHERE status NOT IN (${TERMINAL.map(() => '?').join(',')})`\n )\n .get(...TERMINAL) as { count: number };\n return row.count === 0;\n }\n\n async load(\n key: string\n ): Promise<Readonly<AxProgramStateEnvelope> | undefined> {\n const row = this.db\n .prepare('SELECT state_json FROM event_program_state WHERE state_key=?')\n .get(key) as { state_json: string } | undefined;\n return row\n ? (JSON.parse(row.state_json) as AxProgramStateEnvelope)\n : undefined;\n }\n\n async compareAndSet(\n key: string,\n expectedRevision: number | undefined,\n state: Readonly<Omit<AxProgramStateEnvelope, 'revision'>>,\n fence?: Readonly<{ deliveryId: string; fencingToken: number }>\n ): Promise<Readonly<AxProgramStateEnvelope>> {\n return this.db.transaction(() => {\n if (fence) this.assertFence(fence.deliveryId, fence.fencingToken);\n const current = this.db\n .prepare('SELECT revision FROM event_program_state WHERE state_key=?')\n .get(key) as { revision: number } | undefined;\n if (current?.revision !== expectedRevision) {\n throw new Error(\n `Program state compare-and-set failed for ${key}: expected ${String(expectedRevision)}, current ${String(current?.revision)}`\n );\n }\n const next: AxProgramStateEnvelope = {\n ...structuredClone(state),\n revision: (current?.revision ?? 0) + 1,\n };\n this.db\n .prepare(\n `INSERT INTO event_program_state(state_key, revision, state_json, updated_at)\n VALUES(?,?,?,?)\n ON CONFLICT(state_key) DO UPDATE SET revision=excluded.revision,\n state_json=excluded.state_json, updated_at=excluded.updated_at`\n )\n .run(key, next.revision, JSON.stringify(next), next.updatedAt);\n return next;\n })();\n }\n\n async delete(key: string): Promise<void> {\n this.db\n .prepare('DELETE FROM event_program_state WHERE state_key=?')\n .run(key);\n }\n\n async close(): Promise<void> {\n this.db.close();\n }\n\n private tryEnqueue(\n request: Readonly<AxEventEnqueueRequest>\n ): AxEventPublishReceipt | undefined {\n return this.db.transaction((): AxEventPublishReceipt | undefined => {\n const dedupeKey = axEventScopedDedupeKey(request.ingress);\n const duplicate = this.db\n .prepare(\n 'SELECT event_id, delivery_ids_json FROM event_dedupe WHERE dedupe_key=?'\n )\n .get(dedupeKey) as\n | { event_id: string; delivery_ids_json: string }\n | undefined;\n if (duplicate) {\n return {\n eventId: duplicate.event_id,\n accepted: true,\n duplicate: true,\n durability: 'persistent',\n deliveryIds: JSON.parse(duplicate.delivery_ids_json) as string[],\n };\n }\n const pending = this.db\n .prepare(\n `SELECT COUNT(*) AS count, COALESCE(SUM(size_bytes),0) AS bytes\n FROM event_deliveries WHERE status NOT IN (${TERMINAL.map(() => '?').join(',')})`\n )\n .get(...TERMINAL) as { count: number; bytes: number };\n const requiredBytes = request.deliveries.reduce(\n (sum, delivery) => sum + delivery.sizeBytes,\n 0\n );\n if (\n pending.count + request.deliveries.length > this.maxPendingDeliveries ||\n pending.bytes + requiredBytes > this.maxPendingBytes\n ) {\n return;\n }\n const deliveryIds: string[] = [];\n const insert = this.db.prepare(\n `INSERT INTO event_deliveries(\n id, ingress_json, identity_scope, route_id, action, target_id,\n instance_key, status, attempt, available_at, accepted_at, size_bytes,\n retry_safety, ordering_mode\n ) VALUES(?,?,?,?,?,?,?,'queued',0,?,?,?,?,?)`\n );\n for (const descriptor of request.deliveries) {\n const id = randomUUID();\n deliveryIds.push(id);\n insert.run(\n id,\n JSON.stringify(request.ingress),\n axEventIdentityScope(request.ingress.identity),\n descriptor.routeId,\n descriptor.action,\n descriptor.targetId ?? null,\n descriptor.instanceKey,\n descriptor.availableAt ?? request.acceptedAt,\n request.acceptedAt,\n descriptor.sizeBytes,\n descriptor.retrySafety ?? 'unknown',\n descriptor.ordering ?? 'strict'\n );\n }\n this.db\n .prepare(\n 'INSERT INTO event_dedupe(dedupe_key,event_id,delivery_ids_json,created_at) VALUES(?,?,?,?)'\n )\n .run(\n dedupeKey,\n request.ingress.event.id,\n JSON.stringify(deliveryIds),\n request.acceptedAt\n );\n return {\n eventId: request.ingress.event.id,\n accepted: true,\n duplicate: false,\n durability: 'persistent',\n deliveryIds,\n };\n })();\n }\n\n private rowToDelivery(row: DeliveryRow): AxEventDelivery {\n return {\n id: row.id,\n sequence: row.sequence,\n ingress: JSON.parse(row.ingress_json),\n identityScope: row.identity_scope,\n routeId: row.route_id,\n action: row.action,\n ...(row.target_id ? { targetId: row.target_id } : {}),\n instanceKey: row.instance_key,\n status: row.status,\n attempt: row.attempt,\n availableAt: row.available_at,\n acceptedAt: row.accepted_at,\n ...(row.claimed_by ? { claimedBy: row.claimed_by } : {}),\n ...(row.run_id ? { runId: row.run_id } : {}),\n ...(row.error ? { error: row.error } : {}),\n sizeBytes: row.size_bytes,\n retrySafety: row.retry_safety,\n ordering: row.ordering_mode,\n ...(row.lease_expires_at !== null\n ? { leaseExpiresAt: row.lease_expires_at }\n : {}),\n fencingToken: row.fencing_token,\n invocationStarted: row.invocation_started === 1,\n };\n }\n\n private assertFence(deliveryId: string, fencingToken?: number): void {\n if (fencingToken === undefined) return;\n const row = this.db\n .prepare('SELECT fencing_token FROM event_deliveries WHERE id=?')\n .get(deliveryId) as { fencing_token: number } | undefined;\n if (!row || row.fencing_token !== fencingToken) {\n throw new Error(`Stale fencing token for event delivery ${deliveryId}`);\n }\n }\n\n private migrate(): void {\n const version = this.db.pragma('user_version', { simple: true }) as number;\n if (version > SCHEMA_VERSION) {\n throw new Error(`Unsupported AxSQLiteEventStore schema ${version}`);\n }\n if (version === 0) {\n this.db.exec(`\n CREATE TABLE event_deliveries (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n id TEXT NOT NULL UNIQUE,\n ingress_json TEXT NOT NULL,\n identity_scope TEXT NOT NULL,\n route_id TEXT NOT NULL,\n action TEXT NOT NULL,\n target_id TEXT,\n instance_key TEXT NOT NULL,\n status TEXT NOT NULL,\n attempt INTEGER NOT NULL,\n available_at INTEGER NOT NULL,\n accepted_at INTEGER NOT NULL,\n claimed_by TEXT,\n run_id TEXT,\n error TEXT,\n size_bytes INTEGER NOT NULL,\n retry_safety TEXT NOT NULL,\n ordering_mode TEXT NOT NULL,\n lease_expires_at INTEGER,\n fencing_token INTEGER NOT NULL DEFAULT 0,\n invocation_started INTEGER NOT NULL DEFAULT 0\n );\n CREATE INDEX event_delivery_claim ON event_deliveries(status, available_at, lease_expires_at, sequence);\n CREATE TABLE event_dedupe (\n dedupe_key TEXT PRIMARY KEY,\n event_id TEXT NOT NULL,\n delivery_ids_json TEXT NOT NULL,\n created_at INTEGER NOT NULL\n );\n CREATE TABLE event_runs (\n id TEXT PRIMARY KEY,\n delivery_id TEXT NOT NULL,\n run_json TEXT NOT NULL,\n updated_at INTEGER NOT NULL,\n finished_at INTEGER\n );\n CREATE TABLE event_continuations (\n id TEXT PRIMARY KEY,\n identity_scope TEXT NOT NULL,\n continuation_json TEXT NOT NULL,\n created_at INTEGER NOT NULL,\n expires_at INTEGER,\n completed_at INTEGER\n );\n CREATE TABLE event_continuation_keys (\n correlation_key TEXT PRIMARY KEY,\n continuation_id TEXT NOT NULL REFERENCES event_continuations(id) ON DELETE CASCADE\n );\n CREATE TABLE event_dead_letters (\n id TEXT PRIMARY KEY,\n delivery_id TEXT NOT NULL,\n dead_letter_json TEXT NOT NULL,\n created_at INTEGER NOT NULL\n );\n CREATE TABLE event_program_state (\n state_key TEXT PRIMARY KEY,\n revision INTEGER NOT NULL,\n state_json TEXT NOT NULL,\n updated_at INTEGER NOT NULL\n );\n PRAGMA user_version = 1;\n `);\n }\n }\n\n private prune(now: number): void {\n const retention = this.options.retention;\n this.db.transaction(() => {\n const eventCutoff = now - retention.eventAndResultMs;\n this.db\n .prepare('DELETE FROM event_dedupe WHERE created_at < ?')\n .run(eventCutoff);\n this.db\n .prepare(\n `UPDATE event_runs\n SET run_json=json_remove(run_json, '$.output', '$.chunks')\n WHERE finished_at IS NOT NULL AND finished_at < ?`\n )\n .run(eventCutoff);\n this.db\n .prepare(\n `DELETE FROM event_deliveries\n WHERE accepted_at < ? AND status IN (${TERMINAL.map(() => '?').join(',')})`\n )\n .run(eventCutoff, ...TERMINAL);\n this.db\n .prepare('DELETE FROM event_runs WHERE updated_at < ?')\n .run(now - retention.runMetadataAndDeadLettersMs);\n this.db\n .prepare('DELETE FROM event_dead_letters WHERE created_at < ?')\n .run(now - retention.runMetadataAndDeadLettersMs);\n this.db\n .prepare(\n 'DELETE FROM event_continuations WHERE completed_at IS NOT NULL AND completed_at < ?'\n )\n .run(now - retention.completedContinuationsMs);\n })();\n }\n}\n"]}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { AxEventStore, AxProgramStateStore, AxEventClock, AxEventPayloadStore, AxEventEnqueueRequest, AxEventPublishReceipt, AxEventDelivery, AxEventRun, AxEventContinuation, AxEventCorrelationKey, AxEventDeadLetter, AxProgramStateEnvelope } from '@ax-llm/ax';
|
|
2
|
+
|
|
3
|
+
interface AxSQLiteEventRetention {
|
|
4
|
+
eventAndResultMs: number;
|
|
5
|
+
runMetadataAndDeadLettersMs: number;
|
|
6
|
+
completedContinuationsMs: number;
|
|
7
|
+
}
|
|
8
|
+
declare const AX_SQLITE_EVENT_STANDARD_RETENTION: Readonly<AxSQLiteEventRetention>;
|
|
9
|
+
interface AxSQLiteEventStoreOptions {
|
|
10
|
+
filename: string;
|
|
11
|
+
clock?: AxEventClock;
|
|
12
|
+
busyTimeoutMs?: number;
|
|
13
|
+
maxPendingDeliveries?: number;
|
|
14
|
+
maxPendingBytes?: number;
|
|
15
|
+
maxEventBytes?: number;
|
|
16
|
+
maxInlinePayloadBytes?: number;
|
|
17
|
+
payloadStore?: AxEventPayloadStore;
|
|
18
|
+
retention: Readonly<AxSQLiteEventRetention>;
|
|
19
|
+
}
|
|
20
|
+
declare class AxSQLiteEventStore implements AxEventStore, AxProgramStateStore {
|
|
21
|
+
private readonly options;
|
|
22
|
+
readonly capabilities: {
|
|
23
|
+
readonly durability: "persistent";
|
|
24
|
+
readonly coordination: "multi-worker";
|
|
25
|
+
readonly leases: true;
|
|
26
|
+
readonly transactions: true;
|
|
27
|
+
readonly compareAndSet: true;
|
|
28
|
+
readonly outputPersistence: true;
|
|
29
|
+
readonly conformance: {
|
|
30
|
+
readonly multiWorker: "axevent-store-v1";
|
|
31
|
+
readonly schemaVersion: 1;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
private readonly db;
|
|
35
|
+
private readonly clock;
|
|
36
|
+
private readonly maxPendingDeliveries;
|
|
37
|
+
private readonly maxPendingBytes;
|
|
38
|
+
private readonly maxEventBytes;
|
|
39
|
+
private readonly maxInlinePayloadBytes;
|
|
40
|
+
constructor(options: Readonly<AxSQLiteEventStoreOptions>);
|
|
41
|
+
enqueue(request: Readonly<AxEventEnqueueRequest>, signal?: AbortSignal): Promise<AxEventPublishReceipt>;
|
|
42
|
+
claim(workerId: string, now: number, leaseMs?: number): Promise<AxEventDelivery | undefined>;
|
|
43
|
+
renewClaim(deliveryId: string, workerId: string, fencingToken: number, leaseExpiresAt: number): Promise<void>;
|
|
44
|
+
getDelivery(deliveryId: string): Promise<Readonly<AxEventDelivery> | undefined>;
|
|
45
|
+
saveDelivery(delivery: Readonly<AxEventDelivery>): Promise<void>;
|
|
46
|
+
saveRun(run: Readonly<AxEventRun>): Promise<void>;
|
|
47
|
+
getRun(runId: string): Promise<Readonly<AxEventRun> | undefined>;
|
|
48
|
+
registerContinuation(continuation: Readonly<AxEventContinuation>): Promise<void>;
|
|
49
|
+
findContinuation(identityScope: string, correlation: Readonly<AxEventCorrelationKey>, now: number): Promise<Readonly<AxEventContinuation> | undefined>;
|
|
50
|
+
completeContinuation(id: string): Promise<void>;
|
|
51
|
+
addDeadLetter(deadLetter: Readonly<AxEventDeadLetter>): Promise<void>;
|
|
52
|
+
getDeadLetter(id: string): Promise<Readonly<AxEventDeadLetter> | undefined>;
|
|
53
|
+
removeDeadLetter(id: string): Promise<void>;
|
|
54
|
+
listDeadLetters(): Promise<readonly Readonly<AxEventDeadLetter>[]>;
|
|
55
|
+
redriveDelivery(deliveryId: string, now: number): Promise<void>;
|
|
56
|
+
nextAvailableAt(now: number): Promise<number | undefined>;
|
|
57
|
+
waitForWork(signal?: AbortSignal): Promise<void>;
|
|
58
|
+
isIdle(): Promise<boolean>;
|
|
59
|
+
load(key: string): Promise<Readonly<AxProgramStateEnvelope> | undefined>;
|
|
60
|
+
compareAndSet(key: string, expectedRevision: number | undefined, state: Readonly<Omit<AxProgramStateEnvelope, 'revision'>>, fence?: Readonly<{
|
|
61
|
+
deliveryId: string;
|
|
62
|
+
fencingToken: number;
|
|
63
|
+
}>): Promise<Readonly<AxProgramStateEnvelope>>;
|
|
64
|
+
delete(key: string): Promise<void>;
|
|
65
|
+
close(): Promise<void>;
|
|
66
|
+
private tryEnqueue;
|
|
67
|
+
private rowToDelivery;
|
|
68
|
+
private assertFence;
|
|
69
|
+
private migrate;
|
|
70
|
+
private prune;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export { AX_SQLITE_EVENT_STANDARD_RETENTION, type AxSQLiteEventRetention, AxSQLiteEventStore, type AxSQLiteEventStoreOptions };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { AxEventStore, AxProgramStateStore, AxEventClock, AxEventPayloadStore, AxEventEnqueueRequest, AxEventPublishReceipt, AxEventDelivery, AxEventRun, AxEventContinuation, AxEventCorrelationKey, AxEventDeadLetter, AxProgramStateEnvelope } from '@ax-llm/ax';
|
|
2
|
+
|
|
3
|
+
interface AxSQLiteEventRetention {
|
|
4
|
+
eventAndResultMs: number;
|
|
5
|
+
runMetadataAndDeadLettersMs: number;
|
|
6
|
+
completedContinuationsMs: number;
|
|
7
|
+
}
|
|
8
|
+
declare const AX_SQLITE_EVENT_STANDARD_RETENTION: Readonly<AxSQLiteEventRetention>;
|
|
9
|
+
interface AxSQLiteEventStoreOptions {
|
|
10
|
+
filename: string;
|
|
11
|
+
clock?: AxEventClock;
|
|
12
|
+
busyTimeoutMs?: number;
|
|
13
|
+
maxPendingDeliveries?: number;
|
|
14
|
+
maxPendingBytes?: number;
|
|
15
|
+
maxEventBytes?: number;
|
|
16
|
+
maxInlinePayloadBytes?: number;
|
|
17
|
+
payloadStore?: AxEventPayloadStore;
|
|
18
|
+
retention: Readonly<AxSQLiteEventRetention>;
|
|
19
|
+
}
|
|
20
|
+
declare class AxSQLiteEventStore implements AxEventStore, AxProgramStateStore {
|
|
21
|
+
private readonly options;
|
|
22
|
+
readonly capabilities: {
|
|
23
|
+
readonly durability: "persistent";
|
|
24
|
+
readonly coordination: "multi-worker";
|
|
25
|
+
readonly leases: true;
|
|
26
|
+
readonly transactions: true;
|
|
27
|
+
readonly compareAndSet: true;
|
|
28
|
+
readonly outputPersistence: true;
|
|
29
|
+
readonly conformance: {
|
|
30
|
+
readonly multiWorker: "axevent-store-v1";
|
|
31
|
+
readonly schemaVersion: 1;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
private readonly db;
|
|
35
|
+
private readonly clock;
|
|
36
|
+
private readonly maxPendingDeliveries;
|
|
37
|
+
private readonly maxPendingBytes;
|
|
38
|
+
private readonly maxEventBytes;
|
|
39
|
+
private readonly maxInlinePayloadBytes;
|
|
40
|
+
constructor(options: Readonly<AxSQLiteEventStoreOptions>);
|
|
41
|
+
enqueue(request: Readonly<AxEventEnqueueRequest>, signal?: AbortSignal): Promise<AxEventPublishReceipt>;
|
|
42
|
+
claim(workerId: string, now: number, leaseMs?: number): Promise<AxEventDelivery | undefined>;
|
|
43
|
+
renewClaim(deliveryId: string, workerId: string, fencingToken: number, leaseExpiresAt: number): Promise<void>;
|
|
44
|
+
getDelivery(deliveryId: string): Promise<Readonly<AxEventDelivery> | undefined>;
|
|
45
|
+
saveDelivery(delivery: Readonly<AxEventDelivery>): Promise<void>;
|
|
46
|
+
saveRun(run: Readonly<AxEventRun>): Promise<void>;
|
|
47
|
+
getRun(runId: string): Promise<Readonly<AxEventRun> | undefined>;
|
|
48
|
+
registerContinuation(continuation: Readonly<AxEventContinuation>): Promise<void>;
|
|
49
|
+
findContinuation(identityScope: string, correlation: Readonly<AxEventCorrelationKey>, now: number): Promise<Readonly<AxEventContinuation> | undefined>;
|
|
50
|
+
completeContinuation(id: string): Promise<void>;
|
|
51
|
+
addDeadLetter(deadLetter: Readonly<AxEventDeadLetter>): Promise<void>;
|
|
52
|
+
getDeadLetter(id: string): Promise<Readonly<AxEventDeadLetter> | undefined>;
|
|
53
|
+
removeDeadLetter(id: string): Promise<void>;
|
|
54
|
+
listDeadLetters(): Promise<readonly Readonly<AxEventDeadLetter>[]>;
|
|
55
|
+
redriveDelivery(deliveryId: string, now: number): Promise<void>;
|
|
56
|
+
nextAvailableAt(now: number): Promise<number | undefined>;
|
|
57
|
+
waitForWork(signal?: AbortSignal): Promise<void>;
|
|
58
|
+
isIdle(): Promise<boolean>;
|
|
59
|
+
load(key: string): Promise<Readonly<AxProgramStateEnvelope> | undefined>;
|
|
60
|
+
compareAndSet(key: string, expectedRevision: number | undefined, state: Readonly<Omit<AxProgramStateEnvelope, 'revision'>>, fence?: Readonly<{
|
|
61
|
+
deliveryId: string;
|
|
62
|
+
fencingToken: number;
|
|
63
|
+
}>): Promise<Readonly<AxProgramStateEnvelope>>;
|
|
64
|
+
delete(key: string): Promise<void>;
|
|
65
|
+
close(): Promise<void>;
|
|
66
|
+
private tryEnqueue;
|
|
67
|
+
private rowToDelivery;
|
|
68
|
+
private assertFence;
|
|
69
|
+
private migrate;
|
|
70
|
+
private prune;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export { AX_SQLITE_EVENT_STANDARD_RETENTION, type AxSQLiteEventRetention, AxSQLiteEventStore, type AxSQLiteEventStoreOptions };
|
package/event/sqlite.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import{randomUUID as p}from"crypto";import{AxEventBackpressureError as E,AxSystemEventClock as v,axEventIdentityScope as y,axEventScopedCorrelationKey as l,axEventScopedDedupeKey as T}from"@ax-llm/ax";import m from"better-sqlite3";var c=1,N="axevent-store-v1",d=["waiting_event","succeeded","failed","cancelled","dead_lettered","output_persistence_failed","outcome_unknown"],b={eventAndResultMs:7*24*60*60*1e3,runMetadataAndDeadLettersMs:30*24*60*60*1e3,completedContinuationsMs:7*24*60*60*1e3},_=class{constructor(e){this.options=e;if(!e.retention)throw new Error("AxSQLiteEventStore requires explicit retention");this.clock=e.clock??new v,this.maxPendingDeliveries=e.maxPendingDeliveries??1e5,this.maxPendingBytes=e.maxPendingBytes??1024*1024*1024,this.maxEventBytes=e.maxEventBytes??16*1024*1024,this.maxInlinePayloadBytes=e.maxInlinePayloadBytes??16*1024*1024,this.db=new m(e.filename),this.db.pragma(`busy_timeout = ${e.busyTimeoutMs??5e3}`),this.db.pragma("journal_mode = WAL"),this.db.pragma("foreign_keys = ON"),this.migrate(),this.prune(this.clock.now())}capabilities={durability:"persistent",coordination:"multi-worker",leases:!0,transactions:!0,compareAndSet:!0,outputPersistence:!0,conformance:{multiWorker:N,schemaVersion:c}};db;clock;maxPendingDeliveries;maxPendingBytes;maxEventBytes;maxInlinePayloadBytes;async enqueue(e,t){let n=Buffer.byteLength(JSON.stringify(e.ingress));if(n>this.maxEventBytes)throw new E(`Event is ${n} bytes; maximum is ${this.maxEventBytes}`);let i=this.clock.now()+e.publishTimeoutMs;for(;;){if(t?.aborted)throw t.reason;let s=this.tryEnqueue(e);if(s)return s;let r=i-this.clock.now();if(r<=0)throw new E;await this.clock.sleep(Math.min(25,r),t)}}async claim(e,t,n=3e4){return this.db.transaction(()=>{let i=this.db.prepare(`SELECT d.* FROM event_deliveries d
|
|
2
|
+
WHERE (
|
|
3
|
+
(d.status = 'queued' AND d.available_at <= ?)
|
|
4
|
+
OR
|
|
5
|
+
(d.status IN ('claimed','running') AND d.lease_expires_at <= ?)
|
|
6
|
+
)
|
|
7
|
+
AND (
|
|
8
|
+
d.ordering_mode = 'relaxed'
|
|
9
|
+
OR NOT EXISTS (
|
|
10
|
+
SELECT 1 FROM event_deliveries earlier
|
|
11
|
+
WHERE earlier.sequence < d.sequence
|
|
12
|
+
AND COALESCE(earlier.target_id, '') = COALESCE(d.target_id, '')
|
|
13
|
+
AND earlier.instance_key = d.instance_key
|
|
14
|
+
AND earlier.status NOT IN (${d.map(()=>"?").join(",")})
|
|
15
|
+
)
|
|
16
|
+
)
|
|
17
|
+
ORDER BY d.sequence LIMIT 1`).get(t,t,...d);if(!i)return;let s=i.status!=="queued",r=i.fencing_token+1;if(this.db.prepare(`UPDATE event_deliveries
|
|
18
|
+
SET status='claimed', claimed_by=?, lease_expires_at=?, fencing_token=?
|
|
19
|
+
WHERE id=? AND fencing_token=?`).run(e,t+n,r,i.id,i.fencing_token).changes===1)return{...this.rowToDelivery({...i,status:"claimed",claimed_by:e,lease_expires_at:t+n,fencing_token:r}),...s?{recoveredFromExpiredLease:!0}:{}}})()}async renewClaim(e,t,n,i){if(this.db.prepare(`UPDATE event_deliveries SET lease_expires_at=?
|
|
20
|
+
WHERE id=? AND claimed_by=? AND fencing_token=?
|
|
21
|
+
AND status IN ('claimed','running')`).run(i,e,t,n).changes!==1)throw new Error(`Stale event claim for ${e}`)}async getDelivery(e){let t=this.db.prepare("SELECT * FROM event_deliveries WHERE id=?").get(e);return t?this.rowToDelivery(t):void 0}async saveDelivery(e){if(this.db.prepare(`UPDATE event_deliveries SET
|
|
22
|
+
status=?, attempt=?, available_at=?, claimed_by=?, run_id=?, error=?,
|
|
23
|
+
lease_expires_at=?, invocation_started=?, retry_safety=?, ordering_mode=?
|
|
24
|
+
WHERE id=? AND fencing_token=?`).run(e.status,e.attempt,e.availableAt,e.claimedBy??null,e.runId??null,e.error??null,e.leaseExpiresAt??null,e.invocationStarted?1:0,e.retrySafety,e.ordering,e.id,e.fencingToken??-1).changes!==1)throw new Error(`Stale fencing token for event delivery ${e.id}`)}async saveRun(e){this.assertFence(e.deliveryId,e.fencingToken);let t=structuredClone(e),n=e.output!==void 0||e.chunks!==void 0,i={output:e.output,chunks:e.chunks};if(n&&Buffer.byteLength(JSON.stringify(i))>this.maxInlinePayloadBytes){if(!this.options.payloadStore)throw new Error(`output_persistence_failed: run ${e.id} exceeded ${this.maxInlinePayloadBytes} inline bytes`);let s=await this.options.payloadStore.put(`event-run:${e.id}`,i);t={...t,output:void 0,chunks:void 0,outputRef:s}}this.db.prepare(`INSERT INTO event_runs(id, delivery_id, run_json, updated_at, finished_at)
|
|
25
|
+
VALUES(?,?,?,?,?)
|
|
26
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
27
|
+
run_json=excluded.run_json, updated_at=excluded.updated_at,
|
|
28
|
+
finished_at=excluded.finished_at`).run(e.id,e.deliveryId,JSON.stringify(t),this.clock.now(),e.finishedAt??null)}async getRun(e){let t=this.db.prepare("SELECT run_json FROM event_runs WHERE id=?").get(e);if(!t)return;let n=JSON.parse(t.run_json);if(n.outputRef&&this.options.payloadStore){let i=await this.options.payloadStore.get(n.outputRef);return{...n,output:i.output,chunks:i.chunks}}return n}async registerContinuation(e){this.db.transaction(()=>{this.db.prepare(`INSERT INTO event_continuations
|
|
29
|
+
(id, identity_scope, continuation_json, created_at, expires_at)
|
|
30
|
+
VALUES(?,?,?,?,?)`).run(e.id,e.identityScope,JSON.stringify(e),e.createdAt,e.expiresAt??null);let t=this.db.prepare("INSERT INTO event_continuation_keys(correlation_key, continuation_id) VALUES(?,?)");for(let n of e.correlation)t.run(l(e.identityScope,n.kind,n.value),e.id)})()}async findContinuation(e,t,n){let i=this.db.prepare(`SELECT c.continuation_json, c.id, c.expires_at
|
|
31
|
+
FROM event_continuation_keys k
|
|
32
|
+
JOIN event_continuations c ON c.id=k.continuation_id
|
|
33
|
+
WHERE k.correlation_key=? AND c.completed_at IS NULL`).get(l(e,t.kind,t.value));if(i){if(i.expires_at!==null&&i.expires_at<=n){await this.completeContinuation(i.id);return}return JSON.parse(i.continuation_json)}}async completeContinuation(e){this.db.transaction(()=>{this.db.prepare("DELETE FROM event_continuation_keys WHERE continuation_id=?").run(e),this.db.prepare("UPDATE event_continuations SET completed_at=? WHERE id=? AND completed_at IS NULL").run(this.clock.now(),e)})()}async addDeadLetter(e){this.db.prepare(`INSERT OR REPLACE INTO event_dead_letters
|
|
34
|
+
(id, delivery_id, dead_letter_json, created_at) VALUES(?,?,?,?)`).run(e.id,e.deliveryId,JSON.stringify(e),e.createdAt)}async getDeadLetter(e){let t=this.db.prepare("SELECT dead_letter_json FROM event_dead_letters WHERE id=?").get(e);return t?JSON.parse(t.dead_letter_json):void 0}async removeDeadLetter(e){this.db.prepare("DELETE FROM event_dead_letters WHERE id=?").run(e)}async listDeadLetters(){return this.db.prepare("SELECT dead_letter_json FROM event_dead_letters ORDER BY created_at").all().map(e=>JSON.parse(e.dead_letter_json))}async redriveDelivery(e,t){if(this.db.prepare(`UPDATE event_deliveries SET status='queued', attempt=0, available_at=?,
|
|
35
|
+
claimed_by=NULL, run_id=NULL, error=NULL, lease_expires_at=NULL,
|
|
36
|
+
invocation_started=0
|
|
37
|
+
WHERE id=? AND status IN (${d.map(()=>"?").join(",")})`).run(t,e,...d).changes!==1)throw new Error(`Event delivery ${e} is not terminal`)}async nextAvailableAt(e){let t=this.db.prepare(`SELECT MIN(CASE
|
|
38
|
+
WHEN status='queued' THEN available_at
|
|
39
|
+
ELSE lease_expires_at END) AS next
|
|
40
|
+
FROM event_deliveries
|
|
41
|
+
WHERE status='queued' OR status IN ('claimed','running')`).get();return t.next===null?void 0:Math.max(e,t.next)}async waitForWork(e){for(;!await this.isIdle();){if(e?.aborted)throw e.reason;await this.clock.sleep(25,e);return}await this.clock.sleep(25,e)}async isIdle(){return this.db.prepare(`SELECT COUNT(*) AS count FROM event_deliveries
|
|
42
|
+
WHERE status NOT IN (${d.map(()=>"?").join(",")})`).get(...d).count===0}async load(e){let t=this.db.prepare("SELECT state_json FROM event_program_state WHERE state_key=?").get(e);return t?JSON.parse(t.state_json):void 0}async compareAndSet(e,t,n,i){return this.db.transaction(()=>{i&&this.assertFence(i.deliveryId,i.fencingToken);let s=this.db.prepare("SELECT revision FROM event_program_state WHERE state_key=?").get(e);if(s?.revision!==t)throw new Error(`Program state compare-and-set failed for ${e}: expected ${String(t)}, current ${String(s?.revision)}`);let r={...structuredClone(n),revision:(s?.revision??0)+1};return this.db.prepare(`INSERT INTO event_program_state(state_key, revision, state_json, updated_at)
|
|
43
|
+
VALUES(?,?,?,?)
|
|
44
|
+
ON CONFLICT(state_key) DO UPDATE SET revision=excluded.revision,
|
|
45
|
+
state_json=excluded.state_json, updated_at=excluded.updated_at`).run(e,r.revision,JSON.stringify(r),r.updatedAt),r})()}async delete(e){this.db.prepare("DELETE FROM event_program_state WHERE state_key=?").run(e)}async close(){this.db.close()}tryEnqueue(e){return this.db.transaction(()=>{let t=T(e.ingress),n=this.db.prepare("SELECT event_id, delivery_ids_json FROM event_dedupe WHERE dedupe_key=?").get(t);if(n)return{eventId:n.event_id,accepted:!0,duplicate:!0,durability:"persistent",deliveryIds:JSON.parse(n.delivery_ids_json)};let i=this.db.prepare(`SELECT COUNT(*) AS count, COALESCE(SUM(size_bytes),0) AS bytes
|
|
46
|
+
FROM event_deliveries WHERE status NOT IN (${d.map(()=>"?").join(",")})`).get(...d),s=e.deliveries.reduce((a,o)=>a+o.sizeBytes,0);if(i.count+e.deliveries.length>this.maxPendingDeliveries||i.bytes+s>this.maxPendingBytes)return;let r=[],u=this.db.prepare(`INSERT INTO event_deliveries(
|
|
47
|
+
id, ingress_json, identity_scope, route_id, action, target_id,
|
|
48
|
+
instance_key, status, attempt, available_at, accepted_at, size_bytes,
|
|
49
|
+
retry_safety, ordering_mode
|
|
50
|
+
) VALUES(?,?,?,?,?,?,?,'queued',0,?,?,?,?,?)`);for(let a of e.deliveries){let o=p();r.push(o),u.run(o,JSON.stringify(e.ingress),y(e.ingress.identity),a.routeId,a.action,a.targetId??null,a.instanceKey,a.availableAt??e.acceptedAt,e.acceptedAt,a.sizeBytes,a.retrySafety??"unknown",a.ordering??"strict")}return this.db.prepare("INSERT INTO event_dedupe(dedupe_key,event_id,delivery_ids_json,created_at) VALUES(?,?,?,?)").run(t,e.ingress.event.id,JSON.stringify(r),e.acceptedAt),{eventId:e.ingress.event.id,accepted:!0,duplicate:!1,durability:"persistent",deliveryIds:r}})()}rowToDelivery(e){return{id:e.id,sequence:e.sequence,ingress:JSON.parse(e.ingress_json),identityScope:e.identity_scope,routeId:e.route_id,action:e.action,...e.target_id?{targetId:e.target_id}:{},instanceKey:e.instance_key,status:e.status,attempt:e.attempt,availableAt:e.available_at,acceptedAt:e.accepted_at,...e.claimed_by?{claimedBy:e.claimed_by}:{},...e.run_id?{runId:e.run_id}:{},...e.error?{error:e.error}:{},sizeBytes:e.size_bytes,retrySafety:e.retry_safety,ordering:e.ordering_mode,...e.lease_expires_at!==null?{leaseExpiresAt:e.lease_expires_at}:{},fencingToken:e.fencing_token,invocationStarted:e.invocation_started===1}}assertFence(e,t){if(t===void 0)return;let n=this.db.prepare("SELECT fencing_token FROM event_deliveries WHERE id=?").get(e);if(!n||n.fencing_token!==t)throw new Error(`Stale fencing token for event delivery ${e}`)}migrate(){let e=this.db.pragma("user_version",{simple:!0});if(e>c)throw new Error(`Unsupported AxSQLiteEventStore schema ${e}`);e===0&&this.db.exec(`
|
|
51
|
+
CREATE TABLE event_deliveries (
|
|
52
|
+
sequence INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
53
|
+
id TEXT NOT NULL UNIQUE,
|
|
54
|
+
ingress_json TEXT NOT NULL,
|
|
55
|
+
identity_scope TEXT NOT NULL,
|
|
56
|
+
route_id TEXT NOT NULL,
|
|
57
|
+
action TEXT NOT NULL,
|
|
58
|
+
target_id TEXT,
|
|
59
|
+
instance_key TEXT NOT NULL,
|
|
60
|
+
status TEXT NOT NULL,
|
|
61
|
+
attempt INTEGER NOT NULL,
|
|
62
|
+
available_at INTEGER NOT NULL,
|
|
63
|
+
accepted_at INTEGER NOT NULL,
|
|
64
|
+
claimed_by TEXT,
|
|
65
|
+
run_id TEXT,
|
|
66
|
+
error TEXT,
|
|
67
|
+
size_bytes INTEGER NOT NULL,
|
|
68
|
+
retry_safety TEXT NOT NULL,
|
|
69
|
+
ordering_mode TEXT NOT NULL,
|
|
70
|
+
lease_expires_at INTEGER,
|
|
71
|
+
fencing_token INTEGER NOT NULL DEFAULT 0,
|
|
72
|
+
invocation_started INTEGER NOT NULL DEFAULT 0
|
|
73
|
+
);
|
|
74
|
+
CREATE INDEX event_delivery_claim ON event_deliveries(status, available_at, lease_expires_at, sequence);
|
|
75
|
+
CREATE TABLE event_dedupe (
|
|
76
|
+
dedupe_key TEXT PRIMARY KEY,
|
|
77
|
+
event_id TEXT NOT NULL,
|
|
78
|
+
delivery_ids_json TEXT NOT NULL,
|
|
79
|
+
created_at INTEGER NOT NULL
|
|
80
|
+
);
|
|
81
|
+
CREATE TABLE event_runs (
|
|
82
|
+
id TEXT PRIMARY KEY,
|
|
83
|
+
delivery_id TEXT NOT NULL,
|
|
84
|
+
run_json TEXT NOT NULL,
|
|
85
|
+
updated_at INTEGER NOT NULL,
|
|
86
|
+
finished_at INTEGER
|
|
87
|
+
);
|
|
88
|
+
CREATE TABLE event_continuations (
|
|
89
|
+
id TEXT PRIMARY KEY,
|
|
90
|
+
identity_scope TEXT NOT NULL,
|
|
91
|
+
continuation_json TEXT NOT NULL,
|
|
92
|
+
created_at INTEGER NOT NULL,
|
|
93
|
+
expires_at INTEGER,
|
|
94
|
+
completed_at INTEGER
|
|
95
|
+
);
|
|
96
|
+
CREATE TABLE event_continuation_keys (
|
|
97
|
+
correlation_key TEXT PRIMARY KEY,
|
|
98
|
+
continuation_id TEXT NOT NULL REFERENCES event_continuations(id) ON DELETE CASCADE
|
|
99
|
+
);
|
|
100
|
+
CREATE TABLE event_dead_letters (
|
|
101
|
+
id TEXT PRIMARY KEY,
|
|
102
|
+
delivery_id TEXT NOT NULL,
|
|
103
|
+
dead_letter_json TEXT NOT NULL,
|
|
104
|
+
created_at INTEGER NOT NULL
|
|
105
|
+
);
|
|
106
|
+
CREATE TABLE event_program_state (
|
|
107
|
+
state_key TEXT PRIMARY KEY,
|
|
108
|
+
revision INTEGER NOT NULL,
|
|
109
|
+
state_json TEXT NOT NULL,
|
|
110
|
+
updated_at INTEGER NOT NULL
|
|
111
|
+
);
|
|
112
|
+
PRAGMA user_version = 1;
|
|
113
|
+
`)}prune(e){let t=this.options.retention;this.db.transaction(()=>{let n=e-t.eventAndResultMs;this.db.prepare("DELETE FROM event_dedupe WHERE created_at < ?").run(n),this.db.prepare(`UPDATE event_runs
|
|
114
|
+
SET run_json=json_remove(run_json, '$.output', '$.chunks')
|
|
115
|
+
WHERE finished_at IS NOT NULL AND finished_at < ?`).run(n),this.db.prepare(`DELETE FROM event_deliveries
|
|
116
|
+
WHERE accepted_at < ? AND status IN (${d.map(()=>"?").join(",")})`).run(n,...d),this.db.prepare("DELETE FROM event_runs WHERE updated_at < ?").run(e-t.runMetadataAndDeadLettersMs),this.db.prepare("DELETE FROM event_dead_letters WHERE created_at < ?").run(e-t.runMetadataAndDeadLettersMs),this.db.prepare("DELETE FROM event_continuations WHERE completed_at IS NOT NULL AND completed_at < ?").run(e-t.completedContinuationsMs)})()}};export{b as AX_SQLITE_EVENT_STANDARD_RETENTION,_ as AxSQLiteEventStore};
|
|
117
|
+
//# sourceMappingURL=sqlite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../event/sqlite.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport {\n AxEventBackpressureError,\n type AxEventClock,\n type AxEventContinuation,\n type AxEventCorrelationKey,\n type AxEventDeadLetter,\n type AxEventDelivery,\n type AxEventEnqueueRequest,\n type AxEventPayloadStore,\n type AxEventPublishReceipt,\n type AxEventRun,\n type AxEventStore,\n type AxProgramStateEnvelope,\n type AxProgramStateStore,\n AxSystemEventClock,\n axEventIdentityScope,\n axEventScopedCorrelationKey,\n axEventScopedDedupeKey,\n} from '@ax-llm/ax';\nimport Database from 'better-sqlite3';\n\nconst SCHEMA_VERSION = 1;\nconst MULTI_WORKER_CONFORMANCE = 'axevent-store-v1';\nconst TERMINAL = [\n 'waiting_event',\n 'succeeded',\n 'failed',\n 'cancelled',\n 'dead_lettered',\n 'output_persistence_failed',\n 'outcome_unknown',\n] as const;\n\nexport interface AxSQLiteEventRetention {\n eventAndResultMs: number;\n runMetadataAndDeadLettersMs: number;\n completedContinuationsMs: number;\n}\n\nexport const AX_SQLITE_EVENT_STANDARD_RETENTION: Readonly<AxSQLiteEventRetention> =\n {\n eventAndResultMs: 7 * 24 * 60 * 60 * 1_000,\n runMetadataAndDeadLettersMs: 30 * 24 * 60 * 60 * 1_000,\n completedContinuationsMs: 7 * 24 * 60 * 60 * 1_000,\n };\n\nexport interface AxSQLiteEventStoreOptions {\n filename: string;\n clock?: AxEventClock;\n busyTimeoutMs?: number;\n maxPendingDeliveries?: number;\n maxPendingBytes?: number;\n maxEventBytes?: number;\n maxInlinePayloadBytes?: number;\n payloadStore?: AxEventPayloadStore;\n retention: Readonly<AxSQLiteEventRetention>;\n}\n\ntype DeliveryRow = {\n id: string;\n sequence: number;\n ingress_json: string;\n identity_scope: string;\n route_id: string;\n action: AxEventDelivery['action'];\n target_id: string | null;\n instance_key: string;\n status: AxEventDelivery['status'];\n attempt: number;\n available_at: number;\n accepted_at: number;\n claimed_by: string | null;\n run_id: string | null;\n error: string | null;\n size_bytes: number;\n retry_safety: AxEventDelivery['retrySafety'];\n ordering_mode: AxEventDelivery['ordering'];\n lease_expires_at: number | null;\n fencing_token: number;\n invocation_started: number;\n};\n\nexport class AxSQLiteEventStore implements AxEventStore, AxProgramStateStore {\n readonly capabilities = {\n durability: 'persistent',\n coordination: 'multi-worker',\n leases: true,\n transactions: true,\n compareAndSet: true,\n outputPersistence: true,\n conformance: {\n multiWorker: MULTI_WORKER_CONFORMANCE,\n schemaVersion: SCHEMA_VERSION,\n },\n } as const;\n\n private readonly db: Database.Database;\n private readonly clock: AxEventClock;\n private readonly maxPendingDeliveries: number;\n private readonly maxPendingBytes: number;\n private readonly maxEventBytes: number;\n private readonly maxInlinePayloadBytes: number;\n\n constructor(private readonly options: Readonly<AxSQLiteEventStoreOptions>) {\n if (!options.retention) {\n throw new Error('AxSQLiteEventStore requires explicit retention');\n }\n this.clock = options.clock ?? new AxSystemEventClock();\n this.maxPendingDeliveries = options.maxPendingDeliveries ?? 100_000;\n this.maxPendingBytes = options.maxPendingBytes ?? 1024 * 1024 * 1024;\n this.maxEventBytes = options.maxEventBytes ?? 16 * 1024 * 1024;\n this.maxInlinePayloadBytes =\n options.maxInlinePayloadBytes ?? 16 * 1024 * 1024;\n this.db = new Database(options.filename);\n this.db.pragma(`busy_timeout = ${options.busyTimeoutMs ?? 5_000}`);\n this.db.pragma('journal_mode = WAL');\n this.db.pragma('foreign_keys = ON');\n this.migrate();\n this.prune(this.clock.now());\n }\n\n async enqueue(\n request: Readonly<AxEventEnqueueRequest>,\n signal?: AbortSignal\n ): Promise<AxEventPublishReceipt> {\n const eventBytes = Buffer.byteLength(JSON.stringify(request.ingress));\n if (eventBytes > this.maxEventBytes) {\n throw new AxEventBackpressureError(\n `Event is ${eventBytes} bytes; maximum is ${this.maxEventBytes}`\n );\n }\n const deadline = this.clock.now() + request.publishTimeoutMs;\n for (;;) {\n if (signal?.aborted) throw signal.reason;\n const result = this.tryEnqueue(request);\n if (result) return result;\n const remaining = deadline - this.clock.now();\n if (remaining <= 0) throw new AxEventBackpressureError();\n await this.clock.sleep(Math.min(25, remaining), signal);\n }\n }\n\n async claim(\n workerId: string,\n now: number,\n leaseMs = 30_000\n ): Promise<AxEventDelivery | undefined> {\n return this.db.transaction((): AxEventDelivery | undefined => {\n const row = this.db\n .prepare(\n `SELECT d.* FROM event_deliveries d\n WHERE (\n (d.status = 'queued' AND d.available_at <= ?)\n OR\n (d.status IN ('claimed','running') AND d.lease_expires_at <= ?)\n )\n AND (\n d.ordering_mode = 'relaxed'\n OR NOT EXISTS (\n SELECT 1 FROM event_deliveries earlier\n WHERE earlier.sequence < d.sequence\n AND COALESCE(earlier.target_id, '') = COALESCE(d.target_id, '')\n AND earlier.instance_key = d.instance_key\n AND earlier.status NOT IN (${TERMINAL.map(() => '?').join(',')})\n )\n )\n ORDER BY d.sequence LIMIT 1`\n )\n .get(now, now, ...TERMINAL) as DeliveryRow | undefined;\n if (!row) return;\n const recovered = row.status !== 'queued';\n const fencingToken = row.fencing_token + 1;\n const updated = this.db\n .prepare(\n `UPDATE event_deliveries\n SET status='claimed', claimed_by=?, lease_expires_at=?, fencing_token=?\n WHERE id=? AND fencing_token=?`\n )\n .run(workerId, now + leaseMs, fencingToken, row.id, row.fencing_token);\n if (updated.changes !== 1) return;\n return {\n ...this.rowToDelivery({\n ...row,\n status: 'claimed',\n claimed_by: workerId,\n lease_expires_at: now + leaseMs,\n fencing_token: fencingToken,\n }),\n ...(recovered ? { recoveredFromExpiredLease: true } : {}),\n };\n })();\n }\n\n async renewClaim(\n deliveryId: string,\n workerId: string,\n fencingToken: number,\n leaseExpiresAt: number\n ): Promise<void> {\n const result = this.db\n .prepare(\n `UPDATE event_deliveries SET lease_expires_at=?\n WHERE id=? AND claimed_by=? AND fencing_token=?\n AND status IN ('claimed','running')`\n )\n .run(leaseExpiresAt, deliveryId, workerId, fencingToken);\n if (result.changes !== 1)\n throw new Error(`Stale event claim for ${deliveryId}`);\n }\n\n async getDelivery(\n deliveryId: string\n ): Promise<Readonly<AxEventDelivery> | undefined> {\n const row = this.db\n .prepare('SELECT * FROM event_deliveries WHERE id=?')\n .get(deliveryId) as DeliveryRow | undefined;\n return row ? this.rowToDelivery(row) : undefined;\n }\n\n async saveDelivery(delivery: Readonly<AxEventDelivery>): Promise<void> {\n const result = this.db\n .prepare(\n `UPDATE event_deliveries SET\n status=?, attempt=?, available_at=?, claimed_by=?, run_id=?, error=?,\n lease_expires_at=?, invocation_started=?, retry_safety=?, ordering_mode=?\n WHERE id=? AND fencing_token=?`\n )\n .run(\n delivery.status,\n delivery.attempt,\n delivery.availableAt,\n delivery.claimedBy ?? null,\n delivery.runId ?? null,\n delivery.error ?? null,\n delivery.leaseExpiresAt ?? null,\n delivery.invocationStarted ? 1 : 0,\n delivery.retrySafety,\n delivery.ordering,\n delivery.id,\n delivery.fencingToken ?? -1\n );\n if (result.changes !== 1) {\n throw new Error(`Stale fencing token for event delivery ${delivery.id}`);\n }\n }\n\n async saveRun(run: Readonly<AxEventRun>): Promise<void> {\n this.assertFence(run.deliveryId, run.fencingToken);\n let stored: AxEventRun = structuredClone(run);\n const hasPayload = run.output !== undefined || run.chunks !== undefined;\n const payload = { output: run.output, chunks: run.chunks };\n if (\n hasPayload &&\n Buffer.byteLength(JSON.stringify(payload)) > this.maxInlinePayloadBytes\n ) {\n if (!this.options.payloadStore) {\n throw new Error(\n `output_persistence_failed: run ${run.id} exceeded ${this.maxInlinePayloadBytes} inline bytes`\n );\n }\n const outputRef = await this.options.payloadStore.put(\n `event-run:${run.id}`,\n payload\n );\n stored = {\n ...stored,\n output: undefined,\n chunks: undefined,\n outputRef,\n };\n }\n this.db\n .prepare(\n `INSERT INTO event_runs(id, delivery_id, run_json, updated_at, finished_at)\n VALUES(?,?,?,?,?)\n ON CONFLICT(id) DO UPDATE SET\n run_json=excluded.run_json, updated_at=excluded.updated_at,\n finished_at=excluded.finished_at`\n )\n .run(\n run.id,\n run.deliveryId,\n JSON.stringify(stored),\n this.clock.now(),\n run.finishedAt ?? null\n );\n }\n\n async getRun(runId: string): Promise<Readonly<AxEventRun> | undefined> {\n const row = this.db\n .prepare('SELECT run_json FROM event_runs WHERE id=?')\n .get(runId) as { run_json: string } | undefined;\n if (!row) return;\n const run = JSON.parse(row.run_json) as AxEventRun;\n if (run.outputRef && this.options.payloadStore) {\n const payload = (await this.options.payloadStore.get(run.outputRef)) as {\n output?: unknown;\n chunks?: AxEventRun['chunks'];\n };\n return { ...run, output: payload.output, chunks: payload.chunks };\n }\n return run;\n }\n\n async registerContinuation(\n continuation: Readonly<AxEventContinuation>\n ): Promise<void> {\n this.db.transaction(() => {\n this.db\n .prepare(\n `INSERT INTO event_continuations\n (id, identity_scope, continuation_json, created_at, expires_at)\n VALUES(?,?,?,?,?)`\n )\n .run(\n continuation.id,\n continuation.identityScope,\n JSON.stringify(continuation),\n continuation.createdAt,\n continuation.expiresAt ?? null\n );\n const insert = this.db.prepare(\n 'INSERT INTO event_continuation_keys(correlation_key, continuation_id) VALUES(?,?)'\n );\n for (const correlation of continuation.correlation) {\n insert.run(\n axEventScopedCorrelationKey(\n continuation.identityScope,\n correlation.kind,\n correlation.value\n ),\n continuation.id\n );\n }\n })();\n }\n\n async findContinuation(\n identityScope: string,\n correlation: Readonly<AxEventCorrelationKey>,\n now: number\n ): Promise<Readonly<AxEventContinuation> | undefined> {\n const row = this.db\n .prepare(\n `SELECT c.continuation_json, c.id, c.expires_at\n FROM event_continuation_keys k\n JOIN event_continuations c ON c.id=k.continuation_id\n WHERE k.correlation_key=? AND c.completed_at IS NULL`\n )\n .get(\n axEventScopedCorrelationKey(\n identityScope,\n correlation.kind,\n correlation.value\n )\n ) as\n | { continuation_json: string; id: string; expires_at: number | null }\n | undefined;\n if (!row) return;\n if (row.expires_at !== null && row.expires_at <= now) {\n await this.completeContinuation(row.id);\n return;\n }\n return JSON.parse(row.continuation_json) as AxEventContinuation;\n }\n\n async completeContinuation(id: string): Promise<void> {\n this.db.transaction(() => {\n this.db\n .prepare('DELETE FROM event_continuation_keys WHERE continuation_id=?')\n .run(id);\n this.db\n .prepare(\n 'UPDATE event_continuations SET completed_at=? WHERE id=? AND completed_at IS NULL'\n )\n .run(this.clock.now(), id);\n })();\n }\n\n async addDeadLetter(deadLetter: Readonly<AxEventDeadLetter>): Promise<void> {\n this.db\n .prepare(\n `INSERT OR REPLACE INTO event_dead_letters\n (id, delivery_id, dead_letter_json, created_at) VALUES(?,?,?,?)`\n )\n .run(\n deadLetter.id,\n deadLetter.deliveryId,\n JSON.stringify(deadLetter),\n deadLetter.createdAt\n );\n }\n\n async getDeadLetter(\n id: string\n ): Promise<Readonly<AxEventDeadLetter> | undefined> {\n const row = this.db\n .prepare('SELECT dead_letter_json FROM event_dead_letters WHERE id=?')\n .get(id) as { dead_letter_json: string } | undefined;\n return row\n ? (JSON.parse(row.dead_letter_json) as AxEventDeadLetter)\n : undefined;\n }\n\n async removeDeadLetter(id: string): Promise<void> {\n this.db.prepare('DELETE FROM event_dead_letters WHERE id=?').run(id);\n }\n\n async listDeadLetters(): Promise<readonly Readonly<AxEventDeadLetter>[]> {\n return (\n this.db\n .prepare(\n 'SELECT dead_letter_json FROM event_dead_letters ORDER BY created_at'\n )\n .all() as { dead_letter_json: string }[]\n ).map((row) => JSON.parse(row.dead_letter_json) as AxEventDeadLetter);\n }\n\n async redriveDelivery(deliveryId: string, now: number): Promise<void> {\n const result = this.db\n .prepare(\n `UPDATE event_deliveries SET status='queued', attempt=0, available_at=?,\n claimed_by=NULL, run_id=NULL, error=NULL, lease_expires_at=NULL,\n invocation_started=0\n WHERE id=? AND status IN (${TERMINAL.map(() => '?').join(',')})`\n )\n .run(now, deliveryId, ...TERMINAL);\n if (result.changes !== 1) {\n throw new Error(`Event delivery ${deliveryId} is not terminal`);\n }\n }\n\n async nextAvailableAt(now: number): Promise<number | undefined> {\n const row = this.db\n .prepare(\n `SELECT MIN(CASE\n WHEN status='queued' THEN available_at\n ELSE lease_expires_at END) AS next\n FROM event_deliveries\n WHERE status='queued' OR status IN ('claimed','running')`\n )\n .get() as { next: number | null };\n return row.next === null ? undefined : Math.max(now, row.next);\n }\n\n async waitForWork(signal?: AbortSignal): Promise<void> {\n while (!(await this.isIdle())) {\n if (signal?.aborted) throw signal.reason;\n await this.clock.sleep(25, signal);\n return;\n }\n await this.clock.sleep(25, signal);\n }\n\n async isIdle(): Promise<boolean> {\n const row = this.db\n .prepare(\n `SELECT COUNT(*) AS count FROM event_deliveries\n WHERE status NOT IN (${TERMINAL.map(() => '?').join(',')})`\n )\n .get(...TERMINAL) as { count: number };\n return row.count === 0;\n }\n\n async load(\n key: string\n ): Promise<Readonly<AxProgramStateEnvelope> | undefined> {\n const row = this.db\n .prepare('SELECT state_json FROM event_program_state WHERE state_key=?')\n .get(key) as { state_json: string } | undefined;\n return row\n ? (JSON.parse(row.state_json) as AxProgramStateEnvelope)\n : undefined;\n }\n\n async compareAndSet(\n key: string,\n expectedRevision: number | undefined,\n state: Readonly<Omit<AxProgramStateEnvelope, 'revision'>>,\n fence?: Readonly<{ deliveryId: string; fencingToken: number }>\n ): Promise<Readonly<AxProgramStateEnvelope>> {\n return this.db.transaction(() => {\n if (fence) this.assertFence(fence.deliveryId, fence.fencingToken);\n const current = this.db\n .prepare('SELECT revision FROM event_program_state WHERE state_key=?')\n .get(key) as { revision: number } | undefined;\n if (current?.revision !== expectedRevision) {\n throw new Error(\n `Program state compare-and-set failed for ${key}: expected ${String(expectedRevision)}, current ${String(current?.revision)}`\n );\n }\n const next: AxProgramStateEnvelope = {\n ...structuredClone(state),\n revision: (current?.revision ?? 0) + 1,\n };\n this.db\n .prepare(\n `INSERT INTO event_program_state(state_key, revision, state_json, updated_at)\n VALUES(?,?,?,?)\n ON CONFLICT(state_key) DO UPDATE SET revision=excluded.revision,\n state_json=excluded.state_json, updated_at=excluded.updated_at`\n )\n .run(key, next.revision, JSON.stringify(next), next.updatedAt);\n return next;\n })();\n }\n\n async delete(key: string): Promise<void> {\n this.db\n .prepare('DELETE FROM event_program_state WHERE state_key=?')\n .run(key);\n }\n\n async close(): Promise<void> {\n this.db.close();\n }\n\n private tryEnqueue(\n request: Readonly<AxEventEnqueueRequest>\n ): AxEventPublishReceipt | undefined {\n return this.db.transaction((): AxEventPublishReceipt | undefined => {\n const dedupeKey = axEventScopedDedupeKey(request.ingress);\n const duplicate = this.db\n .prepare(\n 'SELECT event_id, delivery_ids_json FROM event_dedupe WHERE dedupe_key=?'\n )\n .get(dedupeKey) as\n | { event_id: string; delivery_ids_json: string }\n | undefined;\n if (duplicate) {\n return {\n eventId: duplicate.event_id,\n accepted: true,\n duplicate: true,\n durability: 'persistent',\n deliveryIds: JSON.parse(duplicate.delivery_ids_json) as string[],\n };\n }\n const pending = this.db\n .prepare(\n `SELECT COUNT(*) AS count, COALESCE(SUM(size_bytes),0) AS bytes\n FROM event_deliveries WHERE status NOT IN (${TERMINAL.map(() => '?').join(',')})`\n )\n .get(...TERMINAL) as { count: number; bytes: number };\n const requiredBytes = request.deliveries.reduce(\n (sum, delivery) => sum + delivery.sizeBytes,\n 0\n );\n if (\n pending.count + request.deliveries.length > this.maxPendingDeliveries ||\n pending.bytes + requiredBytes > this.maxPendingBytes\n ) {\n return;\n }\n const deliveryIds: string[] = [];\n const insert = this.db.prepare(\n `INSERT INTO event_deliveries(\n id, ingress_json, identity_scope, route_id, action, target_id,\n instance_key, status, attempt, available_at, accepted_at, size_bytes,\n retry_safety, ordering_mode\n ) VALUES(?,?,?,?,?,?,?,'queued',0,?,?,?,?,?)`\n );\n for (const descriptor of request.deliveries) {\n const id = randomUUID();\n deliveryIds.push(id);\n insert.run(\n id,\n JSON.stringify(request.ingress),\n axEventIdentityScope(request.ingress.identity),\n descriptor.routeId,\n descriptor.action,\n descriptor.targetId ?? null,\n descriptor.instanceKey,\n descriptor.availableAt ?? request.acceptedAt,\n request.acceptedAt,\n descriptor.sizeBytes,\n descriptor.retrySafety ?? 'unknown',\n descriptor.ordering ?? 'strict'\n );\n }\n this.db\n .prepare(\n 'INSERT INTO event_dedupe(dedupe_key,event_id,delivery_ids_json,created_at) VALUES(?,?,?,?)'\n )\n .run(\n dedupeKey,\n request.ingress.event.id,\n JSON.stringify(deliveryIds),\n request.acceptedAt\n );\n return {\n eventId: request.ingress.event.id,\n accepted: true,\n duplicate: false,\n durability: 'persistent',\n deliveryIds,\n };\n })();\n }\n\n private rowToDelivery(row: DeliveryRow): AxEventDelivery {\n return {\n id: row.id,\n sequence: row.sequence,\n ingress: JSON.parse(row.ingress_json),\n identityScope: row.identity_scope,\n routeId: row.route_id,\n action: row.action,\n ...(row.target_id ? { targetId: row.target_id } : {}),\n instanceKey: row.instance_key,\n status: row.status,\n attempt: row.attempt,\n availableAt: row.available_at,\n acceptedAt: row.accepted_at,\n ...(row.claimed_by ? { claimedBy: row.claimed_by } : {}),\n ...(row.run_id ? { runId: row.run_id } : {}),\n ...(row.error ? { error: row.error } : {}),\n sizeBytes: row.size_bytes,\n retrySafety: row.retry_safety,\n ordering: row.ordering_mode,\n ...(row.lease_expires_at !== null\n ? { leaseExpiresAt: row.lease_expires_at }\n : {}),\n fencingToken: row.fencing_token,\n invocationStarted: row.invocation_started === 1,\n };\n }\n\n private assertFence(deliveryId: string, fencingToken?: number): void {\n if (fencingToken === undefined) return;\n const row = this.db\n .prepare('SELECT fencing_token FROM event_deliveries WHERE id=?')\n .get(deliveryId) as { fencing_token: number } | undefined;\n if (!row || row.fencing_token !== fencingToken) {\n throw new Error(`Stale fencing token for event delivery ${deliveryId}`);\n }\n }\n\n private migrate(): void {\n const version = this.db.pragma('user_version', { simple: true }) as number;\n if (version > SCHEMA_VERSION) {\n throw new Error(`Unsupported AxSQLiteEventStore schema ${version}`);\n }\n if (version === 0) {\n this.db.exec(`\n CREATE TABLE event_deliveries (\n sequence INTEGER PRIMARY KEY AUTOINCREMENT,\n id TEXT NOT NULL UNIQUE,\n ingress_json TEXT NOT NULL,\n identity_scope TEXT NOT NULL,\n route_id TEXT NOT NULL,\n action TEXT NOT NULL,\n target_id TEXT,\n instance_key TEXT NOT NULL,\n status TEXT NOT NULL,\n attempt INTEGER NOT NULL,\n available_at INTEGER NOT NULL,\n accepted_at INTEGER NOT NULL,\n claimed_by TEXT,\n run_id TEXT,\n error TEXT,\n size_bytes INTEGER NOT NULL,\n retry_safety TEXT NOT NULL,\n ordering_mode TEXT NOT NULL,\n lease_expires_at INTEGER,\n fencing_token INTEGER NOT NULL DEFAULT 0,\n invocation_started INTEGER NOT NULL DEFAULT 0\n );\n CREATE INDEX event_delivery_claim ON event_deliveries(status, available_at, lease_expires_at, sequence);\n CREATE TABLE event_dedupe (\n dedupe_key TEXT PRIMARY KEY,\n event_id TEXT NOT NULL,\n delivery_ids_json TEXT NOT NULL,\n created_at INTEGER NOT NULL\n );\n CREATE TABLE event_runs (\n id TEXT PRIMARY KEY,\n delivery_id TEXT NOT NULL,\n run_json TEXT NOT NULL,\n updated_at INTEGER NOT NULL,\n finished_at INTEGER\n );\n CREATE TABLE event_continuations (\n id TEXT PRIMARY KEY,\n identity_scope TEXT NOT NULL,\n continuation_json TEXT NOT NULL,\n created_at INTEGER NOT NULL,\n expires_at INTEGER,\n completed_at INTEGER\n );\n CREATE TABLE event_continuation_keys (\n correlation_key TEXT PRIMARY KEY,\n continuation_id TEXT NOT NULL REFERENCES event_continuations(id) ON DELETE CASCADE\n );\n CREATE TABLE event_dead_letters (\n id TEXT PRIMARY KEY,\n delivery_id TEXT NOT NULL,\n dead_letter_json TEXT NOT NULL,\n created_at INTEGER NOT NULL\n );\n CREATE TABLE event_program_state (\n state_key TEXT PRIMARY KEY,\n revision INTEGER NOT NULL,\n state_json TEXT NOT NULL,\n updated_at INTEGER NOT NULL\n );\n PRAGMA user_version = 1;\n `);\n }\n }\n\n private prune(now: number): void {\n const retention = this.options.retention;\n this.db.transaction(() => {\n const eventCutoff = now - retention.eventAndResultMs;\n this.db\n .prepare('DELETE FROM event_dedupe WHERE created_at < ?')\n .run(eventCutoff);\n this.db\n .prepare(\n `UPDATE event_runs\n SET run_json=json_remove(run_json, '$.output', '$.chunks')\n WHERE finished_at IS NOT NULL AND finished_at < ?`\n )\n .run(eventCutoff);\n this.db\n .prepare(\n `DELETE FROM event_deliveries\n WHERE accepted_at < ? AND status IN (${TERMINAL.map(() => '?').join(',')})`\n )\n .run(eventCutoff, ...TERMINAL);\n this.db\n .prepare('DELETE FROM event_runs WHERE updated_at < ?')\n .run(now - retention.runMetadataAndDeadLettersMs);\n this.db\n .prepare('DELETE FROM event_dead_letters WHERE created_at < ?')\n .run(now - retention.runMetadataAndDeadLettersMs);\n this.db\n .prepare(\n 'DELETE FROM event_continuations WHERE completed_at IS NOT NULL AND completed_at < ?'\n )\n .run(now - retention.completedContinuationsMs);\n })();\n }\n}\n"],"mappings":"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","names":["randomUUID","AxEventBackpressureError","AxSystemEventClock","axEventIdentityScope","axEventScopedCorrelationKey","axEventScopedDedupeKey","Database","SCHEMA_VERSION","MULTI_WORKER_CONFORMANCE","TERMINAL","AX_SQLITE_EVENT_STANDARD_RETENTION","AxSQLiteEventStore","options","request","signal","eventBytes","deadline","result","remaining","workerId","now","leaseMs","row","recovered","fencingToken","deliveryId","leaseExpiresAt","delivery","run","stored","hasPayload","payload","outputRef","runId","continuation","insert","correlation","identityScope","id","deadLetter","key","expectedRevision","state","fence","current","next","dedupeKey","duplicate","pending","requiredBytes","sum","deliveryIds","descriptor","version","retention","eventCutoff"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ax-llm/ax-tools",
|
|
3
|
-
"version": "23.0.
|
|
3
|
+
"version": "23.0.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Ax tools package",
|
|
6
6
|
"repository": {
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"license": "Apache-2.0",
|
|
14
14
|
"keywords": [],
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@ax-llm/ax": "23.0.
|
|
16
|
+
"@ax-llm/ax": "23.0.2",
|
|
17
|
+
"better-sqlite3": "^12.11.1"
|
|
17
18
|
},
|
|
18
19
|
"files": [
|
|
19
20
|
"**/*"
|