dexie-cloud-addon 4.4.11 → 4.4.13
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/dexie-cloud-addon.js +603 -451
- 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.map +1 -1
- package/dist/modern/middlewares/blobResolveMiddleware.d.ts +5 -4
- package/dist/modern/service-worker.js +409 -257
- 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/BlobDownloadTracker.d.ts +80 -20
- package/dist/modern/sync/BlobSavingQueue.d.ts +20 -2
- package/dist/modern/sync/eagerBlobDownloader.d.ts +37 -3
- package/dist/modern/types/TXExpandos.d.ts +2 -0
- package/dist/umd/dexie-cloud-addon.js +610 -458
- 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.map +1 -1
- package/dist/umd/service-worker.js +411 -259
- 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/package.json +4 -4
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* ==========================================================================
|
|
10
10
|
*
|
|
11
|
-
* Version 4.4.
|
|
11
|
+
* Version 4.4.13, Wed May 27 2026
|
|
12
12
|
*
|
|
13
13
|
* https://dexie.org
|
|
14
14
|
*
|
|
@@ -2923,192 +2923,145 @@
|
|
|
2923
2923
|
}
|
|
2924
2924
|
|
|
2925
2925
|
/**
|
|
2926
|
-
*
|
|
2926
|
+
* BlobSavingQueue - Queues resolved blobs for saving back to IndexedDB.
|
|
2927
2927
|
*
|
|
2928
|
-
*
|
|
2929
|
-
*
|
|
2928
|
+
* This is an internal collaborator of BlobDownloadTracker and is not
|
|
2929
|
+
* intended to be used directly by middleware or other code. See
|
|
2930
|
+
* BlobDownloadTracker.enqueueSave().
|
|
2930
2931
|
*
|
|
2931
|
-
*
|
|
2932
|
-
*
|
|
2933
|
-
|
|
2934
|
-
/**
|
|
2935
|
-
* Download all unresolved blobs in the background.
|
|
2936
|
-
*
|
|
2937
|
-
* This is called when blobMode='eager' (default) after sync completes.
|
|
2938
|
-
* BlobRef URLs are signed (SAS tokens) so no auth header needed.
|
|
2932
|
+
* Uses setTimeout(fn, 0) instead of queueMicrotask to completely isolate
|
|
2933
|
+
* from Dexie's Promise.PSD context. This prevents the save operation
|
|
2934
|
+
* from inheriting any ongoing transaction.
|
|
2939
2935
|
*
|
|
2940
|
-
* Each blob is saved atomically using
|
|
2936
|
+
* Each blob is saved atomically using downCore transaction with the specific
|
|
2937
|
+
* keyPath to avoid race conditions with other property changes.
|
|
2941
2938
|
*/
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2939
|
+
class BlobSavingQueue {
|
|
2940
|
+
constructor(db, onPersisted) {
|
|
2941
|
+
this.queue = [];
|
|
2942
|
+
this.isProcessing = false;
|
|
2943
|
+
this.drainResolvers = [];
|
|
2944
|
+
this.db = db;
|
|
2945
|
+
this.onPersisted = onPersisted;
|
|
2946
|
+
}
|
|
2947
|
+
/**
|
|
2948
|
+
* Queue a resolved blob for saving.
|
|
2949
|
+
* Only the specific blob property will be updated atomically.
|
|
2950
|
+
*/
|
|
2951
|
+
saveBlobs(tableName, primaryKey, resolvedBlobs) {
|
|
2952
|
+
this.queue.push({
|
|
2953
|
+
tableName,
|
|
2954
|
+
primaryKey,
|
|
2955
|
+
resolvedBlobs,
|
|
2956
|
+
});
|
|
2957
|
+
this.startConsumer();
|
|
2958
|
+
}
|
|
2959
|
+
/**
|
|
2960
|
+
* Returns a promise that resolves when the queue is empty AND no item
|
|
2961
|
+
* is currently being processed. Used by callers that need to know when
|
|
2962
|
+
* all previously enqueued saves have been persisted to IndexedDB before
|
|
2963
|
+
* making decisions based on the on-disk state (e.g., the eager blob
|
|
2964
|
+
* downloader looping over `_hasBlobRefs=1` rows in chunks).
|
|
2965
|
+
*
|
|
2966
|
+
* Note: New work enqueued AFTER drain() is called does NOT extend the
|
|
2967
|
+
* wait. Callers that race against concurrent producers should treat the
|
|
2968
|
+
* returned promise as "queue was empty at some point after this call".
|
|
2969
|
+
*/
|
|
2970
|
+
drain() {
|
|
2971
|
+
if (!this.isProcessing && this.queue.length === 0) {
|
|
2972
|
+
return Promise.resolve();
|
|
2964
2973
|
}
|
|
2965
|
-
|
|
2966
|
-
|
|
2974
|
+
return new Promise((resolve) => {
|
|
2975
|
+
this.drainResolvers.push(resolve);
|
|
2976
|
+
});
|
|
2977
|
+
}
|
|
2978
|
+
/**
|
|
2979
|
+
* Start the consumer if not already processing.
|
|
2980
|
+
* Uses setTimeout(fn, 0) to completely break out of any
|
|
2981
|
+
* Dexie transaction context (Promise.PSD).
|
|
2982
|
+
*/
|
|
2983
|
+
startConsumer() {
|
|
2984
|
+
if (this.isProcessing)
|
|
2967
2985
|
return;
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
// Filter to actionable objects first
|
|
2993
|
-
const pending = unresolvedObjects.filter((obj) => {
|
|
2994
|
-
if (!hasUnresolvedBlobRefs(obj))
|
|
2995
|
-
return false;
|
|
2996
|
-
const key = primaryKey.keyPath
|
|
2997
|
-
? Dexie.getByKeyPath(obj, primaryKey.keyPath)
|
|
2998
|
-
: undefined;
|
|
2999
|
-
return key !== undefined;
|
|
3000
|
-
});
|
|
3001
|
-
// Process in parallel with concurrency limit
|
|
3002
|
-
let i = 0;
|
|
3003
|
-
const runNext = () => __awaiter(this, void 0, void 0, function* () {
|
|
3004
|
-
while (i < pending.length) {
|
|
3005
|
-
if (signal === null || signal === void 0 ? void 0 : signal.aborted)
|
|
3006
|
-
;
|
|
3007
|
-
const obj = pending[i++];
|
|
3008
|
-
const key = Dexie.getByKeyPath(obj, primaryKey.keyPath);
|
|
3009
|
-
try {
|
|
3010
|
-
// Refresh token per object — cheap (returns cached) but ensures
|
|
3011
|
-
// we pick up renewed tokens during long download sessions.
|
|
3012
|
-
const resolvedBlobs = [];
|
|
3013
|
-
yield resolveAllBlobRefs(obj, databaseUrl, resolvedBlobs, '', new WeakMap(), db.blobDownloadTracker);
|
|
3014
|
-
const updateSpec = {
|
|
3015
|
-
_hasBlobRefs: undefined,
|
|
3016
|
-
};
|
|
3017
|
-
for (const blob of resolvedBlobs) {
|
|
3018
|
-
updateSpec[blob.keyPath] = blob.data;
|
|
3019
|
-
}
|
|
3020
|
-
debugLog(`Eager download: Updating ${table.name}:${key} with ${resolvedBlobs.length} blobs`);
|
|
3021
|
-
yield table.update(key, updateSpec);
|
|
3022
|
-
// liveQuery in blobProgress.ts auto-detects this change
|
|
3023
|
-
}
|
|
3024
|
-
catch (err) {
|
|
3025
|
-
console.error(`Failed to download blobs for ${table.name}:${key}:`, err);
|
|
3026
|
-
}
|
|
3027
|
-
}
|
|
3028
|
-
});
|
|
3029
|
-
// Launch up to MAX_CONCURRENT workers
|
|
3030
|
-
const workers = [];
|
|
3031
|
-
for (let w = 0; w < Math.min(MAX_CONCURRENT, pending.length); w++) {
|
|
3032
|
-
workers.push(runNext());
|
|
3033
|
-
}
|
|
3034
|
-
yield Promise.all(workers);
|
|
3035
|
-
}
|
|
3036
|
-
catch (err) {
|
|
3037
|
-
// Table might not have _hasBlobRefs index or other issues - skip silently
|
|
3038
|
-
}
|
|
3039
|
-
}
|
|
3040
|
-
}
|
|
3041
|
-
finally {
|
|
3042
|
-
setDownloadingState(downloading$, false);
|
|
3043
|
-
}
|
|
3044
|
-
});
|
|
3045
|
-
}
|
|
3046
|
-
|
|
3047
|
-
//const hasSW = 'serviceWorker' in navigator;
|
|
3048
|
-
let hasComplainedAboutSyncEvent = false;
|
|
3049
|
-
function registerSyncEvent(db, purpose) {
|
|
3050
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
3051
|
-
try {
|
|
3052
|
-
// Send sync event to SW:
|
|
3053
|
-
const sw = yield navigator.serviceWorker.ready;
|
|
3054
|
-
if (purpose === 'push' && sw.sync) {
|
|
3055
|
-
yield sw.sync.register(`dexie-cloud:${db.name}`);
|
|
3056
|
-
}
|
|
3057
|
-
if (sw.active) {
|
|
3058
|
-
// Use postMessage for pull syncs and for browsers not supporting sync event (Firefox, Safari).
|
|
3059
|
-
// Also chromium based browsers with sw.sync as a fallback for sleepy sync events not taking action for a while.
|
|
3060
|
-
sw.active.postMessage({
|
|
3061
|
-
type: 'dexie-cloud-sync',
|
|
3062
|
-
dbName: db.name,
|
|
3063
|
-
purpose,
|
|
3064
|
-
});
|
|
3065
|
-
}
|
|
3066
|
-
else {
|
|
3067
|
-
throw new Error(`Failed to trigger sync - there's no active service worker`);
|
|
2986
|
+
this.isProcessing = true;
|
|
2987
|
+
// Use setTimeout to completely isolate from Dexie's PSD context
|
|
2988
|
+
// queueMicrotask would risk inheriting the current transaction
|
|
2989
|
+
setTimeout(() => {
|
|
2990
|
+
this.processQueue();
|
|
2991
|
+
}, 0);
|
|
2992
|
+
}
|
|
2993
|
+
/**
|
|
2994
|
+
* Process all queued blobs.
|
|
2995
|
+
* Runs in a completely isolated context (no inherited transaction).
|
|
2996
|
+
* Uses atomic updates to avoid race conditions.
|
|
2997
|
+
*/
|
|
2998
|
+
processQueue() {
|
|
2999
|
+
const item = this.queue.shift();
|
|
3000
|
+
if (!item) {
|
|
3001
|
+
this.isProcessing = false;
|
|
3002
|
+
// Fire any pending drain() waiters. New saveBlobs() calls that
|
|
3003
|
+
// arrive after this point will start a fresh processing cycle
|
|
3004
|
+
// and have their own drain() semantics.
|
|
3005
|
+
const resolvers = this.drainResolvers;
|
|
3006
|
+
if (resolvers.length > 0) {
|
|
3007
|
+
this.drainResolvers = [];
|
|
3008
|
+
for (const resolve of resolvers)
|
|
3009
|
+
resolve();
|
|
3068
3010
|
}
|
|
3069
3011
|
return;
|
|
3070
3012
|
}
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3013
|
+
// Atomic update of just the blob property
|
|
3014
|
+
this.db
|
|
3015
|
+
.transaction('rw', item.tableName, (tx) => {
|
|
3016
|
+
const trans = tx.idbtrans;
|
|
3017
|
+
trans.disableChangeTracking = true; // Don't regard this as a change for sync purposes
|
|
3018
|
+
trans.disableAccessControl = true; // Bypass any access control checks since this is an internal operation
|
|
3019
|
+
trans.disableBlobResolve = true; // Custom flag to skip blob resolve middleware during this transaction
|
|
3020
|
+
const updateSpec = {};
|
|
3021
|
+
for (const blob of item.resolvedBlobs) {
|
|
3022
|
+
updateSpec[blob.keyPath] = blob.data;
|
|
3075
3023
|
}
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3024
|
+
tx.table(item.tableName).update(item.primaryKey, (obj) => {
|
|
3025
|
+
// Check that object still has the same unresolved blob refs before applying update (i.e. it hasn't been modified since we read it)
|
|
3026
|
+
for (const blob of item.resolvedBlobs) {
|
|
3027
|
+
// Verify atomicity - none of the blob properties has been modified since we read it. If any of them was modified, skip updating this item to avoid overwriting user changes.
|
|
3028
|
+
const currentValue = Dexie.getByKeyPath(obj, blob.keyPath);
|
|
3029
|
+
if (currentValue === undefined) {
|
|
3030
|
+
// Blob property was removed - skip updating this blob
|
|
3031
|
+
continue;
|
|
3032
|
+
}
|
|
3033
|
+
if (!isBlobRef(currentValue)) {
|
|
3034
|
+
// Blob property was modified to a non-blob-ref value - skip updating this blob
|
|
3035
|
+
continue;
|
|
3036
|
+
}
|
|
3037
|
+
if (currentValue.ref !== blob.ref) {
|
|
3038
|
+
// Blob property was modified - skip updating this blob
|
|
3039
|
+
return; // Stop. Another items has been queued to fully fix the object.
|
|
3040
|
+
}
|
|
3041
|
+
Dexie.setByKeyPath(obj, blob.keyPath, blob.data);
|
|
3093
3042
|
}
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
}
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3043
|
+
delete obj._hasBlobRefs; // Clear the _hasBlobRefs marker if all refs was resolved.
|
|
3044
|
+
});
|
|
3045
|
+
// Note: we intentionally do NOT clear trans.mutatedParts here.
|
|
3046
|
+
// Letting the normal mutation signal through means the
|
|
3047
|
+
// blobProgress liveQuery (and any user-defined liveQuery that
|
|
3048
|
+
// depends on the resolved fields) wakes up and reflects progress
|
|
3049
|
+
// as blobs land in IndexedDB.
|
|
3050
|
+
})
|
|
3051
|
+
.catch((error) => {
|
|
3052
|
+
console.error(`Error saving resolved blobs on ${item.tableName}:${item.primaryKey}:`, error);
|
|
3053
|
+
})
|
|
3054
|
+
.finally(() => {
|
|
3055
|
+
// At this point, the transaction has completed (either successfully or with error),
|
|
3056
|
+
// and the blobs have been saved (or failed to save).
|
|
3057
|
+
// Notify the owner (BlobDownloadTracker) so it can release the
|
|
3058
|
+
// in-flight download cache entries for these refs. The cache was
|
|
3059
|
+
// kept alive until now to maximize reuse while the blob was still
|
|
3060
|
+
// in-flight (downloading or queued for save).
|
|
3061
|
+
this.onPersisted(item.resolvedBlobs.map((b) => b.ref));
|
|
3062
|
+
// Process next item in the queue
|
|
3063
|
+
return this.processQueue();
|
|
3064
|
+
});
|
|
3112
3065
|
}
|
|
3113
3066
|
}
|
|
3114
3067
|
|
|
@@ -3564,62 +3517,454 @@
|
|
|
3564
3517
|
});
|
|
3565
3518
|
throw error;
|
|
3566
3519
|
}
|
|
3567
|
-
let message = `We're having a problem authenticating right now.`;
|
|
3568
|
-
console.error(`Error authenticating`, error);
|
|
3569
|
-
if (error instanceof TypeError) {
|
|
3570
|
-
const isOffline = typeof navigator !== 'undefined' && !navigator.onLine;
|
|
3571
|
-
if (isOffline) {
|
|
3572
|
-
message = `You seem to be offline. Please connect to the internet and try again.`;
|
|
3573
|
-
}
|
|
3574
|
-
else if (typeof location !== 'undefined' &&
|
|
3575
|
-
(Dexie.debug ||
|
|
3576
|
-
location.hostname === 'localhost' ||
|
|
3577
|
-
location.hostname === '127.0.0.1')) {
|
|
3578
|
-
// The audience is most likely the developer. Suggest to whitelist the localhost origin:
|
|
3579
|
-
const whitelistCommand = `npx dexie-cloud whitelist ${location.origin}`;
|
|
3580
|
-
message = `Could not connect to server. Please verify that your origin '${location.origin}' is whitelisted using \`npx dexie-cloud whitelist\``;
|
|
3581
|
-
yield alertUser(userInteraction, 'Authentication Failed', {
|
|
3582
|
-
type: 'error',
|
|
3583
|
-
messageCode: 'GENERIC_ERROR',
|
|
3584
|
-
message,
|
|
3585
|
-
messageParams: {},
|
|
3586
|
-
copyText: whitelistCommand,
|
|
3587
|
-
}).catch(() => { });
|
|
3520
|
+
let message = `We're having a problem authenticating right now.`;
|
|
3521
|
+
console.error(`Error authenticating`, error);
|
|
3522
|
+
if (error instanceof TypeError) {
|
|
3523
|
+
const isOffline = typeof navigator !== 'undefined' && !navigator.onLine;
|
|
3524
|
+
if (isOffline) {
|
|
3525
|
+
message = `You seem to be offline. Please connect to the internet and try again.`;
|
|
3526
|
+
}
|
|
3527
|
+
else if (typeof location !== 'undefined' &&
|
|
3528
|
+
(Dexie.debug ||
|
|
3529
|
+
location.hostname === 'localhost' ||
|
|
3530
|
+
location.hostname === '127.0.0.1')) {
|
|
3531
|
+
// The audience is most likely the developer. Suggest to whitelist the localhost origin:
|
|
3532
|
+
const whitelistCommand = `npx dexie-cloud whitelist ${location.origin}`;
|
|
3533
|
+
message = `Could not connect to server. Please verify that your origin '${location.origin}' is whitelisted using \`npx dexie-cloud whitelist\``;
|
|
3534
|
+
yield alertUser(userInteraction, 'Authentication Failed', {
|
|
3535
|
+
type: 'error',
|
|
3536
|
+
messageCode: 'GENERIC_ERROR',
|
|
3537
|
+
message,
|
|
3538
|
+
messageParams: {},
|
|
3539
|
+
copyText: whitelistCommand,
|
|
3540
|
+
}).catch(() => { });
|
|
3541
|
+
}
|
|
3542
|
+
else {
|
|
3543
|
+
message = `Could not connect to server. Please verify the connection.`;
|
|
3544
|
+
yield alertUser(userInteraction, 'Authentication Failed', {
|
|
3545
|
+
type: 'error',
|
|
3546
|
+
messageCode: 'GENERIC_ERROR',
|
|
3547
|
+
message,
|
|
3548
|
+
messageParams: {},
|
|
3549
|
+
}).catch(() => { });
|
|
3550
|
+
}
|
|
3551
|
+
}
|
|
3552
|
+
throw error;
|
|
3553
|
+
}
|
|
3554
|
+
});
|
|
3555
|
+
}
|
|
3556
|
+
function spkiToPEM(keydata) {
|
|
3557
|
+
const keydataB64 = b64encode(keydata);
|
|
3558
|
+
const keydataB64Pem = formatAsPem(keydataB64);
|
|
3559
|
+
return keydataB64Pem;
|
|
3560
|
+
}
|
|
3561
|
+
function formatAsPem(str) {
|
|
3562
|
+
let finalString = '-----BEGIN PUBLIC KEY-----\n';
|
|
3563
|
+
while (str.length > 0) {
|
|
3564
|
+
finalString += str.substring(0, 64) + '\n';
|
|
3565
|
+
str = str.substring(64);
|
|
3566
|
+
}
|
|
3567
|
+
finalString = finalString + '-----END PUBLIC KEY-----';
|
|
3568
|
+
return finalString;
|
|
3569
|
+
}
|
|
3570
|
+
|
|
3571
|
+
const wm$4 = new WeakMap();
|
|
3572
|
+
function loadCachedAccessToken(db) {
|
|
3573
|
+
var _a, _b, _c, _d;
|
|
3574
|
+
let cached = wm$4.get(db);
|
|
3575
|
+
if (cached && cached.expiration > Date.now() + 5 * MINUTES) {
|
|
3576
|
+
return Promise.resolve(cached.accessToken);
|
|
3577
|
+
}
|
|
3578
|
+
const currentUser = db.cloud.currentUser.value;
|
|
3579
|
+
if (currentUser &&
|
|
3580
|
+
currentUser.accessToken &&
|
|
3581
|
+
((_b = (_a = currentUser.accessTokenExpiration) === null || _a === void 0 ? void 0 : _a.getTime()) !== null && _b !== void 0 ? _b : Infinity) >
|
|
3582
|
+
Date.now() + 5 * MINUTES) {
|
|
3583
|
+
wm$4.set(db, {
|
|
3584
|
+
accessToken: currentUser.accessToken,
|
|
3585
|
+
expiration: (_d = (_c = currentUser.accessTokenExpiration) === null || _c === void 0 ? void 0 : _c.getTime()) !== null && _d !== void 0 ? _d : Infinity,
|
|
3586
|
+
});
|
|
3587
|
+
return Promise.resolve(currentUser.accessToken);
|
|
3588
|
+
}
|
|
3589
|
+
// If the current user is not logged in (no isLoggedIn flag), there's no
|
|
3590
|
+
// token to load from the database — skip the Dexie.ignoreTransaction() call.
|
|
3591
|
+
// This avoids a crash in service worker context where Dexie's Promise zone
|
|
3592
|
+
// (PSD.transless.env) may be undefined when called from within an active
|
|
3593
|
+
// rw transaction (e.g. during applyServerChanges).
|
|
3594
|
+
if (!(currentUser === null || currentUser === void 0 ? void 0 : currentUser.isLoggedIn)) {
|
|
3595
|
+
return Promise.resolve(null);
|
|
3596
|
+
}
|
|
3597
|
+
return Dexie.ignoreTransaction(() => loadAccessToken(db).then((user) => {
|
|
3598
|
+
var _a, _b;
|
|
3599
|
+
if (user === null || user === void 0 ? void 0 : user.accessToken) {
|
|
3600
|
+
wm$4.set(db, {
|
|
3601
|
+
accessToken: user.accessToken,
|
|
3602
|
+
expiration: (_b = (_a = user.accessTokenExpiration) === null || _a === void 0 ? void 0 : _a.getTime()) !== null && _b !== void 0 ? _b : Infinity,
|
|
3603
|
+
});
|
|
3604
|
+
}
|
|
3605
|
+
return (user === null || user === void 0 ? void 0 : user.accessToken) || null;
|
|
3606
|
+
}));
|
|
3607
|
+
}
|
|
3608
|
+
|
|
3609
|
+
/**
|
|
3610
|
+
* Owns the full lifecycle of downloaded blobs:
|
|
3611
|
+
* 1. Deduplicates concurrent downloads for the same ref.
|
|
3612
|
+
* 2. Bounds the number of concurrent network fetches (MAX_CONCURRENT)
|
|
3613
|
+
* so that ad-hoc reads can't starve the HTTP connection pool. Calls
|
|
3614
|
+
* beyond the cap queue in FIFO order as slots free. The slot is held
|
|
3615
|
+
* only for the duration of the fetch — NOT until persistence — to
|
|
3616
|
+
* avoid deadlocks when a single object contains more blob refs than
|
|
3617
|
+
* MAX_CONCURRENT (a sequential resolver would otherwise hold every
|
|
3618
|
+
* slot itself while waiting for the next).
|
|
3619
|
+
* 3. Keeps the in-flight promise alive after the network fetch completes,
|
|
3620
|
+
* until the blob has been persisted back to IndexedDB. This way,
|
|
3621
|
+
* readers that ask for the same ref while it is queued for saving
|
|
3622
|
+
* can piggyback on the existing promise instead of refetching.
|
|
3623
|
+
* In-flight membership and slot ownership are independent: a piggyback
|
|
3624
|
+
* reader consumes neither a slot nor extra memory beyond the existing
|
|
3625
|
+
* cached Uint8Array.
|
|
3626
|
+
* 4. Persists resolved blobs via an internal BlobSavingQueue, and
|
|
3627
|
+
* releases the in-flight entry when persistence completes.
|
|
3628
|
+
*
|
|
3629
|
+
* Both the blob-resolve middleware and the eager blob downloader use this
|
|
3630
|
+
* tracker. Instantiate once per DexieCloudDB.
|
|
3631
|
+
*/
|
|
3632
|
+
/**
|
|
3633
|
+
* Maximum number of concurrent blob fetches.
|
|
3634
|
+
*
|
|
3635
|
+
* Historically 6 to match the HTTP/1.1 same-origin connection cap that
|
|
3636
|
+
* browsers enforce. With HTTP/2 (the typical transport for Dexie Cloud
|
|
3637
|
+
* today) many streams multiplex over a single TCP connection, so the
|
|
3638
|
+
* old cap is overly conservative. 10 is a modest bump that still keeps
|
|
3639
|
+
* memory pressure (in-flight Uint8Arrays) and server load bounded.
|
|
3640
|
+
* Can be made configurable via DexieCloudOptions if a real need arises.
|
|
3641
|
+
*/
|
|
3642
|
+
const MAX_CONCURRENT = 10;
|
|
3643
|
+
class BlobDownloadTracker {
|
|
3644
|
+
constructor(db) {
|
|
3645
|
+
this.inFlight = new Map();
|
|
3646
|
+
this.activeFetches = 0;
|
|
3647
|
+
this.waiting = [];
|
|
3648
|
+
this.db = db;
|
|
3649
|
+
this.savingQueue = new BlobSavingQueue(db, (refs) => {
|
|
3650
|
+
// Called by the queue when a save transaction has completed
|
|
3651
|
+
// (regardless of success). Drop the in-flight cache entries now —
|
|
3652
|
+
// any future reader will go through IndexedDB instead.
|
|
3653
|
+
for (const ref of refs) {
|
|
3654
|
+
this.inFlight.delete(ref);
|
|
3655
|
+
}
|
|
3656
|
+
});
|
|
3657
|
+
}
|
|
3658
|
+
/**
|
|
3659
|
+
* Download a blob, deduplicating concurrent requests for the same ref
|
|
3660
|
+
* and respecting the global fetch concurrency cap.
|
|
3661
|
+
*
|
|
3662
|
+
* Lifecycle:
|
|
3663
|
+
* - Slot is acquired before the fetch and released as soon as the
|
|
3664
|
+
* fetch settles (success or failure).
|
|
3665
|
+
* - The in-flight entry survives a successful fetch and lives on
|
|
3666
|
+
* until persistence completes (via enqueueSave) or releaseRefs
|
|
3667
|
+
* is called. On fetch failure, the entry is removed immediately
|
|
3668
|
+
* so a future call can retry.
|
|
3669
|
+
*
|
|
3670
|
+
* @param blobRef - The BlobRef to download
|
|
3671
|
+
* @param dbUrl - Base URL for the database (e.g., 'https://mydb.dexie.cloud')
|
|
3672
|
+
*/
|
|
3673
|
+
download(blobRef, dbUrl) {
|
|
3674
|
+
let promise = this.inFlight.get(blobRef.ref);
|
|
3675
|
+
if (!promise) {
|
|
3676
|
+
promise = this.acquireSlot()
|
|
3677
|
+
.then(() => this.downloadBlob(blobRef, dbUrl).finally(() => this.releaseSlot()))
|
|
3678
|
+
.catch((err) => {
|
|
3679
|
+
// On error, remove immediately so a future call can retry.
|
|
3680
|
+
// (Slot already released by the .finally above.)
|
|
3681
|
+
this.inFlight.delete(blobRef.ref);
|
|
3682
|
+
throw err;
|
|
3683
|
+
});
|
|
3684
|
+
this.inFlight.set(blobRef.ref, promise);
|
|
3685
|
+
}
|
|
3686
|
+
return promise;
|
|
3687
|
+
}
|
|
3688
|
+
/**
|
|
3689
|
+
* Queue resolved blobs for persisting back to IndexedDB.
|
|
3690
|
+
* When the save transaction completes, the corresponding in-flight
|
|
3691
|
+
* entries are released.
|
|
3692
|
+
*/
|
|
3693
|
+
enqueueSave(tableName, primaryKey, resolvedBlobs) {
|
|
3694
|
+
this.savingQueue.saveBlobs(tableName, primaryKey, resolvedBlobs);
|
|
3695
|
+
}
|
|
3696
|
+
/**
|
|
3697
|
+
* Wait until all previously enqueued saves have been persisted to
|
|
3698
|
+
* IndexedDB. Used by callers that need to make decisions based on
|
|
3699
|
+
* on-disk state — e.g., the eager downloader looping over rows with
|
|
3700
|
+
* `_hasBlobRefs=1` in chunks, where each iteration must see the
|
|
3701
|
+
* previous chunk's writes before re-querying.
|
|
3702
|
+
*
|
|
3703
|
+
* New saves enqueued AFTER drainPendingSaves() is called do NOT extend
|
|
3704
|
+
* the wait.
|
|
3705
|
+
*/
|
|
3706
|
+
drainPendingSaves() {
|
|
3707
|
+
return this.savingQueue.drain();
|
|
3708
|
+
}
|
|
3709
|
+
/**
|
|
3710
|
+
* Release in-flight entries without going through the internal saving
|
|
3711
|
+
* queue. Used when the caller persists the blobs itself, or when no
|
|
3712
|
+
* primary key was available and the data won't be persisted at all.
|
|
3713
|
+
*/
|
|
3714
|
+
releaseRefs(refs) {
|
|
3715
|
+
for (const ref of refs) {
|
|
3716
|
+
this.inFlight.delete(ref);
|
|
3717
|
+
}
|
|
3718
|
+
}
|
|
3719
|
+
acquireSlot() {
|
|
3720
|
+
if (this.activeFetches < MAX_CONCURRENT) {
|
|
3721
|
+
this.activeFetches++;
|
|
3722
|
+
return Promise.resolve();
|
|
3723
|
+
}
|
|
3724
|
+
return new Promise((resolve) => {
|
|
3725
|
+
this.waiting.push(() => {
|
|
3726
|
+
this.activeFetches++;
|
|
3727
|
+
resolve();
|
|
3728
|
+
});
|
|
3729
|
+
});
|
|
3730
|
+
}
|
|
3731
|
+
releaseSlot() {
|
|
3732
|
+
this.activeFetches--;
|
|
3733
|
+
const next = this.waiting.shift();
|
|
3734
|
+
if (next)
|
|
3735
|
+
next();
|
|
3736
|
+
}
|
|
3737
|
+
/**
|
|
3738
|
+
* Download blob data from server via proxy endpoint.
|
|
3739
|
+
* Uses auth header for authentication (same as sync).
|
|
3740
|
+
* When accessToken is null, the request is made without Authorization header —
|
|
3741
|
+
* this allows downloading blobs from public realms (rlm-public) for
|
|
3742
|
+
* unauthenticated users.
|
|
3743
|
+
*
|
|
3744
|
+
* @param blobRef - The BlobRef to download
|
|
3745
|
+
* @param dbUrl - Base URL for the database (e.g., 'https://mydb.dexie.cloud')
|
|
3746
|
+
*/
|
|
3747
|
+
downloadBlob(blobRef, dbUrl) {
|
|
3748
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
3749
|
+
const accessToken = yield loadCachedAccessToken(this.db);
|
|
3750
|
+
const downloadUrl = `${dbUrl}/blob/${blobRef.ref}`;
|
|
3751
|
+
const headers = {};
|
|
3752
|
+
if (accessToken) {
|
|
3753
|
+
// accessToken may be null for anonymous/unauthenticated users.
|
|
3754
|
+
// Public realm blobs (rlm-public) are accessible without auth.
|
|
3755
|
+
// downloadBlob will omit the Authorization header when token is null.
|
|
3756
|
+
headers['Authorization'] = `Bearer ${accessToken}`;
|
|
3757
|
+
}
|
|
3758
|
+
// cache: 'no-store' prevents the browser from storing this response in its
|
|
3759
|
+
// HTTP cache. The server sets a long Expires/Cache-Control header on blob
|
|
3760
|
+
// responses (blobs are immutable and content-addressed), which would
|
|
3761
|
+
// otherwise cause the browser to keep a copy in its disk cache in addition
|
|
3762
|
+
// to the copy we persist to IndexedDB — doubling storage for every blob.
|
|
3763
|
+
// Since we always persist to IndexedDB and subsequent reads go through
|
|
3764
|
+
// IndexedDB (never re-fetch), the browser cache copy is pure overhead.
|
|
3765
|
+
const response = yield fetch(downloadUrl, { headers, cache: 'no-store' });
|
|
3766
|
+
if (!response.ok) {
|
|
3767
|
+
throw new Error(`Failed to download blob ${blobRef.ref}: ${response.status} ${response.statusText}`);
|
|
3768
|
+
}
|
|
3769
|
+
const arrayBuffer = yield response.arrayBuffer();
|
|
3770
|
+
return new Uint8Array(arrayBuffer);
|
|
3771
|
+
});
|
|
3772
|
+
}
|
|
3773
|
+
}
|
|
3774
|
+
|
|
3775
|
+
/**
|
|
3776
|
+
* Eager Blob Downloader
|
|
3777
|
+
*
|
|
3778
|
+
* Downloads unresolved blobs in the background when blobMode='eager'.
|
|
3779
|
+
* Called after sync completes to prefetch blobs for offline access.
|
|
3780
|
+
*
|
|
3781
|
+
* Strategy:
|
|
3782
|
+
* 1. Snapshot the primary keys of all rows currently flagged
|
|
3783
|
+
* `_hasBlobRefs=1` for each syncable table.
|
|
3784
|
+
* 2. Walk that key list in chunks via `bulkGet`. Each `bulkGet`
|
|
3785
|
+
* triggers the blob-resolve middleware, which does all the actual
|
|
3786
|
+
* work — downloading blobs (throttled and deduplicated by the
|
|
3787
|
+
* shared BlobDownloadTracker) and enqueueing them for persistence
|
|
3788
|
+
* via the internal save queue.
|
|
3789
|
+
*
|
|
3790
|
+
* This keeps a single, symmetric code path with normal application
|
|
3791
|
+
* reads, which is important when other middlewares are present
|
|
3792
|
+
* (e.g., a hypothetical encryption middleware): writes from the save
|
|
3793
|
+
* queue and reads from this loop both pass through the full middleware
|
|
3794
|
+
* stack, so on-disk representation stays consistent.
|
|
3795
|
+
*
|
|
3796
|
+
* Why a snapshot of primary keys (rather than re-querying the index)?
|
|
3797
|
+
* - Rows that get resolved by parallel application reads simply
|
|
3798
|
+
* disappear from the table contents we're about to re-fetch; the
|
|
3799
|
+
* middleware skips them since `_hasBlobRefs` is already cleared.
|
|
3800
|
+
* - Stuck rows (e.g., blob 404s) are naturally bypassed: we just
|
|
3801
|
+
* advance to the next chunk in the snapshot. No `seenKeys`
|
|
3802
|
+
* bookkeeping required.
|
|
3803
|
+
* - The snapshot is `string[]`-shaped for typical Dexie Cloud rows
|
|
3804
|
+
* (~36 bytes/UUID), so ~28K keys per MB. Acceptable for any
|
|
3805
|
+
* realistic dataset.
|
|
3806
|
+
*
|
|
3807
|
+
* Progress is tracked automatically via liveQuery in blobProgress.ts —
|
|
3808
|
+
* no manual progress reporting needed here.
|
|
3809
|
+
*
|
|
3810
|
+
* --- Throughput note ---
|
|
3811
|
+
* The chunk loop is sequential: bulkGet → wait for all downloads to
|
|
3812
|
+
* settle → next bulkGet. The save queue drains in the background and
|
|
3813
|
+
* does not block iteration (saves no longer need to be persisted before
|
|
3814
|
+
* the next iteration, since we don't re-query the index). For typical
|
|
3815
|
+
* blob sizes (10 KB – 10 MB) the network dominates total time. If
|
|
3816
|
+
* real-world profiling later shows the per-chunk fixed cost matters,
|
|
3817
|
+
* the next bulkGet could be kicked off in parallel with the current
|
|
3818
|
+
* one's middleware work — but we keep it simple until measurements
|
|
3819
|
+
* justify otherwise.
|
|
3820
|
+
*/
|
|
3821
|
+
// One chunk = one full saturation of the tracker's concurrency semaphore.
|
|
3822
|
+
// Larger chunks would only buffer more downloaded Uint8Arrays in memory
|
|
3823
|
+
// while waiting for the save queue to persist them, without any throughput
|
|
3824
|
+
// benefit (the semaphore is the gate, not the bulkGet).
|
|
3825
|
+
const CHUNK_SIZE = MAX_CONCURRENT - 1; // Leave one slot for parallel app reads that might also trigger downloads
|
|
3826
|
+
/**
|
|
3827
|
+
* Download all unresolved blobs in the background.
|
|
3828
|
+
*
|
|
3829
|
+
* This is called when blobMode='eager' (default) after sync completes.
|
|
3830
|
+
*/
|
|
3831
|
+
function downloadUnresolvedBlobs(db, downloading$, signal) {
|
|
3832
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
3833
|
+
const debugLog = (msg) => console.debug(`[dexie-cloud] ${msg}`);
|
|
3834
|
+
debugLog('Eager download: Starting...');
|
|
3835
|
+
const syncedTables = getSyncableTables(db).filter((t) => t.schema.indexes.some((idx) => idx.name === '_hasBlobRefs'));
|
|
3836
|
+
let started = false;
|
|
3837
|
+
let totalProcessed = 0;
|
|
3838
|
+
try {
|
|
3839
|
+
for (const table of syncedTables) {
|
|
3840
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted)
|
|
3841
|
+
;
|
|
3842
|
+
let keys;
|
|
3843
|
+
try {
|
|
3844
|
+
keys = yield table.where('_hasBlobRefs').equals(1).primaryKeys();
|
|
3845
|
+
}
|
|
3846
|
+
catch (err) {
|
|
3847
|
+
console.error(`Eager download: failed to list unresolved rows for ${table.name}:`, err);
|
|
3848
|
+
continue;
|
|
3849
|
+
}
|
|
3850
|
+
if (keys.length === 0)
|
|
3851
|
+
continue;
|
|
3852
|
+
if (!started) {
|
|
3853
|
+
setDownloadingState(downloading$, true);
|
|
3854
|
+
started = true;
|
|
3855
|
+
}
|
|
3856
|
+
debugLog(`Eager download: ${table.name} has ${keys.length} row(s)`);
|
|
3857
|
+
for (let i = 0; i < keys.length; i += CHUNK_SIZE) {
|
|
3858
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted)
|
|
3859
|
+
;
|
|
3860
|
+
const slice = keys.slice(i, i + CHUNK_SIZE);
|
|
3861
|
+
try {
|
|
3862
|
+
// bulkGet triggers the blob-resolve middleware for each row that
|
|
3863
|
+
// still has `_hasBlobRefs=1`. Rows already resolved by parallel
|
|
3864
|
+
// reads come back without the marker and the middleware no-ops.
|
|
3865
|
+
// Rows that have been deleted return `undefined` and are
|
|
3866
|
+
// likewise skipped.
|
|
3867
|
+
yield table.bulkGet(slice);
|
|
3868
|
+
}
|
|
3869
|
+
catch (err) {
|
|
3870
|
+
console.error(`Eager download: ${table.name} chunk failed:`, err);
|
|
3871
|
+
continue;
|
|
3872
|
+
}
|
|
3873
|
+
totalProcessed += slice.length;
|
|
3874
|
+
debugLog(`Eager download: ${table.name} ${Math.min(i + CHUNK_SIZE, keys.length)}/${keys.length}`);
|
|
3875
|
+
}
|
|
3876
|
+
}
|
|
3877
|
+
if (started) {
|
|
3878
|
+
// Make sure all middleware-enqueued saves have landed before we flip
|
|
3879
|
+
// `downloading$` to false — otherwise observers might see a "done"
|
|
3880
|
+
// signal while writes are still in flight.
|
|
3881
|
+
yield db.blobDownloadTracker.drainPendingSaves();
|
|
3882
|
+
debugLog(`Eager download: done (${totalProcessed} row(s) processed)`);
|
|
3883
|
+
}
|
|
3884
|
+
else {
|
|
3885
|
+
debugLog('Eager download: No blobs remaining, exiting');
|
|
3886
|
+
}
|
|
3887
|
+
}
|
|
3888
|
+
finally {
|
|
3889
|
+
if (started)
|
|
3890
|
+
setDownloadingState(downloading$, false);
|
|
3891
|
+
}
|
|
3892
|
+
});
|
|
3893
|
+
}
|
|
3894
|
+
|
|
3895
|
+
//const hasSW = 'serviceWorker' in navigator;
|
|
3896
|
+
let hasComplainedAboutSyncEvent = false;
|
|
3897
|
+
function registerSyncEvent(db, purpose) {
|
|
3898
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
3899
|
+
try {
|
|
3900
|
+
// Send sync event to SW:
|
|
3901
|
+
const sw = yield navigator.serviceWorker.ready;
|
|
3902
|
+
if (purpose === 'push' && sw.sync) {
|
|
3903
|
+
yield sw.sync.register(`dexie-cloud:${db.name}`);
|
|
3904
|
+
}
|
|
3905
|
+
if (sw.active) {
|
|
3906
|
+
// Use postMessage for pull syncs and for browsers not supporting sync event (Firefox, Safari).
|
|
3907
|
+
// Also chromium based browsers with sw.sync as a fallback for sleepy sync events not taking action for a while.
|
|
3908
|
+
sw.active.postMessage({
|
|
3909
|
+
type: 'dexie-cloud-sync',
|
|
3910
|
+
dbName: db.name,
|
|
3911
|
+
purpose,
|
|
3912
|
+
});
|
|
3913
|
+
}
|
|
3914
|
+
else {
|
|
3915
|
+
throw new Error(`Failed to trigger sync - there's no active service worker`);
|
|
3916
|
+
}
|
|
3917
|
+
return;
|
|
3918
|
+
}
|
|
3919
|
+
catch (e) {
|
|
3920
|
+
if (!hasComplainedAboutSyncEvent) {
|
|
3921
|
+
console.debug(`Dexie Cloud: Could not register sync event`, e);
|
|
3922
|
+
hasComplainedAboutSyncEvent = true;
|
|
3923
|
+
}
|
|
3924
|
+
}
|
|
3925
|
+
});
|
|
3926
|
+
}
|
|
3927
|
+
function registerPeriodicSyncEvent(db) {
|
|
3928
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
3929
|
+
var _a;
|
|
3930
|
+
try {
|
|
3931
|
+
// Register periodicSync event to SW:
|
|
3932
|
+
// @ts-ignore
|
|
3933
|
+
const { periodicSync } = yield navigator.serviceWorker.ready;
|
|
3934
|
+
if (periodicSync) {
|
|
3935
|
+
try {
|
|
3936
|
+
yield periodicSync.register(`dexie-cloud:${db.name}`, (_a = db.cloud.options) === null || _a === void 0 ? void 0 : _a.periodicSync);
|
|
3937
|
+
console.debug(`Dexie Cloud: Successfully registered periodicsync event for ${db.name}`);
|
|
3588
3938
|
}
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
yield alertUser(userInteraction, 'Authentication Failed', {
|
|
3592
|
-
type: 'error',
|
|
3593
|
-
messageCode: 'GENERIC_ERROR',
|
|
3594
|
-
message,
|
|
3595
|
-
messageParams: {},
|
|
3596
|
-
}).catch(() => { });
|
|
3939
|
+
catch (e) {
|
|
3940
|
+
console.debug(`Dexie Cloud: Failed to register periodic sync. Your PWA must be installed to allow background sync.`, e);
|
|
3597
3941
|
}
|
|
3598
3942
|
}
|
|
3599
|
-
|
|
3943
|
+
else {
|
|
3944
|
+
console.debug(`Dexie Cloud: periodicSync not supported.`);
|
|
3945
|
+
}
|
|
3946
|
+
}
|
|
3947
|
+
catch (e) {
|
|
3948
|
+
console.debug(`Dexie Cloud: Could not register periodicSync for ${db.name}`, e);
|
|
3600
3949
|
}
|
|
3601
3950
|
});
|
|
3602
3951
|
}
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
finalString += str.substring(0, 64) + '\n';
|
|
3612
|
-
str = str.substring(64);
|
|
3952
|
+
|
|
3953
|
+
function triggerSync(db, purpose) {
|
|
3954
|
+
if (db.cloud.usingServiceWorker) {
|
|
3955
|
+
console.debug('registering sync event');
|
|
3956
|
+
registerSyncEvent(db, purpose);
|
|
3957
|
+
}
|
|
3958
|
+
else {
|
|
3959
|
+
db.localSyncEvent.next({ purpose });
|
|
3613
3960
|
}
|
|
3614
|
-
finalString = finalString + '-----END PUBLIC KEY-----';
|
|
3615
|
-
return finalString;
|
|
3616
3961
|
}
|
|
3617
3962
|
|
|
3618
3963
|
// Emulate true-private property db. Why? So it's not stored in DB.
|
|
3619
|
-
const wm$
|
|
3964
|
+
const wm$3 = new WeakMap();
|
|
3620
3965
|
class AuthPersistedContext {
|
|
3621
3966
|
constructor(db, userLogin) {
|
|
3622
|
-
wm$
|
|
3967
|
+
wm$3.set(this, db);
|
|
3623
3968
|
Object.assign(this, userLogin);
|
|
3624
3969
|
}
|
|
3625
3970
|
static load(db, userId) {
|
|
@@ -3636,7 +3981,7 @@
|
|
|
3636
3981
|
}
|
|
3637
3982
|
save() {
|
|
3638
3983
|
return __awaiter(this, void 0, void 0, function* () {
|
|
3639
|
-
const db = wm$
|
|
3984
|
+
const db = wm$3.get(this);
|
|
3640
3985
|
db.table('$logins').put(this);
|
|
3641
3986
|
});
|
|
3642
3987
|
}
|
|
@@ -5087,14 +5432,16 @@
|
|
|
5087
5432
|
}
|
|
5088
5433
|
break;
|
|
5089
5434
|
case 'update':
|
|
5090
|
-
if (!primaryKey.outbound &&
|
|
5435
|
+
if (!primaryKey.outbound &&
|
|
5436
|
+
primaryKey.keyPath &&
|
|
5437
|
+
typeof primaryKey.keyPath === 'string') {
|
|
5091
5438
|
// The primary key should never be part of an updateSpec — it cannot change
|
|
5092
5439
|
// and is already communicated via the operation's keys array.
|
|
5093
5440
|
// For private singleton IDs (e.g. "#key:userId" on server, "#key" on client),
|
|
5094
5441
|
// the encoded server-side key may leak into the changeSpec via getObjectDiff().
|
|
5095
5442
|
// Strip it here unconditionally as a defensive measure.
|
|
5096
5443
|
for (const changeSpec of mut.changeSpecs) {
|
|
5097
|
-
|
|
5444
|
+
delete changeSpec[primaryKey.keyPath];
|
|
5098
5445
|
}
|
|
5099
5446
|
}
|
|
5100
5447
|
yield bulkUpdate(table, keys, mut.changeSpecs);
|
|
@@ -14493,7 +14840,7 @@
|
|
|
14493
14840
|
*
|
|
14494
14841
|
* ==========================================================================
|
|
14495
14842
|
*
|
|
14496
|
-
* Version 4.4.0,
|
|
14843
|
+
* Version 4.4.0, Wed May 27 2026
|
|
14497
14844
|
*
|
|
14498
14845
|
* https://dexie.org
|
|
14499
14846
|
*
|
|
@@ -14688,7 +15035,7 @@
|
|
|
14688
15035
|
};
|
|
14689
15036
|
}
|
|
14690
15037
|
|
|
14691
|
-
const wm$
|
|
15038
|
+
const wm$2 = new WeakMap();
|
|
14692
15039
|
function createEvents() {
|
|
14693
15040
|
return Dexie.Dexie.Events(null, 'load', 'sync', 'error');
|
|
14694
15041
|
}
|
|
@@ -14707,7 +15054,7 @@
|
|
|
14707
15054
|
}
|
|
14708
15055
|
static load(doc, options) {
|
|
14709
15056
|
var _a;
|
|
14710
|
-
let p = wm$
|
|
15057
|
+
let p = wm$2.get(doc);
|
|
14711
15058
|
if (p) {
|
|
14712
15059
|
++p.refCount;
|
|
14713
15060
|
if ((options === null || options === void 0 ? void 0 : options.gracePeriod) != null &&
|
|
@@ -14722,14 +15069,14 @@
|
|
|
14722
15069
|
else {
|
|
14723
15070
|
p = new DexieYProvider(doc);
|
|
14724
15071
|
p.graceTimeout = (_a = options === null || options === void 0 ? void 0 : options.gracePeriod) !== null && _a !== void 0 ? _a : -1;
|
|
14725
|
-
wm$
|
|
15072
|
+
wm$2.set(doc, p);
|
|
14726
15073
|
}
|
|
14727
15074
|
return p;
|
|
14728
15075
|
}
|
|
14729
15076
|
static release(doc) {
|
|
14730
15077
|
if (!doc || destroyedDocs.has(doc))
|
|
14731
15078
|
return; // Document already destroyed.
|
|
14732
|
-
const p = wm$
|
|
15079
|
+
const p = wm$2.get(doc);
|
|
14733
15080
|
if (p) {
|
|
14734
15081
|
// There is a provider connected to the doc
|
|
14735
15082
|
if (--p.refCount <= 0) {
|
|
@@ -14774,7 +15121,7 @@
|
|
|
14774
15121
|
});
|
|
14775
15122
|
}
|
|
14776
15123
|
static for(doc) {
|
|
14777
|
-
return wm$
|
|
15124
|
+
return wm$2.get(doc);
|
|
14778
15125
|
}
|
|
14779
15126
|
static get currentUpdateRow() {
|
|
14780
15127
|
return currentUpdateRow;
|
|
@@ -14862,7 +15209,7 @@
|
|
|
14862
15209
|
destroy() {
|
|
14863
15210
|
var _a, _b, _c;
|
|
14864
15211
|
console.debug(`Y.Doc ${(_b = (_a = this.doc) === null || _a === void 0 ? void 0 : _a.meta) === null || _b === void 0 ? void 0 : _b.parentId} was destroyed`);
|
|
14865
|
-
wm$
|
|
15212
|
+
wm$2.delete(this.doc);
|
|
14866
15213
|
this.doc = null;
|
|
14867
15214
|
this.destroyed = true;
|
|
14868
15215
|
this.refCount = 0;
|
|
@@ -15519,44 +15866,6 @@
|
|
|
15519
15866
|
});
|
|
15520
15867
|
}
|
|
15521
15868
|
|
|
15522
|
-
const wm$2 = new WeakMap();
|
|
15523
|
-
function loadCachedAccessToken(db) {
|
|
15524
|
-
var _a, _b, _c, _d;
|
|
15525
|
-
let cached = wm$2.get(db);
|
|
15526
|
-
if (cached && cached.expiration > Date.now() + 5 * MINUTES) {
|
|
15527
|
-
return Promise.resolve(cached.accessToken);
|
|
15528
|
-
}
|
|
15529
|
-
const currentUser = db.cloud.currentUser.value;
|
|
15530
|
-
if (currentUser &&
|
|
15531
|
-
currentUser.accessToken &&
|
|
15532
|
-
((_b = (_a = currentUser.accessTokenExpiration) === null || _a === void 0 ? void 0 : _a.getTime()) !== null && _b !== void 0 ? _b : Infinity) >
|
|
15533
|
-
Date.now() + 5 * MINUTES) {
|
|
15534
|
-
wm$2.set(db, {
|
|
15535
|
-
accessToken: currentUser.accessToken,
|
|
15536
|
-
expiration: (_d = (_c = currentUser.accessTokenExpiration) === null || _c === void 0 ? void 0 : _c.getTime()) !== null && _d !== void 0 ? _d : Infinity,
|
|
15537
|
-
});
|
|
15538
|
-
return Promise.resolve(currentUser.accessToken);
|
|
15539
|
-
}
|
|
15540
|
-
// If the current user is not logged in (no isLoggedIn flag), there's no
|
|
15541
|
-
// token to load from the database — skip the Dexie.ignoreTransaction() call.
|
|
15542
|
-
// This avoids a crash in service worker context where Dexie's Promise zone
|
|
15543
|
-
// (PSD.transless.env) may be undefined when called from within an active
|
|
15544
|
-
// rw transaction (e.g. during applyServerChanges).
|
|
15545
|
-
if (!(currentUser === null || currentUser === void 0 ? void 0 : currentUser.isLoggedIn)) {
|
|
15546
|
-
return Promise.resolve(null);
|
|
15547
|
-
}
|
|
15548
|
-
return Dexie.ignoreTransaction(() => loadAccessToken(db).then((user) => {
|
|
15549
|
-
var _a, _b;
|
|
15550
|
-
if (user === null || user === void 0 ? void 0 : user.accessToken) {
|
|
15551
|
-
wm$2.set(db, {
|
|
15552
|
-
accessToken: user.accessToken,
|
|
15553
|
-
expiration: (_b = (_a = user.accessTokenExpiration) === null || _a === void 0 ? void 0 : _a.getTime()) !== null && _b !== void 0 ? _b : Infinity,
|
|
15554
|
-
});
|
|
15555
|
-
}
|
|
15556
|
-
return (user === null || user === void 0 ? void 0 : user.accessToken) || null;
|
|
15557
|
-
}));
|
|
15558
|
-
}
|
|
15559
|
-
|
|
15560
15869
|
const CURRENT_SYNC_WORKER = 'currentSyncWorker';
|
|
15561
15870
|
function sync(db, options, schema, syncOptions) {
|
|
15562
15871
|
return _sync(db, options, schema, syncOptions)
|
|
@@ -16100,71 +16409,6 @@
|
|
|
16100
16409
|
};
|
|
16101
16410
|
}
|
|
16102
16411
|
|
|
16103
|
-
/**
|
|
16104
|
-
* Deduplicates in-flight blob downloads.
|
|
16105
|
-
*
|
|
16106
|
-
* Both the blob-resolve middleware and the eager blob downloader may
|
|
16107
|
-
* try to fetch the same blob concurrently. This tracker ensures each
|
|
16108
|
-
* unique blob ref is only downloaded once — subsequent requests for
|
|
16109
|
-
* the same ref piggyback on the existing promise.
|
|
16110
|
-
*
|
|
16111
|
-
* Instantiate once per DexieCloudDB.
|
|
16112
|
-
*/
|
|
16113
|
-
class BlobDownloadTracker {
|
|
16114
|
-
constructor(db) {
|
|
16115
|
-
this.inFlight = new Map();
|
|
16116
|
-
this.db = db;
|
|
16117
|
-
}
|
|
16118
|
-
/**
|
|
16119
|
-
* Download a blob, deduplicating concurrent requests for the same ref.
|
|
16120
|
-
*
|
|
16121
|
-
* @param blobRef - The BlobRef to download
|
|
16122
|
-
* @param dbUrl - Base URL for the database (e.g., 'https://mydb.dexie.cloud')
|
|
16123
|
-
*/
|
|
16124
|
-
download(blobRef, dbUrl) {
|
|
16125
|
-
let promise = this.inFlight.get(blobRef.ref);
|
|
16126
|
-
if (!promise) {
|
|
16127
|
-
promise = loadCachedAccessToken(this.db)
|
|
16128
|
-
.then((accessToken) => {
|
|
16129
|
-
// accessToken may be null for anonymous/unauthenticated users.
|
|
16130
|
-
// Public realm blobs (rlm-public) are accessible without auth.
|
|
16131
|
-
// downloadBlob will omit the Authorization header when token is null.
|
|
16132
|
-
return downloadBlob(blobRef, dbUrl, accessToken);
|
|
16133
|
-
})
|
|
16134
|
-
.finally(() => this.inFlight.delete(blobRef.ref));
|
|
16135
|
-
// When the promise settles (either fulfilled or rejected), remove it from the in-flight map
|
|
16136
|
-
this.inFlight.set(blobRef.ref, promise);
|
|
16137
|
-
}
|
|
16138
|
-
return promise;
|
|
16139
|
-
}
|
|
16140
|
-
}
|
|
16141
|
-
/**
|
|
16142
|
-
* Download blob data from server via proxy endpoint.
|
|
16143
|
-
* Uses auth header for authentication (same as sync).
|
|
16144
|
-
* When accessToken is null, the request is made without Authorization header —
|
|
16145
|
-
* this allows downloading blobs from public realms (rlm-public) for
|
|
16146
|
-
* unauthenticated users.
|
|
16147
|
-
*
|
|
16148
|
-
* @param blobRef - The BlobRef to download
|
|
16149
|
-
* @param dbUrl - Base URL for the database (e.g., 'https://mydb.dexie.cloud')
|
|
16150
|
-
* @param accessToken - Access token for authentication, or null for anonymous access
|
|
16151
|
-
*/
|
|
16152
|
-
function downloadBlob(blobRef, dbUrl, accessToken) {
|
|
16153
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
16154
|
-
const downloadUrl = `${dbUrl}/blob/${blobRef.ref}`;
|
|
16155
|
-
const headers = {};
|
|
16156
|
-
if (accessToken) {
|
|
16157
|
-
headers['Authorization'] = `Bearer ${accessToken}`;
|
|
16158
|
-
}
|
|
16159
|
-
const response = yield fetch(downloadUrl, { headers });
|
|
16160
|
-
if (!response.ok) {
|
|
16161
|
-
throw new Error(`Failed to download blob ${blobRef.ref}: ${response.status} ${response.statusText}`);
|
|
16162
|
-
}
|
|
16163
|
-
const arrayBuffer = yield response.arrayBuffer();
|
|
16164
|
-
return new Uint8Array(arrayBuffer);
|
|
16165
|
-
});
|
|
16166
|
-
}
|
|
16167
|
-
|
|
16168
16412
|
const wm$1 = new WeakMap();
|
|
16169
16413
|
const DEXIE_CLOUD_SCHEMA = {
|
|
16170
16414
|
members: '@id, [userId+realmId], [email+realmId], realmId',
|
|
@@ -16960,99 +17204,6 @@
|
|
|
16960
17204
|
};
|
|
16961
17205
|
}
|
|
16962
17206
|
|
|
16963
|
-
/**
|
|
16964
|
-
* BlobSavingQueue - Queues resolved blobs for saving back to IndexedDB
|
|
16965
|
-
*
|
|
16966
|
-
* Uses setTimeout(fn, 0) instead of queueMicrotask to completely isolate
|
|
16967
|
-
* from Dexie's Promise.PSD context. This prevents the save operation
|
|
16968
|
-
* from inheriting any ongoing transaction.
|
|
16969
|
-
*
|
|
16970
|
-
* Each blob is saved atomically using downCore transaction with the specific
|
|
16971
|
-
* keyPath to avoid race conditions with other property changes.
|
|
16972
|
-
*/
|
|
16973
|
-
class BlobSavingQueue {
|
|
16974
|
-
constructor(db) {
|
|
16975
|
-
this.queue = [];
|
|
16976
|
-
this.isProcessing = false;
|
|
16977
|
-
this.db = db;
|
|
16978
|
-
}
|
|
16979
|
-
/**
|
|
16980
|
-
* Queue a resolved blob for saving.
|
|
16981
|
-
* Only the specific blob property will be updated atomically.
|
|
16982
|
-
*/
|
|
16983
|
-
saveBlobs(tableName, primaryKey, resolvedBlobs) {
|
|
16984
|
-
this.queue.push({ tableName, primaryKey, resolvedBlobs });
|
|
16985
|
-
this.startConsumer();
|
|
16986
|
-
}
|
|
16987
|
-
/**
|
|
16988
|
-
* Start the consumer if not already processing.
|
|
16989
|
-
* Uses setTimeout(fn, 0) to completely break out of any
|
|
16990
|
-
* Dexie transaction context (Promise.PSD).
|
|
16991
|
-
*/
|
|
16992
|
-
startConsumer() {
|
|
16993
|
-
if (this.isProcessing)
|
|
16994
|
-
return;
|
|
16995
|
-
this.isProcessing = true;
|
|
16996
|
-
// Use setTimeout to completely isolate from Dexie's PSD context
|
|
16997
|
-
// queueMicrotask would risk inheriting the current transaction
|
|
16998
|
-
setTimeout(() => {
|
|
16999
|
-
this.processQueue();
|
|
17000
|
-
}, 0);
|
|
17001
|
-
}
|
|
17002
|
-
/**
|
|
17003
|
-
* Process all queued blobs.
|
|
17004
|
-
* Runs in a completely isolated context (no inherited transaction).
|
|
17005
|
-
* Uses atomic updates to avoid race conditions.
|
|
17006
|
-
*/
|
|
17007
|
-
processQueue() {
|
|
17008
|
-
const item = this.queue.shift();
|
|
17009
|
-
if (!item) {
|
|
17010
|
-
this.isProcessing = false;
|
|
17011
|
-
return;
|
|
17012
|
-
}
|
|
17013
|
-
// Atomic update of just the blob property
|
|
17014
|
-
this.db
|
|
17015
|
-
.transaction('rw', item.tableName, (tx) => {
|
|
17016
|
-
const trans = tx.idbtrans;
|
|
17017
|
-
trans.disableChangeTracking = true; // Don't regard this as a change for sync purposes
|
|
17018
|
-
trans.disableAccessControl = true; // Bypass any access control checks since this is an internal operation
|
|
17019
|
-
trans.disableBlobResolve = true; // Custom flag to skip blob resolve middleware during this transaction
|
|
17020
|
-
const updateSpec = {};
|
|
17021
|
-
for (const blob of item.resolvedBlobs) {
|
|
17022
|
-
updateSpec[blob.keyPath] = blob.data;
|
|
17023
|
-
}
|
|
17024
|
-
tx.table(item.tableName).update(item.primaryKey, (obj) => {
|
|
17025
|
-
// Check that object still has the same unresolved blob refs before applying update (i.e. it hasn't been modified since we read it)
|
|
17026
|
-
for (const blob of item.resolvedBlobs) {
|
|
17027
|
-
// Verify atomicity - none of the blob properties has been modified since we read it. If any of them was modified, skip updating this item to avoid overwriting user changes.
|
|
17028
|
-
const currentValue = Dexie.getByKeyPath(obj, blob.keyPath);
|
|
17029
|
-
if (currentValue === undefined) {
|
|
17030
|
-
// Blob property was removed - skip updating this blob
|
|
17031
|
-
continue;
|
|
17032
|
-
}
|
|
17033
|
-
if (!isBlobRef(currentValue)) {
|
|
17034
|
-
// Blob property was modified to a non-blob-ref value - skip updating this blob
|
|
17035
|
-
continue;
|
|
17036
|
-
}
|
|
17037
|
-
if (currentValue.ref !== blob.ref) {
|
|
17038
|
-
// Blob property was modified - skip updating this blob
|
|
17039
|
-
return; // Stop. Another items has been queued to fully fix the object.
|
|
17040
|
-
}
|
|
17041
|
-
Dexie.setByKeyPath(obj, blob.keyPath, blob.data);
|
|
17042
|
-
}
|
|
17043
|
-
delete obj._hasBlobRefs; // Clear the _hasBlobRefs marker if all refs was resolved.
|
|
17044
|
-
});
|
|
17045
|
-
})
|
|
17046
|
-
.catch((error) => {
|
|
17047
|
-
console.error(`Error saving resolved blobs on ${item.tableName}:${item.primaryKey}:`, error);
|
|
17048
|
-
})
|
|
17049
|
-
.finally(() => {
|
|
17050
|
-
// Process next item in the queue
|
|
17051
|
-
return this.processQueue();
|
|
17052
|
-
});
|
|
17053
|
-
}
|
|
17054
|
-
}
|
|
17055
|
-
|
|
17056
17207
|
/**
|
|
17057
17208
|
* DBCore Middleware for resolving BlobRefs on read
|
|
17058
17209
|
*
|
|
@@ -17063,10 +17214,11 @@
|
|
|
17063
17214
|
* Uses Dexie.waitFor() only for explicit rw transactions to keep them alive.
|
|
17064
17215
|
* For readonly or implicit transactions, resolves directly (no waitFor needed).
|
|
17065
17216
|
*
|
|
17066
|
-
* Resolved blobs are
|
|
17067
|
-
*
|
|
17068
|
-
* Each blob is saved atomically
|
|
17069
|
-
* avoid race conditions with other
|
|
17217
|
+
* Resolved blobs are persisted via db.blobDownloadTracker.enqueueSave(),
|
|
17218
|
+
* which internally uses a queue that runs in a fresh JS task to completely
|
|
17219
|
+
* isolate from Dexie's transaction context. Each blob is saved atomically
|
|
17220
|
+
* using Table.update() with its keyPath to avoid race conditions with other
|
|
17221
|
+
* property changes.
|
|
17070
17222
|
*
|
|
17071
17223
|
* Blob downloads use Authorization header (same as sync) via the server
|
|
17072
17224
|
* proxy endpoint: GET /blob/{ref}
|
|
@@ -17077,8 +17229,6 @@
|
|
|
17077
17229
|
name: 'blobResolve',
|
|
17078
17230
|
level: 2, // Run above cache (0) and other middlewares (1) to resolve BlobRefs from cached data
|
|
17079
17231
|
create(downlevelDatabase) {
|
|
17080
|
-
// Create a single queue instance for this database
|
|
17081
|
-
const blobSavingQueue = new BlobSavingQueue(db);
|
|
17082
17232
|
return Object.assign(Object.assign({}, downlevelDatabase), { table(tableName) {
|
|
17083
17233
|
var _a;
|
|
17084
17234
|
if (!db.cloud) {
|
|
@@ -17099,7 +17249,7 @@
|
|
|
17099
17249
|
}
|
|
17100
17250
|
return downlevelTable.get(req).then((result) => {
|
|
17101
17251
|
if (result && hasUnresolvedBlobRefs(result)) {
|
|
17102
|
-
return resolveAndSave(downlevelTable, req.trans, req.key, result,
|
|
17252
|
+
return resolveAndSave(downlevelTable, req.trans, req.key, result, db);
|
|
17103
17253
|
}
|
|
17104
17254
|
return result;
|
|
17105
17255
|
});
|
|
@@ -17116,7 +17266,7 @@
|
|
|
17116
17266
|
return results;
|
|
17117
17267
|
return Dexie.Promise.all(results.map((result, index) => {
|
|
17118
17268
|
if (result && hasUnresolvedBlobRefs(result)) {
|
|
17119
|
-
return resolveAndSave(downlevelTable, req.trans, req.keys[index], result,
|
|
17269
|
+
return resolveAndSave(downlevelTable, req.trans, req.keys[index], result, db);
|
|
17120
17270
|
}
|
|
17121
17271
|
return result;
|
|
17122
17272
|
}));
|
|
@@ -17136,7 +17286,7 @@
|
|
|
17136
17286
|
return result;
|
|
17137
17287
|
return Dexie.Promise.all(result.result.map((item) => {
|
|
17138
17288
|
if (item && hasUnresolvedBlobRefs(item)) {
|
|
17139
|
-
return resolveAndSave(downlevelTable, req.trans, undefined, item,
|
|
17289
|
+
return resolveAndSave(downlevelTable, req.trans, undefined, item, db);
|
|
17140
17290
|
}
|
|
17141
17291
|
return item;
|
|
17142
17292
|
})).then((resolved) => (Object.assign(Object.assign({}, result), { result: resolved })));
|
|
@@ -17154,7 +17304,7 @@
|
|
|
17154
17304
|
return cursor; // No values requested, so no resolution needed
|
|
17155
17305
|
if (!dbUrl)
|
|
17156
17306
|
return cursor; // No database URL configured, can't resolve blobs
|
|
17157
|
-
return createBlobResolvingCursor(cursor, downlevelTable,
|
|
17307
|
+
return createBlobResolvingCursor(cursor, downlevelTable, db);
|
|
17158
17308
|
});
|
|
17159
17309
|
} });
|
|
17160
17310
|
} });
|
|
@@ -17171,7 +17321,7 @@
|
|
|
17171
17321
|
* Returns the cursor synchronously. Resolution happens in start() before
|
|
17172
17322
|
* each onNext callback, ensuring cursor.value is always available.
|
|
17173
17323
|
*/
|
|
17174
|
-
function createBlobResolvingCursor(cursor, table,
|
|
17324
|
+
function createBlobResolvingCursor(cursor, table, db) {
|
|
17175
17325
|
// Create wrapped cursor using Object.create() - inherits everything.
|
|
17176
17326
|
// Important: .key and .primaryKey must be explicitly overridden with
|
|
17177
17327
|
// closure-based getters to prevent native IDBCursorWithValue getters from
|
|
@@ -17179,11 +17329,15 @@
|
|
|
17179
17329
|
// throws "Illegal invocation" in Chrome 146+.
|
|
17180
17330
|
const wrappedCursor = Object.create(cursor, {
|
|
17181
17331
|
key: {
|
|
17182
|
-
get() {
|
|
17332
|
+
get() {
|
|
17333
|
+
return cursor.key;
|
|
17334
|
+
},
|
|
17183
17335
|
configurable: true,
|
|
17184
17336
|
},
|
|
17185
17337
|
primaryKey: {
|
|
17186
|
-
get() {
|
|
17338
|
+
get() {
|
|
17339
|
+
return cursor.primaryKey;
|
|
17340
|
+
},
|
|
17187
17341
|
configurable: true,
|
|
17188
17342
|
},
|
|
17189
17343
|
value: {
|
|
@@ -17201,7 +17355,7 @@
|
|
|
17201
17355
|
onNext();
|
|
17202
17356
|
return;
|
|
17203
17357
|
}
|
|
17204
|
-
resolveAndSave(table, cursor.trans, cursor.primaryKey, rawValue,
|
|
17358
|
+
resolveAndSave(table, cursor.trans, cursor.primaryKey, rawValue, db, true).then((resolved) => {
|
|
17205
17359
|
wrappedCursor.value = resolved;
|
|
17206
17360
|
onNext();
|
|
17207
17361
|
}, (err) => {
|
|
@@ -17229,7 +17383,7 @@
|
|
|
17229
17383
|
* Returns Dexie.Promise to preserve PSD context.
|
|
17230
17384
|
*/
|
|
17231
17385
|
function resolveAndSave(table, trans, pKey, // optional. If missing, tries to extract from object using primary key path
|
|
17232
|
-
obj,
|
|
17386
|
+
obj, db, isCursorValue = false // Flag to indicate if we're resolving a cursor value (which may not have a primary key)
|
|
17233
17387
|
) {
|
|
17234
17388
|
var _a;
|
|
17235
17389
|
try {
|
|
@@ -17266,21 +17420,19 @@
|
|
|
17266
17420
|
? Dexie.getByKeyPath(obj, primaryKey.keyPath)
|
|
17267
17421
|
: undefined;
|
|
17268
17422
|
if (key !== undefined) {
|
|
17269
|
-
//
|
|
17270
|
-
//
|
|
17271
|
-
//
|
|
17272
|
-
|
|
17273
|
-
|
|
17274
|
-
|
|
17275
|
-
|
|
17276
|
-
|
|
17277
|
-
|
|
17278
|
-
|
|
17279
|
-
|
|
17280
|
-
|
|
17281
|
-
|
|
17282
|
-
});
|
|
17283
|
-
}
|
|
17423
|
+
// Hand off persistence to the tracker. The tracker owns an
|
|
17424
|
+
// internal save-queue that runs in a fresh JS task (setTimeout 0)
|
|
17425
|
+
// — completely outside any PSD context, so opening a Dexie rw
|
|
17426
|
+
// transaction there is always safe regardless of the calling
|
|
17427
|
+
// context. The tracker also keeps the in-flight download cache
|
|
17428
|
+
// alive until the save completes, so concurrent readers piggyback
|
|
17429
|
+
// on the already-downloaded data instead of refetching.
|
|
17430
|
+
db.blobDownloadTracker.enqueueSave(table.name, key, resolvedBlobs);
|
|
17431
|
+
}
|
|
17432
|
+
else if (resolvedBlobs.length > 0) {
|
|
17433
|
+
// No primary key — we can't persist. Release the in-flight cache
|
|
17434
|
+
// entries explicitly so they don't leak.
|
|
17435
|
+
db.blobDownloadTracker.releaseRefs(resolvedBlobs.map((b) => b.ref));
|
|
17284
17436
|
}
|
|
17285
17437
|
return resolved;
|
|
17286
17438
|
})
|
|
@@ -19741,7 +19893,7 @@
|
|
|
19741
19893
|
const downloading$ = createDownloadingState();
|
|
19742
19894
|
dexie.cloud = {
|
|
19743
19895
|
// @ts-ignore
|
|
19744
|
-
version: "4.4.
|
|
19896
|
+
version: "4.4.13",
|
|
19745
19897
|
options: Object.assign({}, DEFAULT_OPTIONS),
|
|
19746
19898
|
schema: null,
|
|
19747
19899
|
get currentUserId() {
|
|
@@ -20186,7 +20338,7 @@
|
|
|
20186
20338
|
}
|
|
20187
20339
|
}
|
|
20188
20340
|
// @ts-ignore
|
|
20189
|
-
dexieCloud.version = "4.4.
|
|
20341
|
+
dexieCloud.version = "4.4.13";
|
|
20190
20342
|
Dexie.Cloud = dexieCloud;
|
|
20191
20343
|
|
|
20192
20344
|
exports.default = dexieCloud;
|