@confect/foldkit 10.0.0-next.21 → 10.0.0-next.23
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/CHANGELOG.md +76 -3
- package/dist/Command.d.ts +44 -40
- package/dist/Command.d.ts.map +1 -1
- package/dist/Command.js +20 -16
- package/dist/Command.js.map +1 -1
- package/dist/PaginatedQuery.d.ts +94 -30
- package/dist/PaginatedQuery.d.ts.map +1 -1
- package/dist/PaginatedQuery.js +72 -28
- package/dist/PaginatedQuery.js.map +1 -1
- package/dist/Subscription.d.ts +39 -37
- package/dist/Subscription.d.ts.map +1 -1
- package/dist/Subscription.js +23 -19
- package/dist/Subscription.js.map +1 -1
- package/package.json +11 -9
- package/src/Command.ts +44 -40
- package/src/PaginatedQuery.ts +107 -42
- package/src/Subscription.ts +39 -37
package/src/Command.ts
CHANGED
|
@@ -6,10 +6,10 @@ import * as FoldkitCommand from "foldkit/command";
|
|
|
6
6
|
import * as Client from "./Client";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* Everything a Command against `Ref_` can fail with before it is folded into
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* Everything a Command against `Ref_` can fail with before it is folded into an
|
|
10
|
+
* `onError` Message: the ref's typed error (if it declares an `error` schema),
|
|
11
|
+
* a transport-level `WebSocketClientError`, or a `SchemaError` from encoding
|
|
12
|
+
* args or decoding returns.
|
|
13
13
|
*/
|
|
14
14
|
export type Error<Ref_ extends Ref.AnyConfect> =
|
|
15
15
|
| Ref.Error<Ref_>
|
|
@@ -17,11 +17,11 @@ export type Error<Ref_ extends Ref.AnyConfect> =
|
|
|
17
17
|
| Schema.SchemaError;
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
* Maps a Confect function's outcome into the app's Messages. Every failure
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* `SucceededSaveNote
|
|
20
|
+
* Maps a Confect function's outcome into the app's Messages. Every failure—the
|
|
21
|
+
* ref's typed error, a transport error, or a codec error—arrives via `onError`,
|
|
22
|
+
* so the resulting Command's error channel is `never`, as Foldkit requires.
|
|
23
|
+
* Follow Foldkit's naming convention: past-tense facts like
|
|
24
|
+
* `SucceededSaveNote`/`FailedSaveNote`.
|
|
25
25
|
*/
|
|
26
26
|
export interface Handlers<
|
|
27
27
|
Ref_ extends Ref.AnyConfect,
|
|
@@ -50,13 +50,13 @@ type SchemaArgs<Ref_ extends Ref.AnyConfect> = Schema.Schema.Type<
|
|
|
50
50
|
>;
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
|
-
* A Foldkit Command definition whose args are the ref's args: our
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
53
|
+
* A Foldkit Command definition whose args are the ref's args: our ref-derived
|
|
54
|
+
* call signature (args optional when the ref declares none) intersected with
|
|
55
|
+
* Foldkit's own `CommandDefinitionWithArgs`, which supplies the
|
|
56
|
+
* `CommandDefinitionTypeId` brand and makes the definition assignable wherever
|
|
57
|
+
* Foldkit accepts one—including Story/Scene `Command.resolve` and `expectExact`
|
|
58
|
+
* matchers. Call it from `update` to construct a Command instance; nothing runs
|
|
59
|
+
* until the Foldkit runtime executes it.
|
|
60
60
|
*/
|
|
61
61
|
export type Definition<
|
|
62
62
|
Name extends string,
|
|
@@ -70,15 +70,15 @@ export type Definition<
|
|
|
70
70
|
>;
|
|
71
71
|
|
|
72
72
|
/**
|
|
73
|
-
* Makes a factory-built Command interruptible
|
|
73
|
+
* Makes a factory-built Command interruptible—Foldkit's `InterruptOption`
|
|
74
74
|
* applied to the ref's args. `true` keys every invocation by the Command
|
|
75
|
-
* name
|
|
75
|
+
* name—right when at most one invocation is meaningfully in flight; a
|
|
76
76
|
* `KeyedInterrupt` derives the key part from the ref's args, so concurrent
|
|
77
77
|
* invocations can be targeted independently.
|
|
78
78
|
*
|
|
79
79
|
* Interrupting stops the client-side Effect and guarantees the invocation's
|
|
80
|
-
* result Messages never dispatch
|
|
81
|
-
*
|
|
80
|
+
* result Messages never dispatch—it does not cancel the Convex function on the
|
|
81
|
+
* server, which runs to completion once the call is on the wire.
|
|
82
82
|
*/
|
|
83
83
|
export type InterruptOption<
|
|
84
84
|
Ref_ extends Ref.AnyConfect,
|
|
@@ -96,10 +96,10 @@ export type KeyedInterrupt<
|
|
|
96
96
|
> = Exclude<InterruptOption<Ref_, KeyField>, true>;
|
|
97
97
|
|
|
98
98
|
/**
|
|
99
|
-
* An interruptible Command definition whose key is the Command name
|
|
99
|
+
* An interruptible Command definition whose key is the Command name—the
|
|
100
100
|
* ref-derived call signature intersected with Foldkit's
|
|
101
|
-
* `Interruptible.DefinitionWithArgsNameKeyed`, which supplies the brand and
|
|
102
|
-
*
|
|
101
|
+
* `Interruptible.DefinitionWithArgsNameKeyed`, which supplies the brand and the
|
|
102
|
+
* `Interrupt` constructor.
|
|
103
103
|
*/
|
|
104
104
|
export type InterruptibleDefinition<
|
|
105
105
|
Name extends string,
|
|
@@ -116,7 +116,7 @@ export type InterruptibleDefinition<
|
|
|
116
116
|
|
|
117
117
|
/**
|
|
118
118
|
* An interruptible Command definition whose key is derived from the ref's
|
|
119
|
-
* args
|
|
119
|
+
* args—the ref-derived call signature intersected with Foldkit's
|
|
120
120
|
* `Interruptible.DefinitionWithArgs`, which supplies the brand and the
|
|
121
121
|
* key-args-taking `Interrupt` constructor.
|
|
122
122
|
*/
|
|
@@ -149,9 +149,9 @@ const run = <Ref_ extends Ref.AnyConfect, SuccessMessage, ErrorMessage>(
|
|
|
149
149
|
);
|
|
150
150
|
|
|
151
151
|
/**
|
|
152
|
-
* An execute body for a hand-written `Command.define
|
|
153
|
-
* folded into a Message. Reach for these when the factory below doesn't fit:
|
|
154
|
-
*
|
|
152
|
+
* An execute body for a hand-written `Command.define`—every failure already
|
|
153
|
+
* folded into a Message. Reach for these when the factory below doesn't fit: a
|
|
154
|
+
* custom args schema, `interrupt`, or a Command that makes several calls.
|
|
155
155
|
*/
|
|
156
156
|
export const queryEffect =
|
|
157
157
|
<Query extends Ref.AnyConfectPublicQuery, SuccessMessage, ErrorMessage>(
|
|
@@ -164,7 +164,7 @@ export const queryEffect =
|
|
|
164
164
|
run(handlers, (client) => client.query(ref, ...args));
|
|
165
165
|
|
|
166
166
|
/**
|
|
167
|
-
* An execute body for a hand-written `Command.define
|
|
167
|
+
* An execute body for a hand-written `Command.define`—every failure already
|
|
168
168
|
* folded into a Message. See `queryEffect`.
|
|
169
169
|
*/
|
|
170
170
|
export const mutationEffect =
|
|
@@ -178,7 +178,7 @@ export const mutationEffect =
|
|
|
178
178
|
run(handlers, (client) => client.mutation(ref, ...args));
|
|
179
179
|
|
|
180
180
|
/**
|
|
181
|
-
* An execute body for a hand-written `Command.define
|
|
181
|
+
* An execute body for a hand-written `Command.define`—every failure already
|
|
182
182
|
* folded into a Message. See `queryEffect`.
|
|
183
183
|
*/
|
|
184
184
|
export const actionEffect =
|
|
@@ -412,22 +412,26 @@ const makeFactory = <BoundRef extends Ref.AnyConfect>(
|
|
|
412
412
|
* messages: [SucceededGetNote, FailedGetNote],
|
|
413
413
|
* onSuccess: (note) => SucceededGetNote({ note }),
|
|
414
414
|
* onError: (error) => FailedGetNote({ message: String(error) }),
|
|
415
|
-
* })
|
|
415
|
+
* });
|
|
416
416
|
* // In update:
|
|
417
417
|
* // [model, [FetchNote({ noteId })]]
|
|
418
418
|
* ```
|
|
419
419
|
*
|
|
420
420
|
* Pass `interrupt` to make invocations interruptible via the returned
|
|
421
|
-
* definition's `Interrupt` constructor
|
|
422
|
-
*
|
|
421
|
+
* definition's `Interrupt` constructor—`true` keys them by the Command name, `{
|
|
422
|
+
* keyFields, toKey }` by a part derived from the ref's args:
|
|
423
423
|
*
|
|
424
424
|
* ```ts
|
|
425
|
-
* const SaveDraft = Command.mutation(
|
|
426
|
-
*
|
|
427
|
-
*
|
|
428
|
-
*
|
|
429
|
-
*
|
|
430
|
-
* })
|
|
425
|
+
* const SaveDraft = Command.mutation(
|
|
426
|
+
* "SaveDraft",
|
|
427
|
+
* refs.public.notes.insert,
|
|
428
|
+
* {
|
|
429
|
+
* messages: [SucceededSaveDraft, FailedSaveDraft],
|
|
430
|
+
* onSuccess: (noteId) => SucceededSaveDraft({ noteId }),
|
|
431
|
+
* onError: (error) => FailedSaveDraft({ message: String(error) }),
|
|
432
|
+
* interrupt: true,
|
|
433
|
+
* },
|
|
434
|
+
* );
|
|
431
435
|
* // In update:
|
|
432
436
|
* // [model, [SaveDraft.Interrupt((outcome) => CompletedCancelSaveDraft({ outcome }))]]
|
|
433
437
|
* ```
|
|
@@ -444,8 +448,8 @@ export const mutation: ReturnType<
|
|
|
444
448
|
> = makeFactory(mutationEffect);
|
|
445
449
|
|
|
446
450
|
/**
|
|
447
|
-
* A Foldkit Command definition for a Confect action whose Command args are
|
|
448
|
-
*
|
|
451
|
+
* A Foldkit Command definition for a Confect action whose Command args are the
|
|
452
|
+
* ref's args. See `query`.
|
|
449
453
|
*/
|
|
450
454
|
export const action: ReturnType<
|
|
451
455
|
typeof makeFactory<Ref.AnyConfectPublicAction>
|
package/src/PaginatedQuery.ts
CHANGED
|
@@ -11,7 +11,9 @@ import * as SchemaGetter from "effect/SchemaGetter";
|
|
|
11
11
|
import * as SchemaIssue from "effect/SchemaIssue";
|
|
12
12
|
import * as Client from "./Client";
|
|
13
13
|
|
|
14
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* Identifies the cursor range fetched for one page.
|
|
16
|
+
*/
|
|
15
17
|
export interface PageDescriptor {
|
|
16
18
|
readonly cursor: string | null;
|
|
17
19
|
readonly endCursor: Option.Option<string>;
|
|
@@ -22,7 +24,9 @@ export const PageDescriptor = Schema.Struct({
|
|
|
22
24
|
endCursor: Schema.Option(Schema.String),
|
|
23
25
|
});
|
|
24
26
|
|
|
25
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Options that remain fixed for one pagination session.
|
|
29
|
+
*/
|
|
26
30
|
export interface Options {
|
|
27
31
|
readonly initialNumItems: number;
|
|
28
32
|
readonly maximumRowsRead?: number;
|
|
@@ -38,7 +42,9 @@ export const Options = Schema.Struct({
|
|
|
38
42
|
maximumBytesRead: Schema.optionalKey(PositiveInt),
|
|
39
43
|
});
|
|
40
44
|
|
|
41
|
-
/**
|
|
45
|
+
/**
|
|
46
|
+
* Why the machine is fetching its current target.
|
|
47
|
+
*/
|
|
42
48
|
export type Direction =
|
|
43
49
|
| "Initial"
|
|
44
50
|
| "Next"
|
|
@@ -47,7 +53,9 @@ export type Direction =
|
|
|
47
53
|
| "Split"
|
|
48
54
|
| "Reset";
|
|
49
55
|
|
|
50
|
-
/**
|
|
56
|
+
/**
|
|
57
|
+
* A complete page that can remain on screen while another page is fetched.
|
|
58
|
+
*/
|
|
51
59
|
export interface Page<Item_> {
|
|
52
60
|
readonly descriptor: PageDescriptor;
|
|
53
61
|
readonly number: number;
|
|
@@ -89,12 +97,16 @@ interface PhaseDefinition extends Data.TaggedEnum.WithGenerics<2> {
|
|
|
89
97
|
|
|
90
98
|
const Phase = Data.taggedEnum<PhaseDefinition>();
|
|
91
99
|
|
|
92
|
-
/**
|
|
100
|
+
/**
|
|
101
|
+
* A closed machine that retains its generation for stale-result rejection.
|
|
102
|
+
*/
|
|
93
103
|
export type Idle = Data.TaggedEnum<{
|
|
94
104
|
Idle: { readonly generation: number };
|
|
95
105
|
}>;
|
|
96
106
|
|
|
97
|
-
/**
|
|
107
|
+
/**
|
|
108
|
+
* A live cursor-pagination session.
|
|
109
|
+
*/
|
|
98
110
|
export type Active<Item_, UserArgs_, Error_> = Data.TaggedEnum<{
|
|
99
111
|
Active: {
|
|
100
112
|
readonly generation: number;
|
|
@@ -108,7 +120,9 @@ export type Active<Item_, UserArgs_, Error_> = Data.TaggedEnum<{
|
|
|
108
120
|
};
|
|
109
121
|
}>;
|
|
110
122
|
|
|
111
|
-
/**
|
|
123
|
+
/**
|
|
124
|
+
* A schema-backed cursor-pagination machine.
|
|
125
|
+
*/
|
|
112
126
|
export type State<Item_, UserArgs_, Error_> =
|
|
113
127
|
| Idle
|
|
114
128
|
| Active<Item_, UserArgs_, Error_>;
|
|
@@ -119,7 +133,9 @@ interface StateDefinition extends Data.TaggedEnum.WithGenerics<3> {
|
|
|
119
133
|
|
|
120
134
|
const State = Data.taggedEnum<StateDefinition>();
|
|
121
135
|
|
|
122
|
-
/**
|
|
136
|
+
/**
|
|
137
|
+
* The logical request from which a subscription allocates a session id.
|
|
138
|
+
*/
|
|
123
139
|
export interface SubscriptionRequest<UserArgs_> {
|
|
124
140
|
readonly generation: number;
|
|
125
141
|
readonly args: UserArgs_;
|
|
@@ -129,7 +145,9 @@ export interface SubscriptionRequest<UserArgs_> {
|
|
|
129
145
|
readonly descriptor: PageDescriptor;
|
|
130
146
|
}
|
|
131
147
|
|
|
132
|
-
/**
|
|
148
|
+
/**
|
|
149
|
+
* The complete identity and arguments of a subscribed page request.
|
|
150
|
+
*/
|
|
133
151
|
export interface Request<UserArgs_> {
|
|
134
152
|
readonly generation: number;
|
|
135
153
|
readonly args: UserArgs_;
|
|
@@ -139,7 +157,9 @@ export interface Request<UserArgs_> {
|
|
|
139
157
|
readonly descriptor: PageDescriptor;
|
|
140
158
|
}
|
|
141
159
|
|
|
142
|
-
/**
|
|
160
|
+
/**
|
|
161
|
+
* The successful payload returned by a Convex paginated query.
|
|
162
|
+
*/
|
|
143
163
|
export interface PageResult<Item_> {
|
|
144
164
|
readonly page: ReadonlyArray<Item_>;
|
|
145
165
|
readonly isDone: boolean;
|
|
@@ -149,8 +169,8 @@ export interface PageResult<Item_> {
|
|
|
149
169
|
}
|
|
150
170
|
|
|
151
171
|
/**
|
|
152
|
-
* One correlated subscription outcome. Both success and failure travel
|
|
153
|
-
*
|
|
172
|
+
* One correlated subscription outcome. Both success and failure travel through
|
|
173
|
+
* this value so `settle` can mirror `AsyncData.settle`.
|
|
154
174
|
*/
|
|
155
175
|
export interface Settlement<Item_, UserArgs_, Error_> {
|
|
156
176
|
readonly request: Request<UserArgs_>;
|
|
@@ -173,18 +193,24 @@ interface FunctionErrorDefinition extends Data.TaggedEnum.WithGenerics<1> {
|
|
|
173
193
|
readonly taggedEnum: TaggedFunctionError<this["A"]>;
|
|
174
194
|
}
|
|
175
195
|
|
|
176
|
-
/**
|
|
196
|
+
/**
|
|
197
|
+
* Constructs a declared function or middleware failure envelope.
|
|
198
|
+
*/
|
|
177
199
|
export const FunctionError =
|
|
178
200
|
Data.taggedEnum<FunctionErrorDefinition>().FunctionError;
|
|
179
201
|
|
|
180
|
-
/**
|
|
202
|
+
/**
|
|
203
|
+
* A declared function or middleware failure, kept distinct from client errors.
|
|
204
|
+
*/
|
|
181
205
|
export type FunctionError<Query extends Ref.AnyConfectPublicPaginatedQuery> = [
|
|
182
206
|
Ref.Error<Query>,
|
|
183
207
|
] extends [never]
|
|
184
208
|
? never
|
|
185
209
|
: TaggedFunctionError<Ref.Error<Query>>;
|
|
186
210
|
|
|
187
|
-
/**
|
|
211
|
+
/**
|
|
212
|
+
* Every failure a paginated query subscription can settle with.
|
|
213
|
+
*/
|
|
188
214
|
export type Error<Query extends Ref.AnyConfectPublicPaginatedQuery> =
|
|
189
215
|
| FunctionError<Query>
|
|
190
216
|
| PaginationError.InvalidCursor
|
|
@@ -205,7 +231,9 @@ type MachineActive<Query extends Ref.AnyConfectPublicPaginatedQuery> = Active<
|
|
|
205
231
|
Error<Query>
|
|
206
232
|
>;
|
|
207
233
|
|
|
208
|
-
/**
|
|
234
|
+
/**
|
|
235
|
+
* The schema-and-constructor bundle returned by `make`.
|
|
236
|
+
*/
|
|
209
237
|
export interface PaginatedQuery<
|
|
210
238
|
Query extends Ref.AnyConfectPublicPaginatedQuery,
|
|
211
239
|
> {
|
|
@@ -295,7 +323,9 @@ const SerializableSchemaError = Schema.instanceOf(Schema.SchemaError, {
|
|
|
295
323
|
}),
|
|
296
324
|
});
|
|
297
325
|
|
|
298
|
-
/**
|
|
326
|
+
/**
|
|
327
|
+
* Builds a page machine from a paginated ref.
|
|
328
|
+
*/
|
|
299
329
|
export const make = <Query extends Ref.AnyConfectPublicPaginatedQuery>(
|
|
300
330
|
ref: Query,
|
|
301
331
|
): PaginatedQuery<Query> => {
|
|
@@ -483,7 +513,9 @@ const descriptorEquals = (a: PageDescriptor, b: PageDescriptor): boolean =>
|
|
|
483
513
|
}),
|
|
484
514
|
});
|
|
485
515
|
|
|
486
|
-
/**
|
|
516
|
+
/**
|
|
517
|
+
* Returns the logical request currently driving the subscription.
|
|
518
|
+
*/
|
|
487
519
|
export const getSubscriptionRequest = <Item_, UserArgs_, Error_>(
|
|
488
520
|
state: Active<Item_, UserArgs_, Error_>,
|
|
489
521
|
): SubscriptionRequest<UserArgs_> => ({
|
|
@@ -495,7 +527,9 @@ export const getSubscriptionRequest = <Item_, UserArgs_, Error_>(
|
|
|
495
527
|
descriptor: state.current,
|
|
496
528
|
});
|
|
497
529
|
|
|
498
|
-
/**
|
|
530
|
+
/**
|
|
531
|
+
* Attaches the client-allocated pagination id to a logical request.
|
|
532
|
+
*/
|
|
499
533
|
export const allocateRequest = <UserArgs_>(
|
|
500
534
|
request: SubscriptionRequest<UserArgs_>,
|
|
501
535
|
paginationId: number,
|
|
@@ -525,12 +559,16 @@ const pendingPhase = <Item_>(
|
|
|
525
559
|
onSome: (page) => Phase.Refreshing<Item_, never>({ data: page, direction }),
|
|
526
560
|
});
|
|
527
561
|
|
|
528
|
-
/**
|
|
562
|
+
/**
|
|
563
|
+
* Close the subscription while retaining its generation tombstone.
|
|
564
|
+
*/
|
|
529
565
|
export const close = <Item_, UserArgs_, Error_>(
|
|
530
566
|
state: Active<Item_, UserArgs_, Error_>,
|
|
531
567
|
): Idle => State.Idle({ generation: state.generation });
|
|
532
568
|
|
|
533
|
-
/**
|
|
569
|
+
/**
|
|
570
|
+
* Start a fresh pagination session at page one while retaining visible data.
|
|
571
|
+
*/
|
|
534
572
|
export const reset = <Item_, UserArgs_, Error_>(
|
|
535
573
|
state: Active<Item_, UserArgs_, Error_>,
|
|
536
574
|
): Active<Item_, UserArgs_, Error_> => ({
|
|
@@ -543,7 +581,17 @@ export const reset = <Item_, UserArgs_, Error_>(
|
|
|
543
581
|
phase: pendingPhase(phaseData(state.phase), "Reset"),
|
|
544
582
|
});
|
|
545
583
|
|
|
546
|
-
/**
|
|
584
|
+
/**
|
|
585
|
+
* Navigate to the next page, retaining the current page while it loads.
|
|
586
|
+
*
|
|
587
|
+
* The page being left is pushed onto the stack _pinned_ to the range it
|
|
588
|
+
* displayed—its cursor to its continuation cursor—so `prev` reloads exactly
|
|
589
|
+
* that range rather than the first `initialNumItems` documents after its
|
|
590
|
+
* cursor, however the data has moved meanwhile. Convex's own pagination keeps
|
|
591
|
+
* that range in its query journal; stream-paginated queries have no journal, so
|
|
592
|
+
* the pin is what keeps consecutive pages gap-free and duplicate-free for
|
|
593
|
+
* them.
|
|
594
|
+
*/
|
|
547
595
|
export const next = <Item_, UserArgs_, Error_>(
|
|
548
596
|
state: Active<Item_, UserArgs_, Error_>,
|
|
549
597
|
): Option.Option<Active<Item_, UserArgs_, Error_>> =>
|
|
@@ -553,24 +601,25 @@ export const next = <Item_, UserArgs_, Error_>(
|
|
|
553
601
|
Match.value(data.isDone).pipe(
|
|
554
602
|
Match.withReturnType<Option.Option<Active<Item_, UserArgs_, Error_>>>(),
|
|
555
603
|
Match.when(true, () => Option.none()),
|
|
556
|
-
Match.when(false, () =>
|
|
557
|
-
Option.
|
|
604
|
+
Match.when(false, () => {
|
|
605
|
+
const end = Option.getOrElse(
|
|
606
|
+
data.descriptor.endCursor,
|
|
607
|
+
() => data.continueCursor,
|
|
608
|
+
);
|
|
609
|
+
return Option.some({
|
|
558
610
|
...state,
|
|
559
611
|
requestId: state.requestId + 1,
|
|
560
|
-
current: {
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
endCursor: Option.none(),
|
|
566
|
-
},
|
|
567
|
-
prevStack: [...state.prevStack, data.descriptor],
|
|
612
|
+
current: { cursor: end, endCursor: Option.none() },
|
|
613
|
+
prevStack: [
|
|
614
|
+
...state.prevStack,
|
|
615
|
+
{ cursor: data.descriptor.cursor, endCursor: Option.some(end) },
|
|
616
|
+
],
|
|
568
617
|
phase: Phase.Refreshing<Item_, Error_>({
|
|
569
618
|
data,
|
|
570
619
|
direction: "Next",
|
|
571
620
|
}),
|
|
572
|
-
})
|
|
573
|
-
),
|
|
621
|
+
});
|
|
622
|
+
}),
|
|
574
623
|
Match.exhaustive,
|
|
575
624
|
),
|
|
576
625
|
),
|
|
@@ -578,7 +627,9 @@ export const next = <Item_, UserArgs_, Error_>(
|
|
|
578
627
|
Match.exhaustive,
|
|
579
628
|
);
|
|
580
629
|
|
|
581
|
-
/**
|
|
630
|
+
/**
|
|
631
|
+
* Navigate to the previous page, retaining the current page while it loads.
|
|
632
|
+
*/
|
|
582
633
|
export const prev = <Item_, UserArgs_, Error_>(
|
|
583
634
|
state: Active<Item_, UserArgs_, Error_>,
|
|
584
635
|
): Option.Option<Active<Item_, UserArgs_, Error_>> =>
|
|
@@ -608,7 +659,9 @@ export const prev = <Item_, UserArgs_, Error_>(
|
|
|
608
659
|
Match.exhaustive,
|
|
609
660
|
);
|
|
610
661
|
|
|
611
|
-
/**
|
|
662
|
+
/**
|
|
663
|
+
* Navigate to page one without invalidating the current pagination session.
|
|
664
|
+
*/
|
|
612
665
|
export const first = <Item_, UserArgs_, Error_>(
|
|
613
666
|
state: Active<Item_, UserArgs_, Error_>,
|
|
614
667
|
): Option.Option<Active<Item_, UserArgs_, Error_>> =>
|
|
@@ -630,7 +683,9 @@ export const first = <Item_, UserArgs_, Error_>(
|
|
|
630
683
|
Match.exhaustive,
|
|
631
684
|
);
|
|
632
685
|
|
|
633
|
-
/**
|
|
686
|
+
/**
|
|
687
|
+
* Whether a request still names the machine's live pagination session/page.
|
|
688
|
+
*/
|
|
634
689
|
export const isCurrentRequest = <Item_, UserArgs_, Error_>(
|
|
635
690
|
state: State<Item_, UserArgs_, Error_>,
|
|
636
691
|
candidate: Request<UserArgs_>,
|
|
@@ -799,7 +854,9 @@ export const settle: {
|
|
|
799
854
|
),
|
|
800
855
|
);
|
|
801
856
|
|
|
802
|
-
/**
|
|
857
|
+
/**
|
|
858
|
+
* The complete page currently available to render.
|
|
859
|
+
*/
|
|
803
860
|
export const getPage = <Item_, UserArgs_, Error_>(
|
|
804
861
|
state: State<Item_, UserArgs_, Error_>,
|
|
805
862
|
): Option.Option<Page<Item_>> =>
|
|
@@ -809,13 +866,17 @@ export const getPage = <Item_, UserArgs_, Error_>(
|
|
|
809
866
|
Match.exhaustive,
|
|
810
867
|
);
|
|
811
868
|
|
|
812
|
-
/**
|
|
869
|
+
/**
|
|
870
|
+
* The items currently available to render.
|
|
871
|
+
*/
|
|
813
872
|
export const getItems = <Item_, UserArgs_, Error_>(
|
|
814
873
|
state: State<Item_, UserArgs_, Error_>,
|
|
815
874
|
): Option.Option<ReadonlyArray<Item_>> =>
|
|
816
875
|
Option.map(getPage(state), (page) => page.items);
|
|
817
876
|
|
|
818
|
-
/**
|
|
877
|
+
/**
|
|
878
|
+
* The error from the most recent failed request, if any.
|
|
879
|
+
*/
|
|
819
880
|
export const getError = <Item_, UserArgs_, Error_>(
|
|
820
881
|
state: State<Item_, UserArgs_, Error_>,
|
|
821
882
|
): Option.Option<Error_> =>
|
|
@@ -833,7 +894,9 @@ export const getError = <Item_, UserArgs_, Error_>(
|
|
|
833
894
|
Match.exhaustive,
|
|
834
895
|
);
|
|
835
896
|
|
|
836
|
-
/**
|
|
897
|
+
/**
|
|
898
|
+
* The 1-indexed page number currently targeted by the subscription.
|
|
899
|
+
*/
|
|
837
900
|
export const targetPageNumber = <Item_, UserArgs_, Error_>(
|
|
838
901
|
state: Active<Item_, UserArgs_, Error_>,
|
|
839
902
|
): number => state.prevStack.length + 1;
|
|
@@ -948,7 +1011,9 @@ export const canPrev = <Item_, UserArgs_, Error_>(
|
|
|
948
1011
|
Match.exhaustive,
|
|
949
1012
|
);
|
|
950
1013
|
|
|
951
|
-
/**
|
|
1014
|
+
/**
|
|
1015
|
+
* Pattern-match exhaustively on the machine's AsyncData-style state.
|
|
1016
|
+
*/
|
|
952
1017
|
export const match: {
|
|
953
1018
|
<Item_, Error_, A, B, C, D, E, F>(handlers: {
|
|
954
1019
|
readonly onIdle: (idle: Idle) => A;
|
package/src/Subscription.ts
CHANGED
|
@@ -44,18 +44,17 @@ const paginatedError = <Query extends Ref.AnyConfectPublicPaginatedQuery>(
|
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
46
|
* The dependency record of a `reactiveQuery` entry. `None` means the
|
|
47
|
-
* subscription is closed; a change from one `Some` to another tears the
|
|
48
|
-
*
|
|
47
|
+
* subscription is closed; a change from one `Some` to another tears the server
|
|
48
|
+
* subscription down and reopens it with the new args.
|
|
49
49
|
*/
|
|
50
50
|
export interface Dependencies<Query extends Ref.AnyConfectPublicQuery> {
|
|
51
51
|
readonly args: Option.Option<Ref.Args<Query>>;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
|
-
* Maps a reactive query's emissions into the app's Messages. Every failure
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* requires.
|
|
55
|
+
* Maps a reactive query's emissions into the app's Messages. Every failure—the
|
|
56
|
+
* ref's typed error, a transport error, or a codec error—arrives via `onError`,
|
|
57
|
+
* so the resulting stream's error channel is `never`, as Foldkit requires.
|
|
59
58
|
*/
|
|
60
59
|
export interface Handlers<
|
|
61
60
|
Query extends Ref.AnyConfectPublicQuery,
|
|
@@ -79,13 +78,13 @@ type ArgsConfig<Query extends Ref.AnyConfectPublicQuery, Model> = {
|
|
|
79
78
|
});
|
|
80
79
|
|
|
81
80
|
/**
|
|
82
|
-
* A `dependenciesToStream` body for a hand-written subscription entry: runs
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
81
|
+
* A `dependenciesToStream` body for a hand-written subscription entry: runs the
|
|
82
|
+
* ref as a reactive query against the `Client` resource and maps every emission
|
|
83
|
+
* and every failure into a Message. Query failures are values, so the
|
|
84
|
+
* subscription remains live and can later emit a successful result.
|
|
86
85
|
*
|
|
87
|
-
* Prefer `reactiveQuery`, which builds the whole entry; reach for this when
|
|
88
|
-
*
|
|
86
|
+
* Prefer `reactiveQuery`, which builds the whole entry; reach for this when you
|
|
87
|
+
* need control over the entry's dependencies (custom gating, extra
|
|
89
88
|
* dependencies).
|
|
90
89
|
*/
|
|
91
90
|
export const reactiveQueryStream =
|
|
@@ -109,30 +108,34 @@ export const reactiveQueryStream =
|
|
|
109
108
|
|
|
110
109
|
/**
|
|
111
110
|
* A complete Foldkit subscription entry for a Confect reactive query. The
|
|
112
|
-
* leading thunk fixes the `Model` type (TypeScript cannot partially infer
|
|
113
|
-
*
|
|
114
|
-
*
|
|
111
|
+
* leading thunk fixes the `Model` type (TypeScript cannot partially infer type
|
|
112
|
+
* arguments), so the `args` extractor's parameter is already typed. Pass the
|
|
113
|
+
* result as an entry value to `Subscription.make`:
|
|
115
114
|
*
|
|
116
115
|
* ```ts
|
|
117
|
-
* import * as Confect from "@confect/foldkit"
|
|
118
|
-
* import * as Subscription from "foldkit/subscription"
|
|
116
|
+
* import * as Confect from "@confect/foldkit";
|
|
117
|
+
* import * as Subscription from "foldkit/subscription";
|
|
119
118
|
*
|
|
120
119
|
* const subscriptions = Subscription.make<
|
|
121
120
|
* Model,
|
|
122
121
|
* Message,
|
|
123
122
|
* Confect.Client.Client
|
|
124
123
|
* >()(() => ({
|
|
125
|
-
* note: Confect.Subscription.reactiveQuery<Model>()(
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
* })
|
|
124
|
+
* note: Confect.Subscription.reactiveQuery<Model>()(
|
|
125
|
+
* refs.public.notes.get,
|
|
126
|
+
* {
|
|
127
|
+
* args: (model) =>
|
|
128
|
+
* Option.map(model.noteId, (noteId) => ({ noteId })),
|
|
129
|
+
* onSuccess: (note) => SucceededGetNote({ note }),
|
|
130
|
+
* onError: (error) => FailedGetNote({ message: String(error) }),
|
|
131
|
+
* },
|
|
132
|
+
* ),
|
|
133
|
+
* }));
|
|
131
134
|
* ```
|
|
132
135
|
*
|
|
133
136
|
* The entry's dependencies are the query args wrapped in `Option`: `None`
|
|
134
|
-
* closes the subscription, a change from one `Some` to another unsubscribes
|
|
135
|
-
*
|
|
137
|
+
* closes the subscription, a change from one `Some` to another unsubscribes and
|
|
138
|
+
* resubscribes with the new args, and structurally equal args leave the
|
|
136
139
|
* subscription running (equivalence is derived from the ref's args schema).
|
|
137
140
|
* Queries without args may omit `args`, leaving the subscription always open.
|
|
138
141
|
*/
|
|
@@ -170,18 +173,17 @@ export const reactiveQuery =
|
|
|
170
173
|
|
|
171
174
|
/**
|
|
172
175
|
* A complete Foldkit subscription entry that keeps exactly one live reactive
|
|
173
|
-
* page subscription in sync with a `PaginatedQuery` machine in the Model.
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
176
|
+
* page subscription in sync with a `PaginatedQuery` machine in the Model. The
|
|
177
|
+
* machine state is the single source of truth: navigation and split-pinning
|
|
178
|
+
* change the derived args, while `Idle` closes the subscription. Pagination ids
|
|
179
|
+
* are allocated here and installed by the first settlement.
|
|
177
180
|
*
|
|
178
|
-
* The leading thunk fixes the `Model` type (TypeScript cannot partially
|
|
179
|
-
*
|
|
180
|
-
* typed:
|
|
181
|
+
* The leading thunk fixes the `Model` type (TypeScript cannot partially infer
|
|
182
|
+
* type arguments), so the `state` extractor's parameter is already typed:
|
|
181
183
|
*
|
|
182
184
|
* ```ts
|
|
183
|
-
* import * as Confect from "@confect/foldkit"
|
|
184
|
-
* import * as Subscription from "foldkit/subscription"
|
|
185
|
+
* import * as Confect from "@confect/foldkit";
|
|
186
|
+
* import * as Subscription from "foldkit/subscription";
|
|
185
187
|
*
|
|
186
188
|
* const subscriptions = Subscription.make<
|
|
187
189
|
* Model,
|
|
@@ -192,13 +194,13 @@ export const reactiveQuery =
|
|
|
192
194
|
* state: (model) => model.notes,
|
|
193
195
|
* onSettled: (settlement) => SettledGetNotesPage({ settlement }),
|
|
194
196
|
* }),
|
|
195
|
-
* }))
|
|
197
|
+
* }));
|
|
196
198
|
* ```
|
|
197
199
|
*
|
|
198
200
|
* Pass the settlement to `PaginatedQuery.settle` in `update`. The request
|
|
199
201
|
* identity is included in both success and failure Messages, so superseded
|
|
200
|
-
* outcomes cannot settle a newer page or pagination session. Query failures
|
|
201
|
-
*
|
|
202
|
+
* outcomes cannot settle a newer page or pagination session. Query failures do
|
|
203
|
+
* not close the Convex subscription; a later result can recover naturally.
|
|
202
204
|
*/
|
|
203
205
|
type DependenciesSchema<Dependencies_> = Schema.Schema<Dependencies_> & {
|
|
204
206
|
readonly fields: Schema.Struct.Fields;
|