foldkit-remote 0.3.0 → 0.5.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 +266 -6
- package/dist/index.d.mts +452 -70
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +709 -365
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -3
package/dist/index.d.mts
CHANGED
|
@@ -1,9 +1,65 @@
|
|
|
1
1
|
import { Context, Duration, Effect, Layer, Option, Schema, Stream } from "effect";
|
|
2
|
-
import
|
|
2
|
+
import * as Domain from "foldkit-entity";
|
|
3
|
+
import { AnyEntity, AnyQuery, InputExpr, Query as Query$1 } from "foldkit-entity";
|
|
4
|
+
import { EntryWithoutKeepAlive } from "foldkit/subscription";
|
|
5
|
+
import { ActiveSurface, Contract, Invalid, Metadata, ModelRef, Projection, Surface, Wiring } from "foldkit-surface";
|
|
3
6
|
import { Rpc, RpcGroup } from "effect/unstable/rpc";
|
|
4
7
|
import { KeyValueStore } from "effect/unstable/persistence";
|
|
5
8
|
import { Command } from "foldkit/command";
|
|
6
|
-
|
|
9
|
+
//#region ../metadata/dist/index.d.mts
|
|
10
|
+
//#region src/index.d.ts
|
|
11
|
+
/** Marks a `Metadata` value. Its entries are private to this module. */
|
|
12
|
+
declare const MetadataTypeId: unique symbol;
|
|
13
|
+
type MetadataTypeId = typeof MetadataTypeId;
|
|
14
|
+
/**
|
|
15
|
+
* Declarative facts an interpreter attaches to a node it does not own, such as
|
|
16
|
+
* the server data a Remote selection needs. The carrier holds and combines
|
|
17
|
+
* them without knowing what they mean; a package reads only the key it owns.
|
|
18
|
+
*
|
|
19
|
+
* Opaque: only a key's `of` and `Metadata.combine` make one, so entries cannot
|
|
20
|
+
* be forged past a key's type or `merge`, and they are frozen.
|
|
21
|
+
*/
|
|
22
|
+
interface Metadata$1 {
|
|
23
|
+
readonly [MetadataTypeId]: MetadataTypeId;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* An interpreter's typed slot in `Metadata`. Lookup is by the key object, never
|
|
27
|
+
* its `name`, so two packages cannot collide by choosing the same string.
|
|
28
|
+
*/
|
|
29
|
+
interface MetadataKey<A> {
|
|
30
|
+
/** For tooling output only. */
|
|
31
|
+
readonly name: string;
|
|
32
|
+
/** Normalizes the entries gathered from combined values, e.g. unioning duplicates. */
|
|
33
|
+
readonly merge: (values: ReadonlyArray<A>) => ReadonlyArray<A>;
|
|
34
|
+
/** One line per entry for tooling output. */
|
|
35
|
+
readonly summarize: (value: A) => string;
|
|
36
|
+
readonly of: (...values: ReadonlyArray<A>) => Metadata$1;
|
|
37
|
+
readonly get: (metadata: Metadata$1) => ReadonlyArray<A>;
|
|
38
|
+
}
|
|
39
|
+
/** One key's entries as serializable text, named by the key. */
|
|
40
|
+
interface MetadataSummary {
|
|
41
|
+
readonly name: string;
|
|
42
|
+
readonly entries: readonly string[];
|
|
43
|
+
}
|
|
44
|
+
declare const Metadata$1: {
|
|
45
|
+
/**
|
|
46
|
+
* Declares an interpreter's slot. Call once per package, at module level:
|
|
47
|
+
* entries are found by the key object, so two copies of a package (a
|
|
48
|
+
* duplicated install, a reloaded module) do not see each other's entries.
|
|
49
|
+
*/
|
|
50
|
+
key: <A>(name: string, options: {
|
|
51
|
+
readonly merge: (values: ReadonlyArray<A>) => ReadonlyArray<A>;
|
|
52
|
+
readonly summarize: (value: A) => string;
|
|
53
|
+
}) => MetadataKey<A>;
|
|
54
|
+
empty: Metadata$1;
|
|
55
|
+
/** True only for a value this module made; a copy or a hand-built brand is not one. */
|
|
56
|
+
is: (value: unknown) => value is Metadata$1;
|
|
57
|
+
/** Gathers every part's entries and runs each key's `merge` over its group. */
|
|
58
|
+
combine: (parts: ReadonlyArray<Metadata$1>) => Metadata$1;
|
|
59
|
+
summarize: (metadata: Metadata$1) => readonly MetadataSummary[];
|
|
60
|
+
};
|
|
61
|
+
//#endregion
|
|
62
|
+
//#endregion
|
|
7
63
|
//#region src/connection.d.ts
|
|
8
64
|
export interface EdgeRef {
|
|
9
65
|
readonly entity: string;
|
|
@@ -49,6 +105,96 @@ export declare const hasNext: (connection: Connection) => boolean;
|
|
|
49
105
|
/** A connection with more than one segment has an explicit unknown gap. */
|
|
50
106
|
export declare const isGapped: (connection: Connection) => boolean;
|
|
51
107
|
//#endregion
|
|
108
|
+
//#region src/remoteData.d.ts
|
|
109
|
+
export interface RemoteError {
|
|
110
|
+
readonly _tag: string;
|
|
111
|
+
readonly message: string;
|
|
112
|
+
}
|
|
113
|
+
export type RemoteData<A> = {
|
|
114
|
+
readonly _tag: 'Initial';
|
|
115
|
+
} | {
|
|
116
|
+
readonly _tag: 'Loading';
|
|
117
|
+
} | {
|
|
118
|
+
readonly _tag: 'Ready';
|
|
119
|
+
readonly value: A;
|
|
120
|
+
} | {
|
|
121
|
+
readonly _tag: 'Refreshing';
|
|
122
|
+
readonly value: A;
|
|
123
|
+
} | {
|
|
124
|
+
readonly _tag: 'Failed';
|
|
125
|
+
readonly error: RemoteError;
|
|
126
|
+
readonly previous?: A;
|
|
127
|
+
} | {
|
|
128
|
+
readonly _tag: 'NotFound';
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* What `RemoteData.render` tells the `data` branch about the value it is
|
|
132
|
+
* given: whether a newer answer is on its way, and whether this one is what
|
|
133
|
+
* was last known good before a read failed. It is one tag rather than two
|
|
134
|
+
* booleans because a value cannot be both at once, and a type that can say so
|
|
135
|
+
* invites a view to handle a state that never arrives.
|
|
136
|
+
*/
|
|
137
|
+
export type Freshness = {
|
|
138
|
+
readonly _tag: 'Fresh';
|
|
139
|
+
} | {
|
|
140
|
+
readonly _tag: 'Refreshing';
|
|
141
|
+
} | {
|
|
142
|
+
readonly _tag: 'Stale';
|
|
143
|
+
readonly error: RemoteError;
|
|
144
|
+
};
|
|
145
|
+
export declare const remoteErrorSchema: Schema.Struct<{
|
|
146
|
+
readonly _tag: Schema.String;
|
|
147
|
+
readonly message: Schema.String;
|
|
148
|
+
}>;
|
|
149
|
+
export declare const remoteDataSchema: <A>(value: Schema.Schema<A>) => Schema.Codec<RemoteData<A>, unknown>;
|
|
150
|
+
export declare const RemoteData: {
|
|
151
|
+
/**
|
|
152
|
+
* The `RemoteData` schema for a value schema. Exposed so a `RemoteData` can
|
|
153
|
+
* be embedded in a hand-written Model schema, not only through `Remote.select`.
|
|
154
|
+
*/
|
|
155
|
+
schema: <A>(value: Schema.Schema<A>) => Schema.Schema<RemoteData<A>>;
|
|
156
|
+
/** Exhaustive: omitting a state is a compile error. */
|
|
157
|
+
match: <A, R>(data: RemoteData<A>, cases: {
|
|
158
|
+
readonly Initial: () => R;
|
|
159
|
+
readonly Loading: () => R;
|
|
160
|
+
readonly Ready: (value: A) => R;
|
|
161
|
+
readonly Refreshing: (value: A) => R;
|
|
162
|
+
readonly Failed: (error: RemoteError, previous: Option.Option<A>) => R;
|
|
163
|
+
readonly NotFound: () => R;
|
|
164
|
+
}) => R;
|
|
165
|
+
/**
|
|
166
|
+
* The view-oriented fold: the six states as the three things a view actually
|
|
167
|
+
* draws, under the policy that useful data stays on screen. A value that is
|
|
168
|
+
* being refetched, or that a later read failed to replace, still reaches
|
|
169
|
+
* `data` — with `freshness` saying which — instead of being replaced by a
|
|
170
|
+
* spinner or an error the reader cannot act on.
|
|
171
|
+
*
|
|
172
|
+
* ```ts
|
|
173
|
+
* RemoteData.render(model.user, {
|
|
174
|
+
* loading: () => UserSkeleton(),
|
|
175
|
+
* notFound: () => NoSuchUser(),
|
|
176
|
+
* failed: error => ErrorView(error),
|
|
177
|
+
* data: (user, freshness) => UserView({ user, dimmed: freshness._tag !== 'Fresh' }),
|
|
178
|
+
* })
|
|
179
|
+
* ```
|
|
180
|
+
*
|
|
181
|
+
* `notFound` is its own branch and not optional: a row the server answered
|
|
182
|
+
* for and does not have is neither loading nor a failure, and drawing it as
|
|
183
|
+
* either is a spinner that never ends or an error nobody can fix.
|
|
184
|
+
*
|
|
185
|
+
* This does not replace `match`, which is the exhaustive fold over the states
|
|
186
|
+
* themselves. Reach for `match` when the six states really do draw
|
|
187
|
+
* differently, and for this when they draw the usual three ways.
|
|
188
|
+
*/
|
|
189
|
+
render: <A, R>(data: RemoteData<A>, cases: {
|
|
190
|
+
readonly loading: () => R;
|
|
191
|
+
readonly notFound: () => R;
|
|
192
|
+
readonly failed: (error: RemoteError) => R;
|
|
193
|
+
readonly data: (value: A, freshness: Freshness) => R;
|
|
194
|
+
}) => R;
|
|
195
|
+
map: <A, B>(data: RemoteData<A>, f: (value: A) => B) => RemoteData<B>;
|
|
196
|
+
};
|
|
197
|
+
//#endregion
|
|
52
198
|
//#region src/store.d.ts
|
|
53
199
|
export type EntityKey = string;
|
|
54
200
|
export interface EntityEntry {
|
|
@@ -128,6 +274,8 @@ export interface MutationState {
|
|
|
128
274
|
readonly pending: ReadonlySet<string>;
|
|
129
275
|
readonly applied: ReadonlySet<string>;
|
|
130
276
|
readonly failed: ReadonlySet<string>;
|
|
277
|
+
/** Why each request in `failed` failed, kept exactly as long as `failed` keeps its id. */
|
|
278
|
+
readonly errors: ReadonlyMap<string, RemoteError>;
|
|
131
279
|
/**
|
|
132
280
|
* How many mutations this model has started. A bound domain takes its next
|
|
133
281
|
* request id from here, so `update` stays pure and the id exists before the
|
|
@@ -137,7 +285,21 @@ export interface MutationState {
|
|
|
137
285
|
}
|
|
138
286
|
export declare const emptyMutationState: MutationState;
|
|
139
287
|
export declare const beginMutation: (state: MutationState, requestId: string) => MutationState;
|
|
140
|
-
export declare const failMutation: (state: MutationState, requestId: string) => MutationState;
|
|
288
|
+
export declare const failMutation: (state: MutationState, requestId: string, error: RemoteError) => MutationState;
|
|
289
|
+
/** What Remote knows of one mutation request, by the id `mutate` returned. */
|
|
290
|
+
export type MutationStatus =
|
|
291
|
+
/** Never started here, or settled so long ago its id has left the window. */
|
|
292
|
+
{
|
|
293
|
+
readonly _tag: 'Unknown';
|
|
294
|
+
} | {
|
|
295
|
+
readonly _tag: 'Pending';
|
|
296
|
+
} | {
|
|
297
|
+
readonly _tag: 'Applied';
|
|
298
|
+
} | {
|
|
299
|
+
readonly _tag: 'Failed';
|
|
300
|
+
readonly error: RemoteError;
|
|
301
|
+
};
|
|
302
|
+
export declare const mutationStatus: (state: MutationState, requestId: string) => MutationStatus;
|
|
141
303
|
export interface Reconciled {
|
|
142
304
|
readonly store: EntityStore;
|
|
143
305
|
readonly state: MutationState;
|
|
@@ -147,7 +309,10 @@ export interface Reconciled {
|
|
|
147
309
|
* with the same `requestId` (a retry, or a live event describing the same change)
|
|
148
310
|
* does not re-apply the entities, but still clears `pending`.
|
|
149
311
|
*/
|
|
150
|
-
export declare const reconcileMutation: (store: EntityStore, state: MutationState, requestId: string, entities: ReadonlyArray<NormalizedPatch
|
|
312
|
+
export declare const reconcileMutation: (store: EntityStore, state: MutationState, requestId: string, entities: ReadonlyArray<NormalizedPatch>, deleted?: ReadonlyArray<{
|
|
313
|
+
readonly entity: string;
|
|
314
|
+
readonly id: string;
|
|
315
|
+
}>) => Reconciled;
|
|
151
316
|
/** A mutation an application declares: its name and input/output codecs. */
|
|
152
317
|
export interface MutationDescriptor<Name extends string, Input, Output> {
|
|
153
318
|
readonly name: Name;
|
|
@@ -202,7 +367,20 @@ export interface QueryDescriptor<Name extends string, Input, Result> {
|
|
|
202
367
|
readonly Input: Schema.Codec<Input>;
|
|
203
368
|
readonly Result: Result;
|
|
204
369
|
readonly ref: (input: Input) => QueryRef<Name, Input>;
|
|
370
|
+
/**
|
|
371
|
+
* What the query means, when it was declared with `Query.define`: the rows it
|
|
372
|
+
* is about, as a value an interpreter can compile. A descriptor from
|
|
373
|
+
* `Query.make` has none — its meaning lives in whatever the server registered
|
|
374
|
+
* to answer it — and a source is free to answer either.
|
|
375
|
+
*/
|
|
376
|
+
readonly body?: AnyQuery | undefined;
|
|
205
377
|
}
|
|
378
|
+
/**
|
|
379
|
+
* The inputs of a definition's body: one placeholder per key of `Input`, typed
|
|
380
|
+
* as that key's value. A body is built once, so these stand for what the query
|
|
381
|
+
* will be given rather than being it.
|
|
382
|
+
*/
|
|
383
|
+
export type QueryInputs<Input> = { readonly [K in keyof Input & string]: InputExpr<Input[K]>; };
|
|
206
384
|
/** Stable stringify: object keys sorted, undefined-valued keys dropped, so equal inputs encode equally. */
|
|
207
385
|
export declare const stableStringify: (value: unknown) => string;
|
|
208
386
|
/**
|
|
@@ -213,6 +391,8 @@ export declare const stableStringify: (value: unknown) => string;
|
|
|
213
391
|
export type ResultOf<Result> = Result extends {
|
|
214
392
|
readonly name: infer Entity extends string;
|
|
215
393
|
} ? ConnectionSpec<Entity> : Result;
|
|
394
|
+
/** A relational `Query` over one Entity, as `Query.define`'s body returns. */
|
|
395
|
+
type RelationalQueryOf<E extends AnyEntity> = ReturnType<typeof Query$1.from<E>>;
|
|
216
396
|
export declare const Query: {
|
|
217
397
|
/** Declares the entity and options a query's result is a connection over. */
|
|
218
398
|
connection: <Entity extends string>(entity: {
|
|
@@ -230,10 +410,44 @@ export declare const Query: {
|
|
|
230
410
|
readonly Input: Input;
|
|
231
411
|
readonly Result: Result;
|
|
232
412
|
}) => QueryDescriptor<Name, TypeOf<Input>, ResultOf<Result>>;
|
|
413
|
+
/**
|
|
414
|
+
* Declares a query by what it means. The body is built once, here, and the
|
|
415
|
+
* `input` it is given holds placeholders rather than values — there is
|
|
416
|
+
* nothing yet to branch on, so a condition that depends on what was passed is
|
|
417
|
+
* a comparison over the placeholder:
|
|
418
|
+
*
|
|
419
|
+
* ```ts
|
|
420
|
+
* const PostsBySlug = Query.define('PostsBySlug', { slug: Schema.String }, ({ input }) =>
|
|
421
|
+
* Query.from(Post).pipe(
|
|
422
|
+
* Query.where(Expr.eq(Post.fields.slug, input.slug)),
|
|
423
|
+
* Query.orderBy(Order.asc(Post.fields.id)),
|
|
424
|
+
* ),
|
|
425
|
+
* )
|
|
426
|
+
* ```
|
|
427
|
+
*
|
|
428
|
+
* What comes back is an ordinary `QueryDescriptor` — the same name, `Input`,
|
|
429
|
+
* `ref` and connection identity `Query.make` gives — carrying its `body`
|
|
430
|
+
* besides. A server can compile that body instead of being told the same
|
|
431
|
+
* thing again in its own dialect, and one that would rather answer the query
|
|
432
|
+
* its own way still can.
|
|
433
|
+
*
|
|
434
|
+
* The result is a connection over the Entity the body reads, so it is not
|
|
435
|
+
* named twice.
|
|
436
|
+
*/
|
|
437
|
+
define: <const Name extends string, Input extends SchemaOrFields, E extends AnyEntity>(name: Name, Input: Input, body: (context: {
|
|
438
|
+
readonly input: QueryInputs<TypeOf<Input>>;
|
|
439
|
+
}) => RelationalQueryOf<E>, options?: {
|
|
440
|
+
readonly edgeKey?: Schema.Schema<unknown>;
|
|
441
|
+
readonly live?: LivePolicy;
|
|
442
|
+
}) => QueryDescriptor<Name, TypeOf<Input>, ConnectionSpec<E["name"]>>;
|
|
233
443
|
first: (count: number) => <R extends QueryRef<string, unknown>>(ref: R) => R;
|
|
234
444
|
last: (count: number) => <R extends QueryRef<string, unknown>>(ref: R) => R;
|
|
235
445
|
after: (cursor: Cursor) => <R extends QueryRef<string, unknown>>(ref: R) => R;
|
|
236
446
|
before: (cursor: Cursor) => <R extends QueryRef<string, unknown>>(ref: R) => R;
|
|
447
|
+
from: <E extends AnyEntity>(entity: E) => Query$1<E>;
|
|
448
|
+
where: (...predicates: ReadonlyArray<import("foldkit-entity").Predicate>) => <E extends AnyEntity>(self: Query$1<E>) => Query$1<E>;
|
|
449
|
+
orderBy: (...terms: ReadonlyArray<import("foldkit-entity").OrderTerm>) => <E extends AnyEntity>(self: Query$1<E>) => Query$1<E>;
|
|
450
|
+
dependencies: (self: AnyQuery) => import("foldkit-entity").Dependencies;
|
|
237
451
|
};
|
|
238
452
|
//#endregion
|
|
239
453
|
//#region src/requirement.d.ts
|
|
@@ -283,9 +497,9 @@ export declare const Requirement: {
|
|
|
283
497
|
mergeConnections: typeof mergeConnections;
|
|
284
498
|
};
|
|
285
499
|
/** Remote's entity requirements on a Projection, unioned per entity and id as nodes compose. */
|
|
286
|
-
export declare const RemoteRequirements:
|
|
500
|
+
export declare const RemoteRequirements: MetadataKey<Requirement>;
|
|
287
501
|
/** Remote's query connections on a Projection, unioned per connection and window. */
|
|
288
|
-
export declare const RemoteConnections:
|
|
502
|
+
export declare const RemoteConnections: MetadataKey<ConnectionRequirement>;
|
|
289
503
|
/** The entity requirements a Projection carries. */
|
|
290
504
|
export declare const requirementsOf: (projection: {
|
|
291
505
|
readonly metadata: Metadata;
|
|
@@ -381,7 +595,10 @@ export declare const pruneOverlays: (optimistic: OptimisticState, connection: st
|
|
|
381
595
|
* a later layer re-wins on rebase. Confirmed connection changes are recorded
|
|
382
596
|
* once per request, as overlays the request no longer owns.
|
|
383
597
|
*/
|
|
384
|
-
export declare const settleSuccess: (base: EntityStore, optimistic: OptimisticState, state: MutationState, requestId: string, entities: ReadonlyArray<NormalizedPatch>, connections?: ReadonlyArray<ConnectionChange
|
|
598
|
+
export declare const settleSuccess: (base: EntityStore, optimistic: OptimisticState, state: MutationState, requestId: string, entities: ReadonlyArray<NormalizedPatch>, connections?: ReadonlyArray<ConnectionChange>, deleted?: ReadonlyArray<{
|
|
599
|
+
readonly entity: string;
|
|
600
|
+
readonly id: string;
|
|
601
|
+
}>) => {
|
|
385
602
|
readonly store: EntityStore;
|
|
386
603
|
readonly state: MutationState;
|
|
387
604
|
readonly optimistic: OptimisticState;
|
|
@@ -672,6 +889,11 @@ export declare const MutationResult: Schema.Struct<{
|
|
|
672
889
|
readonly key: Schema.String;
|
|
673
890
|
}>;
|
|
674
891
|
}>]>>>;
|
|
892
|
+
/** Entities the mutation deleted; the client knows them absent from then on. */
|
|
893
|
+
readonly deleted: Schema.optional<Schema.$Array<Schema.Struct<{
|
|
894
|
+
readonly entity: Schema.String;
|
|
895
|
+
readonly id: Schema.String;
|
|
896
|
+
}>>>;
|
|
675
897
|
}>;
|
|
676
898
|
export declare const LiveRequirement: Schema.Struct<{
|
|
677
899
|
readonly version: Schema.Number;
|
|
@@ -760,6 +982,11 @@ export declare const Mutate: Rpc.Rpc<"FoldkitRemoteMutate", Schema.Struct<{
|
|
|
760
982
|
readonly key: Schema.String;
|
|
761
983
|
}>;
|
|
762
984
|
}>]>>>;
|
|
985
|
+
/** Entities the mutation deleted; the client knows them absent from then on. */
|
|
986
|
+
readonly deleted: Schema.optional<Schema.$Array<Schema.Struct<{
|
|
987
|
+
readonly entity: Schema.String;
|
|
988
|
+
readonly id: Schema.String;
|
|
989
|
+
}>>>;
|
|
763
990
|
}>, typeof RemoteMutationError, never, never>;
|
|
764
991
|
export declare const Live: Rpc.Rpc<"FoldkitRemoteLive", Schema.Struct<{
|
|
765
992
|
readonly version: Schema.Number;
|
|
@@ -924,6 +1151,11 @@ export declare const RemoteRpc: RpcGroup.RpcGroup<Rpc.Rpc<"FoldkitRemoteRead", S
|
|
|
924
1151
|
readonly key: Schema.String;
|
|
925
1152
|
}>;
|
|
926
1153
|
}>]>>>;
|
|
1154
|
+
/** Entities the mutation deleted; the client knows them absent from then on. */
|
|
1155
|
+
readonly deleted: Schema.optional<Schema.$Array<Schema.Struct<{
|
|
1156
|
+
readonly entity: Schema.String;
|
|
1157
|
+
readonly id: Schema.String;
|
|
1158
|
+
}>>>;
|
|
927
1159
|
}>, typeof RemoteMutationError, never, never> | Rpc.Rpc<"FoldkitRemoteLive", Schema.Struct<{
|
|
928
1160
|
readonly version: Schema.Number;
|
|
929
1161
|
readonly requirements: Schema.$Array<Schema.Codec<Requirement, Requirement, never, never>>;
|
|
@@ -997,51 +1229,6 @@ export declare const RemoteRpc: RpcGroup.RpcGroup<Rpc.Rpc<"FoldkitRemoteRead", S
|
|
|
997
1229
|
}>]>;
|
|
998
1230
|
}>, typeof RemoteQueryError, never, never>>;
|
|
999
1231
|
//#endregion
|
|
1000
|
-
//#region src/remoteData.d.ts
|
|
1001
|
-
export interface RemoteError {
|
|
1002
|
-
readonly _tag: string;
|
|
1003
|
-
readonly message: string;
|
|
1004
|
-
}
|
|
1005
|
-
export type RemoteData<A> = {
|
|
1006
|
-
readonly _tag: 'Initial';
|
|
1007
|
-
} | {
|
|
1008
|
-
readonly _tag: 'Loading';
|
|
1009
|
-
} | {
|
|
1010
|
-
readonly _tag: 'Ready';
|
|
1011
|
-
readonly value: A;
|
|
1012
|
-
} | {
|
|
1013
|
-
readonly _tag: 'Refreshing';
|
|
1014
|
-
readonly value: A;
|
|
1015
|
-
} | {
|
|
1016
|
-
readonly _tag: 'Failed';
|
|
1017
|
-
readonly error: RemoteError;
|
|
1018
|
-
readonly previous?: A;
|
|
1019
|
-
} | {
|
|
1020
|
-
readonly _tag: 'NotFound';
|
|
1021
|
-
};
|
|
1022
|
-
export declare const remoteErrorSchema: Schema.Struct<{
|
|
1023
|
-
readonly _tag: Schema.String;
|
|
1024
|
-
readonly message: Schema.String;
|
|
1025
|
-
}>;
|
|
1026
|
-
export declare const remoteDataSchema: <A>(value: Schema.Schema<A>) => Schema.Codec<RemoteData<A>, unknown>;
|
|
1027
|
-
export declare const RemoteData: {
|
|
1028
|
-
/**
|
|
1029
|
-
* The `RemoteData` schema for a value schema. Exposed so a `RemoteData` can
|
|
1030
|
-
* be embedded in a hand-written Model schema, not only through `Remote.select`.
|
|
1031
|
-
*/
|
|
1032
|
-
schema: <A>(value: Schema.Schema<A>) => Schema.Schema<RemoteData<A>>;
|
|
1033
|
-
/** Exhaustive: omitting a state is a compile error. */
|
|
1034
|
-
match: <A, R>(data: RemoteData<A>, cases: {
|
|
1035
|
-
readonly Initial: () => R;
|
|
1036
|
-
readonly Loading: () => R;
|
|
1037
|
-
readonly Ready: (value: A) => R;
|
|
1038
|
-
readonly Refreshing: (value: A) => R;
|
|
1039
|
-
readonly Failed: (error: RemoteError, previous: Option.Option<A>) => R;
|
|
1040
|
-
readonly NotFound: () => R;
|
|
1041
|
-
}) => R;
|
|
1042
|
-
map: <A, B>(data: RemoteData<A>, f: (value: A) => B) => RemoteData<B>;
|
|
1043
|
-
};
|
|
1044
|
-
//#endregion
|
|
1045
1232
|
//#region src/model.d.ts
|
|
1046
1233
|
/**
|
|
1047
1234
|
* The normalized cache as a Foldkit Submodel. `Remote.define` returns a schema
|
|
@@ -1068,15 +1255,25 @@ export interface RemoteModel {
|
|
|
1068
1255
|
*/
|
|
1069
1256
|
readonly loading: ReadonlySet<string>;
|
|
1070
1257
|
/**
|
|
1071
|
-
* `Remote.refresh` generations
|
|
1072
|
-
*
|
|
1073
|
-
*
|
|
1258
|
+
* `Remote.refresh` generations, held per field rather than for the store as a
|
|
1259
|
+
* whole. A read entry restarts when the generation over the fields it plans
|
|
1260
|
+
* moves, so a read sent before a refresh of its own fields cannot land after
|
|
1261
|
+
* it, and a refresh of one field leaves every other entry's read alone.
|
|
1074
1262
|
*/
|
|
1075
|
-
readonly refresh:
|
|
1076
|
-
readonly requested: number;
|
|
1077
|
-
readonly started: number;
|
|
1078
|
-
};
|
|
1263
|
+
readonly refresh: RefreshState;
|
|
1079
1264
|
}
|
|
1265
|
+
/**
|
|
1266
|
+
* `Remote.refresh` generations, per field mark. `generation` is the last one
|
|
1267
|
+
* handed out; `requested` is when each field was last refreshed, and `started`
|
|
1268
|
+
* is the generation each field's read last began under. Holding them per field
|
|
1269
|
+
* is what keeps one Projection's refresh from restarting every read entry.
|
|
1270
|
+
*/
|
|
1271
|
+
export interface RefreshState {
|
|
1272
|
+
readonly generation: number;
|
|
1273
|
+
readonly requested: ReadonlyMap<string, number>;
|
|
1274
|
+
readonly started: ReadonlyMap<string, number>;
|
|
1275
|
+
}
|
|
1276
|
+
export declare const emptyRefresh: RefreshState;
|
|
1080
1277
|
export declare const initialRemoteModel: RemoteModel;
|
|
1081
1278
|
export declare const remoteModelSchema: () => Schema.Codec<RemoteModel, unknown>;
|
|
1082
1279
|
/** The submodel's Messages; each reduces to `RemoteModel` through `updateRemote`. */
|
|
@@ -1124,10 +1321,28 @@ export type RemoteMessage = {
|
|
|
1124
1321
|
readonly entities: readonly NormalizedPatch[];
|
|
1125
1322
|
/** Connection changes the server confirmed; they replace the request's own. */
|
|
1126
1323
|
readonly connections?: ReadonlyArray<ConnectionChange> | undefined;
|
|
1324
|
+
/** Entities the mutation deleted: tombstoned, which hides them from every connection. */
|
|
1325
|
+
readonly deleted?: ReadonlyArray<{
|
|
1326
|
+
readonly entity: string;
|
|
1327
|
+
readonly id: string;
|
|
1328
|
+
}> | undefined;
|
|
1127
1329
|
} | {
|
|
1128
1330
|
readonly _tag: 'MutationFailed';
|
|
1129
1331
|
readonly requestId: string;
|
|
1130
1332
|
readonly error: RemoteError;
|
|
1333
|
+
} |
|
|
1334
|
+
/**
|
|
1335
|
+
* Operations shown over the store with no request behind them, until they are
|
|
1336
|
+
* lifted: a preview of a change nobody has made. Showing an id again replaces
|
|
1337
|
+
* what it showed.
|
|
1338
|
+
*/
|
|
1339
|
+
{
|
|
1340
|
+
readonly _tag: 'OverlayShown';
|
|
1341
|
+
readonly id: string;
|
|
1342
|
+
readonly optimistic: ReadonlyArray<OptimisticOperation>;
|
|
1343
|
+
} | {
|
|
1344
|
+
readonly _tag: 'OverlayLifted';
|
|
1345
|
+
readonly id: string;
|
|
1131
1346
|
} | {
|
|
1132
1347
|
readonly _tag: 'LiveReceived';
|
|
1133
1348
|
readonly stream: string;
|
|
@@ -1218,6 +1433,10 @@ export declare const remoteMessageCases: {
|
|
|
1218
1433
|
readonly values: Schema.$Record<Schema.String, Schema.Unknown>;
|
|
1219
1434
|
}>>;
|
|
1220
1435
|
connections: Schema.optional<Schema.$Array<Schema.Unknown>>;
|
|
1436
|
+
deleted: Schema.optional<Schema.$Array<Schema.Struct<{
|
|
1437
|
+
readonly entity: Schema.String;
|
|
1438
|
+
readonly id: Schema.String;
|
|
1439
|
+
}>>>;
|
|
1221
1440
|
};
|
|
1222
1441
|
MutationFailed: {
|
|
1223
1442
|
requestId: Schema.String;
|
|
@@ -1226,6 +1445,13 @@ export declare const remoteMessageCases: {
|
|
|
1226
1445
|
readonly message: Schema.String;
|
|
1227
1446
|
}>;
|
|
1228
1447
|
};
|
|
1448
|
+
OverlayShown: {
|
|
1449
|
+
id: Schema.String;
|
|
1450
|
+
optimistic: Schema.$Array<Schema.Unknown>;
|
|
1451
|
+
};
|
|
1452
|
+
OverlayLifted: {
|
|
1453
|
+
id: Schema.String;
|
|
1454
|
+
};
|
|
1229
1455
|
LiveReceived: {
|
|
1230
1456
|
stream: Schema.String;
|
|
1231
1457
|
event: Schema.Unknown;
|
|
@@ -1273,6 +1499,25 @@ export declare const isRemoteMessage: <M extends {
|
|
|
1273
1499
|
readonly _tag: RemoteMessageTag;
|
|
1274
1500
|
}>;
|
|
1275
1501
|
export declare const remoteMessageSchema: Schema.Schema<RemoteMessage>;
|
|
1502
|
+
/**
|
|
1503
|
+
* The generation a read is planned under: the highest any of the fields it
|
|
1504
|
+
* reads or connections it runs was refreshed at. A read entry carries this as a
|
|
1505
|
+
* dependency, so it restarts when what it observes is refreshed and not when
|
|
1506
|
+
* anything else is.
|
|
1507
|
+
*/
|
|
1508
|
+
export declare const refreshedAt: (refresh: RefreshState, requests: ReadonlyArray<Requirement>, connections?: ReadonlyArray<string>) => number;
|
|
1509
|
+
/**
|
|
1510
|
+
* Marks every field of `requests` and every connection in `connections`
|
|
1511
|
+
* refreshed at the next generation.
|
|
1512
|
+
*/
|
|
1513
|
+
export declare const withRefreshRequested: (refresh: RefreshState, requests: ReadonlyArray<Requirement>, connections?: ReadonlyArray<string>) => RefreshState;
|
|
1514
|
+
/**
|
|
1515
|
+
* Whether one field's read already began under the generation that field was
|
|
1516
|
+
* last refreshed at. Such a read is in flight and would otherwise outlive the
|
|
1517
|
+
* refresh, so the refresh takes a new generation to restart it even though the
|
|
1518
|
+
* field is already stale.
|
|
1519
|
+
*/
|
|
1520
|
+
export declare const refreshIsInFlight: (refresh: RefreshState, request: Requirement, field: string) => boolean;
|
|
1276
1521
|
/**
|
|
1277
1522
|
* Whether a read is fetching any of `fields` for this entity. Consulted only
|
|
1278
1523
|
* when the store lacks the value, which is what separates `Loading` from
|
|
@@ -1374,6 +1619,10 @@ export declare const mutateRemote: <Name extends string, Input, Output>(mutation
|
|
|
1374
1619
|
};
|
|
1375
1620
|
}[];
|
|
1376
1621
|
connections: ConnectionChange[];
|
|
1622
|
+
deleted: readonly {
|
|
1623
|
+
readonly entity: string;
|
|
1624
|
+
readonly id: string;
|
|
1625
|
+
}[];
|
|
1377
1626
|
}, RemoteMutationError, RemoteClient>;
|
|
1378
1627
|
//#endregion
|
|
1379
1628
|
//#region src/relation.d.ts
|
|
@@ -1397,6 +1646,25 @@ export interface RelationShape {
|
|
|
1397
1646
|
readonly nullable: boolean;
|
|
1398
1647
|
readonly entity: string;
|
|
1399
1648
|
}
|
|
1649
|
+
/**
|
|
1650
|
+
* A page of a relation that is a whole list is read under a name of its own, the
|
|
1651
|
+
* field's name and the page's size: `comments@first=10`. It is a field like any
|
|
1652
|
+
* other to the store, the planner, and the wire, so the whole list and a page of
|
|
1653
|
+
* it are held side by side, merged, marked stale, and persisted each on its own.
|
|
1654
|
+
* The server reads the name apart, reads the relation with the window, and
|
|
1655
|
+
* answers under the alias. A cursor is not part of the name: a page read from a
|
|
1656
|
+
* cursor merges onto the page it continues.
|
|
1657
|
+
*/
|
|
1658
|
+
export declare const RELATION_ALIAS = "@";
|
|
1659
|
+
/** The alias a relation field is read under, for a window of this size. */
|
|
1660
|
+
export declare const relationAlias: (field: string, window: {
|
|
1661
|
+
readonly first?: number | undefined;
|
|
1662
|
+
readonly last?: number | undefined;
|
|
1663
|
+
readonly after?: string | undefined;
|
|
1664
|
+
readonly before?: string | undefined;
|
|
1665
|
+
}) => string;
|
|
1666
|
+
/** The field an alias reads; a name that is no alias is its own field. */
|
|
1667
|
+
export declare const aliasedField: (name: string) => string;
|
|
1400
1668
|
/** Splits a ref key (`"Entity:id"`, the store key too) back into its parts. */
|
|
1401
1669
|
export declare const refParts: (encoded: string) => RefParts;
|
|
1402
1670
|
/**
|
|
@@ -1458,6 +1726,11 @@ export declare const pageSchema: (item: AnySchema) => AnySchema;
|
|
|
1458
1726
|
/** The requirement a nested selection contributes for its relation's target. */
|
|
1459
1727
|
export declare const relationOf: (selection: Selection<unknown>) => RelationRequirement;
|
|
1460
1728
|
export declare const Selection: {
|
|
1729
|
+
/**
|
|
1730
|
+
* The Remote Selection of a `foldkit-entity` Selection: the same members, read
|
|
1731
|
+
* from Remote's store. `true` on a relation reads its refs, as it does here.
|
|
1732
|
+
*/
|
|
1733
|
+
from: <Name extends string, S extends Schema.Constraint>(selection: Domain.Selection<Name, unknown, S>) => Selection<S["Type"], Name, "entity">;
|
|
1461
1734
|
/**
|
|
1462
1735
|
* The fields to read of `entity`. A nested `Selection` on a relation field
|
|
1463
1736
|
* reads through the ref (or refs, or page of refs) the field holds into the
|
|
@@ -1526,7 +1799,25 @@ export interface RefPage<Name extends string, F extends Schema.Struct.Fields = S
|
|
|
1526
1799
|
readonly hasNext: boolean;
|
|
1527
1800
|
readonly hasPrevious: boolean;
|
|
1528
1801
|
}
|
|
1802
|
+
/** The ref codec a `foldkit-entity` relation becomes: one ref, a nullable ref, or an array of refs. */
|
|
1803
|
+
type RelationField<Spec> = Spec extends Domain.RelationSpec<infer Target, infer Cardinality, infer Optional> ? Cardinality extends 'many' ? Schema.$Array<Schema.Codec<EntityRef<Target['name']>, string>> : Optional extends true ? Schema.NullOr<Schema.Codec<EntityRef<Target['name']>, string>> : Schema.Codec<EntityRef<Target['name']>, string> : never;
|
|
1804
|
+
/**
|
|
1805
|
+
* The fields Remote stores for a `foldkit-entity` Entity: its own fields, its
|
|
1806
|
+
* derived members (the server supplies them like any field), and each relation
|
|
1807
|
+
* as refs.
|
|
1808
|
+
*/
|
|
1809
|
+
export type FieldsFrom<E> = E extends Domain.Entity<any, infer Fields, infer Relations, infer Derived> ? Fields & { readonly [K in keyof Derived]: Derived[K]['schema']; } & { readonly [K in keyof Relations]: RelationField<Relations[K]>; } : never;
|
|
1529
1810
|
declare const Entity$1: {
|
|
1811
|
+
/**
|
|
1812
|
+
* The Remote descriptor of a `foldkit-entity` Entity. Relations declared with
|
|
1813
|
+
* `Entity.relate` become ref fields, so the store, the planner, and the server
|
|
1814
|
+
* treat it as they treat an `Entity.make` descriptor.
|
|
1815
|
+
*/
|
|
1816
|
+
from: <E extends Domain.AnyEntity>(entity: E & {
|
|
1817
|
+
readonly fields: {
|
|
1818
|
+
readonly id: unknown;
|
|
1819
|
+
};
|
|
1820
|
+
}) => EntityDescriptor<E["name"], FieldsFrom<E>>;
|
|
1530
1821
|
make: <const Name extends string, const F extends Schema.Struct.Fields & {
|
|
1531
1822
|
readonly id: Schema.Schema<unknown>;
|
|
1532
1823
|
}>(name: Name, schema: Schema.Struct<F>) => EntityDescriptor<Name, F>;
|
|
@@ -1571,6 +1862,13 @@ export interface RemoteInspection {
|
|
|
1571
1862
|
readonly connections: ReadonlyArray<string>;
|
|
1572
1863
|
readonly live: ReadonlyArray<string>;
|
|
1573
1864
|
readonly gaps: ReadonlyArray<string>;
|
|
1865
|
+
/**
|
|
1866
|
+
* The `entity\0id\0field` marks of reads in flight. This is what "Remote is
|
|
1867
|
+
* working" means here, and it is read from the Model rather than from the
|
|
1868
|
+
* fibers doing the work: a tool that shows it shows something the Model can
|
|
1869
|
+
* be replayed to, and nothing that needs the runtime to be asked.
|
|
1870
|
+
*/
|
|
1871
|
+
readonly loading: ReadonlyArray<string>;
|
|
1574
1872
|
readonly mutations: {
|
|
1575
1873
|
readonly pending: ReadonlyArray<string>;
|
|
1576
1874
|
readonly failed: ReadonlyArray<string>;
|
|
@@ -1640,7 +1938,18 @@ export declare const RemotePolicy: {
|
|
|
1640
1938
|
};
|
|
1641
1939
|
//#endregion
|
|
1642
1940
|
//#region src/index.d.ts
|
|
1643
|
-
|
|
1941
|
+
/** What a domain registers: a Remote descriptor, or a `foldkit-entity` Entity (read through `Entity.from`). */
|
|
1942
|
+
export type EntityLike = EntityDescriptor<any, any> | Domain.AnyEntity;
|
|
1943
|
+
/**
|
|
1944
|
+
* What a read selects: a Remote Selection, or a `foldkit-entity` Selection
|
|
1945
|
+
* (read through `Selection.from`).
|
|
1946
|
+
*/
|
|
1947
|
+
export type EntitySelection<Value, Name extends string, Id extends string = string> = Selection<Value, Name, 'entity'> | Domain.Selection<Name, unknown, Schema.Constraint & {
|
|
1948
|
+
readonly Type: Value;
|
|
1949
|
+
}, Id>;
|
|
1950
|
+
type EntityName<D> = D extends {
|
|
1951
|
+
readonly name: infer Name extends string;
|
|
1952
|
+
} ? Name : never;
|
|
1644
1953
|
type QueryName<D> = D extends QueryDescriptor<infer Name, any, any> ? Name : never;
|
|
1645
1954
|
type MutationName<D> = D extends MutationDescriptor<infer Name, any, any> ? Name : never;
|
|
1646
1955
|
/**
|
|
@@ -1651,7 +1960,7 @@ type MutationName<D> = D extends MutationDescriptor<infer Name, any, any> ? Name
|
|
|
1651
1960
|
export type Registered<Name extends string, Names extends string, Kind extends string> = [Name] extends [Names] ? unknown : Invalid<`${Kind} "${Name}" is not registered with this Remote domain`>;
|
|
1652
1961
|
/** Nothing when a selection is of the query's entity; otherwise a branded failure naming both. */
|
|
1653
1962
|
export type SelectsEntity<Entity extends string, Of extends string> = [Entity] extends [Of] ? unknown : Invalid<`the selection is of "${Entity}", but the query lists "${Of}"`>;
|
|
1654
|
-
export interface RemoteDescriptor<Entities extends readonly
|
|
1963
|
+
export interface RemoteDescriptor<Entities extends readonly EntityLike[] = readonly EntityLike[], Queries extends readonly QueryDescriptor<any, any, any>[] = readonly QueryDescriptor<any, any, any>[], Mutations extends readonly MutationDescriptor<any, any, any>[] = readonly MutationDescriptor<any, any, any>[]> {
|
|
1655
1964
|
readonly entities: Entities;
|
|
1656
1965
|
readonly queries: Queries;
|
|
1657
1966
|
readonly mutations: Mutations;
|
|
@@ -1701,6 +2010,8 @@ export type SubscriptionEntries<AppModel, Active> = { readonly [K in keyof Activ
|
|
|
1701
2010
|
readonly retain: RemoteEntry<AppModel, any>;
|
|
1702
2011
|
};
|
|
1703
2012
|
export interface SubscriptionsOptions extends ObserveOptions, LiveOptions, RetainOptions {}
|
|
2013
|
+
/** A Remote domain's wiring for an assembly: it routes Remote's Messages into `reduce`, brings its Subscriptions and contract, and requires `RemoteClient` from the runtime's resources Layer. */
|
|
2014
|
+
export type RemoteWiring<AppModel> = Wiring<AppModel, RemoteMessage | RemoteMessageInput, RemoteClient>;
|
|
1704
2015
|
/** What `RemoteDomain.mutate` hands `update`: the Model with the request started, and the Command that settles it. */
|
|
1705
2016
|
export interface MutationStarted<AppModel> {
|
|
1706
2017
|
readonly model: AppModel;
|
|
@@ -1715,15 +2026,15 @@ export interface MutationStarted<AppModel> {
|
|
|
1715
2026
|
* operations over them. Every method compiles to the `Remote.*` function of the
|
|
1716
2027
|
* same name, which stays exported for tooling, SSR, and tests.
|
|
1717
2028
|
*/
|
|
1718
|
-
export interface RemoteDomain<AppModel, Store extends RemoteModel, Entities extends readonly
|
|
2029
|
+
export interface RemoteDomain<AppModel, Store extends RemoteModel, Entities extends readonly EntityLike[], Queries extends readonly QueryDescriptor<any, any, any>[], Mutations extends readonly MutationDescriptor<any, any, any>[]> extends BoundRemote<AppModel, Store, EntityName<Entities[number]>>, RemoteDescriptor<Entities, Queries, Mutations> {
|
|
1719
2030
|
/** `Remote.select`: a Projection reading one entity through a selection of a registered entity. */
|
|
1720
|
-
get<Value, Name extends string>(selection:
|
|
2031
|
+
get<Value, Name extends string, Id extends string = string>(selection: EntitySelection<Value, Name, Id> & Registered<Name, EntityName<Entities[number]>, 'Entity'>, id: NoInfer<Id>): Projection<AppModel, RemoteData<Value>>;
|
|
1721
2032
|
/**
|
|
1722
2033
|
* `get`, and the projection also subscribes to the entity's changes: its
|
|
1723
2034
|
* requirements are marked `live`, so `subscriptions` derives a live entry for
|
|
1724
2035
|
* the Surfaces that read it.
|
|
1725
2036
|
*/
|
|
1726
|
-
live<Value, Name extends string>(selection:
|
|
2037
|
+
live<Value, Name extends string, Id extends string = string>(selection: EntitySelection<Value, Name, Id> & Registered<Name, EntityName<Entities[number]>, 'Entity'>, id: NoInfer<Id>): Projection<AppModel, RemoteData<Value>>;
|
|
1727
2038
|
/**
|
|
1728
2039
|
* A query connection read as a `Page` of items selected of the query's
|
|
1729
2040
|
* entity: `Initial` until the page and every item's selected fields are
|
|
@@ -1738,6 +2049,15 @@ export interface RemoteDomain<AppModel, Store extends RemoteModel, Entities exte
|
|
|
1738
2049
|
previous<Name extends string, Input>(model: AppModel, projection: QueryProjection<AppModel, any, Name, Input>): QueryRef<Name, Input> | undefined;
|
|
1739
2050
|
/** A Command that runs the query and yields the `ConnectionMerged` (or `QueryFailed`) that reduces it: "load more". */
|
|
1740
2051
|
fetch(ref: QueryRef<string, unknown>): Command<RemoteMessage, never, RemoteClient>;
|
|
2052
|
+
/**
|
|
2053
|
+
* Shows operations over the store with no request behind them, as a mutation's
|
|
2054
|
+
* `optimistic` ones show while it is in flight: every Selection and view draws
|
|
2055
|
+
* them. For a preview of a change nobody has made. They stay until `lift`;
|
|
2056
|
+
* showing an id again replaces what it showed. Called from `update`.
|
|
2057
|
+
*/
|
|
2058
|
+
overlay(model: AppModel, id: string, optimistic: ReadonlyArray<OptimisticOperation>): AppModel;
|
|
2059
|
+
/** Lifts what `overlay` showed under this id. Lifting nothing returns the same Model. */
|
|
2060
|
+
lift(model: AppModel, id: string): AppModel;
|
|
1741
2061
|
/** `Remote.refresh`: marks what a projection or a Surface requires as due, for its read entry to refetch. */
|
|
1742
2062
|
refresh(model: AppModel, target: Projection<AppModel, unknown> | Surface<AppModel, any, any, void>): AppModel;
|
|
1743
2063
|
/**
|
|
@@ -1753,6 +2073,30 @@ export interface RemoteDomain<AppModel, Store extends RemoteModel, Entities exte
|
|
|
1753
2073
|
plan<Value>(model: AppModel, projection: Projection<AppModel, Value>, options?: PlanOptions): ReadonlyArray<Requirement>;
|
|
1754
2074
|
/** `Remote.storeOf`: the visible store, base under the pending optimistic layers. */
|
|
1755
2075
|
storeOf(model: AppModel): EntityStore;
|
|
2076
|
+
/**
|
|
2077
|
+
* `Remote.confirmed`: the same projection read against the server-derived
|
|
2078
|
+
* store alone, with every pending optimistic layer and connection overlay
|
|
2079
|
+
* left off. What it plans is unchanged — the requirements are the
|
|
2080
|
+
* projection's own — so observing it reads exactly what observing the
|
|
2081
|
+
* projection reads; only what it *shows* differs.
|
|
2082
|
+
*
|
|
2083
|
+
* A view usually wants the projection itself, which is the visible read:
|
|
2084
|
+
* the optimistic layers are there so a change shows before the server has
|
|
2085
|
+
* agreed to it. This is for the reader that must not believe a change until
|
|
2086
|
+
* the server has confirmed it, which in practice is an Agent capability
|
|
2087
|
+
* reporting that what it was asked to do is done:
|
|
2088
|
+
*
|
|
2089
|
+
* ```ts
|
|
2090
|
+
* Agent.when({
|
|
2091
|
+
* projection: Data.confirmed(ProjectPage.model.project),
|
|
2092
|
+
* predicate: (project, request) => project.name === request.name,
|
|
2093
|
+
* })
|
|
2094
|
+
* ```
|
|
2095
|
+
*
|
|
2096
|
+
* There is deliberately no `visible`: a projection is already the visible
|
|
2097
|
+
* read, and a wrapper that only forwards would be a second name for it.
|
|
2098
|
+
*/
|
|
2099
|
+
confirmed<P extends Projection<AppModel, any>>(projection: P): P;
|
|
1756
2100
|
/**
|
|
1757
2101
|
* The plan run through `RemoteClient` and reduced into the Model: the
|
|
1758
2102
|
* projection's pending queries first, then the fields it lacks (the pages'
|
|
@@ -1766,8 +2110,21 @@ export interface RemoteDomain<AppModel, Store extends RemoteModel, Entities exte
|
|
|
1766
2110
|
* mutation sequence unless `options.requestId` is given.
|
|
1767
2111
|
*/
|
|
1768
2112
|
mutate<M extends MutationDescriptor<any, any, any>>(model: AppModel, mutation: M & Registered<M['name'], MutationName<Mutations[number]>, 'Mutation'>, input: MutationInput<M>, options?: DomainMutateOptions): MutationStarted<AppModel>;
|
|
2113
|
+
/**
|
|
2114
|
+
* What Remote knows of a mutation `mutate` started, by its request id:
|
|
2115
|
+
* pending, applied, or failed with the error the server or transport gave.
|
|
2116
|
+
*/
|
|
2117
|
+
mutation(model: AppModel, requestId: string): MutationStatus;
|
|
1769
2118
|
/** `updateRemote` on the bound slice: reduces one of Remote's Messages, as `RemoteMessage` or as the application's union constructs it. */
|
|
1770
2119
|
reduce(model: AppModel, message: RemoteMessage | RemoteMessageInput): AppModel;
|
|
2120
|
+
/**
|
|
2121
|
+
* How this domain joins an assembly: it routes Remote's Messages into
|
|
2122
|
+
* `reduce`, brings its Subscriptions and contract, and requires
|
|
2123
|
+
* `RemoteClient` from the runtime's resources Layer. One assembly holds at
|
|
2124
|
+
* most one Remote wiring: every domain claims the same tags, and routing
|
|
2125
|
+
* takes the first claimant.
|
|
2126
|
+
*/
|
|
2127
|
+
wiring: <const Active extends Readonly<Record<string, ActiveSurface<AppModel> | Surface<AppModel, any, any, void>>>>(active: Active, options?: SubscriptionsOptions) => RemoteWiring<AppModel>;
|
|
1771
2128
|
/** `Remote.inspect` of the bound slice. */
|
|
1772
2129
|
inspect(model: AppModel): RemoteInspection;
|
|
1773
2130
|
}
|
|
@@ -1789,7 +2146,7 @@ export type QueryWindowOptions = {
|
|
|
1789
2146
|
};
|
|
1790
2147
|
export type QueryOptions<Value, Entity extends string, Of extends string = Entity> = {
|
|
1791
2148
|
/** What to read of each item: a selection of the query's entity (`Of`). */
|
|
1792
|
-
readonly select:
|
|
2149
|
+
readonly select: EntitySelection<Value, Entity> & SelectsEntity<Entity, Of>;
|
|
1793
2150
|
} & QueryWindowOptions;
|
|
1794
2151
|
/** A query connection read as a `Page` of selected items; `ref` is the connection with its first window. */
|
|
1795
2152
|
export interface QueryProjection<AppModel, Value, Name extends string, Input> extends Projection<AppModel, RemoteData<Page<Value>>> {
|
|
@@ -1835,7 +2192,11 @@ declare const PlannedQuery: Schema.Struct<{
|
|
|
1835
2192
|
export interface ReadDependencies {
|
|
1836
2193
|
readonly requirements: ReadonlyArray<Requirement>;
|
|
1837
2194
|
readonly queries: ReadonlyArray<Schema.Schema.Type<typeof PlannedQuery>>;
|
|
1838
|
-
/**
|
|
2195
|
+
/**
|
|
2196
|
+
* The highest generation the fields this entry reads were refreshed at: a
|
|
2197
|
+
* refresh of any of them restarts the entry, and a refresh of anything else
|
|
2198
|
+
* leaves it running.
|
|
2199
|
+
*/
|
|
1839
2200
|
readonly refresh: number;
|
|
1840
2201
|
}
|
|
1841
2202
|
export declare const Remote: {
|
|
@@ -1899,6 +2260,10 @@ export declare const Remote: {
|
|
|
1899
2260
|
readonly values: Schema.$Record<Schema.String, Schema.Unknown>;
|
|
1900
2261
|
}>>;
|
|
1901
2262
|
connections: Schema.optional<Schema.$Array<Schema.Unknown>>;
|
|
2263
|
+
deleted: Schema.optional<Schema.$Array<Schema.Struct<{
|
|
2264
|
+
readonly entity: Schema.String;
|
|
2265
|
+
readonly id: Schema.String;
|
|
2266
|
+
}>>>;
|
|
1902
2267
|
};
|
|
1903
2268
|
MutationFailed: {
|
|
1904
2269
|
requestId: Schema.String;
|
|
@@ -1907,6 +2272,13 @@ export declare const Remote: {
|
|
|
1907
2272
|
readonly message: Schema.String;
|
|
1908
2273
|
}>;
|
|
1909
2274
|
};
|
|
2275
|
+
OverlayShown: {
|
|
2276
|
+
id: Schema.String;
|
|
2277
|
+
optimistic: Schema.$Array<Schema.Unknown>;
|
|
2278
|
+
};
|
|
2279
|
+
OverlayLifted: {
|
|
2280
|
+
id: Schema.String;
|
|
2281
|
+
};
|
|
1910
2282
|
LiveReceived: {
|
|
1911
2283
|
stream: Schema.String;
|
|
1912
2284
|
event: Schema.Unknown;
|
|
@@ -1946,7 +2318,7 @@ export declare const Remote: {
|
|
|
1946
2318
|
* in one step; the result carries the application-facing operations. The
|
|
1947
2319
|
* descriptor alone is `Remote.define`, the binding alone `Remote.at`.
|
|
1948
2320
|
*/
|
|
1949
|
-
make: <AppModel, Store extends RemoteModel, const Entities extends readonly
|
|
2321
|
+
make: <AppModel, Store extends RemoteModel, const Entities extends readonly EntityLike[], const Queries extends readonly QueryDescriptor<any, any, any>[] = readonly [], const Mutations extends readonly MutationDescriptor<any, any, any>[] = readonly []>(config: {
|
|
1950
2322
|
readonly model: ModelRef<AppModel, Store>;
|
|
1951
2323
|
readonly entities: Entities;
|
|
1952
2324
|
readonly queries?: Queries;
|
|
@@ -1957,7 +2329,7 @@ export declare const Remote: {
|
|
|
1957
2329
|
* mutations, plus the submodel. For a domain reused across applications or
|
|
1958
2330
|
* bound in a test; `Remote.make` is the one-step form.
|
|
1959
2331
|
*/
|
|
1960
|
-
define: <const Entities extends readonly
|
|
2332
|
+
define: <const Entities extends readonly EntityLike[], const Queries extends readonly QueryDescriptor<any, any, any>[] = readonly QueryDescriptor<any, any, any>[], const Mutations extends readonly MutationDescriptor<any, any, any>[] = readonly MutationDescriptor<any, any, any>[]>(config: {
|
|
1961
2333
|
readonly entities: Entities;
|
|
1962
2334
|
readonly queries?: Queries;
|
|
1963
2335
|
readonly mutations?: Mutations;
|
|
@@ -1967,7 +2339,7 @@ export declare const Remote: {
|
|
|
1967
2339
|
* registered entity names are carried on the returned value, so `Remote.select`
|
|
1968
2340
|
* rejects a selection for an entity this domain never declared.
|
|
1969
2341
|
*/
|
|
1970
|
-
at: <AppModel, Store extends RemoteModel, Entities extends readonly
|
|
2342
|
+
at: <AppModel, Store extends RemoteModel, Entities extends readonly EntityLike[], Queries extends readonly QueryDescriptor<any, any, any>[], Mutations extends readonly MutationDescriptor<any, any, any>[]>(definition: RemoteDescriptor<Entities, Queries, Mutations>, store: ModelRef<AppModel, Store>) => BoundRemote<AppModel, Store, EntityName<Entities[number]>>;
|
|
1971
2343
|
/**
|
|
1972
2344
|
* A Projection node that reads a `RemoteData` value out of the store. The id
|
|
1973
2345
|
* is supplied by the caller, usually from a Surface's params. The assembled
|
|
@@ -1975,7 +2347,7 @@ export declare const Remote: {
|
|
|
1975
2347
|
* as `Failed` instead of being asserted into `Value`. A present value with a
|
|
1976
2348
|
* stale field reads as `Refreshing`: an observer is refetching it.
|
|
1977
2349
|
*/
|
|
1978
|
-
select: <AppModel, Store extends RemoteModel, Names extends string, Value, Name extends string>(bound: BoundRemote<AppModel, Store, Names>,
|
|
2350
|
+
select: <AppModel, Store extends RemoteModel, Names extends string, Value, Name extends string>(bound: BoundRemote<AppModel, Store, Names>, given: EntitySelection<Value, Name> & Registered<Name, Names, "Entity">) => (id: string) => Projection<AppModel, RemoteData<Value>>;
|
|
1979
2351
|
/**
|
|
1980
2352
|
* The pure plan for a projection against a Model: the requirements its
|
|
1981
2353
|
* remote store does not satisfy, under `options` (freshness, force). A
|
|
@@ -1993,6 +2365,12 @@ export declare const Remote: {
|
|
|
1993
2365
|
* has not changed.
|
|
1994
2366
|
*/
|
|
1995
2367
|
storeOf: <AppModel, Store extends RemoteModel>(bound: BoundRemote<AppModel, Store>, model: AppModel) => EntityStore;
|
|
2368
|
+
/**
|
|
2369
|
+
* The projection read against the server-derived store alone: the same
|
|
2370
|
+
* requirements, planned the same way, with the pending optimistic layers and
|
|
2371
|
+
* connection overlays left off. See the bound `Remote.confirmed`.
|
|
2372
|
+
*/
|
|
2373
|
+
confirmed: <AppModel, Store extends RemoteModel, P extends Projection<AppModel, any>>(bound: BoundRemote<AppModel, Store>, projection: P) => P;
|
|
1996
2374
|
/**
|
|
1997
2375
|
* Executes the plan against the `RemoteClient` and returns a new store. Used
|
|
1998
2376
|
* for SSR route prefetch, hover prefetch, and tests. Never called during render.
|
|
@@ -2098,6 +2476,10 @@ export declare const Remote: {
|
|
|
2098
2476
|
};
|
|
2099
2477
|
}[];
|
|
2100
2478
|
connections: ConnectionChange[];
|
|
2479
|
+
deleted: readonly {
|
|
2480
|
+
readonly entity: string;
|
|
2481
|
+
readonly id: string;
|
|
2482
|
+
}[];
|
|
2101
2483
|
}, RemoteMutationError, RemoteClient>;
|
|
2102
2484
|
/**
|
|
2103
2485
|
* Runs a mutation and reconciles its patches into a `RemoteModel` in one step,
|