@pol-studios/powersync 1.0.7 → 1.0.10
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/README.md +933 -0
- package/dist/CacheSettingsManager-uz-kbnRH.d.ts +461 -0
- package/dist/attachments/index.d.ts +709 -6
- package/dist/attachments/index.js +133 -5
- package/dist/chunk-24RDMMCL.js +44 -0
- package/dist/chunk-24RDMMCL.js.map +1 -0
- package/dist/chunk-4TXTAEF2.js +2060 -0
- package/dist/chunk-4TXTAEF2.js.map +1 -0
- package/dist/chunk-63PXSPIN.js +358 -0
- package/dist/chunk-63PXSPIN.js.map +1 -0
- package/dist/chunk-654ERHA7.js +1 -0
- package/dist/{chunk-BREGB4WL.js → chunk-BRXQNASY.js} +287 -335
- package/dist/chunk-BRXQNASY.js.map +1 -0
- package/dist/{chunk-DHYUBVP7.js → chunk-CAB26E6F.js} +20 -9
- package/dist/chunk-CAB26E6F.js.map +1 -0
- package/dist/{chunk-H772V6XQ.js → chunk-CUCAYK7Z.js} +7 -43
- package/dist/chunk-CUCAYK7Z.js.map +1 -0
- package/dist/{chunk-4C3RY5SU.js → chunk-HWSNV45P.js} +76 -1
- package/dist/chunk-HWSNV45P.js.map +1 -0
- package/dist/{chunk-HFOFLW5F.js → chunk-KN2IZERF.js} +139 -6
- package/dist/chunk-KN2IZERF.js.map +1 -0
- package/dist/{chunk-UEYRTLKE.js → chunk-P4HZA6ZT.js} +20 -9
- package/dist/chunk-P4HZA6ZT.js.map +1 -0
- package/dist/chunk-T4AO7JIG.js +1 -0
- package/dist/{chunk-XQAJM2MW.js → chunk-VACPAAQZ.js} +33 -2
- package/dist/{chunk-XQAJM2MW.js.map → chunk-VACPAAQZ.js.map} +1 -1
- package/dist/{chunk-53WH2JJV.js → chunk-WN5ZJ3E2.js} +5 -8
- package/dist/chunk-WN5ZJ3E2.js.map +1 -0
- package/dist/chunk-XAEII4ZX.js +456 -0
- package/dist/chunk-XAEII4ZX.js.map +1 -0
- package/dist/chunk-XOY2CJ67.js +289 -0
- package/dist/chunk-XOY2CJ67.js.map +1 -0
- package/dist/chunk-YHTZ7VMV.js +1 -0
- package/dist/{chunk-MKD2VCX3.js → chunk-Z6VOBGTU.js} +8 -8
- package/dist/chunk-Z6VOBGTU.js.map +1 -0
- package/dist/chunk-ZM4ENYMF.js +230 -0
- package/dist/chunk-ZM4ENYMF.js.map +1 -0
- package/dist/connector/index.d.ts +56 -3
- package/dist/connector/index.js +8 -5
- package/dist/core/index.d.ts +12 -1
- package/dist/core/index.js +3 -2
- package/dist/error/index.js +0 -1
- package/dist/index.d.ts +12 -10
- package/dist/index.js +191 -29
- package/dist/index.native.d.ts +11 -9
- package/dist/index.native.js +191 -29
- package/dist/index.web.d.ts +11 -9
- package/dist/index.web.js +191 -29
- package/dist/maintenance/index.js +0 -1
- package/dist/platform/index.js +0 -2
- package/dist/platform/index.js.map +1 -1
- package/dist/platform/index.native.js +1 -2
- package/dist/platform/index.web.js +0 -1
- package/dist/pol-attachment-queue-BVAIueoP.d.ts +817 -0
- package/dist/provider/index.d.ts +38 -34
- package/dist/provider/index.js +11 -12
- package/dist/react/index.d.ts +372 -0
- package/dist/react/index.js +25 -0
- package/dist/storage/index.d.ts +3 -3
- package/dist/storage/index.js +22 -8
- package/dist/storage/index.native.d.ts +3 -3
- package/dist/storage/index.native.js +21 -7
- package/dist/storage/index.web.d.ts +3 -3
- package/dist/storage/index.web.js +21 -7
- package/dist/storage/upload/index.d.ts +7 -8
- package/dist/storage/upload/index.js +3 -3
- package/dist/storage/upload/index.native.d.ts +7 -8
- package/dist/storage/upload/index.native.js +4 -3
- package/dist/storage/upload/index.web.d.ts +1 -4
- package/dist/storage/upload/index.web.js +3 -3
- package/dist/supabase-connector-T9vHq_3i.d.ts +202 -0
- package/dist/sync/index.js +3 -3
- package/dist/{supabase-connector-qLm-WHkM.d.ts → types-B212hgfA.d.ts} +48 -170
- package/dist/{types-BVacP54t.d.ts → types-CyvBaAl8.d.ts} +12 -4
- package/dist/types-D0WcHrq6.d.ts +234 -0
- package/package.json +18 -4
- package/dist/CacheSettingsManager-1exbOC6S.d.ts +0 -261
- package/dist/chunk-4C3RY5SU.js.map +0 -1
- package/dist/chunk-53WH2JJV.js.map +0 -1
- package/dist/chunk-BREGB4WL.js.map +0 -1
- package/dist/chunk-DGUM43GV.js +0 -11
- package/dist/chunk-DHYUBVP7.js.map +0 -1
- package/dist/chunk-GKF7TOMT.js +0 -1
- package/dist/chunk-H772V6XQ.js.map +0 -1
- package/dist/chunk-HFOFLW5F.js.map +0 -1
- package/dist/chunk-KGSFAE5B.js +0 -1
- package/dist/chunk-LNL64IJZ.js +0 -1
- package/dist/chunk-MKD2VCX3.js.map +0 -1
- package/dist/chunk-UEYRTLKE.js.map +0 -1
- package/dist/chunk-WQ5MPAVC.js +0 -449
- package/dist/chunk-WQ5MPAVC.js.map +0 -1
- package/dist/chunk-ZEOKPWUC.js +0 -1165
- package/dist/chunk-ZEOKPWUC.js.map +0 -1
- package/dist/pol-attachment-queue-C7YNXXhK.d.ts +0 -676
- package/dist/types-Bgvx7-E8.d.ts +0 -187
- /package/dist/{chunk-DGUM43GV.js.map → chunk-654ERHA7.js.map} +0 -0
- /package/dist/{chunk-GKF7TOMT.js.map → chunk-T4AO7JIG.js.map} +0 -0
- /package/dist/{chunk-KGSFAE5B.js.map → chunk-YHTZ7VMV.js.map} +0 -0
- /package/dist/{chunk-LNL64IJZ.js.map → react/index.js.map} +0 -0
package/dist/chunk-ZEOKPWUC.js
DELETED
|
@@ -1,1165 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
addJitter,
|
|
3
|
-
calculateBackoffDelay
|
|
4
|
-
} from "./chunk-FV2HXEIY.js";
|
|
5
|
-
|
|
6
|
-
// src/attachments/pol-storage-adapter.ts
|
|
7
|
-
import { EncodingType } from "@powersync/attachments";
|
|
8
|
-
var PolStorageAdapter = class {
|
|
9
|
-
platform;
|
|
10
|
-
remoteStorage;
|
|
11
|
-
attachmentDirectoryName;
|
|
12
|
-
userStorageDirectory;
|
|
13
|
-
constructor(options) {
|
|
14
|
-
this.platform = options.platform;
|
|
15
|
-
this.remoteStorage = options.remoteStorage;
|
|
16
|
-
this.attachmentDirectoryName = options.attachmentDirectoryName ?? "attachments";
|
|
17
|
-
const cacheDir = this.platform.fileSystem.getCacheDirectory();
|
|
18
|
-
this.userStorageDirectory = cacheDir.endsWith("/") ? `${cacheDir}${this.attachmentDirectoryName}/` : `${cacheDir}/${this.attachmentDirectoryName}/`;
|
|
19
|
-
}
|
|
20
|
-
// ─── Remote Operations ──────────────────────────────────────────────────────
|
|
21
|
-
/**
|
|
22
|
-
* Upload a file to remote storage.
|
|
23
|
-
*/
|
|
24
|
-
async uploadFile(filePath, data, options) {
|
|
25
|
-
if (!this.remoteStorage.uploadFile) {
|
|
26
|
-
throw new Error("Remote storage adapter does not support uploads");
|
|
27
|
-
}
|
|
28
|
-
const base64 = this._arrayBufferToBase64(data);
|
|
29
|
-
await this.remoteStorage.uploadFile(filePath, base64);
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Download a file from remote storage.
|
|
33
|
-
*/
|
|
34
|
-
async downloadFile(filePath) {
|
|
35
|
-
const result = await this.remoteStorage.downloadFile(filePath);
|
|
36
|
-
if (typeof result === "string") {
|
|
37
|
-
const binaryString = atob(result);
|
|
38
|
-
const bytes = new Uint8Array(binaryString.length);
|
|
39
|
-
for (let i = 0; i < binaryString.length; i++) {
|
|
40
|
-
bytes[i] = binaryString.charCodeAt(i);
|
|
41
|
-
}
|
|
42
|
-
return new Blob([bytes]);
|
|
43
|
-
}
|
|
44
|
-
return result;
|
|
45
|
-
}
|
|
46
|
-
// ─── Local File Operations ──────────────────────────────────────────────────
|
|
47
|
-
/**
|
|
48
|
-
* Write data to a local file.
|
|
49
|
-
*/
|
|
50
|
-
async writeFile(fileUri, base64Data, options) {
|
|
51
|
-
const encoding = options?.encoding === EncodingType.UTF8 ? "utf8" : "base64";
|
|
52
|
-
await this.platform.fileSystem.writeFile(fileUri, base64Data, encoding);
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Read a local file's contents.
|
|
56
|
-
*/
|
|
57
|
-
async readFile(fileUri, options) {
|
|
58
|
-
const encoding = options?.encoding === EncodingType.UTF8 ? "utf8" : "base64";
|
|
59
|
-
const content = await this.platform.fileSystem.readFile(fileUri, encoding);
|
|
60
|
-
if (encoding === "base64") {
|
|
61
|
-
return this._base64ToArrayBuffer(content);
|
|
62
|
-
} else {
|
|
63
|
-
const encoder = new TextEncoder();
|
|
64
|
-
return encoder.encode(content).buffer;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* Delete a local file.
|
|
69
|
-
*/
|
|
70
|
-
async deleteFile(uri, _options) {
|
|
71
|
-
try {
|
|
72
|
-
await this.platform.fileSystem.deleteFile(uri);
|
|
73
|
-
} catch (error) {
|
|
74
|
-
const info = await this.platform.fileSystem.getFileInfo(uri);
|
|
75
|
-
if (info?.exists) {
|
|
76
|
-
throw error;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Check if a local file exists.
|
|
82
|
-
*/
|
|
83
|
-
async fileExists(fileUri) {
|
|
84
|
-
const info = await this.platform.fileSystem.getFileInfo(fileUri);
|
|
85
|
-
return info?.exists ?? false;
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Create a directory (with intermediate directories).
|
|
89
|
-
*/
|
|
90
|
-
async makeDir(uri) {
|
|
91
|
-
await this.platform.fileSystem.makeDirectory(uri, {
|
|
92
|
-
intermediates: true
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Copy a file from source to destination.
|
|
97
|
-
*/
|
|
98
|
-
async copyFile(sourceUri, targetUri) {
|
|
99
|
-
await this.platform.fileSystem.copyFile(sourceUri, targetUri);
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Get the user's storage directory for attachments.
|
|
103
|
-
* Returns the cache directory path ending with '/'.
|
|
104
|
-
*/
|
|
105
|
-
getUserStorageDirectory() {
|
|
106
|
-
return this.userStorageDirectory;
|
|
107
|
-
}
|
|
108
|
-
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
109
|
-
/**
|
|
110
|
-
* Warning 8: Optimized base64 encoding using batch processing.
|
|
111
|
-
* Processes in chunks of 1024 bytes for better performance with large files.
|
|
112
|
-
*/
|
|
113
|
-
_arrayBufferToBase64(buffer) {
|
|
114
|
-
const bytes = new Uint8Array(buffer);
|
|
115
|
-
const chunkSize = 1024;
|
|
116
|
-
const chunks = [];
|
|
117
|
-
for (let i = 0; i < bytes.length; i += chunkSize) {
|
|
118
|
-
const chunk = bytes.subarray(i, Math.min(i + chunkSize, bytes.length));
|
|
119
|
-
chunks.push(String.fromCharCode.apply(null, Array.from(chunk)));
|
|
120
|
-
}
|
|
121
|
-
return btoa(chunks.join(""));
|
|
122
|
-
}
|
|
123
|
-
_base64ToArrayBuffer(base64) {
|
|
124
|
-
const binaryString = atob(base64);
|
|
125
|
-
const bytes = new Uint8Array(binaryString.length);
|
|
126
|
-
for (let i = 0; i < binaryString.length; i++) {
|
|
127
|
-
bytes[i] = binaryString.charCodeAt(i);
|
|
128
|
-
}
|
|
129
|
-
return bytes.buffer;
|
|
130
|
-
}
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
// src/attachments/types.ts
|
|
134
|
-
import { AttachmentState, ATTACHMENT_TABLE, AttachmentTable } from "@powersync/attachments";
|
|
135
|
-
import { EncodingType as EncodingType2 } from "@powersync/attachments";
|
|
136
|
-
import { DEFAULT_ATTACHMENT_QUEUE_OPTIONS, AbstractAttachmentQueue } from "@powersync/attachments";
|
|
137
|
-
var PolAttachmentState = /* @__PURE__ */ ((PolAttachmentState2) => {
|
|
138
|
-
PolAttachmentState2[PolAttachmentState2["QUEUED_SYNC"] = 0] = "QUEUED_SYNC";
|
|
139
|
-
PolAttachmentState2[PolAttachmentState2["QUEUED_UPLOAD"] = 1] = "QUEUED_UPLOAD";
|
|
140
|
-
PolAttachmentState2[PolAttachmentState2["QUEUED_DOWNLOAD"] = 2] = "QUEUED_DOWNLOAD";
|
|
141
|
-
PolAttachmentState2[PolAttachmentState2["SYNCED"] = 3] = "SYNCED";
|
|
142
|
-
PolAttachmentState2[PolAttachmentState2["ARCHIVED"] = 4] = "ARCHIVED";
|
|
143
|
-
PolAttachmentState2[PolAttachmentState2["FAILED_PERMANENT"] = 5] = "FAILED_PERMANENT";
|
|
144
|
-
return PolAttachmentState2;
|
|
145
|
-
})(PolAttachmentState || {});
|
|
146
|
-
var DEFAULT_COMPRESSION_CONFIG = {
|
|
147
|
-
enabled: true,
|
|
148
|
-
quality: 0.7,
|
|
149
|
-
maxWidth: 2048,
|
|
150
|
-
skipSizeBytes: 1e5,
|
|
151
|
-
targetSizeBytes: 3e5
|
|
152
|
-
};
|
|
153
|
-
var DEFAULT_UPLOAD_CONFIG = {
|
|
154
|
-
concurrency: 3,
|
|
155
|
-
timeoutMs: 12e4,
|
|
156
|
-
baseRetryDelayMs: 3e4,
|
|
157
|
-
// 30 seconds base delay for battery efficiency
|
|
158
|
-
maxRetryDelayMs: 36e5,
|
|
159
|
-
// 1 hour max
|
|
160
|
-
staleDaysThreshold: 7,
|
|
161
|
-
maxRetryCount: 100
|
|
162
|
-
};
|
|
163
|
-
var DEFAULT_CACHE_CONFIG = {
|
|
164
|
-
maxSize: 5 * 1024 * 1024 * 1024,
|
|
165
|
-
// 5 GB
|
|
166
|
-
downloadStopThreshold: 0.95,
|
|
167
|
-
evictionTriggerThreshold: 1
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
// src/attachments/pol-attachment-queue.ts
|
|
171
|
-
import { AbstractAttachmentQueue as AbstractAttachmentQueue2, AttachmentState as AttachmentState2 } from "@powersync/attachments";
|
|
172
|
-
var WATCH_INTERVAL_MS = 3e4;
|
|
173
|
-
var NOTIFY_THROTTLE_MS = 500;
|
|
174
|
-
var STATS_CACHE_TTL_MS = 500;
|
|
175
|
-
var UPLOAD_PROCESSING_DELAY_MS = 1e3;
|
|
176
|
-
var MIN_POLL_INTERVAL_MS = 5e3;
|
|
177
|
-
var PolAttachmentQueue = class extends AbstractAttachmentQueue2 {
|
|
178
|
-
// ─── Parent Override ──────────────────────────────────────────────────────
|
|
179
|
-
/**
|
|
180
|
-
* Override parent's watchUploads to prevent dual upload engines.
|
|
181
|
-
* POL uses custom _startUploadProcessing() for uploads with:
|
|
182
|
-
* - Exponential backoff retry
|
|
183
|
-
* - Compression support
|
|
184
|
-
* - Custom metadata handling
|
|
185
|
-
*
|
|
186
|
-
* The parent's simple upload watcher would conflict and fail
|
|
187
|
-
* because POL records use upload_source_uri instead of local_uri.
|
|
188
|
-
*/
|
|
189
|
-
watchUploads() {
|
|
190
|
-
this.polLogger?.debug?.("[PolAttachmentQueue] Parent watchUploads disabled - using custom upload engine");
|
|
191
|
-
}
|
|
192
|
-
/**
|
|
193
|
-
* Override parent's uploadAttachment to prevent misuse.
|
|
194
|
-
* POL uploads use upload_source_uri, not local_uri.
|
|
195
|
-
* External code should use queueUpload() instead.
|
|
196
|
-
*/
|
|
197
|
-
async uploadAttachment(record) {
|
|
198
|
-
throw new Error(`uploadAttachment() is disabled in PolAttachmentQueue. Use queueUpload() for uploads. Record ID: ${record.id}`);
|
|
199
|
-
}
|
|
200
|
-
// ─── Additional State ──────────────────────────────────────────────────────
|
|
201
|
-
platform;
|
|
202
|
-
polLogger;
|
|
203
|
-
source;
|
|
204
|
-
uploadHandler;
|
|
205
|
-
uploadConfig;
|
|
206
|
-
compressionConfig;
|
|
207
|
-
cacheConfig;
|
|
208
|
-
// Upload engine state
|
|
209
|
-
_uploadProcessing = false;
|
|
210
|
-
_uploadPaused = false;
|
|
211
|
-
_uploadAbort = new AbortController();
|
|
212
|
-
_activeUploads = /* @__PURE__ */ new Map();
|
|
213
|
-
// Watch state
|
|
214
|
-
_watchInterval = null;
|
|
215
|
-
_disposed = false;
|
|
216
|
-
_initialized = false;
|
|
217
|
-
// Notification state
|
|
218
|
-
_progressCallbacks = /* @__PURE__ */ new Set();
|
|
219
|
-
_lastNotifyTime = 0;
|
|
220
|
-
_notifyTimer = null;
|
|
221
|
-
// Stats cache
|
|
222
|
-
_cachedStats = null;
|
|
223
|
-
_cachedStatsTimestamp = 0;
|
|
224
|
-
constructor(options) {
|
|
225
|
-
const storage = new PolStorageAdapter({
|
|
226
|
-
platform: options.platform,
|
|
227
|
-
remoteStorage: options.remoteStorage,
|
|
228
|
-
attachmentDirectoryName: options.attachmentDirectoryName
|
|
229
|
-
});
|
|
230
|
-
super({
|
|
231
|
-
...options,
|
|
232
|
-
storage
|
|
233
|
-
});
|
|
234
|
-
this._validateSqlIdentifier(options.source.table, "source.table");
|
|
235
|
-
this._validateSqlIdentifier(options.source.idColumn, "source.idColumn");
|
|
236
|
-
if (options.source.orderByColumn) {
|
|
237
|
-
this._validateSqlIdentifier(options.source.orderByColumn, "source.orderByColumn");
|
|
238
|
-
}
|
|
239
|
-
if (options.source.projectFilter) {
|
|
240
|
-
this._validateSqlIdentifier(options.source.projectFilter.foreignKey, "projectFilter.foreignKey");
|
|
241
|
-
this._validateSqlIdentifier(options.source.projectFilter.intermediaryTable, "projectFilter.intermediaryTable");
|
|
242
|
-
this._validateSqlIdentifier(options.source.projectFilter.projectForeignKey, "projectFilter.projectForeignKey");
|
|
243
|
-
}
|
|
244
|
-
this.platform = options.platform;
|
|
245
|
-
this.polLogger = options.platform.logger;
|
|
246
|
-
this.source = options.source;
|
|
247
|
-
this.uploadHandler = options.uploadHandler;
|
|
248
|
-
this.uploadConfig = {
|
|
249
|
-
...DEFAULT_UPLOAD_CONFIG,
|
|
250
|
-
...options.uploadConfig
|
|
251
|
-
};
|
|
252
|
-
this.compressionConfig = {
|
|
253
|
-
...DEFAULT_COMPRESSION_CONFIG,
|
|
254
|
-
...options.compression
|
|
255
|
-
};
|
|
256
|
-
this.cacheConfig = {
|
|
257
|
-
...DEFAULT_CACHE_CONFIG,
|
|
258
|
-
...options.cache
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
|
-
/**
|
|
262
|
-
* Validate that a string is a safe SQL identifier (Warning 5).
|
|
263
|
-
* Prevents SQL injection via config values.
|
|
264
|
-
*/
|
|
265
|
-
_validateSqlIdentifier(value, name) {
|
|
266
|
-
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(value)) {
|
|
267
|
-
throw new Error(`[PolAttachmentQueue] Invalid SQL identifier for ${name}: "${value}". Must match /^[a-zA-Z_][a-zA-Z0-9_]*$/`);
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
// ─── Abstract Method Implementations ───────────────────────────────────────
|
|
271
|
-
/**
|
|
272
|
-
* Watch the source table for attachment IDs and notify when they change.
|
|
273
|
-
*
|
|
274
|
-
* This implementation uses our configurable source table pattern rather
|
|
275
|
-
* than a hardcoded query, supporting project filtering for multi-tenant apps.
|
|
276
|
-
*/
|
|
277
|
-
onAttachmentIdsChange(onUpdate) {
|
|
278
|
-
const {
|
|
279
|
-
table,
|
|
280
|
-
idColumn,
|
|
281
|
-
projectFilter
|
|
282
|
-
} = this.source;
|
|
283
|
-
let query;
|
|
284
|
-
if (projectFilter) {
|
|
285
|
-
const {
|
|
286
|
-
foreignKey,
|
|
287
|
-
intermediaryTable,
|
|
288
|
-
projectForeignKey
|
|
289
|
-
} = projectFilter;
|
|
290
|
-
query = `
|
|
291
|
-
SELECT m.${idColumn} as id
|
|
292
|
-
FROM ${table} m
|
|
293
|
-
JOIN ${intermediaryTable} u ON m.${foreignKey} = u.id
|
|
294
|
-
JOIN ProjectDatabase p ON u.${projectForeignKey} = p.id
|
|
295
|
-
WHERE m.${idColumn} IS NOT NULL AND m.${idColumn} != ''
|
|
296
|
-
`;
|
|
297
|
-
} else {
|
|
298
|
-
query = `
|
|
299
|
-
SELECT ${idColumn} as id
|
|
300
|
-
FROM ${table}
|
|
301
|
-
WHERE ${idColumn} IS NOT NULL AND ${idColumn} != ''
|
|
302
|
-
`;
|
|
303
|
-
}
|
|
304
|
-
try {
|
|
305
|
-
this.powersync.watch(query, [], {
|
|
306
|
-
onResult: (result) => {
|
|
307
|
-
const ids = result.rows?._array?.map((r) => r.id) ?? [];
|
|
308
|
-
onUpdate(ids);
|
|
309
|
-
},
|
|
310
|
-
onError: (error) => {
|
|
311
|
-
const errorMessage = String(error);
|
|
312
|
-
if (errorMessage.includes("no such table") || errorMessage.includes("SQLITE_EMPTY")) {
|
|
313
|
-
this.polLogger.debug("[PolAttachmentQueue] Source table not ready yet");
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
this.polLogger.warn("[PolAttachmentQueue] Watch error:", error);
|
|
317
|
-
}
|
|
318
|
-
});
|
|
319
|
-
} catch (error) {
|
|
320
|
-
this.polLogger.warn("[PolAttachmentQueue] Watch failed, falling back to polling:", error);
|
|
321
|
-
this._startPolling(query, onUpdate);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
/**
|
|
325
|
-
* Create a new attachment record with POL-specific defaults.
|
|
326
|
-
*/
|
|
327
|
-
async newAttachmentRecord(record) {
|
|
328
|
-
if (!record?.id) {
|
|
329
|
-
throw new Error("[PolAttachmentQueue] newAttachmentRecord requires a non-empty record.id");
|
|
330
|
-
}
|
|
331
|
-
const id = record.id;
|
|
332
|
-
const filename = record?.filename ?? id;
|
|
333
|
-
return {
|
|
334
|
-
id,
|
|
335
|
-
filename,
|
|
336
|
-
state: record?.state ?? AttachmentState2.QUEUED_SYNC,
|
|
337
|
-
local_uri: record?.local_uri,
|
|
338
|
-
size: record?.size,
|
|
339
|
-
media_type: record?.media_type,
|
|
340
|
-
timestamp: record?.timestamp ?? Date.now()
|
|
341
|
-
};
|
|
342
|
-
}
|
|
343
|
-
// ─── Lifecycle ─────────────────────────────────────────────────────────────
|
|
344
|
-
/**
|
|
345
|
-
* Initialize the attachment queue.
|
|
346
|
-
*
|
|
347
|
-
* Extends parent init() to also:
|
|
348
|
-
* - Create the attachment table (since we don't use AttachmentTable in Schema)
|
|
349
|
-
* - Add upload-specific columns to the table
|
|
350
|
-
* - Start upload processing if handler is configured
|
|
351
|
-
*/
|
|
352
|
-
async init() {
|
|
353
|
-
await this._createTableIfNotExists();
|
|
354
|
-
await super.init();
|
|
355
|
-
this._initialized = true;
|
|
356
|
-
await this._migrateUploadColumns();
|
|
357
|
-
if (this.uploadHandler && !this._uploadPaused) {
|
|
358
|
-
this._startUploadProcessing();
|
|
359
|
-
}
|
|
360
|
-
this.polLogger.info("[PolAttachmentQueue] Initialized");
|
|
361
|
-
}
|
|
362
|
-
/**
|
|
363
|
-
* Create the attachment table if it doesn't exist.
|
|
364
|
-
* This creates a REAL SQLite table (not a view) that supports all SQL operations.
|
|
365
|
-
*/
|
|
366
|
-
async _createTableIfNotExists() {
|
|
367
|
-
const result = await this.powersync.getOptional(`SELECT name FROM sqlite_master WHERE type='table' AND name=?`, [this.table]);
|
|
368
|
-
if (result) {
|
|
369
|
-
this.polLogger.debug(`[PolAttachmentQueue] Table ${this.table} already exists`);
|
|
370
|
-
return;
|
|
371
|
-
}
|
|
372
|
-
this.polLogger.info(`[PolAttachmentQueue] Creating table ${this.table}`);
|
|
373
|
-
await this.powersync.execute(`
|
|
374
|
-
CREATE TABLE IF NOT EXISTS ${this.table} (
|
|
375
|
-
id TEXT PRIMARY KEY NOT NULL,
|
|
376
|
-
filename TEXT,
|
|
377
|
-
local_uri TEXT,
|
|
378
|
-
timestamp INTEGER,
|
|
379
|
-
media_type TEXT,
|
|
380
|
-
size INTEGER,
|
|
381
|
-
state INTEGER DEFAULT 0,
|
|
382
|
-
upload_source_uri TEXT,
|
|
383
|
-
upload_error TEXT,
|
|
384
|
-
upload_error_code TEXT,
|
|
385
|
-
upload_retry_count INTEGER DEFAULT 0,
|
|
386
|
-
upload_next_retry_at INTEGER,
|
|
387
|
-
upload_metadata TEXT,
|
|
388
|
-
upload_bucket_id TEXT
|
|
389
|
-
)
|
|
390
|
-
`);
|
|
391
|
-
this.polLogger.info(`[PolAttachmentQueue] Table ${this.table} created`);
|
|
392
|
-
}
|
|
393
|
-
/**
|
|
394
|
-
* Dispose the attachment queue, cleaning up all resources.
|
|
395
|
-
*/
|
|
396
|
-
dispose() {
|
|
397
|
-
this._disposed = true;
|
|
398
|
-
this._uploadAbort.abort();
|
|
399
|
-
if (this._watchInterval) {
|
|
400
|
-
clearInterval(this._watchInterval);
|
|
401
|
-
this._watchInterval = null;
|
|
402
|
-
}
|
|
403
|
-
if (this._notifyTimer) {
|
|
404
|
-
clearTimeout(this._notifyTimer);
|
|
405
|
-
this._notifyTimer = null;
|
|
406
|
-
}
|
|
407
|
-
this._progressCallbacks.clear();
|
|
408
|
-
this._activeUploads.clear();
|
|
409
|
-
const parentProto = Object.getPrototypeOf(Object.getPrototypeOf(this));
|
|
410
|
-
if (parentProto && typeof parentProto.dispose === "function") {
|
|
411
|
-
parentProto.dispose.call(this);
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
// ─── Upload API ────────────────────────────────────────────────────────────
|
|
415
|
-
/**
|
|
416
|
-
* Queue a file for upload.
|
|
417
|
-
*
|
|
418
|
-
* The file is immediately copied to managed cache for durability.
|
|
419
|
-
* Upload will retry indefinitely for transient errors until marked
|
|
420
|
-
* as FAILED_PERMANENT for unrecoverable errors.
|
|
421
|
-
*/
|
|
422
|
-
async queueUpload(options) {
|
|
423
|
-
if (!this.uploadHandler) {
|
|
424
|
-
throw new Error("[PolAttachmentQueue] Upload not configured. Provide uploadHandler in options.");
|
|
425
|
-
}
|
|
426
|
-
if (!this._initialized) {
|
|
427
|
-
throw new Error("[PolAttachmentQueue] Queue not initialized. Call init() first or wait for attachmentQueueReady.");
|
|
428
|
-
}
|
|
429
|
-
const managedUri = await this._copyToManagedCache(options.sourceUri, options.storagePath);
|
|
430
|
-
const fileInfo = await this.platform.fileSystem.getFileInfo(managedUri);
|
|
431
|
-
const size = fileInfo?.exists ? fileInfo.size : 0;
|
|
432
|
-
const insertParams = [
|
|
433
|
-
options.storagePath,
|
|
434
|
-
// id = storage path
|
|
435
|
-
options.filename,
|
|
436
|
-
options.mediaType,
|
|
437
|
-
AttachmentState2.QUEUED_UPLOAD,
|
|
438
|
-
null,
|
|
439
|
-
// local_uri is for downloads (preserved on conflict)
|
|
440
|
-
size,
|
|
441
|
-
Date.now(),
|
|
442
|
-
managedUri,
|
|
443
|
-
// upload_source_uri
|
|
444
|
-
0,
|
|
445
|
-
// upload_retry_count
|
|
446
|
-
Date.now(),
|
|
447
|
-
// upload_next_retry_at (ready now)
|
|
448
|
-
options.metadata ? JSON.stringify(options.metadata) : null,
|
|
449
|
-
options.bucketId || null
|
|
450
|
-
];
|
|
451
|
-
this.polLogger.info(`[PolAttachmentQueue] queueUpload params:`, {
|
|
452
|
-
storagePath: options.storagePath,
|
|
453
|
-
filename: options.filename,
|
|
454
|
-
mediaType: options.mediaType,
|
|
455
|
-
size,
|
|
456
|
-
managedUri,
|
|
457
|
-
bucketId: options.bucketId,
|
|
458
|
-
metadataLength: options.metadata ? JSON.stringify(options.metadata).length : 0
|
|
459
|
-
});
|
|
460
|
-
try {
|
|
461
|
-
await this.powersync.execute(`INSERT OR REPLACE INTO ${this.table}
|
|
462
|
-
(id, filename, media_type, state, local_uri, size, timestamp,
|
|
463
|
-
upload_source_uri, upload_retry_count, upload_next_retry_at,
|
|
464
|
-
upload_metadata, upload_bucket_id)
|
|
465
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, insertParams);
|
|
466
|
-
} catch (insertError) {
|
|
467
|
-
this.polLogger.error(`[PolAttachmentQueue] queueUpload INSERT failed:`, insertError);
|
|
468
|
-
throw insertError;
|
|
469
|
-
}
|
|
470
|
-
this.polLogger.info(`[PolAttachmentQueue] Queued upload: ${options.storagePath}`);
|
|
471
|
-
if (!this._uploadPaused && !this._uploadProcessing) {
|
|
472
|
-
this._startUploadProcessing();
|
|
473
|
-
}
|
|
474
|
-
this._notify(true);
|
|
475
|
-
}
|
|
476
|
-
/**
|
|
477
|
-
* Get pending uploads that need retry.
|
|
478
|
-
*/
|
|
479
|
-
async getPendingUploads() {
|
|
480
|
-
const now = Date.now();
|
|
481
|
-
return this.powersync.getAll(`SELECT * FROM ${this.table}
|
|
482
|
-
WHERE state = ? AND (upload_next_retry_at IS NULL OR upload_next_retry_at <= ?)
|
|
483
|
-
ORDER BY timestamp ASC`, [AttachmentState2.QUEUED_UPLOAD, now]);
|
|
484
|
-
}
|
|
485
|
-
/**
|
|
486
|
-
* Get the soonest retry time for all uploads currently in backoff.
|
|
487
|
-
* Returns null if there are no uploads waiting for retry.
|
|
488
|
-
*/
|
|
489
|
-
async getSoonestRetryTime() {
|
|
490
|
-
const now = Date.now();
|
|
491
|
-
const result = await this.powersync.getOptional(`SELECT MIN(upload_next_retry_at) as soonest FROM ${this.table}
|
|
492
|
-
WHERE state = ? AND upload_next_retry_at > ?`, [AttachmentState2.QUEUED_UPLOAD, now]);
|
|
493
|
-
return result?.soonest ?? null;
|
|
494
|
-
}
|
|
495
|
-
/**
|
|
496
|
-
* Get uploads that have permanently failed.
|
|
497
|
-
*/
|
|
498
|
-
async getFailedPermanentUploads() {
|
|
499
|
-
return this.powersync.getAll(`SELECT * FROM ${this.table} WHERE state = ?`, [5 /* FAILED_PERMANENT */]);
|
|
500
|
-
}
|
|
501
|
-
/**
|
|
502
|
-
* Get stale uploads (failing for > staleDaysThreshold).
|
|
503
|
-
*/
|
|
504
|
-
async getStaleUploads() {
|
|
505
|
-
const threshold = Date.now() - this.uploadConfig.staleDaysThreshold * 24 * 60 * 60 * 1e3;
|
|
506
|
-
return this.powersync.getAll(`SELECT * FROM ${this.table}
|
|
507
|
-
WHERE state = ? AND timestamp < ?`, [AttachmentState2.QUEUED_UPLOAD, threshold]);
|
|
508
|
-
}
|
|
509
|
-
/**
|
|
510
|
-
* Get SYNCED uploads that have pending onComplete callbacks.
|
|
511
|
-
* Used by useQueuedUpload to recover callbacks after unmount/remount.
|
|
512
|
-
*
|
|
513
|
-
* Records are identified by having 'onCompleteCallback' flag in their upload_metadata.
|
|
514
|
-
* Once the callback is invoked, the hook should call clearUploadCallback() to remove the flag.
|
|
515
|
-
*/
|
|
516
|
-
async getSyncedUploadsWithPendingCallback() {
|
|
517
|
-
return this.powersync.getAll(`SELECT * FROM ${this.table}
|
|
518
|
-
WHERE state = ? AND upload_metadata LIKE '%"onCompleteCallback":true%'`, [AttachmentState2.SYNCED]);
|
|
519
|
-
}
|
|
520
|
-
/**
|
|
521
|
-
* Clear the onCompleteCallback flag from a record's metadata after callback invocation.
|
|
522
|
-
*/
|
|
523
|
-
async clearUploadCallback(id) {
|
|
524
|
-
const record = await this.record(id);
|
|
525
|
-
if (!record) return;
|
|
526
|
-
const polRecord = record;
|
|
527
|
-
if (!polRecord.upload_metadata) return;
|
|
528
|
-
try {
|
|
529
|
-
const metadata = JSON.parse(polRecord.upload_metadata);
|
|
530
|
-
delete metadata.onCompleteCallback;
|
|
531
|
-
await this.powersync.execute(`UPDATE ${this.table} SET upload_metadata = ? WHERE id = ?`, [JSON.stringify(metadata), id]);
|
|
532
|
-
} catch {
|
|
533
|
-
await this.powersync.execute(`UPDATE ${this.table} SET upload_metadata = NULL WHERE id = ?`, [id]);
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
/**
|
|
537
|
-
* Retry a failed upload.
|
|
538
|
-
*/
|
|
539
|
-
async retryUpload(id) {
|
|
540
|
-
await this.powersync.execute(`UPDATE ${this.table}
|
|
541
|
-
SET state = ?, upload_retry_count = 0, upload_next_retry_at = ?, upload_error = NULL
|
|
542
|
-
WHERE id = ?`, [AttachmentState2.QUEUED_UPLOAD, Date.now(), id]);
|
|
543
|
-
if (!this._uploadPaused && !this._uploadProcessing) {
|
|
544
|
-
this._startUploadProcessing();
|
|
545
|
-
}
|
|
546
|
-
this._notify(true);
|
|
547
|
-
}
|
|
548
|
-
/**
|
|
549
|
-
* Delete an upload from the queue.
|
|
550
|
-
*/
|
|
551
|
-
async deleteUpload(id) {
|
|
552
|
-
const record = await this.record(id);
|
|
553
|
-
if (record) {
|
|
554
|
-
const polRecord = record;
|
|
555
|
-
if (polRecord.upload_source_uri) {
|
|
556
|
-
try {
|
|
557
|
-
await this.platform.fileSystem.deleteFile(polRecord.upload_source_uri);
|
|
558
|
-
} catch {
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
await this.powersync.execute(`DELETE FROM ${this.table} WHERE id = ?`, [id]);
|
|
563
|
-
this._notify(true);
|
|
564
|
-
}
|
|
565
|
-
// ─── Compatibility Aliases ────────────────────────────────────────────────────
|
|
566
|
-
/**
|
|
567
|
-
* Alias for record() for backward compatibility.
|
|
568
|
-
* Returns an attachment record by ID.
|
|
569
|
-
*/
|
|
570
|
-
async getRecord(id) {
|
|
571
|
-
const record = await this.record(id);
|
|
572
|
-
return record;
|
|
573
|
-
}
|
|
574
|
-
/**
|
|
575
|
-
* Alias for getFailedPermanentUploads() for backward compatibility.
|
|
576
|
-
*/
|
|
577
|
-
async getFailedUploads() {
|
|
578
|
-
return this.getFailedPermanentUploads();
|
|
579
|
-
}
|
|
580
|
-
/**
|
|
581
|
-
* Alias for retryUpload() for backward compatibility.
|
|
582
|
-
*/
|
|
583
|
-
async retryFailedUpload(id) {
|
|
584
|
-
return this.retryUpload(id);
|
|
585
|
-
}
|
|
586
|
-
/**
|
|
587
|
-
* Alias for deleteUpload() for backward compatibility.
|
|
588
|
-
*/
|
|
589
|
-
async deleteFailedUpload(id) {
|
|
590
|
-
return this.deleteUpload(id);
|
|
591
|
-
}
|
|
592
|
-
/**
|
|
593
|
-
* Get currently active uploads.
|
|
594
|
-
*/
|
|
595
|
-
get activeUploads() {
|
|
596
|
-
return [...this._activeUploads.values()];
|
|
597
|
-
}
|
|
598
|
-
/**
|
|
599
|
-
* Pause upload processing.
|
|
600
|
-
*/
|
|
601
|
-
pauseUploads() {
|
|
602
|
-
this.polLogger.info("[PolAttachmentQueue] Pausing uploads");
|
|
603
|
-
this._uploadPaused = true;
|
|
604
|
-
this._uploadAbort.abort();
|
|
605
|
-
this._uploadAbort = new AbortController();
|
|
606
|
-
this._notify(true);
|
|
607
|
-
}
|
|
608
|
-
/**
|
|
609
|
-
* Resume upload processing.
|
|
610
|
-
*/
|
|
611
|
-
resumeUploads() {
|
|
612
|
-
this.polLogger.info("[PolAttachmentQueue] Resuming uploads");
|
|
613
|
-
this._uploadPaused = false;
|
|
614
|
-
this._uploadAbort = new AbortController();
|
|
615
|
-
if (!this._uploadProcessing) {
|
|
616
|
-
this._startUploadProcessing();
|
|
617
|
-
}
|
|
618
|
-
this._notify(true);
|
|
619
|
-
}
|
|
620
|
-
// ─── Cache Management ──────────────────────────────────────────────────────
|
|
621
|
-
/**
|
|
622
|
-
* Clear all cached files and re-queue for download.
|
|
623
|
-
*/
|
|
624
|
-
async clearCache() {
|
|
625
|
-
this.polLogger.info("[PolAttachmentQueue] Clearing cache...");
|
|
626
|
-
const records = await this.powersync.getAll(`SELECT id, local_uri FROM ${this.table}
|
|
627
|
-
WHERE local_uri IS NOT NULL
|
|
628
|
-
AND state IN (?, ?, ?)`, [AttachmentState2.QUEUED_DOWNLOAD, AttachmentState2.SYNCED, AttachmentState2.QUEUED_SYNC]);
|
|
629
|
-
for (const record of records) {
|
|
630
|
-
if (record.local_uri) {
|
|
631
|
-
try {
|
|
632
|
-
const fullPath = this.getLocalUri(record.local_uri);
|
|
633
|
-
await this.platform.fileSystem.deleteFile(fullPath);
|
|
634
|
-
} catch {
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
await this.powersync.execute(`UPDATE ${this.table}
|
|
639
|
-
SET state = ?, local_uri = NULL, size = 0
|
|
640
|
-
WHERE state IN (?, ?, ?)`, [AttachmentState2.QUEUED_DOWNLOAD, AttachmentState2.QUEUED_DOWNLOAD, AttachmentState2.SYNCED, AttachmentState2.QUEUED_SYNC]);
|
|
641
|
-
this._cachedStats = null;
|
|
642
|
-
this._cachedStatsTimestamp = 0;
|
|
643
|
-
this._notify(true);
|
|
644
|
-
this.polLogger.info("[PolAttachmentQueue] Cache cleared");
|
|
645
|
-
}
|
|
646
|
-
/**
|
|
647
|
-
* Cache a local file (e.g., one just uploaded) into the attachment cache.
|
|
648
|
-
* This avoids a redundant download by copying the source file directly
|
|
649
|
-
* into the cache directory and marking it as SYNCED.
|
|
650
|
-
*/
|
|
651
|
-
async cacheLocalFile(storagePath, sourceUri) {
|
|
652
|
-
try {
|
|
653
|
-
const sourceInfo = await this.platform.fileSystem.getFileInfo(sourceUri);
|
|
654
|
-
if (!sourceInfo || !sourceInfo.exists) {
|
|
655
|
-
this.polLogger.warn(`[PolAttachmentQueue] Source file does not exist: ${sourceUri}`);
|
|
656
|
-
return;
|
|
657
|
-
}
|
|
658
|
-
const localPath = storagePath.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
659
|
-
const localUri = this.getLocalUri(localPath);
|
|
660
|
-
const dir = localUri.substring(0, localUri.lastIndexOf("/"));
|
|
661
|
-
await this.platform.fileSystem.makeDirectory(dir, {
|
|
662
|
-
intermediates: true
|
|
663
|
-
});
|
|
664
|
-
await this.platform.fileSystem.copyFile(sourceUri, localUri);
|
|
665
|
-
const info = await this.platform.fileSystem.getFileInfo(localUri);
|
|
666
|
-
const size = info && info.exists ? info.size : 0;
|
|
667
|
-
const ext = storagePath.split(".").pop()?.toLowerCase() ?? "";
|
|
668
|
-
const mediaTypeMap = {
|
|
669
|
-
jpg: "image/jpeg",
|
|
670
|
-
jpeg: "image/jpeg",
|
|
671
|
-
png: "image/png",
|
|
672
|
-
webp: "image/webp",
|
|
673
|
-
heic: "image/heic",
|
|
674
|
-
heif: "image/heif",
|
|
675
|
-
gif: "image/gif",
|
|
676
|
-
mp4: "video/mp4",
|
|
677
|
-
mov: "video/quicktime"
|
|
678
|
-
};
|
|
679
|
-
const mediaType = mediaTypeMap[ext] ?? "application/octet-stream";
|
|
680
|
-
await this.powersync.execute(`INSERT INTO ${this.table} (id, filename, media_type, state, local_uri, size, timestamp)
|
|
681
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
682
|
-
ON CONFLICT(id) DO UPDATE SET
|
|
683
|
-
local_uri = excluded.local_uri,
|
|
684
|
-
size = excluded.size,
|
|
685
|
-
state = excluded.state,
|
|
686
|
-
timestamp = excluded.timestamp`, [storagePath, storagePath, mediaType, AttachmentState2.SYNCED, localPath, size, Date.now()]);
|
|
687
|
-
this._cachedStats = null;
|
|
688
|
-
this._cachedStatsTimestamp = 0;
|
|
689
|
-
this._notify(true);
|
|
690
|
-
this.polLogger.info(`[PolAttachmentQueue] Cached local file: ${storagePath} (${Math.round(size / 1024)}KB)`);
|
|
691
|
-
} catch (err) {
|
|
692
|
-
this.polLogger.warn(`[PolAttachmentQueue] Failed to cache local file: ${storagePath}`, err);
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
/**
|
|
696
|
-
* Get the local file URI for a storage path.
|
|
697
|
-
* Checks both downloaded files (SYNCED state, local_uri) and
|
|
698
|
-
* pending uploads (QUEUED_UPLOAD state, upload_source_uri).
|
|
699
|
-
*
|
|
700
|
-
* @param storagePath - The storage path (e.g., "14/uuid.jpg")
|
|
701
|
-
* @returns Full file:// URI if available locally, null otherwise
|
|
702
|
-
*/
|
|
703
|
-
async getLocalUriForStoragePath(storagePath) {
|
|
704
|
-
let result = null;
|
|
705
|
-
try {
|
|
706
|
-
result = await this.powersync.getOptional(`SELECT local_uri, upload_source_uri, state FROM ${this.table}
|
|
707
|
-
WHERE id = ? AND (
|
|
708
|
-
(state = ? AND local_uri IS NOT NULL) OR
|
|
709
|
-
(state = ? AND upload_source_uri IS NOT NULL)
|
|
710
|
-
)`, [storagePath, AttachmentState2.SYNCED, AttachmentState2.QUEUED_UPLOAD]);
|
|
711
|
-
} catch (queryError) {
|
|
712
|
-
this.polLogger.error(`[PolAttachmentQueue] getLocalUriForStoragePath query failed:`, {
|
|
713
|
-
storagePath,
|
|
714
|
-
syncedState: AttachmentState2.SYNCED,
|
|
715
|
-
queuedUploadState: AttachmentState2.QUEUED_UPLOAD,
|
|
716
|
-
error: queryError
|
|
717
|
-
});
|
|
718
|
-
throw queryError;
|
|
719
|
-
}
|
|
720
|
-
if (!result) return null;
|
|
721
|
-
let localPath = null;
|
|
722
|
-
if (result.state === AttachmentState2.SYNCED && result.local_uri) {
|
|
723
|
-
localPath = result.local_uri;
|
|
724
|
-
} else if (result.state === AttachmentState2.QUEUED_UPLOAD && result.upload_source_uri) {
|
|
725
|
-
const uri2 = result.upload_source_uri.startsWith("file://") ? result.upload_source_uri : `file://${result.upload_source_uri}`;
|
|
726
|
-
try {
|
|
727
|
-
const exists = await this.storage.fileExists(uri2.replace("file://", ""));
|
|
728
|
-
if (!exists) {
|
|
729
|
-
this.polLogger.warn(`[PolAttachmentQueue] Upload source file missing: ${storagePath}`);
|
|
730
|
-
return null;
|
|
731
|
-
}
|
|
732
|
-
} catch (e) {
|
|
733
|
-
this.polLogger.warn(`[PolAttachmentQueue] Failed to verify upload source exists: ${storagePath}`, e);
|
|
734
|
-
}
|
|
735
|
-
return uri2;
|
|
736
|
-
}
|
|
737
|
-
if (!localPath) return null;
|
|
738
|
-
const fullPath = this.getLocalUri(localPath);
|
|
739
|
-
const uri = fullPath.startsWith("file://") ? fullPath : `file://${fullPath}`;
|
|
740
|
-
try {
|
|
741
|
-
const exists = await this.storage.fileExists(uri.replace("file://", ""));
|
|
742
|
-
if (!exists) {
|
|
743
|
-
this.polLogger.warn(`[PolAttachmentQueue] Cached file missing from disk, re-queuing: ${storagePath}`);
|
|
744
|
-
await this.powersync.execute(`UPDATE ${this.table} SET state = ?, local_uri = NULL WHERE id = ?`, [AttachmentState2.QUEUED_DOWNLOAD, storagePath]);
|
|
745
|
-
return null;
|
|
746
|
-
}
|
|
747
|
-
} catch (e) {
|
|
748
|
-
this.polLogger.warn(`[PolAttachmentQueue] Failed to verify file exists: ${storagePath}`, e);
|
|
749
|
-
}
|
|
750
|
-
return uri;
|
|
751
|
-
}
|
|
752
|
-
// ─── Progress Subscription ─────────────────────────────────────────────────
|
|
753
|
-
/**
|
|
754
|
-
* Subscribe to real-time progress updates.
|
|
755
|
-
* Returns an unsubscribe function.
|
|
756
|
-
*/
|
|
757
|
-
onProgress(callback) {
|
|
758
|
-
this._progressCallbacks.add(callback);
|
|
759
|
-
this._notify(true);
|
|
760
|
-
return () => {
|
|
761
|
-
this._progressCallbacks.delete(callback);
|
|
762
|
-
};
|
|
763
|
-
}
|
|
764
|
-
/**
|
|
765
|
-
* Get current sync statistics.
|
|
766
|
-
*/
|
|
767
|
-
async getStats() {
|
|
768
|
-
const now = Date.now();
|
|
769
|
-
if (this._cachedStats && now - this._cachedStatsTimestamp < STATS_CACHE_TTL_MS) {
|
|
770
|
-
return {
|
|
771
|
-
...this._cachedStats,
|
|
772
|
-
status: this._getStatus(this._cachedStats.pendingCount),
|
|
773
|
-
isPaused: this._uploadPaused,
|
|
774
|
-
isProcessing: this.uploading || this.downloading || this._uploadProcessing,
|
|
775
|
-
activeDownloads: [],
|
|
776
|
-
activeUploads: this.activeUploads
|
|
777
|
-
};
|
|
778
|
-
}
|
|
779
|
-
const rows = await this.powersync.getAll(`SELECT state, COUNT(*) as cnt, COALESCE(SUM(size), 0) as sz
|
|
780
|
-
FROM ${this.table} GROUP BY state`);
|
|
781
|
-
let synced = 0;
|
|
782
|
-
let syncedSize = 0;
|
|
783
|
-
let pending = 0;
|
|
784
|
-
let pendingUpload = 0;
|
|
785
|
-
let failedPermanent = 0;
|
|
786
|
-
for (const r of rows) {
|
|
787
|
-
if (r.state === AttachmentState2.SYNCED) {
|
|
788
|
-
synced = r.cnt;
|
|
789
|
-
syncedSize = r.sz;
|
|
790
|
-
}
|
|
791
|
-
if (r.state === AttachmentState2.QUEUED_DOWNLOAD || r.state === AttachmentState2.QUEUED_SYNC) {
|
|
792
|
-
pending += r.cnt;
|
|
793
|
-
}
|
|
794
|
-
if (r.state === AttachmentState2.QUEUED_UPLOAD) {
|
|
795
|
-
pendingUpload = r.cnt;
|
|
796
|
-
}
|
|
797
|
-
if (r.state === 5 /* FAILED_PERMANENT */) {
|
|
798
|
-
failedPermanent = r.cnt;
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
const staleUploads = await this.getStaleUploads();
|
|
802
|
-
const stats = {
|
|
803
|
-
syncedCount: synced,
|
|
804
|
-
syncedSize,
|
|
805
|
-
pendingCount: pending,
|
|
806
|
-
totalExpected: synced + pending,
|
|
807
|
-
maxCacheSize: this.cacheConfig.maxSize,
|
|
808
|
-
compressionQuality: this.compressionConfig.quality,
|
|
809
|
-
status: this._getStatus(pending),
|
|
810
|
-
isPaused: this._uploadPaused,
|
|
811
|
-
isProcessing: this.uploading || this.downloading || this._uploadProcessing,
|
|
812
|
-
activeDownloads: [],
|
|
813
|
-
pendingUploadCount: pendingUpload,
|
|
814
|
-
failedPermanentCount: failedPermanent,
|
|
815
|
-
staleUploadCount: staleUploads.length,
|
|
816
|
-
activeUploads: this.activeUploads
|
|
817
|
-
};
|
|
818
|
-
this._cachedStats = stats;
|
|
819
|
-
this._cachedStatsTimestamp = now;
|
|
820
|
-
return stats;
|
|
821
|
-
}
|
|
822
|
-
// ─── Upload Processing ─────────────────────────────────────────────────────
|
|
823
|
-
async _startUploadProcessing() {
|
|
824
|
-
if (this._uploadPaused || this._uploadProcessing || !this.uploadHandler) return;
|
|
825
|
-
this.polLogger.info("[PolAttachmentQueue] Starting upload processing");
|
|
826
|
-
this._uploadProcessing = true;
|
|
827
|
-
try {
|
|
828
|
-
while (!this._uploadAbort.signal.aborted) {
|
|
829
|
-
const pending = await this.getPendingUploads();
|
|
830
|
-
if (pending.length > 0) {
|
|
831
|
-
for (let i = 0; i < pending.length; i += this.uploadConfig.concurrency) {
|
|
832
|
-
if (this._uploadAbort.signal.aborted) break;
|
|
833
|
-
const batch = pending.slice(i, i + this.uploadConfig.concurrency);
|
|
834
|
-
await Promise.allSettled(batch.map((record) => this._uploadOne(record)));
|
|
835
|
-
}
|
|
836
|
-
await this._sleep(UPLOAD_PROCESSING_DELAY_MS);
|
|
837
|
-
} else {
|
|
838
|
-
const soonestRetry = await this.getSoonestRetryTime();
|
|
839
|
-
if (soonestRetry === null) {
|
|
840
|
-
this.polLogger.info("[PolAttachmentQueue] No pending uploads, stopping processing");
|
|
841
|
-
break;
|
|
842
|
-
}
|
|
843
|
-
const now = Date.now();
|
|
844
|
-
const sleepDuration = Math.max(MIN_POLL_INTERVAL_MS, Math.min(soonestRetry - now, this.uploadConfig.maxRetryDelayMs));
|
|
845
|
-
this.polLogger.info(`[PolAttachmentQueue] All uploads in backoff, sleeping ${Math.round(sleepDuration / 1e3)}s until next retry`);
|
|
846
|
-
await this._sleep(sleepDuration);
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
} catch (error) {
|
|
850
|
-
this.polLogger.error("[PolAttachmentQueue] Upload loop error:", error);
|
|
851
|
-
} finally {
|
|
852
|
-
this._uploadProcessing = false;
|
|
853
|
-
this.polLogger.info("[PolAttachmentQueue] Upload processing stopped");
|
|
854
|
-
this._notify(true);
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
async _uploadOne(record) {
|
|
858
|
-
const signal = this._uploadAbort.signal;
|
|
859
|
-
if (signal.aborted) return;
|
|
860
|
-
this._activeUploads.set(record.id, {
|
|
861
|
-
id: record.id,
|
|
862
|
-
filename: record.filename,
|
|
863
|
-
phase: "uploading"
|
|
864
|
-
});
|
|
865
|
-
try {
|
|
866
|
-
if (!record.upload_source_uri) {
|
|
867
|
-
await this._markUploadPermanentFailure(record, "No source file URI");
|
|
868
|
-
return;
|
|
869
|
-
}
|
|
870
|
-
const fileInfo = await this.platform.fileSystem.getFileInfo(record.upload_source_uri);
|
|
871
|
-
if (!fileInfo || !fileInfo.exists) {
|
|
872
|
-
await this._markUploadPermanentFailure(record, "Source file no longer available");
|
|
873
|
-
return;
|
|
874
|
-
}
|
|
875
|
-
await this._withTimeout(this.uploadHandler.uploadFile(record.id, record.upload_source_uri, record.media_type || "application/octet-stream", signal), this.uploadConfig.timeoutMs, "Upload timed out", signal);
|
|
876
|
-
await this._markUploadSynced(record);
|
|
877
|
-
} catch (error) {
|
|
878
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
879
|
-
if (err.name === "AbortError" || err instanceof DOMException && err.name === "AbortError") {
|
|
880
|
-
this.polLogger?.debug?.("[PolAttachmentQueue] Upload aborted (pause/dispose), will retry on resume", {
|
|
881
|
-
id: record.id
|
|
882
|
-
});
|
|
883
|
-
return;
|
|
884
|
-
}
|
|
885
|
-
if (this._isPermanentError(err)) {
|
|
886
|
-
await this._markUploadPermanentFailure(record, err.message, this._extractErrorCode(err));
|
|
887
|
-
} else {
|
|
888
|
-
await this._scheduleUploadRetry(record, err);
|
|
889
|
-
}
|
|
890
|
-
} finally {
|
|
891
|
-
this._activeUploads.delete(record.id);
|
|
892
|
-
this._notify(false);
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
_isPermanentError(error) {
|
|
896
|
-
const message = error.message.toLowerCase();
|
|
897
|
-
const code = this._extractErrorCode(error);
|
|
898
|
-
const permanentCodes = ["400", "401", "403", "404", "413", "415", "422"];
|
|
899
|
-
if (code && permanentCodes.includes(code)) {
|
|
900
|
-
return true;
|
|
901
|
-
}
|
|
902
|
-
if (message.includes("not found") || message.includes("unauthorized") || message.includes("forbidden") || message.includes("access denied")) {
|
|
903
|
-
return true;
|
|
904
|
-
}
|
|
905
|
-
return false;
|
|
906
|
-
}
|
|
907
|
-
/**
|
|
908
|
-
* Extract HTTP status code from error message.
|
|
909
|
-
* Uses specific patterns to avoid false positives from numbers in other contexts.
|
|
910
|
-
*/
|
|
911
|
-
_extractErrorCode(error) {
|
|
912
|
-
const message = error.message;
|
|
913
|
-
const patterns = [
|
|
914
|
-
/\bstatus[:\s]+(\d{3})\b/i,
|
|
915
|
-
// "status: 404" or "status 404"
|
|
916
|
-
/\bHTTP[\/\s]+\d+(?:\.\d+)?\s+(\d{3})/i,
|
|
917
|
-
// "HTTP/1.1 404" or "HTTP 404"
|
|
918
|
-
/\b(\d{3})\s+(?:error|failed|forbidden|unauthorized|not\s+found)/i,
|
|
919
|
-
// "404 not found"
|
|
920
|
-
/\berror[:\s]+(\d{3})\b/i
|
|
921
|
-
// "error: 500" or "error 500"
|
|
922
|
-
];
|
|
923
|
-
for (const pattern of patterns) {
|
|
924
|
-
const match = message.match(pattern);
|
|
925
|
-
if (match) {
|
|
926
|
-
return match[1];
|
|
927
|
-
}
|
|
928
|
-
}
|
|
929
|
-
if ("status" in error && typeof error.status === "number") {
|
|
930
|
-
return String(error.status);
|
|
931
|
-
}
|
|
932
|
-
return void 0;
|
|
933
|
-
}
|
|
934
|
-
async _markUploadSynced(record) {
|
|
935
|
-
await this.powersync.execute(`UPDATE ${this.table}
|
|
936
|
-
SET state = ?, local_uri = ?, upload_error = NULL, upload_error_code = NULL
|
|
937
|
-
WHERE id = ?`, [AttachmentState2.SYNCED, record.upload_source_uri, record.id]);
|
|
938
|
-
this.polLogger.info(`[PolAttachmentQueue] Upload complete: ${record.id}`);
|
|
939
|
-
if (this.options.onUploadComplete) {
|
|
940
|
-
try {
|
|
941
|
-
await this.options.onUploadComplete(record);
|
|
942
|
-
} catch (error) {
|
|
943
|
-
this.polLogger.error(`[PolAttachmentQueue] onUploadComplete failed for ${record.id}:`, error);
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
}
|
|
947
|
-
async _markUploadPermanentFailure(record, errorMessage, errorCode) {
|
|
948
|
-
await this.powersync.execute(`UPDATE ${this.table}
|
|
949
|
-
SET state = ?, upload_error = ?, upload_error_code = ?
|
|
950
|
-
WHERE id = ?`, [5 /* FAILED_PERMANENT */, errorMessage, errorCode || null, record.id]);
|
|
951
|
-
this.polLogger.warn(`[PolAttachmentQueue] Permanent failure: ${record.id} - ${errorMessage}`);
|
|
952
|
-
if (this.options.onUploadFailed) {
|
|
953
|
-
this.options.onUploadFailed(record, new Error(errorMessage));
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
async _scheduleUploadRetry(record, error) {
|
|
957
|
-
const retryCount = (record.upload_retry_count || 0) + 1;
|
|
958
|
-
if (retryCount > this.uploadConfig.maxRetryCount) {
|
|
959
|
-
this.polLogger.warn(`[PolAttachmentQueue] Max retry count (${this.uploadConfig.maxRetryCount}) exceeded for ${record.id}`);
|
|
960
|
-
await this._markUploadPermanentFailure(record, `Max retry count exceeded after ${retryCount - 1} attempts. Last error: ${error.message}`, "MAX_RETRIES");
|
|
961
|
-
return;
|
|
962
|
-
}
|
|
963
|
-
const baseDelay = calculateBackoffDelay(retryCount - 1, {
|
|
964
|
-
baseDelayMs: this.uploadConfig.baseRetryDelayMs,
|
|
965
|
-
maxDelayMs: this.uploadConfig.maxRetryDelayMs,
|
|
966
|
-
backoffMultiplier: 2
|
|
967
|
-
});
|
|
968
|
-
const nextRetryAt = Date.now() + addJitter(baseDelay);
|
|
969
|
-
await this.powersync.execute(`UPDATE ${this.table}
|
|
970
|
-
SET upload_retry_count = ?, upload_next_retry_at = ?, upload_error = ?
|
|
971
|
-
WHERE id = ?`, [retryCount, nextRetryAt, error.message, record.id]);
|
|
972
|
-
const delaySeconds = Math.round((nextRetryAt - Date.now()) / 1e3);
|
|
973
|
-
this.polLogger.info(`[PolAttachmentQueue] Scheduled retry ${retryCount}/${this.uploadConfig.maxRetryCount} for ${record.id} in ${delaySeconds}s`);
|
|
974
|
-
}
|
|
975
|
-
// ─── Helpers ───────────────────────────────────────────────────────────────
|
|
976
|
-
_startPolling(query, onUpdate) {
|
|
977
|
-
const poll = async () => {
|
|
978
|
-
if (this._disposed) return;
|
|
979
|
-
try {
|
|
980
|
-
const result = await this.powersync.getAll(query);
|
|
981
|
-
const ids = result?.map((r) => r.id) ?? [];
|
|
982
|
-
onUpdate(ids);
|
|
983
|
-
} catch (error) {
|
|
984
|
-
const errorMessage = String(error);
|
|
985
|
-
if (!errorMessage.includes("no such table") && !errorMessage.includes("SQLITE_EMPTY")) {
|
|
986
|
-
this.polLogger.warn("[PolAttachmentQueue] Poll error:", error);
|
|
987
|
-
}
|
|
988
|
-
}
|
|
989
|
-
};
|
|
990
|
-
poll();
|
|
991
|
-
this._watchInterval = setInterval(poll, WATCH_INTERVAL_MS);
|
|
992
|
-
}
|
|
993
|
-
async _migrateUploadColumns() {
|
|
994
|
-
const tableInfo = await this.powersync.getAll(`PRAGMA table_info(${this.table})`);
|
|
995
|
-
const existingColumns = new Set(tableInfo.map((r) => r.name));
|
|
996
|
-
const uploadColumns = [{
|
|
997
|
-
name: "upload_source_uri",
|
|
998
|
-
type: "TEXT"
|
|
999
|
-
}, {
|
|
1000
|
-
name: "upload_error",
|
|
1001
|
-
type: "TEXT"
|
|
1002
|
-
}, {
|
|
1003
|
-
name: "upload_error_code",
|
|
1004
|
-
type: "TEXT"
|
|
1005
|
-
}, {
|
|
1006
|
-
name: "upload_retry_count",
|
|
1007
|
-
type: "INTEGER DEFAULT 0"
|
|
1008
|
-
}, {
|
|
1009
|
-
name: "upload_next_retry_at",
|
|
1010
|
-
type: "INTEGER"
|
|
1011
|
-
}, {
|
|
1012
|
-
name: "upload_metadata",
|
|
1013
|
-
type: "TEXT"
|
|
1014
|
-
}, {
|
|
1015
|
-
name: "upload_bucket_id",
|
|
1016
|
-
type: "TEXT"
|
|
1017
|
-
}];
|
|
1018
|
-
for (const col of uploadColumns) {
|
|
1019
|
-
if (!existingColumns.has(col.name)) {
|
|
1020
|
-
this.polLogger.info(`[PolAttachmentQueue] Adding column: ${col.name}`);
|
|
1021
|
-
await this.powersync.execute(`ALTER TABLE ${this.table} ADD COLUMN ${col.name} ${col.type}`);
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
1024
|
-
}
|
|
1025
|
-
async _copyToManagedCache(sourceUri, storagePath) {
|
|
1026
|
-
const localPath = storagePath.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
1027
|
-
const cacheDir = this.platform.fileSystem.getCacheDirectory();
|
|
1028
|
-
const targetUri = `${cacheDir}upload_queue/${localPath}`;
|
|
1029
|
-
const dir = targetUri.substring(0, targetUri.lastIndexOf("/"));
|
|
1030
|
-
await this.platform.fileSystem.makeDirectory(dir, {
|
|
1031
|
-
intermediates: true
|
|
1032
|
-
});
|
|
1033
|
-
await this.platform.fileSystem.copyFile(sourceUri, targetUri);
|
|
1034
|
-
this.polLogger.info(`[PolAttachmentQueue] Copied to managed cache: ${targetUri}`);
|
|
1035
|
-
return targetUri;
|
|
1036
|
-
}
|
|
1037
|
-
_getStatus(pendingCount) {
|
|
1038
|
-
if (this.uploading || this.downloading || this._uploadProcessing) return "syncing";
|
|
1039
|
-
if (this._uploadPaused) return "paused";
|
|
1040
|
-
return "complete";
|
|
1041
|
-
}
|
|
1042
|
-
_notify(forceImmediate = false) {
|
|
1043
|
-
if (this._progressCallbacks.size === 0) return;
|
|
1044
|
-
const now = Date.now();
|
|
1045
|
-
const timeSinceLastNotify = now - this._lastNotifyTime;
|
|
1046
|
-
if (this._notifyTimer) {
|
|
1047
|
-
clearTimeout(this._notifyTimer);
|
|
1048
|
-
this._notifyTimer = null;
|
|
1049
|
-
}
|
|
1050
|
-
const notifyAll = (stats) => {
|
|
1051
|
-
for (const cb of this._progressCallbacks) {
|
|
1052
|
-
try {
|
|
1053
|
-
cb(stats);
|
|
1054
|
-
} catch (err) {
|
|
1055
|
-
this.polLogger.warn("[PolAttachmentQueue] Callback error:", err);
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
};
|
|
1059
|
-
if (forceImmediate || timeSinceLastNotify >= NOTIFY_THROTTLE_MS) {
|
|
1060
|
-
this._lastNotifyTime = now;
|
|
1061
|
-
this.getStats().then(notifyAll).catch((err) => {
|
|
1062
|
-
this.polLogger.warn("[PolAttachmentQueue] Stats error:", err);
|
|
1063
|
-
});
|
|
1064
|
-
} else {
|
|
1065
|
-
const delay = NOTIFY_THROTTLE_MS - timeSinceLastNotify;
|
|
1066
|
-
this._notifyTimer = setTimeout(() => {
|
|
1067
|
-
this._notifyTimer = null;
|
|
1068
|
-
this._lastNotifyTime = Date.now();
|
|
1069
|
-
this.getStats().then(notifyAll).catch((err) => {
|
|
1070
|
-
this.polLogger.warn("[PolAttachmentQueue] Stats error:", err);
|
|
1071
|
-
});
|
|
1072
|
-
}, delay);
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1075
|
-
_sleep(ms) {
|
|
1076
|
-
return new Promise((resolve) => {
|
|
1077
|
-
const timer = setTimeout(resolve, ms);
|
|
1078
|
-
this._uploadAbort.signal.addEventListener("abort", () => {
|
|
1079
|
-
clearTimeout(timer);
|
|
1080
|
-
resolve();
|
|
1081
|
-
}, {
|
|
1082
|
-
once: true
|
|
1083
|
-
});
|
|
1084
|
-
});
|
|
1085
|
-
}
|
|
1086
|
-
/**
|
|
1087
|
-
* Warning 6: Enhanced timeout that supports AbortSignal for actual cancellation.
|
|
1088
|
-
* When signal is provided and aborts, the promise rejects with an AbortError.
|
|
1089
|
-
*/
|
|
1090
|
-
_withTimeout(promise, ms, message, signal) {
|
|
1091
|
-
return new Promise((resolve, reject) => {
|
|
1092
|
-
if (signal?.aborted) {
|
|
1093
|
-
reject(new DOMException("Operation aborted", "AbortError"));
|
|
1094
|
-
return;
|
|
1095
|
-
}
|
|
1096
|
-
const timer = setTimeout(() => {
|
|
1097
|
-
reject(new Error(message));
|
|
1098
|
-
}, ms);
|
|
1099
|
-
const abortHandler = () => {
|
|
1100
|
-
clearTimeout(timer);
|
|
1101
|
-
reject(new DOMException("Operation aborted", "AbortError"));
|
|
1102
|
-
};
|
|
1103
|
-
if (signal) {
|
|
1104
|
-
signal.addEventListener("abort", abortHandler, {
|
|
1105
|
-
once: true
|
|
1106
|
-
});
|
|
1107
|
-
}
|
|
1108
|
-
promise.then((result) => {
|
|
1109
|
-
clearTimeout(timer);
|
|
1110
|
-
if (signal) {
|
|
1111
|
-
signal.removeEventListener("abort", abortHandler);
|
|
1112
|
-
}
|
|
1113
|
-
resolve(result);
|
|
1114
|
-
}, (err) => {
|
|
1115
|
-
clearTimeout(timer);
|
|
1116
|
-
if (signal) {
|
|
1117
|
-
signal.removeEventListener("abort", abortHandler);
|
|
1118
|
-
}
|
|
1119
|
-
reject(err);
|
|
1120
|
-
});
|
|
1121
|
-
});
|
|
1122
|
-
}
|
|
1123
|
-
};
|
|
1124
|
-
function createPolAttachmentQueue(powersync, platform, config) {
|
|
1125
|
-
return new PolAttachmentQueue({
|
|
1126
|
-
powersync,
|
|
1127
|
-
// storage is created internally by the constructor using platform + remoteStorage + attachmentDirectoryName
|
|
1128
|
-
storage: void 0,
|
|
1129
|
-
// Will be overridden by constructor
|
|
1130
|
-
platform,
|
|
1131
|
-
remoteStorage: config.remoteStorage,
|
|
1132
|
-
source: config.source,
|
|
1133
|
-
attachmentDirectoryName: config.attachmentTableName ?? "attachments",
|
|
1134
|
-
attachmentTableName: config.attachmentTableName ?? "attachments",
|
|
1135
|
-
performInitialSync: config.performInitialSync,
|
|
1136
|
-
downloadAttachments: true,
|
|
1137
|
-
onDownloadError: config.onDownloadError ? async (attachment, error) => {
|
|
1138
|
-
const result = await config.onDownloadError(attachment, error);
|
|
1139
|
-
return result;
|
|
1140
|
-
} : void 0,
|
|
1141
|
-
uploadHandler: config.upload?.handler,
|
|
1142
|
-
uploadConfig: config.upload?.config,
|
|
1143
|
-
onUploadComplete: config.onUploadComplete,
|
|
1144
|
-
onUploadFailed: config.onUploadFailed,
|
|
1145
|
-
compression: config.compression,
|
|
1146
|
-
cache: config.cache
|
|
1147
|
-
});
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
export {
|
|
1151
|
-
PolStorageAdapter,
|
|
1152
|
-
PolAttachmentState,
|
|
1153
|
-
DEFAULT_COMPRESSION_CONFIG,
|
|
1154
|
-
DEFAULT_UPLOAD_CONFIG,
|
|
1155
|
-
DEFAULT_CACHE_CONFIG,
|
|
1156
|
-
AttachmentState,
|
|
1157
|
-
ATTACHMENT_TABLE,
|
|
1158
|
-
AttachmentTable,
|
|
1159
|
-
EncodingType2 as EncodingType,
|
|
1160
|
-
DEFAULT_ATTACHMENT_QUEUE_OPTIONS,
|
|
1161
|
-
AbstractAttachmentQueue,
|
|
1162
|
-
PolAttachmentQueue,
|
|
1163
|
-
createPolAttachmentQueue
|
|
1164
|
-
};
|
|
1165
|
-
//# sourceMappingURL=chunk-ZEOKPWUC.js.map
|