@powersync/common 0.0.0-dev-20260311103504 → 0.0.0-dev-20260503073249
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 +791 -489
- package/dist/bundle.cjs.map +1 -1
- package/dist/bundle.mjs +785 -485
- package/dist/bundle.mjs.map +1 -1
- package/dist/bundle.node.cjs +789 -488
- package/dist/bundle.node.cjs.map +1 -1
- package/dist/bundle.node.mjs +783 -484
- package/dist/bundle.node.mjs.map +1 -1
- package/dist/index.d.cts +165 -103
- package/lib/attachments/AttachmentQueue.d.ts +10 -4
- package/lib/attachments/AttachmentQueue.js +10 -4
- package/lib/attachments/AttachmentQueue.js.map +1 -1
- package/lib/attachments/AttachmentService.js +2 -3
- package/lib/attachments/AttachmentService.js.map +1 -1
- package/lib/attachments/SyncingService.d.ts +2 -1
- package/lib/attachments/SyncingService.js +4 -5
- package/lib/attachments/SyncingService.js.map +1 -1
- package/lib/client/AbstractPowerSyncDatabase.d.ts +5 -1
- package/lib/client/AbstractPowerSyncDatabase.js +9 -5
- package/lib/client/AbstractPowerSyncDatabase.js.map +1 -1
- package/lib/client/sync/stream/AbstractRemote.d.ts +29 -8
- package/lib/client/sync/stream/AbstractRemote.js +154 -177
- package/lib/client/sync/stream/AbstractRemote.js.map +1 -1
- package/lib/client/sync/stream/AbstractStreamingSyncImplementation.d.ts +4 -0
- package/lib/client/sync/stream/AbstractStreamingSyncImplementation.js +88 -88
- package/lib/client/sync/stream/AbstractStreamingSyncImplementation.js.map +1 -1
- package/lib/db/DBAdapter.d.ts +55 -9
- package/lib/db/DBAdapter.js +126 -0
- package/lib/db/DBAdapter.js.map +1 -1
- package/lib/db/crud/SyncStatus.d.ts +0 -4
- package/lib/db/crud/SyncStatus.js +0 -4
- package/lib/db/crud/SyncStatus.js.map +1 -1
- package/lib/db/schema/RawTable.d.ts +0 -5
- package/lib/db/schema/Schema.d.ts +0 -2
- package/lib/db/schema/Schema.js +0 -2
- package/lib/db/schema/Schema.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.js +0 -1
- package/lib/index.js.map +1 -1
- package/lib/utils/async.d.ts +0 -9
- package/lib/utils/async.js +0 -9
- package/lib/utils/async.js.map +1 -1
- package/lib/utils/mutex.d.ts +47 -5
- package/lib/utils/mutex.js +146 -21
- package/lib/utils/mutex.js.map +1 -1
- package/lib/utils/queue.d.ts +16 -0
- package/lib/utils/queue.js +42 -0
- package/lib/utils/queue.js.map +1 -0
- package/lib/utils/stream_transform.d.ts +39 -0
- package/lib/utils/stream_transform.js +206 -0
- package/lib/utils/stream_transform.js.map +1 -0
- package/package.json +9 -8
- package/src/attachments/AttachmentQueue.ts +10 -4
- package/src/attachments/AttachmentService.ts +2 -3
- package/src/attachments/README.md +6 -4
- package/src/attachments/SyncingService.ts +4 -5
- package/src/client/AbstractPowerSyncDatabase.ts +9 -5
- package/src/client/sync/stream/AbstractRemote.ts +182 -206
- package/src/client/sync/stream/AbstractStreamingSyncImplementation.ts +96 -83
- package/src/db/DBAdapter.ts +167 -9
- package/src/db/crud/SyncStatus.ts +0 -4
- package/src/db/schema/RawTable.ts +0 -5
- package/src/db/schema/Schema.ts +0 -2
- package/src/index.ts +1 -1
- package/src/utils/async.ts +0 -11
- package/src/utils/mutex.ts +184 -26
- package/src/utils/queue.ts +48 -0
- package/src/utils/stream_transform.ts +252 -0
- package/lib/utils/DataStream.d.ts +0 -62
- package/lib/utils/DataStream.js +0 -169
- package/lib/utils/DataStream.js.map +0 -1
- package/src/utils/DataStream.ts +0 -222
|
@@ -4,7 +4,6 @@ import { InternalProgressInformation } from '../../../db/crud/SyncProgress.js';
|
|
|
4
4
|
import { SyncStatus, SyncStatusOptions } from '../../../db/crud/SyncStatus.js';
|
|
5
5
|
import { AbortOperation } from '../../../utils/AbortOperation.js';
|
|
6
6
|
import { BaseListener, BaseObserver, BaseObserverInterface, Disposable } from '../../../utils/BaseObserver.js';
|
|
7
|
-
import { DataStream } from '../../../utils/DataStream.js';
|
|
8
7
|
import { throttleLeadingTrailing } from '../../../utils/async.js';
|
|
9
8
|
import {
|
|
10
9
|
BucketChecksum,
|
|
@@ -19,7 +18,6 @@ import { AbstractRemote, FetchStrategy, SyncStreamOptions } from './AbstractRemo
|
|
|
19
18
|
import { EstablishSyncStream, Instruction, coreStatusToJs } from './core-instruction.js';
|
|
20
19
|
import {
|
|
21
20
|
BucketRequest,
|
|
22
|
-
CrudUploadNotification,
|
|
23
21
|
StreamingSyncLine,
|
|
24
22
|
StreamingSyncLineOrCrudUploadComplete,
|
|
25
23
|
StreamingSyncRequestParameterType,
|
|
@@ -30,6 +28,8 @@ import {
|
|
|
30
28
|
isStreamingSyncCheckpointPartiallyComplete,
|
|
31
29
|
isStreamingSyncData
|
|
32
30
|
} from './streaming-sync-types.js';
|
|
31
|
+
import { injectable, InjectableIterator, map, SimpleAsyncIterator } from '../../../utils/stream_transform.js';
|
|
32
|
+
import type { BSON } from 'bson';
|
|
33
33
|
|
|
34
34
|
export enum LockType {
|
|
35
35
|
CRUD = 'crud',
|
|
@@ -209,6 +209,7 @@ export interface StreamingSyncImplementation
|
|
|
209
209
|
waitForStatus(status: SyncStatusOptions): Promise<void>;
|
|
210
210
|
waitUntilStatusMatches(predicate: (status: SyncStatus) => boolean): Promise<void>;
|
|
211
211
|
updateSubscriptions(subscriptions: SubscribedStream[]): void;
|
|
212
|
+
markConnectionMayHaveChanged(): void;
|
|
212
213
|
}
|
|
213
214
|
|
|
214
215
|
export const DEFAULT_CRUD_UPLOAD_THROTTLE_MS = 1000;
|
|
@@ -256,6 +257,7 @@ export abstract class AbstractStreamingSyncImplementation
|
|
|
256
257
|
protected streamingSyncPromise?: Promise<void>;
|
|
257
258
|
protected logger: ILogger;
|
|
258
259
|
private activeStreams: SubscribedStream[];
|
|
260
|
+
private connectionMayHaveChanged = false;
|
|
259
261
|
|
|
260
262
|
private isUploadingCrud: boolean = false;
|
|
261
263
|
private notifyCompletedUploads?: () => void;
|
|
@@ -572,6 +574,10 @@ The next upload iteration will be delayed.`);
|
|
|
572
574
|
this.logger.warn(ex);
|
|
573
575
|
shouldDelayRetry = false;
|
|
574
576
|
// A disconnect was requested, we should not delay since there is no explicit retry
|
|
577
|
+
} else if (this.connectionMayHaveChanged && (ex as Error).message?.indexOf('No iteration is active') >= 0) {
|
|
578
|
+
this.connectionMayHaveChanged = false;
|
|
579
|
+
this.logger.info('Sync error after changed connection, retrying immediately');
|
|
580
|
+
shouldDelayRetry = false;
|
|
575
581
|
} else {
|
|
576
582
|
this.logger.error(ex);
|
|
577
583
|
}
|
|
@@ -607,6 +613,17 @@ The next upload iteration will be delayed.`);
|
|
|
607
613
|
this.updateSyncStatus({ connected: false, connecting: false });
|
|
608
614
|
}
|
|
609
615
|
|
|
616
|
+
markConnectionMayHaveChanged() {
|
|
617
|
+
// By setting this field, we'll immediately retry if the next sync event causes an error triggered by us not having
|
|
618
|
+
// an active sync iteration on the connection in use.
|
|
619
|
+
this.connectionMayHaveChanged = true;
|
|
620
|
+
|
|
621
|
+
// This triggers a `powersync_control` invocation if a sync iteration is currently active. This is a cheap call to
|
|
622
|
+
// make when no subscriptions have actually changed, we're mainly interested in this immediately throwing if no
|
|
623
|
+
// iteration is active. That allows us to reconnect ASAP, instead of having to wait for the next sync line.
|
|
624
|
+
this.handleActiveStreamsChange?.();
|
|
625
|
+
}
|
|
626
|
+
|
|
610
627
|
private async collectLocalBucketState(): Promise<[BucketRequest[], Map<string, BucketDescription | null>]> {
|
|
611
628
|
const bucketEntries = await this.options.adapter.getBucketStates();
|
|
612
629
|
const req: BucketRequest[] = bucketEntries.map((entry) => ({
|
|
@@ -682,6 +699,27 @@ The next upload iteration will be delayed.`);
|
|
|
682
699
|
});
|
|
683
700
|
}
|
|
684
701
|
|
|
702
|
+
private async receiveSyncLines(data: {
|
|
703
|
+
options: SyncStreamOptions;
|
|
704
|
+
connection: RequiredPowerSyncConnectionOptions;
|
|
705
|
+
bson?: typeof BSON;
|
|
706
|
+
}): Promise<SimpleAsyncIterator<Uint8Array | string>> {
|
|
707
|
+
const { options, connection, bson } = data;
|
|
708
|
+
const remote = this.options.remote;
|
|
709
|
+
|
|
710
|
+
if (connection.connectionMethod == SyncStreamConnectionMethod.HTTP) {
|
|
711
|
+
return await remote.fetchStream(options);
|
|
712
|
+
} else {
|
|
713
|
+
return await this.options.remote.socketStreamRaw(
|
|
714
|
+
{
|
|
715
|
+
...options,
|
|
716
|
+
...{ fetchStrategy: connection.fetchStrategy }
|
|
717
|
+
},
|
|
718
|
+
bson
|
|
719
|
+
);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
685
723
|
private async legacyStreamingSyncIteration(signal: AbortSignal, resolvedOptions: RequiredPowerSyncConnectionOptions) {
|
|
686
724
|
const rawTables = resolvedOptions.serializedSchema?.raw_tables;
|
|
687
725
|
if (rawTables != null && rawTables.length) {
|
|
@@ -717,46 +755,31 @@ The next upload iteration will be delayed.`);
|
|
|
717
755
|
}
|
|
718
756
|
};
|
|
719
757
|
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
758
|
+
const bson = await this.options.remote.getBSON();
|
|
759
|
+
const source = await this.receiveSyncLines({
|
|
760
|
+
options: syncOptions,
|
|
761
|
+
connection: resolvedOptions,
|
|
762
|
+
bson
|
|
763
|
+
});
|
|
764
|
+
const stream: InjectableIterator<StreamingSyncLineOrCrudUploadComplete> = injectable(
|
|
765
|
+
map(source, (line) => {
|
|
723
766
|
if (typeof line == 'string') {
|
|
724
767
|
return JSON.parse(line) as StreamingSyncLine;
|
|
725
768
|
} else {
|
|
726
|
-
|
|
727
|
-
return line;
|
|
769
|
+
return bson.deserialize(line) as StreamingSyncLine;
|
|
728
770
|
}
|
|
729
|
-
})
|
|
730
|
-
|
|
731
|
-
const bson = await this.options.remote.getBSON();
|
|
732
|
-
stream = await this.options.remote.socketStreamRaw(
|
|
733
|
-
{
|
|
734
|
-
...syncOptions,
|
|
735
|
-
...{ fetchStrategy: resolvedOptions.fetchStrategy }
|
|
736
|
-
},
|
|
737
|
-
(payload: Uint8Array | CrudUploadNotification) => {
|
|
738
|
-
if (payload instanceof Uint8Array) {
|
|
739
|
-
return bson.deserialize(payload) as StreamingSyncLine;
|
|
740
|
-
} else {
|
|
741
|
-
// Directly enqueued by us
|
|
742
|
-
return payload;
|
|
743
|
-
}
|
|
744
|
-
},
|
|
745
|
-
bson
|
|
746
|
-
);
|
|
747
|
-
}
|
|
771
|
+
})
|
|
772
|
+
);
|
|
748
773
|
|
|
749
774
|
this.logger.debug('Stream established. Processing events');
|
|
750
775
|
|
|
751
776
|
this.notifyCompletedUploads = () => {
|
|
752
|
-
|
|
753
|
-
stream.enqueueData({ crud_upload_completed: null });
|
|
754
|
-
}
|
|
777
|
+
stream.inject({ crud_upload_completed: null });
|
|
755
778
|
};
|
|
756
779
|
|
|
757
|
-
while (
|
|
758
|
-
const line = await stream.
|
|
759
|
-
if (
|
|
780
|
+
while (true) {
|
|
781
|
+
const { value: line, done } = await stream.next();
|
|
782
|
+
if (done) {
|
|
760
783
|
// The stream has closed while waiting
|
|
761
784
|
return;
|
|
762
785
|
}
|
|
@@ -942,6 +965,11 @@ The next upload iteration will be delayed.`);
|
|
|
942
965
|
const syncImplementation = this;
|
|
943
966
|
const adapter = this.options.adapter;
|
|
944
967
|
const remote = this.options.remote;
|
|
968
|
+
const controller = new AbortController();
|
|
969
|
+
const abort = () => {
|
|
970
|
+
return controller.abort(signal.reason);
|
|
971
|
+
};
|
|
972
|
+
signal.addEventListener('abort', abort);
|
|
945
973
|
let receivingLines: Promise<void> | null = null;
|
|
946
974
|
let hadSyncLine = false;
|
|
947
975
|
let hideDisconnectOnRestart = false;
|
|
@@ -949,64 +977,53 @@ The next upload iteration will be delayed.`);
|
|
|
949
977
|
if (signal.aborted) {
|
|
950
978
|
throw new AbortOperation('Connection request has been aborted');
|
|
951
979
|
}
|
|
952
|
-
const abortController = new AbortController();
|
|
953
|
-
signal.addEventListener('abort', () => abortController.abort());
|
|
954
980
|
|
|
955
981
|
// Pending sync lines received from the service, as well as local events that trigger a powersync_control
|
|
956
982
|
// invocation (local events include refreshed tokens and completed uploads).
|
|
957
983
|
// This is a single data stream so that we can handle all control calls from a single place.
|
|
958
|
-
let controlInvocations:
|
|
984
|
+
let controlInvocations: InjectableIterator<EnqueuedCommand> | null = null;
|
|
959
985
|
|
|
960
986
|
async function connect(instr: EstablishSyncStream) {
|
|
961
987
|
const syncOptions: SyncStreamOptions = {
|
|
962
988
|
path: '/sync/stream',
|
|
963
|
-
abortSignal:
|
|
989
|
+
abortSignal: controller.signal,
|
|
964
990
|
data: instr.request
|
|
965
991
|
};
|
|
966
992
|
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
// Directly enqueued by us
|
|
976
|
-
return line;
|
|
977
|
-
}
|
|
978
|
-
});
|
|
979
|
-
} else {
|
|
980
|
-
controlInvocations = await remote.socketStreamRaw(
|
|
981
|
-
{
|
|
982
|
-
...syncOptions,
|
|
983
|
-
fetchStrategy: resolvedOptions.fetchStrategy
|
|
984
|
-
},
|
|
985
|
-
(payload: Uint8Array | EnqueuedCommand) => {
|
|
986
|
-
if (payload instanceof Uint8Array) {
|
|
993
|
+
controlInvocations = injectable(
|
|
994
|
+
map(
|
|
995
|
+
await syncImplementation.receiveSyncLines({
|
|
996
|
+
options: syncOptions,
|
|
997
|
+
connection: resolvedOptions
|
|
998
|
+
}),
|
|
999
|
+
(line) => {
|
|
1000
|
+
if (typeof line == 'string') {
|
|
987
1001
|
return {
|
|
988
|
-
command: PowerSyncControlCommand.
|
|
989
|
-
payload:
|
|
1002
|
+
command: PowerSyncControlCommand.PROCESS_TEXT_LINE,
|
|
1003
|
+
payload: line
|
|
990
1004
|
};
|
|
991
1005
|
} else {
|
|
992
|
-
|
|
993
|
-
|
|
1006
|
+
return {
|
|
1007
|
+
command: PowerSyncControlCommand.PROCESS_BSON_LINE,
|
|
1008
|
+
payload: line
|
|
1009
|
+
};
|
|
994
1010
|
}
|
|
995
1011
|
}
|
|
996
|
-
)
|
|
997
|
-
|
|
1012
|
+
)
|
|
1013
|
+
);
|
|
998
1014
|
|
|
999
1015
|
// The rust client will set connected: true after the first sync line because that's when it gets invoked, but
|
|
1000
1016
|
// we're already connected here and can report that.
|
|
1001
1017
|
syncImplementation.updateSyncStatus({ connected: true });
|
|
1002
1018
|
|
|
1003
1019
|
try {
|
|
1004
|
-
while (
|
|
1005
|
-
|
|
1006
|
-
if (
|
|
1007
|
-
|
|
1020
|
+
while (true) {
|
|
1021
|
+
let event = await controlInvocations.next();
|
|
1022
|
+
if (event.done) {
|
|
1023
|
+
break;
|
|
1008
1024
|
}
|
|
1009
1025
|
|
|
1026
|
+
const line = event.value;
|
|
1010
1027
|
await control(line.command, line.payload);
|
|
1011
1028
|
|
|
1012
1029
|
if (!hadSyncLine) {
|
|
@@ -1015,12 +1032,8 @@ The next upload iteration will be delayed.`);
|
|
|
1015
1032
|
}
|
|
1016
1033
|
}
|
|
1017
1034
|
} finally {
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
// refreshed. That would throw after closing (and we can't handle those events either way), so set this back
|
|
1021
|
-
// to null.
|
|
1022
|
-
controlInvocations = null;
|
|
1023
|
-
await activeInstructions.close();
|
|
1035
|
+
abort();
|
|
1036
|
+
signal.removeEventListener('abort', abort);
|
|
1024
1037
|
}
|
|
1025
1038
|
}
|
|
1026
1039
|
|
|
@@ -1038,6 +1051,10 @@ The next upload iteration will be delayed.`);
|
|
|
1038
1051
|
rawResponse
|
|
1039
1052
|
);
|
|
1040
1053
|
|
|
1054
|
+
if (op != PowerSyncControlCommand.STOP) {
|
|
1055
|
+
// Evidently we have a working connection here, otherwise powersync_control would have failed.
|
|
1056
|
+
syncImplementation.connectionMayHaveChanged = false;
|
|
1057
|
+
}
|
|
1041
1058
|
await handleInstructions(JSON.parse(rawResponse));
|
|
1042
1059
|
}
|
|
1043
1060
|
|
|
@@ -1072,7 +1089,7 @@ The next upload iteration will be delayed.`);
|
|
|
1072
1089
|
// Restart iteration after the credentials have been refreshed.
|
|
1073
1090
|
remote.fetchCredentials().then(
|
|
1074
1091
|
(_) => {
|
|
1075
|
-
controlInvocations?.
|
|
1092
|
+
controlInvocations?.inject({ command: PowerSyncControlCommand.NOTIFY_TOKEN_REFRESHED });
|
|
1076
1093
|
},
|
|
1077
1094
|
(err) => {
|
|
1078
1095
|
syncImplementation.logger.warn('Could not prefetch credentials', err);
|
|
@@ -1080,7 +1097,7 @@ The next upload iteration will be delayed.`);
|
|
|
1080
1097
|
);
|
|
1081
1098
|
}
|
|
1082
1099
|
} else if ('CloseSyncStream' in instruction) {
|
|
1083
|
-
|
|
1100
|
+
controller.abort();
|
|
1084
1101
|
hideDisconnectOnRestart = instruction.CloseSyncStream.hide_disconnect;
|
|
1085
1102
|
} else if ('FlushFileSystem' in instruction) {
|
|
1086
1103
|
// Not necessary on JS platforms.
|
|
@@ -1113,17 +1130,13 @@ The next upload iteration will be delayed.`);
|
|
|
1113
1130
|
await control(PowerSyncControlCommand.START, JSON.stringify(options));
|
|
1114
1131
|
|
|
1115
1132
|
this.notifyCompletedUploads = () => {
|
|
1116
|
-
|
|
1117
|
-
controlInvocations.enqueueData({ command: PowerSyncControlCommand.NOTIFY_CRUD_UPLOAD_COMPLETED });
|
|
1118
|
-
}
|
|
1133
|
+
controlInvocations?.inject({ command: PowerSyncControlCommand.NOTIFY_CRUD_UPLOAD_COMPLETED });
|
|
1119
1134
|
};
|
|
1120
1135
|
this.handleActiveStreamsChange = () => {
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
});
|
|
1126
|
-
}
|
|
1136
|
+
controlInvocations?.inject({
|
|
1137
|
+
command: PowerSyncControlCommand.UPDATE_SUBSCRIPTIONS,
|
|
1138
|
+
payload: JSON.stringify(this.activeStreams)
|
|
1139
|
+
});
|
|
1127
1140
|
};
|
|
1128
1141
|
await receivingLines;
|
|
1129
1142
|
} finally {
|
package/src/db/DBAdapter.ts
CHANGED
|
@@ -16,7 +16,13 @@ import { BaseListener, BaseObserverInterface } from '../utils/BaseObserver.js';
|
|
|
16
16
|
export type QueryResult = {
|
|
17
17
|
/** Represents the auto-generated row id if applicable. */
|
|
18
18
|
insertId?: number;
|
|
19
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* Number of affected rows reported by SQLite for a write query.
|
|
21
|
+
*
|
|
22
|
+
* When using the default client-side [JSON-based view system](https://docs.powersync.com/architecture/client-architecture#client-side-schema-and-sqlite-database-structure),
|
|
23
|
+
* `rowsAffected` may be `0` for successful `UPDATE` and `DELETE` statements.
|
|
24
|
+
* Use a `RETURNING` clause and inspect `rows` when you need to confirm which rows changed.
|
|
25
|
+
*/
|
|
20
26
|
rowsAffected: number;
|
|
21
27
|
/** if status is undefined or 0 this object will contain the query results */
|
|
22
28
|
rows?: {
|
|
@@ -41,7 +47,7 @@ export interface DBGetUtils {
|
|
|
41
47
|
get<T>(sql: string, parameters?: any[]): Promise<T>;
|
|
42
48
|
}
|
|
43
49
|
|
|
44
|
-
export interface
|
|
50
|
+
export interface SqlExecutor {
|
|
45
51
|
/** Execute a single write statement. */
|
|
46
52
|
execute: (query: string, params?: any[] | undefined) => Promise<QueryResult>;
|
|
47
53
|
/**
|
|
@@ -59,6 +65,61 @@ export interface LockContext extends DBGetUtils {
|
|
|
59
65
|
* ```[ { id: '33', name: 'list 1', content: 'Post content', list_id: '1' } ]```
|
|
60
66
|
*/
|
|
61
67
|
executeRaw: (query: string, params?: any[] | undefined) => Promise<any[][]>;
|
|
68
|
+
|
|
69
|
+
executeBatch: (query: string, params?: any[][]) => Promise<QueryResult>;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface LockContext extends SqlExecutor, DBGetUtils {}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Implements {@link DBGetUtils} on a {@link SqlRunner}.
|
|
76
|
+
*/
|
|
77
|
+
export function DBGetUtilsDefaultMixin<TBase extends new (...args: any[]) => Omit<SqlExecutor, 'executeBatch'>>(
|
|
78
|
+
Base: TBase
|
|
79
|
+
) {
|
|
80
|
+
return class extends Base implements DBGetUtils, SqlExecutor {
|
|
81
|
+
async getAll<T>(sql: string, parameters?: any[]): Promise<T[]> {
|
|
82
|
+
const res = await this.execute(sql, parameters);
|
|
83
|
+
return res.rows?._array ?? [];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async getOptional<T>(sql: string, parameters?: any[]): Promise<T | null> {
|
|
87
|
+
const res = await this.execute(sql, parameters);
|
|
88
|
+
return res.rows?.item(0) ?? null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async get<T>(sql: string, parameters?: any[]): Promise<T> {
|
|
92
|
+
const res = await this.execute(sql, parameters);
|
|
93
|
+
const first = res.rows?.item(0);
|
|
94
|
+
if (!first) {
|
|
95
|
+
throw new Error('Result set is empty');
|
|
96
|
+
}
|
|
97
|
+
return first;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async executeBatch(query: string, params: any[][] = []): Promise<QueryResult> {
|
|
101
|
+
// If this context can run batch statements natively, use that.
|
|
102
|
+
// @ts-ignore
|
|
103
|
+
if (super.executeBatch) {
|
|
104
|
+
// @ts-ignore
|
|
105
|
+
return super.executeBatch(query, params);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Emulate executeBatch by running statements individually.
|
|
109
|
+
let lastInsertId: number | undefined;
|
|
110
|
+
let rowsAffected = 0;
|
|
111
|
+
for (const set of params) {
|
|
112
|
+
const result = await this.execute(query, set);
|
|
113
|
+
lastInsertId = result.insertId;
|
|
114
|
+
rowsAffected += result.rowsAffected;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
rowsAffected,
|
|
119
|
+
insertId: lastInsertId
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
};
|
|
62
123
|
}
|
|
63
124
|
|
|
64
125
|
export interface Transaction extends LockContext {
|
|
@@ -107,22 +168,119 @@ export interface DBLockOptions {
|
|
|
107
168
|
timeoutMs?: number;
|
|
108
169
|
}
|
|
109
170
|
|
|
110
|
-
export interface
|
|
111
|
-
close: () => void | Promise<void>;
|
|
112
|
-
execute: (query: string, params?: any[]) => Promise<QueryResult>;
|
|
113
|
-
executeRaw: (query: string, params?: any[]) => Promise<any[][]>;
|
|
114
|
-
executeBatch: (query: string, params?: any[][]) => Promise<QueryResult>;
|
|
171
|
+
export interface ConnectionPool extends BaseObserverInterface<DBAdapterListener> {
|
|
115
172
|
name: string;
|
|
173
|
+
close: () => void | Promise<void>;
|
|
116
174
|
readLock: <T>(fn: (tx: LockContext) => Promise<T>, options?: DBLockOptions) => Promise<T>;
|
|
117
|
-
readTransaction: <T>(fn: (tx: Transaction) => Promise<T>, options?: DBLockOptions) => Promise<T>;
|
|
118
175
|
writeLock: <T>(fn: (tx: LockContext) => Promise<T>, options?: DBLockOptions) => Promise<T>;
|
|
119
|
-
|
|
176
|
+
|
|
120
177
|
/**
|
|
121
178
|
* This method refreshes the schema information across all connections. This is for advanced use cases, and should generally not be needed.
|
|
122
179
|
*/
|
|
123
180
|
refreshSchema: () => Promise<void>;
|
|
124
181
|
}
|
|
125
182
|
|
|
183
|
+
export interface DBAdapter extends ConnectionPool, SqlExecutor, DBGetUtils {
|
|
184
|
+
readTransaction: <T>(fn: (tx: Transaction) => Promise<T>, options?: DBLockOptions) => Promise<T>;
|
|
185
|
+
writeTransaction: <T>(fn: (tx: Transaction) => Promise<T>, options?: DBLockOptions) => Promise<T>;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* A mixin to implement {@link DBAdapter} by delegating to {@link ConnectionPool.readLock} and
|
|
190
|
+
* {@link ConnectionPool.writeLock}.
|
|
191
|
+
*/
|
|
192
|
+
export function DBAdapterDefaultMixin<TBase extends new (...args: any[]) => ConnectionPool>(Base: TBase) {
|
|
193
|
+
return class extends Base implements DBAdapter {
|
|
194
|
+
readTransaction<T>(fn: (tx: Transaction) => Promise<T>, options?: DBLockOptions): Promise<T> {
|
|
195
|
+
return this.readLock((ctx) => TransactionImplementation.runWith(ctx, fn), options);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
writeTransaction<T>(fn: (tx: Transaction) => Promise<T>, options?: DBLockOptions): Promise<T> {
|
|
199
|
+
return this.writeLock((ctx) => TransactionImplementation.runWith(ctx, fn), options);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
getAll<T>(sql: string, parameters?: any[]): Promise<T[]> {
|
|
203
|
+
return this.readLock((ctx) => ctx.getAll(sql, parameters));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
getOptional<T>(sql: string, parameters?: any[]): Promise<T | null> {
|
|
207
|
+
return this.readLock((ctx) => ctx.getOptional(sql, parameters));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
get<T>(sql: string, parameters?: any[]): Promise<T> {
|
|
211
|
+
return this.readLock((ctx) => ctx.get(sql, parameters));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
execute(query: string, params?: any[]): Promise<QueryResult> {
|
|
215
|
+
return this.writeLock((ctx) => ctx.execute(query, params));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
executeRaw(query: string, params?: any[]): Promise<any[][]> {
|
|
219
|
+
return this.writeLock((ctx) => ctx.executeRaw(query, params));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
executeBatch(query: string, params?: any[][]): Promise<QueryResult> {
|
|
223
|
+
return this.writeTransaction((tx) => tx.executeBatch(query, params));
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
class BaseTransaction implements SqlExecutor {
|
|
229
|
+
private finalized = false;
|
|
230
|
+
|
|
231
|
+
constructor(private inner: SqlExecutor) {}
|
|
232
|
+
|
|
233
|
+
async commit(): Promise<QueryResult> {
|
|
234
|
+
if (this.finalized) {
|
|
235
|
+
return { rowsAffected: 0 };
|
|
236
|
+
}
|
|
237
|
+
this.finalized = true;
|
|
238
|
+
return this.inner.execute('COMMIT');
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async rollback(): Promise<QueryResult> {
|
|
242
|
+
if (this.finalized) {
|
|
243
|
+
return { rowsAffected: 0 };
|
|
244
|
+
}
|
|
245
|
+
this.finalized = true;
|
|
246
|
+
return this.inner.execute('ROLLBACK');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
execute(query: string, params?: any[] | undefined): Promise<QueryResult> {
|
|
250
|
+
return this.inner.execute(query, params);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
executeRaw(query: string, params?: any[] | undefined): Promise<any[][]> {
|
|
254
|
+
return this.inner.executeRaw(query, params);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
executeBatch(query: string, params?: any[][]): Promise<QueryResult> {
|
|
258
|
+
return this.inner.executeBatch(query, params);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
class TransactionImplementation extends DBGetUtilsDefaultMixin(BaseTransaction) {
|
|
263
|
+
static async runWith<T>(ctx: LockContext, fn: (tx: Transaction) => Promise<T>): Promise<T> {
|
|
264
|
+
let tx = new TransactionImplementation(ctx);
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
await ctx.execute('BEGIN IMMEDIATE');
|
|
268
|
+
|
|
269
|
+
const result = await fn(tx);
|
|
270
|
+
await tx.commit();
|
|
271
|
+
return result;
|
|
272
|
+
} catch (ex) {
|
|
273
|
+
try {
|
|
274
|
+
await tx.rollback();
|
|
275
|
+
} catch (ex2) {
|
|
276
|
+
// In rare cases, a rollback may fail.
|
|
277
|
+
// Safe to ignore.
|
|
278
|
+
}
|
|
279
|
+
throw ex;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
126
284
|
export function isBatchedUpdateNotification(
|
|
127
285
|
update: BatchedUpdateNotification | UpdateNotification
|
|
128
286
|
): update is BatchedUpdateNotification {
|
|
@@ -122,8 +122,6 @@ export class SyncStatus {
|
|
|
122
122
|
*
|
|
123
123
|
* This returns null when the database is currently being opened and we don't have reliable information about all
|
|
124
124
|
* included streams yet.
|
|
125
|
-
*
|
|
126
|
-
* @experimental Sync streams are currently in alpha.
|
|
127
125
|
*/
|
|
128
126
|
get syncStreams(): SyncStreamStatus[] | undefined {
|
|
129
127
|
return this.options.dataFlow?.internalStreamSubscriptions?.map((core) => new SyncStreamStatusView(this, core));
|
|
@@ -131,8 +129,6 @@ export class SyncStatus {
|
|
|
131
129
|
|
|
132
130
|
/**
|
|
133
131
|
* If the `stream` appears in {@link syncStreams}, returns the current status for that stream.
|
|
134
|
-
*
|
|
135
|
-
* @experimental Sync streams are currently in alpha.
|
|
136
132
|
*/
|
|
137
133
|
forStream(stream: SyncStreamDescription): SyncStreamStatus | undefined {
|
|
138
134
|
const asJson = JSON.stringify(stream.parameters);
|
|
@@ -9,11 +9,6 @@ import { TableOrRawTableOptions } from './Table.js';
|
|
|
9
9
|
* To collect local writes to raw tables with PowerSync, custom triggers are required. See
|
|
10
10
|
* {@link https://docs.powersync.com/usage/use-case-examples/raw-tables the documentation} for details and an example on
|
|
11
11
|
* using raw tables.
|
|
12
|
-
*
|
|
13
|
-
* Note that raw tables are only supported when using the new `SyncClientImplementation.rust` sync client.
|
|
14
|
-
*
|
|
15
|
-
* @experimental Please note that this feature is experimental at the moment, and not covered by PowerSync semver or
|
|
16
|
-
* stability guarantees.
|
|
17
12
|
*/
|
|
18
13
|
export type RawTableType = RawTableTypeWithStatements | InferredRawTableType;
|
|
19
14
|
|
package/src/db/schema/Schema.ts
CHANGED
|
@@ -51,10 +51,8 @@ export class Schema<S extends SchemaType = SchemaType> {
|
|
|
51
51
|
* developer instead of automatically by PowerSync.
|
|
52
52
|
* Since raw tables are not backed by JSON, running complex queries on them may be more efficient. Further, they allow
|
|
53
53
|
* using client-side table and column constraints.
|
|
54
|
-
* Note that raw tables are only supported when using the new `SyncClientImplementation.rust` sync client.
|
|
55
54
|
*
|
|
56
55
|
* @param tables An object of (table name, raw table definition) entries.
|
|
57
|
-
* @experimental Note that the raw tables API is still experimental and may change in the future.
|
|
58
56
|
*/
|
|
59
57
|
withRawTables(tables: Record<string, RawTableType>) {
|
|
60
58
|
for (const [name, rawTableDefinition] of Object.entries(tables)) {
|
package/src/index.ts
CHANGED
|
@@ -59,9 +59,9 @@ export * from './client/watched/WatchedQuery.js';
|
|
|
59
59
|
export * from './utils/AbortOperation.js';
|
|
60
60
|
export * from './utils/BaseObserver.js';
|
|
61
61
|
export * from './utils/ControlledExecutor.js';
|
|
62
|
-
export * from './utils/DataStream.js';
|
|
63
62
|
export * from './utils/Logger.js';
|
|
64
63
|
export * from './utils/mutex.js';
|
|
65
64
|
export * from './utils/parseQuery.js';
|
|
65
|
+
export type { SimpleAsyncIterator } from './utils/stream_transform.js';
|
|
66
66
|
|
|
67
67
|
export * from './types/types.js';
|
package/src/utils/async.ts
CHANGED
|
@@ -1,14 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A ponyfill for `Symbol.asyncIterator` that is compatible with the
|
|
3
|
-
* [recommended polyfill](https://github.com/Azure/azure-sdk-for-js/blob/%40azure/core-asynciterator-polyfill_1.0.2/sdk/core/core-asynciterator-polyfill/src/index.ts#L4-L6)
|
|
4
|
-
* we recommend for React Native.
|
|
5
|
-
*
|
|
6
|
-
* As long as we use this symbol (instead of `for await` and `async *`) in this package, we can be compatible with async
|
|
7
|
-
* iterators without requiring them.
|
|
8
|
-
*/
|
|
9
|
-
export const symbolAsyncIterator: typeof Symbol.asyncIterator =
|
|
10
|
-
Symbol.asyncIterator ?? Symbol.for('Symbol.asyncIterator');
|
|
11
|
-
|
|
12
1
|
/**
|
|
13
2
|
* Throttle a function to be called at most once every "wait" milliseconds,
|
|
14
3
|
* on the trailing edge.
|