boundlessdb 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.
Files changed (60) hide show
  1. package/CHANGELOG.md +89 -0
  2. package/LICENSE +21 -0
  3. package/README.md +545 -0
  4. package/dist/better-sqlite3-shim.d.ts +12 -0
  5. package/dist/better-sqlite3-shim.d.ts.map +1 -0
  6. package/dist/better-sqlite3-shim.js +18 -0
  7. package/dist/better-sqlite3-shim.js.map +1 -0
  8. package/dist/browser.d.ts +14 -0
  9. package/dist/browser.d.ts.map +1 -0
  10. package/dist/browser.js +14 -0
  11. package/dist/browser.js.map +1 -0
  12. package/dist/config/extractor.d.ts +22 -0
  13. package/dist/config/extractor.d.ts.map +1 -0
  14. package/dist/config/extractor.js +132 -0
  15. package/dist/config/extractor.js.map +1 -0
  16. package/dist/config/validator.d.ts +14 -0
  17. package/dist/config/validator.d.ts.map +1 -0
  18. package/dist/config/validator.js +94 -0
  19. package/dist/config/validator.js.map +1 -0
  20. package/dist/event-store.browser.d.ts +61 -0
  21. package/dist/event-store.browser.d.ts.map +1 -0
  22. package/dist/event-store.browser.js +323 -0
  23. package/dist/event-store.browser.js.map +1 -0
  24. package/dist/event-store.d.ts +101 -0
  25. package/dist/event-store.d.ts.map +1 -0
  26. package/dist/event-store.js +249 -0
  27. package/dist/event-store.js.map +1 -0
  28. package/dist/index.d.ts +15 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.js +16 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/query-builder.d.ts +72 -0
  33. package/dist/query-builder.d.ts.map +1 -0
  34. package/dist/query-builder.js +84 -0
  35. package/dist/query-builder.js.map +1 -0
  36. package/dist/storage/interface.d.ts +48 -0
  37. package/dist/storage/interface.d.ts.map +1 -0
  38. package/dist/storage/interface.js +5 -0
  39. package/dist/storage/interface.js.map +1 -0
  40. package/dist/storage/memory.d.ts +27 -0
  41. package/dist/storage/memory.d.ts.map +1 -0
  42. package/dist/storage/memory.js +94 -0
  43. package/dist/storage/memory.js.map +1 -0
  44. package/dist/storage/postgres.d.ts +76 -0
  45. package/dist/storage/postgres.d.ts.map +1 -0
  46. package/dist/storage/postgres.js +346 -0
  47. package/dist/storage/postgres.js.map +1 -0
  48. package/dist/storage/sqlite.d.ts +47 -0
  49. package/dist/storage/sqlite.d.ts.map +1 -0
  50. package/dist/storage/sqlite.js +249 -0
  51. package/dist/storage/sqlite.js.map +1 -0
  52. package/dist/storage/sqljs.d.ts +60 -0
  53. package/dist/storage/sqljs.d.ts.map +1 -0
  54. package/dist/storage/sqljs.js +354 -0
  55. package/dist/storage/sqljs.js.map +1 -0
  56. package/dist/types.d.ts +172 -0
  57. package/dist/types.d.ts.map +1 -0
  58. package/dist/types.js +52 -0
  59. package/dist/types.js.map +1 -0
  60. package/package.json +75 -0
