dexie-cloud-addon 4.4.0 → 4.4.2

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 (160) hide show
  1. package/dist/modern/DexieCloudOptions.d.ts +12 -0
  2. package/dist/modern/default-ui/AuthProviderButton.d.ts +21 -0
  3. package/dist/modern/default-ui/ProviderSelectionDialog.d.ts +7 -0
  4. package/dist/modern/default-ui/SelectDialog.d.ts +10 -0
  5. package/dist/modern/dexie-cloud-addon.js +132 -35
  6. package/dist/modern/dexie-cloud-addon.js.map +1 -1
  7. package/dist/modern/dexie-cloud-addon.min.js +1 -1
  8. package/dist/modern/dexie-cloud-addon.min.js.gz +0 -0
  9. package/dist/modern/dexie-cloud-addon.min.js.map +1 -1
  10. package/dist/modern/service-worker.js +269 -172
  11. package/dist/modern/service-worker.js.map +1 -1
  12. package/dist/modern/service-worker.min.js +1 -1
  13. package/dist/modern/service-worker.min.js.map +1 -1
  14. package/dist/modern/sync/blobOffloading.d.ts +5 -4
  15. package/dist/modern/sync/blobResolve.d.ts +3 -3
  16. package/dist/umd/DISABLE_SERVICEWORKER_STRATEGY.d.ts +1 -0
  17. package/dist/umd/DXCWebSocketStatus.d.ts +1 -0
  18. package/dist/umd/DexieCloudAPI.d.ts +75 -0
  19. package/dist/umd/DexieCloudOptions.d.ts +27 -0
  20. package/dist/umd/DexieCloudSyncOptions.d.ts +4 -0
  21. package/dist/umd/DexieCloudTable.d.ts +18 -0
  22. package/dist/umd/InvalidLicenseError.d.ts +5 -0
  23. package/dist/umd/Invite.d.ts +8 -0
  24. package/dist/umd/PermissionChecker.d.ts +15 -0
  25. package/dist/umd/TSON.d.ts +17 -0
  26. package/dist/umd/WSObservable.d.ts +72 -0
  27. package/dist/umd/associate.d.ts +1 -0
  28. package/dist/umd/authentication/AuthPersistedContext.d.ts +9 -0
  29. package/dist/umd/authentication/TokenErrorResponseError.d.ts +10 -0
  30. package/dist/umd/authentication/TokenExpiredError.d.ts +3 -0
  31. package/dist/umd/authentication/UNAUTHORIZED_USER.d.ts +2 -0
  32. package/dist/umd/authentication/authenticate.d.ts +13 -0
  33. package/dist/umd/authentication/currentUserObservable.d.ts +1 -0
  34. package/dist/umd/authentication/interactWithUser.d.ts +21 -0
  35. package/dist/umd/authentication/login.d.ts +3 -0
  36. package/dist/umd/authentication/logout.d.ts +5 -0
  37. package/dist/umd/authentication/otpFetchTokenCallback.d.ts +3 -0
  38. package/dist/umd/authentication/setCurrentUser.d.ts +14 -0
  39. package/dist/umd/authentication/waitUntil.d.ts +3 -0
  40. package/dist/umd/computeSyncState.d.ts +4 -0
  41. package/dist/umd/createSharedValueObservable.d.ts +3 -0
  42. package/dist/umd/currentUserEmitter.d.ts +3 -0
  43. package/dist/umd/db/DexieCloudDB.d.ts +61 -0
  44. package/dist/umd/db/entities/BaseRevisionMapEntry.d.ts +5 -0
  45. package/dist/umd/db/entities/EntityCommon.d.ts +5 -0
  46. package/dist/umd/db/entities/GuardedJob.d.ts +5 -0
  47. package/dist/umd/db/entities/Member.d.ts +19 -0
  48. package/dist/umd/db/entities/PersistedSyncState.d.ts +22 -0
  49. package/dist/umd/db/entities/Realm.d.ts +14 -0
  50. package/dist/umd/db/entities/Role.d.ts +11 -0
  51. package/dist/umd/db/entities/UserLogin.d.ts +23 -0
  52. package/dist/umd/default-ui/Dialog.d.ts +5 -0
  53. package/dist/umd/default-ui/LoginDialog.d.ts +3 -0
  54. package/dist/umd/default-ui/Styles.d.ts +3 -0
  55. package/dist/umd/default-ui/index.d.ts +24 -0
  56. package/dist/umd/define-ydoc-trigger.d.ts +3 -0
  57. package/dist/umd/dexie-cloud-addon.d.ts +3 -0
  58. package/dist/umd/dexie-cloud-addon.js +133 -36
  59. package/dist/umd/dexie-cloud-addon.js.gz +0 -0
  60. package/dist/umd/dexie-cloud-addon.js.map +1 -1
  61. package/dist/umd/dexie-cloud-addon.min.js +1 -1
  62. package/dist/umd/dexie-cloud-addon.min.js.gz +0 -0
  63. package/dist/umd/dexie-cloud-addon.min.js.map +1 -1
  64. package/dist/umd/dexie-cloud-client.d.ts +23 -0
  65. package/dist/umd/errors/HttpError.d.ts +5 -0
  66. package/dist/umd/extend-dexie-interface.d.ts +23 -0
  67. package/dist/umd/getGlobalRolesObservable.d.ts +5 -0
  68. package/dist/umd/getInternalAccessControlObservable.d.ts +12 -0
  69. package/dist/umd/getInvitesObservable.d.ts +23 -0
  70. package/dist/umd/getPermissionsLookupObservable.d.ts +16 -0
  71. package/dist/umd/getTiedRealmId.d.ts +2 -0
  72. package/dist/umd/helpers/BroadcastedAndLocalEvent.d.ts +8 -0
  73. package/dist/umd/helpers/CancelToken.d.ts +4 -0
  74. package/dist/umd/helpers/IS_SERVICE_WORKER.d.ts +1 -0
  75. package/dist/umd/helpers/SWBroadcastChannel.d.ts +12 -0
  76. package/dist/umd/helpers/allSettled.d.ts +1 -0
  77. package/dist/umd/helpers/bulkUpdate.d.ts +4 -0
  78. package/dist/umd/helpers/computeRealmSetHash.d.ts +2 -0
  79. package/dist/umd/helpers/date-constants.d.ts +5 -0
  80. package/dist/umd/helpers/flatten.d.ts +1 -0
  81. package/dist/umd/helpers/getMutationTable.d.ts +1 -0
  82. package/dist/umd/helpers/getSyncableTables.d.ts +4 -0
  83. package/dist/umd/helpers/getTableFromMutationTable.d.ts +1 -0
  84. package/dist/umd/helpers/makeArray.d.ts +1 -0
  85. package/dist/umd/helpers/randomString.d.ts +1 -0
  86. package/dist/umd/helpers/resolveText.d.ts +16 -0
  87. package/dist/umd/helpers/throwVersionIncrementNeeded.d.ts +1 -0
  88. package/dist/umd/helpers/visibilityState.d.ts +1 -0
  89. package/dist/umd/isEagerSyncDisabled.d.ts +2 -0
  90. package/dist/umd/isFirefox.d.ts +1 -0
  91. package/dist/umd/isSafari.d.ts +2 -0
  92. package/dist/umd/mapValueObservable.d.ts +5 -0
  93. package/dist/umd/mergePermissions.d.ts +2 -0
  94. package/dist/umd/middleware-helpers/guardedTable.d.ts +11 -0
  95. package/dist/umd/middleware-helpers/idGenerationHelpers.d.ts +18 -0
  96. package/dist/umd/middlewares/createIdGenerationMiddleware.d.ts +3 -0
  97. package/dist/umd/middlewares/createImplicitPropSetterMiddleware.d.ts +3 -0
  98. package/dist/umd/middlewares/createMutationTrackingMiddleware.d.ts +17 -0
  99. package/dist/umd/middlewares/outstandingTransaction.d.ts +4 -0
  100. package/dist/umd/overrideParseStoresSpec.d.ts +4 -0
  101. package/dist/umd/performInitialSync.d.ts +4 -0
  102. package/dist/umd/permissions.d.ts +9 -0
  103. package/dist/umd/prodLog.d.ts +9 -0
  104. package/dist/umd/service-worker.d.ts +1 -0
  105. package/dist/umd/service-worker.js +270 -173
  106. package/dist/umd/service-worker.js.map +1 -1
  107. package/dist/umd/service-worker.min.js +1 -1
  108. package/dist/umd/service-worker.min.js.map +1 -1
  109. package/dist/umd/sync/DEXIE_CLOUD_SYNCER_ID.d.ts +1 -0
  110. package/dist/umd/sync/LocalSyncWorker.d.ts +7 -0
  111. package/dist/umd/sync/SyncRequiredError.d.ts +3 -0
  112. package/dist/umd/sync/applyServerChanges.d.ts +3 -0
  113. package/dist/umd/sync/connectWebSocket.d.ts +2 -0
  114. package/dist/umd/sync/encodeIdsForServer.d.ts +4 -0
  115. package/dist/umd/sync/extractRealm.d.ts +2 -0
  116. package/dist/umd/sync/getLatestRevisionsPerTable.d.ts +6 -0
  117. package/dist/umd/sync/getTablesToSyncify.d.ts +3 -0
  118. package/dist/umd/sync/isOnline.d.ts +1 -0
  119. package/dist/umd/sync/isSyncNeeded.d.ts +2 -0
  120. package/dist/umd/sync/listClientChanges.d.ts +9 -0
  121. package/dist/umd/sync/listSyncifiedChanges.d.ts +5 -0
  122. package/dist/umd/sync/messageConsumerIsReady.d.ts +2 -0
  123. package/dist/umd/sync/messagesFromServerQueue.d.ts +8 -0
  124. package/dist/umd/sync/modifyLocalObjectsWithNewUserId.d.ts +4 -0
  125. package/dist/umd/sync/myId.d.ts +1 -0
  126. package/dist/umd/sync/numUnsyncedMutations.d.ts +2 -0
  127. package/dist/umd/sync/old_startSyncingClientChanges.d.ts +39 -0
  128. package/dist/umd/sync/performGuardedJob.d.ts +2 -0
  129. package/dist/umd/sync/ratelimit.d.ts +3 -0
  130. package/dist/umd/sync/registerSyncEvent.d.ts +3 -0
  131. package/dist/umd/sync/sync.d.ts +15 -0
  132. package/dist/umd/sync/syncIfPossible.d.ts +5 -0
  133. package/dist/umd/sync/syncWithServer.d.ts +6 -0
  134. package/dist/umd/sync/triggerSync.d.ts +2 -0
  135. package/dist/umd/sync/updateBaseRevs.d.ts +5 -0
  136. package/dist/umd/types/DXCAlert.d.ts +25 -0
  137. package/dist/umd/types/DXCInputField.d.ts +11 -0
  138. package/dist/umd/types/DXCUserInteraction.d.ts +93 -0
  139. package/dist/umd/types/NewIdOptions.d.ts +3 -0
  140. package/dist/umd/types/SWMessageEvent.d.ts +3 -0
  141. package/dist/umd/types/SWSyncEvent.d.ts +4 -0
  142. package/dist/umd/types/SyncState.d.ts +9 -0
  143. package/dist/umd/types/TXExpandos.d.ts +11 -0
  144. package/dist/umd/updateSchemaFromOptions.d.ts +3 -0
  145. package/dist/umd/userIsActive.d.ts +7 -0
  146. package/dist/umd/verifyConfig.d.ts +2 -0
  147. package/dist/umd/verifySchema.d.ts +2 -0
  148. package/dist/umd/yjs/YDexieCloudSyncState.d.ts +3 -0
  149. package/dist/umd/yjs/YTable.d.ts +3 -0
  150. package/dist/umd/yjs/applyYMessages.d.ts +9 -0
  151. package/dist/umd/yjs/awareness.d.ts +3 -0
  152. package/dist/umd/yjs/createYClientUpdateObservable.d.ts +4 -0
  153. package/dist/umd/yjs/createYHandler.d.ts +2 -0
  154. package/dist/umd/yjs/downloadYDocsFromServer.d.ts +3 -0
  155. package/dist/umd/yjs/getUpdatesTable.d.ts +3 -0
  156. package/dist/umd/yjs/listUpdatesSince.d.ts +3 -0
  157. package/dist/umd/yjs/listYClientMessagesAndStateVector.d.ts +26 -0
  158. package/dist/umd/yjs/reopenDocSignal.d.ts +10 -0
  159. package/dist/umd/yjs/updateYSyncStates.d.ts +6 -0
  160. package/package.json +1 -1
