@powersync/web 2.1.0 → 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.
Files changed (62) hide show
  1. package/dist/index.react_native_web.js +212 -92
  2. package/dist/index.react_native_web.js.map +1 -1
  3. package/dist/worker/AccessHandlePoolVFS-BPUHfZME.js.map +1 -1
  4. package/dist/worker/FacadeVFS-d1ZDvud7.js.map +1 -1
  5. package/dist/worker/IDBBatchAtomicVFS-DbkDb777.js.map +1 -1
  6. package/dist/worker/MemoryVFS-DVJL5F8j.js.map +1 -1
  7. package/dist/worker/OPFSCoopSyncVFS-BgTiWPfa.js.map +1 -1
  8. package/dist/worker/OPFSWriteAheadVFS-Rt5CGCZP.js +2 -0
  9. package/dist/worker/OPFSWriteAheadVFS-Rt5CGCZP.js.map +1 -0
  10. package/dist/worker/mc-wa-sqlite-DDFgWP93.js.map +1 -1
  11. package/dist/worker/mc-wa-sqlite-async-lGclTjKJ.js.map +1 -1
  12. package/dist/worker/wa-sqlite-B0tZMM0j.js.map +1 -1
  13. package/dist/worker/wa-sqlite-async-CM6BmfRh.js.map +1 -1
  14. package/dist/worker/worker.js +1 -1
  15. package/dist/worker/worker.js.map +1 -1
  16. package/lib/db/PowerSyncDatabase.js +16 -11
  17. package/lib/db/PowerSyncDatabase.js.map +1 -1
  18. package/lib/db/adapters/AsyncWebAdapter.js +4 -55
  19. package/lib/db/adapters/AsyncWebAdapter.js.map +1 -1
  20. package/lib/db/adapters/acquireFromPool.d.ts +7 -0
  21. package/lib/db/adapters/acquireFromPool.js +60 -0
  22. package/lib/db/adapters/acquireFromPool.js.map +1 -0
  23. package/lib/db/adapters/memory-pool/client.d.ts +70 -0
  24. package/lib/db/adapters/memory-pool/client.js +269 -0
  25. package/lib/db/adapters/memory-pool/client.js.map +1 -0
  26. package/lib/db/adapters/memory-pool/shared.d.ts +51 -0
  27. package/lib/db/adapters/memory-pool/shared.js +16 -0
  28. package/lib/db/adapters/memory-pool/shared.js.map +1 -0
  29. package/lib/db/adapters/memory-pool/vfs.d.ts +19 -0
  30. package/lib/db/adapters/memory-pool/vfs.js +247 -0
  31. package/lib/db/adapters/memory-pool/vfs.js.map +1 -0
  32. package/lib/db/adapters/memory-pool/worker.d.ts +1 -0
  33. package/lib/db/adapters/memory-pool/worker.js +45 -0
  34. package/lib/db/adapters/memory-pool/worker.js.map +1 -0
  35. package/lib/db/adapters/options.d.ts +7 -0
  36. package/lib/db/adapters/options.js.map +1 -1
  37. package/lib/db/adapters/wa-sqlite/RawSqliteConnection.d.ts +8 -0
  38. package/lib/db/adapters/wa-sqlite/RawSqliteConnection.js +56 -5
  39. package/lib/db/adapters/wa-sqlite/RawSqliteConnection.js.map +1 -1
  40. package/lib/db/adapters/wa-sqlite/StatementCache.d.ts +16 -0
  41. package/lib/db/adapters/wa-sqlite/StatementCache.js +44 -0
  42. package/lib/db/adapters/wa-sqlite/StatementCache.js.map +1 -0
  43. package/lib/db/adapters/wa-sqlite/WASQLiteOpenFactory.js +7 -4
  44. package/lib/db/adapters/wa-sqlite/WASQLiteOpenFactory.js.map +1 -1
  45. package/lib/worker/db/MultiDatabaseServer.d.ts +1 -2
  46. package/lib/worker/db/MultiDatabaseServer.js +28 -17
  47. package/lib/worker/db/MultiDatabaseServer.js.map +1 -1
  48. package/package.json +15 -8
  49. package/src/db/PowerSyncDatabase.ts +18 -11
  50. package/src/db/adapters/AsyncWebAdapter.ts +13 -61
  51. package/src/db/adapters/acquireFromPool.ts +73 -0
  52. package/src/db/adapters/memory-pool/client.ts +367 -0
  53. package/src/db/adapters/memory-pool/shared.ts +79 -0
  54. package/src/db/adapters/memory-pool/vfs.ts +293 -0
  55. package/src/db/adapters/memory-pool/worker.ts +53 -0
  56. package/src/db/adapters/options.ts +8 -0
  57. package/src/db/adapters/wa-sqlite/RawSqliteConnection.ts +71 -6
  58. package/src/db/adapters/wa-sqlite/StatementCache.ts +50 -0
  59. package/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.ts +17 -5
  60. package/src/worker/db/MultiDatabaseServer.ts +29 -21
  61. package/dist/worker/OPFSWriteAheadVFS-BzodSqNq.js +0 -2
  62. 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 api = (this._sqliteAPI = await this.openSQLiteAPI());
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 api.statements(this.db, sql)) {
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
- await this.requireSqlite().close(this.db);
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.activeDatabases.get(name);
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.databaseOpenAttempt(logger, options);
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.databaseOpenAttempt(logger, options);
851
+ server ??= await this.#databaseOpenAttempt(logger, options);
757
852
  return server.connect(lockName);
758
853
  }
759
- async databaseOpenAttempt(logger, options) {
760
- return getNavigatorLocks().request(OPEN_DB_LOCK, async () => {
761
- const { filename, readonly, vfs } = options;
762
- let server = this.activeDatabases.get(filename);
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 = () => this.activeDatabases.delete(filename);
880
+ const onClose = () => activeDatabases.delete(filename);
785
881
  server = new DatabaseServer({
786
882
  inner: withSafeConcurrency,
787
883
  logger,
788
884
  onClose
789
885
  });
790
- this.activeDatabases.set(filename, server);
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.activeDatabases.values()];
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
- const abortController = new AbortController();
1125
- const abortSignal = abortController.signal;
1126
- let timeout = null;
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
- const reader = await openDatabaseWorker(true);
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
- switch (true) {
1952
- case this.resolvedOpenOptions.ssrMode:
1953
- return new SSRStreamingSyncImplementation();
1954
- case this.resolvedOpenOptions.enableMultiTabs:
1955
- if (!this.enableBroadcastLogs) {
1956
- const warning = `
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
- const logger = this.options.logger;
1961
- logger ? logger.log({ level: LogLevels.warn, message: warning }) : console.warn(warning);
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
- default:
1970
- return new TabLocalStreamingSyncImplementation(syncOptions);
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
  /**