@@ -0,0 +1,249 @@
1
+ /**
2
+ * SQLite Storage implementation using better-sqlite3
3
+ */
4
+ import Database from 'better-sqlite3';
5
+ import { isConstrainedCondition } from '../types.js';
6
+ const SCHEMA = `
7
+ -- Events (Append-Only Log)
8
+ CREATE TABLE IF NOT EXISTS events (
9
+ position INTEGER PRIMARY KEY AUTOINCREMENT,
10
+ event_id TEXT NOT NULL UNIQUE,
11
+ event_type TEXT NOT NULL,
12
+ data TEXT NOT NULL,
13
+ metadata TEXT,
14
+ timestamp TEXT NOT NULL,
15
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP
16
+ );
17
+
18
+ -- Consistency Key Index (populated by store on write)
19
+ CREATE TABLE IF NOT EXISTS event_keys (
20
+ position INTEGER NOT NULL REFERENCES events(position),
21
+ key_name TEXT NOT NULL,
22
+ key_value TEXT NOT NULL,
23
+ PRIMARY KEY (position, key_name, key_value)
24
+ );
25
+
26
+ -- Metadata (for config hash, etc.)
27
+ CREATE TABLE IF NOT EXISTS metadata (
28
+ key TEXT PRIMARY KEY,
29
+ value TEXT NOT NULL
30
+ );
31
+
32
+ -- Indices for fast queries
33
+ CREATE INDEX IF NOT EXISTS idx_event_type ON events(event_type);
34
+ CREATE INDEX IF NOT EXISTS idx_key ON event_keys(key_name, key_value);
35
+ CREATE INDEX IF NOT EXISTS idx_key_position ON event_keys(key_name, key_value, position);
36
+ `;
37
+ /**
38
+ * SQLite-backed event storage
39
+ */
40
+ export class SqliteStorage {
41
+ db;
42
+ constructor(path = ':memory:') {
43
+ this.db = new Database(path);
44
+ this.db.pragma('journal_mode = WAL');
45
+ this.db.pragma('foreign_keys = ON');
46
+ this.db.exec(SCHEMA);
47
+ }
48
+ async append(eventsToStore, keys) {
49
+ if (eventsToStore.length !== keys.length) {
50
+ throw new Error('Events and keys arrays must have the same length');
51
+ }
52
+ if (eventsToStore.length === 0) {
53
+ return this.getLatestPosition();
54
+ }
55
+ const insertEvent = this.db.prepare(`
56
+ INSERT INTO events (event_id, event_type, data, metadata, timestamp)
57
+ VALUES (?, ?, ?, ?, ?)
58
+ `);
59
+ const insertKey = this.db.prepare(`
60
+ INSERT INTO event_keys (position, key_name, key_value)
61
+ VALUES (?, ?, ?)
62
+ `);
63
+ let lastPosition = 0n;
64
+ // Everything in one transaction for atomicity
65
+ const transaction = this.db.transaction(() => {
66
+ for (let i = 0; i < eventsToStore.length; i++) {
67
+ const event = eventsToStore[i];
68
+ const eventKeys = keys[i];
69
+ // Insert event
70
+ const result = insertEvent.run(event.id, event.type, JSON.stringify(event.data), event.metadata ? JSON.stringify(event.metadata) : null, event.timestamp.toISOString());
71
+ const position = BigInt(result.lastInsertRowid);
72
+ lastPosition = position;
73
+ // Insert keys
74
+ for (const key of eventKeys) {
75
+ insertKey.run(Number(position), key.name, key.value);
76
+ }
77
+ }
78
+ });
79
+ transaction();
80
+ return lastPosition;
81
+ }
82
+ async query(conditions, fromPosition, limit) {
83
+ if (conditions.length === 0) {
84
+ // No conditions = return all events
85
+ let sql = `
86
+ SELECT position, event_id, event_type, data, metadata, timestamp
87
+ FROM events
88
+ `;
89
+ const params = [];
90
+ if (fromPosition !== undefined) {
91
+ sql += ' WHERE position > ?';
92
+ params.push(Number(fromPosition));
93
+ }
94
+ sql += ' ORDER BY position';
95
+ if (limit !== undefined) {
96
+ sql += ' LIMIT ?';
97
+ params.push(limit);
98
+ }
99
+ const rows = this.db.prepare(sql).all(...params);
100
+ return rows.map(row => this.rowToEvent(row));
101
+ }
102
+ // Separate conditions: constrained (with key/value) vs unconstrained (type only)
103
+ const constrained = conditions.filter(isConstrainedCondition);
104
+ const unconstrained = conditions.filter(c => !isConstrainedCondition(c));
105
+ const whereClauses = [];
106
+ const whereParams = [];
107
+ // Unconstrained: match by type only (no join needed)
108
+ if (unconstrained.length > 0) {
109
+ const typePlaceholders = unconstrained.map(() => '?').join(', ');
110
+ whereClauses.push(`e.event_type IN (${typePlaceholders})`);
111
+ whereParams.push(...unconstrained.map(c => c.type));
112
+ }
113
+ // Constrained: match by type + key + value
114
+ if (constrained.length > 0) {
115
+ const constrainedClauses = constrained.map(() => '(e.event_type = ? AND k.key_name = ? AND k.key_value = ?)');
116
+ whereClauses.push(`(${constrainedClauses.join(' OR ')})`);
117
+ whereParams.push(...constrained.flatMap(c => [c.type, c.key, c.value]));
118
+ }
119
+ // Build SQL
120
+ let sql;
121
+ if (constrained.length > 0) {
122
+ // Need JOIN for constrained conditions
123
+ sql = `
124
+ SELECT DISTINCT
125
+ e.position,
126
+ e.event_id,
127
+ e.event_type,
128
+ e.data,
129
+ e.metadata,
130
+ e.timestamp
131
+ FROM events e
132
+ LEFT JOIN event_keys k ON e.position = k.position
133
+ WHERE (${whereClauses.join(' OR ')})
134
+ `;
135
+ }
136
+ else {
137
+ // No constrained conditions, no JOIN needed
138
+ sql = `
139
+ SELECT
140
+ position,
141
+ event_id,
142
+ event_type,
143
+ data,
144
+ metadata,
145
+ timestamp
146
+ FROM events e
147
+ WHERE (${whereClauses.join(' OR ')})
148
+ `;
149
+ }
150
+ const params = [...whereParams];
151
+ if (fromPosition !== undefined) {
152
+ sql += ' AND e.position > ?';
153
+ params.push(Number(fromPosition));
154
+ }
155
+ sql += ' ORDER BY e.position';
156
+ if (limit !== undefined) {
157
+ sql += ' LIMIT ?';
158
+ params.push(limit);
159
+ }
160
+ const stmt = this.db.prepare(sql);
161
+ const rows = stmt.all(...params);
162
+ return rows.map(row => this.rowToEvent(row));
163
+ }
164
+ async getEventsSince(conditions, sincePosition) {
165
+ return this.query(conditions, sincePosition);
166
+ }
167
+ async getLatestPosition() {
168
+ const row = this.db.prepare('SELECT MAX(position) as pos FROM events').get();
169
+ return row.pos ? BigInt(row.pos) : 0n;
170
+ }
171
+ async close() {
172
+ this.db.close();
173
+ }
174
+ // --- UI Helper Methods (not part of core DCB API) ---
175
+ /**
176
+ * Get all events (for debugging/UI)
177
+ */
178
+ getAllEvents() {
179
+ const rows = this.db.prepare(`
180
+ SELECT position, event_id, event_type, data, metadata, timestamp
181
+ FROM events
182
+ ORDER BY position ASC
183
+ `).all();
184
+ return rows.map(row => this.rowToEvent(row));
185
+ }
186
+ /**
187
+ * Get all keys (for debugging/UI)
188
+ */
189
+ getAllKeys() {
190
+ return this.db.prepare(`
191
+ SELECT position, key_name, key_value
192
+ FROM event_keys
193
+ ORDER BY position ASC
194
+ `).all();
195
+ }
196
+ /**
197
+ * Clear all data (for testing)
198
+ */
199
+ clear() {
200
+ this.db.exec('DELETE FROM event_keys');
201
+ this.db.exec('DELETE FROM events');
202
+ this.db.exec("DELETE FROM sqlite_sequence WHERE name IN ('events', 'event_keys')");
203
+ }
204
+ // --- Metadata Methods ---
205
+ /**
206
+ * Get stored config hash
207
+ */
208
+ getConfigHash() {
209
+ const row = this.db.prepare("SELECT value FROM metadata WHERE key = 'config_hash'").get();
210
+ return row?.value ?? null;
211
+ }
212
+ /**
213
+ * Set config hash
214
+ */
215
+ setConfigHash(hash) {
216
+ this.db.prepare("INSERT OR REPLACE INTO metadata (key, value) VALUES ('config_hash', ?)").run(hash);
217
+ }
218
+ /**
219
+ * Reindex all events with new keys
220
+ */
221
+ reindex(extractKeys) {
222
+ const events = this.getAllEvents();
223
+ const deleteKeys = this.db.prepare('DELETE FROM event_keys');
224
+ const insertKey = this.db.prepare('INSERT INTO event_keys (position, key_name, key_value) VALUES (?, ?, ?)');
225
+ const transaction = this.db.transaction(() => {
226
+ // Clear all keys
227
+ deleteKeys.run();
228
+ // Re-extract and insert keys for all events
229
+ for (const event of events) {
230
+ const keys = extractKeys(event);
231
+ for (const key of keys) {
232
+ insertKey.run(Number(event.position), key.name, key.value);
233
+ }
234
+ }
235
+ });
236
+ transaction();
237
+ }
238
+ rowToEvent(row) {
239
+ return {
240
+ id: row.event_id,
241
+ type: row.event_type,
242
+ data: JSON.parse(row.data),
243
+ metadata: row.metadata ? JSON.parse(row.metadata) : undefined,
244
+ timestamp: new Date(row.timestamp),
245
+ position: BigInt(row.position),
246
+ };
247
+ }
248
+ }
249
+ //# sourceMappingURL=sqlite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite.js","sourceRoot":"","sources":["../../src/storage/sqlite.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * sql.js Storage implementation for browser environments
3
+ */
4
+ import { type ExtractedKey, type QueryCondition, type StoredEvent } from '../types.js';
5
+ import type { EventStorage, EventToStore } from './interface.js';
6
+ export interface SqlJsStorageOptions {
7
+ /** URL to load sql.js WASM from (optional, uses CDN by default) */
8
+ wasmUrl?: string;
9
+ /** Existing database bytes to restore from */
10
+ data?: ArrayLike<number>;
11
+ }
12
+ /**
13
+ * sql.js-backed event storage for browser environments
14
+ */
15
+ export declare class SqlJsStorage implements EventStorage {
16
+ private db;
17
+ private initPromise;
18
+ constructor(options?: SqlJsStorageOptions);
19
+ private initialize;
20
+ private ensureInitialized;
21
+ append(eventsToStore: EventToStore[], keys: ExtractedKey[][]): Promise<bigint>;
22
+ query(conditions: QueryCondition[], fromPosition?: bigint, limit?: number): Promise<StoredEvent[]>;
23
+ getEventsSince(conditions: QueryCondition[], sincePosition: bigint): Promise<StoredEvent[]>;
24
+ getLatestPosition(): Promise<bigint>;
25
+ close(): Promise<void>;
26
+ /**
27
+ * Get all events (for debugging/UI)
28
+ */
29
+ getAllEvents(): Promise<StoredEvent[]>;
30
+ /**
31
+ * Get all keys (for debugging/UI)
32
+ */
33
+ getAllKeys(): Promise<Array<{
34
+ position: number;
35
+ key_name: string;
36
+ key_value: string;
37
+ }>>;
38
+ /**
39
+ * Clear all data (for testing)
40
+ */
41
+ clear(): Promise<void>;
42
+ /**
43
+ * Export database as Uint8Array for persistence
44
+ */
45
+ export(): Promise<Uint8Array>;
46
+ /**
47
+ * Get stored config hash
48
+ */
49
+ getConfigHash(): Promise<string | null>;
50
+ /**
51
+ * Set config hash
52
+ */
53
+ setConfigHash(hash: string): Promise<void>;
54
+ /**
55
+ * Reindex all events with new keys
56
+ */
57
+ reindex(extractKeys: (event: StoredEvent) => ExtractedKey[]): Promise<void>;
58
+ private rowToEvent;
59
+ }
60
+ //# sourceMappingURL=sqljs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqljs.d.ts","sourceRoot":"","sources":["../../src/storage/sqljs.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAA0B,KAAK,YAAY,EAAE,KAAK,cAAc,EAAE,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/G,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AA4CjE,MAAM,WAAW,mBAAmB;IAClC,mEAAmE;IACnE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8CAA8C;IAC9C,IAAI,CAAC,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;CAC1B;AAED;;GAEG;AACH,qBAAa,YAAa,YAAW,YAAY;IAC/C,OAAO,CAAC,EAAE,CAA8B;IACxC,OAAO,CAAC,WAAW,CAAgB;gBAEvB,OAAO,GAAE,mBAAwB;YAI/B,UAAU;YAkBV,iBAAiB;IAQzB,MAAM,CAAC,aAAa,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAgE9E,KAAK,CACT,UAAU,EAAE,cAAc,EAAE,EAC5B,YAAY,CAAC,EAAE,MAAM,EACrB,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,WAAW,EAAE,CAAC;IAmHnB,cAAc,CAClB,UAAU,EAAE,cAAc,EAAE,EAC5B,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,WAAW,EAAE,CAAC;IAInB,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC;IASpC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ5B;;OAEG;IACG,YAAY,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAyB5C;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAiB7F;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAO5B;;OAEG;IACG,MAAM,IAAI,OAAO,CAAC,UAAU,CAAC;IAOnC;;OAEG;IACG,aAAa,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAS7C;;OAEG;IACG,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAahD;;OAEG;IACG,OAAO,CAAC,WAAW,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,YAAY,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IA4BjF,OAAO,CAAC,UAAU;CAUnB"}
@@ -0,0 +1,354 @@
1
+ /**
2
+ * sql.js Storage implementation for browser environments
3
+ */
4
+ import initSqlJs from 'sql.js';
5
+ import { isConstrainedCondition } from '../types.js';
6
+ const SCHEMA = `
7
+ -- Events (Append-Only Log)
8
+ CREATE TABLE IF NOT EXISTS events (
9
+ position INTEGER PRIMARY KEY AUTOINCREMENT,
10
+ event_id TEXT NOT NULL UNIQUE,
11
+ event_type TEXT NOT NULL,
12
+ data TEXT NOT NULL,
13
+ metadata TEXT,
14
+ timestamp TEXT NOT NULL,
15
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP
16
+ );
17
+
18
+ -- Consistency Key Index (populated by store on write)
19
+ CREATE TABLE IF NOT EXISTS event_keys (
20
+ position INTEGER NOT NULL,
21
+ key_name TEXT NOT NULL,
22
+ key_value TEXT NOT NULL,
23
+ PRIMARY KEY (position, key_name, key_value),
24
+ FOREIGN KEY (position) REFERENCES events(position)
25
+ );
26
+
27
+ -- Metadata (for config hash, etc.)
28
+ CREATE TABLE IF NOT EXISTS metadata (
29
+ key TEXT PRIMARY KEY,
30
+ value TEXT NOT NULL
31
+ );
32
+
33
+ -- Indices for fast queries
34
+ CREATE INDEX IF NOT EXISTS idx_event_type ON events(event_type);
35
+ CREATE INDEX IF NOT EXISTS idx_key ON event_keys(key_name, key_value);
36
+ CREATE INDEX IF NOT EXISTS idx_key_position ON event_keys(key_name, key_value, position);
37
+ `;
38
+ /**
39
+ * sql.js-backed event storage for browser environments
40
+ */
41
+ export class SqlJsStorage {
42
+ db = null;
43
+ initPromise;
44
+ constructor(options = {}) {
45
+ this.initPromise = this.initialize(options);
46
+ }
47
+ async initialize(options) {
48
+ const SQL = await initSqlJs({
49
+ locateFile: (file) => {
50
+ if (options.wasmUrl)
51
+ return options.wasmUrl;
52
+ // Use CDN by default
53
+ return `https://sql.js.org/dist/${file}`;
54
+ }
55
+ });
56
+ if (options.data) {
57
+ this.db = new SQL.Database(new Uint8Array(options.data));
58
+ }
59
+ else {
60
+ this.db = new SQL.Database();
61
+ }
62
+ this.db.run(SCHEMA);
63
+ }
64
+ async ensureInitialized() {
65
+ await this.initPromise;
66
+ if (!this.db) {
67
+ throw new Error('Database not initialized');
68
+ }
69
+ return this.db;
70
+ }
71
+ async append(eventsToStore, keys) {
72
+ const db = await this.ensureInitialized();
73
+ if (eventsToStore.length !== keys.length) {
74
+ throw new Error('Events and keys arrays must have the same length');
75
+ }
76
+ if (eventsToStore.length === 0) {
77
+ return this.getLatestPosition();
78
+ }
79
+ let lastPosition = 0n;
80
+ // sql.js doesn't have built-in transactions, but we can use BEGIN/COMMIT
81
+ db.run('BEGIN TRANSACTION');
82
+ try {
83
+ // sql.js db.run() doesn't support params properly - use escaped SQL
84
+ const escapeSql = (s) => {
85
+ if (s === null)
86
+ return 'NULL';
87
+ return "'" + s.replace(/'/g, "''") + "'";
88
+ };
89
+ for (let i = 0; i < eventsToStore.length; i++) {
90
+ const event = eventsToStore[i];
91
+ const eventKeys = keys[i];
92
+ // Insert event
93
+ const eventId = String(event.id);
94
+ const eventType = String(event.type);
95
+ const eventData = JSON.stringify(event.data);
96
+ const eventMeta = event.metadata ? JSON.stringify(event.metadata) : null;
97
+ const eventTime = event.timestamp.toISOString();
98
+ if (!eventId || eventId === 'undefined' || eventId === 'null') {
99
+ throw new Error(`[SqlJsStorage] Invalid event ID: "${eventId}"`);
100
+ }
101
+ const sql = `INSERT INTO events (event_id, event_type, data, metadata, timestamp)
102
+ VALUES (${escapeSql(eventId)}, ${escapeSql(eventType)}, ${escapeSql(eventData)}, ${eventMeta === null ? 'NULL' : escapeSql(eventMeta)}, ${escapeSql(eventTime)})`;
103
+ db.run(sql);
104
+ // Get the last inserted position
105
+ const result = db.exec('SELECT last_insert_rowid() as position');
106
+ const position = BigInt(result[0].values[0][0]);
107
+ lastPosition = position;
108
+ // Insert keys
109
+ for (const key of eventKeys) {
110
+ const keySql = `INSERT INTO event_keys (position, key_name, key_value) VALUES (${Number(position)}, ${escapeSql(key.name)}, ${escapeSql(key.value)})`;
111
+ db.run(keySql);
112
+ }
113
+ }
114
+ db.run('COMMIT');
115
+ }
116
+ catch (error) {
117
+ db.run('ROLLBACK');
118
+ throw error;
119
+ }
120
+ return lastPosition;
121
+ }
122
+ async query(conditions, fromPosition, limit) {
123
+ const db = await this.ensureInitialized();
124
+ // sql.js doesn't support params properly - use escaped SQL
125
+ const escapeSql = (s) => "'" + s.replace(/'/g, "''") + "'";
126
+ if (conditions.length === 0) {
127
+ // No conditions = return all events
128
+ let sql = `
129
+ SELECT position, event_id, event_type, data, metadata, timestamp
130
+ FROM events
131
+ `;
132
+ if (fromPosition !== undefined) {
133
+ sql += ` WHERE position > ${Number(fromPosition)}`;
134
+ }
135
+ sql += ' ORDER BY position';
136
+ if (limit !== undefined) {
137
+ sql += ` LIMIT ${Number(limit)}`;
138
+ }
139
+ const result = db.exec(sql);
140
+ if (result.length === 0)
141
+ return [];
142
+ const columns = result[0].columns || result[0].lc;
143
+ const rows = result[0].values;
144
+ return rows.map((row) => {
145
+ const obj = {};
146
+ columns.forEach((col, i) => {
147
+ obj[col] = row[i];
148
+ });
149
+ return this.rowToEvent(obj);
150
+ });
151
+ }
152
+ // Separate conditions: constrained (with key/value) vs unconstrained (type only)
153
+ const constrained = conditions.filter(isConstrainedCondition);
154
+ const unconstrained = conditions.filter(c => !isConstrainedCondition(c));
155
+ const whereClauses = [];
156
+ // Unconstrained: match by type only
157
+ if (unconstrained.length > 0) {
158
+ const typeList = unconstrained.map(c => escapeSql(c.type)).join(', ');
159
+ whereClauses.push(`e.event_type IN (${typeList})`);
160
+ }
161
+ // Constrained: match by type + key + value
162
+ if (constrained.length > 0) {
163
+ const constrainedClauses = constrained.map(c => `(e.event_type = ${escapeSql(c.type)} AND k.key_name = ${escapeSql(c.key)} AND k.key_value = ${escapeSql(c.value)})`);
164
+ whereClauses.push(`(${constrainedClauses.join(' OR ')})`);
165
+ }
166
+ // Build SQL
167
+ let sql;
168
+ if (constrained.length > 0) {
169
+ // Need JOIN for constrained conditions
170
+ sql = `
171
+ SELECT DISTINCT
172
+ e.position,
173
+ e.event_id,
174
+ e.event_type,
175
+ e.data,
176
+ e.metadata,
177
+ e.timestamp
178
+ FROM events e
179
+ LEFT JOIN event_keys k ON e.position = k.position
180
+ WHERE (${whereClauses.join(' OR ')})
181
+ `;
182
+ }
183
+ else {
184
+ // No constrained conditions, no JOIN needed
185
+ sql = `
186
+ SELECT
187
+ position,
188
+ event_id,
189
+ event_type,
190
+ data,
191
+ metadata,
192
+ timestamp
193
+ FROM events e
194
+ WHERE (${whereClauses.join(' OR ')})
195
+ `;
196
+ }
197
+ if (fromPosition !== undefined) {
198
+ sql += ` AND e.position > ${Number(fromPosition)}`;
199
+ }
200
+ sql += ' ORDER BY e.position';
201
+ if (limit !== undefined) {
202
+ sql += ` LIMIT ${Number(limit)}`;
203
+ }
204
+ const result = db.exec(sql);
205
+ if (result.length === 0)
206
+ return [];
207
+ // sql.js uses 'columns' or 'lc' depending on version
208
+ const columns = result[0].columns || result[0].lc;
209
+ const rows = result[0].values;
210
+ return rows.map((row) => {
211
+ const obj = {};
212
+ columns.forEach((col, i) => {
213
+ obj[col] = row[i];
214
+ });
215
+ return this.rowToEvent(obj);
216
+ });
217
+ }
218
+ async getEventsSince(conditions, sincePosition) {
219
+ return this.query(conditions, sincePosition);
220
+ }
221
+ async getLatestPosition() {
222
+ const db = await this.ensureInitialized();
223
+ const result = db.exec('SELECT MAX(position) as pos FROM events');
224
+ if (result.length === 0 || result[0].values[0][0] === null) {
225
+ return 0n;
226
+ }
227
+ return BigInt(result[0].values[0][0]);
228
+ }
229
+ async close() {
230
+ const db = await this.ensureInitialized();
231
+ db.close();
232
+ this.db = null;
233
+ }
234
+ // --- UI Helper Methods (not part of core DCB API) ---
235
+ /**
236
+ * Get all events (for debugging/UI)
237
+ */
238
+ async getAllEvents() {
239
+ const db = await this.ensureInitialized();
240
+ const result = db.exec(`
241
+ SELECT position, event_id, event_type, data, metadata, timestamp
242
+ FROM events
243
+ ORDER BY position ASC
244
+ `);
245
+ if (result.length === 0) {
246
+ return [];
247
+ }
248
+ // sql.js uses 'columns' or 'lc' depending on version
249
+ const columns = result[0].columns || result[0].lc;
250
+ const rows = result[0].values;
251
+ return rows.map((row) => {
252
+ const obj = {};
253
+ columns.forEach((col, i) => {
254
+ obj[col] = row[i];
255
+ });
256
+ return this.rowToEvent(obj);
257
+ });
258
+ }
259
+ /**
260
+ * Get all keys (for debugging/UI)
261
+ */
262
+ async getAllKeys() {
263
+ const db = await this.ensureInitialized();
264
+ const result = db.exec(`
265
+ SELECT position, key_name, key_value
266
+ FROM event_keys
267
+ ORDER BY position ASC
268
+ `);
269
+ if (result.length === 0)
270
+ return [];
271
+ return result[0].values.map((row) => ({
272
+ position: row[0],
273
+ key_name: row[1],
274
+ key_value: row[2]
275
+ }));
276
+ }
277
+ /**
278
+ * Clear all data (for testing)
279
+ */
280
+ async clear() {
281
+ const db = await this.ensureInitialized();
282
+ db.run('DELETE FROM event_keys');
283
+ db.run('DELETE FROM events');
284
+ db.run("DELETE FROM sqlite_sequence WHERE name IN ('events', 'event_keys')");
285
+ }
286
+ /**
287
+ * Export database as Uint8Array for persistence
288
+ */
289
+ async export() {
290
+ const db = await this.ensureInitialized();
291
+ return db.export();
292
+ }
293
+ // --- Metadata Methods ---
294
+ /**
295
+ * Get stored config hash
296
+ */
297
+ async getConfigHash() {
298
+ const db = await this.ensureInitialized();
299
+ const result = db.exec("SELECT value FROM metadata WHERE key = 'config_hash'");
300
+ if (result.length === 0 || result[0].values.length === 0) {
301
+ return null;
302
+ }
303
+ return result[0].values[0][0];
304
+ }
305
+ /**
306
+ * Set config hash
307
+ */
308
+ async setConfigHash(hash) {
309
+ if (!hash) {
310
+ console.warn('[SqlJsStorage] setConfigHash called with empty hash, skipping');
311
+ return;
312
+ }
313
+ const db = await this.ensureInitialized();
314
+ // sql.js ignores params - use escaped SQL
315
+ const escapedHash = hash.replace(/'/g, "''");
316
+ db.run(`INSERT OR REPLACE INTO metadata (key, value) VALUES ('config_hash', '${escapedHash}')`);
317
+ }
318
+ /**
319
+ * Reindex all events with new keys
320
+ */
321
+ async reindex(extractKeys) {
322
+ const db = await this.ensureInitialized();
323
+ const events = await this.getAllEvents();
324
+ db.run('BEGIN TRANSACTION');
325
+ try {
326
+ // Clear all keys
327
+ db.run('DELETE FROM event_keys');
328
+ // Re-extract and insert keys for all events
329
+ const escapeSql = (s) => "'" + s.replace(/'/g, "''") + "'";
330
+ for (const event of events) {
331
+ const keys = extractKeys(event);
332
+ for (const key of keys) {
333
+ db.run(`INSERT INTO event_keys (position, key_name, key_value) VALUES (${Number(event.position)}, ${escapeSql(key.name)}, ${escapeSql(key.value)})`);
334
+ }
335
+ }
336
+ db.run('COMMIT');
337
+ }
338
+ catch (error) {
339
+ db.run('ROLLBACK');
340
+ throw error;
341
+ }
342
+ }
343
+ rowToEvent(row) {
344
+ return {
345
+ id: row.event_id,
346
+ type: row.event_type,
347
+ data: JSON.parse(row.data),
348
+ metadata: row.metadata ? JSON.parse(row.metadata) : undefined,
349
+ timestamp: new Date(row.timestamp),
350
+ position: BigInt(row.position),
351
+ };
352
+ }
353
+ }
354
+ //# sourceMappingURL=sqljs.js.map