foldkit-remote 0.20.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 +987 -198
- 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 +19 -14
- package/dist/index-BEvVAL76.d.mts +0 -4097
- package/dist/index-BEvVAL76.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-BDqn7Ocr.mjs +0 -6344
- package/dist/src-BDqn7Ocr.mjs.map +0 -1
package/dist/why.js
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { RemoteData, remoteErrorSchema } from "./remoteData.js";
|
|
2
|
+
import { Option, Schema } from "effect";
|
|
3
|
+
import { defineTaggedUnion } from "foldkit/schema";
|
|
4
|
+
// src/why.ts
|
|
5
|
+
/**
|
|
6
|
+
* Why a read shows what it shows: every fact the Model holds about what the
|
|
7
|
+
* read reads, by typed reference rather than by the store's marks (#248).
|
|
8
|
+
*/
|
|
9
|
+
const RowRef = Schema.Struct({
|
|
10
|
+
entity: Schema.String,
|
|
11
|
+
id: Schema.String
|
|
12
|
+
});
|
|
13
|
+
/** A list by its query's name and its connection identity (the query and its encoded input). */
|
|
14
|
+
const ListRef = Schema.Struct({
|
|
15
|
+
query: Schema.String,
|
|
16
|
+
identity: Schema.String
|
|
17
|
+
});
|
|
18
|
+
/** `Missing`: the store does not hold it. `Stale`: it holds it, marked out of date. */
|
|
19
|
+
const Held = Schema.Literals(["Missing", "Stale"]);
|
|
20
|
+
const Names = Schema.Array(Schema.String);
|
|
21
|
+
/** What shows something the server has not said: a write, an overlay, or a live event. */
|
|
22
|
+
const ShownBy = defineTaggedUnion({
|
|
23
|
+
/** `Data.mutate`'s request; the mutation is none once the ledger no longer holds its write. */
|
|
24
|
+
Write: {
|
|
25
|
+
requestId: Schema.String,
|
|
26
|
+
mutation: Schema.OptionFromNullOr(Schema.String)
|
|
27
|
+
},
|
|
28
|
+
/** `Data.overlay`'s id. */
|
|
29
|
+
Overlay: { id: Schema.String },
|
|
30
|
+
/** A live event's insert or remove, by its cursor. */
|
|
31
|
+
Live: { cursor: Schema.String }
|
|
32
|
+
});
|
|
33
|
+
/**
|
|
34
|
+
* One `where` predicate of a list's body, judged over the row as the client
|
|
35
|
+
* placed it. `Null` is SQL's unknown, which keeps a row out; `Unknown` rests
|
|
36
|
+
* on what the store lacks, and `reason` says what.
|
|
37
|
+
*/
|
|
38
|
+
const WhyTerm = Schema.Struct({
|
|
39
|
+
term: Schema.String,
|
|
40
|
+
truth: Schema.Literals([
|
|
41
|
+
"True",
|
|
42
|
+
"False",
|
|
43
|
+
"Null",
|
|
44
|
+
"Unknown"
|
|
45
|
+
]),
|
|
46
|
+
reason: Schema.OptionFromNullOr(Schema.String)
|
|
47
|
+
});
|
|
48
|
+
/** One fact about a read. `ReadWhy.because` lists every one the Model holds. */
|
|
49
|
+
const Why = defineTaggedUnion({
|
|
50
|
+
/** `Initial`, and no active Surface asks for all of the read. */
|
|
51
|
+
NotObserved: {},
|
|
52
|
+
/** `Initial`, and these active Surfaces read it: Remote's Subscriptions are not running. */
|
|
53
|
+
NotFetching: { surfaces: Names },
|
|
54
|
+
/** `Initial`, and no active record was given to tell which mistake it is. */
|
|
55
|
+
NoActiveRecord: {},
|
|
56
|
+
/** The list's own page is missing or stale; `inFlight` while its query is out. */
|
|
57
|
+
List: {
|
|
58
|
+
list: ListRef,
|
|
59
|
+
held: Held,
|
|
60
|
+
inFlight: Schema.Boolean
|
|
61
|
+
},
|
|
62
|
+
/** Fields of one row are missing or stale; `via` is the relation path from the read's row. */
|
|
63
|
+
Fields: {
|
|
64
|
+
row: RowRef,
|
|
65
|
+
via: Names,
|
|
66
|
+
fields: Names,
|
|
67
|
+
held: Held,
|
|
68
|
+
inFlight: Schema.Boolean
|
|
69
|
+
},
|
|
70
|
+
/** The list's query failed, and nothing has settled it. Nothing retries it on its own. */
|
|
71
|
+
ListFailed: {
|
|
72
|
+
list: ListRef,
|
|
73
|
+
error: remoteErrorSchema
|
|
74
|
+
},
|
|
75
|
+
/** Reading these fields failed, and nothing has settled it. Nothing retries it on its own. */
|
|
76
|
+
FieldsFailed: {
|
|
77
|
+
row: RowRef,
|
|
78
|
+
via: Names,
|
|
79
|
+
fields: Names,
|
|
80
|
+
error: remoteErrorSchema
|
|
81
|
+
},
|
|
82
|
+
/** The server settled these fields without a value for this principal. */
|
|
83
|
+
Unavailable: {
|
|
84
|
+
row: RowRef,
|
|
85
|
+
via: Names,
|
|
86
|
+
fields: Names
|
|
87
|
+
},
|
|
88
|
+
/** What the store holds does not decode against the Selection. */
|
|
89
|
+
Undecodable: { message: Schema.String },
|
|
90
|
+
/** The row is shown only by an overlay, which lacks fields nothing will fetch. */
|
|
91
|
+
OverlayOnly: { message: Schema.String },
|
|
92
|
+
/** The row is gone: the server said so (`by` none), or a pending write deletes it. */
|
|
93
|
+
Deleted: {
|
|
94
|
+
row: RowRef,
|
|
95
|
+
by: Schema.OptionFromNullOr(ShownBy)
|
|
96
|
+
},
|
|
97
|
+
/** A live stream of a Surface reading it broke; the live entry resubscribes on its own. */
|
|
98
|
+
LiveReconnecting: {
|
|
99
|
+
surface: Schema.String,
|
|
100
|
+
attempt: Schema.Number,
|
|
101
|
+
error: Schema.OptionFromNullOr(remoteErrorSchema)
|
|
102
|
+
},
|
|
103
|
+
/** The server refused a Surface's live stream; nothing asks again until what it reads changes. */
|
|
104
|
+
LiveRefused: {
|
|
105
|
+
surface: Schema.String,
|
|
106
|
+
error: remoteErrorSchema
|
|
107
|
+
},
|
|
108
|
+
/** A pending layer writes these fields of a row the read shows. */
|
|
109
|
+
Pending: {
|
|
110
|
+
row: RowRef,
|
|
111
|
+
fields: Names,
|
|
112
|
+
by: ShownBy
|
|
113
|
+
},
|
|
114
|
+
/** The list shows a row its server pages do not hold. */
|
|
115
|
+
Joined: {
|
|
116
|
+
list: ListRef,
|
|
117
|
+
row: RowRef,
|
|
118
|
+
by: Schema.OptionFromNullOr(ShownBy),
|
|
119
|
+
terms: Schema.Array(WhyTerm)
|
|
120
|
+
},
|
|
121
|
+
/** The list's server pages hold a row it does not show. */
|
|
122
|
+
Left: {
|
|
123
|
+
list: ListRef,
|
|
124
|
+
row: RowRef,
|
|
125
|
+
by: Schema.OptionFromNullOr(ShownBy),
|
|
126
|
+
terms: Schema.Array(WhyTerm)
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
const State = Schema.Literals([
|
|
130
|
+
"Initial",
|
|
131
|
+
"Loading",
|
|
132
|
+
"Ready",
|
|
133
|
+
"Refreshing",
|
|
134
|
+
"Failed",
|
|
135
|
+
"NotFound"
|
|
136
|
+
]);
|
|
137
|
+
/**
|
|
138
|
+
* Why a read shows what it shows. `because` is every fact the Model holds
|
|
139
|
+
* about what it reads, the reason that decides `state` first; `message` is
|
|
140
|
+
* that reason in words. `surfaces` are the active Surfaces reading all of it,
|
|
141
|
+
* when the active record was given. Encode it with this schema for JSON.
|
|
142
|
+
*/
|
|
143
|
+
const ReadWhy = Schema.Struct({
|
|
144
|
+
state: State,
|
|
145
|
+
because: Schema.Array(Why),
|
|
146
|
+
message: Schema.String,
|
|
147
|
+
surfaces: Schema.OptionFromNullOr(Names)
|
|
148
|
+
});
|
|
149
|
+
/** Remote's own failures say what to do; a server's error says itself. */
|
|
150
|
+
const failedReadWords = (error) => {
|
|
151
|
+
switch (error._tag) {
|
|
152
|
+
case "DecodeError": return `What the server sent does not decode against the Selection: ${error.message}`;
|
|
153
|
+
case "Unavailable": return `${error.message} Select without it, or Data.refresh asks again.`;
|
|
154
|
+
case "Overlaid": return `${error.message} Overlay every field the Selection reads, or select fewer.`;
|
|
155
|
+
default: return `Its request failed: ${error.message}. Nothing retries a failed read on its own; Data.refresh asks again.`;
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
const rowWords = (row, via) => `${row.entity} ${row.id}${via.length === 0 ? "" : ` (through ${via.join(".")})`}`;
|
|
159
|
+
const sourceWords = (source) => ShownBy.match(source, {
|
|
160
|
+
Write: ({ requestId, mutation }) => Option.match(mutation, {
|
|
161
|
+
onNone: () => `request ${requestId}`,
|
|
162
|
+
onSome: (name) => `${name} (request ${requestId})`
|
|
163
|
+
}),
|
|
164
|
+
Overlay: ({ id }) => `overlay "${id}"`,
|
|
165
|
+
Live: () => "a live event"
|
|
166
|
+
});
|
|
167
|
+
const termWords = (terms) => terms.length === 0 ? "" : ` Its terms: ${terms.map((term) => `${term.term} is ${term.truth}`).join(", ")}.`;
|
|
168
|
+
const heldWords = {
|
|
169
|
+
Missing: {
|
|
170
|
+
true: (what) => `${what} is being fetched.`,
|
|
171
|
+
false: (what) => `${what} is not held, and nothing is fetching it.`
|
|
172
|
+
},
|
|
173
|
+
Stale: {
|
|
174
|
+
true: (what) => `${what} is shown while a newer value is fetched.`,
|
|
175
|
+
false: (what) => `${what} is out of date; the read entry fetches it again while a Surface reads it.`
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
/** One reason in words, with what restarts it. */
|
|
179
|
+
const whyWords = (why) => Why.match(why, {
|
|
180
|
+
NotObserved: () => "No active Surface reads it, so nothing fetches it. Include it in a Surface that is active for this Model, in the record given to Data.subscriptions.",
|
|
181
|
+
NotFetching: ({ surfaces }) => `${surfaces.join(", ")} ${surfaces.length === 1 ? "reads it and is" : "read it and are"} active, yet nothing is fetching it. Right after a Message, that is the moment before the read entry asks; if it stays so, Remote's Subscriptions are not installed: give the runtime Data.subscriptions (or Data.wiring) and a RemoteClient.`,
|
|
182
|
+
NoActiveRecord: () => "Nothing is fetching it. Pass the active record you give Data.subscriptions as `surfaces` to tell whether an active Surface reads it.",
|
|
183
|
+
List: ({ list, held, inFlight }) => heldWords[held][`${inFlight}`](`Its list "${list.query}"`),
|
|
184
|
+
Fields: ({ row, via, fields, held, inFlight }) => heldWords[held][`${inFlight}`](`${fields.join(", ")} of ${rowWords(row, via)}`),
|
|
185
|
+
ListFailed: ({ list, error }) => `Its list "${list.query}" failed. ${failedReadWords(error)}`,
|
|
186
|
+
FieldsFailed: ({ row, via, fields, error }) => `Reading ${fields.join(", ")} of ${rowWords(row, via)} failed. ${failedReadWords(error)}`,
|
|
187
|
+
Unavailable: ({ row, via, fields }) => `The server settled ${fields.join(", ")} of ${rowWords(row, via)} without a value for this principal. Select without them, or Data.refresh asks again.`,
|
|
188
|
+
Undecodable: ({ message }) => `What the store holds does not decode against the Selection: ${message}`,
|
|
189
|
+
OverlayOnly: ({ message }) => `${message} Overlay every field the Selection reads, or select fewer.`,
|
|
190
|
+
Deleted: ({ row, by }) => Option.match(by, {
|
|
191
|
+
onNone: () => `The server answered without ${rowWords(row, [])}: it does not exist, or this principal may not see it.`,
|
|
192
|
+
onSome: (source) => `${sourceWords(source)} deletes ${rowWords(row, [])}; its answer settles it, and its failure brings the row back.`
|
|
193
|
+
}),
|
|
194
|
+
LiveReconnecting: ({ surface, attempt }) => `${surface}'s live stream broke (attempt ${attempt}); the live entry resubscribes on its own, with backoff, and what changes meanwhile is fetched again.`,
|
|
195
|
+
LiveRefused: ({ surface, error }) => `The server refused ${surface}'s live stream: ${error.message}. Nothing asks again until what it reads changes.`,
|
|
196
|
+
Pending: ({ row, fields, by }) => `${fields.join(", ")} of ${rowWords(row, [])} show what ${sourceWords(by)} wrote, until it is answered.`,
|
|
197
|
+
Joined: ({ list, row, by, terms }) => `${rowWords(row, [])} shows in "${list.query}" before the server lists it${Option.match(by, {
|
|
198
|
+
onNone: () => "",
|
|
199
|
+
onSome: (source) => `, for ${sourceWords(source)}`
|
|
200
|
+
})}.${termWords(terms)}`,
|
|
201
|
+
Left: ({ list, row, by, terms }) => `${rowWords(row, [])} is left out of "${list.query}", which the server still lists it in${Option.match(by, {
|
|
202
|
+
onNone: () => "",
|
|
203
|
+
onSome: (source) => `, for ${sourceWords(source)}`
|
|
204
|
+
})}.${termWords(terms)}`
|
|
205
|
+
});
|
|
206
|
+
/** A list's own page: missing reads `Loading` while fetched, `Initial` otherwise. */
|
|
207
|
+
const listDecides = {
|
|
208
|
+
Missing: {
|
|
209
|
+
true: ["Loading"],
|
|
210
|
+
false: ["Initial"]
|
|
211
|
+
},
|
|
212
|
+
Stale: {
|
|
213
|
+
true: ["Refreshing"],
|
|
214
|
+
false: ["Refreshing"]
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
/**
|
|
218
|
+
* A row's fields: an entity read missing them reads `Loading` or `Initial`;
|
|
219
|
+
* a list leaves the row out and reads `Refreshing`.
|
|
220
|
+
*/
|
|
221
|
+
const fieldsDecide = {
|
|
222
|
+
Missing: {
|
|
223
|
+
true: ["Loading", "Refreshing"],
|
|
224
|
+
false: ["Initial", "Refreshing"]
|
|
225
|
+
},
|
|
226
|
+
Stale: {
|
|
227
|
+
true: ["Refreshing"],
|
|
228
|
+
false: ["Refreshing"]
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
/** The states each reason can decide: the one `message` speaks of is the first that decides `state`. */
|
|
232
|
+
const decides = (why) => Why.match(why, {
|
|
233
|
+
NotObserved: () => ["Initial"],
|
|
234
|
+
NotFetching: () => ["Initial"],
|
|
235
|
+
NoActiveRecord: () => ["Initial"],
|
|
236
|
+
List: ({ held, inFlight }) => listDecides[held][`${inFlight}`],
|
|
237
|
+
Fields: ({ held, inFlight }) => fieldsDecide[held][`${inFlight}`],
|
|
238
|
+
ListFailed: () => ["Failed"],
|
|
239
|
+
FieldsFailed: () => ["Failed"],
|
|
240
|
+
Unavailable: () => ["Failed"],
|
|
241
|
+
Undecodable: () => ["Failed"],
|
|
242
|
+
OverlayOnly: () => ["Failed"],
|
|
243
|
+
Deleted: () => ["NotFound"],
|
|
244
|
+
LiveReconnecting: () => [],
|
|
245
|
+
LiveRefused: () => [],
|
|
246
|
+
Pending: () => ["Ready", "Refreshing"],
|
|
247
|
+
Joined: () => ["Ready", "Refreshing"],
|
|
248
|
+
Left: () => ["Ready", "Refreshing"]
|
|
249
|
+
});
|
|
250
|
+
const stateWords = {
|
|
251
|
+
Initial: "Nothing is fetching it.",
|
|
252
|
+
Loading: "A request for it is in flight.",
|
|
253
|
+
Ready: "Everything it selects is here.",
|
|
254
|
+
Refreshing: "It is shown while a newer value is fetched.",
|
|
255
|
+
Failed: "It failed.",
|
|
256
|
+
NotFound: "The server answered without it: it does not exist, or this principal may not see it."
|
|
257
|
+
};
|
|
258
|
+
/** The explanation of a read in `state`, from its reasons. */
|
|
259
|
+
const readWhy = (state, because, surfaces) => {
|
|
260
|
+
const decisive = because.findIndex((why) => decides(why).includes(state));
|
|
261
|
+
return {
|
|
262
|
+
state,
|
|
263
|
+
because: decisive <= 0 ? because : [because[decisive], ...because.filter((_, at) => at !== decisive)],
|
|
264
|
+
message: decisive === -1 ? stateWords[state] : whyWords(because[decisive]),
|
|
265
|
+
surfaces
|
|
266
|
+
};
|
|
267
|
+
};
|
|
268
|
+
/** Whether a read shows a value. */
|
|
269
|
+
const shows = (data) => RemoteData.match(data, {
|
|
270
|
+
Initial: () => false,
|
|
271
|
+
Loading: () => false,
|
|
272
|
+
Ready: () => true,
|
|
273
|
+
Refreshing: () => true,
|
|
274
|
+
Failed: () => false,
|
|
275
|
+
NotFound: () => false
|
|
276
|
+
});
|
|
277
|
+
/** Whether a read that showed a value still does, or says why not: failed with it, or found it gone. */
|
|
278
|
+
const keeps = (data) => RemoteData.match(data, {
|
|
279
|
+
Initial: () => false,
|
|
280
|
+
Loading: () => false,
|
|
281
|
+
Ready: () => true,
|
|
282
|
+
Refreshing: () => true,
|
|
283
|
+
Failed: (_, previous) => Option.isSome(previous),
|
|
284
|
+
NotFound: () => true
|
|
285
|
+
});
|
|
286
|
+
/**
|
|
287
|
+
* #234's rule: a read that showed a value stopped showing it with no reason
|
|
288
|
+
* a reader can see (`Loading`, `Initial`, or `Failed` without the value).
|
|
289
|
+
*/
|
|
290
|
+
const regressed = (from, to) => shows(from) && !keeps(to);
|
|
291
|
+
export { ReadWhy, ShownBy, Why, WhyTerm, readWhy, regressed, whyWords };
|
|
292
|
+
|
|
293
|
+
//# sourceMappingURL=why.js.map
|
package/dist/why.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"why.js","names":[],"sources":["../src/why.ts"],"sourcesContent":["/**\n * Why a read shows what it shows: every fact the Model holds about what the\n * read reads, by typed reference rather than by the store's marks (#248).\n */\nimport { Option, Schema } from 'effect'\nimport { defineTaggedUnion } from 'foldkit/schema'\nimport { RemoteData, remoteErrorSchema, type RemoteError } from './remoteData.js'\n\nconst RowRef = Schema.Struct({ entity: Schema.String, id: Schema.String })\n/** A list by its query's name and its connection identity (the query and its encoded input). */\nconst ListRef = Schema.Struct({ query: Schema.String, identity: Schema.String })\nexport type ListRef = typeof ListRef.Type\n/** `Missing`: the store does not hold it. `Stale`: it holds it, marked out of date. */\nconst Held = Schema.Literals(['Missing', 'Stale'])\nconst Names = Schema.Array(Schema.String)\n\n/** What shows something the server has not said: a write, an overlay, or a live event. */\nexport const ShownBy = defineTaggedUnion({\n /** `Data.mutate`'s request; the mutation is none once the ledger no longer holds its write. */\n Write: { requestId: Schema.String, mutation: Schema.OptionFromNullOr(Schema.String) },\n /** `Data.overlay`'s id. */\n Overlay: { id: Schema.String },\n /** A live event's insert or remove, by its cursor. */\n Live: { cursor: Schema.String },\n})\nexport type ShownBy = typeof ShownBy.Type\n\n/**\n * One `where` predicate of a list's body, judged over the row as the client\n * placed it. `Null` is SQL's unknown, which keeps a row out; `Unknown` rests\n * on what the store lacks, and `reason` says what.\n */\nexport const WhyTerm = Schema.Struct({\n term: Schema.String,\n truth: Schema.Literals(['True', 'False', 'Null', 'Unknown']),\n reason: Schema.OptionFromNullOr(Schema.String),\n})\nexport type WhyTerm = typeof WhyTerm.Type\n\n/** One fact about a read. `ReadWhy.because` lists every one the Model holds. */\nexport const Why = defineTaggedUnion({\n /** `Initial`, and no active Surface asks for all of the read. */\n NotObserved: {},\n /** `Initial`, and these active Surfaces read it: Remote's Subscriptions are not running. */\n NotFetching: { surfaces: Names },\n /** `Initial`, and no active record was given to tell which mistake it is. */\n NoActiveRecord: {},\n /** The list's own page is missing or stale; `inFlight` while its query is out. */\n List: { list: ListRef, held: Held, inFlight: Schema.Boolean },\n /** Fields of one row are missing or stale; `via` is the relation path from the read's row. */\n Fields: { row: RowRef, via: Names, fields: Names, held: Held, inFlight: Schema.Boolean },\n /** The list's query failed, and nothing has settled it. Nothing retries it on its own. */\n ListFailed: { list: ListRef, error: remoteErrorSchema },\n /** Reading these fields failed, and nothing has settled it. Nothing retries it on its own. */\n FieldsFailed: { row: RowRef, via: Names, fields: Names, error: remoteErrorSchema },\n /** The server settled these fields without a value for this principal. */\n Unavailable: { row: RowRef, via: Names, fields: Names },\n /** What the store holds does not decode against the Selection. */\n Undecodable: { message: Schema.String },\n /** The row is shown only by an overlay, which lacks fields nothing will fetch. */\n OverlayOnly: { message: Schema.String },\n /** The row is gone: the server said so (`by` none), or a pending write deletes it. */\n Deleted: { row: RowRef, by: Schema.OptionFromNullOr(ShownBy) },\n /** A live stream of a Surface reading it broke; the live entry resubscribes on its own. */\n LiveReconnecting: {\n surface: Schema.String,\n attempt: Schema.Number,\n error: Schema.OptionFromNullOr(remoteErrorSchema),\n },\n /** The server refused a Surface's live stream; nothing asks again until what it reads changes. */\n LiveRefused: { surface: Schema.String, error: remoteErrorSchema },\n /** A pending layer writes these fields of a row the read shows. */\n Pending: { row: RowRef, fields: Names, by: ShownBy },\n /** The list shows a row its server pages do not hold. */\n Joined: {\n list: ListRef,\n row: RowRef,\n by: Schema.OptionFromNullOr(ShownBy),\n terms: Schema.Array(WhyTerm),\n },\n /** The list's server pages hold a row it does not show. */\n Left: {\n list: ListRef,\n row: RowRef,\n by: Schema.OptionFromNullOr(ShownBy),\n terms: Schema.Array(WhyTerm),\n },\n})\nexport type Why = typeof Why.Type\n\nconst State = Schema.Literals(['Initial', 'Loading', 'Ready', 'Refreshing', 'Failed', 'NotFound'])\ntype State = typeof State.Type\n\n/**\n * Why a read shows what it shows. `because` is every fact the Model holds\n * about what it reads, the reason that decides `state` first; `message` is\n * that reason in words. `surfaces` are the active Surfaces reading all of it,\n * when the active record was given. Encode it with this schema for JSON.\n */\nexport const ReadWhy = Schema.Struct({\n state: State,\n because: Schema.Array(Why),\n message: Schema.String,\n surfaces: Schema.OptionFromNullOr(Names),\n})\nexport type ReadWhy = typeof ReadWhy.Type\n\n/** Remote's own failures say what to do; a server's error says itself. */\nconst failedReadWords = (error: RemoteError): string => {\n // tag-check: open — a server names its own errors; only these three are Remote's.\n switch (error._tag) {\n case 'DecodeError':\n return `What the server sent does not decode against the Selection: ${error.message}`\n case 'Unavailable':\n return `${error.message} Select without it, or Data.refresh asks again.`\n case 'Overlaid':\n return `${error.message} Overlay every field the Selection reads, or select fewer.`\n default:\n return `Its request failed: ${error.message}. Nothing retries a failed read on its own; Data.refresh asks again.`\n }\n}\n\nconst rowWords = (row: typeof RowRef.Type, via: ReadonlyArray<string>): string =>\n `${row.entity} ${row.id}${via.length === 0 ? '' : ` (through ${via.join('.')})`}`\n\nconst sourceWords = (source: ShownBy): string =>\n ShownBy.match(source, {\n Write: ({ requestId, mutation }) =>\n Option.match(mutation, {\n onNone: () => `request ${requestId}`,\n onSome: name => `${name} (request ${requestId})`,\n }),\n Overlay: ({ id }) => `overlay \"${id}\"`,\n Live: () => 'a live event',\n })\n\nconst termWords = (terms: ReadonlyArray<WhyTerm>): string =>\n terms.length === 0\n ? ''\n : ` Its terms: ${terms.map(term => `${term.term} is ${term.truth}`).join(', ')}.`\n\nconst heldWords: Readonly<\n Record<typeof Held.Type, Readonly<Record<'true' | 'false', (what: string) => string>>>\n> = {\n Missing: {\n true: what => `${what} is being fetched.`,\n false: what => `${what} is not held, and nothing is fetching it.`,\n },\n Stale: {\n true: what => `${what} is shown while a newer value is fetched.`,\n false: what =>\n `${what} is out of date; the read entry fetches it again while a Surface reads it.`,\n },\n}\n\n/** One reason in words, with what restarts it. */\nexport const whyWords = (why: Why): string =>\n Why.match(why, {\n NotObserved: () =>\n 'No active Surface reads it, so nothing fetches it. Include it in a Surface that is active for this Model, in the record given to Data.subscriptions.',\n NotFetching: ({ surfaces }) =>\n `${surfaces.join(', ')} ${surfaces.length === 1 ? 'reads it and is' : 'read it and are'} active, yet nothing is fetching it. Right after a Message, that is the moment before the read entry asks; if it stays so, Remote's Subscriptions are not installed: give the runtime Data.subscriptions (or Data.wiring) and a RemoteClient.`,\n NoActiveRecord: () =>\n 'Nothing is fetching it. Pass the active record you give Data.subscriptions as `surfaces` to tell whether an active Surface reads it.',\n List: ({ list, held, inFlight }) => heldWords[held][`${inFlight}`](`Its list \"${list.query}\"`),\n Fields: ({ row, via, fields, held, inFlight }) =>\n heldWords[held][`${inFlight}`](`${fields.join(', ')} of ${rowWords(row, via)}`),\n ListFailed: ({ list, error }) => `Its list \"${list.query}\" failed. ${failedReadWords(error)}`,\n FieldsFailed: ({ row, via, fields, error }) =>\n `Reading ${fields.join(', ')} of ${rowWords(row, via)} failed. ${failedReadWords(error)}`,\n Unavailable: ({ row, via, fields }) =>\n `The server settled ${fields.join(', ')} of ${rowWords(row, via)} without a value for this principal. Select without them, or Data.refresh asks again.`,\n Undecodable: ({ message }) =>\n `What the store holds does not decode against the Selection: ${message}`,\n OverlayOnly: ({ message }) =>\n `${message} Overlay every field the Selection reads, or select fewer.`,\n Deleted: ({ row, by }) =>\n Option.match(by, {\n onNone: () =>\n `The server answered without ${rowWords(row, [])}: it does not exist, or this principal may not see it.`,\n onSome: source =>\n `${sourceWords(source)} deletes ${rowWords(row, [])}; its answer settles it, and its failure brings the row back.`,\n }),\n LiveReconnecting: ({ surface, attempt }) =>\n `${surface}'s live stream broke (attempt ${attempt}); the live entry resubscribes on its own, with backoff, and what changes meanwhile is fetched again.`,\n LiveRefused: ({ surface, error }) =>\n `The server refused ${surface}'s live stream: ${error.message}. Nothing asks again until what it reads changes.`,\n Pending: ({ row, fields, by }) =>\n `${fields.join(', ')} of ${rowWords(row, [])} show what ${sourceWords(by)} wrote, until it is answered.`,\n Joined: ({ list, row, by, terms }) =>\n `${rowWords(row, [])} shows in \"${list.query}\" before the server lists it${Option.match(by, { onNone: () => '', onSome: source => `, for ${sourceWords(source)}` })}.${termWords(terms)}`,\n Left: ({ list, row, by, terms }) =>\n `${rowWords(row, [])} is left out of \"${list.query}\", which the server still lists it in${Option.match(by, { onNone: () => '', onSome: source => `, for ${sourceWords(source)}` })}.${termWords(terms)}`,\n })\n\ntype Decided = Readonly<\n Record<typeof Held.Type, Readonly<Record<'true' | 'false', ReadonlyArray<State>>>>\n>\n\n/** A list's own page: missing reads `Loading` while fetched, `Initial` otherwise. */\nconst listDecides: Decided = {\n Missing: { true: ['Loading'], false: ['Initial'] },\n Stale: { true: ['Refreshing'], false: ['Refreshing'] },\n}\n\n/**\n * A row's fields: an entity read missing them reads `Loading` or `Initial`;\n * a list leaves the row out and reads `Refreshing`.\n */\nconst fieldsDecide: Decided = {\n Missing: { true: ['Loading', 'Refreshing'], false: ['Initial', 'Refreshing'] },\n Stale: { true: ['Refreshing'], false: ['Refreshing'] },\n}\n\n/** The states each reason can decide: the one `message` speaks of is the first that decides `state`. */\nconst decides = (why: Why): ReadonlyArray<State> =>\n Why.match<ReadonlyArray<State>>(why, {\n NotObserved: () => ['Initial'],\n NotFetching: () => ['Initial'],\n NoActiveRecord: () => ['Initial'],\n List: ({ held, inFlight }) => listDecides[held][`${inFlight}`],\n Fields: ({ held, inFlight }) => fieldsDecide[held][`${inFlight}`],\n ListFailed: () => ['Failed'],\n FieldsFailed: () => ['Failed'],\n Unavailable: () => ['Failed'],\n Undecodable: () => ['Failed'],\n OverlayOnly: () => ['Failed'],\n Deleted: () => ['NotFound'],\n LiveReconnecting: () => [],\n LiveRefused: () => [],\n Pending: () => ['Ready', 'Refreshing'],\n Joined: () => ['Ready', 'Refreshing'],\n Left: () => ['Ready', 'Refreshing'],\n })\n\nconst stateWords: Readonly<Record<State, string>> = {\n Initial: 'Nothing is fetching it.',\n Loading: 'A request for it is in flight.',\n Ready: 'Everything it selects is here.',\n Refreshing: 'It is shown while a newer value is fetched.',\n Failed: 'It failed.',\n NotFound: 'The server answered without it: it does not exist, or this principal may not see it.',\n}\n\n/** The explanation of a read in `state`, from its reasons. */\nexport const readWhy = (\n state: State,\n because: ReadonlyArray<Why>,\n surfaces: Option.Option<ReadonlyArray<string>>,\n): ReadWhy => {\n const decisive = because.findIndex(why => decides(why).includes(state))\n const ordered =\n decisive <= 0 ? because : [because[decisive]!, ...because.filter((_, at) => at !== decisive)]\n return {\n state,\n because: ordered,\n message: decisive === -1 ? stateWords[state] : whyWords(because[decisive]!),\n surfaces,\n }\n}\n\n/** Whether a read shows a value. */\nconst shows = (data: RemoteData<unknown>): boolean =>\n RemoteData.match(data, {\n Initial: () => false,\n Loading: () => false,\n Ready: () => true,\n Refreshing: () => true,\n Failed: () => false,\n NotFound: () => false,\n })\n\n/** Whether a read that showed a value still does, or says why not: failed with it, or found it gone. */\nconst keeps = (data: RemoteData<unknown>): boolean =>\n RemoteData.match(data, {\n Initial: () => false,\n Loading: () => false,\n Ready: () => true,\n Refreshing: () => true,\n Failed: (_, previous) => Option.isSome(previous),\n NotFound: () => true,\n })\n\n/**\n * #234's rule: a read that showed a value stopped showing it with no reason\n * a reader can see (`Loading`, `Initial`, or `Failed` without the value).\n */\nexport const regressed = (from: RemoteData<unknown>, to: RemoteData<unknown>): boolean =>\n shows(from) && !keeps(to)\n\n/** One change of a read's tag in a history, with the Message that made it. */\nexport interface ReadStep<Message> {\n /** The read's name, as `Data.timeline` was given it. */\n readonly read: string\n /** The history entry it changed at, from 0. */\n readonly index: number\n readonly message: Message\n readonly from: State\n readonly to: State\n /** `Data.why` at the Model it changed to. */\n readonly why: ReadWhy\n /** It showed a value and stopped, with no reason a reader can see (#234). */\n readonly regression: boolean\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { r as RemoteData } from "./remoteData-mK1suYi1.js";
|
|
2
|
+
import { i as Edge } from "./connection-BRyaJy89.js";
|
|
3
|
+
import { r as EntityStore } from "./store-ah2qjcJW.js";
|
|
4
|
+
import { a as Requirement, t as ConnectionRequirement } from "./requirement-D57S2LpH.js";
|
|
5
|
+
import { a as Why, t as ListRef } from "./why-kgp_Wr3z.js";
|
|
6
|
+
import { s as RemoteModel } from "./model-B6zk1NMB.js";
|
|
7
|
+
import { Option } from "effect";
|
|
8
|
+
import { AnyQuery, ExprContext, MemoryOperators, Row } from "foldkit-entity";
|
|
9
|
+
/** What a read asks of the store: entity requirements and list connections. */
|
|
10
|
+
export interface Asked {
|
|
11
|
+
readonly requirements: ReadonlyArray<Requirement>;
|
|
12
|
+
readonly connections: ReadonlyArray<ConnectionRequirement>;
|
|
13
|
+
}
|
|
14
|
+
/** What the reasons are read from, as the bound domain reads its lists. */
|
|
15
|
+
export interface WhyReader {
|
|
16
|
+
readonly remote: RemoteModel;
|
|
17
|
+
/** The store as reads see it: the server's under every pending layer. */
|
|
18
|
+
readonly visible: EntityStore;
|
|
19
|
+
readonly listOf: (identity: string) => ListRef;
|
|
20
|
+
/** The list's body and its encoded input, when its query was declared with one. */
|
|
21
|
+
readonly bodyOf: (identity: string) => Option.Option<{
|
|
22
|
+
readonly body: AnyQuery;
|
|
23
|
+
readonly encoded: Row;
|
|
24
|
+
}>;
|
|
25
|
+
/**
|
|
26
|
+
* The rows the read shows (`shown`, cut to its window), every row the list
|
|
27
|
+
* shows (`placed`), and the server's pages (`pages`).
|
|
28
|
+
*/
|
|
29
|
+
readonly edgesOf: (connection: ConnectionRequirement) => {
|
|
30
|
+
readonly shown: ReadonlyArray<Edge>;
|
|
31
|
+
readonly placed: ReadonlyArray<Edge>;
|
|
32
|
+
readonly pages: ReadonlyArray<Edge>;
|
|
33
|
+
};
|
|
34
|
+
readonly operators: MemoryOperators;
|
|
35
|
+
/** What a body's `Expr.viewer` reads: the viewer the newest pending operation was predicted for. */
|
|
36
|
+
readonly context: ExprContext | undefined;
|
|
37
|
+
}
|
|
38
|
+
/** Every fact the Model holds about what a read asks: its lists first, then its rows. */
|
|
39
|
+
export declare const reasonsOf: (reader: WhyReader, asked: Asked) => ReadonlyArray<Why>;
|
|
40
|
+
/** The reasons a `Failed` state carries in its error alone: values the store holds that cannot be shown. */
|
|
41
|
+
export declare const failedReasons: (data: RemoteData<unknown>) => ReadonlyArray<Why>;
|
|
42
|
+
//# sourceMappingURL=whyReasons.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"whyReasons.d.ts","names":[],"sources":["../src/whyReasons.ts"],"mappings":";;;;;;;;;iBAwBiB;WACN,cAAc,cAAc;WAC5B,aAAa,cAAc;;;iBAIrB;WACN,QAAQ;;WAER,SAAS;WACT,SAAS,qBAAqB;;WAE9B,SACP,qBACG,OAAO;aAAkB,MAAM;aAAmB,SAAS;;;;;;WAKvD,UAAU,YAAY;aACpB,OAAO,cAAc;aACrB,QAAQ,cAAc;aACtB,OAAO,cAAc;;WAEvB,WAAW;;WAEX,SAAS;;;qBAmOP,YAAS,QAAY,WAAS,OAAS,UAAQ,cAAc;;qBAW7D,gBAAa,MAAU,wBAAsB,cAAc"}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { targetsOf } from "./relation.js";
|
|
2
|
+
import { entityKey } from "./store.js";
|
|
3
|
+
import { holderOf } from "./held.js";
|
|
4
|
+
import { RemoteData } from "./remoteData.js";
|
|
5
|
+
import { ShownBy, Why } from "./why.js";
|
|
6
|
+
import { fieldMark, isQueryLoading } from "./model.js";
|
|
7
|
+
import { Array, Option } from "effect";
|
|
8
|
+
import { Expr, Judge } from "foldkit-entity";
|
|
9
|
+
// src/whyReasons.ts
|
|
10
|
+
/**
|
|
11
|
+
* The reasons `Data.why` reads out of the Model: what it holds about each list
|
|
12
|
+
* and each row a read reads, and what pending writes show of them (#248).
|
|
13
|
+
*/
|
|
14
|
+
/** What an optimistic id names: a request, an overlay, or a live event. */
|
|
15
|
+
const sourceOf = (remote, id) => {
|
|
16
|
+
if (id.startsWith("overlay:")) return ShownBy.Overlay({ id: id.slice(8) });
|
|
17
|
+
if (id.startsWith("live:")) return ShownBy.Live({ cursor: id.slice(5) });
|
|
18
|
+
return ShownBy.Write({
|
|
19
|
+
requestId: id,
|
|
20
|
+
mutation: Option.fromUndefinedOr(remote.mutations.writes.get(id)?.mutation)
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
const touches = (layer, entity, id) => layer.patches.some((patch) => patch.entity === entity && patch.id === id) || layer.deletes.some((gone) => gone.entity === entity && gone.id === id);
|
|
24
|
+
/** The newest layer that writes or deletes the row, as the source shown. */
|
|
25
|
+
const writerOf = (remote, entity, id) => Option.map(Array.findLast(remote.optimistic.layers, (layer) => touches(layer, entity, id)), (layer) => sourceOf(remote, layer.id));
|
|
26
|
+
/** Fields grouped by whether a read of each is in flight. */
|
|
27
|
+
const byFlight = (remote, entity, id, fields) => {
|
|
28
|
+
const flying = [];
|
|
29
|
+
const resting = [];
|
|
30
|
+
for (const field of fields) (remote.loading.has(fieldMark(entity, id, field)) ? flying : resting).push(field);
|
|
31
|
+
return [[true, flying], [false, resting]];
|
|
32
|
+
};
|
|
33
|
+
/** The reasons for one row the read reads, and for each row its relations reach. */
|
|
34
|
+
const rowReasons = (reader, entity, id, relation, via, walked) => {
|
|
35
|
+
const { remote, visible } = reader;
|
|
36
|
+
const key = entityKey(entity, id);
|
|
37
|
+
const seen = walked.get(key) ?? /* @__PURE__ */ new Set();
|
|
38
|
+
if (seen.has(relation)) return [];
|
|
39
|
+
seen.add(relation);
|
|
40
|
+
walked.set(key, seen);
|
|
41
|
+
const row = {
|
|
42
|
+
entity,
|
|
43
|
+
id
|
|
44
|
+
};
|
|
45
|
+
const entry = visible[key];
|
|
46
|
+
if (entry?.tombstone === true) return [Why.Deleted({
|
|
47
|
+
row,
|
|
48
|
+
by: writerOf(remote, entity, id)
|
|
49
|
+
})];
|
|
50
|
+
const reasons = [];
|
|
51
|
+
const failed = /* @__PURE__ */ new Map();
|
|
52
|
+
for (const field of relation.fields) {
|
|
53
|
+
const error = remote.failures.fields[fieldMark(entity, id, field)];
|
|
54
|
+
if (error !== void 0) failed.set(error, [...failed.get(error) ?? [], field]);
|
|
55
|
+
}
|
|
56
|
+
for (const [error, fields] of failed) reasons.push(Why.FieldsFailed({
|
|
57
|
+
row,
|
|
58
|
+
via,
|
|
59
|
+
fields,
|
|
60
|
+
error
|
|
61
|
+
}));
|
|
62
|
+
const unavailable = relation.fields.filter((field) => entry?.unavailable.has(field) === true);
|
|
63
|
+
if (unavailable.length > 0) reasons.push(Why.Unavailable({
|
|
64
|
+
row,
|
|
65
|
+
via,
|
|
66
|
+
fields: unavailable
|
|
67
|
+
}));
|
|
68
|
+
const missing = relation.fields.filter((field) => entry === void 0 || !entry.present.has(field) && !entry.unavailable.has(field));
|
|
69
|
+
const stale = relation.fields.filter((field) => entry !== void 0 && entry.present.has(field) && entry.stale.has(field));
|
|
70
|
+
for (const [held, fields] of [["Missing", missing], ["Stale", stale]]) for (const [inFlight, part] of byFlight(remote, entity, id, fields)) if (part.length > 0) reasons.push(Why.Fields({
|
|
71
|
+
row,
|
|
72
|
+
via,
|
|
73
|
+
fields: part,
|
|
74
|
+
held,
|
|
75
|
+
inFlight
|
|
76
|
+
}));
|
|
77
|
+
for (const layer of remote.optimistic.layers) {
|
|
78
|
+
const written = new Set(layer.patches.filter((patch) => patch.entity === entity && patch.id === id).flatMap((patch) => [
|
|
79
|
+
...Object.keys(patch.values),
|
|
80
|
+
...Object.keys(patch.adds ?? {}),
|
|
81
|
+
...Object.keys(patch.computes ?? {})
|
|
82
|
+
]));
|
|
83
|
+
const fields = relation.fields.filter((field) => written.has(field));
|
|
84
|
+
if (fields.length > 0) reasons.push(Why.Pending({
|
|
85
|
+
row,
|
|
86
|
+
fields,
|
|
87
|
+
by: sourceOf(remote, layer.id)
|
|
88
|
+
}));
|
|
89
|
+
}
|
|
90
|
+
for (const [field, nested] of Object.entries(relation.relations ?? {})) {
|
|
91
|
+
if (entry === void 0 || !entry.present.has(field)) continue;
|
|
92
|
+
for (const target of targetsOf(entry.values[field], nested)) reasons.push(...rowReasons(reader, target.entity, target.id, nested, [...via, field], walked));
|
|
93
|
+
}
|
|
94
|
+
return reasons;
|
|
95
|
+
};
|
|
96
|
+
const truths = {
|
|
97
|
+
true: "True",
|
|
98
|
+
false: "False",
|
|
99
|
+
unknown: "Null"
|
|
100
|
+
};
|
|
101
|
+
/** Each `where` predicate of the list's body, judged over the row as the client holds it. */
|
|
102
|
+
const termsOf = (reader, identity, entity, id) => Option.match(reader.bodyOf(identity), {
|
|
103
|
+
onNone: () => [],
|
|
104
|
+
onSome: ({ body, encoded }) => {
|
|
105
|
+
const holder = holderOf(reader.visible);
|
|
106
|
+
return Option.match(holder.rows.row(body.entity, id), {
|
|
107
|
+
onNone: () => body.where.map((predicate) => ({
|
|
108
|
+
term: Expr.show(predicate),
|
|
109
|
+
truth: "Unknown",
|
|
110
|
+
reason: Option.some(`${entity} ${id} is not held`)
|
|
111
|
+
})),
|
|
112
|
+
onSome: (held) => body.where.map((predicate) => Judge.match(Judge.predicate(predicate, held, {
|
|
113
|
+
input: encoded,
|
|
114
|
+
operators: reader.operators,
|
|
115
|
+
context: reader.context,
|
|
116
|
+
rows: holder.rows
|
|
117
|
+
}), {
|
|
118
|
+
onKnown: (truth) => ({
|
|
119
|
+
term: Expr.show(predicate),
|
|
120
|
+
truth: truths[`${truth}`],
|
|
121
|
+
reason: Option.none()
|
|
122
|
+
}),
|
|
123
|
+
onUnknown: (reason) => ({
|
|
124
|
+
term: Expr.show(predicate),
|
|
125
|
+
truth: "Unknown",
|
|
126
|
+
reason: Option.some(reason)
|
|
127
|
+
})
|
|
128
|
+
}))
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
/** The overlay that shows or hides this edge in this list, newest first. */
|
|
133
|
+
const overlayOf = (remote, identity, key, removes) => Option.map(Array.findLast(remote.optimistic.overlays, (overlay) => overlay.connection === identity && overlay.position === "remove" === removes && overlay.edges.some((edge) => edge.key === key)), (overlay) => sourceOf(remote, overlay.id));
|
|
134
|
+
const listReasons = (reader, asked) => {
|
|
135
|
+
const { remote } = reader;
|
|
136
|
+
const { identity } = asked;
|
|
137
|
+
const list = reader.listOf(identity);
|
|
138
|
+
const connection = remote.connections[identity];
|
|
139
|
+
const failure = remote.failures.connections[identity];
|
|
140
|
+
const inFlight = isQueryLoading(remote, identity);
|
|
141
|
+
const reasons = failure === void 0 ? [] : [Why.ListFailed({
|
|
142
|
+
list,
|
|
143
|
+
error: failure
|
|
144
|
+
})];
|
|
145
|
+
if (connection === void 0 || connection.stale && connection.segments.length === 0) return [...reasons, Why.List({
|
|
146
|
+
list,
|
|
147
|
+
held: "Missing",
|
|
148
|
+
inFlight
|
|
149
|
+
})];
|
|
150
|
+
if (connection.stale) reasons.push(Why.List({
|
|
151
|
+
list,
|
|
152
|
+
held: "Stale",
|
|
153
|
+
inFlight
|
|
154
|
+
}));
|
|
155
|
+
const { shown, placed, pages } = reader.edgesOf(asked);
|
|
156
|
+
const walked = /* @__PURE__ */ new Map();
|
|
157
|
+
for (const edge of shown) {
|
|
158
|
+
if (edge.ref.entity !== asked.select.entity) continue;
|
|
159
|
+
reasons.push(...rowReasons(reader, edge.ref.entity, edge.ref.id, asked.select, [], walked));
|
|
160
|
+
}
|
|
161
|
+
const served = new Set(pages.map((edge) => edge.key));
|
|
162
|
+
const shows = new Set(placed.map((edge) => edge.key));
|
|
163
|
+
for (const edge of shown) {
|
|
164
|
+
if (served.has(edge.key)) continue;
|
|
165
|
+
reasons.push(Why.Joined({
|
|
166
|
+
list,
|
|
167
|
+
row: {
|
|
168
|
+
entity: edge.ref.entity,
|
|
169
|
+
id: edge.ref.id
|
|
170
|
+
},
|
|
171
|
+
by: Option.orElse(overlayOf(remote, identity, edge.key, false), () => writerOf(remote, edge.ref.entity, edge.ref.id)),
|
|
172
|
+
terms: termsOf(reader, identity, edge.ref.entity, edge.ref.id)
|
|
173
|
+
}));
|
|
174
|
+
}
|
|
175
|
+
for (const edge of pages) {
|
|
176
|
+
if (shows.has(edge.key)) continue;
|
|
177
|
+
reasons.push(Why.Left({
|
|
178
|
+
list,
|
|
179
|
+
row: {
|
|
180
|
+
entity: edge.ref.entity,
|
|
181
|
+
id: edge.ref.id
|
|
182
|
+
},
|
|
183
|
+
by: Option.orElse(overlayOf(remote, identity, edge.key, true), () => writerOf(remote, edge.ref.entity, edge.ref.id)),
|
|
184
|
+
terms: termsOf(reader, identity, edge.ref.entity, edge.ref.id)
|
|
185
|
+
}));
|
|
186
|
+
}
|
|
187
|
+
return reasons;
|
|
188
|
+
};
|
|
189
|
+
/** Every fact the Model holds about what a read asks: its lists first, then its rows. */
|
|
190
|
+
const reasonsOf = (reader, asked) => {
|
|
191
|
+
const walked = /* @__PURE__ */ new Map();
|
|
192
|
+
return [...asked.connections.flatMap((connection) => listReasons(reader, connection)), ...asked.requirements.flatMap((requirement) => rowReasons(reader, requirement.entity, requirement.id, requirement, [], walked))];
|
|
193
|
+
};
|
|
194
|
+
/** The reasons a `Failed` state carries in its error alone: values the store holds that cannot be shown. */
|
|
195
|
+
const failedReasons = (data) => RemoteData.match(data, {
|
|
196
|
+
Initial: () => [],
|
|
197
|
+
Loading: () => [],
|
|
198
|
+
Ready: () => [],
|
|
199
|
+
Refreshing: () => [],
|
|
200
|
+
NotFound: () => [],
|
|
201
|
+
Failed: (error) => {
|
|
202
|
+
switch (error._tag) {
|
|
203
|
+
case "DecodeError": return [Why.Undecodable({ message: error.message })];
|
|
204
|
+
case "Overlaid": return [Why.OverlayOnly({ message: error.message })];
|
|
205
|
+
default: return [];
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
export { failedReasons, reasonsOf };
|
|
210
|
+
|
|
211
|
+
//# sourceMappingURL=whyReasons.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"whyReasons.js","names":["Arr"],"sources":["../src/whyReasons.ts"],"sourcesContent":["/**\n * The reasons `Data.why` reads out of the Model: what it holds about each list\n * and each row a read reads, and what pending writes show of them (#248).\n */\nimport { Array as Arr, Option } from 'effect'\nimport {\n Expr,\n Judge,\n type AnyQuery,\n type ExprContext,\n type MemoryOperators,\n type Row,\n} from 'foldkit-entity'\nimport type { Edge } from './connection.js'\nimport { holderOf } from './held.js'\nimport { fieldMark, isQueryLoading, type RemoteModel } from './model.js'\nimport type { EntityLayer } from './optimistic.js'\nimport { targetsOf } from './relation.js'\nimport { RemoteData, type RemoteError } from './remoteData.js'\nimport type { ConnectionRequirement, RelationRequirement, Requirement } from './requirement.js'\nimport { entityKey, type EntityStore } from './store.js'\nimport { ShownBy, Why, type ListRef, type WhyTerm } from './why.js'\n\n/** What a read asks of the store: entity requirements and list connections. */\nexport interface Asked {\n readonly requirements: ReadonlyArray<Requirement>\n readonly connections: ReadonlyArray<ConnectionRequirement>\n}\n\n/** What the reasons are read from, as the bound domain reads its lists. */\nexport interface WhyReader {\n readonly remote: RemoteModel\n /** The store as reads see it: the server's under every pending layer. */\n readonly visible: EntityStore\n readonly listOf: (identity: string) => ListRef\n /** The list's body and its encoded input, when its query was declared with one. */\n readonly bodyOf: (\n identity: string,\n ) => Option.Option<{ readonly body: AnyQuery; readonly encoded: Row }>\n /**\n * The rows the read shows (`shown`, cut to its window), every row the list\n * shows (`placed`), and the server's pages (`pages`).\n */\n readonly edgesOf: (connection: ConnectionRequirement) => {\n readonly shown: ReadonlyArray<Edge>\n readonly placed: ReadonlyArray<Edge>\n readonly pages: ReadonlyArray<Edge>\n }\n readonly operators: MemoryOperators\n /** What a body's `Expr.viewer` reads: the viewer the newest pending operation was predicted for. */\n readonly context: ExprContext | undefined\n}\n\n/** What an optimistic id names: a request, an overlay, or a live event. */\nconst sourceOf = (remote: RemoteModel, id: string): ShownBy => {\n if (id.startsWith('overlay:')) return ShownBy.Overlay({ id: id.slice('overlay:'.length) })\n if (id.startsWith('live:')) return ShownBy.Live({ cursor: id.slice('live:'.length) })\n return ShownBy.Write({\n requestId: id,\n mutation: Option.fromUndefinedOr(remote.mutations.writes.get(id)?.mutation),\n })\n}\n\nconst touches = (layer: EntityLayer, entity: string, id: string): boolean =>\n layer.patches.some(patch => patch.entity === entity && patch.id === id) ||\n layer.deletes.some(gone => gone.entity === entity && gone.id === id)\n\n/** The newest layer that writes or deletes the row, as the source shown. */\nconst writerOf = (remote: RemoteModel, entity: string, id: string): Option.Option<ShownBy> =>\n Option.map(\n Arr.findLast(remote.optimistic.layers, layer => touches(layer, entity, id)),\n layer => sourceOf(remote, layer.id),\n )\n\n/** Fields grouped by whether a read of each is in flight. */\nconst byFlight = (\n remote: RemoteModel,\n entity: string,\n id: string,\n fields: ReadonlyArray<string>,\n): ReadonlyArray<readonly [boolean, ReadonlyArray<string>]> => {\n const flying: string[] = []\n const resting: string[] = []\n for (const field of fields) {\n ;(remote.loading.has(fieldMark(entity, id, field)) ? flying : resting).push(field)\n }\n return [\n [true, flying],\n [false, resting],\n ] as const\n}\n\n/** The reasons for one row the read reads, and for each row its relations reach. */\nconst rowReasons = (\n reader: WhyReader,\n entity: string,\n id: string,\n relation: RelationRequirement,\n via: ReadonlyArray<string>,\n walked: Map<string, Set<RelationRequirement>>,\n): ReadonlyArray<Why> => {\n const { remote, visible } = reader\n const key = entityKey(entity, id)\n const seen = walked.get(key) ?? new Set()\n // Once per row and requirement, as the read assembles it; a cycle ends.\n if (seen.has(relation)) return []\n seen.add(relation)\n walked.set(key, seen)\n const row = { entity, id }\n const entry = visible[key]\n if (entry?.tombstone === true) return [Why.Deleted({ row, by: writerOf(remote, entity, id) })]\n\n const reasons: Why[] = []\n const failed = new Map<RemoteError, string[]>()\n for (const field of relation.fields) {\n const error = remote.failures.fields[fieldMark(entity, id, field)]\n if (error !== undefined) failed.set(error, [...(failed.get(error) ?? []), field])\n }\n for (const [error, fields] of failed) reasons.push(Why.FieldsFailed({ row, via, fields, error }))\n\n const unavailable = relation.fields.filter(field => entry?.unavailable.has(field) === true)\n if (unavailable.length > 0) reasons.push(Why.Unavailable({ row, via, fields: unavailable }))\n\n const missing = relation.fields.filter(\n field => entry === undefined || (!entry.present.has(field) && !entry.unavailable.has(field)),\n )\n const stale = relation.fields.filter(\n field => entry !== undefined && entry.present.has(field) && entry.stale.has(field),\n )\n for (const [held, fields] of [\n ['Missing', missing],\n ['Stale', stale],\n ] as const) {\n for (const [inFlight, part] of byFlight(remote, entity, id, fields)) {\n if (part.length > 0) reasons.push(Why.Fields({ row, via, fields: part, held, inFlight }))\n }\n }\n\n for (const layer of remote.optimistic.layers) {\n const written = new Set(\n layer.patches\n .filter(patch => patch.entity === entity && patch.id === id)\n .flatMap(patch => [\n ...Object.keys(patch.values),\n ...Object.keys(patch.adds ?? {}),\n ...Object.keys(patch.computes ?? {}),\n ]),\n )\n const fields = relation.fields.filter(field => written.has(field))\n if (fields.length > 0)\n reasons.push(Why.Pending({ row, fields, by: sourceOf(remote, layer.id) }))\n }\n\n for (const [field, nested] of Object.entries(relation.relations ?? {})) {\n if (entry === undefined || !entry.present.has(field)) continue\n for (const target of targetsOf(entry.values[field], nested)) {\n reasons.push(...rowReasons(reader, target.entity, target.id, nested, [...via, field], walked))\n }\n }\n return reasons\n}\n\nconst truths: Readonly<Record<'true' | 'false' | 'unknown', WhyTerm['truth']>> = {\n true: 'True',\n false: 'False',\n unknown: 'Null',\n}\n\n/** Each `where` predicate of the list's body, judged over the row as the client holds it. */\nconst termsOf = (reader: WhyReader, identity: string, entity: string, id: string) =>\n Option.match(reader.bodyOf(identity), {\n onNone: (): ReadonlyArray<WhyTerm> => [],\n onSome: ({ body, encoded }) => {\n const holder = holderOf(reader.visible)\n return Option.match(holder.rows.row(body.entity, id), {\n onNone: (): ReadonlyArray<WhyTerm> =>\n body.where.map(predicate => ({\n term: Expr.show(predicate),\n truth: 'Unknown',\n reason: Option.some(`${entity} ${id} is not held`),\n })),\n onSome: held =>\n body.where.map(predicate =>\n Judge.match(\n Judge.predicate(predicate, held, {\n input: encoded,\n operators: reader.operators,\n context: reader.context,\n rows: holder.rows,\n }),\n {\n onKnown: (truth): WhyTerm => ({\n term: Expr.show(predicate),\n truth: truths[`${truth}`],\n reason: Option.none(),\n }),\n onUnknown: (reason): WhyTerm => ({\n term: Expr.show(predicate),\n truth: 'Unknown',\n reason: Option.some(reason),\n }),\n },\n ),\n ),\n })\n },\n })\n\n/** The overlay that shows or hides this edge in this list, newest first. */\nconst overlayOf = (\n remote: RemoteModel,\n identity: string,\n key: string,\n removes: boolean,\n): Option.Option<ShownBy> =>\n Option.map(\n Arr.findLast(\n remote.optimistic.overlays,\n overlay =>\n overlay.connection === identity &&\n (overlay.position === 'remove') === removes &&\n overlay.edges.some(edge => edge.key === key),\n ),\n overlay => sourceOf(remote, overlay.id),\n )\n\nconst listReasons = (reader: WhyReader, asked: ConnectionRequirement): ReadonlyArray<Why> => {\n const { remote } = reader\n const { identity } = asked\n const list = reader.listOf(identity)\n const connection = remote.connections[identity]\n const failure = remote.failures.connections[identity]\n const inFlight = isQueryLoading(remote, identity)\n const reasons: Why[] = failure === undefined ? [] : [Why.ListFailed({ list, error: failure })]\n // A stale list with no pages has nothing to show: it is missing, as the read reads it.\n if (connection === undefined || (connection.stale && connection.segments.length === 0)) {\n return [...reasons, Why.List({ list, held: 'Missing', inFlight })]\n }\n if (connection.stale) reasons.push(Why.List({ list, held: 'Stale', inFlight }))\n const { shown, placed, pages } = reader.edgesOf(asked)\n const walked = new Map<string, Set<RelationRequirement>>()\n for (const edge of shown) {\n if (edge.ref.entity !== asked.select.entity) continue\n reasons.push(...rowReasons(reader, edge.ref.entity, edge.ref.id, asked.select, [], walked))\n }\n const served = new Set(pages.map(edge => edge.key))\n const shows = new Set(placed.map(edge => edge.key))\n for (const edge of shown) {\n if (served.has(edge.key)) continue\n reasons.push(\n Why.Joined({\n list,\n row: { entity: edge.ref.entity, id: edge.ref.id },\n by: Option.orElse(overlayOf(remote, identity, edge.key, false), () =>\n writerOf(remote, edge.ref.entity, edge.ref.id),\n ),\n terms: termsOf(reader, identity, edge.ref.entity, edge.ref.id),\n }),\n )\n }\n for (const edge of pages) {\n if (shows.has(edge.key)) continue\n reasons.push(\n Why.Left({\n list,\n row: { entity: edge.ref.entity, id: edge.ref.id },\n by: Option.orElse(overlayOf(remote, identity, edge.key, true), () =>\n writerOf(remote, edge.ref.entity, edge.ref.id),\n ),\n terms: termsOf(reader, identity, edge.ref.entity, edge.ref.id),\n }),\n )\n }\n return reasons\n}\n\n/** Every fact the Model holds about what a read asks: its lists first, then its rows. */\nexport const reasonsOf = (reader: WhyReader, asked: Asked): ReadonlyArray<Why> => {\n const walked = new Map<string, Set<RelationRequirement>>()\n return [\n ...asked.connections.flatMap(connection => listReasons(reader, connection)),\n ...asked.requirements.flatMap(requirement =>\n rowReasons(reader, requirement.entity, requirement.id, requirement, [], walked),\n ),\n ]\n}\n\n/** The reasons a `Failed` state carries in its error alone: values the store holds that cannot be shown. */\nexport const failedReasons = (data: RemoteData<unknown>): ReadonlyArray<Why> =>\n RemoteData.match(data, {\n Initial: () => [],\n Loading: () => [],\n Ready: () => [],\n Refreshing: () => [],\n NotFound: () => [],\n Failed: error => {\n // tag-check: open — a `RemoteError`'s tag is open; these two are the store's own.\n switch (error._tag) {\n case 'DecodeError':\n return [Why.Undecodable({ message: error.message })]\n case 'Overlaid':\n return [Why.OverlayOnly({ message: error.message })]\n default:\n return []\n }\n },\n })\n"],"mappings":"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"}
|