dexie-cloud-addon 4.0.0-beta.14 → 4.0.0-beta.17

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.
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * ==========================================================================
10
10
  *
11
- * Version 4.0.0-beta.14, Mon Dec 20 2021
11
+ * Version 4.0.0-beta.17, Fri Apr 08 2022
12
12
  *
13
13
  * https://dexie.org
14
14
  *
@@ -17,7 +17,7 @@
17
17
  */
18
18
 
19
19
  import Dexie, { cmp, liveQuery } from 'dexie';
20
- import { Observable as Observable$1, BehaviorSubject, Subject, from as from$1, fromEvent, of, merge, Subscription as Subscription$1, throwError, combineLatest, map as map$1, share, timer as timer$1, switchMap as switchMap$1 } from 'rxjs';
20
+ import { Observable as Observable$1, BehaviorSubject, Subject, fromEvent, of, merge, Subscription as Subscription$1, from as from$1, throwError, combineLatest, map as map$1, share, timer as timer$1, switchMap as switchMap$1 } from 'rxjs';
21
21
 
22
22
  //@ts-check
23
23
  const randomFillSync = crypto.getRandomValues;
@@ -4660,110 +4660,13 @@ function overrideParseStoresSpec(origFunc, dexie) {
4660
4660
  };
4661
4661
  }
4662
4662
 
4663
- const SECONDS = 1000;
4664
- const MINUTES = 60 * SECONDS;
4665
-
4666
- const myId = randomString(16);
4667
-
4668
- const GUARDED_JOB_HEARTBEAT = 1 * SECONDS;
4669
- const GUARDED_JOB_TIMEOUT = 1 * MINUTES;
4670
- async function performGuardedJob(db, jobName, jobsTableName, job, { awaitRemoteJob } = {}) {
4671
- // Start working.
4672
- //
4673
- // Check if someone else is working on this already.
4674
- //
4675
- const jobsTable = db.table(jobsTableName);
4676
- async function aquireLock() {
4677
- const gotTheLock = await db.transaction('rw!', jobsTableName, async () => {
4678
- const currentWork = await jobsTable.get(jobName);
4679
- if (!currentWork) {
4680
- // No one else is working. Let's record that we are.
4681
- await jobsTable.add({
4682
- nodeId: myId,
4683
- started: new Date(),
4684
- heartbeat: new Date()
4685
- }, jobName);
4686
- return true;
4687
- }
4688
- else if (currentWork.heartbeat.getTime() <
4689
- Date.now() - GUARDED_JOB_TIMEOUT) {
4690
- console.warn(`Latest ${jobName} worker seem to have died.\n`, `The dead job started:`, currentWork.started, `\n`, `Last heart beat was:`, currentWork.heartbeat, '\n', `We're now taking over!`);
4691
- // Now, take over!
4692
- await jobsTable.put({
4693
- nodeId: myId,
4694
- started: new Date(),
4695
- heartbeat: new Date()
4696
- }, jobName);
4697
- return true;
4698
- }
4699
- return false;
4700
- });
4701
- if (gotTheLock)
4702
- return true;
4703
- // Someone else took the job.
4704
- if (awaitRemoteJob) {
4705
- try {
4706
- const jobDoneObservable = from$1(liveQuery(() => jobsTable.get(jobName))).pipe(timeout(GUARDED_JOB_TIMEOUT), filter((job) => !job)); // Wait til job is not there anymore.
4707
- await jobDoneObservable.toPromise();
4708
- return false;
4709
- }
4710
- catch (err) {
4711
- if (err.name !== 'TimeoutError') {
4712
- throw err;
4713
- }
4714
- // Timeout stopped us! Try aquire the lock now.
4715
- // It will likely succeed this time unless
4716
- // another client took it.
4717
- return await aquireLock();
4718
- }
4719
- }
4720
- return false;
4721
- }
4722
- if (await aquireLock()) {
4723
- // We own the lock entry and can do our job undisturbed.
4724
- // We're not within a transaction, but these type of locks
4725
- // spans over transactions.
4726
- // Start our heart beat during the job.
4727
- // Use setInterval to make sure we are updating heartbeat even during long-lived fetch calls.
4728
- const heartbeat = setInterval(() => {
4729
- jobsTable.update(jobName, (job) => {
4730
- if (job.nodeId === myId) {
4731
- job.heartbeat = new Date();
4732
- }
4733
- });
4734
- }, GUARDED_JOB_HEARTBEAT);
4735
- try {
4736
- return await job();
4737
- }
4738
- finally {
4739
- // Stop heartbeat
4740
- clearInterval(heartbeat);
4741
- // Remove the persisted job state:
4742
- await db.transaction('rw!', jobsTableName, async () => {
4743
- const currentWork = await jobsTable.get(jobName);
4744
- if (currentWork && currentWork.nodeId === myId) {
4745
- jobsTable.delete(jobName);
4746
- }
4747
- });
4748
- }
4749
- }
4750
- }
4751
-
4752
4663
  async function performInitialSync(db, cloudOptions, cloudSchema) {
4753
- console.debug("Performing initial sync");
4754
- await performGuardedJob(db, 'initialSync', '$jobs', async () => {
4755
- // Even though caller has already checked it,
4756
- // Do check again (now within a transaction) that we really do not have a sync state:
4757
- const syncState = await db.getPersistedSyncState();
4758
- if (!syncState?.initiallySynced) {
4759
- await sync(db, cloudOptions, cloudSchema, { isInitialSync: true });
4760
- }
4761
- }, { awaitRemoteJob: true } // Don't return until the job is done!
4762
- );
4763
- console.debug("Done initial sync");
4664
+ console.debug('Performing initial sync');
4665
+ await sync(db, cloudOptions, cloudSchema, { isInitialSync: true });
4666
+ console.debug('Done initial sync');
4764
4667
  }
