@powersync/common 1.33.1 → 1.34.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.
- package/dist/bundle.cjs +3 -3
- package/dist/bundle.mjs +3 -3
- package/lib/client/AbstractPowerSyncDatabase.d.ts +2 -8
- package/lib/client/AbstractPowerSyncDatabase.js +11 -21
- package/lib/client/ConnectionManager.d.ts +4 -4
- package/lib/client/sync/bucket/SqliteBucketStorage.d.ts +1 -3
- package/lib/client/sync/bucket/SqliteBucketStorage.js +15 -17
- package/lib/client/sync/stream/AbstractRemote.d.ts +1 -10
- package/lib/client/sync/stream/AbstractRemote.js +0 -16
- package/lib/client/sync/stream/AbstractStreamingSyncImplementation.d.ts +10 -6
- package/lib/client/sync/stream/AbstractStreamingSyncImplementation.js +97 -79
- package/lib/client/sync/stream/streaming-sync-types.d.ts +4 -0
- package/lib/db/crud/SyncProgress.d.ts +1 -1
- package/lib/db/schema/RawTable.d.ts +61 -0
- package/lib/db/schema/RawTable.js +32 -0
- package/lib/db/schema/Schema.d.ts +14 -0
- package/lib/db/schema/Schema.js +20 -1
- package/lib/utils/async.d.ts +0 -1
- package/lib/utils/async.js +0 -10
- package/package.json +1 -1
|
@@ -3,7 +3,7 @@ import { SyncStatus } from '../../../db/crud/SyncStatus.js';
|
|
|
3
3
|
import { FULL_SYNC_PRIORITY } from '../../../db/crud/SyncProgress.js';
|
|
4
4
|
import { AbortOperation } from '../../../utils/AbortOperation.js';
|
|
5
5
|
import { BaseObserver } from '../../../utils/BaseObserver.js';
|
|
6
|
-
import {
|
|
6
|
+
import { throttleLeadingTrailing } from '../../../utils/async.js';
|
|
7
7
|
import { PowerSyncControlCommand } from '../bucket/BucketStorageAdapter.js';
|
|
8
8
|
import { SyncDataBucket } from '../bucket/SyncDataBucket.js';
|
|
9
9
|
import { FetchStrategy } from './AbstractRemote.js';
|
|
@@ -65,14 +65,14 @@ export const DEFAULT_CRUD_UPLOAD_THROTTLE_MS = 1000;
|
|
|
65
65
|
export const DEFAULT_RETRY_DELAY_MS = 5000;
|
|
66
66
|
export const DEFAULT_STREAMING_SYNC_OPTIONS = {
|
|
67
67
|
retryDelayMs: DEFAULT_RETRY_DELAY_MS,
|
|
68
|
-
logger: Logger.get('PowerSyncStream'),
|
|
69
68
|
crudUploadThrottleMs: DEFAULT_CRUD_UPLOAD_THROTTLE_MS
|
|
70
69
|
};
|
|
71
70
|
export const DEFAULT_STREAM_CONNECTION_OPTIONS = {
|
|
72
71
|
connectionMethod: SyncStreamConnectionMethod.WEB_SOCKET,
|
|
73
72
|
clientImplementation: DEFAULT_SYNC_CLIENT_IMPLEMENTATION,
|
|
74
73
|
fetchStrategy: FetchStrategy.Buffered,
|
|
75
|
-
params: {}
|
|
74
|
+
params: {},
|
|
75
|
+
serializedSchema: undefined
|
|
76
76
|
};
|
|
77
77
|
// The priority we assume when we receive checkpoint lines where no priority is set.
|
|
78
78
|
// This is the default priority used by the sync service, but can be set to an arbitrary
|
|
@@ -85,13 +85,15 @@ export class AbstractStreamingSyncImplementation extends BaseObserver {
|
|
|
85
85
|
abortController;
|
|
86
86
|
crudUpdateListener;
|
|
87
87
|
streamingSyncPromise;
|
|
88
|
-
|
|
88
|
+
logger;
|
|
89
|
+
isUploadingCrud = false;
|
|
89
90
|
notifyCompletedUploads;
|
|
90
91
|
syncStatus;
|
|
91
92
|
triggerCrudUpload;
|
|
92
93
|
constructor(options) {
|
|
93
94
|
super();
|
|
94
95
|
this.options = { ...DEFAULT_STREAMING_SYNC_OPTIONS, ...options };
|
|
96
|
+
this.logger = options.logger ?? Logger.get('PowerSyncStream');
|
|
95
97
|
this.syncStatus = new SyncStatus({
|
|
96
98
|
connected: false,
|
|
97
99
|
connecting: false,
|
|
@@ -103,15 +105,13 @@ export class AbstractStreamingSyncImplementation extends BaseObserver {
|
|
|
103
105
|
});
|
|
104
106
|
this.abortController = null;
|
|
105
107
|
this.triggerCrudUpload = throttleLeadingTrailing(() => {
|
|
106
|
-
if (!this.syncStatus.connected || this.
|
|
108
|
+
if (!this.syncStatus.connected || this.isUploadingCrud) {
|
|
107
109
|
return;
|
|
108
110
|
}
|
|
109
|
-
this.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
resolve();
|
|
114
|
-
});
|
|
111
|
+
this.isUploadingCrud = true;
|
|
112
|
+
this._uploadAllCrud().finally(() => {
|
|
113
|
+
this.notifyCompletedUploads?.();
|
|
114
|
+
this.isUploadingCrud = false;
|
|
115
115
|
});
|
|
116
116
|
}, this.options.crudUploadThrottleMs);
|
|
117
117
|
}
|
|
@@ -157,9 +157,6 @@ export class AbstractStreamingSyncImplementation extends BaseObserver {
|
|
|
157
157
|
get isConnected() {
|
|
158
158
|
return this.syncStatus.connected;
|
|
159
159
|
}
|
|
160
|
-
get logger() {
|
|
161
|
-
return this.options.logger;
|
|
162
|
-
}
|
|
163
160
|
async dispose() {
|
|
164
161
|
this.crudUpdateListener?.();
|
|
165
162
|
this.crudUpdateListener = undefined;
|
|
@@ -171,7 +168,9 @@ export class AbstractStreamingSyncImplementation extends BaseObserver {
|
|
|
171
168
|
const clientId = await this.options.adapter.getClientId();
|
|
172
169
|
let path = `/write-checkpoint2.json?client_id=${clientId}`;
|
|
173
170
|
const response = await this.options.remote.get(path);
|
|
174
|
-
|
|
171
|
+
const checkpoint = response['data']['write_checkpoint'];
|
|
172
|
+
this.logger.debug(`Created write checkpoint: ${checkpoint}`);
|
|
173
|
+
return checkpoint;
|
|
175
174
|
}
|
|
176
175
|
async _uploadAllCrud() {
|
|
177
176
|
return this.obtainLock({
|
|
@@ -210,7 +209,11 @@ The next upload iteration will be delayed.`);
|
|
|
210
209
|
}
|
|
211
210
|
else {
|
|
212
211
|
// Uploading is completed
|
|
213
|
-
await this.options.adapter.updateLocalTarget(() => this.getWriteCheckpoint());
|
|
212
|
+
const neededUpdate = await this.options.adapter.updateLocalTarget(() => this.getWriteCheckpoint());
|
|
213
|
+
if (neededUpdate == false && checkedCrudItem != null) {
|
|
214
|
+
// Only log this if there was something to upload
|
|
215
|
+
this.logger.debug('Upload complete, no write checkpoint needed.');
|
|
216
|
+
}
|
|
214
217
|
break;
|
|
215
218
|
}
|
|
216
219
|
}
|
|
@@ -361,6 +364,7 @@ The next upload iteration will be delayed.`);
|
|
|
361
364
|
});
|
|
362
365
|
}
|
|
363
366
|
finally {
|
|
367
|
+
this.notifyCompletedUploads = undefined;
|
|
364
368
|
if (!signal.aborted) {
|
|
365
369
|
nestedAbortController.abort(new AbortOperation('Closing sync stream network requests before retry.'));
|
|
366
370
|
nestedAbortController = new AbortController();
|
|
@@ -435,13 +439,15 @@ The next upload iteration will be delayed.`);
|
|
|
435
439
|
});
|
|
436
440
|
}
|
|
437
441
|
async legacyStreamingSyncIteration(signal, resolvedOptions) {
|
|
442
|
+
if (resolvedOptions.serializedSchema?.raw_tables != null) {
|
|
443
|
+
this.logger.warn('Raw tables require the Rust-based sync client. The JS client will ignore them.');
|
|
444
|
+
}
|
|
438
445
|
this.logger.debug('Streaming sync iteration started');
|
|
439
446
|
this.options.adapter.startSession();
|
|
440
447
|
let [req, bucketMap] = await this.collectLocalBucketState();
|
|
441
|
-
// These are compared by reference
|
|
442
448
|
let targetCheckpoint = null;
|
|
443
|
-
|
|
444
|
-
let
|
|
449
|
+
// A checkpoint that has been validated but not applied (e.g. due to pending local writes)
|
|
450
|
+
let pendingValidatedCheckpoint = null;
|
|
445
451
|
const clientId = await this.options.adapter.getClientId();
|
|
446
452
|
const usingFixedKeyFormat = await this.requireKeyFormat(false);
|
|
447
453
|
this.logger.debug('Requesting stream from server');
|
|
@@ -458,21 +464,55 @@ The next upload iteration will be delayed.`);
|
|
|
458
464
|
};
|
|
459
465
|
let stream;
|
|
460
466
|
if (resolvedOptions?.connectionMethod == SyncStreamConnectionMethod.HTTP) {
|
|
461
|
-
stream = await this.options.remote.
|
|
467
|
+
stream = await this.options.remote.postStreamRaw(syncOptions, (line) => {
|
|
468
|
+
if (typeof line == 'string') {
|
|
469
|
+
return JSON.parse(line);
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
// Directly enqueued by us
|
|
473
|
+
return line;
|
|
474
|
+
}
|
|
475
|
+
});
|
|
462
476
|
}
|
|
463
477
|
else {
|
|
464
|
-
|
|
478
|
+
const bson = await this.options.remote.getBSON();
|
|
479
|
+
stream = await this.options.remote.socketStreamRaw({
|
|
465
480
|
...syncOptions,
|
|
466
481
|
...{ fetchStrategy: resolvedOptions.fetchStrategy }
|
|
467
|
-
})
|
|
482
|
+
}, (payload) => {
|
|
483
|
+
if (payload instanceof Uint8Array) {
|
|
484
|
+
return bson.deserialize(payload);
|
|
485
|
+
}
|
|
486
|
+
else {
|
|
487
|
+
// Directly enqueued by us
|
|
488
|
+
return payload;
|
|
489
|
+
}
|
|
490
|
+
}, bson);
|
|
468
491
|
}
|
|
469
492
|
this.logger.debug('Stream established. Processing events');
|
|
493
|
+
this.notifyCompletedUploads = () => {
|
|
494
|
+
if (!stream.closed) {
|
|
495
|
+
stream.enqueueData({ crud_upload_completed: null });
|
|
496
|
+
}
|
|
497
|
+
};
|
|
470
498
|
while (!stream.closed) {
|
|
471
499
|
const line = await stream.read();
|
|
472
500
|
if (!line) {
|
|
473
501
|
// The stream has closed while waiting
|
|
474
502
|
return;
|
|
475
503
|
}
|
|
504
|
+
if ('crud_upload_completed' in line) {
|
|
505
|
+
if (pendingValidatedCheckpoint != null) {
|
|
506
|
+
const { applied, endIteration } = await this.applyCheckpoint(pendingValidatedCheckpoint);
|
|
507
|
+
if (applied) {
|
|
508
|
+
pendingValidatedCheckpoint = null;
|
|
509
|
+
}
|
|
510
|
+
else if (endIteration) {
|
|
511
|
+
break;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
476
516
|
// A connection is active and messages are being received
|
|
477
517
|
if (!this.syncStatus.connected) {
|
|
478
518
|
// There is a connection now
|
|
@@ -483,6 +523,8 @@ The next upload iteration will be delayed.`);
|
|
|
483
523
|
}
|
|
484
524
|
if (isStreamingSyncCheckpoint(line)) {
|
|
485
525
|
targetCheckpoint = line.checkpoint;
|
|
526
|
+
// New checkpoint - existing validated checkpoint is no longer valid
|
|
527
|
+
pendingValidatedCheckpoint = null;
|
|
486
528
|
const bucketsToDelete = new Set(bucketMap.keys());
|
|
487
529
|
const newBuckets = new Map();
|
|
488
530
|
for (const checksum of line.checkpoint.buckets) {
|
|
@@ -501,14 +543,20 @@ The next upload iteration will be delayed.`);
|
|
|
501
543
|
await this.updateSyncStatusForStartingCheckpoint(targetCheckpoint);
|
|
502
544
|
}
|
|
503
545
|
else if (isStreamingSyncCheckpointComplete(line)) {
|
|
504
|
-
const result = await this.applyCheckpoint(targetCheckpoint
|
|
546
|
+
const result = await this.applyCheckpoint(targetCheckpoint);
|
|
505
547
|
if (result.endIteration) {
|
|
506
548
|
return;
|
|
507
549
|
}
|
|
508
|
-
else if (result.applied) {
|
|
509
|
-
|
|
550
|
+
else if (!result.applied) {
|
|
551
|
+
// "Could not apply checkpoint due to local data". We need to retry after
|
|
552
|
+
// finishing uploads.
|
|
553
|
+
pendingValidatedCheckpoint = targetCheckpoint;
|
|
554
|
+
}
|
|
555
|
+
else {
|
|
556
|
+
// Nothing to retry later. This would likely already be null from the last
|
|
557
|
+
// checksum or checksum_diff operation, but we make sure.
|
|
558
|
+
pendingValidatedCheckpoint = null;
|
|
510
559
|
}
|
|
511
|
-
validatedCheckpoint = targetCheckpoint;
|
|
512
560
|
}
|
|
513
561
|
else if (isStreamingSyncCheckpointPartiallyComplete(line)) {
|
|
514
562
|
const priority = line.partial_checkpoint_complete.priority;
|
|
@@ -545,6 +593,8 @@ The next upload iteration will be delayed.`);
|
|
|
545
593
|
if (targetCheckpoint == null) {
|
|
546
594
|
throw new Error('Checkpoint diff without previous checkpoint');
|
|
547
595
|
}
|
|
596
|
+
// New checkpoint - existing validated checkpoint is no longer valid
|
|
597
|
+
pendingValidatedCheckpoint = null;
|
|
548
598
|
const diff = line.checkpoint_diff;
|
|
549
599
|
const newBuckets = new Map();
|
|
550
600
|
for (const checksum of targetCheckpoint.buckets) {
|
|
@@ -618,26 +668,7 @@ The next upload iteration will be delayed.`);
|
|
|
618
668
|
this.triggerCrudUpload();
|
|
619
669
|
}
|
|
620
670
|
else {
|
|
621
|
-
this.logger.debug('
|
|
622
|
-
if (targetCheckpoint === appliedCheckpoint) {
|
|
623
|
-
this.updateSyncStatus({
|
|
624
|
-
connected: true,
|
|
625
|
-
lastSyncedAt: new Date(),
|
|
626
|
-
priorityStatusEntries: [],
|
|
627
|
-
dataFlow: {
|
|
628
|
-
downloadError: undefined
|
|
629
|
-
}
|
|
630
|
-
});
|
|
631
|
-
}
|
|
632
|
-
else if (validatedCheckpoint === targetCheckpoint) {
|
|
633
|
-
const result = await this.applyCheckpoint(targetCheckpoint, signal);
|
|
634
|
-
if (result.endIteration) {
|
|
635
|
-
return;
|
|
636
|
-
}
|
|
637
|
-
else if (result.applied) {
|
|
638
|
-
appliedCheckpoint = targetCheckpoint;
|
|
639
|
-
}
|
|
640
|
-
}
|
|
671
|
+
this.logger.debug('Received unknown sync line', line);
|
|
641
672
|
}
|
|
642
673
|
}
|
|
643
674
|
this.logger.debug('Stream input empty');
|
|
@@ -803,9 +834,11 @@ The next upload iteration will be delayed.`);
|
|
|
803
834
|
}
|
|
804
835
|
}
|
|
805
836
|
try {
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
837
|
+
const options = { parameters: resolvedOptions.params };
|
|
838
|
+
if (resolvedOptions.serializedSchema) {
|
|
839
|
+
options.schema = resolvedOptions.serializedSchema;
|
|
840
|
+
}
|
|
841
|
+
await control(PowerSyncControlCommand.START, JSON.stringify(options));
|
|
809
842
|
this.notifyCompletedUploads = () => {
|
|
810
843
|
controlInvocations?.enqueueData({ command: PowerSyncControlCommand.NOTIFY_CRUD_UPLOAD_COMPLETED });
|
|
811
844
|
};
|
|
@@ -853,45 +886,30 @@ The next upload iteration will be delayed.`);
|
|
|
853
886
|
}
|
|
854
887
|
});
|
|
855
888
|
}
|
|
856
|
-
async applyCheckpoint(checkpoint
|
|
889
|
+
async applyCheckpoint(checkpoint) {
|
|
857
890
|
let result = await this.options.adapter.syncLocalDatabase(checkpoint);
|
|
858
|
-
const pending = this.pendingCrudUpload;
|
|
859
891
|
if (!result.checkpointValid) {
|
|
860
|
-
this.logger.debug(
|
|
892
|
+
this.logger.debug(`Checksum mismatch in checkpoint ${checkpoint.last_op_id}, will reconnect`);
|
|
861
893
|
// This means checksums failed. Start again with a new checkpoint.
|
|
862
894
|
// TODO: better back-off
|
|
863
895
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
864
896
|
return { applied: false, endIteration: true };
|
|
865
897
|
}
|
|
866
|
-
else if (!result.ready
|
|
867
|
-
|
|
868
|
-
// checkpoint, which prevented this checkpoint from applying. Wait for that to complete and
|
|
869
|
-
// try again.
|
|
870
|
-
this.logger.debug('Could not apply checkpoint due to local data. Waiting for in-progress upload before retrying.');
|
|
871
|
-
await Promise.race([pending, onAbortPromise(abort)]);
|
|
872
|
-
if (abort.aborted) {
|
|
873
|
-
return { applied: false, endIteration: true };
|
|
874
|
-
}
|
|
875
|
-
// Try again now that uploads have completed.
|
|
876
|
-
result = await this.options.adapter.syncLocalDatabase(checkpoint);
|
|
877
|
-
}
|
|
878
|
-
if (result.checkpointValid && result.ready) {
|
|
879
|
-
this.logger.debug('validated checkpoint', checkpoint);
|
|
880
|
-
this.updateSyncStatus({
|
|
881
|
-
connected: true,
|
|
882
|
-
lastSyncedAt: new Date(),
|
|
883
|
-
dataFlow: {
|
|
884
|
-
downloading: false,
|
|
885
|
-
downloadProgress: null,
|
|
886
|
-
downloadError: undefined
|
|
887
|
-
}
|
|
888
|
-
});
|
|
889
|
-
return { applied: true, endIteration: false };
|
|
890
|
-
}
|
|
891
|
-
else {
|
|
892
|
-
this.logger.debug('Could not apply checkpoint. Waiting for next sync complete line.');
|
|
898
|
+
else if (!result.ready) {
|
|
899
|
+
this.logger.debug(`Could not apply checkpoint ${checkpoint.last_op_id} due to local data. We will retry applying the checkpoint after that upload is completed.`);
|
|
893
900
|
return { applied: false, endIteration: false };
|
|
894
901
|
}
|
|
902
|
+
this.logger.debug(`Applied checkpoint ${checkpoint.last_op_id}`, checkpoint);
|
|
903
|
+
this.updateSyncStatus({
|
|
904
|
+
connected: true,
|
|
905
|
+
lastSyncedAt: new Date(),
|
|
906
|
+
dataFlow: {
|
|
907
|
+
downloading: false,
|
|
908
|
+
downloadProgress: null,
|
|
909
|
+
downloadError: undefined
|
|
910
|
+
}
|
|
911
|
+
});
|
|
912
|
+
return { applied: true, endIteration: false };
|
|
895
913
|
}
|
|
896
914
|
updateSyncStatus(options) {
|
|
897
915
|
const updatedStatus = new SyncStatus({
|
|
@@ -101,6 +101,10 @@ export interface StreamingSyncKeepalive {
|
|
|
101
101
|
token_expires_in: number;
|
|
102
102
|
}
|
|
103
103
|
export type StreamingSyncLine = StreamingSyncDataJSON | StreamingSyncCheckpoint | StreamingSyncCheckpointDiff | StreamingSyncCheckpointComplete | StreamingSyncCheckpointPartiallyComplete | StreamingSyncKeepalive;
|
|
104
|
+
export type CrudUploadNotification = {
|
|
105
|
+
crud_upload_completed: null;
|
|
106
|
+
};
|
|
107
|
+
export type StreamingSyncLineOrCrudUploadComplete = StreamingSyncLine | CrudUploadNotification;
|
|
104
108
|
export interface BucketRequest {
|
|
105
109
|
name: string;
|
|
106
110
|
/**
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A pending variant of a {@link RawTable} that doesn't have a name (because it would be inferred when creating the
|
|
3
|
+
* schema).
|
|
4
|
+
*/
|
|
5
|
+
export type RawTableType = {
|
|
6
|
+
/**
|
|
7
|
+
* The statement to run when PowerSync detects that a row needs to be inserted or updated.
|
|
8
|
+
*/
|
|
9
|
+
put: PendingStatement;
|
|
10
|
+
/**
|
|
11
|
+
* The statement to run when PowerSync detects that a row needs to be deleted.
|
|
12
|
+
*/
|
|
13
|
+
delete: PendingStatement;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* A parameter to use as part of {@link PendingStatement}.
|
|
17
|
+
*
|
|
18
|
+
* For delete statements, only the `"Id"` value is supported - the sync client will replace it with the id of the row to
|
|
19
|
+
* be synced.
|
|
20
|
+
*
|
|
21
|
+
* For insert and replace operations, the values of columns in the table are available as parameters through
|
|
22
|
+
* `{Column: 'name'}`.
|
|
23
|
+
*/
|
|
24
|
+
export type PendingStatementParameter = 'Id' | {
|
|
25
|
+
Column: string;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* A statement that the PowerSync client should use to insert or delete data into a table managed by the user.
|
|
29
|
+
*/
|
|
30
|
+
export type PendingStatement = {
|
|
31
|
+
sql: string;
|
|
32
|
+
params: PendingStatementParameter[];
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Instructs PowerSync to sync data into a "raw" table.
|
|
36
|
+
*
|
|
37
|
+
* Since raw tables are not backed by JSON, running complex queries on them may be more efficient. Further, they allow
|
|
38
|
+
* using client-side table and column constraints.
|
|
39
|
+
*
|
|
40
|
+
* To collect local writes to raw tables with PowerSync, custom triggers are required. See
|
|
41
|
+
* {@link https://docs.powersync.com/usage/use-case-examples/raw-tables the documentation} for details and an example on
|
|
42
|
+
* using raw tables.
|
|
43
|
+
*
|
|
44
|
+
* Note that raw tables are only supported when using the new `SyncClientImplementation.rust` sync client.
|
|
45
|
+
*
|
|
46
|
+
* @experimental Please note that this feature is experimental at the moment, and not covered by PowerSync semver or
|
|
47
|
+
* stability guarantees.
|
|
48
|
+
*/
|
|
49
|
+
export declare class RawTable implements RawTableType {
|
|
50
|
+
/**
|
|
51
|
+
* The name of the table.
|
|
52
|
+
*
|
|
53
|
+
* This does not have to match the actual table name in the schema - {@link put} and {@link delete} are free to use
|
|
54
|
+
* another table. Instead, this name is used by the sync client to recognize that operations on this table (as it
|
|
55
|
+
* appears in the source / backend database) are to be handled specially.
|
|
56
|
+
*/
|
|
57
|
+
name: string;
|
|
58
|
+
put: PendingStatement;
|
|
59
|
+
delete: PendingStatement;
|
|
60
|
+
constructor(name: string, type: RawTableType);
|
|
61
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Instructs PowerSync to sync data into a "raw" table.
|
|
3
|
+
*
|
|
4
|
+
* Since raw tables are not backed by JSON, running complex queries on them may be more efficient. Further, they allow
|
|
5
|
+
* using client-side table and column constraints.
|
|
6
|
+
*
|
|
7
|
+
* To collect local writes to raw tables with PowerSync, custom triggers are required. See
|
|
8
|
+
* {@link https://docs.powersync.com/usage/use-case-examples/raw-tables the documentation} for details and an example on
|
|
9
|
+
* using raw tables.
|
|
10
|
+
*
|
|
11
|
+
* Note that raw tables are only supported when using the new `SyncClientImplementation.rust` sync client.
|
|
12
|
+
*
|
|
13
|
+
* @experimental Please note that this feature is experimental at the moment, and not covered by PowerSync semver or
|
|
14
|
+
* stability guarantees.
|
|
15
|
+
*/
|
|
16
|
+
export class RawTable {
|
|
17
|
+
/**
|
|
18
|
+
* The name of the table.
|
|
19
|
+
*
|
|
20
|
+
* This does not have to match the actual table name in the schema - {@link put} and {@link delete} are free to use
|
|
21
|
+
* another table. Instead, this name is used by the sync client to recognize that operations on this table (as it
|
|
22
|
+
* appears in the source / backend database) are to be handled specially.
|
|
23
|
+
*/
|
|
24
|
+
name;
|
|
25
|
+
put;
|
|
26
|
+
delete;
|
|
27
|
+
constructor(name, type) {
|
|
28
|
+
this.name = name;
|
|
29
|
+
this.put = type.put;
|
|
30
|
+
this.delete = type.delete;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { RawTable, RawTableType } from './RawTable.js';
|
|
1
2
|
import { RowType, Table } from './Table.js';
|
|
2
3
|
type SchemaType = Record<string, Table<any>>;
|
|
3
4
|
export type SchemaTableType<S extends SchemaType> = {
|
|
@@ -10,7 +11,19 @@ export declare class Schema<S extends SchemaType = SchemaType> {
|
|
|
10
11
|
readonly types: SchemaTableType<S>;
|
|
11
12
|
readonly props: S;
|
|
12
13
|
readonly tables: Table[];
|
|
14
|
+
readonly rawTables: RawTable[];
|
|
13
15
|
constructor(tables: Table[] | S);
|
|
16
|
+
/**
|
|
17
|
+
* Adds raw tables to this schema. Raw tables are identified by their name, but entirely managed by the application
|
|
18
|
+
* developer instead of automatically by PowerSync.
|
|
19
|
+
* Since raw tables are not backed by JSON, running complex queries on them may be more efficient. Further, they allow
|
|
20
|
+
* using client-side table and column constraints.
|
|
21
|
+
* Note that raw tables are only supported when using the new `SyncClientImplementation.rust` sync client.
|
|
22
|
+
*
|
|
23
|
+
* @param tables An object of (table name, raw table definition) entries.
|
|
24
|
+
* @experimental Note that the raw tables API is still experimental and may change in the future.
|
|
25
|
+
*/
|
|
26
|
+
withRawTables(tables: Record<string, RawTableType>): void;
|
|
14
27
|
validate(): void;
|
|
15
28
|
toJSON(): {
|
|
16
29
|
tables: {
|
|
@@ -35,6 +48,7 @@ export declare class Schema<S extends SchemaType = SchemaType> {
|
|
|
35
48
|
}[];
|
|
36
49
|
}[];
|
|
37
50
|
}[];
|
|
51
|
+
raw_tables: RawTable[];
|
|
38
52
|
};
|
|
39
53
|
private convertToClassicTables;
|
|
40
54
|
}
|
package/lib/db/schema/Schema.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { RawTable } from './RawTable.js';
|
|
1
2
|
/**
|
|
2
3
|
* A schema is a collection of tables. It is used to define the structure of a database.
|
|
3
4
|
*/
|
|
@@ -8,6 +9,7 @@ export class Schema {
|
|
|
8
9
|
types;
|
|
9
10
|
props;
|
|
10
11
|
tables;
|
|
12
|
+
rawTables;
|
|
11
13
|
constructor(tables) {
|
|
12
14
|
if (Array.isArray(tables)) {
|
|
13
15
|
/*
|
|
@@ -26,6 +28,22 @@ export class Schema {
|
|
|
26
28
|
this.props = tables;
|
|
27
29
|
this.tables = this.convertToClassicTables(this.props);
|
|
28
30
|
}
|
|
31
|
+
this.rawTables = [];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Adds raw tables to this schema. Raw tables are identified by their name, but entirely managed by the application
|
|
35
|
+
* developer instead of automatically by PowerSync.
|
|
36
|
+
* Since raw tables are not backed by JSON, running complex queries on them may be more efficient. Further, they allow
|
|
37
|
+
* using client-side table and column constraints.
|
|
38
|
+
* Note that raw tables are only supported when using the new `SyncClientImplementation.rust` sync client.
|
|
39
|
+
*
|
|
40
|
+
* @param tables An object of (table name, raw table definition) entries.
|
|
41
|
+
* @experimental Note that the raw tables API is still experimental and may change in the future.
|
|
42
|
+
*/
|
|
43
|
+
withRawTables(tables) {
|
|
44
|
+
for (const [name, rawTableDefinition] of Object.entries(tables)) {
|
|
45
|
+
this.rawTables.push(new RawTable(name, rawTableDefinition));
|
|
46
|
+
}
|
|
29
47
|
}
|
|
30
48
|
validate() {
|
|
31
49
|
for (const table of this.tables) {
|
|
@@ -35,7 +53,8 @@ export class Schema {
|
|
|
35
53
|
toJSON() {
|
|
36
54
|
return {
|
|
37
55
|
// This is required because "name" field is not present in TableV2
|
|
38
|
-
tables: this.tables.map((t) => t.toJSON())
|
|
56
|
+
tables: this.tables.map((t) => t.toJSON()),
|
|
57
|
+
raw_tables: this.rawTables
|
|
39
58
|
};
|
|
40
59
|
}
|
|
41
60
|
convertToClassicTables(props) {
|
package/lib/utils/async.d.ts
CHANGED
|
@@ -12,4 +12,3 @@ export declare function throttleTrailing(func: () => void, wait: number): () =>
|
|
|
12
12
|
* Roughly equivalent to lodash/throttle with {leading: true, trailing: true}
|
|
13
13
|
*/
|
|
14
14
|
export declare function throttleLeadingTrailing(func: () => void, wait: number): () => void;
|
|
15
|
-
export declare function onAbortPromise(signal: AbortSignal): Promise<void>;
|
package/lib/utils/async.js
CHANGED
|
@@ -43,13 +43,3 @@ export function throttleLeadingTrailing(func, wait) {
|
|
|
43
43
|
}
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
|
-
export function onAbortPromise(signal) {
|
|
47
|
-
return new Promise((resolve) => {
|
|
48
|
-
if (signal.aborted) {
|
|
49
|
-
resolve();
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
signal.onabort = () => resolve();
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
}
|