foldkit-remote 0.21.0 → 0.22.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/README.md +977 -193
- package/dist/answerGaps.d.ts +16 -0
- package/dist/answerGaps.d.ts.map +1 -0
- package/dist/answerGaps.js +60 -0
- package/dist/answerGaps.js.map +1 -0
- package/dist/client-B055kQuP.d.ts +110 -0
- package/dist/client-B055kQuP.d.ts.map +1 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.js +142 -0
- package/dist/client.js.map +1 -0
- package/dist/coalesce-CLAr92Nk.d.ts +34 -0
- package/dist/coalesce-CLAr92Nk.d.ts.map +1 -0
- package/dist/coalesce.d.ts +2 -0
- package/dist/coalesce.js +131 -0
- package/dist/coalesce.js.map +1 -0
- package/dist/connection-BRyaJy89.d.ts +45 -0
- package/dist/connection-BRyaJy89.d.ts.map +1 -0
- package/dist/connection.d.ts +2 -0
- package/dist/connection.js +170 -0
- package/dist/connection.js.map +1 -0
- package/dist/data.d.ts +7 -0
- package/dist/data.d.ts.map +1 -0
- package/dist/data.js +21 -0
- package/dist/data.js.map +1 -0
- package/dist/diagnostics-DHhVYflV.d.ts +125 -0
- package/dist/diagnostics-DHhVYflV.d.ts.map +1 -0
- package/dist/diagnostics.d.ts +2 -0
- package/dist/diagnostics.js +60 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/entity-CgsG5Uel.d.ts +196 -0
- package/dist/entity-CgsG5Uel.d.ts.map +1 -0
- package/dist/entity-DuDxaa3n.js +279 -0
- package/dist/entity-DuDxaa3n.js.map +1 -0
- package/dist/entity.d.ts +2 -0
- package/dist/entity.js +1 -0
- package/dist/held.d.ts +19 -0
- package/dist/held.d.ts.map +1 -0
- package/dist/held.js +70 -0
- package/dist/held.js.map +1 -0
- package/dist/impact.d.ts +79 -0
- package/dist/impact.d.ts.map +1 -0
- package/dist/impact.js +166 -0
- package/dist/impact.js.map +1 -0
- package/dist/index.d.ts +1542 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2791 -0
- package/dist/index.js.map +1 -0
- package/dist/inspect-DtRQGCRP.d.ts +134 -0
- package/dist/inspect-DtRQGCRP.d.ts.map +1 -0
- package/dist/inspect.d.ts +2 -0
- package/dist/inspect.js +33 -0
- package/dist/inspect.js.map +1 -0
- package/dist/json-DowLl1eB.d.ts +52 -0
- package/dist/json-DowLl1eB.d.ts.map +1 -0
- package/dist/json.d.ts +2 -0
- package/dist/json.js +123 -0
- package/dist/json.js.map +1 -0
- package/dist/law.d.ts +56 -0
- package/dist/law.d.ts.map +1 -0
- package/dist/law.js +201 -0
- package/dist/law.js.map +1 -0
- package/dist/live-BpTzlCnY.d.ts +79 -0
- package/dist/live-BpTzlCnY.d.ts.map +1 -0
- package/dist/live.d.ts +2 -0
- package/dist/live.js +126 -0
- package/dist/live.js.map +1 -0
- package/dist/matching-DF5F9Us8.d.ts +100 -0
- package/dist/matching-DF5F9Us8.d.ts.map +1 -0
- package/dist/matching.d.ts +2 -0
- package/dist/matching.js +172 -0
- package/dist/matching.js.map +1 -0
- package/dist/model-B6zk1NMB.d.ts +710 -0
- package/dist/model-B6zk1NMB.d.ts.map +1 -0
- package/dist/model.d.ts +2 -0
- package/dist/model.js +1308 -0
- package/dist/model.js.map +1 -0
- package/dist/mutation.d.ts +2 -0
- package/dist/mutation.js +612 -0
- package/dist/mutation.js.map +1 -0
- package/dist/optimistic-CECsfNGY.d.ts +523 -0
- package/dist/optimistic-CECsfNGY.d.ts.map +1 -0
- package/dist/optimistic.d.ts +2 -0
- package/dist/optimistic.js +336 -0
- package/dist/optimistic.js.map +1 -0
- package/dist/outbox-DNuc4kZp.d.ts +259 -0
- package/dist/outbox-DNuc4kZp.d.ts.map +1 -0
- package/dist/outbox.d.ts +2 -0
- package/dist/outbox.js +326 -0
- package/dist/outbox.js.map +1 -0
- package/dist/patchValues.d.ts +10 -0
- package/dist/patchValues.d.ts.map +1 -0
- package/dist/patchValues.js +57 -0
- package/dist/patchValues.js.map +1 -0
- package/dist/persistence-CCMnvg7-.d.ts +114 -0
- package/dist/persistence-CCMnvg7-.d.ts.map +1 -0
- package/dist/persistence.d.ts +2 -0
- package/dist/persistence.js +205 -0
- package/dist/persistence.js.map +1 -0
- package/dist/placement-jZdsIpmj.d.ts +50 -0
- package/dist/placement-jZdsIpmj.d.ts.map +1 -0
- package/dist/placement.d.ts +2 -0
- package/dist/placement.js +116 -0
- package/dist/placement.js.map +1 -0
- package/dist/plan-D9cftxC_.d.ts +35 -0
- package/dist/plan-D9cftxC_.d.ts.map +1 -0
- package/dist/plan.d.ts +2 -0
- package/dist/plan.js +100 -0
- package/dist/plan.js.map +1 -0
- package/dist/policy-DsWHmIAq.d.ts +42 -0
- package/dist/policy-DsWHmIAq.d.ts.map +1 -0
- package/dist/policy.d.ts +2 -0
- package/dist/policy.js +32 -0
- package/dist/policy.js.map +1 -0
- package/dist/{port.d.mts → port.d.ts} +3 -2
- package/dist/port.d.ts.map +1 -0
- package/dist/{port.mjs → port.js} +3 -2
- package/dist/port.js.map +1 -0
- package/dist/query-BuIU93xA.d.ts +141 -0
- package/dist/query-BuIU93xA.d.ts.map +1 -0
- package/dist/query.d.ts +2 -0
- package/dist/query.js +0 -0
- package/dist/query.js.map +1 -0
- package/dist/record.d.ts +11 -0
- package/dist/record.d.ts.map +1 -0
- package/dist/record.js +21 -0
- package/dist/record.js.map +1 -0
- package/dist/relation-BLBGOZom.d.ts +59 -0
- package/dist/relation-BLBGOZom.d.ts.map +1 -0
- package/dist/relation.d.ts +2 -0
- package/dist/relation.js +93 -0
- package/dist/relation.js.map +1 -0
- package/dist/remoteData-mK1suYi1.d.ts +148 -0
- package/dist/remoteData-mK1suYi1.d.ts.map +1 -0
- package/dist/remoteData.d.ts +2 -0
- package/dist/remoteData.js +179 -0
- package/dist/remoteData.js.map +1 -0
- package/dist/reporting.d.ts +12 -0
- package/dist/reporting.d.ts.map +1 -0
- package/dist/reporting.js +45 -0
- package/dist/reporting.js.map +1 -0
- package/dist/requirement-D57S2LpH.d.ts +64 -0
- package/dist/requirement-D57S2LpH.d.ts.map +1 -0
- package/dist/requirement.d.ts +2 -0
- package/dist/requirement.js +114 -0
- package/dist/requirement.js.map +1 -0
- package/dist/resume-Ct5R6yli.d.ts +92 -0
- package/dist/resume-Ct5R6yli.d.ts.map +1 -0
- package/dist/resume.d.ts +2 -0
- package/dist/resume.js +222 -0
- package/dist/resume.js.map +1 -0
- package/dist/retain-BfNTvBRb.d.ts +48 -0
- package/dist/retain-BfNTvBRb.d.ts.map +1 -0
- package/dist/retain.d.ts +2 -0
- package/dist/retain.js +107 -0
- package/dist/retain.js.map +1 -0
- package/dist/selection.d.ts +2 -0
- package/dist/selection.js +1 -0
- package/dist/shown.d.ts +20 -0
- package/dist/shown.d.ts.map +1 -0
- package/dist/shown.js +40 -0
- package/dist/shown.js.map +1 -0
- package/dist/sse-C7LhdbiQ.d.ts +17 -0
- package/dist/sse-C7LhdbiQ.d.ts.map +1 -0
- package/dist/sse.d.ts +2 -0
- package/dist/sse.js +127 -0
- package/dist/sse.js.map +1 -0
- package/dist/store-ah2qjcJW.d.ts +100 -0
- package/dist/store-ah2qjcJW.d.ts.map +1 -0
- package/dist/store.d.ts +2 -0
- package/dist/store.js +218 -0
- package/dist/store.js.map +1 -0
- package/dist/testing.d.ts +133 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +0 -0
- package/dist/testing.js.map +1 -0
- package/dist/why-kgp_Wr3z.d.ts +483 -0
- package/dist/why-kgp_Wr3z.d.ts.map +1 -0
- package/dist/why.d.ts +2 -0
- package/dist/why.js +293 -0
- package/dist/why.js.map +1 -0
- package/dist/whyReasons.d.ts +42 -0
- package/dist/whyReasons.d.ts.map +1 -0
- package/dist/whyReasons.js +211 -0
- package/dist/whyReasons.js.map +1 -0
- package/dist/wire-DthPcTM6.d.ts +787 -0
- package/dist/wire-DthPcTM6.d.ts.map +1 -0
- package/dist/wire.d.ts +2 -0
- package/dist/wire.js +329 -0
- package/dist/wire.js.map +1 -0
- package/package.json +18 -13
- package/dist/index-YyucgTYm.d.mts +0 -4115
- package/dist/index-YyucgTYm.d.mts.map +0 -1
- package/dist/index.d.mts +0 -2
- package/dist/index.mjs +0 -1
- package/dist/port.d.mts.map +0 -1
- package/dist/port.mjs.map +0 -1
- package/dist/src-lVP_7bf4.mjs +0 -6382
- package/dist/src-lVP_7bf4.mjs.map +0 -1
package/dist/impact.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { sameData } from "./data.js";
|
|
2
|
+
import { entityKey } from "./store.js";
|
|
3
|
+
import { edge, hasNext, hasPrevious, isGapped } from "./connection.js";
|
|
4
|
+
import { RowPlace, placeIn, withEdge, withoutEdge } from "./placement.js";
|
|
5
|
+
import { belongsEncoded } from "./matching.js";
|
|
6
|
+
import { Data, Match, Option, Schema } from "effect";
|
|
7
|
+
import { Query, dependenciesOf } from "foldkit-entity";
|
|
8
|
+
import { defineTaggedUnion } from "foldkit/schema";
|
|
9
|
+
// src/impact.ts
|
|
10
|
+
/**
|
|
11
|
+
* What a write did to the lists the client holds: given one change and one
|
|
12
|
+
* loaded connection, whether the connection is still right as it stands.
|
|
13
|
+
*
|
|
14
|
+
* Pure, and asked once per connection a write could touch. The answer is never
|
|
15
|
+
* a guess: a connection kept must be provably unchanged in which rows it holds
|
|
16
|
+
* and in what order, a connection placed is the client's exact deduction of
|
|
17
|
+
* what the server now lists, and everything else is invalidated, with the
|
|
18
|
+
* reason, for the read entry to fetch again.
|
|
19
|
+
*
|
|
20
|
+
* Membership is judged by the query's body, as a live insert is
|
|
21
|
+
* (`belongsEncoded`), and position by its declared order (`placeIn`), when
|
|
22
|
+
* the order is placeable. A body that names no order leaves it to the server,
|
|
23
|
+
* by fields the client cannot see, so any change to a row it holds
|
|
24
|
+
* invalidates it.
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* One row a write changed: the fields it wrote, and whether the client held
|
|
28
|
+
* no values of the row before (`created`), or its deletion.
|
|
29
|
+
*/
|
|
30
|
+
const EntityChange = defineTaggedUnion({
|
|
31
|
+
Changed: {
|
|
32
|
+
entity: Schema.String,
|
|
33
|
+
id: Schema.String,
|
|
34
|
+
fields: Schema.Array(Schema.String),
|
|
35
|
+
created: Schema.Boolean
|
|
36
|
+
},
|
|
37
|
+
Deleted: {
|
|
38
|
+
entity: Schema.String,
|
|
39
|
+
id: Schema.String
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
/**
|
|
43
|
+
* What a mutation's answer changed, against `before`, the store as the server
|
|
44
|
+
* last said it: each patch's fields whose value is new, and each deletion. An
|
|
45
|
+
* answer returns more than it changed (a whole row, say), and counting an
|
|
46
|
+
* unchanged ordering field would refetch every list on every save.
|
|
47
|
+
*/
|
|
48
|
+
const changesOf = (answer, before) => [...answer.entities.flatMap((patch) => {
|
|
49
|
+
const held = before[entityKey(patch.entity, patch.id)]?.values;
|
|
50
|
+
const fields = Object.keys(patch.values).filter((field) => held === void 0 || !Object.hasOwn(held, field) || !sameData(held[field], patch.values[field]));
|
|
51
|
+
return fields.length === 0 ? [] : [EntityChange.Changed({
|
|
52
|
+
entity: patch.entity,
|
|
53
|
+
id: patch.id,
|
|
54
|
+
fields,
|
|
55
|
+
created: held === void 0
|
|
56
|
+
})];
|
|
57
|
+
}), ...(answer.deleted ?? []).map((gone) => EntityChange.Deleted({
|
|
58
|
+
entity: gone.entity,
|
|
59
|
+
id: gone.id
|
|
60
|
+
}))];
|
|
61
|
+
const Impact = Data.taggedEnum();
|
|
62
|
+
const kept = Impact.Kept();
|
|
63
|
+
const reachedBy = /* @__PURE__ */ new WeakMap();
|
|
64
|
+
const reachedOf = (body) => {
|
|
65
|
+
const known = reachedBy.get(body);
|
|
66
|
+
if (known !== void 0) return known;
|
|
67
|
+
const own = /* @__PURE__ */ new Set();
|
|
68
|
+
const others = /* @__PURE__ */ new Map();
|
|
69
|
+
const other = (entity, key) => {
|
|
70
|
+
const keys = others.get(entity) ?? /* @__PURE__ */ new Set();
|
|
71
|
+
keys.add(key);
|
|
72
|
+
others.set(entity, keys);
|
|
73
|
+
};
|
|
74
|
+
/** Relations followed from the judged row: the first is its own link, the rest other rows'. */
|
|
75
|
+
const fromRow = (via) => via.forEach((relation, index) => index === 0 ? own.add(relation.key) : other(relation.owner.name, relation.key));
|
|
76
|
+
for (const reach of Query.dependencies(body).reaches) Match.value(reach).pipe(Match.tagsExhaustive({
|
|
77
|
+
Path: (path) => {
|
|
78
|
+
fromRow(path.via);
|
|
79
|
+
other(path.field.owner.name, path.field.key);
|
|
80
|
+
},
|
|
81
|
+
Exists: (exists) => {
|
|
82
|
+
fromRow(exists.via);
|
|
83
|
+
other(exists.relation.owner.name, exists.relation.key);
|
|
84
|
+
Option.map(exists.where, (where) => dependenciesOf(where).fields.forEach((field) => other(field.entity, field.key)));
|
|
85
|
+
},
|
|
86
|
+
ViewerPath: (path) => {
|
|
87
|
+
for (const relation of path.via) other(relation.owner.name, relation.key);
|
|
88
|
+
other(path.field.owner.name, path.field.key);
|
|
89
|
+
}
|
|
90
|
+
}));
|
|
91
|
+
const reached = {
|
|
92
|
+
own,
|
|
93
|
+
others
|
|
94
|
+
};
|
|
95
|
+
reachedBy.set(body, reached);
|
|
96
|
+
return reached;
|
|
97
|
+
};
|
|
98
|
+
/** Whether two versions of a list hold the same rows in the same order, segment by segment. */
|
|
99
|
+
const sameOrder = (left, right) => left.segments.length === right.segments.length && left.segments.every((segment, index) => {
|
|
100
|
+
const other = right.segments[index].edges;
|
|
101
|
+
return segment.edges.length === other.length && segment.edges.every((edge, position) => edge.key === other[position].key);
|
|
102
|
+
});
|
|
103
|
+
/**
|
|
104
|
+
* What `change` does to `connection`, judged against `store`, the store after
|
|
105
|
+
* the write.
|
|
106
|
+
*/
|
|
107
|
+
const impactOn = (connection, store, change, operators, context) => {
|
|
108
|
+
const { descriptor } = connection;
|
|
109
|
+
const body = descriptor.body;
|
|
110
|
+
const over = body?.entity.name ?? descriptor.Result.entity;
|
|
111
|
+
const reached = body === void 0 ? void 0 : reachedOf(body);
|
|
112
|
+
const read = reached?.others.get(change.entity);
|
|
113
|
+
if (read !== void 0 && EntityChange.match(change, {
|
|
114
|
+
Deleted: () => true,
|
|
115
|
+
Changed: ({ fields }) => fields.some((field) => read.has(field))
|
|
116
|
+
})) return Impact.Invalidated({ reason: `${entityKey(change.entity, change.id)} changed what "${descriptor.name}" reads through another row` });
|
|
117
|
+
if (over !== change.entity) return kept;
|
|
118
|
+
return EntityChange.match(change, {
|
|
119
|
+
Deleted: () => kept,
|
|
120
|
+
Changed: ({ fields, created }) => {
|
|
121
|
+
if (body === void 0) return Impact.Invalidated({ reason: `query "${descriptor.name}" declares no body, so what it reads is unknown` });
|
|
122
|
+
const key = entityKey(change.entity, change.id);
|
|
123
|
+
const held = connection.holds.has(key);
|
|
124
|
+
const ruled = fields.find((field) => connection.filtered.includes(field));
|
|
125
|
+
if (!held && ruled !== void 0) return Impact.Invalidated({ reason: `${key} changed "${ruled}", which the server's read rule for ${change.entity} filters on` });
|
|
126
|
+
const roles = Query.dependencies(body);
|
|
127
|
+
const touches = (keys) => keys.some((field) => fields.includes(field.key));
|
|
128
|
+
let order;
|
|
129
|
+
try {
|
|
130
|
+
order = Query.orderFor(body, connection.encoded);
|
|
131
|
+
} catch {
|
|
132
|
+
return Impact.Invalidated({ reason: `"${descriptor.name}" was read in an order its body does not offer` });
|
|
133
|
+
}
|
|
134
|
+
if (held && body.orderBy.length === 0) return Impact.Invalidated({ reason: `${key} changed, and "${descriptor.name}" is ordered by the server, by fields its body does not name` });
|
|
135
|
+
const pages = connection.connection;
|
|
136
|
+
const reorders = touches(dependenciesOf(...order).fields);
|
|
137
|
+
const refilters = touches(roles.predicate) || fields.some((field) => reached?.own.has(field) === true);
|
|
138
|
+
const unloaded = hasPrevious(pages) || hasNext(pages) || isGapped(pages);
|
|
139
|
+
if (!(held && reorders) && !refilters && !(!held && (created || reorders && unloaded))) return kept;
|
|
140
|
+
const belongs = held && !refilters ? "yes" : belongsEncoded(store, descriptor, connection.encoded, key, operators, context);
|
|
141
|
+
if (belongs === "unknown") return Impact.Invalidated({ reason: `${key} changed a field "${descriptor.name}" filters on, and the client cannot judge it` });
|
|
142
|
+
if (!held && belongs === "no") return kept;
|
|
143
|
+
if (held && belongs === "yes" && !reorders) return kept;
|
|
144
|
+
const inPages = pages.segments.some((segment) => segment.edges.some((edge) => edge.key === key));
|
|
145
|
+
if (held && !inPages) return Impact.Invalidated({ reason: `${key} is shown in "${descriptor.name}" by an overlay, which a placement does not move` });
|
|
146
|
+
if (belongs === "no") return Impact.Placed({ connection: withoutEdge(pages, key) });
|
|
147
|
+
/** The row where `place` says; the list as it was when that is where it already stood. */
|
|
148
|
+
const placedAt = (place) => {
|
|
149
|
+
const next = withEdge(withoutEdge(pages, key), edge({
|
|
150
|
+
entity: change.entity,
|
|
151
|
+
id: change.id
|
|
152
|
+
}), place);
|
|
153
|
+
return sameOrder(pages, next) ? kept : Impact.Placed({ connection: next });
|
|
154
|
+
};
|
|
155
|
+
return RowPlace.$match(placeIn(store, pages, body, connection.encoded, key, operators), {
|
|
156
|
+
Before: placedAt,
|
|
157
|
+
End: placedAt,
|
|
158
|
+
Outside: () => held ? Impact.Placed({ connection: withoutEdge(pages, key) }) : kept,
|
|
159
|
+
Unknown: ({ reason }) => Impact.Invalidated({ reason: `${key} ${held ? "moved in" : "joined"} "${descriptor.name}", and cannot be placed: ${reason}` })
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
};
|
|
164
|
+
export { EntityChange, Impact, changesOf, impactOn };
|
|
165
|
+
|
|
166
|
+
//# sourceMappingURL=impact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"impact.js","names":["Relational"],"sources":["../src/impact.ts"],"sourcesContent":["/**\n * What a write did to the lists the client holds: given one change and one\n * loaded connection, whether the connection is still right as it stands.\n *\n * Pure, and asked once per connection a write could touch. The answer is never\n * a guess: a connection kept must be provably unchanged in which rows it holds\n * and in what order, a connection placed is the client's exact deduction of\n * what the server now lists, and everything else is invalidated, with the\n * reason, for the read entry to fetch again.\n *\n * Membership is judged by the query's body, as a live insert is\n * (`belongsEncoded`), and position by its declared order (`placeIn`), when\n * the order is placeable. A body that names no order leaves it to the server,\n * by fields the client cannot see, so any change to a row it holds\n * invalidates it.\n */\nimport {\n Query as Relational,\n dependenciesOf,\n type AnyQuery,\n type ExprContext,\n type MemoryOperators,\n type OneRelation,\n type OrderTerm,\n} from 'foldkit-entity'\nimport { defineTaggedUnion } from 'foldkit/schema'\nimport { edge, hasNext, hasPrevious, isGapped, type Connection } from './connection.js'\nimport { RowPlace, placeIn, withEdge, withoutEdge } from './placement.js'\nimport { Data, Match, Option, Schema } from 'effect'\nimport { sameData } from './data.js'\nimport { belongsEncoded } from './matching.js'\nimport type { NormalizedPatch } from './mutation.js'\nimport type { ConnectionSpec, QueryDescriptor } from './query.js'\nimport { entityKey, type EntityKey, type EntityStore } from './store.js'\n\n/**\n * One row a write changed: the fields it wrote, and whether the client held\n * no values of the row before (`created`), or its deletion.\n */\nexport const EntityChange = defineTaggedUnion({\n Changed: {\n entity: Schema.String,\n id: Schema.String,\n fields: Schema.Array(Schema.String),\n created: Schema.Boolean,\n },\n Deleted: { entity: Schema.String, id: Schema.String },\n})\nexport type EntityChange = typeof EntityChange.Type\n\n/**\n * What a mutation's answer changed, against `before`, the store as the server\n * last said it: each patch's fields whose value is new, and each deletion. An\n * answer returns more than it changed (a whole row, say), and counting an\n * unchanged ordering field would refetch every list on every save.\n */\nexport const changesOf = (\n answer: {\n readonly entities: ReadonlyArray<NormalizedPatch>\n readonly deleted?: ReadonlyArray<{ readonly entity: string; readonly id: string }> | undefined\n },\n before: EntityStore,\n): ReadonlyArray<EntityChange> => [\n ...answer.entities.flatMap(patch => {\n const held = before[entityKey(patch.entity, patch.id)]?.values\n const fields = Object.keys(patch.values).filter(\n field =>\n held === undefined ||\n !Object.hasOwn(held, field) ||\n !sameData(held[field], patch.values[field]),\n )\n return fields.length === 0\n ? []\n : [\n EntityChange.Changed({\n entity: patch.entity,\n id: patch.id,\n fields,\n created: held === undefined,\n }),\n ]\n }),\n ...(answer.deleted ?? []).map(gone => EntityChange.Deleted({ entity: gone.entity, id: gone.id })),\n]\n\n/**\n * What a change does to a connection: nothing it shows, a change the client\n * places itself (`Placed`, the connection as it now is), or a change it\n * cannot place, for its read entry to fetch again.\n */\nexport type Impact = Data.TaggedEnum<{\n Kept: {}\n Placed: { readonly connection: Connection }\n Invalidated: { readonly reason: string }\n}>\nexport const Impact = Data.taggedEnum<Impact>()\n\n/** A loaded connection, as impact needs it. */\nexport interface HeldConnection {\n readonly descriptor: QueryDescriptor<string, unknown, unknown>\n /** The connection's input, encoded, as its identity carries it. */\n readonly encoded: Readonly<Record<string, unknown>>\n /** The rows it shows now, overlays included. */\n readonly holds: ReadonlySet<EntityKey>\n /** Its pages as the server sent them, which a placement edits. */\n readonly connection: Connection\n /** The members its Entity's server filters rows by under a rule the client does not run. */\n readonly filtered: ReadonlyArray<string>\n}\n\nconst kept = Impact.Kept()\n\n/**\n * What a body's paths, exists and paths from the viewer read beyond the\n * fields of the row judged: `own`, the links of that row a path starts from,\n * which a change re-judges for that row alone; `others`, by Entity name, the\n * members they read in other rows, a change to any of which may move any row\n * of the list.\n */\ninterface Reached {\n readonly own: ReadonlySet<string>\n readonly others: ReadonlyMap<string, ReadonlySet<string>>\n}\n\nconst reachedBy = new WeakMap<AnyQuery, Reached>()\n\nconst reachedOf = (body: AnyQuery): Reached => {\n const known = reachedBy.get(body)\n if (known !== undefined) return known\n const own = new Set<string>()\n const others = new Map<string, Set<string>>()\n const other = (entity: string, key: string) => {\n const keys = others.get(entity) ?? new Set<string>()\n keys.add(key)\n others.set(entity, keys)\n }\n /** Relations followed from the judged row: the first is its own link, the rest other rows'. */\n const fromRow = (via: ReadonlyArray<OneRelation>) =>\n via.forEach((relation, index) =>\n index === 0 ? own.add(relation.key) : other(relation.owner.name, relation.key),\n )\n for (const reach of Relational.dependencies(body).reaches)\n Match.value(reach).pipe(\n Match.tagsExhaustive({\n Path: path => {\n fromRow(path.via)\n other(path.field.owner.name, path.field.key)\n },\n Exists: exists => {\n fromRow(exists.via)\n other(exists.relation.owner.name, exists.relation.key)\n Option.map(exists.where, where =>\n dependenciesOf(where).fields.forEach(field => other(field.entity, field.key)),\n )\n },\n ViewerPath: path => {\n for (const relation of path.via) other(relation.owner.name, relation.key)\n other(path.field.owner.name, path.field.key)\n },\n }),\n )\n const reached = { own, others }\n reachedBy.set(body, reached)\n return reached\n}\n\n/** Whether two versions of a list hold the same rows in the same order, segment by segment. */\nconst sameOrder = (left: Connection, right: Connection): boolean =>\n left.segments.length === right.segments.length &&\n left.segments.every((segment, index) => {\n const other = right.segments[index]!.edges\n return (\n segment.edges.length === other.length &&\n segment.edges.every((edge, position) => edge.key === other[position]!.key)\n )\n })\n\n/**\n * What `change` does to `connection`, judged against `store`, the store after\n * the write.\n */\nexport const impactOn = (\n connection: HeldConnection,\n store: EntityStore,\n change: EntityChange,\n operators: MemoryOperators,\n /** What a body's `Expr.viewer` and `Expr.now` read; see `MatchingOptions.context`. */\n context?: ExprContext | undefined,\n): Impact => {\n const { descriptor } = connection\n const body = descriptor.body\n const over = body?.entity.name ?? (descriptor.Result as Partial<ConnectionSpec>).entity\n const reached = body === undefined ? undefined : reachedOf(body)\n // A row another reads through a path or an exists: any row of the list may have moved.\n const read = reached?.others.get(change.entity)\n if (\n read !== undefined &&\n EntityChange.match(change, {\n Deleted: () => true,\n Changed: ({ fields }) => fields.some(field => read.has(field)),\n })\n ) {\n return Impact.Invalidated({\n reason: `${entityKey(change.entity, change.id)} changed what \"${descriptor.name}\" reads through another row`,\n })\n }\n if (over !== change.entity) return kept\n return EntityChange.match(change, {\n // A deletion leaves every list through its tombstone; nothing to fetch.\n Deleted: () => kept,\n Changed: ({ fields, created }) => {\n if (body === undefined) {\n return Impact.Invalidated({\n reason: `query \"${descriptor.name}\" declares no body, so what it reads is unknown`,\n })\n }\n const key = entityKey(change.entity, change.id)\n const held = connection.holds.has(key)\n // The server lists only the rows its read rules admit, and the client\n // runs none of them: a row this list does not hold may have joined it\n // on a change to what those rules read, which only the server can say.\n const ruled = fields.find(field => connection.filtered.includes(field))\n if (!held && ruled !== undefined) {\n return Impact.Invalidated({\n reason: `${key} changed \"${ruled}\", which the server's read rule for ${change.entity} filters on`,\n })\n }\n const roles = Relational.dependencies(body)\n const touches = (keys: ReadonlyArray<{ readonly key: string }>) =>\n keys.some(field => fields.includes(field.key))\n // The order this list was read in: a chosen order is the field its own\n // input names, not every field it could have named.\n let order: ReadonlyArray<OrderTerm>\n try {\n order = Relational.orderFor(body, connection.encoded)\n } catch {\n return Impact.Invalidated({\n reason: `\"${descriptor.name}\" was read in an order its body does not offer`,\n })\n }\n\n if (held && body.orderBy.length === 0) {\n return Impact.Invalidated({\n reason: `${key} changed, and \"${descriptor.name}\" is ordered by the server, by fields its body does not name`,\n })\n }\n const pages = connection.connection\n const reorders = touches(dependenciesOf(...order).fields)\n const refilters =\n touches(roles.predicate) || fields.some(field => reached?.own.has(field) === true)\n // A row the list does not hold can join it: one new to the client,\n // whatever the list filters on, and one whose order moved it in from\n // rows the list has not loaded.\n const unloaded = hasPrevious(pages) || hasNext(pages) || isGapped(pages)\n const joins = !held && (created || (reorders && unloaded))\n if (!(held && reorders) && !refilters && !joins) return kept\n\n // A held row whose order alone changed still matches.\n const belongs =\n held && !refilters\n ? 'yes'\n : belongsEncoded(store, descriptor, connection.encoded, key, operators, context)\n if (belongs === 'unknown') {\n return Impact.Invalidated({\n reason: `${key} changed a field \"${descriptor.name}\" filters on, and the client cannot judge it`,\n })\n }\n // Not held and still not matching, or held and still matching where it was.\n if (!held && belongs === 'no') return kept\n if (held && belongs === 'yes' && !reorders) return kept\n\n // The list's rows, or their order, changed: placed where the client can.\n const inPages = pages.segments.some(segment => segment.edges.some(edge => edge.key === key))\n if (held && !inPages) {\n return Impact.Invalidated({\n reason: `${key} is shown in \"${descriptor.name}\" by an overlay, which a placement does not move`,\n })\n }\n // Leaving is exact whatever the order: membership is the body's.\n if (belongs === 'no') return Impact.Placed({ connection: withoutEdge(pages, key) })\n /** The row where `place` says; the list as it was when that is where it already stood. */\n const placedAt = (place: Extract<RowPlace, { readonly _tag: 'Before' | 'End' }>) => {\n const next = withEdge(\n withoutEdge(pages, key),\n edge({ entity: change.entity, id: change.id }),\n place,\n )\n return sameOrder(pages, next) ? kept : Impact.Placed({ connection: next })\n }\n return RowPlace.$match(placeIn(store, pages, body, connection.encoded, key, operators), {\n Before: placedAt,\n End: placedAt,\n // It belongs to rows not loaded: gone from these, if they showed it.\n Outside: () => (held ? Impact.Placed({ connection: withoutEdge(pages, key) }) : kept),\n Unknown: ({ reason }) =>\n Impact.Invalidated({\n reason: `${key} ${held ? 'moved in' : 'joined'} \"${descriptor.name}\", and cannot be placed: ${reason}`,\n }),\n })\n },\n })\n}\n"],"mappings":"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"}
|