local-lite 0.0.21 → 0.0.22

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.
@@ -1,6 +1,7 @@
1
1
  import { RemoteDBAdapter } from './index.js';
2
2
  import { DeltaChanges, SyncOperation, PushResult } from '../../core/types.js';
3
3
  import { SyncLogger, CustomLogger, LogLevel } from '../../utils/logger.js';
4
+ import { createConvexStorageUploader, ConvexStorageUploaderOptions, ConvexStorageUploaderInstance } from '../../storage/convex.js';
4
5
  export interface ConvexRemoteAdapterOptions {
5
6
  /**
6
7
  * Tables synchronized with Convex.
@@ -68,4 +69,10 @@ export declare class ConvexRemoteAdapter implements RemoteDBAdapter {
68
69
  * Cleans up live subscriptions.
69
70
  */
70
71
  destroy(): void;
72
+ /**
73
+ * Helper to create a remote storage uploader targeting Convex File Storage.
74
+ * Connects local-lite offline blob/file storage directly to Convex File Storage.
75
+ */
76
+ createStorageUploader(options?: ConvexStorageUploaderOptions): ConvexStorageUploaderInstance;
71
77
  }
78
+ export { createConvexStorageUploader, type ConvexStorageUploaderOptions, type ConvexStorageUploaderInstance, };
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ConvexRemoteAdapter = void 0;
3
+ exports.createConvexStorageUploader = exports.ConvexRemoteAdapter = void 0;
4
4
  const logger_js_1 = require("../../utils/logger.js");
5
+ const convex_js_1 = require("../../storage/convex.js");
6
+ Object.defineProperty(exports, "createConvexStorageUploader", { enumerable: true, get: function () { return convex_js_1.createConvexStorageUploader; } });
5
7
  class ConvexRemoteAdapter {
6
8
  client;
7
9
  tables;
@@ -177,5 +179,12 @@ class ConvexRemoteAdapter {
177
179
  this.unsubscribeLive = undefined;
178
180
  }
179
181
  }
182
+ /**
183
+ * Helper to create a remote storage uploader targeting Convex File Storage.
184
+ * Connects local-lite offline blob/file storage directly to Convex File Storage.
185
+ */
186
+ createStorageUploader(options = {}) {
187
+ return (0, convex_js_1.createConvexStorageUploader)(this.client, options);
188
+ }
180
189
  }
181
190
  exports.ConvexRemoteAdapter = ConvexRemoteAdapter;
@@ -1,6 +1,7 @@
1
1
  import { RemoteDBAdapter } from './index.js';
2
2
  import { DeltaChanges, SyncOperation, PushResult } from '../../core/types.js';
3
3
  import { SyncLogger, CustomLogger, LogLevel } from '../../utils/logger.js';
4
+ import { createFirebaseStorageUploader, FirebaseStorageUploaderOptions, FirebaseStorageUploaderInstance } from '../../storage/firebase.js';
4
5
  export interface FirestoreAdapterOptions {
5
6
  /**
6
7
  * Collections (tables) synchronized with Firestore.
@@ -62,4 +63,10 @@ export declare class FirestoreRemoteAdapter implements RemoteDBAdapter {
62
63
  * Cleans up all active real-time listeners.
63
64
  */
64
65
  destroy(): void;
66
+ /**
67
+ * Helper to create a remote storage uploader targeting Firebase Cloud Storage.
68
+ * Connects local-lite offline blob/file storage directly to Firebase Storage.
69
+ */
70
+ createStorageUploader(storageOrApp?: any, options?: FirebaseStorageUploaderOptions): FirebaseStorageUploaderInstance;
65
71
  }
72
+ export { FirestoreRemoteAdapter as FirestoreAdapter, createFirebaseStorageUploader, type FirebaseStorageUploaderOptions, type FirebaseStorageUploaderInstance, };
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.FirestoreRemoteAdapter = void 0;
3
+ exports.createFirebaseStorageUploader = exports.FirestoreAdapter = exports.FirestoreRemoteAdapter = void 0;
4
4
  const logger_js_1 = require("../../utils/logger.js");
5
+ const firebase_js_1 = require("../../storage/firebase.js");
6
+ Object.defineProperty(exports, "createFirebaseStorageUploader", { enumerable: true, get: function () { return firebase_js_1.createFirebaseStorageUploader; } });
5
7
  /**
6
8
  * Safely extracts epoch milliseconds from numbers, Firestore Timestamps, or ISO strings.
7
9
  */