@@ -50,4 +50,16 @@ export interface DexieCloudOptions {
50
50
  * Best for apps with large media that may not all be needed offline.
51
51
  */
52
52
  blobMode?: 'eager' | 'lazy';
53
+ /** Maximum string length (in characters) before offloading to blob storage during sync.
54
+ *
55
+ * Strings longer than this threshold are uploaded as blobs during sync,
56
+ * reducing sync payload size. The original string is kept intact in IndexedDB.
57
+ *
58
+ * Set to `Infinity` to disable string offloading.
59
+ * Minimum value is 100 to prevent accidental offloading of primary keys.
60
+ * Maximum value is 32768 (server limit).
61
+ *
62
+ * @default 32768
63
+ */
64
+ maxStringLength?: number;
53
65
  }
@@ -0,0 +1,21 @@
1
+ import { h } from 'preact';
2
+ import type { OAuthProviderInfo } from 'dexie-cloud-common';
3
+ export interface AuthProviderButtonProps {
4
+ provider: OAuthProviderInfo;
5
+ onClick: () => void;
6
+ }
7
+ /**
8
+ * Button component for OAuth provider login.
9
+ * Displays the provider's icon and name following provider branding guidelines.
10
+ */
11
+ export declare function AuthProviderButton({ provider, onClick }: AuthProviderButtonProps): h.JSX.Element;
12
+ /**
13
+ * Button for email/OTP authentication option.
14
+ */
15
+ export declare function OtpButton({ onClick }: {
16
+ onClick: () => void;
17
+ }): h.JSX.Element;
18
+ /**
19
+ * Visual divider with "or" text.
20
+ */
21
+ export declare function Divider(): h.JSX.Element;
@@ -0,0 +1,7 @@
1
+ import { h } from 'preact';
2
+ import { DXCProviderSelection } from '../types/DXCUserInteraction';
3
+ /**
4
+ * Dialog component for OAuth provider selection.
5
+ * Displays available OAuth providers as buttons and optionally an email/OTP option.
6
+ */
7
+ export declare function ProviderSelectionDialog({ title, alerts, providers, otpEnabled, cancelLabel, onSelectProvider, onSelectOtp, onCancel, }: DXCProviderSelection): h.JSX.Element;
@@ -0,0 +1,10 @@
1
+ import { h } from 'preact';
2
+ import { DXCSelect } from '../types/DXCUserInteraction';
3
+ /**
4
+ * Dialog component for generic option selection.
5
+ * Displays available options as buttons.
6
+ *
7
+ * This component is UI-agnostic and works with any DXCSelect interaction,
8
+ * whether for authentication, settings, or other selection purposes.
9
+ */
10
+ export declare function SelectDialog({ title, alerts, options, cancelLabel, onSelect, onCancel, }: DXCSelect): h.JSX.Element;
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * ==========================================================================
10
10
  *
