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.
- package/dist/modern/DexieCloudOptions.d.ts +12 -0
- package/dist/modern/default-ui/AuthProviderButton.d.ts +21 -0
- package/dist/modern/default-ui/ProviderSelectionDialog.d.ts +7 -0
- package/dist/modern/default-ui/SelectDialog.d.ts +10 -0
- package/dist/modern/dexie-cloud-addon.js +132 -35
- package/dist/modern/dexie-cloud-addon.js.map +1 -1
- package/dist/modern/dexie-cloud-addon.min.js +1 -1
- package/dist/modern/dexie-cloud-addon.min.js.gz +0 -0
- package/dist/modern/dexie-cloud-addon.min.js.map +1 -1
- package/dist/modern/service-worker.js +269 -172
- package/dist/modern/service-worker.js.map +1 -1
- package/dist/modern/service-worker.min.js +1 -1
- package/dist/modern/service-worker.min.js.map +1 -1
- package/dist/modern/sync/blobOffloading.d.ts +5 -4
- package/dist/modern/sync/blobResolve.d.ts +3 -3
- package/dist/umd/DISABLE_SERVICEWORKER_STRATEGY.d.ts +1 -0
- package/dist/umd/DXCWebSocketStatus.d.ts +1 -0
- package/dist/umd/DexieCloudAPI.d.ts +75 -0
- package/dist/umd/DexieCloudOptions.d.ts +27 -0
- package/dist/umd/DexieCloudSyncOptions.d.ts +4 -0
- package/dist/umd/DexieCloudTable.d.ts +18 -0
- package/dist/umd/InvalidLicenseError.d.ts +5 -0
- package/dist/umd/Invite.d.ts +8 -0
- package/dist/umd/PermissionChecker.d.ts +15 -0
- package/dist/umd/TSON.d.ts +17 -0
- package/dist/umd/WSObservable.d.ts +72 -0
- package/dist/umd/associate.d.ts +1 -0
- package/dist/umd/authentication/AuthPersistedContext.d.ts +9 -0
- package/dist/umd/authentication/TokenErrorResponseError.d.ts +10 -0
- package/dist/umd/authentication/TokenExpiredError.d.ts +3 -0
- package/dist/umd/authentication/UNAUTHORIZED_USER.d.ts +2 -0
- package/dist/umd/authentication/authenticate.d.ts +13 -0
- package/dist/umd/authentication/currentUserObservable.d.ts +1 -0
- package/dist/umd/authentication/interactWithUser.d.ts +21 -0
- package/dist/umd/authentication/login.d.ts +3 -0
- package/dist/umd/authentication/logout.d.ts +5 -0
- package/dist/umd/authentication/otpFetchTokenCallback.d.ts +3 -0
- package/dist/umd/authentication/setCurrentUser.d.ts +14 -0
- package/dist/umd/authentication/waitUntil.d.ts +3 -0
- package/dist/umd/computeSyncState.d.ts +4 -0
- package/dist/umd/createSharedValueObservable.d.ts +3 -0
- package/dist/umd/currentUserEmitter.d.ts +3 -0
- package/dist/umd/db/DexieCloudDB.d.ts +61 -0
- package/dist/umd/db/entities/BaseRevisionMapEntry.d.ts +5 -0
- package/dist/umd/db/entities/EntityCommon.d.ts +5 -0
- package/dist/umd/db/entities/GuardedJob.d.ts +5 -0
- package/dist/umd/db/entities/Member.d.ts +19 -0
- package/dist/umd/db/entities/PersistedSyncState.d.ts +22 -0
- package/dist/umd/db/entities/Realm.d.ts +14 -0
- package/dist/umd/db/entities/Role.d.ts +11 -0
- package/dist/umd/db/entities/UserLogin.d.ts +23 -0
- package/dist/umd/default-ui/Dialog.d.ts +5 -0
- package/dist/umd/default-ui/LoginDialog.d.ts +3 -0
- package/dist/umd/default-ui/Styles.d.ts +3 -0
- package/dist/umd/default-ui/index.d.ts +24 -0
- package/dist/umd/define-ydoc-trigger.d.ts +3 -0
- package/dist/umd/dexie-cloud-addon.d.ts +3 -0
- package/dist/umd/dexie-cloud-addon.js +133 -36
- package/dist/umd/dexie-cloud-addon.js.gz +0 -0
- package/dist/umd/dexie-cloud-addon.js.map +1 -1
- package/dist/umd/dexie-cloud-addon.min.js +1 -1
- package/dist/umd/dexie-cloud-addon.min.js.gz +0 -0
- package/dist/umd/dexie-cloud-addon.min.js.map +1 -1
- package/dist/umd/dexie-cloud-client.d.ts +23 -0
- package/dist/umd/errors/HttpError.d.ts +5 -0
- package/dist/umd/extend-dexie-interface.d.ts +23 -0
- package/dist/umd/getGlobalRolesObservable.d.ts +5 -0
- package/dist/umd/getInternalAccessControlObservable.d.ts +12 -0
- package/dist/umd/getInvitesObservable.d.ts +23 -0
- package/dist/umd/getPermissionsLookupObservable.d.ts +16 -0
- package/dist/umd/getTiedRealmId.d.ts +2 -0
- package/dist/umd/helpers/BroadcastedAndLocalEvent.d.ts +8 -0
- package/dist/umd/helpers/CancelToken.d.ts +4 -0
- package/dist/umd/helpers/IS_SERVICE_WORKER.d.ts +1 -0
- package/dist/umd/helpers/SWBroadcastChannel.d.ts +12 -0
- package/dist/umd/helpers/allSettled.d.ts +1 -0
- package/dist/umd/helpers/bulkUpdate.d.ts +4 -0
- package/dist/umd/helpers/computeRealmSetHash.d.ts +2 -0
- package/dist/umd/helpers/date-constants.d.ts +5 -0
- package/dist/umd/helpers/flatten.d.ts +1 -0
- package/dist/umd/helpers/getMutationTable.d.ts +1 -0
- package/dist/umd/helpers/getSyncableTables.d.ts +4 -0
- package/dist/umd/helpers/getTableFromMutationTable.d.ts +1 -0
- package/dist/umd/helpers/makeArray.d.ts +1 -0
- package/dist/umd/helpers/randomString.d.ts +1 -0
- package/dist/umd/helpers/resolveText.d.ts +16 -0
- package/dist/umd/helpers/throwVersionIncrementNeeded.d.ts +1 -0
- package/dist/umd/helpers/visibilityState.d.ts +1 -0
- package/dist/umd/isEagerSyncDisabled.d.ts +2 -0
- package/dist/umd/isFirefox.d.ts +1 -0
- package/dist/umd/isSafari.d.ts +2 -0
- package/dist/umd/mapValueObservable.d.ts +5 -0
- package/dist/umd/mergePermissions.d.ts +2 -0
- package/dist/umd/middleware-helpers/guardedTable.d.ts +11 -0
- package/dist/umd/middleware-helpers/idGenerationHelpers.d.ts +18 -0
- package/dist/umd/middlewares/createIdGenerationMiddleware.d.ts +3 -0
- package/dist/umd/middlewares/createImplicitPropSetterMiddleware.d.ts +3 -0
- package/dist/umd/middlewares/createMutationTrackingMiddleware.d.ts +17 -0
- package/dist/umd/middlewares/outstandingTransaction.d.ts +4 -0
- package/dist/umd/overrideParseStoresSpec.d.ts +4 -0
- package/dist/umd/performInitialSync.d.ts +4 -0
- package/dist/umd/permissions.d.ts +9 -0
- package/dist/umd/prodLog.d.ts +9 -0
- package/dist/umd/service-worker.d.ts +1 -0
- package/dist/umd/service-worker.js +270 -173
- package/dist/umd/service-worker.js.map +1 -1
- package/dist/umd/service-worker.min.js +1 -1
- package/dist/umd/service-worker.min.js.map +1 -1
- package/dist/umd/sync/DEXIE_CLOUD_SYNCER_ID.d.ts +1 -0
- package/dist/umd/sync/LocalSyncWorker.d.ts +7 -0
- package/dist/umd/sync/SyncRequiredError.d.ts +3 -0
- package/dist/umd/sync/applyServerChanges.d.ts +3 -0
- package/dist/umd/sync/connectWebSocket.d.ts +2 -0
- package/dist/umd/sync/encodeIdsForServer.d.ts +4 -0
- package/dist/umd/sync/extractRealm.d.ts +2 -0
- package/dist/umd/sync/getLatestRevisionsPerTable.d.ts +6 -0
- package/dist/umd/sync/getTablesToSyncify.d.ts +3 -0
- package/dist/umd/sync/isOnline.d.ts +1 -0
- package/dist/umd/sync/isSyncNeeded.d.ts +2 -0
- package/dist/umd/sync/listClientChanges.d.ts +9 -0
- package/dist/umd/sync/listSyncifiedChanges.d.ts +5 -0
- package/dist/umd/sync/messageConsumerIsReady.d.ts +2 -0
- package/dist/umd/sync/messagesFromServerQueue.d.ts +8 -0
- package/dist/umd/sync/modifyLocalObjectsWithNewUserId.d.ts +4 -0
- package/dist/umd/sync/myId.d.ts +1 -0
- package/dist/umd/sync/numUnsyncedMutations.d.ts +2 -0
- package/dist/umd/sync/old_startSyncingClientChanges.d.ts +39 -0
- package/dist/umd/sync/performGuardedJob.d.ts +2 -0
- package/dist/umd/sync/ratelimit.d.ts +3 -0
- package/dist/umd/sync/registerSyncEvent.d.ts +3 -0
- package/dist/umd/sync/sync.d.ts +15 -0
- package/dist/umd/sync/syncIfPossible.d.ts +5 -0
- package/dist/umd/sync/syncWithServer.d.ts +6 -0
- package/dist/umd/sync/triggerSync.d.ts +2 -0
- package/dist/umd/sync/updateBaseRevs.d.ts +5 -0
- package/dist/umd/types/DXCAlert.d.ts +25 -0
- package/dist/umd/types/DXCInputField.d.ts +11 -0
- package/dist/umd/types/DXCUserInteraction.d.ts +93 -0
- package/dist/umd/types/NewIdOptions.d.ts +3 -0
- package/dist/umd/types/SWMessageEvent.d.ts +3 -0
- package/dist/umd/types/SWSyncEvent.d.ts +4 -0
- package/dist/umd/types/SyncState.d.ts +9 -0
- package/dist/umd/types/TXExpandos.d.ts +11 -0
- package/dist/umd/updateSchemaFromOptions.d.ts +3 -0
- package/dist/umd/userIsActive.d.ts +7 -0
- package/dist/umd/verifyConfig.d.ts +2 -0
- package/dist/umd/verifySchema.d.ts +2 -0
- package/dist/umd/yjs/YDexieCloudSyncState.d.ts +3 -0
- package/dist/umd/yjs/YTable.d.ts +3 -0
- package/dist/umd/yjs/applyYMessages.d.ts +9 -0
- package/dist/umd/yjs/awareness.d.ts +3 -0
- package/dist/umd/yjs/createYClientUpdateObservable.d.ts +4 -0
- package/dist/umd/yjs/createYHandler.d.ts +2 -0
- package/dist/umd/yjs/downloadYDocsFromServer.d.ts +3 -0
- package/dist/umd/yjs/getUpdatesTable.d.ts +3 -0
- package/dist/umd/yjs/listUpdatesSince.d.ts +3 -0
- package/dist/umd/yjs/listYClientMessagesAndStateVector.d.ts +26 -0
- package/dist/umd/yjs/reopenDocSignal.d.ts +10 -0
- package/dist/umd/yjs/updateYSyncStates.d.ts +6 -0
- 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.
|
|
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(
|
|
3987
|
-
return __awaiter(this,
|
|
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(
|
|
4056
|
-
return __awaiter(this,
|
|
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(
|
|
4073
|
-
return __awaiter(this,
|
|
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
|
|
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:
|
|
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.
|
|
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.
|
|
8703
|
+
dexieCloud.version = "4.4.2";
|
|
8607
8704
|
Dexie.Cloud = dexieCloud;
|
|
8608
8705
|
|
|
8609
8706
|
export { dexieCloud as default, defineYDocTrigger, dexieCloud, getTiedObjectId, getTiedRealmId, resolveText };
|