@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
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getStorageErrorMessage,
|
|
3
|
+
isStorageAuthError,
|
|
4
|
+
normalizeStoragePath
|
|
5
|
+
} from "./chunk-XAEII4ZX.js";
|
|
6
|
+
import {
|
|
7
|
+
resolveBucket
|
|
8
|
+
} from "./chunk-Z6VOBGTU.js";
|
|
9
|
+
|
|
10
|
+
// src/storage/supabase.ts
|
|
11
|
+
function createSupabaseStorage(options) {
|
|
12
|
+
const {
|
|
13
|
+
client,
|
|
14
|
+
defaultBucket,
|
|
15
|
+
bucketMap,
|
|
16
|
+
bucketResolver,
|
|
17
|
+
signedUrlExpiry = 60,
|
|
18
|
+
fileSystem,
|
|
19
|
+
logger
|
|
20
|
+
} = options;
|
|
21
|
+
function getBucket(storagePath) {
|
|
22
|
+
return resolveBucket({
|
|
23
|
+
defaultBucket,
|
|
24
|
+
bucketMap,
|
|
25
|
+
bucketResolver
|
|
26
|
+
}, storagePath);
|
|
27
|
+
}
|
|
28
|
+
async function getSignedUrl(remotePath) {
|
|
29
|
+
const normalizedPath = normalizeStoragePath(remotePath);
|
|
30
|
+
const bucket = getBucket(normalizedPath);
|
|
31
|
+
logger?.debug?.(`[SupabaseStorage] Creating signed URL`, {
|
|
32
|
+
bucket,
|
|
33
|
+
path: normalizedPath,
|
|
34
|
+
originalPath: remotePath !== normalizedPath ? remotePath : void 0
|
|
35
|
+
});
|
|
36
|
+
const {
|
|
37
|
+
data,
|
|
38
|
+
error
|
|
39
|
+
} = await client.storage.from(bucket).createSignedUrl(normalizedPath, signedUrlExpiry);
|
|
40
|
+
if (error) {
|
|
41
|
+
const errorMessage = getStorageErrorMessage(error);
|
|
42
|
+
if (isStorageAuthError(error)) {
|
|
43
|
+
logger?.warn?.(`[SupabaseStorage] Auth error creating signed URL, refreshing session:`, {
|
|
44
|
+
path: normalizedPath,
|
|
45
|
+
bucket,
|
|
46
|
+
error: errorMessage
|
|
47
|
+
});
|
|
48
|
+
try {
|
|
49
|
+
await client.auth.refreshSession();
|
|
50
|
+
const {
|
|
51
|
+
data: retryData,
|
|
52
|
+
error: retryError
|
|
53
|
+
} = await client.storage.from(bucket).createSignedUrl(normalizedPath, signedUrlExpiry);
|
|
54
|
+
if (retryError) {
|
|
55
|
+
const retryErrorMessage = getStorageErrorMessage(retryError);
|
|
56
|
+
throw new Error(`Failed to create signed URL after session refresh: ${retryErrorMessage}`);
|
|
57
|
+
}
|
|
58
|
+
if (!retryData?.signedUrl) {
|
|
59
|
+
throw new Error("Failed to create signed URL after session refresh: No URL returned");
|
|
60
|
+
}
|
|
61
|
+
logger?.info?.(`[SupabaseStorage] Successfully created signed URL after session refresh`);
|
|
62
|
+
return retryData.signedUrl;
|
|
63
|
+
} catch (refreshError) {
|
|
64
|
+
throw new Error(`Failed to create signed URL: ${errorMessage} (session refresh also failed: ${refreshError instanceof Error ? refreshError.message : String(refreshError)})`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
throw new Error(`Failed to create signed URL for '${normalizedPath}' in bucket '${bucket}': ${errorMessage}`);
|
|
68
|
+
}
|
|
69
|
+
if (!data?.signedUrl) {
|
|
70
|
+
throw new Error(`Failed to create signed URL for '${normalizedPath}' in bucket '${bucket}': No URL returned`);
|
|
71
|
+
}
|
|
72
|
+
return data.signedUrl;
|
|
73
|
+
}
|
|
74
|
+
async function download(remotePath) {
|
|
75
|
+
const signedUrl = await getSignedUrl(remotePath);
|
|
76
|
+
if (fileSystem) {
|
|
77
|
+
const cacheDir = fileSystem.getCacheDirectory();
|
|
78
|
+
const tempFileName = `download_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
79
|
+
const tempPath = `${cacheDir}/${tempFileName}`;
|
|
80
|
+
try {
|
|
81
|
+
await fileSystem.downloadFile(signedUrl, tempPath);
|
|
82
|
+
return {
|
|
83
|
+
type: "file",
|
|
84
|
+
uri: tempPath
|
|
85
|
+
};
|
|
86
|
+
} catch (downloadError) {
|
|
87
|
+
try {
|
|
88
|
+
const info = await fileSystem.getFileInfo(tempPath);
|
|
89
|
+
if (info?.exists) {
|
|
90
|
+
await fileSystem.deleteFile(tempPath);
|
|
91
|
+
}
|
|
92
|
+
} catch {
|
|
93
|
+
}
|
|
94
|
+
throw downloadError;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const response = await fetch(signedUrl);
|
|
98
|
+
if (!response.ok) {
|
|
99
|
+
throw new Error(`Download failed with status ${response.status}: ${remotePath}`);
|
|
100
|
+
}
|
|
101
|
+
const blob = await response.blob();
|
|
102
|
+
return {
|
|
103
|
+
type: "blob",
|
|
104
|
+
data: blob
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
async function upload(remotePath, localUri, contentType, options2) {
|
|
108
|
+
const normalizedPath = normalizeStoragePath(remotePath);
|
|
109
|
+
const bucket = getBucket(normalizedPath);
|
|
110
|
+
const {
|
|
111
|
+
signal,
|
|
112
|
+
onProgress
|
|
113
|
+
} = options2 ?? {};
|
|
114
|
+
let fileData;
|
|
115
|
+
if (fileSystem && localUri.startsWith("file://")) {
|
|
116
|
+
const base64Content = await fileSystem.readFile(localUri, "base64");
|
|
117
|
+
const binaryString = atob(base64Content);
|
|
118
|
+
const bytes = new Uint8Array(binaryString.length);
|
|
119
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
120
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
121
|
+
}
|
|
122
|
+
fileData = new Blob([bytes], {
|
|
123
|
+
type: contentType
|
|
124
|
+
});
|
|
125
|
+
} else if (localUri.startsWith("blob:") || localUri.startsWith("http")) {
|
|
126
|
+
const response = await fetch(localUri);
|
|
127
|
+
fileData = await response.blob();
|
|
128
|
+
} else {
|
|
129
|
+
throw new Error(`Unsupported URI format: ${localUri}`);
|
|
130
|
+
}
|
|
131
|
+
if (signal?.aborted) {
|
|
132
|
+
throw new Error("Upload cancelled");
|
|
133
|
+
}
|
|
134
|
+
onProgress?.({
|
|
135
|
+
loaded: 0,
|
|
136
|
+
total: fileData.size
|
|
137
|
+
});
|
|
138
|
+
const {
|
|
139
|
+
error
|
|
140
|
+
} = await client.storage.from(bucket).upload(normalizedPath, fileData, {
|
|
141
|
+
contentType,
|
|
142
|
+
upsert: true
|
|
143
|
+
});
|
|
144
|
+
if (error) {
|
|
145
|
+
const errorMessage = getStorageErrorMessage(error);
|
|
146
|
+
throw new Error(`Upload failed for '${normalizedPath}' in bucket '${bucket}': ${errorMessage}`);
|
|
147
|
+
}
|
|
148
|
+
onProgress?.({
|
|
149
|
+
loaded: fileData.size,
|
|
150
|
+
total: fileData.size
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
async function deleteFile(remotePath) {
|
|
154
|
+
const normalizedPath = normalizeStoragePath(remotePath);
|
|
155
|
+
const bucket = getBucket(normalizedPath);
|
|
156
|
+
const {
|
|
157
|
+
error
|
|
158
|
+
} = await client.storage.from(bucket).remove([normalizedPath]);
|
|
159
|
+
if (error) {
|
|
160
|
+
const errorMessage = getStorageErrorMessage(error);
|
|
161
|
+
throw new Error(`Delete failed for '${normalizedPath}' in bucket '${bucket}': ${errorMessage}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
download,
|
|
166
|
+
upload,
|
|
167
|
+
delete: deleteFile,
|
|
168
|
+
resolveBucket: getBucket
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function isFileResult(result) {
|
|
172
|
+
return result.type === "file";
|
|
173
|
+
}
|
|
174
|
+
function isBlobResult(result) {
|
|
175
|
+
return result.type === "blob";
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// src/storage/cache/types.ts
|
|
179
|
+
var DEFAULT_CACHE_SETTINGS = {
|
|
180
|
+
maxCacheSizeMb: 5120,
|
|
181
|
+
// 5 GB
|
|
182
|
+
compressionQuality: 0.7
|
|
183
|
+
// 70% quality
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// src/storage/cache/CacheSettingsManager.ts
|
|
187
|
+
var STORAGE_KEYS = {
|
|
188
|
+
MAX_CACHE_SIZE_MB: "max-cache-size-mb",
|
|
189
|
+
COMPRESSION_QUALITY: "compression-quality"
|
|
190
|
+
};
|
|
191
|
+
var CacheSettingsManager = class {
|
|
192
|
+
storage;
|
|
193
|
+
keyPrefix;
|
|
194
|
+
defaults;
|
|
195
|
+
constructor(storage, options) {
|
|
196
|
+
this.storage = storage;
|
|
197
|
+
this.keyPrefix = options?.storageKeyPrefix ?? "@pol-studios/cache";
|
|
198
|
+
this.defaults = {
|
|
199
|
+
...DEFAULT_CACHE_SETTINGS,
|
|
200
|
+
...options?.defaults
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Get the full storage key for a setting.
|
|
205
|
+
*/
|
|
206
|
+
getKey(setting) {
|
|
207
|
+
return `${this.keyPrefix}/${STORAGE_KEYS[setting]}`;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Get all cache settings.
|
|
211
|
+
*/
|
|
212
|
+
async getSettings() {
|
|
213
|
+
const keys = [this.getKey("MAX_CACHE_SIZE_MB"), this.getKey("COMPRESSION_QUALITY")];
|
|
214
|
+
const results = await this.storage.multiGet(keys);
|
|
215
|
+
const sizeValue = results.find(([k]) => k === keys[0])?.[1];
|
|
216
|
+
const qualityValue = results.find(([k]) => k === keys[1])?.[1];
|
|
217
|
+
return {
|
|
218
|
+
maxCacheSizeMb: sizeValue ? Number(sizeValue) : this.defaults.maxCacheSizeMb,
|
|
219
|
+
compressionQuality: qualityValue ? Number(qualityValue) : this.defaults.compressionQuality
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Get the maximum cache size in megabytes.
|
|
224
|
+
* Returns 0 for unlimited.
|
|
225
|
+
*/
|
|
226
|
+
async getMaxCacheSizeMb() {
|
|
227
|
+
const value = await this.storage.getItem(this.getKey("MAX_CACHE_SIZE_MB"));
|
|
228
|
+
return value ? Number(value) : this.defaults.maxCacheSizeMb;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Set the maximum cache size in megabytes.
|
|
232
|
+
* Use 0 for unlimited.
|
|
233
|
+
*
|
|
234
|
+
* @param mb - Maximum cache size in megabytes
|
|
235
|
+
*/
|
|
236
|
+
async setMaxCacheSizeMb(mb) {
|
|
237
|
+
if (mb < 0) {
|
|
238
|
+
throw new Error("Cache size must be non-negative");
|
|
239
|
+
}
|
|
240
|
+
await this.storage.setItem(this.getKey("MAX_CACHE_SIZE_MB"), String(mb));
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Get the maximum cache size in bytes.
|
|
244
|
+
* Returns Number.MAX_SAFE_INTEGER for unlimited.
|
|
245
|
+
*/
|
|
246
|
+
async getMaxCacheSizeBytes() {
|
|
247
|
+
const mb = await this.getMaxCacheSizeMb();
|
|
248
|
+
return mb === 0 ? Number.MAX_SAFE_INTEGER : mb * 1024 * 1024;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Get the compression quality (0.0 to 1.0).
|
|
252
|
+
*/
|
|
253
|
+
async getCompressionQuality() {
|
|
254
|
+
const value = await this.storage.getItem(this.getKey("COMPRESSION_QUALITY"));
|
|
255
|
+
return value ? Number(value) : this.defaults.compressionQuality;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Set the compression quality.
|
|
259
|
+
*
|
|
260
|
+
* @param quality - Compression quality (0.0 to 1.0)
|
|
261
|
+
*/
|
|
262
|
+
async setCompressionQuality(quality) {
|
|
263
|
+
if (quality < 0 || quality > 1) {
|
|
264
|
+
throw new Error("Compression quality must be between 0 and 1");
|
|
265
|
+
}
|
|
266
|
+
await this.storage.setItem(this.getKey("COMPRESSION_QUALITY"), String(quality));
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Reset all settings to defaults.
|
|
270
|
+
*/
|
|
271
|
+
async resetToDefaults() {
|
|
272
|
+
await this.storage.multiSet([[this.getKey("MAX_CACHE_SIZE_MB"), String(this.defaults.maxCacheSizeMb)], [this.getKey("COMPRESSION_QUALITY"), String(this.defaults.compressionQuality)]]);
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Clear all stored settings.
|
|
276
|
+
*/
|
|
277
|
+
async clear() {
|
|
278
|
+
await Promise.all([this.storage.removeItem(this.getKey("MAX_CACHE_SIZE_MB")), this.storage.removeItem(this.getKey("COMPRESSION_QUALITY"))]);
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
export {
|
|
283
|
+
createSupabaseStorage,
|
|
284
|
+
isFileResult,
|
|
285
|
+
isBlobResult,
|
|
286
|
+
DEFAULT_CACHE_SETTINGS,
|
|
287
|
+
CacheSettingsManager
|
|
288
|
+
};
|
|
289
|
+
//# sourceMappingURL=chunk-XOY2CJ67.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/storage/supabase.ts","../src/storage/cache/types.ts","../src/storage/cache/CacheSettingsManager.ts"],"sourcesContent":["/**\n * Supabase Storage Backend for @pol-studios/powersync\n *\n * Unified storage implementation using Supabase Storage.\n * Handles both web (Blob) and native (file URI) downloads.\n *\n * @example\n * ```typescript\n * import { createSupabaseStorage } from '@pol-studios/powersync/storage';\n *\n * const storage = createSupabaseStorage({\n * client: supabaseClient,\n * defaultBucket: 'attachments',\n * fileSystem: platform.fileSystem, // Required for native\n * });\n *\n * // Download\n * const result = await storage.download('photos/image.jpg');\n * if (result.type === 'file') {\n * // Native: zero-copy file path\n * console.log('Downloaded to:', result.uri);\n * } else {\n * // Web: Blob in memory\n * console.log('Downloaded blob:', result.data.size, 'bytes');\n * }\n *\n * // Upload with progress\n * await storage.upload('photos/new.jpg', 'file:///local/path.jpg', 'image/jpeg', {\n * onProgress: ({ loaded, total }) => {\n * console.log(`Progress: ${Math.round(loaded / total * 100)}%`);\n * },\n * });\n * ```\n */\n\nimport type { StorageBackend, DownloadResult, StorageUploadOptions, SupabaseStorageOptions, SupabaseClient } from './types';\nimport { resolveBucket } from './types';\nimport { isStorageAuthError, getStorageErrorMessage, normalizeStoragePath } from './auth-error-utils';\n\n/**\n * Create a Supabase storage backend.\n *\n * @param options - Configuration options\n * @returns StorageBackend implementation\n */\nexport function createSupabaseStorage(options: SupabaseStorageOptions): StorageBackend {\n const {\n client,\n defaultBucket,\n bucketMap,\n bucketResolver,\n signedUrlExpiry = 60,\n fileSystem,\n logger\n } = options;\n\n /**\n * Get the bucket for a storage path.\n */\n function getBucket(storagePath: string): string {\n return resolveBucket({\n defaultBucket,\n bucketMap,\n bucketResolver\n }, storagePath);\n }\n\n /**\n * Create a signed download URL with automatic session refresh on auth errors.\n */\n async function getSignedUrl(remotePath: string): Promise<string> {\n // Normalize the path to remove any leading slashes\n // Supabase Storage expects paths without leading slashes\n const normalizedPath = normalizeStoragePath(remotePath);\n const bucket = getBucket(normalizedPath);\n logger?.debug?.(`[SupabaseStorage] Creating signed URL`, {\n bucket,\n path: normalizedPath,\n originalPath: remotePath !== normalizedPath ? remotePath : undefined\n });\n\n // First attempt\n const {\n data,\n error\n } = await client.storage.from(bucket).createSignedUrl(normalizedPath, signedUrlExpiry);\n if (error) {\n const errorMessage = getStorageErrorMessage(error);\n\n // Check if this is an auth error that might be fixed by refreshing the session\n if (isStorageAuthError(error)) {\n logger?.warn?.(`[SupabaseStorage] Auth error creating signed URL, refreshing session:`, {\n path: normalizedPath,\n bucket,\n error: errorMessage\n });\n try {\n // Refresh the session\n await client.auth.refreshSession();\n\n // Retry the request\n const {\n data: retryData,\n error: retryError\n } = await client.storage.from(bucket).createSignedUrl(normalizedPath, signedUrlExpiry);\n if (retryError) {\n const retryErrorMessage = getStorageErrorMessage(retryError);\n throw new Error(`Failed to create signed URL after session refresh: ${retryErrorMessage}`);\n }\n if (!retryData?.signedUrl) {\n throw new Error('Failed to create signed URL after session refresh: No URL returned');\n }\n logger?.info?.(`[SupabaseStorage] Successfully created signed URL after session refresh`);\n return retryData.signedUrl;\n } catch (refreshError) {\n throw new Error(`Failed to create signed URL: ${errorMessage} (session refresh also failed: ${refreshError instanceof Error ? refreshError.message : String(refreshError)})`);\n }\n }\n\n // Non-auth error, throw with normalized error message\n throw new Error(`Failed to create signed URL for '${normalizedPath}' in bucket '${bucket}': ${errorMessage}`);\n }\n if (!data?.signedUrl) {\n throw new Error(`Failed to create signed URL for '${normalizedPath}' in bucket '${bucket}': No URL returned`);\n }\n return data.signedUrl;\n }\n\n /**\n * Download a file from remote storage.\n *\n * Returns a discriminated union:\n * - Native (with fileSystem): `{ type: 'file', uri: string }` - zero-copy file path\n * - Web (no fileSystem): `{ type: 'blob', data: Blob }` - blob in memory\n */\n async function download(remotePath: string): Promise<DownloadResult> {\n const signedUrl = await getSignedUrl(remotePath);\n\n // Native path: download directly to temp file\n if (fileSystem) {\n const cacheDir = fileSystem.getCacheDirectory();\n const tempFileName = `download_${Date.now()}_${Math.random().toString(36).slice(2)}`;\n const tempPath = `${cacheDir}/${tempFileName}`;\n try {\n await fileSystem.downloadFile(signedUrl, tempPath);\n return {\n type: 'file',\n uri: tempPath\n };\n } catch (downloadError) {\n // Clean up temp file on error\n try {\n const info = await fileSystem.getFileInfo(tempPath);\n if (info?.exists) {\n await fileSystem.deleteFile(tempPath);\n }\n } catch {\n // Ignore cleanup errors\n }\n throw downloadError;\n }\n }\n\n // Web path: fetch as blob\n const response = await fetch(signedUrl);\n if (!response.ok) {\n throw new Error(`Download failed with status ${response.status}: ${remotePath}`);\n }\n const blob = await response.blob();\n return {\n type: 'blob',\n data: blob\n };\n }\n\n /**\n * Upload a file to remote storage.\n *\n * Supports both native file URIs and web blob URLs.\n */\n async function upload(remotePath: string, localUri: string, contentType: string, options?: StorageUploadOptions): Promise<void> {\n // Normalize the path to remove any leading slashes\n const normalizedPath = normalizeStoragePath(remotePath);\n const bucket = getBucket(normalizedPath);\n const {\n signal,\n onProgress\n } = options ?? {};\n\n // Read file content\n let fileData: Blob;\n if (fileSystem && localUri.startsWith('file://')) {\n // Native: read file as base64, convert to blob\n const base64Content = await fileSystem.readFile(localUri, 'base64');\n const binaryString = atob(base64Content);\n const bytes = new Uint8Array(binaryString.length);\n for (let i = 0; i < binaryString.length; i++) {\n bytes[i] = binaryString.charCodeAt(i);\n }\n fileData = new Blob([bytes], {\n type: contentType\n });\n } else if (localUri.startsWith('blob:') || localUri.startsWith('http')) {\n // Web: fetch blob URL\n const response = await fetch(localUri);\n fileData = await response.blob();\n } else {\n throw new Error(`Unsupported URI format: ${localUri}`);\n }\n\n // Check for cancellation\n if (signal?.aborted) {\n throw new Error('Upload cancelled');\n }\n\n // Report initial progress\n onProgress?.({\n loaded: 0,\n total: fileData.size\n });\n\n // Upload to Supabase\n const {\n error\n } = await client.storage.from(bucket).upload(normalizedPath, fileData, {\n contentType,\n upsert: true\n });\n if (error) {\n const errorMessage = getStorageErrorMessage(error);\n throw new Error(`Upload failed for '${normalizedPath}' in bucket '${bucket}': ${errorMessage}`);\n }\n\n // Report completion\n onProgress?.({\n loaded: fileData.size,\n total: fileData.size\n });\n }\n\n /**\n * Delete a file from remote storage.\n */\n async function deleteFile(remotePath: string): Promise<void> {\n // Normalize the path to remove any leading slashes\n const normalizedPath = normalizeStoragePath(remotePath);\n const bucket = getBucket(normalizedPath);\n const {\n error\n } = await client.storage.from(bucket).remove([normalizedPath]);\n if (error) {\n const errorMessage = getStorageErrorMessage(error);\n throw new Error(`Delete failed for '${normalizedPath}' in bucket '${bucket}': ${errorMessage}`);\n }\n }\n return {\n download,\n upload,\n delete: deleteFile,\n resolveBucket: getBucket\n };\n}\n\n// ─── Type Guard Utilities ───────────────────────────────────────────────────\n\n/**\n * Type guard to check if download result is a file URI.\n */\nexport function isFileResult(result: DownloadResult): result is {\n type: 'file';\n uri: string;\n} {\n return result.type === 'file';\n}\n\n/**\n * Type guard to check if download result is a blob.\n */\nexport function isBlobResult(result: DownloadResult): result is {\n type: 'blob';\n data: Blob;\n} {\n return result.type === 'blob';\n}","/**\n * Cache Settings Types for @pol-studios/powersync\n *\n * Defines types for cache configuration storage.\n * Note: UI-specific options (labels, descriptions) should be defined in the app.\n */\n\n/**\n * Cache settings stored via AsyncStorageAdapter.\n */\nexport interface CacheSettings {\n /**\n * Maximum cache size in megabytes.\n * A value of 0 indicates unlimited cache size.\n */\n maxCacheSizeMb: number;\n\n /**\n * Compression quality for cached images (0.0 to 1.0).\n * Higher values mean better quality but larger file sizes.\n */\n compressionQuality: number;\n}\n\n/**\n * Options for creating a CacheSettingsManager.\n */\nexport interface CacheSettingsManagerOptions {\n /**\n * Prefix for storage keys.\n * Useful for namespacing when multiple apps share storage.\n * @default '@pol-studios/cache'\n */\n storageKeyPrefix?: string;\n\n /**\n * Default values for cache settings.\n */\n defaults?: Partial<CacheSettings>;\n}\n\n/**\n * Default cache settings values.\n */\nexport const DEFAULT_CACHE_SETTINGS: CacheSettings = {\n maxCacheSizeMb: 5120,\n // 5 GB\n compressionQuality: 0.7 // 70% quality\n};","/**\n * Cache Settings Manager for @pol-studios/powersync\n *\n * Manages persistence of cache settings via AsyncStorageAdapter.\n * Uses platform-agnostic storage interface for cross-platform support.\n */\n\nimport type { AsyncStorageAdapter } from '../../platform/types';\nimport type { CacheSettings, CacheSettingsManagerOptions } from './types';\nimport { DEFAULT_CACHE_SETTINGS } from './types';\n\n/**\n * Storage keys used by the cache settings manager.\n */\nconst STORAGE_KEYS = {\n MAX_CACHE_SIZE_MB: 'max-cache-size-mb',\n COMPRESSION_QUALITY: 'compression-quality'\n} as const;\n\n/**\n * Manages cache settings persistence using AsyncStorageAdapter.\n *\n * @example\n * ```typescript\n * const manager = new CacheSettingsManager(platform.storage, {\n * storageKeyPrefix: '@myapp/cache',\n * defaults: { maxCacheSizeMb: 2048 },\n * });\n *\n * const settings = await manager.getSettings();\n * await manager.setMaxCacheSizeMb(1024);\n * ```\n */\nexport class CacheSettingsManager {\n private storage: AsyncStorageAdapter;\n private keyPrefix: string;\n private defaults: CacheSettings;\n constructor(storage: AsyncStorageAdapter, options?: CacheSettingsManagerOptions) {\n this.storage = storage;\n this.keyPrefix = options?.storageKeyPrefix ?? '@pol-studios/cache';\n this.defaults = {\n ...DEFAULT_CACHE_SETTINGS,\n ...options?.defaults\n };\n }\n\n /**\n * Get the full storage key for a setting.\n */\n private getKey(setting: keyof typeof STORAGE_KEYS): string {\n return `${this.keyPrefix}/${STORAGE_KEYS[setting]}`;\n }\n\n /**\n * Get all cache settings.\n */\n async getSettings(): Promise<CacheSettings> {\n const keys = [this.getKey('MAX_CACHE_SIZE_MB'), this.getKey('COMPRESSION_QUALITY')];\n const results = await this.storage.multiGet(keys);\n\n // Build settings object from stored values or defaults\n const sizeValue = results.find(([k]) => k === keys[0])?.[1];\n const qualityValue = results.find(([k]) => k === keys[1])?.[1];\n return {\n maxCacheSizeMb: sizeValue ? Number(sizeValue) : this.defaults.maxCacheSizeMb,\n compressionQuality: qualityValue ? Number(qualityValue) : this.defaults.compressionQuality\n };\n }\n\n /**\n * Get the maximum cache size in megabytes.\n * Returns 0 for unlimited.\n */\n async getMaxCacheSizeMb(): Promise<number> {\n const value = await this.storage.getItem(this.getKey('MAX_CACHE_SIZE_MB'));\n return value ? Number(value) : this.defaults.maxCacheSizeMb;\n }\n\n /**\n * Set the maximum cache size in megabytes.\n * Use 0 for unlimited.\n *\n * @param mb - Maximum cache size in megabytes\n */\n async setMaxCacheSizeMb(mb: number): Promise<void> {\n if (mb < 0) {\n throw new Error('Cache size must be non-negative');\n }\n await this.storage.setItem(this.getKey('MAX_CACHE_SIZE_MB'), String(mb));\n }\n\n /**\n * Get the maximum cache size in bytes.\n * Returns Number.MAX_SAFE_INTEGER for unlimited.\n */\n async getMaxCacheSizeBytes(): Promise<number> {\n const mb = await this.getMaxCacheSizeMb();\n return mb === 0 ? Number.MAX_SAFE_INTEGER : mb * 1024 * 1024;\n }\n\n /**\n * Get the compression quality (0.0 to 1.0).\n */\n async getCompressionQuality(): Promise<number> {\n const value = await this.storage.getItem(this.getKey('COMPRESSION_QUALITY'));\n return value ? Number(value) : this.defaults.compressionQuality;\n }\n\n /**\n * Set the compression quality.\n *\n * @param quality - Compression quality (0.0 to 1.0)\n */\n async setCompressionQuality(quality: number): Promise<void> {\n if (quality < 0 || quality > 1) {\n throw new Error('Compression quality must be between 0 and 1');\n }\n await this.storage.setItem(this.getKey('COMPRESSION_QUALITY'), String(quality));\n }\n\n /**\n * Reset all settings to defaults.\n */\n async resetToDefaults(): Promise<void> {\n await this.storage.multiSet([[this.getKey('MAX_CACHE_SIZE_MB'), String(this.defaults.maxCacheSizeMb)], [this.getKey('COMPRESSION_QUALITY'), String(this.defaults.compressionQuality)]]);\n }\n\n /**\n * Clear all stored settings.\n */\n async clear(): Promise<void> {\n await Promise.all([this.storage.removeItem(this.getKey('MAX_CACHE_SIZE_MB')), this.storage.removeItem(this.getKey('COMPRESSION_QUALITY'))]);\n }\n}"],"mappings":"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","names":["options"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=chunk-YHTZ7VMV.js.map
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
// src/storage/types.ts
|
|
2
|
-
function
|
|
3
|
-
if (
|
|
4
|
-
const resolved =
|
|
2
|
+
function resolveBucket(options, storagePath) {
|
|
3
|
+
if (options.bucketResolver) {
|
|
4
|
+
const resolved = options.bucketResolver(storagePath);
|
|
5
5
|
if (resolved !== void 0) {
|
|
6
6
|
return resolved;
|
|
7
7
|
}
|
|
8
8
|
}
|
|
9
|
-
if (
|
|
10
|
-
for (const [prefix, bucket] of
|
|
9
|
+
if (options.bucketMap) {
|
|
10
|
+
for (const [prefix, bucket] of options.bucketMap) {
|
|
11
11
|
if (storagePath.startsWith(prefix)) {
|
|
12
12
|
return bucket;
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
|
-
return
|
|
16
|
+
return options.defaultBucket;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
// src/storage/upload/types.ts
|
|
@@ -26,7 +26,7 @@ var DEFAULT_UPLOAD_NOTIFICATION = {
|
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
export {
|
|
29
|
-
|
|
29
|
+
resolveBucket,
|
|
30
30
|
DEFAULT_UPLOAD_NOTIFICATION
|
|
31
31
|
};
|
|
32
|
-
//# sourceMappingURL=chunk-
|
|
32
|
+
//# sourceMappingURL=chunk-Z6VOBGTU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/storage/types.ts","../src/storage/upload/types.ts"],"sourcesContent":["/**\n * Unified Storage Types for @pol-studios/powersync\n *\n * Single storage interface for all Supabase storage operations.\n * Consolidates the previous 6 fragmented interfaces into one:\n * - AttachmentStorageAdapter (attachments/types.ts)\n * - RemoteStorageAdapter (storage/types.ts - removed)\n * - StorageAdapter (@powersync/attachments)\n * - PowerSyncStorageAdapter (storage/types.ts - removed)\n * - StorageUploadHandler (storage/types.ts - removed)\n * - UploadHandler (attachments/types.ts)\n */\n\n// ─── Unified Storage Interface ──────────────────────────────────────────────\n\n/**\n * Unified storage interface for Supabase storage operations.\n * Handles both upload and download for attachments.\n *\n * This is the ONLY storage interface consumers need to implement.\n * It replaces all previous storage adapter interfaces.\n *\n * @example\n * ```typescript\n * const storage: SupabaseStorage = {\n * async download(path) {\n * const { data } = await supabase.storage\n * .from('attachments')\n * .createSignedUrl(path, 60);\n * // Download to temp file and return URI\n * return tempFileUri;\n * },\n *\n * async upload(path, localUri, mediaType) {\n * const file = await readFile(localUri);\n * await supabase.storage\n * .from('attachments')\n * .upload(path, file, { contentType: mediaType });\n * },\n * };\n * ```\n */\nexport interface SupabaseStorage {\n /**\n * Download a file and return the local file:// URI.\n *\n * @param path - Remote storage path\n * @returns Local file:// URI pointing to the downloaded file\n */\n download(path: string): Promise<string>;\n\n /**\n * Upload a local file to remote storage.\n *\n * @param path - Remote storage path\n * @param localUri - Local file:// URI to upload from\n * @param mediaType - MIME type of the file\n * @param signal - Optional AbortSignal for cancellation\n */\n upload(path: string, localUri: string, mediaType: string, signal?: AbortSignal): Promise<void>;\n\n /**\n * Delete a file from remote storage.\n * Optional - not all implementations need deletion.\n *\n * @param path - Remote storage path to delete\n */\n delete?(path: string): Promise<void>;\n\n /**\n * Resolve which bucket a path belongs to.\n * Optional - useful for multi-bucket configurations.\n *\n * @param path - Remote storage path\n * @returns The bucket name\n */\n resolveBucket?(path: string): string;\n}\n\n// ─── Storage Backend (Factory API) ──────────────────────────────────────────\n\n/**\n * Download result discriminated union.\n * Native platforms return file URIs, web returns blobs.\n */\nexport type DownloadResult = {\n type: 'file';\n uri: string;\n} | {\n type: 'blob';\n data: Blob;\n};\n\n/**\n * Storage backend returned by createSupabaseStorage factory.\n * This is a more flexible interface that returns discriminated unions\n * to handle both native (file URI) and web (blob) download results.\n *\n * For simpler use cases, use SupabaseStorage interface directly.\n */\nexport interface StorageBackend {\n /** Download a file, returns discriminated union based on platform */\n download(path: string): Promise<DownloadResult>;\n\n /** Upload a local file to remote storage */\n upload(path: string, localUri: string, contentType: string, options?: StorageUploadOptions): Promise<void>;\n\n /** Delete a file from remote storage */\n delete(path: string): Promise<void>;\n\n /** Resolve which bucket a path belongs to */\n resolveBucket(path: string): string;\n}\n\n// ─── Upload Progress ────────────────────────────────────────────────────────\n\n/**\n * Progress information for upload operations.\n */\nexport interface UploadProgress {\n /** Bytes uploaded so far */\n loaded: number;\n /** Total bytes to upload */\n total: number;\n}\n\n// ─── Upload Options ─────────────────────────────────────────────────────────\n\n/**\n * Options for upload operations.\n */\nexport interface StorageUploadOptions {\n /** AbortSignal for cancellation support */\n signal?: AbortSignal;\n /** Progress callback */\n onProgress?: (progress: UploadProgress) => void;\n}\n\n// ─── Supabase Storage Options ───────────────────────────────────────────────\n\nimport type { LoggerAdapter, FileSystemAdapter } from '../platform/types';\n\n/**\n * Options for creating a Supabase storage backend.\n */\nexport interface SupabaseStorageOptions {\n /** Supabase client instance */\n client: SupabaseClient;\n\n /** Default storage bucket */\n defaultBucket: string;\n\n /**\n * Map of path prefixes to bucket names.\n * Checked in iteration order (use Map to preserve order).\n *\n * @example\n * ```typescript\n * bucketMap: new Map([\n * ['avatars/', 'user-avatars'],\n * ['docs/', 'documents'],\n * ])\n * ```\n */\n bucketMap?: Map<string, string>;\n\n /**\n * Custom bucket resolver function.\n * Takes precedence over bucketMap.\n * Return undefined to fall through to bucketMap/defaultBucket.\n */\n bucketResolver?: (storagePath: string) => string | undefined;\n\n /**\n * Signed URL expiry in seconds.\n * @default 60 (short-lived for security)\n */\n signedUrlExpiry?: number;\n\n /**\n * File system adapter for native file operations.\n * Required for native platforms to enable file-based downloads.\n */\n fileSystem?: FileSystemAdapter;\n\n /**\n * Optional logger for diagnostic messages.\n */\n logger?: LoggerAdapter;\n}\n\n// ─── Supabase Client Type ───────────────────────────────────────────────────\n\n/**\n * Transform options for Supabase image transformation.\n * See: https://supabase.com/docs/guides/storage/serving/image-transformations\n */\nexport interface SupabaseTransformOptions {\n width?: number;\n height?: number;\n resize?: 'cover' | 'contain' | 'fill';\n format?: 'origin' | 'avif' | 'webp';\n quality?: number; // 1-100\n}\n\n/**\n * Minimal Supabase client interface for storage operations.\n * Using a minimal interface to avoid version conflicts with @supabase/supabase-js.\n */\nexport interface SupabaseClient {\n storage: {\n from(bucket: string): {\n createSignedUrl(path: string, expiresIn: number, options?: {\n download?: string | boolean;\n transform?: SupabaseTransformOptions;\n }): Promise<{\n data: {\n signedUrl: string;\n } | null;\n error: Error | null;\n }>;\n upload(path: string, file: Blob | ArrayBuffer | FormData, options?: {\n contentType?: string;\n upsert?: boolean;\n }): Promise<{\n data: {\n path: string;\n } | null;\n error: Error | null;\n }>;\n remove(paths: string[]): Promise<{\n data: {\n name: string;\n }[] | null;\n error: Error | null;\n }>;\n list(path?: string, options?: {\n search?: string;\n limit?: number;\n }): Promise<{\n data: {\n name: string;\n }[] | null;\n error: Error | null;\n }>;\n };\n };\n auth: {\n refreshSession(): Promise<{\n data: unknown;\n error: Error | null;\n }>;\n };\n}\n\n// ─── Bucket Resolution Helpers ──────────────────────────────────────────────\n\n/**\n * Resolve bucket from storage path using SupabaseStorageOptions.\n *\n * Resolution order:\n * 1. Custom bucketResolver (if provided and returns a value)\n * 2. bucketMap prefix matching (if provided)\n * 3. defaultBucket\n */\nexport function resolveBucket(options: Pick<SupabaseStorageOptions, 'defaultBucket' | 'bucketMap' | 'bucketResolver'>, storagePath: string): string {\n // Try custom resolver first\n if (options.bucketResolver) {\n const resolved = options.bucketResolver(storagePath);\n if (resolved !== undefined) {\n return resolved;\n }\n }\n\n // Try bucket map prefix matching\n if (options.bucketMap) {\n for (const [prefix, bucket] of options.bucketMap) {\n if (storagePath.startsWith(prefix)) {\n return bucket;\n }\n }\n }\n\n // Fall back to default\n return options.defaultBucket;\n}","/**\n * Upload Handler Types for @pol-studios/powersync\n *\n * Defines types for platform-specific upload handlers.\n */\n\n/**\n * Bucket configuration for multi-bucket routing.\n */\nexport interface BucketConfig {\n /** Default bucket for storage operations */\n defaultBucket: string;\n\n /** Map of path prefixes to bucket names */\n bucketMap?: Map<string, string>;\n\n /** Custom bucket resolver function */\n resolver?: (storagePath: string) => string | undefined;\n}\n\n/**\n * Options for creating a SupabaseUploadHandler.\n */\nexport interface SupabaseUploadHandlerOptions {\n /** Supabase client instance */\n supabaseClient: any; // SupabaseClient - using any to avoid version conflicts\n\n /** Bucket configuration for multi-bucket routing */\n bucketConfig: BucketConfig;\n}\n\n/**\n * Event handlers for upload progress tracking.\n */\nexport interface UploadEventHandlers {\n /** Called with progress updates during upload */\n onProgress?: (progress: {\n loaded: number;\n total: number;\n }) => void;\n\n /** Called when upload completes successfully */\n onComplete?: () => void;\n\n /** Called when upload fails */\n onError?: (error: Error) => void;\n}\n\n/**\n * Configuration for native upload notifications (Android).\n */\nexport interface UploadNotificationConfig {\n /** Whether to show upload notifications */\n enabled: boolean;\n\n /** Whether to auto-clear notification on completion */\n autoClear: boolean;\n\n /** Title shown during upload progress */\n onProgressTitle: string;\n\n /** Title shown when upload completes */\n onCompleteTitle: string;\n\n /** Title shown when upload fails */\n onErrorTitle: string;\n}\n\n/**\n * Default notification configuration for Android uploads.\n */\nexport const DEFAULT_UPLOAD_NOTIFICATION: UploadNotificationConfig = {\n enabled: true,\n autoClear: true,\n onProgressTitle: 'Uploading...',\n onCompleteTitle: 'Upload complete',\n onErrorTitle: 'Upload failed'\n};"],"mappings":";AAyQO,SAAS,cAAc,SAAyF,aAA6B;AAElJ,MAAI,QAAQ,gBAAgB;AAC1B,UAAM,WAAW,QAAQ,eAAe,WAAW;AACnD,QAAI,aAAa,QAAW;AAC1B,aAAO;AAAA,IACT;AAAA,EACF;AAGA,MAAI,QAAQ,WAAW;AACrB,eAAW,CAAC,QAAQ,MAAM,KAAK,QAAQ,WAAW;AAChD,UAAI,YAAY,WAAW,MAAM,GAAG;AAClC,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF;AAGA,SAAO,QAAQ;AACjB;;;ACtNO,IAAM,8BAAwD;AAAA,EACnE,SAAS;AAAA,EACT,WAAW;AAAA,EACX,iBAAiB;AAAA,EACjB,iBAAiB;AAAA,EACjB,cAAc;AAChB;","names":[]}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
// src/attachments/query-builder.ts
|
|
2
|
+
var VALID_IDENTIFIER_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
3
|
+
var SQL_RESERVED_WORDS = /* @__PURE__ */ new Set(["SELECT", "FROM", "WHERE", "ORDER", "BY", "AND", "OR", "NOT", "NULL", "INSERT", "UPDATE", "DELETE", "DROP", "CREATE", "TABLE", "INDEX", "JOIN", "LEFT", "RIGHT", "INNER", "OUTER", "ON", "AS", "DISTINCT", "GROUP", "HAVING", "LIMIT", "OFFSET", "UNION", "EXCEPT", "INTERSECT", "IN", "BETWEEN", "LIKE", "IS", "TRUE", "FALSE", "CASE", "WHEN", "THEN", "ELSE", "END", "ASC", "DESC", "NULLS", "FIRST", "LAST"]);
|
|
4
|
+
function validateSqlIdentifier(identifier, context) {
|
|
5
|
+
if (!identifier || typeof identifier !== "string") {
|
|
6
|
+
throw new Error(`Invalid ${context}: must be a non-empty string`);
|
|
7
|
+
}
|
|
8
|
+
if (!VALID_IDENTIFIER_PATTERN.test(identifier)) {
|
|
9
|
+
throw new Error(`Invalid ${context}: "${identifier}" contains invalid characters. Identifiers must start with a letter or underscore and contain only alphanumeric characters and underscores.`);
|
|
10
|
+
}
|
|
11
|
+
if (SQL_RESERVED_WORDS.has(identifier.toUpperCase())) {
|
|
12
|
+
throw new Error(`Invalid ${context}: "${identifier}" is a SQL reserved word. Use a different name or quote the identifier.`);
|
|
13
|
+
}
|
|
14
|
+
const dangerousPatterns = [
|
|
15
|
+
/--/,
|
|
16
|
+
// SQL comment
|
|
17
|
+
/;/,
|
|
18
|
+
// Statement terminator
|
|
19
|
+
/'/,
|
|
20
|
+
// String delimiter
|
|
21
|
+
/"/,
|
|
22
|
+
// Quote
|
|
23
|
+
/\\/
|
|
24
|
+
// Escape character
|
|
25
|
+
];
|
|
26
|
+
for (const pattern of dangerousPatterns) {
|
|
27
|
+
if (pattern.test(identifier)) {
|
|
28
|
+
throw new Error(`Invalid ${context}: "${identifier}" contains potentially dangerous characters.`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function validateWhereClause(whereClause) {
|
|
33
|
+
if (!whereClause || typeof whereClause !== "string") {
|
|
34
|
+
throw new Error("Invalid WHERE clause: must be a non-empty string");
|
|
35
|
+
}
|
|
36
|
+
const dangerousPatterns = [{
|
|
37
|
+
pattern: /;\s*(SELECT|INSERT|UPDATE|DELETE|DROP|CREATE)/i,
|
|
38
|
+
name: "SQL injection (statement)"
|
|
39
|
+
}, {
|
|
40
|
+
pattern: /UNION\s+(ALL\s+)?SELECT/i,
|
|
41
|
+
name: "UNION injection"
|
|
42
|
+
}, {
|
|
43
|
+
pattern: /--/,
|
|
44
|
+
name: "SQL comment"
|
|
45
|
+
}, {
|
|
46
|
+
pattern: /\/\*/,
|
|
47
|
+
name: "block comment"
|
|
48
|
+
}, {
|
|
49
|
+
pattern: /xp_|sp_|exec\s*\(/i,
|
|
50
|
+
name: "stored procedure"
|
|
51
|
+
}];
|
|
52
|
+
for (const {
|
|
53
|
+
pattern,
|
|
54
|
+
name
|
|
55
|
+
} of dangerousPatterns) {
|
|
56
|
+
if (pattern.test(whereClause)) {
|
|
57
|
+
throw new Error(`Invalid WHERE clause: contains ${name}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function buildWatchQuery(config) {
|
|
62
|
+
validateSqlIdentifier(config.table, "table");
|
|
63
|
+
validateSqlIdentifier(config.idColumn, "idColumn");
|
|
64
|
+
if (config.selectColumns) {
|
|
65
|
+
for (const col of config.selectColumns) {
|
|
66
|
+
validateSqlIdentifier(col, "selectColumns");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (config.orderBy) {
|
|
70
|
+
validateSqlIdentifier(config.orderBy.column, "orderBy.column");
|
|
71
|
+
if (config.orderBy.direction !== "ASC" && config.orderBy.direction !== "DESC") {
|
|
72
|
+
throw new Error(`Invalid orderBy.direction: must be "ASC" or "DESC"`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (config.where) {
|
|
76
|
+
validateWhereClause(config.where);
|
|
77
|
+
}
|
|
78
|
+
const selectParts = [`${config.idColumn} AS id`];
|
|
79
|
+
if (config.selectColumns && config.selectColumns.length > 0) {
|
|
80
|
+
selectParts.push(...config.selectColumns);
|
|
81
|
+
}
|
|
82
|
+
const selectClause = selectParts.join(", ");
|
|
83
|
+
const fromClause = config.table;
|
|
84
|
+
let whereClause = `${config.idColumn} IS NOT NULL AND ${config.idColumn} != ''`;
|
|
85
|
+
if (config.where) {
|
|
86
|
+
whereClause = `${whereClause} AND (${config.where})`;
|
|
87
|
+
}
|
|
88
|
+
let orderByClause = "";
|
|
89
|
+
if (config.orderBy) {
|
|
90
|
+
orderByClause = `ORDER BY ${config.orderBy.column} ${config.orderBy.direction}`;
|
|
91
|
+
}
|
|
92
|
+
const parts = [`SELECT ${selectClause}`, `FROM ${fromClause}`, `WHERE ${whereClause}`];
|
|
93
|
+
if (orderByClause) {
|
|
94
|
+
parts.push(orderByClause);
|
|
95
|
+
}
|
|
96
|
+
return parts.join("\n");
|
|
97
|
+
}
|
|
98
|
+
function buildIdOnlyWatchQuery(config) {
|
|
99
|
+
return buildWatchQuery({
|
|
100
|
+
...config,
|
|
101
|
+
selectColumns: void 0
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
function buildRecordFetchQuery(config, ids) {
|
|
105
|
+
validateSqlIdentifier(config.table, "table");
|
|
106
|
+
validateSqlIdentifier(config.idColumn, "idColumn");
|
|
107
|
+
if (config.selectColumns) {
|
|
108
|
+
for (const col of config.selectColumns) {
|
|
109
|
+
validateSqlIdentifier(col, "selectColumns");
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const selectParts = [`${config.idColumn} AS id`];
|
|
113
|
+
if (config.selectColumns && config.selectColumns.length > 0) {
|
|
114
|
+
selectParts.push(...config.selectColumns);
|
|
115
|
+
}
|
|
116
|
+
const selectClause = selectParts.join(", ");
|
|
117
|
+
let query = `SELECT ${selectClause} FROM ${config.table}`;
|
|
118
|
+
const params = [];
|
|
119
|
+
if (ids && ids.length > 0) {
|
|
120
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
121
|
+
query += ` WHERE ${config.idColumn} IN (${placeholders})`;
|
|
122
|
+
params.push(...ids);
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
query,
|
|
126
|
+
params
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function watchConfigToSourceConfig(watchConfig) {
|
|
130
|
+
return {
|
|
131
|
+
table: watchConfig.table,
|
|
132
|
+
idColumn: watchConfig.idColumn,
|
|
133
|
+
orderByColumn: watchConfig.orderBy?.column ?? null
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// src/attachments/migration.ts
|
|
138
|
+
import { AttachmentState } from "@powersync/attachments";
|
|
139
|
+
var STATE_MAPPING = /* @__PURE__ */ new Map([
|
|
140
|
+
// Official states (1:1 mapping)
|
|
141
|
+
[AttachmentState.QUEUED_SYNC, 0 /* QUEUED_SYNC */],
|
|
142
|
+
[AttachmentState.QUEUED_UPLOAD, 1 /* QUEUED_UPLOAD */],
|
|
143
|
+
[AttachmentState.QUEUED_DOWNLOAD, 2 /* QUEUED_DOWNLOAD */],
|
|
144
|
+
[AttachmentState.SYNCED, 3 /* SYNCED */],
|
|
145
|
+
[AttachmentState.ARCHIVED, 4 /* ARCHIVED */],
|
|
146
|
+
// POL extension states (identity mapping)
|
|
147
|
+
[5 /* FAILED_PERMANENT */, 5 /* FAILED_PERMANENT */],
|
|
148
|
+
[6 /* DOWNLOAD_SKIPPED */, 6 /* DOWNLOAD_SKIPPED */]
|
|
149
|
+
]);
|
|
150
|
+
var STATE_NAMES = /* @__PURE__ */ new Map([[0 /* QUEUED_SYNC */, "QUEUED_SYNC"], [1 /* QUEUED_UPLOAD */, "QUEUED_UPLOAD"], [2 /* QUEUED_DOWNLOAD */, "QUEUED_DOWNLOAD"], [3 /* SYNCED */, "SYNCED"], [4 /* ARCHIVED */, "ARCHIVED"], [5 /* FAILED_PERMANENT */, "FAILED_PERMANENT"], [6 /* DOWNLOAD_SKIPPED */, "DOWNLOAD_SKIPPED"]]);
|
|
151
|
+
var VALID_STATES = /* @__PURE__ */ new Set([0 /* QUEUED_SYNC */, 1 /* QUEUED_UPLOAD */, 2 /* QUEUED_DOWNLOAD */, 3 /* SYNCED */, 4 /* ARCHIVED */, 5 /* FAILED_PERMANENT */, 6 /* DOWNLOAD_SKIPPED */]);
|
|
152
|
+
var UPLOAD_WORKFLOW_STATES = /* @__PURE__ */ new Set([1 /* QUEUED_UPLOAD */, 5 /* FAILED_PERMANENT */]);
|
|
153
|
+
var DOWNLOAD_WORKFLOW_STATES = /* @__PURE__ */ new Set([2 /* QUEUED_DOWNLOAD */, 0 /* QUEUED_SYNC */]);
|
|
154
|
+
var TERMINAL_STATES = /* @__PURE__ */ new Set([3 /* SYNCED */, 4 /* ARCHIVED */, 6 /* DOWNLOAD_SKIPPED */]);
|
|
155
|
+
function migrateAttachmentState(oldState) {
|
|
156
|
+
const newState = STATE_MAPPING.get(oldState);
|
|
157
|
+
if (newState === void 0) {
|
|
158
|
+
throw new Error(`Invalid attachment state: ${oldState}. Valid states are: ${Array.from(STATE_NAMES.entries()).map(([v, n]) => `${n}(${v})`).join(", ")}`);
|
|
159
|
+
}
|
|
160
|
+
return newState;
|
|
161
|
+
}
|
|
162
|
+
function migrateAttachmentStateSafe(oldState, fallback = 0 /* QUEUED_SYNC */) {
|
|
163
|
+
const newState = STATE_MAPPING.get(oldState);
|
|
164
|
+
return newState !== void 0 ? newState : fallback;
|
|
165
|
+
}
|
|
166
|
+
function isValidAttachmentState(value) {
|
|
167
|
+
return typeof value === "number" && VALID_STATES.has(value);
|
|
168
|
+
}
|
|
169
|
+
function isUploadWorkflowState(state) {
|
|
170
|
+
return UPLOAD_WORKFLOW_STATES.has(state);
|
|
171
|
+
}
|
|
172
|
+
function isDownloadWorkflowState(state) {
|
|
173
|
+
return DOWNLOAD_WORKFLOW_STATES.has(state);
|
|
174
|
+
}
|
|
175
|
+
function isTerminalState(state) {
|
|
176
|
+
return TERMINAL_STATES.has(state);
|
|
177
|
+
}
|
|
178
|
+
function getStateName(state) {
|
|
179
|
+
return STATE_NAMES.get(state) ?? "UNKNOWN";
|
|
180
|
+
}
|
|
181
|
+
function createMigrationStats() {
|
|
182
|
+
return {
|
|
183
|
+
total: 0,
|
|
184
|
+
migrated: 0,
|
|
185
|
+
invalid: 0,
|
|
186
|
+
byState: /* @__PURE__ */ new Map()
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
function recordMigration(stats, oldState, newState, wasValid) {
|
|
190
|
+
stats.total++;
|
|
191
|
+
if (wasValid) {
|
|
192
|
+
stats.migrated++;
|
|
193
|
+
} else {
|
|
194
|
+
stats.invalid++;
|
|
195
|
+
}
|
|
196
|
+
stats.byState.set(newState, (stats.byState.get(newState) ?? 0) + 1);
|
|
197
|
+
}
|
|
198
|
+
function formatMigrationStats(stats) {
|
|
199
|
+
const lines = ["Migration Summary:", ` Total: ${stats.total}`, ` Migrated: ${stats.migrated}`, ` Invalid (used fallback): ${stats.invalid}`, " By State:"];
|
|
200
|
+
for (const [state, count] of stats.byState.entries()) {
|
|
201
|
+
lines.push(` ${getStateName(state)}: ${count}`);
|
|
202
|
+
}
|
|
203
|
+
return lines.join("\n");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export {
|
|
207
|
+
validateSqlIdentifier,
|
|
208
|
+
validateWhereClause,
|
|
209
|
+
buildWatchQuery,
|
|
210
|
+
buildIdOnlyWatchQuery,
|
|
211
|
+
buildRecordFetchQuery,
|
|
212
|
+
watchConfigToSourceConfig,
|
|
213
|
+
STATE_MAPPING,
|
|
214
|
+
STATE_NAMES,
|
|
215
|
+
VALID_STATES,
|
|
216
|
+
UPLOAD_WORKFLOW_STATES,
|
|
217
|
+
DOWNLOAD_WORKFLOW_STATES,
|
|
218
|
+
TERMINAL_STATES,
|
|
219
|
+
migrateAttachmentState,
|
|
220
|
+
migrateAttachmentStateSafe,
|
|
221
|
+
isValidAttachmentState,
|
|
222
|
+
isUploadWorkflowState,
|
|
223
|
+
isDownloadWorkflowState,
|
|
224
|
+
isTerminalState,
|
|
225
|
+
getStateName,
|
|
226
|
+
createMigrationStats,
|
|
227
|
+
recordMigration,
|
|
228
|
+
formatMigrationStats
|
|
229
|
+
};
|
|
230
|
+
//# sourceMappingURL=chunk-ZM4ENYMF.js.map
|