@powersync/common 1.46.0 → 1.48.0

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.
Files changed (71) hide show
  1. package/README.md +5 -1
  2. package/dist/bundle.cjs +1298 -395
  3. package/dist/bundle.cjs.map +1 -1
  4. package/dist/bundle.mjs +1291 -395
  5. package/dist/bundle.mjs.map +1 -1
  6. package/dist/bundle.node.cjs +1298 -395
  7. package/dist/bundle.node.cjs.map +1 -1
  8. package/dist/bundle.node.mjs +1291 -395
  9. package/dist/bundle.node.mjs.map +1 -1
  10. package/dist/index.d.cts +652 -106
  11. package/lib/attachments/AttachmentContext.d.ts +86 -0
  12. package/lib/attachments/AttachmentContext.js +229 -0
  13. package/lib/attachments/AttachmentContext.js.map +1 -0
  14. package/lib/attachments/AttachmentErrorHandler.d.ts +31 -0
  15. package/lib/attachments/AttachmentErrorHandler.js +2 -0
  16. package/lib/attachments/AttachmentErrorHandler.js.map +1 -0
  17. package/lib/attachments/AttachmentQueue.d.ts +149 -0
  18. package/lib/attachments/AttachmentQueue.js +362 -0
  19. package/lib/attachments/AttachmentQueue.js.map +1 -0
  20. package/lib/attachments/AttachmentService.d.ts +29 -0
  21. package/lib/attachments/AttachmentService.js +56 -0
  22. package/lib/attachments/AttachmentService.js.map +1 -0
  23. package/lib/attachments/LocalStorageAdapter.d.ts +62 -0
  24. package/lib/attachments/LocalStorageAdapter.js +6 -0
  25. package/lib/attachments/LocalStorageAdapter.js.map +1 -0
  26. package/lib/attachments/RemoteStorageAdapter.d.ts +27 -0
  27. package/lib/attachments/RemoteStorageAdapter.js +2 -0
  28. package/lib/attachments/RemoteStorageAdapter.js.map +1 -0
  29. package/lib/attachments/Schema.d.ts +50 -0
  30. package/lib/attachments/Schema.js +62 -0
  31. package/lib/attachments/Schema.js.map +1 -0
  32. package/lib/attachments/SyncingService.d.ts +62 -0
  33. package/lib/attachments/SyncingService.js +168 -0
  34. package/lib/attachments/SyncingService.js.map +1 -0
  35. package/lib/attachments/WatchedAttachmentItem.d.ts +17 -0
  36. package/lib/attachments/WatchedAttachmentItem.js +2 -0
  37. package/lib/attachments/WatchedAttachmentItem.js.map +1 -0
  38. package/lib/db/schema/RawTable.d.ts +61 -26
  39. package/lib/db/schema/RawTable.js +1 -32
  40. package/lib/db/schema/RawTable.js.map +1 -1
  41. package/lib/db/schema/Schema.d.ts +14 -7
  42. package/lib/db/schema/Schema.js +25 -3
  43. package/lib/db/schema/Schema.js.map +1 -1
  44. package/lib/db/schema/Table.d.ts +13 -8
  45. package/lib/db/schema/Table.js +3 -8
  46. package/lib/db/schema/Table.js.map +1 -1
  47. package/lib/db/schema/internal.d.ts +12 -0
  48. package/lib/db/schema/internal.js +15 -0
  49. package/lib/db/schema/internal.js.map +1 -0
  50. package/lib/index.d.ts +11 -1
  51. package/lib/index.js +10 -1
  52. package/lib/index.js.map +1 -1
  53. package/lib/utils/mutex.d.ts +1 -1
  54. package/lib/utils/mutex.js.map +1 -1
  55. package/package.json +1 -1
  56. package/src/attachments/AttachmentContext.ts +279 -0
  57. package/src/attachments/AttachmentErrorHandler.ts +34 -0
  58. package/src/attachments/AttachmentQueue.ts +472 -0
  59. package/src/attachments/AttachmentService.ts +62 -0
  60. package/src/attachments/LocalStorageAdapter.ts +72 -0
  61. package/src/attachments/README.md +718 -0
  62. package/src/attachments/RemoteStorageAdapter.ts +30 -0
  63. package/src/attachments/Schema.ts +87 -0
  64. package/src/attachments/SyncingService.ts +193 -0
  65. package/src/attachments/WatchedAttachmentItem.ts +19 -0
  66. package/src/db/schema/RawTable.ts +66 -31
  67. package/src/db/schema/Schema.ts +27 -2
  68. package/src/db/schema/Table.ts +11 -11
  69. package/src/db/schema/internal.ts +17 -0
  70. package/src/index.ts +12 -1
  71. package/src/utils/mutex.ts +1 -1
