indexeddbshim 17.0.0 → 17.2.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 +62 -0
- package/badges/licenses-badge-dev.svg +1 -1
- package/badges/licenses-badge.svg +1 -1
- package/dist/CFG.d.ts +1 -0
- package/dist/CFG.d.ts.map +1 -1
- package/dist/DOMException.d.ts.map +1 -1
- package/dist/IDBCursor.d.ts +25 -18
- package/dist/IDBCursor.d.ts.map +1 -1
- package/dist/IDBFactory.d.ts +19 -19
- package/dist/IDBFactory.d.ts.map +1 -1
- package/dist/IDBIndex.d.ts.map +1 -1
- package/dist/IDBKeyRange.d.ts +5 -5
- package/dist/IDBKeyRange.d.ts.map +1 -1
- package/dist/IDBTransaction.d.ts +24 -3
- package/dist/IDBTransaction.d.ts.map +1 -1
- package/dist/Key.d.ts +41 -41
- package/dist/Key.d.ts.map +1 -1
- package/dist/indexeddbshim-Key.js +88 -69
- package/dist/indexeddbshim-Key.js.map +1 -1
- package/dist/indexeddbshim-Key.min.js +2 -2
- package/dist/indexeddbshim-Key.min.js.map +1 -1
- package/dist/indexeddbshim-UnicodeIdentifiers-node.cjs +1682 -1056
- package/dist/indexeddbshim-UnicodeIdentifiers-node.cjs.map +1 -1
- package/dist/indexeddbshim-UnicodeIdentifiers.js +886 -663
- package/dist/indexeddbshim-UnicodeIdentifiers.js.map +1 -1
- package/dist/indexeddbshim-UnicodeIdentifiers.min.js +4 -4
- package/dist/indexeddbshim-UnicodeIdentifiers.min.js.map +1 -1
- package/dist/indexeddbshim-node.cjs +1682 -1056
- package/dist/indexeddbshim-node.cjs.map +1 -1
- package/dist/indexeddbshim-noninvasive.js +886 -663
- package/dist/indexeddbshim-noninvasive.js.map +1 -1
- package/dist/indexeddbshim-noninvasive.min.js +4 -4
- package/dist/indexeddbshim-noninvasive.min.js.map +1 -1
- package/dist/indexeddbshim.js +890 -660
- package/dist/indexeddbshim.js.map +1 -1
- package/dist/indexeddbshim.min.js +4 -4
- package/dist/indexeddbshim.min.js.map +1 -1
- package/dist/node-UnicodeIdentifiers.d.ts.map +1 -1
- package/dist/node.d.ts.map +1 -1
- package/dist/nodeSQLiteDatabase.d.ts +65 -0
- package/dist/nodeSQLiteDatabase.d.ts.map +1 -0
- package/dist/nodeWebSQL.d.ts.map +1 -1
- package/dist/util.d.ts +5 -0
- package/dist/util.d.ts.map +1 -1
- package/package.json +22 -18
- package/src/CFG.js +3 -0
- package/src/DOMException.js +10 -10
- package/src/IDBCursor.js +152 -102
- package/src/IDBFactory.js +30 -26
- package/src/IDBIndex.js +1 -2
- package/src/IDBKeyRange.js +5 -5
- package/src/IDBTransaction.js +35 -5
- package/src/Key.js +63 -55
- package/src/node-UnicodeIdentifiers.js +5 -1
- package/src/node.js +5 -1
- package/src/nodeSQLiteDatabase.js +174 -0
- package/src/nodeWebSQL.js +1 -3
- package/src/util.js +62 -3
package/src/IDBFactory.js
CHANGED
|
@@ -388,7 +388,7 @@ function IDBFactory () {
|
|
|
388
388
|
* __connections: {
|
|
389
389
|
* [key: string]: import('./IDBDatabase.js').IDBDatabaseFull[]
|
|
390
390
|
* }
|
|
391
|
-
* }} IDBFactoryFull
|
|
391
|
+
* }} IDBFactoryFull
|
|
392
392
|
*/
|
|
393
393
|
|
|
394
394
|
const IDBFactoryAlias = IDBFactory;
|
|
@@ -593,7 +593,7 @@ IDBFactory.prototype.open = function (name /* , version */) {
|
|
|
593
593
|
req.__result
|
|
594
594
|
).__versionTransaction = null;
|
|
595
595
|
sysdbFinishedCb(systx, false, function () {
|
|
596
|
-
req.transaction.
|
|
596
|
+
req.transaction.__callTransFinishedCb(false, function () {
|
|
597
597
|
ev.complete();
|
|
598
598
|
req.__transaction = null;
|
|
599
599
|
});
|
|
@@ -716,6 +716,13 @@ IDBFactory.prototype.open = function (name /* , version */) {
|
|
|
716
716
|
function openDB (oldVersion) {
|
|
717
717
|
/** @type {DatabaseFull} */
|
|
718
718
|
let db;
|
|
719
|
+
if (version === undefined) {
|
|
720
|
+
// Resolve before use as a cache key below, or a `open(name)` call
|
|
721
|
+
// (no explicit version) would cache/look up under `undefined`
|
|
722
|
+
// instead of the actual version, causing a second, separate
|
|
723
|
+
// connection to be opened for the same database on reopen.
|
|
724
|
+
version = oldVersion || 1;
|
|
725
|
+
}
|
|
719
726
|
if ((useMemoryDatabase || useDatabaseCache) && Object.hasOwn(websqlDBCache, name) && Object.hasOwn(websqlDBCache[name], version)) {
|
|
720
727
|
db = websqlDBCache[name][version];
|
|
721
728
|
} else {
|
|
@@ -733,9 +740,6 @@ IDBFactory.prototype.open = function (name /* , version */) {
|
|
|
733
740
|
}
|
|
734
741
|
}
|
|
735
742
|
|
|
736
|
-
if (version === undefined) {
|
|
737
|
-
version = oldVersion || 1;
|
|
738
|
-
}
|
|
739
743
|
if (oldVersion > version) {
|
|
740
744
|
const err = createDOMException('VersionError', 'An attempt was made to open a database using a lower version than the existing version.', version);
|
|
741
745
|
if (useDatabaseCache) {
|
|
@@ -905,7 +909,7 @@ IDBFactory.prototype.deleteDatabase = function (name) {
|
|
|
905
909
|
({version} = data.rows.item(0));
|
|
906
910
|
|
|
907
911
|
const openConnections = me.__connections[name] || [];
|
|
908
|
-
triggerAnyVersionChangeAndBlockedEvents(openConnections, req, version, null).then(function () {
|
|
912
|
+
triggerAnyVersionChangeAndBlockedEvents(openConnections, req, version, null).then(function () {
|
|
909
913
|
// Since we need two databases which can't be in a single transaction, we
|
|
910
914
|
// do this deleting from `dbVersions` first since the `__sys__` deleting
|
|
911
915
|
// only impacts file memory whereas this one is critical for avoiding it
|
|
@@ -934,7 +938,7 @@ IDBFactory.prototype.deleteDatabase = function (name) {
|
|
|
934
938
|
});
|
|
935
939
|
return undefined;
|
|
936
940
|
// @ts-expect-error It's ok
|
|
937
|
-
}
|
|
941
|
+
}).catch(dbError);
|
|
938
942
|
return undefined;
|
|
939
943
|
}, dbError);
|
|
940
944
|
});
|
|
@@ -966,14 +970,14 @@ IDBFactory.prototype.cmp = function (key1, key2) {
|
|
|
966
970
|
};
|
|
967
971
|
|
|
968
972
|
/**
|
|
969
|
-
* May return outdated information if a database has since been deleted.
|
|
970
|
-
* @see https://github.com/w3c/IndexedDB/pull/240/files
|
|
971
|
-
* @this {IDBFactoryFull}
|
|
972
|
-
* @returns {Promise<{
|
|
973
|
-
* name: string,
|
|
974
|
-
* version: Integer
|
|
975
|
-
* }[]>}
|
|
976
|
-
*/
|
|
973
|
+
* May return outdated information if a database has since been deleted.
|
|
974
|
+
* @see https://github.com/w3c/IndexedDB/pull/240/files
|
|
975
|
+
* @this {IDBFactoryFull}
|
|
976
|
+
* @returns {Promise<{
|
|
977
|
+
* name: string,
|
|
978
|
+
* version: Integer
|
|
979
|
+
* }[]>}
|
|
980
|
+
*/
|
|
977
981
|
IDBFactory.prototype.databases = function () {
|
|
978
982
|
const me = this;
|
|
979
983
|
let calledDbCreateError = false;
|
|
@@ -1018,17 +1022,17 @@ IDBFactory.prototype.databases = function () {
|
|
|
1018
1022
|
};
|
|
1019
1023
|
|
|
1020
1024
|
/**
|
|
1021
|
-
* @todo forceClose: Test
|
|
1022
|
-
* This is provided to facilitate unit-testing of the
|
|
1023
|
-
* closing of a database connection with a forced flag:
|
|
1024
|
-
* <https://w3c.github.io/IndexedDB/#steps-for-closing-a-database-connection>
|
|
1025
|
-
* @param {string} dbName
|
|
1026
|
-
* @param {Integer} connIdx
|
|
1027
|
-
* @param {string} msg
|
|
1028
|
-
* @throws {TypeError}
|
|
1029
|
-
* @this {IDBFactoryFull}
|
|
1030
|
-
* @returns {void}
|
|
1031
|
-
*/
|
|
1025
|
+
* @todo forceClose: Test
|
|
1026
|
+
* This is provided to facilitate unit-testing of the
|
|
1027
|
+
* closing of a database connection with a forced flag:
|
|
1028
|
+
* <https://w3c.github.io/IndexedDB/#steps-for-closing-a-database-connection>
|
|
1029
|
+
* @param {string} dbName
|
|
1030
|
+
* @param {Integer} connIdx
|
|
1031
|
+
* @param {string} msg
|
|
1032
|
+
* @throws {TypeError}
|
|
1033
|
+
* @this {IDBFactoryFull}
|
|
1034
|
+
* @returns {void}
|
|
1035
|
+
*/
|
|
1032
1036
|
IDBFactory.prototype.__forceClose = function (dbName, connIdx, msg) {
|
|
1033
1037
|
const me = this;
|
|
1034
1038
|
/**
|
package/src/IDBIndex.js
CHANGED
|
@@ -733,8 +733,7 @@ IDBIndex.prototype.__renameIndex = function (store, oldName, newName, colInfoToP
|
|
|
733
733
|
);
|
|
734
734
|
})
|
|
735
735
|
);
|
|
736
|
-
SyncPromise.all(indexCreations).then(
|
|
737
|
-
finish,
|
|
736
|
+
SyncPromise.all(indexCreations).then(finish).catch(
|
|
738
737
|
/** @type {(reason: any) => PromiseLike<never>} */
|
|
739
738
|
(error)
|
|
740
739
|
).catch((err) => {
|
package/src/IDBKeyRange.js
CHANGED
|
@@ -6,11 +6,11 @@ const readonlyProperties = /** @type {const} */ (['lower', 'upper', 'lowerOpen',
|
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* @typedef {globalThis.IDBKeyRange & {
|
|
9
|
-
* __lowerCached: string|null|false,
|
|
10
|
-
* __upperCached: string|null|false,
|
|
11
|
-
* __lowerOpen: boolean,
|
|
12
|
-
* }} IDBKeyRangeFull
|
|
13
|
-
*/
|
|
9
|
+
* __lowerCached: string|null|false,
|
|
10
|
+
* __upperCached: string|null|false,
|
|
11
|
+
* __lowerOpen: boolean,
|
|
12
|
+
* }} IDBKeyRangeFull
|
|
13
|
+
*/
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* The IndexedDB KeyRange object.
|
package/src/IDBTransaction.js
CHANGED
|
@@ -50,6 +50,7 @@ const readonlyProperties = ['objectStoreNames', 'mode', 'db', 'error'];
|
|
|
50
50
|
* },
|
|
51
51
|
* __requestsFinished: boolean,
|
|
52
52
|
* __transFinishedCb: (err: boolean, cb: ((bool?: boolean) => void)) => void,
|
|
53
|
+
* __callTransFinishedCb: (err: boolean, cb: ((bool?: boolean) => void)) => void,
|
|
53
54
|
* __transactionEndCallback: () => void,
|
|
54
55
|
* __transactionFinished: boolean,
|
|
55
56
|
* __completed: boolean,
|
|
@@ -157,6 +158,35 @@ IDBTransaction.prototype = EventTargetFactory.createInstance({
|
|
|
157
158
|
IDBTransaction.prototype.__transFinishedCb = function (err, cb) {
|
|
158
159
|
cb(Boolean(err));
|
|
159
160
|
};
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* In Node, the real (SQL-commit-capable) `__transFinishedCb` is only
|
|
164
|
+
* installed once the underlying WebSQL driver's own SQL-queue-idle check
|
|
165
|
+
* has fired at least once for this transaction (asynchronously, via the
|
|
166
|
+
* `nonstandardTransCb` passed to `db.transaction`/`.readTransaction`).
|
|
167
|
+
* Since our own request processing can now finish synchronously (e.g., a
|
|
168
|
+
* trivial upgrade using a synchronous SQL driver), it is possible to reach
|
|
169
|
+
* transaction completion here before that has happened, in which case
|
|
170
|
+
* `__transFinishedCb` is still the non-committing default above. Calling
|
|
171
|
+
* that default directly would silently skip the actual SQL commit and
|
|
172
|
+
* leave the underlying WebSQL transaction "running" forever, hanging any
|
|
173
|
+
* later transaction on that same database connection. So, if the real
|
|
174
|
+
* callback isn't installed yet, defer and retry until it is.
|
|
175
|
+
* @this {IDBTransactionFull}
|
|
176
|
+
* @param {boolean} err
|
|
177
|
+
* @param {(bool?: boolean) => void} cb
|
|
178
|
+
* @returns {void}
|
|
179
|
+
*/
|
|
180
|
+
IDBTransaction.prototype.__callTransFinishedCb = function (err, cb) {
|
|
181
|
+
const me = this;
|
|
182
|
+
if (me.__transFinishedCb === IDBTransaction.prototype.__transFinishedCb) {
|
|
183
|
+
setTimeout(() => {
|
|
184
|
+
me.__callTransFinishedCb(err, cb);
|
|
185
|
+
}, 0);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
me.__transFinishedCb(err, cb);
|
|
189
|
+
};
|
|
160
190
|
/**
|
|
161
191
|
* @this {IDBTransactionFull}
|
|
162
192
|
* @returns {void}
|
|
@@ -214,7 +244,7 @@ IDBTransaction.prototype.__executeRequests = function () {
|
|
|
214
244
|
me.__abortTransaction(createDOMException('AbortError', 'A request was aborted (in user handler after success).'));
|
|
215
245
|
return;
|
|
216
246
|
}
|
|
217
|
-
executeNextRequest
|
|
247
|
+
util.runContinuationSafely(executeNextRequest);
|
|
218
248
|
}
|
|
219
249
|
|
|
220
250
|
/**
|
|
@@ -282,7 +312,7 @@ IDBTransaction.prototype.__executeRequests = function () {
|
|
|
282
312
|
try {
|
|
283
313
|
q = me.__requests[i];
|
|
284
314
|
if (!q.req) {
|
|
285
|
-
q.op(tx, q.args, executeNextRequest, error);
|
|
315
|
+
q.op(tx, q.args, () => util.runContinuationSafely(executeNextRequest), error);
|
|
286
316
|
return;
|
|
287
317
|
}
|
|
288
318
|
if (q.req.__done) { // Avoid continuing with aborted requests
|
|
@@ -746,10 +776,10 @@ IDBTransaction.__assertActive = function (tx) {
|
|
|
746
776
|
};
|
|
747
777
|
|
|
748
778
|
/**
|
|
749
|
-
* Used by our `EventTarget.prototype` library to implement bubbling/capturing.
|
|
779
|
+
* Used by our `EventTarget.prototype` library to implement bubbling/capturing.
|
|
750
780
|
* @this {IDBTransactionFull}
|
|
751
|
-
* @returns {import('./IDBDatabase.js').IDBDatabaseFull}
|
|
752
|
-
*/
|
|
781
|
+
* @returns {import('./IDBDatabase.js').IDBDatabaseFull}
|
|
782
|
+
*/
|
|
753
783
|
IDBTransaction.prototype.__getParent = function () {
|
|
754
784
|
return this.db;
|
|
755
785
|
};
|
package/src/Key.js
CHANGED
|
@@ -32,14 +32,14 @@ import CFG from './CFG.js';
|
|
|
32
32
|
*/
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
|
-
* @typedef {object} KeyValueObject
|
|
36
|
-
* @property {KeyType|"NaN"|"null"|"undefined"|"boolean"|"object"|"symbol"|
|
|
37
|
-
* "function"|"bigint"} type If not `KeyType`, indicates invalid value
|
|
38
|
-
* @property {Value} [value]
|
|
39
|
-
* @property {boolean} [invalid]
|
|
40
|
-
* @property {string} [message]
|
|
41
|
-
* @todo Specify acceptable `value` more precisely
|
|
42
|
-
*/
|
|
35
|
+
* @typedef {object} KeyValueObject
|
|
36
|
+
* @property {KeyType|"NaN"|"null"|"undefined"|"boolean"|"object"|"symbol"|
|
|
37
|
+
* "function"|"bigint"} type If not `KeyType`, indicates invalid value
|
|
38
|
+
* @property {Value} [value]
|
|
39
|
+
* @property {boolean} [invalid]
|
|
40
|
+
* @property {string} [message]
|
|
41
|
+
* @todo Specify acceptable `value` more precisely
|
|
42
|
+
*/
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
45
|
* @typedef {number|string|Date|ArrayBuffer} ValueTypePrimitive
|
|
@@ -283,14 +283,22 @@ const types = {
|
|
|
283
283
|
encoded[i] = encodedItem;
|
|
284
284
|
}
|
|
285
285
|
encoded.push(keyTypeToEncodedChar.invalid + '-'); // append an extra item, so empty arrays sort correctly
|
|
286
|
-
|
|
286
|
+
let encodedKey = JSON.stringify(encoded);
|
|
287
|
+
if (CFG.escapeNULForSQLiteStatements === false) {
|
|
288
|
+
encodedKey = encodedKey.replaceAll(String.raw`\u0000`, '\0');
|
|
289
|
+
}
|
|
290
|
+
return keyTypeToEncodedChar.array + '-' + encodedKey;
|
|
287
291
|
},
|
|
288
292
|
/**
|
|
289
293
|
* @param {string} key
|
|
290
294
|
* @returns {ValueTypeArray}
|
|
291
295
|
*/
|
|
292
296
|
decode (key) {
|
|
293
|
-
|
|
297
|
+
let decodedKey = key.slice(2);
|
|
298
|
+
if (CFG.escapeNULForSQLiteStatements === false) {
|
|
299
|
+
decodedKey = decodedKey.replaceAll('\0', String.raw`\u0000`);
|
|
300
|
+
}
|
|
301
|
+
const decoded = JSON.parse(decodedKey);
|
|
294
302
|
decoded.pop(); // remove the extra item
|
|
295
303
|
for (let i = 0; i < decoded.length; i++) {
|
|
296
304
|
const item = decoded[i];
|
|
@@ -466,10 +474,10 @@ function convertValueToKey (input, seen) {
|
|
|
466
474
|
}
|
|
467
475
|
|
|
468
476
|
/**
|
|
469
|
-
* Currently not in use.
|
|
470
|
-
* @param {Value} input
|
|
471
|
-
* @returns {KeyValueObject}
|
|
472
|
-
*/
|
|
477
|
+
* Currently not in use.
|
|
478
|
+
* @param {Value} input
|
|
479
|
+
* @returns {KeyValueObject}
|
|
480
|
+
*/
|
|
473
481
|
function convertValueToMultiEntryKey (input) {
|
|
474
482
|
return convertValueToKeyValueDecoded(input, null, true, true);
|
|
475
483
|
}
|
|
@@ -508,17 +516,17 @@ function getCopyBytesHeldByBufferSource (O) {
|
|
|
508
516
|
}
|
|
509
517
|
|
|
510
518
|
/**
|
|
511
|
-
* Shortcut utility to avoid returning full keys from `convertValueToKey`
|
|
512
|
-
* and subsequent need to process in calling code unless `fullKeys` is
|
|
513
|
-
* set; may throw.
|
|
514
|
-
* @param {Value} input
|
|
515
|
-
* @param {Value[]|null} [seen]
|
|
516
|
-
* @param {boolean} [multiEntry]
|
|
517
|
-
* @param {boolean} [fullKeys]
|
|
518
|
-
* @throws {TypeError} See `getCopyBytesHeldByBufferSource`
|
|
519
|
-
* @todo Document other allowable `input`
|
|
520
|
-
* @returns {KeyValueObject}
|
|
521
|
-
*/
|
|
519
|
+
* Shortcut utility to avoid returning full keys from `convertValueToKey`
|
|
520
|
+
* and subsequent need to process in calling code unless `fullKeys` is
|
|
521
|
+
* set; may throw.
|
|
522
|
+
* @param {Value} input
|
|
523
|
+
* @param {Value[]|null} [seen]
|
|
524
|
+
* @param {boolean} [multiEntry]
|
|
525
|
+
* @param {boolean} [fullKeys]
|
|
526
|
+
* @throws {TypeError} See `getCopyBytesHeldByBufferSource`
|
|
527
|
+
* @todo Document other allowable `input`
|
|
528
|
+
* @returns {KeyValueObject}
|
|
529
|
+
*/
|
|
522
530
|
function convertValueToKeyValueDecoded (input, seen, multiEntry, fullKeys) {
|
|
523
531
|
seen ||= [];
|
|
524
532
|
if (seen.includes(input)) {
|
|
@@ -615,12 +623,12 @@ function convertValueToMultiEntryKeyDecoded (key, fullKeys) {
|
|
|
615
623
|
}
|
|
616
624
|
|
|
617
625
|
/**
|
|
618
|
-
* An internal utility.
|
|
619
|
-
* @param {Value} input
|
|
620
|
-
* @param {Value[]|null|undefined} [seen]
|
|
621
|
-
* @throws {DOMException} `DataError`
|
|
622
|
-
* @returns {KeyValueObject}
|
|
623
|
-
*/
|
|
626
|
+
* An internal utility.
|
|
627
|
+
* @param {Value} input
|
|
628
|
+
* @param {Value[]|null|undefined} [seen]
|
|
629
|
+
* @throws {DOMException} `DataError`
|
|
630
|
+
* @returns {KeyValueObject}
|
|
631
|
+
*/
|
|
624
632
|
function convertValueToKeyRethrowingAndIfInvalid (input, seen) {
|
|
625
633
|
const key = convertValueToKey(input, seen);
|
|
626
634
|
if (key.invalid) {
|
|
@@ -641,26 +649,26 @@ function extractKeyFromValueUsingKeyPath (value, keyPath, multiEntry) {
|
|
|
641
649
|
return extractKeyValueDecodedFromValueUsingKeyPath(value, keyPath, multiEntry, true);
|
|
642
650
|
}
|
|
643
651
|
/**
|
|
644
|
-
* Not currently in use.
|
|
645
|
-
* @param {Value} value
|
|
646
|
-
* @param {KeyPath} keyPath
|
|
647
|
-
* @param {boolean} multiEntry
|
|
648
|
-
* @returns {KeyPathEvaluateValue}
|
|
649
|
-
*/
|
|
652
|
+
* Not currently in use.
|
|
653
|
+
* @param {Value} value
|
|
654
|
+
* @param {KeyPath} keyPath
|
|
655
|
+
* @param {boolean} multiEntry
|
|
656
|
+
* @returns {KeyPathEvaluateValue}
|
|
657
|
+
*/
|
|
650
658
|
function evaluateKeyPathOnValue (value, keyPath, multiEntry) {
|
|
651
659
|
return evaluateKeyPathOnValueToDecodedValue(value, keyPath, multiEntry, true);
|
|
652
660
|
}
|
|
653
661
|
|
|
654
662
|
/**
|
|
655
|
-
* May throw, return `{failure: true}` (e.g., non-object on keyPath resolution)
|
|
656
|
-
* or `{invalid: true}` (e.g., `NaN`).
|
|
657
|
-
* @param {Value} value
|
|
658
|
-
* @param {KeyPath} keyPath
|
|
659
|
-
* @param {boolean} [multiEntry]
|
|
660
|
-
* @param {boolean} [fullKeys]
|
|
661
|
-
* @returns {KeyValueObject|KeyPathEvaluateValue}
|
|
662
|
-
* @todo Document other possible return?
|
|
663
|
-
*/
|
|
663
|
+
* May throw, return `{failure: true}` (e.g., non-object on keyPath resolution)
|
|
664
|
+
* or `{invalid: true}` (e.g., `NaN`).
|
|
665
|
+
* @param {Value} value
|
|
666
|
+
* @param {KeyPath} keyPath
|
|
667
|
+
* @param {boolean} [multiEntry]
|
|
668
|
+
* @param {boolean} [fullKeys]
|
|
669
|
+
* @returns {KeyValueObject|KeyPathEvaluateValue}
|
|
670
|
+
* @todo Document other possible return?
|
|
671
|
+
*/
|
|
664
672
|
function extractKeyValueDecodedFromValueUsingKeyPath (value, keyPath, multiEntry, fullKeys) {
|
|
665
673
|
const r = evaluateKeyPathOnValueToDecodedValue(value, keyPath, multiEntry, fullKeys);
|
|
666
674
|
if (r.failure) {
|
|
@@ -891,11 +899,11 @@ function findMultiEntryMatches (keyEntry, range) {
|
|
|
891
899
|
}
|
|
892
900
|
|
|
893
901
|
/**
|
|
894
|
-
* Not currently in use but keeping for spec parity.
|
|
895
|
-
* @param {Key} key
|
|
896
|
-
* @throws {Error} Upon a "bad key"
|
|
897
|
-
* @returns {ValueType}
|
|
898
|
-
*/
|
|
902
|
+
* Not currently in use but keeping for spec parity.
|
|
903
|
+
* @param {Key} key
|
|
904
|
+
* @throws {Error} Upon a "bad key"
|
|
905
|
+
* @returns {ValueType}
|
|
906
|
+
*/
|
|
899
907
|
function convertKeyToValue (key) {
|
|
900
908
|
const {type, value} = key;
|
|
901
909
|
switch (type) {
|
|
@@ -977,10 +985,10 @@ const MAX_ALLOWED_CURRENT_NUMBER = 9007199254740992; // 2 ^ 53 (Also equal to `N
|
|
|
977
985
|
*/
|
|
978
986
|
|
|
979
987
|
/**
|
|
980
|
-
* @callback SQLFailureCallback
|
|
981
|
-
* @param {DOMException|Error} exception
|
|
982
|
-
* @returns {void}
|
|
983
|
-
*/
|
|
988
|
+
* @callback SQLFailureCallback
|
|
989
|
+
* @param {DOMException|Error} exception
|
|
990
|
+
* @returns {void}
|
|
991
|
+
*/
|
|
984
992
|
|
|
985
993
|
/**
|
|
986
994
|
*
|
|
@@ -13,7 +13,11 @@ CFG.win = {openDatabase: nodeWebSQL};
|
|
|
13
13
|
* @returns {import('./setGlobalVars.js').ShimmedObject|Window}
|
|
14
14
|
*/
|
|
15
15
|
const __setGlobalVars = function (idb, initialConfig = {}) {
|
|
16
|
-
const obj = setGlobalVars(idb, {
|
|
16
|
+
const obj = setGlobalVars(idb, {
|
|
17
|
+
fs,
|
|
18
|
+
escapeNULForSQLiteStatements: false,
|
|
19
|
+
...initialConfig
|
|
20
|
+
});
|
|
17
21
|
/* istanbul ignore next -- TS guard */
|
|
18
22
|
if (!obj.shimIndexedDB) {
|
|
19
23
|
return obj;
|
package/src/node.js
CHANGED
|
@@ -12,7 +12,11 @@ CFG.win = {openDatabase: nodeWebSQL};
|
|
|
12
12
|
* @returns {import('./setGlobalVars.js').ShimmedObject|Window}
|
|
13
13
|
*/
|
|
14
14
|
const __setGlobalVars = function (idb, initialConfig = {}) {
|
|
15
|
-
return setGlobalVars(idb, {
|
|
15
|
+
return setGlobalVars(idb, {
|
|
16
|
+
fs,
|
|
17
|
+
escapeNULForSQLiteStatements: false,
|
|
18
|
+
...initialConfig
|
|
19
|
+
});
|
|
16
20
|
};
|
|
17
21
|
|
|
18
22
|
export default __setGlobalVars;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
class SQLiteResult {
|
|
7
|
+
/**
|
|
8
|
+
* @param {Error|null|undefined} error
|
|
9
|
+
* @param {number|undefined} [insertId]
|
|
10
|
+
* @param {number} [rowsAffected]
|
|
11
|
+
* @param {object[]} [rows]
|
|
12
|
+
*/
|
|
13
|
+
constructor (error, insertId, rowsAffected, rows) {
|
|
14
|
+
this.error = error;
|
|
15
|
+
this.insertId = insertId;
|
|
16
|
+
this.rowsAffected = rowsAffected;
|
|
17
|
+
this.rows = rows;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const READ_ONLY_ERROR = new Error(
|
|
22
|
+
'could not prepare statement (23 not authorized)'
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @typedef {(sql: string, duration: number) => void} SQLProfileCallback
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @typedef {((sql: string) => void)|undefined} SQLTraceCallback
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param {string} name
|
|
35
|
+
* @param {{busyTimeout?: number, trace?: (sql: string) => void, profile?: SQLProfileCallback}} [opts]
|
|
36
|
+
* @returns {void}
|
|
37
|
+
*/
|
|
38
|
+
function SQLiteDatabase (name, opts = {}) {
|
|
39
|
+
/** @type {import('better-sqlite3').Database} */
|
|
40
|
+
const db = new Database(name);
|
|
41
|
+
|
|
42
|
+
/** @type {SQLTraceCallback} */
|
|
43
|
+
// eslint-disable-next-line prefer-destructuring -- TS
|
|
44
|
+
let trace = opts.trace;
|
|
45
|
+
/** @type {SQLProfileCallback|undefined} */
|
|
46
|
+
// eslint-disable-next-line prefer-destructuring -- TS
|
|
47
|
+
let profile = opts.profile;
|
|
48
|
+
|
|
49
|
+
if (opts.busyTimeout) {
|
|
50
|
+
db.pragma('busy_timeout = ' + Number(opts.busyTimeout));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Kept untyped (rather than the better-sqlite3 `Database` type) since that
|
|
54
|
+
// type is internal to `@types/better-sqlite3` and can't be named in this
|
|
55
|
+
// file's emitted declaration.
|
|
56
|
+
this._db = /** @type {any} */ ({
|
|
57
|
+
_db: db,
|
|
58
|
+
/**
|
|
59
|
+
* Compatibility with node-sqlite3's configure API.
|
|
60
|
+
* @param {'busyTimeout'|'trace'|'profile'} option
|
|
61
|
+
* @param {number|((sql: string, duration?: number) => void)} value
|
|
62
|
+
* @returns {void}
|
|
63
|
+
*/
|
|
64
|
+
configure (option, value) {
|
|
65
|
+
if (option === 'busyTimeout') {
|
|
66
|
+
db.pragma('busy_timeout = ' + Number(/** @type {number} */ (value)));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (option === 'trace') {
|
|
70
|
+
trace = /** @type {(sql: string) => void} */ (value);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (option === 'profile') {
|
|
74
|
+
profile = /** @type {SQLProfileCallback} */ (value);
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
/**
|
|
78
|
+
* Compatibility with callback-oriented close semantics.
|
|
79
|
+
* @param {(err?: Error|null) => void} [cb]
|
|
80
|
+
* @returns {void}
|
|
81
|
+
*/
|
|
82
|
+
close (cb) {
|
|
83
|
+
try {
|
|
84
|
+
db.close();
|
|
85
|
+
if (cb) {
|
|
86
|
+
return cb(null);
|
|
87
|
+
}
|
|
88
|
+
} catch (err) {
|
|
89
|
+
if (cb) {
|
|
90
|
+
return cb(/** @type {Error} */ (err));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return undefined;
|
|
94
|
+
},
|
|
95
|
+
getTrace () {
|
|
96
|
+
return trace;
|
|
97
|
+
},
|
|
98
|
+
getProfile () {
|
|
99
|
+
return profile;
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* @param {import('better-sqlite3').Database} db
|
|
106
|
+
* @param {string} sql
|
|
107
|
+
* @param {unknown[]} args
|
|
108
|
+
* @returns {object[]}
|
|
109
|
+
*/
|
|
110
|
+
function runSelect (db, sql, args) {
|
|
111
|
+
const stmt = db.prepare(sql);
|
|
112
|
+
return stmt.reader ? /** @type {object[]} */ (stmt.all(...args)) : [];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @param {import('better-sqlite3').Database} db
|
|
117
|
+
* @param {string} sql
|
|
118
|
+
* @param {unknown[]} args
|
|
119
|
+
* @returns {import('better-sqlite3').RunResult}
|
|
120
|
+
*/
|
|
121
|
+
function runNonSelect (db, sql, args) {
|
|
122
|
+
const stmt = db.prepare(sql);
|
|
123
|
+
return stmt.run(...args);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* @param {{sql: string, args: unknown[]}[]} queries
|
|
128
|
+
* @param {boolean} readOnly
|
|
129
|
+
* @param {(err: Error|null, results?: SQLiteResult[]) => void} callback
|
|
130
|
+
* @returns {void}
|
|
131
|
+
*/
|
|
132
|
+
SQLiteDatabase.prototype.exec = function exec (queries, readOnly, callback) {
|
|
133
|
+
const db = this._db._db;
|
|
134
|
+
const len = queries.length;
|
|
135
|
+
const results = Array.from({length: len});
|
|
136
|
+
|
|
137
|
+
for (let i = 0; i < len; i++) {
|
|
138
|
+
const query = queries[i];
|
|
139
|
+
const {sql, args} = query;
|
|
140
|
+
const isSelect = (/^\s*SELECT\b/iu).test(sql);
|
|
141
|
+
if (readOnly && !isSelect) {
|
|
142
|
+
results[i] = new SQLiteResult(READ_ONLY_ERROR);
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const trace = this._db.getTrace();
|
|
146
|
+
const profile = this._db.getProfile();
|
|
147
|
+
// eslint-disable-next-line unicorn/prefer-bigint-literals -- `0n` needs ES2020+ target for tsc
|
|
148
|
+
const start = profile ? process.hrtime.bigint() : BigInt(0);
|
|
149
|
+
try {
|
|
150
|
+
if (trace) {
|
|
151
|
+
trace(sql);
|
|
152
|
+
}
|
|
153
|
+
if (isSelect) {
|
|
154
|
+
const rows = runSelect(db, sql, args);
|
|
155
|
+
results[i] = new SQLiteResult(null, undefined, 0, rows);
|
|
156
|
+
} else {
|
|
157
|
+
const executionResult = runNonSelect(db, sql, args);
|
|
158
|
+
const insertId = Number(executionResult.lastInsertRowid);
|
|
159
|
+
results[i] = new SQLiteResult(null, insertId, executionResult.changes, []);
|
|
160
|
+
}
|
|
161
|
+
} catch (err) {
|
|
162
|
+
results[i] = new SQLiteResult(/** @type {Error} */ (err));
|
|
163
|
+
} finally {
|
|
164
|
+
if (profile) {
|
|
165
|
+
profile(sql, Number(process.hrtime.bigint() - start));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
queueMicrotask(() => {
|
|
170
|
+
callback(null, results);
|
|
171
|
+
});
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
export default SQLiteDatabase;
|
package/src/nodeWebSQL.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import customOpenDatabase from 'websql-configurable/custom/index.js';
|
|
2
|
-
import SQLiteDatabase from '
|
|
2
|
+
import SQLiteDatabase from './nodeSQLiteDatabase.js';
|
|
3
3
|
import CFG from './CFG.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -12,11 +12,9 @@ function wrappedSQLiteDatabase (name) {
|
|
|
12
12
|
db._db.configure('busyTimeout', /** @type {number} */ (CFG.sqlBusyTimeout)); // Default is 1000
|
|
13
13
|
}
|
|
14
14
|
if (CFG.sqlTrace) {
|
|
15
|
-
// @ts-expect-error native API?
|
|
16
15
|
db._db.configure('trace', CFG.sqlTrace);
|
|
17
16
|
}
|
|
18
17
|
if (CFG.sqlProfile) {
|
|
19
|
-
// @ts-expect-error native API?
|
|
20
18
|
db._db.configure('profile', CFG.sqlProfile);
|
|
21
19
|
}
|
|
22
20
|
return db;
|