@ax-llm/ax-tools 25.0.0 → 25.0.1
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/event/sqlite.cjs +18 -18
- package/event/sqlite.cjs.map +1 -1
- package/event/sqlite.d.cts +69 -69
- package/event/sqlite.d.ts +69 -69
- package/event/sqlite.js +17 -17
- package/event/sqlite.js.map +1 -1
- package/index.cjs +1 -4
- package/index.cjs.map +1 -1
- package/index.d.cts +28 -23
- package/index.d.ts +28 -23
- package/index.js +1 -4
- package/index.js.map +1 -1
- package/package.json +2 -2
- package/rolldown-runtime-BocRIvOZ.cjs +1 -0
package/event/sqlite.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../rolldown-runtime-BocRIvOZ.cjs");let t=require("node:crypto"),n=require("@ax-llm/ax"),r=require("better-sqlite3");r=e.t(r,1);const i=[`waiting_event`,`succeeded`,`failed`,`cancelled`,`dead_lettered`,`output_persistence_failed`,`outcome_unknown`],a={eventAndResultMs:6048e5,runMetadataAndDeadLettersMs:2592e6,completedContinuationsMs:6048e5};var o=class{options;capabilities={durability:`persistent`,coordination:`multi-worker`,leases:!0,transactions:!0,compareAndSet:!0,outputPersistence:!0,conformance:{multiWorker:`axevent-store-v1`,schemaVersion:1}};db;clock;maxPendingDeliveries;maxPendingBytes;maxEventBytes;maxInlinePayloadBytes;constructor(e){if(this.options=e,!e.retention)throw Error(`AxSQLiteEventStore requires explicit retention`);this.clock=e.clock??new n.AxSystemEventClock,this.maxPendingDeliveries=e.maxPendingDeliveries??1e5,this.maxPendingBytes=e.maxPendingBytes??1073741824,this.maxEventBytes=e.maxEventBytes??16777216,this.maxInlinePayloadBytes=e.maxInlinePayloadBytes??16777216,this.db=new r.default(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())}async enqueue(e,t){let r=Buffer.byteLength(JSON.stringify(e.ingress));if(r>this.maxEventBytes)throw new n.AxEventBackpressureError(`Event is ${r} bytes; maximum is ${this.maxEventBytes}`);let i=this.clock.now()+e.publishTimeoutMs;for(;;){if(t?.aborted)throw t.reason;let r=this.tryEnqueue(e);if(r)return r;let a=i-this.clock.now();if(a<=0)throw new n.AxEventBackpressureError;await this.clock.sleep(Math.min(25,a),t)}}async claim(e,t,n=3e4){return this.db.transaction(()=>{let r=this.db.prepare(`SELECT d.* FROM event_deliveries d
|
|
2
2
|
WHERE (
|
|
3
3
|
(d.status = 'queued' AND d.available_at <= ?)
|
|
4
4
|
OR
|
|
@@ -11,43 +11,43 @@
|
|
|
11
11
|
WHERE earlier.sequence < d.sequence
|
|
12
12
|
AND COALESCE(earlier.target_id, '') = COALESCE(d.target_id, '')
|
|
13
13
|
AND earlier.instance_key = d.instance_key
|
|
14
|
-
AND earlier.status NOT IN (${
|
|
14
|
+
AND earlier.status NOT IN (${i.map(()=>`?`).join(`,`)})
|
|
15
15
|
)
|
|
16
16
|
)
|
|
17
|
-
ORDER BY d.sequence LIMIT 1`).get(t,t,...
|
|
17
|
+
ORDER BY d.sequence LIMIT 1`).get(t,t,...i);if(!r)return;let a=r.status!==`queued`,o=r.fencing_token+1;if(this.db.prepare(`UPDATE event_deliveries
|
|
18
18
|
SET status='claimed', claimed_by=?, lease_expires_at=?, fencing_token=?
|
|
19
|
-
WHERE id=? AND fencing_token=?`).run(e,t+n,r
|
|
19
|
+
WHERE id=? AND fencing_token=?`).run(e,t+n,o,r.id,r.fencing_token).changes===1)return{...this.rowToDelivery({...r,status:`claimed`,claimed_by:e,lease_expires_at:t+n,fencing_token:o}),...a?{recoveredFromExpiredLease:!0}:{}}})()}async renewClaim(e,t,n,r){if(this.db.prepare(`UPDATE event_deliveries SET lease_expires_at=?
|
|
20
20
|
WHERE id=? AND claimed_by=? AND fencing_token=?
|
|
21
|
-
AND status IN ('claimed','running')`).run(
|
|
21
|
+
AND status IN ('claimed','running')`).run(r,e,t,n).changes!==1)throw 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
22
|
status=?, attempt=?, available_at=?, claimed_by=?, run_id=?, error=?,
|
|
23
23
|
lease_expires_at=?, invocation_started=?, retry_safety=?, ordering_mode=?
|
|
24
|
-
WHERE id=? AND fencing_token=?`).run(e.status,e.attempt,e.availableAt,
|
|
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,e.retrySafety,e.ordering,e.id,e.fencingToken??-1).changes!==1)throw 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,r={output:e.output,chunks:e.chunks};if(n&&Buffer.byteLength(JSON.stringify(r))>this.maxInlinePayloadBytes){if(!this.options.payloadStore)throw Error(`output_persistence_failed: run ${e.id} exceeded ${this.maxInlinePayloadBytes} inline bytes`);let n=await this.options.payloadStore.put(`event-run:${e.id}`,r);t={...t,output:void 0,chunks:void 0,outputRef:n}}this.db.prepare(`INSERT INTO event_runs(id, delivery_id, run_json, updated_at, finished_at)
|
|
25
25
|
VALUES(?,?,?,?,?)
|
|
26
26
|
ON CONFLICT(id) DO UPDATE SET
|
|
27
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(),
|
|
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 e=await this.options.payloadStore.get(n.outputRef);return{...n,output:e.output,chunks:e.chunks}}return n}async registerContinuation(e){this.db.transaction(()=>{this.db.prepare(`INSERT INTO event_continuations
|
|
29
29
|
(id, identity_scope, continuation_json, created_at, expires_at)
|
|
30
|
-
VALUES(?,?,?,?,?)`).run(e.id,e.identityScope,JSON.stringify(e),e.createdAt,
|
|
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 r of e.correlation)t.run((0,n.axEventScopedCorrelationKey)(e.identityScope,r.kind,r.value),e.id)})()}async findContinuation(e,t,r){let i=this.db.prepare(`SELECT c.continuation_json, c.id, c.expires_at
|
|
31
31
|
FROM event_continuation_keys k
|
|
32
32
|
JOIN event_continuations c ON c.id=k.continuation_id
|
|
33
|
-
WHERE k.correlation_key=? AND c.completed_at IS NULL`).get(
|
|
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(
|
|
33
|
+
WHERE k.correlation_key=? AND c.completed_at IS NULL`).get((0,n.axEventScopedCorrelationKey)(e,t.kind,t.value));if(i){if(i.expires_at!==null&&i.expires_at<=r){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
35
|
claimed_by=NULL, run_id=NULL, error=NULL, lease_expires_at=NULL,
|
|
36
36
|
invocation_started=0
|
|
37
|
-
WHERE id=? AND status IN (${
|
|
37
|
+
WHERE id=? AND status IN (${i.map(()=>`?`).join(`,`)})`).run(t,e,...i).changes!==1)throw Error(`Event delivery ${e} is not terminal`)}async nextAvailableAt(e){let t=this.db.prepare(`SELECT MIN(CASE
|
|
38
38
|
WHEN status='queued' THEN available_at
|
|
39
39
|
ELSE lease_expires_at END) AS next
|
|
40
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(
|
|
42
|
-
WHERE status NOT IN (${
|
|
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 (${i.map(()=>`?`).join(`,`)})`).get(...i).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,r){return this.db.transaction(()=>{r&&this.assertFence(r.deliveryId,r.fencingToken);let i=this.db.prepare(`SELECT revision FROM event_program_state WHERE state_key=?`).get(e);if(i?.revision!==t)throw Error(`Program state compare-and-set failed for ${e}: expected ${String(t)}, current ${String(i?.revision)}`);let a={...structuredClone(n),revision:(i?.revision??0)+1};return this.db.prepare(`INSERT INTO event_program_state(state_key, revision, state_json, updated_at)
|
|
43
43
|
VALUES(?,?,?,?)
|
|
44
44
|
ON CONFLICT(state_key) DO UPDATE SET revision=excluded.revision,
|
|
45
|
-
state_json=excluded.state_json, updated_at=excluded.updated_at`).run(e,
|
|
46
|
-
FROM event_deliveries WHERE status NOT IN (${
|
|
45
|
+
state_json=excluded.state_json, updated_at=excluded.updated_at`).run(e,a.revision,JSON.stringify(a),a.updatedAt),a})()}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 r=(0,n.axEventScopedDedupeKey)(e.ingress),a=this.db.prepare(`SELECT event_id, delivery_ids_json FROM event_dedupe WHERE dedupe_key=?`).get(r);if(a)return{eventId:a.event_id,accepted:!0,duplicate:!0,durability:`persistent`,deliveryIds:JSON.parse(a.delivery_ids_json)};let o=this.db.prepare(`SELECT COUNT(*) AS count, COALESCE(SUM(size_bytes),0) AS bytes
|
|
46
|
+
FROM event_deliveries WHERE status NOT IN (${i.map(()=>`?`).join(`,`)})`).get(...i),s=e.deliveries.reduce((e,t)=>e+t.sizeBytes,0);if(o.count+e.deliveries.length>this.maxPendingDeliveries||o.bytes+s>this.maxPendingBytes)return;let c=[],l=this.db.prepare(`INSERT INTO event_deliveries(
|
|
47
47
|
id, ingress_json, identity_scope, route_id, action, target_id,
|
|
48
48
|
instance_key, status, attempt, available_at, accepted_at, size_bytes,
|
|
49
49
|
retry_safety, ordering_mode
|
|
50
|
-
) VALUES(?,?,?,?,?,?,?,'queued',0,?,?,?,?,?)`);for(let
|
|
50
|
+
) VALUES(?,?,?,?,?,?,?,'queued',0,?,?,?,?,?)`);for(let r of e.deliveries){let i=(0,t.randomUUID)();c.push(i),l.run(i,JSON.stringify(e.ingress),(0,n.axEventIdentityScope)(e.ingress.identity),r.routeId,r.action,r.targetId??null,r.instanceKey,r.availableAt??e.acceptedAt,e.acceptedAt,r.sizeBytes,r.retrySafety??`unknown`,r.ordering??`strict`)}return this.db.prepare(`INSERT INTO event_dedupe(dedupe_key,event_id,delivery_ids_json,created_at) VALUES(?,?,?,?)`).run(r,e.ingress.event.id,JSON.stringify(c),e.acceptedAt),{eventId:e.ingress.event.id,accepted:!0,duplicate:!1,durability:`persistent`,deliveryIds:c}})()}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 Error(`Stale fencing token for event delivery ${e}`)}migrate(){let e=this.db.pragma(`user_version`,{simple:!0});if(e>1)throw Error(`Unsupported AxSQLiteEventStore schema ${e}`);e===0&&this.db.exec(`
|
|
51
51
|
CREATE TABLE event_deliveries (
|
|
52
52
|
sequence INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
53
53
|
id TEXT NOT NULL UNIQUE,
|
|
@@ -110,8 +110,8 @@
|
|
|
110
110
|
updated_at INTEGER NOT NULL
|
|
111
111
|
);
|
|
112
112
|
PRAGMA user_version = 1;
|
|
113
|
-
`)}prune(e){let t=this.options.retention;this.db.transaction(()=>{let n=e-t.eventAndResultMs;this.db.prepare(
|
|
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
114
|
SET run_json=json_remove(run_json, '$.output', '$.chunks')
|
|
115
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 (${
|
|
116
|
+
WHERE accepted_at < ? AND status IN (${i.map(()=>`?`).join(`,`)})`).run(n,...i),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)})()}};exports.AX_SQLITE_EVENT_STANDARD_RETENTION=a,exports.AxSQLiteEventStore=o;
|
|
117
117
|
//# sourceMappingURL=sqlite.cjs.map
|
package/event/sqlite.cjs.map
CHANGED
|
@@ -1 +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"]}
|
|
1
|
+
{"version":3,"file":"sqlite.cjs","names":["AxSystemEventClock","Database","AxEventBackpressureError","axEventScopedCorrelationKey","axEventScopedDedupeKey","randomUUID","axEventIdentityScope","require_rolldown_runtime","node_crypto","_ax_llm_ax","better_sqlite3","TERMINAL","AX_SQLITE_EVENT_STANDARD_RETENTION","AxSQLiteEventStore","options","eventBytes","request","deadline","signal","result","remaining","row","now","recovered","fencingToken","workerId","leaseMs","leaseExpiresAt","deliveryId","delivery","run","stored","hasPayload","payload","outputRef","runId","continuation","insert","correlation","identityScope","id","deadLetter","key","fence","current","expectedRevision","next","state","dedupeKey","duplicate","pending","requiredBytes","sum","deliveryIds","descriptor","version","retention","eventCutoff"],"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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"}
|
package/event/sqlite.d.cts
CHANGED
|
@@ -1,73 +1,73 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
interface AxSQLiteEventRetention {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { AxEventClock, AxEventContinuation, AxEventCorrelationKey, AxEventDeadLetter, AxEventDelivery, AxEventEnqueueRequest, AxEventPayloadStore, AxEventPublishReceipt, AxEventRun, AxEventStore, AxProgramStateEnvelope, AxProgramStateStore } from "@ax-llm/ax";
|
|
2
|
+
//#region event/sqlite.d.ts
|
|
3
|
+
export interface AxSQLiteEventRetention {
|
|
4
|
+
eventAndResultMs: number;
|
|
5
|
+
runMetadataAndDeadLettersMs: number;
|
|
6
|
+
completedContinuationsMs: number;
|
|
7
7
|
}
|
|
8
|
-
declare const AX_SQLITE_EVENT_STANDARD_RETENTION: Readonly<AxSQLiteEventRetention>;
|
|
9
|
-
interface AxSQLiteEventStoreOptions {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
8
|
+
export declare const AX_SQLITE_EVENT_STANDARD_RETENTION: Readonly<AxSQLiteEventRetention>;
|
|
9
|
+
export 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
19
|
}
|
|
20
|
-
declare class AxSQLiteEventStore implements AxEventStore, AxProgramStateStore {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
};
|
|
20
|
+
export 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;
|
|
33
32
|
};
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
71
|
}
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
//#endregion
|
|
73
|
+
//# sourceMappingURL=sqlite.d.cts.map
|
package/event/sqlite.d.ts
CHANGED
|
@@ -1,73 +1,73 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
interface AxSQLiteEventRetention {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { AxEventClock, AxEventContinuation, AxEventCorrelationKey, AxEventDeadLetter, AxEventDelivery, AxEventEnqueueRequest, AxEventPayloadStore, AxEventPublishReceipt, AxEventRun, AxEventStore, AxProgramStateEnvelope, AxProgramStateStore } from "@ax-llm/ax";
|
|
2
|
+
//#region event/sqlite.d.ts
|
|
3
|
+
export interface AxSQLiteEventRetention {
|
|
4
|
+
eventAndResultMs: number;
|
|
5
|
+
runMetadataAndDeadLettersMs: number;
|
|
6
|
+
completedContinuationsMs: number;
|
|
7
7
|
}
|
|
8
|
-
declare const AX_SQLITE_EVENT_STANDARD_RETENTION: Readonly<AxSQLiteEventRetention>;
|
|
9
|
-
interface AxSQLiteEventStoreOptions {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
8
|
+
export declare const AX_SQLITE_EVENT_STANDARD_RETENTION: Readonly<AxSQLiteEventRetention>;
|
|
9
|
+
export 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
19
|
}
|
|
20
|
-
declare class AxSQLiteEventStore implements AxEventStore, AxProgramStateStore {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
};
|
|
20
|
+
export 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;
|
|
33
32
|
};
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
71
|
}
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
//#endregion
|
|
73
|
+
//# sourceMappingURL=sqlite.d.ts.map
|
package/event/sqlite.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{randomUUID as
|
|
1
|
+
import{randomUUID as e}from"node:crypto";import{AxEventBackpressureError as t,AxSystemEventClock as n,axEventIdentityScope as r,axEventScopedCorrelationKey as i,axEventScopedDedupeKey as a}from"@ax-llm/ax";import o from"better-sqlite3";const s=[`waiting_event`,`succeeded`,`failed`,`cancelled`,`dead_lettered`,`output_persistence_failed`,`outcome_unknown`],c={eventAndResultMs:6048e5,runMetadataAndDeadLettersMs:2592e6,completedContinuationsMs:6048e5};var l=class{options;capabilities={durability:`persistent`,coordination:`multi-worker`,leases:!0,transactions:!0,compareAndSet:!0,outputPersistence:!0,conformance:{multiWorker:`axevent-store-v1`,schemaVersion:1}};db;clock;maxPendingDeliveries;maxPendingBytes;maxEventBytes;maxInlinePayloadBytes;constructor(e){if(this.options=e,!e.retention)throw Error(`AxSQLiteEventStore requires explicit retention`);this.clock=e.clock??new n,this.maxPendingDeliveries=e.maxPendingDeliveries??1e5,this.maxPendingBytes=e.maxPendingBytes??1073741824,this.maxEventBytes=e.maxEventBytes??16777216,this.maxInlinePayloadBytes=e.maxInlinePayloadBytes??16777216,this.db=new o(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())}async enqueue(e,n){let r=Buffer.byteLength(JSON.stringify(e.ingress));if(r>this.maxEventBytes)throw new t(`Event is ${r} bytes; maximum is ${this.maxEventBytes}`);let i=this.clock.now()+e.publishTimeoutMs;for(;;){if(n?.aborted)throw n.reason;let r=this.tryEnqueue(e);if(r)return r;let a=i-this.clock.now();if(a<=0)throw new t;await this.clock.sleep(Math.min(25,a),n)}}async claim(e,t,n=3e4){return this.db.transaction(()=>{let r=this.db.prepare(`SELECT d.* FROM event_deliveries d
|
|
2
2
|
WHERE (
|
|
3
3
|
(d.status = 'queued' AND d.available_at <= ?)
|
|
4
4
|
OR
|
|
@@ -11,43 +11,43 @@ import{randomUUID as p}from"crypto";import{AxEventBackpressureError as E,AxSyste
|
|
|
11
11
|
WHERE earlier.sequence < d.sequence
|
|
12
12
|
AND COALESCE(earlier.target_id, '') = COALESCE(d.target_id, '')
|
|
13
13
|
AND earlier.instance_key = d.instance_key
|
|
14
|
-
AND earlier.status NOT IN (${
|
|
14
|
+
AND earlier.status NOT IN (${s.map(()=>`?`).join(`,`)})
|
|
15
15
|
)
|
|
16
16
|
)
|
|
17
|
-
ORDER BY d.sequence LIMIT 1`).get(t,t,...
|
|
17
|
+
ORDER BY d.sequence LIMIT 1`).get(t,t,...s);if(!r)return;let i=r.status!==`queued`,a=r.fencing_token+1;if(this.db.prepare(`UPDATE event_deliveries
|
|
18
18
|
SET status='claimed', claimed_by=?, lease_expires_at=?, fencing_token=?
|
|
19
|
-
WHERE id=? AND fencing_token=?`).run(e,t+n,r
|
|
19
|
+
WHERE id=? AND fencing_token=?`).run(e,t+n,a,r.id,r.fencing_token).changes===1)return{...this.rowToDelivery({...r,status:`claimed`,claimed_by:e,lease_expires_at:t+n,fencing_token:a}),...i?{recoveredFromExpiredLease:!0}:{}}})()}async renewClaim(e,t,n,r){if(this.db.prepare(`UPDATE event_deliveries SET lease_expires_at=?
|
|
20
20
|
WHERE id=? AND claimed_by=? AND fencing_token=?
|
|
21
|
-
AND status IN ('claimed','running')`).run(
|
|
21
|
+
AND status IN ('claimed','running')`).run(r,e,t,n).changes!==1)throw 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
22
|
status=?, attempt=?, available_at=?, claimed_by=?, run_id=?, error=?,
|
|
23
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
|
|
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,e.retrySafety,e.ordering,e.id,e.fencingToken??-1).changes!==1)throw 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,r={output:e.output,chunks:e.chunks};if(n&&Buffer.byteLength(JSON.stringify(r))>this.maxInlinePayloadBytes){if(!this.options.payloadStore)throw Error(`output_persistence_failed: run ${e.id} exceeded ${this.maxInlinePayloadBytes} inline bytes`);let n=await this.options.payloadStore.put(`event-run:${e.id}`,r);t={...t,output:void 0,chunks:void 0,outputRef:n}}this.db.prepare(`INSERT INTO event_runs(id, delivery_id, run_json, updated_at, finished_at)
|
|
25
25
|
VALUES(?,?,?,?,?)
|
|
26
26
|
ON CONFLICT(id) DO UPDATE SET
|
|
27
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(
|
|
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 e=await this.options.payloadStore.get(n.outputRef);return{...n,output:e.output,chunks:e.chunks}}return n}async registerContinuation(e){this.db.transaction(()=>{this.db.prepare(`INSERT INTO event_continuations
|
|
29
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(
|
|
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(i(e.identityScope,n.kind,n.value),e.id)})()}async findContinuation(e,t,n){let r=this.db.prepare(`SELECT c.continuation_json, c.id, c.expires_at
|
|
31
31
|
FROM event_continuation_keys k
|
|
32
32
|
JOIN event_continuations c ON c.id=k.continuation_id
|
|
33
|
-
WHERE k.correlation_key=? AND c.completed_at IS NULL`).get(
|
|
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(
|
|
33
|
+
WHERE k.correlation_key=? AND c.completed_at IS NULL`).get(i(e,t.kind,t.value));if(r){if(r.expires_at!==null&&r.expires_at<=n){await this.completeContinuation(r.id);return}return JSON.parse(r.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
35
|
claimed_by=NULL, run_id=NULL, error=NULL, lease_expires_at=NULL,
|
|
36
36
|
invocation_started=0
|
|
37
|
-
WHERE id=? AND status IN (${
|
|
37
|
+
WHERE id=? AND status IN (${s.map(()=>`?`).join(`,`)})`).run(t,e,...s).changes!==1)throw Error(`Event delivery ${e} is not terminal`)}async nextAvailableAt(e){let t=this.db.prepare(`SELECT MIN(CASE
|
|
38
38
|
WHEN status='queued' THEN available_at
|
|
39
39
|
ELSE lease_expires_at END) AS next
|
|
40
40
|
FROM event_deliveries
|
|
41
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 (${
|
|
42
|
+
WHERE status NOT IN (${s.map(()=>`?`).join(`,`)})`).get(...s).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,r){return this.db.transaction(()=>{r&&this.assertFence(r.deliveryId,r.fencingToken);let i=this.db.prepare(`SELECT revision FROM event_program_state WHERE state_key=?`).get(e);if(i?.revision!==t)throw Error(`Program state compare-and-set failed for ${e}: expected ${String(t)}, current ${String(i?.revision)}`);let a={...structuredClone(n),revision:(i?.revision??0)+1};return this.db.prepare(`INSERT INTO event_program_state(state_key, revision, state_json, updated_at)
|
|
43
43
|
VALUES(?,?,?,?)
|
|
44
44
|
ON CONFLICT(state_key) DO UPDATE SET revision=excluded.revision,
|
|
45
|
-
state_json=excluded.state_json, updated_at=excluded.updated_at`).run(e,
|
|
46
|
-
FROM event_deliveries WHERE status NOT IN (${
|
|
45
|
+
state_json=excluded.state_json, updated_at=excluded.updated_at`).run(e,a.revision,JSON.stringify(a),a.updatedAt),a})()}async delete(e){this.db.prepare(`DELETE FROM event_program_state WHERE state_key=?`).run(e)}async close(){this.db.close()}tryEnqueue(t){return this.db.transaction(()=>{let n=a(t.ingress),i=this.db.prepare(`SELECT event_id, delivery_ids_json FROM event_dedupe WHERE dedupe_key=?`).get(n);if(i)return{eventId:i.event_id,accepted:!0,duplicate:!0,durability:`persistent`,deliveryIds:JSON.parse(i.delivery_ids_json)};let o=this.db.prepare(`SELECT COUNT(*) AS count, COALESCE(SUM(size_bytes),0) AS bytes
|
|
46
|
+
FROM event_deliveries WHERE status NOT IN (${s.map(()=>`?`).join(`,`)})`).get(...s),c=t.deliveries.reduce((e,t)=>e+t.sizeBytes,0);if(o.count+t.deliveries.length>this.maxPendingDeliveries||o.bytes+c>this.maxPendingBytes)return;let l=[],u=this.db.prepare(`INSERT INTO event_deliveries(
|
|
47
47
|
id, ingress_json, identity_scope, route_id, action, target_id,
|
|
48
48
|
instance_key, status, attempt, available_at, accepted_at, size_bytes,
|
|
49
49
|
retry_safety, ordering_mode
|
|
50
|
-
) VALUES(?,?,?,?,?,?,?,'queued',0,?,?,?,?,?)`);for(let
|
|
50
|
+
) VALUES(?,?,?,?,?,?,?,'queued',0,?,?,?,?,?)`);for(let n of t.deliveries){let i=e();l.push(i),u.run(i,JSON.stringify(t.ingress),r(t.ingress.identity),n.routeId,n.action,n.targetId??null,n.instanceKey,n.availableAt??t.acceptedAt,t.acceptedAt,n.sizeBytes,n.retrySafety??`unknown`,n.ordering??`strict`)}return this.db.prepare(`INSERT INTO event_dedupe(dedupe_key,event_id,delivery_ids_json,created_at) VALUES(?,?,?,?)`).run(n,t.ingress.event.id,JSON.stringify(l),t.acceptedAt),{eventId:t.ingress.event.id,accepted:!0,duplicate:!1,durability:`persistent`,deliveryIds:l}})()}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 Error(`Stale fencing token for event delivery ${e}`)}migrate(){let e=this.db.pragma(`user_version`,{simple:!0});if(e>1)throw Error(`Unsupported AxSQLiteEventStore schema ${e}`);e===0&&this.db.exec(`
|
|
51
51
|
CREATE TABLE event_deliveries (
|
|
52
52
|
sequence INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
53
53
|
id TEXT NOT NULL UNIQUE,
|
|
@@ -110,8 +110,8 @@ import{randomUUID as p}from"crypto";import{AxEventBackpressureError as E,AxSyste
|
|
|
110
110
|
updated_at INTEGER NOT NULL
|
|
111
111
|
);
|
|
112
112
|
PRAGMA user_version = 1;
|
|
113
|
-
`)}prune(e){let t=this.options.retention;this.db.transaction(()=>{let n=e-t.eventAndResultMs;this.db.prepare(
|
|
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
114
|
SET run_json=json_remove(run_json, '$.output', '$.chunks')
|
|
115
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 (${
|
|
116
|
+
WHERE accepted_at < ? AND status IN (${s.map(()=>`?`).join(`,`)})`).run(n,...s),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{c as AX_SQLITE_EVENT_STANDARD_RETENTION,l as AxSQLiteEventStore};
|
|
117
117
|
//# sourceMappingURL=sqlite.js.map
|
package/event/sqlite.js.map
CHANGED
|
@@ -1 +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"]}
|
|
1
|
+
{"version":3,"file":"sqlite.js","names":["Database","TERMINAL","AX_SQLITE_EVENT_STANDARD_RETENTION","AxSQLiteEventStore","options","AxSystemEventClock","eventBytes","request","AxEventBackpressureError","deadline","signal","result","remaining","row","now","recovered","fencingToken","workerId","leaseMs","leaseExpiresAt","deliveryId","delivery","run","stored","hasPayload","payload","outputRef","runId","continuation","insert","correlation","axEventScopedCorrelationKey","identityScope","id","deadLetter","key","fence","current","expectedRevision","next","state","dedupeKey","axEventScopedDedupeKey","duplicate","pending","requiredBytes","sum","deliveryIds","descriptor","randomUUID","axEventIdentityScope","version","retention","eventCutoff"],"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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"}
|
package/index.cjs
CHANGED
|
@@ -1,5 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
`)})}async sendNotification(e){this.process.stdin.write(`${JSON.stringify(e)}
|
|
3
|
-
`)}async sendResponse(e){this.process.stdin.write(`${JSON.stringify(e)}
|
|
4
|
-
`)}setMessageHandler(e){this.messageHandler=e}async connect(){return Promise.resolve()}async terminate(){return this.rl.close(),this.process.kill(),new Promise(e=>{this.process.on("exit",()=>e())})}}, _class);function a(t){return new o(t)}exports.AxMCPStdioTransport = o; exports.axCreateMCPStdioTransport = a;
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./rolldown-runtime-BocRIvOZ.cjs");let t=require("node:child_process"),n=require("node:readline");n=e.t(n,1);var r=class{process;rl;pendingResponses=/* @__PURE__ */ new Map;closed=!1;closePromise;messageHandler;constructor(e){this.process=(0,t.spawn)(e.command,e.args??[],{env:e.env?{...process.env,...e.env}:process.env}),this.closePromise=new Promise(e=>{this.process.once(`close`,()=>{this.closeWithError(/* @__PURE__ */ Error(`MCP server process closed`)),e()})}),this.process.once(`error`,e=>this.closeWithError(e)),this.process.once(`exit`,(e,t)=>{this.closeWithError(/* @__PURE__ */ Error(`MCP server process exited${e===null?``:` with code ${e}`}${t?` (${t})`:``}`))}),this.process.stdin.on(`error`,e=>this.closeWithError(e)),this.rl=n.default.createInterface({input:this.process.stdout}),this.rl.on(`line`,e=>{try{let t=JSON.parse(e);if(`method`in t){this.messageHandler?.(t);return}let n=t,r=n.id===null?void 0:this.pendingResponses.get(n.id);r?(r.resolve(n),n.id!==null&&this.pendingResponses.delete(n.id)):this.messageHandler?.(t)}catch{console.warn(`Non-JSON output from MCP server:`,e)}})}async send(e){return new Promise((t,n)=>{if(this.closed){n(/* @__PURE__ */ Error(`MCP server process is not running`));return}this.pendingResponses.set(e.id,{resolve:e=>t(e),reject:n}),this.writeMessage(e).catch(t=>{this.pendingResponses.delete(e.id),n(t)})})}async sendNotification(e){await this.writeMessage(e)}async sendResponse(e){await this.writeMessage(e)}setMessageHandler(e){this.messageHandler=e}async connect(){return Promise.resolve()}async terminate(){this.closeWithError(/* @__PURE__ */ Error(`MCP server process terminated`)),this.rl.close(),this.process.exitCode===null&&this.process.signalCode===null&&this.process.kill(),await this.closePromise}async writeMessage(e){if(this.closed||this.process.stdin.destroyed)throw Error(`MCP server process is not running`);await new Promise((t,n)=>{this.process.stdin.write(`${JSON.stringify(e)}\n`,e=>{e?n(e):t()})})}closeWithError(e){if(!this.closed){this.closed=!0;for(let t of this.pendingResponses.values())t.reject(e);this.pendingResponses.clear()}}};function i(e){return new r(e)}exports.AxMCPStdioTransport=r,exports.axCreateMCPStdioTransport=i;
|
|
5
2
|
//# sourceMappingURL=index.cjs.map
|
package/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["spawn","readline","require_rolldown_runtime","node_child_process","node_readline","AxMCPStdioTransport","config","resolve","error","code","signal","message","line","response","pending","reject","res","handler","axCreateMCPStdioTransport"],"sources":["../mcp/stdioTransport.ts"],"sourcesContent":["import { type ChildProcessWithoutNullStreams, spawn } from 'node:child_process';\nimport readline from 'node:readline';\n\nimport type {\n AxMCPJSONRPCMessage,\n AxMCPJSONRPCNotification,\n AxMCPJSONRPCRequest,\n AxMCPJSONRPCResponse,\n AxMCPTransport,\n} from '@ax-llm/ax';\n\nexport interface StdioTransportConfig {\n command: string;\n args?: string[];\n env?: NodeJS.ProcessEnv;\n}\n\nexport class AxMCPStdioTransport implements AxMCPTransport {\n private process: ChildProcessWithoutNullStreams;\n private rl: readline.Interface;\n private pendingResponses = new Map<\n string | number,\n {\n resolve: (res: AxMCPJSONRPCResponse) => void;\n reject: (error: Error) => void;\n }\n >();\n private closed = false;\n private closePromise: Promise<void>;\n private messageHandler?: (\n message: Readonly<AxMCPJSONRPCMessage>\n ) => void | Promise<void>;\n\n constructor(config: Readonly<StdioTransportConfig>) {\n this.process = spawn(config.command, config.args ?? [], {\n env: config.env ? { ...process.env, ...config.env } : process.env,\n });\n this.closePromise = new Promise((resolve) => {\n this.process.once('close', () => {\n this.closeWithError(new Error('MCP server process closed'));\n resolve();\n });\n });\n this.process.once('error', (error) => this.closeWithError(error));\n this.process.once('exit', (code, signal) => {\n this.closeWithError(\n new Error(\n `MCP server process exited${code === null ? '' : ` with code ${code}`}${signal ? ` (${signal})` : ''}`\n )\n );\n });\n this.process.stdin.on('error', (error) => this.closeWithError(error));\n this.rl = readline.createInterface({ input: this.process.stdout });\n this.rl.on('line', (line) => {\n try {\n const message: AxMCPJSONRPCMessage = JSON.parse(line);\n if ('method' in message) {\n void this.messageHandler?.(message);\n return;\n }\n const response = message as AxMCPJSONRPCResponse;\n const pending =\n response.id === null\n ? undefined\n : this.pendingResponses.get(response.id);\n if (pending) {\n pending.resolve(response);\n if (response.id !== null) this.pendingResponses.delete(response.id);\n } else {\n void this.messageHandler?.(message);\n }\n } catch (_error) {\n // Skip non-JSON lines (might be debug output from the MCP server)\n console.warn('Non-JSON output from MCP server:', line);\n }\n });\n }\n\n async send(\n message: Readonly<AxMCPJSONRPCRequest<unknown>>\n ): Promise<AxMCPJSONRPCResponse<unknown>> {\n return new Promise<AxMCPJSONRPCResponse<unknown>>((resolve, reject) => {\n if (this.closed) {\n reject(new Error('MCP server process is not running'));\n return;\n }\n this.pendingResponses.set(message.id, {\n resolve: (res) => resolve(res as AxMCPJSONRPCResponse<unknown>),\n reject,\n });\n void this.writeMessage(message).catch((error: Error) => {\n this.pendingResponses.delete(message.id);\n reject(error);\n });\n });\n }\n\n async sendNotification(\n message: Readonly<AxMCPJSONRPCNotification>\n ): Promise<void> {\n await this.writeMessage(message);\n }\n\n async sendResponse(message: Readonly<AxMCPJSONRPCResponse>): Promise<void> {\n await this.writeMessage(message);\n }\n\n setMessageHandler(\n handler: (message: Readonly<AxMCPJSONRPCMessage>) => void | Promise<void>\n ): void {\n this.messageHandler = handler;\n }\n\n async connect(): Promise<void> {\n // Connection is implicit when the process is spawned\n return Promise.resolve();\n }\n\n /**\n * Terminate the child process and clean up resources\n */\n async terminate(): Promise<void> {\n this.closeWithError(new Error('MCP server process terminated'));\n this.rl.close();\n // A failed stdin closes the transport without stopping the child.\n if (this.process.exitCode === null && this.process.signalCode === null) {\n this.process.kill();\n }\n await this.closePromise;\n }\n\n private async writeMessage(\n message: Readonly<AxMCPJSONRPCMessage>\n ): Promise<void> {\n if (this.closed || this.process.stdin.destroyed) {\n throw new Error('MCP server process is not running');\n }\n await new Promise<void>((resolve, reject) => {\n this.process.stdin.write(`${JSON.stringify(message)}\\n`, (error) => {\n if (error) reject(error);\n else resolve();\n });\n });\n }\n\n private closeWithError(error: Error): void {\n if (this.closed) return;\n this.closed = true;\n for (const pending of this.pendingResponses.values()) {\n pending.reject(error);\n }\n this.pendingResponses.clear();\n }\n}\n\n/**\n * Create a new AxMCPStdioTransport instance\n * @param config Configuration for the stdio transport\n * @returns A new AxMCPStdioTransport instance\n */\nexport function axCreateMCPStdioTransport(\n config: Readonly<StdioTransportConfig>\n): AxMCPStdioTransport {\n return new AxMCPStdioTransport(config);\n}\n"],"mappings":"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"}
|
package/index.d.cts
CHANGED
|
@@ -1,31 +1,36 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { AxMCPJSONRPCMessage, AxMCPJSONRPCNotification, AxMCPJSONRPCRequest, AxMCPJSONRPCResponse, AxMCPTransport } from "@ax-llm/ax";
|
|
2
|
+
//#region mcp/stdioTransport.d.ts
|
|
3
3
|
interface StdioTransportConfig {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
command: string;
|
|
5
|
+
args?: string[];
|
|
6
|
+
env?: NodeJS.ProcessEnv;
|
|
7
7
|
}
|
|
8
|
-
declare class AxMCPStdioTransport implements AxMCPTransport {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
8
|
+
export declare class AxMCPStdioTransport implements AxMCPTransport {
|
|
9
|
+
private process;
|
|
10
|
+
private rl;
|
|
11
|
+
private pendingResponses;
|
|
12
|
+
private closed;
|
|
13
|
+
private closePromise;
|
|
14
|
+
private messageHandler?;
|
|
15
|
+
constructor(config: Readonly<StdioTransportConfig>);
|
|
16
|
+
send(message: Readonly<AxMCPJSONRPCRequest<unknown>>): Promise<AxMCPJSONRPCResponse<unknown>>;
|
|
17
|
+
sendNotification(message: Readonly<AxMCPJSONRPCNotification>): Promise<void>;
|
|
18
|
+
sendResponse(message: Readonly<AxMCPJSONRPCResponse>): Promise<void>;
|
|
19
|
+
setMessageHandler(handler: (message: Readonly<AxMCPJSONRPCMessage>) => void | Promise<void>): void;
|
|
20
|
+
connect(): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Terminate the child process and clean up resources
|
|
23
|
+
*/
|
|
24
|
+
terminate(): Promise<void>;
|
|
25
|
+
private writeMessage;
|
|
26
|
+
private closeWithError;
|
|
23
27
|
}
|
|
24
28
|
/**
|
|
25
29
|
* Create a new AxMCPStdioTransport instance
|
|
26
30
|
* @param config Configuration for the stdio transport
|
|
27
31
|
* @returns A new AxMCPStdioTransport instance
|
|
28
32
|
*/
|
|
29
|
-
declare function axCreateMCPStdioTransport(config: Readonly<StdioTransportConfig>): AxMCPStdioTransport;
|
|
30
|
-
|
|
31
|
-
export {
|
|
33
|
+
export declare function axCreateMCPStdioTransport(config: Readonly<StdioTransportConfig>): AxMCPStdioTransport;
|
|
34
|
+
//#endregion
|
|
35
|
+
export type { StdioTransportConfig };
|
|
36
|
+
//# sourceMappingURL=index.d.cts.map
|
package/index.d.ts
CHANGED
|
@@ -1,31 +1,36 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { AxMCPJSONRPCMessage, AxMCPJSONRPCNotification, AxMCPJSONRPCRequest, AxMCPJSONRPCResponse, AxMCPTransport } from "@ax-llm/ax";
|
|
2
|
+
//#region mcp/stdioTransport.d.ts
|
|
3
3
|
interface StdioTransportConfig {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
command: string;
|
|
5
|
+
args?: string[];
|
|
6
|
+
env?: NodeJS.ProcessEnv;
|
|
7
7
|
}
|
|
8
|
-
declare class AxMCPStdioTransport implements AxMCPTransport {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
8
|
+
export declare class AxMCPStdioTransport implements AxMCPTransport {
|
|
9
|
+
private process;
|
|
10
|
+
private rl;
|
|
11
|
+
private pendingResponses;
|
|
12
|
+
private closed;
|
|
13
|
+
private closePromise;
|
|
14
|
+
private messageHandler?;
|
|
15
|
+
constructor(config: Readonly<StdioTransportConfig>);
|
|
16
|
+
send(message: Readonly<AxMCPJSONRPCRequest<unknown>>): Promise<AxMCPJSONRPCResponse<unknown>>;
|
|
17
|
+
sendNotification(message: Readonly<AxMCPJSONRPCNotification>): Promise<void>;
|
|
18
|
+
sendResponse(message: Readonly<AxMCPJSONRPCResponse>): Promise<void>;
|
|
19
|
+
setMessageHandler(handler: (message: Readonly<AxMCPJSONRPCMessage>) => void | Promise<void>): void;
|
|
20
|
+
connect(): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Terminate the child process and clean up resources
|
|
23
|
+
*/
|
|
24
|
+
terminate(): Promise<void>;
|
|
25
|
+
private writeMessage;
|
|
26
|
+
private closeWithError;
|
|
23
27
|
}
|
|
24
28
|
/**
|
|
25
29
|
* Create a new AxMCPStdioTransport instance
|
|
26
30
|
* @param config Configuration for the stdio transport
|
|
27
31
|
* @returns A new AxMCPStdioTransport instance
|
|
28
32
|
*/
|
|
29
|
-
declare function axCreateMCPStdioTransport(config: Readonly<StdioTransportConfig>): AxMCPStdioTransport;
|
|
30
|
-
|
|
31
|
-
export {
|
|
33
|
+
export declare function axCreateMCPStdioTransport(config: Readonly<StdioTransportConfig>): AxMCPStdioTransport;
|
|
34
|
+
//#endregion
|
|
35
|
+
export type { StdioTransportConfig };
|
|
36
|
+
//# sourceMappingURL=index.d.ts.map
|
package/index.js
CHANGED
|
@@ -1,5 +1,2 @@
|
|
|
1
|
-
import{spawn as
|
|
2
|
-
`)})}async sendNotification(e){this.process.stdin.write(`${JSON.stringify(e)}
|
|
3
|
-
`)}async sendResponse(e){this.process.stdin.write(`${JSON.stringify(e)}
|
|
4
|
-
`)}setMessageHandler(e){this.messageHandler=e}async connect(){return Promise.resolve()}async terminate(){return this.rl.close(),this.process.kill(),new Promise(e=>{this.process.on("exit",()=>e())})}};function a(t){return new o(t)}export{o as AxMCPStdioTransport,a as axCreateMCPStdioTransport};
|
|
1
|
+
import{spawn as e}from"node:child_process";import t from"node:readline";var n=class{process;rl;pendingResponses=/* @__PURE__ */ new Map;closed=!1;closePromise;messageHandler;constructor(n){this.process=e(n.command,n.args??[],{env:n.env?{...process.env,...n.env}:process.env}),this.closePromise=new Promise(e=>{this.process.once(`close`,()=>{this.closeWithError(/* @__PURE__ */ Error(`MCP server process closed`)),e()})}),this.process.once(`error`,e=>this.closeWithError(e)),this.process.once(`exit`,(e,t)=>{this.closeWithError(/* @__PURE__ */ Error(`MCP server process exited${e===null?``:` with code ${e}`}${t?` (${t})`:``}`))}),this.process.stdin.on(`error`,e=>this.closeWithError(e)),this.rl=t.createInterface({input:this.process.stdout}),this.rl.on(`line`,e=>{try{let t=JSON.parse(e);if(`method`in t){this.messageHandler?.(t);return}let n=t,r=n.id===null?void 0:this.pendingResponses.get(n.id);r?(r.resolve(n),n.id!==null&&this.pendingResponses.delete(n.id)):this.messageHandler?.(t)}catch{console.warn(`Non-JSON output from MCP server:`,e)}})}async send(e){return new Promise((t,n)=>{if(this.closed){n(/* @__PURE__ */ Error(`MCP server process is not running`));return}this.pendingResponses.set(e.id,{resolve:e=>t(e),reject:n}),this.writeMessage(e).catch(t=>{this.pendingResponses.delete(e.id),n(t)})})}async sendNotification(e){await this.writeMessage(e)}async sendResponse(e){await this.writeMessage(e)}setMessageHandler(e){this.messageHandler=e}async connect(){return Promise.resolve()}async terminate(){this.closeWithError(/* @__PURE__ */ Error(`MCP server process terminated`)),this.rl.close(),this.process.exitCode===null&&this.process.signalCode===null&&this.process.kill(),await this.closePromise}async writeMessage(e){if(this.closed||this.process.stdin.destroyed)throw Error(`MCP server process is not running`);await new Promise((t,n)=>{this.process.stdin.write(`${JSON.stringify(e)}\n`,e=>{e?n(e):t()})})}closeWithError(e){if(!this.closed){this.closed=!0;for(let t of this.pendingResponses.values())t.reject(e);this.pendingResponses.clear()}}};function r(e){return new n(e)}export{n as AxMCPStdioTransport,r as axCreateMCPStdioTransport};
|
|
5
2
|
//# sourceMappingURL=index.js.map
|
package/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../mcp/stdioTransport.ts"],"sourcesContent":["import { type ChildProcessWithoutNullStreams, spawn } from 'node:child_process';\nimport readline from 'node:readline';\n\nimport type {\n AxMCPJSONRPCMessage,\n AxMCPJSONRPCNotification,\n AxMCPJSONRPCRequest,\n AxMCPJSONRPCResponse,\n AxMCPTransport,\n} from '@ax-llm/ax';\n\nexport interface StdioTransportConfig {\n command: string;\n args?: string[];\n env?: NodeJS.ProcessEnv;\n}\n\nexport class AxMCPStdioTransport implements AxMCPTransport {\n private process: ChildProcessWithoutNullStreams;\n private rl: readline.Interface;\n private pendingResponses = new Map<\n string | number,\n (res: AxMCPJSONRPCResponse) => void\n >();\n private messageHandler?: (\n message: Readonly<AxMCPJSONRPCMessage>\n ) => void | Promise<void>;\n\n constructor(config: Readonly<StdioTransportConfig>) {\n this.process = spawn(config.command, config.args ?? [], {\n env: config.env ? { ...process.env, ...config.env } : process.env,\n });\n this.rl = readline.createInterface({ input: this.process.stdout });\n this.rl.on('line', (line) => {\n try {\n const message: AxMCPJSONRPCMessage = JSON.parse(line);\n if ('method' in message) {\n void this.messageHandler?.(message);\n return;\n }\n const response = message as AxMCPJSONRPCResponse;\n const
|
|
1
|
+
{"version":3,"file":"index.js","names":["readline","AxMCPStdioTransport","spawn","config","resolve","error","code","signal","message","line","response","pending","reject","res","handler","axCreateMCPStdioTransport"],"sources":["../mcp/stdioTransport.ts"],"sourcesContent":["import { type ChildProcessWithoutNullStreams, spawn } from 'node:child_process';\nimport readline from 'node:readline';\n\nimport type {\n AxMCPJSONRPCMessage,\n AxMCPJSONRPCNotification,\n AxMCPJSONRPCRequest,\n AxMCPJSONRPCResponse,\n AxMCPTransport,\n} from '@ax-llm/ax';\n\nexport interface StdioTransportConfig {\n command: string;\n args?: string[];\n env?: NodeJS.ProcessEnv;\n}\n\nexport class AxMCPStdioTransport implements AxMCPTransport {\n private process: ChildProcessWithoutNullStreams;\n private rl: readline.Interface;\n private pendingResponses = new Map<\n string | number,\n {\n resolve: (res: AxMCPJSONRPCResponse) => void;\n reject: (error: Error) => void;\n }\n >();\n private closed = false;\n private closePromise: Promise<void>;\n private messageHandler?: (\n message: Readonly<AxMCPJSONRPCMessage>\n ) => void | Promise<void>;\n\n constructor(config: Readonly<StdioTransportConfig>) {\n this.process = spawn(config.command, config.args ?? [], {\n env: config.env ? { ...process.env, ...config.env } : process.env,\n });\n this.closePromise = new Promise((resolve) => {\n this.process.once('close', () => {\n this.closeWithError(new Error('MCP server process closed'));\n resolve();\n });\n });\n this.process.once('error', (error) => this.closeWithError(error));\n this.process.once('exit', (code, signal) => {\n this.closeWithError(\n new Error(\n `MCP server process exited${code === null ? '' : ` with code ${code}`}${signal ? ` (${signal})` : ''}`\n )\n );\n });\n this.process.stdin.on('error', (error) => this.closeWithError(error));\n this.rl = readline.createInterface({ input: this.process.stdout });\n this.rl.on('line', (line) => {\n try {\n const message: AxMCPJSONRPCMessage = JSON.parse(line);\n if ('method' in message) {\n void this.messageHandler?.(message);\n return;\n }\n const response = message as AxMCPJSONRPCResponse;\n const pending =\n response.id === null\n ? undefined\n : this.pendingResponses.get(response.id);\n if (pending) {\n pending.resolve(response);\n if (response.id !== null) this.pendingResponses.delete(response.id);\n } else {\n void this.messageHandler?.(message);\n }\n } catch (_error) {\n // Skip non-JSON lines (might be debug output from the MCP server)\n console.warn('Non-JSON output from MCP server:', line);\n }\n });\n }\n\n async send(\n message: Readonly<AxMCPJSONRPCRequest<unknown>>\n ): Promise<AxMCPJSONRPCResponse<unknown>> {\n return new Promise<AxMCPJSONRPCResponse<unknown>>((resolve, reject) => {\n if (this.closed) {\n reject(new Error('MCP server process is not running'));\n return;\n }\n this.pendingResponses.set(message.id, {\n resolve: (res) => resolve(res as AxMCPJSONRPCResponse<unknown>),\n reject,\n });\n void this.writeMessage(message).catch((error: Error) => {\n this.pendingResponses.delete(message.id);\n reject(error);\n });\n });\n }\n\n async sendNotification(\n message: Readonly<AxMCPJSONRPCNotification>\n ): Promise<void> {\n await this.writeMessage(message);\n }\n\n async sendResponse(message: Readonly<AxMCPJSONRPCResponse>): Promise<void> {\n await this.writeMessage(message);\n }\n\n setMessageHandler(\n handler: (message: Readonly<AxMCPJSONRPCMessage>) => void | Promise<void>\n ): void {\n this.messageHandler = handler;\n }\n\n async connect(): Promise<void> {\n // Connection is implicit when the process is spawned\n return Promise.resolve();\n }\n\n /**\n * Terminate the child process and clean up resources\n */\n async terminate(): Promise<void> {\n this.closeWithError(new Error('MCP server process terminated'));\n this.rl.close();\n // A failed stdin closes the transport without stopping the child.\n if (this.process.exitCode === null && this.process.signalCode === null) {\n this.process.kill();\n }\n await this.closePromise;\n }\n\n private async writeMessage(\n message: Readonly<AxMCPJSONRPCMessage>\n ): Promise<void> {\n if (this.closed || this.process.stdin.destroyed) {\n throw new Error('MCP server process is not running');\n }\n await new Promise<void>((resolve, reject) => {\n this.process.stdin.write(`${JSON.stringify(message)}\\n`, (error) => {\n if (error) reject(error);\n else resolve();\n });\n });\n }\n\n private closeWithError(error: Error): void {\n if (this.closed) return;\n this.closed = true;\n for (const pending of this.pendingResponses.values()) {\n pending.reject(error);\n }\n this.pendingResponses.clear();\n }\n}\n\n/**\n * Create a new AxMCPStdioTransport instance\n * @param config Configuration for the stdio transport\n * @returns A new AxMCPStdioTransport instance\n */\nexport function axCreateMCPStdioTransport(\n config: Readonly<StdioTransportConfig>\n): AxMCPStdioTransport {\n return new AxMCPStdioTransport(config);\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ax-llm/ax-tools",
|
|
3
|
-
"version": "25.0.
|
|
3
|
+
"version": "25.0.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Ax tools package",
|
|
6
6
|
"repository": {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"license": "Apache-2.0",
|
|
14
14
|
"keywords": [],
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@ax-llm/ax": "25.0.
|
|
16
|
+
"@ax-llm/ax": "25.0.1",
|
|
17
17
|
"better-sqlite3": "^12.11.1"
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,s)=>(s=n==null?{}:e(i(n)),o(r||!n||!n.__esModule||!a.call(n,`default`)?t(s,`default`,{value:n,enumerable:!0}):s,n));Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return s}});
|