@nt-ai-lab/deterministic-agent-workflow-event-store 0.2.0 → 0.2.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.
|
@@ -1,20 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { StoredEvent } from '@nt-ai-lab/deterministic-agent-workflow-engine';
|
|
2
2
|
import { type SqliteDatabase } from '../infra/external-clients/sqlite/sqlite-runtime';
|
|
3
|
-
declare const passthroughEventSchema: z.ZodObject<{
|
|
4
|
-
type: z.ZodString;
|
|
5
|
-
at: z.ZodString;
|
|
6
|
-
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
7
|
-
type: z.ZodString;
|
|
8
|
-
at: z.ZodString;
|
|
9
|
-
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
10
|
-
type: z.ZodString;
|
|
11
|
-
at: z.ZodString;
|
|
12
|
-
}, z.ZodTypeAny, "passthrough">>;
|
|
13
|
-
type BaseEvent = z.infer<typeof passthroughEventSchema>;
|
|
14
3
|
/** @riviere-role value-object */
|
|
15
4
|
export type SqliteEventStore = {
|
|
16
|
-
readonly readEvents: (sessionId: string) => readonly
|
|
17
|
-
readonly appendEvents: (sessionId: string, events: readonly
|
|
5
|
+
readonly readEvents: (sessionId: string) => readonly StoredEvent[];
|
|
6
|
+
readonly appendEvents: (sessionId: string, events: readonly StoredEvent[]) => void;
|
|
18
7
|
readonly sessionExists: (sessionId: string) => boolean;
|
|
19
8
|
readonly hasSessionStarted: (sessionId: string) => boolean;
|
|
20
9
|
readonly listSessions: () => readonly string[];
|
|
@@ -24,4 +13,3 @@ export type SqliteEventStore = {
|
|
|
24
13
|
export declare function createStore(dbPath: string): SqliteEventStore;
|
|
25
14
|
/** @riviere-role domain-service */
|
|
26
15
|
export declare function resolveSessionId(store: SqliteEventStore, input: string): string;
|
|
27
|
-
export {};
|
|
@@ -1,50 +1,47 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { WorkflowStateError } from '@nt-ai-lab/deterministic-agent-workflow-engine';
|
|
2
|
+
import { stripEnvelopeKeys, WorkflowStateError, } from '@nt-ai-lab/deterministic-agent-workflow-engine';
|
|
3
3
|
import { enableWalMode, openSqliteDatabase, } from '../infra/external-clients/sqlite/sqlite-runtime.js';
|
|
4
|
-
const passthroughEventSchema = z.object({
|
|
5
|
-
type: z.string(),
|
|
6
|
-
at: z.string(),
|
|
7
|
-
}).passthrough();
|
|
8
4
|
const createTableSql = `
|
|
9
5
|
CREATE TABLE IF NOT EXISTS events (
|
|
10
6
|
seq INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
11
7
|
session_id TEXT NOT NULL,
|
|
12
8
|
type TEXT NOT NULL,
|
|
13
9
|
at TEXT NOT NULL,
|
|
10
|
+
state TEXT,
|
|
14
11
|
payload TEXT NOT NULL
|
|
15
12
|
)
|
|
16
13
|
`;
|
|
17
|
-
const
|
|
14
|
+
const eventRowSchema = z.array(z.object({
|
|
15
|
+
type: z.string(),
|
|
16
|
+
at: z.string(),
|
|
17
|
+
state: z.string().nullable(),
|
|
18
|
+
payload: z.string(),
|
|
19
|
+
}));
|
|
18
20
|
const rowWithSessionIdSchema = z.array(z.object({ session_id: z.string() }));
|
|
19
21
|
const countFieldSchema = z.union([z.number(), z.bigint(), z.string()]);
|
|
20
22
|
const countRowSchema = z.object({ count: countFieldSchema });
|
|
23
|
+
const tableInfoRowSchema = z.array(z.object({ name: z.string() }));
|
|
21
24
|
/** @riviere-role domain-service */
|
|
22
25
|
export function createStore(dbPath) {
|
|
23
26
|
const db = openSqliteDatabase(dbPath);
|
|
24
27
|
enableWalMode(db);
|
|
25
28
|
db.exec(createTableSql);
|
|
29
|
+
ensureStateColumn(db);
|
|
26
30
|
return {
|
|
27
31
|
db,
|
|
28
32
|
readEvents(sessionId) {
|
|
29
|
-
const rawRows = db.prepare('SELECT payload FROM events WHERE session_id = ? ORDER BY seq').all(sessionId);
|
|
30
|
-
const rows =
|
|
31
|
-
return rows.map((row, index) =>
|
|
32
|
-
const parsedPayload = tryParsePayload(row.payload, index);
|
|
33
|
-
const parsedEvent = passthroughEventSchema.safeParse(parsedPayload);
|
|
34
|
-
if (!parsedEvent.success) {
|
|
35
|
-
throw new WorkflowStateError(`Invalid event at index ${index} for session ${sessionId}: ${parsedEvent.error.message}`);
|
|
36
|
-
}
|
|
37
|
-
return parsedEvent.data;
|
|
38
|
-
});
|
|
33
|
+
const rawRows = db.prepare('SELECT type, at, state, payload FROM events WHERE session_id = ? ORDER BY seq').all(sessionId);
|
|
34
|
+
const rows = eventRowSchema.parse(rawRows);
|
|
35
|
+
return rows.map((row, index) => buildStoredEvent(row, sessionId, index));
|
|
39
36
|
},
|
|
40
37
|
appendEvents(sessionId, events) {
|
|
41
38
|
if (events.length === 0)
|
|
42
39
|
return;
|
|
43
|
-
const insert = db.prepare('INSERT INTO events (session_id, type, at, payload) VALUES (?, ?, ?, ?)');
|
|
40
|
+
const insert = db.prepare('INSERT INTO events (session_id, type, at, state, payload) VALUES (?, ?, ?, ?, ?)');
|
|
44
41
|
db.exec('BEGIN IMMEDIATE');
|
|
45
42
|
try {
|
|
46
43
|
for (const event of events) {
|
|
47
|
-
insert.run(sessionId, event.type, event.at, JSON.stringify(event));
|
|
44
|
+
insert.run(sessionId, event.envelope.type, event.envelope.at, event.envelope.state ?? null, JSON.stringify(event.payload));
|
|
48
45
|
}
|
|
49
46
|
db.exec('COMMIT');
|
|
50
47
|
}
|
|
@@ -101,3 +98,33 @@ function tryParsePayload(payload, index) {
|
|
|
101
98
|
throw new WorkflowStateError(`Cannot parse event payload at index ${index}: ${String(cause)}`);
|
|
102
99
|
}
|
|
103
100
|
}
|
|
101
|
+
function ensureStateColumn(db) {
|
|
102
|
+
const rawColumns = db.prepare('PRAGMA table_info(events)').all();
|
|
103
|
+
const columns = tableInfoRowSchema.parse(rawColumns);
|
|
104
|
+
if (columns.some((column) => column.name === 'state'))
|
|
105
|
+
return;
|
|
106
|
+
db.exec('ALTER TABLE events ADD COLUMN state TEXT');
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Dual-read adapter. Modern rows carry only domain fields in `payload` JSON
|
|
110
|
+
* (envelope in columns). Legacy rows have `type`/`at` duplicated inside the
|
|
111
|
+
* payload JSON and no `state` column value. Either way, normalize to
|
|
112
|
+
* `StoredEvent` with undefined state for legacy rows.
|
|
113
|
+
*/
|
|
114
|
+
function buildStoredEvent(row, sessionId, index) {
|
|
115
|
+
const parsedPayload = tryParsePayload(row.payload, index);
|
|
116
|
+
if (!isRecord(parsedPayload)) {
|
|
117
|
+
throw new WorkflowStateError(`Invalid event payload at index ${index} for session ${sessionId}: expected object`);
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
envelope: {
|
|
121
|
+
type: row.type,
|
|
122
|
+
at: row.at,
|
|
123
|
+
state: row.state ?? undefined,
|
|
124
|
+
},
|
|
125
|
+
payload: stripEnvelopeKeys(parsedPayload),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function isRecord(value) {
|
|
129
|
+
return typeof value === 'object' && value !== null;
|
|
130
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nt-ai-lab/deterministic-agent-workflow-event-store",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
"dist"
|
|
12
12
|
],
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"
|
|
15
|
-
"
|
|
14
|
+
"zod": "^3.25.76",
|
|
15
|
+
"@nt-ai-lab/deterministic-agent-workflow-engine": "0.2.1"
|
|
16
16
|
},
|
|
17
17
|
"publishConfig": {
|
|
18
18
|
"access": "public"
|