@powersync/common 0.0.0-dev-20260630144038 → 0.0.0-dev-20260827080125
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/lib/attachments/AttachmentQueue.d.ts +97 -42
- package/lib/attachments/AttachmentQueue.js +91 -18
- package/lib/attachments/AttachmentQueue.js.map +1 -1
- package/lib/attachments/AttachmentTransportAdapter.d.ts +47 -0
- package/lib/attachments/AttachmentTransportAdapter.js +2 -0
- package/lib/attachments/AttachmentTransportAdapter.js.map +1 -0
- package/lib/attachments/BufferedAttachmentTransport.d.ts +24 -0
- package/lib/attachments/BufferedAttachmentTransport.js +32 -0
- package/lib/attachments/BufferedAttachmentTransport.js.map +1 -0
- package/lib/attachments/LocalStorageAdapter.d.ts +19 -0
- package/lib/attachments/Schema.d.ts +21 -2
- package/lib/attachments/Schema.js +14 -10
- package/lib/attachments/Schema.js.map +1 -1
- package/lib/attachments/SyncingService.d.ts +21 -8
- package/lib/attachments/SyncingService.js +59 -30
- package/lib/attachments/SyncingService.js.map +1 -1
- package/lib/client/CommonPowerSyncDatabase.d.ts +14 -1
- package/lib/client/connection/PowerSyncBackendConnector.d.ts +11 -0
- package/lib/client/sync/CheckpointRequest.d.ts +32 -0
- package/lib/client/sync/CheckpointRequest.js +2 -0
- package/lib/client/sync/CheckpointRequest.js.map +1 -0
- package/lib/client/sync/options.d.ts +34 -0
- package/lib/client/sync/options.js.map +1 -1
- package/lib/db/crud/SyncStatus.d.ts +1 -1
- package/lib/index.d.ts +3 -1
- package/lib/index.js +2 -0
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/attachments/AttachmentQueue.ts +162 -63
- package/src/attachments/AttachmentTransportAdapter.ts +49 -0
- package/src/attachments/BufferedAttachmentTransport.ts +37 -0
- package/src/attachments/LocalStorageAdapter.ts +20 -0
- package/src/attachments/Schema.ts +29 -20
- package/src/attachments/SyncingService.ts +67 -34
- package/src/client/CommonPowerSyncDatabase.ts +15 -1
- package/src/client/connection/PowerSyncBackendConnector.ts +12 -0
- package/src/client/sync/CheckpointRequest.ts +31 -0
- package/src/client/sync/options.ts +35 -0
- package/src/db/crud/SyncStatus.ts +1 -1
- package/src/index.ts +9 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { LogLevels, PowerSyncLogger } from '../utils/Logger.js';
|
|
2
2
|
import { AttachmentService } from './AttachmentService.js';
|
|
3
|
+
import { AttachmentTransportAdapter } from './AttachmentTransportAdapter.js';
|
|
3
4
|
import { LocalStorageAdapter } from './LocalStorageAdapter.js';
|
|
4
|
-
import { RemoteStorageAdapter } from './RemoteStorageAdapter.js';
|
|
5
5
|
import { AttachmentRecord, AttachmentState } from './Schema.js';
|
|
6
6
|
import { AttachmentErrorHandler } from './AttachmentErrorHandler.js';
|
|
7
7
|
import { AttachmentContext } from './AttachmentContext.js';
|
|
@@ -10,60 +10,96 @@ import { AttachmentContext } from './AttachmentContext.js';
|
|
|
10
10
|
* Orchestrates attachment synchronization between local and remote storage.
|
|
11
11
|
* Handles uploads, downloads, deletions, and state transitions.
|
|
12
12
|
*
|
|
13
|
+
* Remote operations (upload/download/delete) go through the {@link AttachmentTransportAdapter};
|
|
14
|
+
* local file operations use the {@link LocalStorageAdapter}.
|
|
15
|
+
*
|
|
13
16
|
* @internal
|
|
14
17
|
*/
|
|
15
18
|
export class SyncingService {
|
|
16
19
|
private attachmentService: AttachmentService;
|
|
17
20
|
private localStorage: LocalStorageAdapter;
|
|
18
|
-
private
|
|
21
|
+
private transport: AttachmentTransportAdapter;
|
|
19
22
|
private logger: PowerSyncLogger;
|
|
20
23
|
private errorHandler?: AttachmentErrorHandler;
|
|
21
24
|
|
|
22
25
|
constructor(
|
|
23
26
|
attachmentService: AttachmentService,
|
|
24
27
|
localStorage: LocalStorageAdapter,
|
|
25
|
-
|
|
28
|
+
transport: AttachmentTransportAdapter,
|
|
26
29
|
logger: PowerSyncLogger,
|
|
27
30
|
errorHandler?: AttachmentErrorHandler
|
|
28
31
|
) {
|
|
29
32
|
this.attachmentService = attachmentService;
|
|
30
33
|
this.localStorage = localStorage;
|
|
31
|
-
this.
|
|
34
|
+
this.transport = transport;
|
|
32
35
|
this.logger = logger;
|
|
33
36
|
this.errorHandler = errorHandler;
|
|
34
37
|
}
|
|
35
38
|
|
|
36
39
|
/**
|
|
37
40
|
* Processes attachments based on their state (upload, download, or delete).
|
|
38
|
-
*
|
|
41
|
+
*
|
|
42
|
+
* Each attachment's I/O runs outside the attachment-service mutex, and the row's
|
|
43
|
+
* state transition is persisted immediately after it completes. This keeps the
|
|
44
|
+
* mutex available to concurrent `saveFile` / `deleteFile` / watched-attachment
|
|
45
|
+
* processing while a batch is in flight, and means consumer queries against the
|
|
46
|
+
* attachments queue see incremental progress instead of one atomic commit at the
|
|
47
|
+
* end of the batch.
|
|
39
48
|
*
|
|
40
49
|
* @param attachments - Array of attachment records to process
|
|
41
|
-
* @param
|
|
42
|
-
*
|
|
50
|
+
* @param options - Optional controls. Pass `signal` (an `AbortSignal`) to interrupt
|
|
51
|
+
* the batch: it is checked between attachments and, once aborted, the
|
|
52
|
+
* loop exits early — letting `stopSync` stop a running batch within
|
|
53
|
+
* one attachment's processing time.
|
|
43
54
|
*/
|
|
44
|
-
async processAttachments(
|
|
45
|
-
|
|
55
|
+
async processAttachments(
|
|
56
|
+
attachments: AttachmentRecord[],
|
|
57
|
+
options?: {
|
|
58
|
+
signal?: AbortSignal;
|
|
59
|
+
}
|
|
60
|
+
): Promise<void> {
|
|
61
|
+
const signal = options?.signal;
|
|
62
|
+
this.logger.log({
|
|
63
|
+
level: LogLevels.info,
|
|
64
|
+
message: `Starting processAttachments with ${attachments.length} attachments`
|
|
65
|
+
});
|
|
66
|
+
|
|
46
67
|
for (const attachment of attachments) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const downloaded = await this.downloadAttachment(attachment);
|
|
54
|
-
updatedAttachments.push(downloaded);
|
|
55
|
-
break;
|
|
56
|
-
case AttachmentState.QUEUED_DELETE:
|
|
57
|
-
const deleted = await this.deleteAttachment(attachment, context);
|
|
58
|
-
updatedAttachments.push(deleted);
|
|
59
|
-
break;
|
|
60
|
-
|
|
61
|
-
default:
|
|
62
|
-
break;
|
|
68
|
+
if (signal?.aborted) {
|
|
69
|
+
this.logger.log({
|
|
70
|
+
level: LogLevels.info,
|
|
71
|
+
message: 'Sync cancelled; stopping iteration early'
|
|
72
|
+
});
|
|
73
|
+
return;
|
|
63
74
|
}
|
|
64
|
-
}
|
|
65
75
|
|
|
66
|
-
|
|
76
|
+
try {
|
|
77
|
+
let updated: AttachmentRecord;
|
|
78
|
+
switch (attachment.state) {
|
|
79
|
+
case AttachmentState.QUEUED_UPLOAD:
|
|
80
|
+
updated = await this.uploadAttachment(attachment);
|
|
81
|
+
break;
|
|
82
|
+
case AttachmentState.QUEUED_DOWNLOAD:
|
|
83
|
+
updated = await this.downloadAttachment(attachment);
|
|
84
|
+
break;
|
|
85
|
+
case AttachmentState.QUEUED_DELETE:
|
|
86
|
+
// `deleteAttachment` needs a context (it removes the row in a
|
|
87
|
+
// transaction); briefly re-acquire the mutex for just this row.
|
|
88
|
+
updated = await this.attachmentService.withContext((ctx) => this.deleteAttachment(attachment, ctx));
|
|
89
|
+
break;
|
|
90
|
+
default:
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
await this.attachmentService.withContext((ctx) => ctx.saveAttachments([updated]));
|
|
95
|
+
} catch (error) {
|
|
96
|
+
this.logger.log({
|
|
97
|
+
level: LogLevels.warn,
|
|
98
|
+
message: `Error during sync for ${attachment.id}`,
|
|
99
|
+
error
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
67
103
|
}
|
|
68
104
|
|
|
69
105
|
/**
|
|
@@ -81,8 +117,7 @@ export class SyncingService {
|
|
|
81
117
|
throw new Error(`No localUri for attachment ${attachment.id}`);
|
|
82
118
|
}
|
|
83
119
|
|
|
84
|
-
|
|
85
|
-
await this.remoteStorage.uploadFile(fileBlob, attachment);
|
|
120
|
+
await this.transport.upload({ ...attachment, localUri: attachment.localUri });
|
|
86
121
|
|
|
87
122
|
return {
|
|
88
123
|
...attachment,
|
|
@@ -104,7 +139,7 @@ export class SyncingService {
|
|
|
104
139
|
|
|
105
140
|
/**
|
|
106
141
|
* Downloads an attachment from remote storage to local storage.
|
|
107
|
-
*
|
|
142
|
+
* The destination `localUri` is assigned here and the transport writes the file to it.
|
|
108
143
|
* On success, marks as SYNCED. On failure, defers to error handler or archives.
|
|
109
144
|
*
|
|
110
145
|
* @param attachment - The attachment record to download
|
|
@@ -113,10 +148,8 @@ export class SyncingService {
|
|
|
113
148
|
async downloadAttachment(attachment: AttachmentRecord): Promise<AttachmentRecord> {
|
|
114
149
|
this.logger.log({ level: LogLevels.info, message: `Downloading attachment ${attachment.filename}` });
|
|
115
150
|
try {
|
|
116
|
-
const fileData = await this.remoteStorage.downloadFile(attachment);
|
|
117
|
-
|
|
118
151
|
const localUri = this.localStorage.getLocalUri(attachment.filename);
|
|
119
|
-
await this.
|
|
152
|
+
await this.transport.download({ ...attachment, localUri });
|
|
120
153
|
|
|
121
154
|
return {
|
|
122
155
|
...attachment,
|
|
@@ -148,7 +181,7 @@ export class SyncingService {
|
|
|
148
181
|
*/
|
|
149
182
|
async deleteAttachment(attachment: AttachmentRecord, context: AttachmentContext): Promise<AttachmentRecord> {
|
|
150
183
|
try {
|
|
151
|
-
await this.
|
|
184
|
+
await this.transport.delete(attachment);
|
|
152
185
|
if (attachment.localUri) {
|
|
153
186
|
await this.localStorage.deleteFile(attachment.localUri);
|
|
154
187
|
}
|
|
@@ -16,6 +16,7 @@ import { ArrayQueryDefinition, Query } from './Query.js';
|
|
|
16
16
|
import { WatchCompatibleQuery } from './watched/WatchedQuery.js';
|
|
17
17
|
import { Mutex } from '../utils/mutex.js';
|
|
18
18
|
import { QueryResult } from '../db/QueryResult.js';
|
|
19
|
+
import { CheckpointRequest } from './sync/CheckpointRequest.js';
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* @public
|
|
@@ -233,6 +234,19 @@ export interface CommonPowerSyncDatabase extends BaseObserverInterface<PowerSync
|
|
|
233
234
|
*/
|
|
234
235
|
syncStream(name: string, params?: Record<string, any>): SyncStream;
|
|
235
236
|
|
|
237
|
+
/**
|
|
238
|
+
* Requests a checkpoint from the PowerSync service.
|
|
239
|
+
*
|
|
240
|
+
* The returned request can be awaited (using {@link CheckpointRequest#waitForSync}) to confirm that the local
|
|
241
|
+
* database has applied server-side changes up to the checkpoint. This method requires an active or connecting sync
|
|
242
|
+
* client connected with a {@link CheckpointMode} set to `requests` and PowerSync service version 1.24.0 or later.
|
|
243
|
+
*
|
|
244
|
+
* It can throw for connection, mode, authentication, or service request failures.
|
|
245
|
+
*
|
|
246
|
+
* @alpha
|
|
247
|
+
*/
|
|
248
|
+
requestCheckpoint(): Promise<CheckpointRequest>;
|
|
249
|
+
|
|
236
250
|
/**
|
|
237
251
|
* Close the database, releasing resources.
|
|
238
252
|
*
|
|
@@ -343,7 +357,7 @@ export interface CommonPowerSyncDatabase extends BaseObserverInterface<PowerSync
|
|
|
343
357
|
|
|
344
358
|
/**
|
|
345
359
|
* Open a read-only transaction.
|
|
346
|
-
*
|
|
360
|
+
* When multiple connections are available, read transactions can run concurrently to a write transaction.
|
|
347
361
|
* Changes from any write transaction are not visible to read transactions started before it.
|
|
348
362
|
*
|
|
349
363
|
* @param callback - Function to execute within the transaction
|
|
@@ -25,4 +25,16 @@ export interface PowerSyncBackendConnector {
|
|
|
25
25
|
* Any thrown errors will result in a retry after the configured wait period (default: 5 seconds).
|
|
26
26
|
*/
|
|
27
27
|
uploadData: (database: CommonPowerSyncDatabase) => Promise<void>;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Posts a client-generated checkpoint request to the backend and returns the effective checkpoint request state.
|
|
31
|
+
*
|
|
32
|
+
* This method is optional. It only needs to be implemented when the selected {@link CheckpointMode} is `requests`
|
|
33
|
+
* and [asynchronous backend uploads](https://docs.powersync.com/client-sdks/advanced/checkpoint-requests#asynchronous-upload-backends)
|
|
34
|
+
* are used. In any other case, this method should not be present on backend connectors.
|
|
35
|
+
*
|
|
36
|
+
* @param requestId - The client-generated checkpoint request ID (a positive 64-bit integer encoded as a string).
|
|
37
|
+
* @param clientId - The PowerSync client ID for the current device.
|
|
38
|
+
*/
|
|
39
|
+
postCheckpointRequest?(clientId: string, requestId: string): Promise<string>;
|
|
28
40
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A checkpoint request created by {@link CommonPowerSyncDatabase#requestCheckpoint}.
|
|
3
|
+
*
|
|
4
|
+
* Use this value to wait until the local database has applied server-side changes up to the requested checkpoint. This
|
|
5
|
+
* is useful for explicit refresh flows where the caller wants confirmation that the local view has caught up to the
|
|
6
|
+
* service.
|
|
7
|
+
*
|
|
8
|
+
* Checkpoint requests are backed by request ids tracked in the local database, so they are reusable across disconnect
|
|
9
|
+
* and reconnect cycles. A wait interrupted by a disconnect throws an error, but the same request can be awaited again
|
|
10
|
+
* once a new connection is established.
|
|
11
|
+
*
|
|
12
|
+
* Requests do not survive {@link CommonPowerSyncDatabase#disconnectAndClear}, instances created before a clear should
|
|
13
|
+
* be discarded and requested again.
|
|
14
|
+
*
|
|
15
|
+
* @alpha
|
|
16
|
+
*/
|
|
17
|
+
export interface CheckpointRequest {
|
|
18
|
+
/**
|
|
19
|
+
* Whether this checkpoint request has synced before.
|
|
20
|
+
*/
|
|
21
|
+
readonly hasSynced: boolean;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Waits until this checkpoint has been synced locally.
|
|
25
|
+
*
|
|
26
|
+
* This method fails on sync errors: If a download or upload error occurs before this checkpoint request has synced,
|
|
27
|
+
* that error is rethrown here. This makes it easier to observe sync errors when relying on checkpoints. Once sync has
|
|
28
|
+
* recovered, it is valid to call this method again to await the checkpoint.
|
|
29
|
+
*/
|
|
30
|
+
waitForSync(options?: { signal?: AbortSignal }): Promise<void>;
|
|
31
|
+
}
|
|
@@ -49,6 +49,11 @@ export interface SyncOptions {
|
|
|
49
49
|
* milliseconds.
|
|
50
50
|
*/
|
|
51
51
|
crudUploadThrottleMs?: number;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The mode used to request checkpoints from the service (used after uploading local data).
|
|
55
|
+
*/
|
|
56
|
+
checkpointMode?: CheckpointMode;
|
|
52
57
|
}
|
|
53
58
|
|
|
54
59
|
/**
|
|
@@ -75,3 +80,33 @@ export enum FetchStrategy {
|
|
|
75
80
|
*/
|
|
76
81
|
Sequential = 'sequential'
|
|
77
82
|
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* The mechanism to request checkpoints from the PowerSync service.
|
|
86
|
+
*
|
|
87
|
+
* Checkpoint requests are used after a client uploads local mutations. The PowerSync service later references them in
|
|
88
|
+
* downloaded data, allowing the SDK to assume that uploaded data has been synced down again.
|
|
89
|
+
*
|
|
90
|
+
* There are two ways to send checkpoint requests: A legacy (but default and stable) format supported by all PowerSync
|
|
91
|
+
* service versions, and a newer (`requests`) method which is only available from PowerSync service version 1.24.0 or
|
|
92
|
+
* later.
|
|
93
|
+
*
|
|
94
|
+
* Note that the requests checkpoint mode is an alpha API.
|
|
95
|
+
*
|
|
96
|
+
* @public
|
|
97
|
+
*/
|
|
98
|
+
export type CheckpointMode = 'legacy' | 'requests' | { requests: CheckpointRequestsOptions };
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Options associated with a {@link CheckpointMode} when the requests-based checkpoint option is used.
|
|
102
|
+
*
|
|
103
|
+
* @public
|
|
104
|
+
*/
|
|
105
|
+
export interface CheckpointRequestsOptions {
|
|
106
|
+
/**
|
|
107
|
+
* The delay, in milliseconds, to wait before re-sending a checkpoint request when it hasn't been applied in time.
|
|
108
|
+
*
|
|
109
|
+
* The minimum value for this is 10 seconds, lower values will be ignored.
|
|
110
|
+
*/
|
|
111
|
+
retryDelay: number;
|
|
112
|
+
}
|
|
@@ -94,7 +94,7 @@ export interface SyncStatus {
|
|
|
94
94
|
get hasSynced(): boolean | undefined;
|
|
95
95
|
|
|
96
96
|
/**
|
|
97
|
-
* All sync streams currently being tracked in
|
|
97
|
+
* All sync streams currently being tracked in the database.
|
|
98
98
|
*
|
|
99
99
|
* This returns null when the database is currently being opened and we don't have reliable information about all
|
|
100
100
|
* included streams yet.
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from './attachments/AttachmentContext.js';
|
|
2
2
|
export * from './attachments/AttachmentErrorHandler.js';
|
|
3
3
|
export * from './attachments/AttachmentQueue.js';
|
|
4
|
+
export * from './attachments/AttachmentTransportAdapter.js';
|
|
4
5
|
export * from './attachments/LocalStorageAdapter.js';
|
|
5
6
|
export * from './attachments/RemoteStorageAdapter.js';
|
|
6
7
|
export * from './attachments/Schema.js';
|
|
@@ -14,9 +15,16 @@ export * from './client/SQLOpenFactory.js';
|
|
|
14
15
|
export * from './client/sync/bucket/CrudBatch.js';
|
|
15
16
|
export { CrudEntry, OpId, UpdateType } from './client/sync/bucket/CrudEntry.js';
|
|
16
17
|
export * from './client/sync/bucket/CrudTransaction.js';
|
|
18
|
+
export * from './client/sync/CheckpointRequest.js';
|
|
17
19
|
export * from './client/sync/stream/JsonValue.js';
|
|
18
20
|
export * from './client/sync/sync-streams.js';
|
|
19
|
-
export {
|
|
21
|
+
export {
|
|
22
|
+
SyncOptions,
|
|
23
|
+
SyncStreamConnectionMethod,
|
|
24
|
+
FetchStrategy,
|
|
25
|
+
CheckpointMode,
|
|
26
|
+
CheckpointRequestsOptions
|
|
27
|
+
} from './client/sync/options.js';
|
|
20
28
|
|
|
21
29
|
export { ProgressWithOperations, SyncProgress } from './db/crud/SyncProgress.js';
|
|
22
30
|
export * from './db/crud/SyncStatus.js';
|