@powersync/common 1.34.0 → 1.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bundle.cjs +5 -5
- package/dist/bundle.mjs +3 -3
- package/dist/index.d.cts +2934 -0
- package/lib/client/AbstractPowerSyncDatabase.d.ts +61 -5
- package/lib/client/AbstractPowerSyncDatabase.js +103 -29
- package/lib/client/CustomQuery.d.ts +22 -0
- package/lib/client/CustomQuery.js +42 -0
- package/lib/client/Query.d.ts +97 -0
- package/lib/client/Query.js +1 -0
- package/lib/client/sync/bucket/BucketStorageAdapter.d.ts +2 -2
- package/lib/client/sync/stream/AbstractRemote.js +31 -19
- package/lib/client/sync/stream/AbstractStreamingSyncImplementation.d.ts +3 -2
- package/lib/client/sync/stream/AbstractStreamingSyncImplementation.js +19 -5
- package/lib/client/watched/GetAllQuery.d.ts +32 -0
- package/lib/client/watched/GetAllQuery.js +24 -0
- package/lib/client/watched/WatchedQuery.d.ts +98 -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 +135 -0
- package/lib/client/watched/processors/DifferentialQueryProcessor.d.ts +121 -0
- package/lib/client/watched/processors/DifferentialQueryProcessor.js +166 -0
- package/lib/client/watched/processors/OnChangeQueryProcessor.d.ts +33 -0
- package/lib/client/watched/processors/OnChangeQueryProcessor.js +76 -0
- package/lib/client/watched/processors/comparators.d.ts +30 -0
- package/lib/client/watched/processors/comparators.js +34 -0
- package/lib/index.d.ts +8 -0
- package/lib/index.js +8 -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 +12 -7
|
@@ -0,0 +1,166 @@
|
|
|
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 DifferentialWatchedQueryComparator} for watched queries.
|
|
15
|
+
* It keys items by their `id` property if available, alternatively it uses JSON stringification
|
|
16
|
+
* of the entire item for the key and comparison.
|
|
17
|
+
*/
|
|
18
|
+
export const DEFAULT_ROW_COMPARATOR = {
|
|
19
|
+
keyBy: (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
|
+
comparator;
|
|
35
|
+
constructor(options) {
|
|
36
|
+
super(options);
|
|
37
|
+
this.options = options;
|
|
38
|
+
this.comparator = options.rowComparator ?? DEFAULT_ROW_COMPARATOR;
|
|
39
|
+
}
|
|
40
|
+
/*
|
|
41
|
+
* @returns If the sets are equal
|
|
42
|
+
*/
|
|
43
|
+
differentiate(current, previousMap) {
|
|
44
|
+
const { keyBy, compareBy } = this.comparator;
|
|
45
|
+
let hasChanged = false;
|
|
46
|
+
const currentMap = new Map();
|
|
47
|
+
const removedTracker = new Set(previousMap.keys());
|
|
48
|
+
// Allow mutating to populate the data temporarily.
|
|
49
|
+
const diff = {
|
|
50
|
+
all: [],
|
|
51
|
+
added: [],
|
|
52
|
+
removed: [],
|
|
53
|
+
updated: [],
|
|
54
|
+
unchanged: []
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Looping over the current result set array is important to preserve
|
|
58
|
+
* the ordering of the result set.
|
|
59
|
+
* We can replace items in the current array with previous object references if they are equal.
|
|
60
|
+
*/
|
|
61
|
+
for (const item of current) {
|
|
62
|
+
const key = keyBy(item);
|
|
63
|
+
const hash = compareBy(item);
|
|
64
|
+
currentMap.set(key, { hash, item });
|
|
65
|
+
const previousItem = previousMap.get(key);
|
|
66
|
+
if (!previousItem) {
|
|
67
|
+
// New item
|
|
68
|
+
hasChanged = true;
|
|
69
|
+
diff.added.push(item);
|
|
70
|
+
diff.all.push(item);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
// Existing item
|
|
74
|
+
if (hash == previousItem.hash) {
|
|
75
|
+
diff.unchanged.push(previousItem.item);
|
|
76
|
+
// Use the previous object reference
|
|
77
|
+
diff.all.push(previousItem.item);
|
|
78
|
+
// update the map to preserve the reference
|
|
79
|
+
currentMap.set(key, previousItem);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
hasChanged = true;
|
|
83
|
+
diff.updated.push({ current: item, previous: previousItem.item });
|
|
84
|
+
// Use the new reference
|
|
85
|
+
diff.all.push(item);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// The item is present, we don't consider it removed
|
|
89
|
+
removedTracker.delete(key);
|
|
90
|
+
}
|
|
91
|
+
diff.removed = Array.from(removedTracker).map((key) => previousMap.get(key).item);
|
|
92
|
+
hasChanged = hasChanged || diff.removed.length > 0;
|
|
93
|
+
return {
|
|
94
|
+
diff,
|
|
95
|
+
hasChanged,
|
|
96
|
+
map: currentMap
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
async linkQuery(options) {
|
|
100
|
+
const { db, watchOptions } = this.options;
|
|
101
|
+
const { abortSignal } = options;
|
|
102
|
+
const compiledQuery = watchOptions.query.compile();
|
|
103
|
+
const tables = await db.resolveTables(compiledQuery.sql, compiledQuery.parameters, {
|
|
104
|
+
tables: options.settings.triggerOnTables
|
|
105
|
+
});
|
|
106
|
+
let currentMap = new Map();
|
|
107
|
+
// populate the currentMap from the placeholder data
|
|
108
|
+
this.state.data.forEach((item) => {
|
|
109
|
+
currentMap.set(this.comparator.keyBy(item), {
|
|
110
|
+
hash: this.comparator.compareBy(item),
|
|
111
|
+
item
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
db.onChangeWithCallback({
|
|
115
|
+
onChange: async () => {
|
|
116
|
+
if (this.closed) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
// This fires for each change of the relevant tables
|
|
120
|
+
try {
|
|
121
|
+
if (this.reportFetching && !this.state.isFetching) {
|
|
122
|
+
await this.updateState({ isFetching: true });
|
|
123
|
+
}
|
|
124
|
+
const partialStateUpdate = {};
|
|
125
|
+
// Always run the query if an underlying table has changed
|
|
126
|
+
const result = await watchOptions.query.execute({
|
|
127
|
+
sql: compiledQuery.sql,
|
|
128
|
+
// Allows casting from ReadOnlyArray[unknown] to Array<unknown>
|
|
129
|
+
// This allows simpler compatibility with PowerSync queries
|
|
130
|
+
parameters: [...compiledQuery.parameters],
|
|
131
|
+
db: this.options.db
|
|
132
|
+
});
|
|
133
|
+
if (this.reportFetching) {
|
|
134
|
+
partialStateUpdate.isFetching = false;
|
|
135
|
+
}
|
|
136
|
+
if (this.state.isLoading) {
|
|
137
|
+
partialStateUpdate.isLoading = false;
|
|
138
|
+
}
|
|
139
|
+
const { diff, hasChanged, map } = this.differentiate(result, currentMap);
|
|
140
|
+
// Update for future comparisons
|
|
141
|
+
currentMap = map;
|
|
142
|
+
if (hasChanged) {
|
|
143
|
+
await this.iterateAsyncListenersWithError((l) => l.onDiff?.(diff));
|
|
144
|
+
Object.assign(partialStateUpdate, {
|
|
145
|
+
data: diff.all
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
if (Object.keys(partialStateUpdate).length > 0) {
|
|
149
|
+
await this.updateState(partialStateUpdate);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
await this.updateState({ error });
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
onError: async (error) => {
|
|
157
|
+
await this.updateState({ error });
|
|
158
|
+
}
|
|
159
|
+
}, {
|
|
160
|
+
signal: abortSignal,
|
|
161
|
+
tables,
|
|
162
|
+
throttleMs: watchOptions.throttleMs,
|
|
163
|
+
triggerImmediate: true // used to emit the initial state
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { WatchCompatibleQuery, WatchedQuery, WatchedQueryOptions } from '../WatchedQuery.js';
|
|
2
|
+
import { AbstractQueryProcessor, AbstractQueryProcessorOptions, LinkQueryOptions } from './AbstractQueryProcessor.js';
|
|
3
|
+
import { WatchedQueryComparator } from './comparators.js';
|
|
4
|
+
/**
|
|
5
|
+
* Settings for {@link WatchedQuery} instances created via {@link Query#watch}.
|
|
6
|
+
*/
|
|
7
|
+
export interface WatchedQuerySettings<DataType> extends WatchedQueryOptions {
|
|
8
|
+
query: WatchCompatibleQuery<DataType>;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* {@link WatchedQuery} returned from {@link Query#watch}.
|
|
12
|
+
*/
|
|
13
|
+
export type StandardWatchedQuery<DataType> = WatchedQuery<DataType, WatchedQuerySettings<DataType>>;
|
|
14
|
+
/**
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
17
|
+
export interface OnChangeQueryProcessorOptions<Data> extends AbstractQueryProcessorOptions<Data, WatchedQuerySettings<Data>> {
|
|
18
|
+
comparator?: WatchedQueryComparator<Data>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Uses the PowerSync onChange event to trigger watched queries.
|
|
22
|
+
* Results are emitted on every change of the relevant tables.
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
25
|
+
export declare class OnChangeQueryProcessor<Data> extends AbstractQueryProcessor<Data, WatchedQuerySettings<Data>> {
|
|
26
|
+
protected options: OnChangeQueryProcessorOptions<Data>;
|
|
27
|
+
constructor(options: OnChangeQueryProcessorOptions<Data>);
|
|
28
|
+
/**
|
|
29
|
+
* @returns If the sets are equal
|
|
30
|
+
*/
|
|
31
|
+
protected checkEquality(current: Data, previous: Data): boolean;
|
|
32
|
+
protected linkQuery(options: LinkQueryOptions<Data>): Promise<void>;
|
|
33
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
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
|
+
tables: options.settings.triggerOnTables
|
|
26
|
+
});
|
|
27
|
+
db.onChangeWithCallback({
|
|
28
|
+
onChange: async () => {
|
|
29
|
+
if (this.closed) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
// This fires for each change of the relevant tables
|
|
33
|
+
try {
|
|
34
|
+
if (this.reportFetching && !this.state.isFetching) {
|
|
35
|
+
await this.updateState({ isFetching: true });
|
|
36
|
+
}
|
|
37
|
+
const partialStateUpdate = {};
|
|
38
|
+
// Always run the query if an underlying table has changed
|
|
39
|
+
const result = await watchOptions.query.execute({
|
|
40
|
+
sql: compiledQuery.sql,
|
|
41
|
+
// Allows casting from ReadOnlyArray[unknown] to Array<unknown>
|
|
42
|
+
// This allows simpler compatibility with PowerSync queries
|
|
43
|
+
parameters: [...compiledQuery.parameters],
|
|
44
|
+
db: this.options.db
|
|
45
|
+
});
|
|
46
|
+
if (this.reportFetching) {
|
|
47
|
+
partialStateUpdate.isFetching = false;
|
|
48
|
+
}
|
|
49
|
+
if (this.state.isLoading) {
|
|
50
|
+
partialStateUpdate.isLoading = false;
|
|
51
|
+
}
|
|
52
|
+
// Check if the result has changed
|
|
53
|
+
if (!this.checkEquality(result, this.state.data)) {
|
|
54
|
+
Object.assign(partialStateUpdate, {
|
|
55
|
+
data: result
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
if (Object.keys(partialStateUpdate).length > 0) {
|
|
59
|
+
await this.updateState(partialStateUpdate);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
await this.updateState({ error });
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
onError: async (error) => {
|
|
67
|
+
await this.updateState({ error });
|
|
68
|
+
}
|
|
69
|
+
}, {
|
|
70
|
+
signal: abortSignal,
|
|
71
|
+
tables,
|
|
72
|
+
throttleMs: watchOptions.throttleMs,
|
|
73
|
+
triggerImmediate: true // used to emit the initial state
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
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
|
+
* Options for {@link ArrayComparator}
|
|
11
|
+
*/
|
|
12
|
+
export type ArrayComparatorOptions<ItemType> = {
|
|
13
|
+
/**
|
|
14
|
+
* Returns a string to uniquely identify an item in the array.
|
|
15
|
+
*/
|
|
16
|
+
compareBy: (item: ItemType) => string;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* An efficient comparator for {@link WatchedQuery} created with {@link Query#watch}. This has the ability to determine if a query
|
|
20
|
+
* result has changes without necessarily processing all items in the result.
|
|
21
|
+
*/
|
|
22
|
+
export declare class ArrayComparator<ItemType> implements WatchedQueryComparator<ItemType[]> {
|
|
23
|
+
protected options: ArrayComparatorOptions<ItemType>;
|
|
24
|
+
constructor(options: ArrayComparatorOptions<ItemType>);
|
|
25
|
+
checkEquality(current: ItemType[], previous: ItemType[]): boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Watched query comparator that always reports changed result sets.
|
|
29
|
+
*/
|
|
30
|
+
export declare const FalsyComparator: WatchedQueryComparator<unknown>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An efficient comparator for {@link WatchedQuery} created with {@link Query#watch}. This has the ability to determine if a query
|
|
3
|
+
* result has changes without necessarily processing all items in the result.
|
|
4
|
+
*/
|
|
5
|
+
export class ArrayComparator {
|
|
6
|
+
options;
|
|
7
|
+
constructor(options) {
|
|
8
|
+
this.options = options;
|
|
9
|
+
}
|
|
10
|
+
checkEquality(current, previous) {
|
|
11
|
+
if (current.length === 0 && previous.length === 0) {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
if (current.length !== previous.length) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
const { compareBy } = this.options;
|
|
18
|
+
// At this point the lengths are equal
|
|
19
|
+
for (let i = 0; i < current.length; i++) {
|
|
20
|
+
const currentItem = compareBy(current[i]);
|
|
21
|
+
const previousItem = compareBy(previous[i]);
|
|
22
|
+
if (currentItem !== previousItem) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Watched query comparator that always reports changed result sets.
|
|
31
|
+
*/
|
|
32
|
+
export const FalsyComparator = {
|
|
33
|
+
checkEquality: () => false // Default comparator that always returns false
|
|
34
|
+
};
|
package/lib/index.d.ts
CHANGED
|
@@ -29,8 +29,16 @@ 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';
|
|
41
|
+
export * from './utils/ControlledExecutor.js';
|
|
34
42
|
export * from './utils/DataStream.js';
|
|
35
43
|
export * from './utils/Logger.js';
|
|
36
44
|
export * from './utils/parseQuery.js';
|
package/lib/index.js
CHANGED
|
@@ -29,8 +29,16 @@ 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';
|
|
41
|
+
export * from './utils/ControlledExecutor.js';
|
|
34
42
|
export * from './utils/DataStream.js';
|
|
35
43
|
export * from './utils/Logger.js';
|
|
36
44
|
export * from './utils/parseQuery.js';
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
export interface Disposable {
|
|
2
|
-
dispose: () => Promise<void
|
|
2
|
+
dispose: () => Promise<void> | void;
|
|
3
3
|
}
|
|
4
|
+
export type BaseListener = Record<string, ((...event: any) => any) | undefined>;
|
|
4
5
|
export interface BaseObserverInterface<T extends BaseListener> {
|
|
5
6
|
registerListener(listener: Partial<T>): () => void;
|
|
6
7
|
}
|
|
7
|
-
export type BaseListener = {
|
|
8
|
-
[key: string]: ((...event: any) => any) | undefined;
|
|
9
|
-
};
|
|
10
8
|
export declare class BaseObserver<T extends BaseListener = BaseListener> implements BaseObserverInterface<T> {
|
|
11
9
|
protected listeners: Set<Partial<T>>;
|
|
12
10
|
constructor();
|
|
11
|
+
dispose(): void;
|
|
13
12
|
/**
|
|
14
13
|
* Register a listener for updates to the PowerSync client.
|
|
15
14
|
*/
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { BaseListener, BaseObserver, BaseObserverInterface } from './BaseObserver.js';
|
|
2
|
+
/**
|
|
3
|
+
* Represents the counts of listeners for each event type in a BaseListener.
|
|
4
|
+
*/
|
|
5
|
+
export type ListenerCounts<Listener extends BaseListener> = Partial<Record<keyof Listener, number>> & {
|
|
6
|
+
total: number;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Meta listener which reports the counts of listeners for each event type.
|
|
10
|
+
*/
|
|
11
|
+
export interface MetaListener<ParentListener extends BaseListener> extends BaseListener {
|
|
12
|
+
listenersChanged?: (counts: ListenerCounts<ParentListener>) => void;
|
|
13
|
+
}
|
|
14
|
+
export interface ListenerMetaManager<Listener extends BaseListener> extends BaseObserverInterface<MetaListener<Listener>> {
|
|
15
|
+
counts: ListenerCounts<Listener>;
|
|
16
|
+
}
|
|
17
|
+
export interface MetaBaseObserverInterface<Listener extends BaseListener> extends BaseObserverInterface<Listener> {
|
|
18
|
+
listenerMeta: ListenerMetaManager<Listener>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A BaseObserver that tracks the counts of listeners for each event type.
|
|
22
|
+
*/
|
|
23
|
+
export declare class MetaBaseObserver<Listener extends BaseListener> extends BaseObserver<Listener> implements MetaBaseObserverInterface<Listener> {
|
|
24
|
+
protected get listenerCounts(): ListenerCounts<Listener>;
|
|
25
|
+
get listenerMeta(): ListenerMetaManager<Listener>;
|
|
26
|
+
protected metaListener: BaseObserver<MetaListener<Listener>>;
|
|
27
|
+
constructor();
|
|
28
|
+
registerListener(listener: Partial<Listener>): () => void;
|
|
29
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { BaseObserver } from './BaseObserver.js';
|
|
2
|
+
/**
|
|
3
|
+
* A BaseObserver that tracks the counts of listeners for each event type.
|
|
4
|
+
*/
|
|
5
|
+
export class MetaBaseObserver extends BaseObserver {
|
|
6
|
+
get listenerCounts() {
|
|
7
|
+
const counts = {};
|
|
8
|
+
let total = 0;
|
|
9
|
+
for (const listener of this.listeners) {
|
|
10
|
+
for (const key in listener) {
|
|
11
|
+
if (listener[key]) {
|
|
12
|
+
counts[key] = (counts[key] ?? 0) + 1;
|
|
13
|
+
total++;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
...counts,
|
|
19
|
+
total
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
get listenerMeta() {
|
|
23
|
+
return {
|
|
24
|
+
counts: this.listenerCounts,
|
|
25
|
+
// Allows registering a meta listener that will be notified of changes in listener counts
|
|
26
|
+
registerListener: (listener) => {
|
|
27
|
+
return this.metaListener.registerListener(listener);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
metaListener;
|
|
32
|
+
constructor() {
|
|
33
|
+
super();
|
|
34
|
+
this.metaListener = new BaseObserver();
|
|
35
|
+
}
|
|
36
|
+
registerListener(listener) {
|
|
37
|
+
const dispose = super.registerListener(listener);
|
|
38
|
+
const updatedCount = this.listenerCounts;
|
|
39
|
+
this.metaListener.iterateListeners((l) => {
|
|
40
|
+
l.listenersChanged?.(updatedCount);
|
|
41
|
+
});
|
|
42
|
+
return () => {
|
|
43
|
+
dispose();
|
|
44
|
+
const updatedCount = this.listenerCounts;
|
|
45
|
+
this.metaListener.iterateListeners((l) => {
|
|
46
|
+
l.listenersChanged?.(updatedCount);
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@powersync/common",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.36.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"registry": "https://registry.npmjs.org/",
|
|
6
6
|
"access": "public"
|
|
@@ -12,10 +12,14 @@
|
|
|
12
12
|
"types": "lib/index.d.ts",
|
|
13
13
|
"exports": {
|
|
14
14
|
".": {
|
|
15
|
-
"import":
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
"import": {
|
|
16
|
+
"types": "./lib/index.d.ts",
|
|
17
|
+
"default": "./dist/bundle.mjs"
|
|
18
|
+
},
|
|
19
|
+
"require": {
|
|
20
|
+
"types": "./dist/index.d.cts",
|
|
21
|
+
"require": "./dist/bundle.cjs"
|
|
22
|
+
}
|
|
19
23
|
}
|
|
20
24
|
},
|
|
21
25
|
"author": "JOURNEYAPPS",
|
|
@@ -57,7 +61,8 @@
|
|
|
57
61
|
"scripts": {
|
|
58
62
|
"build": "tsc -b && rollup -c rollup.config.mjs",
|
|
59
63
|
"build:prod": "tsc -b --sourceMap false && rollup -c rollup.config.mjs --sourceMap false",
|
|
60
|
-
"clean": "rm -rf lib dist tsconfig.tsbuildinfo
|
|
61
|
-
"test": "vitest"
|
|
64
|
+
"clean": "rm -rf lib dist tsconfig.tsbuildinfo",
|
|
65
|
+
"test": "vitest",
|
|
66
|
+
"test:exports": "attw --pack ."
|
|
62
67
|
}
|
|
63
68
|
}
|