@powersync/common 0.0.0-dev-20250714144421 → 0.0.0-dev-20250715080712
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 +5 -5
- package/dist/bundle.mjs +3 -3
- package/lib/client/AbstractPowerSyncDatabase.d.ts +59 -7
- package/lib/client/AbstractPowerSyncDatabase.js +105 -35
- package/lib/client/ConnectionManager.d.ts +4 -4
- package/lib/client/CustomQuery.d.ts +25 -0
- package/lib/client/CustomQuery.js +41 -0
- package/lib/client/Query.d.ts +79 -0
- package/lib/client/Query.js +1 -0
- package/lib/client/sync/bucket/BucketStorageAdapter.d.ts +2 -2
- package/lib/client/sync/bucket/SqliteBucketStorage.js +14 -14
- package/lib/client/sync/stream/AbstractStreamingSyncImplementation.d.ts +10 -4
- package/lib/client/sync/stream/AbstractStreamingSyncImplementation.js +26 -18
- package/lib/client/watched/GetAllQuery.d.ts +32 -0
- package/lib/client/watched/GetAllQuery.js +24 -0
- package/lib/client/watched/WatchedQuery.d.ts +93 -0
- package/lib/client/watched/WatchedQuery.js +12 -0
- package/lib/client/watched/processors/AbstractQueryProcessor.d.ts +67 -0
- package/lib/client/watched/processors/AbstractQueryProcessor.js +136 -0
- package/lib/client/watched/processors/DifferentialQueryProcessor.d.ts +129 -0
- package/lib/client/watched/processors/DifferentialQueryProcessor.js +175 -0
- package/lib/client/watched/processors/OnChangeQueryProcessor.d.ts +27 -0
- package/lib/client/watched/processors/OnChangeQueryProcessor.js +74 -0
- package/lib/client/watched/processors/comparators.d.ts +24 -0
- package/lib/client/watched/processors/comparators.js +33 -0
- package/lib/db/schema/RawTable.d.ts +57 -0
- package/lib/db/schema/RawTable.js +28 -0
- package/lib/db/schema/Schema.d.ts +14 -0
- package/lib/db/schema/Schema.js +20 -1
- package/lib/index.d.ts +7 -0
- package/lib/index.js +7 -0
- package/lib/utils/BaseObserver.d.ts +3 -4
- package/lib/utils/BaseObserver.js +3 -0
- package/lib/utils/MetaBaseObserver.d.ts +29 -0
- package/lib/utils/MetaBaseObserver.js +50 -0
- package/package.json +1 -1
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { WatchCompatibleQuery, WatchedQuery, WatchedQueryListener, WatchedQueryOptions, WatchedQueryState } from '../WatchedQuery.js';
|
|
2
|
+
import { AbstractQueryProcessor, AbstractQueryProcessorOptions, LinkQueryOptions } from './AbstractQueryProcessor.js';
|
|
3
|
+
/**
|
|
4
|
+
* Represents an updated row in a differential watched query.
|
|
5
|
+
* It contains both the current and previous state of the row.
|
|
6
|
+
*/
|
|
7
|
+
export interface WatchedQueryRowDifferential<RowType> {
|
|
8
|
+
readonly current: RowType;
|
|
9
|
+
readonly previous: RowType;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Represents the result of a watched query that has been diffed.
|
|
13
|
+
* {@link DifferentialWatchedQueryState#diff} is of the {@link WatchedQueryDifferential} form.
|
|
14
|
+
*/
|
|
15
|
+
export interface WatchedQueryDifferential<RowType> {
|
|
16
|
+
readonly added: ReadonlyArray<Readonly<RowType>>;
|
|
17
|
+
/**
|
|
18
|
+
* The entire current result set.
|
|
19
|
+
* Array item object references are preserved between updates if the item is unchanged.
|
|
20
|
+
*
|
|
21
|
+
* e.g. In the query
|
|
22
|
+
* ```sql
|
|
23
|
+
* SELECT name, make FROM assets ORDER BY make ASC;
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* If a previous result set contains an item (A) `{name: 'pc', make: 'Cool PC'}` and
|
|
27
|
+
* an update has been made which adds another item (B) to the result set (the item A is unchanged) - then
|
|
28
|
+
* the updated result set will be contain the same object reference, to item A, as the previous result set.
|
|
29
|
+
* This is regardless of the item A's position in the updated result set.
|
|
30
|
+
*/
|
|
31
|
+
readonly all: ReadonlyArray<Readonly<RowType>>;
|
|
32
|
+
readonly removed: ReadonlyArray<Readonly<RowType>>;
|
|
33
|
+
readonly updated: ReadonlyArray<WatchedQueryRowDifferential<Readonly<RowType>>>;
|
|
34
|
+
readonly unchanged: ReadonlyArray<Readonly<RowType>>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Differentiator for incremental watched queries which allows to identify and compare items in the result set.
|
|
38
|
+
*/
|
|
39
|
+
export interface WatchedQueryDifferentiator<RowType> {
|
|
40
|
+
/**
|
|
41
|
+
* Unique identifier for the item.
|
|
42
|
+
*/
|
|
43
|
+
identify: (item: RowType) => string;
|
|
44
|
+
/**
|
|
45
|
+
* Generates a key for comparing items with matching identifiers.
|
|
46
|
+
*/
|
|
47
|
+
compareBy: (item: RowType) => string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Options for building a differential watched query with the {@link Query} builder.
|
|
51
|
+
*/
|
|
52
|
+
export interface DifferentialWatchedQueryOptions<RowType> extends WatchedQueryOptions {
|
|
53
|
+
/**
|
|
54
|
+
* Initial result data which is presented while the initial loading is executing.
|
|
55
|
+
*/
|
|
56
|
+
placeholderData?: RowType[];
|
|
57
|
+
/**
|
|
58
|
+
* Differentiator used to identify and compare items in the result set.
|
|
59
|
+
* If not provided, the default differentiator will be used which identifies items by their `id` property if available,
|
|
60
|
+
* otherwise it uses JSON stringification of the entire item for identification and comparison.
|
|
61
|
+
* @defaultValue {@link DEFAULT_WATCHED_QUERY_DIFFERENTIATOR}
|
|
62
|
+
*/
|
|
63
|
+
differentiator?: WatchedQueryDifferentiator<RowType>;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Settings for differential incremental watched queries using.
|
|
67
|
+
*/
|
|
68
|
+
export interface DifferentialWatchedQuerySettings<RowType> extends DifferentialWatchedQueryOptions<RowType> {
|
|
69
|
+
/**
|
|
70
|
+
* The query here must return an array of items that can be differentiated.
|
|
71
|
+
*/
|
|
72
|
+
query: WatchCompatibleQuery<RowType[]>;
|
|
73
|
+
}
|
|
74
|
+
export interface DifferentialWatchedQueryState<RowType> extends WatchedQueryState<ReadonlyArray<Readonly<RowType>>> {
|
|
75
|
+
/**
|
|
76
|
+
* The difference between the current and previous result set.
|
|
77
|
+
*/
|
|
78
|
+
readonly diff: WatchedQueryDifferential<RowType>;
|
|
79
|
+
}
|
|
80
|
+
export interface DifferentialWatchedQueryListener<RowType> extends WatchedQueryListener<ReadonlyArray<Readonly<RowType>>> {
|
|
81
|
+
onDiff?: (diff: WatchedQueryDifferential<RowType>) => void | Promise<void>;
|
|
82
|
+
}
|
|
83
|
+
export type DifferentialWatchedQuery<RowType> = WatchedQuery<ReadonlyArray<Readonly<RowType>>, DifferentialWatchedQuerySettings<RowType>, DifferentialWatchedQueryListener<RowType>>;
|
|
84
|
+
/**
|
|
85
|
+
* @internal
|
|
86
|
+
*/
|
|
87
|
+
export interface DifferentialQueryProcessorOptions<RowType> extends AbstractQueryProcessorOptions<RowType[], DifferentialWatchedQuerySettings<RowType>> {
|
|
88
|
+
differentiator?: WatchedQueryDifferentiator<RowType>;
|
|
89
|
+
}
|
|
90
|
+
type DataHashMap<RowType> = Map<string, {
|
|
91
|
+
hash: string;
|
|
92
|
+
item: RowType;
|
|
93
|
+
}>;
|
|
94
|
+
/**
|
|
95
|
+
* An empty differential result set.
|
|
96
|
+
* This is used as the initial state for differential incrementally watched queries.
|
|
97
|
+
*/
|
|
98
|
+
export declare const EMPTY_DIFFERENTIAL: {
|
|
99
|
+
added: never[];
|
|
100
|
+
all: never[];
|
|
101
|
+
removed: never[];
|
|
102
|
+
updated: never[];
|
|
103
|
+
unchanged: never[];
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* Default implementation of the {@link Differentiator} for watched queries.
|
|
107
|
+
* It identifies items by their `id` property if available, otherwise it uses JSON stringification
|
|
108
|
+
* of the entire item for identification and comparison.
|
|
109
|
+
*/
|
|
110
|
+
export declare const DEFAULT_WATCHED_QUERY_DIFFERENTIATOR: WatchedQueryDifferentiator<any>;
|
|
111
|
+
/**
|
|
112
|
+
* Uses the PowerSync onChange event to trigger watched queries.
|
|
113
|
+
* Results are emitted on every change of the relevant tables.
|
|
114
|
+
* @internal
|
|
115
|
+
*/
|
|
116
|
+
export declare class DifferentialQueryProcessor<RowType> extends AbstractQueryProcessor<ReadonlyArray<Readonly<RowType>>, DifferentialWatchedQuerySettings<RowType>> implements DifferentialWatchedQuery<RowType> {
|
|
117
|
+
protected options: DifferentialQueryProcessorOptions<RowType>;
|
|
118
|
+
readonly state: DifferentialWatchedQueryState<RowType>;
|
|
119
|
+
protected differentiator: WatchedQueryDifferentiator<RowType>;
|
|
120
|
+
constructor(options: DifferentialQueryProcessorOptions<RowType>);
|
|
121
|
+
protected constructInitialState(): DifferentialWatchedQueryState<RowType>;
|
|
122
|
+
protected differentiate(current: RowType[], previousMap: DataHashMap<RowType>): {
|
|
123
|
+
diff: WatchedQueryDifferential<RowType>;
|
|
124
|
+
map: DataHashMap<RowType>;
|
|
125
|
+
hasChanged: boolean;
|
|
126
|
+
};
|
|
127
|
+
protected linkQuery(options: LinkQueryOptions<WatchedQueryDifferential<RowType>>): Promise<void>;
|
|
128
|
+
}
|
|
129
|
+
export {};
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { AbstractQueryProcessor } from './AbstractQueryProcessor.js';
|
|
2
|
+
/**
|
|
3
|
+
* An empty differential result set.
|
|
4
|
+
* This is used as the initial state for differential incrementally watched queries.
|
|
5
|
+
*/
|
|
6
|
+
export const EMPTY_DIFFERENTIAL = {
|
|
7
|
+
added: [],
|
|
8
|
+
all: [],
|
|
9
|
+
removed: [],
|
|
10
|
+
updated: [],
|
|
11
|
+
unchanged: []
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Default implementation of the {@link Differentiator} for watched queries.
|
|
15
|
+
* It identifies items by their `id` property if available, otherwise it uses JSON stringification
|
|
16
|
+
* of the entire item for identification and comparison.
|
|
17
|
+
*/
|
|
18
|
+
export const DEFAULT_WATCHED_QUERY_DIFFERENTIATOR = {
|
|
19
|
+
identify: (item) => {
|
|
20
|
+
if (item && typeof item == 'object' && typeof item['id'] == 'string') {
|
|
21
|
+
return item['id'];
|
|
22
|
+
}
|
|
23
|
+
return JSON.stringify(item);
|
|
24
|
+
},
|
|
25
|
+
compareBy: (item) => JSON.stringify(item)
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Uses the PowerSync onChange event to trigger watched queries.
|
|
29
|
+
* Results are emitted on every change of the relevant tables.
|
|
30
|
+
* @internal
|
|
31
|
+
*/
|
|
32
|
+
export class DifferentialQueryProcessor extends AbstractQueryProcessor {
|
|
33
|
+
options;
|
|
34
|
+
state;
|
|
35
|
+
differentiator;
|
|
36
|
+
constructor(options) {
|
|
37
|
+
super(options);
|
|
38
|
+
this.options = options;
|
|
39
|
+
this.state = this.constructInitialState();
|
|
40
|
+
this.differentiator = options.differentiator ?? DEFAULT_WATCHED_QUERY_DIFFERENTIATOR;
|
|
41
|
+
}
|
|
42
|
+
constructInitialState() {
|
|
43
|
+
return {
|
|
44
|
+
...super.constructInitialState(),
|
|
45
|
+
diff: {
|
|
46
|
+
...EMPTY_DIFFERENTIAL,
|
|
47
|
+
all: this.options.placeholderData
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/*
|
|
52
|
+
* @returns If the sets are equal
|
|
53
|
+
*/
|
|
54
|
+
differentiate(current, previousMap) {
|
|
55
|
+
const { identify, compareBy } = this.differentiator;
|
|
56
|
+
let hasChanged = false;
|
|
57
|
+
const currentMap = new Map();
|
|
58
|
+
const removedTracker = new Set(previousMap.keys());
|
|
59
|
+
// Allow mutating to populate the data temporarily.
|
|
60
|
+
const diff = {
|
|
61
|
+
all: [],
|
|
62
|
+
added: [],
|
|
63
|
+
removed: [],
|
|
64
|
+
updated: [],
|
|
65
|
+
unchanged: []
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Looping over the current result set array is important to preserve
|
|
69
|
+
* the ordering of the result set.
|
|
70
|
+
* We can replace items in the current array with previous object references if they are equal.
|
|
71
|
+
*/
|
|
72
|
+
for (const item of current) {
|
|
73
|
+
const key = identify(item);
|
|
74
|
+
const hash = compareBy(item);
|
|
75
|
+
currentMap.set(key, { hash, item });
|
|
76
|
+
const previousItem = previousMap.get(key);
|
|
77
|
+
if (!previousItem) {
|
|
78
|
+
// New item
|
|
79
|
+
hasChanged = true;
|
|
80
|
+
diff.added.push(item);
|
|
81
|
+
diff.all.push(item);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
// Existing item
|
|
85
|
+
if (hash == previousItem.hash) {
|
|
86
|
+
diff.unchanged.push(item);
|
|
87
|
+
// Use the previous object reference
|
|
88
|
+
diff.all.push(previousItem.item);
|
|
89
|
+
// update the map to preserve the reference
|
|
90
|
+
currentMap.set(key, previousItem);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
hasChanged = true;
|
|
94
|
+
diff.updated.push({ current: item, previous: previousItem.item });
|
|
95
|
+
// Use the new reference
|
|
96
|
+
diff.all.push(item);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// The item is present, we don't consider it removed
|
|
100
|
+
removedTracker.delete(key);
|
|
101
|
+
}
|
|
102
|
+
diff.removed = Array.from(removedTracker).map((key) => previousMap.get(key).item);
|
|
103
|
+
hasChanged = hasChanged || diff.removed.length > 0;
|
|
104
|
+
return {
|
|
105
|
+
diff,
|
|
106
|
+
hasChanged,
|
|
107
|
+
map: currentMap
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
async linkQuery(options) {
|
|
111
|
+
const { db, watchOptions } = this.options;
|
|
112
|
+
const { abortSignal } = options;
|
|
113
|
+
const compiledQuery = watchOptions.query.compile();
|
|
114
|
+
const tables = await db.resolveTables(compiledQuery.sql, compiledQuery.parameters);
|
|
115
|
+
let currentMap = new Map();
|
|
116
|
+
// populate the currentMap from the placeholder data
|
|
117
|
+
this.state.data.forEach((item) => {
|
|
118
|
+
currentMap.set(this.differentiator.identify(item), {
|
|
119
|
+
hash: this.differentiator.compareBy(item),
|
|
120
|
+
item
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
db.onChangeWithCallback({
|
|
124
|
+
onChange: async () => {
|
|
125
|
+
if (this.closed) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
// This fires for each change of the relevant tables
|
|
129
|
+
try {
|
|
130
|
+
if (this.reportFetching && !this.state.isFetching) {
|
|
131
|
+
await this.updateState({ isFetching: true });
|
|
132
|
+
}
|
|
133
|
+
const partialStateUpdate = {};
|
|
134
|
+
// Always run the query if an underlying table has changed
|
|
135
|
+
const result = await watchOptions.query.execute({
|
|
136
|
+
sql: compiledQuery.sql,
|
|
137
|
+
// Allows casting from ReadOnlyArray[unknown] to Array<unknown>
|
|
138
|
+
// This allows simpler compatibility with PowerSync queries
|
|
139
|
+
parameters: [...compiledQuery.parameters],
|
|
140
|
+
db: this.options.db
|
|
141
|
+
});
|
|
142
|
+
if (this.reportFetching) {
|
|
143
|
+
partialStateUpdate.isFetching = false;
|
|
144
|
+
}
|
|
145
|
+
if (this.state.isLoading) {
|
|
146
|
+
partialStateUpdate.isLoading = false;
|
|
147
|
+
}
|
|
148
|
+
const { diff, hasChanged, map } = this.differentiate(result, currentMap);
|
|
149
|
+
// Update for future comparisons
|
|
150
|
+
currentMap = map;
|
|
151
|
+
if (hasChanged) {
|
|
152
|
+
await this.iterateAsyncListenersWithError((l) => l.onDiff?.(diff));
|
|
153
|
+
Object.assign(partialStateUpdate, {
|
|
154
|
+
data: diff.all
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
if (Object.keys(partialStateUpdate).length > 0) {
|
|
158
|
+
await this.updateState(partialStateUpdate);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
await this.updateState({ error });
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
onError: async (error) => {
|
|
166
|
+
await this.updateState({ error });
|
|
167
|
+
}
|
|
168
|
+
}, {
|
|
169
|
+
signal: abortSignal,
|
|
170
|
+
tables,
|
|
171
|
+
throttleMs: watchOptions.throttleMs,
|
|
172
|
+
triggerImmediate: true // used to emit the initial state
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { WatchCompatibleQuery, WatchedQuery, WatchedQueryOptions } from '../WatchedQuery.js';
|
|
2
|
+
import { AbstractQueryProcessor, AbstractQueryProcessorOptions, LinkQueryOptions } from './AbstractQueryProcessor.js';
|
|
3
|
+
import { WatchedQueryComparator } from './comparators.js';
|
|
4
|
+
export interface ComparisonWatchedQuerySettings<DataType> extends WatchedQueryOptions {
|
|
5
|
+
query: WatchCompatibleQuery<DataType>;
|
|
6
|
+
}
|
|
7
|
+
export type ComparisonWatchedQuery<DataType> = WatchedQuery<DataType, ComparisonWatchedQuerySettings<DataType>>;
|
|
8
|
+
/**
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
export interface OnChangeQueryProcessorOptions<Data> extends AbstractQueryProcessorOptions<Data, ComparisonWatchedQuerySettings<Data>> {
|
|
12
|
+
comparator?: WatchedQueryComparator<Data>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Uses the PowerSync onChange event to trigger watched queries.
|
|
16
|
+
* Results are emitted on every change of the relevant tables.
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
19
|
+
export declare class OnChangeQueryProcessor<Data> extends AbstractQueryProcessor<Data, ComparisonWatchedQuerySettings<Data>> {
|
|
20
|
+
protected options: OnChangeQueryProcessorOptions<Data>;
|
|
21
|
+
constructor(options: OnChangeQueryProcessorOptions<Data>);
|
|
22
|
+
/**
|
|
23
|
+
* @returns If the sets are equal
|
|
24
|
+
*/
|
|
25
|
+
protected checkEquality(current: Data, previous: Data): boolean;
|
|
26
|
+
protected linkQuery(options: LinkQueryOptions<Data>): Promise<void>;
|
|
27
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { AbstractQueryProcessor } from './AbstractQueryProcessor.js';
|
|
2
|
+
/**
|
|
3
|
+
* Uses the PowerSync onChange event to trigger watched queries.
|
|
4
|
+
* Results are emitted on every change of the relevant tables.
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
export class OnChangeQueryProcessor extends AbstractQueryProcessor {
|
|
8
|
+
options;
|
|
9
|
+
constructor(options) {
|
|
10
|
+
super(options);
|
|
11
|
+
this.options = options;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* @returns If the sets are equal
|
|
15
|
+
*/
|
|
16
|
+
checkEquality(current, previous) {
|
|
17
|
+
// Use the provided comparator if available. Assume values are unique if not available.
|
|
18
|
+
return this.options.comparator?.checkEquality?.(current, previous) ?? false;
|
|
19
|
+
}
|
|
20
|
+
async linkQuery(options) {
|
|
21
|
+
const { db, watchOptions } = this.options;
|
|
22
|
+
const { abortSignal } = options;
|
|
23
|
+
const compiledQuery = watchOptions.query.compile();
|
|
24
|
+
const tables = await db.resolveTables(compiledQuery.sql, compiledQuery.parameters);
|
|
25
|
+
db.onChangeWithCallback({
|
|
26
|
+
onChange: async () => {
|
|
27
|
+
if (this.closed) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
// This fires for each change of the relevant tables
|
|
31
|
+
try {
|
|
32
|
+
if (this.reportFetching && !this.state.isFetching) {
|
|
33
|
+
await this.updateState({ isFetching: true });
|
|
34
|
+
}
|
|
35
|
+
const partialStateUpdate = {};
|
|
36
|
+
// Always run the query if an underlying table has changed
|
|
37
|
+
const result = await watchOptions.query.execute({
|
|
38
|
+
sql: compiledQuery.sql,
|
|
39
|
+
// Allows casting from ReadOnlyArray[unknown] to Array<unknown>
|
|
40
|
+
// This allows simpler compatibility with PowerSync queries
|
|
41
|
+
parameters: [...compiledQuery.parameters],
|
|
42
|
+
db: this.options.db
|
|
43
|
+
});
|
|
44
|
+
if (this.reportFetching) {
|
|
45
|
+
partialStateUpdate.isFetching = false;
|
|
46
|
+
}
|
|
47
|
+
if (this.state.isLoading) {
|
|
48
|
+
partialStateUpdate.isLoading = false;
|
|
49
|
+
}
|
|
50
|
+
// Check if the result has changed
|
|
51
|
+
if (!this.checkEquality(result, this.state.data)) {
|
|
52
|
+
Object.assign(partialStateUpdate, {
|
|
53
|
+
data: result
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
if (Object.keys(partialStateUpdate).length > 0) {
|
|
57
|
+
await this.updateState(partialStateUpdate);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
await this.updateState({ error });
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
onError: async (error) => {
|
|
65
|
+
await this.updateState({ error });
|
|
66
|
+
}
|
|
67
|
+
}, {
|
|
68
|
+
signal: abortSignal,
|
|
69
|
+
tables,
|
|
70
|
+
throttleMs: watchOptions.throttleMs,
|
|
71
|
+
triggerImmediate: true // used to emit the initial state
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface WatchedQueryComparator<Data> {
|
|
2
|
+
checkEquality: (current: Data, previous: Data) => boolean;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Options for {@link ArrayComparator}
|
|
6
|
+
*/
|
|
7
|
+
export type ArrayComparatorOptions<ItemType> = {
|
|
8
|
+
/**
|
|
9
|
+
* Returns a string to uniquely identify an item in the array.
|
|
10
|
+
*/
|
|
11
|
+
compareBy: (item: ItemType) => string;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Compares array results of watched queries for incrementally watched queries created in the standard mode.
|
|
15
|
+
*/
|
|
16
|
+
export declare class ArrayComparator<ItemType> implements WatchedQueryComparator<ItemType[]> {
|
|
17
|
+
protected options: ArrayComparatorOptions<ItemType>;
|
|
18
|
+
constructor(options: ArrayComparatorOptions<ItemType>);
|
|
19
|
+
checkEquality(current: ItemType[], previous: ItemType[]): boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Watched query comparator that always reports changed result sets.
|
|
23
|
+
*/
|
|
24
|
+
export declare const FalsyComparator: WatchedQueryComparator<unknown>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compares array results of watched queries for incrementally watched queries created in the standard mode.
|
|
3
|
+
*/
|
|
4
|
+
export class ArrayComparator {
|
|
5
|
+
options;
|
|
6
|
+
constructor(options) {
|
|
7
|
+
this.options = options;
|
|
8
|
+
}
|
|
9
|
+
checkEquality(current, previous) {
|
|
10
|
+
if (current.length === 0 && previous.length === 0) {
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
if (current.length !== previous.length) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
const { compareBy } = this.options;
|
|
17
|
+
// At this point the lengths are equal
|
|
18
|
+
for (let i = 0; i < current.length; i++) {
|
|
19
|
+
const currentItem = compareBy(current[i]);
|
|
20
|
+
const previousItem = compareBy(previous[i]);
|
|
21
|
+
if (currentItem !== previousItem) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Watched query comparator that always reports changed result sets.
|
|
30
|
+
*/
|
|
31
|
+
export const FalsyComparator = {
|
|
32
|
+
checkEquality: () => false // Default comparator that always returns false
|
|
33
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A pending variant of a {@link RawTable} that doesn't have a name (because it would be inferred when creating the
|
|
3
|
+
* schema).
|
|
4
|
+
*/
|
|
5
|
+
export type RawTableType = {
|
|
6
|
+
/**
|
|
7
|
+
* The statement to run when PowerSync detects that a row needs to be inserted or updated.
|
|
8
|
+
*/
|
|
9
|
+
put: PendingStatement;
|
|
10
|
+
/**
|
|
11
|
+
* The statement to run when PowerSync detects that a row needs to be deleted.
|
|
12
|
+
*/
|
|
13
|
+
delete: PendingStatement;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* A parameter to use as part of {@link PendingStatement}.
|
|
17
|
+
*
|
|
18
|
+
* For delete statements, only the `"Id"` value is supported - the sync client will replace it with the id of the row to
|
|
19
|
+
* be synced.
|
|
20
|
+
*
|
|
21
|
+
* For insert and replace operations, the values of columns in the table are available as parameters through
|
|
22
|
+
* `{Column: 'name'}`.
|
|
23
|
+
*/
|
|
24
|
+
export type PendingStatementParameter = 'Id' | {
|
|
25
|
+
Column: string;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* A statement that the PowerSync client should use to insert or delete data into a table managed by the user.
|
|
29
|
+
*/
|
|
30
|
+
export type PendingStatement = {
|
|
31
|
+
sql: string;
|
|
32
|
+
params: PendingStatementParameter[];
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Instructs PowerSync to sync data into a "raw" table.
|
|
36
|
+
*
|
|
37
|
+
* Since raw tables are not backed by JSON, running complex queries on them may be more efficient. Further, they allow
|
|
38
|
+
* using client-side table and column constraints.
|
|
39
|
+
*
|
|
40
|
+
* Note that raw tables are only supported when using the new `SyncClientImplementation.rust` sync client.
|
|
41
|
+
*
|
|
42
|
+
* @experimental Please note that this feature is experimental at the moment, and not covered by PowerSync semver or
|
|
43
|
+
* stability guarantees.
|
|
44
|
+
*/
|
|
45
|
+
export declare class RawTable implements RawTableType {
|
|
46
|
+
/**
|
|
47
|
+
* The name of the table.
|
|
48
|
+
*
|
|
49
|
+
* This does not have to match the actual table name in the schema - {@link put} and {@link delete} are free to use
|
|
50
|
+
* another table. Instead, this name is used by the sync client to recognize that operations on this table (as it
|
|
51
|
+
* appears in the source / backend database) are to be handled specially.
|
|
52
|
+
*/
|
|
53
|
+
name: string;
|
|
54
|
+
put: PendingStatement;
|
|
55
|
+
delete: PendingStatement;
|
|
56
|
+
constructor(name: string, type: RawTableType);
|
|
57
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Instructs PowerSync to sync data into a "raw" table.
|
|
3
|
+
*
|
|
4
|
+
* Since raw tables are not backed by JSON, running complex queries on them may be more efficient. Further, they allow
|
|
5
|
+
* using client-side table and column constraints.
|
|
6
|
+
*
|
|
7
|
+
* Note that raw tables are only supported when using the new `SyncClientImplementation.rust` sync client.
|
|
8
|
+
*
|
|
9
|
+
* @experimental Please note that this feature is experimental at the moment, and not covered by PowerSync semver or
|
|
10
|
+
* stability guarantees.
|
|
11
|
+
*/
|
|
12
|
+
export class RawTable {
|
|
13
|
+
/**
|
|
14
|
+
* The name of the table.
|
|
15
|
+
*
|
|
16
|
+
* This does not have to match the actual table name in the schema - {@link put} and {@link delete} are free to use
|
|
17
|
+
* another table. Instead, this name is used by the sync client to recognize that operations on this table (as it
|
|
18
|
+
* appears in the source / backend database) are to be handled specially.
|
|
19
|
+
*/
|
|
20
|
+
name;
|
|
21
|
+
put;
|
|
22
|
+
delete;
|
|
23
|
+
constructor(name, type) {
|
|
24
|
+
this.name = name;
|
|
25
|
+
this.put = type.put;
|
|
26
|
+
this.delete = type.delete;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { RawTable, RawTableType } from './RawTable.js';
|
|
1
2
|
import { RowType, Table } from './Table.js';
|
|
2
3
|
type SchemaType = Record<string, Table<any>>;
|
|
3
4
|
export type SchemaTableType<S extends SchemaType> = {
|
|
@@ -10,7 +11,19 @@ export declare class Schema<S extends SchemaType = SchemaType> {
|
|
|
10
11
|
readonly types: SchemaTableType<S>;
|
|
11
12
|
readonly props: S;
|
|
12
13
|
readonly tables: Table[];
|
|
14
|
+
readonly rawTables: RawTable[];
|
|
13
15
|
constructor(tables: Table[] | S);
|
|
16
|
+
/**
|
|
17
|
+
* Adds raw tables to this schema. Raw tables are identified by their name, but entirely managed by the application
|
|
18
|
+
* developer instead of automatically by PowerSync.
|
|
19
|
+
* Since raw tables are not backed by JSON, running complex queries on them may be more efficient. Further, they allow
|
|
20
|
+
* using client-side table and column constraints.
|
|
21
|
+
* Note that raw tables are only supported when using the new `SyncClientImplementation.rust` sync client.
|
|
22
|
+
*
|
|
23
|
+
* @param tables An object of (table name, raw table definition) entries.
|
|
24
|
+
* @experimental Note that the raw tables API is still experimental and may change in the future.
|
|
25
|
+
*/
|
|
26
|
+
withRawTables(tables: Record<string, RawTableType>): void;
|
|
14
27
|
validate(): void;
|
|
15
28
|
toJSON(): {
|
|
16
29
|
tables: {
|
|
@@ -35,6 +48,7 @@ export declare class Schema<S extends SchemaType = SchemaType> {
|
|
|
35
48
|
}[];
|
|
36
49
|
}[];
|
|
37
50
|
}[];
|
|
51
|
+
raw_tables: RawTable[];
|
|
38
52
|
};
|
|
39
53
|
private convertToClassicTables;
|
|
40
54
|
}
|
package/lib/db/schema/Schema.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { RawTable } from './RawTable.js';
|
|
1
2
|
/**
|
|
2
3
|
* A schema is a collection of tables. It is used to define the structure of a database.
|
|
3
4
|
*/
|
|
@@ -8,6 +9,7 @@ export class Schema {
|
|
|
8
9
|
types;
|
|
9
10
|
props;
|
|
10
11
|
tables;
|
|
12
|
+
rawTables;
|
|
11
13
|
constructor(tables) {
|
|
12
14
|
if (Array.isArray(tables)) {
|
|
13
15
|
/*
|
|
@@ -26,6 +28,22 @@ export class Schema {
|
|
|
26
28
|
this.props = tables;
|
|
27
29
|
this.tables = this.convertToClassicTables(this.props);
|
|
28
30
|
}
|
|
31
|
+
this.rawTables = [];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Adds raw tables to this schema. Raw tables are identified by their name, but entirely managed by the application
|
|
35
|
+
* developer instead of automatically by PowerSync.
|
|
36
|
+
* Since raw tables are not backed by JSON, running complex queries on them may be more efficient. Further, they allow
|
|
37
|
+
* using client-side table and column constraints.
|
|
38
|
+
* Note that raw tables are only supported when using the new `SyncClientImplementation.rust` sync client.
|
|
39
|
+
*
|
|
40
|
+
* @param tables An object of (table name, raw table definition) entries.
|
|
41
|
+
* @experimental Note that the raw tables API is still experimental and may change in the future.
|
|
42
|
+
*/
|
|
43
|
+
withRawTables(tables) {
|
|
44
|
+
for (const [name, rawTableDefinition] of Object.entries(tables)) {
|
|
45
|
+
this.rawTables.push(new RawTable(name, rawTableDefinition));
|
|
46
|
+
}
|
|
29
47
|
}
|
|
30
48
|
validate() {
|
|
31
49
|
for (const table of this.tables) {
|
|
@@ -35,7 +53,8 @@ export class Schema {
|
|
|
35
53
|
toJSON() {
|
|
36
54
|
return {
|
|
37
55
|
// This is required because "name" field is not present in TableV2
|
|
38
|
-
tables: this.tables.map((t) => t.toJSON())
|
|
56
|
+
tables: this.tables.map((t) => t.toJSON()),
|
|
57
|
+
raw_tables: this.rawTables
|
|
39
58
|
};
|
|
40
59
|
}
|
|
41
60
|
convertToClassicTables(props) {
|
package/lib/index.d.ts
CHANGED
|
@@ -29,6 +29,13 @@ export * from './db/schema/IndexedColumn.js';
|
|
|
29
29
|
export * from './db/schema/Schema.js';
|
|
30
30
|
export * from './db/schema/Table.js';
|
|
31
31
|
export * from './db/schema/TableV2.js';
|
|
32
|
+
export * from './client/Query.js';
|
|
33
|
+
export * from './client/watched/GetAllQuery.js';
|
|
34
|
+
export * from './client/watched/processors/AbstractQueryProcessor.js';
|
|
35
|
+
export * from './client/watched/processors/comparators.js';
|
|
36
|
+
export * from './client/watched/processors/DifferentialQueryProcessor.js';
|
|
37
|
+
export * from './client/watched/processors/OnChangeQueryProcessor.js';
|
|
38
|
+
export * from './client/watched/WatchedQuery.js';
|
|
32
39
|
export * from './utils/AbortOperation.js';
|
|
33
40
|
export * from './utils/BaseObserver.js';
|
|
34
41
|
export * from './utils/DataStream.js';
|
package/lib/index.js
CHANGED
|
@@ -29,6 +29,13 @@ export * from './db/schema/IndexedColumn.js';
|
|
|
29
29
|
export * from './db/schema/Schema.js';
|
|
30
30
|
export * from './db/schema/Table.js';
|
|
31
31
|
export * from './db/schema/TableV2.js';
|
|
32
|
+
export * from './client/Query.js';
|
|
33
|
+
export * from './client/watched/GetAllQuery.js';
|
|
34
|
+
export * from './client/watched/processors/AbstractQueryProcessor.js';
|
|
35
|
+
export * from './client/watched/processors/comparators.js';
|
|
36
|
+
export * from './client/watched/processors/DifferentialQueryProcessor.js';
|
|
37
|
+
export * from './client/watched/processors/OnChangeQueryProcessor.js';
|
|
38
|
+
export * from './client/watched/WatchedQuery.js';
|
|
32
39
|
export * from './utils/AbortOperation.js';
|
|
33
40
|
export * from './utils/BaseObserver.js';
|
|
34
41
|
export * from './utils/DataStream.js';
|