@powersync/common 0.0.0-dev-20260504100448 → 0.0.0-dev-20260630141119
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/AttachmentContext.d.ts +9 -8
- package/lib/attachments/AttachmentContext.js +8 -3
- package/lib/attachments/AttachmentContext.js.map +1 -1
- package/lib/attachments/AttachmentErrorHandler.d.ts +6 -6
- package/lib/attachments/AttachmentQueue.d.ts +85 -36
- package/lib/attachments/AttachmentQueue.js +19 -21
- package/lib/attachments/AttachmentQueue.js.map +1 -1
- package/lib/attachments/AttachmentService.d.ts +3 -3
- package/lib/attachments/AttachmentService.js +4 -3
- package/lib/attachments/AttachmentService.js.map +1 -1
- package/lib/attachments/LocalStorageAdapter.d.ts +14 -8
- package/lib/attachments/LocalStorageAdapter.js +3 -0
- package/lib/attachments/LocalStorageAdapter.js.map +1 -1
- package/lib/attachments/RemoteStorageAdapter.d.ts +4 -4
- package/lib/attachments/Schema.d.ts +14 -6
- package/lib/attachments/Schema.js +8 -3
- package/lib/attachments/Schema.js.map +1 -1
- package/lib/attachments/SyncingService.d.ts +2 -2
- package/lib/attachments/SyncingService.js +8 -3
- package/lib/attachments/SyncingService.js.map +1 -1
- package/lib/attachments/WatchedAttachmentItem.d.ts +3 -1
- package/lib/client/{AbstractPowerSyncDatabase.d.ts → CommonPowerSyncDatabase.d.ts} +112 -238
- package/lib/client/CommonPowerSyncDatabase.js +2 -0
- package/lib/client/CommonPowerSyncDatabase.js.map +1 -0
- package/lib/client/Query.d.ts +9 -0
- package/lib/client/SQLOpenFactory.d.ts +44 -10
- package/lib/client/SQLOpenFactory.js +1 -19
- package/lib/client/SQLOpenFactory.js.map +1 -1
- package/lib/client/compilableQueryWatch.d.ts +8 -2
- package/lib/client/compilableQueryWatch.js +4 -2
- package/lib/client/compilableQueryWatch.js.map +1 -1
- package/lib/client/connection/PowerSyncBackendConnector.d.ts +6 -3
- package/lib/client/connection/PowerSyncCredentials.d.ts +3 -0
- package/lib/client/runOnSchemaChange.d.ts +5 -2
- package/lib/client/runOnSchemaChange.js +3 -0
- package/lib/client/runOnSchemaChange.js.map +1 -1
- package/lib/client/sync/bucket/CrudBatch.d.ts +2 -0
- package/lib/client/sync/bucket/CrudBatch.js +2 -0
- package/lib/client/sync/bucket/CrudBatch.js.map +1 -1
- package/lib/client/sync/bucket/CrudEntry.d.ts +9 -31
- package/lib/client/sync/bucket/CrudEntry.js +2 -97
- package/lib/client/sync/bucket/CrudEntry.js.map +1 -1
- package/lib/client/sync/bucket/CrudTransaction.d.ts +3 -0
- package/lib/client/sync/bucket/CrudTransaction.js +3 -0
- package/lib/client/sync/bucket/CrudTransaction.js.map +1 -1
- package/lib/client/sync/options.d.ts +65 -0
- package/lib/client/sync/options.js +25 -0
- package/lib/client/sync/options.js.map +1 -0
- package/lib/client/sync/stream/JsonValue.d.ts +3 -0
- package/lib/client/sync/sync-streams.d.ts +23 -8
- package/lib/client/triggers/TriggerManager.d.ts +28 -56
- package/lib/client/triggers/TriggerManager.js +2 -1
- package/lib/client/triggers/TriggerManager.js.map +1 -1
- package/lib/client/triggers/sanitizeSQL.d.ts +4 -0
- package/lib/client/triggers/sanitizeSQL.js +4 -0
- package/lib/client/triggers/sanitizeSQL.js.map +1 -1
- package/lib/client/watched/GetAllQuery.d.ts +7 -3
- package/lib/client/watched/GetAllQuery.js +3 -1
- package/lib/client/watched/GetAllQuery.js.map +1 -1
- package/lib/client/watched/WatchedQuery.d.ts +20 -6
- package/lib/client/watched/WatchedQuery.js +3 -5
- package/lib/client/watched/WatchedQuery.js.map +1 -1
- package/lib/client/watched/processors/DifferentialQueryProcessor.d.ts +15 -45
- package/lib/client/watched/processors/DifferentialQueryProcessor.js +1 -172
- package/lib/client/watched/processors/DifferentialQueryProcessor.js.map +1 -1
- package/lib/client/watched/processors/OnChangeQueryProcessor.d.ts +4 -22
- package/lib/client/watched/processors/OnChangeQueryProcessor.js +1 -82
- package/lib/client/watched/processors/OnChangeQueryProcessor.js.map +1 -1
- package/lib/client/watched/processors/comparators.d.ts +8 -0
- package/lib/client/watched/processors/comparators.js +4 -0
- package/lib/client/watched/processors/comparators.js.map +1 -1
- package/lib/db/DBAdapter.d.ts +90 -113
- package/lib/db/DBAdapter.js +70 -89
- package/lib/db/DBAdapter.js.map +1 -1
- package/lib/db/QueryResult.d.ts +104 -0
- package/lib/db/QueryResult.js +96 -0
- package/lib/db/QueryResult.js.map +1 -0
- package/lib/db/crud/SyncProgress.d.ts +7 -14
- package/lib/db/crud/SyncProgress.js +1 -60
- package/lib/db/crud/SyncProgress.js.map +1 -1
- package/lib/db/crud/SyncStatus.d.ts +54 -91
- package/lib/db/crud/SyncStatus.js +1 -245
- package/lib/db/crud/SyncStatus.js.map +1 -1
- package/lib/db/crud/UploadQueueStatus.d.ts +3 -0
- package/lib/db/crud/UploadQueueStatus.js +3 -0
- package/lib/db/crud/UploadQueueStatus.js.map +1 -1
- package/lib/db/schema/Column.d.ts +22 -1
- package/lib/db/schema/Column.js +10 -4
- package/lib/db/schema/Column.js.map +1 -1
- package/lib/db/schema/Index.d.ts +8 -3
- package/lib/db/schema/Index.js +4 -1
- package/lib/db/schema/Index.js.map +1 -1
- package/lib/db/schema/IndexedColumn.d.ts +8 -3
- package/lib/db/schema/IndexedColumn.js +4 -1
- package/lib/db/schema/IndexedColumn.js.map +1 -1
- package/lib/db/schema/RawTable.d.ts +7 -1
- package/lib/db/schema/Schema.d.ts +10 -29
- package/lib/db/schema/Schema.js +3 -1
- package/lib/db/schema/Schema.js.map +1 -1
- package/lib/db/schema/Table.d.ts +69 -91
- package/lib/db/schema/Table.js +88 -87
- package/lib/db/schema/Table.js.map +1 -1
- package/lib/index.d.ts +6 -22
- package/lib/index.js +5 -22
- package/lib/index.js.map +1 -1
- package/lib/types/types.d.ts +6 -0
- package/lib/utils/BaseObserver.d.ts +12 -0
- package/lib/utils/BaseObserver.js +3 -0
- package/lib/utils/BaseObserver.js.map +1 -1
- package/lib/utils/Logger.d.ts +60 -22
- package/lib/utils/Logger.js +38 -30
- package/lib/utils/Logger.js.map +1 -1
- package/lib/utils/MetaBaseObserver.d.ts +11 -11
- package/lib/utils/MetaBaseObserver.js +1 -50
- package/lib/utils/MetaBaseObserver.js.map +1 -1
- package/lib/utils/mutex.d.ts +3 -45
- package/lib/utils/mutex.js +1 -153
- package/lib/utils/mutex.js.map +1 -1
- package/package.json +9 -46
- package/src/attachments/AttachmentContext.ts +15 -13
- package/src/attachments/AttachmentErrorHandler.ts +6 -6
- package/src/attachments/AttachmentQueue.ts +99 -42
- package/src/attachments/AttachmentService.ts +7 -6
- package/src/attachments/LocalStorageAdapter.ts +14 -8
- package/src/attachments/README.md +2 -0
- package/src/attachments/RemoteStorageAdapter.ts +4 -4
- package/src/attachments/Schema.ts +14 -6
- package/src/attachments/SyncingService.ts +10 -6
- package/src/attachments/WatchedAttachmentItem.ts +3 -1
- package/src/client/CommonPowerSyncDatabase.ts +540 -0
- package/src/client/Query.ts +9 -0
- package/src/client/SQLOpenFactory.ts +47 -19
- package/src/client/compilableQueryWatch.ts +9 -4
- package/src/client/connection/PowerSyncBackendConnector.ts +6 -3
- package/src/client/connection/PowerSyncCredentials.ts +3 -0
- package/src/client/runOnSchemaChange.ts +5 -2
- package/src/client/sync/bucket/CrudBatch.ts +2 -0
- package/src/client/sync/bucket/CrudEntry.ts +10 -101
- package/src/client/sync/bucket/CrudTransaction.ts +3 -0
- package/src/client/sync/options.ts +77 -0
- package/src/client/sync/stream/JsonValue.ts +3 -0
- package/src/client/sync/sync-streams.ts +23 -10
- package/src/client/triggers/TriggerManager.ts +29 -59
- package/src/client/triggers/sanitizeSQL.ts +5 -0
- package/src/client/watched/GetAllQuery.ts +8 -4
- package/src/client/watched/WatchedQuery.ts +20 -11
- package/src/client/watched/processors/DifferentialQueryProcessor.ts +19 -219
- package/src/client/watched/processors/OnChangeQueryProcessor.ts +4 -109
- package/src/client/watched/processors/comparators.ts +8 -0
- package/src/db/DBAdapter.ts +160 -175
- package/src/db/QueryResult.ts +195 -0
- package/src/db/crud/SyncProgress.ts +8 -43
- package/src/db/crud/SyncStatus.ts +66 -216
- package/src/db/crud/UploadQueueStatus.ts +3 -0
- package/src/db/schema/Column.ts +22 -5
- package/src/db/schema/Index.ts +9 -3
- package/src/db/schema/IndexedColumn.ts +9 -3
- package/src/db/schema/RawTable.ts +7 -1
- package/src/db/schema/Schema.ts +12 -7
- package/src/db/schema/Table.ts +130 -180
- package/src/index.ts +6 -22
- package/src/types/types.ts +6 -0
- package/src/utils/BaseObserver.ts +12 -0
- package/src/utils/Logger.ts +86 -31
- package/src/utils/MetaBaseObserver.ts +14 -60
- package/src/utils/mutex.ts +4 -189
- package/dist/bundle.cjs +0 -14136
- package/dist/bundle.cjs.map +0 -1
- package/dist/bundle.mjs +0 -14056
- package/dist/bundle.mjs.map +0 -1
- package/dist/bundle.node.cjs +0 -11613
- package/dist/bundle.node.cjs.map +0 -1
- package/dist/bundle.node.mjs +0 -11533
- package/dist/bundle.node.mjs.map +0 -1
- package/dist/index.d.cts +0 -4052
- package/legacy/sync_protocol.d.ts +0 -103
- package/lib/client/AbstractPowerSyncDatabase.js +0 -987
- package/lib/client/AbstractPowerSyncDatabase.js.map +0 -1
- package/lib/client/AbstractPowerSyncOpenFactory.d.ts +0 -22
- package/lib/client/AbstractPowerSyncOpenFactory.js +0 -25
- package/lib/client/AbstractPowerSyncOpenFactory.js.map +0 -1
- package/lib/client/ConnectionManager.d.ts +0 -112
- package/lib/client/ConnectionManager.js +0 -294
- package/lib/client/ConnectionManager.js.map +0 -1
- package/lib/client/CustomQuery.d.ts +0 -22
- package/lib/client/CustomQuery.js +0 -43
- package/lib/client/CustomQuery.js.map +0 -1
- package/lib/client/constants.d.ts +0 -1
- package/lib/client/constants.js +0 -2
- package/lib/client/constants.js.map +0 -1
- package/lib/client/sync/bucket/BucketStorageAdapter.d.ts +0 -40
- package/lib/client/sync/bucket/BucketStorageAdapter.js +0 -19
- package/lib/client/sync/bucket/BucketStorageAdapter.js.map +0 -1
- package/lib/client/sync/bucket/SqliteBucketStorage.d.ts +0 -34
- package/lib/client/sync/bucket/SqliteBucketStorage.js +0 -162
- package/lib/client/sync/bucket/SqliteBucketStorage.js.map +0 -1
- package/lib/client/sync/stream/AbstractRemote.d.ts +0 -151
- package/lib/client/sync/stream/AbstractRemote.js +0 -473
- package/lib/client/sync/stream/AbstractRemote.js.map +0 -1
- package/lib/client/sync/stream/AbstractStreamingSyncImplementation.d.ts +0 -218
- package/lib/client/sync/stream/AbstractStreamingSyncImplementation.js +0 -663
- package/lib/client/sync/stream/AbstractStreamingSyncImplementation.js.map +0 -1
- package/lib/client/sync/stream/WebsocketClientTransport.d.ts +0 -15
- package/lib/client/sync/stream/WebsocketClientTransport.js +0 -61
- package/lib/client/sync/stream/WebsocketClientTransport.js.map +0 -1
- package/lib/client/sync/stream/core-instruction.d.ts +0 -71
- package/lib/client/sync/stream/core-instruction.js +0 -27
- package/lib/client/sync/stream/core-instruction.js.map +0 -1
- package/lib/client/triggers/MemoryTriggerClaimManager.d.ts +0 -6
- package/lib/client/triggers/MemoryTriggerClaimManager.js +0 -21
- package/lib/client/triggers/MemoryTriggerClaimManager.js.map +0 -1
- package/lib/client/triggers/TriggerManagerImpl.d.ts +0 -39
- package/lib/client/triggers/TriggerManagerImpl.js +0 -405
- package/lib/client/triggers/TriggerManagerImpl.js.map +0 -1
- package/lib/client/watched/processors/AbstractQueryProcessor.d.ts +0 -68
- package/lib/client/watched/processors/AbstractQueryProcessor.js +0 -151
- package/lib/client/watched/processors/AbstractQueryProcessor.js.map +0 -1
- package/lib/db/ConnectionClosedError.d.ts +0 -10
- package/lib/db/ConnectionClosedError.js +0 -21
- package/lib/db/ConnectionClosedError.js.map +0 -1
- package/lib/db/schema/TableV2.d.ts +0 -9
- package/lib/db/schema/TableV2.js +0 -9
- package/lib/db/schema/TableV2.js.map +0 -1
- package/lib/utils/AbortOperation.d.ts +0 -9
- package/lib/utils/AbortOperation.js +0 -19
- package/lib/utils/AbortOperation.js.map +0 -1
- package/lib/utils/ControlledExecutor.d.ts +0 -25
- package/lib/utils/ControlledExecutor.js +0 -51
- package/lib/utils/ControlledExecutor.js.map +0 -1
- package/lib/utils/async.d.ts +0 -14
- package/lib/utils/async.js +0 -46
- package/lib/utils/async.js.map +0 -1
- package/lib/utils/parseQuery.d.ts +0 -6
- package/lib/utils/parseQuery.js +0 -17
- package/lib/utils/parseQuery.js.map +0 -1
- package/lib/utils/queue.d.ts +0 -16
- package/lib/utils/queue.js +0 -42
- package/lib/utils/queue.js.map +0 -1
- package/lib/utils/stream_transform.d.ts +0 -39
- package/lib/utils/stream_transform.js +0 -206
- package/lib/utils/stream_transform.js.map +0 -1
- package/src/client/AbstractPowerSyncDatabase.ts +0 -1362
- package/src/client/AbstractPowerSyncOpenFactory.ts +0 -39
- package/src/client/ConnectionManager.ts +0 -402
- package/src/client/CustomQuery.ts +0 -56
- package/src/client/constants.ts +0 -1
- package/src/client/sync/bucket/BucketStorageAdapter.ts +0 -49
- package/src/client/sync/bucket/SqliteBucketStorage.ts +0 -199
- package/src/client/sync/stream/AbstractRemote.ts +0 -602
- package/src/client/sync/stream/AbstractStreamingSyncImplementation.ts +0 -905
- package/src/client/sync/stream/WebsocketClientTransport.ts +0 -80
- package/src/client/sync/stream/core-instruction.ts +0 -98
- package/src/client/triggers/MemoryTriggerClaimManager.ts +0 -25
- package/src/client/triggers/TriggerManagerImpl.ts +0 -496
- package/src/client/watched/processors/AbstractQueryProcessor.ts +0 -226
- package/src/db/ConnectionClosedError.ts +0 -23
- package/src/db/schema/TableV2.ts +0 -9
- package/src/utils/AbortOperation.ts +0 -17
- package/src/utils/ControlledExecutor.ts +0 -72
- package/src/utils/async.ts +0 -50
- package/src/utils/parseQuery.ts +0 -25
- package/src/utils/queue.ts +0 -48
- package/src/utils/stream_transform.ts +0 -252
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Adapted from rsocket-websocket-client
|
|
3
|
-
* https://github.com/rsocket/rsocket-js/blob/e224cf379e747c4f1ddc4f2fa111854626cc8575/packages/rsocket-websocket-client/src/WebsocketClientTransport.ts#L17
|
|
4
|
-
* This adds additional error handling for React Native iOS.
|
|
5
|
-
* This particularly adds a close listener to handle cases where the WebSocket
|
|
6
|
-
* connection closes immediately after opening without emitting an error.
|
|
7
|
-
*/
|
|
8
|
-
import {
|
|
9
|
-
ClientTransport,
|
|
10
|
-
Closeable,
|
|
11
|
-
Demultiplexer,
|
|
12
|
-
Deserializer,
|
|
13
|
-
DuplexConnection,
|
|
14
|
-
FrameHandler,
|
|
15
|
-
Multiplexer,
|
|
16
|
-
Outbound
|
|
17
|
-
} from 'rsocket-core';
|
|
18
|
-
import { ClientOptions } from 'rsocket-websocket-client';
|
|
19
|
-
import { WebsocketDuplexConnection } from 'rsocket-websocket-client/dist/WebsocketDuplexConnection.js';
|
|
20
|
-
|
|
21
|
-
export class WebsocketClientTransport implements ClientTransport {
|
|
22
|
-
private readonly url: string;
|
|
23
|
-
private readonly factory: (url: string) => WebSocket;
|
|
24
|
-
|
|
25
|
-
constructor(options: ClientOptions) {
|
|
26
|
-
this.url = options.url;
|
|
27
|
-
this.factory = options.wsCreator ?? ((url: string) => new WebSocket(url));
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
connect(
|
|
31
|
-
multiplexerDemultiplexerFactory: (outbound: Outbound & Closeable) => Multiplexer & Demultiplexer & FrameHandler
|
|
32
|
-
): Promise<DuplexConnection> {
|
|
33
|
-
return new Promise((resolve, reject) => {
|
|
34
|
-
const websocket = this.factory(this.url);
|
|
35
|
-
|
|
36
|
-
websocket.binaryType = 'arraybuffer';
|
|
37
|
-
|
|
38
|
-
let removeListeners: () => void;
|
|
39
|
-
|
|
40
|
-
const openListener = () => {
|
|
41
|
-
removeListeners();
|
|
42
|
-
resolve(new WebsocketDuplexConnection(websocket, new Deserializer(), multiplexerDemultiplexerFactory));
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
const errorListener = (ev: ErrorEvent) => {
|
|
46
|
-
removeListeners();
|
|
47
|
-
// We add a default error in that case.
|
|
48
|
-
if (ev.error != null) {
|
|
49
|
-
// undici typically provides an error object
|
|
50
|
-
reject(ev.error);
|
|
51
|
-
} else if (ev.message != null) {
|
|
52
|
-
// React Native typically does not provide an error object, but does provide a message
|
|
53
|
-
reject(new Error(`Failed to create websocket connection: ${ev.message}`));
|
|
54
|
-
} else {
|
|
55
|
-
// Browsers often provide no details at all
|
|
56
|
-
reject(new Error(`Failed to create websocket connection to ${this.url}`));
|
|
57
|
-
}
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* In some cases, such as React Native iOS, the WebSocket connection may close immediately after opening
|
|
62
|
-
* without and error. In such cases, we need to handle the close event to reject the promise.
|
|
63
|
-
*/
|
|
64
|
-
const closeListener = () => {
|
|
65
|
-
removeListeners();
|
|
66
|
-
reject(new Error('WebSocket connection closed while opening'));
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
removeListeners = () => {
|
|
70
|
-
websocket.removeEventListener('open', openListener);
|
|
71
|
-
websocket.removeEventListener('error', errorListener);
|
|
72
|
-
websocket.removeEventListener('close', closeListener);
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
websocket.addEventListener('open', openListener);
|
|
76
|
-
websocket.addEventListener('error', errorListener);
|
|
77
|
-
websocket.addEventListener('close', closeListener);
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
}
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import * as sync_status from '../../../db/crud/SyncStatus.js';
|
|
2
|
-
import { FULL_SYNC_PRIORITY } from '../../../db/crud/SyncProgress.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* An internal instruction emitted by the sync client in the core extension in response to the JS
|
|
6
|
-
* SDK passing sync data into the extension.
|
|
7
|
-
*/
|
|
8
|
-
export type Instruction =
|
|
9
|
-
| { LogLine: LogLine }
|
|
10
|
-
| { UpdateSyncStatus: UpdateSyncStatus }
|
|
11
|
-
| { EstablishSyncStream: EstablishSyncStream }
|
|
12
|
-
| { FetchCredentials: FetchCredentials }
|
|
13
|
-
| { CloseSyncStream: { hide_disconnect: boolean } }
|
|
14
|
-
| { FlushFileSystem: any }
|
|
15
|
-
| { DidCompleteSync: any };
|
|
16
|
-
|
|
17
|
-
export interface LogLine {
|
|
18
|
-
severity: 'DEBUG' | 'INFO' | 'WARNING';
|
|
19
|
-
line: string;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface EstablishSyncStream {
|
|
23
|
-
request: unknown;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface UpdateSyncStatus {
|
|
27
|
-
status: CoreSyncStatus;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface CoreSyncStatus {
|
|
31
|
-
connected: boolean;
|
|
32
|
-
connecting: boolean;
|
|
33
|
-
priority_status: SyncPriorityStatus[];
|
|
34
|
-
downloading: DownloadProgress | null;
|
|
35
|
-
streams: CoreStreamSubscription[];
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/// An `ActiveStreamSubscription` from the core extension + serialized progress information.
|
|
39
|
-
export interface CoreStreamSubscription {
|
|
40
|
-
progress: { total: number; downloaded: number };
|
|
41
|
-
name: string;
|
|
42
|
-
parameters: any;
|
|
43
|
-
priority: number | null;
|
|
44
|
-
active: boolean;
|
|
45
|
-
is_default: boolean;
|
|
46
|
-
has_explicit_subscription: boolean;
|
|
47
|
-
expires_at: number | null;
|
|
48
|
-
last_synced_at: number | null;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export interface SyncPriorityStatus {
|
|
52
|
-
priority: number;
|
|
53
|
-
last_synced_at: number | number;
|
|
54
|
-
has_synced: boolean | null;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export interface DownloadProgress {
|
|
58
|
-
buckets: Record<string, BucketProgress>;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export interface BucketProgress {
|
|
62
|
-
priority: number;
|
|
63
|
-
at_last: number;
|
|
64
|
-
since_last: number;
|
|
65
|
-
target_count: number;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export interface FetchCredentials {
|
|
69
|
-
did_expire: boolean;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function priorityToJs(status: SyncPriorityStatus): sync_status.SyncPriorityStatus {
|
|
73
|
-
return {
|
|
74
|
-
priority: status.priority,
|
|
75
|
-
hasSynced: status.has_synced ?? undefined,
|
|
76
|
-
lastSyncedAt: status.last_synced_at != null ? new Date(status.last_synced_at * 1000) : undefined
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export function coreStatusToJs(status: CoreSyncStatus): sync_status.SyncStatusOptions {
|
|
81
|
-
const coreCompleteSync = status.priority_status.find((s) => s.priority == FULL_SYNC_PRIORITY);
|
|
82
|
-
const completeSync = coreCompleteSync != null ? priorityToJs(coreCompleteSync) : null;
|
|
83
|
-
|
|
84
|
-
return {
|
|
85
|
-
connected: status.connected,
|
|
86
|
-
connecting: status.connecting,
|
|
87
|
-
dataFlow: {
|
|
88
|
-
// We expose downloading as a boolean field, the core extension reports download information as a nullable
|
|
89
|
-
// download status. When that status is non-null, a download is in progress.
|
|
90
|
-
downloading: status.downloading != null,
|
|
91
|
-
downloadProgress: status.downloading?.buckets,
|
|
92
|
-
internalStreamSubscriptions: status.streams
|
|
93
|
-
},
|
|
94
|
-
lastSyncedAt: completeSync?.lastSyncedAt,
|
|
95
|
-
hasSynced: completeSync?.hasSynced,
|
|
96
|
-
priorityStatusEntries: status.priority_status.map(priorityToJs)
|
|
97
|
-
};
|
|
98
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { TriggerClaimManager } from './TriggerManager.js';
|
|
2
|
-
|
|
3
|
-
const CLAIM_STORE = new Map<string, () => Promise<void>>();
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* @internal
|
|
7
|
-
* @experimental
|
|
8
|
-
*/
|
|
9
|
-
export const MEMORY_TRIGGER_CLAIM_MANAGER: TriggerClaimManager = {
|
|
10
|
-
async obtainClaim(identifier: string): Promise<() => Promise<void>> {
|
|
11
|
-
if (CLAIM_STORE.has(identifier)) {
|
|
12
|
-
throw new Error(`A claim is already present for ${identifier}`);
|
|
13
|
-
}
|
|
14
|
-
const release = async () => {
|
|
15
|
-
CLAIM_STORE.delete(identifier);
|
|
16
|
-
};
|
|
17
|
-
CLAIM_STORE.set(identifier, release);
|
|
18
|
-
|
|
19
|
-
return release;
|
|
20
|
-
},
|
|
21
|
-
|
|
22
|
-
async checkClaim(identifier: string): Promise<boolean> {
|
|
23
|
-
return CLAIM_STORE.has(identifier);
|
|
24
|
-
}
|
|
25
|
-
};
|
|
@@ -1,496 +0,0 @@
|
|
|
1
|
-
import { LockContext } from '../../db/DBAdapter.js';
|
|
2
|
-
import { Schema } from '../../db/schema/Schema.js';
|
|
3
|
-
import type { AbstractPowerSyncDatabase } from '../AbstractPowerSyncDatabase.js';
|
|
4
|
-
import { DEFAULT_WATCH_THROTTLE_MS } from '../watched/WatchedQuery.js';
|
|
5
|
-
import {
|
|
6
|
-
CreateDiffTriggerOptions,
|
|
7
|
-
DiffTriggerOperation,
|
|
8
|
-
TrackDiffOptions,
|
|
9
|
-
TriggerManager,
|
|
10
|
-
TriggerManagerConfig,
|
|
11
|
-
TriggerRemoveCallback,
|
|
12
|
-
TriggerRemoveCallbackOptions,
|
|
13
|
-
WithDiffOptions
|
|
14
|
-
} from './TriggerManager.js';
|
|
15
|
-
|
|
16
|
-
export type TriggerManagerImplOptions = TriggerManagerConfig & {
|
|
17
|
-
db: AbstractPowerSyncDatabase;
|
|
18
|
-
schema: Schema;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export type TriggerManagerImplConfiguration = {
|
|
22
|
-
useStorageByDefault: boolean;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export const DEFAULT_TRIGGER_MANAGER_CONFIGURATION: TriggerManagerImplConfiguration = {
|
|
26
|
-
useStorageByDefault: false
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* A record of persisted table/trigger information.
|
|
31
|
-
* This is used for fail-safe cleanup.
|
|
32
|
-
*/
|
|
33
|
-
type TrackedTableRecord = {
|
|
34
|
-
/**
|
|
35
|
-
* The id of the trigger. This is used in the SQLite trigger name
|
|
36
|
-
*/
|
|
37
|
-
id: string;
|
|
38
|
-
/**
|
|
39
|
-
* The destination table name for the trigger
|
|
40
|
-
*/
|
|
41
|
-
table: string;
|
|
42
|
-
/**
|
|
43
|
-
* Array of actual trigger names found for this table/id combo
|
|
44
|
-
*/
|
|
45
|
-
triggerNames: string[];
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
const TRIGGER_CLEANUP_INTERVAL_MS = 120_000; // 2 minutes
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* @internal
|
|
52
|
-
* @experimental
|
|
53
|
-
*/
|
|
54
|
-
export class TriggerManagerImpl implements TriggerManager {
|
|
55
|
-
protected schema: Schema;
|
|
56
|
-
|
|
57
|
-
protected defaultConfig: TriggerManagerImplConfiguration;
|
|
58
|
-
protected cleanupTimeout: ReturnType<typeof setTimeout> | null;
|
|
59
|
-
protected isDisposed: boolean;
|
|
60
|
-
|
|
61
|
-
constructor(protected options: TriggerManagerImplOptions) {
|
|
62
|
-
this.schema = options.schema;
|
|
63
|
-
options.db.registerListener({
|
|
64
|
-
schemaChanged: (schema) => {
|
|
65
|
-
this.schema = schema;
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
this.isDisposed = false;
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Configure a cleanup to run on an interval.
|
|
72
|
-
* The interval is configured using setTimeout to take the async
|
|
73
|
-
* execution time of the callback into account.
|
|
74
|
-
*/
|
|
75
|
-
this.defaultConfig = DEFAULT_TRIGGER_MANAGER_CONFIGURATION;
|
|
76
|
-
const cleanupCallback = async () => {
|
|
77
|
-
this.cleanupTimeout = null;
|
|
78
|
-
if (this.isDisposed) {
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
try {
|
|
82
|
-
await this.cleanupResources();
|
|
83
|
-
} catch (ex) {
|
|
84
|
-
this.db.logger.error(`Caught error while attempting to cleanup triggers`, ex);
|
|
85
|
-
} finally {
|
|
86
|
-
// if not closed, set another timeout
|
|
87
|
-
if (this.isDisposed) {
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
this.cleanupTimeout = setTimeout(cleanupCallback, TRIGGER_CLEANUP_INTERVAL_MS);
|
|
91
|
-
}
|
|
92
|
-
};
|
|
93
|
-
this.cleanupTimeout = setTimeout(cleanupCallback, TRIGGER_CLEANUP_INTERVAL_MS);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
protected get db() {
|
|
97
|
-
return this.options.db;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
protected async getUUID() {
|
|
101
|
-
const { id: uuid } = await this.db.get<{ id: string }>(/* sql */ `
|
|
102
|
-
SELECT
|
|
103
|
-
uuid () as id
|
|
104
|
-
`);
|
|
105
|
-
|
|
106
|
-
// Replace dashes with underscores for SQLite table/trigger name compatibility
|
|
107
|
-
return uuid.replace(/-/g, '_');
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
protected async removeTriggers(tx: LockContext, triggerIds: string[]) {
|
|
111
|
-
for (const triggerId of triggerIds) {
|
|
112
|
-
await tx.execute(/* sql */ `DROP TRIGGER IF EXISTS ${triggerId}; `);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
dispose() {
|
|
117
|
-
this.isDisposed = true;
|
|
118
|
-
if (this.cleanupTimeout) {
|
|
119
|
-
clearTimeout(this.cleanupTimeout);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Updates default config settings for platform specific use-cases.
|
|
125
|
-
*/
|
|
126
|
-
updateDefaults(config: TriggerManagerImplConfiguration) {
|
|
127
|
-
this.defaultConfig = {
|
|
128
|
-
...this.defaultConfig,
|
|
129
|
-
...config
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
protected generateTriggerName(operation: DiffTriggerOperation, destinationTable: string, triggerId: string) {
|
|
134
|
-
return `__ps_temp_trigger_${operation.toLowerCase()}__${destinationTable}__${triggerId}`;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Cleanup any SQLite triggers or tables that are no longer in use.
|
|
139
|
-
*/
|
|
140
|
-
async cleanupResources() {
|
|
141
|
-
// we use the database here since cleanupResources is called during the PowerSyncDatabase initialization
|
|
142
|
-
await this.db.database.writeLock(async (ctx) => {
|
|
143
|
-
/**
|
|
144
|
-
* Note: We only cleanup persisted triggers. These are tracked in the sqlite_master table.
|
|
145
|
-
* temporary triggers will not be affected by this.
|
|
146
|
-
* Query all triggers that match our naming pattern
|
|
147
|
-
*/
|
|
148
|
-
const triggers = await ctx.getAll<{ name: string }>(/* sql */ `
|
|
149
|
-
SELECT
|
|
150
|
-
name
|
|
151
|
-
FROM
|
|
152
|
-
sqlite_master
|
|
153
|
-
WHERE
|
|
154
|
-
type = 'trigger'
|
|
155
|
-
AND name LIKE '__ps_temp_trigger_%'
|
|
156
|
-
`);
|
|
157
|
-
|
|
158
|
-
/** Use regex to extract table names and IDs from trigger names
|
|
159
|
-
* Trigger naming convention: __ps_temp_trigger_<operation>__<destination_table>__<id>
|
|
160
|
-
*/
|
|
161
|
-
const triggerPattern = /^__ps_temp_trigger_(?:insert|update|delete)__(.+)__([a-f0-9_]{36})$/i;
|
|
162
|
-
const trackedItems = new Map<string, TrackedTableRecord>();
|
|
163
|
-
|
|
164
|
-
for (const trigger of triggers) {
|
|
165
|
-
const match = trigger.name.match(triggerPattern);
|
|
166
|
-
if (match) {
|
|
167
|
-
const [, table, id] = match;
|
|
168
|
-
// Collect all trigger names for each id combo
|
|
169
|
-
const existing = trackedItems.get(id);
|
|
170
|
-
if (existing) {
|
|
171
|
-
existing.triggerNames.push(trigger.name);
|
|
172
|
-
} else {
|
|
173
|
-
trackedItems.set(id, { table, id, triggerNames: [trigger.name] });
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
for (const trackedItem of trackedItems.values()) {
|
|
179
|
-
// check if there is anything holding on to this item
|
|
180
|
-
const hasClaim = await this.options.claimManager.checkClaim(trackedItem.id);
|
|
181
|
-
if (hasClaim) {
|
|
182
|
-
// This does not require cleanup
|
|
183
|
-
continue;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
this.db.logger.debug(`Clearing resources for trigger ${trackedItem.id} with table ${trackedItem.table}`);
|
|
187
|
-
|
|
188
|
-
// We need to delete the triggers and table
|
|
189
|
-
for (const triggerName of trackedItem.triggerNames) {
|
|
190
|
-
await ctx.execute(`DROP TRIGGER IF EXISTS ${triggerName}`);
|
|
191
|
-
}
|
|
192
|
-
await ctx.execute(`DROP TABLE IF EXISTS ${trackedItem.table}`);
|
|
193
|
-
}
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
async createDiffTrigger(options: CreateDiffTriggerOptions) {
|
|
198
|
-
await this.db.waitForReady();
|
|
199
|
-
const {
|
|
200
|
-
source,
|
|
201
|
-
destination,
|
|
202
|
-
columns,
|
|
203
|
-
when,
|
|
204
|
-
hooks,
|
|
205
|
-
setupContext,
|
|
206
|
-
// Fall back to the provided default if not given on this level
|
|
207
|
-
useStorage = this.defaultConfig.useStorageByDefault
|
|
208
|
-
} = options;
|
|
209
|
-
const operations = Object.keys(when) as DiffTriggerOperation[];
|
|
210
|
-
if (operations.length == 0) {
|
|
211
|
-
throw new Error('At least one WHEN operation must be specified for the trigger.');
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* The clause to use when executing
|
|
216
|
-
* CREATE ${tableTriggerTypeClause} TABLE
|
|
217
|
-
* OR
|
|
218
|
-
* CREATE ${tableTriggerTypeClause} TRIGGER
|
|
219
|
-
*/
|
|
220
|
-
const tableTriggerTypeClause = !useStorage ? 'TEMP' : '';
|
|
221
|
-
|
|
222
|
-
const whenClauses = Object.fromEntries(
|
|
223
|
-
Object.entries(when).map(([operation, filter]) => [operation, `WHEN ${filter}`])
|
|
224
|
-
);
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Allow specifying the View name as the source.
|
|
228
|
-
* We can lookup the internal table name from the schema.
|
|
229
|
-
*/
|
|
230
|
-
const sourceDefinition = this.schema.tables.find((table) => table.viewName == source);
|
|
231
|
-
if (!sourceDefinition) {
|
|
232
|
-
throw new Error(`Source table or view "${source}" not found in the schema.`);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
const replicatedColumns = columns ?? sourceDefinition.columns.map((col) => col.name);
|
|
236
|
-
|
|
237
|
-
const internalSource = sourceDefinition.internalName;
|
|
238
|
-
const triggerIds: string[] = [];
|
|
239
|
-
|
|
240
|
-
const id = await this.getUUID();
|
|
241
|
-
|
|
242
|
-
const releaseStorageClaim = useStorage ? await this.options.claimManager.obtainClaim(id) : null;
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* We default to replicating all columns if no columns array is provided.
|
|
246
|
-
*/
|
|
247
|
-
const jsonFragment = (source: 'NEW' | 'OLD' = 'NEW') => {
|
|
248
|
-
if (columns == null) {
|
|
249
|
-
// Track all columns
|
|
250
|
-
return `${source}.data`;
|
|
251
|
-
} else if (columns.length == 0) {
|
|
252
|
-
// Don't track any columns except for the id
|
|
253
|
-
return `'{}'`;
|
|
254
|
-
} else {
|
|
255
|
-
// Filter the data by the replicated columns
|
|
256
|
-
return `json_object(${replicatedColumns.map((col) => `'${col}', json_extract(${source}.data, '$.${col}')`).join(', ')})`;
|
|
257
|
-
}
|
|
258
|
-
};
|
|
259
|
-
|
|
260
|
-
const disposeWarningListener = this.db.registerListener({
|
|
261
|
-
schemaChanged: () => {
|
|
262
|
-
this.db.logger.warn(
|
|
263
|
-
`The PowerSync schema has changed while previously configured triggers are still operational. This might cause unexpected results.`
|
|
264
|
-
);
|
|
265
|
-
}
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* Declare the cleanup function early since if any of the init steps fail,
|
|
270
|
-
* we need to ensure we can cleanup the created resources.
|
|
271
|
-
* We unfortunately cannot rely on transaction rollback.
|
|
272
|
-
*/
|
|
273
|
-
const cleanup = async (options?: TriggerRemoveCallbackOptions) => {
|
|
274
|
-
const { context } = options ?? {};
|
|
275
|
-
disposeWarningListener();
|
|
276
|
-
const doCleanup = async (tx: LockContext) => {
|
|
277
|
-
await this.removeTriggers(tx, triggerIds);
|
|
278
|
-
await tx.execute(`DROP TABLE IF EXISTS ${destination};`);
|
|
279
|
-
await releaseStorageClaim?.();
|
|
280
|
-
};
|
|
281
|
-
if (context) {
|
|
282
|
-
await doCleanup(context);
|
|
283
|
-
} else {
|
|
284
|
-
await this.db.writeLock(doCleanup);
|
|
285
|
-
}
|
|
286
|
-
};
|
|
287
|
-
|
|
288
|
-
const setup = async (tx: LockContext) => {
|
|
289
|
-
// Allow user code to execute in this lock context before the trigger is created.
|
|
290
|
-
await hooks?.beforeCreate?.(tx);
|
|
291
|
-
await tx.execute(/* sql */ `
|
|
292
|
-
CREATE ${tableTriggerTypeClause} TABLE ${destination} (
|
|
293
|
-
operation_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
294
|
-
id TEXT,
|
|
295
|
-
operation TEXT,
|
|
296
|
-
timestamp TEXT,
|
|
297
|
-
value TEXT,
|
|
298
|
-
previous_value TEXT
|
|
299
|
-
)
|
|
300
|
-
`);
|
|
301
|
-
|
|
302
|
-
if (operations.includes(DiffTriggerOperation.INSERT)) {
|
|
303
|
-
const insertTriggerId = this.generateTriggerName(DiffTriggerOperation.INSERT, destination, id);
|
|
304
|
-
triggerIds.push(insertTriggerId);
|
|
305
|
-
|
|
306
|
-
await tx.execute(/* sql */ `
|
|
307
|
-
CREATE ${tableTriggerTypeClause} TRIGGER ${insertTriggerId} AFTER INSERT ON ${internalSource} ${whenClauses[
|
|
308
|
-
DiffTriggerOperation.INSERT
|
|
309
|
-
]} BEGIN
|
|
310
|
-
INSERT INTO
|
|
311
|
-
${destination} (id, operation, timestamp, value)
|
|
312
|
-
VALUES
|
|
313
|
-
(
|
|
314
|
-
NEW.id,
|
|
315
|
-
'INSERT',
|
|
316
|
-
strftime ('%Y-%m-%dT%H:%M:%fZ', 'now'),
|
|
317
|
-
${jsonFragment('NEW')}
|
|
318
|
-
);
|
|
319
|
-
|
|
320
|
-
END
|
|
321
|
-
`);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
if (operations.includes(DiffTriggerOperation.UPDATE)) {
|
|
325
|
-
const updateTriggerId = this.generateTriggerName(DiffTriggerOperation.UPDATE, destination, id);
|
|
326
|
-
triggerIds.push(updateTriggerId);
|
|
327
|
-
|
|
328
|
-
await tx.execute(/* sql */ `
|
|
329
|
-
CREATE ${tableTriggerTypeClause} TRIGGER ${updateTriggerId} AFTER
|
|
330
|
-
UPDATE ON ${internalSource} ${whenClauses[DiffTriggerOperation.UPDATE]} BEGIN
|
|
331
|
-
INSERT INTO
|
|
332
|
-
${destination} (id, operation, timestamp, value, previous_value)
|
|
333
|
-
VALUES
|
|
334
|
-
(
|
|
335
|
-
NEW.id,
|
|
336
|
-
'UPDATE',
|
|
337
|
-
strftime ('%Y-%m-%dT%H:%M:%fZ', 'now'),
|
|
338
|
-
${jsonFragment('NEW')},
|
|
339
|
-
${jsonFragment('OLD')}
|
|
340
|
-
);
|
|
341
|
-
|
|
342
|
-
END;
|
|
343
|
-
`);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
if (operations.includes(DiffTriggerOperation.DELETE)) {
|
|
347
|
-
const deleteTriggerId = this.generateTriggerName(DiffTriggerOperation.DELETE, destination, id);
|
|
348
|
-
triggerIds.push(deleteTriggerId);
|
|
349
|
-
|
|
350
|
-
// Create delete trigger for basic JSON
|
|
351
|
-
await tx.execute(/* sql */ `
|
|
352
|
-
CREATE ${tableTriggerTypeClause} TRIGGER ${deleteTriggerId} AFTER DELETE ON ${internalSource} ${whenClauses[
|
|
353
|
-
DiffTriggerOperation.DELETE
|
|
354
|
-
]} BEGIN
|
|
355
|
-
INSERT INTO
|
|
356
|
-
${destination} (id, operation, timestamp, value)
|
|
357
|
-
VALUES
|
|
358
|
-
(
|
|
359
|
-
OLD.id,
|
|
360
|
-
'DELETE',
|
|
361
|
-
strftime ('%Y-%m-%dT%H:%M:%fZ', 'now'),
|
|
362
|
-
${jsonFragment('OLD')}
|
|
363
|
-
);
|
|
364
|
-
|
|
365
|
-
END;
|
|
366
|
-
`);
|
|
367
|
-
}
|
|
368
|
-
};
|
|
369
|
-
|
|
370
|
-
try {
|
|
371
|
-
if (setupContext) {
|
|
372
|
-
await setup(setupContext);
|
|
373
|
-
} else {
|
|
374
|
-
await this.db.writeLock(setup);
|
|
375
|
-
}
|
|
376
|
-
return cleanup;
|
|
377
|
-
} catch (error) {
|
|
378
|
-
try {
|
|
379
|
-
await cleanup(setupContext ? { context: setupContext } : undefined);
|
|
380
|
-
} catch (cleanupError) {
|
|
381
|
-
throw new AggregateError([error, cleanupError], 'Error during operation and cleanup');
|
|
382
|
-
}
|
|
383
|
-
throw error;
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
async trackTableDiff(options: TrackDiffOptions): Promise<TriggerRemoveCallback> {
|
|
388
|
-
const { source, when, columns, hooks, throttleMs = DEFAULT_WATCH_THROTTLE_MS } = options;
|
|
389
|
-
|
|
390
|
-
await this.db.waitForReady();
|
|
391
|
-
|
|
392
|
-
/**
|
|
393
|
-
* Allow specifying the View name as the source.
|
|
394
|
-
* We can lookup the internal table name from the schema.
|
|
395
|
-
*/
|
|
396
|
-
const sourceDefinition = this.schema.tables.find((table) => table.viewName == source);
|
|
397
|
-
if (!sourceDefinition) {
|
|
398
|
-
throw new Error(`Source table or view "${source}" not found in the schema.`);
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
// The columns to present in the onChange context methods.
|
|
402
|
-
// If no array is provided, we use all columns from the source table.
|
|
403
|
-
const contextColumns = columns ?? sourceDefinition.columns.map((col) => col.name);
|
|
404
|
-
|
|
405
|
-
const id = await this.getUUID();
|
|
406
|
-
const destination = `__ps_temp_track_${source}_${id}`;
|
|
407
|
-
|
|
408
|
-
// register an onChange before the trigger is created
|
|
409
|
-
const abortController = new AbortController();
|
|
410
|
-
const abortOnChange = () => abortController.abort();
|
|
411
|
-
this.db.onChange(
|
|
412
|
-
{
|
|
413
|
-
// Note that the onChange events here have their execution scheduled.
|
|
414
|
-
// Callbacks are throttled and are sequential.
|
|
415
|
-
onChange: async () => {
|
|
416
|
-
if (abortController.signal.aborted) return;
|
|
417
|
-
|
|
418
|
-
// Run the handler in a write lock to keep the state of the
|
|
419
|
-
// destination table consistent.
|
|
420
|
-
await this.db.writeTransaction(async (tx) => {
|
|
421
|
-
const callbackResult = await options.onChange({
|
|
422
|
-
...tx,
|
|
423
|
-
destinationTable: destination,
|
|
424
|
-
withDiff: async <T>(query, params, options?: WithDiffOptions) => {
|
|
425
|
-
// Wrap the query to expose the destination table
|
|
426
|
-
const operationIdSelect = options?.castOperationIdAsText
|
|
427
|
-
? 'id, operation, CAST(operation_id AS TEXT) as operation_id, timestamp, value, previous_value'
|
|
428
|
-
: '*';
|
|
429
|
-
const wrappedQuery = /* sql */ `
|
|
430
|
-
WITH
|
|
431
|
-
DIFF AS (
|
|
432
|
-
SELECT
|
|
433
|
-
${operationIdSelect}
|
|
434
|
-
FROM
|
|
435
|
-
${destination}
|
|
436
|
-
ORDER BY
|
|
437
|
-
operation_id ASC
|
|
438
|
-
) ${query}
|
|
439
|
-
`;
|
|
440
|
-
return tx.getAll<T>(wrappedQuery, params);
|
|
441
|
-
},
|
|
442
|
-
withExtractedDiff: async <T>(query, params) => {
|
|
443
|
-
// Wrap the query to expose the destination table
|
|
444
|
-
const wrappedQuery = /* sql */ `
|
|
445
|
-
WITH
|
|
446
|
-
DIFF AS (
|
|
447
|
-
SELECT
|
|
448
|
-
id,
|
|
449
|
-
${contextColumns.length > 0
|
|
450
|
-
? `${contextColumns.map((col) => `json_extract(value, '$.${col}') as ${col}`).join(', ')},`
|
|
451
|
-
: ''} operation_id as __operation_id,
|
|
452
|
-
operation as __operation,
|
|
453
|
-
timestamp as __timestamp,
|
|
454
|
-
previous_value as __previous_value
|
|
455
|
-
FROM
|
|
456
|
-
${destination}
|
|
457
|
-
ORDER BY
|
|
458
|
-
__operation_id ASC
|
|
459
|
-
) ${query}
|
|
460
|
-
`;
|
|
461
|
-
return tx.getAll<T>(wrappedQuery, params);
|
|
462
|
-
}
|
|
463
|
-
});
|
|
464
|
-
|
|
465
|
-
// Clear the destination table after processing
|
|
466
|
-
await tx.execute(/* sql */ `DELETE FROM ${destination};`);
|
|
467
|
-
return callbackResult;
|
|
468
|
-
});
|
|
469
|
-
}
|
|
470
|
-
},
|
|
471
|
-
{ tables: [destination], signal: abortController.signal, throttleMs }
|
|
472
|
-
);
|
|
473
|
-
|
|
474
|
-
try {
|
|
475
|
-
const removeTrigger = await this.createDiffTrigger({
|
|
476
|
-
source,
|
|
477
|
-
destination,
|
|
478
|
-
columns: contextColumns,
|
|
479
|
-
when,
|
|
480
|
-
hooks
|
|
481
|
-
});
|
|
482
|
-
|
|
483
|
-
return async () => {
|
|
484
|
-
abortOnChange();
|
|
485
|
-
await removeTrigger();
|
|
486
|
-
};
|
|
487
|
-
} catch (error) {
|
|
488
|
-
try {
|
|
489
|
-
abortOnChange();
|
|
490
|
-
} catch (cleanupError) {
|
|
491
|
-
throw new AggregateError([error, cleanupError], 'Error during operation and cleanup');
|
|
492
|
-
}
|
|
493
|
-
throw error;
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
}
|