@@ -0,0 +1,86 @@
1
+ import { AbstractPowerSyncDatabase } from '../client/AbstractPowerSyncDatabase.js';
2
+ import { ILogger } from '../utils/Logger.js';
3
+ import { Transaction } from '../db/DBAdapter.js';
4
+ import { AttachmentRecord } from './Schema.js';
5
+ /**
6
+ * AttachmentContext provides database operations for managing attachment records.
7
+ *
8
+ * Provides methods to query, insert, update, and delete attachment records with
9
+ * proper transaction management through PowerSync.
10
+ *
11
+ * @internal
12
+ */
13
+ export declare class AttachmentContext {
14
+ /** PowerSync database instance for executing queries */
15
+ db: AbstractPowerSyncDatabase;
16
+ /** Name of the database table storing attachment records */
17
+ tableName: string;
18
+ /** Logger instance for diagnostic information */
19
+ logger: ILogger;
20
+ /** Maximum number of archived attachments to keep before cleanup */
21
+ archivedCacheLimit: number;
22
+ /**
23
+ * Creates a new AttachmentContext instance.
24
+ *
25
+ * @param db - PowerSync database instance
26
+ * @param tableName - Name of the table storing attachment records. Default: 'attachments'
27
+ * @param logger - Logger instance for diagnostic output
28
+ */
29
+ constructor(db: AbstractPowerSyncDatabase, tableName: string | undefined, logger: ILogger, archivedCacheLimit: number);
30
+ /**
31
+ * Retrieves all active attachments that require synchronization.
32
+ * Active attachments include those queued for upload, download, or delete.
33
+ * Results are ordered by timestamp in ascending order.
34
+ *
35
+ * @returns Promise resolving to an array of active attachment records
36
+ */
37
+ getActiveAttachments(): Promise<AttachmentRecord[]>;
38
+ /**
39
+ * Retrieves all archived attachments.
40
+ *
41
+ * Archived attachments are no longer referenced but haven't been permanently deleted.
42
+ * These are candidates for cleanup operations to free up storage space.
43
+ *
44
+ * @returns Promise resolving to an array of archived attachment records
45
+ */
46
+ getArchivedAttachments(): Promise<AttachmentRecord[]>;
47
+ /**
48
+ * Retrieves all attachment records regardless of state.
49
+ * Results are ordered by timestamp in ascending order.
50
+ *
51
+ * @returns Promise resolving to an array of all attachment records
52
+ */
53
+ getAttachments(): Promise<AttachmentRecord[]>;
54
+ /**
55
+ * Inserts or updates an attachment record within an existing transaction.
56
+ *
57
+ * Performs an upsert operation (INSERT OR REPLACE). Must be called within
58
+ * an active database transaction context.
59
+ *
60
+ * @param attachment - The attachment record to upsert
61
+ * @param context - Active database transaction context
62
+ */
63
+ upsertAttachment(attachment: AttachmentRecord, context: Transaction): Promise<void>;
64
+ getAttachment(id: string): Promise<AttachmentRecord | undefined>;
65
+ /**
66
+ * Permanently deletes an attachment record from the database.
67
+ *
68
+ * This operation removes the attachment record but does not delete
69
+ * the associated local or remote files. File deletion should be handled
70
+ * separately through the appropriate storage adapters.
71
+ *
72
+ * @param attachmentId - Unique identifier of the attachment to delete
73
+ */
74
+ deleteAttachment(attachmentId: string): Promise<void>;
75
+ clearQueue(): Promise<void>;
76
+ deleteArchivedAttachments(callback?: (attachments: AttachmentRecord[]) => Promise<void>): Promise<boolean>;
77
+ /**
78
+ * Saves multiple attachment records in a single transaction.
79
+ *
80
+ * All updates are saved in a single batch after processing.
81
+ * If the attachments array is empty, no database operations are performed.
82
+ *
83
+ * @param attachments - Array of attachment records to save
84
+ */
85
+ saveAttachments(attachments: AttachmentRecord[]): Promise<void>;
86
+ }
@@ -0,0 +1,229 @@
1
+ import { AttachmentState, attachmentFromSql } from './Schema.js';
2
+ /**
3
+ * AttachmentContext provides database operations for managing attachment records.
4
+ *
5
+ * Provides methods to query, insert, update, and delete attachment records with
6
+ * proper transaction management through PowerSync.
7
+ *
8
+ * @internal
9
+ */
10
+ export class AttachmentContext {
11
+ /** PowerSync database instance for executing queries */
12
+ db;
13
+ /** Name of the database table storing attachment records */
14
+ tableName;
15
+ /** Logger instance for diagnostic information */
16
+ logger;
17
+ /** Maximum number of archived attachments to keep before cleanup */
18
+ archivedCacheLimit = 100;
19
+ /**
20
+ * Creates a new AttachmentContext instance.
21
+ *
22
+ * @param db - PowerSync database instance
23
+ * @param tableName - Name of the table storing attachment records. Default: 'attachments'
24
+ * @param logger - Logger instance for diagnostic output
25
+ */
26
+ constructor(db, tableName = 'attachments', logger, archivedCacheLimit) {
27
+ this.db = db;
28
+ this.tableName = tableName;
29
+ this.logger = logger;
30
+ this.archivedCacheLimit = archivedCacheLimit;
31
+ }
32
+ /**
33
+ * Retrieves all active attachments that require synchronization.
34
+ * Active attachments include those queued for upload, download, or delete.
35
+ * Results are ordered by timestamp in ascending order.
36
+ *
37
+ * @returns Promise resolving to an array of active attachment records
38
+ */
39
+ async getActiveAttachments() {
40
+ const attachments = await this.db.getAll(
41
+ /* sql */
42
+ `
43
+ SELECT
44
+ *
45
+ FROM
46
+ ${this.tableName}
47
+ WHERE
48
+ state = ?
49
+ OR state = ?
50
+ OR state = ?
51
+ ORDER BY
52
+ timestamp ASC
53
+ `, [AttachmentState.QUEUED_UPLOAD, AttachmentState.QUEUED_DOWNLOAD, AttachmentState.QUEUED_DELETE]);
54
+ return attachments.map(attachmentFromSql);
55
+ }
56
+ /**
57
+ * Retrieves all archived attachments.
58
+ *
59
+ * Archived attachments are no longer referenced but haven't been permanently deleted.
60
+ * These are candidates for cleanup operations to free up storage space.
61
+ *
62
+ * @returns Promise resolving to an array of archived attachment records
63
+ */
64
+ async getArchivedAttachments() {
65
+ const attachments = await this.db.getAll(
66
+ /* sql */
67
+ `
68
+ SELECT
69
+ *
70
+ FROM
71
+ ${this.tableName}
72
+ WHERE
73
+ state = ?
74
+ ORDER BY
75
+ timestamp ASC
76
+ `, [AttachmentState.ARCHIVED]);
77
+ return attachments.map(attachmentFromSql);
78
+ }
79
+ /**
80
+ * Retrieves all attachment records regardless of state.
81
+ * Results are ordered by timestamp in ascending order.
82
+ *
83
+ * @returns Promise resolving to an array of all attachment records
84
+ */
85
+ async getAttachments() {
86
+ const attachments = await this.db.getAll(
87
+ /* sql */
88
+ `
89
+ SELECT
90
+ *
91
+ FROM
92
+ ${this.tableName}
93
+ ORDER BY
94
+ timestamp ASC
95
+ `, []);
96
+ return attachments.map(attachmentFromSql);
97
+ }
98
+ /**
99
+ * Inserts or updates an attachment record within an existing transaction.
100
+ *
101
+ * Performs an upsert operation (INSERT OR REPLACE). Must be called within
102
+ * an active database transaction context.
103
+ *
104
+ * @param attachment - The attachment record to upsert
105
+ * @param context - Active database transaction context
106
+ */
107
+ async upsertAttachment(attachment, context) {
108
+ await context.execute(
109
+ /* sql */
110
+ `
111
+ INSERT
112
+ OR REPLACE INTO ${this.tableName} (
113
+ id,
114
+ filename,
115
+ local_uri,
116
+ size,
117
+ media_type,
118
+ timestamp,
119
+ state,
120
+ has_synced,
121
+ meta_data
122
+ )
123
+ VALUES
124
+ (?, ?, ?, ?, ?, ?, ?, ?, ?)
125
+ `, [
126
+ attachment.id,
127
+ attachment.filename,
128
+ attachment.localUri || null,
129
+ attachment.size || null,
130
+ attachment.mediaType || null,
131
+ attachment.timestamp,
132
+ attachment.state,
133
+ attachment.hasSynced ? 1 : 0,
134
+ attachment.metaData || null
135
+ ]);
136
+ }
137
+ async getAttachment(id) {
138
+ const attachment = await this.db.get(
139
+ /* sql */
140
+ `
141
+ SELECT
142
+ *
143
+ FROM
144
+ ${this.tableName}
145
+ WHERE
146
+ id = ?
147
+ `, [id]);
148
+ return attachment ? attachmentFromSql(attachment) : undefined;
149
+ }
150
+ /**
151
+ * Permanently deletes an attachment record from the database.
152
+ *
153
+ * This operation removes the attachment record but does not delete
154
+ * the associated local or remote files. File deletion should be handled
155
+ * separately through the appropriate storage adapters.
156
+ *
157
+ * @param attachmentId - Unique identifier of the attachment to delete
158
+ */
159
+ async deleteAttachment(attachmentId) {
160
+ await this.db.writeTransaction((tx) => tx.execute(
161
+ /* sql */
162
+ `
163
+ DELETE FROM ${this.tableName}
164
+ WHERE
165
+ id = ?
166
+ `, [attachmentId]));
167
+ }
168
+ async clearQueue() {
169
+ await this.db.writeTransaction((tx) => tx.execute(/* sql */ ` DELETE FROM ${this.tableName} `));
170
+ }
171
+ async deleteArchivedAttachments(callback) {
172
+ const limit = 1000;
173
+ const results = await this.db.getAll(
174
+ /* sql */
175
+ `
176
+ SELECT
177
+ *
178
+ FROM
179
+ ${this.tableName}
180
+ WHERE
181
+ state = ?
182
+ ORDER BY
183
+ timestamp DESC
184
+ LIMIT
185
+ ?
186
+ OFFSET
187
+ ?
188
+ `, [AttachmentState.ARCHIVED, limit, this.archivedCacheLimit]);
189
+ const archivedAttachments = results.map(attachmentFromSql);
190
+ if (archivedAttachments.length === 0)
191
+ return false;
192
+ await callback?.(archivedAttachments);
193
+ this.logger.info(`Deleting ${archivedAttachments.length} archived attachments. Archived attachment exceeds cache archiveCacheLimit of ${this.archivedCacheLimit}.`);
194
+ const ids = archivedAttachments.map((attachment) => attachment.id);
195
+ await this.db.execute(
196
+ /* sql */
197
+ `
198
+ DELETE FROM ${this.tableName}
199
+ WHERE
200
+ id IN (
201
+ SELECT
202
+ json_each.value
203
+ FROM
204
+ json_each (?)
205
+ );
206
+ `, [JSON.stringify(ids)]);
207
+ this.logger.info(`Deleted ${archivedAttachments.length} archived attachments`);
208
+ return archivedAttachments.length < limit;
209
+ }
210
+ /**
211
+ * Saves multiple attachment records in a single transaction.
212
+ *
213
+ * All updates are saved in a single batch after processing.
214
+ * If the attachments array is empty, no database operations are performed.
215
+ *
216
+ * @param attachments - Array of attachment records to save
217
+ */
218
+ async saveAttachments(attachments) {
219
+ if (attachments.length === 0) {
220
+ return;
221
+ }
222
+ await this.db.writeTransaction(async (tx) => {
223
+ for (const attachment of attachments) {
224
+ await this.upsertAttachment(attachment, tx);
225
+ }
226
+ });
227
+ }
228
+ }
229
+ //# sourceMappingURL=AttachmentContext.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AttachmentContext.js","sourceRoot":"","sources":["../../src/attachments/AttachmentContext.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import { AttachmentRecord } from './Schema.js';
2
+ /**
3
+ * SyncErrorHandler provides custom error handling for attachment sync operations.
4
+ * Implementations determine whether failed operations should be retried or archived.
5
+ *
6
+ * @experimental
7
+ * @alpha This is currently experimental and may change without a major version bump.
8
+ */
9
+ export interface AttachmentErrorHandler {
10
+ /**
11
+ * Handles a download error for a specific attachment.
12
+ * @param attachment The attachment that failed to download
13
+ * @param error The error encountered during the download
14
+ * @returns `true` to retry the operation, `false` to archive the attachment
15
+ */
16
+ onDownloadError(attachment: AttachmentRecord, error: unknown): Promise<boolean>;
17
+ /**
18
+ * Handles an upload error for a specific attachment.
19
+ * @param attachment The attachment that failed to upload
20
+ * @param error The error encountered during the upload
21
+ * @returns `true` to retry the operation, `false` to archive the attachment
22
+ */
23
+ onUploadError(attachment: AttachmentRecord, error: unknown): Promise<boolean>;
24
+ /**
25
+ * Handles a delete error for a specific attachment.
26
+ * @param attachment The attachment that failed to delete
27
+ * @param error The error encountered during the delete
28
+ * @returns `true` to retry the operation, `false` to archive the attachment
29
+ */
30
+ onDeleteError(attachment: AttachmentRecord, error: unknown): Promise<boolean>;
31
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=AttachmentErrorHandler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AttachmentErrorHandler.js","sourceRoot":"","sources":["../../src/attachments/AttachmentErrorHandler.ts"],"names":[],"mappings":""}
@@ -0,0 +1,149 @@
1
+ import { AbstractPowerSyncDatabase } from '../client/AbstractPowerSyncDatabase.js';
2
+ import { ILogger } from '../utils/Logger.js';
3
+ import { Transaction } from '../db/DBAdapter.js';
4
+ import { AttachmentData, LocalStorageAdapter } from './LocalStorageAdapter.js';
5
+ import { RemoteStorageAdapter } from './RemoteStorageAdapter.js';
6
+ import { AttachmentRecord } from './Schema.js';
7
+ import { WatchedAttachmentItem } from './WatchedAttachmentItem.js';
8
+ import { AttachmentErrorHandler } from './AttachmentErrorHandler.js';
9
+ /**
10
+ * AttachmentQueue manages the lifecycle and synchronization of attachments
11
+ * between local and remote storage.
12
+ * Provides automatic synchronization, upload/download queuing, attachment monitoring,
13
+ * verification and repair of local files, and cleanup of archived attachments.
14
+ *
15
+ * @experimental
16
+ * @alpha This is currently experimental and may change without a major version bump.
17
+ */
18
+ export declare class AttachmentQueue {
19
+ /** Timer for periodic synchronization operations */
20
+ private periodicSyncTimer?;
21
+ /** Service for synchronizing attachments between local and remote storage */
22
+ private readonly syncingService;
23
+ /** Adapter for local file storage operations */
24
+ readonly localStorage: LocalStorageAdapter;
25
+ /** Adapter for remote file storage operations */
26
+ readonly remoteStorage: RemoteStorageAdapter;
27
+ /**
28
+ * Callback function to watch for changes in attachment references in your data model.
29
+ *
30
+ * This should be implemented by the user of AttachmentQueue to monitor changes in your application's
31
+ * data that reference attachments. When attachments are added, removed, or modified,
32
+ * this callback should trigger the onUpdate function with the current set of attachments.
33
+ */
34
+ private readonly watchAttachments;
35
+ /** Name of the database table storing attachment records */
36
+ readonly tableName: string;
37
+ /** Logger instance for diagnostic information */
38
+ readonly logger: ILogger;
39
+ /** Interval in milliseconds between periodic sync operations. Default: 30000 (30 seconds) */
40
+ readonly syncIntervalMs: number;
41
+ /** Duration in milliseconds to throttle sync operations */
42
+ readonly syncThrottleDuration: number;
43
+ /** Whether to automatically download remote attachments. Default: true */
44
+ readonly downloadAttachments: boolean;
45
+ /** Maximum number of archived attachments to keep before cleanup. Default: 100 */
46
+ readonly archivedCacheLimit: number;
47
+ /** Service for managing attachment-related database operations */
48
+ private readonly attachmentService;
49
+ /** PowerSync database instance */
50
+ private readonly db;
51
+ /** Cleanup function for status change listener */
52
+ private statusListenerDispose?;
53
+ private watchActiveAttachments;
54
+ private watchAttachmentsAbortController;
55
+ /**
56
+ * Creates a new AttachmentQueue instance.
57
+ *
58
+ * @param options - Configuration options
59
+ * @param options.db - PowerSync database instance
60
+ * @param options.remoteStorage - Remote storage adapter for upload/download operations
61
+ * @param options.localStorage - Local storage adapter for file persistence
62
+ * @param options.watchAttachments - Callback for monitoring attachment changes in your data model
63
+ * @param options.tableName - Name of the table to store attachment records. Default: 'ps_attachment_queue'
64
+ * @param options.logger - Logger instance. Defaults to db.logger
65
+ * @param options.syncIntervalMs - Interval between automatic syncs in milliseconds. Default: 30000
66
+ * @param options.syncThrottleDuration - Throttle duration for sync operations in milliseconds. Default: 1000
67
+ * @param options.downloadAttachments - Whether to automatically download remote attachments. Default: true
68
+ * @param options.archivedCacheLimit - Maximum archived attachments before cleanup. Default: 100
69
+ */
70
+ constructor({ db, localStorage, remoteStorage, watchAttachments, logger, tableName, syncIntervalMs, syncThrottleDuration, downloadAttachments, archivedCacheLimit, errorHandler }: {
71
+ db: AbstractPowerSyncDatabase;
72
+ remoteStorage: RemoteStorageAdapter;
73
+ localStorage: LocalStorageAdapter;
74
+ watchAttachments: (onUpdate: (attachment: WatchedAttachmentItem[]) => Promise<void>, signal: AbortSignal) => void;
75
+ tableName?: string;
76
+ logger?: ILogger;
77
+ syncIntervalMs?: number;
78
+ syncThrottleDuration?: number;
79
+ downloadAttachments?: boolean;
80
+ archivedCacheLimit?: number;
81
+ errorHandler?: AttachmentErrorHandler;
82
+ });
83
+ /**
84
+ * Generates a new attachment ID using a SQLite UUID function.
85
+ *
86
+ * @returns Promise resolving to the new attachment ID
87
+ */
88
+ generateAttachmentId(): Promise<string>;
89
+ /**
90
+ * Starts the attachment synchronization process.
91
+ *
92
+ * This method:
93
+ * - Stops any existing sync operations
94
+ * - Sets up periodic synchronization based on syncIntervalMs
95
+ * - Registers listeners for active attachment changes
96
+ * - Processes watched attachments to queue uploads/downloads
97
+ * - Handles state transitions for archived and new attachments
98
+ */
99
+ startSync(): Promise<void>;
100
+ /**
101
+ * Synchronizes all active attachments between local and remote storage.
102
+ *
103
+ * This is called automatically at regular intervals when sync is started,
104
+ * but can also be called manually to trigger an immediate sync.
105
+ */
106
+ syncStorage(): Promise<void>;
107
+ /**
108
+ * Stops the attachment synchronization process.
109
+ *
110
+ * Clears the periodic sync timer and closes all active attachment watchers.
111
+ */
112
+ stopSync(): Promise<void>;
113
+ /**
114
+ * Saves a file to local storage and queues it for upload to remote storage.
115
+ *
116
+ * @param options - File save options
117
+ * @param options.data - The file data as ArrayBuffer, Blob, or base64 string
118
+ * @param options.fileExtension - File extension (e.g., 'jpg', 'pdf')
119
+ * @param options.mediaType - MIME type of the file (e.g., 'image/jpeg')
120
+ * @param options.metaData - Optional metadata to associate with the attachment
121
+ * @param options.id - Optional custom ID. If not provided, a UUID will be generated
122
+ * @param options.updateHook - Optional callback to execute additional database operations
123
+ * within the same transaction as the attachment creation
124
+ * @returns Promise resolving to the created attachment record
125
+ */
126
+ saveFile({ data, fileExtension, mediaType, metaData, id, updateHook }: {
127
+ data: AttachmentData;
128
+ fileExtension: string;
129
+ mediaType?: string;
130
+ metaData?: string;
131
+ id?: string;
132
+ updateHook?: (transaction: Transaction, attachment: AttachmentRecord) => Promise<void>;
133
+ }): Promise<AttachmentRecord>;
134
+ deleteFile({ id, updateHook }: {
135
+ id: string;
136
+ updateHook?: (transaction: Transaction, attachment: AttachmentRecord) => Promise<void>;
137
+ }): Promise<void>;
138
+ expireCache(): Promise<void>;
139
+ clearQueue(): Promise<void>;
140
+ /**
141
+ * Verifies the integrity of all attachment records and repairs inconsistencies.
142
+ *
143
+ * This method checks each attachment record against the local filesystem and:
144
+ * - Updates localUri if the file exists at a different path
145
+ * - Archives attachments with missing local files that haven't been uploaded
146
+ * - Requeues synced attachments for download if their local files are missing
147
+ */
148
+ verifyAttachments(): Promise<void>;
149
+ }