11
- * Version 4.4.0, Wed Mar 18 2026
11
+ * Version 4.4.2, Thu Mar 19 2026
12
12
  *
13
13
  * https://dexie.org
14
14
  *
@@ -1365,10 +1365,48 @@ function isSerializedTSONRef(value) {
1365
1365
  obj._bt === undefined // Not a raw BlobRef
1366
1366
  );
1367
1367
  }
1368
+ /**
1369
+ * Recursively check if an object contains any BlobRefs
1370
+ */
1371
+ function hasBlobRefs(obj, visited = new WeakSet()) {
1372
+ if (obj === null || obj === undefined) {
1373
+ return false;
1374
+ }
1375
+ if (isBlobRef(obj)) {
1376
+ return true;
1377
+ }
1378
+ if (typeof obj !== 'object') {
1379
+ return false;
1380
+ }
1381
+ // Avoid circular references - check BEFORE processing
1382
+ if (visited.has(obj)) {
1383
+ return false;
1384
+ }
1385
+ visited.add(obj);
1386
+ // Skip special objects that can't contain BlobRefs
1387
+ if (obj instanceof Date || obj instanceof RegExp || obj instanceof Blob) {
1388
+ return false;
1389
+ }
1390
+ if (obj instanceof ArrayBuffer || ArrayBuffer.isView(obj)) {
1391
+ return false;
1392
+ }
1393
+ if (Array.isArray(obj)) {
1394
+ return obj.some(item => hasBlobRefs(item, visited));
1395
+ }
1396
+ // Only traverse POJOs
1397
+ if (obj.constructor === Object) {
1398
+ return Object.values(obj).some(value => hasBlobRefs(value, visited));
1399
+ }
1400
+ return false;
1401
+ }
1368
1402
  /**
1369
1403
  * Convert downloaded Uint8Array to the original type specified in BlobRef
1370
1404
  */
