foldkit-remote 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Context, Duration, Effect, Layer, Option, Schema, Stream } from "effect";
2
2
  import * as Domain from "foldkit-entity";
3
- import { AnyEntity, AnyQuery, InputExpr, Query as Query$1 } from "foldkit-entity";
3
+ import { AnyEntity, AnyQuery, Dependencies, InputExpr, Query as Query$1 } from "foldkit-entity";
4
4
  import { EntryWithoutKeepAlive } from "foldkit/subscription";
5
5
  import { ActiveSurface, Contract, Invalid, Metadata, ModelRef, Projection, Surface, Wiring } from "foldkit-surface";
6
6
  import { Rpc, RpcGroup } from "effect/unstable/rpc";
@@ -381,6 +381,16 @@ export interface QueryDescriptor<Name extends string, Input, Result> {
381
381
  * will be given rather than being it.
382
382
  */
383
383
  export type QueryInputs<Input> = { readonly [K in keyof Input & string]: InputExpr<Input[K]>; };
384
+ /**
385
+ * What separates a query's name from its canonical encoded input in a
386
+ * connection identity.
387
+ *
388
+ * NUL, because `JSON.stringify` escapes it inside a string (`\u0000`) and a
389
+ * query name is a TypeScript identifier — so the first occurrence is always the
390
+ * boundary, and a name or an input value cannot forge one. Anything recovering
391
+ * the query from an identity splits here.
392
+ */
393
+ export declare const IDENTITY_SEPARATOR = "\0";
384
394
  /** Stable stringify: object keys sorted, undefined-valued keys dropped, so equal inputs encode equally. */
385
395
  export declare const stableStringify: (value: unknown) => string;
386
396
  /**
@@ -448,6 +458,8 @@ export declare const Query: {
448
458
  where: (...predicates: ReadonlyArray<import("foldkit-entity").Predicate>) => <E extends AnyEntity>(self: Query$1<E>) => Query$1<E>;
449
459
  orderBy: (...terms: ReadonlyArray<import("foldkit-entity").OrderTerm>) => <E extends AnyEntity>(self: Query$1<E>) => Query$1<E>;
450
460
  dependencies: (self: AnyQuery) => import("foldkit-entity").Dependencies;
461
+ unsupported: (self: AnyQuery, supported: ReadonlyArray<import("foldkit-entity").Operation>) => ReadonlyArray<import("foldkit-entity").Operation>;
462
+ show: (self: AnyQuery) => string;
451
463
  };
452
464
  //#endregion
453
465
  //#region src/requirement.d.ts
@@ -651,8 +663,6 @@ export interface BoundaryCounts {
651
663
  export interface LiveState {
652
664
  /** Last applied cursor for this stream. */
653
665
  readonly cursor: LiveCursor;
654
- /** Connections marked stale by an invalidating event. */
655
- readonly stale: ReadonlySet<string>;
656
666
  /** Edges recorded outside the loaded boundary, not visible in `items`. */
657
667
  readonly boundary: Readonly<Record<string, BoundaryCounts>>;
658
668
  }
@@ -665,7 +675,6 @@ export type LiveOutcome = 'applied' | 'duplicate' | 'gap';
665
675
  * spurious gap.
666
676
  */
667
677
  export declare const classifyLive: (state: LiveState, cursor: LiveCursor) => LiveOutcome;
668
- export declare const invalidateConnection: (state: LiveState, connection: string) => LiveState;
669
678
  /**
670
679
  * A subscriber is woken only if the event changed a field it selects. A
671
680
  * change to an unselected field is skipped entirely.
@@ -683,17 +692,17 @@ export interface ConnectionApplied {
683
692
  readonly state: LiveState;
684
693
  readonly optimistic: OptimisticState;
685
694
  readonly outcome: LiveOutcome;
695
+ /**
696
+ * A connection the event says to refetch. Reported rather than recorded here,
697
+ * because staleness lives on the connection in the Model — which is what the
698
+ * planner and every read consult. It used to be written into a set on this
699
+ * live state that nothing read, so an invalidating event did nothing at all.
700
+ */
701
+ readonly invalidated?: string | undefined;
686
702
  }
687
703
  export declare const applyConnectionEvent: (state: LiveState, optimistic: OptimisticState, event: Extract<LiveEvent, {
688
704
  _tag: "ConnectionInsert" | "ConnectionRemove" | "ConnectionInvalidate";
689
705
  }>, policy?: LivePolicy) => ConnectionApplied;
690
- export declare const isStale: (state: LiveState, connection: string) => boolean;
691
- /**
692
- * Clears a connection's stale mark once a fresh page has been adopted. An
693
- * invalidating event only records that a refetch is due; whoever merges the
694
- * refetched page calls this so `isStale` stops reporting.
695
- */
696
- export declare const refreshConnection: (state: LiveState, connection: string) => LiveState;
697
706
  /** `hasPrevious` accounting for edges recorded outside the loaded boundary. */
