@signaldb/svelte 2.0.0-beta.2 → 2.0.0-beta.21
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/base/core/src/AsyncDataAdapter.d.ts +112 -0
- package/dist/base/core/src/AsyncDataAdapter.js +549 -0
- package/dist/base/core/src/AutoFetchDataAdapter.d.ts +112 -0
- package/dist/base/core/src/AutoFetchDataAdapter.js +612 -0
- package/dist/base/core/src/Collection/Cursor.d.ts +165 -0
- package/dist/base/core/src/Collection/Cursor.js +352 -0
- package/dist/base/core/src/Collection/Observer.d.ts +95 -0
- package/dist/base/core/src/Collection/Observer.js +227 -0
- package/dist/base/core/src/Collection/index.d.ts +341 -0
- package/dist/base/core/src/Collection/index.js +761 -0
- package/dist/base/core/src/Collection/types.d.ts +28 -0
- package/dist/base/core/src/Collection/types.js +2 -0
- package/dist/base/core/src/DataAdapter.d.ts +54 -0
- package/dist/base/core/src/DataAdapter.js +2 -0
- package/dist/base/core/src/DefaultDataAdapter.d.ts +35 -0
- package/dist/base/core/src/DefaultDataAdapter.js +408 -0
- package/dist/base/core/src/WorkerDataAdapter.d.ts +79 -0
- package/dist/base/core/src/WorkerDataAdapter.js +749 -0
- package/dist/base/core/src/WorkerDataAdapterHost.d.ts +64 -0
- package/dist/base/core/src/WorkerDataAdapterHost.js +430 -0
- package/dist/base/core/src/createIndex.d.ts +7 -0
- package/dist/base/core/src/createIndex.js +120 -0
- package/dist/base/core/src/createIndexProvider.d.ts +8 -0
- package/dist/base/core/src/createIndexProvider.js +11 -0
- package/dist/base/core/src/createReactivityAdapter.d.ts +8 -0
- package/dist/base/core/src/createReactivityAdapter.js +11 -0
- package/dist/base/core/src/createStorageAdapter.d.ts +9 -0
- package/dist/base/core/src/createStorageAdapter.js +11 -0
- package/dist/base/core/src/getIndexInfo.d.ts +39 -0
- package/dist/base/core/src/getIndexInfo.js +188 -0
- package/dist/base/core/src/index.d.ts +26 -0
- package/dist/base/core/src/index.js +39 -0
- package/dist/base/core/src/types/Dependency.d.ts +4 -0
- package/dist/base/core/src/types/Dependency.js +2 -0
- package/dist/base/core/src/types/IndexProvider.d.ts +26 -0
- package/dist/base/core/src/types/IndexProvider.js +2 -0
- package/dist/base/core/src/types/Modifier.d.ts +46 -0
- package/dist/base/core/src/types/Modifier.js +3 -0
- package/dist/base/core/src/types/ReactivityAdapter.d.ts +6 -0
- package/dist/base/core/src/types/ReactivityAdapter.js +2 -0
- package/dist/base/core/src/types/Selector.d.ts +46 -0
- package/dist/base/core/src/types/Selector.js +3 -0
- package/dist/base/core/src/types/Signal.d.ts +4 -0
- package/dist/base/core/src/types/Signal.js +2 -0
- package/dist/base/core/src/types/StorageAdapter.d.ts +28 -0
- package/dist/base/core/src/types/StorageAdapter.js +2 -0
- package/dist/base/core/src/utils/EventEmitter.d.ts +71 -0
- package/dist/base/core/src/utils/EventEmitter.js +144 -0
- package/dist/base/core/src/utils/applyQueryOptions.d.ts +15 -0
- package/dist/base/core/src/utils/applyQueryOptions.js +36 -0
- package/dist/base/core/src/utils/batchOnNextTick.d.ts +16 -0
- package/dist/base/core/src/utils/batchOnNextTick.js +72 -0
- package/dist/base/core/src/utils/compact.d.ts +9 -0
- package/dist/base/core/src/utils/compact.js +21 -0
- package/dist/base/core/src/utils/createSignal.d.ts +14 -0
- package/dist/base/core/src/utils/createSignal.js +38 -0
- package/dist/base/core/src/utils/deepClone.d.ts +17 -0
- package/dist/base/core/src/utils/deepClone.js +68 -0
- package/dist/base/core/src/utils/get.d.ts +9 -0
- package/dist/base/core/src/utils/get.js +27 -0
- package/dist/base/core/src/utils/getMatchingKeys.d.ts +19 -0
- package/dist/base/core/src/utils/getMatchingKeys.js +49 -0
- package/dist/base/core/src/utils/idIndexQuery.d.ts +20 -0
- package/dist/base/core/src/utils/idIndexQuery.js +49 -0
- package/dist/base/core/src/utils/incrementalQueryUpdate.d.ts +62 -0
- package/dist/base/core/src/utils/incrementalQueryUpdate.js +197 -0
- package/dist/base/core/src/utils/intersection.d.ts +9 -0
- package/dist/base/core/src/utils/intersection.js +16 -0
- package/dist/base/core/src/utils/isEqual.d.ts +14 -0
- package/dist/base/core/src/utils/isEqual.js +43 -0
- package/dist/base/core/src/utils/isFieldExpression.d.ts +11 -0
- package/dist/base/core/src/utils/isFieldExpression.js +52 -0
- package/dist/base/core/src/utils/match.d.ts +12 -0
- package/dist/base/core/src/utils/match.js +16 -0
- package/dist/base/core/src/utils/modify.d.ts +14 -0
- package/dist/base/core/src/utils/modify.js +29 -0
- package/dist/base/core/src/utils/project.d.ts +15 -0
- package/dist/base/core/src/utils/project.js +43 -0
- package/dist/base/core/src/utils/projectItems.d.ts +12 -0
- package/dist/base/core/src/utils/projectItems.js +25 -0
- package/dist/base/core/src/utils/queryDelta.d.ts +83 -0
- package/dist/base/core/src/utils/queryDelta.js +231 -0
- package/dist/base/core/src/utils/queryId.d.ts +9 -0
- package/dist/base/core/src/utils/queryId.js +61 -0
- package/dist/base/core/src/utils/randomId.d.ts +7 -0
- package/dist/base/core/src/utils/randomId.js +12 -0
- package/dist/base/core/src/utils/reactiveOrAsync.d.ts +59 -0
- package/dist/base/core/src/utils/reactiveOrAsync.js +93 -0
- package/dist/base/core/src/utils/serializeValue.d.ts +12 -0
- package/dist/base/core/src/utils/serializeValue.js +27 -0
- package/dist/base/core/src/utils/set.d.ts +13 -0
- package/dist/base/core/src/utils/set.js +43 -0
- package/dist/base/core/src/utils/sortItems.d.ts +12 -0
- package/dist/base/core/src/utils/sortItems.js +23 -0
- package/dist/base/core/src/utils/storageIndexQuery.d.ts +20 -0
- package/dist/base/core/src/utils/storageIndexQuery.js +75 -0
- package/dist/base/core/src/utils/uniqueBy.d.ts +10 -0
- package/dist/base/core/src/utils/uniqueBy.js +19 -0
- package/dist/index.cjs +38 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.mjs +38 -0
- package/dist/index.mjs.map +1 -0
- package/dist/reactivity-adapters/svelte/index.svelte.d.ts +10 -0
- package/dist/reactivity-adapters/svelte/index.svelte.js +36 -0
- package/package.json +3 -3
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { BaseItem } from '../Collection/types';
|
|
2
|
+
/**
|
|
3
|
+
* The change between two consecutive results of the same query, expressed so it can be applied to
|
|
4
|
+
* the earlier result to obtain the later one.
|
|
5
|
+
*
|
|
6
|
+
* Indices in `added` and `moved` are positions in the *resulting* array and are always ascending,
|
|
7
|
+
* which is what makes applying them a matter of splicing in order. Removals and moves name items by
|
|
8
|
+
* id only: whoever applies the delta still holds the previous result and can look the item up
|
|
9
|
+
* there, so there is no reason to send it twice — the point of the whole exercise is that a change
|
|
10
|
+
* costs the size of the change, not the size of the result.
|
|
11
|
+
*/
|
|
12
|
+
export interface QueryDelta<T extends BaseItem = BaseItem> {
|
|
13
|
+
/** Items that were not in the previous result, at their position in the new one. */
|
|
14
|
+
added: {
|
|
15
|
+
index: number;
|
|
16
|
+
item: T;
|
|
17
|
+
}[];
|
|
18
|
+
/** Items that were in the previous result and whose contents changed. */
|
|
19
|
+
changed: T[];
|
|
20
|
+
/** Ids of items that are no longer in the result. */
|
|
21
|
+
removed: any[];
|
|
22
|
+
/** Items that stayed, at their new position, because the order around them changed. */
|
|
23
|
+
moved: {
|
|
24
|
+
index: number;
|
|
25
|
+
id: any;
|
|
26
|
+
}[];
|
|
27
|
+
/** Length of the resulting array — lets a recipient verify it applied the delta to the result it was computed against. */
|
|
28
|
+
resultCount: number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Checks whether a delta leaves the result it is applied to unchanged.
|
|
32
|
+
* @param delta - The delta to inspect.
|
|
33
|
+
* @returns `true` when applying the delta would be a no-op.
|
|
34
|
+
*/
|
|
35
|
+
export declare function isEmptyQueryDelta(delta: QueryDelta<any>): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Calls a state-change callback, passing the delta only when there is one.
|
|
38
|
+
*
|
|
39
|
+
* A callback invoked as `callback(state, undefined)` has been handed two arguments, which is a
|
|
40
|
+
* different thing from being handed one — visible to anything that inspects arity, and to any test
|
|
41
|
+
* that asserts on the call.
|
|
42
|
+
* @template T - The type of the items.
|
|
43
|
+
* @param callback - The callback to invoke.
|
|
44
|
+
* @param state - The state to report.
|
|
45
|
+
* @param delta - The delta to report, if there is one.
|
|
46
|
+
*/
|
|
47
|
+
export declare function callWithDelta<T extends BaseItem>(callback: (state: 'active' | 'complete' | 'error', delta?: QueryDelta<T>) => void, state: 'active' | 'complete' | 'error', delta?: QueryDelta<T>): void;
|
|
48
|
+
/**
|
|
49
|
+
* Checks whether a delta describes a change to the given result.
|
|
50
|
+
*
|
|
51
|
+
* A delta is only meaningful against the exact result it was computed from — it names positions in
|
|
52
|
+
* an array and items by id alone. Applying one to anything else produces a result that looks
|
|
53
|
+
* plausible and is wrong, and from then on every further delta compounds the error. This is the
|
|
54
|
+
* cheap structural check that catches that: every id the delta expects to find is there, every id
|
|
55
|
+
* it expects to be new is not, and the arithmetic on the length works out. It costs the size of the
|
|
56
|
+
* delta, not the size of the result.
|
|
57
|
+
* @template T - The type of the items.
|
|
58
|
+
* @param previous - The result the delta would be applied to.
|
|
59
|
+
* @param delta - The delta to check.
|
|
60
|
+
* @returns `true` when the delta can be applied.
|
|
61
|
+
*/
|
|
62
|
+
export declare function canApplyQueryDelta<T extends BaseItem>(previous: T[], delta: QueryDelta<T>): boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Computes the delta between two results of the same query.
|
|
65
|
+
*
|
|
66
|
+
* A fallback for the cases where the change that produced the new result is not available — a query
|
|
67
|
+
* that had to be re-executed in full, for instance. It costs a pass over both results, but it is
|
|
68
|
+
* paid once, on the side that has both of them, instead of shipping the entire new result to
|
|
69
|
+
* everyone who only needs to know what changed.
|
|
70
|
+
* @template T - The type of the items.
|
|
71
|
+
* @param previous - The result the delta should be relative to.
|
|
72
|
+
* @param next - The result the delta should produce.
|
|
73
|
+
* @returns The delta between the two results.
|
|
74
|
+
*/
|
|
75
|
+
export declare function diffQueryResults<T extends BaseItem>(previous: T[], next: T[]): QueryDelta<T>;
|
|
76
|
+
/**
|
|
77
|
+
* Applies a delta to the result it was computed against.
|
|
78
|
+
* @template T - The type of the items.
|
|
79
|
+
* @param previous - The result the delta is relative to. Not modified.
|
|
80
|
+
* @param delta - The delta to apply.
|
|
81
|
+
* @returns The resulting items.
|
|
82
|
+
*/
|
|
83
|
+
export declare function applyQueryDelta<T extends BaseItem>(previous: T[], delta: QueryDelta<T>): T[];
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.isEmptyQueryDelta = isEmptyQueryDelta;
|
|
7
|
+
exports.callWithDelta = callWithDelta;
|
|
8
|
+
exports.canApplyQueryDelta = canApplyQueryDelta;
|
|
9
|
+
exports.diffQueryResults = diffQueryResults;
|
|
10
|
+
exports.applyQueryDelta = applyQueryDelta;
|
|
11
|
+
const isEqual_1 = __importDefault(require("./isEqual"));
|
|
12
|
+
/**
|
|
13
|
+
* Checks whether a delta leaves the result it is applied to unchanged.
|
|
14
|
+
* @param delta - The delta to inspect.
|
|
15
|
+
* @returns `true` when applying the delta would be a no-op.
|
|
16
|
+
*/
|
|
17
|
+
function isEmptyQueryDelta(delta) {
|
|
18
|
+
return delta.added.length === 0
|
|
19
|
+
&& delta.changed.length === 0
|
|
20
|
+
&& delta.removed.length === 0
|
|
21
|
+
&& delta.moved.length === 0;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Calls a state-change callback, passing the delta only when there is one.
|
|
25
|
+
*
|
|
26
|
+
* A callback invoked as `callback(state, undefined)` has been handed two arguments, which is a
|
|
27
|
+
* different thing from being handed one — visible to anything that inspects arity, and to any test
|
|
28
|
+
* that asserts on the call.
|
|
29
|
+
* @template T - The type of the items.
|
|
30
|
+
* @param callback - The callback to invoke.
|
|
31
|
+
* @param state - The state to report.
|
|
32
|
+
* @param delta - The delta to report, if there is one.
|
|
33
|
+
*/
|
|
34
|
+
function callWithDelta(callback, state, delta) {
|
|
35
|
+
if (delta == null) {
|
|
36
|
+
callback(state);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
callback(state, delta);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Checks whether a delta describes a change to the given result.
|
|
43
|
+
*
|
|
44
|
+
* A delta is only meaningful against the exact result it was computed from — it names positions in
|
|
45
|
+
* an array and items by id alone. Applying one to anything else produces a result that looks
|
|
46
|
+
* plausible and is wrong, and from then on every further delta compounds the error. This is the
|
|
47
|
+
* cheap structural check that catches that: every id the delta expects to find is there, every id
|
|
48
|
+
* it expects to be new is not, and the arithmetic on the length works out. It costs the size of the
|
|
49
|
+
* delta, not the size of the result.
|
|
50
|
+
* @template T - The type of the items.
|
|
51
|
+
* @param previous - The result the delta would be applied to.
|
|
52
|
+
* @param delta - The delta to check.
|
|
53
|
+
* @returns `true` when the delta can be applied.
|
|
54
|
+
*/
|
|
55
|
+
function canApplyQueryDelta(previous, delta) {
|
|
56
|
+
const present = new Set(previous.map(item => item.id));
|
|
57
|
+
const seen = new Set();
|
|
58
|
+
const claim = (id, shouldExist) => {
|
|
59
|
+
if (seen.has(id))
|
|
60
|
+
return false;
|
|
61
|
+
seen.add(id);
|
|
62
|
+
return present.has(id) === shouldExist;
|
|
63
|
+
};
|
|
64
|
+
const expectedCount = previous.length - delta.removed.length + delta.added.length;
|
|
65
|
+
if (expectedCount !== delta.resultCount)
|
|
66
|
+
return false;
|
|
67
|
+
if (!delta.removed.every(id => claim(id, true)))
|
|
68
|
+
return false;
|
|
69
|
+
if (!delta.added.every(({ index, item }) => claim(item.id, false)
|
|
70
|
+
&& index >= 0 && index < delta.resultCount))
|
|
71
|
+
return false;
|
|
72
|
+
if (!delta.changed.every(item => present.has(item.id)))
|
|
73
|
+
return false;
|
|
74
|
+
return delta.moved.every(({ index, id }) => present.has(id)
|
|
75
|
+
&& !delta.removed.includes(id)
|
|
76
|
+
&& index >= 0 && index < delta.resultCount);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Indices of the longest strictly increasing subsequence of the given numbers.
|
|
80
|
+
* Used to decide which items keep their place when a result is reordered: everything outside the
|
|
81
|
+
* subsequence has to move, everything inside it is already in the right relative order.
|
|
82
|
+
* @param sequence - The numbers to inspect.
|
|
83
|
+
* @returns The indices into `sequence` that form the longest increasing subsequence.
|
|
84
|
+
*/
|
|
85
|
+
function longestIncreasingSubsequence(sequence) {
|
|
86
|
+
if (sequence.length === 0)
|
|
87
|
+
return [];
|
|
88
|
+
// `tails[length - 1]` is the index of the smallest possible tail of an increasing subsequence of
|
|
89
|
+
// that length; `previous` links each index back to its predecessor so the run can be walked out.
|
|
90
|
+
const tails = [];
|
|
91
|
+
const previous = Array.from({ length: sequence.length }).fill(-1);
|
|
92
|
+
for (let index = 0; index < sequence.length; index += 1) {
|
|
93
|
+
const value = sequence[index];
|
|
94
|
+
let low = 0;
|
|
95
|
+
let high = tails.length;
|
|
96
|
+
while (low < high) {
|
|
97
|
+
const middle = (low + high) >> 1;
|
|
98
|
+
if (sequence[tails[middle]] < value) {
|
|
99
|
+
low = middle + 1;
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
high = middle;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (low > 0)
|
|
106
|
+
previous[index] = tails[low - 1];
|
|
107
|
+
tails[low] = index;
|
|
108
|
+
}
|
|
109
|
+
const result = [];
|
|
110
|
+
let cursor = tails.at(-1);
|
|
111
|
+
while (cursor !== -1) {
|
|
112
|
+
result.push(cursor);
|
|
113
|
+
cursor = previous[cursor];
|
|
114
|
+
}
|
|
115
|
+
return result.toReversed();
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Computes the delta between two results of the same query.
|
|
119
|
+
*
|
|
120
|
+
* A fallback for the cases where the change that produced the new result is not available — a query
|
|
121
|
+
* that had to be re-executed in full, for instance. It costs a pass over both results, but it is
|
|
122
|
+
* paid once, on the side that has both of them, instead of shipping the entire new result to
|
|
123
|
+
* everyone who only needs to know what changed.
|
|
124
|
+
* @template T - The type of the items.
|
|
125
|
+
* @param previous - The result the delta should be relative to.
|
|
126
|
+
* @param next - The result the delta should produce.
|
|
127
|
+
* @returns The delta between the two results.
|
|
128
|
+
*/
|
|
129
|
+
function diffQueryResults(previous, next) {
|
|
130
|
+
// Both arrays are walked in full here, so the cheap ways out are worth taking. Two results
|
|
131
|
+
// holding the same items in the same places are the common case by some margin: this is asked
|
|
132
|
+
// several times per write, and most of those ask about a query the write did not really change.
|
|
133
|
+
if (holdsTheSameItems(previous, next)) {
|
|
134
|
+
return { added: [], changed: [], removed: [], moved: [], resultCount: next.length };
|
|
135
|
+
}
|
|
136
|
+
const previousIndexById = new Map();
|
|
137
|
+
previous.forEach((item, index) => previousIndexById.set(item.id, index));
|
|
138
|
+
const added = [];
|
|
139
|
+
const changed = [];
|
|
140
|
+
// Positions in `previous` of the items that survive, in the order they appear in `next`. An
|
|
141
|
+
// increasing run in here is a stretch of items whose relative order did not change — and when
|
|
142
|
+
// the whole thing is increasing, nothing moved and the work below can be skipped entirely.
|
|
143
|
+
const survivingPreviousIndices = [];
|
|
144
|
+
const survivingNextIndices = [];
|
|
145
|
+
const survived = Array.from({ length: previous.length }).fill(false);
|
|
146
|
+
let orderPreserved = true;
|
|
147
|
+
let lastPreviousIndex = -1;
|
|
148
|
+
next.forEach((item, index) => {
|
|
149
|
+
const previousIndex = previousIndexById.get(item.id);
|
|
150
|
+
if (previousIndex == null) {
|
|
151
|
+
added.push({ index, item });
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
survived[previousIndex] = true;
|
|
155
|
+
if (!(0, isEqual_1.default)(previous[previousIndex], item))
|
|
156
|
+
changed.push(item);
|
|
157
|
+
if (previousIndex < lastPreviousIndex)
|
|
158
|
+
orderPreserved = false;
|
|
159
|
+
lastPreviousIndex = previousIndex;
|
|
160
|
+
survivingPreviousIndices.push(previousIndex);
|
|
161
|
+
survivingNextIndices.push(index);
|
|
162
|
+
});
|
|
163
|
+
const removed = [];
|
|
164
|
+
previous.forEach((item, index) => {
|
|
165
|
+
if (!survived[index])
|
|
166
|
+
removed.push(item.id);
|
|
167
|
+
});
|
|
168
|
+
const moved = [];
|
|
169
|
+
if (!orderPreserved) {
|
|
170
|
+
const stationary = new Set(longestIncreasingSubsequence(survivingPreviousIndices)
|
|
171
|
+
.map(position => survivingNextIndices[position]));
|
|
172
|
+
survivingNextIndices.forEach((nextIndex) => {
|
|
173
|
+
if (stationary.has(nextIndex))
|
|
174
|
+
return;
|
|
175
|
+
moved.push({ index: nextIndex, id: next[nextIndex].id });
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return { added, changed, removed, moved, resultCount: next.length };
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Whether two results hold the same items in the same order, by identity.
|
|
182
|
+
*
|
|
183
|
+
* Items are replaced rather than mutated wherever they come from, so identity is a sound answer to
|
|
184
|
+
* "unchanged" — and a wrong one is impossible, only a missed shortcut.
|
|
185
|
+
* @template T - The type of the items.
|
|
186
|
+
* @param previous - One result.
|
|
187
|
+
* @param next - The other.
|
|
188
|
+
* @returns `true` when the two are element-for-element the same objects.
|
|
189
|
+
*/
|
|
190
|
+
function holdsTheSameItems(previous, next) {
|
|
191
|
+
if (previous === next)
|
|
192
|
+
return true;
|
|
193
|
+
if (previous.length !== next.length)
|
|
194
|
+
return false;
|
|
195
|
+
return previous.every((item, index) => item === next[index]);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Applies a delta to the result it was computed against.
|
|
199
|
+
* @template T - The type of the items.
|
|
200
|
+
* @param previous - The result the delta is relative to. Not modified.
|
|
201
|
+
* @param delta - The delta to apply.
|
|
202
|
+
* @returns The resulting items.
|
|
203
|
+
*/
|
|
204
|
+
function applyQueryDelta(previous, delta) {
|
|
205
|
+
const removed = new Set(delta.removed);
|
|
206
|
+
const changedById = new Map(delta.changed.map(item => [item.id, item]));
|
|
207
|
+
const movedIds = new Set(delta.moved.map(({ id }) => id));
|
|
208
|
+
const stationary = [];
|
|
209
|
+
const byId = new Map();
|
|
210
|
+
previous.forEach((item) => {
|
|
211
|
+
if (removed.has(item.id))
|
|
212
|
+
return;
|
|
213
|
+
const current = changedById.get(item.id) ?? item;
|
|
214
|
+
byId.set(current.id, current);
|
|
215
|
+
if (!movedIds.has(current.id))
|
|
216
|
+
stationary.push(current);
|
|
217
|
+
});
|
|
218
|
+
// Insertions carry positions in the resulting array, so splicing them in ascending order lands
|
|
219
|
+
// every one of them at its final index — each is placed only after everything before it is there.
|
|
220
|
+
const insertions = [
|
|
221
|
+
...delta.added.map(({ index, item }) => ({ index, item })),
|
|
222
|
+
...delta.moved.map(({ index, id }) => ({ index, item: byId.get(id) })),
|
|
223
|
+
].sort((a, b) => a.index - b.index); // eslint-disable-line unicorn/no-array-sort -- unavailable on Hermes
|
|
224
|
+
const result = stationary;
|
|
225
|
+
insertions.forEach(({ index, item }) => {
|
|
226
|
+
if (item == null)
|
|
227
|
+
return;
|
|
228
|
+
result.splice(index, 0, item);
|
|
229
|
+
});
|
|
230
|
+
return result;
|
|
231
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { QueryOptions } from '../DataAdapter';
|
|
2
|
+
import type Selector from '../types/Selector';
|
|
3
|
+
/**
|
|
4
|
+
* Generates a unique identifier for a query based on its selector and options.
|
|
5
|
+
* @param selector - The selector object.
|
|
6
|
+
* @param options - The query options object (optional).
|
|
7
|
+
* @returns A unique identifier string for the query.
|
|
8
|
+
*/
|
|
9
|
+
export default function queryId(selector: Selector<any>, options?: QueryOptions<any>): string;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = queryId;
|
|
4
|
+
/**
|
|
5
|
+
* Returns true when `options` is effectively "empty" for the purpose of queryId generation.
|
|
6
|
+
* Treats `undefined`/`null` as empty, and only considers plain objects with zero own enumerable keys as empty.
|
|
7
|
+
* Arrays and non-object values are not considered empty.
|
|
8
|
+
* @param options - Query options to test.
|
|
9
|
+
* @returns `true` if `options` is `null`/`undefined` or a plain object with no own keys; otherwise `false`.
|
|
10
|
+
*/
|
|
11
|
+
function isEmptyOptions(options) {
|
|
12
|
+
if (options == null)
|
|
13
|
+
return true;
|
|
14
|
+
if (typeof options !== 'object')
|
|
15
|
+
return false;
|
|
16
|
+
if (Array.isArray(options))
|
|
17
|
+
return false;
|
|
18
|
+
return Object.keys(options).length === 0;
|
|
19
|
+
}
|
|
20
|
+
// Stands in for absent options so they can be a `WeakMap` key like any other pair half.
|
|
21
|
+
const noOptions = {};
|
|
22
|
+
// Ids are asked for far more often than queries are created — every cursor read resolves its query
|
|
23
|
+
// through one, and a single `postMessage` round trip goes through several. Serializing the same two
|
|
24
|
+
// objects over and over is pure waste, so a pair of objects that has been seen before answers from
|
|
25
|
+
// here. Keyed weakly on both halves: an entry lives exactly as long as the objects it describes,
|
|
26
|
+
// and a selector built fresh at the call site simply misses and is collected again.
|
|
27
|
+
//
|
|
28
|
+
// The cache assumes a selector or options object is not mutated after it has been used to identify
|
|
29
|
+
// a query. That already holds today for a different reason — a query is registered, cached and
|
|
30
|
+
// looked up under the id its selector had at registration time, so mutating it afterwards loses the
|
|
31
|
+
// query either way.
|
|
32
|
+
const cache = new WeakMap();
|
|
33
|
+
/**
|
|
34
|
+
* Generates a unique identifier for a query based on its selector and options.
|
|
35
|
+
* @param selector - The selector object.
|
|
36
|
+
* @param options - The query options object (optional).
|
|
37
|
+
* @returns A unique identifier string for the query.
|
|
38
|
+
*/
|
|
39
|
+
function queryId(selector, options) {
|
|
40
|
+
const isCacheable = selector != null && typeof selector === 'object'
|
|
41
|
+
&& (options == null || typeof options === 'object');
|
|
42
|
+
if (!isCacheable) {
|
|
43
|
+
const optionsId = isEmptyOptions(options) ? -1 : JSON.stringify(options);
|
|
44
|
+
return `${JSON.stringify(selector)}:${optionsId}`;
|
|
45
|
+
}
|
|
46
|
+
const optionsKey = (options ?? noOptions);
|
|
47
|
+
const cachedForSelector = cache.get(selector);
|
|
48
|
+
const cached = cachedForSelector?.get(optionsKey);
|
|
49
|
+
if (cached != null)
|
|
50
|
+
return cached;
|
|
51
|
+
const selectorId = JSON.stringify(selector);
|
|
52
|
+
const optionsId = isEmptyOptions(options) ? -1 : JSON.stringify(options);
|
|
53
|
+
const id = `${selectorId}:${optionsId}`;
|
|
54
|
+
if (cachedForSelector) {
|
|
55
|
+
cachedForSelector.set(optionsKey, id);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
cache.set(selector, new WeakMap([[optionsKey, id]]));
|
|
59
|
+
}
|
|
60
|
+
return id;
|
|
61
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = randomId;
|
|
4
|
+
/**
|
|
5
|
+
* creates a random id
|
|
6
|
+
* @returns a random string of 16 characters
|
|
7
|
+
* @example
|
|
8
|
+
* randomId() // '1234567890abcdef'
|
|
9
|
+
*/
|
|
10
|
+
function randomId() {
|
|
11
|
+
return Array.from({ length: 16 }, () => 'abcdefghijklmnopqrstuvwxyz0123456789'[Math.floor(Math.random() * 36)]).join('');
|
|
12
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export type MaybePromise<T> = T | Promise<T>;
|
|
2
|
+
/**
|
|
3
|
+
* Options that control execution mode (and potential future mode-specific behavior).
|
|
4
|
+
* Keep this minimal; you can extend it later (e.g. signal, timeoutMs, debugLabel).
|
|
5
|
+
*/
|
|
6
|
+
export type ModeOptions = {
|
|
7
|
+
async?: boolean;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* A generator helper that makes TypeScript infer the “synchronous value type” for maybe-async expressions.
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* const doc = yield* unwrap(Collection.findOne(...))
|
|
14
|
+
* const list = yield* unwrap(Collection.find(...).fetch())
|
|
15
|
+
*
|
|
16
|
+
* Runtime note:
|
|
17
|
+
* This does not “unwrap” Promises by itself. It yields the value/Promise to the runner and returns the
|
|
18
|
+
* value that the runner feeds back via `.next(...)`.
|
|
19
|
+
* @param value The value (or Promise of a value) to yield to the runner.
|
|
20
|
+
* @returns A generator that yields `value` and resolves to the runner-supplied unwrapped `T`.
|
|
21
|
+
*/
|
|
22
|
+
export declare function unwrap<T>(value: MaybePromise<T>): Generator<MaybePromise<T>, T, T>;
|
|
23
|
+
/**
|
|
24
|
+
* Generator shape used by the factory.
|
|
25
|
+
*
|
|
26
|
+
* `TThis` is the type of `this` inside the generator.
|
|
27
|
+
* `Args` are the method parameters (excluding the mode flag).
|
|
28
|
+
* `TReturn` is the final return value of the workflow.
|
|
29
|
+
* `TNext` is the type that is yielded/awaited and fed back via `.next(...)`.
|
|
30
|
+
*
|
|
31
|
+
* Note:
|
|
32
|
+
* - For best inference at yield sites, prefer `yield* unwrap(expr)` for maybe-async expressions.
|
|
33
|
+
*/
|
|
34
|
+
export type ReactiveOrAsyncGen<TThis, Arguments extends any[], TReturn, TNext> = (this: TThis, a: boolean, ...args: Arguments) => Generator<MaybePromise<TNext>, TReturn, TNext>;
|
|
35
|
+
/**
|
|
36
|
+
* The method type produced from the generator signature.
|
|
37
|
+
* Adds overloads so that `{ async: true }` yields a `Promise<...>` return type.
|
|
38
|
+
*/
|
|
39
|
+
export type ReactiveOrAsyncMethod<TThis, P extends any[], R, N> = {
|
|
40
|
+
(this: TThis, ...args: P): R;
|
|
41
|
+
(this: TThis, ...args: [...P, ModeOptions?]): MaybePromise<R>;
|
|
42
|
+
(this: TThis, ...args: [...P, {
|
|
43
|
+
async: true;
|
|
44
|
+
}]): Promise<R>;
|
|
45
|
+
} & {
|
|
46
|
+
/** Exposes the underlying generator for composition via `yield* method.generator.call(this, a, ...)` */
|
|
47
|
+
generator: (this: TThis, a: boolean, ...args: P) => Generator<MaybePromise<N>, R, N>;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Factory that turns a generator workflow into a callable method that can run in sync (reactive) or async mode.
|
|
51
|
+
*
|
|
52
|
+
* Call style:
|
|
53
|
+
* fn(a, b) -> sync/reactive return
|
|
54
|
+
* await fn(a, b, { async: true }) -> async return
|
|
55
|
+
* @param gen Generator workflow. Receives `(a)` which indicates async mode and should `yield`/`yield* unwrap(...)`
|
|
56
|
+
* any values that may be Promises.
|
|
57
|
+
* @returns A callable method with overloads plus a `.generator` property for composition.
|
|
58
|
+
*/
|
|
59
|
+
export default function reactiveOrAsync<TThis, P extends any[], R, N>(gen: (this: TThis, a: boolean, ...args: P) => Generator<MaybePromise<N>, R, N>): ReactiveOrAsyncMethod<TThis, P, R, N>;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.unwrap = unwrap;
|
|
4
|
+
exports.default = reactiveOrAsync;
|
|
5
|
+
/**
|
|
6
|
+
* A generator helper that makes TypeScript infer the “synchronous value type” for maybe-async expressions.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* const doc = yield* unwrap(Collection.findOne(...))
|
|
10
|
+
* const list = yield* unwrap(Collection.find(...).fetch())
|
|
11
|
+
*
|
|
12
|
+
* Runtime note:
|
|
13
|
+
* This does not “unwrap” Promises by itself. It yields the value/Promise to the runner and returns the
|
|
14
|
+
* value that the runner feeds back via `.next(...)`.
|
|
15
|
+
* @param value The value (or Promise of a value) to yield to the runner.
|
|
16
|
+
* @returns A generator that yields `value` and resolves to the runner-supplied unwrapped `T`.
|
|
17
|
+
*/
|
|
18
|
+
function unwrap(value) {
|
|
19
|
+
return (function* () {
|
|
20
|
+
return yield value;
|
|
21
|
+
})();
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Internal: checks for thenables (Promise-like).
|
|
25
|
+
* @param value The value to test.
|
|
26
|
+
* @returns `true` if `value` looks like a Promise/thenable.
|
|
27
|
+
*/
|
|
28
|
+
function isThenable(value) {
|
|
29
|
+
return typeof value === 'object' && value !== null && typeof value.then === 'function';
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Internal runner: executes a generator either synchronously (reactive) or asynchronously (imperative).
|
|
33
|
+
*
|
|
34
|
+
* - In sync mode, yielding a Promise is a programming error and throws.
|
|
35
|
+
* - In async mode, yielded Promises are awaited.
|
|
36
|
+
* @param thisArgument The `this` value to bind when invoking `gen`.
|
|
37
|
+
* @param mode Execution mode options.
|
|
38
|
+
* @param gen The generator workflow to run.
|
|
39
|
+
* @returns The workflow result (a plain value in sync mode, or a Promise in async mode).
|
|
40
|
+
*/
|
|
41
|
+
function runReactiveOrAsync(thisArgument, mode, gen) {
|
|
42
|
+
const a = !!mode?.async;
|
|
43
|
+
const it = gen.call(thisArgument, a);
|
|
44
|
+
if (!a) {
|
|
45
|
+
let step = it.next();
|
|
46
|
+
while (!step.done) {
|
|
47
|
+
const y = step.value;
|
|
48
|
+
if (isThenable(y))
|
|
49
|
+
throw new Error('Promise yielded in sync flow');
|
|
50
|
+
step = it.next(y);
|
|
51
|
+
}
|
|
52
|
+
return step.value;
|
|
53
|
+
}
|
|
54
|
+
return (async function () {
|
|
55
|
+
let step = it.next();
|
|
56
|
+
while (!step.done) {
|
|
57
|
+
const y = step.value;
|
|
58
|
+
const v = isThenable(y) ? await y : y;
|
|
59
|
+
step = it.next(v);
|
|
60
|
+
}
|
|
61
|
+
return step.value;
|
|
62
|
+
})();
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Factory that turns a generator workflow into a callable method that can run in sync (reactive) or async mode.
|
|
66
|
+
*
|
|
67
|
+
* Call style:
|
|
68
|
+
* fn(a, b) -> sync/reactive return
|
|
69
|
+
* await fn(a, b, { async: true }) -> async return
|
|
70
|
+
* @param gen Generator workflow. Receives `(a)` which indicates async mode and should `yield`/`yield* unwrap(...)`
|
|
71
|
+
* any values that may be Promises.
|
|
72
|
+
* @returns A callable method with overloads plus a `.generator` property for composition.
|
|
73
|
+
*/
|
|
74
|
+
function reactiveOrAsync(gen) {
|
|
75
|
+
/**
|
|
76
|
+
* The generated method wrapper.
|
|
77
|
+
* @param allArguments Method arguments, optionally ending with a `ModeOptions` object.
|
|
78
|
+
* @returns The workflow result (sync) or a Promise of the result (async).
|
|
79
|
+
*/
|
|
80
|
+
function method(...allArguments) {
|
|
81
|
+
const last = allArguments.length > 0 ? allArguments.at(-1) : undefined;
|
|
82
|
+
const hasMode = typeof last === 'object'
|
|
83
|
+
&& last !== null
|
|
84
|
+
&& 'async' in (last);
|
|
85
|
+
const mode = hasMode ? last : undefined;
|
|
86
|
+
const parameters = (hasMode ? allArguments.slice(0, -1) : allArguments);
|
|
87
|
+
return runReactiveOrAsync(this, mode, function* (a) {
|
|
88
|
+
return yield* gen.call(this, a, ...parameters);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
method.generator = gen;
|
|
92
|
+
return method;
|
|
93
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serializes a value into a string representation.
|
|
3
|
+
* Handles various types, including strings, numbers, booleans, dates, and objects.
|
|
4
|
+
* Falls back to JSON stringification for unsupported types.
|
|
5
|
+
* @param value - The value to serialize.
|
|
6
|
+
* - Strings are returned as-is.
|
|
7
|
+
* - Numbers and booleans are converted to their string representation.
|
|
8
|
+
* - Dates are converted to ISO string format.
|
|
9
|
+
* - Other values are stringified using `JSON.stringify`.
|
|
10
|
+
* @returns A string representation of the value.
|
|
11
|
+
*/
|
|
12
|
+
export default function serializeValue(value: any): string | null;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = serializeValue;
|
|
4
|
+
/**
|
|
5
|
+
* Serializes a value into a string representation.
|
|
6
|
+
* Handles various types, including strings, numbers, booleans, dates, and objects.
|
|
7
|
+
* Falls back to JSON stringification for unsupported types.
|
|
8
|
+
* @param value - The value to serialize.
|
|
9
|
+
* - Strings are returned as-is.
|
|
10
|
+
* - Numbers and booleans are converted to their string representation.
|
|
11
|
+
* - Dates are converted to ISO string format.
|
|
12
|
+
* - Other values are stringified using `JSON.stringify`.
|
|
13
|
+
* @returns A string representation of the value.
|
|
14
|
+
*/
|
|
15
|
+
function serializeValue(value) {
|
|
16
|
+
if (value == null)
|
|
17
|
+
return null;
|
|
18
|
+
if (typeof value === 'string')
|
|
19
|
+
return value;
|
|
20
|
+
if (typeof value === 'number')
|
|
21
|
+
return value.toString();
|
|
22
|
+
if (typeof value === 'boolean')
|
|
23
|
+
return value.toString();
|
|
24
|
+
if (value instanceof Date)
|
|
25
|
+
return value.toISOString();
|
|
26
|
+
return JSON.stringify(value);
|
|
27
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sets a value at a specified path within an object. Creates nested structures
|
|
3
|
+
* (arrays or objects) as needed to set the value at the correct location. Supports
|
|
4
|
+
* deleting the key if the value is `undefined` and the `deleteIfUndefined` flag is set to `true`.
|
|
5
|
+
* @template T - The type of the object to modify.
|
|
6
|
+
* @template K - The type of the value to set.
|
|
7
|
+
* @param object - The object to modify. The object is mutated directly.
|
|
8
|
+
* @param path - The path (dot or bracket notation) where the value should be set.
|
|
9
|
+
* @param value - The value to set at the specified path.
|
|
10
|
+
* @param deleteIfUndefined - A boolean indicating whether to delete the key if the value is `undefined` (default: `false`).
|
|
11
|
+
* @returns The modified object.
|
|
12
|
+
*/
|
|
13
|
+
export default function set<T extends object, K>(object: T, path: string, value: K, deleteIfUndefined?: boolean): T;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = set;
|
|
4
|
+
/**
|
|
5
|
+
* Sets a value at a specified path within an object. Creates nested structures
|
|
6
|
+
* (arrays or objects) as needed to set the value at the correct location. Supports
|
|
7
|
+
* deleting the key if the value is `undefined` and the `deleteIfUndefined` flag is set to `true`.
|
|
8
|
+
* @template T - The type of the object to modify.
|
|
9
|
+
* @template K - The type of the value to set.
|
|
10
|
+
* @param object - The object to modify. The object is mutated directly.
|
|
11
|
+
* @param path - The path (dot or bracket notation) where the value should be set.
|
|
12
|
+
* @param value - The value to set at the specified path.
|
|
13
|
+
* @param deleteIfUndefined - A boolean indicating whether to delete the key if the value is `undefined` (default: `false`).
|
|
14
|
+
* @returns The modified object.
|
|
15
|
+
*/
|
|
16
|
+
function set(object, path, value, deleteIfUndefined = false) {
|
|
17
|
+
if (object == null)
|
|
18
|
+
return object;
|
|
19
|
+
const segments = path.split(/[.[\]]/g);
|
|
20
|
+
if (segments[0] === '')
|
|
21
|
+
segments.shift();
|
|
22
|
+
if (segments.at(-1) === '')
|
|
23
|
+
segments.pop();
|
|
24
|
+
const apply = (node) => {
|
|
25
|
+
if (segments.length > 1) {
|
|
26
|
+
const key = segments.shift();
|
|
27
|
+
const nextIsNumber = !Number.isNaN(Number.parseInt(segments[0], 10));
|
|
28
|
+
if (node[key] === undefined) {
|
|
29
|
+
node[key] = nextIsNumber ? [] : {};
|
|
30
|
+
}
|
|
31
|
+
apply(node[key]);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
if (deleteIfUndefined && value === undefined) {
|
|
35
|
+
delete node[segments[0]];
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
node[segments[0]] = value;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
apply(object);
|
|
42
|
+
return object;
|
|
43
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sorts an array of items based on multiple fields and their specified sort order.
|
|
3
|
+
* Uses the `fast-sort` library for efficient sorting.
|
|
4
|
+
* @template T - The type of the items in the array.
|
|
5
|
+
* @param items - The array of items to be sorted.
|
|
6
|
+
* @param sortFields - An object defining the sort order for each field.
|
|
7
|
+
* - Keys are the field names, and values are either `1` (ascending) or `-1` (descending).
|
|
8
|
+
* @returns A new array of items sorted based on the specified fields and their order.
|
|
9
|
+
*/
|
|
10
|
+
export default function sortItems<T extends Record<string, any>>(items: T[], sortFields: {
|
|
11
|
+
[P in keyof T]?: -1 | 1;
|
|
12
|
+
} & Record<string, -1 | 1>): T[];
|