@signaldb/core 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/.vite/manifest.json +183 -80
- package/dist/AsyncDataAdapter.d.ts +53 -7
- package/dist/AutoFetchDataAdapter.d.ts +4 -6
- package/dist/Collection/Cursor.d.ts +56 -4
- package/dist/Collection/Observer.d.ts +33 -0
- package/dist/Collection/index.d.ts +66 -19
- package/dist/Collection/types.d.ts +9 -2
- package/dist/DataAdapter.d.ts +24 -6
- package/dist/DefaultDataAdapter.d.ts +5 -5
- package/dist/WorkerDataAdapter.d.ts +57 -5
- package/dist/WorkerDataAdapterHost.d.ts +9 -7
- package/dist/createIndex.d.ts +2 -2
- package/dist/createIndexProvider.d.ts +2 -2
- package/dist/createReactivityAdapter.d.ts +2 -2
- package/dist/createStorageAdapter.d.ts +1 -1
- package/dist/getIndexInfo.d.ts +3 -4
- package/dist/index.cjs.js +33 -31
- package/dist/index.d.ts +6 -1
- package/dist/index.mjs +17 -32
- package/dist/index10.cjs.js +11 -0
- package/dist/index10.mjs +10 -18
- package/dist/index11.cjs.js +23 -0
- package/dist/index11.mjs +22 -15
- package/dist/index12.cjs.js +45 -0
- package/dist/index12.mjs +44 -353
- package/dist/index13.cjs.js +22 -0
- package/dist/index13.mjs +21 -419
- package/dist/index14.cjs.js +45 -0
- package/dist/index14.mjs +44 -206
- package/dist/index15.cjs.js +86 -0
- package/dist/index15.mjs +85 -388
- package/dist/index16.cjs.js +15 -0
- package/dist/index16.mjs +14 -579
- package/dist/index17.cjs.js +141 -0
- package/dist/index17.mjs +140 -152
- package/dist/index18.cjs.js +47 -0
- package/dist/index18.mjs +46 -26
- package/dist/index19.cjs.js +15 -0
- package/dist/index19.mjs +14 -41
- package/dist/index2.cjs.js +33 -0
- package/dist/index2.mjs +32 -258
- package/dist/index20.cjs.js +23 -0
- package/dist/index20.mjs +22 -16
- package/dist/index21.cjs.js +37 -0
- package/dist/index21.mjs +36 -104
- package/dist/index22.cjs.js +35 -0
- package/dist/index22.mjs +34 -128
- package/dist/index23.cjs.js +21 -0
- package/dist/index23.mjs +20 -7
- package/dist/index24.cjs.js +19 -0
- package/dist/index24.mjs +18 -27
- package/dist/index25.cjs.js +157 -0
- package/dist/index25.mjs +156 -11
- package/dist/index26.cjs.js +38 -0
- package/dist/index26.mjs +37 -29
- package/dist/index27.cjs.js +42 -0
- package/dist/index27.mjs +41 -46
- package/dist/index28.cjs.js +347 -0
- package/dist/index28.mjs +345 -7
- package/dist/index29.cjs.js +602 -0
- package/dist/index29.mjs +601 -7
- package/dist/index3.cjs.js +19 -0
- package/dist/index3.mjs +18 -556
- package/dist/index30.cjs.js +11 -0
- package/dist/index30.mjs +10 -42
- package/dist/index31.cjs.js +11 -0
- package/dist/index31.mjs +10 -29
- package/dist/index32.cjs.js +90 -0
- package/dist/index32.mjs +89 -10
- package/dist/index33.cjs.js +53 -0
- package/dist/index33.mjs +52 -5
- package/dist/index34.cjs.js +453 -0
- package/dist/index34.mjs +453 -0
- package/dist/index35.cjs.js +72 -0
- package/dist/index35.mjs +72 -0
- package/dist/index36.cjs.js +580 -0
- package/dist/index36.mjs +580 -0
- package/dist/index37.cjs.js +21 -0
- package/dist/index37.mjs +21 -0
- package/dist/index38.cjs.js +363 -0
- package/dist/index38.mjs +363 -0
- package/dist/index39.cjs.js +513 -0
- package/dist/index39.mjs +513 -0
- package/dist/index4.cjs.js +209 -0
- package/dist/index4.mjs +204 -5
- package/dist/index5.cjs.js +187 -0
- package/dist/index5.mjs +186 -5
- package/dist/index6.cjs.js +303 -0
- package/dist/index6.mjs +301 -28
- package/dist/index7.cjs.js +125 -0
- package/dist/index7.mjs +124 -12
- package/dist/index8.cjs.js +33 -0
- package/dist/index8.mjs +32 -5
- package/dist/index9.cjs.js +12 -0
- package/dist/index9.mjs +11 -133
- package/dist/types/IndexProvider.d.ts +2 -2
- package/dist/types/Modifier.d.ts +1 -1
- package/dist/types/ReactivityAdapter.d.ts +1 -1
- package/dist/types/StorageAdapter.d.ts +9 -1
- package/dist/utils/applyQueryOptions.d.ts +15 -0
- package/dist/utils/createSignal.d.ts +2 -2
- package/dist/utils/getMatchingKeys.d.ts +2 -2
- package/dist/utils/idIndexQuery.d.ts +20 -0
- package/dist/utils/incrementalQueryUpdate.d.ts +62 -0
- package/dist/utils/isFieldExpression.d.ts +1 -1
- package/dist/utils/match.d.ts +1 -1
- package/dist/utils/modify.d.ts +1 -1
- package/dist/utils/projectItems.d.ts +12 -0
- package/dist/utils/queryDelta.d.ts +83 -0
- package/dist/utils/queryId.d.ts +2 -2
- package/dist/utils/reactiveOrAsync.d.ts +59 -0
- package/dist/utils/storageIndexQuery.d.ts +20 -0
- package/package.json +1 -1
- package/dist/index.cjs10.js +0 -18
- package/dist/index.cjs11.js +0 -15
- package/dist/index.cjs12.js +0 -353
- package/dist/index.cjs13.js +0 -419
- package/dist/index.cjs14.js +0 -206
- package/dist/index.cjs15.js +0 -388
- package/dist/index.cjs16.js +0 -579
- package/dist/index.cjs17.js +0 -152
- package/dist/index.cjs18.js +0 -26
- package/dist/index.cjs19.js +0 -42
- package/dist/index.cjs2.js +0 -259
- package/dist/index.cjs20.js +0 -16
- package/dist/index.cjs21.js +0 -104
- package/dist/index.cjs22.js +0 -129
- package/dist/index.cjs23.js +0 -7
- package/dist/index.cjs24.js +0 -27
- package/dist/index.cjs25.js +0 -10
- package/dist/index.cjs26.js +0 -29
- package/dist/index.cjs27.js +0 -46
- package/dist/index.cjs28.js +0 -8
- package/dist/index.cjs29.js +0 -7
- package/dist/index.cjs3.js +0 -557
- package/dist/index.cjs30.js +0 -42
- package/dist/index.cjs31.js +0 -29
- package/dist/index.cjs32.js +0 -9
- package/dist/index.cjs33.js +0 -5
- package/dist/index.cjs4.js +0 -5
- package/dist/index.cjs5.js +0 -5
- package/dist/index.cjs6.js +0 -29
- package/dist/index.cjs7.js +0 -12
- package/dist/index.cjs8.js +0 -5
- package/dist/index.cjs9.js +0 -133
package/dist/index22.mjs
CHANGED
|
@@ -1,129 +1,35 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
1
|
+
import get from "./index11.mjs";
|
|
2
|
+
import set from "./index21.mjs";
|
|
3
|
+
//#region src/utils/project.ts
|
|
4
|
+
/**
|
|
5
|
+
* Projects the fields of an object based on a specified fields configuration.
|
|
6
|
+
* Supports inclusion (`1`) and exclusion (`0`) of specific fields. Creates a new object
|
|
7
|
+
* with the desired fields included or excluded, based on the configuration.
|
|
8
|
+
* @template T - The type of the object being projected.
|
|
9
|
+
* @param item - The original object to project fields from.
|
|
10
|
+
* @param fields - An object defining the fields to include (`1`) or exclude (`0`).
|
|
11
|
+
* - Keys are the field names, and values are either `1` (include) or `0` (exclude).
|
|
12
|
+
* @returns A new object with the specified fields included or excluded.
|
|
13
|
+
* - If all fields are set to `0`, the excluded fields are removed from the result.
|
|
14
|
+
* - If fields are set to `1`, only the included fields are retained.
|
|
15
|
+
*/
|
|
16
|
+
function project(item, fields) {
|
|
17
|
+
if (Object.values(fields).every((value) => value === 0)) {
|
|
18
|
+
const result = { ...item };
|
|
19
|
+
Object.keys(fields).forEach((key) => {
|
|
20
|
+
if (get(item, key) === void 0) return;
|
|
21
|
+
set(result, key, void 0, true);
|
|
22
|
+
});
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
const result = {};
|
|
26
|
+
Object.entries(fields).forEach(([key, value]) => {
|
|
27
|
+
const fieldValue = get(item, key);
|
|
28
|
+
if (fieldValue === void 0) return;
|
|
29
|
+
if (fieldValue == null && value !== 1) return;
|
|
30
|
+
set(result, key, value === 1 ? fieldValue : void 0);
|
|
31
|
+
});
|
|
32
|
+
return result;
|
|
30
33
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const getSelector = (indexInfo) => {
|
|
34
|
-
const { matched: selMatched, ids: selIds, optimizedSelector: optimizedSelector2 } = indexInfo;
|
|
35
|
-
if (selMatched) {
|
|
36
|
-
idsCallback(true, selIds);
|
|
37
|
-
if (Object.keys(optimizedSelector2).length > 0) {
|
|
38
|
-
return optimizedSelector2;
|
|
39
|
-
}
|
|
40
|
-
} else {
|
|
41
|
-
idsCallback(false, []);
|
|
42
|
-
return sel;
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
const indexInfoOrPromise = getIndexInfo(queryFunctions, sel);
|
|
46
|
-
if (indexInfoOrPromise instanceof Promise) {
|
|
47
|
-
return indexInfoOrPromise.then(async (indexInfo) => {
|
|
48
|
-
const memo2 = memoOrPromise instanceof Promise ? await memoOrPromise : memoOrPromise;
|
|
49
|
-
const optimizedSelector2 = getSelector(indexInfo);
|
|
50
|
-
if (optimizedSelector2)
|
|
51
|
-
memo2.push(optimizedSelector2);
|
|
52
|
-
return memo2;
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
const memo = memoOrPromise;
|
|
56
|
-
if (memo instanceof Promise)
|
|
57
|
-
throw new Error("Mixing async and sync index providers is not supported");
|
|
58
|
-
const optimizedSelector = getSelector(indexInfoOrPromise);
|
|
59
|
-
if (optimizedSelector)
|
|
60
|
-
memo.push(optimizedSelector);
|
|
61
|
-
return memo;
|
|
62
|
-
}, []);
|
|
63
|
-
}
|
|
64
|
-
function getIndexInfo(queryFunctions, selector) {
|
|
65
|
-
if (selector == null || Object.keys(selector).length <= 0) {
|
|
66
|
-
return {
|
|
67
|
-
matched: false,
|
|
68
|
-
ids: [],
|
|
69
|
-
optimizedSelector: selector
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
const { $and, $or, ...rest } = selector;
|
|
73
|
-
const flatInfoOrPromise = getMergedIndexInfo(queryFunctions, rest);
|
|
74
|
-
const processFlatInfo = (flatInfo) => {
|
|
75
|
-
let { matched, ids } = flatInfo;
|
|
76
|
-
const newSelector = flatInfo.optimizedSelector;
|
|
77
|
-
const $andNewOrPromise = Array.isArray($and) ? optimizeLogicGate(queryFunctions, $and, (match, selIds) => {
|
|
78
|
-
if (!match)
|
|
79
|
-
return;
|
|
80
|
-
ids = matched ? intersection(ids, selIds) : selIds;
|
|
81
|
-
matched = true;
|
|
82
|
-
}) : void 0;
|
|
83
|
-
const process$and = ($andNew) => {
|
|
84
|
-
if ($andNew && $andNew.length > 0)
|
|
85
|
-
newSelector.$and = $andNew;
|
|
86
|
-
let hasNonIndexField = false;
|
|
87
|
-
const matchedBefore = matched;
|
|
88
|
-
const idsBefore = ids;
|
|
89
|
-
const process$or = ($orNew) => {
|
|
90
|
-
if ($orNew && $orNew.length > 0)
|
|
91
|
-
newSelector.$or = $orNew;
|
|
92
|
-
if (hasNonIndexField) {
|
|
93
|
-
newSelector.$or = $or;
|
|
94
|
-
matched = matchedBefore;
|
|
95
|
-
ids = idsBefore;
|
|
96
|
-
}
|
|
97
|
-
return {
|
|
98
|
-
matched,
|
|
99
|
-
ids: ids || [],
|
|
100
|
-
optimizedSelector: newSelector
|
|
101
|
-
};
|
|
102
|
-
};
|
|
103
|
-
const $orNewOrPromise = Array.isArray($or) ? optimizeLogicGate(queryFunctions, $or, (match, selIds) => {
|
|
104
|
-
if (match) {
|
|
105
|
-
ids = [.../* @__PURE__ */ new Set([...ids, ...selIds])];
|
|
106
|
-
matched = true;
|
|
107
|
-
} else {
|
|
108
|
-
hasNonIndexField = true;
|
|
109
|
-
}
|
|
110
|
-
}) : void 0;
|
|
111
|
-
if ($orNewOrPromise instanceof Promise) {
|
|
112
|
-
return $orNewOrPromise.then(($orNew) => process$or($orNew));
|
|
113
|
-
}
|
|
114
|
-
return process$or($orNewOrPromise);
|
|
115
|
-
};
|
|
116
|
-
if ($andNewOrPromise instanceof Promise) {
|
|
117
|
-
return $andNewOrPromise.then(($andNew) => process$and($andNew));
|
|
118
|
-
}
|
|
119
|
-
return process$and($andNewOrPromise);
|
|
120
|
-
};
|
|
121
|
-
if (flatInfoOrPromise instanceof Promise) {
|
|
122
|
-
return flatInfoOrPromise.then(processFlatInfo);
|
|
123
|
-
}
|
|
124
|
-
return processFlatInfo(flatInfoOrPromise);
|
|
125
|
-
}
|
|
126
|
-
export {
|
|
127
|
-
getIndexInfo as default,
|
|
128
|
-
getMergedIndexInfo
|
|
129
|
-
};
|
|
34
|
+
//#endregion
|
|
35
|
+
export { project as default };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const require_project = require("./index22.cjs.js");
|
|
2
|
+
//#region src/utils/projectItems.ts
|
|
3
|
+
/**
|
|
4
|
+
* Applies a query's field projection to a list of items, keeping the primary key unless the
|
|
5
|
+
* projection excludes it outright. Returns the items untouched when there is no projection, so a
|
|
6
|
+
* caller does not have to check for one first.
|
|
7
|
+
* @template T - The type of the items.
|
|
8
|
+
* @param items - The items to project.
|
|
9
|
+
* @param fields - The projection, or `undefined` for none.
|
|
10
|
+
* @returns The projected items.
|
|
11
|
+
*/
|
|
12
|
+
function projectItems(items, fields) {
|
|
13
|
+
if (!fields) return items;
|
|
14
|
+
const idExcluded = fields.id === 0;
|
|
15
|
+
return items.map((item) => ({
|
|
16
|
+
...idExcluded ? {} : { id: item.id },
|
|
17
|
+
...require_project.default(item, fields)
|
|
18
|
+
}));
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
exports.default = projectItems;
|
package/dist/index23.mjs
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import project from "./index22.mjs";
|
|
2
|
+
//#region src/utils/projectItems.ts
|
|
3
|
+
/**
|
|
4
|
+
* Applies a query's field projection to a list of items, keeping the primary key unless the
|
|
5
|
+
* projection excludes it outright. Returns the items untouched when there is no projection, so a
|
|
6
|
+
* caller does not have to check for one first.
|
|
7
|
+
* @template T - The type of the items.
|
|
8
|
+
* @param items - The items to project.
|
|
9
|
+
* @param fields - The projection, or `undefined` for none.
|
|
10
|
+
* @returns The projected items.
|
|
11
|
+
*/
|
|
12
|
+
function projectItems(items, fields) {
|
|
13
|
+
if (!fields) return items;
|
|
14
|
+
const idExcluded = fields.id === 0;
|
|
15
|
+
return items.map((item) => ({
|
|
16
|
+
...idExcluded ? {} : { id: item.id },
|
|
17
|
+
...project(item, fields)
|
|
18
|
+
}));
|
|
5
19
|
}
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
};
|
|
20
|
+
//#endregion
|
|
21
|
+
export { projectItems as default };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const require_get = require("./index11.cjs.js");
|
|
2
|
+
let fast_sort = require("fast-sort");
|
|
3
|
+
//#region src/utils/sortItems.ts
|
|
4
|
+
/**
|
|
5
|
+
* Sorts an array of items based on multiple fields and their specified sort order.
|
|
6
|
+
* Uses the `fast-sort` library for efficient sorting.
|
|
7
|
+
* @template T - The type of the items in the array.
|
|
8
|
+
* @param items - The array of items to be sorted.
|
|
9
|
+
* @param sortFields - An object defining the sort order for each field.
|
|
10
|
+
* - Keys are the field names, and values are either `1` (ascending) or `-1` (descending).
|
|
11
|
+
* @returns A new array of items sorted based on the specified fields and their order.
|
|
12
|
+
*/
|
|
13
|
+
function sortItems(items, sortFields) {
|
|
14
|
+
return (0, fast_sort.sort)(items).by(Object.entries(sortFields).map(([key, value]) => {
|
|
15
|
+
return { [value === 1 ? "asc" : "desc"]: (i) => require_get.default(i, key) };
|
|
16
|
+
}));
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
exports.default = sortItems;
|
package/dist/index24.mjs
CHANGED
|
@@ -1,28 +1,19 @@
|
|
|
1
|
-
import get from "./
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const fieldValue = get(item, key);
|
|
18
|
-
if (fieldValue === void 0)
|
|
19
|
-
return;
|
|
20
|
-
if (fieldValue == null && value !== 1)
|
|
21
|
-
return;
|
|
22
|
-
set(result, key, value === 1 ? fieldValue : void 0);
|
|
23
|
-
});
|
|
24
|
-
return result;
|
|
1
|
+
import get from "./index11.mjs";
|
|
2
|
+
import { sort } from "fast-sort";
|
|
3
|
+
//#region src/utils/sortItems.ts
|
|
4
|
+
/**
|
|
5
|
+
* Sorts an array of items based on multiple fields and their specified sort order.
|
|
6
|
+
* Uses the `fast-sort` library for efficient sorting.
|
|
7
|
+
* @template T - The type of the items in the array.
|
|
8
|
+
* @param items - The array of items to be sorted.
|
|
9
|
+
* @param sortFields - An object defining the sort order for each field.
|
|
10
|
+
* - Keys are the field names, and values are either `1` (ascending) or `-1` (descending).
|
|
11
|
+
* @returns A new array of items sorted based on the specified fields and their order.
|
|
12
|
+
*/
|
|
13
|
+
function sortItems(items, sortFields) {
|
|
14
|
+
return sort(items).by(Object.entries(sortFields).map(([key, value]) => {
|
|
15
|
+
return { [value === 1 ? "asc" : "desc"]: (i) => get(i, key) };
|
|
16
|
+
}));
|
|
25
17
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
};
|
|
18
|
+
//#endregion
|
|
19
|
+
export { sortItems as default };
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
const require_match = require("./index19.cjs.js");
|
|
2
|
+
const require_projectItems = require("./index23.cjs.js");
|
|
3
|
+
const require_sortItems = require("./index24.cjs.js");
|
|
4
|
+
//#region src/utils/incrementalQueryUpdate.ts
|
|
5
|
+
/**
|
|
6
|
+
* Recomputes a query's result from its previous result and the change that was just written,
|
|
7
|
+
* without going back to the store.
|
|
8
|
+
*
|
|
9
|
+
* A store re-executing the query instead reads every item it holds (or every item an index points
|
|
10
|
+
* at) and filters, sorts and projects the lot — for a write that touched one row. This does the
|
|
11
|
+
* same job in the size of the write, which is what a query's result costs to keep up to date when
|
|
12
|
+
* the change that affects it is already in hand.
|
|
13
|
+
*
|
|
14
|
+
* Returns `null` when the previous result is not enough to answer, and the caller has to re-execute
|
|
15
|
+
* the query after all:
|
|
16
|
+
* - `limit` or `skip`: the result is a window onto a larger set, and an item leaving the window has
|
|
17
|
+
* to be replaced by one the previous result never contained.
|
|
18
|
+
* - `fields` together with a `sort` the projection does not keep: the previous items are
|
|
19
|
+
* projected, so a sort key the projection dropped is no longer there to sort by. A projection
|
|
20
|
+
* that keeps every sort key is fine, and is the common case — a list sorted by the same date it
|
|
21
|
+
* displays.
|
|
22
|
+
* - a `null` selector, which matches nothing and is not worth a special case.
|
|
23
|
+
* @template T - The type of the items.
|
|
24
|
+
* @param previous - The query's previous result.
|
|
25
|
+
* @param selector - The query's selector.
|
|
26
|
+
* @param options - The query's options.
|
|
27
|
+
* @param changes - The items the write created, updated or removed.
|
|
28
|
+
* @returns The new result, or `null` when the query has to be re-executed.
|
|
29
|
+
*/
|
|
30
|
+
function incrementalQueryUpdate(previous, selector, options, changes) {
|
|
31
|
+
if (selector == null) return null;
|
|
32
|
+
const { sort, skip, limit, fields } = options || {};
|
|
33
|
+
if (skip != null) return null;
|
|
34
|
+
if (fields != null && sort != null && !sortKeysSurviveProjection(sort, fields)) return null;
|
|
35
|
+
if (limit != null && !windowStaysClosed(previous, selector, options, changes)) return null;
|
|
36
|
+
return mergeChangesetIntoResult(previous, selector, options, changes);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Whether a projection keeps every field a sort is keyed on.
|
|
40
|
+
*
|
|
41
|
+
* The previous result is the projected items, so this decides whether they still carry what the
|
|
42
|
+
* sort needs. Both projection modes are covered, because `project` treats an all-zero spec as an
|
|
43
|
+
* exclusion and anything else as an inclusion:
|
|
44
|
+
*
|
|
45
|
+
* - An inclusion keeps a key when the key itself is included, or an ancestor of it is (`{a: 1}`
|
|
46
|
+
* keeps `a.b`), and always keeps `id` unless the spec excludes it outright.
|
|
47
|
+
* - An exclusion keeps a key unless the key or an ancestor of it is excluded.
|
|
48
|
+
*
|
|
49
|
+
* Anything it cannot account for is a "no": re-executing is slower, not wrong.
|
|
50
|
+
* @template T - The type of the items.
|
|
51
|
+
* @param sort - The query's sort.
|
|
52
|
+
* @param fields - The query's projection.
|
|
53
|
+
* @returns `true` when every sort key survives the projection.
|
|
54
|
+
*/
|
|
55
|
+
function sortKeysSurviveProjection(sort, fields) {
|
|
56
|
+
const entries = Object.entries(fields);
|
|
57
|
+
if (entries.length === 0) return true;
|
|
58
|
+
const isExclusion = entries.every(([, value]) => value === 0);
|
|
59
|
+
const pathsFor = (key) => key.split(".").map((_, index, parts) => parts.slice(0, index + 1).join("."));
|
|
60
|
+
return Object.keys(sort).every((key) => {
|
|
61
|
+
const paths = pathsFor(key);
|
|
62
|
+
if (isExclusion) return paths.every((path) => fields[path] !== 0);
|
|
63
|
+
if (key === "id") return fields.id !== 0;
|
|
64
|
+
return paths.some((path) => fields[path] === 1);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Whether two items are in the given order under the given sort, deciding ties against the caller.
|
|
69
|
+
*
|
|
70
|
+
* Uses the sort itself rather than a comparator of its own: a rule about which side of a window an
|
|
71
|
+
* item falls on is only as good as its agreement with the ordering that drew the window. A tie
|
|
72
|
+
* comes back as `false`, because a tie is exactly the case where an item could belong on either
|
|
73
|
+
* side and the answer has to be taken from the store.
|
|
74
|
+
* @template T - The type of the items.
|
|
75
|
+
* @param item - The item whose position is in question.
|
|
76
|
+
* @param edge - The item at the edge of the window.
|
|
77
|
+
* @param sort - The query's sort.
|
|
78
|
+
* @returns `true` when `item` sorts strictly before `edge`.
|
|
79
|
+
*/
|
|
80
|
+
function sortsBefore(item, edge, sort) {
|
|
81
|
+
return require_sortItems.default([edge, item], sort)[0] === item;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Whether a change to a windowed query can be answered from the window alone.
|
|
85
|
+
*
|
|
86
|
+
* A window holds the first `limit` items in sort order, and nothing about what lies beyond it. An
|
|
87
|
+
* item leaving the window therefore has to be replaced by one the window has never seen, and that
|
|
88
|
+
* answer can only come from the store. An item arriving is a different matter: it takes its place
|
|
89
|
+
* and pushes the last one out, and where that one goes is not the window's problem.
|
|
90
|
+
*
|
|
91
|
+
* The one case where none of this applies is a window that was never full, because then the query
|
|
92
|
+
* already returns everything it matches and there is no "beyond".
|
|
93
|
+
* @template T - The type of the items.
|
|
94
|
+
* @param previous - The query's previous result.
|
|
95
|
+
* @param selector - The query's selector.
|
|
96
|
+
* @param options - The query's options.
|
|
97
|
+
* @param changes - The items the write created, updated or removed.
|
|
98
|
+
* @returns `true` when the new window follows from the old one and the change.
|
|
99
|
+
*/
|
|
100
|
+
function windowStaysClosed(previous, selector, options, changes) {
|
|
101
|
+
const { sort, limit, fields } = options || {};
|
|
102
|
+
if (limit == null || previous.length < limit) return true;
|
|
103
|
+
if (sort == null || fields != null) return false;
|
|
104
|
+
const edge = previous.at(-1);
|
|
105
|
+
const runnerUp = previous.at(-2);
|
|
106
|
+
if (runnerUp != null && !sortsBefore(runnerUp, edge, sort)) return false;
|
|
107
|
+
const inWindow = new Set(previous.map((item) => item.id));
|
|
108
|
+
if (changes.deletes.some((id) => inWindow.has(id))) return false;
|
|
109
|
+
return changes.upserts.every((item) => {
|
|
110
|
+
if (item === edge) return true;
|
|
111
|
+
const before = sortsBefore(item, edge, sort);
|
|
112
|
+
if (!inWindow.has(item.id)) {
|
|
113
|
+
if (!require_match.default(item, selector)) return true;
|
|
114
|
+
return before || sortsBefore(edge, item, sort);
|
|
115
|
+
}
|
|
116
|
+
if (!require_match.default(item, selector)) return false;
|
|
117
|
+
if (before) return true;
|
|
118
|
+
return item.id === edge.id && !sortsBefore(edge, item, sort);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Folds a change into a query's result, whatever the query's options.
|
|
123
|
+
*
|
|
124
|
+
* The unguarded version of `incrementalQueryUpdate`, for the places where the alternative is not a
|
|
125
|
+
* more accurate answer but a wrong one — layering a write that has not been confirmed yet on top of
|
|
126
|
+
* the last confirmed result, say. For a query returning everything it matches, this is exact. For a
|
|
127
|
+
* window onto a larger set it is the closest the window itself can get: an item that no longer
|
|
128
|
+
* belongs is dropped, one that does is placed, and the window is trimmed back to its length — but
|
|
129
|
+
* an item pulled in from beyond the window is not something the window knows about.
|
|
130
|
+
*
|
|
131
|
+
* What it never does is re-examine the items already in the result. They matched when the store
|
|
132
|
+
* produced them, they still match, and asking again is both wasteful and — for a projected result,
|
|
133
|
+
* whose items no longer carry the fields the selector names — wrong.
|
|
134
|
+
* @template T - The type of the items.
|
|
135
|
+
* @param previous - The query's previous result.
|
|
136
|
+
* @param selector - The query's selector.
|
|
137
|
+
* @param options - The query's options.
|
|
138
|
+
* @param changes - The items the write created, updated or removed.
|
|
139
|
+
* @returns The resulting items.
|
|
140
|
+
*/
|
|
141
|
+
function mergeChangesetIntoResult(previous, selector, options, changes) {
|
|
142
|
+
if (selector == null) return [];
|
|
143
|
+
const { sort, limit, fields } = options || {};
|
|
144
|
+
const byId = /* @__PURE__ */ new Map();
|
|
145
|
+
previous.forEach((item) => byId.set(item.id, item));
|
|
146
|
+
changes.deletes.forEach((id) => byId.delete(id));
|
|
147
|
+
changes.upserts.forEach((item) => {
|
|
148
|
+
if (require_match.default(item, selector)) byId.set(item.id, require_projectItems.default([item], fields)[0]);
|
|
149
|
+
else byId.delete(item.id);
|
|
150
|
+
});
|
|
151
|
+
const items = [...byId.values()];
|
|
152
|
+
const sorted = sort ? require_sortItems.default(items, sort) : items;
|
|
153
|
+
return limit == null ? sorted : sorted.slice(0, limit);
|
|
154
|
+
}
|
|
155
|
+
//#endregion
|
|
156
|
+
exports.default = incrementalQueryUpdate;
|
|
157
|
+
exports.mergeChangesetIntoResult = mergeChangesetIntoResult;
|
package/dist/index25.mjs
CHANGED
|
@@ -1,11 +1,156 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
import match from "./index19.mjs";
|
|
2
|
+
import projectItems from "./index23.mjs";
|
|
3
|
+
import sortItems from "./index24.mjs";
|
|
4
|
+
//#region src/utils/incrementalQueryUpdate.ts
|
|
5
|
+
/**
|
|
6
|
+
* Recomputes a query's result from its previous result and the change that was just written,
|
|
7
|
+
* without going back to the store.
|
|
8
|
+
*
|
|
9
|
+
* A store re-executing the query instead reads every item it holds (or every item an index points
|
|
10
|
+
* at) and filters, sorts and projects the lot — for a write that touched one row. This does the
|
|
11
|
+
* same job in the size of the write, which is what a query's result costs to keep up to date when
|
|
12
|
+
* the change that affects it is already in hand.
|
|
13
|
+
*
|
|
14
|
+
* Returns `null` when the previous result is not enough to answer, and the caller has to re-execute
|
|
15
|
+
* the query after all:
|
|
16
|
+
* - `limit` or `skip`: the result is a window onto a larger set, and an item leaving the window has
|
|
17
|
+
* to be replaced by one the previous result never contained.
|
|
18
|
+
* - `fields` together with a `sort` the projection does not keep: the previous items are
|
|
19
|
+
* projected, so a sort key the projection dropped is no longer there to sort by. A projection
|
|
20
|
+
* that keeps every sort key is fine, and is the common case — a list sorted by the same date it
|
|
21
|
+
* displays.
|
|
22
|
+
* - a `null` selector, which matches nothing and is not worth a special case.
|
|
23
|
+
* @template T - The type of the items.
|
|
24
|
+
* @param previous - The query's previous result.
|
|
25
|
+
* @param selector - The query's selector.
|
|
26
|
+
* @param options - The query's options.
|
|
27
|
+
* @param changes - The items the write created, updated or removed.
|
|
28
|
+
* @returns The new result, or `null` when the query has to be re-executed.
|
|
29
|
+
*/
|
|
30
|
+
function incrementalQueryUpdate(previous, selector, options, changes) {
|
|
31
|
+
if (selector == null) return null;
|
|
32
|
+
const { sort, skip, limit, fields } = options || {};
|
|
33
|
+
if (skip != null) return null;
|
|
34
|
+
if (fields != null && sort != null && !sortKeysSurviveProjection(sort, fields)) return null;
|
|
35
|
+
if (limit != null && !windowStaysClosed(previous, selector, options, changes)) return null;
|
|
36
|
+
return mergeChangesetIntoResult(previous, selector, options, changes);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Whether a projection keeps every field a sort is keyed on.
|
|
40
|
+
*
|
|
41
|
+
* The previous result is the projected items, so this decides whether they still carry what the
|
|
42
|
+
* sort needs. Both projection modes are covered, because `project` treats an all-zero spec as an
|
|
43
|
+
* exclusion and anything else as an inclusion:
|
|
44
|
+
*
|
|
45
|
+
* - An inclusion keeps a key when the key itself is included, or an ancestor of it is (`{a: 1}`
|
|
46
|
+
* keeps `a.b`), and always keeps `id` unless the spec excludes it outright.
|
|
47
|
+
* - An exclusion keeps a key unless the key or an ancestor of it is excluded.
|
|
48
|
+
*
|
|
49
|
+
* Anything it cannot account for is a "no": re-executing is slower, not wrong.
|
|
50
|
+
* @template T - The type of the items.
|
|
51
|
+
* @param sort - The query's sort.
|
|
52
|
+
* @param fields - The query's projection.
|
|
53
|
+
* @returns `true` when every sort key survives the projection.
|
|
54
|
+
*/
|
|
55
|
+
function sortKeysSurviveProjection(sort, fields) {
|
|
56
|
+
const entries = Object.entries(fields);
|
|
57
|
+
if (entries.length === 0) return true;
|
|
58
|
+
const isExclusion = entries.every(([, value]) => value === 0);
|
|
59
|
+
const pathsFor = (key) => key.split(".").map((_, index, parts) => parts.slice(0, index + 1).join("."));
|
|
60
|
+
return Object.keys(sort).every((key) => {
|
|
61
|
+
const paths = pathsFor(key);
|
|
62
|
+
if (isExclusion) return paths.every((path) => fields[path] !== 0);
|
|
63
|
+
if (key === "id") return fields.id !== 0;
|
|
64
|
+
return paths.some((path) => fields[path] === 1);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Whether two items are in the given order under the given sort, deciding ties against the caller.
|
|
69
|
+
*
|
|
70
|
+
* Uses the sort itself rather than a comparator of its own: a rule about which side of a window an
|
|
71
|
+
* item falls on is only as good as its agreement with the ordering that drew the window. A tie
|
|
72
|
+
* comes back as `false`, because a tie is exactly the case where an item could belong on either
|
|
73
|
+
* side and the answer has to be taken from the store.
|
|
74
|
+
* @template T - The type of the items.
|
|
75
|
+
* @param item - The item whose position is in question.
|
|
76
|
+
* @param edge - The item at the edge of the window.
|
|
77
|
+
* @param sort - The query's sort.
|
|
78
|
+
* @returns `true` when `item` sorts strictly before `edge`.
|
|
79
|
+
*/
|
|
80
|
+
function sortsBefore(item, edge, sort) {
|
|
81
|
+
return sortItems([edge, item], sort)[0] === item;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Whether a change to a windowed query can be answered from the window alone.
|
|
85
|
+
*
|
|
86
|
+
* A window holds the first `limit` items in sort order, and nothing about what lies beyond it. An
|
|
87
|
+
* item leaving the window therefore has to be replaced by one the window has never seen, and that
|
|
88
|
+
* answer can only come from the store. An item arriving is a different matter: it takes its place
|
|
89
|
+
* and pushes the last one out, and where that one goes is not the window's problem.
|
|
90
|
+
*
|
|
91
|
+
* The one case where none of this applies is a window that was never full, because then the query
|
|
92
|
+
* already returns everything it matches and there is no "beyond".
|
|
93
|
+
* @template T - The type of the items.
|
|
94
|
+
* @param previous - The query's previous result.
|
|
95
|
+
* @param selector - The query's selector.
|
|
96
|
+
* @param options - The query's options.
|
|
97
|
+
* @param changes - The items the write created, updated or removed.
|
|
98
|
+
* @returns `true` when the new window follows from the old one and the change.
|
|
99
|
+
*/
|
|
100
|
+
function windowStaysClosed(previous, selector, options, changes) {
|
|
101
|
+
const { sort, limit, fields } = options || {};
|
|
102
|
+
if (limit == null || previous.length < limit) return true;
|
|
103
|
+
if (sort == null || fields != null) return false;
|
|
104
|
+
const edge = previous.at(-1);
|
|
105
|
+
const runnerUp = previous.at(-2);
|
|
106
|
+
if (runnerUp != null && !sortsBefore(runnerUp, edge, sort)) return false;
|
|
107
|
+
const inWindow = new Set(previous.map((item) => item.id));
|
|
108
|
+
if (changes.deletes.some((id) => inWindow.has(id))) return false;
|
|
109
|
+
return changes.upserts.every((item) => {
|
|
110
|
+
if (item === edge) return true;
|
|
111
|
+
const before = sortsBefore(item, edge, sort);
|
|
112
|
+
if (!inWindow.has(item.id)) {
|
|
113
|
+
if (!match(item, selector)) return true;
|
|
114
|
+
return before || sortsBefore(edge, item, sort);
|
|
115
|
+
}
|
|
116
|
+
if (!match(item, selector)) return false;
|
|
117
|
+
if (before) return true;
|
|
118
|
+
return item.id === edge.id && !sortsBefore(edge, item, sort);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Folds a change into a query's result, whatever the query's options.
|
|
123
|
+
*
|
|
124
|
+
* The unguarded version of `incrementalQueryUpdate`, for the places where the alternative is not a
|
|
125
|
+
* more accurate answer but a wrong one — layering a write that has not been confirmed yet on top of
|
|
126
|
+
* the last confirmed result, say. For a query returning everything it matches, this is exact. For a
|
|
127
|
+
* window onto a larger set it is the closest the window itself can get: an item that no longer
|
|
128
|
+
* belongs is dropped, one that does is placed, and the window is trimmed back to its length — but
|
|
129
|
+
* an item pulled in from beyond the window is not something the window knows about.
|
|
130
|
+
*
|
|
131
|
+
* What it never does is re-examine the items already in the result. They matched when the store
|
|
132
|
+
* produced them, they still match, and asking again is both wasteful and — for a projected result,
|
|
133
|
+
* whose items no longer carry the fields the selector names — wrong.
|
|
134
|
+
* @template T - The type of the items.
|
|
135
|
+
* @param previous - The query's previous result.
|
|
136
|
+
* @param selector - The query's selector.
|
|
137
|
+
* @param options - The query's options.
|
|
138
|
+
* @param changes - The items the write created, updated or removed.
|
|
139
|
+
* @returns The resulting items.
|
|
140
|
+
*/
|
|
141
|
+
function mergeChangesetIntoResult(previous, selector, options, changes) {
|
|
142
|
+
if (selector == null) return [];
|
|
143
|
+
const { sort, limit, fields } = options || {};
|
|
144
|
+
const byId = /* @__PURE__ */ new Map();
|
|
145
|
+
previous.forEach((item) => byId.set(item.id, item));
|
|
146
|
+
changes.deletes.forEach((id) => byId.delete(id));
|
|
147
|
+
changes.upserts.forEach((item) => {
|
|
148
|
+
if (match(item, selector)) byId.set(item.id, projectItems([item], fields)[0]);
|
|
149
|
+
else byId.delete(item.id);
|
|
150
|
+
});
|
|
151
|
+
const items = [...byId.values()];
|
|
152
|
+
const sorted = sort ? sortItems(items, sort) : items;
|
|
153
|
+
return limit == null ? sorted : sorted.slice(0, limit);
|
|
154
|
+
}
|
|
155
|
+
//#endregion
|
|
156
|
+
export { incrementalQueryUpdate as default, mergeChangesetIntoResult };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//#region src/utils/queryId.ts
|
|
2
|
+
/**
|
|
3
|
+
* Returns true when `options` is effectively "empty" for the purpose of queryId generation.
|
|
4
|
+
* Treats `undefined`/`null` as empty, and only considers plain objects with zero own enumerable keys as empty.
|
|
5
|
+
* Arrays and non-object values are not considered empty.
|
|
6
|
+
* @param options - Query options to test.
|
|
7
|
+
* @returns `true` if `options` is `null`/`undefined` or a plain object with no own keys; otherwise `false`.
|
|
8
|
+
*/
|
|
9
|
+
function isEmptyOptions(options) {
|
|
10
|
+
if (options == null) return true;
|
|
11
|
+
if (typeof options !== "object") return false;
|
|
12
|
+
if (Array.isArray(options)) return false;
|
|
13
|
+
return Object.keys(options).length === 0;
|
|
14
|
+
}
|
|
15
|
+
var noOptions = {};
|
|
16
|
+
var cache = /* @__PURE__ */ new WeakMap();
|
|
17
|
+
/**
|
|
18
|
+
* Generates a unique identifier for a query based on its selector and options.
|
|
19
|
+
* @param selector - The selector object.
|
|
20
|
+
* @param options - The query options object (optional).
|
|
21
|
+
* @returns A unique identifier string for the query.
|
|
22
|
+
*/
|
|
23
|
+
function queryId(selector, options) {
|
|
24
|
+
if (!(selector != null && typeof selector === "object" && (options == null || typeof options === "object"))) {
|
|
25
|
+
const optionsId = isEmptyOptions(options) ? -1 : JSON.stringify(options);
|
|
26
|
+
return `${JSON.stringify(selector)}:${optionsId}`;
|
|
27
|
+
}
|
|
28
|
+
const optionsKey = options ?? noOptions;
|
|
29
|
+
const cachedForSelector = cache.get(selector);
|
|
30
|
+
const cached = cachedForSelector?.get(optionsKey);
|
|
31
|
+
if (cached != null) return cached;
|
|
32
|
+
const id = `${JSON.stringify(selector)}:${isEmptyOptions(options) ? -1 : JSON.stringify(options)}`;
|
|
33
|
+
if (cachedForSelector) cachedForSelector.set(optionsKey, id);
|
|
34
|
+
else cache.set(selector, new WeakMap([[optionsKey, id]]));
|
|
35
|
+
return id;
|
|
36
|
+
}
|
|
37
|
+
//#endregion
|
|
38
|
+
exports.default = queryId;
|