@powersync/web 2.1.1 → 2.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/dist/index.react_native_web.js +212 -92
- package/dist/index.react_native_web.js.map +1 -1
- package/dist/worker/AccessHandlePoolVFS-BPUHfZME.js.map +1 -1
- package/dist/worker/FacadeVFS-d1ZDvud7.js.map +1 -1
- package/dist/worker/IDBBatchAtomicVFS-DbkDb777.js.map +1 -1
- package/dist/worker/MemoryVFS-DVJL5F8j.js.map +1 -1
- package/dist/worker/OPFSCoopSyncVFS-BgTiWPfa.js.map +1 -1
- package/dist/worker/OPFSWriteAheadVFS-Rt5CGCZP.js +2 -0
- package/dist/worker/OPFSWriteAheadVFS-Rt5CGCZP.js.map +1 -0
- package/dist/worker/mc-wa-sqlite-DDFgWP93.js.map +1 -1
- package/dist/worker/mc-wa-sqlite-async-lGclTjKJ.js.map +1 -1
- package/dist/worker/wa-sqlite-B0tZMM0j.js.map +1 -1
- package/dist/worker/wa-sqlite-async-CM6BmfRh.js.map +1 -1
- package/dist/worker/worker.js +1 -1
- package/dist/worker/worker.js.map +1 -1
- package/lib/db/PowerSyncDatabase.js +16 -11
- package/lib/db/PowerSyncDatabase.js.map +1 -1
- package/lib/db/adapters/AsyncWebAdapter.js +4 -55
- package/lib/db/adapters/AsyncWebAdapter.js.map +1 -1
- package/lib/db/adapters/acquireFromPool.d.ts +7 -0
- package/lib/db/adapters/acquireFromPool.js +60 -0
- package/lib/db/adapters/acquireFromPool.js.map +1 -0
- package/lib/db/adapters/memory-pool/client.d.ts +70 -0
- package/lib/db/adapters/memory-pool/client.js +269 -0
- package/lib/db/adapters/memory-pool/client.js.map +1 -0
- package/lib/db/adapters/memory-pool/shared.d.ts +51 -0
- package/lib/db/adapters/memory-pool/shared.js +16 -0
- package/lib/db/adapters/memory-pool/shared.js.map +1 -0
- package/lib/db/adapters/memory-pool/vfs.d.ts +19 -0
- package/lib/db/adapters/memory-pool/vfs.js +247 -0
- package/lib/db/adapters/memory-pool/vfs.js.map +1 -0
- package/lib/db/adapters/memory-pool/worker.d.ts +1 -0
- package/lib/db/adapters/memory-pool/worker.js +45 -0
- package/lib/db/adapters/memory-pool/worker.js.map +1 -0
- package/lib/db/adapters/options.d.ts +7 -0
- package/lib/db/adapters/options.js.map +1 -1
- package/lib/db/adapters/wa-sqlite/RawSqliteConnection.d.ts +8 -0
- package/lib/db/adapters/wa-sqlite/RawSqliteConnection.js +56 -5
- package/lib/db/adapters/wa-sqlite/RawSqliteConnection.js.map +1 -1
- package/lib/db/adapters/wa-sqlite/StatementCache.d.ts +16 -0
- package/lib/db/adapters/wa-sqlite/StatementCache.js +44 -0
- package/lib/db/adapters/wa-sqlite/StatementCache.js.map +1 -0
- package/lib/db/adapters/wa-sqlite/WASQLiteOpenFactory.js +7 -4
- package/lib/db/adapters/wa-sqlite/WASQLiteOpenFactory.js.map +1 -1
- package/lib/worker/db/MultiDatabaseServer.d.ts +1 -2
- package/lib/worker/db/MultiDatabaseServer.js +28 -17
- package/lib/worker/db/MultiDatabaseServer.js.map +1 -1
- package/package.json +13 -6
- package/src/db/PowerSyncDatabase.ts +18 -11
- package/src/db/adapters/AsyncWebAdapter.ts +13 -61
- package/src/db/adapters/acquireFromPool.ts +73 -0
- package/src/db/adapters/memory-pool/client.ts +367 -0
- package/src/db/adapters/memory-pool/shared.ts +79 -0
- package/src/db/adapters/memory-pool/vfs.ts +293 -0
- package/src/db/adapters/memory-pool/worker.ts +53 -0
- package/src/db/adapters/options.ts +8 -0
- package/src/db/adapters/wa-sqlite/RawSqliteConnection.ts +71 -6
- package/src/db/adapters/wa-sqlite/StatementCache.ts +50 -0
- package/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.ts +17 -5
- package/src/worker/db/MultiDatabaseServer.ts +29 -21
- package/dist/worker/OPFSWriteAheadVFS-BzodSqNq.js +0 -2
- package/dist/worker/OPFSWriteAheadVFS-BzodSqNq.js.map +0 -1
|
@@ -442,6 +442,50 @@ function resolveAndValidateOptions(options) {
|
|
|
442
442
|
return resolved;
|
|
443
443
|
}
|
|
444
444
|
|
|
445
|
+
class PreparedStatementCache {
|
|
446
|
+
#size;
|
|
447
|
+
// Note that Map preserves insertion order, which allows using it as an LRU
|
|
448
|
+
// cache (with the first element being the first element to evict).
|
|
449
|
+
#statements = new Map();
|
|
450
|
+
constructor(size) {
|
|
451
|
+
this.#size = size;
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Attempts to look up the cached sql statement, if it's currently cached.
|
|
455
|
+
*/
|
|
456
|
+
lookup(sql) {
|
|
457
|
+
const foundStatement = this.#statements.get(sql);
|
|
458
|
+
if (foundStatement != null) {
|
|
459
|
+
// Delete and re-insert to move to the end (most-recently-used position).
|
|
460
|
+
this.#statements.delete(sql);
|
|
461
|
+
this.#statements.set(sql, foundStatement);
|
|
462
|
+
return foundStatement;
|
|
463
|
+
}
|
|
464
|
+
return null;
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Adds a new statement into the cache.
|
|
468
|
+
*
|
|
469
|
+
* If that exceeds the target size of the statement cache, returns an old statement to evict.
|
|
470
|
+
* The caller is responsible for freeing that statement.
|
|
471
|
+
*/
|
|
472
|
+
addStatement(sql, statement) {
|
|
473
|
+
this.#statements.set(sql, statement);
|
|
474
|
+
if (this.#statements.size > this.#size) {
|
|
475
|
+
for (const [k, v] of this.#statements.entries()) {
|
|
476
|
+
this.#statements.delete(k);
|
|
477
|
+
return v;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
return null;
|
|
481
|
+
}
|
|
482
|
+
drain() {
|
|
483
|
+
const values = [...this.#statements.values()];
|
|
484
|
+
this.#statements.clear();
|
|
485
|
+
return values;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
445
489
|
/**
|
|
446
490
|
* A small wrapper around WA-sqlite to help with opening databases and running statements by preparing them internally.
|
|
447
491
|
*
|
|
@@ -451,18 +495,26 @@ function resolveAndValidateOptions(options) {
|
|
|
451
495
|
class RawSqliteConnection {
|
|
452
496
|
options;
|
|
453
497
|
_sqliteAPI = null;
|
|
498
|
+
sqlite3_stmt_isexplain;
|
|
454
499
|
/**
|
|
455
500
|
* The `sqlite3*` connection pointer.
|
|
456
501
|
*/
|
|
457
502
|
db = 0;
|
|
503
|
+
statementCache;
|
|
458
504
|
constructor(options) {
|
|
459
505
|
this.options = options;
|
|
506
|
+
this.statementCache =
|
|
507
|
+
options.preparedStatementsCache > 0 ? new PreparedStatementCache(options.preparedStatementsCache) : null;
|
|
460
508
|
}
|
|
461
509
|
get isOpen() {
|
|
462
510
|
return this.db != 0;
|
|
463
511
|
}
|
|
464
512
|
async init() {
|
|
465
|
-
const
|
|
513
|
+
const { module, vfs } = await loadModuleAndVfs(this.options);
|
|
514
|
+
await this.initWithModule(module, vfs);
|
|
515
|
+
}
|
|
516
|
+
async initWithModule(module, vfs) {
|
|
517
|
+
const api = (this._sqliteAPI = await this.openSQLiteAPI(module, vfs));
|
|
466
518
|
this.db = await api.open_v2(this.options.filename, this.options.readonly ? 1 /* SQLITE_OPEN_READONLY */ : 6 /* SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE */);
|
|
467
519
|
await this.executeRaw(`PRAGMA temp_store = ${this.options.temporaryStorage};`);
|
|
468
520
|
if (this.options.encryptionKey) {
|
|
@@ -472,9 +524,9 @@ class RawSqliteConnection {
|
|
|
472
524
|
await this.executeRaw(`PRAGMA cache_size = -${this.options.cacheSizeKb};`);
|
|
473
525
|
await this.executeRaw(`SELECT powersync_update_hooks('install');`);
|
|
474
526
|
}
|
|
475
|
-
async openSQLiteAPI() {
|
|
476
|
-
const { module, vfs } = await loadModuleAndVfs(this.options);
|
|
527
|
+
async openSQLiteAPI(module, vfs) {
|
|
477
528
|
vfs.mxPathname = maxPathNameLength;
|
|
529
|
+
this.sqlite3_stmt_isexplain = module.cwrap('sqlite3_stmt_isexplain', 'int', ['int']);
|
|
478
530
|
const sqlite3 = Factory(module);
|
|
479
531
|
sqlite3.vfs_register(vfs, true);
|
|
480
532
|
/**
|
|
@@ -542,7 +594,7 @@ class RawSqliteConnection {
|
|
|
542
594
|
async executeRaw(sql, bindings) {
|
|
543
595
|
const results = [];
|
|
544
596
|
const api = this.requireSqlite();
|
|
545
|
-
for await (const stmt of
|
|
597
|
+
for await (const stmt of this.cachedStatements(api, sql)) {
|
|
546
598
|
let columns;
|
|
547
599
|
const rs = await this.stepThroughStatement(api, stmt, bindings ?? [], columns);
|
|
548
600
|
columns = rs.columnNames;
|
|
@@ -579,10 +631,52 @@ class RawSqliteConnection {
|
|
|
579
631
|
}
|
|
580
632
|
async close() {
|
|
581
633
|
if (this.isOpen) {
|
|
582
|
-
|
|
634
|
+
const api = this.requireSqlite();
|
|
635
|
+
if (this.statementCache) {
|
|
636
|
+
for (const stmt of this.statementCache.drain()) {
|
|
637
|
+
await api.finalize(stmt);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
await api.close(this.db);
|
|
583
641
|
this.db = 0;
|
|
584
642
|
}
|
|
585
643
|
}
|
|
644
|
+
async *cachedStatements(api, sql) {
|
|
645
|
+
{
|
|
646
|
+
const existing = this.statementCache?.lookup(sql);
|
|
647
|
+
if (existing != null) {
|
|
648
|
+
yield existing;
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
const inner = api.statements(this.db, sql, { unscoped: true });
|
|
653
|
+
const preparedStatements = [];
|
|
654
|
+
try {
|
|
655
|
+
for await (const stmt of inner) {
|
|
656
|
+
preparedStatements.push(stmt);
|
|
657
|
+
yield stmt;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
finally {
|
|
661
|
+
// We can only cache statements if the sql text corresponds to a single statement, otherwise it's not clear what
|
|
662
|
+
// portion of the original sql text to use as a key.
|
|
663
|
+
if (preparedStatements.length === 1 && this.statementCache) {
|
|
664
|
+
const stmt = preparedStatements[0];
|
|
665
|
+
// Don't cache EXPLAIN statements, their result becomes invalid after schema changes.
|
|
666
|
+
if (this.sqlite3_stmt_isexplain(stmt) == 0) {
|
|
667
|
+
const evicted = this.statementCache.addStatement(sql, stmt);
|
|
668
|
+
if (evicted != null) {
|
|
669
|
+
await api.finalize(evicted);
|
|
670
|
+
}
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
// We're not caching statements, so finalize them.
|
|
675
|
+
for (const stmt of preparedStatements) {
|
|
676
|
+
await api.finalize(stmt);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
}
|
|
586
680
|
}
|
|
587
681
|
|
|
588
682
|
/**
|
|
@@ -712,7 +806,8 @@ const OPEN_DB_LOCK = 'open-wasqlite-db';
|
|
|
712
806
|
*/
|
|
713
807
|
class MultiDatabaseServer {
|
|
714
808
|
logger;
|
|
715
|
-
activeDatabases = new Map();
|
|
809
|
+
#activeDatabases = new Map();
|
|
810
|
+
#localOpenLock = new Mutex();
|
|
716
811
|
constructor(logger) {
|
|
717
812
|
this.logger = logger;
|
|
718
813
|
}
|
|
@@ -727,7 +822,7 @@ class MultiDatabaseServer {
|
|
|
727
822
|
}
|
|
728
823
|
async connectToExisting(name, lockName) {
|
|
729
824
|
return getNavigatorLocks().request(OPEN_DB_LOCK, async () => {
|
|
730
|
-
const server = this
|
|
825
|
+
const server = this.#activeDatabases.get(name);
|
|
731
826
|
if (server == null) {
|
|
732
827
|
throw new Error(`connectToExisting(${name}) failed because the worker doesn't own a database with that name.`);
|
|
733
828
|
}
|
|
@@ -741,7 +836,7 @@ class MultiDatabaseServer {
|
|
|
741
836
|
let server;
|
|
742
837
|
for (let count = 0; count < maxAttempts - 1; count++) {
|
|
743
838
|
try {
|
|
744
|
-
server = await this
|
|
839
|
+
server = await this.#databaseOpenAttempt(logger, options);
|
|
745
840
|
}
|
|
746
841
|
catch (error) {
|
|
747
842
|
this.logger.log({
|
|
@@ -753,19 +848,20 @@ class MultiDatabaseServer {
|
|
|
753
848
|
}
|
|
754
849
|
}
|
|
755
850
|
// Final attempt if we haven't been able to open the server - rethrow errors if we still can't open.
|
|
756
|
-
server ??= await this
|
|
851
|
+
server ??= await this.#databaseOpenAttempt(logger, options);
|
|
757
852
|
return server.connect(lockName);
|
|
758
853
|
}
|
|
759
|
-
async databaseOpenAttempt(logger, options) {
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
854
|
+
async #databaseOpenAttempt(logger, options) {
|
|
855
|
+
const { filename, readonly, vfs } = options;
|
|
856
|
+
// We don't need navigator locks for shared workers because all queries run in this shared worker exclusively.
|
|
857
|
+
// For read-only connections, we use a VFS that supports concurrent reads (so a single lock on the connection is
|
|
858
|
+
// fine). In-memory databases either run in a shared worker or aren't shared across tabs at all, so the internal
|
|
859
|
+
// lock is enough.
|
|
860
|
+
const needsNavigatorLocks = !(isSharedWorker$1 || readonly || vfs == WASQLiteVFS.InMemoryVfs);
|
|
861
|
+
const activeDatabases = this.#activeDatabases;
|
|
862
|
+
async function openDatabase() {
|
|
863
|
+
let server = activeDatabases.get(filename);
|
|
763
864
|
if (server == null) {
|
|
764
|
-
// We don't need navigator locks for shared workers because all queries run in this shared worker exclusively.
|
|
765
|
-
// For read-only connections, we use a VFS that supports concurrent reads (so a single lock on the connection is
|
|
766
|
-
// fine). In-memory databases either run in a shared worker or aren't shared across tabs at all, so the internal
|
|
767
|
-
// lock is enough.
|
|
768
|
-
const needsNavigatorLocks = !(isSharedWorker$1 || readonly || vfs == WASQLiteVFS.InMemoryVfs);
|
|
769
865
|
const connection = new RawSqliteConnection(options);
|
|
770
866
|
const withSafeConcurrency = new ConcurrentSqliteConnection(connection, needsNavigatorLocks);
|
|
771
867
|
// Initializing the RawSqliteConnection will run some pragmas that might write to the database file, so we want
|
|
@@ -781,19 +877,27 @@ class MultiDatabaseServer {
|
|
|
781
877
|
throw e;
|
|
782
878
|
}
|
|
783
879
|
returnLease();
|
|
784
|
-
const onClose = () =>
|
|
880
|
+
const onClose = () => activeDatabases.delete(filename);
|
|
785
881
|
server = new DatabaseServer({
|
|
786
882
|
inner: withSafeConcurrency,
|
|
787
883
|
logger,
|
|
788
884
|
onClose
|
|
789
885
|
});
|
|
790
|
-
|
|
886
|
+
activeDatabases.set(filename, server);
|
|
791
887
|
}
|
|
792
888
|
return server;
|
|
793
|
-
}
|
|
889
|
+
}
|
|
890
|
+
if (needsNavigatorLocks) {
|
|
891
|
+
return getNavigatorLocks().request(OPEN_DB_LOCK, openDatabase);
|
|
892
|
+
}
|
|
893
|
+
else {
|
|
894
|
+
// Even if we don't need navigator locks, this avoids a race between the activeDatabases.get() call, the async
|
|
895
|
+
// open logic and the final activeDatabases.set() step.
|
|
896
|
+
return this.#localOpenLock.runExclusive(openDatabase);
|
|
897
|
+
}
|
|
794
898
|
}
|
|
795
899
|
closeAll() {
|
|
796
|
-
const existingDatabases = [...this
|
|
900
|
+
const existingDatabases = [...this.#activeDatabases.values()];
|
|
797
901
|
return Promise.all(existingDatabases.map((db) => {
|
|
798
902
|
db.forceClose();
|
|
799
903
|
}));
|
|
@@ -1026,6 +1130,66 @@ function generateTabCloseSignal(abort) {
|
|
|
1026
1130
|
});
|
|
1027
1131
|
}
|
|
1028
1132
|
|
|
1133
|
+
/**
|
|
1134
|
+
* Internal helper function to acquire a connection from a pool that has a designated writer, additional readers, and
|
|
1135
|
+
* also allows dispatching reads to the writer.
|
|
1136
|
+
*/
|
|
1137
|
+
async function acquireFromPool(writerMutex, writer, readers, callback, options, allowReadOnly) {
|
|
1138
|
+
const abortController = new AbortController();
|
|
1139
|
+
const abortSignal = abortController.signal;
|
|
1140
|
+
let timeout = null;
|
|
1141
|
+
let release;
|
|
1142
|
+
if (options?.timeoutMs) {
|
|
1143
|
+
timeout = setTimeout(() => abortController.abort('requesting database timed out'), options.timeoutMs);
|
|
1144
|
+
}
|
|
1145
|
+
try {
|
|
1146
|
+
if (allowReadOnly) {
|
|
1147
|
+
let connection;
|
|
1148
|
+
// Even if we have a pool of read connections, it's typically very small and we assume that most queries are
|
|
1149
|
+
// reads. So, we want to request any connection from the read pool and the dedicated write connection (which
|
|
1150
|
+
// can also serve reads). We race for the first connection we can obtain this way, and then abort the other
|
|
1151
|
+
// request.
|
|
1152
|
+
[connection, release] = await new Promise((resolve, reject) => {
|
|
1153
|
+
let didComplete = false;
|
|
1154
|
+
function complete() {
|
|
1155
|
+
didComplete = true;
|
|
1156
|
+
abortController.abort();
|
|
1157
|
+
}
|
|
1158
|
+
function completeSuccess(connection, returnFn) {
|
|
1159
|
+
if (didComplete) {
|
|
1160
|
+
// We're not going to use this connection, so return it immediately.
|
|
1161
|
+
returnFn();
|
|
1162
|
+
}
|
|
1163
|
+
else {
|
|
1164
|
+
complete();
|
|
1165
|
+
resolve([connection, returnFn]);
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
function completeError(error) {
|
|
1169
|
+
// We either have a working connection already, or we've rejected the promise. Either way, we don't need
|
|
1170
|
+
// to do either thing again.
|
|
1171
|
+
if (didComplete)
|
|
1172
|
+
return;
|
|
1173
|
+
complete();
|
|
1174
|
+
reject(error);
|
|
1175
|
+
}
|
|
1176
|
+
writerMutex.acquire(abortSignal).then((unlock) => completeSuccess(writer, unlock), completeError);
|
|
1177
|
+
readers?.requestOne(abortSignal).then(({ item, release }) => completeSuccess(item, release), completeError);
|
|
1178
|
+
});
|
|
1179
|
+
return await callback(connection);
|
|
1180
|
+
}
|
|
1181
|
+
else {
|
|
1182
|
+
return await writerMutex.runExclusive(() => callback(writer), abortSignal);
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
finally {
|
|
1186
|
+
if (timeout != null) {
|
|
1187
|
+
clearTimeout(timeout);
|
|
1188
|
+
}
|
|
1189
|
+
release?.();
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1029
1193
|
/**
|
|
1030
1194
|
* A connection pool implementation delegating to another pool opened asynchronnously.
|
|
1031
1195
|
*/
|
|
@@ -1121,61 +1285,9 @@ function readWritePoolState(writer, readers) {
|
|
|
1121
1285
|
return {
|
|
1122
1286
|
writer,
|
|
1123
1287
|
async withConnection(allowReadOnly, fn, options) {
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
let release;
|
|
1128
|
-
if (options?.timeoutMs) {
|
|
1129
|
-
timeout = setTimeout(() => abortController.abort('requesting database timed out'), options.timeoutMs);
|
|
1130
|
-
}
|
|
1131
|
-
try {
|
|
1132
|
-
if (allowReadOnly) {
|
|
1133
|
-
let connection;
|
|
1134
|
-
// Even if we have a pool of read connections, it's typically very small and we assume that most queries are
|
|
1135
|
-
// reads. So, we want to request any connection from the read pool and the dedicated write connection (which
|
|
1136
|
-
// can also serve reads). We race for the first connection we can obtain this way, and then abort the other
|
|
1137
|
-
// request.
|
|
1138
|
-
[connection, release] = await new Promise((resolve, reject) => {
|
|
1139
|
-
let didComplete = false;
|
|
1140
|
-
function complete() {
|
|
1141
|
-
didComplete = true;
|
|
1142
|
-
abortController.abort();
|
|
1143
|
-
}
|
|
1144
|
-
function completeSuccess(connection, returnFn) {
|
|
1145
|
-
if (didComplete) {
|
|
1146
|
-
// We're not going to use this connection, so return it immediately.
|
|
1147
|
-
returnFn();
|
|
1148
|
-
}
|
|
1149
|
-
else {
|
|
1150
|
-
complete();
|
|
1151
|
-
resolve([connection, returnFn]);
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
function completeError(error) {
|
|
1155
|
-
// We either have a working connection already, or we've rejected the promise. Either way, we don't need
|
|
1156
|
-
// to do either thing again.
|
|
1157
|
-
if (didComplete)
|
|
1158
|
-
return;
|
|
1159
|
-
complete();
|
|
1160
|
-
reject(error);
|
|
1161
|
-
}
|
|
1162
|
-
writerMutex.acquire(abortSignal).then((unlock) => completeSuccess(writer, unlock), completeError);
|
|
1163
|
-
readerSemaphore
|
|
1164
|
-
.requestOne(abortSignal)
|
|
1165
|
-
.then(({ item, release }) => completeSuccess(item, release), completeError);
|
|
1166
|
-
});
|
|
1167
|
-
return await connection.readLock(fn);
|
|
1168
|
-
}
|
|
1169
|
-
else {
|
|
1170
|
-
return await writerMutex.runExclusive(() => writer.writeLock(fn), abortSignal);
|
|
1171
|
-
}
|
|
1172
|
-
}
|
|
1173
|
-
finally {
|
|
1174
|
-
if (timeout != null) {
|
|
1175
|
-
clearTimeout(timeout);
|
|
1176
|
-
}
|
|
1177
|
-
release?.();
|
|
1178
|
-
}
|
|
1288
|
+
return acquireFromPool(writerMutex, writer, readerSemaphore, (connection) => {
|
|
1289
|
+
return allowReadOnly ? connection.readLock(fn) : connection.writeLock(fn);
|
|
1290
|
+
}, options, allowReadOnly);
|
|
1179
1291
|
},
|
|
1180
1292
|
async close() {
|
|
1181
1293
|
await writer.close();
|
|
@@ -1309,7 +1421,7 @@ class WASQLiteOpenFactory {
|
|
|
1309
1421
|
return this.openAdapter();
|
|
1310
1422
|
}
|
|
1311
1423
|
async openConnection() {
|
|
1312
|
-
const { enableMultiTabs, useWebWorker, vfs, dbFilename, encryptionKey, temporaryStorage, cacheSizeKb } = this.options;
|
|
1424
|
+
const { enableMultiTabs, useWebWorker, vfs, dbFilename, encryptionKey, temporaryStorage, cacheSizeKb, preparedStatementsCache } = this.options;
|
|
1313
1425
|
if (!enableMultiTabs) {
|
|
1314
1426
|
this.logger.log({ level: LogLevels.warn, message: 'Multiple tabs are not enabled in this browser' });
|
|
1315
1427
|
}
|
|
@@ -1323,7 +1435,9 @@ class WASQLiteOpenFactory {
|
|
|
1323
1435
|
vfs,
|
|
1324
1436
|
encryptionKey,
|
|
1325
1437
|
temporaryStorage,
|
|
1326
|
-
cacheSizeKb
|
|
1438
|
+
cacheSizeKb,
|
|
1439
|
+
// TODO: Enable prepared statement cache by default?
|
|
1440
|
+
preparedStatementsCache: preparedStatementsCache ?? 0
|
|
1327
1441
|
};
|
|
1328
1442
|
}
|
|
1329
1443
|
if (useWebWorker) {
|
|
@@ -1372,10 +1486,11 @@ class WASQLiteOpenFactory {
|
|
|
1372
1486
|
// This VFS supports concurrent reads, so we can open additional workers to host read-only connections for
|
|
1373
1487
|
// concurrent reads / writes.
|
|
1374
1488
|
const additionalReadersCount = this.options.additionalReaders ?? 1;
|
|
1489
|
+
const additionalReaderPromises = [];
|
|
1375
1490
|
for (let i = 0; i < additionalReadersCount; i++) {
|
|
1376
|
-
|
|
1377
|
-
additionalReaders.push(reader);
|
|
1491
|
+
additionalReaderPromises.push(openDatabaseWorker(true));
|
|
1378
1492
|
}
|
|
1493
|
+
additionalReaders.push(...(await Promise.all(additionalReaderPromises)));
|
|
1379
1494
|
}
|
|
1380
1495
|
}
|
|
1381
1496
|
else {
|
|
@@ -1948,27 +2063,32 @@ class WebPowerSyncDatabase extends BasePowerSyncDatabase {
|
|
|
1948
2063
|
identifier: this.database.name,
|
|
1949
2064
|
logger: this.logger
|
|
1950
2065
|
};
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
2066
|
+
if (this.resolvedOpenOptions.ssrMode) {
|
|
2067
|
+
return new SSRStreamingSyncImplementation();
|
|
2068
|
+
}
|
|
2069
|
+
else if (this.resolvedOpenOptions.enableMultiTabs) {
|
|
2070
|
+
if (!this.enableBroadcastLogs) {
|
|
2071
|
+
const warning = `
|
|
1957
2072
|
Multiple tabs are enabled, but broadcasting of logs is disabled.
|
|
1958
2073
|
Logs for shared sync worker will only be available in the shared worker context
|
|
1959
2074
|
`;
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
2075
|
+
const logger = this.options.logger;
|
|
2076
|
+
logger ? logger.log({ level: LogLevels.warn, message: warning }) : console.warn(warning);
|
|
2077
|
+
}
|
|
2078
|
+
if ('shareConnection' in this.database) {
|
|
1963
2079
|
return new SharedWebStreamingSyncImplementation({
|
|
1964
2080
|
...syncOptions,
|
|
1965
2081
|
db: this.database, // This should always be the case
|
|
1966
2082
|
logLevel: this.options.sync?.logLevel ?? LogLevels.info,
|
|
1967
2083
|
enableBroadcastLogs: this.enableBroadcastLogs
|
|
1968
2084
|
});
|
|
1969
|
-
|
|
1970
|
-
|
|
2085
|
+
}
|
|
2086
|
+
this.logger.log({
|
|
2087
|
+
level: LogLevels.warn,
|
|
2088
|
+
message: "Not using a shared sync worker because the database adapter doesn't support it."
|
|
2089
|
+
});
|
|
1971
2090
|
}
|
|
2091
|
+
return new TabLocalStreamingSyncImplementation(syncOptions);
|
|
1972
2092
|
}
|
|
1973
2093
|
}
|
|
1974
2094
|
/**
|