@@ -328,5 +330,25 @@ class FirestoreRemoteAdapter {
328
330
  }
329
331
  this.unsubscribers = [];
330
332
  }
333
+ /**
334
+ * Helper to create a remote storage uploader targeting Firebase Cloud Storage.
335
+ * Connects local-lite offline blob/file storage directly to Firebase Storage.
336
+ */
337
+ createStorageUploader(storageOrApp, options = {}) {
338
+ let client = storageOrApp;
339
+ let opts = options;
340
+ if (storageOrApp &&
341
+ !storageOrApp.ref &&
342
+ !storageOrApp.storage &&
343
+ !storageOrApp.app &&
344
+ typeof storageOrApp === 'object' &&
345
+ ('bucket' in storageOrApp || 'storageModule' in storageOrApp || 'basePath' in storageOrApp)) {
346
+ opts = storageOrApp;
347
+ client = this.db.app || this.db;
348
+ }
349
+ const resolvedClient = client || this.db.app || this.db;
350
+ return (0, firebase_js_1.createFirebaseStorageUploader)(resolvedClient, opts);
351
+ }
331
352
  }
332
353
  exports.FirestoreRemoteAdapter = FirestoreRemoteAdapter;
354
+ exports.FirestoreAdapter = FirestoreRemoteAdapter;
@@ -2,6 +2,7 @@ import type { SupabaseClient } from '@supabase/supabase-js';
2
2
  import { RemoteDBAdapter } from './index.js';
3
3
  import { DeltaChanges, PushResult, SyncOperation } from '../../core/types.js';
4
4
  import { SyncLogger, CustomLogger, LogLevel } from '../../utils/logger.js';
5
+ import { createSupabaseStorageUploader, type SupabaseStorageUploaderOptions, type SupabaseStorageUploaderInstance } from '../../storage/supabase.js';
5
6
  export interface SupabaseAdapterOptions {
6
7
  /**
7
8
  * The list of Postgres tables to sync.
@@ -123,4 +124,10 @@ export declare class SupabaseRemoteAdapter implements RemoteDBAdapter {
123
124
  * Clean up active realtime channels.
124
125
  */
125
126
  destroy(): void;
127
+ /**
128
+ * Helper to create a remote storage uploader targeting Supabase Storage buckets.
129
+ * Connects local-lite offline blob/file storage directly to Supabase Storage.
130
+ */
131
+ createStorageUploader(options: SupabaseStorageUploaderOptions): SupabaseStorageUploaderInstance;
126
132
  }
133
+ export { SupabaseRemoteAdapter as SupabaseAdapter, createSupabaseStorageUploader, type SupabaseStorageUploaderOptions, type SupabaseStorageUploaderInstance, };
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SupabaseRemoteAdapter = void 0;
3
+ exports.createSupabaseStorageUploader = exports.SupabaseAdapter = exports.SupabaseRemoteAdapter = void 0;
4
4
  const logger_js_1 = require("../../utils/logger.js");
5
+ const supabase_js_1 = require("../../storage/supabase.js");
6
+ Object.defineProperty(exports, "createSupabaseStorageUploader", { enumerable: true, get: function () { return supabase_js_1.createSupabaseStorageUploader; } });
5
7
  class SupabaseRemoteAdapter {
6
8
  clientResolver;
7
9
  activeLiveCallback;
@@ -25,7 +27,8 @@ class SupabaseRemoteAdapter {
25
27
  typeof clientOrOptions === 'object' &&
26
28
  !('channel' in clientOrOptions) &&
27
29
  !('from' in clientOrOptions) &&
28
- !('auth' in clientOrOptions)) {
30
+ !('auth' in clientOrOptions) &&
31
+ !('storage' in clientOrOptions)) {
29
32
  client = null;
30
33
  options = clientOrOptions;
31
34
  }
@@ -640,5 +643,13 @@ class SupabaseRemoteAdapter {
640
643
  this.activeChannel = undefined;
641
644
  }
642
645
  }
646
+ /**
647
+ * Helper to create a remote storage uploader targeting Supabase Storage buckets.
648
+ * Connects local-lite offline blob/file storage directly to Supabase Storage.
649
+ */
650
+ createStorageUploader(options) {
651
+ return (0, supabase_js_1.createSupabaseStorageUploader)(this.getClient(), options);
652
+ }
643
653
  }
644
654
  exports.SupabaseRemoteAdapter = SupabaseRemoteAdapter;
