@ccheever/exact-ibex-runtime 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 (161) hide show
  1. package/package.json +63 -0
  2. package/src/abort/AbortController.ts +23 -0
  3. package/src/abort/AbortSignal.ts +152 -0
  4. package/src/abort/index.ts +2 -0
  5. package/src/accessibility.ts +12 -0
  6. package/src/arraybuffer-detach.ts +109 -0
  7. package/src/base64/base64.ts +168 -0
  8. package/src/base64/index.ts +1 -0
  9. package/src/blob/Blob.ts +259 -0
  10. package/src/blob/File.ts +59 -0
  11. package/src/blob/FormData.ts +323 -0
  12. package/src/blob/index.ts +3 -0
  13. package/src/bootstrap.ts +1946 -0
  14. package/src/broadcast/BroadcastChannel.ts +280 -0
  15. package/src/broadcast/index.ts +5 -0
  16. package/src/cache/Cache.ts +349 -0
  17. package/src/cache/CacheStorage.ts +89 -0
  18. package/src/cache/index.ts +27 -0
  19. package/src/camera/index.ts +6202 -0
  20. package/src/camera/processor.worker.ts +194 -0
  21. package/src/camera/scene.ts +195 -0
  22. package/src/clipboard/Clipboard.ts +129 -0
  23. package/src/clipboard/ClipboardItem.ts +97 -0
  24. package/src/clipboard/index.ts +6 -0
  25. package/src/clone/index.ts +1 -0
  26. package/src/clone/structuredClone.ts +389 -0
  27. package/src/clone/transferableSymbols.ts +2 -0
  28. package/src/compression/CompressionStream.ts +146 -0
  29. package/src/compression/DecompressionStream.ts +342 -0
  30. package/src/compression/index.ts +4 -0
  31. package/src/console/Console.ts +341 -0
  32. package/src/console/index.ts +2 -0
  33. package/src/core/accessibility-state.ts +263 -0
  34. package/src/core/accessibility.ts +184 -0
  35. package/src/core/agent-state.ts +37 -0
  36. package/src/core/diagnostics-logs.ts +144 -0
  37. package/src/core/host-call-bridge.ts +16 -0
  38. package/src/core/i18n-helpers.ts +189 -0
  39. package/src/core/locale-state.ts +253 -0
  40. package/src/core/locale.ts +95 -0
  41. package/src/crypto/Crypto.ts +2743 -0
  42. package/src/crypto/index.ts +1 -0
  43. package/src/diagnostics/logs.ts +7 -0
  44. package/src/encoding/TextDecoder.ts +1181 -0
  45. package/src/encoding/TextDecoderStream.ts +58 -0
  46. package/src/encoding/TextEncoder.ts +180 -0
  47. package/src/encoding/TextEncoderStream.ts +39 -0
  48. package/src/encoding/index.ts +8 -0
  49. package/src/events/CloseEvent.ts +91 -0
  50. package/src/events/DOMException.ts +409 -0
  51. package/src/events/ErrorEvent.ts +39 -0
  52. package/src/events/Event.ts +151 -0
  53. package/src/events/EventTarget.ts +280 -0
  54. package/src/events/FocusEvent.ts +27 -0
  55. package/src/events/KeyboardEvent.ts +46 -0
  56. package/src/events/MessageEvent.ts +61 -0
  57. package/src/events/ProgressEvent.ts +33 -0
  58. package/src/events/PromiseRejectionEvent.ts +31 -0
  59. package/src/events/index.ts +52 -0
  60. package/src/eventsource/EventSource.ts +371 -0
  61. package/src/eventsource/index.ts +2 -0
  62. package/src/fetch/Headers.ts +642 -0
  63. package/src/fetch/Request.ts +760 -0
  64. package/src/fetch/Response.ts +543 -0
  65. package/src/fetch/body.ts +1256 -0
  66. package/src/fetch/cookie-jar.ts +566 -0
  67. package/src/fetch/demo.ts +207 -0
  68. package/src/fetch/errors.ts +101 -0
  69. package/src/fetch/fetch.ts +2610 -0
  70. package/src/fetch/index.ts +101 -0
  71. package/src/fetch/native-bridge.ts +65 -0
  72. package/src/fetch/types.ts +258 -0
  73. package/src/filereader/FileReader.ts +236 -0
  74. package/src/filereader/index.ts +1 -0
  75. package/src/fs/Dirent.ts +39 -0
  76. package/src/fs/ExactFile.ts +450 -0
  77. package/src/fs/Stats.ts +80 -0
  78. package/src/fs/index.ts +944 -0
  79. package/src/fs/promises.ts +386 -0
  80. package/src/fs/shared.ts +328 -0
  81. package/src/http-server/index.js +697 -0
  82. package/src/http-server/index.ts +27 -0
  83. package/src/identity.generated.ts +14 -0
  84. package/src/index.ts +283 -0
  85. package/src/indexeddb/IDBCursor.ts +188 -0
  86. package/src/indexeddb/IDBDatabase.ts +343 -0
  87. package/src/indexeddb/IDBFactory.ts +269 -0
  88. package/src/indexeddb/IDBIndex.ts +194 -0
  89. package/src/indexeddb/IDBKeyRange.ts +109 -0
  90. package/src/indexeddb/IDBObjectStore.ts +468 -0
  91. package/src/indexeddb/IDBRequest.ts +163 -0
  92. package/src/indexeddb/IDBTransaction.ts +207 -0
  93. package/src/indexeddb/index.ts +34 -0
  94. package/src/indexeddb/utils.ts +52 -0
  95. package/src/inspect/index.ts +1 -0
  96. package/src/inspect/inspect.ts +465 -0
  97. package/src/internal/detect.ts +104 -0
  98. package/src/locale.ts +10 -0
  99. package/src/location/index.ts +1059 -0
  100. package/src/locks/LockManager.ts +460 -0
  101. package/src/locks/index.ts +12 -0
  102. package/src/media/VideoFrame.ts +58 -0
  103. package/src/messaging/MessageChannel.ts +31 -0
  104. package/src/messaging/MessagePort.ts +180 -0
  105. package/src/messaging/index.ts +2 -0
  106. package/src/messaging.ts +247 -0
  107. package/src/native/NativeModules.ts +354 -0
  108. package/src/native/index.ts +1 -0
  109. package/src/navigator/Navigator.ts +351 -0
  110. package/src/navigator/index.ts +1 -0
  111. package/src/node/Buffer.ts +1786 -0
  112. package/src/node/index.ts +4 -0
  113. package/src/node/path.ts +495 -0
  114. package/src/node/process.ts +2528 -0
  115. package/src/performance/Performance.ts +532 -0
  116. package/src/performance/index.ts +21 -0
  117. package/src/polyfills/array.ts +236 -0
  118. package/src/polyfills/arraybuffer.ts +172 -0
  119. package/src/polyfills/groupby.ts +85 -0
  120. package/src/polyfills/index.ts +85 -0
  121. package/src/polyfills/intl.ts +1956 -0
  122. package/src/polyfills/iterator.ts +479 -0
  123. package/src/polyfills/promise.ts +37 -0
  124. package/src/polyfills/set.ts +245 -0
  125. package/src/polyfills/string.ts +85 -0
  126. package/src/polyfills/typedarray.ts +110 -0
  127. package/src/promise-rejection-tracking.ts +464 -0
  128. package/src/react-native/index.ts +388 -0
  129. package/src/runtime-entry.ts +55 -0
  130. package/src/scheduling/AnimationFrame.ts +105 -0
  131. package/src/scheduling/IdleCallback.ts +167 -0
  132. package/src/scheduling/index.ts +13 -0
  133. package/src/security/Capabilities.ts +1146 -0
  134. package/src/security/Permissions.ts +392 -0
  135. package/src/security/capability-bits.generated.ts +63 -0
  136. package/src/security/index.ts +16 -0
  137. package/src/sqlite/Database.ts +456 -0
  138. package/src/sqlite/Statement.ts +206 -0
  139. package/src/sqlite/constants.ts +79 -0
  140. package/src/sqlite/errors.ts +25 -0
  141. package/src/sqlite/index.ts +34 -0
  142. package/src/sqlite/module.js +438 -0
  143. package/src/storage/Storage.ts +291 -0
  144. package/src/storage/StorageManager.ts +91 -0
  145. package/src/storage/index.ts +3 -0
  146. package/src/stream-compat.ts +47 -0
  147. package/src/streams/ReadableStream.ts +4131 -0
  148. package/src/streams/TransformStream.ts +375 -0
  149. package/src/streams/WritableStream.ts +866 -0
  150. package/src/streams/index.ts +41 -0
  151. package/src/timers/Timers.ts +296 -0
  152. package/src/timers/index.ts +11 -0
  153. package/src/url/URL.ts +656 -0
  154. package/src/url/URLPattern.ts +850 -0
  155. package/src/url/URLSearchParams.ts +244 -0
  156. package/src/url/index.ts +9 -0
  157. package/src/websocket/WebSocket.ts +770 -0
  158. package/src/websocket/WebSocketError.ts +52 -0
  159. package/src/websocket/WebSocketStream.ts +628 -0
  160. package/src/websocket/index.ts +7 -0
  161. package/src/window/index.ts +872 -0