4765
4668
 
4766
- const USER_INACTIVITY_TIMEOUT = 300000; // 300_000;
4669
+ const USER_INACTIVITY_TIMEOUT = 180000; // 3 minutes
4767
4670
  const INACTIVE_WAIT_TIME = 20000;
4768
4671
  // This observable will be emitted to later down....
4769
4672
  const userIsActive = new BehaviorSubject(true);
@@ -4777,9 +4680,13 @@ const userIsActive = new BehaviorSubject(true);
4777
4680
  // for just a short time.
4778
4681
  const userIsReallyActive = new BehaviorSubject(true);
4779
4682
  userIsActive
4780
- .pipe(switchMap((isActive) => isActive
4781
- ? of(true)
4782
- : of(false).pipe(delay(INACTIVE_WAIT_TIME))), distinctUntilChanged())
4683
+ .pipe(switchMap((isActive) => {
4684
+ //console.debug('SyncStatus: DUBB: isActive changed to', isActive);
4685
+ return isActive
4686
+ ? of(true)
4687
+ : of(false).pipe(delay(INACTIVE_WAIT_TIME))
4688
+ ;
4689
+ }), distinctUntilChanged())
4783
4690
  .subscribe(userIsReallyActive);
4784
4691
  //
4785
4692
  // First create some corner-stone observables to build the flow on
