@powersync/common 1.40.0 → 1.41.1
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 +10809 -22
- package/dist/bundle.cjs.map +1 -0
- package/dist/bundle.mjs +10730 -22
- package/dist/bundle.mjs.map +1 -0
- package/dist/bundle.node.cjs +10809 -0
- package/dist/bundle.node.cjs.map +1 -0
- package/dist/bundle.node.mjs +10730 -0
- package/dist/bundle.node.mjs.map +1 -0
- package/dist/index.d.cts +5 -1
- package/lib/client/AbstractPowerSyncDatabase.js +1 -0
- package/lib/client/AbstractPowerSyncDatabase.js.map +1 -0
- package/lib/client/AbstractPowerSyncOpenFactory.js +1 -0
- package/lib/client/AbstractPowerSyncOpenFactory.js.map +1 -0
- package/lib/client/ConnectionManager.js +1 -0
- package/lib/client/ConnectionManager.js.map +1 -0
- package/lib/client/CustomQuery.js +1 -0
- package/lib/client/CustomQuery.js.map +1 -0
- package/lib/client/Query.js +1 -0
- package/lib/client/Query.js.map +1 -0
- package/lib/client/SQLOpenFactory.js +1 -0
- package/lib/client/SQLOpenFactory.js.map +1 -0
- package/lib/client/compilableQueryWatch.js +1 -0
- package/lib/client/compilableQueryWatch.js.map +1 -0
- package/lib/client/connection/PowerSyncBackendConnector.js +1 -0
- package/lib/client/connection/PowerSyncBackendConnector.js.map +1 -0
- package/lib/client/connection/PowerSyncCredentials.js +1 -0
- package/lib/client/connection/PowerSyncCredentials.js.map +1 -0
- package/lib/client/constants.js +1 -0
- package/lib/client/constants.js.map +1 -0
- package/lib/client/runOnSchemaChange.js +1 -0
- package/lib/client/runOnSchemaChange.js.map +1 -0
- package/lib/client/sync/bucket/BucketStorageAdapter.js +1 -0
- package/lib/client/sync/bucket/BucketStorageAdapter.js.map +1 -0
- package/lib/client/sync/bucket/CrudBatch.js +1 -0
- package/lib/client/sync/bucket/CrudBatch.js.map +1 -0
- package/lib/client/sync/bucket/CrudEntry.js +1 -0
- package/lib/client/sync/bucket/CrudEntry.js.map +1 -0
- package/lib/client/sync/bucket/CrudTransaction.js +1 -0
- package/lib/client/sync/bucket/CrudTransaction.js.map +1 -0
- package/lib/client/sync/bucket/OpType.js +1 -0
- package/lib/client/sync/bucket/OpType.js.map +1 -0
- package/lib/client/sync/bucket/OplogEntry.js +1 -0
- package/lib/client/sync/bucket/OplogEntry.js.map +1 -0
- package/lib/client/sync/bucket/SqliteBucketStorage.js +1 -0
- package/lib/client/sync/bucket/SqliteBucketStorage.js.map +1 -0
- package/lib/client/sync/bucket/SyncDataBatch.js +1 -0
- package/lib/client/sync/bucket/SyncDataBatch.js.map +1 -0
- package/lib/client/sync/bucket/SyncDataBucket.js +1 -0
- package/lib/client/sync/bucket/SyncDataBucket.js.map +1 -0
- package/lib/client/sync/stream/AbstractRemote.d.ts +5 -0
- package/lib/client/sync/stream/AbstractRemote.js +9 -2
- package/lib/client/sync/stream/AbstractRemote.js.map +1 -0
- package/lib/client/sync/stream/AbstractStreamingSyncImplementation.js +1 -0
- package/lib/client/sync/stream/AbstractStreamingSyncImplementation.js.map +1 -0
- package/lib/client/sync/stream/WebsocketClientTransport.js +1 -0
- package/lib/client/sync/stream/WebsocketClientTransport.js.map +1 -0
- package/lib/client/sync/stream/core-instruction.js +1 -0
- package/lib/client/sync/stream/core-instruction.js.map +1 -0
- package/lib/client/sync/stream/streaming-sync-types.js +1 -0
- package/lib/client/sync/stream/streaming-sync-types.js.map +1 -0
- package/lib/client/sync/sync-streams.js +1 -0
- package/lib/client/sync/sync-streams.js.map +1 -0
- package/lib/client/triggers/TriggerManager.js +1 -0
- package/lib/client/triggers/TriggerManager.js.map +1 -0
- package/lib/client/triggers/TriggerManagerImpl.js +1 -0
- package/lib/client/triggers/TriggerManagerImpl.js.map +1 -0
- package/lib/client/triggers/sanitizeSQL.js +1 -0
- package/lib/client/triggers/sanitizeSQL.js.map +1 -0
- package/lib/client/watched/GetAllQuery.js +1 -0
- package/lib/client/watched/GetAllQuery.js.map +1 -0
- package/lib/client/watched/WatchedQuery.js +1 -0
- package/lib/client/watched/WatchedQuery.js.map +1 -0
- package/lib/client/watched/processors/AbstractQueryProcessor.js +1 -0
- package/lib/client/watched/processors/AbstractQueryProcessor.js.map +1 -0
- package/lib/client/watched/processors/DifferentialQueryProcessor.js +1 -0
- package/lib/client/watched/processors/DifferentialQueryProcessor.js.map +1 -0
- package/lib/client/watched/processors/OnChangeQueryProcessor.js +1 -0
- package/lib/client/watched/processors/OnChangeQueryProcessor.js.map +1 -0
- package/lib/client/watched/processors/comparators.js +1 -0
- package/lib/client/watched/processors/comparators.js.map +1 -0
- package/lib/db/DBAdapter.js +1 -0
- package/lib/db/DBAdapter.js.map +1 -0
- package/lib/db/crud/SyncProgress.js +1 -0
- package/lib/db/crud/SyncProgress.js.map +1 -0
- package/lib/db/crud/SyncStatus.js +1 -0
- package/lib/db/crud/SyncStatus.js.map +1 -0
- package/lib/db/crud/UploadQueueStatus.js +1 -0
- package/lib/db/crud/UploadQueueStatus.js.map +1 -0
- package/lib/db/schema/Column.js +1 -0
- package/lib/db/schema/Column.js.map +1 -0
- package/lib/db/schema/Index.js +1 -0
- package/lib/db/schema/Index.js.map +1 -0
- package/lib/db/schema/IndexedColumn.js +1 -0
- package/lib/db/schema/IndexedColumn.js.map +1 -0
- package/lib/db/schema/RawTable.js +1 -0
- package/lib/db/schema/RawTable.js.map +1 -0
- package/lib/db/schema/Schema.d.ts +0 -1
- package/lib/db/schema/Schema.js +4 -8
- package/lib/db/schema/Schema.js.map +1 -0
- package/lib/db/schema/Table.js +1 -0
- package/lib/db/schema/Table.js.map +1 -0
- package/lib/db/schema/TableV2.js +1 -0
- package/lib/db/schema/TableV2.js.map +1 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -0
- package/lib/types/types.js +1 -0
- package/lib/types/types.js.map +1 -0
- package/lib/utils/AbortOperation.js +1 -0
- package/lib/utils/AbortOperation.js.map +1 -0
- package/lib/utils/BaseObserver.js +1 -0
- package/lib/utils/BaseObserver.js.map +1 -0
- package/lib/utils/ControlledExecutor.js +1 -0
- package/lib/utils/ControlledExecutor.js.map +1 -0
- package/lib/utils/DataStream.js +1 -0
- package/lib/utils/DataStream.js.map +1 -0
- package/lib/utils/Logger.js +1 -0
- package/lib/utils/Logger.js.map +1 -0
- package/lib/utils/MetaBaseObserver.js +1 -0
- package/lib/utils/MetaBaseObserver.js.map +1 -0
- package/lib/utils/async.js +1 -0
- package/lib/utils/async.js.map +1 -0
- package/lib/utils/mutex.js +1 -0
- package/lib/utils/mutex.js.map +1 -0
- package/lib/utils/parseQuery.js +1 -0
- package/lib/utils/parseQuery.js.map +1 -0
- package/package.json +23 -15
- package/src/client/AbstractPowerSyncDatabase.ts +1343 -0
- package/src/client/AbstractPowerSyncOpenFactory.ts +39 -0
- package/src/client/ConnectionManager.ts +402 -0
- package/src/client/CustomQuery.ts +56 -0
- package/src/client/Query.ts +106 -0
- package/src/client/SQLOpenFactory.ts +55 -0
- package/src/client/compilableQueryWatch.ts +55 -0
- package/src/client/connection/PowerSyncBackendConnector.ts +25 -0
- package/src/client/connection/PowerSyncCredentials.ts +5 -0
- package/src/client/constants.ts +1 -0
- package/src/client/runOnSchemaChange.ts +31 -0
- package/src/client/sync/bucket/BucketStorageAdapter.ts +118 -0
- package/src/client/sync/bucket/CrudBatch.ts +21 -0
- package/src/client/sync/bucket/CrudEntry.ts +172 -0
- package/src/client/sync/bucket/CrudTransaction.ts +21 -0
- package/src/client/sync/bucket/OpType.ts +23 -0
- package/src/client/sync/bucket/OplogEntry.ts +50 -0
- package/src/client/sync/bucket/SqliteBucketStorage.ts +395 -0
- package/src/client/sync/bucket/SyncDataBatch.ts +11 -0
- package/src/client/sync/bucket/SyncDataBucket.ts +49 -0
- package/src/client/sync/stream/AbstractRemote.ts +626 -0
- package/src/client/sync/stream/AbstractStreamingSyncImplementation.ts +1258 -0
- package/src/client/sync/stream/WebsocketClientTransport.ts +80 -0
- package/src/client/sync/stream/core-instruction.ts +99 -0
- package/src/client/sync/stream/streaming-sync-types.ts +205 -0
- package/src/client/sync/sync-streams.ts +107 -0
- package/src/client/triggers/TriggerManager.ts +384 -0
- package/src/client/triggers/TriggerManagerImpl.ts +314 -0
- package/src/client/triggers/sanitizeSQL.ts +66 -0
- package/src/client/watched/GetAllQuery.ts +46 -0
- package/src/client/watched/WatchedQuery.ts +121 -0
- package/src/client/watched/processors/AbstractQueryProcessor.ts +226 -0
- package/src/client/watched/processors/DifferentialQueryProcessor.ts +305 -0
- package/src/client/watched/processors/OnChangeQueryProcessor.ts +122 -0
- package/src/client/watched/processors/comparators.ts +57 -0
- package/src/db/DBAdapter.ts +134 -0
- package/src/db/crud/SyncProgress.ts +100 -0
- package/src/db/crud/SyncStatus.ts +308 -0
- package/src/db/crud/UploadQueueStatus.ts +20 -0
- package/src/db/schema/Column.ts +60 -0
- package/src/db/schema/Index.ts +39 -0
- package/src/db/schema/IndexedColumn.ts +42 -0
- package/src/db/schema/RawTable.ts +67 -0
- package/src/db/schema/Schema.ts +76 -0
- package/src/db/schema/Table.ts +359 -0
- package/src/db/schema/TableV2.ts +9 -0
- package/src/index.ts +52 -0
- package/src/types/types.ts +9 -0
- package/src/utils/AbortOperation.ts +17 -0
- package/src/utils/BaseObserver.ts +41 -0
- package/src/utils/ControlledExecutor.ts +72 -0
- package/src/utils/DataStream.ts +211 -0
- package/src/utils/Logger.ts +47 -0
- package/src/utils/MetaBaseObserver.ts +81 -0
- package/src/utils/async.ts +61 -0
- package/src/utils/mutex.ts +34 -0
- package/src/utils/parseQuery.ts +25 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { WatchCompatibleQuery, WatchedQuery, WatchedQueryListener, WatchedQueryOptions } from '../WatchedQuery.js';
|
|
2
|
+
import {
|
|
3
|
+
AbstractQueryProcessor,
|
|
4
|
+
AbstractQueryProcessorOptions,
|
|
5
|
+
LinkQueryOptions,
|
|
6
|
+
MutableWatchedQueryState
|
|
7
|
+
} from './AbstractQueryProcessor.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Represents an updated row in a differential watched query.
|
|
11
|
+
* It contains both the current and previous state of the row.
|
|
12
|
+
*/
|
|
13
|
+
export interface WatchedQueryRowDifferential<RowType> {
|
|
14
|
+
readonly current: RowType;
|
|
15
|
+
readonly previous: RowType;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Represents the result of a watched query that has been diffed.
|
|
20
|
+
* {@link DifferentialWatchedQueryState#diff} is of the {@link WatchedQueryDifferential} form.
|
|
21
|
+
*/
|
|
22
|
+
export interface WatchedQueryDifferential<RowType> {
|
|
23
|
+
readonly added: ReadonlyArray<Readonly<RowType>>;
|
|
24
|
+
/**
|
|
25
|
+
* The entire current result set.
|
|
26
|
+
* Array item object references are preserved between updates if the item is unchanged.
|
|
27
|
+
*
|
|
28
|
+
* e.g. In the query
|
|
29
|
+
* ```sql
|
|
30
|
+
* SELECT name, make FROM assets ORDER BY make ASC;
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* If a previous result set contains an item (A) `{name: 'pc', make: 'Cool PC'}` and
|
|
34
|
+
* an update has been made which adds another item (B) to the result set (the item A is unchanged) - then
|
|
35
|
+
* the updated result set will be contain the same object reference, to item A, as the previous result set.
|
|
36
|
+
* This is regardless of the item A's position in the updated result set.
|
|
37
|
+
*/
|
|
38
|
+
readonly all: ReadonlyArray<Readonly<RowType>>;
|
|
39
|
+
readonly removed: ReadonlyArray<Readonly<RowType>>;
|
|
40
|
+
readonly updated: ReadonlyArray<WatchedQueryRowDifferential<Readonly<RowType>>>;
|
|
41
|
+
readonly unchanged: ReadonlyArray<Readonly<RowType>>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Row comparator for differentially watched queries which keys and compares items in the result set.
|
|
46
|
+
*/
|
|
47
|
+
export interface DifferentialWatchedQueryComparator<RowType> {
|
|
48
|
+
/**
|
|
49
|
+
* Generates a unique key for the item.
|
|
50
|
+
*/
|
|
51
|
+
keyBy: (item: RowType) => string;
|
|
52
|
+
/**
|
|
53
|
+
* Generates a token for comparing items with matching keys.
|
|
54
|
+
*/
|
|
55
|
+
compareBy: (item: RowType) => string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Options for building a differential watched query with the {@link Query} builder.
|
|
60
|
+
*/
|
|
61
|
+
export interface DifferentialWatchedQueryOptions<RowType> extends WatchedQueryOptions {
|
|
62
|
+
/**
|
|
63
|
+
* Initial result data which is presented while the initial loading is executing.
|
|
64
|
+
*/
|
|
65
|
+
placeholderData?: RowType[];
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Row comparator used to identify and compare rows in the result set.
|
|
69
|
+
* If not provided, the default comparator will be used which keys items by their `id` property if available,
|
|
70
|
+
* otherwise it uses JSON stringification of the entire item for keying and comparison.
|
|
71
|
+
* @defaultValue {@link DEFAULT_ROW_COMPARATOR}
|
|
72
|
+
*/
|
|
73
|
+
rowComparator?: DifferentialWatchedQueryComparator<RowType>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Settings for differential incremental watched queries using.
|
|
78
|
+
*/
|
|
79
|
+
export interface DifferentialWatchedQuerySettings<RowType> extends DifferentialWatchedQueryOptions<RowType> {
|
|
80
|
+
/**
|
|
81
|
+
* The query here must return an array of items that can be differentiated.
|
|
82
|
+
*/
|
|
83
|
+
query: WatchCompatibleQuery<RowType[]>;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface DifferentialWatchedQueryListener<RowType>
|
|
87
|
+
extends WatchedQueryListener<ReadonlyArray<Readonly<RowType>>> {
|
|
88
|
+
onDiff?: (diff: WatchedQueryDifferential<RowType>) => void | Promise<void>;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export type DifferentialWatchedQuery<RowType> = WatchedQuery<
|
|
92
|
+
ReadonlyArray<Readonly<RowType>>,
|
|
93
|
+
DifferentialWatchedQuerySettings<RowType>,
|
|
94
|
+
DifferentialWatchedQueryListener<RowType>
|
|
95
|
+
>;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @internal
|
|
99
|
+
*/
|
|
100
|
+
export interface DifferentialQueryProcessorOptions<RowType>
|
|
101
|
+
extends AbstractQueryProcessorOptions<RowType[], DifferentialWatchedQuerySettings<RowType>> {
|
|
102
|
+
rowComparator?: DifferentialWatchedQueryComparator<RowType>;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
type DataHashMap<RowType> = Map<string, { hash: string; item: RowType }>;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* An empty differential result set.
|
|
109
|
+
* This is used as the initial state for differential incrementally watched queries.
|
|
110
|
+
*/
|
|
111
|
+
export const EMPTY_DIFFERENTIAL = {
|
|
112
|
+
added: [],
|
|
113
|
+
all: [],
|
|
114
|
+
removed: [],
|
|
115
|
+
updated: [],
|
|
116
|
+
unchanged: []
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Default implementation of the {@link DifferentialWatchedQueryComparator} for watched queries.
|
|
121
|
+
* It keys items by their `id` property if available, alternatively it uses JSON stringification
|
|
122
|
+
* of the entire item for the key and comparison.
|
|
123
|
+
*/
|
|
124
|
+
export const DEFAULT_ROW_COMPARATOR: DifferentialWatchedQueryComparator<any> = {
|
|
125
|
+
keyBy: (item) => {
|
|
126
|
+
if (item && typeof item == 'object' && typeof item['id'] == 'string') {
|
|
127
|
+
return item['id'];
|
|
128
|
+
}
|
|
129
|
+
return JSON.stringify(item);
|
|
130
|
+
},
|
|
131
|
+
compareBy: (item) => JSON.stringify(item)
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Uses the PowerSync onChange event to trigger watched queries.
|
|
136
|
+
* Results are emitted on every change of the relevant tables.
|
|
137
|
+
* @internal
|
|
138
|
+
*/
|
|
139
|
+
export class DifferentialQueryProcessor<RowType>
|
|
140
|
+
extends AbstractQueryProcessor<ReadonlyArray<Readonly<RowType>>, DifferentialWatchedQuerySettings<RowType>>
|
|
141
|
+
implements DifferentialWatchedQuery<RowType>
|
|
142
|
+
{
|
|
143
|
+
protected comparator: DifferentialWatchedQueryComparator<RowType>;
|
|
144
|
+
|
|
145
|
+
constructor(protected options: DifferentialQueryProcessorOptions<RowType>) {
|
|
146
|
+
super(options);
|
|
147
|
+
this.comparator = options.rowComparator ?? DEFAULT_ROW_COMPARATOR;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/*
|
|
151
|
+
* @returns If the sets are equal
|
|
152
|
+
*/
|
|
153
|
+
protected differentiate(
|
|
154
|
+
current: RowType[],
|
|
155
|
+
previousMap: DataHashMap<RowType>
|
|
156
|
+
): { diff: WatchedQueryDifferential<RowType>; map: DataHashMap<RowType>; hasChanged: boolean } {
|
|
157
|
+
const { keyBy, compareBy } = this.comparator;
|
|
158
|
+
|
|
159
|
+
let hasChanged = false;
|
|
160
|
+
const currentMap = new Map<string, { hash: string; item: RowType }>();
|
|
161
|
+
const removedTracker = new Set(previousMap.keys());
|
|
162
|
+
|
|
163
|
+
// Allow mutating to populate the data temporarily.
|
|
164
|
+
const diff = {
|
|
165
|
+
all: [] as RowType[],
|
|
166
|
+
added: [] as RowType[],
|
|
167
|
+
removed: [] as RowType[],
|
|
168
|
+
updated: [] as WatchedQueryRowDifferential<RowType>[],
|
|
169
|
+
unchanged: [] as RowType[]
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Looping over the current result set array is important to preserve
|
|
174
|
+
* the ordering of the result set.
|
|
175
|
+
* We can replace items in the current array with previous object references if they are equal.
|
|
176
|
+
*/
|
|
177
|
+
for (const item of current) {
|
|
178
|
+
const key = keyBy(item);
|
|
179
|
+
const hash = compareBy(item);
|
|
180
|
+
currentMap.set(key, { hash, item });
|
|
181
|
+
|
|
182
|
+
const previousItem = previousMap.get(key);
|
|
183
|
+
if (!previousItem) {
|
|
184
|
+
// New item
|
|
185
|
+
hasChanged = true;
|
|
186
|
+
diff.added.push(item);
|
|
187
|
+
diff.all.push(item);
|
|
188
|
+
} else {
|
|
189
|
+
// Existing item
|
|
190
|
+
if (hash == previousItem.hash) {
|
|
191
|
+
diff.unchanged.push(previousItem.item);
|
|
192
|
+
// Use the previous object reference
|
|
193
|
+
diff.all.push(previousItem.item);
|
|
194
|
+
// update the map to preserve the reference
|
|
195
|
+
currentMap.set(key, previousItem);
|
|
196
|
+
} else {
|
|
197
|
+
hasChanged = true;
|
|
198
|
+
diff.updated.push({ current: item, previous: previousItem.item });
|
|
199
|
+
// Use the new reference
|
|
200
|
+
diff.all.push(item);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// The item is present, we don't consider it removed
|
|
204
|
+
removedTracker.delete(key);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
diff.removed = Array.from(removedTracker).map((key) => previousMap.get(key)!.item);
|
|
208
|
+
hasChanged = hasChanged || diff.removed.length > 0;
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
diff,
|
|
212
|
+
hasChanged,
|
|
213
|
+
map: currentMap
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
protected async linkQuery(options: LinkQueryOptions<WatchedQueryDifferential<RowType>>): Promise<void> {
|
|
218
|
+
const { db, watchOptions } = this.options;
|
|
219
|
+
const { abortSignal } = options;
|
|
220
|
+
|
|
221
|
+
const compiledQuery = watchOptions.query.compile();
|
|
222
|
+
const tables = await db.resolveTables(compiledQuery.sql, compiledQuery.parameters as any[], {
|
|
223
|
+
tables: options.settings.triggerOnTables
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
let currentMap: DataHashMap<RowType> = new Map();
|
|
227
|
+
|
|
228
|
+
// populate the currentMap from the placeholder data
|
|
229
|
+
this.state.data.forEach((item) => {
|
|
230
|
+
currentMap.set(this.comparator.keyBy(item), {
|
|
231
|
+
hash: this.comparator.compareBy(item),
|
|
232
|
+
item
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
db.onChangeWithCallback(
|
|
237
|
+
{
|
|
238
|
+
onChange: async () => {
|
|
239
|
+
if (this.closed || abortSignal.aborted) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
// This fires for each change of the relevant tables
|
|
243
|
+
try {
|
|
244
|
+
if (this.reportFetching && !this.state.isFetching) {
|
|
245
|
+
await this.updateState({ isFetching: true });
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const partialStateUpdate: Partial<MutableWatchedQueryState<RowType[]>> = {};
|
|
249
|
+
|
|
250
|
+
// Always run the query if an underlying table has changed
|
|
251
|
+
const result = await watchOptions.query.execute({
|
|
252
|
+
sql: compiledQuery.sql,
|
|
253
|
+
// Allows casting from ReadOnlyArray[unknown] to Array<unknown>
|
|
254
|
+
// This allows simpler compatibility with PowerSync queries
|
|
255
|
+
parameters: [...compiledQuery.parameters],
|
|
256
|
+
db: this.options.db
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
if (abortSignal.aborted) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (this.reportFetching) {
|
|
264
|
+
partialStateUpdate.isFetching = false;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (this.state.isLoading) {
|
|
268
|
+
partialStateUpdate.isLoading = false;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const { diff, hasChanged, map } = this.differentiate(result, currentMap);
|
|
272
|
+
// Update for future comparisons
|
|
273
|
+
currentMap = map;
|
|
274
|
+
|
|
275
|
+
if (hasChanged) {
|
|
276
|
+
await this.iterateAsyncListenersWithError((l) => l.onDiff?.(diff));
|
|
277
|
+
Object.assign(partialStateUpdate, {
|
|
278
|
+
data: diff.all
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (this.state.error) {
|
|
283
|
+
partialStateUpdate.error = null;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (Object.keys(partialStateUpdate).length > 0) {
|
|
287
|
+
await this.updateState(partialStateUpdate);
|
|
288
|
+
}
|
|
289
|
+
} catch (error) {
|
|
290
|
+
await this.updateState({ error });
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
onError: async (error) => {
|
|
294
|
+
await this.updateState({ error });
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
signal: abortSignal,
|
|
299
|
+
tables,
|
|
300
|
+
throttleMs: watchOptions.throttleMs,
|
|
301
|
+
triggerImmediate: true // used to emit the initial state
|
|
302
|
+
}
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { WatchCompatibleQuery, WatchedQuery, WatchedQueryOptions } from '../WatchedQuery.js';
|
|
2
|
+
import {
|
|
3
|
+
AbstractQueryProcessor,
|
|
4
|
+
AbstractQueryProcessorOptions,
|
|
5
|
+
LinkQueryOptions,
|
|
6
|
+
MutableWatchedQueryState
|
|
7
|
+
} from './AbstractQueryProcessor.js';
|
|
8
|
+
import { WatchedQueryComparator } from './comparators.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Settings for {@link WatchedQuery} instances created via {@link Query#watch}.
|
|
12
|
+
*/
|
|
13
|
+
export interface WatchedQuerySettings<DataType> extends WatchedQueryOptions {
|
|
14
|
+
query: WatchCompatibleQuery<DataType>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* {@link WatchedQuery} returned from {@link Query#watch}.
|
|
19
|
+
*/
|
|
20
|
+
export type StandardWatchedQuery<DataType> = WatchedQuery<DataType, WatchedQuerySettings<DataType>>;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
25
|
+
export interface OnChangeQueryProcessorOptions<Data>
|
|
26
|
+
extends AbstractQueryProcessorOptions<Data, WatchedQuerySettings<Data>> {
|
|
27
|
+
comparator?: WatchedQueryComparator<Data>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Uses the PowerSync onChange event to trigger watched queries.
|
|
32
|
+
* Results are emitted on every change of the relevant tables.
|
|
33
|
+
* @internal
|
|
34
|
+
*/
|
|
35
|
+
export class OnChangeQueryProcessor<Data> extends AbstractQueryProcessor<Data, WatchedQuerySettings<Data>> {
|
|
36
|
+
constructor(protected options: OnChangeQueryProcessorOptions<Data>) {
|
|
37
|
+
super(options);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @returns If the sets are equal
|
|
42
|
+
*/
|
|
43
|
+
protected checkEquality(current: Data, previous: Data): boolean {
|
|
44
|
+
// Use the provided comparator if available. Assume values are unique if not available.
|
|
45
|
+
return this.options.comparator?.checkEquality?.(current, previous) ?? false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
protected async linkQuery(options: LinkQueryOptions<Data>): Promise<void> {
|
|
49
|
+
const { db, watchOptions } = this.options;
|
|
50
|
+
const { abortSignal } = options;
|
|
51
|
+
|
|
52
|
+
const compiledQuery = watchOptions.query.compile();
|
|
53
|
+
const tables = await db.resolveTables(compiledQuery.sql, compiledQuery.parameters as any[], {
|
|
54
|
+
tables: options.settings.triggerOnTables
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
db.onChangeWithCallback(
|
|
58
|
+
{
|
|
59
|
+
onChange: async () => {
|
|
60
|
+
if (this.closed || abortSignal.aborted) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
// This fires for each change of the relevant tables
|
|
64
|
+
try {
|
|
65
|
+
if (this.reportFetching && !this.state.isFetching) {
|
|
66
|
+
await this.updateState({ isFetching: true });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const partialStateUpdate: Partial<MutableWatchedQueryState<Data>> & { data?: Data } = {};
|
|
70
|
+
|
|
71
|
+
// Always run the query if an underlying table has changed
|
|
72
|
+
const result = await watchOptions.query.execute({
|
|
73
|
+
sql: compiledQuery.sql,
|
|
74
|
+
// Allows casting from ReadOnlyArray[unknown] to Array<unknown>
|
|
75
|
+
// This allows simpler compatibility with PowerSync queries
|
|
76
|
+
parameters: [...compiledQuery.parameters],
|
|
77
|
+
db: this.options.db
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
if (abortSignal.aborted) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (this.reportFetching) {
|
|
85
|
+
partialStateUpdate.isFetching = false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (this.state.isLoading) {
|
|
89
|
+
partialStateUpdate.isLoading = false;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Check if the result has changed
|
|
93
|
+
if (!this.checkEquality(result, this.state.data)) {
|
|
94
|
+
Object.assign(partialStateUpdate, {
|
|
95
|
+
data: result
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (this.state.error) {
|
|
100
|
+
partialStateUpdate.error = null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (Object.keys(partialStateUpdate).length > 0) {
|
|
104
|
+
await this.updateState(partialStateUpdate);
|
|
105
|
+
}
|
|
106
|
+
} catch (error) {
|
|
107
|
+
await this.updateState({ error });
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
onError: async (error) => {
|
|
111
|
+
await this.updateState({ error });
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
signal: abortSignal,
|
|
116
|
+
tables,
|
|
117
|
+
throttleMs: watchOptions.throttleMs,
|
|
118
|
+
triggerImmediate: true // used to emit the initial state
|
|
119
|
+
}
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A basic comparator for incrementally watched queries. This performs a single comparison which
|
|
3
|
+
* determines if the result set has changed. The {@link WatchedQuery} will only emit the new result
|
|
4
|
+
* if a change has been detected.
|
|
5
|
+
*/
|
|
6
|
+
export interface WatchedQueryComparator<Data> {
|
|
7
|
+
checkEquality: (current: Data, previous: Data) => boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Options for {@link ArrayComparator}
|
|
12
|
+
*/
|
|
13
|
+
export type ArrayComparatorOptions<ItemType> = {
|
|
14
|
+
/**
|
|
15
|
+
* Returns a string to uniquely identify an item in the array.
|
|
16
|
+
*/
|
|
17
|
+
compareBy: (item: ItemType) => string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* An efficient comparator for {@link WatchedQuery} created with {@link Query#watch}. This has the ability to determine if a query
|
|
22
|
+
* result has changes without necessarily processing all items in the result.
|
|
23
|
+
*/
|
|
24
|
+
export class ArrayComparator<ItemType> implements WatchedQueryComparator<ItemType[]> {
|
|
25
|
+
constructor(protected options: ArrayComparatorOptions<ItemType>) {}
|
|
26
|
+
|
|
27
|
+
checkEquality(current: ItemType[], previous: ItemType[]) {
|
|
28
|
+
if (current.length === 0 && previous.length === 0) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (current.length !== previous.length) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const { compareBy } = this.options;
|
|
37
|
+
|
|
38
|
+
// At this point the lengths are equal
|
|
39
|
+
for (let i = 0; i < current.length; i++) {
|
|
40
|
+
const currentItem = compareBy(current[i]);
|
|
41
|
+
const previousItem = compareBy(previous[i]);
|
|
42
|
+
|
|
43
|
+
if (currentItem !== previousItem) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Watched query comparator that always reports changed result sets.
|
|
54
|
+
*/
|
|
55
|
+
export const FalsyComparator: WatchedQueryComparator<unknown> = {
|
|
56
|
+
checkEquality: () => false // Default comparator that always returns false
|
|
57
|
+
};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Set of generic interfaces to allow PowerSync compatibility with
|
|
3
|
+
* different SQLite DB implementations.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { BaseListener, BaseObserverInterface } from '../utils/BaseObserver.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* TODO most of these types could be exported to a common `types` package
|
|
10
|
+
* which is used by the DB adapter libraries as well.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Object returned by SQL Query executions.
|
|
15
|
+
*/
|
|
16
|
+
export type QueryResult = {
|
|
17
|
+
/** Represents the auto-generated row id if applicable. */
|
|
18
|
+
insertId?: number;
|
|
19
|
+
/** Number of affected rows if result of a update query. */
|
|
20
|
+
rowsAffected: number;
|
|
21
|
+
/** if status is undefined or 0 this object will contain the query results */
|
|
22
|
+
rows?: {
|
|
23
|
+
/** Raw array with all dataset */
|
|
24
|
+
_array: any[];
|
|
25
|
+
/** The length of the dataset */
|
|
26
|
+
length: number;
|
|
27
|
+
/** A convenience function to acess the index based the row object
|
|
28
|
+
* @param idx the row index
|
|
29
|
+
* @returns the row structure identified by column names
|
|
30
|
+
*/
|
|
31
|
+
item: (idx: number) => any;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export interface DBGetUtils {
|
|
36
|
+
/** Execute a read-only query and return results. */
|
|
37
|
+
getAll<T>(sql: string, parameters?: any[]): Promise<T[]>;
|
|
38
|
+
/** Execute a read-only query and return the first result, or null if the ResultSet is empty. */
|
|
39
|
+
getOptional<T>(sql: string, parameters?: any[]): Promise<T | null>;
|
|
40
|
+
/** Execute a read-only query and return the first result, error if the ResultSet is empty. */
|
|
41
|
+
get<T>(sql: string, parameters?: any[]): Promise<T>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface LockContext extends DBGetUtils {
|
|
45
|
+
/** Execute a single write statement. */
|
|
46
|
+
execute: (query: string, params?: any[] | undefined) => Promise<QueryResult>;
|
|
47
|
+
/**
|
|
48
|
+
* Execute a single write statement and return raw results.
|
|
49
|
+
* Unlike `execute`, which returns an object with structured key-value pairs,
|
|
50
|
+
* `executeRaw` returns a nested array of raw values, where each row is
|
|
51
|
+
* represented as an array of column values without field names.
|
|
52
|
+
*
|
|
53
|
+
* Example result:
|
|
54
|
+
*
|
|
55
|
+
* ```[ [ '1', 'list 1', '33', 'Post content', '1' ] ]```
|
|
56
|
+
*
|
|
57
|
+
* Where as `execute`'s `rows._array` would have been:
|
|
58
|
+
*
|
|
59
|
+
* ```[ { id: '33', name: 'list 1', content: 'Post content', list_id: '1' } ]```
|
|
60
|
+
*/
|
|
61
|
+
executeRaw: (query: string, params?: any[] | undefined) => Promise<any[][]>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface Transaction extends LockContext {
|
|
65
|
+
/** Commit multiple changes to the local DB using the Transaction context. */
|
|
66
|
+
commit: () => Promise<QueryResult>;
|
|
67
|
+
/** Roll back multiple attempted changes using the Transaction context. */
|
|
68
|
+
rollback: () => Promise<QueryResult>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Update table operation numbers from SQLite
|
|
73
|
+
*/
|
|
74
|
+
export enum RowUpdateType {
|
|
75
|
+
SQLITE_INSERT = 18,
|
|
76
|
+
SQLITE_DELETE = 9,
|
|
77
|
+
SQLITE_UPDATE = 23
|
|
78
|
+
}
|
|
79
|
+
export interface TableUpdateOperation {
|
|
80
|
+
opType: RowUpdateType;
|
|
81
|
+
rowId: number;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Notification of an update to one or more tables, for the purpose of realtime change notifications.
|
|
85
|
+
*/
|
|
86
|
+
export interface UpdateNotification extends TableUpdateOperation {
|
|
87
|
+
table: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface BatchedUpdateNotification {
|
|
91
|
+
rawUpdates: UpdateNotification[];
|
|
92
|
+
tables: string[];
|
|
93
|
+
groupedUpdates: Record<string, TableUpdateOperation[]>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface DBAdapterListener extends BaseListener {
|
|
97
|
+
/**
|
|
98
|
+
* Listener for table updates.
|
|
99
|
+
* Allows for single table updates in order to maintain API compatibility
|
|
100
|
+
* without the need for a major version bump
|
|
101
|
+
* The DB adapter can also batch update notifications if supported.
|
|
102
|
+
*/
|
|
103
|
+
tablesUpdated: (updateNotification: BatchedUpdateNotification | UpdateNotification) => void;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface DBLockOptions {
|
|
107
|
+
timeoutMs?: number;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface DBAdapter extends BaseObserverInterface<DBAdapterListener>, DBGetUtils {
|
|
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>;
|
|
115
|
+
name: string;
|
|
116
|
+
readLock: <T>(fn: (tx: LockContext) => Promise<T>, options?: DBLockOptions) => Promise<T>;
|
|
117
|
+
readTransaction: <T>(fn: (tx: Transaction) => Promise<T>, options?: DBLockOptions) => Promise<T>;
|
|
118
|
+
writeLock: <T>(fn: (tx: LockContext) => Promise<T>, options?: DBLockOptions) => Promise<T>;
|
|
119
|
+
writeTransaction: <T>(fn: (tx: Transaction) => Promise<T>, options?: DBLockOptions) => Promise<T>;
|
|
120
|
+
/**
|
|
121
|
+
* This method refreshes the schema information across all connections. This is for advanced use cases, and should generally not be needed.
|
|
122
|
+
*/
|
|
123
|
+
refreshSchema: () => Promise<void>;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function isBatchedUpdateNotification(
|
|
127
|
+
update: BatchedUpdateNotification | UpdateNotification
|
|
128
|
+
): update is BatchedUpdateNotification {
|
|
129
|
+
return 'tables' in update;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function extractTableUpdates(update: BatchedUpdateNotification | UpdateNotification) {
|
|
133
|
+
return isBatchedUpdateNotification(update) ? update.tables : [update.table];
|
|
134
|
+
}
|