@powersync/common 0.0.0-dev-20250423120133 → 0.0.0-dev-20250526133243
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/dist/bundle.mjs +3 -3
- package/lib/client/AbstractPowerSyncDatabase.d.ts +37 -0
- package/lib/client/AbstractPowerSyncDatabase.js +136 -22
- package/lib/client/sync/bucket/BucketStorageAdapter.d.ts +6 -0
- package/lib/client/sync/bucket/CrudEntry.d.ts +13 -1
- package/lib/client/sync/bucket/CrudEntry.js +16 -2
- package/lib/client/sync/bucket/SqliteBucketStorage.d.ts +2 -1
- package/lib/client/sync/bucket/SqliteBucketStorage.js +18 -3
- package/lib/client/sync/stream/AbstractRemote.d.ts +29 -0
- package/lib/client/sync/stream/AbstractRemote.js +71 -10
- package/lib/client/sync/stream/AbstractStreamingSyncImplementation.d.ts +1 -0
- package/lib/client/sync/stream/AbstractStreamingSyncImplementation.js +57 -6
- package/lib/client/sync/stream/streaming-sync-types.d.ts +1 -1
- package/lib/db/crud/SyncProgress.d.ts +72 -0
- package/lib/db/crud/SyncProgress.js +60 -0
- package/lib/db/crud/SyncStatus.d.ts +20 -0
- package/lib/db/crud/SyncStatus.js +14 -0
- package/lib/db/schema/Schema.d.ts +4 -0
- package/lib/db/schema/Schema.js +1 -10
- package/lib/db/schema/Table.d.ts +34 -8
- package/lib/db/schema/Table.js +48 -9
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/package.json +1 -1
|
@@ -196,11 +196,12 @@ The next upload iteration will be delayed.`);
|
|
|
196
196
|
if (this.abortController) {
|
|
197
197
|
await this.disconnect();
|
|
198
198
|
}
|
|
199
|
-
|
|
199
|
+
const controller = new AbortController();
|
|
200
|
+
this.abortController = controller;
|
|
200
201
|
this.streamingSyncPromise = this.streamingSync(this.abortController.signal, options);
|
|
201
202
|
// Return a promise that resolves when the connection status is updated
|
|
202
203
|
return new Promise((resolve) => {
|
|
203
|
-
const
|
|
204
|
+
const disposer = this.registerListener({
|
|
204
205
|
statusUpdated: (update) => {
|
|
205
206
|
// This is triggered as soon as a connection is read from
|
|
206
207
|
if (typeof update.connected == 'undefined') {
|
|
@@ -209,12 +210,14 @@ The next upload iteration will be delayed.`);
|
|
|
209
210
|
}
|
|
210
211
|
if (update.connected == false) {
|
|
211
212
|
/**
|
|
212
|
-
* This function does not reject if initial connect attempt failed
|
|
213
|
+
* This function does not reject if initial connect attempt failed.
|
|
214
|
+
* Connected can be false if the connection attempt was aborted or if the initial connection
|
|
215
|
+
* attempt failed.
|
|
213
216
|
*/
|
|
214
217
|
this.logger.warn('Initial connect attempt did not successfully connect to server');
|
|
215
218
|
}
|
|
219
|
+
disposer();
|
|
216
220
|
resolve();
|
|
217
|
-
l();
|
|
218
221
|
}
|
|
219
222
|
});
|
|
220
223
|
});
|
|
@@ -270,7 +273,8 @@ The next upload iteration will be delayed.`);
|
|
|
270
273
|
connected: false,
|
|
271
274
|
connecting: false,
|
|
272
275
|
dataFlow: {
|
|
273
|
-
downloading: false
|
|
276
|
+
downloading: false,
|
|
277
|
+
downloadProgress: null
|
|
274
278
|
}
|
|
275
279
|
});
|
|
276
280
|
});
|
|
@@ -355,6 +359,9 @@ The next upload iteration will be delayed.`);
|
|
|
355
359
|
let validatedCheckpoint = null;
|
|
356
360
|
let appliedCheckpoint = null;
|
|
357
361
|
const clientId = await this.options.adapter.getClientId();
|
|
362
|
+
if (signal.aborted) {
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
358
365
|
this.logger.debug('Requesting stream from server');
|
|
359
366
|
const syncOptions = {
|
|
360
367
|
path: '/sync/stream',
|
|
@@ -409,6 +416,7 @@ The next upload iteration will be delayed.`);
|
|
|
409
416
|
bucketMap = newBuckets;
|
|
410
417
|
await this.options.adapter.removeBuckets([...bucketsToDelete]);
|
|
411
418
|
await this.options.adapter.setTargetCheckpoint(targetCheckpoint);
|
|
419
|
+
await this.updateSyncStatusForStartingCheckpoint(targetCheckpoint);
|
|
412
420
|
}
|
|
413
421
|
else if (isStreamingSyncCheckpointComplete(line)) {
|
|
414
422
|
const result = await this.applyCheckpoint(targetCheckpoint, signal);
|
|
@@ -472,6 +480,7 @@ The next upload iteration will be delayed.`);
|
|
|
472
480
|
write_checkpoint: diff.write_checkpoint
|
|
473
481
|
};
|
|
474
482
|
targetCheckpoint = newCheckpoint;
|
|
483
|
+
await this.updateSyncStatusForStartingCheckpoint(targetCheckpoint);
|
|
475
484
|
bucketMap = new Map();
|
|
476
485
|
newBuckets.forEach((checksum, name) => bucketMap.set(name, {
|
|
477
486
|
name: checksum.bucket,
|
|
@@ -486,9 +495,22 @@ The next upload iteration will be delayed.`);
|
|
|
486
495
|
}
|
|
487
496
|
else if (isStreamingSyncData(line)) {
|
|
488
497
|
const { data } = line;
|
|
498
|
+
const previousProgress = this.syncStatus.dataFlowStatus.downloadProgress;
|
|
499
|
+
let updatedProgress = null;
|
|
500
|
+
if (previousProgress) {
|
|
501
|
+
updatedProgress = { ...previousProgress };
|
|
502
|
+
const progressForBucket = updatedProgress[data.bucket];
|
|
503
|
+
if (progressForBucket) {
|
|
504
|
+
updatedProgress[data.bucket] = {
|
|
505
|
+
...progressForBucket,
|
|
506
|
+
sinceLast: progressForBucket.sinceLast + data.data.length
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
}
|
|
489
510
|
this.updateSyncStatus({
|
|
490
511
|
dataFlow: {
|
|
491
|
-
downloading: true
|
|
512
|
+
downloading: true,
|
|
513
|
+
downloadProgress: updatedProgress
|
|
492
514
|
}
|
|
493
515
|
});
|
|
494
516
|
await this.options.adapter.saveSyncData({ buckets: [SyncDataBucket.fromRow(data)] });
|
|
@@ -498,6 +520,7 @@ The next upload iteration will be delayed.`);
|
|
|
498
520
|
if (remaining_seconds == 0) {
|
|
499
521
|
// Connection would be closed automatically right after this
|
|
500
522
|
this.logger.debug('Token expiring; reconnect');
|
|
523
|
+
this.options.remote.invalidateCredentials();
|
|
501
524
|
/**
|
|
502
525
|
* For a rare case where the backend connector does not update the token
|
|
503
526
|
* (uses the same one), this should have some delay.
|
|
@@ -505,6 +528,12 @@ The next upload iteration will be delayed.`);
|
|
|
505
528
|
await this.delayRetry();
|
|
506
529
|
return;
|
|
507
530
|
}
|
|
531
|
+
else if (remaining_seconds < 30) {
|
|
532
|
+
this.logger.debug('Token will expire soon; reconnect');
|
|
533
|
+
// Pre-emptively refresh the token
|
|
534
|
+
this.options.remote.invalidateCredentials();
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
508
537
|
this.triggerCrudUpload();
|
|
509
538
|
}
|
|
510
539
|
else {
|
|
@@ -536,6 +565,27 @@ The next upload iteration will be delayed.`);
|
|
|
536
565
|
}
|
|
537
566
|
});
|
|
538
567
|
}
|
|
568
|
+
async updateSyncStatusForStartingCheckpoint(checkpoint) {
|
|
569
|
+
const localProgress = await this.options.adapter.getBucketOperationProgress();
|
|
570
|
+
const progress = {};
|
|
571
|
+
for (const bucket of checkpoint.buckets) {
|
|
572
|
+
const savedProgress = localProgress[bucket.bucket];
|
|
573
|
+
progress[bucket.bucket] = {
|
|
574
|
+
// The fallback priority doesn't matter here, but 3 is the one newer versions of the sync service
|
|
575
|
+
// will use by default.
|
|
576
|
+
priority: bucket.priority ?? 3,
|
|
577
|
+
atLast: savedProgress?.atLast ?? 0,
|
|
578
|
+
sinceLast: savedProgress?.sinceLast ?? 0,
|
|
579
|
+
targetCount: bucket.count ?? 0
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
this.updateSyncStatus({
|
|
583
|
+
dataFlow: {
|
|
584
|
+
downloading: true,
|
|
585
|
+
downloadProgress: progress
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
}
|
|
539
589
|
async applyCheckpoint(checkpoint, abort) {
|
|
540
590
|
let result = await this.options.adapter.syncLocalDatabase(checkpoint);
|
|
541
591
|
const pending = this.pendingCrudUpload;
|
|
@@ -565,6 +615,7 @@ The next upload iteration will be delayed.`);
|
|
|
565
615
|
lastSyncedAt: new Date(),
|
|
566
616
|
dataFlow: {
|
|
567
617
|
downloading: false,
|
|
618
|
+
downloadProgress: null,
|
|
568
619
|
downloadError: undefined
|
|
569
620
|
}
|
|
570
621
|
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/** @internal */
|
|
2
|
+
export type InternalProgressInformation = Record<string, {
|
|
3
|
+
priority: number;
|
|
4
|
+
atLast: number;
|
|
5
|
+
sinceLast: number;
|
|
6
|
+
targetCount: number;
|
|
7
|
+
}>;
|
|
8
|
+
/**
|
|
9
|
+
* @internal The priority used by the core extension to indicate that a full sync was completed.
|
|
10
|
+
*/
|
|
11
|
+
export declare const FULL_SYNC_PRIORITY = 2147483647;
|
|
12
|
+
/**
|
|
13
|
+
* Information about a progressing download made by the PowerSync SDK.
|
|
14
|
+
*
|
|
15
|
+
* To obtain these values, use {@link SyncProgress}, available through
|
|
16
|
+
* {@link SyncStatus#downloadProgress}.
|
|
17
|
+
*/
|
|
18
|
+
export interface ProgressWithOperations {
|
|
19
|
+
/**
|
|
20
|
+
* The total amount of operations to download for the current sync iteration
|
|
21
|
+
* to complete.
|
|
22
|
+
*/
|
|
23
|
+
totalOperations: number;
|
|
24
|
+
/**
|
|
25
|
+
* The amount of operations that have already been downloaded.
|
|
26
|
+
*/
|
|
27
|
+
downloadedOperations: number;
|
|
28
|
+
/**
|
|
29
|
+
* Relative progress, as {@link downloadedOperations} of {@link totalOperations}.
|
|
30
|
+
*
|
|
31
|
+
* This will be a number between `0.0` and `1.0` (inclusive).
|
|
32
|
+
*
|
|
33
|
+
* When this number reaches `1.0`, all changes have been received from the sync service.
|
|
34
|
+
* Actually applying these changes happens before the `downloadProgress` field is cleared from
|
|
35
|
+
* {@link SyncStatus}, so progress can stay at `1.0` for a short while before completing.
|
|
36
|
+
*/
|
|
37
|
+
downloadedFraction: number;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Provides realtime progress on how PowerSync is downloading rows.
|
|
41
|
+
*
|
|
42
|
+
* The progress until the next complete sync is available through the fields on {@link ProgressWithOperations},
|
|
43
|
+
* which this class implements.
|
|
44
|
+
* Additionally, the {@link SyncProgress.untilPriority} method can be used to otbain progress towards
|
|
45
|
+
* a specific priority (instead of the progress for the entire download).
|
|
46
|
+
*
|
|
47
|
+
* The reported progress always reflects the status towards the end of a sync iteration (after
|
|
48
|
+
* which a consistent snapshot of all buckets is available locally).
|
|
49
|
+
*
|
|
50
|
+
* In rare cases (in particular, when a [compacting](https://docs.powersync.com/usage/lifecycle-maintenance/compacting-buckets)
|
|
51
|
+
* operation takes place between syncs), it's possible for the returned numbers to be slightly
|
|
52
|
+
* inaccurate. For this reason, {@link SyncProgress} should be seen as an approximation of progress.
|
|
53
|
+
* The information returned is good enough to build progress bars, but not exact enough to track
|
|
54
|
+
* individual download counts.
|
|
55
|
+
*
|
|
56
|
+
* Also note that data is downloaded in bulk, which means that individual counters are unlikely
|
|
57
|
+
* to be updated one-by-one.
|
|
58
|
+
*/
|
|
59
|
+
export declare class SyncProgress implements ProgressWithOperations {
|
|
60
|
+
protected internal: InternalProgressInformation;
|
|
61
|
+
totalOperations: number;
|
|
62
|
+
downloadedOperations: number;
|
|
63
|
+
downloadedFraction: number;
|
|
64
|
+
constructor(internal: InternalProgressInformation);
|
|
65
|
+
/**
|
|
66
|
+
* Returns download progress towards all data up until the specified priority being received.
|
|
67
|
+
*
|
|
68
|
+
* The returned {@link ProgressWithOperations} tracks the target amount of operations that need
|
|
69
|
+
* to be downloaded in total and how many of them have already been received.
|
|
70
|
+
*/
|
|
71
|
+
untilPriority(priority: number): ProgressWithOperations;
|
|
72
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @internal The priority used by the core extension to indicate that a full sync was completed.
|
|
3
|
+
*/
|
|
4
|
+
export const FULL_SYNC_PRIORITY = 2147483647;
|
|
5
|
+
/**
|
|
6
|
+
* Provides realtime progress on how PowerSync is downloading rows.
|
|
7
|
+
*
|
|
8
|
+
* The progress until the next complete sync is available through the fields on {@link ProgressWithOperations},
|
|
9
|
+
* which this class implements.
|
|
10
|
+
* Additionally, the {@link SyncProgress.untilPriority} method can be used to otbain progress towards
|
|
11
|
+
* a specific priority (instead of the progress for the entire download).
|
|
12
|
+
*
|
|
13
|
+
* The reported progress always reflects the status towards the end of a sync iteration (after
|
|
14
|
+
* which a consistent snapshot of all buckets is available locally).
|
|
15
|
+
*
|
|
16
|
+
* In rare cases (in particular, when a [compacting](https://docs.powersync.com/usage/lifecycle-maintenance/compacting-buckets)
|
|
17
|
+
* operation takes place between syncs), it's possible for the returned numbers to be slightly
|
|
18
|
+
* inaccurate. For this reason, {@link SyncProgress} should be seen as an approximation of progress.
|
|
19
|
+
* The information returned is good enough to build progress bars, but not exact enough to track
|
|
20
|
+
* individual download counts.
|
|
21
|
+
*
|
|
22
|
+
* Also note that data is downloaded in bulk, which means that individual counters are unlikely
|
|
23
|
+
* to be updated one-by-one.
|
|
24
|
+
*/
|
|
25
|
+
export class SyncProgress {
|
|
26
|
+
internal;
|
|
27
|
+
totalOperations;
|
|
28
|
+
downloadedOperations;
|
|
29
|
+
downloadedFraction;
|
|
30
|
+
constructor(internal) {
|
|
31
|
+
this.internal = internal;
|
|
32
|
+
const untilCompletion = this.untilPriority(FULL_SYNC_PRIORITY);
|
|
33
|
+
this.totalOperations = untilCompletion.totalOperations;
|
|
34
|
+
this.downloadedOperations = untilCompletion.downloadedOperations;
|
|
35
|
+
this.downloadedFraction = untilCompletion.downloadedFraction;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Returns download progress towards all data up until the specified priority being received.
|
|
39
|
+
*
|
|
40
|
+
* The returned {@link ProgressWithOperations} tracks the target amount of operations that need
|
|
41
|
+
* to be downloaded in total and how many of them have already been received.
|
|
42
|
+
*/
|
|
43
|
+
untilPriority(priority) {
|
|
44
|
+
let total = 0;
|
|
45
|
+
let downloaded = 0;
|
|
46
|
+
for (const progress of Object.values(this.internal)) {
|
|
47
|
+
// Include higher-priority buckets, which are represented by lower numbers.
|
|
48
|
+
if (progress.priority <= priority) {
|
|
49
|
+
downloaded += progress.sinceLast;
|
|
50
|
+
total += progress.targetCount - progress.atLast;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
let progress = total == 0 ? 0.0 : downloaded / total;
|
|
54
|
+
return {
|
|
55
|
+
totalOperations: total,
|
|
56
|
+
downloadedOperations: downloaded,
|
|
57
|
+
downloadedFraction: progress
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { InternalProgressInformation, SyncProgress } from './SyncProgress.js';
|
|
1
2
|
export type SyncDataFlowStatus = Partial<{
|
|
2
3
|
downloading: boolean;
|
|
3
4
|
uploading: boolean;
|
|
@@ -12,6 +13,12 @@ export type SyncDataFlowStatus = Partial<{
|
|
|
12
13
|
* Cleared on the next successful upload.
|
|
13
14
|
*/
|
|
14
15
|
uploadError?: Error;
|
|
16
|
+
/**
|
|
17
|
+
* Internal information about how far we are downloading operations in buckets.
|
|
18
|
+
*
|
|
19
|
+
* Please use the {@link SyncStatus#downloadProgress} property to track sync progress.
|
|
20
|
+
*/
|
|
21
|
+
downloadProgress: InternalProgressInformation | null;
|
|
15
22
|
}>;
|
|
16
23
|
export interface SyncPriorityStatus {
|
|
17
24
|
priority: number;
|
|
@@ -77,6 +84,12 @@ export declare class SyncStatus {
|
|
|
77
84
|
* Cleared on the next successful upload.
|
|
78
85
|
*/
|
|
79
86
|
uploadError?: Error;
|
|
87
|
+
/**
|
|
88
|
+
* Internal information about how far we are downloading operations in buckets.
|
|
89
|
+
*
|
|
90
|
+
* Please use the {@link SyncStatus#downloadProgress} property to track sync progress.
|
|
91
|
+
*/
|
|
92
|
+
downloadProgress: InternalProgressInformation | null;
|
|
80
93
|
}>;
|
|
81
94
|
/**
|
|
82
95
|
* Provides sync status information for all bucket priorities, sorted by priority (highest first).
|
|
@@ -85,6 +98,13 @@ export declare class SyncStatus {
|
|
|
85
98
|
* sorted with highest priorities (lower numbers) first.
|
|
86
99
|
*/
|
|
87
100
|
get priorityStatusEntries(): SyncPriorityStatus[];
|
|
101
|
+
/**
|
|
102
|
+
* A realtime progress report on how many operations have been downloaded and
|
|
103
|
+
* how many are necessary in total to complete the next sync iteration.
|
|
104
|
+
*
|
|
105
|
+
* This field is only set when {@link SyncDataFlowStatus#downloading} is also true.
|
|
106
|
+
*/
|
|
107
|
+
get downloadProgress(): SyncProgress | null;
|
|
88
108
|
/**
|
|
89
109
|
* Reports the sync status (a pair of {@link SyncStatus#hasSynced} and {@link SyncStatus#lastSyncedAt} fields)
|
|
90
110
|
* for a specific bucket priority level.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SyncProgress } from './SyncProgress.js';
|
|
1
2
|
export class SyncStatus {
|
|
2
3
|
options;
|
|
3
4
|
constructor(options) {
|
|
@@ -67,6 +68,19 @@ export class SyncStatus {
|
|
|
67
68
|
get priorityStatusEntries() {
|
|
68
69
|
return (this.options.priorityStatusEntries ?? []).slice().sort(SyncStatus.comparePriorities);
|
|
69
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* A realtime progress report on how many operations have been downloaded and
|
|
73
|
+
* how many are necessary in total to complete the next sync iteration.
|
|
74
|
+
*
|
|
75
|
+
* This field is only set when {@link SyncDataFlowStatus#downloading} is also true.
|
|
76
|
+
*/
|
|
77
|
+
get downloadProgress() {
|
|
78
|
+
const internalProgress = this.options.dataFlow?.downloadProgress;
|
|
79
|
+
if (internalProgress == null) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
return new SyncProgress(internalProgress);
|
|
83
|
+
}
|
|
70
84
|
/**
|
|
71
85
|
* Reports the sync status (a pair of {@link SyncStatus#hasSynced} and {@link SyncStatus#lastSyncedAt} fields)
|
|
72
86
|
* for a specific bucket priority level.
|
|
@@ -18,6 +18,10 @@ export declare class Schema<S extends SchemaType = SchemaType> {
|
|
|
18
18
|
view_name: string;
|
|
19
19
|
local_only: boolean;
|
|
20
20
|
insert_only: boolean;
|
|
21
|
+
include_old: any;
|
|
22
|
+
include_old_only_when_changed: boolean;
|
|
23
|
+
include_metadata: boolean;
|
|
24
|
+
ignore_empty_update: boolean;
|
|
21
25
|
columns: {
|
|
22
26
|
name: string;
|
|
23
27
|
type: import("./Column.js").ColumnType | undefined;
|
package/lib/db/schema/Schema.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { Table } from './Table.js';
|
|
2
1
|
/**
|
|
3
2
|
* A schema is a collection of tables. It is used to define the structure of a database.
|
|
4
3
|
*/
|
|
@@ -41,15 +40,7 @@ export class Schema {
|
|
|
41
40
|
}
|
|
42
41
|
convertToClassicTables(props) {
|
|
43
42
|
return Object.entries(props).map(([name, table]) => {
|
|
44
|
-
|
|
45
|
-
name,
|
|
46
|
-
columns: table.columns,
|
|
47
|
-
indexes: table.indexes,
|
|
48
|
-
localOnly: table.localOnly,
|
|
49
|
-
insertOnly: table.insertOnly,
|
|
50
|
-
viewName: table.viewNameOverride || name
|
|
51
|
-
});
|
|
52
|
-
return convertedTable;
|
|
43
|
+
return table.copyWithName(name);
|
|
53
44
|
});
|
|
54
45
|
}
|
|
55
46
|
}
|
package/lib/db/schema/Table.d.ts
CHANGED
|
@@ -1,16 +1,32 @@
|
|
|
1
1
|
import { Column, ColumnsType, ColumnType, ExtractColumnValueType } from './Column.js';
|
|
2
2
|
import { Index } from './Index.js';
|
|
3
3
|
import { TableV2 } from './TableV2.js';
|
|
4
|
-
|
|
4
|
+
interface SharedTableOptions {
|
|
5
|
+
localOnly?: boolean;
|
|
6
|
+
insertOnly?: boolean;
|
|
7
|
+
viewName?: string;
|
|
8
|
+
trackPrevious?: boolean | TrackPreviousOptions;
|
|
9
|
+
trackMetadata?: boolean;
|
|
10
|
+
ignoreEmptyUpdates?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/** Whether to include previous column values when PowerSync tracks local changes.
|
|
13
|
+
*
|
|
14
|
+
* Including old values may be helpful for some backend connector implementations, which is
|
|
15
|
+
* why it can be enabled on per-table or per-columm basis.
|
|
16
|
+
*/
|
|
17
|
+
export interface TrackPreviousOptions {
|
|
18
|
+
/** When defined, a list of column names for which old values should be tracked. */
|
|
19
|
+
columns?: string[];
|
|
20
|
+
/** When enabled, only include values that have actually been changed by an update. */
|
|
21
|
+
onlyWhenChanged?: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface TableOptions extends SharedTableOptions {
|
|
5
24
|
/**
|
|
6
25
|
* The synced table name, matching sync rules
|
|
7
26
|
*/
|
|
8
27
|
name: string;
|
|
9
28
|
columns: Column[];
|
|
10
29
|
indexes?: Index[];
|
|
11
|
-
localOnly?: boolean;
|
|
12
|
-
insertOnly?: boolean;
|
|
13
|
-
viewName?: string;
|
|
14
30
|
}
|
|
15
31
|
export type RowType<T extends TableV2<any>> = {
|
|
16
32
|
[K in keyof T['columnMap']]: ExtractColumnValueType<T['columnMap'][K]>;
|
|
@@ -18,16 +34,16 @@ export type RowType<T extends TableV2<any>> = {
|
|
|
18
34
|
id: string;
|
|
19
35
|
};
|
|
20
36
|
export type IndexShorthand = Record<string, string[]>;
|
|
21
|
-
export interface TableV2Options {
|
|
37
|
+
export interface TableV2Options extends SharedTableOptions {
|
|
22
38
|
indexes?: IndexShorthand;
|
|
23
|
-
localOnly?: boolean;
|
|
24
|
-
insertOnly?: boolean;
|
|
25
|
-
viewName?: string;
|
|
26
39
|
}
|
|
27
40
|
export declare const DEFAULT_TABLE_OPTIONS: {
|
|
28
41
|
indexes: never[];
|
|
29
42
|
insertOnly: boolean;
|
|
30
43
|
localOnly: boolean;
|
|
44
|
+
trackPrevious: boolean;
|
|
45
|
+
trackMetadata: boolean;
|
|
46
|
+
ignoreEmptyUpdates: boolean;
|
|
31
47
|
};
|
|
32
48
|
export declare const InvalidSQLCharacters: RegExp;
|
|
33
49
|
export declare class Table<Columns extends ColumnsType = ColumnsType> {
|
|
@@ -96,9 +112,11 @@ export declare class Table<Columns extends ColumnsType = ColumnsType> {
|
|
|
96
112
|
*```
|
|
97
113
|
*/
|
|
98
114
|
constructor(options: TableOptions);
|
|
115
|
+
copyWithName(name: string): Table;
|
|
99
116
|
private isTableV1;
|
|
100
117
|
private initTableV1;
|
|
101
118
|
private initTableV2;
|
|
119
|
+
private applyDefaultOptions;
|
|
102
120
|
get name(): string;
|
|
103
121
|
get viewNameOverride(): string | undefined;
|
|
104
122
|
get viewName(): string;
|
|
@@ -107,6 +125,9 @@ export declare class Table<Columns extends ColumnsType = ColumnsType> {
|
|
|
107
125
|
get indexes(): Index[];
|
|
108
126
|
get localOnly(): boolean;
|
|
109
127
|
get insertOnly(): boolean;
|
|
128
|
+
get trackPrevious(): boolean | TrackPreviousOptions;
|
|
129
|
+
get trackMetadata(): boolean;
|
|
130
|
+
get ignoreEmptyUpdates(): boolean;
|
|
110
131
|
get internalName(): string;
|
|
111
132
|
get validName(): boolean;
|
|
112
133
|
validate(): void;
|
|
@@ -115,6 +136,10 @@ export declare class Table<Columns extends ColumnsType = ColumnsType> {
|
|
|
115
136
|
view_name: string;
|
|
116
137
|
local_only: boolean;
|
|
117
138
|
insert_only: boolean;
|
|
139
|
+
include_old: any;
|
|
140
|
+
include_old_only_when_changed: boolean;
|
|
141
|
+
include_metadata: boolean;
|
|
142
|
+
ignore_empty_update: boolean;
|
|
118
143
|
columns: {
|
|
119
144
|
name: string;
|
|
120
145
|
type: ColumnType | undefined;
|
|
@@ -129,3 +154,4 @@ export declare class Table<Columns extends ColumnsType = ColumnsType> {
|
|
|
129
154
|
}[];
|
|
130
155
|
};
|
|
131
156
|
}
|
|
157
|
+
export {};
|
package/lib/db/schema/Table.js
CHANGED
|
@@ -4,7 +4,10 @@ import { IndexedColumn } from './IndexedColumn.js';
|
|
|
4
4
|
export const DEFAULT_TABLE_OPTIONS = {
|
|
5
5
|
indexes: [],
|
|
6
6
|
insertOnly: false,
|
|
7
|
-
localOnly: false
|
|
7
|
+
localOnly: false,
|
|
8
|
+
trackPrevious: false,
|
|
9
|
+
trackMetadata: false,
|
|
10
|
+
ignoreEmptyUpdates: false
|
|
8
11
|
};
|
|
9
12
|
export const InvalidSQLCharacters = /["'%,.#\s[\]]/;
|
|
10
13
|
export class Table {
|
|
@@ -41,16 +44,21 @@ export class Table {
|
|
|
41
44
|
this.initTableV2(optionsOrColumns, v2Options);
|
|
42
45
|
}
|
|
43
46
|
}
|
|
47
|
+
copyWithName(name) {
|
|
48
|
+
return new Table({
|
|
49
|
+
...this.options,
|
|
50
|
+
name
|
|
51
|
+
});
|
|
52
|
+
}
|
|
44
53
|
isTableV1(arg) {
|
|
45
54
|
return 'columns' in arg && Array.isArray(arg.columns);
|
|
46
55
|
}
|
|
47
56
|
initTableV1(options) {
|
|
48
57
|
this.options = {
|
|
49
58
|
...options,
|
|
50
|
-
indexes: options.indexes || []
|
|
51
|
-
insertOnly: options.insertOnly ?? DEFAULT_TABLE_OPTIONS.insertOnly,
|
|
52
|
-
localOnly: options.localOnly ?? DEFAULT_TABLE_OPTIONS.localOnly
|
|
59
|
+
indexes: options.indexes || []
|
|
53
60
|
};
|
|
61
|
+
this.applyDefaultOptions();
|
|
54
62
|
}
|
|
55
63
|
initTableV2(columns, options) {
|
|
56
64
|
const convertedColumns = Object.entries(columns).map(([name, columnInfo]) => new Column({ name, type: columnInfo.type }));
|
|
@@ -65,12 +73,23 @@ export class Table {
|
|
|
65
73
|
name: '',
|
|
66
74
|
columns: convertedColumns,
|
|
67
75
|
indexes: convertedIndexes,
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
76
|
+
viewName: options?.viewName,
|
|
77
|
+
insertOnly: options?.insertOnly,
|
|
78
|
+
localOnly: options?.localOnly,
|
|
79
|
+
trackPrevious: options?.trackPrevious,
|
|
80
|
+
trackMetadata: options?.trackMetadata,
|
|
81
|
+
ignoreEmptyUpdates: options?.ignoreEmptyUpdates
|
|
71
82
|
};
|
|
83
|
+
this.applyDefaultOptions();
|
|
72
84
|
this._mappedColumns = columns;
|
|
73
85
|
}
|
|
86
|
+
applyDefaultOptions() {
|
|
87
|
+
this.options.insertOnly ??= DEFAULT_TABLE_OPTIONS.insertOnly;
|
|
88
|
+
this.options.localOnly ??= DEFAULT_TABLE_OPTIONS.localOnly;
|
|
89
|
+
this.options.trackPrevious ??= DEFAULT_TABLE_OPTIONS.trackPrevious;
|
|
90
|
+
this.options.trackMetadata ??= DEFAULT_TABLE_OPTIONS.trackMetadata;
|
|
91
|
+
this.options.ignoreEmptyUpdates ??= DEFAULT_TABLE_OPTIONS.ignoreEmptyUpdates;
|
|
92
|
+
}
|
|
74
93
|
get name() {
|
|
75
94
|
return this.options.name;
|
|
76
95
|
}
|
|
@@ -94,10 +113,19 @@ export class Table {
|
|
|
94
113
|
return this.options.indexes ?? [];
|
|
95
114
|
}
|
|
96
115
|
get localOnly() {
|
|
97
|
-
return this.options.localOnly
|
|
116
|
+
return this.options.localOnly;
|
|
98
117
|
}
|
|
99
118
|
get insertOnly() {
|
|
100
|
-
return this.options.insertOnly
|
|
119
|
+
return this.options.insertOnly;
|
|
120
|
+
}
|
|
121
|
+
get trackPrevious() {
|
|
122
|
+
return this.options.trackPrevious;
|
|
123
|
+
}
|
|
124
|
+
get trackMetadata() {
|
|
125
|
+
return this.options.trackMetadata;
|
|
126
|
+
}
|
|
127
|
+
get ignoreEmptyUpdates() {
|
|
128
|
+
return this.options.ignoreEmptyUpdates;
|
|
101
129
|
}
|
|
102
130
|
get internalName() {
|
|
103
131
|
if (this.options.localOnly) {
|
|
@@ -124,6 +152,12 @@ export class Table {
|
|
|
124
152
|
if (this.columns.length > MAX_AMOUNT_OF_COLUMNS) {
|
|
125
153
|
throw new Error(`Table has too many columns. The maximum number of columns is ${MAX_AMOUNT_OF_COLUMNS}.`);
|
|
126
154
|
}
|
|
155
|
+
if (this.trackMetadata && this.localOnly) {
|
|
156
|
+
throw new Error(`Can't include metadata for local-only tables.`);
|
|
157
|
+
}
|
|
158
|
+
if (this.trackPrevious != false && this.localOnly) {
|
|
159
|
+
throw new Error(`Can't include old values for local-only tables.`);
|
|
160
|
+
}
|
|
127
161
|
const columnNames = new Set();
|
|
128
162
|
columnNames.add('id');
|
|
129
163
|
for (const column of this.columns) {
|
|
@@ -156,11 +190,16 @@ export class Table {
|
|
|
156
190
|
}
|
|
157
191
|
}
|
|
158
192
|
toJSON() {
|
|
193
|
+
const trackPrevious = this.trackPrevious;
|
|
159
194
|
return {
|
|
160
195
|
name: this.name,
|
|
161
196
|
view_name: this.viewName,
|
|
162
197
|
local_only: this.localOnly,
|
|
163
198
|
insert_only: this.insertOnly,
|
|
199
|
+
include_old: trackPrevious && (trackPrevious.columns ?? true),
|
|
200
|
+
include_old_only_when_changed: typeof trackPrevious == 'object' && trackPrevious.onlyWhenChanged == true,
|
|
201
|
+
include_metadata: this.trackMetadata,
|
|
202
|
+
ignore_empty_update: this.ignoreEmptyUpdates,
|
|
164
203
|
columns: this.columns.map((c) => c.toJSON()),
|
|
165
204
|
indexes: this.indexes.map((e) => e.toJSON(this))
|
|
166
205
|
};
|
package/lib/index.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ export * from './client/sync/stream/AbstractRemote.js';
|
|
|
18
18
|
export * from './client/sync/stream/AbstractStreamingSyncImplementation.js';
|
|
19
19
|
export * from './client/sync/stream/streaming-sync-types.js';
|
|
20
20
|
export { MAX_OP_ID } from './client/constants.js';
|
|
21
|
+
export { ProgressWithOperations, SyncProgress } from './db/crud/SyncProgress.js';
|
|
21
22
|
export * from './db/crud/SyncStatus.js';
|
|
22
23
|
export * from './db/crud/UploadQueueStatus.js';
|
|
23
24
|
export * from './db/schema/Schema.js';
|
package/lib/index.js
CHANGED
|
@@ -18,6 +18,7 @@ export * from './client/sync/stream/AbstractRemote.js';
|
|
|
18
18
|
export * from './client/sync/stream/AbstractStreamingSyncImplementation.js';
|
|
19
19
|
export * from './client/sync/stream/streaming-sync-types.js';
|
|
20
20
|
export { MAX_OP_ID } from './client/constants.js';
|
|
21
|
+
export { SyncProgress } from './db/crud/SyncProgress.js';
|
|
21
22
|
export * from './db/crud/SyncStatus.js';
|
|
22
23
|
export * from './db/crud/UploadQueueStatus.js';
|
|
23
24
|
export * from './db/schema/Schema.js';
|