foldkit-remote 0.3.0 → 0.4.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 +97 -1
- package/dist/index.d.mts +224 -62
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +426 -345
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -293,6 +293,32 @@ const clientLayer = Remote.clientLayer(rpcClient)
|
|
|
293
293
|
`RemoteClient` service used by subscriptions and Commands. The transport itself
|
|
294
294
|
is not owned by Remote.
|
|
295
295
|
|
|
296
|
+
## One list per application with `Data.wiring`
|
|
297
|
+
|
|
298
|
+
The integration steps above — spread `Remote.messages`, reduce by tag, derive
|
|
299
|
+
`Data.subscriptions`, provide the client — are one value when the application
|
|
300
|
+
uses `foldkit-bundle`:
|
|
301
|
+
|
|
302
|
+
```ts
|
|
303
|
+
import { Bundle } from 'foldkit-bundle'
|
|
304
|
+
|
|
305
|
+
const Page = Bundle.parent({ Model, Message })
|
|
306
|
+
const wiring = Page.assemble(
|
|
307
|
+
Data.wiring({
|
|
308
|
+
project: Surface.at(ProjectPage, model =>
|
|
309
|
+
model.route._tag === 'project' ? { projectId: model.route.projectId } : undefined,
|
|
310
|
+
),
|
|
311
|
+
}),
|
|
312
|
+
)
|
|
313
|
+
const update = wiring.update(model => ({ model }))
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
`Data.wiring` routes Remote's Messages into `Data.reduce`, brings the active
|
|
317
|
+
Surfaces' Subscriptions and the domain's contract, and requires `RemoteClient`
|
|
318
|
+
from the runtime's resources — the same client layer from above. One assembly
|
|
319
|
+
holds at most one Remote domain: every domain claims the same Message tags,
|
|
320
|
+
and the assembly refuses a second claimant at startup, naming both.
|
|
321
|
+
|
|
296
322
|
## `RemoteData`: what does the Model know right now?
|
|
297
323
|
|
|
298
324
|
A Remote Projection never lies by pretending an absent value is present. It
|
|
@@ -329,7 +355,13 @@ The states are:
|
|
|
329
355
|
- **`Ready`** — every selected field is present and decodes.
|
|
330
356
|
- **`Refreshing`** — the current value remains visible while it is being refetched.
|
|
331
357
|
- **`Failed`** — stored server data does not decode against the Selection.
|
|
332
|
-
- **`NotFound`** — the entity is represented by a tombstone
|
|
358
|
+
- **`NotFound`** — the entity is represented by a tombstone: a live event
|
|
359
|
+
deleted it, or the server was asked for it by id and answered without it. That
|
|
360
|
+
id is then known absent, whether it never existed, is gone, or is not this
|
|
361
|
+
principal's to see. It is not planned again until `Data.refresh` forces it or a
|
|
362
|
+
write brings it back. The target of a returned ref is not marked this way: a
|
|
363
|
+
server need not expand a relation that rides on a request, and the planner asks
|
|
364
|
+
for such a target by id next, which is when its absence is learned.
|
|
333
365
|
|
|
334
366
|
`Initial` is intentionally different from `Loading`. A Projection belonging to
|
|
335
367
|
no active Surface may remain `Initial` forever. Rendering a spinner for
|
|
@@ -384,6 +416,50 @@ An unknown field or a nested Selection for the wrong entity is a type error.
|
|
|
384
416
|
Recursive relations remain finite because the entity schema stores references
|
|
385
417
|
(`Entity.ref(User)` / `Entity.refTo('Node')`), not recursively inlined schemas.
|
|
386
418
|
|
|
419
|
+
### Entities declared with `foldkit-entity`
|
|
420
|
+
|
|
421
|
+
[`foldkit-entity`](../entity/README.md) declares a domain without Remote in it:
|
|
422
|
+
fields, relations as navigation edges, derived members. `Remote.make` registers
|
|
423
|
+
those Entities, and `Data.get`, `Data.live`, and a query's `select` take their
|
|
424
|
+
Selections. Remote compiles both into the descriptor and Selection above, so
|
|
425
|
+
the store, the planner, and the server see nothing new.
|
|
426
|
+
|
|
427
|
+
```ts
|
|
428
|
+
import { Schema } from 'effect'
|
|
429
|
+
import { Entity, Relation } from 'foldkit-entity'
|
|
430
|
+
import { Remote } from 'foldkit-remote'
|
|
431
|
+
|
|
432
|
+
const User = Entity.define('User', Schema.Struct({ id: Schema.String, name: Schema.String }))
|
|
433
|
+
const Project = Entity.define('Project', Schema.Struct({ id: Schema.String, name: Schema.String }))
|
|
434
|
+
const Work = Entity.relate({ User, Project }, { Project: { owner: Relation.one(User) } })
|
|
435
|
+
|
|
436
|
+
const ProjectCard = Entity.select(Work.Project, {
|
|
437
|
+
name: true,
|
|
438
|
+
owner: Entity.select(Work.User, { name: true }),
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
const Data = Remote.make({ model: App.model.remote, entities: [Work.User, Work.Project] })
|
|
442
|
+
const card = Data.get(ProjectCard, 'p1') // Projection<Model, RemoteData<{ name; owner: { name } }>>
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
- A relation becomes a ref field: `one` a ref, an optional `one` a nullable ref,
|
|
446
|
+
`many` an array of refs. A derived member becomes a field the server supplies.
|
|
447
|
+
- The Entity needs an `id` field; Remote keys the store by it.
|
|
448
|
+
- Register the Entities of one `Entity.relate` result and select from that same
|
|
449
|
+
result; `Object.values(Work)` registers them all.
|
|
450
|
+
- `Entity.from(entity)` and `Selection.from(selection)` are the compile steps,
|
|
451
|
+
for when you need the descriptor itself: `patch` and `ref` in a mutation
|
|
452
|
+
handler.
|
|
453
|
+
- `Entity.page(selection, window)` in an Entity Selection is Remote's
|
|
454
|
+
`Selection.connection`: the window travels with the read, and the server
|
|
455
|
+
answers with a page of refs. One relation of one entity is held whole or as
|
|
456
|
+
one window at a time, so two views of the same entity should not read the
|
|
457
|
+
same relation both ways at once.
|
|
458
|
+
|
|
459
|
+
`Entity.make` with `Entity.ref` keeps working, and both kinds can share one
|
|
460
|
+
domain. Both packages export `Entity`; a module that needs `Entity.from` beside
|
|
461
|
+
`Entity.define` aliases one of them.
|
|
462
|
+
|
|
387
463
|
Presence is tracked separately from the JavaScript value. These are distinct:
|
|
388
464
|
|
|
389
465
|
```text
|
|
@@ -552,6 +628,17 @@ Those settlement Messages go through `Data.reduce` like every other Remote fact.
|
|
|
552
628
|
The request id comes from the Remote Model's sequence, so `update` stays pure.
|
|
553
629
|
Settling is idempotent per `requestId`.
|
|
554
630
|
|
|
631
|
+
A mutation that deletes says so. The server's outcome carries
|
|
632
|
+
`deleted: [{ entity, id }]`, and settling it tombstones those entities: they read
|
|
633
|
+
`NotFound`, and they leave every connection and relation they were in, so the
|
|
634
|
+
server names no list. Patches apply first, so an entity named both ways is
|
|
635
|
+
deleted; a retry of the same request deletes nothing again.
|
|
636
|
+
|
|
637
|
+
`Data.mutation(model, requestId)` reads what became of it from the Model:
|
|
638
|
+
`Pending`, `Applied`, `Failed` with the error the server or transport gave, or
|
|
639
|
+
`Unknown` for an id never started here (or settled so long ago it left the
|
|
640
|
+
bounded ledger). A retry reuses its id, and the latest outcome wins.
|
|
641
|
+
|
|
555
642
|
Optimistic entity patches are **layers over the base store**, not inverse
|
|
556
643
|
patches. If multiple mutations overlap, the visible cache is recomputed as base
|
|
557
644
|
plus the still-pending layers, so settling one does not require trying to undo
|
|
@@ -911,6 +998,15 @@ Remote does **not** own:
|
|
|
911
998
|
- authentication; server-side authorization belongs at the Source boundary;
|
|
912
999
|
- a background scheduler or general-purpose database/query engine.
|
|
913
1000
|
|
|
1001
|
+
## How state changes here
|
|
1002
|
+
|
|
1003
|
+
Local application state uses `evo` inside `update`, like anywhere else.
|
|
1004
|
+
Remote Messages go through `Data.reduce` into the embedded `Remote.Model`
|
|
1005
|
+
submodel — Remote owns that reducer, and it is the only writer of the
|
|
1006
|
+
cache. Never install cache state with a ref `set`; the requirements,
|
|
1007
|
+
staleness, and tombstones only stay coherent when every fact arrives as a
|
|
1008
|
+
Remote Message.
|
|
1009
|
+
|
|
914
1010
|
## Limits
|
|
915
1011
|
|
|
916
1012
|
- Effect RPC is the wire contract; the application supplies deployment layers.
|
package/dist/index.d.mts
CHANGED
|
@@ -1,9 +1,64 @@
|
|
|
1
1
|
import { Context, Duration, Effect, Layer, Option, Schema, Stream } from "effect";
|
|
2
|
-
import
|
|
2
|
+
import * as Domain from "foldkit-entity";
|
|
3
|
+
import { EntryWithoutKeepAlive } from "foldkit/subscription";
|
|
4
|
+
import { ActiveSurface, Contract, Invalid, Metadata, ModelRef, Projection, Surface, Wiring } from "foldkit-surface";
|
|
3
5
|
import { Rpc, RpcGroup } from "effect/unstable/rpc";
|
|
4
6
|
import { KeyValueStore } from "effect/unstable/persistence";
|
|
5
7
|
import { Command } from "foldkit/command";
|
|
6
|
-
|
|
8
|
+
//#region ../metadata/dist/index.d.mts
|
|
9
|
+
//#region src/index.d.ts
|
|
10
|
+
/** Marks a `Metadata` value. Its entries are private to this module. */
|
|
11
|
+
declare const MetadataTypeId: unique symbol;
|
|
12
|
+
type MetadataTypeId = typeof MetadataTypeId;
|
|
13
|
+
/**
|
|
14
|
+
* Declarative facts an interpreter attaches to a node it does not own, such as
|
|
15
|
+
* the server data a Remote selection needs. The carrier holds and combines
|
|
16
|
+
* them without knowing what they mean; a package reads only the key it owns.
|
|
17
|
+
*
|
|
18
|
+
* Opaque: only a key's `of` and `Metadata.combine` make one, so entries cannot
|
|
19
|
+
* be forged past a key's type or `merge`, and they are frozen.
|
|
20
|
+
*/
|
|
21
|
+
interface Metadata$1 {
|
|
22
|
+
readonly [MetadataTypeId]: MetadataTypeId;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* An interpreter's typed slot in `Metadata`. Lookup is by the key object, never
|
|
26
|
+
* its `name`, so two packages cannot collide by choosing the same string.
|
|
27
|
+
*/
|
|
28
|
+
interface MetadataKey<A> {
|
|
29
|
+
/** For tooling output only. */
|
|
30
|
+
readonly name: string;
|
|
31
|
+
/** Normalizes the entries gathered from combined values, e.g. unioning duplicates. */
|
|
32
|
+
readonly merge: (values: ReadonlyArray<A>) => ReadonlyArray<A>;
|
|
33
|
+
/** One line per entry for tooling output. */
|
|
34
|
+
readonly summarize: (value: A) => string;
|
|
35
|
+
readonly of: (...values: ReadonlyArray<A>) => Metadata$1;
|
|
36
|
+
readonly get: (metadata: Metadata$1) => ReadonlyArray<A>;
|
|
37
|
+
}
|
|
38
|
+
/** One key's entries as serializable text, named by the key. */
|
|
39
|
+
interface MetadataSummary {
|
|
40
|
+
readonly name: string;
|
|
41
|
+
readonly entries: readonly string[];
|
|
42
|
+
}
|
|
43
|
+
declare const Metadata$1: {
|
|
44
|
+
/**
|
|
45
|
+
* Declares an interpreter's slot. Call once per package, at module level:
|
|
46
|
+
* entries are found by the key object, so two copies of a package (a
|
|
47
|
+
* duplicated install, a reloaded module) do not see each other's entries.
|
|
48
|
+
*/
|
|
49
|
+
key: <A>(name: string, options: {
|
|
50
|
+
readonly merge: (values: ReadonlyArray<A>) => ReadonlyArray<A>;
|
|
51
|
+
readonly summarize: (value: A) => string;
|
|
52
|
+
}) => MetadataKey<A>;
|
|
53
|
+
empty: Metadata$1;
|
|
54
|
+
/** True only for a value this module made; a copy or a hand-built brand is not one. */
|
|
55
|
+
is: (value: unknown) => value is Metadata$1;
|
|
56
|
+
/** Gathers every part's entries and runs each key's `merge` over its group. */
|
|
57
|
+
combine: (parts: ReadonlyArray<Metadata$1>) => Metadata$1;
|
|
58
|
+
summarize: (metadata: Metadata$1) => readonly MetadataSummary[];
|
|
59
|
+
};
|
|
60
|
+
//#endregion
|
|
61
|
+
//#endregion
|
|
7
62
|
//#region src/connection.d.ts
|
|
8
63
|
export interface EdgeRef {
|
|
9
64
|
readonly entity: string;
|
|
@@ -49,6 +104,51 @@ export declare const hasNext: (connection: Connection) => boolean;
|
|
|
49
104
|
/** A connection with more than one segment has an explicit unknown gap. */
|
|
50
105
|
export declare const isGapped: (connection: Connection) => boolean;
|
|
51
106
|
//#endregion
|
|
107
|
+
//#region src/remoteData.d.ts
|
|
108
|
+
export interface RemoteError {
|
|
109
|
+
readonly _tag: string;
|
|
110
|
+
readonly message: string;
|
|
111
|
+
}
|
|
112
|
+
export type RemoteData<A> = {
|
|
113
|
+
readonly _tag: 'Initial';
|
|
114
|
+
} | {
|
|
115
|
+
readonly _tag: 'Loading';
|
|
116
|
+
} | {
|
|
117
|
+
readonly _tag: 'Ready';
|
|
118
|
+
readonly value: A;
|
|
119
|
+
} | {
|
|
120
|
+
readonly _tag: 'Refreshing';
|
|
121
|
+
readonly value: A;
|
|
122
|
+
} | {
|
|
123
|
+
readonly _tag: 'Failed';
|
|
124
|
+
readonly error: RemoteError;
|
|
125
|
+
readonly previous?: A;
|
|
126
|
+
} | {
|
|
127
|
+
readonly _tag: 'NotFound';
|
|
128
|
+
};
|
|
129
|
+
export declare const remoteErrorSchema: Schema.Struct<{
|
|
130
|
+
readonly _tag: Schema.String;
|
|
131
|
+
readonly message: Schema.String;
|
|
132
|
+
}>;
|
|
133
|
+
export declare const remoteDataSchema: <A>(value: Schema.Schema<A>) => Schema.Codec<RemoteData<A>, unknown>;
|
|
134
|
+
export declare const RemoteData: {
|
|
135
|
+
/**
|
|
136
|
+
* The `RemoteData` schema for a value schema. Exposed so a `RemoteData` can
|
|
137
|
+
* be embedded in a hand-written Model schema, not only through `Remote.select`.
|
|
138
|
+
*/
|
|
139
|
+
schema: <A>(value: Schema.Schema<A>) => Schema.Schema<RemoteData<A>>;
|
|
140
|
+
/** Exhaustive: omitting a state is a compile error. */
|
|
141
|
+
match: <A, R>(data: RemoteData<A>, cases: {
|
|
142
|
+
readonly Initial: () => R;
|
|
143
|
+
readonly Loading: () => R;
|
|
144
|
+
readonly Ready: (value: A) => R;
|
|
145
|
+
readonly Refreshing: (value: A) => R;
|
|
146
|
+
readonly Failed: (error: RemoteError, previous: Option.Option<A>) => R;
|
|
147
|
+
readonly NotFound: () => R;
|
|
148
|
+
}) => R;
|
|
149
|
+
map: <A, B>(data: RemoteData<A>, f: (value: A) => B) => RemoteData<B>;
|
|
150
|
+
};
|
|
151
|
+
//#endregion
|
|
52
152
|
//#region src/store.d.ts
|
|
53
153
|
export type EntityKey = string;
|
|
54
154
|
export interface EntityEntry {
|
|
@@ -128,6 +228,8 @@ export interface MutationState {
|
|
|
128
228
|
readonly pending: ReadonlySet<string>;
|
|
129
229
|
readonly applied: ReadonlySet<string>;
|
|
130
230
|
readonly failed: ReadonlySet<string>;
|
|
231
|
+
/** Why each request in `failed` failed, kept exactly as long as `failed` keeps its id. */
|
|
232
|
+
readonly errors: ReadonlyMap<string, RemoteError>;
|
|
131
233
|
/**
|
|
132
234
|
* How many mutations this model has started. A bound domain takes its next
|
|
133
235
|
* request id from here, so `update` stays pure and the id exists before the
|
|
@@ -137,7 +239,21 @@ export interface MutationState {
|
|
|
137
239
|
}
|
|
138
240
|
export declare const emptyMutationState: MutationState;
|
|
139
241
|
export declare const beginMutation: (state: MutationState, requestId: string) => MutationState;
|
|
140
|
-
export declare const failMutation: (state: MutationState, requestId: string) => MutationState;
|
|
242
|
+
export declare const failMutation: (state: MutationState, requestId: string, error: RemoteError) => MutationState;
|
|
243
|
+
/** What Remote knows of one mutation request, by the id `mutate` returned. */
|
|
244
|
+
export type MutationStatus =
|
|
245
|
+
/** Never started here, or settled so long ago its id has left the window. */
|
|
246
|
+
{
|
|
247
|
+
readonly _tag: 'Unknown';
|
|
248
|
+
} | {
|
|
249
|
+
readonly _tag: 'Pending';
|
|
250
|
+
} | {
|
|
251
|
+
readonly _tag: 'Applied';
|
|
252
|
+
} | {
|
|
253
|
+
readonly _tag: 'Failed';
|
|
254
|
+
readonly error: RemoteError;
|
|
255
|
+
};
|
|
256
|
+
export declare const mutationStatus: (state: MutationState, requestId: string) => MutationStatus;
|
|
141
257
|
export interface Reconciled {
|
|
142
258
|
readonly store: EntityStore;
|
|
143
259
|
readonly state: MutationState;
|
|
@@ -147,7 +263,10 @@ export interface Reconciled {
|
|
|
147
263
|
* with the same `requestId` (a retry, or a live event describing the same change)
|
|
148
264
|
* does not re-apply the entities, but still clears `pending`.
|
|
149
265
|
*/
|
|
150
|
-
export declare const reconcileMutation: (store: EntityStore, state: MutationState, requestId: string, entities: ReadonlyArray<NormalizedPatch
|
|
266
|
+
export declare const reconcileMutation: (store: EntityStore, state: MutationState, requestId: string, entities: ReadonlyArray<NormalizedPatch>, deleted?: ReadonlyArray<{
|
|
267
|
+
readonly entity: string;
|
|
268
|
+
readonly id: string;
|
|
269
|
+
}>) => Reconciled;
|
|
151
270
|
/** A mutation an application declares: its name and input/output codecs. */
|
|
152
271
|
export interface MutationDescriptor<Name extends string, Input, Output> {
|
|
153
272
|
readonly name: Name;
|
|
@@ -283,9 +402,9 @@ export declare const Requirement: {
|
|
|
283
402
|
mergeConnections: typeof mergeConnections;
|
|
284
403
|
};
|
|
285
404
|
/** Remote's entity requirements on a Projection, unioned per entity and id as nodes compose. */
|
|
286
|
-
export declare const RemoteRequirements:
|
|
405
|
+
export declare const RemoteRequirements: MetadataKey<Requirement>;
|
|
287
406
|
/** Remote's query connections on a Projection, unioned per connection and window. */
|
|
288
|
-
export declare const RemoteConnections:
|
|
407
|
+
export declare const RemoteConnections: MetadataKey<ConnectionRequirement>;
|
|
289
408
|
/** The entity requirements a Projection carries. */
|
|
290
409
|
export declare const requirementsOf: (projection: {
|
|
291
410
|
readonly metadata: Metadata;
|
|
@@ -381,7 +500,10 @@ export declare const pruneOverlays: (optimistic: OptimisticState, connection: st
|
|
|
381
500
|
* a later layer re-wins on rebase. Confirmed connection changes are recorded
|
|
382
501
|
* once per request, as overlays the request no longer owns.
|
|
383
502
|
*/
|
|
384
|
-
export declare const settleSuccess: (base: EntityStore, optimistic: OptimisticState, state: MutationState, requestId: string, entities: ReadonlyArray<NormalizedPatch>, connections?: ReadonlyArray<ConnectionChange
|
|
503
|
+
export declare const settleSuccess: (base: EntityStore, optimistic: OptimisticState, state: MutationState, requestId: string, entities: ReadonlyArray<NormalizedPatch>, connections?: ReadonlyArray<ConnectionChange>, deleted?: ReadonlyArray<{
|
|
504
|
+
readonly entity: string;
|
|
505
|
+
readonly id: string;
|
|
506
|
+
}>) => {
|
|
385
507
|
readonly store: EntityStore;
|
|
386
508
|
readonly state: MutationState;
|
|
387
509
|
readonly optimistic: OptimisticState;
|
|
@@ -672,6 +794,11 @@ export declare const MutationResult: Schema.Struct<{
|
|
|
672
794
|
readonly key: Schema.String;
|
|
673
795
|
}>;
|
|
674
796
|
}>]>>>;
|
|
797
|
+
/** Entities the mutation deleted; the client knows them absent from then on. */
|
|
798
|
+
readonly deleted: Schema.optional<Schema.$Array<Schema.Struct<{
|
|
799
|
+
readonly entity: Schema.String;
|
|
800
|
+
readonly id: Schema.String;
|
|
801
|
+
}>>>;
|
|
675
802
|
}>;
|
|
676
803
|
export declare const LiveRequirement: Schema.Struct<{
|
|
677
804
|
readonly version: Schema.Number;
|
|
@@ -760,6 +887,11 @@ export declare const Mutate: Rpc.Rpc<"FoldkitRemoteMutate", Schema.Struct<{
|
|
|
760
887
|
readonly key: Schema.String;
|
|
761
888
|
}>;
|
|
762
889
|
}>]>>>;
|
|
890
|
+
/** Entities the mutation deleted; the client knows them absent from then on. */
|
|
891
|
+
readonly deleted: Schema.optional<Schema.$Array<Schema.Struct<{
|
|
892
|
+
readonly entity: Schema.String;
|
|
893
|
+
readonly id: Schema.String;
|
|
894
|
+
}>>>;
|
|
763
895
|
}>, typeof RemoteMutationError, never, never>;
|
|
764
896
|
export declare const Live: Rpc.Rpc<"FoldkitRemoteLive", Schema.Struct<{
|
|
765
897
|
readonly version: Schema.Number;
|
|
@@ -924,6 +1056,11 @@ export declare const RemoteRpc: RpcGroup.RpcGroup<Rpc.Rpc<"FoldkitRemoteRead", S
|
|
|
924
1056
|
readonly key: Schema.String;
|
|
925
1057
|
}>;
|
|
926
1058
|
}>]>>>;
|
|
1059
|
+
/** Entities the mutation deleted; the client knows them absent from then on. */
|
|
1060
|
+
readonly deleted: Schema.optional<Schema.$Array<Schema.Struct<{
|
|
1061
|
+
readonly entity: Schema.String;
|
|
1062
|
+
readonly id: Schema.String;
|
|
1063
|
+
}>>>;
|
|
927
1064
|
}>, typeof RemoteMutationError, never, never> | Rpc.Rpc<"FoldkitRemoteLive", Schema.Struct<{
|
|
928
1065
|
readonly version: Schema.Number;
|
|
929
1066
|
readonly requirements: Schema.$Array<Schema.Codec<Requirement, Requirement, never, never>>;
|
|
@@ -997,51 +1134,6 @@ export declare const RemoteRpc: RpcGroup.RpcGroup<Rpc.Rpc<"FoldkitRemoteRead", S
|
|
|
997
1134
|
}>]>;
|
|
998
1135
|
}>, typeof RemoteQueryError, never, never>>;
|
|
999
1136
|
//#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
1137
|
//#region src/model.d.ts
|
|
1046
1138
|
/**
|
|
1047
1139
|
* The normalized cache as a Foldkit Submodel. `Remote.define` returns a schema
|
|
@@ -1124,6 +1216,11 @@ export type RemoteMessage = {
|
|
|
1124
1216
|
readonly entities: readonly NormalizedPatch[];
|
|
1125
1217
|
/** Connection changes the server confirmed; they replace the request's own. */
|
|
1126
1218
|
readonly connections?: ReadonlyArray<ConnectionChange> | undefined;
|
|
1219
|
+
/** Entities the mutation deleted: tombstoned, which hides them from every connection. */
|
|
1220
|
+
readonly deleted?: ReadonlyArray<{
|
|
1221
|
+
readonly entity: string;
|
|
1222
|
+
readonly id: string;
|
|
1223
|
+
}> | undefined;
|
|
1127
1224
|
} | {
|
|
1128
1225
|
readonly _tag: 'MutationFailed';
|
|
1129
1226
|
readonly requestId: string;
|
|
@@ -1218,6 +1315,10 @@ export declare const remoteMessageCases: {
|
|
|
1218
1315
|
readonly values: Schema.$Record<Schema.String, Schema.Unknown>;
|
|
1219
1316
|
}>>;
|
|
1220
1317
|
connections: Schema.optional<Schema.$Array<Schema.Unknown>>;
|
|
1318
|
+
deleted: Schema.optional<Schema.$Array<Schema.Struct<{
|
|
1319
|
+
readonly entity: Schema.String;
|
|
1320
|
+
readonly id: Schema.String;
|
|
1321
|
+
}>>>;
|
|
1221
1322
|
};
|
|
1222
1323
|
MutationFailed: {
|
|
1223
1324
|
requestId: Schema.String;
|
|
@@ -1374,6 +1475,10 @@ export declare const mutateRemote: <Name extends string, Input, Output>(mutation
|
|
|
1374
1475
|
};
|
|
1375
1476
|
}[];
|
|
1376
1477
|
connections: ConnectionChange[];
|
|
1478
|
+
deleted: readonly {
|
|
1479
|
+
readonly entity: string;
|
|
1480
|
+
readonly id: string;
|
|
1481
|
+
}[];
|
|
1377
1482
|
}, RemoteMutationError, RemoteClient>;
|
|
1378
1483
|
//#endregion
|
|
1379
1484
|
//#region src/relation.d.ts
|
|
@@ -1458,6 +1563,11 @@ export declare const pageSchema: (item: AnySchema) => AnySchema;
|
|
|
1458
1563
|
/** The requirement a nested selection contributes for its relation's target. */
|
|
1459
1564
|
export declare const relationOf: (selection: Selection<unknown>) => RelationRequirement;
|
|
1460
1565
|
export declare const Selection: {
|
|
1566
|
+
/**
|
|
1567
|
+
* The Remote Selection of a `foldkit-entity` Selection: the same members, read
|
|
1568
|
+
* from Remote's store. `true` on a relation reads its refs, as it does here.
|
|
1569
|
+
*/
|
|
1570
|
+
from: <Name extends string, S extends Schema.Constraint>(selection: Domain.Selection<Name, unknown, S>) => Selection<S["Type"], Name, "entity">;
|
|
1461
1571
|
/**
|
|
1462
1572
|
* The fields to read of `entity`. A nested `Selection` on a relation field
|
|
1463
1573
|
* reads through the ref (or refs, or page of refs) the field holds into the
|
|
@@ -1526,7 +1636,25 @@ export interface RefPage<Name extends string, F extends Schema.Struct.Fields = S
|
|
|
1526
1636
|
readonly hasNext: boolean;
|
|
1527
1637
|
readonly hasPrevious: boolean;
|
|
1528
1638
|
}
|
|
1639
|
+
/** The ref codec a `foldkit-entity` relation becomes: one ref, a nullable ref, or an array of refs. */
|
|
1640
|
+
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;
|
|
1641
|
+
/**
|
|
1642
|
+
* The fields Remote stores for a `foldkit-entity` Entity: its own fields, its
|
|
1643
|
+
* derived members (the server supplies them like any field), and each relation
|
|
1644
|
+
* as refs.
|
|
1645
|
+
*/
|
|
1646
|
+
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
1647
|
declare const Entity$1: {
|
|
1648
|
+
/**
|
|
1649
|
+
* The Remote descriptor of a `foldkit-entity` Entity. Relations declared with
|
|
1650
|
+
* `Entity.relate` become ref fields, so the store, the planner, and the server
|
|
1651
|
+
* treat it as they treat an `Entity.make` descriptor.
|
|
1652
|
+
*/
|
|
1653
|
+
from: <E extends Domain.AnyEntity>(entity: E & {
|
|
1654
|
+
readonly fields: {
|
|
1655
|
+
readonly id: unknown;
|
|
1656
|
+
};
|
|
1657
|
+
}) => EntityDescriptor<E["name"], FieldsFrom<E>>;
|
|
1530
1658
|
make: <const Name extends string, const F extends Schema.Struct.Fields & {
|
|
1531
1659
|
readonly id: Schema.Schema<unknown>;
|
|
1532
1660
|
}>(name: Name, schema: Schema.Struct<F>) => EntityDescriptor<Name, F>;
|
|
@@ -1640,7 +1768,18 @@ export declare const RemotePolicy: {
|
|
|
1640
1768
|
};
|
|
1641
1769
|
//#endregion
|
|
1642
1770
|
//#region src/index.d.ts
|
|
1643
|
-
|
|
1771
|
+
/** What a domain registers: a Remote descriptor, or a `foldkit-entity` Entity (read through `Entity.from`). */
|
|
1772
|
+
export type EntityLike = EntityDescriptor<any, any> | Domain.AnyEntity;
|
|
1773
|
+
/**
|
|
1774
|
+
* What a read selects: a Remote Selection, or a `foldkit-entity` Selection
|
|
1775
|
+
* (read through `Selection.from`).
|
|
1776
|
+
*/
|
|
1777
|
+
export type EntitySelection<Value, Name extends string> = Selection<Value, Name, 'entity'> | Domain.Selection<Name, unknown, Schema.Constraint & {
|
|
1778
|
+
readonly Type: Value;
|
|
1779
|
+
}>;
|
|
1780
|
+
type EntityName<D> = D extends {
|
|
1781
|
+
readonly name: infer Name extends string;
|
|
1782
|
+
} ? Name : never;
|
|
1644
1783
|
type QueryName<D> = D extends QueryDescriptor<infer Name, any, any> ? Name : never;
|
|
1645
1784
|
type MutationName<D> = D extends MutationDescriptor<infer Name, any, any> ? Name : never;
|
|
1646
1785
|
/**
|
|
@@ -1651,7 +1790,7 @@ type MutationName<D> = D extends MutationDescriptor<infer Name, any, any> ? Name
|
|
|
1651
1790
|
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
1791
|
/** Nothing when a selection is of the query's entity; otherwise a branded failure naming both. */
|
|
1653
1792
|
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
|
|
1793
|
+
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
1794
|
readonly entities: Entities;
|
|
1656
1795
|
readonly queries: Queries;
|
|
1657
1796
|
readonly mutations: Mutations;
|
|
@@ -1701,6 +1840,8 @@ export type SubscriptionEntries<AppModel, Active> = { readonly [K in keyof Activ
|
|
|
1701
1840
|
readonly retain: RemoteEntry<AppModel, any>;
|
|
1702
1841
|
};
|
|
1703
1842
|
export interface SubscriptionsOptions extends ObserveOptions, LiveOptions, RetainOptions {}
|
|
1843
|
+
/** 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. */
|
|
1844
|
+
export type RemoteWiring<AppModel> = Wiring<AppModel, RemoteMessage | RemoteMessageInput, RemoteClient>;
|
|
1704
1845
|
/** What `RemoteDomain.mutate` hands `update`: the Model with the request started, and the Command that settles it. */
|
|
1705
1846
|
export interface MutationStarted<AppModel> {
|
|
1706
1847
|
readonly model: AppModel;
|
|
@@ -1715,15 +1856,15 @@ export interface MutationStarted<AppModel> {
|
|
|
1715
1856
|
* operations over them. Every method compiles to the `Remote.*` function of the
|
|
1716
1857
|
* same name, which stays exported for tooling, SSR, and tests.
|
|
1717
1858
|
*/
|
|
1718
|
-
export interface RemoteDomain<AppModel, Store extends RemoteModel, Entities extends readonly
|
|
1859
|
+
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
1860
|
/** `Remote.select`: a Projection reading one entity through a selection of a registered entity. */
|
|
1720
|
-
get<Value, Name extends string>(selection:
|
|
1861
|
+
get<Value, Name extends string>(selection: EntitySelection<Value, Name> & Registered<Name, EntityName<Entities[number]>, 'Entity'>, id: string): Projection<AppModel, RemoteData<Value>>;
|
|
1721
1862
|
/**
|
|
1722
1863
|
* `get`, and the projection also subscribes to the entity's changes: its
|
|
1723
1864
|
* requirements are marked `live`, so `subscriptions` derives a live entry for
|
|
1724
1865
|
* the Surfaces that read it.
|
|
1725
1866
|
*/
|
|
1726
|
-
live<Value, Name extends string>(selection:
|
|
1867
|
+
live<Value, Name extends string>(selection: EntitySelection<Value, Name> & Registered<Name, EntityName<Entities[number]>, 'Entity'>, id: string): Projection<AppModel, RemoteData<Value>>;
|
|
1727
1868
|
/**
|
|
1728
1869
|
* A query connection read as a `Page` of items selected of the query's
|
|
1729
1870
|
* entity: `Initial` until the page and every item's selected fields are
|
|
@@ -1766,8 +1907,21 @@ export interface RemoteDomain<AppModel, Store extends RemoteModel, Entities exte
|
|
|
1766
1907
|
* mutation sequence unless `options.requestId` is given.
|
|
1767
1908
|
*/
|
|
1768
1909
|
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>;
|
|
1910
|
+
/**
|
|
1911
|
+
* What Remote knows of a mutation `mutate` started, by its request id:
|
|
1912
|
+
* pending, applied, or failed with the error the server or transport gave.
|
|
1913
|
+
*/
|
|
1914
|
+
mutation(model: AppModel, requestId: string): MutationStatus;
|
|
1769
1915
|
/** `updateRemote` on the bound slice: reduces one of Remote's Messages, as `RemoteMessage` or as the application's union constructs it. */
|
|
1770
1916
|
reduce(model: AppModel, message: RemoteMessage | RemoteMessageInput): AppModel;
|
|
1917
|
+
/**
|
|
1918
|
+
* How this domain joins an assembly: it routes Remote's Messages into
|
|
1919
|
+
* `reduce`, brings its Subscriptions and contract, and requires
|
|
1920
|
+
* `RemoteClient` from the runtime's resources Layer. One assembly holds at
|
|
1921
|
+
* most one Remote wiring: every domain claims the same tags, and routing
|
|
1922
|
+
* takes the first claimant.
|
|
1923
|
+
*/
|
|
1924
|
+
wiring: <const Active extends Readonly<Record<string, ActiveSurface<AppModel> | Surface<AppModel, any, any, void>>>>(active: Active, options?: SubscriptionsOptions) => RemoteWiring<AppModel>;
|
|
1771
1925
|
/** `Remote.inspect` of the bound slice. */
|
|
1772
1926
|
inspect(model: AppModel): RemoteInspection;
|
|
1773
1927
|
}
|
|
@@ -1789,7 +1943,7 @@ export type QueryWindowOptions = {
|
|
|
1789
1943
|
};
|
|
1790
1944
|
export type QueryOptions<Value, Entity extends string, Of extends string = Entity> = {
|
|
1791
1945
|
/** What to read of each item: a selection of the query's entity (`Of`). */
|
|
1792
|
-
readonly select:
|
|
1946
|
+
readonly select: EntitySelection<Value, Entity> & SelectsEntity<Entity, Of>;
|
|
1793
1947
|
} & QueryWindowOptions;
|
|
1794
1948
|
/** A query connection read as a `Page` of selected items; `ref` is the connection with its first window. */
|
|
1795
1949
|
export interface QueryProjection<AppModel, Value, Name extends string, Input> extends Projection<AppModel, RemoteData<Page<Value>>> {
|
|
@@ -1899,6 +2053,10 @@ export declare const Remote: {
|
|
|
1899
2053
|
readonly values: Schema.$Record<Schema.String, Schema.Unknown>;
|
|
1900
2054
|
}>>;
|
|
1901
2055
|
connections: Schema.optional<Schema.$Array<Schema.Unknown>>;
|
|
2056
|
+
deleted: Schema.optional<Schema.$Array<Schema.Struct<{
|
|
2057
|
+
readonly entity: Schema.String;
|
|
2058
|
+
readonly id: Schema.String;
|
|
2059
|
+
}>>>;
|
|
1902
2060
|
};
|
|
1903
2061
|
MutationFailed: {
|
|
1904
2062
|
requestId: Schema.String;
|
|
@@ -1946,7 +2104,7 @@ export declare const Remote: {
|
|
|
1946
2104
|
* in one step; the result carries the application-facing operations. The
|
|
1947
2105
|
* descriptor alone is `Remote.define`, the binding alone `Remote.at`.
|
|
1948
2106
|
*/
|
|
1949
|
-
make: <AppModel, Store extends RemoteModel, const Entities extends readonly
|
|
2107
|
+
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
2108
|
readonly model: ModelRef<AppModel, Store>;
|
|
1951
2109
|
readonly entities: Entities;
|
|
1952
2110
|
readonly queries?: Queries;
|
|
@@ -1957,7 +2115,7 @@ export declare const Remote: {
|
|
|
1957
2115
|
* mutations, plus the submodel. For a domain reused across applications or
|
|
1958
2116
|
* bound in a test; `Remote.make` is the one-step form.
|
|
1959
2117
|
*/
|
|
1960
|
-
define: <const Entities extends readonly
|
|
2118
|
+
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
2119
|
readonly entities: Entities;
|
|
1962
2120
|
readonly queries?: Queries;
|
|
1963
2121
|
readonly mutations?: Mutations;
|
|
@@ -1967,7 +2125,7 @@ export declare const Remote: {
|
|
|
1967
2125
|
* registered entity names are carried on the returned value, so `Remote.select`
|
|
1968
2126
|
* rejects a selection for an entity this domain never declared.
|
|
1969
2127
|
*/
|
|
1970
|
-
at: <AppModel, Store extends RemoteModel, Entities extends readonly
|
|
2128
|
+
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
2129
|
/**
|
|
1972
2130
|
* A Projection node that reads a `RemoteData` value out of the store. The id
|
|
1973
2131
|
* is supplied by the caller, usually from a Surface's params. The assembled
|
|
@@ -1975,7 +2133,7 @@ export declare const Remote: {
|
|
|
1975
2133
|
* as `Failed` instead of being asserted into `Value`. A present value with a
|
|
1976
2134
|
* stale field reads as `Refreshing`: an observer is refetching it.
|
|
1977
2135
|
*/
|
|
1978
|
-
select: <AppModel, Store extends RemoteModel, Names extends string, Value, Name extends string>(bound: BoundRemote<AppModel, Store, Names>,
|
|
2136
|
+
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
2137
|
/**
|
|
1980
2138
|
* The pure plan for a projection against a Model: the requirements its
|
|
1981
2139
|
* remote store does not satisfy, under `options` (freshness, force). A
|
|
@@ -2098,6 +2256,10 @@ export declare const Remote: {
|
|
|
2098
2256
|
};
|
|
2099
2257
|
}[];
|
|
2100
2258
|
connections: ConnectionChange[];
|
|
2259
|
+
deleted: readonly {
|
|
2260
|
+
readonly entity: string;
|
|
2261
|
+
readonly id: string;
|
|
2262
|
+
}[];
|
|
2101
2263
|
}, RemoteMutationError, RemoteClient>;
|
|
2102
2264
|
/**
|
|
2103
2265
|
* Runs a mutation and reconciles its patches into a `RemoteModel` in one step,
|