@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 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-step and per-workflow
11
- - **ReplayEngine** — re-run workflows from stored event history
12
- - **SQLiteStorageAdapter** — persistent storage backend
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
- Pre-alpha. EventLogger, CostAggregator, and SQLiteStorageAdapter have working implementations.
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
- /** Replay events for a given orchestration from stored events */
84
- async replay(_orchestrationId, _onEvent) {
85
- return [];
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(_query) {
110
- const rows = this.db.prepare("SELECT value FROM kv").all();
111
- return rows.map((r) => JSON.parse(r.value));
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);
@@ -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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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,EAEpB,MAAM,OACJ,iBACA,SACuB;AACvB,UAAM,SAAU,MAAM,KAAK,QAAQ,MAAM;AAAA,MACvC;AAAA,IACF,CAAC;AAGD,QAAI,SAAS;AACX,iBAAW,SAAS,QAAQ;AAC1B,gBAAQ,KAAK;AAAA,MACf;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,iBACJ,YACA,SACuB;AAEvB,UAAM,YAAa,MAAM,KAAK,QAAQ,MAAM,CAAC,CAAC;AAC9C,UAAM,WAAW,UAAU,OAAO,CAAC,MAAM,EAAE,eAAe,UAAU;AACpE,aAAS,KAAK,CAAC,GAAG,MAAM,EAAE,YAAY,EAAE,SAAS;AAEjD,QAAI,SAAS;AACX,iBAAW,SAAS,UAAU;AAC5B,gBAAQ,KAAK;AAAA,MACf;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;;;ACvCA,4BAAqB;AAEd,IAAM,uBAAN,MAAqD;AAAA,EAClD;AAAA,EAER,YAAY,QAAgB;AAC1B,SAAK,KAAK,IAAI,sBAAAA,QAAS,MAAM;AAC7B,SAAK,GAAG,OAAO,oBAAoB;AAGnC,SAAK,GAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA,KAKZ;AAGD,SAAK,GAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAYZ;AAED,SAAK,GAAG;AAAA,MACN;AAAA,IACF;AACA,SAAK,GAAG;AAAA,MACN;AAAA,IACF;AACA,SAAK,GAAG;AAAA,MACN;AAAA,IACF;AACA,SAAK,GAAG;AAAA,MACN;AAAA,IACF;AACA,SAAK,GAAG;AAAA,MACN;AAAA,IACF;AAAA,EACF;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;AAEpD,UAAM,MAAM;AACZ,QAAI,IAAI,WAAW,QAAQ,KAAK,IAAI,MAAM,IAAI,MAAM;AAClD,WAAK,GACF;AAAA,QACC;AAAA;AAAA,MAEF,EACC;AAAA,QACC,IAAI;AAAA,QACJ,IAAI;AAAA,QACH,IAAI,UAAqB;AAAA,QACzB,IAAI,aAAwB,KAAK,IAAI;AAAA,QACrC,IAAI,aAAwB;AAAA,QAC5B,IAAI,mBAA8B;AAAA,QAClC,IAAI,cAAyB;AAAA,QAC7B,IAAI,UAAqB;AAAA,QAC1B,KAAK,UAAU,KAAK;AAAA,MACtB;AAAA,IACJ;AAGA,SAAK,GACF,QAAQ,sDAAsD,EAC9D,IAAI,KAAK,KAAK,UAAU,KAAK,CAAC;AAAA,EACnC;AAAA,EAEA,MAAM,MAAM,OAAyC;AACnD,UAAM,aAAuB,CAAC;AAC9B,UAAM,SAAoB,CAAC;AAE3B,QAAI,MAAM,MAAM;AACd,iBAAW,KAAK,UAAU;AAC1B,aAAO,KAAK,MAAM,IAAI;AAAA,IACxB;AACA,QAAI,MAAM,WAAW;AACnB,iBAAW,KAAK,gBAAgB;AAChC,aAAO,KAAK,MAAM,SAAS;AAAA,IAC7B;AACA,QAAI,MAAM,iBAAiB;AACzB,iBAAW,KAAK,sBAAsB;AACtC,aAAO,KAAK,MAAM,eAAe;AAAA,IACnC;AACA,QAAI,MAAM,MAAM;AACd,iBAAW,KAAK,gBAAgB;AAChC,aAAO,KAAK,OAAO,MAAM,IAAI,CAAC;AAAA,IAChC;AACA,QAAI,MAAM,IAAI;AACZ,iBAAW,KAAK,gBAAgB;AAChC,aAAO,KAAK,OAAO,MAAM,EAAE,CAAC;AAAA,IAC9B;AAEA,QAAI,MAAM;AACV,QAAI,WAAW,SAAS,GAAG;AACzB,aAAO,YAAY,WAAW,KAAK,OAAO;AAAA,IAC5C;AACA,WAAO;AACP,QAAI,MAAM,OAAO;AACf,aAAO;AACP,aAAO,KAAK,MAAM,KAAK;AAAA,IACzB;AAEA,UAAM,OAAO,KAAK,GAAG,QAAQ,GAAG,EAAE,IAAI,GAAG,MAAM;AAG/C,WAAO,KAAK,IAAI,CAAC,MAAM,KAAK,MAAM,EAAE,OAAO,CAAC;AAAA,EAC9C;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"]}
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
- /** Replay events for a given orchestration from stored events */
33
- replay(_orchestrationId: string, _onEvent?: (event: YesodEvent) => void): Promise<YesodEvent[]>;
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(_query: StorageQuery): Promise<unknown[]>;
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
- /** Replay events for a given orchestration from stored events */
33
- replay(_orchestrationId: string, _onEvent?: (event: YesodEvent) => void): Promise<YesodEvent[]>;
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(_query: StorageQuery): Promise<unknown[]>;
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
- /** Replay events for a given orchestration from stored events */
45
- async replay(_orchestrationId, _onEvent) {
46
- return [];
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(_query) {
71
- const rows = this.db.prepare("SELECT value FROM kv").all();
72
- return rows.map((r) => JSON.parse(r.value));
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":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yesod/observer",
3
- "version": "0.0.2",
3
+ "version": "0.1.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/eryv-ai/yesod.git",