@@ -4794,7 +4701,7 @@ const documentBecomesHidden = visibilityStateIsChanged.pipe(filter(() => documen
4794
4701
  const documentBecomesVisible = visibilityStateIsChanged.pipe(filter(() => document.visibilityState === 'visible'));
4795
4702
  // Any of various user-activity-related events happen:
4796
4703
  const userDoesSomething = typeof window !== 'undefined'
4797
- ? merge(documentBecomesVisible, fromEvent(window, 'mousemove'), fromEvent(window, 'keydown'), fromEvent(window, 'wheel'), fromEvent(window, 'touchmove'))
4704
+ ? merge(documentBecomesVisible, fromEvent(window, 'mousedown'), fromEvent(window, 'mousemove'), fromEvent(window, 'keydown'), fromEvent(window, 'wheel'), fromEvent(window, 'touchmove'))
4798
4705
  : of({});
4799
4706
  if (typeof document !== 'undefined') {
4800
4707
  //
@@ -4845,6 +4752,7 @@ class WSConnection extends Subscription$1 {
4845
4752
  constructor(databaseUrl, rev, realmSetHash, clientIdentity, token, tokenExpiration, subscriber, messageProducer, webSocketStatus) {
4846
4753
  super(() => this.teardown());
4847
4754
  this.id = ++counter;
4755
+ this.reconnecting = false;
4848
4756
  console.debug('New WebSocket Connection', this.id, token ? 'authorized' : 'unauthorized');
4849
4757
  this.databaseUrl = databaseUrl;
4850
4758
  this.rev = rev;
@@ -4864,7 +4772,7 @@ class WSConnection extends Subscription$1 {
4864
4772
  this.disconnect();
4865
4773
  }
4866
4774
  disconnect() {
4867
- this.webSocketStatus.next("disconnected");
4775
+ this.webSocketStatus.next('disconnected');
4868
4776
  if (this.pinger) {
4869
4777
  clearInterval(this.pinger);
4870
4778
  this.pinger = null;
@@ -4882,11 +4790,18 @@ class WSConnection extends Subscription$1 {
4882
4790
  }
4883
4791
  }
4884
4792
  reconnect() {
4885
- this.disconnect();
4886
- this.connect();
4793
+ if (this.reconnecting)
4794
+ return;
4795
+ this.reconnecting = true;
4796
+ try {
4797
+ this.disconnect();
4798
+ }
4799
+ catch { }
4800
+ this.connect()
4801
+ .catch(() => { })
4802
+ .then(() => (this.reconnecting = false)); // finally()
4887
4803
  }
4888
4804
  async connect() {
4889
- this.webSocketStatus.next("connecting");
4890
4805
  this.lastServerActivity = new Date();
4891
4806
  if (this.pauseUntil && this.pauseUntil > new Date()) {
4892
4807
  console.debug('WS not reconnecting just yet', {
@@ -4901,12 +4816,14 @@ class WSConnection extends Subscription$1 {
4901
4816
  if (!this.databaseUrl)
4902
4817
  throw new Error(`Cannot connect without a database URL`);
4903
4818
  if (this.closed) {
4819
+ //console.debug('SyncStatus: DUBB: Ooops it was closed!');
4904
4820
  return;
4905
4821
  }
4906
4822
  if (this.tokenExpiration && this.tokenExpiration < new Date()) {
4907
4823
  this.subscriber.error(new TokenExpiredError()); // Will be handled in connectWebSocket.ts.
4908
4824
  return;
4909
4825
  }
4826
+ this.webSocketStatus.next('connecting');
4910
4827
  this.pinger = setInterval(async () => {
4911
4828
  if (this.closed) {
4912
4829
  console.debug('pinger check', this.id, 'CLOSED.');
@@ -4953,7 +4870,7 @@ class WSConnection extends Subscription$1 {
4953
4870
  const searchParams = new URLSearchParams();
4954
4871
  if (this.subscriber.closed)
4955
4872
  return;
4956
- searchParams.set('v', "2");
4873
+ searchParams.set('v', '2');
4957
4874
  searchParams.set('rev', this.rev);
4958
4875
  searchParams.set('realmsHash', this.realmSetHash);
4959
4876
  searchParams.set('clientId', this.clientIdentity);
@@ -4992,23 +4909,30 @@ class WSConnection extends Subscription$1 {
4992
4909
  }
4993
4910
  };
4994
4911
  try {
4912
+ let everConnected = false;
4995
4913
  await new Promise((resolve, reject) => {
4996
4914
  ws.onopen = (event) => {
4997
4915
  console.debug('dexie-cloud WebSocket onopen');
4916
+ everConnected = true;
4998
4917
  resolve(null);
4999
4918
  };
5000
4919
  ws.onerror = (event) => {
5001
- const error = event.error || new Error('WebSocket Error');
5002
- this.disconnect();
5003
- this.subscriber.error(error);
5004
- this.webSocketStatus.next("error");
5005
- reject(error);
4920
+ if (!everConnected) {
4921
+ const error = event.error || new Error('WebSocket Error');
4922
+ this.subscriber.error(error);
4923
+ this.webSocketStatus.next('error');
4924
+ reject(error);
4925
+ }
4926
+ else {
4927
+ this.reconnect();
4928
+ }
5006
4929
  };
5007
4930
  });
5008
- this.messageProducerSubscription = this.messageProducer.subscribe(msg => {
4931
+ this.messageProducerSubscription = this.messageProducer.subscribe((msg) => {
5009
4932
  if (!this.closed) {
5010
- if (msg.type === 'ready' && this.webSocketStatus.value !== 'connected') {
5011
- this.webSocketStatus.next("connected");
4933
+ if (msg.type === 'ready' &&
4934
+ this.webSocketStatus.value !== 'connected') {
4935
+ this.webSocketStatus.next('connected');
5012
4936
  }
5013
4937
  this.ws?.send(TSON.stringify(msg));
5014
4938
  }
@@ -5045,9 +4969,9 @@ function connectWebSocket(db) {
5045
4969
  rev: syncState.serverRevision,
5046
4970
  })));
5047
4971
  function createObservable() {
5048
- return db.cloud.persistedSyncState.pipe(filter(syncState => syncState?.serverRevision), // Don't connect before there's no initial sync performed.
4972
+ return db.cloud.persistedSyncState.pipe(filter((syncState) => syncState?.serverRevision), // Don't connect before there's no initial sync performed.
5049
4973
  take(1), // Don't continue waking up whenever syncState change
5050
- switchMap((syncState) => db.cloud.currentUser.pipe(map(userLogin => [userLogin, syncState]))), switchMap(([userLogin, syncState]) => userIsReallyActive.pipe(map((isActive) => [isActive ? userLogin : null, syncState]))), switchMap(async ([userLogin, syncState]) => [userLogin, await computeRealmSetHash(syncState)]), switchMap(([userLogin, realmSetHash]) =>
4974
+ switchMap((syncState) => db.cloud.currentUser.pipe(map((userLogin) => [userLogin, syncState]))), switchMap(([userLogin, syncState]) => userIsReallyActive.pipe(map((isActive) => [isActive ? userLogin : null, syncState]))), switchMap(async ([userLogin, syncState]) => [userLogin, await computeRealmSetHash(syncState)]), switchMap(([userLogin, realmSetHash]) =>
5051
4975
  // Let server end query changes from last entry of same client-ID and forward.
5052
4976
  // If no new entries, server won't bother the client. If new entries, server sends only those
5053
4977
  // and the baseRev of the last from same client-ID.
@@ -5070,7 +4994,10 @@ function connectWebSocket(db) {
5070
4994
  else {
5071
4995
  return throwError(error);
5072
4996
  }
5073
- }), catchError((error) => from$1(waitAndReconnectWhenUserDoesSomething(error)).pipe(switchMap(() => createObservable()))));
4997
+ }), catchError((error) => {
4998
+ db.cloud.webSocketStatus.next("error");
4999
+ return from$1(waitAndReconnectWhenUserDoesSomething(error)).pipe(switchMap(() => createObservable()));
5000
+ }));
5074
5001
  }
5075
5002
  return createObservable().subscribe((msg) => {
5076
5003
  if (msg) {
@@ -5090,6 +5017,95 @@ async function isSyncNeeded(db) {
5090
5017
  : false;
5091
5018
  }
5092
5019
 
5020
+ const SECONDS = 1000;
5021
+ const MINUTES = 60 * SECONDS;
5022
+
5023
+ const myId = randomString(16);
5024
+
5025
+ const GUARDED_JOB_HEARTBEAT = 1 * SECONDS;
5026
+ const GUARDED_JOB_TIMEOUT = 1 * MINUTES;
5027
+ async function performGuardedJob(db, jobName, jobsTableName, job, { awaitRemoteJob } = {}) {
5028
+ // Start working.
5029
+ //
5030
+ // Check if someone else is working on this already.
5031
+ //
5032
+ const jobsTable = db.table(jobsTableName);
5033
+ async function aquireLock() {
5034
+ const gotTheLock = await db.transaction('rw!', jobsTableName, async () => {
5035
+ const currentWork = await jobsTable.get(jobName);
5036
+ if (!currentWork) {
5037
+ // No one else is working. Let's record that we are.
5038
+ await jobsTable.add({
5039
+ nodeId: myId,
5040
+ started: new Date(),
5041
+ heartbeat: new Date()
5042
+ }, jobName);
5043
+ return true;
5044
+ }
5045
+ else if (currentWork.heartbeat.getTime() <
5046
+ Date.now() - GUARDED_JOB_TIMEOUT) {
5047
+ console.warn(`Latest ${jobName} worker seem to have died.\n`, `The dead job started:`, currentWork.started, `\n`, `Last heart beat was:`, currentWork.heartbeat, '\n', `We're now taking over!`);
5048
+ // Now, take over!
5049
+ await jobsTable.put({
5050
+ nodeId: myId,
5051
+ started: new Date(),
5052
+ heartbeat: new Date()
5053
+ }, jobName);
5054
+ return true;
5055
+ }
5056
+ return false;
5057
+ });
5058
+ if (gotTheLock)
5059
+ return true;
5060
+ // Someone else took the job.
5061
+ if (awaitRemoteJob) {
5062
+ try {
5063
+ const jobDoneObservable = from$1(liveQuery(() => jobsTable.get(jobName))).pipe(timeout(GUARDED_JOB_TIMEOUT), filter((job) => !job)); // Wait til job is not there anymore.
5064
+ await jobDoneObservable.toPromise();
5065
+ return false;
5066
+ }
5067
+ catch (err) {
5068
+ if (err.name !== 'TimeoutError') {
5069
+ throw err;
5070
+ }
5071
+ // Timeout stopped us! Try aquire the lock now.
5072
+ // It will likely succeed this time unless
5073
+ // another client took it.
5074
+ return await aquireLock();
5075
+ }
5076
+ }
5077
+ return false;
5078
+ }
5079
+ if (await aquireLock()) {
5080
+ // We own the lock entry and can do our job undisturbed.
5081
+ // We're not within a transaction, but these type of locks
5082
+ // spans over transactions.
5083
+ // Start our heart beat during the job.
5084
+ // Use setInterval to make sure we are updating heartbeat even during long-lived fetch calls.
5085
+ const heartbeat = setInterval(() => {
5086
+ jobsTable.update(jobName, (job) => {
5087
+ if (job.nodeId === myId) {
5088
+ job.heartbeat = new Date();
5089
+ }
5090
+ });
5091
+ }, GUARDED_JOB_HEARTBEAT);
5092
+ try {
5093
+ return await job();
5094
+ }
5095
+ finally {
5096
+ // Stop heartbeat
5097
+ clearInterval(heartbeat);
5098
+ // Remove the persisted job state:
5099
+ await db.transaction('rw!', jobsTableName, async () => {
5100
+ const currentWork = await jobsTable.get(jobName);
5101
+ if (currentWork && currentWork.nodeId === myId) {
5102
+ await jobsTable.delete(jobName);
5103
+ }
5104
+ });
5105
+ }
5106
+ }
5107
+ }
5108
+
5093
5109
  const ongoingSyncs = new WeakMap();
5094
5110
  function syncIfPossible(db, cloudOptions, cloudSchema, options) {
5095
5111
  const ongoing = ongoingSyncs.get(db);
@@ -5494,6 +5510,21 @@ function createSharedValueObservable(o, defaultValue) {
5494
5510
  return rv;
5495
5511
  }
5496
5512
 
5513
+ const getGlobalRolesObservable = associate((db) => {
5514
+ return createSharedValueObservable(liveQuery(() => db.roles
5515
+ .where({ realmId: 'rlm-public' })
5516
+ .toArray()
5517
+ .then((roles) => {
5518
+ const rv = {};
5519
+ for (const role of roles
5520
+ .slice()
5521
+ .sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0))) {
5522
+ rv[role.name] = role;
5523
+ }
5524
+ return rv;
5525
+ })), {});
5526
+ });
5527
+
5497
5528
  const getCurrentUserEmitter = associate((db) => new BehaviorSubject(UNAUTHORIZED_USER));
5498
5529
 
5499
5530
  const getInternalAccessControlObservable = associate((db) => {
@@ -5595,18 +5626,38 @@ function mergePermissions(...permissions) {
5595
5626
  }
5596
5627
 
5597
5628
  const getPermissionsLookupObservable = associate((db) => {
5598
- const o = getInternalAccessControlObservable(db._novip);
5599
- return mapValueObservable(o, ({ selfMembers, realms, userId }) => {
5629
+ const o = createSharedValueObservable(combineLatest([
5630
+ getInternalAccessControlObservable(db._novip),
5631
+ getGlobalRolesObservable(db._novip),
5632
+ ]).pipe(map(([{ selfMembers, realms, userId }, globalRoles]) => ({
5633
+ selfMembers,
5634
+ realms,
5635
+ userId,
5636
+ globalRoles,
5637
+ }))), {
5638
+ selfMembers: [],
5639
+ realms: [],
5640
+ userId: UNAUTHORIZED_USER.userId,
5641
+ globalRoles: {},
5642
+ });
5643
+ return mapValueObservable(o, ({ selfMembers, realms, userId, globalRoles }) => {
5600
5644
  const rv = realms
5601
- .map((realm) => ({
5602
- ...realm,
5603
- permissions: realm.owner === userId
5604
- ? { manage: '*' }
5605
- : mergePermissions(...selfMembers
5606
- .filter((m) => m.realmId === realm.realmId)
5607
- .map((m) => m.permissions)
5608
- .filter((p) => p)),
5609
- }))
5645
+ .map((realm) => {
5646
+ const selfRealmMembers = selfMembers.filter((m) => m.realmId === realm.realmId);
5647
+ const directPermissionSets = selfRealmMembers
5648
+ .map((m) => m.permissions)
5649
+ .filter((p) => p);
5650
+ const rolePermissionSets = flatten(selfRealmMembers.map((m) => m.roles).filter((roleName) => roleName))
5651
+ .map((role) => globalRoles[role])
5652
+ .filter((role) => role)
5653
+ .map((role) => role.permissions);
5654
+ return {
5655
+ ...realm,
5656
+ permissions: realm.owner === userId
5657
+ ? { manage: '*' }
5658
+ : mergePermissions(...directPermissionSets, ...rolePermissionSets),
5659
+ };
5660
+ })
5610
5661
  .reduce((p, c) => ({ ...p, [c.realmId]: c }), {
5611
5662
  [userId]: {
5612
5663
  realmId: userId,
@@ -5690,7 +5741,7 @@ function permissions(dexie, obj, tableName) {
5690
5741
  const realm = permissionsLookup[realmId || dexie.cloud.currentUserId];
5691
5742
  if (!realm)
5692
5743
  return new PermissionChecker({}, tableName, !owner || owner === dexie.cloud.currentUserId);
5693
- return new PermissionChecker(realm.permissions, tableName, !owner || owner === dexie.cloud.currentUserId);
5744
+ return new PermissionChecker(realm.permissions, tableName, realmId === dexie.cloud.currentUserId || owner === dexie.cloud.currentUserId);
5694
5745
  };
5695
5746
  const o = source.pipe(map(mapper));
5696
5747
  o.getValue = () => mapper(source.getValue());
@@ -5726,6 +5777,7 @@ function dexieCloud(dexie) {
5726
5777
  //
5727
5778
  const currentUserEmitter = getCurrentUserEmitter(dexie);
5728
5779
  const subscriptions = [];
5780
+ let configuredProgramatically = false;
5729
5781
  // local sync worker - used when there's no service worker.
5730
5782
  let localSyncWorker = null;
5731
5783
  dexie.on('ready', async (dexie) => {
@@ -5752,7 +5804,7 @@ function dexieCloud(dexie) {
5752
5804
  currentUserEmitter.next(UNAUTHORIZED_USER);
5753
5805
  });
5754
5806
  dexie.cloud = {
5755
- version: '4.0.0-beta.14',
5807
+ version: '4.0.0-beta.17',
5756
5808
  options: { ...DEFAULT_OPTIONS },
5757
5809
  schema: null,
5758
5810
  serverState: null,
@@ -5773,8 +5825,10 @@ function dexieCloud(dexie) {
5773
5825
  await login(db, hint);
5774
5826
  },
5775
5827
  invites: getInvitesObservable(dexie),
5828
+ roles: getGlobalRolesObservable(dexie),
5776
5829
  configure(options) {
5777
5830
  options = dexie.cloud.options = { ...dexie.cloud.options, ...options };
5831
+ configuredProgramatically = true;
5778
5832
  if (options.databaseUrl && options.nameSuffix) {
5779
5833
  // @ts-ignore
5780
5834
  dexie.name = `${origIdbName}-${getDbNameFromDbUrl(options.databaseUrl)}`;
@@ -5861,7 +5915,7 @@ function dexieCloud(dexie) {
5861
5915
  db.getSchema(),
5862
5916
  db.getPersistedSyncState(),
5863
5917
  ]);
5864
- if (!options) {
5918
+ if (!configuredProgramatically) {
5865
5919
  // Options not specified programatically (use case for SW!)
5866
5920
  // Take persisted options:
5867
5921
  db.cloud.options = persistedOptions || null;
@@ -5869,6 +5923,8 @@ function dexieCloud(dexie) {
5869
5923
  else if (!persistedOptions ||
5870
5924
  JSON.stringify(persistedOptions) !== JSON.stringify(options)) {
5871
5925
  // Update persisted options:
5926
+ if (!options)
5927
+ throw new Error(`Internal error`); // options cannot be null if configuredProgramatically is set.
5872
5928
  await db.$syncState.put(options, 'options');
5873
5929
  }
5874
5930
  if (db.cloud.options?.tryUseServiceWorker &&
@@ -5990,7 +6046,7 @@ function dexieCloud(dexie) {
5990
6046
  }
5991
6047
  }
5992
6048
  }
5993
- dexieCloud.version = '4.0.0-beta.14';
6049
+ dexieCloud.version = '4.0.0-beta.17';
5994
6050
  Dexie.Cloud = dexieCloud;
5995
6051
 
5996
6052
  export { dexieCloud as default, dexieCloud, getTiedObjectId, getTiedRealmId };