@@ -0,0 +1,456 @@
1
+ /**
2
+ * SQLite Database for the Ibex runtime.
3
+ *
4
+ * API matches bun:sqlite's Database class with cr-sqlite extension support.
5
+ *
6
+ * @see https://bun.sh/docs/api/sqlite
7
+ * @see https://vlcn.io/docs/cr-sqlite/intro
8
+ */
9
+
10
+ import { Statement, type Changes } from './Statement';
11
+
12
+ const g = globalThis as any;
13
+
14
+ export interface DatabaseOptions {
15
+ /** Open the database as read-only. */
16
+ readonly?: boolean;
17
+ /** Create the database if it doesn't exist (default: true). */
18
+ create?: boolean;
19
+ /** Open for read-write (default: true). */
20
+ readwrite?: boolean;
21
+ /** Return integers as BigInt instead of Number. */
22
+ safeIntegers?: boolean;
23
+ /** Strict parameter binding (no prefix needed, errors on missing params). */
24
+ strict?: boolean;
25
+ }
26
+
27
+ export class Database {
28
+ /** @internal */
29
+ _handle: any;
30
+ /** @internal */
31
+ _closed = false;
32
+ /** @internal */
33
+ _queryCache = new Map<string, Statement>();
34
+ /** @internal */
35
+ _options: DatabaseOptions;
36
+ /** @internal */
37
+ _crSqliteLoaded = false;
38
+
39
+ /** The filename of the database. */
40
+ readonly filename: string;
41
+
42
+ /**
43
+ * Create or open a SQLite database.
44
+ *
45
+ * @param filename Path to the database file.
46
+ * - `:memory:` or `""` or omitted for in-memory database
47
+ * @param options Database options or raw SQLite open flags
48
+ */
49
+ constructor(filename?: string, options?: number | DatabaseOptions) {
50
+ this.filename = filename ?? ':memory:';
51
+
52
+ if (typeof options === 'number') {
53
+ this._options = {};
54
+ } else {
55
+ this._options = options ?? {};
56
+ }
57
+
58
+ if (!g.__exactSqliteOpen) {
59
+ throw new Error(
60
+ 'exact:sqlite native bridge not available. ' +
61
+ 'The exact:sqlite module requires the Ibex runtime with SQLite support.'
62
+ );
63
+ }
64
+
65
+ const flags = typeof options === 'number' ? options : undefined;
66
+ this._handle = g.__exactSqliteOpen(this.filename, {
67
+ readonly: this._options.readonly ?? false,
68
+ create: this._options.create ?? true,
69
+ readwrite: this._options.readwrite ?? true,
70
+ safeIntegers: this._options.safeIntegers ?? false,
71
+ flags,
72
+ });
73
+
74
+ // Enable WAL mode by default for better concurrent access
75
+ this.run('PRAGMA journal_mode = WAL');
76
+ this.run('PRAGMA foreign_keys = ON');
77
+ }
78
+
79
+ /**
80
+ * The native SQLite handle.
81
+ */
82
+ get handle(): any {
83
+ return this._handle;
84
+ }
85
+
86
+ /**
87
+ * Whether the database is currently in a transaction.
88
+ */
89
+ get inTransaction(): boolean {
90
+ this._checkClosed();
91
+ if (g.__exactSqliteInTransaction) {
92
+ return g.__exactSqliteInTransaction(this._handle);
93
+ }
94
+ return false;
95
+ }
96
+
97
+ /**
98
+ * Prepare and cache a SQL statement.
99
+ * Returns the cached statement if the same SQL was previously queried.
100
+ */
101
+ query<R = any, P extends any[] = any[]>(sql: string): Statement<R, P> {
102
+ this._checkClosed();
103
+ let stmt = this._queryCache.get(sql);
104
+ if (!stmt) {
105
+ stmt = new Statement(this._handle, sql);
106
+ this._queryCache.set(sql, stmt);
107
+ }
108
+ return stmt as Statement<R, P>;
109
+ }
110
+
111
+ /**
112
+ * Prepare a SQL statement WITHOUT caching.
113
+ * Creates a fresh Statement each time.
114
+ */
115
+ prepare<R = any, P extends any[] = any[]>(sql: string): Statement<R, P> {
116
+ this._checkClosed();
117
+ return new Statement<R, P>(this._handle, sql);
118
+ }
119
+
120
+ /**
121
+ * Execute a SQL statement directly.
122
+ * Supports multi-statement SQL (e.g., "SELECT 1; SELECT 2;").
123
+ *
124
+ * @returns Changes info with `changes` and `lastInsertRowid`
125
+ */
126
+ run(sql: string, ...bindings: any[]): Changes {
127
+ this._checkClosed();
128
+ if (g.__exactSqliteExec) {
129
+ const params = bindings.length === 1 && typeof bindings[0] === 'object' && bindings[0] !== null && !(bindings[0] instanceof Uint8Array)
130
+ ? bindings[0]
131
+ : bindings.length > 0 ? bindings : undefined;
132
+ return g.__exactSqliteExec(this._handle, sql, params);
133
+ }
134
+ // Fallback: use prepare + run
135
+ const stmt = this.prepare(sql);
136
+ try {
137
+ return stmt.run(...bindings);
138
+ } finally {
139
+ stmt.finalize();
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Alias for run().
145
+ */
146
+ exec(sql: string, ...bindings: any[]): Changes {
147
+ return this.run(sql, ...bindings);
148
+ }
149
+
150
+ /**
151
+ * Create a transaction wrapper function.
152
+ *
153
+ * @returns A callable function that wraps execution in BEGIN/COMMIT.
154
+ * If the function throws, the transaction is rolled back.
155
+ * The returned function has `.deferred()`, `.immediate()`, and `.exclusive()` variants.
156
+ */
157
+ transaction<T extends (...args: any[]) => any>(fn: T): T & {
158
+ deferred: T;
159
+ immediate: T;
160
+ exclusive: T;
161
+ } {
162
+ this._checkClosed();
163
+ const db = this;
164
+
165
+ const wrapTransaction = (beginStatement: string) => {
166
+ return function (this: any, ...args: any[]) {
167
+ // Nested transactions use savepoints
168
+ if (db.inTransaction) {
169
+ const savepointName = `_sp_${Date.now()}_${Math.random().toString(36).slice(2)}`;
170
+ db.run(`SAVEPOINT "${savepointName}"`);
171
+ try {
172
+ const result = fn.apply(this, args);
173
+ db.run(`RELEASE "${savepointName}"`);
174
+ return result;
175
+ } catch (e) {
176
+ db.run(`ROLLBACK TO "${savepointName}"`);
177
+ db.run(`RELEASE "${savepointName}"`);
178
+ throw e;
179
+ }
180
+ }
181
+
182
+ db.run(beginStatement);
183
+ try {
184
+ const result = fn.apply(this, args);
185
+ db.run('COMMIT');
186
+ return result;
187
+ } catch (e) {
188
+ db.run('ROLLBACK');
189
+ throw e;
190
+ }
191
+ } as any;
192
+ };
193
+
194
+ const txnFn = wrapTransaction('BEGIN') as any;
195
+ txnFn.deferred = wrapTransaction('BEGIN DEFERRED');
196
+ txnFn.immediate = wrapTransaction('BEGIN IMMEDIATE');
197
+ txnFn.exclusive = wrapTransaction('BEGIN EXCLUSIVE');
198
+
199
+ return txnFn;
200
+ }
201
+
202
+ /**
203
+ * Close the database connection.
204
+ * @param throwOnError If true, throws on close errors (default: false)
205
+ */
206
+ close(throwOnError = false): void {
207
+ if (this._closed) return;
208
+
209
+ // Finalize cr-sqlite before closing (needs db still open)
210
+ if (this._crSqliteLoaded) {
211
+ try {
212
+ this.run('SELECT crsql_finalize()');
213
+ } catch {
214
+ // Ignore errors during cleanup
215
+ }
216
+ }
217
+
218
+ this._closed = true;
219
+
220
+ // Finalize cached statements
221
+ for (const stmt of this._queryCache.values()) {
222
+ stmt.finalize();
223
+ }
224
+ this._queryCache.clear();
225
+
226
+ if (g.__exactSqliteClose) {
227
+ try {
228
+ g.__exactSqliteClose(this._handle);
229
+ } catch (e) {
230
+ if (throwOnError) throw e;
231
+ }
232
+ }
233
+ }
234
+
235
+ /**
236
+ * Serialize the database to a Buffer/Uint8Array.
237
+ * @param name Database name (default: "main")
238
+ */
239
+ serialize(name?: string): Uint8Array {
240
+ this._checkClosed();
241
+ if (!g.__exactSqliteSerialize) {
242
+ throw new Error('Database serialization not supported in this build');
243
+ }
244
+ return g.__exactSqliteSerialize(this._handle, name ?? 'main');
245
+ }
246
+
247
+ /**
248
+ * Load a SQLite extension.
249
+ * @param path Path to the extension shared library
250
+ * @param entryPoint Optional entry point function name
251
+ */
252
+ loadExtension(path: string, entryPoint?: string): void {
253
+ this._checkClosed();
254
+ if (!g.__exactSqliteLoadExtension) {
255
+ throw new Error('Extension loading not supported in this build');
256
+ }
257
+ g.__exactSqliteLoadExtension(this._handle, path, entryPoint);
258
+ }
259
+
260
+ /**
261
+ * Advanced sqlite3_file_control interface.
262
+ */
263
+ fileControl(cmd: number, value?: any): any;
264
+ fileControl(zDbName: string, cmd: number, value?: any): any;
265
+ fileControl(...args: any[]): any {
266
+ this._checkClosed();
267
+ if (!g.__exactSqliteFileControl) {
268
+ throw new Error('fileControl not supported in this build');
269
+ }
270
+ return g.__exactSqliteFileControl(this._handle, ...args);
271
+ }
272
+
273
+ // ================================================================
274
+ // cr-sqlite extension support
275
+ // ================================================================
276
+
277
+ /**
278
+ * Load and initialize the cr-sqlite extension for CRDT-based replication.
279
+ *
280
+ * After calling this, you can use:
281
+ * - `db.run("SELECT crsql_as_crr('table_name')")` to make a table a CRR
282
+ * - `db.query("SELECT * FROM crsql_changes WHERE db_version > ?")` to get changes
283
+ * - `db.run("INSERT INTO crsql_changes ...")` to apply changes from another peer
284
+ *
285
+ * @see https://vlcn.io/docs/cr-sqlite/intro
286
+ */
287
+ enableCrSqlite(): void {
288
+ this._checkClosed();
289
+ if (this._crSqliteLoaded) return;
290
+
291
+ if (g.__exactCrSqlitePath) {
292
+ this.loadExtension(g.__exactCrSqlitePath());
293
+ } else if (g.__exactSqliteLoadCrSqlite) {
294
+ g.__exactSqliteLoadCrSqlite(this._handle);
295
+ } else {
296
+ throw new Error(
297
+ 'cr-sqlite extension not available. ' +
298
+ 'The Ibex runtime must be built with cr-sqlite support.'
299
+ );
300
+ }
301
+ this._crSqliteLoaded = true;
302
+ }
303
+
304
+ /**
305
+ * Get the unique site ID for this database (cr-sqlite).
306
+ * Each database instance has a unique identifier for replication.
307
+ */
308
+ getSiteId(): Uint8Array {
309
+ this._checkClosed();
310
+ this._requireCrSqlite();
311
+ const result = this.query<{ id: Uint8Array }>('SELECT crsql_site_id() as id').get();
312
+ return result!.id;
313
+ }
314
+
315
+ /**
316
+ * Get the current database version (cr-sqlite logical clock).
317
+ */
318
+ getDbVersion(): number | bigint {
319
+ this._checkClosed();
320
+ this._requireCrSqlite();
321
+ const result = this.query<{ version: number }>('SELECT crsql_db_version() as version').get();
322
+ return result!.version;
323
+ }
324
+
325
+ /**
326
+ * Mark a table as a Conflict-free Replicated Relation (CRR).
327
+ * This adds CRDT metadata and triggers for automatic conflict resolution.
328
+ *
329
+ * @param tableName Name of the existing table to make a CRR
330
+ */
331
+ markAsCrr(tableName: string): void {
332
+ this._checkClosed();
333
+ this._requireCrSqlite();
334
+ this.run(`SELECT crsql_as_crr('${tableName}')`);
335
+ }
336
+
337
+ /**
338
+ * Get changesets since a given database version.
339
+ * Used for syncing with other peers.
340
+ *
341
+ * @param sinceVersion Get changes after this version (0 for all changes)
342
+ * @param excludeSiteId Optional site ID to exclude (optimization for bilateral sync)
343
+ */
344
+ getChanges(sinceVersion: number | bigint = 0, excludeSiteId?: Uint8Array): any[] {
345
+ this._checkClosed();
346
+ this._requireCrSqlite();
347
+
348
+ if (excludeSiteId) {
349
+ return this.query(
350
+ `SELECT "table", "pk", "cid", "val", "col_version", "db_version", "site_id", "cl", "seq"
351
+ FROM crsql_changes
352
+ WHERE db_version > ? AND site_id IS NOT ?`
353
+ ).all(sinceVersion, excludeSiteId);
354
+ }
355
+
356
+ return this.query(
357
+ `SELECT "table", "pk", "cid", "val", "col_version", "db_version", "site_id", "cl", "seq"
358
+ FROM crsql_changes
359
+ WHERE db_version > ?`
360
+ ).all(sinceVersion);
361
+ }
362
+
363
+ /**
364
+ * Apply a changeset from another peer.
365
+ * The changeset should be the output of getChanges() from another database.
366
+ *
367
+ * @param changes Array of change rows from getChanges()
368
+ */
369
+ applyChanges(changes: any[]): void {
370
+ this._checkClosed();
371
+ this._requireCrSqlite();
372
+
373
+ const insert = this.prepare(
374
+ `INSERT INTO crsql_changes ("table", "pk", "cid", "val", "col_version", "db_version", "site_id", "cl", "seq")
375
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
376
+ );
377
+
378
+ const applyAll = this.transaction((changes: any[]) => {
379
+ for (const change of changes) {
380
+ insert.run(
381
+ change.table, change.pk, change.cid, change.val,
382
+ change.col_version, change.db_version, change.site_id,
383
+ change.cl, change.seq
384
+ );
385
+ }
386
+ });
387
+
388
+ try {
389
+ applyAll(changes);
390
+ } finally {
391
+ insert.finalize();
392
+ }
393
+ }
394
+
395
+ // ================================================================
396
+ // Static methods
397
+ // ================================================================
398
+
399
+ /**
400
+ * Create or open a SQLite database (alias for constructor).
401
+ */
402
+ static open(filename?: string, options?: number | DatabaseOptions): Database {
403
+ return new Database(filename, options);
404
+ }
405
+
406
+ /**
407
+ * Create a database from serialized data.
408
+ */
409
+ static deserialize(data: Uint8Array, options?: boolean | DatabaseOptions): Database {
410
+ if (!g.__exactSqliteDeserialize) {
411
+ throw new Error('Database deserialization not supported in this build');
412
+ }
413
+
414
+ const isReadOnly = typeof options === 'boolean' ? options : (options as DatabaseOptions)?.readonly ?? false;
415
+ const handle = g.__exactSqliteDeserialize(data, isReadOnly);
416
+
417
+ const db = Object.create(Database.prototype) as Database;
418
+ db._handle = handle;
419
+ db._closed = false;
420
+ db._queryCache = new Map();
421
+ db._options = typeof options === 'object' ? options : { readonly: isReadOnly };
422
+ (db as any).filename = ':memory:';
423
+ db._crSqliteLoaded = false;
424
+ return db;
425
+ }
426
+
427
+ [Symbol.dispose](): void {
428
+ this.close();
429
+ }
430
+
431
+ get [Symbol.toStringTag](): string {
432
+ return 'Database';
433
+ }
434
+
435
+ // ================================================================
436
+ // Internal helpers
437
+ // ================================================================
438
+
439
+ /** @internal */
440
+ private _checkClosed(): void {
441
+ if (this._closed) {
442
+ throw new Error('Database is closed');
443
+ }
444
+ }
445
+
446
+ /** @internal */
447
+ private _requireCrSqlite(): void {
448
+ if (!this._crSqliteLoaded) {
449
+ throw new Error(
450
+ 'cr-sqlite is not loaded. Call db.enableCrSqlite() first.'
451
+ );
452
+ }
453
+ }
454
+ }
455
+
456
+ export default Database;
@@ -0,0 +1,206 @@
1
+ /**
2
+ * SQLite Statement for the Ibex runtime.
3
+ *
4
+ * Prepared statement wrapping native SQLite3 operations.
5
+ * API matches bun:sqlite's Statement class.
6
+ *
7
+ * @see https://bun.sh/docs/api/sqlite#statement
8
+ */
9
+
10
+ const g = globalThis as any;
11
+
12
+ export interface Changes {
13
+ changes: number;
14
+ lastInsertRowid: number | bigint;
15
+ }
16
+
17
+ type SQLBindings = string | number | bigint | boolean | null | Uint8Array;
18
+ type SQLParams = SQLBindings | Record<string, SQLBindings>;
19
+
20
+ export class Statement<ReturnType = unknown, ParamsType extends any[] = any[]> {
21
+ /** @internal */
22
+ _handle: any;
23
+ /** @internal */
24
+ _db: any;
25
+ /** @internal */
26
+ _sql: string;
27
+ /** @internal */
28
+ _finalized = false;
29
+ /** @internal */
30
+ _columnTypes: (string | null)[] = [];
31
+ /** @internal */
32
+ _declaredTypes: (string | null)[] = [];
33
+ /** @internal */
34
+ _readOnly = true;
35
+ /** @internal */
36
+ _executed = false;
37
+
38
+ /** Column names of the result set. */
39
+ columnNames: string[] = [];
40
+
41
+ /** Number of parameters expected by the statement. */
42
+ paramsCount = 0;
43
+
44
+ /** The native handle. */
45
+ get native(): any {
46
+ return {
47
+ columns: [...this.columnNames],
48
+ columnsCount: this.columnNames.length,
49
+ };
50
+ }
51
+
52
+ /** Column types based on actual values. */
53
+ get columnTypes(): (string | null)[] {
54
+ if (!this._readOnly) {
55
+ throw new Error('columnTypes is not available for non-read-only statements');
56
+ }
57
+ return this._columnTypes;
58
+ }
59
+
60
+ /** Types from CREATE TABLE schema. */
61
+ get declaredTypes(): (string | null)[] {
62
+ if (this._readOnly && !this._executed) {
63
+ throw new Error('Statement must be executed before accessing declaredTypes');
64
+ }
65
+ return this._declaredTypes;
66
+ }
67
+
68
+ /** @internal */
69
+ constructor(dbHandle: any, sql: string) {
70
+ this._db = dbHandle;
71
+ this._sql = sql;
72
+
73
+ if (!g.__exactSqlitePrepare) {
74
+ throw new Error(
75
+ 'exact:sqlite native bridge not available. ' +
76
+ 'The exact:sqlite module requires the Ibex runtime with SQLite support.'
77
+ );
78
+ }
79
+
80
+ const result = g.__exactSqlitePrepare(dbHandle, sql);
81
+ this._handle = result.handle;
82
+ this.columnNames = result.columnNames || [];
83
+ this._declaredTypes = result.declaredTypes || [];
84
+ this.paramsCount = result.paramsCount || 0;
85
+ this._readOnly = result.readOnly !== false;
86
+ }
87
+
88
+ /**
89
+ * Execute the statement and return all matching rows as objects.
90
+ */
91
+ all(...params: ParamsType): ReturnType[] {
92
+ this._checkFinalized();
93
+ const bindings = this._normalizeParams(params);
94
+ const result = g.__exactSqliteAll(this._handle, bindings);
95
+ this._recordExecution(result);
96
+ return result.rows;
97
+ }
98
+
99
+ /**
100
+ * Execute the statement and return the first matching row as an object.
101
+ * Returns null if no rows match.
102
+ */
103
+ get(...params: ParamsType): ReturnType | null {
104
+ this._checkFinalized();
105
+ const bindings = this._normalizeParams(params);
106
+ const result = g.__exactSqliteGet(this._handle, bindings);
107
+ this._recordExecution(result);
108
+ return result.row ?? null;
109
+ }
110
+
111
+ /**
112
+ * Execute the statement. Returns changes info.
113
+ */
114
+ run(...params: ParamsType): Changes {
115
+ this._checkFinalized();
116
+ const bindings = this._normalizeParams(params);
117
+ const result = g.__exactSqliteRun(this._handle, bindings);
118
+ this._executed = true;
119
+ return result;
120
+ }
121
+
122
+ /**
123
+ * Execute the statement and return all rows as arrays of values (no keys).
124
+ */
125
+ values(...params: ParamsType): unknown[][] {
126
+ this._checkFinalized();
127
+ const bindings = this._normalizeParams(params);
128
+ const result = g.__exactSqliteValues(this._handle, bindings);
129
+ this._recordExecution(result);
130
+ return result.rows;
131
+ }
132
+
133
+ /**
134
+ * Destroy the statement and free resources.
135
+ * Once finalized, the statement cannot be used again.
136
+ */
137
+ finalize(): void {
138
+ if (this._finalized) return;
139
+ this._finalized = true;
140
+ if (g.__exactSqliteFinalize) {
141
+ g.__exactSqliteFinalize(this._handle);
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Return the expanded SQL with most recent bound values.
147
+ */
148
+ toString(): string {
149
+ this._checkFinalized();
150
+ if (g.__exactSqliteExpandedSql) {
151
+ return g.__exactSqliteExpandedSql(this._handle);
152
+ }
153
+ return this._sql;
154
+ }
155
+
156
+ /**
157
+ * Map results to class instances.
158
+ * Uses Object.create (NOT new) - constructor is NOT called.
159
+ */
160
+ as<T>(Class: new (...args: any[]) => T): Statement<T, ParamsType> {
161
+ const wrapped = Object.create(this);
162
+ const origAll = this.all.bind(this);
163
+ const origGet = this.get.bind(this);
164
+
165
+ wrapped.all = function (...params: any[]) {
166
+ const rows = origAll(...params);
167
+ return rows.map((row: any) => Object.assign(Object.create(Class.prototype), row));
168
+ };
169
+
170
+ wrapped.get = function (...params: any[]) {
171
+ const row = origGet(...params);
172
+ if (row === null) return null;
173
+ return Object.assign(Object.create(Class.prototype), row);
174
+ };
175
+
176
+ return wrapped;
177
+ }
178
+
179
+ [Symbol.dispose](): void {
180
+ this.finalize();
181
+ }
182
+
183
+ /** @internal */
184
+ private _checkFinalized(): void {
185
+ if (this._finalized) {
186
+ throw new Error('Statement has been finalized and cannot be used');
187
+ }
188
+ }
189
+
190
+ /** @internal */
191
+ private _normalizeParams(params: any[]): any {
192
+ if (params.length === 0) return undefined;
193
+ if (params.length === 1 && typeof params[0] === 'object' && params[0] !== null && !(params[0] instanceof Uint8Array)) {
194
+ return params[0]; // Named parameters
195
+ }
196
+ return params; // Positional parameters
197
+ }
198
+
199
+ /** @internal */
200
+ private _recordExecution(result: any): void {
201
+ this._executed = true;
202
+ this._columnTypes = Array.isArray(result?.columnTypes) ? result.columnTypes : [];
203
+ }
204
+ }
205
+
206
+ export default Statement;