@yesod/observer 0.0.2 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/index.cjs +101 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +101 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,14 +6,14 @@ Subscribes to the event bus and provides structured logging, cost aggregation, a
|
|
|
6
6
|
|
|
7
7
|
## Components
|
|
8
8
|
|
|
9
|
-
- **EventLogger** — persists orchestration events to storage
|
|
10
|
-
- **CostAggregator** — tracks token usage and cost per-
|
|
11
|
-
- **ReplayEngine** —
|
|
12
|
-
- **SQLiteStorageAdapter** — persistent storage
|
|
9
|
+
- **EventLogger** — persists orchestration events to pluggable storage
|
|
10
|
+
- **CostAggregator** — tracks token usage and cost per-session and per-workflow
|
|
11
|
+
- **ReplayEngine** — replay stored events by orchestration or workflow ID
|
|
12
|
+
- **SQLiteStorageAdapter** — persistent storage with indexed events table (type, session, workflow, timestamp)
|
|
13
13
|
|
|
14
14
|
## Status
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
Phase 0 complete. All components implemented and tested (11 tests).
|
|
17
17
|
|
|
18
18
|
## Links
|
|
19
19
|
|
package/dist/index.cjs
CHANGED
|
@@ -80,9 +80,27 @@ var ReplayEngine = class {
|
|
|
80
80
|
this.storage = storage;
|
|
81
81
|
}
|
|
82
82
|
storage;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
async replay(orchestrationId, onEvent) {
|
|
84
|
+
const events = await this.storage.query({
|
|
85
|
+
orchestrationId
|
|
86
|
+
});
|
|
87
|
+
if (onEvent) {
|
|
88
|
+
for (const event of events) {
|
|
89
|
+
onEvent(event);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return events;
|
|
93
|
+
}
|
|
94
|
+
async replayByWorkflow(workflowId, onEvent) {
|
|
95
|
+
const allEvents = await this.storage.query({});
|
|
96
|
+
const filtered = allEvents.filter((e) => e.workflowId === workflowId);
|
|
97
|
+
filtered.sort((a, b) => a.timestamp - b.timestamp);
|
|
98
|
+
if (onEvent) {
|
|
99
|
+
for (const event of filtered) {
|
|
100
|
+
onEvent(event);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return filtered;
|
|
86
104
|
}
|
|
87
105
|
};
|
|
88
106
|
|
|
@@ -92,23 +110,100 @@ var SQLiteStorageAdapter = class {
|
|
|
92
110
|
db;
|
|
93
111
|
constructor(dbPath) {
|
|
94
112
|
this.db = new import_better_sqlite3.default(dbPath);
|
|
113
|
+
this.db.pragma("journal_mode = WAL");
|
|
95
114
|
this.db.exec(`
|
|
96
115
|
CREATE TABLE IF NOT EXISTS kv (
|
|
97
116
|
key TEXT PRIMARY KEY,
|
|
98
117
|
value TEXT NOT NULL
|
|
99
118
|
)
|
|
100
119
|
`);
|
|
120
|
+
this.db.exec(`
|
|
121
|
+
CREATE TABLE IF NOT EXISTS events (
|
|
122
|
+
id TEXT PRIMARY KEY,
|
|
123
|
+
type TEXT NOT NULL,
|
|
124
|
+
source TEXT NOT NULL DEFAULT '',
|
|
125
|
+
timestamp INTEGER NOT NULL,
|
|
126
|
+
session_id TEXT,
|
|
127
|
+
orchestration_id TEXT,
|
|
128
|
+
workflow_id TEXT,
|
|
129
|
+
step_id TEXT,
|
|
130
|
+
payload TEXT NOT NULL
|
|
131
|
+
)
|
|
132
|
+
`);
|
|
133
|
+
this.db.exec(
|
|
134
|
+
`CREATE INDEX IF NOT EXISTS idx_events_type ON events(type)`
|
|
135
|
+
);
|
|
136
|
+
this.db.exec(
|
|
137
|
+
`CREATE INDEX IF NOT EXISTS idx_events_session ON events(session_id)`
|
|
138
|
+
);
|
|
139
|
+
this.db.exec(
|
|
140
|
+
`CREATE INDEX IF NOT EXISTS idx_events_orchestration ON events(orchestration_id)`
|
|
141
|
+
);
|
|
142
|
+
this.db.exec(
|
|
143
|
+
`CREATE INDEX IF NOT EXISTS idx_events_workflow ON events(workflow_id)`
|
|
144
|
+
);
|
|
145
|
+
this.db.exec(
|
|
146
|
+
`CREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp)`
|
|
147
|
+
);
|
|
101
148
|
}
|
|
102
149
|
async get(key) {
|
|
103
150
|
const row = this.db.prepare("SELECT value FROM kv WHERE key = ?").get(key);
|
|
104
151
|
return row ? JSON.parse(row.value) : void 0;
|
|
105
152
|
}
|
|
106
153
|
async put(key, value) {
|
|
154
|
+
const val = value;
|
|
155
|
+
if (key.startsWith("event:") && val.id && val.type) {
|
|
156
|
+
this.db.prepare(
|
|
157
|
+
`INSERT OR REPLACE INTO events (id, type, source, timestamp, session_id, orchestration_id, workflow_id, step_id, payload)
|
|
158
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
159
|
+
).run(
|
|
160
|
+
val.id,
|
|
161
|
+
val.type,
|
|
162
|
+
val.source ?? "",
|
|
163
|
+
val.timestamp ?? Date.now(),
|
|
164
|
+
val.sessionId ?? null,
|
|
165
|
+
val.orchestrationId ?? null,
|
|
166
|
+
val.workflowId ?? null,
|
|
167
|
+
val.stepId ?? null,
|
|
168
|
+
JSON.stringify(value)
|
|
169
|
+
);
|
|
170
|
+
}
|
|
107
171
|
this.db.prepare("INSERT OR REPLACE INTO kv (key, value) VALUES (?, ?)").run(key, JSON.stringify(value));
|
|
108
172
|
}
|
|
109
|
-
async query(
|
|
110
|
-
const
|
|
111
|
-
|
|
173
|
+
async query(query) {
|
|
174
|
+
const conditions = [];
|
|
175
|
+
const params = [];
|
|
176
|
+
if (query.type) {
|
|
177
|
+
conditions.push("type = ?");
|
|
178
|
+
params.push(query.type);
|
|
179
|
+
}
|
|
180
|
+
if (query.sessionId) {
|
|
181
|
+
conditions.push("session_id = ?");
|
|
182
|
+
params.push(query.sessionId);
|
|
183
|
+
}
|
|
184
|
+
if (query.orchestrationId) {
|
|
185
|
+
conditions.push("orchestration_id = ?");
|
|
186
|
+
params.push(query.orchestrationId);
|
|
187
|
+
}
|
|
188
|
+
if (query.from) {
|
|
189
|
+
conditions.push("timestamp >= ?");
|
|
190
|
+
params.push(Number(query.from));
|
|
191
|
+
}
|
|
192
|
+
if (query.to) {
|
|
193
|
+
conditions.push("timestamp <= ?");
|
|
194
|
+
params.push(Number(query.to));
|
|
195
|
+
}
|
|
196
|
+
let sql = "SELECT payload FROM events";
|
|
197
|
+
if (conditions.length > 0) {
|
|
198
|
+
sql += " WHERE " + conditions.join(" AND ");
|
|
199
|
+
}
|
|
200
|
+
sql += " ORDER BY timestamp ASC";
|
|
201
|
+
if (query.limit) {
|
|
202
|
+
sql += " LIMIT ?";
|
|
203
|
+
params.push(query.limit);
|
|
204
|
+
}
|
|
205
|
+
const rows = this.db.prepare(sql).all(...params);
|
|
206
|
+
return rows.map((r) => JSON.parse(r.payload));
|
|
112
207
|
}
|
|
113
208
|
async delete(key) {
|
|
114
209
|
const result = this.db.prepare("DELETE FROM kv WHERE key = ?").run(key);
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/logger.ts","../src/cost.ts","../src/replay.ts","../src/sqlite-adapter.ts"],"sourcesContent":["export { EventLogger } from \"./logger.js\";\nexport { CostAggregator } from \"./cost.js\";\nexport type { CostEntry } from \"./cost.js\";\nexport { ReplayEngine } from \"./replay.js\";\nexport { SQLiteStorageAdapter } from \"./sqlite-adapter.js\";\n","import type { YesodEvent } from \"@yesod/core\";\nimport type { StorageAdapter } from \"@yesod/core\";\n\nexport class EventLogger {\n constructor(private storage: StorageAdapter) {}\n\n async log(event: YesodEvent): Promise<void> {\n await this.storage.put(`event:${event.id}`, event);\n }\n\n async getEvent(eventId: string): Promise<YesodEvent | undefined> {\n return (await this.storage.get(`event:${eventId}`)) as\n | YesodEvent\n | undefined;\n }\n}\n","export interface CostEntry {\n sessionId: string;\n orchestrationId: string;\n inputTokens: number;\n outputTokens: number;\n cost: number;\n timestamp: string;\n}\n\nexport class CostAggregator {\n private entries: CostEntry[] = [];\n\n record(entry: CostEntry): void {\n this.entries.push(entry);\n }\n\n getSessionCost(sessionId: string): number {\n return this.entries\n .filter((e) => e.sessionId === sessionId)\n .reduce((sum, e) => sum + e.cost, 0);\n }\n\n getOrchestrationCost(orchestrationId: string): number {\n return this.entries\n .filter((e) => e.orchestrationId === orchestrationId)\n .reduce((sum, e) => sum + e.cost, 0);\n }\n\n getTotalTokens(sessionId: string): {\n input: number;\n output: number;\n } {\n const sessionEntries = this.entries.filter(\n (e) => e.sessionId === sessionId,\n );\n return {\n input: sessionEntries.reduce((sum, e) => sum + e.inputTokens, 0),\n output: sessionEntries.reduce((sum, e) => sum + e.outputTokens, 0),\n };\n }\n}\n","import type { YesodEvent, StorageAdapter } from \"@yesod/core\";\n\nexport class ReplayEngine {\n constructor(private storage: StorageAdapter) {}\n\n /** Replay events for a given orchestration from stored events */\n async replay(\n _orchestrationId: string,\n _onEvent?: (event: YesodEvent) => void,\n ): Promise<YesodEvent[]> {\n // Stub — will query storage for events and replay them in order\n return [];\n }\n}\n","import type { StorageAdapter, StorageQuery } from \"@yesod/core\";\nimport Database from \"better-sqlite3\";\n\nexport class SQLiteStorageAdapter implements StorageAdapter {\n private db: Database.Database;\n\n constructor(dbPath: string) {\n this.db = new Database(dbPath);\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS kv (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n )\n `);\n }\n\n async get(key: string): Promise<unknown | undefined> {\n const row = this.db\n .prepare(\"SELECT value FROM kv WHERE key = ?\")\n .get(key) as { value: string } | undefined;\n return row ? JSON.parse(row.value) : undefined;\n }\n\n async put(key: string, value: unknown): Promise<void> {\n this.db\n .prepare(\"INSERT OR REPLACE INTO kv (key, value) VALUES (?, ?)\")\n .run(key, JSON.stringify(value));\n }\n\n async query(_query: StorageQuery): Promise<unknown[]> {\n // Stub — will implement filtered queries over stored events\n const rows = this.db.prepare(\"SELECT value FROM kv\").all() as {\n value: string;\n }[];\n return rows.map((r) => JSON.parse(r.value));\n }\n\n async delete(key: string): Promise<boolean> {\n const result = this.db.prepare(\"DELETE FROM kv WHERE key = ?\").run(key);\n return result.changes > 0;\n }\n\n close(): void {\n this.db.close();\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACGO,IAAM,cAAN,MAAkB;AAAA,EACvB,YAAoB,SAAyB;AAAzB;AAAA,EAA0B;AAAA,EAA1B;AAAA,EAEpB,MAAM,IAAI,OAAkC;AAC1C,UAAM,KAAK,QAAQ,IAAI,SAAS,MAAM,EAAE,IAAI,KAAK;AAAA,EACnD;AAAA,EAEA,MAAM,SAAS,SAAkD;AAC/D,WAAQ,MAAM,KAAK,QAAQ,IAAI,SAAS,OAAO,EAAE;AAAA,EAGnD;AACF;;;ACNO,IAAM,iBAAN,MAAqB;AAAA,EAClB,UAAuB,CAAC;AAAA,EAEhC,OAAO,OAAwB;AAC7B,SAAK,QAAQ,KAAK,KAAK;AAAA,EACzB;AAAA,EAEA,eAAe,WAA2B;AACxC,WAAO,KAAK,QACT,OAAO,CAAC,MAAM,EAAE,cAAc,SAAS,EACvC,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,MAAM,CAAC;AAAA,EACvC;AAAA,EAEA,qBAAqB,iBAAiC;AACpD,WAAO,KAAK,QACT,OAAO,CAAC,MAAM,EAAE,oBAAoB,eAAe,EACnD,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,MAAM,CAAC;AAAA,EACvC;AAAA,EAEA,eAAe,WAGb;AACA,UAAM,iBAAiB,KAAK,QAAQ;AAAA,MAClC,CAAC,MAAM,EAAE,cAAc;AAAA,IACzB;AACA,WAAO;AAAA,MACL,OAAO,eAAe,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,aAAa,CAAC;AAAA,MAC/D,QAAQ,eAAe,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,cAAc,CAAC;AAAA,IACnE;AAAA,EACF;AACF;;;ACtCO,IAAM,eAAN,MAAmB;AAAA,EACxB,YAAoB,SAAyB;AAAzB;AAAA,EAA0B;AAAA,EAA1B;AAAA;AAAA,EAGpB,MAAM,OACJ,kBACA,UACuB;AAEvB,WAAO,CAAC;AAAA,EACV;AACF;;;ACZA,4BAAqB;AAEd,IAAM,uBAAN,MAAqD;AAAA,EAClD;AAAA,EAER,YAAY,QAAgB;AAC1B,SAAK,KAAK,IAAI,sBAAAA,QAAS,MAAM;AAC7B,SAAK,GAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA,KAKZ;AAAA,EACH;AAAA,EAEA,MAAM,IAAI,KAA2C;AACnD,UAAM,MAAM,KAAK,GACd,QAAQ,oCAAoC,EAC5C,IAAI,GAAG;AACV,WAAO,MAAM,KAAK,MAAM,IAAI,KAAK,IAAI;AAAA,EACvC;AAAA,EAEA,MAAM,IAAI,KAAa,OAA+B;AACpD,SAAK,GACF,QAAQ,sDAAsD,EAC9D,IAAI,KAAK,KAAK,UAAU,KAAK,CAAC;AAAA,EACnC;AAAA,EAEA,MAAM,MAAM,QAA0C;AAEpD,UAAM,OAAO,KAAK,GAAG,QAAQ,sBAAsB,EAAE,IAAI;AAGzD,WAAO,KAAK,IAAI,CAAC,MAAM,KAAK,MAAM,EAAE,KAAK,CAAC;AAAA,EAC5C;AAAA,EAEA,MAAM,OAAO,KAA+B;AAC1C,UAAM,SAAS,KAAK,GAAG,QAAQ,8BAA8B,EAAE,IAAI,GAAG;AACtE,WAAO,OAAO,UAAU;AAAA,EAC1B;AAAA,EAEA,QAAc;AACZ,SAAK,GAAG,MAAM;AAAA,EAChB;AACF;","names":["Database"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/logger.ts","../src/cost.ts","../src/replay.ts","../src/sqlite-adapter.ts"],"sourcesContent":["export { EventLogger } from \"./logger.js\";\nexport { CostAggregator } from \"./cost.js\";\nexport type { CostEntry } from \"./cost.js\";\nexport { ReplayEngine } from \"./replay.js\";\nexport { SQLiteStorageAdapter } from \"./sqlite-adapter.js\";\n","import type { YesodEvent } from \"@yesod/core\";\nimport type { StorageAdapter } from \"@yesod/core\";\n\nexport class EventLogger {\n constructor(private storage: StorageAdapter) {}\n\n async log(event: YesodEvent): Promise<void> {\n await this.storage.put(`event:${event.id}`, event);\n }\n\n async getEvent(eventId: string): Promise<YesodEvent | undefined> {\n return (await this.storage.get(`event:${eventId}`)) as\n | YesodEvent\n | undefined;\n }\n}\n","export interface CostEntry {\n sessionId: string;\n orchestrationId: string;\n inputTokens: number;\n outputTokens: number;\n cost: number;\n timestamp: string;\n}\n\nexport class CostAggregator {\n private entries: CostEntry[] = [];\n\n record(entry: CostEntry): void {\n this.entries.push(entry);\n }\n\n getSessionCost(sessionId: string): number {\n return this.entries\n .filter((e) => e.sessionId === sessionId)\n .reduce((sum, e) => sum + e.cost, 0);\n }\n\n getOrchestrationCost(orchestrationId: string): number {\n return this.entries\n .filter((e) => e.orchestrationId === orchestrationId)\n .reduce((sum, e) => sum + e.cost, 0);\n }\n\n getTotalTokens(sessionId: string): {\n input: number;\n output: number;\n } {\n const sessionEntries = this.entries.filter(\n (e) => e.sessionId === sessionId,\n );\n return {\n input: sessionEntries.reduce((sum, e) => sum + e.inputTokens, 0),\n output: sessionEntries.reduce((sum, e) => sum + e.outputTokens, 0),\n };\n }\n}\n","import type { YesodEvent, StorageAdapter } from \"@yesod/core\";\n\nexport class ReplayEngine {\n constructor(private storage: StorageAdapter) {}\n\n async replay(\n orchestrationId: string,\n onEvent?: (event: YesodEvent) => void,\n ): Promise<YesodEvent[]> {\n const events = (await this.storage.query({\n orchestrationId,\n })) as YesodEvent[];\n\n // Events are already sorted by timestamp from the query\n if (onEvent) {\n for (const event of events) {\n onEvent(event);\n }\n }\n\n return events;\n }\n\n async replayByWorkflow(\n workflowId: string,\n onEvent?: (event: YesodEvent) => void,\n ): Promise<YesodEvent[]> {\n // Query all events and filter by workflowId\n const allEvents = (await this.storage.query({})) as YesodEvent[];\n const filtered = allEvents.filter((e) => e.workflowId === workflowId);\n filtered.sort((a, b) => a.timestamp - b.timestamp);\n\n if (onEvent) {\n for (const event of filtered) {\n onEvent(event);\n }\n }\n\n return filtered;\n }\n}\n","import type { StorageAdapter, StorageQuery } from \"@yesod/core\";\nimport Database from \"better-sqlite3\";\n\nexport class SQLiteStorageAdapter implements StorageAdapter {\n private db: Database.Database;\n\n constructor(dbPath: string) {\n this.db = new Database(dbPath);\n this.db.pragma(\"journal_mode = WAL\");\n\n // KV table for general storage\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS kv (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n )\n `);\n\n // Events table for indexed event queries\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS events (\n id TEXT PRIMARY KEY,\n type TEXT NOT NULL,\n source TEXT NOT NULL DEFAULT '',\n timestamp INTEGER NOT NULL,\n session_id TEXT,\n orchestration_id TEXT,\n workflow_id TEXT,\n step_id TEXT,\n payload TEXT NOT NULL\n )\n `);\n\n this.db.exec(\n `CREATE INDEX IF NOT EXISTS idx_events_type ON events(type)`,\n );\n this.db.exec(\n `CREATE INDEX IF NOT EXISTS idx_events_session ON events(session_id)`,\n );\n this.db.exec(\n `CREATE INDEX IF NOT EXISTS idx_events_orchestration ON events(orchestration_id)`,\n );\n this.db.exec(\n `CREATE INDEX IF NOT EXISTS idx_events_workflow ON events(workflow_id)`,\n );\n this.db.exec(\n `CREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp)`,\n );\n }\n\n async get(key: string): Promise<unknown | undefined> {\n const row = this.db\n .prepare(\"SELECT value FROM kv WHERE key = ?\")\n .get(key) as { value: string } | undefined;\n return row ? JSON.parse(row.value) : undefined;\n }\n\n async put(key: string, value: unknown): Promise<void> {\n // If this is an event, also store in the events table\n const val = value as Record<string, unknown>;\n if (key.startsWith(\"event:\") && val.id && val.type) {\n this.db\n .prepare(\n `INSERT OR REPLACE INTO events (id, type, source, timestamp, session_id, orchestration_id, workflow_id, step_id, payload)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n )\n .run(\n val.id as string,\n val.type as string,\n (val.source as string) ?? \"\",\n (val.timestamp as number) ?? Date.now(),\n (val.sessionId as string) ?? null,\n (val.orchestrationId as string) ?? null,\n (val.workflowId as string) ?? null,\n (val.stepId as string) ?? null,\n JSON.stringify(value),\n );\n }\n\n // Always store in KV\n this.db\n .prepare(\"INSERT OR REPLACE INTO kv (key, value) VALUES (?, ?)\")\n .run(key, JSON.stringify(value));\n }\n\n async query(query: StorageQuery): Promise<unknown[]> {\n const conditions: string[] = [];\n const params: unknown[] = [];\n\n if (query.type) {\n conditions.push(\"type = ?\");\n params.push(query.type);\n }\n if (query.sessionId) {\n conditions.push(\"session_id = ?\");\n params.push(query.sessionId);\n }\n if (query.orchestrationId) {\n conditions.push(\"orchestration_id = ?\");\n params.push(query.orchestrationId);\n }\n if (query.from) {\n conditions.push(\"timestamp >= ?\");\n params.push(Number(query.from));\n }\n if (query.to) {\n conditions.push(\"timestamp <= ?\");\n params.push(Number(query.to));\n }\n\n let sql = \"SELECT payload FROM events\";\n if (conditions.length > 0) {\n sql += \" WHERE \" + conditions.join(\" AND \");\n }\n sql += \" ORDER BY timestamp ASC\";\n if (query.limit) {\n sql += \" LIMIT ?\";\n params.push(query.limit);\n }\n\n const rows = this.db.prepare(sql).all(...params) as {\n payload: string;\n }[];\n return rows.map((r) => JSON.parse(r.payload));\n }\n\n async delete(key: string): Promise<boolean> {\n const result = this.db.prepare(\"DELETE FROM kv WHERE key = ?\").run(key);\n return result.changes > 0;\n }\n\n close(): void {\n this.db.close();\n }\n}\n"],"mappings":"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","names":["Database"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -29,8 +29,8 @@ declare class CostAggregator {
|
|
|
29
29
|
declare class ReplayEngine {
|
|
30
30
|
private storage;
|
|
31
31
|
constructor(storage: StorageAdapter);
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
replay(orchestrationId: string, onEvent?: (event: YesodEvent) => void): Promise<YesodEvent[]>;
|
|
33
|
+
replayByWorkflow(workflowId: string, onEvent?: (event: YesodEvent) => void): Promise<YesodEvent[]>;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
declare class SQLiteStorageAdapter implements StorageAdapter {
|
|
@@ -38,7 +38,7 @@ declare class SQLiteStorageAdapter implements StorageAdapter {
|
|
|
38
38
|
constructor(dbPath: string);
|
|
39
39
|
get(key: string): Promise<unknown | undefined>;
|
|
40
40
|
put(key: string, value: unknown): Promise<void>;
|
|
41
|
-
query(
|
|
41
|
+
query(query: StorageQuery): Promise<unknown[]>;
|
|
42
42
|
delete(key: string): Promise<boolean>;
|
|
43
43
|
close(): void;
|
|
44
44
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -29,8 +29,8 @@ declare class CostAggregator {
|
|
|
29
29
|
declare class ReplayEngine {
|
|
30
30
|
private storage;
|
|
31
31
|
constructor(storage: StorageAdapter);
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
replay(orchestrationId: string, onEvent?: (event: YesodEvent) => void): Promise<YesodEvent[]>;
|
|
33
|
+
replayByWorkflow(workflowId: string, onEvent?: (event: YesodEvent) => void): Promise<YesodEvent[]>;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
declare class SQLiteStorageAdapter implements StorageAdapter {
|
|
@@ -38,7 +38,7 @@ declare class SQLiteStorageAdapter implements StorageAdapter {
|
|
|
38
38
|
constructor(dbPath: string);
|
|
39
39
|
get(key: string): Promise<unknown | undefined>;
|
|
40
40
|
put(key: string, value: unknown): Promise<void>;
|
|
41
|
-
query(
|
|
41
|
+
query(query: StorageQuery): Promise<unknown[]>;
|
|
42
42
|
delete(key: string): Promise<boolean>;
|
|
43
43
|
close(): void;
|
|
44
44
|
}
|
package/dist/index.js
CHANGED
|
@@ -41,9 +41,27 @@ var ReplayEngine = class {
|
|
|
41
41
|
this.storage = storage;
|
|
42
42
|
}
|
|
43
43
|
storage;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
async replay(orchestrationId, onEvent) {
|
|
45
|
+
const events = await this.storage.query({
|
|
46
|
+
orchestrationId
|
|
47
|
+
});
|
|
48
|
+
if (onEvent) {
|
|
49
|
+
for (const event of events) {
|
|
50
|
+
onEvent(event);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return events;
|
|
54
|
+
}
|
|
55
|
+
async replayByWorkflow(workflowId, onEvent) {
|
|
56
|
+
const allEvents = await this.storage.query({});
|
|
57
|
+
const filtered = allEvents.filter((e) => e.workflowId === workflowId);
|
|
58
|
+
filtered.sort((a, b) => a.timestamp - b.timestamp);
|
|
59
|
+
if (onEvent) {
|
|
60
|
+
for (const event of filtered) {
|
|
61
|
+
onEvent(event);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return filtered;
|
|
47
65
|
}
|
|
48
66
|
};
|
|
49
67
|
|
|
@@ -53,23 +71,100 @@ var SQLiteStorageAdapter = class {
|
|
|
53
71
|
db;
|
|
54
72
|
constructor(dbPath) {
|
|
55
73
|
this.db = new Database(dbPath);
|
|
74
|
+
this.db.pragma("journal_mode = WAL");
|
|
56
75
|
this.db.exec(`
|
|
57
76
|
CREATE TABLE IF NOT EXISTS kv (
|
|
58
77
|
key TEXT PRIMARY KEY,
|
|
59
78
|
value TEXT NOT NULL
|
|
60
79
|
)
|
|
61
80
|
`);
|
|
81
|
+
this.db.exec(`
|
|
82
|
+
CREATE TABLE IF NOT EXISTS events (
|
|
83
|
+
id TEXT PRIMARY KEY,
|
|
84
|
+
type TEXT NOT NULL,
|
|
85
|
+
source TEXT NOT NULL DEFAULT '',
|
|
86
|
+
timestamp INTEGER NOT NULL,
|
|
87
|
+
session_id TEXT,
|
|
88
|
+
orchestration_id TEXT,
|
|
89
|
+
workflow_id TEXT,
|
|
90
|
+
step_id TEXT,
|
|
91
|
+
payload TEXT NOT NULL
|
|
92
|
+
)
|
|
93
|
+
`);
|
|
94
|
+
this.db.exec(
|
|
95
|
+
`CREATE INDEX IF NOT EXISTS idx_events_type ON events(type)`
|
|
96
|
+
);
|
|
97
|
+
this.db.exec(
|
|
98
|
+
`CREATE INDEX IF NOT EXISTS idx_events_session ON events(session_id)`
|
|
99
|
+
);
|
|
100
|
+
this.db.exec(
|
|
101
|
+
`CREATE INDEX IF NOT EXISTS idx_events_orchestration ON events(orchestration_id)`
|
|
102
|
+
);
|
|
103
|
+
this.db.exec(
|
|
104
|
+
`CREATE INDEX IF NOT EXISTS idx_events_workflow ON events(workflow_id)`
|
|
105
|
+
);
|
|
106
|
+
this.db.exec(
|
|
107
|
+
`CREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp)`
|
|
108
|
+
);
|
|
62
109
|
}
|
|
63
110
|
async get(key) {
|
|
64
111
|
const row = this.db.prepare("SELECT value FROM kv WHERE key = ?").get(key);
|
|
65
112
|
return row ? JSON.parse(row.value) : void 0;
|
|
66
113
|
}
|
|
67
114
|
async put(key, value) {
|
|
115
|
+
const val = value;
|
|
116
|
+
if (key.startsWith("event:") && val.id && val.type) {
|
|
117
|
+
this.db.prepare(
|
|
118
|
+
`INSERT OR REPLACE INTO events (id, type, source, timestamp, session_id, orchestration_id, workflow_id, step_id, payload)
|
|
119
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
120
|
+
).run(
|
|
121
|
+
val.id,
|
|
122
|
+
val.type,
|
|
123
|
+
val.source ?? "",
|
|
124
|
+
val.timestamp ?? Date.now(),
|
|
125
|
+
val.sessionId ?? null,
|
|
126
|
+
val.orchestrationId ?? null,
|
|
127
|
+
val.workflowId ?? null,
|
|
128
|
+
val.stepId ?? null,
|
|
129
|
+
JSON.stringify(value)
|
|
130
|
+
);
|
|
131
|
+
}
|
|
68
132
|
this.db.prepare("INSERT OR REPLACE INTO kv (key, value) VALUES (?, ?)").run(key, JSON.stringify(value));
|
|
69
133
|
}
|
|
70
|
-
async query(
|
|
71
|
-
const
|
|
72
|
-
|
|
134
|
+
async query(query) {
|
|
135
|
+
const conditions = [];
|
|
136
|
+
const params = [];
|
|
137
|
+
if (query.type) {
|
|
138
|
+
conditions.push("type = ?");
|
|
139
|
+
params.push(query.type);
|
|
140
|
+
}
|
|
141
|
+
if (query.sessionId) {
|
|
142
|
+
conditions.push("session_id = ?");
|
|
143
|
+
params.push(query.sessionId);
|
|
144
|
+
}
|
|
145
|
+
if (query.orchestrationId) {
|
|
146
|
+
conditions.push("orchestration_id = ?");
|
|
147
|
+
params.push(query.orchestrationId);
|
|
148
|
+
}
|
|
149
|
+
if (query.from) {
|
|
150
|
+
conditions.push("timestamp >= ?");
|
|
151
|
+
params.push(Number(query.from));
|
|
152
|
+
}
|
|
153
|
+
if (query.to) {
|
|
154
|
+
conditions.push("timestamp <= ?");
|
|
155
|
+
params.push(Number(query.to));
|
|
156
|
+
}
|
|
157
|
+
let sql = "SELECT payload FROM events";
|
|
158
|
+
if (conditions.length > 0) {
|
|
159
|
+
sql += " WHERE " + conditions.join(" AND ");
|
|
160
|
+
}
|
|
161
|
+
sql += " ORDER BY timestamp ASC";
|
|
162
|
+
if (query.limit) {
|
|
163
|
+
sql += " LIMIT ?";
|
|
164
|
+
params.push(query.limit);
|
|
165
|
+
}
|
|
166
|
+
const rows = this.db.prepare(sql).all(...params);
|
|
167
|
+
return rows.map((r) => JSON.parse(r.payload));
|
|
73
168
|
}
|
|
74
169
|
async delete(key) {
|
|
75
170
|
const result = this.db.prepare("DELETE FROM kv WHERE key = ?").run(key);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/logger.ts","../src/cost.ts","../src/replay.ts","../src/sqlite-adapter.ts"],"sourcesContent":["import type { YesodEvent } from \"@yesod/core\";\nimport type { StorageAdapter } from \"@yesod/core\";\n\nexport class EventLogger {\n constructor(private storage: StorageAdapter) {}\n\n async log(event: YesodEvent): Promise<void> {\n await this.storage.put(`event:${event.id}`, event);\n }\n\n async getEvent(eventId: string): Promise<YesodEvent | undefined> {\n return (await this.storage.get(`event:${eventId}`)) as\n | YesodEvent\n | undefined;\n }\n}\n","export interface CostEntry {\n sessionId: string;\n orchestrationId: string;\n inputTokens: number;\n outputTokens: number;\n cost: number;\n timestamp: string;\n}\n\nexport class CostAggregator {\n private entries: CostEntry[] = [];\n\n record(entry: CostEntry): void {\n this.entries.push(entry);\n }\n\n getSessionCost(sessionId: string): number {\n return this.entries\n .filter((e) => e.sessionId === sessionId)\n .reduce((sum, e) => sum + e.cost, 0);\n }\n\n getOrchestrationCost(orchestrationId: string): number {\n return this.entries\n .filter((e) => e.orchestrationId === orchestrationId)\n .reduce((sum, e) => sum + e.cost, 0);\n }\n\n getTotalTokens(sessionId: string): {\n input: number;\n output: number;\n } {\n const sessionEntries = this.entries.filter(\n (e) => e.sessionId === sessionId,\n );\n return {\n input: sessionEntries.reduce((sum, e) => sum + e.inputTokens, 0),\n output: sessionEntries.reduce((sum, e) => sum + e.outputTokens, 0),\n };\n }\n}\n","import type { YesodEvent, StorageAdapter } from \"@yesod/core\";\n\nexport class ReplayEngine {\n constructor(private storage: StorageAdapter) {}\n\n /** Replay events for a given orchestration from stored events */\n async replay(\n _orchestrationId: string,\n _onEvent?: (event: YesodEvent) => void,\n ): Promise<YesodEvent[]> {\n // Stub — will query storage for events and replay them in order\n return [];\n }\n}\n","import type { StorageAdapter, StorageQuery } from \"@yesod/core\";\nimport Database from \"better-sqlite3\";\n\nexport class SQLiteStorageAdapter implements StorageAdapter {\n private db: Database.Database;\n\n constructor(dbPath: string) {\n this.db = new Database(dbPath);\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS kv (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n )\n `);\n }\n\n async get(key: string): Promise<unknown | undefined> {\n const row = this.db\n .prepare(\"SELECT value FROM kv WHERE key = ?\")\n .get(key) as { value: string } | undefined;\n return row ? JSON.parse(row.value) : undefined;\n }\n\n async put(key: string, value: unknown): Promise<void> {\n this.db\n .prepare(\"INSERT OR REPLACE INTO kv (key, value) VALUES (?, ?)\")\n .run(key, JSON.stringify(value));\n }\n\n async query(_query: StorageQuery): Promise<unknown[]> {\n // Stub — will implement filtered queries over stored events\n const rows = this.db.prepare(\"SELECT value FROM kv\").all() as {\n value: string;\n }[];\n return rows.map((r) => JSON.parse(r.value));\n }\n\n async delete(key: string): Promise<boolean> {\n const result = this.db.prepare(\"DELETE FROM kv WHERE key = ?\").run(key);\n return result.changes > 0;\n }\n\n close(): void {\n this.db.close();\n }\n}\n"],"mappings":";AAGO,IAAM,cAAN,MAAkB;AAAA,EACvB,YAAoB,SAAyB;AAAzB;AAAA,EAA0B;AAAA,EAA1B;AAAA,EAEpB,MAAM,IAAI,OAAkC;AAC1C,UAAM,KAAK,QAAQ,IAAI,SAAS,MAAM,EAAE,IAAI,KAAK;AAAA,EACnD;AAAA,EAEA,MAAM,SAAS,SAAkD;AAC/D,WAAQ,MAAM,KAAK,QAAQ,IAAI,SAAS,OAAO,EAAE;AAAA,EAGnD;AACF;;;ACNO,IAAM,iBAAN,MAAqB;AAAA,EAClB,UAAuB,CAAC;AAAA,EAEhC,OAAO,OAAwB;AAC7B,SAAK,QAAQ,KAAK,KAAK;AAAA,EACzB;AAAA,EAEA,eAAe,WAA2B;AACxC,WAAO,KAAK,QACT,OAAO,CAAC,MAAM,EAAE,cAAc,SAAS,EACvC,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,MAAM,CAAC;AAAA,EACvC;AAAA,EAEA,qBAAqB,iBAAiC;AACpD,WAAO,KAAK,QACT,OAAO,CAAC,MAAM,EAAE,oBAAoB,eAAe,EACnD,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,MAAM,CAAC;AAAA,EACvC;AAAA,EAEA,eAAe,WAGb;AACA,UAAM,iBAAiB,KAAK,QAAQ;AAAA,MAClC,CAAC,MAAM,EAAE,cAAc;AAAA,IACzB;AACA,WAAO;AAAA,MACL,OAAO,eAAe,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,aAAa,CAAC;AAAA,MAC/D,QAAQ,eAAe,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,cAAc,CAAC;AAAA,IACnE;AAAA,EACF;AACF;;;ACtCO,IAAM,eAAN,MAAmB;AAAA,EACxB,YAAoB,SAAyB;AAAzB;AAAA,EAA0B;AAAA,EAA1B;AAAA;AAAA,EAGpB,MAAM,OACJ,kBACA,UACuB;AAEvB,WAAO,CAAC;AAAA,EACV;AACF;;;ACZA,OAAO,cAAc;AAEd,IAAM,uBAAN,MAAqD;AAAA,EAClD;AAAA,EAER,YAAY,QAAgB;AAC1B,SAAK,KAAK,IAAI,SAAS,MAAM;AAC7B,SAAK,GAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA,KAKZ;AAAA,EACH;AAAA,EAEA,MAAM,IAAI,KAA2C;AACnD,UAAM,MAAM,KAAK,GACd,QAAQ,oCAAoC,EAC5C,IAAI,GAAG;AACV,WAAO,MAAM,KAAK,MAAM,IAAI,KAAK,IAAI;AAAA,EACvC;AAAA,EAEA,MAAM,IAAI,KAAa,OAA+B;AACpD,SAAK,GACF,QAAQ,sDAAsD,EAC9D,IAAI,KAAK,KAAK,UAAU,KAAK,CAAC;AAAA,EACnC;AAAA,EAEA,MAAM,MAAM,QAA0C;AAEpD,UAAM,OAAO,KAAK,GAAG,QAAQ,sBAAsB,EAAE,IAAI;AAGzD,WAAO,KAAK,IAAI,CAAC,MAAM,KAAK,MAAM,EAAE,KAAK,CAAC;AAAA,EAC5C;AAAA,EAEA,MAAM,OAAO,KAA+B;AAC1C,UAAM,SAAS,KAAK,GAAG,QAAQ,8BAA8B,EAAE,IAAI,GAAG;AACtE,WAAO,OAAO,UAAU;AAAA,EAC1B;AAAA,EAEA,QAAc;AACZ,SAAK,GAAG,MAAM;AAAA,EAChB;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/logger.ts","../src/cost.ts","../src/replay.ts","../src/sqlite-adapter.ts"],"sourcesContent":["import type { YesodEvent } from \"@yesod/core\";\nimport type { StorageAdapter } from \"@yesod/core\";\n\nexport class EventLogger {\n constructor(private storage: StorageAdapter) {}\n\n async log(event: YesodEvent): Promise<void> {\n await this.storage.put(`event:${event.id}`, event);\n }\n\n async getEvent(eventId: string): Promise<YesodEvent | undefined> {\n return (await this.storage.get(`event:${eventId}`)) as\n | YesodEvent\n | undefined;\n }\n}\n","export interface CostEntry {\n sessionId: string;\n orchestrationId: string;\n inputTokens: number;\n outputTokens: number;\n cost: number;\n timestamp: string;\n}\n\nexport class CostAggregator {\n private entries: CostEntry[] = [];\n\n record(entry: CostEntry): void {\n this.entries.push(entry);\n }\n\n getSessionCost(sessionId: string): number {\n return this.entries\n .filter((e) => e.sessionId === sessionId)\n .reduce((sum, e) => sum + e.cost, 0);\n }\n\n getOrchestrationCost(orchestrationId: string): number {\n return this.entries\n .filter((e) => e.orchestrationId === orchestrationId)\n .reduce((sum, e) => sum + e.cost, 0);\n }\n\n getTotalTokens(sessionId: string): {\n input: number;\n output: number;\n } {\n const sessionEntries = this.entries.filter(\n (e) => e.sessionId === sessionId,\n );\n return {\n input: sessionEntries.reduce((sum, e) => sum + e.inputTokens, 0),\n output: sessionEntries.reduce((sum, e) => sum + e.outputTokens, 0),\n };\n }\n}\n","import type { YesodEvent, StorageAdapter } from \"@yesod/core\";\n\nexport class ReplayEngine {\n constructor(private storage: StorageAdapter) {}\n\n async replay(\n orchestrationId: string,\n onEvent?: (event: YesodEvent) => void,\n ): Promise<YesodEvent[]> {\n const events = (await this.storage.query({\n orchestrationId,\n })) as YesodEvent[];\n\n // Events are already sorted by timestamp from the query\n if (onEvent) {\n for (const event of events) {\n onEvent(event);\n }\n }\n\n return events;\n }\n\n async replayByWorkflow(\n workflowId: string,\n onEvent?: (event: YesodEvent) => void,\n ): Promise<YesodEvent[]> {\n // Query all events and filter by workflowId\n const allEvents = (await this.storage.query({})) as YesodEvent[];\n const filtered = allEvents.filter((e) => e.workflowId === workflowId);\n filtered.sort((a, b) => a.timestamp - b.timestamp);\n\n if (onEvent) {\n for (const event of filtered) {\n onEvent(event);\n }\n }\n\n return filtered;\n }\n}\n","import type { StorageAdapter, StorageQuery } from \"@yesod/core\";\nimport Database from \"better-sqlite3\";\n\nexport class SQLiteStorageAdapter implements StorageAdapter {\n private db: Database.Database;\n\n constructor(dbPath: string) {\n this.db = new Database(dbPath);\n this.db.pragma(\"journal_mode = WAL\");\n\n // KV table for general storage\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS kv (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n )\n `);\n\n // Events table for indexed event queries\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS events (\n id TEXT PRIMARY KEY,\n type TEXT NOT NULL,\n source TEXT NOT NULL DEFAULT '',\n timestamp INTEGER NOT NULL,\n session_id TEXT,\n orchestration_id TEXT,\n workflow_id TEXT,\n step_id TEXT,\n payload TEXT NOT NULL\n )\n `);\n\n this.db.exec(\n `CREATE INDEX IF NOT EXISTS idx_events_type ON events(type)`,\n );\n this.db.exec(\n `CREATE INDEX IF NOT EXISTS idx_events_session ON events(session_id)`,\n );\n this.db.exec(\n `CREATE INDEX IF NOT EXISTS idx_events_orchestration ON events(orchestration_id)`,\n );\n this.db.exec(\n `CREATE INDEX IF NOT EXISTS idx_events_workflow ON events(workflow_id)`,\n );\n this.db.exec(\n `CREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp)`,\n );\n }\n\n async get(key: string): Promise<unknown | undefined> {\n const row = this.db\n .prepare(\"SELECT value FROM kv WHERE key = ?\")\n .get(key) as { value: string } | undefined;\n return row ? JSON.parse(row.value) : undefined;\n }\n\n async put(key: string, value: unknown): Promise<void> {\n // If this is an event, also store in the events table\n const val = value as Record<string, unknown>;\n if (key.startsWith(\"event:\") && val.id && val.type) {\n this.db\n .prepare(\n `INSERT OR REPLACE INTO events (id, type, source, timestamp, session_id, orchestration_id, workflow_id, step_id, payload)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n )\n .run(\n val.id as string,\n val.type as string,\n (val.source as string) ?? \"\",\n (val.timestamp as number) ?? Date.now(),\n (val.sessionId as string) ?? null,\n (val.orchestrationId as string) ?? null,\n (val.workflowId as string) ?? null,\n (val.stepId as string) ?? null,\n JSON.stringify(value),\n );\n }\n\n // Always store in KV\n this.db\n .prepare(\"INSERT OR REPLACE INTO kv (key, value) VALUES (?, ?)\")\n .run(key, JSON.stringify(value));\n }\n\n async query(query: StorageQuery): Promise<unknown[]> {\n const conditions: string[] = [];\n const params: unknown[] = [];\n\n if (query.type) {\n conditions.push(\"type = ?\");\n params.push(query.type);\n }\n if (query.sessionId) {\n conditions.push(\"session_id = ?\");\n params.push(query.sessionId);\n }\n if (query.orchestrationId) {\n conditions.push(\"orchestration_id = ?\");\n params.push(query.orchestrationId);\n }\n if (query.from) {\n conditions.push(\"timestamp >= ?\");\n params.push(Number(query.from));\n }\n if (query.to) {\n conditions.push(\"timestamp <= ?\");\n params.push(Number(query.to));\n }\n\n let sql = \"SELECT payload FROM events\";\n if (conditions.length > 0) {\n sql += \" WHERE \" + conditions.join(\" AND \");\n }\n sql += \" ORDER BY timestamp ASC\";\n if (query.limit) {\n sql += \" LIMIT ?\";\n params.push(query.limit);\n }\n\n const rows = this.db.prepare(sql).all(...params) as {\n payload: string;\n }[];\n return rows.map((r) => JSON.parse(r.payload));\n }\n\n async delete(key: string): Promise<boolean> {\n const result = this.db.prepare(\"DELETE FROM kv WHERE key = ?\").run(key);\n return result.changes > 0;\n }\n\n close(): void {\n this.db.close();\n }\n}\n"],"mappings":"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","names":[]}
|