@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.
- package/package.json +63 -0
- package/src/abort/AbortController.ts +23 -0
- package/src/abort/AbortSignal.ts +152 -0
- package/src/abort/index.ts +2 -0
- package/src/accessibility.ts +12 -0
- package/src/arraybuffer-detach.ts +109 -0
- package/src/base64/base64.ts +168 -0
- package/src/base64/index.ts +1 -0
- package/src/blob/Blob.ts +259 -0
- package/src/blob/File.ts +59 -0
- package/src/blob/FormData.ts +323 -0
- package/src/blob/index.ts +3 -0
- package/src/bootstrap.ts +1946 -0
- package/src/broadcast/BroadcastChannel.ts +280 -0
- package/src/broadcast/index.ts +5 -0
- package/src/cache/Cache.ts +349 -0
- package/src/cache/CacheStorage.ts +89 -0
- package/src/cache/index.ts +27 -0
- package/src/camera/index.ts +6202 -0
- package/src/camera/processor.worker.ts +194 -0
- package/src/camera/scene.ts +195 -0
- package/src/clipboard/Clipboard.ts +129 -0
- package/src/clipboard/ClipboardItem.ts +97 -0
- package/src/clipboard/index.ts +6 -0
- package/src/clone/index.ts +1 -0
- package/src/clone/structuredClone.ts +389 -0
- package/src/clone/transferableSymbols.ts +2 -0
- package/src/compression/CompressionStream.ts +146 -0
- package/src/compression/DecompressionStream.ts +342 -0
- package/src/compression/index.ts +4 -0
- package/src/console/Console.ts +341 -0
- package/src/console/index.ts +2 -0
- package/src/core/accessibility-state.ts +263 -0
- package/src/core/accessibility.ts +184 -0
- package/src/core/agent-state.ts +37 -0
- package/src/core/diagnostics-logs.ts +144 -0
- package/src/core/host-call-bridge.ts +16 -0
- package/src/core/i18n-helpers.ts +189 -0
- package/src/core/locale-state.ts +253 -0
- package/src/core/locale.ts +95 -0
- package/src/crypto/Crypto.ts +2743 -0
- package/src/crypto/index.ts +1 -0
- package/src/diagnostics/logs.ts +7 -0
- package/src/encoding/TextDecoder.ts +1181 -0
- package/src/encoding/TextDecoderStream.ts +58 -0
- package/src/encoding/TextEncoder.ts +180 -0
- package/src/encoding/TextEncoderStream.ts +39 -0
- package/src/encoding/index.ts +8 -0
- package/src/events/CloseEvent.ts +91 -0
- package/src/events/DOMException.ts +409 -0
- package/src/events/ErrorEvent.ts +39 -0
- package/src/events/Event.ts +151 -0
- package/src/events/EventTarget.ts +280 -0
- package/src/events/FocusEvent.ts +27 -0
- package/src/events/KeyboardEvent.ts +46 -0
- package/src/events/MessageEvent.ts +61 -0
- package/src/events/ProgressEvent.ts +33 -0
- package/src/events/PromiseRejectionEvent.ts +31 -0
- package/src/events/index.ts +52 -0
- package/src/eventsource/EventSource.ts +371 -0
- package/src/eventsource/index.ts +2 -0
- package/src/fetch/Headers.ts +642 -0
- package/src/fetch/Request.ts +760 -0
- package/src/fetch/Response.ts +543 -0
- package/src/fetch/body.ts +1256 -0
- package/src/fetch/cookie-jar.ts +566 -0
- package/src/fetch/demo.ts +207 -0
- package/src/fetch/errors.ts +101 -0
- package/src/fetch/fetch.ts +2610 -0
- package/src/fetch/index.ts +101 -0
- package/src/fetch/native-bridge.ts +65 -0
- package/src/fetch/types.ts +258 -0
- package/src/filereader/FileReader.ts +236 -0
- package/src/filereader/index.ts +1 -0
- package/src/fs/Dirent.ts +39 -0
- package/src/fs/ExactFile.ts +450 -0
- package/src/fs/Stats.ts +80 -0
- package/src/fs/index.ts +944 -0
- package/src/fs/promises.ts +386 -0
- package/src/fs/shared.ts +328 -0
- package/src/http-server/index.js +697 -0
- package/src/http-server/index.ts +27 -0
- package/src/identity.generated.ts +14 -0
- package/src/index.ts +283 -0
- package/src/indexeddb/IDBCursor.ts +188 -0
- package/src/indexeddb/IDBDatabase.ts +343 -0
- package/src/indexeddb/IDBFactory.ts +269 -0
- package/src/indexeddb/IDBIndex.ts +194 -0
- package/src/indexeddb/IDBKeyRange.ts +109 -0
- package/src/indexeddb/IDBObjectStore.ts +468 -0
- package/src/indexeddb/IDBRequest.ts +163 -0
- package/src/indexeddb/IDBTransaction.ts +207 -0
- package/src/indexeddb/index.ts +34 -0
- package/src/indexeddb/utils.ts +52 -0
- package/src/inspect/index.ts +1 -0
- package/src/inspect/inspect.ts +465 -0
- package/src/internal/detect.ts +104 -0
- package/src/locale.ts +10 -0
- package/src/location/index.ts +1059 -0
- package/src/locks/LockManager.ts +460 -0
- package/src/locks/index.ts +12 -0
- package/src/media/VideoFrame.ts +58 -0
- package/src/messaging/MessageChannel.ts +31 -0
- package/src/messaging/MessagePort.ts +180 -0
- package/src/messaging/index.ts +2 -0
- package/src/messaging.ts +247 -0
- package/src/native/NativeModules.ts +354 -0
- package/src/native/index.ts +1 -0
- package/src/navigator/Navigator.ts +351 -0
- package/src/navigator/index.ts +1 -0
- package/src/node/Buffer.ts +1786 -0
- package/src/node/index.ts +4 -0
- package/src/node/path.ts +495 -0
- package/src/node/process.ts +2528 -0
- package/src/performance/Performance.ts +532 -0
- package/src/performance/index.ts +21 -0
- package/src/polyfills/array.ts +236 -0
- package/src/polyfills/arraybuffer.ts +172 -0
- package/src/polyfills/groupby.ts +85 -0
- package/src/polyfills/index.ts +85 -0
- package/src/polyfills/intl.ts +1956 -0
- package/src/polyfills/iterator.ts +479 -0
- package/src/polyfills/promise.ts +37 -0
- package/src/polyfills/set.ts +245 -0
- package/src/polyfills/string.ts +85 -0
- package/src/polyfills/typedarray.ts +110 -0
- package/src/promise-rejection-tracking.ts +464 -0
- package/src/react-native/index.ts +388 -0
- package/src/runtime-entry.ts +55 -0
- package/src/scheduling/AnimationFrame.ts +105 -0
- package/src/scheduling/IdleCallback.ts +167 -0
- package/src/scheduling/index.ts +13 -0
- package/src/security/Capabilities.ts +1146 -0
- package/src/security/Permissions.ts +392 -0
- package/src/security/capability-bits.generated.ts +63 -0
- package/src/security/index.ts +16 -0
- package/src/sqlite/Database.ts +456 -0
- package/src/sqlite/Statement.ts +206 -0
- package/src/sqlite/constants.ts +79 -0
- package/src/sqlite/errors.ts +25 -0
- package/src/sqlite/index.ts +34 -0
- package/src/sqlite/module.js +438 -0
- package/src/storage/Storage.ts +291 -0
- package/src/storage/StorageManager.ts +91 -0
- package/src/storage/index.ts +3 -0
- package/src/stream-compat.ts +47 -0
- package/src/streams/ReadableStream.ts +4131 -0
- package/src/streams/TransformStream.ts +375 -0
- package/src/streams/WritableStream.ts +866 -0
- package/src/streams/index.ts +41 -0
- package/src/timers/Timers.ts +296 -0
- package/src/timers/index.ts +11 -0
- package/src/url/URL.ts +656 -0
- package/src/url/URLPattern.ts +850 -0
- package/src/url/URLSearchParams.ts +244 -0
- package/src/url/index.ts +9 -0
- package/src/websocket/WebSocket.ts +770 -0
- package/src/websocket/WebSocketError.ts +52 -0
- package/src/websocket/WebSocketStream.ts +628 -0
- package/src/websocket/index.ts +7 -0
- package/src/window/index.ts +872 -0
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IDBDatabase - IndexedDB Database
|
|
3
|
+
*
|
|
4
|
+
* Represents an open connection to a database. Backed by a SQLite database.
|
|
5
|
+
*
|
|
6
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { IDBTransaction, type IDBTransactionMode } from './IDBTransaction';
|
|
10
|
+
import { IDBObjectStore, type IDBObjectStoreParameters } from './IDBObjectStore';
|
|
11
|
+
import { IDBIndex } from './IDBIndex';
|
|
12
|
+
import { DOMException, makeDOMStringList, sanitizeName } from './utils';
|
|
13
|
+
|
|
14
|
+
export class IDBDatabase {
|
|
15
|
+
readonly name: string;
|
|
16
|
+
private _version: number;
|
|
17
|
+
private _objectStores: Map<string, { options: IDBObjectStoreParameters; indexes: Map<string, any> }> = new Map();
|
|
18
|
+
private _closed = false;
|
|
19
|
+
/** @internal - SQLite database wrapper */
|
|
20
|
+
_sqliteDb: any;
|
|
21
|
+
/** @internal - The active versionchange transaction during upgradeneeded, if any */
|
|
22
|
+
_upgradeTransaction: IDBTransaction | null = null;
|
|
23
|
+
/** @internal - EventTarget listeners keyed by event type */
|
|
24
|
+
private _listeners: Record<string, Function[]> = {};
|
|
25
|
+
|
|
26
|
+
onclose: ((event: any) => void) | null = null;
|
|
27
|
+
onversionchange: ((event: any) => void) | null = null;
|
|
28
|
+
onerror: ((event: any) => void) | null = null;
|
|
29
|
+
onabort: ((event: any) => void) | null = null;
|
|
30
|
+
|
|
31
|
+
constructor(name: string, version: number, sqliteDb: any) {
|
|
32
|
+
this.name = name;
|
|
33
|
+
this._version = version;
|
|
34
|
+
this._sqliteDb = sqliteDb;
|
|
35
|
+
|
|
36
|
+
// Initialize meta tables
|
|
37
|
+
this._initMeta();
|
|
38
|
+
// Load existing object store definitions
|
|
39
|
+
this._loadStoreDefinitions();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
get version(): number {
|
|
43
|
+
return this._version;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
get objectStoreNames(): any {
|
|
47
|
+
return makeDOMStringList(Array.from(this._objectStores.keys()).sort());
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
addEventListener(type: string, fn: Function): void {
|
|
51
|
+
if (!this._listeners[type]) this._listeners[type] = [];
|
|
52
|
+
this._listeners[type].push(fn);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
removeEventListener(type: string, fn: Function): void {
|
|
56
|
+
const list = this._listeners[type];
|
|
57
|
+
if (list) this._listeners[type] = list.filter(f => f !== fn);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Create a new object store. Only valid during upgradeneeded.
|
|
62
|
+
*/
|
|
63
|
+
createObjectStore(name: string, options?: IDBObjectStoreParameters): IDBObjectStore {
|
|
64
|
+
// Guard: only allowed during an active versionchange transaction
|
|
65
|
+
if (this._upgradeTransaction && this._upgradeTransaction._state !== 'active') {
|
|
66
|
+
throw new DOMException(
|
|
67
|
+
'Can only be called during upgradeneeded',
|
|
68
|
+
'InvalidStateError',
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (this._objectStores.has(name)) {
|
|
73
|
+
throw new DOMException(
|
|
74
|
+
`Object store "${name}" already exists`,
|
|
75
|
+
'ConstraintError',
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const opts: IDBObjectStoreParameters = {
|
|
80
|
+
keyPath: options?.keyPath ?? null,
|
|
81
|
+
autoIncrement: options?.autoIncrement ?? false,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
this._objectStores.set(name, { options: opts, indexes: new Map() });
|
|
85
|
+
|
|
86
|
+
// Persist store definition to meta table
|
|
87
|
+
this._saveStoreMeta(name, opts);
|
|
88
|
+
|
|
89
|
+
// Create the store object with a versionchange transaction
|
|
90
|
+
const txn = this._upgradeTransaction ?? new IDBTransaction(this, [name], 'versionchange');
|
|
91
|
+
const store = new IDBObjectStore(name, opts, txn, this);
|
|
92
|
+
return store;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Delete an object store. Only valid during upgradeneeded.
|
|
97
|
+
*/
|
|
98
|
+
deleteObjectStore(name: string): void {
|
|
99
|
+
// Guard: only allowed during an active versionchange transaction
|
|
100
|
+
if (this._upgradeTransaction && this._upgradeTransaction._state !== 'active') {
|
|
101
|
+
throw new DOMException(
|
|
102
|
+
'Can only be called during upgradeneeded',
|
|
103
|
+
'InvalidStateError',
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (!this._objectStores.has(name)) {
|
|
108
|
+
throw new DOMException(
|
|
109
|
+
`Object store "${name}" does not exist`,
|
|
110
|
+
'NotFoundError',
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Drop the SQLite table
|
|
115
|
+
const tableName = `idb_store_${sanitizeName(name)}`;
|
|
116
|
+
this._exec(`DROP TABLE IF EXISTS "${tableName}"`);
|
|
117
|
+
|
|
118
|
+
// Remove from meta
|
|
119
|
+
this._exec(
|
|
120
|
+
`DELETE FROM _idb_stores WHERE store_name = ?`,
|
|
121
|
+
[name]
|
|
122
|
+
);
|
|
123
|
+
this._exec(
|
|
124
|
+
`DELETE FROM _idb_indexes WHERE store_name = ?`,
|
|
125
|
+
[name]
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
this._objectStores.delete(name);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Create a transaction for the given object stores.
|
|
133
|
+
*/
|
|
134
|
+
transaction(storeNames: string | string[], mode?: IDBTransactionMode): IDBTransaction {
|
|
135
|
+
this._checkClosed();
|
|
136
|
+
const names = Array.isArray(storeNames) ? storeNames : [storeNames];
|
|
137
|
+
|
|
138
|
+
// Verify all stores exist
|
|
139
|
+
for (const name of names) {
|
|
140
|
+
if (!this._objectStores.has(name)) {
|
|
141
|
+
throw new DOMException(
|
|
142
|
+
`Object store "${name}" does not exist`,
|
|
143
|
+
'NotFoundError',
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const txn = new IDBTransaction(this, names, mode ?? 'readonly');
|
|
149
|
+
|
|
150
|
+
// Auto-commit after microtasks settle
|
|
151
|
+
queueMicrotask(() => {
|
|
152
|
+
queueMicrotask(() => {
|
|
153
|
+
txn._autoCommit();
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
return txn;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Close the database connection.
|
|
162
|
+
*/
|
|
163
|
+
close(): void {
|
|
164
|
+
if (this._closed) return;
|
|
165
|
+
this._closed = true;
|
|
166
|
+
if (this._sqliteDb && this._sqliteDb.close) {
|
|
167
|
+
this._sqliteDb.close();
|
|
168
|
+
}
|
|
169
|
+
if (this.onclose) {
|
|
170
|
+
const event = { type: 'close', target: this };
|
|
171
|
+
this.onclose(event);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ================================================================
|
|
176
|
+
// Internal SQLite operations
|
|
177
|
+
// ================================================================
|
|
178
|
+
|
|
179
|
+
/** @internal */
|
|
180
|
+
_initMeta(): void {
|
|
181
|
+
this._exec(`
|
|
182
|
+
CREATE TABLE IF NOT EXISTS _idb_meta (
|
|
183
|
+
key TEXT PRIMARY KEY,
|
|
184
|
+
value TEXT
|
|
185
|
+
)
|
|
186
|
+
`);
|
|
187
|
+
this._exec(`
|
|
188
|
+
CREATE TABLE IF NOT EXISTS _idb_stores (
|
|
189
|
+
store_name TEXT PRIMARY KEY,
|
|
190
|
+
key_path TEXT,
|
|
191
|
+
auto_increment INTEGER DEFAULT 0
|
|
192
|
+
)
|
|
193
|
+
`);
|
|
194
|
+
this._exec(`
|
|
195
|
+
CREATE TABLE IF NOT EXISTS _idb_indexes (
|
|
196
|
+
store_name TEXT,
|
|
197
|
+
index_name TEXT,
|
|
198
|
+
key_path TEXT,
|
|
199
|
+
unique_flag INTEGER DEFAULT 0,
|
|
200
|
+
multi_entry INTEGER DEFAULT 0,
|
|
201
|
+
PRIMARY KEY (store_name, index_name)
|
|
202
|
+
)
|
|
203
|
+
`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** @internal */
|
|
207
|
+
_loadStoreDefinitions(): void {
|
|
208
|
+
const stores = this._all('SELECT store_name, key_path, auto_increment FROM _idb_stores');
|
|
209
|
+
for (const row of stores) {
|
|
210
|
+
const keyPath = row.key_path ? JSON.parse(row.key_path) : null;
|
|
211
|
+
const opts: IDBObjectStoreParameters = {
|
|
212
|
+
keyPath,
|
|
213
|
+
autoIncrement: !!row.auto_increment,
|
|
214
|
+
};
|
|
215
|
+
const indexes = new Map<string, any>();
|
|
216
|
+
|
|
217
|
+
// Load indexes for this store
|
|
218
|
+
const idxRows = this._all(
|
|
219
|
+
'SELECT index_name, key_path, unique_flag, multi_entry FROM _idb_indexes WHERE store_name = ?',
|
|
220
|
+
[row.store_name]
|
|
221
|
+
);
|
|
222
|
+
for (const idx of idxRows) {
|
|
223
|
+
indexes.set(idx.index_name, {
|
|
224
|
+
keyPath: JSON.parse(idx.key_path),
|
|
225
|
+
unique: !!idx.unique_flag,
|
|
226
|
+
multiEntry: !!idx.multi_entry,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
this._objectStores.set(row.store_name, { options: opts, indexes });
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** @internal */
|
|
235
|
+
_saveStoreMeta(name: string, options: IDBObjectStoreParameters): void {
|
|
236
|
+
this._exec(
|
|
237
|
+
`INSERT OR REPLACE INTO _idb_stores (store_name, key_path, auto_increment) VALUES (?, ?, ?)`,
|
|
238
|
+
[name, JSON.stringify(options.keyPath), options.autoIncrement ? 1 : 0]
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** @internal */
|
|
243
|
+
_saveIndexMeta(storeName: string, indexName: string, keyPath: string | string[], options: any): void {
|
|
244
|
+
this._exec(
|
|
245
|
+
`INSERT OR REPLACE INTO _idb_indexes (store_name, index_name, key_path, unique_flag, multi_entry) VALUES (?, ?, ?, ?, ?)`,
|
|
246
|
+
[storeName, indexName, JSON.stringify(keyPath), options.unique ? 1 : 0, options.multiEntry ? 1 : 0]
|
|
247
|
+
);
|
|
248
|
+
// Update in-memory definition
|
|
249
|
+
const storeInfo = this._objectStores.get(storeName);
|
|
250
|
+
if (storeInfo) {
|
|
251
|
+
storeInfo.indexes.set(indexName, {
|
|
252
|
+
keyPath,
|
|
253
|
+
unique: options.unique ?? false,
|
|
254
|
+
multiEntry: options.multiEntry ?? false,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** @internal */
|
|
260
|
+
_deleteIndexMeta(storeName: string, indexName: string): void {
|
|
261
|
+
this._exec(
|
|
262
|
+
`DELETE FROM _idb_indexes WHERE store_name = ? AND index_name = ?`,
|
|
263
|
+
[storeName, indexName]
|
|
264
|
+
);
|
|
265
|
+
const storeInfo = this._objectStores.get(storeName);
|
|
266
|
+
if (storeInfo) {
|
|
267
|
+
storeInfo.indexes.delete(indexName);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** @internal - Get an object store instance for a transaction */
|
|
272
|
+
_getObjectStore(name: string, transaction: IDBTransaction): IDBObjectStore | null {
|
|
273
|
+
const storeInfo = this._objectStores.get(name);
|
|
274
|
+
if (!storeInfo) return null;
|
|
275
|
+
|
|
276
|
+
const store = new IDBObjectStore(name, storeInfo.options, transaction, this);
|
|
277
|
+
|
|
278
|
+
// Restore indexes
|
|
279
|
+
for (const [indexName, indexDef] of storeInfo.indexes) {
|
|
280
|
+
const idx = new IDBIndex(indexName, indexDef.keyPath, indexDef, store);
|
|
281
|
+
store._indexes.set(indexName, idx);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return store;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/** @internal */
|
|
288
|
+
_checkClosed(): void {
|
|
289
|
+
if (this._closed) {
|
|
290
|
+
throw new DOMException(
|
|
291
|
+
'Database connection is closed',
|
|
292
|
+
'InvalidStateError',
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// ================================================================
|
|
298
|
+
// SQLite execution wrappers
|
|
299
|
+
// ================================================================
|
|
300
|
+
|
|
301
|
+
/** @internal - Execute a SQL statement */
|
|
302
|
+
_exec(sql: string, params?: any[]): void {
|
|
303
|
+
if (this._sqliteDb.exec) {
|
|
304
|
+
if (params && params.length > 0) {
|
|
305
|
+
this._sqliteDb.exec(sql, ...params);
|
|
306
|
+
} else {
|
|
307
|
+
this._sqliteDb.exec(sql);
|
|
308
|
+
}
|
|
309
|
+
} else if (this._sqliteDb.run) {
|
|
310
|
+
if (params && params.length > 0) {
|
|
311
|
+
this._sqliteDb.run(sql, ...params);
|
|
312
|
+
} else {
|
|
313
|
+
this._sqliteDb.run(sql);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/** @internal - Query a single row */
|
|
319
|
+
_get(sql: string, params?: any[]): any {
|
|
320
|
+
if (this._sqliteDb.query) {
|
|
321
|
+
const stmt = this._sqliteDb.query(sql);
|
|
322
|
+
return params ? stmt.get(...params) : stmt.get();
|
|
323
|
+
}
|
|
324
|
+
if (this._sqliteDb.prepare) {
|
|
325
|
+
const stmt = this._sqliteDb.prepare(sql);
|
|
326
|
+
return params ? stmt.get(...params) : stmt.get();
|
|
327
|
+
}
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/** @internal - Query all rows */
|
|
332
|
+
_all(sql: string, params?: any[]): any[] {
|
|
333
|
+
if (this._sqliteDb.query) {
|
|
334
|
+
const stmt = this._sqliteDb.query(sql);
|
|
335
|
+
return params ? stmt.all(...params) : stmt.all();
|
|
336
|
+
}
|
|
337
|
+
if (this._sqliteDb.prepare) {
|
|
338
|
+
const stmt = this._sqliteDb.prepare(sql);
|
|
339
|
+
return params ? stmt.all(...params) : stmt.all();
|
|
340
|
+
}
|
|
341
|
+
return [];
|
|
342
|
+
}
|
|
343
|
+
}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* IDBFactory - IndexedDB Factory
|
|
4
|
+
*
|
|
5
|
+
* The main entry point for IndexedDB. Provides open() and deleteDatabase().
|
|
6
|
+
* Backed by SQLite via the exact:sqlite module.
|
|
7
|
+
*
|
|
8
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/IDBFactory
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { IDBDatabase } from './IDBDatabase';
|
|
12
|
+
import { IDBOpenDBRequest } from './IDBRequest';
|
|
13
|
+
import { IDBTransaction } from './IDBTransaction';
|
|
14
|
+
import { compareKeys } from './IDBKeyRange';
|
|
15
|
+
import { DOMException } from './utils';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Interface for creating SQLite database instances.
|
|
19
|
+
* This allows the factory to work with either the real Database class
|
|
20
|
+
* or a mock for testing.
|
|
21
|
+
*/
|
|
22
|
+
export interface SQLiteDatabaseProvider {
|
|
23
|
+
create(name: string): any;
|
|
24
|
+
delete?(name: string): void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Default provider that uses exact:sqlite Database.
|
|
29
|
+
*/
|
|
30
|
+
class DefaultSQLiteProvider implements SQLiteDatabaseProvider {
|
|
31
|
+
create(name: string): any {
|
|
32
|
+
const g = globalThis as any;
|
|
33
|
+
if (typeof g.__exactSqliteOpen !== 'function' && typeof g.__exactEnsureSqlite === 'function') {
|
|
34
|
+
g.__exactEnsureSqlite();
|
|
35
|
+
}
|
|
36
|
+
if (g.__exactSqliteOpen) {
|
|
37
|
+
// Import is done lazily to avoid circular dependencies
|
|
38
|
+
const { Database } = require('../sqlite');
|
|
39
|
+
return new Database(indexedDbPath(name));
|
|
40
|
+
}
|
|
41
|
+
throw new Error(
|
|
42
|
+
'IndexedDB requires SQLite support. The exact:sqlite native bridge is not available.'
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
delete(name: string): void {
|
|
47
|
+
const g = globalThis as any;
|
|
48
|
+
if (typeof g.__exactUnlink !== 'function' && typeof g.__exactEnsureFs === 'function') {
|
|
49
|
+
g.__exactEnsureFs();
|
|
50
|
+
}
|
|
51
|
+
if (typeof g.__exactUnlink !== 'function') return;
|
|
52
|
+
for (const path of [indexedDbPath(name), `${indexedDbPath(name)}-wal`, `${indexedDbPath(name)}-shm`]) {
|
|
53
|
+
try {
|
|
54
|
+
g.__exactUnlink(path);
|
|
55
|
+
} catch (_) {}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function trimTrailingSlash(path: string): string {
|
|
61
|
+
return path.replace(/\/+$/, '') || '/';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function indexedDbRoot(): string {
|
|
65
|
+
const g = globalThis as any;
|
|
66
|
+
const androidFilesDir = g.__exactAndroidStoragePaths?.filesDir;
|
|
67
|
+
if (typeof androidFilesDir === 'string' && androidFilesDir.length > 0) {
|
|
68
|
+
return trimTrailingSlash(androidFilesDir);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const env = g.process?.env;
|
|
72
|
+
const envFilesDir = env?.EXACT_ANDROID_FILES_DIR ?? env?.HOME;
|
|
73
|
+
if (typeof envFilesDir === 'string' && envFilesDir.length > 0) {
|
|
74
|
+
return trimTrailingSlash(envFilesDir);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return '/tmp';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function ensureIndexedDbDirectory(directory: string): void {
|
|
81
|
+
const g = globalThis as any;
|
|
82
|
+
try {
|
|
83
|
+
if (typeof g.__exactMkdir !== 'function' && typeof g.__exactEnsureFs === 'function') {
|
|
84
|
+
g.__exactEnsureFs();
|
|
85
|
+
}
|
|
86
|
+
if (typeof g.__exactMkdir === 'function') {
|
|
87
|
+
g.__exactMkdir(directory, true);
|
|
88
|
+
}
|
|
89
|
+
} catch (_) {}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function indexedDbPath(name: string): string {
|
|
93
|
+
// @ref LLP 0008#android-backend-matrix — IndexedDB persists under Android app filesDir.
|
|
94
|
+
const directory = `${indexedDbRoot()}/.ibex/indexeddb`;
|
|
95
|
+
ensureIndexedDbDirectory(directory);
|
|
96
|
+
const encodedName = encodeURIComponent(String(name)) || 'default';
|
|
97
|
+
return `${directory}/${encodedName}.sqlite`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function readStoredVersion(sqliteDb: any): number {
|
|
101
|
+
try {
|
|
102
|
+
const row = sqliteDb.query('SELECT value FROM _idb_meta WHERE key = ?').get('version');
|
|
103
|
+
const version = Number(row?.value);
|
|
104
|
+
return Number.isFinite(version) && version > 0 ? Math.floor(version) : 0;
|
|
105
|
+
} catch (_) {
|
|
106
|
+
return 0;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function writeStoredVersion(sqliteDb: any, version: number): void {
|
|
111
|
+
try {
|
|
112
|
+
sqliteDb.run(`
|
|
113
|
+
CREATE TABLE IF NOT EXISTS _idb_meta (
|
|
114
|
+
key TEXT PRIMARY KEY,
|
|
115
|
+
value TEXT
|
|
116
|
+
)
|
|
117
|
+
`);
|
|
118
|
+
sqliteDb.run(
|
|
119
|
+
'INSERT INTO _idb_meta(key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value',
|
|
120
|
+
'version',
|
|
121
|
+
String(version),
|
|
122
|
+
);
|
|
123
|
+
} catch (_) {}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export class IDBFactory {
|
|
127
|
+
/** @internal - Map of database name -> { version, sqliteDb } */
|
|
128
|
+
private _databases: Map<string, { version: number; sqliteDb: any }> = new Map();
|
|
129
|
+
/** @internal - SQLite provider for creating database instances */
|
|
130
|
+
private _sqliteProvider: SQLiteDatabaseProvider;
|
|
131
|
+
|
|
132
|
+
constructor(provider?: SQLiteDatabaseProvider) {
|
|
133
|
+
this._sqliteProvider = provider ?? new DefaultSQLiteProvider();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Open (or create) a database with the given name and optional version.
|
|
138
|
+
*
|
|
139
|
+
* Returns an IDBOpenDBRequest. Set onupgradeneeded and onsuccess handlers
|
|
140
|
+
* on the request before the event loop yields.
|
|
141
|
+
*/
|
|
142
|
+
open(name: string, version?: number): IDBOpenDBRequest<IDBDatabase> {
|
|
143
|
+
const request = new IDBOpenDBRequest<IDBDatabase>();
|
|
144
|
+
|
|
145
|
+
if (
|
|
146
|
+
version !== undefined &&
|
|
147
|
+
(version <= 0 || !Number.isFinite(version) || Math.floor(version) !== version)
|
|
148
|
+
) {
|
|
149
|
+
queueMicrotask(() => {
|
|
150
|
+
request._reject(new TypeError(
|
|
151
|
+
`The version provided (${version}) is not a valid positive integer.`
|
|
152
|
+
));
|
|
153
|
+
});
|
|
154
|
+
return request;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Process in a microtask to allow handlers to be attached
|
|
158
|
+
queueMicrotask(() => {
|
|
159
|
+
try {
|
|
160
|
+
const existing = this._databases.get(name);
|
|
161
|
+
|
|
162
|
+
let sqliteDb: any;
|
|
163
|
+
let oldVersion = 0;
|
|
164
|
+
if (existing) {
|
|
165
|
+
sqliteDb = existing.sqliteDb;
|
|
166
|
+
oldVersion = existing.version;
|
|
167
|
+
} else {
|
|
168
|
+
sqliteDb = this._sqliteProvider.create(name);
|
|
169
|
+
oldVersion = readStoredVersion(sqliteDb);
|
|
170
|
+
}
|
|
171
|
+
const resolvedVersion = version ?? (oldVersion > 0 ? oldVersion : 1);
|
|
172
|
+
if (oldVersion > 0 && resolvedVersion < oldVersion) {
|
|
173
|
+
throw new DOMException(
|
|
174
|
+
`The requested version (${resolvedVersion}) is less than the existing version (${oldVersion}).`,
|
|
175
|
+
'VersionError',
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const db = new IDBDatabase(name, resolvedVersion, sqliteDb);
|
|
180
|
+
|
|
181
|
+
// Set the result on the request before upgradeneeded fires,
|
|
182
|
+
// because onupgradeneeded handlers access event.target.result
|
|
183
|
+
// to get a reference to the database.
|
|
184
|
+
request._setResult(db);
|
|
185
|
+
|
|
186
|
+
// Version upgrade needed?
|
|
187
|
+
if (resolvedVersion > oldVersion) {
|
|
188
|
+
// Create an upgrade transaction so createObjectStore/deleteObjectStore
|
|
189
|
+
// can detect that they are being called during upgradeneeded.
|
|
190
|
+
const storeNames = Array.from((db as any)._objectStores?.keys?.() ?? []);
|
|
191
|
+
const upgradeTxn = new IDBTransaction(db, storeNames, 'versionchange');
|
|
192
|
+
db._upgradeTransaction = upgradeTxn;
|
|
193
|
+
|
|
194
|
+
// Fire onupgradeneeded synchronously
|
|
195
|
+
if (request.onupgradeneeded) {
|
|
196
|
+
const event = {
|
|
197
|
+
type: 'upgradeneeded',
|
|
198
|
+
target: request,
|
|
199
|
+
oldVersion,
|
|
200
|
+
newVersion: resolvedVersion,
|
|
201
|
+
};
|
|
202
|
+
// During upgradeneeded, the database is in a special state
|
|
203
|
+
// where createObjectStore/deleteObjectStore can be called
|
|
204
|
+
request.onupgradeneeded(event);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Clear the upgrade transaction after upgradeneeded completes
|
|
208
|
+
db._upgradeTransaction = null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Store the database reference
|
|
212
|
+
this._databases.set(name, { version: resolvedVersion, sqliteDb });
|
|
213
|
+
writeStoredVersion(sqliteDb, resolvedVersion);
|
|
214
|
+
|
|
215
|
+
// Fire onsuccess
|
|
216
|
+
request._resolveSync(db);
|
|
217
|
+
} catch (e: any) {
|
|
218
|
+
request._reject(e);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
return request;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Delete a database.
|
|
227
|
+
*/
|
|
228
|
+
deleteDatabase(name: string): IDBOpenDBRequest<undefined> {
|
|
229
|
+
const request = new IDBOpenDBRequest<undefined>();
|
|
230
|
+
|
|
231
|
+
queueMicrotask(() => {
|
|
232
|
+
try {
|
|
233
|
+
const existing = this._databases.get(name);
|
|
234
|
+
if (existing) {
|
|
235
|
+
// Close the SQLite database
|
|
236
|
+
if (existing.sqliteDb && existing.sqliteDb.close) {
|
|
237
|
+
existing.sqliteDb.close();
|
|
238
|
+
}
|
|
239
|
+
this._databases.delete(name);
|
|
240
|
+
}
|
|
241
|
+
this._sqliteProvider.delete?.(name);
|
|
242
|
+
request._resolveSync(undefined);
|
|
243
|
+
} catch (e: any) {
|
|
244
|
+
request._reject(e);
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
return request;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Compare two keys using IndexedDB key comparison rules.
|
|
253
|
+
*/
|
|
254
|
+
cmp(first: any, second: any): number {
|
|
255
|
+
const result = compareKeys(first, second);
|
|
256
|
+
return result < 0 ? -1 : result > 0 ? 1 : 0;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* List all databases. Returns a promise.
|
|
261
|
+
*/
|
|
262
|
+
databases(): Promise<Array<{ name: string; version: number }>> {
|
|
263
|
+
const result = Array.from(this._databases.entries()).map(([name, info]) => ({
|
|
264
|
+
name,
|
|
265
|
+
version: info.version,
|
|
266
|
+
}));
|
|
267
|
+
return Promise.resolve(result);
|
|
268
|
+
}
|
|
269
|
+
}
|