698
707
  export declare const liveHasPrevious: (connection: Connection, state: LiveState, connectionId: string) => boolean;
699
708
  /** `hasNext` accounting for edges recorded outside the loaded boundary. */
@@ -731,7 +740,33 @@ export declare const gc: (state: Retained & {
731
740
  //#endregion
732
741
  //#region src/persistence.d.ts
733
742
  /** Bump when the serialized shape changes; a mismatch discards the cache. */
734
- export declare const REMOTE_CACHE_VERSION = 3;
743
+ export declare const REMOTE_CACHE_VERSION = 4;
744
+ /**
745
+ * What survives a reload: the entity cache, and the connections an application
746
+ * **declared** should come back.
747
+ *
748
+ * Connections are session state by default and stay that way — `snapshotOf`
749
+ * takes none unless asked. What a declared one keeps is its **edges and
750
+ * nothing else**, which is not a simplification but the rule: a connection's
751
+ * boundaries are cursors the server minted, and a cursor may name server state
752
+ * that no longer exists. Restoring one would be claiming to know where the
753
+ * page ends. The shape has nowhere to put a cursor, so that cannot be got
754
+ * wrong later.
755
+ */
756
+ export interface Snapshot {
757
+ readonly entities: EntityStore;
758
+ /**
759
+ * By connection identity: its **segments**, each a run of edges. No
760
+ * boundaries.
761
+ *
762
+ * Segments rather than one flat list, because the gaps between them are
763
+ * knowledge too. A connection paged from both ends holds a head and a tail
764
+ * with an unfetched middle; flattening those into one run would claim the
765
+ * tail's first row follows the head's last, which is a thing the client was
766
+ * never told.
767
+ */
768
+ readonly connections: Readonly<Record<string, ReadonlyArray<ReadonlyArray<Edge>>>>;
769
+ }
735
770
  export interface SnapshotOptions {
736
771
  /**
737
772
  * Who or what the snapshot is for (a user id, a tenant, a build). A snapshot
@@ -745,36 +780,50 @@ export interface SnapshotOptions {
745
780
  /** How a snapshot meets a store that already has entries. */
746
781
  export type MergePolicy = 'replace' | 'preserve-existing';
747
782
  /**
748
- * The snapshot text, or `undefined` when it would exceed `maxBytes`. Only the
749
- * entity store goes in; pass `model.entities`, never the whole `RemoteModel`.
783
+ * The snapshot text, or `undefined` when it would exceed `maxBytes`. Build the
784
+ * snapshot with `snapshotOf`, which is where the connections that survive are
785
+ * named; runtime state — optimistic layers, live cursors, gaps, the mutation
786
+ * ledger — is never in one.
750
787
  */
751
- declare function dehydrate(store: EntityStore, options?: SnapshotOptions & {
788
+ declare function dehydrate(snapshot: Snapshot, options?: SnapshotOptions & {
752
789
  readonly maxBytes?: undefined;
753
790
  }): string;
754
- declare function dehydrate(store: EntityStore, options?: SnapshotOptions): string | undefined;
791
+ declare function dehydrate(snapshot: Snapshot, options?: SnapshotOptions): string | undefined;
755
792
  export declare const RemotePersistence: {
756
793
  /** The snapshot text, or `undefined` when it would exceed `maxBytes`. */
757
794
  dehydrate: typeof dehydrate;
758
- /** The store a snapshot text holds, or `undefined` when it is refused. */
759
- hydrate: (raw: string | undefined | null, options?: SnapshotOptions) => EntityStore | undefined;
760
- /** A snapshot brought into a store by policy. */
795
+ /** What a snapshot text holds, or `undefined` when it is refused. */
796
+ hydrate: (raw: string | undefined | null, options?: SnapshotOptions) => Snapshot | undefined;
797
+ /**
798
+ * What to keep of a Model, naming the connections that should survive a
799
+ * reload. Naming none is the default and is what Remote always did.
800
+ */
801
+ snapshotOf: (model: {
802
+ readonly entities: EntityStore;
803
+ readonly connections: Readonly<Record<string, Connection>>;
804
+ }, options?: {
805
+ readonly connections?: ReadonlyArray<ConnectionIdentity> | undefined;
806
+ }) => Snapshot;
807
+ /** A snapshot's entities brought into a store by policy. */
761
808
  mergeStores: (current: EntityStore, snapshot: EntityStore, policy: MergePolicy) => EntityStore;
809
+ /** The cache as a fresh session has it; what a refused `restore` yields. */
810
+ emptySnapshot: Snapshot;
762
811
  /**
763
812
  * Writes the snapshot under `key`, or removes the key when the store would
764
813
  * exceed `maxBytes`, so a stale smaller snapshot cannot outlive a larger
765
814
  * store it no longer describes.
766
815
  */
767
- save: (store: EntityStore, options: {
816
+ save: (snapshot: Snapshot, options: {
768
817
  readonly key: string;
769
818
  } & SnapshotOptions) => Effect.Effect<void, KeyValueStore.KeyValueStoreError, KeyValueStore.KeyValueStore>;
770
819
  /**
771
- * Reads the cache. A missing key yields `emptyStore`; anything `hydrate`
772
- * refuses yields `emptyStore` and removes the key, so the next `restore` is
773
- * clean and the planner refetches.
820
+ * Reads the cache. A missing key yields `emptySnapshot`; anything `hydrate`
821
+ * refuses yields `emptySnapshot` and removes the key, so the next `restore`
822
+ * is clean and the planner refetches.
774
823
  */
775
824
  restore: (options: {
776
825
  readonly key: string;
777
- } & SnapshotOptions) => Effect.Effect<Readonly<Record<string, EntityEntry>>, KeyValueStore.KeyValueStoreError, KeyValueStore.KeyValueStore>;
826
+ } & SnapshotOptions) => Effect.Effect<Snapshot, KeyValueStore.KeyValueStoreError, KeyValueStore.KeyValueStore>;
778
827
  };
779
828
  //#endregion
780
829
  //#region src/wire.d.ts
@@ -1261,7 +1310,34 @@ export interface RemoteModel {
1261
1310
  * it, and a refresh of one field leaves every other entry's read alone.
1262
1311
  */
1263
1312
  readonly refresh: RefreshState;
1313
+ /**
1314
+ * The reads that failed and have not been settled since, so a read can say
1315
+ * `Failed` rather than `Initial`.
1316
+ *
1317
+ * Without them a read that failed before anything loaded read exactly like
1318
+ * one nobody had asked for, and nothing asked again: a read entry restarts
1319
+ * only when what it plans changes, and a failure changed nothing. What failed
1320
+ * is not planned again on its own either, so a persistent error is not
1321
+ * retried every time an unrelated read restarts the entry. `Remote.refresh`
1322
+ * is how a view retries.
1323
+ */
1324
+ readonly failures: Failures;
1264
1325
  }
1326
+ /**
1327
+ * Failed reads, as the errors they failed with.
1328
+ *
1329
+ * `connections` is by connection identity, and is settled by a page arriving,
1330
+ * an invalidation, or retention dropping the connection. `fields` is by field
1331
+ * mark (`entity\0id\0field`, the marks `loading` uses), and is settled by the
1332
+ * field being written again, a refresh asking for it, or retention dropping the
1333
+ * entity. A read of it in flight reads `Loading` over the failure without
1334
+ * settling it: if that read fails too, the failure is simply recorded again.
1335
+ */
1336
+ export interface Failures {
1337
+ readonly connections: Readonly<Record<string, RemoteError>>;
1338
+ readonly fields: Readonly<Record<string, RemoteError>>;
1339
+ }
1340
+ export declare const noFailures: Failures;
1265
1341
  /**
1266
1342
  * `Remote.refresh` generations, per field mark. `generation` is the last one
1267
1343
  * handed out; `requested` is when each field was last refreshed, and `started`
@@ -1286,6 +1362,13 @@ export type RemoteMessage = {
1286
1362
  readonly _tag: 'ReadFailed';
1287
1363
  readonly requests: readonly Requirement[];
1288
1364
  readonly error: RemoteError;
1365
+ /**
1366
+ * The live stream whose subscription broke, when this is that rather
1367
+ * than a read. Nothing was being read, so no field failed: the stream is
1368
+ * recorded as a gap, since changes after it are missed until the host
1369
+ * resubscribes.
1370
+ */
1371
+ readonly stream?: string | undefined;
1289
1372
  } |
1290
1373
  /** A refetch of present fields began; they read as `Refreshing` until it lands. */
1291
1374
  {
@@ -1299,6 +1382,15 @@ export type RemoteMessage = {
1299
1382
  /** The refresh generation the read was planned under. */
1300
1383
  readonly refresh?: number | undefined;
1301
1384
  } |
1385
+ /**
1386
+ * Queries for these connections were sent. One with nothing to show reads
1387
+ * `Loading` until its page or its failure arrives, which is what separates
1388
+ * "being fetched" from `Initial`, "nothing is fetching this".
1389
+ */
1390
+ {
1391
+ readonly _tag: 'QueryStarted';
1392
+ readonly connections: readonly string[];
1393
+ } |
1302
1394
  /** The active Surfaces' roots changed; everything they do not reach is collected. */
1303
1395
  {
1304
1396
  readonly _tag: 'RetentionChanged';
@@ -1308,6 +1400,13 @@ export type RemoteMessage = {
1308
1400
  {
1309
1401
  readonly _tag: 'Hydrated';
1310
1402
  readonly entities: EntityStore;
1403
+ /**
1404
+ * The connections a snapshot declared should survive, as their edges.
1405
+ * Restored with **unknown** boundaries and marked stale: the rows show
1406
+ * at once and the connection is refetched, because a persisted cursor
1407
+ * may name server state that is gone.
1408
+ */
1409
+ readonly connections?: Readonly<Record<string, ReadonlyArray<ReadonlyArray<Edge>>>> | undefined;
1311
1410
  readonly merge: MergePolicy;
1312
1411
  } |
1313
1412
  /** A mutation began; its optimistic operations show until it settles. */
@@ -1365,7 +1464,11 @@ export type RemoteMessage = {
1365
1464
  readonly _tag: 'ConnectionRefreshed';
1366
1465
  readonly connection: string;
1367
1466
  } |
1368
- /** A query for the connection failed; it reads as it did before the request. */
1467
+ /**
1468
+ * A query for the connection failed. Its pages stay as they were, and it
1469
+ * reads `Failed` — with them as `previous`, when it has any — until a page
1470
+ * arrives, it is invalidated, or retention drops it.
1471
+ */
1369
1472
  {
1370
1473
  readonly _tag: 'QueryFailed';
1371
1474
  readonly connection: string;
@@ -1400,6 +1503,7 @@ export declare const remoteMessageCases: {
1400
1503
  readonly _tag: Schema.String;
1401
1504
  readonly message: Schema.String;
1402
1505
  }>;
1506
+ stream: Schema.optional<Schema.String>;
1403
1507
  };
1404
1508
  RefreshStarted: {
1405
1509
  requests: Schema.$Array<Schema.Codec<Requirement, Requirement, never, never>>;
@@ -1408,6 +1512,9 @@ export declare const remoteMessageCases: {
1408
1512
  requests: Schema.$Array<Schema.Codec<Requirement, Requirement, never, never>>;
1409
1513
  refresh: Schema.optional<Schema.Number>;
1410
1514
  };
1515
+ QueryStarted: {
1516
+ connections: Schema.$Array<Schema.String>;
1517
+ };
1411
1518
  RetentionChanged: {
1412
1519
  roots: Schema.Struct<{
1413
1520
  readonly requirements: Schema.$Array<Schema.Codec<Requirement, Requirement, never, never>>;
@@ -1418,6 +1525,7 @@ export declare const remoteMessageCases: {
1418
1525
  }>;
1419
1526
  };
1420
1527
  Hydrated: {
1528
+ connections: Schema.optional<Schema.Unknown>;
1421
1529
  entities: Schema.Unknown;
1422
1530
  merge: Schema.Union<readonly [Schema.Literal<"replace">, Schema.Literal<"preserve-existing">]>;
1423
1531
  };
@@ -1499,6 +1607,8 @@ export declare const isRemoteMessage: <M extends {
1499
1607
  readonly _tag: RemoteMessageTag;
1500
1608
  }>;
1501
1609
  export declare const remoteMessageSchema: Schema.Schema<RemoteMessage>;
1610
+ /** Whether a query for this connection is in flight. */
1611
+ export declare const isQueryLoading: (model: RemoteModel, identity: string) => boolean;
1502
1612
  /**
1503
1613
  * The generation a read is planned under: the highest any of the fields it
1504
1614
  * reads or connections it runs was refreshed at. A read entry carries this as a
@@ -1530,6 +1640,15 @@ export declare const isLoading: (model: RemoteModel, entity: string, id: string,
1530
1640
  * can resubscribe rather than silently miss facts.
1531
1641
  */
1532
1642
  export declare const updateRemote: (model: RemoteModel, message: RemoteMessage) => RemoteModel;
1643
+ /** Whether this field's last read failed and nothing has settled it since. */
1644
+ export declare const isFieldFailed: (model: RemoteModel, entity: string, id: string, field: string) => boolean;
1645
+ /**
1646
+ * The first failure among the fields `relation` reads of an entity, following
1647
+ * each relation the store holds into its targets, as `assemble` does. A read
1648
+ * that consults it shows `Failed` for a related entity's failure too, rather
1649
+ * than waiting on a field nothing will fetch.
1650
+ */
1651
+ export declare const failureOf: (model: RemoteModel, entity: string, id: string, relation: RelationRequirement) => RemoteError | undefined;
1533
1652
  /**
1534
1653
  * Writes a read result into the store, recording each field's applied window so
1535
1654
  * a later request with a different window refetches. A relation page requested
@@ -1848,6 +1967,92 @@ declare const Entity$1: {
1848
1967
  patch: <Name extends string, F extends Schema.Struct.Fields>(ref: EntityRef<Name, F>, patch: Partial<Schema.Struct.Encoded<F>>) => EntityPatch<Name, F>;
1849
1968
  };
1850
1969
  //#endregion
1970
+ //#region src/matching.d.ts
1971
+ /**
1972
+ * An answer over the rows the client holds — never over the rows that exist.
1973
+ *
1974
+ * `complete` is the whole reason this is its own type rather than a `Page`. A
1975
+ * `Page` carries `hasNext`/`hasPrevious`, which are facts the *server* stated
1976
+ * about rows beyond the ones it delivered; a local answer has no such facts and
1977
+ * cannot invent them. What it can say is whether the population it judged was
1978
+ * all of it, which is decidable: a connection terminal at both ends is wholly
1979
+ * held, and nothing was skipped for a missing field.
1980
+ *
1981
+ * An incomplete answer is not wrong — it is an answer about less than the
1982
+ * caller may have meant, and the flag is what lets a view say "3 so far" rather
1983
+ * than "3", or a caller decide to ask the server.
1984
+ */
1985
+ export interface Matched<A> {
1986
+ readonly items: ReadonlyArray<A>;
1987
+ /** Whether every row the answer is about was judged. */
1988
+ readonly complete: boolean;
1989
+ }
1990
+ /**
1991
+ * What the rows the client holds had to say about a body.
1992
+ *
1993
+ * `skipped` is the honest half. A row missing a field the body reads cannot be
1994
+ * judged — it is neither a match nor a non-match — and dropping it silently
1995
+ * would turn "I could not tell" into "no". Naming it lets a caller decide:
1996
+ * fetch the field, ask the server, or say the answer is partial.
1997
+ */
1998
+ export interface Judged {
1999
+ /** Keys of the rows that satisfy the body, in the order the body asks for. */
2000
+ readonly matched: ReadonlyArray<EntityKey>;
2001
+ /** Keys of rows held but missing a field the body reads, so not judged either way. */
2002
+ readonly skipped: ReadonlyArray<EntityKey>;
2003
+ }
2004
+ export interface MatchingOptions {
2005
+ /**
2006
+ * Judge only these keys. Without it every row of the body's Entity in the
2007
+ * store is a candidate, which is what a filter over a loaded list wants; with
2008
+ * it, a caller narrows to a connection's own edges.
2009
+ */
2010
+ readonly among?: ReadonlyArray<EntityKey> | undefined;
2011
+ }
2012
+ /**
2013
+ * The rows of `descriptor`'s Entity that the store holds and the body matches.
2014
+ *
2015
+ * The input is given **decoded**, as an application writes it, and encoded here
2016
+ * through the descriptor's own `Input` codec. That is deliberate and is the
2017
+ * whole reason this takes a descriptor rather than a bare body: a store holds
2018
+ * wire values, so an input has to be in that space to be compared against them,
2019
+ * and handing an interpreter a decoded value is a mistake **no runtime error
2020
+ * catches** — the reference interpreter answers "no rows", which reads exactly
2021
+ * like a correct answer. Doing it here means a caller cannot get it wrong.
2022
+ *
2023
+ * Literals in the body are deliberately *not* encoded. A literal written in
2024
+ * domain terms is already broken against the server, which binds it to a column
2025
+ * the same way; accepting it here would make the client quietly more permissive
2026
+ * than the thing it has to agree with.
2027
+ */
2028
+ export declare const matching: <Name extends string, Input>(store: EntityStore, descriptor: QueryDescriptor<Name, Input, unknown>, input: Input, options?: MatchingOptions) => Judged;
2029
+ /**
2030
+ * Whether one row belongs to a query, as far as the client can tell.
2031
+ *
2032
+ * Three answers, and the third is the one that makes this usable. `'unknown'`
2033
+ * is a row the store never fetched, or holds without a field the body reads —
2034
+ * a caller that collapses it into `'no'` has turned "I could not tell" into an
2035
+ * answer, which is the mistake this whole module is shaped against.
2036
+ *
2037
+ * **Membership is decidable where position is not.** Whether a row satisfies
2038
+ * `eq`, `isNull` or `contains` needs no collation: equality on text is exact
2039
+ * and `contains` folds ASCII, both stated in §6.0.1. *Where* a row sorts needs
2040
+ * text collation, which is the backend's and deliberately outside the
2041
+ * conformant subset — so this answers the half that is always answerable, and
2042
+ * ordering stays the server's until a query can declare its collation.
2043
+ */
2044
+ export type Belongs = 'yes' | 'no' | 'unknown';
2045
+ /**
2046
+ * Takes the input **encoded**, because its one caller holds it that way: a
2047
+ * connection identity carries the canonical encoded input, and making it decode
2048
+ * only for this to encode again would be a round trip that can fail.
2049
+ *
2050
+ * A decoded-input sibling was written first and deleted: nothing called it. The
2051
+ * asymmetry with `matching`, which takes a decoded input, is the shape of the
2052
+ * two real callers rather than an oversight.
2053
+ */
2054
+ export declare const belongsEncoded: <Name extends string, Input>(store: EntityStore, descriptor: QueryDescriptor<Name, Input, unknown>, encoded: Readonly<Record<string, unknown>>, key: EntityKey) => Belongs;
2055
+ //#endregion
1851
2056
  //#region src/inspect.d.ts
1852
2057
  /** A serializable summary of a RemoteModel for DevTools and diagnostics. */
1853
2058
  export interface RemoteInspection {
@@ -1863,12 +2068,18 @@ export interface RemoteInspection {
1863
2068
  readonly live: ReadonlyArray<string>;
1864
2069
  readonly gaps: ReadonlyArray<string>;
1865
2070
  /**
1866
- * The `entity\0id\0field` marks of reads in flight. This is what "Remote is
2071
+ * The `entity\0id\0field` marks of reads in flight, and a
2072
+ * `\0connection\0identity` mark for each query in flight. This is what "Remote is
1867
2073
  * working" means here, and it is read from the Model rather than from the
1868
2074
  * fibers doing the work: a tool that shows it shows something the Model can
1869
2075
  * be replayed to, and nothing that needs the runtime to be asked.
1870
2076
  */
1871
2077
  readonly loading: ReadonlyArray<string>;
2078
+ /**
2079
+ * The reads that failed and are not yet settled: queries by connection
2080
+ * identity, entity fields by `entity\0id\0field` mark, each with its error.
2081
+ */
2082
+ readonly failures: Failures;
1872
2083
  readonly mutations: {
1873
2084
  readonly pending: ReadonlyArray<string>;
1874
2085
  readonly failed: ReadonlyArray<string>;
@@ -1879,6 +2090,75 @@ export interface RemoteInspection {
1879
2090
  export declare const inspectRemote: (model: RemoteModel) => RemoteInspection;
1880
2091
  /** A pure, serializable view of one entity, or `undefined` if unknown. */
1881
2092
  export declare const inspectEntity: (model: RemoteModel, key: string) => RemoteInspection["entities"][number] | undefined;
2093
+ /**
2094
+ * What one query read is, and what it currently is — data-query-DESIGN §29.1.
2095
+ *
2096
+ * Every member is gathered from something that already existed: the read was
2097
+ * always this many pieces, and nothing had ever been asked to put them in one
2098
+ * place. It is plain serializable data, so a DevTools panel, a log line and a
2099
+ * test assert on the same value.
2100
+ *
2101
+ * Two members of §29.1's sketch are **not** here, and their absence is the
2102
+ * finding rather than an omission:
2103
+ *
2104
+ * - **Surface.** A Projection does not know which Surface reads it, and often
2105
+ * several do — so it is not a property of the read, and `explain` cannot
2106
+ * recover it alone. Given the active Surfaces (the same record
2107
+ * `subscriptions` takes), it reports **every** Surface reading the connection
2108
+ * rather than guessing one, and omits the member entirely when it was not
2109
+ * given them.
2110
+ * - **Executor.** The thing that answers a query is a `RemoteClient` Layer in
2111
+ * the runtime, not a value in the Model, and a pure read of the Model cannot
2112
+ * see it. That is the same boundary that makes this function pure and
2113
+ * replayable, so it is worth more than the line of text.
2114
+ *
2115
+ * And one that the design proposed and this does not want: *expectation*
2116
+ * (required versus optional). §11 left it unbuilt for want of a consumer, and
2117
+ * the explanation was the likeliest consumer. It turns out not to need it — a
2118
+ * query's result is a connection, so the shape is a `Page`, decided by the
2119
+ * definition rather than by the read. Nothing here has an opinion about whether
2120
+ * an empty one is an error, because nothing here has to have one.
2121
+ */
2122
+ export interface QueryExplanation {
2123
+ /** The bound Remote domain that answers it. */
2124
+ readonly domain: string;
2125
+ /** The definition's name. */
2126
+ readonly query: string;
2127
+ /** The input as encoded, which is what the identity is built from. */
2128
+ readonly input: unknown;
2129
+ /** The connection identity: definition plus canonical input, excluding the window. */
2130
+ readonly identity: string;
2131
+ /** How much of the connection this read asks for. */
2132
+ readonly window: QueryWindow;
2133
+ /** What it reads of each item, as the slice asked of the server. */
2134
+ readonly select: RelationRequirement;
2135
+ /**
2136
+ * What the query means, as readable text — `Query.show`, and so neither SQL
2137
+ * nor whatever the backend compiled. Absent for a definition made with
2138
+ * `Query.make`, whose meaning lives on the server that answers it.
2139
+ */
2140
+ readonly body?: string | undefined;
2141
+ /** The fields, inputs and operations the body reads; absent with the body. */
2142
+ readonly dependencies?: Dependencies | undefined;
2143
+ /** What the read answers from this Model right now. */
2144
+ readonly state: RemoteData<unknown>['_tag'];
2145
+ /**
2146
+ * The active Surfaces reading this connection at this Model, if the active
2147
+ * record was supplied. Plural because several may, which is why a Projection
2148
+ * cannot carry the answer itself.
2149
+ */
2150
+ readonly surfaces?: ReadonlyArray<string> | undefined;
2151
+ /**
2152
+ * Why each of those Surfaces is active, where it was placed with
2153
+ * `Surface.when` — the Model path and the tag, without running anything. A
2154
+ * Surface placed with `Surface.at` has no entry: a callback cannot be read.
2155
+ */
2156
+ readonly activation?: ReadonlyArray<{
2157
+ readonly surface: string;
2158
+ readonly path: readonly string[];
2159
+ readonly tag: string;
2160
+ }> | undefined;
2161
+ }
1882
2162
  //#endregion
1883
2163
  //#region src/plan.d.ts
1884
2164
  type Window = NonNullable<Requirement['windows']>[string];
@@ -2047,6 +2327,44 @@ export interface RemoteDomain<AppModel, Store extends RemoteModel, Entities exte
2047
2327
  next<Name extends string, Input>(model: AppModel, projection: QueryProjection<AppModel, any, Name, Input>): QueryRef<Name, Input> | undefined;
2048
2328
  /** The `QueryRef` for the page before the loaded start, or `undefined`. */
2049
2329
  previous<Name extends string, Input>(model: AppModel, projection: QueryProjection<AppModel, any, Name, Input>): QueryRef<Name, Input> | undefined;
2330
+ /**
2331
+ * What a query read is and what it currently is, as one serializable value —
2332
+ * data-query-DESIGN §29.1: the domain, the definition and its input, the
2333
+ * connection identity, the window, the Selection, the body as readable text
2334
+ * with what it depends on, and what the read answers from this Model.
2335
+ *
2336
+ * Pure, so a DevTools panel showing it shows something the Model can be
2337
+ * replayed to.
2338
+ *
2339
+ * Given `surfaces` — the same active record `subscriptions` takes — it also
2340
+ * reports which of them read this connection, and why each is active where
2341
+ * that is a readable fact (`Surface.when`) rather than a callback
2342
+ * (`Surface.at`). A Projection cannot carry this itself: several Surfaces may
2343
+ * read one connection, so it is not a property of the read.
2344
+ */
2345
+ explain<Name extends string, Input>(model: AppModel, projection: QueryProjection<AppModel, any, Name, Input>, options?: {
2346
+ readonly surfaces?: Readonly<Record<string, ActiveSurface<AppModel>>> | undefined;
2347
+ }): QueryExplanation;
2348
+ /**
2349
+ * The rows of a loaded list that a body matches, decoded as the list decodes
2350
+ * them — a filter that asks the server nothing.
2351
+ *
2352
+ * **It filters a list; it does not run a query.** That distinction is what
2353
+ * keeps it honest. "Which rows match" would need to know that the list holds
2354
+ * every row the body could match, which is predicate containment and is
2355
+ * deliberately not built. "Which rows *of this list* match" is decidable from
2356
+ * what is already here, and is what a search box over a loaded page actually
2357
+ * wants.
2358
+ *
2359
+ * `complete` says whether the answer is about the whole list: every edge
2360
+ * judged, and the connection terminal at both ends. An incomplete answer is
2361
+ * not wrong — it is about less than the caller may have meant, which is why
2362
+ * it is said rather than left for a view to assume.
2363
+ *
2364
+ * Creates no connection, so there is nothing new to retain and nothing new to
2365
+ * fetch. The server stays authoritative for which rows exist.
2366
+ */
2367
+ filtered<Value, Name extends string, Input, Q extends QueryDescriptor<any, any, any>>(model: AppModel, over: QueryProjection<AppModel, Value, Name, Input>, by: Q & Registered<Q['name'], QueryName<Queries[number]>, 'Query'>, input: QueryInput<Q>): Matched<Value>;
2050
2368
  /** A Command that runs the query and yields the `ConnectionMerged` (or `QueryFailed`) that reduces it: "load more". */
2051
2369
  fetch(ref: QueryRef<string, unknown>): Command<RemoteMessage, never, RemoteClient>;
2052
2370
  /**
@@ -2151,6 +2469,8 @@ export type QueryOptions<Value, Entity extends string, Of extends string = Entit
2151
2469
  /** A query connection read as a `Page` of selected items; `ref` is the connection with its first window. */
2152
2470
  export interface QueryProjection<AppModel, Value, Name extends string, Input> extends Projection<AppModel, RemoteData<Page<Value>>> {
2153
2471
  readonly ref: QueryRef<Name, Input>;
2472
+ /** What it reads of each item, so a filter over the same list decodes identically. */
2473
+ readonly selection: Selection<Value, string>;
2154
2474
  }
2155
2475
  /** The input of a query descriptor. */
2156
2476
  export type QueryInput<Q> = Q extends QueryDescriptor<any, infer Input, any> ? Input : never;
@@ -2227,6 +2547,7 @@ export declare const Remote: {
2227
2547
  readonly _tag: Schema.String;
2228
2548
  readonly message: Schema.String;
2229
2549
  }>;
2550
+ stream: Schema.optional<Schema.String>;
2230
2551
  };
2231
2552
  RefreshStarted: {
2232
2553
  requests: Schema.$Array<Schema.Codec<Requirement, Requirement, never, never>>;
@@ -2235,6 +2556,9 @@ export declare const Remote: {
2235
2556
  requests: Schema.$Array<Schema.Codec<Requirement, Requirement, never, never>>;
2236
2557
  refresh: Schema.optional<Schema.Number>;
2237
2558
  };
2559
+ QueryStarted: {
2560
+ connections: Schema.$Array<Schema.String>;
2561
+ };
2238
2562
  RetentionChanged: {
2239
2563
  roots: Schema.Struct<{
2240
2564
  readonly requirements: Schema.$Array<Schema.Codec<Requirement, Requirement, never, never>>;
@@ -2245,6 +2569,7 @@ export declare const Remote: {
2245
2569
  }>;
2246
2570
  };
2247
2571
  Hydrated: {
2572
+ connections: Schema.optional<Schema.Unknown>;
2248
2573
  entities: Schema.Unknown;
2249
2574
  merge: Schema.Union<readonly [Schema.Literal<"replace">, Schema.Literal<"preserve-existing">]>;
2250
2575
  };
@@ -2391,6 +2716,9 @@ export declare const Remote: {
2391
2716
  * again with `next`/`previous`. The read entries restart, so a read or query
2392
2717
  * sent before the refresh is not applied after it.
2393
2718
  *
2719
+ * It is also how a failed query is retried: a connection whose query failed
2720
+ * is not run again on its own, so its read stays `Failed` until this asks.
2721
+ *
2394
2722
  * The projection must be observed, which it is while it is on screen. Live
2395
2723
  * subscriptions are left as they are. For data nothing observes (SSR, tests),
2396
2724
  * use `Remote.prefetch` with `RemotePolicy.networkOnly`. Refreshing what is
@@ -2503,8 +2831,10 @@ export declare const Remote: {
2503
2831
  observe: <AppModel, Store extends RemoteModel, Names extends string, Model, SurfaceMessage, Params, Message = RemoteMessage>(bound: BoundRemote<AppModel, Store, Names>, surface: Surface<AppModel, Model, SurfaceMessage, Params>, params: Params, toMessage?: (message: RemoteMessage) => Message, options?: ObserveOptions) => EntryWithoutKeepAlive<AppModel, Message, ReadDependencies, RemoteClient>;
2504
2832
  /**
2505
2833
  * A Foldkit Subscription entry that consumes the live stream for a Surface's
2506
- * requirements, emitting a `LiveReceived` per event and a `ReadFailed` when
2507
- * the stream breaks (including `ResumeUnavailable`). The resume cursor is read
2834
+ * requirements, emitting a `LiveReceived` per event and a `ReadFailed`
2835
+ * carrying its `stream` when the stream breaks (including
2836
+ * `ResumeUnavailable`). That records a gap on the stream rather than a failed
2837
+ * read, since nothing was being read. The resume cursor is read
2508
2838
  * from `RemoteModel.live`, so the application tracks no cursor of its own.
2509
2839
  */
2510
2840
  live: <AppModel, Store extends RemoteModel, Names extends string, Model, SurfaceMessage, Params, Message = RemoteMessage>(bound: BoundRemote<AppModel, Store, Names>, surface: Surface<AppModel, Model, SurfaceMessage, Params>, params: Params, toMessage?: (message: RemoteMessage) => Message, options?: LiveOptions) => EntryWithoutKeepAlive<AppModel, Message, {