@signaldb/core 2.0.0-beta.19 → 2.0.0-beta.20

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.
@@ -15,8 +15,10 @@ const require_sortItems = require("./index24.cjs.js");
15
15
  * the query after all:
16
16
  * - `limit` or `skip`: the result is a window onto a larger set, and an item leaving the window has
17
17
  * to be replaced by one the previous result never contained.
18
- * - `fields` together with `sort`: the previous items are projected, so the field the sort is keyed
19
- * on may no longer be there to sort by.
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.
20
22
  * - a `null` selector, which matches nothing and is not worth a special case.
21
23
  * @template T - The type of the items.
22
24
  * @param previous - The query's previous result.
@@ -29,11 +31,40 @@ function incrementalQueryUpdate(previous, selector, options, changes) {
29
31
  if (selector == null) return null;
30
32
  const { sort, skip, limit, fields } = options || {};
31
33
  if (skip != null) return null;
32
- if (fields != null && sort != null) return null;
34
+ if (fields != null && sort != null && !sortKeysSurviveProjection(sort, fields)) return null;
33
35
  if (limit != null && !windowStaysClosed(previous, selector, options, changes)) return null;
34
36
  return mergeChangesetIntoResult(previous, selector, options, changes);
35
37
  }
36
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
+ /**
37
68
  * Whether two items are in the given order under the given sort, deciding ties against the caller.
38
69
  *
39
70
  * Uses the sort itself rather than a comparator of its own: a rule about which side of a window an
package/dist/index25.mjs CHANGED
@@ -15,8 +15,10 @@ import sortItems from "./index24.mjs";
15
15
  * the query after all:
16
16
  * - `limit` or `skip`: the result is a window onto a larger set, and an item leaving the window has
17
17
  * to be replaced by one the previous result never contained.
18
- * - `fields` together with `sort`: the previous items are projected, so the field the sort is keyed
19
- * on may no longer be there to sort by.
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.
20
22
  * - a `null` selector, which matches nothing and is not worth a special case.
21
23
  * @template T - The type of the items.
22
24
  * @param previous - The query's previous result.
@@ -29,11 +31,40 @@ function incrementalQueryUpdate(previous, selector, options, changes) {
29
31
  if (selector == null) return null;
30
32
  const { sort, skip, limit, fields } = options || {};
31
33
  if (skip != null) return null;
32
- if (fields != null && sort != null) return null;
34
+ if (fields != null && sort != null && !sortKeysSurviveProjection(sort, fields)) return null;
33
35
  if (limit != null && !windowStaysClosed(previous, selector, options, changes)) return null;
34
36
  return mergeChangesetIntoResult(previous, selector, options, changes);
35
37
  }
36
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
+ /**
37
68
  * Whether two items are in the given order under the given sort, deciding ties against the caller.
38
69
  *
39
70
  * Uses the sort itself rather than a comparator of its own: a rule about which side of a window an
@@ -26,8 +26,10 @@ export interface QueryChangeset<T extends BaseItem> {
26
26
  * the query after all:
27
27
  * - `limit` or `skip`: the result is a window onto a larger set, and an item leaving the window has
28
28
  * to be replaced by one the previous result never contained.
29
- * - `fields` together with `sort`: the previous items are projected, so the field the sort is keyed
30
- * on may no longer be there to sort by.
29
+ * - `fields` together with a `sort` the projection does not keep: the previous items are
30
+ * projected, so a sort key the projection dropped is no longer there to sort by. A projection
31
+ * that keeps every sort key is fine, and is the common case — a list sorted by the same date it
32
+ * displays.
31
33
  * - a `null` selector, which matches nothing and is not worth a special case.
32
34
  * @template T - The type of the items.
33
35
  * @param previous - The query's previous result.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signaldb/core",
3
- "version": "2.0.0-beta.19",
3
+ "version": "2.0.0-beta.20",
4
4
  "description": "SignalDB is a client-side database that provides a simple MongoDB-like interface to the data with first-class typescript support to achieve an optimistic UI. Data persistence can be achieved by using storage providers that store the data through a JSON interface to places such as localStorage.",
5
5
  "scripts": {
6
6
  "build": "rimraf dist && vite build",