foldkit 0.121.0 → 0.122.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/dist/asyncData/asyncData.d.ts +405 -0
- package/dist/asyncData/asyncData.d.ts.map +1 -0
- package/dist/asyncData/asyncData.js +415 -0
- package/dist/asyncData/index.d.ts +2 -0
- package/dist/asyncData/index.d.ts.map +1 -0
- package/dist/asyncData/index.js +1 -0
- package/dist/asyncData/public.d.ts +3 -0
- package/dist/asyncData/public.d.ts.map +1 -0
- package/dist/asyncData/public.js +1 -0
- package/dist/html/index.d.ts +3 -3
- package/dist/html/index.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +78 -0
- package/package.json +5 -1
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
import { Array, Function, Match as M, Option, Predicate, Result, Schema as S, } from 'effect';
|
|
2
|
+
import { ts } from '../schema/index.js';
|
|
3
|
+
/** Constructs the `Idle` state. A parameter-free callable Schema, so it can
|
|
4
|
+
* also serve as a Union member. */
|
|
5
|
+
export const Idle = ts('Idle');
|
|
6
|
+
/** Constructs the `Loading` state. A parameter-free callable Schema, so it
|
|
7
|
+
* can also serve as a Union member. */
|
|
8
|
+
export const Loading = ts('Loading');
|
|
9
|
+
/** Constructs a `Refreshing` state holding the previous good data. Plain
|
|
10
|
+
* value builder, generic in `A`; use the `Schema` factory's `Refreshing`
|
|
11
|
+
* for a Schema-bound constructor. */
|
|
12
|
+
export const Refreshing = (payload) => ({
|
|
13
|
+
_tag: 'Refreshing',
|
|
14
|
+
data: payload.data,
|
|
15
|
+
});
|
|
16
|
+
/** Constructs a `Failure` state carrying the error only. Plain value
|
|
17
|
+
* builder, generic in `E`; use the `Schema` factory's `Failure` for a
|
|
18
|
+
* Schema-bound constructor. */
|
|
19
|
+
export const Failure = (payload) => ({
|
|
20
|
+
_tag: 'Failure',
|
|
21
|
+
error: payload.error,
|
|
22
|
+
});
|
|
23
|
+
/** Constructs a `Stale` state carrying both the refresh error and the
|
|
24
|
+
* last good data. Plain value builder, generic in `A` and `E`; use the
|
|
25
|
+
* `Schema` factory's `Stale` for a Schema-bound constructor. */
|
|
26
|
+
export const Stale = (payload) => ({
|
|
27
|
+
_tag: 'Stale',
|
|
28
|
+
error: payload.error,
|
|
29
|
+
data: payload.data,
|
|
30
|
+
});
|
|
31
|
+
/** Constructs a `Success` state holding the data. Plain value builder,
|
|
32
|
+
* generic in `A`; use the `Schema` factory's `Success` for a Schema-bound
|
|
33
|
+
* constructor. */
|
|
34
|
+
export const Success = (payload) => ({
|
|
35
|
+
_tag: 'Success',
|
|
36
|
+
data: payload.data,
|
|
37
|
+
});
|
|
38
|
+
/** Bare-value alias for `Success({ data })`, mirroring `Result.succeed`. */
|
|
39
|
+
export const succeed = (data) => Success({ data });
|
|
40
|
+
/** Bare-value alias for `Failure({ error })`, mirroring `Result.fail`. */
|
|
41
|
+
export const fail = (error) => Failure({ error });
|
|
42
|
+
/** Builds the six-state `AsyncData` Schema for the given data and error
|
|
43
|
+
* Schemas (value-first). Put `schema` in your Model; use the returned
|
|
44
|
+
* constructors when you want ones typed to this instance's `A` and `E`.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* import { AsyncData } from 'foldkit'
|
|
49
|
+
* import { Schema as S } from 'effect'
|
|
50
|
+
*
|
|
51
|
+
* const Note = S.Struct({ id: S.String, body: S.String })
|
|
52
|
+
* const Notes = AsyncData.Schema(S.Array(Note), S.String)
|
|
53
|
+
*
|
|
54
|
+
* // Model field: typeof Notes.schema.Type
|
|
55
|
+
* const initial = AsyncData.Idle()
|
|
56
|
+
* const loaded = Notes.Success({ data: [] })
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export const Schema = (dataSchema, errorSchema) => {
|
|
60
|
+
const RefreshingSchema = ts('Refreshing', { data: dataSchema });
|
|
61
|
+
const FailureSchema = ts('Failure', { error: errorSchema });
|
|
62
|
+
const StaleSchema = ts('Stale', { error: errorSchema, data: dataSchema });
|
|
63
|
+
const SuccessSchema = ts('Success', { data: dataSchema });
|
|
64
|
+
const schema = S.Union([
|
|
65
|
+
Idle,
|
|
66
|
+
Loading,
|
|
67
|
+
RefreshingSchema,
|
|
68
|
+
FailureSchema,
|
|
69
|
+
StaleSchema,
|
|
70
|
+
SuccessSchema,
|
|
71
|
+
]);
|
|
72
|
+
return {
|
|
73
|
+
schema,
|
|
74
|
+
Idle,
|
|
75
|
+
Loading,
|
|
76
|
+
Refreshing: RefreshingSchema,
|
|
77
|
+
Failure: FailureSchema,
|
|
78
|
+
Stale: StaleSchema,
|
|
79
|
+
Success: SuccessSchema,
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
// OPERATION
|
|
83
|
+
/** Handles all six states exhaustively, passing each handler its unwrapped
|
|
84
|
+
* payload. `onStale` alone receives the whole `{ error, data }` payload
|
|
85
|
+
* object because `Stale` carries two fields. Use `matchData` when the view
|
|
86
|
+
* does not care which of the data-bearing states it is rendering.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* import { AsyncData } from 'foldkit'
|
|
91
|
+
*
|
|
92
|
+
* AsyncData.match(notes, {
|
|
93
|
+
* onIdle: () => 'Not loaded',
|
|
94
|
+
* onLoading: () => 'Loading',
|
|
95
|
+
* onRefreshing: notes => `Refreshing ${notes.length} notes`,
|
|
96
|
+
* onFailure: error => `Failed: ${error}`,
|
|
97
|
+
* onStale: ({ error, data }) => `${data.length} notes (stale: ${error})`,
|
|
98
|
+
* onSuccess: notes => `${notes.length} notes`,
|
|
99
|
+
* })
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
// NOTE: match, matchData, matchDataSplit, getData, and getError use
|
|
103
|
+
// refinement chains instead of Match because tagsExhaustive returns
|
|
104
|
+
// Unify<B>, which does not reduce when the handlers return a caller's
|
|
105
|
+
// naked generic in effect 4.0.0-beta.88. Combinators whose handlers
|
|
106
|
+
// return concrete AsyncData shapes use Match as usual.
|
|
107
|
+
export const match = Function.dual(2, (self, handlers) => {
|
|
108
|
+
if (isIdle(self)) {
|
|
109
|
+
return handlers.onIdle();
|
|
110
|
+
}
|
|
111
|
+
if (isLoading(self)) {
|
|
112
|
+
return handlers.onLoading();
|
|
113
|
+
}
|
|
114
|
+
if (isRefreshing(self)) {
|
|
115
|
+
return handlers.onRefreshing(self.data);
|
|
116
|
+
}
|
|
117
|
+
if (isFailure(self)) {
|
|
118
|
+
return handlers.onFailure(self.error);
|
|
119
|
+
}
|
|
120
|
+
if (isStale(self)) {
|
|
121
|
+
return handlers.onStale({ error: self.error, data: self.data });
|
|
122
|
+
}
|
|
123
|
+
return handlers.onSuccess(self.data);
|
|
124
|
+
});
|
|
125
|
+
/** Collapses the six states to the three channels a view usually renders:
|
|
126
|
+
* `onData` spans the data-bearing states (`Success`, `Refreshing`,
|
|
127
|
+
* `Stale`), `onFailure` receives the `Failure` error, and `onEmpty` covers
|
|
128
|
+
* `Idle` and `Loading` together. A `Stale` renders through `onData` so its
|
|
129
|
+
* data stays on screen. Use `matchDataSplit` when `Idle` and `Loading`
|
|
130
|
+
* render differently, and `match` when the stale error or the `Refreshing`
|
|
131
|
+
* signal matters. */
|
|
132
|
+
export const matchData = Function.dual(2, (self, handlers) => {
|
|
133
|
+
const maybeData = getData(self);
|
|
134
|
+
if (Option.isSome(maybeData)) {
|
|
135
|
+
return handlers.onData(maybeData.value);
|
|
136
|
+
}
|
|
137
|
+
if (isFailure(self)) {
|
|
138
|
+
return handlers.onFailure(self.error);
|
|
139
|
+
}
|
|
140
|
+
return handlers.onEmpty();
|
|
141
|
+
});
|
|
142
|
+
/** Like `matchData`, but the two cold states are split: `onIdle` handles
|
|
143
|
+
* `Idle` and `onLoading` handles `Loading`, for views that render nothing
|
|
144
|
+
* requested yet differently from a request in flight. `onData` and
|
|
145
|
+
* `onFailure` behave exactly as in `matchData`. */
|
|
146
|
+
export const matchDataSplit = Function.dual(2, (self, handlers) => {
|
|
147
|
+
const maybeData = getData(self);
|
|
148
|
+
if (Option.isSome(maybeData)) {
|
|
149
|
+
return handlers.onData(maybeData.value);
|
|
150
|
+
}
|
|
151
|
+
if (isFailure(self)) {
|
|
152
|
+
return handlers.onFailure(self.error);
|
|
153
|
+
}
|
|
154
|
+
if (isLoading(self)) {
|
|
155
|
+
return handlers.onLoading();
|
|
156
|
+
}
|
|
157
|
+
return handlers.onIdle();
|
|
158
|
+
});
|
|
159
|
+
/** Maps the data of all three data-bearing states, preserving each tag so
|
|
160
|
+
* the `Refreshing` and `Stale` signals survive a pure transform. `Stale`
|
|
161
|
+
* maps only its `data`, keeping its `error`. The no-data states pass
|
|
162
|
+
* through unchanged. */
|
|
163
|
+
export const map = Function.dual(2, (self, f) => M.value(self).pipe(M.withReturnType(), M.tagsExhaustive({
|
|
164
|
+
Idle: idle => idle,
|
|
165
|
+
Loading: loading => loading,
|
|
166
|
+
Refreshing: ({ data }) => Refreshing({ data: f(data) }),
|
|
167
|
+
Failure: failure => failure,
|
|
168
|
+
Stale: ({ error, data }) => Stale({ error, data: f(data) }),
|
|
169
|
+
Success: ({ data }) => Success({ data: f(data) }),
|
|
170
|
+
})));
|
|
171
|
+
/** Maps the error of the two error-bearing states: `Failure` and `Stale`
|
|
172
|
+
* transform (`Stale` keeps its `data`), everything else passes through.
|
|
173
|
+
* Use it to unify heterogeneous error types before a combine. */
|
|
174
|
+
export const mapError = Function.dual(2, (self, f) => M.value(self).pipe(M.withReturnType(), M.tagsExhaustive({
|
|
175
|
+
Idle: idle => idle,
|
|
176
|
+
Loading: loading => loading,
|
|
177
|
+
Refreshing: refreshing => refreshing,
|
|
178
|
+
Failure: ({ error }) => Failure({ error: f(error) }),
|
|
179
|
+
Stale: ({ error, data }) => Stale({ error: f(error), data }),
|
|
180
|
+
Success: success => success,
|
|
181
|
+
})));
|
|
182
|
+
/** Maps both channels with channel-named handlers: `onData` spans `Success`,
|
|
183
|
+
* `Refreshing`, and `Stale`; `onError` spans `Failure` and `Stale`. For
|
|
184
|
+
* `Stale`, both handlers apply. Tags are preserved. */
|
|
185
|
+
export const mapBoth = Function.dual(2, (self, handlers) => M.value(self).pipe(M.withReturnType(), M.tagsExhaustive({
|
|
186
|
+
Idle: idle => idle,
|
|
187
|
+
Loading: loading => loading,
|
|
188
|
+
Refreshing: ({ data }) => Refreshing({ data: handlers.onData(data) }),
|
|
189
|
+
Failure: ({ error }) => Failure({ error: handlers.onError(error) }),
|
|
190
|
+
Stale: ({ error, data }) => Stale({
|
|
191
|
+
error: handlers.onError(error),
|
|
192
|
+
data: handlers.onData(data),
|
|
193
|
+
}),
|
|
194
|
+
Success: ({ data }) => Success({ data: handlers.onData(data) }),
|
|
195
|
+
})));
|
|
196
|
+
/** Treats every data-bearing state (`Success`, `Refreshing`, `Stale`)
|
|
197
|
+
* exactly like `Success(data)`: returns `f(data)` unchanged, dropping the
|
|
198
|
+
* tag and any `Stale` error, so a caller can settle in-flight or stale data
|
|
199
|
+
* into `Success`. The no-data states pass through. Widens the error channel
|
|
200
|
+
* to `E | E2`, matching `Result.flatMap`. */
|
|
201
|
+
export const flatMap = Function.dual(2, (self, f) => M.value(self).pipe(M.withReturnType(), M.tagsExhaustive({
|
|
202
|
+
Idle: idle => idle,
|
|
203
|
+
Loading: loading => loading,
|
|
204
|
+
Refreshing: ({ data }) => f(data),
|
|
205
|
+
Failure: failure => failure,
|
|
206
|
+
Stale: ({ data }) => f(data),
|
|
207
|
+
Success: ({ data }) => f(data),
|
|
208
|
+
})));
|
|
209
|
+
/** Returns the data as an `Option`, spanning all three data-bearing states:
|
|
210
|
+
* `Some` for `Success`, `Refreshing`, and `Stale`, `None` otherwise.
|
|
211
|
+
* Payload-named because it deliberately spans three tags. */
|
|
212
|
+
export const getData = (self) => {
|
|
213
|
+
if (isSuccess(self) || isRefreshing(self) || isStale(self)) {
|
|
214
|
+
return Option.some(self.data);
|
|
215
|
+
}
|
|
216
|
+
return Option.none();
|
|
217
|
+
};
|
|
218
|
+
/** Returns the error as an `Option`, spanning both error-bearing states:
|
|
219
|
+
* `Some` for `Failure` and `Stale`, `None` otherwise. Symmetric with
|
|
220
|
+
* `getData`. */
|
|
221
|
+
export const getError = (self) => {
|
|
222
|
+
if (isFailure(self) || isStale(self)) {
|
|
223
|
+
return Option.some(self.error);
|
|
224
|
+
}
|
|
225
|
+
return Option.none();
|
|
226
|
+
};
|
|
227
|
+
/** Returns the data of any data-bearing state, or `onEmpty()` for the three
|
|
228
|
+
* no-data states. The fallback is a nullary thunk, mirroring
|
|
229
|
+
* `Option.getOrElse`, because the no-data states collapse to empty with no
|
|
230
|
+
* single payload. */
|
|
231
|
+
export const getOrElse = Function.dual(2, (self, onEmpty) => Option.getOrElse(getData(self), onEmpty));
|
|
232
|
+
/** Returns `true` only for the `Idle` state. Refinement. */
|
|
233
|
+
export const isIdle = (self) => self._tag === 'Idle';
|
|
234
|
+
/** Returns `true` only for the empty `Loading` state, not `Refreshing`.
|
|
235
|
+
* Refinement. */
|
|
236
|
+
export const isLoading = (self) => self._tag === 'Loading';
|
|
237
|
+
/** Returns `true` only for the `Refreshing` state. Refinement. */
|
|
238
|
+
export const isRefreshing = (self) => self._tag === 'Refreshing';
|
|
239
|
+
/** Returns `true` only for the `Failure` state, not `Stale`. Refinement. */
|
|
240
|
+
export const isFailure = (self) => self._tag === 'Failure';
|
|
241
|
+
/** Returns `true` only for the `Stale` state: a failed refresh holding the
|
|
242
|
+
* last good data. Refinement. */
|
|
243
|
+
export const isStale = (self) => self._tag === 'Stale';
|
|
244
|
+
/** Returns `true` only for the `Success` state, not `Refreshing` or
|
|
245
|
+
* `Stale`. Refinement. */
|
|
246
|
+
export const isSuccess = (self) => self._tag === 'Success';
|
|
247
|
+
/** Returns `true` when the state holds data: `Success`, `Refreshing`, or
|
|
248
|
+
* `Stale`. */
|
|
249
|
+
export const hasData = (self) => Option.isSome(getData(self));
|
|
250
|
+
/** Returns `true` when the state carries an error: `Failure` or `Stale`.
|
|
251
|
+
* The error-channel twin of `hasData`. */
|
|
252
|
+
export const hasError = (self) => Option.isSome(getError(self));
|
|
253
|
+
/** Returns `true` when a request is in flight: `Loading` or `Refreshing`
|
|
254
|
+
* only. `Stale` is not pending because its fetch already failed. Use for a
|
|
255
|
+
* spinner regardless of held data. */
|
|
256
|
+
export const isPending = (self) => isLoading(self) || isRefreshing(self);
|
|
257
|
+
const stateTags = [
|
|
258
|
+
'Idle',
|
|
259
|
+
'Loading',
|
|
260
|
+
'Refreshing',
|
|
261
|
+
'Failure',
|
|
262
|
+
'Stale',
|
|
263
|
+
'Success',
|
|
264
|
+
];
|
|
265
|
+
/** Type guard on `unknown`: checks that the value has a `_tag` belonging to
|
|
266
|
+
* the six `AsyncData` states. */
|
|
267
|
+
export const isAsyncData = (input) => Predicate.hasProperty(input, '_tag') &&
|
|
268
|
+
Predicate.isString(input._tag) &&
|
|
269
|
+
Array.contains(stateTags, input._tag);
|
|
270
|
+
/** Returns `self` when it holds data (`Success`, `Refreshing`, or `Stale`),
|
|
271
|
+
* otherwise `that()`. The recovery and cache-fallback combinator: recover
|
|
272
|
+
* an `Idle`, `Loading`, or `Failure` into a secondary source without
|
|
273
|
+
* `match`. */
|
|
274
|
+
export const orElse = Function.dual(2, (self, that) => (hasData(self) ? self : that()));
|
|
275
|
+
/** The revalidate-on-entry transition: revalidates loaded data and loads
|
|
276
|
+
* cold data. The data-bearing loaded states (`Success`, `Stale`) move to
|
|
277
|
+
* `Refreshing`; the cold no-data states (`Idle`, `Failure`) start a fresh
|
|
278
|
+
* `Loading`; the already-pending states (`Loading`, `Refreshing`) yield
|
|
279
|
+
* `None` so the request in flight is not restarted. `None` means no
|
|
280
|
+
* transition, and no load Command, is needed. */
|
|
281
|
+
export const revalidateOrLoad = (self) => M.value(self).pipe(M.withReturnType(), M.tagsExhaustive({
|
|
282
|
+
Idle: () => Option.some(Loading()),
|
|
283
|
+
Loading: () => Option.none(),
|
|
284
|
+
Refreshing: () => Option.none(),
|
|
285
|
+
Failure: () => Option.some(Loading()),
|
|
286
|
+
Stale: ({ data }) => Option.some(Refreshing({ data })),
|
|
287
|
+
Success: ({ data }) => Option.some(Refreshing({ data })),
|
|
288
|
+
}));
|
|
289
|
+
/** The loaded-only revalidation transition: `Success` and `Stale` move to
|
|
290
|
+
* `Refreshing`, every other state yields `None`. Unlike
|
|
291
|
+
* `revalidateOrLoad` there is no cold-start `Loading`, so only caches
|
|
292
|
+
* that actually hold data revalidate; this is the generic refresher path
|
|
293
|
+
* after a mutation. */
|
|
294
|
+
export const revalidate = (self) => M.value(self).pipe(M.tag('Success', 'Stale', ({ data }) => Refreshing({ data })), M.option);
|
|
295
|
+
/** Combines two values under the two-tier lattice
|
|
296
|
+
* `Failure > Loading > Idle > Stale > Refreshing > Success`. If either
|
|
297
|
+
* input is a no-data state, the highest-ranked such state wins with no
|
|
298
|
+
* combination (`self`'s error wins when both are `Failure`). Otherwise both
|
|
299
|
+
* hold data: combine with `f` and tag the result with the highest-ranked
|
|
300
|
+
* data state present (`self`'s error wins when both are `Stale`).
|
|
301
|
+
*
|
|
302
|
+
* Because the combined value needs both inputs' data, a single no-data
|
|
303
|
+
* input collapses the whole combine to that state:
|
|
304
|
+
* `zipWith(Idle(), Success({ data }), f)` yields `Idle`. */
|
|
305
|
+
export const zipWith = Function.dual(3, (self, that, f) => {
|
|
306
|
+
if (isFailure(self)) {
|
|
307
|
+
return self;
|
|
308
|
+
}
|
|
309
|
+
if (isFailure(that)) {
|
|
310
|
+
return that;
|
|
311
|
+
}
|
|
312
|
+
if (isLoading(self) || isLoading(that)) {
|
|
313
|
+
return Loading();
|
|
314
|
+
}
|
|
315
|
+
if (isIdle(self) || isIdle(that)) {
|
|
316
|
+
return Idle();
|
|
317
|
+
}
|
|
318
|
+
const data = f(self.data, that.data);
|
|
319
|
+
if (isStale(self)) {
|
|
320
|
+
return Stale({ error: self.error, data });
|
|
321
|
+
}
|
|
322
|
+
if (isStale(that)) {
|
|
323
|
+
return Stale({ error: that.error, data });
|
|
324
|
+
}
|
|
325
|
+
if (isRefreshing(self) || isRefreshing(that)) {
|
|
326
|
+
return Refreshing({ data });
|
|
327
|
+
}
|
|
328
|
+
return Success({ data });
|
|
329
|
+
});
|
|
330
|
+
const combineLattice = (states, combineData) => {
|
|
331
|
+
const maybeFailure = Array.findFirst(states, isFailure);
|
|
332
|
+
if (Option.isSome(maybeFailure)) {
|
|
333
|
+
return maybeFailure.value;
|
|
334
|
+
}
|
|
335
|
+
if (Array.some(states, isLoading)) {
|
|
336
|
+
return Loading();
|
|
337
|
+
}
|
|
338
|
+
if (Array.some(states, isIdle)) {
|
|
339
|
+
return Idle();
|
|
340
|
+
}
|
|
341
|
+
const data = combineData(Array.getSomes(Array.map(states, getData)));
|
|
342
|
+
const maybeStale = Array.findFirst(states, isStale);
|
|
343
|
+
if (Option.isSome(maybeStale)) {
|
|
344
|
+
return Stale({ error: maybeStale.value.error, data });
|
|
345
|
+
}
|
|
346
|
+
if (Array.some(states, isRefreshing)) {
|
|
347
|
+
return Refreshing({ data });
|
|
348
|
+
}
|
|
349
|
+
return Success({ data });
|
|
350
|
+
};
|
|
351
|
+
const allIterable = (inputs) => combineLattice(Array.fromIterable(inputs), Function.identity);
|
|
352
|
+
const allRecord = (inputs) => {
|
|
353
|
+
const entries = Object.entries(inputs);
|
|
354
|
+
const keys = Array.map(entries, ([key]) => key);
|
|
355
|
+
const states = Array.map(entries, ([, state]) => state);
|
|
356
|
+
return combineLattice(states, datas => Object.fromEntries(Array.zip(keys, datas)));
|
|
357
|
+
};
|
|
358
|
+
/** Combines an iterable or record of values under the `zipWith` lattice.
|
|
359
|
+
* An iterable collapses to an in-order array of data (empty input yields
|
|
360
|
+
* `Success({ data: [] })`); a record builds a struct (empty input yields
|
|
361
|
+
* `Success({ data: {} })`). The highest-ranked no-data state blocks, the
|
|
362
|
+
* leftmost `Failure` error wins, and any `Stale` in an all-data set makes
|
|
363
|
+
* the result `Stale` with the leftmost `Stale` error. All inputs must share
|
|
364
|
+
* one error type; unify with `mapError` first.
|
|
365
|
+
*
|
|
366
|
+
* The record form is the multi-resource screen:
|
|
367
|
+
* `all({ user, orders, prefs })` combines into one value whose data is the
|
|
368
|
+
* struct of all datas. */
|
|
369
|
+
export const all = (inputs) => {
|
|
370
|
+
if (Symbol.iterator in inputs) {
|
|
371
|
+
return allIterable(inputs);
|
|
372
|
+
}
|
|
373
|
+
return allRecord(inputs);
|
|
374
|
+
};
|
|
375
|
+
/** Folds a settled `Result` into the previous state, keeping the last good
|
|
376
|
+
* data: a `Result` success becomes `Success`, and a `Result` failure
|
|
377
|
+
* becomes `Stale({ error, data })` when `self` holds data, else a bare
|
|
378
|
+
* `Failure`. The primary way to fold a fetch back into the Model, and the
|
|
379
|
+
* reason a failed refresh keeps data on screen: without `settle`, every
|
|
380
|
+
* fetch needs a success arm and a failure arm that hand-assemble
|
|
381
|
+
* `Success`, `Stale`, and `Failure` from the previous state. When a
|
|
382
|
+
* failure should deliberately drop the previous data, match on the
|
|
383
|
+
* `Result` directly and build the `Failure` explicitly.
|
|
384
|
+
*
|
|
385
|
+
* @example
|
|
386
|
+
* ```ts
|
|
387
|
+
* import { Effect, pipe } from 'effect'
|
|
388
|
+
* import { AsyncData, Command } from 'foldkit'
|
|
389
|
+
*
|
|
390
|
+
* // The Command settles the fetch into a Result instead of throwing:
|
|
391
|
+
* const LoadNotes = Command.define(
|
|
392
|
+
* 'LoadNotes',
|
|
393
|
+
* SettledLoadNotes,
|
|
394
|
+
* )(
|
|
395
|
+
* pipe(
|
|
396
|
+
* fetchNotes,
|
|
397
|
+
* Effect.result,
|
|
398
|
+
* Effect.map(result => SettledLoadNotes({ result })),
|
|
399
|
+
* ),
|
|
400
|
+
* )
|
|
401
|
+
*
|
|
402
|
+
* // One update arm folds it in, whatever the previous state was:
|
|
403
|
+
* SettledLoadNotes: ({ result }) => [
|
|
404
|
+
* evo(model, { notes: AsyncData.settle(result) }),
|
|
405
|
+
* [],
|
|
406
|
+
* ]
|
|
407
|
+
* ```
|
|
408
|
+
*/
|
|
409
|
+
export const settle = Function.dual(2, (self, result) => Result.match(result, {
|
|
410
|
+
onSuccess: data => Success({ data }),
|
|
411
|
+
onFailure: error => Option.match(getData(self), {
|
|
412
|
+
onNone: () => Failure({ error }),
|
|
413
|
+
onSome: data => Stale({ error, data }),
|
|
414
|
+
}),
|
|
415
|
+
}));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/asyncData/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './asyncData.js';
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { Idle, Loading, Refreshing, Failure, Stale, Success, succeed, fail, Schema, match, matchData, matchDataSplit, map, mapError, mapBoth, flatMap, getData, getError, getOrElse, isIdle, isLoading, isRefreshing, isFailure, isStale, isSuccess, hasData, hasError, isPending, isAsyncData, orElse, revalidate, revalidateOrLoad, zipWith, all, settle, } from './index.js';
|
|
2
|
+
export type { AsyncData, AsyncDataEncoded, AsyncDataSchema } from './index.js';
|
|
3
|
+
//# sourceMappingURL=public.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/asyncData/public.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,IAAI,EACJ,OAAO,EACP,UAAU,EACV,OAAO,EACP,KAAK,EACL,OAAO,EACP,OAAO,EACP,IAAI,EACJ,MAAM,EACN,KAAK,EACL,SAAS,EACT,cAAc,EACd,GAAG,EACH,QAAQ,EACR,OAAO,EACP,OAAO,EACP,OAAO,EACP,QAAQ,EACR,SAAS,EACT,MAAM,EACN,SAAS,EACT,YAAY,EACZ,SAAS,EACT,OAAO,EACP,SAAS,EACT,OAAO,EACP,QAAQ,EACR,SAAS,EACT,WAAW,EACX,MAAM,EACN,UAAU,EACV,gBAAgB,EAChB,OAAO,EACP,GAAG,EACH,MAAM,GACP,MAAM,YAAY,CAAA;AAEnB,YAAY,EAAE,SAAS,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Idle, Loading, Refreshing, Failure, Stale, Success, succeed, fail, Schema, match, matchData, matchDataSplit, map, mapError, mapBoth, flatMap, getData, getError, getOrElse, isIdle, isLoading, isRefreshing, isFailure, isStale, isSuccess, hasData, hasError, isPending, isAsyncData, orElse, revalidate, revalidateOrLoad, zipWith, all, settle, } from './index.js';
|
package/dist/html/index.d.ts
CHANGED
|
@@ -1070,6 +1070,9 @@ declare const buildHtmlFactory: <Message>() => {
|
|
|
1070
1070
|
} | {
|
|
1071
1071
|
readonly _tag: "Filter";
|
|
1072
1072
|
readonly value: string;
|
|
1073
|
+
} | {
|
|
1074
|
+
readonly _tag: "Loading";
|
|
1075
|
+
readonly value: string;
|
|
1073
1076
|
} | {
|
|
1074
1077
|
readonly _tag: "Start";
|
|
1075
1078
|
readonly value: number;
|
|
@@ -1513,9 +1516,6 @@ declare const buildHtmlFactory: <Message>() => {
|
|
|
1513
1516
|
} | {
|
|
1514
1517
|
readonly _tag: "Sizes";
|
|
1515
1518
|
readonly value: string;
|
|
1516
|
-
} | {
|
|
1517
|
-
readonly _tag: "Loading";
|
|
1518
|
-
readonly value: string;
|
|
1519
1519
|
} | {
|
|
1520
1520
|
readonly _tag: "Decoding";
|
|
1521
1521
|
readonly value: string;
|