dexie-cloud-addon 4.4.0 → 4.4.1
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 +10 -0
- package/dist/modern/dexie-cloud-addon.js +115 -29
- 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/service-worker.js +252 -166
- 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/dexie-cloud-addon.js +116 -30
- 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 +253 -167
- 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 +1 -1
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* ==========================================================================
|
|
10
10
|
*
|
|
11
|
-
* Version 4.4.
|
|
11
|
+
* Version 4.4.1, Thu Mar 19 2026
|
|
12
12
|
*
|
|
13
13
|
* https://dexie.org
|
|
14
14
|
*
|
|
@@ -3903,6 +3903,194 @@
|
|
|
3903
3903
|
});
|
|
3904
3904
|
}
|
|
3905
3905
|
|
|
3906
|
+
/**
|
|
3907
|
+
* Check if a value is a BlobRef (offloaded binary data)
|
|
3908
|
+
* A BlobRef has _bt (type), ref (blob ID), but no v (inline data)
|
|
3909
|
+
*/
|
|
3910
|
+
function isBlobRef(value) {
|
|
3911
|
+
if (typeof value !== 'object' || value === null)
|
|
3912
|
+
return false;
|
|
3913
|
+
const obj = value;
|
|
3914
|
+
return (typeof obj._bt === 'string' &&
|
|
3915
|
+
typeof obj.ref === 'string' &&
|
|
3916
|
+
obj.v === undefined // No inline data = it's a reference
|
|
3917
|
+
);
|
|
3918
|
+
}
|
|
3919
|
+
/**
|
|
3920
|
+
* Check if a value is a serialized TSONRef (after IndexedDB storage)
|
|
3921
|
+
* Has 'type' instead of '$t', and no Symbol marker
|
|
3922
|
+
*/
|
|
3923
|
+
function isSerializedTSONRef(value) {
|
|
3924
|
+
if (typeof value !== 'object' || value === null)
|
|
3925
|
+
return false;
|
|
3926
|
+
const obj = value;
|
|
3927
|
+
return (typeof obj.type === 'string' &&
|
|
3928
|
+
typeof obj.ref === 'string' &&
|
|
3929
|
+
typeof obj.size === 'number' &&
|
|
3930
|
+
obj._bt === undefined // Not a raw BlobRef
|
|
3931
|
+
);
|
|
3932
|
+
}
|
|
3933
|
+
/**
|
|
3934
|
+
* Recursively check if an object contains any BlobRefs
|
|
3935
|
+
*/
|
|
3936
|
+
function hasBlobRefs(obj, visited = new WeakSet()) {
|
|
3937
|
+
if (obj === null || obj === undefined) {
|
|
3938
|
+
return false;
|
|
3939
|
+
}
|
|
3940
|
+
if (isBlobRef(obj)) {
|
|
3941
|
+
return true;
|
|
3942
|
+
}
|
|
3943
|
+
if (typeof obj !== 'object') {
|
|
3944
|
+
return false;
|
|
3945
|
+
}
|
|
3946
|
+
// Avoid circular references - check BEFORE processing
|
|
3947
|
+
if (visited.has(obj)) {
|
|
3948
|
+
return false;
|
|
3949
|
+
}
|
|
3950
|
+
visited.add(obj);
|
|
3951
|
+
// Skip special objects that can't contain BlobRefs
|
|
3952
|
+
if (obj instanceof Date || obj instanceof RegExp || obj instanceof Blob) {
|
|
3953
|
+
return false;
|
|
3954
|
+
}
|
|
3955
|
+
if (obj instanceof ArrayBuffer || ArrayBuffer.isView(obj)) {
|
|
3956
|
+
return false;
|
|
3957
|
+
}
|
|
3958
|
+
if (Array.isArray(obj)) {
|
|
3959
|
+
return obj.some(item => hasBlobRefs(item, visited));
|
|
3960
|
+
}
|
|
3961
|
+
// Only traverse POJOs
|
|
3962
|
+
if (obj.constructor === Object) {
|
|
3963
|
+
return Object.values(obj).some(value => hasBlobRefs(value, visited));
|
|
3964
|
+
}
|
|
3965
|
+
return false;
|
|
3966
|
+
}
|
|
3967
|
+
/**
|
|
3968
|
+
* Convert downloaded Uint8Array to the original type specified in BlobRef
|
|
3969
|
+
*/
|
|
3970
|
+
function convertToOriginalType(data, ref) {
|
|
3971
|
+
// String type: decode UTF-8 back to string
|
|
3972
|
+
if (ref._bt === 'string') {
|
|
3973
|
+
return new TextDecoder().decode(data);
|
|
3974
|
+
}
|
|
3975
|
+
// Get the underlying ArrayBuffer (handle shared buffer case)
|
|
3976
|
+
const buffer = data.buffer.byteLength === data.byteLength
|
|
3977
|
+
? data.buffer
|
|
3978
|
+
: data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
|
3979
|
+
switch (ref._bt) {
|
|
3980
|
+
case 'Blob':
|
|
3981
|
+
return new Blob([new Uint8Array(buffer)], { type: ref.ct || '' });
|
|
3982
|
+
case 'ArrayBuffer':
|
|
3983
|
+
return buffer;
|
|
3984
|
+
case 'Uint8Array':
|
|
3985
|
+
return data;
|
|
3986
|
+
case 'Int8Array':
|
|
3987
|
+
return new Int8Array(buffer);
|
|
3988
|
+
case 'Uint8ClampedArray':
|
|
3989
|
+
return new Uint8ClampedArray(buffer);
|
|
3990
|
+
case 'Int16Array':
|
|
3991
|
+
return new Int16Array(buffer);
|
|
3992
|
+
case 'Uint16Array':
|
|
3993
|
+
return new Uint16Array(buffer);
|
|
3994
|
+
case 'Int32Array':
|
|
3995
|
+
return new Int32Array(buffer);
|
|
3996
|
+
case 'Uint32Array':
|
|
3997
|
+
return new Uint32Array(buffer);
|
|
3998
|
+
case 'Float32Array':
|
|
3999
|
+
return new Float32Array(buffer);
|
|
4000
|
+
case 'Float64Array':
|
|
4001
|
+
return new Float64Array(buffer);
|
|
4002
|
+
case 'BigInt64Array':
|
|
4003
|
+
return new BigInt64Array(buffer);
|
|
4004
|
+
case 'BigUint64Array':
|
|
4005
|
+
return new BigUint64Array(buffer);
|
|
4006
|
+
case 'DataView':
|
|
4007
|
+
return new DataView(buffer);
|
|
4008
|
+
default:
|
|
4009
|
+
// Fallback to Uint8Array for unknown types
|
|
4010
|
+
return data;
|
|
4011
|
+
}
|
|
4012
|
+
}
|
|
4013
|
+
/**
|
|
4014
|
+
* Recursively resolve all BlobRefs in an object and collect them for queueing.
|
|
4015
|
+
* Returns a new object with BlobRefs replaced by their original type data,
|
|
4016
|
+
* and populates the resolvedBlobs array with keyPath info for each blob.
|
|
4017
|
+
*
|
|
4018
|
+
* @param obj - Object to resolve
|
|
4019
|
+
* @param dbUrl - Base URL for the database
|
|
4020
|
+
* @param accessToken - Access token for blob downloads
|
|
4021
|
+
* @param resolvedBlobs - Array to collect resolved blob info
|
|
4022
|
+
* @param currentPath - Current property path (for tracking)
|
|
4023
|
+
* @param visited - WeakMap for circular reference detection
|
|
4024
|
+
*/
|
|
4025
|
+
function resolveAllBlobRefs(obj_1, dbUrl_1) {
|
|
4026
|
+
return __awaiter(this, arguments, void 0, function* (obj, dbUrl, resolvedBlobs = [], currentPath = '', visited = new WeakMap(), tracker) {
|
|
4027
|
+
if (obj == null) { // null or undefined
|
|
4028
|
+
return obj;
|
|
4029
|
+
}
|
|
4030
|
+
// Check if this is a BlobRef - resolve it and track it
|
|
4031
|
+
if (isBlobRef(obj)) {
|
|
4032
|
+
const rawData = yield tracker.download(obj, dbUrl);
|
|
4033
|
+
const data = convertToOriginalType(rawData, obj);
|
|
4034
|
+
resolvedBlobs.push({ keyPath: currentPath, data, ref: obj.ref });
|
|
4035
|
+
return data;
|
|
4036
|
+
}
|
|
4037
|
+
// Handle arrays
|
|
4038
|
+
if (Array.isArray(obj)) {
|
|
4039
|
+
// Avoid circular references - check and set BEFORE iterating
|
|
4040
|
+
if (visited.has(obj)) {
|
|
4041
|
+
return visited.get(obj);
|
|
4042
|
+
}
|
|
4043
|
+
const result = [];
|
|
4044
|
+
visited.set(obj, result); // Set before iterating to handle self-references
|
|
4045
|
+
for (let i = 0; i < obj.length; i++) {
|
|
4046
|
+
const itemPath = currentPath ? `${currentPath}.${i}` : `${i}`;
|
|
4047
|
+
result.push(yield resolveAllBlobRefs(obj[i], dbUrl, resolvedBlobs, itemPath, visited, tracker));
|
|
4048
|
+
}
|
|
4049
|
+
return result;
|
|
4050
|
+
}
|
|
4051
|
+
// Handle POJO objects only (not Date, RegExp, Blob, ArrayBuffer, etc.)
|
|
4052
|
+
if (typeof obj === 'object' && obj.constructor === Object) {
|
|
4053
|
+
// Avoid circular references
|
|
4054
|
+
if (visited.has(obj)) {
|
|
4055
|
+
return visited.get(obj);
|
|
4056
|
+
}
|
|
4057
|
+
const result = {};
|
|
4058
|
+
visited.set(obj, result);
|
|
4059
|
+
for (const [propName, value] of Object.entries(obj)) {
|
|
4060
|
+
// Skip the _hasBlobRefs marker itself
|
|
4061
|
+
if (propName === '_hasBlobRefs') {
|
|
4062
|
+
continue;
|
|
4063
|
+
}
|
|
4064
|
+
const propPath = currentPath ? `${currentPath}.${propName}` : propName;
|
|
4065
|
+
result[propName] = yield resolveAllBlobRefs(value, dbUrl, resolvedBlobs, propPath, visited, tracker);
|
|
4066
|
+
}
|
|
4067
|
+
return result;
|
|
4068
|
+
}
|
|
4069
|
+
return obj;
|
|
4070
|
+
});
|
|
4071
|
+
}
|
|
4072
|
+
/**
|
|
4073
|
+
* Check if an object has unresolved BlobRefs
|
|
4074
|
+
*/
|
|
4075
|
+
function hasUnresolvedBlobRefs(obj) {
|
|
4076
|
+
return (typeof obj === 'object' &&
|
|
4077
|
+
obj !== null &&
|
|
4078
|
+
obj._hasBlobRefs === 1);
|
|
4079
|
+
}
|
|
4080
|
+
|
|
4081
|
+
/**
|
|
4082
|
+
* If the incoming value contains BlobRefs (e.g. offloaded strings or binaries),
|
|
4083
|
+
* mark it with _hasBlobRefs = 1 so the blobResolveMiddleware will resolve them
|
|
4084
|
+
* on the next read.
|
|
4085
|
+
*/
|
|
4086
|
+
function markIfHasBlobRefs(obj) {
|
|
4087
|
+
if (obj !== null &&
|
|
4088
|
+
typeof obj === 'object' &&
|
|
4089
|
+
obj.constructor === Object &&
|
|
4090
|
+
hasBlobRefs(obj)) {
|
|
4091
|
+
obj._hasBlobRefs = 1;
|
|
4092
|
+
}
|
|
4093
|
+
}
|
|
3906
4094
|
function applyServerChanges(changes, db) {
|
|
3907
4095
|
return __awaiter(this, void 0, void 0, function* () {
|
|
3908
4096
|
console.debug('Applying server changes', changes, Dexie.currentTransaction);
|
|
@@ -3938,6 +4126,7 @@
|
|
|
3938
4126
|
const keys = mut.keys.map(keyDecoder);
|
|
3939
4127
|
switch (mut.type) {
|
|
3940
4128
|
case 'insert':
|
|
4129
|
+
mut.values.forEach(markIfHasBlobRefs);
|
|
3941
4130
|
if (primaryKey.outbound) {
|
|
3942
4131
|
yield table.bulkAdd(mut.values, keys);
|
|
3943
4132
|
}
|
|
@@ -3950,6 +4139,7 @@
|
|
|
3950
4139
|
}
|
|
3951
4140
|
break;
|
|
3952
4141
|
case 'upsert':
|
|
4142
|
+
mut.values.forEach(markIfHasBlobRefs);
|
|
3953
4143
|
if (primaryKey.outbound) {
|
|
3954
4144
|
yield table.bulkPut(mut.values, keys);
|
|
3955
4145
|
}
|
|
@@ -13368,7 +13558,7 @@
|
|
|
13368
13558
|
*
|
|
13369
13559
|
* ==========================================================================
|
|
13370
13560
|
*
|
|
13371
|
-
* Version 4.4.0,
|
|
13561
|
+
* Version 4.4.0, Thu Mar 19 2026
|
|
13372
13562
|
*
|
|
13373
13563
|
* https://dexie.org
|
|
13374
13564
|
*
|
|
@@ -13865,143 +14055,6 @@
|
|
|
13865
14055
|
});
|
|
13866
14056
|
}
|
|
13867
14057
|
|
|
13868
|
-
/**
|
|
13869
|
-
* Check if a value is a BlobRef (offloaded binary data)
|
|
13870
|
-
* A BlobRef has _bt (type), ref (blob ID), but no v (inline data)
|
|
13871
|
-
*/
|
|
13872
|
-
function isBlobRef(value) {
|
|
13873
|
-
if (typeof value !== 'object' || value === null)
|
|
13874
|
-
return false;
|
|
13875
|
-
const obj = value;
|
|
13876
|
-
return (typeof obj._bt === 'string' &&
|
|
13877
|
-
typeof obj.ref === 'string' &&
|
|
13878
|
-
obj.v === undefined // No inline data = it's a reference
|
|
13879
|
-
);
|
|
13880
|
-
}
|
|
13881
|
-
/**
|
|
13882
|
-
* Check if a value is a serialized TSONRef (after IndexedDB storage)
|
|
13883
|
-
* Has 'type' instead of '$t', and no Symbol marker
|
|
13884
|
-
*/
|
|
13885
|
-
function isSerializedTSONRef(value) {
|
|
13886
|
-
if (typeof value !== 'object' || value === null)
|
|
13887
|
-
return false;
|
|
13888
|
-
const obj = value;
|
|
13889
|
-
return (typeof obj.type === 'string' &&
|
|
13890
|
-
typeof obj.ref === 'string' &&
|
|
13891
|
-
typeof obj.size === 'number' &&
|
|
13892
|
-
obj._bt === undefined // Not a raw BlobRef
|
|
13893
|
-
);
|
|
13894
|
-
}
|
|
13895
|
-
/**
|
|
13896
|
-
* Convert downloaded Uint8Array to the original type specified in BlobRef
|
|
13897
|
-
*/
|
|
13898
|
-
function convertToOriginalType(data, ref) {
|
|
13899
|
-
// Get the underlying ArrayBuffer (handle shared buffer case)
|
|
13900
|
-
const buffer = data.buffer.byteLength === data.byteLength
|
|
13901
|
-
? data.buffer
|
|
13902
|
-
: data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
|
13903
|
-
switch (ref._bt) {
|
|
13904
|
-
case 'Blob':
|
|
13905
|
-
return new Blob([new Uint8Array(buffer)], { type: ref.ct || '' });
|
|
13906
|
-
case 'ArrayBuffer':
|
|
13907
|
-
return buffer;
|
|
13908
|
-
case 'Uint8Array':
|
|
13909
|
-
return data;
|
|
13910
|
-
case 'Int8Array':
|
|
13911
|
-
return new Int8Array(buffer);
|
|
13912
|
-
case 'Uint8ClampedArray':
|
|
13913
|
-
return new Uint8ClampedArray(buffer);
|
|
13914
|
-
case 'Int16Array':
|
|
13915
|
-
return new Int16Array(buffer);
|
|
13916
|
-
case 'Uint16Array':
|
|
13917
|
-
return new Uint16Array(buffer);
|
|
13918
|
-
case 'Int32Array':
|
|
13919
|
-
return new Int32Array(buffer);
|
|
13920
|
-
case 'Uint32Array':
|
|
13921
|
-
return new Uint32Array(buffer);
|
|
13922
|
-
case 'Float32Array':
|
|
13923
|
-
return new Float32Array(buffer);
|
|
13924
|
-
case 'Float64Array':
|
|
13925
|
-
return new Float64Array(buffer);
|
|
13926
|
-
case 'BigInt64Array':
|
|
13927
|
-
return new BigInt64Array(buffer);
|
|
13928
|
-
case 'BigUint64Array':
|
|
13929
|
-
return new BigUint64Array(buffer);
|
|
13930
|
-
case 'DataView':
|
|
13931
|
-
return new DataView(buffer);
|
|
13932
|
-
default:
|
|
13933
|
-
// Fallback to Uint8Array for unknown types
|
|
13934
|
-
return data;
|
|
13935
|
-
}
|
|
13936
|
-
}
|
|
13937
|
-
/**
|
|
13938
|
-
* Recursively resolve all BlobRefs in an object and collect them for queueing.
|
|
13939
|
-
* Returns a new object with BlobRefs replaced by their original type data,
|
|
13940
|
-
* and populates the resolvedBlobs array with keyPath info for each blob.
|
|
13941
|
-
*
|
|
13942
|
-
* @param obj - Object to resolve
|
|
13943
|
-
* @param dbUrl - Base URL for the database
|
|
13944
|
-
* @param accessToken - Access token for blob downloads
|
|
13945
|
-
* @param resolvedBlobs - Array to collect resolved blob info
|
|
13946
|
-
* @param currentPath - Current property path (for tracking)
|
|
13947
|
-
* @param visited - WeakMap for circular reference detection
|
|
13948
|
-
*/
|
|
13949
|
-
function resolveAllBlobRefs(obj_1, dbUrl_1) {
|
|
13950
|
-
return __awaiter(this, arguments, void 0, function* (obj, dbUrl, resolvedBlobs = [], currentPath = '', visited = new WeakMap(), tracker) {
|
|
13951
|
-
if (obj == null) { // null or undefined
|
|
13952
|
-
return obj;
|
|
13953
|
-
}
|
|
13954
|
-
// Check if this is a BlobRef - resolve it and track it
|
|
13955
|
-
if (isBlobRef(obj)) {
|
|
13956
|
-
const rawData = yield tracker.download(obj, dbUrl);
|
|
13957
|
-
const data = convertToOriginalType(rawData, obj);
|
|
13958
|
-
resolvedBlobs.push({ keyPath: currentPath, data, ref: obj.ref });
|
|
13959
|
-
return data;
|
|
13960
|
-
}
|
|
13961
|
-
// Handle arrays
|
|
13962
|
-
if (Array.isArray(obj)) {
|
|
13963
|
-
// Avoid circular references - check and set BEFORE iterating
|
|
13964
|
-
if (visited.has(obj)) {
|
|
13965
|
-
return visited.get(obj);
|
|
13966
|
-
}
|
|
13967
|
-
const result = [];
|
|
13968
|
-
visited.set(obj, result); // Set before iterating to handle self-references
|
|
13969
|
-
for (let i = 0; i < obj.length; i++) {
|
|
13970
|
-
const itemPath = currentPath ? `${currentPath}.${i}` : `${i}`;
|
|
13971
|
-
result.push(yield resolveAllBlobRefs(obj[i], dbUrl, resolvedBlobs, itemPath, visited, tracker));
|
|
13972
|
-
}
|
|
13973
|
-
return result;
|
|
13974
|
-
}
|
|
13975
|
-
// Handle POJO objects only (not Date, RegExp, Blob, ArrayBuffer, etc.)
|
|
13976
|
-
if (typeof obj === 'object' && obj.constructor === Object) {
|
|
13977
|
-
// Avoid circular references
|
|
13978
|
-
if (visited.has(obj)) {
|
|
13979
|
-
return visited.get(obj);
|
|
13980
|
-
}
|
|
13981
|
-
const result = {};
|
|
13982
|
-
visited.set(obj, result);
|
|
13983
|
-
for (const [propName, value] of Object.entries(obj)) {
|
|
13984
|
-
// Skip the _hasBlobRefs marker itself
|
|
13985
|
-
if (propName === '_hasBlobRefs') {
|
|
13986
|
-
continue;
|
|
13987
|
-
}
|
|
13988
|
-
const propPath = currentPath ? `${currentPath}.${propName}` : propName;
|
|
13989
|
-
result[propName] = yield resolveAllBlobRefs(value, dbUrl, resolvedBlobs, propPath, visited, tracker);
|
|
13990
|
-
}
|
|
13991
|
-
return result;
|
|
13992
|
-
}
|
|
13993
|
-
return obj;
|
|
13994
|
-
});
|
|
13995
|
-
}
|
|
13996
|
-
/**
|
|
13997
|
-
* Check if an object has unresolved BlobRefs
|
|
13998
|
-
*/
|
|
13999
|
-
function hasUnresolvedBlobRefs(obj) {
|
|
14000
|
-
return (typeof obj === 'object' &&
|
|
14001
|
-
obj !== null &&
|
|
14002
|
-
obj._hasBlobRefs === 1);
|
|
14003
|
-
}
|
|
14004
|
-
|
|
14005
14058
|
/**
|
|
14006
14059
|
* Blob Offloading for Dexie Cloud
|
|
14007
14060
|
*
|
|
@@ -14010,6 +14063,8 @@
|
|
|
14010
14063
|
*/
|
|
14011
14064
|
// Blobs >= 4KB are offloaded to blob storage
|
|
14012
14065
|
const BLOB_OFFLOAD_THRESHOLD = 4096;
|
|
14066
|
+
// Default max string length before offloading (32KB characters)
|
|
14067
|
+
const DEFAULT_MAX_STRING_LENGTH = 32768;
|
|
14013
14068
|
// Cache: once we know the server doesn't support blob storage, skip future uploads.
|
|
14014
14069
|
// Maps databaseUrl → boolean (true = supported, false = not supported).
|
|
14015
14070
|
const blobEndpointSupported = new Map();
|
|
@@ -14150,10 +14205,10 @@
|
|
|
14150
14205
|
);
|
|
14151
14206
|
});
|
|
14152
14207
|
}
|
|
14153
|
-
function offloadBlobsAndMarkDirty(
|
|
14154
|
-
return __awaiter(this,
|
|
14208
|
+
function offloadBlobsAndMarkDirty(obj_1, databaseUrl_1, getCachedAccessToken_1) {
|
|
14209
|
+
return __awaiter(this, arguments, void 0, function* (obj, databaseUrl, getCachedAccessToken, maxStringLength = DEFAULT_MAX_STRING_LENGTH) {
|
|
14155
14210
|
const dirtyFlag = { dirty: false };
|
|
14156
|
-
const result = yield offloadBlobs(obj, databaseUrl, getCachedAccessToken, dirtyFlag);
|
|
14211
|
+
const result = yield offloadBlobs(obj, databaseUrl, getCachedAccessToken, maxStringLength, dirtyFlag);
|
|
14157
14212
|
// Mark the object as dirty for sync if any blobs were offloaded
|
|
14158
14213
|
if (dirtyFlag.dirty && typeof result === 'object' && result !== null && result.constructor === Object) {
|
|
14159
14214
|
result._hasBlobRefs = 1;
|
|
@@ -14166,10 +14221,26 @@
|
|
|
14166
14221
|
* Returns a new object with blobs replaced by BlobRefs
|
|
14167
14222
|
*/
|
|
14168
14223
|
function offloadBlobs(obj_1, databaseUrl_1, getCachedAccessToken_1) {
|
|
14169
|
-
return __awaiter(this, arguments, void 0, function* (obj, databaseUrl, getCachedAccessToken, dirtyFlag = { dirty: false }, visited = new WeakSet()) {
|
|
14224
|
+
return __awaiter(this, arguments, void 0, function* (obj, databaseUrl, getCachedAccessToken, maxStringLength = DEFAULT_MAX_STRING_LENGTH, dirtyFlag = { dirty: false }, visited = new WeakSet()) {
|
|
14170
14225
|
if (obj === null || obj === undefined) {
|
|
14171
14226
|
return obj;
|
|
14172
14227
|
}
|
|
14228
|
+
// Check if this is a long string that should be offloaded
|
|
14229
|
+
if (typeof obj === 'string' && obj.length > maxStringLength && maxStringLength !== Infinity) {
|
|
14230
|
+
if (blobEndpointSupported.get(databaseUrl) === false) {
|
|
14231
|
+
return obj;
|
|
14232
|
+
}
|
|
14233
|
+
const blob = new Blob([obj], { type: 'text/plain;charset=utf-8' });
|
|
14234
|
+
const blobRef = yield uploadBlob(databaseUrl, getCachedAccessToken, blob);
|
|
14235
|
+
if (blobRef === null) {
|
|
14236
|
+
blobEndpointSupported.set(databaseUrl, false);
|
|
14237
|
+
return obj;
|
|
14238
|
+
}
|
|
14239
|
+
blobEndpointSupported.set(databaseUrl, true);
|
|
14240
|
+
dirtyFlag.dirty = true;
|
|
14241
|
+
// Mark as string type so it's resolved back to string, not Blob
|
|
14242
|
+
return Object.assign(Object.assign({}, blobRef), { _bt: 'string' });
|
|
14243
|
+
}
|
|
14173
14244
|
// Check if this is a blob that should be offloaded
|
|
14174
14245
|
if (shouldOffloadBlob(obj)) {
|
|
14175
14246
|
if (blobEndpointSupported.get(databaseUrl) === false) {
|
|
@@ -14198,7 +14269,7 @@
|
|
|
14198
14269
|
if (Array.isArray(obj)) {
|
|
14199
14270
|
const result = [];
|
|
14200
14271
|
for (const item of obj) {
|
|
14201
|
-
result.push(yield offloadBlobs(item, databaseUrl, getCachedAccessToken, dirtyFlag, visited));
|
|
14272
|
+
result.push(yield offloadBlobs(item, databaseUrl, getCachedAccessToken, maxStringLength, dirtyFlag, visited));
|
|
14202
14273
|
}
|
|
14203
14274
|
return result;
|
|
14204
14275
|
}
|
|
@@ -14210,7 +14281,7 @@
|
|
|
14210
14281
|
}
|
|
14211
14282
|
const result = {};
|
|
14212
14283
|
for (const [key, value] of Object.entries(obj)) {
|
|
14213
|
-
result[key] = yield offloadBlobs(value, databaseUrl, getCachedAccessToken, dirtyFlag, visited);
|
|
14284
|
+
result[key] = yield offloadBlobs(value, databaseUrl, getCachedAccessToken, maxStringLength, dirtyFlag, visited);
|
|
14214
14285
|
}
|
|
14215
14286
|
return result;
|
|
14216
14287
|
});
|
|
@@ -14219,13 +14290,13 @@
|
|
|
14219
14290
|
* Process a DBOperationsSet and offload any large blobs
|
|
14220
14291
|
* Returns a new DBOperationsSet with blobs replaced by BlobRefs
|
|
14221
14292
|
*/
|
|
14222
|
-
function offloadBlobsInOperations(
|
|
14223
|
-
return __awaiter(this,
|
|
14293
|
+
function offloadBlobsInOperations(operations_1, databaseUrl_1, getCachedAccessToken_1) {
|
|
14294
|
+
return __awaiter(this, arguments, void 0, function* (operations, databaseUrl, getCachedAccessToken, maxStringLength = DEFAULT_MAX_STRING_LENGTH) {
|
|
14224
14295
|
const result = [];
|
|
14225
14296
|
for (const tableOps of operations) {
|
|
14226
14297
|
const processedMuts = [];
|
|
14227
14298
|
for (const mut of tableOps.muts) {
|
|
14228
|
-
const processedMut = yield offloadBlobsInOperation(mut, databaseUrl, getCachedAccessToken);
|
|
14299
|
+
const processedMut = yield offloadBlobsInOperation(mut, databaseUrl, getCachedAccessToken, maxStringLength);
|
|
14229
14300
|
processedMuts.push(processedMut);
|
|
14230
14301
|
}
|
|
14231
14302
|
result.push({
|
|
@@ -14236,20 +14307,20 @@
|
|
|
14236
14307
|
return result;
|
|
14237
14308
|
});
|
|
14238
14309
|
}
|
|
14239
|
-
function offloadBlobsInOperation(
|
|
14240
|
-
return __awaiter(this,
|
|
14310
|
+
function offloadBlobsInOperation(op_1, databaseUrl_1, getCachedAccessToken_1) {
|
|
14311
|
+
return __awaiter(this, arguments, void 0, function* (op, databaseUrl, getCachedAccessToken, maxStringLength = DEFAULT_MAX_STRING_LENGTH) {
|
|
14241
14312
|
switch (op.type) {
|
|
14242
14313
|
case 'insert':
|
|
14243
14314
|
case 'upsert': {
|
|
14244
|
-
const processedValues = yield Promise.all(op.values.map(value => offloadBlobsAndMarkDirty(value, databaseUrl, getCachedAccessToken)));
|
|
14315
|
+
const processedValues = yield Promise.all(op.values.map(value => offloadBlobsAndMarkDirty(value, databaseUrl, getCachedAccessToken, maxStringLength)));
|
|
14245
14316
|
return Object.assign(Object.assign({}, op), { values: processedValues });
|
|
14246
14317
|
}
|
|
14247
14318
|
case 'update': {
|
|
14248
|
-
const processedChangeSpecs = yield Promise.all(op.changeSpecs.map(spec => offloadBlobsAndMarkDirty(spec, databaseUrl, getCachedAccessToken)));
|
|
14319
|
+
const processedChangeSpecs = yield Promise.all(op.changeSpecs.map(spec => offloadBlobsAndMarkDirty(spec, databaseUrl, getCachedAccessToken, maxStringLength)));
|
|
14249
14320
|
return Object.assign(Object.assign({}, op), { changeSpecs: processedChangeSpecs });
|
|
14250
14321
|
}
|
|
14251
14322
|
case 'modify': {
|
|
14252
|
-
const processedChangeSpec = yield offloadBlobsAndMarkDirty(op.changeSpec, databaseUrl, getCachedAccessToken);
|
|
14323
|
+
const processedChangeSpec = yield offloadBlobsAndMarkDirty(op.changeSpec, databaseUrl, getCachedAccessToken, maxStringLength);
|
|
14253
14324
|
return Object.assign(Object.assign({}, op), { changeSpec: processedChangeSpec });
|
|
14254
14325
|
}
|
|
14255
14326
|
case 'delete':
|
|
@@ -14264,33 +14335,37 @@
|
|
|
14264
14335
|
* Check if there are any large blobs in the operations that need offloading
|
|
14265
14336
|
* This is a quick check to avoid unnecessary processing
|
|
14266
14337
|
*/
|
|
14267
|
-
function hasLargeBlobsInOperations(operations) {
|
|
14338
|
+
function hasLargeBlobsInOperations(operations, maxStringLength = DEFAULT_MAX_STRING_LENGTH) {
|
|
14268
14339
|
for (const tableOps of operations) {
|
|
14269
14340
|
for (const mut of tableOps.muts) {
|
|
14270
|
-
if (hasLargeBlobsInOperation(mut)) {
|
|
14341
|
+
if (hasLargeBlobsInOperation(mut, maxStringLength)) {
|
|
14271
14342
|
return true;
|
|
14272
14343
|
}
|
|
14273
14344
|
}
|
|
14274
14345
|
}
|
|
14275
14346
|
return false;
|
|
14276
14347
|
}
|
|
14277
|
-
function hasLargeBlobsInOperation(op) {
|
|
14348
|
+
function hasLargeBlobsInOperation(op, maxStringLength) {
|
|
14278
14349
|
switch (op.type) {
|
|
14279
14350
|
case 'insert':
|
|
14280
14351
|
case 'upsert':
|
|
14281
|
-
return op.values.some(value => hasLargeBlobs(value));
|
|
14352
|
+
return op.values.some(value => hasLargeBlobs(value, maxStringLength));
|
|
14282
14353
|
case 'update':
|
|
14283
|
-
return op.changeSpecs.some(spec => hasLargeBlobs(spec));
|
|
14354
|
+
return op.changeSpecs.some(spec => hasLargeBlobs(spec, maxStringLength));
|
|
14284
14355
|
case 'modify':
|
|
14285
|
-
return hasLargeBlobs(op.changeSpec);
|
|
14356
|
+
return hasLargeBlobs(op.changeSpec, maxStringLength);
|
|
14286
14357
|
default:
|
|
14287
14358
|
return false;
|
|
14288
14359
|
}
|
|
14289
14360
|
}
|
|
14290
|
-
function hasLargeBlobs(obj, visited = new WeakSet()) {
|
|
14361
|
+
function hasLargeBlobs(obj, maxStringLength, visited = new WeakSet()) {
|
|
14291
14362
|
if (obj === null || obj === undefined) {
|
|
14292
14363
|
return false;
|
|
14293
14364
|
}
|
|
14365
|
+
// Check long strings
|
|
14366
|
+
if (typeof obj === 'string' && obj.length > maxStringLength && maxStringLength !== Infinity) {
|
|
14367
|
+
return true;
|
|
14368
|
+
}
|
|
14294
14369
|
if (shouldOffloadBlob(obj)) {
|
|
14295
14370
|
return true;
|
|
14296
14371
|
}
|
|
@@ -14303,13 +14378,13 @@
|
|
|
14303
14378
|
}
|
|
14304
14379
|
visited.add(obj);
|
|
14305
14380
|
if (Array.isArray(obj)) {
|
|
14306
|
-
return obj.some(item => hasLargeBlobs(item, visited));
|
|
14381
|
+
return obj.some(item => hasLargeBlobs(item, maxStringLength, visited));
|
|
14307
14382
|
}
|
|
14308
14383
|
// Traverse plain objects (POJO-like) - use duck typing since IndexedDB
|
|
14309
14384
|
// may return objects where constructor !== Object
|
|
14310
14385
|
const proto = Object.getPrototypeOf(obj);
|
|
14311
14386
|
if (proto === Object.prototype || proto === null) {
|
|
14312
|
-
return Object.values(obj).some(value => hasLargeBlobs(value, visited));
|
|
14387
|
+
return Object.values(obj).some(value => hasLargeBlobs(value, maxStringLength, visited));
|
|
14313
14388
|
}
|
|
14314
14389
|
return false;
|
|
14315
14390
|
}
|
|
@@ -14570,7 +14645,7 @@
|
|
|
14570
14645
|
return __awaiter(this, arguments, void 0, function* (db, options, schema, { isInitialSync, cancelToken, justCheckIfNeeded, purpose } = {
|
|
14571
14646
|
isInitialSync: false,
|
|
14572
14647
|
}) {
|
|
14573
|
-
var _a;
|
|
14648
|
+
var _a, _b, _c;
|
|
14574
14649
|
if (!justCheckIfNeeded) {
|
|
14575
14650
|
console.debug('SYNC STARTED', { isInitialSync, purpose });
|
|
14576
14651
|
}
|
|
@@ -14644,9 +14719,10 @@
|
|
|
14644
14719
|
// Offload large blobs to blob storage before sync
|
|
14645
14720
|
//
|
|
14646
14721
|
let processedChangeSet = clientChangeSet;
|
|
14647
|
-
const
|
|
14722
|
+
const maxStringLength = (_c = (_b = db.cloud.options) === null || _b === void 0 ? void 0 : _b.maxStringLength) !== null && _c !== void 0 ? _c : 32768;
|
|
14723
|
+
const hasLargeBlobs = hasLargeBlobsInOperations(clientChangeSet, maxStringLength);
|
|
14648
14724
|
if (hasLargeBlobs) {
|
|
14649
|
-
processedChangeSet = yield offloadBlobsInOperations(clientChangeSet, databaseUrl, () => loadCachedAccessToken(db));
|
|
14725
|
+
processedChangeSet = yield offloadBlobsInOperations(clientChangeSet, databaseUrl, () => loadCachedAccessToken(db), maxStringLength);
|
|
14650
14726
|
}
|
|
14651
14727
|
//
|
|
14652
14728
|
// Push changes to server
|
|
@@ -19248,7 +19324,7 @@
|
|
|
19248
19324
|
const downloading$ = createDownloadingState();
|
|
19249
19325
|
dexie.cloud = {
|
|
19250
19326
|
// @ts-ignore
|
|
19251
|
-
version: "4.4.
|
|
19327
|
+
version: "4.4.1",
|
|
19252
19328
|
options: Object.assign({}, DEFAULT_OPTIONS),
|
|
19253
19329
|
schema: null,
|
|
19254
19330
|
get currentUserId() {
|
|
@@ -19276,6 +19352,16 @@
|
|
|
19276
19352
|
invites: getInvitesObservable(dexie),
|
|
19277
19353
|
roles: getGlobalRolesObservable(dexie),
|
|
19278
19354
|
configure(options) {
|
|
19355
|
+
// Validate maxStringLength — Infinity disables offloading, otherwise must be
|
|
19356
|
+
// a finite positive number not exceeding the server limit (32768).
|
|
19357
|
+
const MAX_SERVER_STRING_LENGTH = 32768;
|
|
19358
|
+
if (options.maxStringLength !== undefined &&
|
|
19359
|
+
options.maxStringLength !== Infinity &&
|
|
19360
|
+
(!Number.isFinite(options.maxStringLength) ||
|
|
19361
|
+
options.maxStringLength < 0 ||
|
|
19362
|
+
options.maxStringLength > MAX_SERVER_STRING_LENGTH)) {
|
|
19363
|
+
throw new Error(`maxStringLength must be Infinity or a finite number in [0, ${MAX_SERVER_STRING_LENGTH}]. Got: ${options.maxStringLength}`);
|
|
19364
|
+
}
|
|
19279
19365
|
options = dexie.cloud.options = Object.assign(Object.assign({}, dexie.cloud.options), options);
|
|
19280
19366
|
configuredProgramatically = true;
|
|
19281
19367
|
if (options.databaseUrl && options.nameSuffix) {
|
|
@@ -19662,7 +19748,7 @@
|
|
|
19662
19748
|
}
|
|
19663
19749
|
}
|
|
19664
19750
|
// @ts-ignore
|
|
19665
|
-
dexieCloud.version = "4.4.
|
|
19751
|
+
dexieCloud.version = "4.4.1";
|
|
19666
19752
|
Dexie.Cloud = dexieCloud;
|
|
19667
19753
|
|
|
19668
19754
|
// In case the SW lives for a while, let it reuse already opened connections:
|