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
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { Option, Schema } from "effect";
|
|
2
|
+
interface RemoteError {
|
|
3
|
+
readonly _tag: string;
|
|
4
|
+
readonly message: string;
|
|
5
|
+
/**
|
|
6
|
+
* A refused mutation's refusal, encoded as the server sent it. Read it
|
|
7
|
+
* decoded, by its mutation, with `Data.refusal`.
|
|
8
|
+
*/
|
|
9
|
+
readonly refusal?: unknown;
|
|
10
|
+
/**
|
|
11
|
+
* Why the server refused who is asking (`_tag: 'Unauthenticated'` or
|
|
12
|
+
* `'Forbidden'`), when it said. Read it with `RemoteData.denied`.
|
|
13
|
+
*/
|
|
14
|
+
readonly reason?: string | undefined;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The server would not answer who is asking: their session ended or was
|
|
18
|
+
* never there (`Unauthenticated`, a 401), or they may not (`Forbidden`, a
|
|
19
|
+
* 403). `reason` is the server's sentence, when it gave one. Not a mutation's
|
|
20
|
+
* declared `refusal`, which the server answered and `Data.refusal` reads.
|
|
21
|
+
*/
|
|
22
|
+
declare const Denied: import("foldkit/schema").TaggedUnion<{
|
|
23
|
+
readonly Unauthenticated: {
|
|
24
|
+
reason: Schema.Option<Schema.String>;
|
|
25
|
+
};
|
|
26
|
+
readonly Forbidden: {
|
|
27
|
+
reason: Schema.Option<Schema.String>;
|
|
28
|
+
};
|
|
29
|
+
}>;
|
|
30
|
+
type Denied = typeof Denied.Type;
|
|
31
|
+
type RemoteData<A> = {
|
|
32
|
+
readonly _tag: 'Initial';
|
|
33
|
+
} | {
|
|
34
|
+
readonly _tag: 'Loading';
|
|
35
|
+
} | {
|
|
36
|
+
readonly _tag: 'Ready';
|
|
37
|
+
readonly value: A;
|
|
38
|
+
} | {
|
|
39
|
+
readonly _tag: 'Refreshing';
|
|
40
|
+
readonly value: A;
|
|
41
|
+
} | {
|
|
42
|
+
readonly _tag: 'Failed';
|
|
43
|
+
readonly error: RemoteError;
|
|
44
|
+
readonly previous?: A;
|
|
45
|
+
} | {
|
|
46
|
+
readonly _tag: 'NotFound';
|
|
47
|
+
};
|
|
48
|
+
declare const remoteErrorSchema: Schema.Struct<{
|
|
49
|
+
readonly _tag: Schema.String;
|
|
50
|
+
readonly message: Schema.String;
|
|
51
|
+
readonly refusal: Schema.optional<Schema.Unknown>;
|
|
52
|
+
readonly reason: Schema.optional<Schema.String>;
|
|
53
|
+
}>;
|
|
54
|
+
/**
|
|
55
|
+
* What `RemoteData.render` tells the `data` branch about the value it is
|
|
56
|
+
* given: whether a newer answer is on its way, and whether this one is what
|
|
57
|
+
* was last known good before a read failed. It is one tag rather than two
|
|
58
|
+
* booleans because a value cannot be both at once, and a type that can say so
|
|
59
|
+
* invites a view to handle a state that never arrives. Fold it with
|
|
60
|
+
* `Freshness.match`.
|
|
61
|
+
*/
|
|
62
|
+
declare const Freshness: import("foldkit/schema").TaggedUnion<{
|
|
63
|
+
readonly Fresh: {};
|
|
64
|
+
readonly Refreshing: {};
|
|
65
|
+
readonly Stale: {
|
|
66
|
+
error: Schema.Struct<{
|
|
67
|
+
readonly _tag: Schema.String;
|
|
68
|
+
readonly message: Schema.String;
|
|
69
|
+
readonly refusal: Schema.optional<Schema.Unknown>;
|
|
70
|
+
readonly reason: Schema.optional<Schema.String>;
|
|
71
|
+
}>;
|
|
72
|
+
};
|
|
73
|
+
}>;
|
|
74
|
+
type Freshness = typeof Freshness.Type;
|
|
75
|
+
declare const remoteDataSchema: <A>(value: Schema.Schema<A>) => Schema.Codec<RemoteData<A>, unknown>;
|
|
76
|
+
declare const RemoteData: {
|
|
77
|
+
/**
|
|
78
|
+
* The `RemoteData` schema for a value schema. Exposed so a `RemoteData` can
|
|
79
|
+
* be embedded in a hand-written Model schema, not only through `Remote.select`.
|
|
80
|
+
*/
|
|
81
|
+
schema: <A>(value: Schema.Schema<A>) => Schema.Schema<RemoteData<A>>;
|
|
82
|
+
/**
|
|
83
|
+
* Whether the read has an answer: `Ready`, `Refreshing`, `Failed` or
|
|
84
|
+
* `NotFound`. `Initial` and `Loading` are still waiting for one.
|
|
85
|
+
*/
|
|
86
|
+
answered: <A>(data: RemoteData<A>) => boolean;
|
|
87
|
+
/** Exhaustive: omitting a state is a compile error. */
|
|
88
|
+
match: <A, R>(data: RemoteData<A>, cases: {
|
|
89
|
+
readonly Initial: () => R;
|
|
90
|
+
readonly Loading: () => R;
|
|
91
|
+
readonly Ready: (value: A) => R;
|
|
92
|
+
readonly Refreshing: (value: A) => R;
|
|
93
|
+
readonly Failed: (error: RemoteError, previous: Option.Option<A>) => R;
|
|
94
|
+
readonly NotFound: () => R;
|
|
95
|
+
}) => R;
|
|
96
|
+
/**
|
|
97
|
+
* The view-oriented fold: the six states as the three things a view actually
|
|
98
|
+
* draws, under the policy that useful data stays on screen. A value that is
|
|
99
|
+
* being refetched, or that a later read failed to replace, still reaches
|
|
100
|
+
* `data` — with `freshness` saying which — instead of being replaced by a
|
|
101
|
+
* spinner or an error the reader cannot act on.
|
|
102
|
+
*
|
|
103
|
+
* ```ts
|
|
104
|
+
* RemoteData.render(model.user, {
|
|
105
|
+
* loading: () => UserSkeleton(),
|
|
106
|
+
* notFound: () => NoSuchUser(),
|
|
107
|
+
* failed: error => ErrorView(error),
|
|
108
|
+
* data: (user, freshness) => UserView({ user, dimmed: freshness._tag !== 'Fresh' }),
|
|
109
|
+
* })
|
|
110
|
+
* ```
|
|
111
|
+
*
|
|
112
|
+
* `notFound` is its own branch and not optional: a row the server answered
|
|
113
|
+
* for and does not have is neither loading nor a failure, and drawing it as
|
|
114
|
+
* either is a spinner that never ends or an error nobody can fix.
|
|
115
|
+
*
|
|
116
|
+
* This does not replace `match`, which is the exhaustive fold over the states
|
|
117
|
+
* themselves. Reach for `match` when the six states really do draw
|
|
118
|
+
* differently, and for this when they draw the usual three ways.
|
|
119
|
+
*/
|
|
120
|
+
render: <A, R>(data: RemoteData<A>, cases: {
|
|
121
|
+
readonly loading: () => R;
|
|
122
|
+
readonly notFound: () => R;
|
|
123
|
+
readonly failed: (error: RemoteError) => R;
|
|
124
|
+
readonly data: (value: A, freshness: Freshness) => R;
|
|
125
|
+
}) => R;
|
|
126
|
+
/**
|
|
127
|
+
* The denial a failure was, typed, or none for any other failure (a
|
|
128
|
+
* network error, a protocol mismatch): what to read before telling a reader
|
|
129
|
+
* to sign in again.
|
|
130
|
+
*
|
|
131
|
+
* ```ts
|
|
132
|
+
* Option.match(RemoteData.denied(error), {
|
|
133
|
+
* onNone: () => 'Could not load',
|
|
134
|
+
* onSome: Denied.match({ Unauthenticated: () => 'Sign in again', Forbidden: () => 'Not yours' }),
|
|
135
|
+
* })
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
denied: (error: RemoteError) => Option.Option<Denied>;
|
|
139
|
+
/**
|
|
140
|
+
* Whether nothing answered (`RemoteUnreachable`): the request was not sent,
|
|
141
|
+
* or what came back was no Remote answer, so a write may or may not have
|
|
142
|
+
* run. A failure the server answered (a refusal, a denial) is not one.
|
|
143
|
+
*/
|
|
144
|
+
isUnreachable: (error: RemoteError) => boolean;
|
|
145
|
+
map: <A, B>(data: RemoteData<A>, f: (value: A) => B) => RemoteData<B>;
|
|
146
|
+
};
|
|
147
|
+
export { remoteDataSchema as a, RemoteError as i, Freshness as n, remoteErrorSchema as o, RemoteData as r, Denied as t };
|
|
148
|
+
//# sourceMappingURL=remoteData-mK1suYi1.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remoteData-mK1suYi1.d.ts","names":[],"sources":["../src/remoteData.ts"],"mappings":";UAKiB;WACN;WACA;;;;;WAKA;;;;;WAKA;;;;;;;;cASE,iCAAM;;IACE,QAAM,OAAA,OAAA,OAAA;;;IACZ,QAAM,OAAA,OAAA,OAAA;;;KAET,gBAAgB,OAAO;KAUvB,WAAW;WACR;;WACA;;WACA;WAAwB,OAAO;;WAC/B;WAA6B,OAAO;;WACpC;WAAyB,OAAO;WAAsB,WAAW;;WACjE;;cAEF,mBAAiB,OAAA;;;;;;;;;;;;;;cAejB,oCAAS;;;;IAGX,OAAK,OAAA;;;;;;;;KAEJ,mBAAmB,UAAU;cAW5B,mBAAoB,GAAC,OACzB,OAAO,OAAO,OACpB,OAAO,MAAM,WAAW;cAmBd;;;;;EAKX,SAAS,GAAC,OAAS,OAAO,OAAO,OAAK,OAAO,OAAO,WAAW;;;;;EAM/D,WAAW,GAAC,MAAQ,WAAW;;EAW/B,QAAQ,GAAG,GAAC,MACJ,WAAW,IAAE;aAER,eAAe;aACf,eAAe;aACf,QAAQ,OAAO,MAAM;aACrB,aAAa,OAAO,MAAM;aAC1B,SAAS,OAAO,aAAa,UAAU,OAAO,OAAO,OAAO;aAC5D,gBAAgB;QAE1B;;;;;;;;;;;;;;;;;;;;;;;;;EA4CH,SAAS,GAAG,GAAC,MACL,WAAW,IAAE;aAER,eAAe;aACf,gBAAgB;aAChB,SAAS,OAAO,gBAAgB;aAChC,OAAO,OAAO,GAAG,WAAW,cAAc;QAEpD;;;;;;;;;;;;;EA8BH,SAAM,OAAU,gBAAc,OAAO,OAAO;;;;;;EAW5C,gBAAa,OAAU;EAIvB,MAAM,GAAG,GAAC,MAAQ,WAAW,IAAE,IAAM,OAAO,MAAM,MAAI,WAAW"}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { Option, Schema } from "effect";
|
|
2
|
+
import { defineTaggedUnion } from "foldkit/schema";
|
|
3
|
+
// src/remoteData.ts
|
|
4
|
+
/** `RemoteData`: the state of a value the store may not hold yet. */
|
|
5
|
+
/**
|
|
6
|
+
* The server would not answer who is asking: their session ended or was
|
|
7
|
+
* never there (`Unauthenticated`, a 401), or they may not (`Forbidden`, a
|
|
8
|
+
* 403). `reason` is the server's sentence, when it gave one. Not a mutation's
|
|
9
|
+
* declared `refusal`, which the server answered and `Data.refusal` reads.
|
|
10
|
+
*/
|
|
11
|
+
const Denied = defineTaggedUnion({
|
|
12
|
+
Unauthenticated: { reason: Schema.Option(Schema.String) },
|
|
13
|
+
Forbidden: { reason: Schema.Option(Schema.String) }
|
|
14
|
+
});
|
|
15
|
+
/** The tags `denied` reads: a `RemoteError`'s `_tag` is open, for any failure a transport reports. */
|
|
16
|
+
const denials = /* @__PURE__ */ new Set(["Unauthenticated", "Forbidden"]);
|
|
17
|
+
const isDenial = (tag) => denials.has(tag);
|
|
18
|
+
const deny = {
|
|
19
|
+
Unauthenticated: Denied.Unauthenticated,
|
|
20
|
+
Forbidden: Denied.Forbidden
|
|
21
|
+
};
|
|
22
|
+
/** Typed by the error class, so renaming its tag is a type error here. */
|
|
23
|
+
const unreachable = "Unreachable";
|
|
24
|
+
const remoteErrorSchema = Schema.Struct({
|
|
25
|
+
_tag: Schema.String,
|
|
26
|
+
message: Schema.String,
|
|
27
|
+
refusal: Schema.optional(Schema.Unknown),
|
|
28
|
+
reason: Schema.optional(Schema.String)
|
|
29
|
+
});
|
|
30
|
+
/**
|
|
31
|
+
* What `RemoteData.render` tells the `data` branch about the value it is
|
|
32
|
+
* given: whether a newer answer is on its way, and whether this one is what
|
|
33
|
+
* was last known good before a read failed. It is one tag rather than two
|
|
34
|
+
* booleans because a value cannot be both at once, and a type that can say so
|
|
35
|
+
* invites a view to handle a state that never arrives. Fold it with
|
|
36
|
+
* `Freshness.match`.
|
|
37
|
+
*/
|
|
38
|
+
const Freshness = defineTaggedUnion({
|
|
39
|
+
Fresh: {},
|
|
40
|
+
Refreshing: {},
|
|
41
|
+
Stale: { error: remoteErrorSchema }
|
|
42
|
+
});
|
|
43
|
+
const fresh = Freshness.Fresh();
|
|
44
|
+
const refreshing = Freshness.Refreshing();
|
|
45
|
+
/** A `RemoteData` schema, so a projection that reads remote state is typed. */
|
|
46
|
+
const remoteDataSchemas = /* @__PURE__ */ new WeakMap();
|
|
47
|
+
const remoteDataSchema = (value) => {
|
|
48
|
+
const cached = remoteDataSchemas.get(value);
|
|
49
|
+
if (cached !== void 0) return cached;
|
|
50
|
+
const built = Schema.Union([
|
|
51
|
+
Schema.Struct({ _tag: Schema.Literal("Initial") }),
|
|
52
|
+
Schema.Struct({ _tag: Schema.Literal("Loading") }),
|
|
53
|
+
Schema.Struct({
|
|
54
|
+
_tag: Schema.Literal("Ready"),
|
|
55
|
+
value
|
|
56
|
+
}),
|
|
57
|
+
Schema.Struct({
|
|
58
|
+
_tag: Schema.Literal("Refreshing"),
|
|
59
|
+
value
|
|
60
|
+
}),
|
|
61
|
+
Schema.Struct({
|
|
62
|
+
_tag: Schema.Literal("Failed"),
|
|
63
|
+
error: remoteErrorSchema,
|
|
64
|
+
previous: Schema.optional(value)
|
|
65
|
+
}),
|
|
66
|
+
Schema.Struct({ _tag: Schema.Literal("NotFound") })
|
|
67
|
+
]);
|
|
68
|
+
remoteDataSchemas.set(value, built);
|
|
69
|
+
return built;
|
|
70
|
+
};
|
|
71
|
+
const RemoteData = {
|
|
72
|
+
/**
|
|
73
|
+
* The `RemoteData` schema for a value schema. Exposed so a `RemoteData` can
|
|
74
|
+
* be embedded in a hand-written Model schema, not only through `Remote.select`.
|
|
75
|
+
*/
|
|
76
|
+
schema: (value) => remoteDataSchema(value),
|
|
77
|
+
/**
|
|
78
|
+
* Whether the read has an answer: `Ready`, `Refreshing`, `Failed` or
|
|
79
|
+
* `NotFound`. `Initial` and `Loading` are still waiting for one.
|
|
80
|
+
*/
|
|
81
|
+
answered: (data) => RemoteData.match(data, {
|
|
82
|
+
Initial: () => false,
|
|
83
|
+
Loading: () => false,
|
|
84
|
+
Ready: () => true,
|
|
85
|
+
Refreshing: () => true,
|
|
86
|
+
Failed: () => true,
|
|
87
|
+
NotFound: () => true
|
|
88
|
+
}),
|
|
89
|
+
/** Exhaustive: omitting a state is a compile error. */
|
|
90
|
+
match: (data, cases) => {
|
|
91
|
+
switch (data._tag) {
|
|
92
|
+
case "Initial": return cases.Initial();
|
|
93
|
+
case "Loading": return cases.Loading();
|
|
94
|
+
case "Ready": return cases.Ready(data.value);
|
|
95
|
+
case "Refreshing": return cases.Refreshing(data.value);
|
|
96
|
+
case "Failed": return cases.Failed(data.error, data.previous === void 0 ? Option.none() : Option.some(data.previous));
|
|
97
|
+
case "NotFound": return cases.NotFound();
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
/**
|
|
101
|
+
* The view-oriented fold: the six states as the three things a view actually
|
|
102
|
+
* draws, under the policy that useful data stays on screen. A value that is
|
|
103
|
+
* being refetched, or that a later read failed to replace, still reaches
|
|
104
|
+
* `data` — with `freshness` saying which — instead of being replaced by a
|
|
105
|
+
* spinner or an error the reader cannot act on.
|
|
106
|
+
*
|
|
107
|
+
* ```ts
|
|
108
|
+
* RemoteData.render(model.user, {
|
|
109
|
+
* loading: () => UserSkeleton(),
|
|
110
|
+
* notFound: () => NoSuchUser(),
|
|
111
|
+
* failed: error => ErrorView(error),
|
|
112
|
+
* data: (user, freshness) => UserView({ user, dimmed: freshness._tag !== 'Fresh' }),
|
|
113
|
+
* })
|
|
114
|
+
* ```
|
|
115
|
+
*
|
|
116
|
+
* `notFound` is its own branch and not optional: a row the server answered
|
|
117
|
+
* for and does not have is neither loading nor a failure, and drawing it as
|
|
118
|
+
* either is a spinner that never ends or an error nobody can fix.
|
|
119
|
+
*
|
|
120
|
+
* This does not replace `match`, which is the exhaustive fold over the states
|
|
121
|
+
* themselves. Reach for `match` when the six states really do draw
|
|
122
|
+
* differently, and for this when they draw the usual three ways.
|
|
123
|
+
*/
|
|
124
|
+
render: (data, cases) => {
|
|
125
|
+
switch (data._tag) {
|
|
126
|
+
case "Initial":
|
|
127
|
+
case "Loading": return cases.loading();
|
|
128
|
+
case "NotFound": return cases.notFound();
|
|
129
|
+
case "Ready": return cases.data(data.value, fresh);
|
|
130
|
+
case "Refreshing": return cases.data(data.value, refreshing);
|
|
131
|
+
case "Failed": return data.previous === void 0 ? cases.failed(data.error) : cases.data(data.previous, Freshness.Stale({ error: data.error }));
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
/**
|
|
135
|
+
* The denial a failure was, typed, or none for any other failure (a
|
|
136
|
+
* network error, a protocol mismatch): what to read before telling a reader
|
|
137
|
+
* to sign in again.
|
|
138
|
+
*
|
|
139
|
+
* ```ts
|
|
140
|
+
* Option.match(RemoteData.denied(error), {
|
|
141
|
+
* onNone: () => 'Could not load',
|
|
142
|
+
* onSome: Denied.match({ Unauthenticated: () => 'Sign in again', Forbidden: () => 'Not yours' }),
|
|
143
|
+
* })
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
denied: (error) => isDenial(error._tag) ? Option.some(deny[error._tag]({ reason: Option.fromUndefinedOr(error.reason) })) : Option.none(),
|
|
147
|
+
/**
|
|
148
|
+
* Whether nothing answered (`RemoteUnreachable`): the request was not sent,
|
|
149
|
+
* or what came back was no Remote answer, so a write may or may not have
|
|
150
|
+
* run. A failure the server answered (a refusal, a denial) is not one.
|
|
151
|
+
*/
|
|
152
|
+
isUnreachable: (error) => error._tag === unreachable,
|
|
153
|
+
map: (data, f) => {
|
|
154
|
+
switch (data._tag) {
|
|
155
|
+
case "Ready": return {
|
|
156
|
+
_tag: "Ready",
|
|
157
|
+
value: f(data.value)
|
|
158
|
+
};
|
|
159
|
+
case "Refreshing": return {
|
|
160
|
+
_tag: "Refreshing",
|
|
161
|
+
value: f(data.value)
|
|
162
|
+
};
|
|
163
|
+
case "Failed": return data.previous === void 0 ? {
|
|
164
|
+
_tag: "Failed",
|
|
165
|
+
error: data.error
|
|
166
|
+
} : {
|
|
167
|
+
_tag: "Failed",
|
|
168
|
+
error: data.error,
|
|
169
|
+
previous: f(data.previous)
|
|
170
|
+
};
|
|
171
|
+
case "Initial":
|
|
172
|
+
case "Loading":
|
|
173
|
+
case "NotFound": return data;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
export { Denied, Freshness, RemoteData, remoteDataSchema, remoteErrorSchema };
|
|
178
|
+
|
|
179
|
+
//# sourceMappingURL=remoteData.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remoteData.js","names":[],"sources":["../src/remoteData.ts"],"sourcesContent":["/** `RemoteData`: the state of a value the store may not hold yet. */\nimport { Option, Schema } from 'effect'\nimport { defineTaggedUnion } from 'foldkit/schema'\nimport type { RemoteUnreachable } from './wire.js'\n\nexport interface RemoteError {\n readonly _tag: string\n readonly message: string\n /**\n * A refused mutation's refusal, encoded as the server sent it. Read it\n * decoded, by its mutation, with `Data.refusal`.\n */\n readonly refusal?: unknown\n /**\n * Why the server refused who is asking (`_tag: 'Unauthenticated'` or\n * `'Forbidden'`), when it said. Read it with `RemoteData.denied`.\n */\n readonly reason?: string | undefined\n}\n\n/**\n * The server would not answer who is asking: their session ended or was\n * never there (`Unauthenticated`, a 401), or they may not (`Forbidden`, a\n * 403). `reason` is the server's sentence, when it gave one. Not a mutation's\n * declared `refusal`, which the server answered and `Data.refusal` reads.\n */\nexport const Denied = defineTaggedUnion({\n Unauthenticated: { reason: Schema.Option(Schema.String) },\n Forbidden: { reason: Schema.Option(Schema.String) },\n})\nexport type Denied = typeof Denied.Type\n\n/** The tags `denied` reads: a `RemoteError`'s `_tag` is open, for any failure a transport reports. */\nconst denials: ReadonlySet<string> = new Set<Denied['_tag']>(['Unauthenticated', 'Forbidden'])\nconst isDenial = (tag: string): tag is Denied['_tag'] => denials.has(tag)\nconst deny = { Unauthenticated: Denied.Unauthenticated, Forbidden: Denied.Forbidden }\n\n/** Typed by the error class, so renaming its tag is a type error here. */\nconst unreachable: RemoteUnreachable['_tag'] = 'Unreachable'\n\nexport type RemoteData<A> =\n | { readonly _tag: 'Initial' }\n | { readonly _tag: 'Loading' }\n | { readonly _tag: 'Ready'; readonly value: A }\n | { readonly _tag: 'Refreshing'; readonly value: A }\n | { readonly _tag: 'Failed'; readonly error: RemoteError; readonly previous?: A }\n | { readonly _tag: 'NotFound' }\n\nexport const remoteErrorSchema = Schema.Struct({\n _tag: Schema.String,\n message: Schema.String,\n refusal: Schema.optional(Schema.Unknown),\n reason: Schema.optional(Schema.String),\n})\n\n/**\n * What `RemoteData.render` tells the `data` branch about the value it is\n * given: whether a newer answer is on its way, and whether this one is what\n * was last known good before a read failed. It is one tag rather than two\n * booleans because a value cannot be both at once, and a type that can say so\n * invites a view to handle a state that never arrives. Fold it with\n * `Freshness.match`.\n */\nexport const Freshness = defineTaggedUnion({\n Fresh: {},\n Refreshing: {},\n Stale: { error: remoteErrorSchema },\n})\nexport type Freshness = typeof Freshness.Type\n\nconst fresh = Freshness.Fresh()\nconst refreshing = Freshness.Refreshing()\n\n/** A `RemoteData` schema, so a projection that reads remote state is typed. */\n// A projection is rebuilt on every Model change (a Surface's `model` callback\n// runs per dependency computation), so the schema for a value schema is built\n// once and shared: the value schema is a module-level constant in practice.\nconst remoteDataSchemas = new WeakMap<object, Schema.Schema<unknown>>()\n\nexport const remoteDataSchema = <A>(\n value: Schema.Schema<A>,\n): Schema.Codec<RemoteData<A>, unknown> => {\n const cached = remoteDataSchemas.get(value)\n if (cached !== undefined) return cached as Schema.Codec<RemoteData<A>, unknown>\n const built = Schema.Union([\n Schema.Struct({ _tag: Schema.Literal('Initial') }),\n Schema.Struct({ _tag: Schema.Literal('Loading') }),\n Schema.Struct({ _tag: Schema.Literal('Ready'), value }),\n Schema.Struct({ _tag: Schema.Literal('Refreshing'), value }),\n Schema.Struct({\n _tag: Schema.Literal('Failed'),\n error: remoteErrorSchema,\n previous: Schema.optional(value),\n }),\n Schema.Struct({ _tag: Schema.Literal('NotFound') }),\n ]) as unknown as Schema.Codec<RemoteData<A>, unknown>\n remoteDataSchemas.set(value, built as Schema.Schema<unknown>)\n return built\n}\n\nexport const RemoteData = {\n /**\n * The `RemoteData` schema for a value schema. Exposed so a `RemoteData` can\n * be embedded in a hand-written Model schema, not only through `Remote.select`.\n */\n schema: <A>(value: Schema.Schema<A>): Schema.Schema<RemoteData<A>> => remoteDataSchema(value),\n\n /**\n * Whether the read has an answer: `Ready`, `Refreshing`, `Failed` or\n * `NotFound`. `Initial` and `Loading` are still waiting for one.\n */\n answered: <A>(data: RemoteData<A>): boolean =>\n RemoteData.match(data, {\n Initial: () => false,\n Loading: () => false,\n Ready: () => true,\n Refreshing: () => true,\n Failed: () => true,\n NotFound: () => true,\n }),\n\n /** Exhaustive: omitting a state is a compile error. */\n match: <A, R>(\n data: RemoteData<A>,\n cases: {\n readonly Initial: () => R\n readonly Loading: () => R\n readonly Ready: (value: A) => R\n readonly Refreshing: (value: A) => R\n readonly Failed: (error: RemoteError, previous: Option.Option<A>) => R\n readonly NotFound: () => R\n },\n ): R => {\n switch (data._tag) {\n case 'Initial':\n return cases.Initial()\n case 'Loading':\n return cases.Loading()\n case 'Ready':\n return cases.Ready(data.value)\n case 'Refreshing':\n return cases.Refreshing(data.value)\n case 'Failed':\n return cases.Failed(\n data.error,\n data.previous === undefined ? Option.none() : Option.some(data.previous),\n )\n case 'NotFound':\n return cases.NotFound()\n }\n },\n\n /**\n * The view-oriented fold: the six states as the three things a view actually\n * draws, under the policy that useful data stays on screen. A value that is\n * being refetched, or that a later read failed to replace, still reaches\n * `data` — with `freshness` saying which — instead of being replaced by a\n * spinner or an error the reader cannot act on.\n *\n * ```ts\n * RemoteData.render(model.user, {\n * loading: () => UserSkeleton(),\n * notFound: () => NoSuchUser(),\n * failed: error => ErrorView(error),\n * data: (user, freshness) => UserView({ user, dimmed: freshness._tag !== 'Fresh' }),\n * })\n * ```\n *\n * `notFound` is its own branch and not optional: a row the server answered\n * for and does not have is neither loading nor a failure, and drawing it as\n * either is a spinner that never ends or an error nobody can fix.\n *\n * This does not replace `match`, which is the exhaustive fold over the states\n * themselves. Reach for `match` when the six states really do draw\n * differently, and for this when they draw the usual three ways.\n */\n render: <A, R>(\n data: RemoteData<A>,\n cases: {\n readonly loading: () => R\n readonly notFound: () => R\n readonly failed: (error: RemoteError) => R\n readonly data: (value: A, freshness: Freshness) => R\n },\n ): R => {\n switch (data._tag) {\n case 'Initial':\n case 'Loading':\n return cases.loading()\n case 'NotFound':\n return cases.notFound()\n case 'Ready':\n return cases.data(data.value, fresh)\n case 'Refreshing':\n return cases.data(data.value, refreshing)\n case 'Failed':\n return data.previous === undefined\n ? cases.failed(data.error)\n : cases.data(data.previous, Freshness.Stale({ error: data.error }))\n }\n },\n\n /**\n * The denial a failure was, typed, or none for any other failure (a\n * network error, a protocol mismatch): what to read before telling a reader\n * to sign in again.\n *\n * ```ts\n * Option.match(RemoteData.denied(error), {\n * onNone: () => 'Could not load',\n * onSome: Denied.match({ Unauthenticated: () => 'Sign in again', Forbidden: () => 'Not yours' }),\n * })\n * ```\n */\n denied: (error: RemoteError): Option.Option<Denied> =>\n // tag-check: open — a RemoteError carries any transport failure's tag.\n isDenial(error._tag)\n ? Option.some(deny[error._tag]({ reason: Option.fromUndefinedOr(error.reason) }))\n : Option.none(),\n\n /**\n * Whether nothing answered (`RemoteUnreachable`): the request was not sent,\n * or what came back was no Remote answer, so a write may or may not have\n * run. A failure the server answered (a refusal, a denial) is not one.\n */\n isUnreachable: (error: RemoteError): boolean =>\n // tag-check: open — a RemoteError carries any transport failure's tag.\n error._tag === unreachable,\n\n map: <A, B>(data: RemoteData<A>, f: (value: A) => B): RemoteData<B> => {\n switch (data._tag) {\n case 'Ready':\n return { _tag: 'Ready', value: f(data.value) }\n case 'Refreshing':\n return { _tag: 'Refreshing', value: f(data.value) }\n case 'Failed':\n return data.previous === undefined\n ? { _tag: 'Failed', error: data.error }\n : { _tag: 'Failed', error: data.error, previous: f(data.previous) }\n case 'Initial':\n case 'Loading':\n case 'NotFound':\n return data\n }\n },\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { i as RemoteMessage, s as RemoteModel } from "./model-B6zk1NMB.js";
|
|
2
|
+
import { Focus } from "foldkit-surface";
|
|
3
|
+
import * as Update from "foldkit/update";
|
|
4
|
+
/**
|
|
5
|
+
* `reduce`, and the Command reporting each diagnostic it recorded. `heard`
|
|
6
|
+
* says whether the application's own `update` receives `MutationFailed`;
|
|
7
|
+
* when it does not, a pending write's failure is recorded as `WriteUnheard`.
|
|
8
|
+
*/
|
|
9
|
+
export declare const reduceReporting: <AppModel, Store extends RemoteModel, Message extends {
|
|
10
|
+
readonly _tag: string;
|
|
11
|
+
}>(store: Focus<AppModel, Store>, reduce: (model: AppModel, message: Message) => AppModel, model: AppModel, message: Message, heard: boolean) => Update.Return<AppModel, RemoteMessage>;
|
|
12
|
+
//# sourceMappingURL=reporting.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reporting.d.ts","names":[],"sources":["../src/reporting.ts"],"mappings":";;;;;;;;qBAyCa,kBACX,UACA,cAAc,aACd;WAA2B;GAAc,OAElC,MAAM,UAAU,QAAM,SACpB,OAAO,UAAU,SAAS,YAAY,UAAQ,OAChD,UAAQ,SACN,SAAO,mBAEf,OAAO,OAAO,UAAU"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { withDiagnostics, writeUnheard } from "./diagnostics.js";
|
|
2
|
+
import { Effect, Option } from "effect";
|
|
3
|
+
import { Diagnostics } from "foldkit-surface";
|
|
4
|
+
// src/reporting.ts
|
|
5
|
+
/**
|
|
6
|
+
* Reporting what a reduction recorded. The reducer stays pure and only
|
|
7
|
+
* records in `RemoteModel.diagnostics`; the wiring's route and `Remote.fold`,
|
|
8
|
+
* which return Commands, report what it added through `Diagnostics.report`.
|
|
9
|
+
*/
|
|
10
|
+
const completed = { _tag: "CompletedReportDiagnostics" };
|
|
11
|
+
const reportDiagnostics = (diagnostics) => ({
|
|
12
|
+
name: "ReportDiagnostics",
|
|
13
|
+
effect: Effect.as(Effect.forEach(diagnostics, Diagnostics.report, { discard: true }), completed)
|
|
14
|
+
});
|
|
15
|
+
/** The failed write `message` settles, when it was pending: one the application did not hear. */
|
|
16
|
+
const unheardOf = (before, message) => message._tag === "MutationFailed" && before.mutations.pending.has(message.requestId) ? [writeUnheard({
|
|
17
|
+
requestId: message.requestId,
|
|
18
|
+
mutation: Option.map(Option.fromUndefinedOr(before.mutations.writes.get(message.requestId)), (write) => write.mutation),
|
|
19
|
+
error: message.error
|
|
20
|
+
})] : [];
|
|
21
|
+
/**
|
|
22
|
+
* `reduce`, and the Command reporting each diagnostic it recorded. `heard`
|
|
23
|
+
* says whether the application's own `update` receives `MutationFailed`;
|
|
24
|
+
* when it does not, a pending write's failure is recorded as `WriteUnheard`.
|
|
25
|
+
*/
|
|
26
|
+
const reduceReporting = (store, reduce, model, message, heard) => {
|
|
27
|
+
const before = store.get(model);
|
|
28
|
+
const reduced = reduce(model, message);
|
|
29
|
+
const unheard = heard ? [] : unheardOf(before, message);
|
|
30
|
+
const next = unheard.length === 0 ? reduced : store.modify(reduced, (remote) => ({
|
|
31
|
+
...remote,
|
|
32
|
+
diagnostics: withDiagnostics(remote.diagnostics, unheard)
|
|
33
|
+
}));
|
|
34
|
+
const recorded = store.get(next).diagnostics;
|
|
35
|
+
if (recorded === before.diagnostics) return { model: next };
|
|
36
|
+
const known = new Set(before.diagnostics);
|
|
37
|
+
const news = recorded.filter((diagnostic) => !known.has(diagnostic));
|
|
38
|
+
return news.length === 0 ? { model: next } : {
|
|
39
|
+
model: next,
|
|
40
|
+
commands: [reportDiagnostics(news)]
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
export { reduceReporting };
|
|
44
|
+
|
|
45
|
+
//# sourceMappingURL=reporting.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reporting.js","names":[],"sources":["../src/reporting.ts"],"sourcesContent":["/**\n * Reporting what a reduction recorded. The reducer stays pure and only\n * records in `RemoteModel.diagnostics`; the wiring's route and `Remote.fold`,\n * which return Commands, report what it added through `Diagnostics.report`.\n */\nimport { Effect, Option } from 'effect'\nimport type { Command } from 'foldkit/command'\nimport type * as Update from 'foldkit/update'\nimport { Diagnostics, type Focus } from 'foldkit-surface'\nimport { withDiagnostics, writeUnheard, type RemoteDiagnostic } from './diagnostics.js'\nimport type { RemoteMessage, RemoteModel } from './model.js'\n\nconst completed: RemoteMessage = { _tag: 'CompletedReportDiagnostics' }\n\nconst reportDiagnostics = (\n diagnostics: ReadonlyArray<RemoteDiagnostic>,\n): Command<RemoteMessage> => ({\n name: 'ReportDiagnostics',\n effect: Effect.as(Effect.forEach(diagnostics, Diagnostics.report, { discard: true }), completed),\n})\n\n/** The failed write `message` settles, when it was pending: one the application did not hear. */\nconst unheardOf = (before: RemoteModel, message: RemoteMessage): ReadonlyArray<RemoteDiagnostic> =>\n message._tag === 'MutationFailed' && before.mutations.pending.has(message.requestId)\n ? [\n writeUnheard({\n requestId: message.requestId,\n mutation: Option.map(\n Option.fromUndefinedOr(before.mutations.writes.get(message.requestId)),\n write => write.mutation,\n ),\n error: message.error,\n }),\n ]\n : []\n\n/**\n * `reduce`, and the Command reporting each diagnostic it recorded. `heard`\n * says whether the application's own `update` receives `MutationFailed`;\n * when it does not, a pending write's failure is recorded as `WriteUnheard`.\n */\nexport const reduceReporting = <\n AppModel,\n Store extends RemoteModel,\n Message extends { readonly _tag: string },\n>(\n store: Focus<AppModel, Store>,\n reduce: (model: AppModel, message: Message) => AppModel,\n model: AppModel,\n message: Message,\n heard: boolean,\n): Update.Return<AppModel, RemoteMessage> => {\n const before = store.get(model)\n const reduced = reduce(model, message)\n // An application-union case has the runtime shape of the `RemoteMessage` it names.\n const unheard = heard ? [] : unheardOf(before, message as unknown as RemoteMessage)\n // Still the reduction of this Message into Remote's own slice, which `reduce`\n // writes through the same `store`: not an application transition.\n const next =\n unheard.length === 0\n ? reduced\n : store.modify(reduced, remote => ({\n ...remote,\n diagnostics: withDiagnostics(remote.diagnostics, unheard),\n }))\n const recorded = store.get(next).diagnostics\n if (recorded === before.diagnostics) return { model: next }\n // Recorded ones are appended, but `forget` clears them all: compare by identity.\n const known = new Set(before.diagnostics)\n const news = recorded.filter(diagnostic => !known.has(diagnostic))\n return news.length === 0 ? { model: next } : { model: next, commands: [reportDiagnostics(news)] }\n}\n"],"mappings":";;;;;;;;;AAYA,MAAM,YAA2B,EAAE,MAAM,6BAA6B;AAEtE,MAAM,qBACJ,iBAC4B;CAC5B,MAAM;CACN,QAAQ,OAAO,GAAG,OAAO,QAAQ,aAAa,YAAY,QAAQ,EAAE,SAAS,KAAK,CAAC,GAAG,SAAS;AACjG;;AAGA,MAAM,aAAa,QAAqB,YACtC,QAAQ,SAAS,oBAAoB,OAAO,UAAU,QAAQ,IAAI,QAAQ,SAAS,IAC/E,CACE,aAAa;CACX,WAAW,QAAQ;CACnB,UAAU,OAAO,IACf,OAAO,gBAAgB,OAAO,UAAU,OAAO,IAAI,QAAQ,SAAS,CAAC,IACrE,UAAS,MAAM,QACjB;CACA,OAAO,QAAQ;AACjB,CAAC,CACH,IACA,CAAC;;;;;;AAOP,MAAa,mBAKX,OACA,QACA,OACA,SACA,UAC2C;CAC3C,MAAM,SAAS,MAAM,IAAI,KAAK;CAC9B,MAAM,UAAU,OAAO,OAAO,OAAO;CAErC,MAAM,UAAU,QAAQ,CAAC,IAAI,UAAU,QAAQ,OAAmC;CAGlF,MAAM,OACJ,QAAQ,WAAW,IACf,UACA,MAAM,OAAO,UAAS,YAAW;EAC/B,GAAG;EACH,aAAa,gBAAgB,OAAO,aAAa,OAAO;CAC1D,EAAE;CACR,MAAM,WAAW,MAAM,IAAI,IAAI,CAAC,CAAC;CACjC,IAAI,aAAa,OAAO,aAAa,OAAO,EAAE,OAAO,KAAK;CAE1D,MAAM,QAAQ,IAAI,IAAI,OAAO,WAAW;CACxC,MAAM,OAAO,SAAS,QAAO,eAAc,CAAC,MAAM,IAAI,UAAU,CAAC;CACjE,OAAO,KAAK,WAAW,IAAI,EAAE,OAAO,KAAK,IAAI;EAAE,OAAO;EAAM,UAAU,CAAC,kBAAkB,IAAI,CAAC;CAAE;AAClG"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { l as QueryWindow } from "./query-BuIU93xA.js";
|
|
2
|
+
import { Metadata } from "foldkit-surface";
|
|
3
|
+
/**
|
|
4
|
+
* The slice required of an entity. `windows` carries a pagination window per
|
|
5
|
+
* relation field, and `relations` the slice required of each relation's
|
|
6
|
+
* target, so one requirement describes a whole selection graph.
|
|
7
|
+
*/
|
|
8
|
+
interface RelationRequirement {
|
|
9
|
+
readonly entity: string;
|
|
10
|
+
readonly fields: readonly string[];
|
|
11
|
+
readonly windows?: Readonly<Record<string, QueryWindow>> | undefined;
|
|
12
|
+
readonly relations?: Readonly<Record<string, RelationRequirement>> | undefined;
|
|
13
|
+
}
|
|
14
|
+
interface Requirement extends RelationRequirement {
|
|
15
|
+
readonly id: string;
|
|
16
|
+
/** The projection also subscribes to changes of this entity (`Data.live`). */
|
|
17
|
+
readonly live?: boolean | undefined;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* A query connection a projection reads: the page it first asks for and the
|
|
21
|
+
* slice it selects of each item. `identity` is the connection's (query plus
|
|
22
|
+
* canonical input, excluding the window).
|
|
23
|
+
*/
|
|
24
|
+
interface ConnectionRequirement {
|
|
25
|
+
readonly identity: string;
|
|
26
|
+
readonly window: QueryWindow;
|
|
27
|
+
readonly select: RelationRequirement;
|
|
28
|
+
/** The projection also watches the list live (`Data.query(…, { live: true })`). */
|
|
29
|
+
readonly live?: boolean | undefined;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Merges connection requirements for the same connection and window into one,
|
|
33
|
+
* unioning what they select of each item, live when either is; the first keeps
|
|
34
|
+
* its other properties.
|
|
35
|
+
*/
|
|
36
|
+
declare function mergeConnections(connections: readonly ConnectionRequirement[]): readonly ConnectionRequirement[];
|
|
37
|
+
/** Unions two relation slices for the same target: fields, windows, and nested relations. */
|
|
38
|
+
declare function mergeRelation(current: RelationRequirement, next: RelationRequirement): RelationRequirement;
|
|
39
|
+
/** Unions requirements for the same entity + id, dropping duplicate fields. */
|
|
40
|
+
declare function mergeRequirements(requirements: readonly Requirement[]): readonly Requirement[];
|
|
41
|
+
/**
|
|
42
|
+
* Requirement helpers, for the packages that plan and serve requirements.
|
|
43
|
+
* `merge` unions same-entity+id requirements; `mergeRelation` unions two
|
|
44
|
+
* slices of one target (fields, windows, nested relations).
|
|
45
|
+
*/
|
|
46
|
+
declare const Requirement: {
|
|
47
|
+
merge: typeof mergeRequirements;
|
|
48
|
+
mergeRelation: typeof mergeRelation;
|
|
49
|
+
mergeConnections: typeof mergeConnections;
|
|
50
|
+
};
|
|
51
|
+
/** Remote's entity requirements on a Projection, unioned per entity and id as nodes compose. */
|
|
52
|
+
declare const RemoteRequirements: import("foldkit-metadata").MetadataKey<Requirement>;
|
|
53
|
+
/** Remote's query connections on a Projection, unioned per connection and window. */
|
|
54
|
+
declare const RemoteConnections: import("foldkit-metadata").MetadataKey<ConnectionRequirement>;
|
|
55
|
+
/** The entity requirements a Projection carries. */
|
|
56
|
+
declare const requirementsOf: (projection: {
|
|
57
|
+
readonly metadata: Metadata;
|
|
58
|
+
}) => ReadonlyArray<Requirement>;
|
|
59
|
+
/** The query connections a Projection carries. */
|
|
60
|
+
declare const connectionsOf: (projection: {
|
|
61
|
+
readonly metadata: Metadata;
|
|
62
|
+
}) => ReadonlyArray<ConnectionRequirement>;
|
|
63
|
+
export { Requirement as a, RemoteRequirements as i, RelationRequirement as n, connectionsOf as o, RemoteConnections as r, requirementsOf as s, ConnectionRequirement as t };
|
|
64
|
+
//# sourceMappingURL=requirement-D57S2LpH.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requirement-D57S2LpH.d.ts","names":[],"sources":["../src/requirement.ts"],"mappings":";;;;;;;UAciB;WACN;WACA;WACA,UAAU,SAAS,eAAe;WAClC,YAAY,SAAS,eAAe;;UAG9B,oBAAoB;WAC1B;;WAEA;;;;;;;UAQM;WACN;WACA,QAAQ;WACR,QAAQ;;WAER;;;;;;;iBAgBF,iBACP,sBAAsB,mCACZ;;iBAoBH,cACP,SAAS,qBACT,MAAM,sBACL;;iBAkCM,kBAAkB,uBAAuB,yBAAyB;;;;;;cAqB9D;;;;;;cAOA,+CAAkB,YAAA;;cAMlB,8CAAiB,YAAA;;cAMjB,iBAAc;WAChB,UAAU;MACjB,cAAc;;cAGL,gBAAa;WACf,UAAU;MACjB,cAAc"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as Requirement, i as RemoteRequirements, n as RelationRequirement, o as connectionsOf, r as RemoteConnections, s as requirementsOf, t as ConnectionRequirement } from "./requirement-D57S2LpH.js";
|
|
2
|
+
export { ConnectionRequirement, RelationRequirement, RemoteConnections, RemoteRequirements, Requirement, connectionsOf, requirementsOf };
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { own, put } from "./record.js";
|
|
2
|
+
import { Metadata } from "foldkit-surface";
|
|
3
|
+
// src/requirement.ts
|
|
4
|
+
/**
|
|
5
|
+
* What a Remote Projection needs from the server: entity slices and query
|
|
6
|
+
* connections. They ride on `Projection.metadata` under Remote's own keys, so
|
|
7
|
+
* Surface composes them without knowing their shape.
|
|
8
|
+
*/
|
|
9
|
+
const windowKey = (window) => JSON.stringify([
|
|
10
|
+
window.first ?? null,
|
|
11
|
+
window.last ?? null,
|
|
12
|
+
window.after ?? null,
|
|
13
|
+
window.before ?? null
|
|
14
|
+
]);
|
|
15
|
+
/**
|
|
16
|
+
* Merges connection requirements for the same connection and window into one,
|
|
17
|
+
* unioning what they select of each item, live when either is; the first keeps
|
|
18
|
+
* its other properties.
|
|
19
|
+
*/
|
|
20
|
+
function mergeConnections(connections) {
|
|
21
|
+
const merged = /* @__PURE__ */ new Map();
|
|
22
|
+
for (const connection of connections) {
|
|
23
|
+
const key = `${connection.identity}\u0000${windowKey(connection.window)}`;
|
|
24
|
+
const current = merged.get(key);
|
|
25
|
+
if (current === void 0) {
|
|
26
|
+
merged.set(key, connection);
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
const live = current.live === true || connection.live === true;
|
|
30
|
+
merged.set(key, {
|
|
31
|
+
...current,
|
|
32
|
+
select: mergeRelation(current.select, connection.select),
|
|
33
|
+
...live ? { live } : {}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return [...merged.values()];
|
|
37
|
+
}
|
|
38
|
+
/** Unions two relation slices for the same target: fields, windows, and nested relations. */
|
|
39
|
+
function mergeRelation(current, next) {
|
|
40
|
+
const fields = [...current.fields];
|
|
41
|
+
const seen = new Set(fields);
|
|
42
|
+
for (const field of next.fields) {
|
|
43
|
+
if (seen.has(field)) continue;
|
|
44
|
+
seen.add(field);
|
|
45
|
+
fields.push(field);
|
|
46
|
+
}
|
|
47
|
+
const windows = {
|
|
48
|
+
...current.windows,
|
|
49
|
+
...next.windows
|
|
50
|
+
};
|
|
51
|
+
const relations = mergeRelations(current.relations, next.relations);
|
|
52
|
+
return {
|
|
53
|
+
entity: current.entity,
|
|
54
|
+
fields,
|
|
55
|
+
...Object.keys(windows).length === 0 ? {} : { windows },
|
|
56
|
+
...relations === void 0 ? {} : { relations }
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/** Unions two relation maps field by field; a field in both merges recursively. */
|
|
60
|
+
function mergeRelations(current, next) {
|
|
61
|
+
if (current === void 0) return next;
|
|
62
|
+
if (next === void 0) return current;
|
|
63
|
+
const merged = { ...current };
|
|
64
|
+
for (const [field, relation] of Object.entries(next)) {
|
|
65
|
+
const existing = own(merged, field);
|
|
66
|
+
put(merged, field, existing === void 0 ? relation : mergeRelation(existing, relation));
|
|
67
|
+
}
|
|
68
|
+
return merged;
|
|
69
|
+
}
|
|
70
|
+
/** Unions requirements for the same entity + id, dropping duplicate fields. */
|
|
71
|
+
function mergeRequirements(requirements) {
|
|
72
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
73
|
+
for (const requirement of requirements) {
|
|
74
|
+
const key = `${requirement.entity}\u0000${requirement.id}`;
|
|
75
|
+
const group = grouped.get(key);
|
|
76
|
+
const live = group?.live === true || requirement.live === true;
|
|
77
|
+
grouped.set(key, {
|
|
78
|
+
...mergeRelation(group ?? {
|
|
79
|
+
entity: requirement.entity,
|
|
80
|
+
fields: []
|
|
81
|
+
}, requirement),
|
|
82
|
+
id: requirement.id,
|
|
83
|
+
...live ? { live } : {}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return [...grouped.values()];
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Requirement helpers, for the packages that plan and serve requirements.
|
|
90
|
+
* `merge` unions same-entity+id requirements; `mergeRelation` unions two
|
|
91
|
+
* slices of one target (fields, windows, nested relations).
|
|
92
|
+
*/
|
|
93
|
+
const Requirement = {
|
|
94
|
+
merge: mergeRequirements,
|
|
95
|
+
mergeRelation,
|
|
96
|
+
mergeConnections
|
|
97
|
+
};
|
|
98
|
+
/** Remote's entity requirements on a Projection, unioned per entity and id as nodes compose. */
|
|
99
|
+
const RemoteRequirements = Metadata.key("remote", {
|
|
100
|
+
merge: mergeRequirements,
|
|
101
|
+
summarize: (requirement) => `${requirement.entity}:${requirement.id}`
|
|
102
|
+
});
|
|
103
|
+
/** Remote's query connections on a Projection, unioned per connection and window. */
|
|
104
|
+
const RemoteConnections = Metadata.key("remote.connection", {
|
|
105
|
+
merge: mergeConnections,
|
|
106
|
+
summarize: (connection) => connection.identity.split("\0").join(" ")
|
|
107
|
+
});
|
|
108
|
+
/** The entity requirements a Projection carries. */
|
|
109
|
+
const requirementsOf = (projection) => RemoteRequirements.get(projection.metadata);
|
|
110
|
+
/** The query connections a Projection carries. */
|
|
111
|
+
const connectionsOf = (projection) => RemoteConnections.get(projection.metadata);
|
|
112
|
+
export { RemoteConnections, RemoteRequirements, Requirement, connectionsOf, requirementsOf };
|
|
113
|
+
|
|
114
|
+
//# sourceMappingURL=requirement.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requirement.js","names":[],"sources":["../src/requirement.ts"],"sourcesContent":["/**\n * What a Remote Projection needs from the server: entity slices and query\n * connections. They ride on `Projection.metadata` under Remote's own keys, so\n * Surface composes them without knowing their shape.\n */\nimport { Metadata } from 'foldkit-surface'\nimport type { QueryWindow } from './query.js'\nimport { own, put } from './record.js'\n\n/**\n * The slice required of an entity. `windows` carries a pagination window per\n * relation field, and `relations` the slice required of each relation's\n * target, so one requirement describes a whole selection graph.\n */\nexport interface RelationRequirement {\n readonly entity: string\n readonly fields: readonly string[]\n readonly windows?: Readonly<Record<string, QueryWindow>> | undefined\n readonly relations?: Readonly<Record<string, RelationRequirement>> | undefined\n}\n\nexport interface Requirement extends RelationRequirement {\n readonly id: string\n /** The projection also subscribes to changes of this entity (`Data.live`). */\n readonly live?: boolean | undefined\n}\n\n/**\n * A query connection a projection reads: the page it first asks for and the\n * slice it selects of each item. `identity` is the connection's (query plus\n * canonical input, excluding the window).\n */\nexport interface ConnectionRequirement {\n readonly identity: string\n readonly window: QueryWindow\n readonly select: RelationRequirement\n /** The projection also watches the list live (`Data.query(…, { live: true })`). */\n readonly live?: boolean | undefined\n}\n\nconst windowKey = (window: QueryWindow): string =>\n JSON.stringify([\n window.first ?? null,\n window.last ?? null,\n window.after ?? null,\n window.before ?? null,\n ])\n\n/**\n * Merges connection requirements for the same connection and window into one,\n * unioning what they select of each item, live when either is; the first keeps\n * its other properties.\n */\nfunction mergeConnections(\n connections: readonly ConnectionRequirement[],\n): readonly ConnectionRequirement[] {\n const merged = new Map<string, ConnectionRequirement>()\n for (const connection of connections) {\n const key = `${connection.identity}\\u0000${windowKey(connection.window)}`\n const current = merged.get(key)\n if (current === undefined) {\n merged.set(key, connection)\n continue\n }\n const live = current.live === true || connection.live === true\n merged.set(key, {\n ...current,\n select: mergeRelation(current.select, connection.select),\n ...(live ? { live } : {}),\n })\n }\n return [...merged.values()]\n}\n\n/** Unions two relation slices for the same target: fields, windows, and nested relations. */\nfunction mergeRelation(\n current: RelationRequirement,\n next: RelationRequirement,\n): RelationRequirement {\n const fields = [...current.fields]\n const seen = new Set(fields)\n for (const field of next.fields) {\n if (seen.has(field)) continue\n seen.add(field)\n fields.push(field)\n }\n const windows = { ...current.windows, ...next.windows }\n const relations = mergeRelations(current.relations, next.relations)\n return {\n entity: current.entity,\n fields,\n ...(Object.keys(windows).length === 0 ? {} : { windows }),\n ...(relations === undefined ? {} : { relations }),\n }\n}\n\n/** Unions two relation maps field by field; a field in both merges recursively. */\nfunction mergeRelations(\n current: Readonly<Record<string, RelationRequirement>> | undefined,\n next: Readonly<Record<string, RelationRequirement>> | undefined,\n): Readonly<Record<string, RelationRequirement>> | undefined {\n if (current === undefined) return next\n if (next === undefined) return current\n const merged: Record<string, RelationRequirement> = { ...current }\n for (const [field, relation] of Object.entries(next)) {\n const existing = own(merged, field)\n put(merged, field, existing === undefined ? relation : mergeRelation(existing, relation))\n }\n return merged\n}\n\n/** Unions requirements for the same entity + id, dropping duplicate fields. */\nfunction mergeRequirements(requirements: readonly Requirement[]): readonly Requirement[] {\n const grouped = new Map<string, Requirement>()\n for (const requirement of requirements) {\n const key = `${requirement.entity}\\u0000${requirement.id}`\n // Later windows win; a duplicate is a caller bug, not a merge policy.\n const group = grouped.get(key)\n const live = group?.live === true || requirement.live === true\n grouped.set(key, {\n ...mergeRelation(group ?? { entity: requirement.entity, fields: [] }, requirement),\n id: requirement.id,\n ...(live ? { live } : {}),\n })\n }\n return [...grouped.values()]\n}\n\n/**\n * Requirement helpers, for the packages that plan and serve requirements.\n * `merge` unions same-entity+id requirements; `mergeRelation` unions two\n * slices of one target (fields, windows, nested relations).\n */\nexport const Requirement = {\n merge: mergeRequirements,\n mergeRelation,\n mergeConnections,\n}\n\n/** Remote's entity requirements on a Projection, unioned per entity and id as nodes compose. */\nexport const RemoteRequirements = Metadata.key<Requirement>('remote', {\n merge: mergeRequirements,\n summarize: requirement => `${requirement.entity}:${requirement.id}`,\n})\n\n/** Remote's query connections on a Projection, unioned per connection and window. */\nexport const RemoteConnections = Metadata.key<ConnectionRequirement>('remote.connection', {\n merge: mergeConnections,\n summarize: connection => connection.identity.split('\\u0000').join(' '),\n})\n\n/** The entity requirements a Projection carries. */\nexport const requirementsOf = (projection: {\n readonly metadata: Metadata\n}): ReadonlyArray<Requirement> => RemoteRequirements.get(projection.metadata)\n\n/** The query connections a Projection carries. */\nexport const connectionsOf = (projection: {\n readonly metadata: Metadata\n}): ReadonlyArray<ConnectionRequirement> => RemoteConnections.get(projection.metadata)\n"],"mappings":"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"}
|