655
+ exports.SupabaseAdapter = SupabaseRemoteAdapter;
@@ -219,9 +219,21 @@ class LocalFirstClient {
219
219
  this.keys = new index_js_3.ApiKeyManager(this.db, config.keys);
220
220
  this.kv = new index_js_4.KVManager(this.db, this.events, config.kv);
221
221
  this.search = new index_js_5.SearchManager(this.db, this.events, (table, id) => this.findById(table, id));
222
+ let storageUploader = config.storage?.uploader;
223
+ if (!storageUploader && config.storage) {
224
+ if (typeof this.remote?.createStorageUploader === 'function' &&
225
+ (config.storage.autoRemote !== false &&
226
+ (config.storage.autoRemote || config.storage.bucket || config.storage.uploadMutation))) {
227
+ try {
228
+ storageUploader = this.remote.createStorageUploader(config.storage);
229
+ }
230
+ catch { }
231
+ }
232
+ }
222
233
  this.storage = new index_js_6.BlobStorageManager(this.db, {
223
234
  network: config.network,
224
235
  ...config.storage,
236
+ uploader: storageUploader,
225
237
  });
226
238
  this.scheduler = new index_js_7.JobScheduler(this.db, config.scheduler);
227
239
  this.session = new session_js_1.SessionManager(this.db, this.events, {
@@ -0,0 +1,43 @@
1
+ import { BlobRecord, RemoteStorageUploader } from './types.js';
2
+ export interface ConvexStorageUploaderOptions {
3
+ /**
4
+ * Name or function reference for Convex mutation that generates an upload URL.
5
+ * Default: 'sync:generateUploadUrl' (or can be `api.files.generateUploadUrl`)
6
+ */
7
+ uploadMutation?: any;
8
+ /**
9
+ * Optional arguments passed to uploadMutation.
10
+ */
11
+ uploadMutationArgs?: Record<string, any> | ((blob: BlobRecord) => Record<string, any>);
12
+ /**
13
+ * Optional name or function reference for Convex query that gets a serving URL from a storageId.
14
+ * e.g. `api.files.getUrl`
15
+ */
16
+ getUrlQuery?: any;
17
+ /**
18
+ * Optional name or function reference for Convex mutation called after file is uploaded,
19
+ * allowing you to persist metadata or link the storageId to your Convex schema.
20
+ */
21
+ saveFileMutation?: any;
22
+ /**
23
+ * Optional name or function reference for Convex mutation called when a file is deleted.
24
+ */
25
+ deleteMutation?: any;
26
+ /**
27
+ * Custom path / metadata transformation.
28
+ */
29
+ pathTransform?: (key: string, blob?: BlobRecord) => string;
30
+ /**
31
+ * Custom fetch function (defaults to global fetch).
32
+ */
33
+ fetchFn?: typeof fetch;
34
+ }
35
+ export interface ConvexStorageUploaderInstance extends RemoteStorageUploader {
36
+ getUrl(storageId: string): Promise<string | null>;
37
+ getDownloadUrl(keyOrStorageId: string): Promise<string>;
38
+ delete(storageIdOrKey: string): Promise<void>;
39
+ }
40
+ /**
41
+ * Creates a RemoteStorageUploader that syncs binary files to Convex File Storage.
42
+ */
43
+ export declare function createConvexStorageUploader(convex: any, options?: ConvexStorageUploaderOptions): ConvexStorageUploaderInstance;
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createConvexStorageUploader = createConvexStorageUploader;
4
+ /**
5
+ * Creates a RemoteStorageUploader that syncs binary files to Convex File Storage.
6
+ */
7
+ function createConvexStorageUploader(convex, options = {}) {
8
+ const uploadMutation = options.uploadMutation ?? 'sync:generateUploadUrl';
9
+ const fetchFn = options.fetchFn ?? (typeof fetch !== 'undefined' ? fetch : undefined);
10
+ if (!convex) {
11
+ throw new Error('Convex client must be provided to createConvexStorageUploader.');
12
+ }
13
+ // Cache of key/storageId mapping
14
+ const storageIdMap = new Map();
15
+ return {
16
+ async upload(blob, data) {
17
+ if (!fetchFn) {
18
+ throw new Error('Fetch API is not available in the current environment.');
19
+ }
20
+ // 1. Generate upload URL via mutation
21
+ const mutationArgs = typeof options.uploadMutationArgs === 'function'
22
+ ? options.uploadMutationArgs(blob)
23
+ : options.uploadMutationArgs || {};
24
+ let postUrl;
25
+ if (typeof convex.mutation === 'function') {
26
+ postUrl = await convex.mutation(uploadMutation, mutationArgs);
27
+ }
28
+ else if (typeof convex === 'function') {
29
+ postUrl = await convex(uploadMutation, mutationArgs);
30
+ }
31
+ else {
32
+ throw new Error('Convex client must support .mutation() method.');
33
+ }
34
+ if (!postUrl || typeof postUrl !== 'string') {
35
+ throw new Error(`Convex generateUploadUrl mutation did not return a valid URL: ${JSON.stringify(postUrl)}`);
36
+ }
37
+ // 2. POST binary data to upload URL
38
+ const response = await fetchFn(postUrl, {
39
+ method: 'POST',
40
+ headers: {
41
+ 'Content-Type': blob.mimeType || 'application/octet-stream',
42
+ },
43
+ body: data,
44
+ });
45
+ if (!response.ok) {
46
+ throw new Error(`Convex file upload failed with status ${response.status}: ${response.statusText}`);
47
+ }
48
+ const jsonResult = await response.json();
49
+ const storageId = jsonResult?.storageId || jsonResult?.id || jsonResult;
50
+ if (!storageId) {
51
+ throw new Error('Convex upload response did not contain a valid storageId.');
52
+ }
53
+ storageIdMap.set(blob.key, storageId);
54
+ // 3. Optional: save file record in Convex schema
55
+ if (options.saveFileMutation) {
56
+ const filePayload = {
57
+ storageId,
58
+ key: blob.key,
59
+ name: blob.name,
60
+ mimeType: blob.mimeType,
61
+ size: blob.size,
62
+ sha256: blob.sha256,
63
+ metadata: blob.metadata,
64
+ };
65
+ if (typeof convex.mutation === 'function') {
66
+ await convex.mutation(options.saveFileMutation, filePayload);
67
+ }
68
+ }
69
+ // 4. Resolve serving URL if query is provided
70
+ let remoteUrl = storageId;
71
+ if (options.getUrlQuery) {
72
+ const resolved = await this.getUrl(storageId);
73
+ if (resolved) {
74
+ remoteUrl = resolved;
75
+ }
76
+ }
77
+ return {
78
+ remoteUrl,
79
+ storageId,
80
+ metadata: { storageId },
81
+ };
82
+ },
83
+ async getUrl(storageId) {
84
+ if (!options.getUrlQuery) {
85
+ return null;
86
+ }
87
+ try {
88
+ if (typeof convex.query === 'function') {
89
+ return await convex.query(options.getUrlQuery, { storageId });
90
+ }
91
+ return null;
92
+ }
93
+ catch {
94
+ return null;
95
+ }
96
+ },
97
+ async getDownloadUrl(keyOrStorageId) {
98
+ const storageId = storageIdMap.get(keyOrStorageId) || keyOrStorageId;
99
+ const url = await this.getUrl(storageId);
100
+ return url || storageId;
101
+ },
102
+ async delete(storageIdOrKey) {
103
+ const storageId = storageIdMap.get(storageIdOrKey) || storageIdOrKey;
104
+ storageIdMap.delete(storageIdOrKey);
105
+ if (options.deleteMutation) {
106
+ if (typeof convex.mutation === 'function') {
107
+ await convex.mutation(options.deleteMutation, { storageId });
108
+ }
109
+ }
110
+ },
111
+ };
112
+ }
@@ -0,0 +1,132 @@
1
+ import { FileDriver } from './types.js';
2
+ export interface ExpoFileSystemModule {
3
+ File?: any;
4
+ Directory?: any;
5
+ Paths?: any;
6
+ documentDirectory?: string | null;
7
+ cacheDirectory?: string | null;
8
+ EncodingType?: {
9
+ UTF8: string;
10
+ Base64: string;
11
+ };
12
+ getInfoAsync?(fileUri: string, options?: {
13
+ md5?: boolean;
14
+ size?: boolean;
15
+ }): Promise<{
16
+ exists: boolean;
17
+ isDirectory?: boolean;
18
+ size?: number;
19
+ uri?: string;
20
+ }>;
21
+ writeAsStringAsync?(fileUri: string, contents: string, options?: {
22
+ encoding?: string;
23
+ }): Promise<void>;
24
+ readAsStringAsync?(fileUri: string, options?: {
25
+ encoding?: string;
26
+ length?: number;
27
+ position?: number;
28
+ }): Promise<string>;
29
+ makeDirectoryAsync?(fileUri: string, options?: {
30
+ intermediates?: boolean;
31
+ }): Promise<void>;
32
+ deleteAsync?(fileUri: string, options?: {
33
+ idempotent?: boolean;
34
+ }): Promise<void>;
35
+ copyAsync?(options: {
36
+ from: string;
37
+ to: string;
38
+ }): Promise<void>;
39
+ moveAsync?(options: {
40
+ from: string;
41
+ to: string;
42
+ }): Promise<void>;
43
+ downloadAsync?(url: string, fileUri: string, options?: {
44
+ md5?: boolean;
45
+ headers?: Record<string, string>;
46
+ sessionType?: any;
47
+ }): Promise<{
48
+ uri: string;
49
+ status: number;
50
+ headers: Record<string, string>;
51
+ md5?: string;
52
+ }>;
53
+ }
54
+ export interface ExpoFileDriverOptions {
55
+ /**
56
+ * The modern `File` class from `expo-file-system` (SDK >57/58).
57
+ * e.g. `import { File } from 'expo-file-system'`
58
+ */
59
+ File?: any;
60
+ /**
61
+ * The modern `Directory` class from `expo-file-system`.
62
+ * e.g. `import { Directory } from 'expo-file-system'`
63
+ */
64
+ Directory?: any;
65
+ /**
66
+ * The modern `Paths` object from `expo-file-system`.
67
+ * e.g. `import { Paths } from 'expo-file-system'`
68
+ */
69
+ Paths?: any;
70
+ /**
71
+ * The `expo-file-system` module instance.
72
+ * e.g. `import * as FileSystem from 'expo-file-system'`
73
+ */
74
+ fileSystem?: ExpoFileSystemModule | any;
75
+ /**
76
+ * Base directory path or `Directory` instance for storing files.
77
+ * Defaults to: `new Directory(Paths.document, 'local-lite-blobs')`
78
+ */
79
+ baseDir?: any;
80
+ /**
81
+ * Whether to store files in `Paths.cache` instead of `Paths.document`.
82
+ * Files in cache directory may be cleared by the OS if device storage runs low.
83
+ * Default: false
84
+ */
85
+ useCache?: boolean;
86
+ }
87
+ /**
88
+ * Universal binary <-> base64 utilities compatible with Node, Browser, and React Native (Hermes).
89
+ */
90
+ export declare function bytesToBase64(bytes: Uint8Array): string;
91
+ export declare function base64ToBytes(base64: string): Uint8Array;
92
+ /**
93
+ * FileDriver implementation for Expo / React Native environments.
94
+ * Uses modern Expo (>57/58) `File`, `Directory`, and `Paths` APIs when available,
95
+ * with seamless fallback for legacy `expo-file-system` environments.
96
+ */
97
+ export declare class ExpoFileDriver implements FileDriver {
98
+ private FileClass?;
99
+ private DirectoryClass?;
100
+ private PathsObj?;
101
+ private legacyFs?;
102
+ private baseDirOption?;
103
+ private useCache;
104
+ private resolvedBaseDir;
105
+ private resolvedBaseDirPath;
106
+ private ensuredDirs;
107
+ constructor(options?: ExpoFileDriverOptions);
108
+ private resolveClasses;
109
+ private getBaseDirectory;
110
+ /**
111
+ * Resolves a key to a modern Expo `File` instance (if File class is available)
112
+ * or a string URI path.
113
+ */
114
+ private resolveFileOrUri;
115
+ resolveUri(key: string): Promise<string>;
116
+ getUri(key: string): Promise<string>;
117
+ private ensureDirectory;
118
+ write(key: string, data: Uint8Array): Promise<string>;
119
+ read(key: string): Promise<Uint8Array | null>;
120
+ delete(key: string): Promise<void>;
121
+ exists(key: string): Promise<boolean>;
122
+ getSize(key: string): Promise<number>;
123
+ /**
124
+ * Fast file copy from an existing local URI (e.g. from expo-image-picker or expo-document-picker)
125
+ * into local-lite managed storage without reading entire file contents into memory.
126
+ */
127
+ copyFrom(sourceUri: string, key: string): Promise<string>;
128
+ /**
129
+ * Downloads a remote URL directly to the destination key on disk.
130
+ */
131
+ downloadFrom(remoteUrl: string, key: string): Promise<string>;
132
+ }