1371
1405
  function convertToOriginalType(data, ref) {
1406
+ // String type: decode UTF-8 back to string
1407
+ if (ref._bt === 'string') {
1408
+ return new TextDecoder().decode(data);
1409
+ }
1372
1410
  // Get the underlying ArrayBuffer (handle shared buffer case)
1373
1411
  const buffer = data.buffer.byteLength === data.byteLength
1374
1412
  ? data.buffer
@@ -3541,6 +3579,19 @@ function bulkUpdate(table, keys, changeSpecs) {
3541
3579
  });
3542
3580
  }
3543
3581
 
3582
+ /**
3583
+ * If the incoming value contains BlobRefs (e.g. offloaded strings or binaries),
3584
+ * mark it with _hasBlobRefs = 1 so the blobResolveMiddleware will resolve them
3585
+ * on the next read.
3586
+ */
3587
+ function markIfHasBlobRefs(obj) {
3588
+ if (obj !== null &&
3589
+ typeof obj === 'object' &&
3590
+ obj.constructor === Object &&
3591
+ hasBlobRefs(obj)) {
3592
+ obj._hasBlobRefs = 1;
3593
+ }
3594
+ }
3544
3595
  function applyServerChanges(changes, db) {
3545
3596
  return __awaiter(this, void 0, void 0, function* () {
3546
3597
  console.debug('Applying server changes', changes, Dexie.currentTransaction);
@@ -3576,6 +3627,7 @@ function applyServerChanges(changes, db) {
3576
3627
  const keys = mut.keys.map(keyDecoder);
3577
3628
  switch (mut.type) {
3578
3629
  case 'insert':
3630
+ mut.values.forEach(markIfHasBlobRefs);
3579
3631
  if (primaryKey.outbound) {
3580
3632
  yield table.bulkAdd(mut.values, keys);
3581
3633
  }
@@ -3588,6 +3640,7 @@ function applyServerChanges(changes, db) {
3588
3640
  }
3589
3641
  break;
3590
3642
  case 'upsert':
3643
+ mut.values.forEach(markIfHasBlobRefs);
3591
3644
  if (primaryKey.outbound) {
3592
3645
  yield table.bulkPut(mut.values, keys);
3593
3646
  }
@@ -3843,6 +3896,8 @@ function applyYServerMessages(yMessages, db) {
3843
3896
  */
3844
3897
  // Blobs >= 4KB are offloaded to blob storage
3845
3898
  const BLOB_OFFLOAD_THRESHOLD = 4096;
3899
+ // Default max string length before offloading (32KB characters)
3900
+ const DEFAULT_MAX_STRING_LENGTH = 32768;
3846
3901
  // Cache: once we know the server doesn't support blob storage, skip future uploads.
3847
3902
  // Maps databaseUrl → boolean (true = supported, false = not supported).
3848
3903
  const blobEndpointSupported = new Map();
@@ -3983,10 +4038,10 @@ function uploadBlob(databaseUrl, getCachedAccessToken, blob) {
3983
4038
  );
3984
4039
  });
3985
4040
  }
3986
- function offloadBlobsAndMarkDirty(obj, databaseUrl, getCachedAccessToken) {
3987
- return __awaiter(this, void 0, void 0, function* () {
4041
+ function offloadBlobsAndMarkDirty(obj_1, databaseUrl_1, getCachedAccessToken_1) {
4042
+ return __awaiter(this, arguments, void 0, function* (obj, databaseUrl, getCachedAccessToken, maxStringLength = DEFAULT_MAX_STRING_LENGTH) {
3988
4043
  const dirtyFlag = { dirty: false };
3989
- const result = yield offloadBlobs(obj, databaseUrl, getCachedAccessToken, dirtyFlag);
4044
+ const result = yield offloadBlobs(obj, databaseUrl, getCachedAccessToken, maxStringLength, dirtyFlag);
3990
4045
  // Mark the object as dirty for sync if any blobs were offloaded
3991
4046
  if (dirtyFlag.dirty && typeof result === 'object' && result !== null && result.constructor === Object) {
3992
4047
  result._hasBlobRefs = 1;
@@ -3999,10 +4054,26 @@ function offloadBlobsAndMarkDirty(obj, databaseUrl, getCachedAccessToken) {
3999
4054
  * Returns a new object with blobs replaced by BlobRefs
4000
4055
  */
4001
4056
  function offloadBlobs(obj_1, databaseUrl_1, getCachedAccessToken_1) {
4002
- return __awaiter(this, arguments, void 0, function* (obj, databaseUrl, getCachedAccessToken, dirtyFlag = { dirty: false }, visited = new WeakSet()) {
4057
+ return __awaiter(this, arguments, void 0, function* (obj, databaseUrl, getCachedAccessToken, maxStringLength = DEFAULT_MAX_STRING_LENGTH, dirtyFlag = { dirty: false }, visited = new WeakSet()) {
4003
4058
  if (obj === null || obj === undefined) {
4004
4059
  return obj;
4005
4060
  }
4061
+ // Check if this is a long string that should be offloaded
4062
+ if (typeof obj === 'string' && obj.length > maxStringLength && maxStringLength !== Infinity) {
4063
+ if (blobEndpointSupported.get(databaseUrl) === false) {
4064
+ return obj;
4065
+ }
4066
+ const blob = new Blob([obj], { type: 'text/plain;charset=utf-8' });
4067
+ const blobRef = yield uploadBlob(databaseUrl, getCachedAccessToken, blob);
4068
+ if (blobRef === null) {
4069
+ blobEndpointSupported.set(databaseUrl, false);
4070
+ return obj;
4071
+ }
4072
+ blobEndpointSupported.set(databaseUrl, true);
4073
+ dirtyFlag.dirty = true;
4074
+ // Mark as string type so it's resolved back to string, not Blob
4075
+ return Object.assign(Object.assign({}, blobRef), { _bt: 'string' });
4076
+ }
4006
4077
  // Check if this is a blob that should be offloaded
4007
4078
  if (shouldOffloadBlob(obj)) {
4008
4079
  if (blobEndpointSupported.get(databaseUrl) === false) {
@@ -4031,7 +4102,7 @@ function offloadBlobs(obj_1, databaseUrl_1, getCachedAccessToken_1) {
4031
4102
  if (Array.isArray(obj)) {
4032
4103
  const result = [];
4033
4104
  for (const item of obj) {
4034
- result.push(yield offloadBlobs(item, databaseUrl, getCachedAccessToken, dirtyFlag, visited));
4105
+ result.push(yield offloadBlobs(item, databaseUrl, getCachedAccessToken, maxStringLength, dirtyFlag, visited));
4035
4106
  }
4036
4107
  return result;
4037
4108
  }
@@ -4043,7 +4114,7 @@ function offloadBlobs(obj_1, databaseUrl_1, getCachedAccessToken_1) {
4043
4114
  }
4044
4115
  const result = {};
4045
4116
  for (const [key, value] of Object.entries(obj)) {
4046
- result[key] = yield offloadBlobs(value, databaseUrl, getCachedAccessToken, dirtyFlag, visited);
4117
+ result[key] = yield offloadBlobs(value, databaseUrl, getCachedAccessToken, maxStringLength, dirtyFlag, visited);
4047
4118
  }
4048
4119
  return result;
4049
4120
  });
@@ -4052,13 +4123,13 @@ function offloadBlobs(obj_1, databaseUrl_1, getCachedAccessToken_1) {
4052
4123
  * Process a DBOperationsSet and offload any large blobs
4053
4124
  * Returns a new DBOperationsSet with blobs replaced by BlobRefs
4054
4125
  */
4055
- function offloadBlobsInOperations(operations, databaseUrl, getCachedAccessToken) {
4056
- return __awaiter(this, void 0, void 0, function* () {
4126
+ function offloadBlobsInOperations(operations_1, databaseUrl_1, getCachedAccessToken_1) {
4127
+ return __awaiter(this, arguments, void 0, function* (operations, databaseUrl, getCachedAccessToken, maxStringLength = DEFAULT_MAX_STRING_LENGTH) {
4057
4128
  const result = [];
4058
4129
  for (const tableOps of operations) {
4059
4130
  const processedMuts = [];
4060
4131
  for (const mut of tableOps.muts) {
4061
- const processedMut = yield offloadBlobsInOperation(mut, databaseUrl, getCachedAccessToken);
4132
+ const processedMut = yield offloadBlobsInOperation(mut, databaseUrl, getCachedAccessToken, maxStringLength);
4062
4133
  processedMuts.push(processedMut);
4063
4134
  }
4064
4135
  result.push({
@@ -4069,20 +4140,20 @@ function offloadBlobsInOperations(operations, databaseUrl, getCachedAccessToken)
4069
4140
  return result;
4070
4141
  });
4071
4142
  }
4072
- function offloadBlobsInOperation(op, databaseUrl, getCachedAccessToken) {
4073
- return __awaiter(this, void 0, void 0, function* () {
4143
+ function offloadBlobsInOperation(op_1, databaseUrl_1, getCachedAccessToken_1) {
4144
+ return __awaiter(this, arguments, void 0, function* (op, databaseUrl, getCachedAccessToken, maxStringLength = DEFAULT_MAX_STRING_LENGTH) {
4074
4145
  switch (op.type) {
4075
4146
  case 'insert':
4076
4147
  case 'upsert': {
4077
- const processedValues = yield Promise.all(op.values.map(value => offloadBlobsAndMarkDirty(value, databaseUrl, getCachedAccessToken)));
4148
+ const processedValues = yield Promise.all(op.values.map(value => offloadBlobsAndMarkDirty(value, databaseUrl, getCachedAccessToken, maxStringLength)));
4078
4149
  return Object.assign(Object.assign({}, op), { values: processedValues });
4079
4150
  }
4080
4151
  case 'update': {
4081
- const processedChangeSpecs = yield Promise.all(op.changeSpecs.map(spec => offloadBlobsAndMarkDirty(spec, databaseUrl, getCachedAccessToken)));
4152
+ const processedChangeSpecs = yield Promise.all(op.changeSpecs.map(spec => offloadBlobsAndMarkDirty(spec, databaseUrl, getCachedAccessToken, maxStringLength)));
4082
4153
  return Object.assign(Object.assign({}, op), { changeSpecs: processedChangeSpecs });
4083
4154
  }
4084
4155
  case 'modify': {
4085
- const processedChangeSpec = yield offloadBlobsAndMarkDirty(op.changeSpec, databaseUrl, getCachedAccessToken);
4156
+ const processedChangeSpec = yield offloadBlobsAndMarkDirty(op.changeSpec, databaseUrl, getCachedAccessToken, maxStringLength);
4086
4157
  return Object.assign(Object.assign({}, op), { changeSpec: processedChangeSpec });
4087
4158
  }
4088
4159
  case 'delete':
@@ -4097,33 +4168,37 @@ function offloadBlobsInOperation(op, databaseUrl, getCachedAccessToken) {
4097
4168
  * Check if there are any large blobs in the operations that need offloading
4098
4169
  * This is a quick check to avoid unnecessary processing
4099
4170
  */
4100
- function hasLargeBlobsInOperations(operations) {
4171
+ function hasLargeBlobsInOperations(operations, maxStringLength = DEFAULT_MAX_STRING_LENGTH) {
4101
4172
  for (const tableOps of operations) {
4102
4173
  for (const mut of tableOps.muts) {
4103
- if (hasLargeBlobsInOperation(mut)) {
4174
+ if (hasLargeBlobsInOperation(mut, maxStringLength)) {
4104
4175
  return true;
4105
4176
  }
4106
4177
  }
4107
4178
  }
4108
4179
  return false;
4109
4180
  }
4110
- function hasLargeBlobsInOperation(op) {
4181
+ function hasLargeBlobsInOperation(op, maxStringLength) {
4111
4182
  switch (op.type) {
4112
4183
  case 'insert':
4113
4184
  case 'upsert':
4114
- return op.values.some(value => hasLargeBlobs(value));
4185
+ return op.values.some(value => hasLargeBlobs(value, maxStringLength));
4115
4186
  case 'update':
4116
- return op.changeSpecs.some(spec => hasLargeBlobs(spec));
4187
+ return op.changeSpecs.some(spec => hasLargeBlobs(spec, maxStringLength));
4117
4188
  case 'modify':
4118
- return hasLargeBlobs(op.changeSpec);
4189
+ return hasLargeBlobs(op.changeSpec, maxStringLength);
4119
4190
  default:
4120
4191
  return false;
4121
4192
  }
4122
4193
  }
4123
- function hasLargeBlobs(obj, visited = new WeakSet()) {
4194
+ function hasLargeBlobs(obj, maxStringLength, visited = new WeakSet()) {
4124
4195
  if (obj === null || obj === undefined) {
4125
4196
  return false;
4126
4197
  }
4198
+ // Check long strings
4199
+ if (typeof obj === 'string' && obj.length > maxStringLength && maxStringLength !== Infinity) {
4200
+ return true;
4201
+ }
4127
4202
  if (shouldOffloadBlob(obj)) {
4128
4203
  return true;
4129
4204
  }
@@ -4136,13 +4211,13 @@ function hasLargeBlobs(obj, visited = new WeakSet()) {
4136
4211
  }
4137
4212
  visited.add(obj);
4138
4213
  if (Array.isArray(obj)) {
4139
- return obj.some(item => hasLargeBlobs(item, visited));
4214
+ return obj.some(item => hasLargeBlobs(item, maxStringLength, visited));
4140
4215
  }
4141
4216
  // Traverse plain objects (POJO-like) - use duck typing since IndexedDB
4142
4217
  // may return objects where constructor !== Object
4143
4218
  const proto = Object.getPrototypeOf(obj);
4144
4219
  if (proto === Object.prototype || proto === null) {
4145
- return Object.values(obj).some(value => hasLargeBlobs(value, visited));
4220
+ return Object.values(obj).some(value => hasLargeBlobs(value, maxStringLength, visited));
4146
4221
  }
4147
4222
  return false;
4148
4223
  }
@@ -4403,7 +4478,7 @@ function _sync(db_1, options_1, schema_1) {
4403
4478
  return __awaiter(this, arguments, void 0, function* (db, options, schema, { isInitialSync, cancelToken, justCheckIfNeeded, purpose } = {
4404
4479
  isInitialSync: false,
4405
4480
  }) {
4406
- var _a;
4481
+ var _a, _b, _c;
4407
4482
  if (!justCheckIfNeeded) {
4408
4483
  console.debug('SYNC STARTED', { isInitialSync, purpose });
4409
4484
  }
@@ -4477,9 +4552,10 @@ function _sync(db_1, options_1, schema_1) {
4477
4552
  // Offload large blobs to blob storage before sync
4478
4553
  //
4479
4554
  let processedChangeSet = clientChangeSet;
4480
- const hasLargeBlobs = hasLargeBlobsInOperations(clientChangeSet);
4555
+ const maxStringLength = (_c = (_b = db.cloud.options) === null || _b === void 0 ? void 0 : _b.maxStringLength) !== null && _c !== void 0 ? _c : 32768;
4556
+ const hasLargeBlobs = hasLargeBlobsInOperations(clientChangeSet, maxStringLength);
4481
4557
  if (hasLargeBlobs) {
4482
- processedChangeSet = yield offloadBlobsInOperations(clientChangeSet, databaseUrl, () => loadCachedAccessToken(db));
4558
+ processedChangeSet = yield offloadBlobsInOperations(clientChangeSet, databaseUrl, () => loadCachedAccessToken(db), maxStringLength);
4483
4559
  }
4484
4560
  //
4485
4561
  // Push changes to server
@@ -4785,13 +4861,13 @@ function MessagesFromServerConsumer(db) {
4785
4861
  //triggerSync(db, 'pull');
4786
4862
  yield db.cloud.sync({ purpose: 'pull', wait: true });
4787
4863
  break;
4788
- case 'changes':
4864
+ case 'changes': {
4789
4865
  console.debug('changes');
4790
4866
  if (((_f = db.cloud.syncState.value) === null || _f === void 0 ? void 0 : _f.phase) === 'error') {
4791
4867
  triggerSync(db, 'pull');
4792
4868
  break;
4793
4869
  }
4794
- yield db.transaction('rw', db.dx.tables, (tx) => __awaiter(this, void 0, void 0, function* () {
4870
+ const didApplyChanges = yield db.transaction('rw', db.dx.tables, (tx) => __awaiter(this, void 0, void 0, function* () {
4795
4871
  // @ts-ignore
4796
4872
  tx.idbtrans.disableChangeTracking = true;
4797
4873
  // @ts-ignore
@@ -4808,7 +4884,7 @@ function MessagesFromServerConsumer(db) {
4808
4884
  schema,
4809
4885
  currentUser,
4810
4886
  });
4811
- return; // Initial sync must have taken place - otherwise, ignore this.
4887
+ return false; // Initial sync must have taken place - otherwise, ignore this.
4812
4888
  }
4813
4889
  // Verify again in ACID tx that we're on same server revision.
4814
4890
  if (msg.baseRev !== syncState.serverRevision) {
@@ -4829,7 +4905,7 @@ function MessagesFromServerConsumer(db) {
4829
4905
  // If we don't do a sync request now, we could stuck in an endless loop.
4830
4906
  triggerSync(db, 'pull');
4831
4907
  }
4832
- return; // Ignore message
4908
+ return false; // Ignore message
4833
4909
  }
4834
4910
  // Verify also that the message is based on the exact same set of realms
4835
4911
  const ourRealmSetHash = yield Dexie.waitFor(
@@ -4841,7 +4917,7 @@ function MessagesFromServerConsumer(db) {
4841
4917
  triggerSync(db, 'pull');
4842
4918
  // The message isn't based on the same realms.
4843
4919
  // Trigger a sync instead to resolve all things up.
4844
- return;
4920
+ return false;
4845
4921
  }
4846
4922
  // Get clientChanges
4847
4923
  let clientChanges = [];
@@ -4871,9 +4947,17 @@ function MessagesFromServerConsumer(db) {
4871
4947
  //
4872
4948
  console.debug('Updating syncState', syncState);
4873
4949
  yield db.$syncState.put(syncState, 'syncState');
4950
+ return true;
4874
4951
  }));
4875
4952
  console.debug('msg queue: done with rw transaction');
4953
+ // Trigger eager blob download for any BlobRefs received via WebSocket.
4954
+ // This mirrors the behavior after normal HTTP sync (syncCompleteEvent).
4955
+ // Only emit if changes were actually applied (not on early returns).
4956
+ if (didApplyChanges && msg.changes.length > 0) {
4957
+ db.syncCompleteEvent.next();
4958
+ }
4876
4959
  break;
4960
+ }
4877
4961
  }
4878
4962
  }
4879
4963
  catch (error) {
@@ -5844,7 +5928,7 @@ function createBlobResolveMiddleware(db) {
5844
5928
  return {
5845
5929
  stack: 'dbcore',
5846
5930
  name: 'blobResolve',
5847
- level: -2, // Run below other middlewares and after sync and caching middlewares
5931
+ level: 2, // Run above cache (0) and other middlewares (1) to resolve BlobRefs from cached data
5848
5932
  create(downlevelDatabase) {
5849
5933
  // Create a single queue instance for this database
5850
5934
  const blobSavingQueue = new BlobSavingQueue(db);
@@ -8189,7 +8273,7 @@ function dexieCloud(dexie) {
8189
8273
  const downloading$ = createDownloadingState();
8190
8274
  dexie.cloud = {
8191
8275
  // @ts-ignore
8192
- version: "4.4.0",
8276
+ version: "4.4.2",
8193
8277
  options: Object.assign({}, DEFAULT_OPTIONS),
8194
8278
  schema: null,
8195
8279
  get currentUserId() {
@@ -8217,6 +8301,19 @@ function dexieCloud(dexie) {
8217
8301
  invites: getInvitesObservable(dexie),
8218
8302
  roles: getGlobalRolesObservable(dexie),
8219
8303
  configure(options) {
8304
+ // Validate maxStringLength — Infinity disables offloading, otherwise must be
8305
+ // a finite number between 100 and the server limit (32768).
8306
+ // Minimum 100 prevents accidental offloading of primary keys and short strings
8307
+ // that would break sync.
8308
+ const MIN_STRING_LENGTH = 100;
8309
+ const MAX_SERVER_STRING_LENGTH = 32768;
8310
+ if (options.maxStringLength !== undefined &&
8311
+ options.maxStringLength !== Infinity &&
8312
+ (!Number.isFinite(options.maxStringLength) ||
8313
+ options.maxStringLength < MIN_STRING_LENGTH ||
8314
+ options.maxStringLength > MAX_SERVER_STRING_LENGTH)) {
8315
+ throw new Error(`maxStringLength must be Infinity or a finite number in [${MIN_STRING_LENGTH}, ${MAX_SERVER_STRING_LENGTH}]. Got: ${options.maxStringLength}`);
8316
+ }
8220
8317
  options = dexie.cloud.options = Object.assign(Object.assign({}, dexie.cloud.options), options);
8221
8318
  configuredProgramatically = true;
8222
8319
  if (options.databaseUrl && options.nameSuffix) {
@@ -8603,7 +8700,7 @@ function dexieCloud(dexie) {
8603
8700
  }
8604
8701
  }
8605
8702
  // @ts-ignore
8606
- dexieCloud.version = "4.4.0";
8703
+ dexieCloud.version = "4.4.2";
8607
8704
  Dexie.Cloud = dexieCloud;
8608
8705
 
8609
8706
  export { dexieCloud as default, defineYDocTrigger, dexieCloud, getTiedObjectId, getTiedRealmId, resolveText };