foldkit-remote 0.7.0 → 0.8.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.mjs CHANGED
@@ -1,6 +1,8 @@
1
1
  import { Context, Deferred, Duration, Effect, Layer, Option, Request, RequestResolver, Result, Schema, SchemaGetter, Stream } from "effect";
2
2
  import * as Domain from "foldkit-entity";
3
3
  import { Entity as Entity$1, Expr, Query as Query$1, SelectionTypeId, evaluate } from "foldkit-entity";
4
+ import { mapMessage } from "foldkit/command";
5
+ import { defineMessageUnion } from "foldkit/message";
4
6
  import * as Subscription from "foldkit/subscription";
5
7
  import { Metadata } from "foldkit-surface";
6
8
  import { Rpc, RpcGroup } from "effect/unstable/rpc";
@@ -208,6 +210,7 @@ const emptyEntry = {
208
210
  values: {},
209
211
  present: /* @__PURE__ */ new Set(),
210
212
  stale: /* @__PURE__ */ new Set(),
213
+ unavailable: /* @__PURE__ */ new Set(),
211
214
  tombstone: false,
212
215
  updatedAt: 0,
213
216
  windows: {}
@@ -220,10 +223,12 @@ const replace = (store, key, entry) => ({
220
223
  const written = (previous, values, now, windows) => {
221
224
  const present = new Set(previous.present);
222
225
  const stale = new Set(previous.stale);
226
+ const unavailable = new Set(previous.unavailable);
223
227
  const nextWindows = { ...previous.windows };
224
228
  for (const field of Object.keys(values)) {
225
229
  present.add(field);
226
230
  stale.delete(field);
231
+ unavailable.delete(field);
227
232
  const requested = windows?.[field];
228
233
  if (requested !== void 0 && requested !== "") nextWindows[field] = requested;
229
234
  else delete nextWindows[field];
@@ -238,6 +243,7 @@ const written = (previous, values, now, windows) => {
238
243
  },
239
244
  present,
240
245
  stale,
246
+ unavailable,
241
247
  tombstone: false,
242
248
  updatedAt: now,
243
249
  windows: nextWindows
@@ -287,11 +293,40 @@ const setStale = (store, marks, stale) => {
287
293
  const markStale = (store, key, fields) => setStale(store, [[key, fields]], true);
288
294
  /** Ends a refresh that did not land: the fields are present again as they were. */
289
295
  const clearStale = (store, key, fields) => setStale(store, [[key, fields]], false);
296
+ /**
297
+ * Records fields the server settled without a value. A present field is left
298
+ * as it is: the answer that settled it was not about the value it holds.
299
+ * `false` forgets the marks, which is how a refresh asks again.
300
+ */
301
+ const setUnavailable = (store, marks, unavailable) => {
302
+ let next;
303
+ for (const [key, fields] of marks) {
304
+ const previous = (next ?? store)[key] ?? emptyEntry;
305
+ if (previous.tombstone) continue;
306
+ const marked = new Set(previous.unavailable);
307
+ for (const field of fields) if (unavailable) {
308
+ if (!previous.present.has(field)) marked.add(field);
309
+ } else marked.delete(field);
310
+ if (marked.size === previous.unavailable.size) continue;
311
+ next ??= { ...store };
312
+ next[key] = {
313
+ ...previous,
314
+ unavailable: marked
315
+ };
316
+ }
317
+ return next ?? store;
318
+ };
319
+ /** Whether the server settled this field without a value, and nothing has written it since. */
320
+ const isFieldUnavailable = (store, key, field) => {
321
+ const value = store[key];
322
+ return value !== void 0 && !value.tombstone && value.unavailable.has(field);
323
+ };
290
324
  /** Records that the entity is known to be absent, so it is not refetched. */
291
325
  const tombstone = (store, key) => replace(store, key, {
292
326
  values: {},
293
327
  present: /* @__PURE__ */ new Set(),
294
328
  stale: /* @__PURE__ */ new Set(),
329
+ unavailable: /* @__PURE__ */ new Set(),
295
330
  tombstone: true,
296
331
  updatedAt: 0,
297
332
  windows: {}
@@ -329,7 +364,8 @@ const readField = (store, key, field) => {
329
364
  };
330
365
  /**
331
366
  * The fields the planner must fetch. A tombstone makes every field known
332
- * (absent), so it returns an empty list and the entity is not refetched. A
367
+ * (absent), so it returns an empty list and the entity is not refetched, and a
368
+ * field the server settled without a value is known the same way. A
333
369
  * requested window that differs from the one a field was fetched with also
334
370
  * marks it missing, but only when the stored window is known (never `""`), so a
335
371
  * writer that does not record windows cannot cause a refetch loop.
@@ -338,7 +374,9 @@ const missingFields = (store, key, fields, windows) => {
338
374
  const value = store[key];
339
375
  if (value !== void 0 && value.tombstone) return [];
340
376
  return [...fields].filter((field) => {
341
- if (value === void 0 || !value.present.has(field) || value.stale.has(field)) return true;
377
+ if (value === void 0) return true;
378
+ if (!value.present.has(field)) return !value.unavailable.has(field);
379
+ if (value.stale.has(field)) return true;
342
380
  const requested = windows?.[field];
343
381
  const stored = value.windows[field];
344
382
  return requested !== void 0 && stored !== void 0 && stored !== "" && stored !== requested;
@@ -588,7 +626,7 @@ const Query = {
588
626
  * server refuses a mismatch with `RemoteProtocolError`, so a shape change never
589
627
  * drifts silently: bump it whenever `ReadRequest` or `LiveRequirement` change.
590
628
  */
591
- const REMOTE_PROTOCOL_VERSION = 3;
629
+ const REMOTE_PROTOCOL_VERSION = 4;
592
630
  var RemoteReadError = class extends Schema.TaggedError()("RemoteReadError", { message: Schema.String }) {};
593
631
  var RemoteMutationError = class extends Schema.TaggedError()("RemoteMutationError", { message: Schema.String }) {};
594
632
  var RemoteLiveError = class extends Schema.TaggedError()("RemoteLiveError", { message: Schema.String }) {};
@@ -649,7 +687,20 @@ const NormalizedEntity = Schema.Struct({
649
687
  id: Schema.String,
650
688
  values: Schema.Record(Schema.String, Schema.Unknown)
651
689
  });
652
- const ReadBatchResult = Schema.Struct({ entities: Schema.Array(NormalizedEntity) });
690
+ /**
691
+ * Fields a read asked for that the server answered without, and will not
692
+ * answer with: the client stops asking. It says nothing about why, so a rule
693
+ * that withholds a field is not itself on the wire.
694
+ */
695
+ const SettledFields = Schema.Struct({
696
+ entity: Schema.String,
697
+ id: Schema.String,
698
+ fields: Schema.Array(Schema.String)
699
+ });
700
+ const ReadBatchResult = Schema.Struct({
701
+ entities: Schema.Array(NormalizedEntity),
702
+ settled: Schema.Array(SettledFields)
703
+ });
653
704
  const MutationRequest = Schema.Struct({
654
705
  /** Stable across transport retries so a mutation is not applied twice. */
655
706
  requestId: Schema.String,
@@ -867,7 +918,7 @@ const coalesceReads = (read, options = {}) => Effect.gen(function* () {
867
918
  for (const entry of entries) if (own.has(entry.request.key)) owned.set(entry.request.key, entry.request.requirement);
868
919
  yield* Effect.forEach(readsOf(owned), ({ keys, requests }) => Effect.gen(function* () {
869
920
  const exit = yield* Effect.exit(read({
870
- version: 3,
921
+ version: 4,
871
922
  requests
872
923
  }));
873
924
  for (const key of keys) {
@@ -883,10 +934,19 @@ const coalesceReads = (read, options = {}) => Effect.gen(function* () {
883
934
  });
884
935
  let resolver = RequestResolver.make(runAll);
885
936
  if (options.window !== void 0) resolver = RequestResolver.setDelay(resolver, Duration.fromInputUnsafe(options.window));
886
- return (batch) => batch.requests.length === 0 ? Effect.succeed({ entities: [] }) : Effect.forEach(batch.requests, (requirement) => Effect.request(new ReadRequirement({
937
+ return (batch) => batch.requests.length === 0 ? Effect.succeed({
938
+ entities: [],
939
+ settled: []
940
+ }) : Effect.forEach(batch.requests, (requirement) => Effect.request(new ReadRequirement({
887
941
  key: requirementKey(requirement),
888
942
  requirement
889
- }), resolver), { concurrency: "unbounded" }).pipe(Effect.map((results) => ({ entities: [...new Set(results)].flatMap((result) => result.entities) })));
943
+ }), resolver), { concurrency: "unbounded" }).pipe(Effect.map((results) => {
944
+ const distinct = [...new Set(results)];
945
+ return {
946
+ entities: distinct.flatMap((result) => result.entities),
947
+ settled: distinct.flatMap((result) => result.settled)
948
+ };
949
+ }));
890
950
  });
891
951
  //#endregion
892
952
  //#region src/client.ts
@@ -1302,6 +1362,24 @@ const assemble = (store, key, requirement) => {
1302
1362
  refreshing
1303
1363
  };
1304
1364
  };
1365
+ /**
1366
+ * The first field `requirement` reads, at any depth, that the server settled
1367
+ * without a value, as `Entity:id.field`. A read that consults it says why it
1368
+ * cannot assemble rather than waiting on a field nothing will fetch.
1369
+ */
1370
+ const unavailableOf = (store, key, requirement) => {
1371
+ for (const field of requirement.fields) {
1372
+ if (isFieldUnavailable(store, key, field)) return `${key}.${field}`;
1373
+ const relation = requirement.relations?.[field];
1374
+ if (relation === void 0) continue;
1375
+ const value = readField(store, key, field);
1376
+ if (Option.isNone(value) || value.value === null || value.value === void 0) continue;
1377
+ for (const ref of targetsOf(value.value, relation)) {
1378
+ const found = unavailableOf(store, entityKey(ref.entity, ref.id), relation);
1379
+ if (found !== void 0) return found;
1380
+ }
1381
+ }
1382
+ };
1305
1383
  const assembleTarget = (store, ref, relation) => {
1306
1384
  const key = entityKey(ref.entity, ref.id);
1307
1385
  return isTombstone(store, key) ? "absent" : assemble(store, key, relation);
@@ -1545,6 +1623,7 @@ const inspectEntry = (key, entry) => ({
1545
1623
  key,
1546
1624
  present: [...entry.present],
1547
1625
  stale: [...entry.stale],
1626
+ unavailable: [...entry.unavailable],
1548
1627
  tombstone: entry.tombstone,
1549
1628
  updatedAt: entry.updatedAt,
1550
1629
  windows: { ...entry.windows }
@@ -1878,6 +1957,41 @@ const followRelation = (value, relation) => targetsOf(value, relation).map((ref)
1878
1957
  ...relation,
1879
1958
  id: ref.id
1880
1959
  }));
1960
+ /**
1961
+ * The earliest moment a value `requirements` read, following the relations the
1962
+ * store holds as `plan` does, goes stale under `freshness`, with the
1963
+ * requirements of the entities that go stale then. Nothing when no value is
1964
+ * both present and still fresh: what is already expired or stale is planned,
1965
+ * not awaited, which is what keeps a timer from firing on what it already
1966
+ * fired on.
1967
+ */
1968
+ const deadlineOf = (store, requirements, freshness) => {
1969
+ let at;
1970
+ let due = [];
1971
+ const walk = (group) => {
1972
+ const key = entityKey(group.entity, group.id);
1973
+ const entry = store[key];
1974
+ if (entry === void 0 || entry.tombstone) return;
1975
+ if (group.fields.some((field) => entry.present.has(field) && !entry.stale.has(field))) {
1976
+ const expires = entry.updatedAt + freshness.freshness;
1977
+ if (expires > freshness.now) {
1978
+ if (at === void 0 || expires < at) {
1979
+ at = expires;
1980
+ due = [group];
1981
+ } else if (expires === at) due.push(group);
1982
+ }
1983
+ }
1984
+ for (const [field, relation] of Object.entries(group.relations ?? {})) {
1985
+ const value = readField(store, key, field);
1986
+ if (value._tag === "Some") for (const next of followRelation(value.value, relation)) walk(next);
1987
+ }
1988
+ };
1989
+ for (const group of Requirement.merge(requirements)) walk(group);
1990
+ return at === void 0 ? void 0 : {
1991
+ at,
1992
+ due: Requirement.merge(due)
1993
+ };
1994
+ };
1881
1995
  const plan = (store, requirements, options = {}) => {
1882
1996
  const { freshness, force = false } = options;
1883
1997
  const planned = [];
@@ -1989,7 +2103,7 @@ const gc = (state, roots) => {
1989
2103
  * application's choice.
1990
2104
  */
1991
2105
  /** Bump when the serialized shape changes; a mismatch discards the cache. */
1992
- const REMOTE_CACHE_VERSION = 4;
2106
+ const REMOTE_CACHE_VERSION = 5;
1993
2107
  /**
1994
2108
  * A snapshot of a Model, keeping the entity cache and only the connections
1995
2109
  * named.
@@ -2012,13 +2126,14 @@ const sorted = (values) => [...values].sort();
2012
2126
  * whatever order they were built in.
2013
2127
  */
2014
2128
  const serializeStore = (snapshot, scope) => ({
2015
- version: 4,
2129
+ version: 5,
2016
2130
  scope: scope ?? null,
2017
2131
  connections: snapshot.connections,
2018
2132
  entities: Object.fromEntries(Object.entries(snapshot.entities).map(([key, entry]) => [key, {
2019
2133
  values: entry.values,
2020
2134
  present: sorted(entry.present),
2021
2135
  stale: sorted(entry.stale),
2136
+ unavailable: sorted(entry.unavailable),
2022
2137
  tombstone: entry.tombstone,
2023
2138
  updatedAt: entry.updatedAt,
2024
2139
  windows: entry.windows
@@ -2033,6 +2148,7 @@ const parseEntry = (value) => {
2033
2148
  if (entry.values === null || typeof entry.values !== "object" || Array.isArray(entry.values)) throw new Error("entry.values is not a record");
2034
2149
  if (!isStringArray(entry.present)) throw new Error("entry.present is not a string array");
2035
2150
  if (!isStringArray(entry.stale)) throw new Error("entry.stale is not a string array");
2151
+ if (!isStringArray(entry.unavailable)) throw new Error("entry.unavailable is not a string array");
2036
2152
  if (typeof entry.tombstone !== "boolean") throw new Error("entry.tombstone is not a boolean");
2037
2153
  if (typeof entry.updatedAt !== "number") throw new Error("entry.updatedAt is not a number");
2038
2154
  if (!isStringRecord(entry.windows)) throw new Error("entry.windows is not a string record");
@@ -2040,6 +2156,7 @@ const parseEntry = (value) => {
2040
2156
  values: entry.values,
2041
2157
  present: new Set(entry.present),
2042
2158
  stale: new Set(entry.stale),
2159
+ unavailable: new Set(entry.unavailable),
2043
2160
  tombstone: entry.tombstone,
2044
2161
  updatedAt: entry.updatedAt,
2045
2162
  windows: entry.windows
@@ -2098,7 +2215,7 @@ const hydrate = (raw, options = {}) => {
2098
2215
  }
2099
2216
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return void 0;
2100
2217
  const candidate = parsed;
2101
- if (candidate.version !== 4) return void 0;
2218
+ if (candidate.version !== 5) return void 0;
2102
2219
  if ((candidate.scope ?? null) !== (options.scope ?? null)) return void 0;
2103
2220
  if (candidate.entities === null || typeof candidate.entities !== "object") return void 0;
2104
2221
  if (Array.isArray(candidate.entities)) return void 0;
@@ -2293,7 +2410,32 @@ const noFailures = {
2293
2410
  const emptyRefresh = {
2294
2411
  generation: 0,
2295
2412
  requested: /* @__PURE__ */ new Map(),
2296
- started: /* @__PURE__ */ new Map()
2413
+ started: /* @__PURE__ */ new Map(),
2414
+ floor: 0
2415
+ };
2416
+ /**
2417
+ * The Model with everything the server told it forgotten: values, tombstones,
2418
+ * unavailable fields, connections, live cursors, failures, and what is in
2419
+ * flight. For a change of principal, whose facts these were. The mutation
2420
+ * sequence is kept, so request ids stay unique, and a mutation in flight is
2421
+ * treated as applied: its answer, when it comes, writes nothing here.
2422
+ */
2423
+ const forgetRemote = (model) => {
2424
+ const generation = model.refresh.generation + 1;
2425
+ return {
2426
+ ...initialRemoteModel,
2427
+ refresh: {
2428
+ generation,
2429
+ requested: /* @__PURE__ */ new Map(),
2430
+ started: /* @__PURE__ */ new Map(),
2431
+ floor: generation
2432
+ },
2433
+ mutations: {
2434
+ ...emptyMutationState,
2435
+ sequence: model.mutations.sequence,
2436
+ applied: /* @__PURE__ */ new Set([...model.mutations.applied, ...model.mutations.pending])
2437
+ }
2438
+ };
2297
2439
  };
2298
2440
  const initialRemoteModel = {
2299
2441
  entities: emptyStore,
@@ -2487,7 +2629,7 @@ const prunedRefresh = (refresh, retained) => {
2487
2629
  * anything else is.
2488
2630
  */
2489
2631
  const refreshedAt = (refresh, requests, connections = []) => {
2490
- let at = 0;
2632
+ let at = refresh.floor;
2491
2633
  for (const mark of fieldMarks(requests)) at = Math.max(at, refresh.requested.get(mark) ?? 0);
2492
2634
  for (const identity of connections) at = Math.max(at, refresh.requested.get(connectionMark(identity)) ?? 0);
2493
2635
  return at;
@@ -2619,7 +2761,8 @@ const updateRemote = (model, message) => {
2619
2761
  }
2620
2762
  case "RefreshStarted": {
2621
2763
  const asked = message.requests.reduce((store, request) => isTombstone(store, entityKey(request.entity, request.id)) ? remove(store, entityKey(request.entity, request.id)) : store, model.entities);
2622
- const entities = setStale(asked, marksOf(message.requests), true);
2764
+ const marks = marksOf(message.requests);
2765
+ const entities = setUnavailable(setStale(asked, marks, true), marks, false);
2623
2766
  const failures = withoutFieldFailures(model.failures, reachedMarks(model.entities, message.requests));
2624
2767
  return entities === model.entities && failures === model.failures ? model : {
2625
2768
  ...model,
@@ -2974,12 +3117,13 @@ const writeRead = (store, requests, result, now = 0) => {
2974
3117
  windows: entry?.windows
2975
3118
  });
2976
3119
  }
3120
+ const settled = new Set(result.settled.map((entry) => entityKey(entry.entity, entry.id)));
2977
3121
  let answered = store;
2978
3122
  for (const request of requests) {
2979
3123
  const key = entityKey(request.entity, request.id);
2980
- if (!returned.has(key)) answered = tombstone(answered, key);
3124
+ if (!returned.has(key) && !settled.has(key)) answered = tombstone(answered, key);
2981
3125
  }
2982
- return writeEntities(answered, writes, now);
3126
+ return setUnavailable(writeEntities(answered, writes, now), result.settled.map((entry) => [entityKey(entry.entity, entry.id), entry.fields]), true);
2983
3127
  };
2984
3128
  //#endregion
2985
3129
  //#region src/policy.ts
@@ -3012,6 +3156,221 @@ const RemotePolicy = {
3012
3156
  refreshes: (policy) => policy._tag !== "CacheFirst"
3013
3157
  };
3014
3158
  //#endregion
3159
+ //#region src/resume.ts
3160
+ /**
3161
+ * What of Remote's state crosses from a server render to the browser.
3162
+ *
3163
+ * A page rendered a moment ago sends exactly what its active Surfaces read:
3164
+ * for each requirement the fields it names, following relations through the
3165
+ * refs the store holds, and each connection whole, with its boundaries, so
3166
+ * "load more" knows where the server stopped. A live subscription's cursor
3167
+ * crosses with the entities it covers, so a subscription started in the
3168
+ * browser resumes where the server's left off.
3169
+ *
3170
+ * This is not a `Snapshot`, which is built for a cache that survived a reload
3171
+ * and deliberately keeps no cursors. Nothing else of `RemoteModel` crosses:
3172
+ * loading marks, failures, the mutation ledger, optimistic layers, gaps and
3173
+ * retention start from their initial values in the browser.
3174
+ */
3175
+ const CapturedEntry = Schema.Struct({
3176
+ values: Schema.Record(Schema.String, Schema.Unknown),
3177
+ present: Schema.Array(Schema.String),
3178
+ stale: Schema.Array(Schema.String),
3179
+ unavailable: Schema.Array(Schema.String),
3180
+ windows: Schema.Record(Schema.String, Schema.String),
3181
+ tombstone: Schema.Boolean,
3182
+ updatedAt: Schema.Number
3183
+ });
3184
+ const CapturedBoundary = Schema.Union([
3185
+ Schema.TaggedStruct("Terminal", {}),
3186
+ Schema.TaggedStruct("Cursor", { cursor: Schema.String }),
3187
+ Schema.TaggedStruct("Unknown", {})
3188
+ ]);
3189
+ const CapturedConnection = Schema.Struct({
3190
+ segments: Schema.Array(Schema.Struct({
3191
+ edges: Schema.Array(Schema.Struct({
3192
+ key: Schema.String,
3193
+ ref: Schema.Struct({
3194
+ entity: Schema.String,
3195
+ id: Schema.String
3196
+ })
3197
+ })),
3198
+ start: CapturedBoundary,
3199
+ end: CapturedBoundary
3200
+ })),
3201
+ stale: Schema.Boolean
3202
+ });
3203
+ const CapturedLive = Schema.Struct({
3204
+ cursor: Schema.Number,
3205
+ boundary: Schema.Record(Schema.String, Schema.Struct({
3206
+ before: Schema.Number,
3207
+ after: Schema.Number
3208
+ }))
3209
+ });
3210
+ /**
3211
+ * Marks a Subscription entry Remote built as one that may start late under
3212
+ * `foldkit-ssr`'s deferred boot (its decision 10): a read with resumed data
3213
+ * plans nothing, a live subscription resumes from the cursor the envelope
3214
+ * carries, and retention running late collects late. Non-enumerable, so the
3215
+ * runtime's iteration of an entry never sees it.
3216
+ */
3217
+ const DEFERRABLE = Symbol.for("foldkit-remote/deferrable");
3218
+ /** Marks every entry of a record Remote built, lifted or not. */
3219
+ const markAll = (entries) => {
3220
+ for (const entry of Object.values(entries)) Object.defineProperty(entry, DEFERRABLE, {
3221
+ value: true,
3222
+ enumerable: false
3223
+ });
3224
+ return entries;
3225
+ };
3226
+ /** The captured part of a Remote store, as JSON. */
3227
+ const RemoteCapture = Schema.Struct({
3228
+ entities: Schema.Record(Schema.String, CapturedEntry),
3229
+ connections: Schema.Record(Schema.String, CapturedConnection),
3230
+ live: Schema.Record(Schema.String, CapturedLive)
3231
+ });
3232
+ /** What some projections read, from their metadata. */
3233
+ const askedOf$1 = (projections) => ({
3234
+ requirements: projections.flatMap((projection) => RemoteRequirements.get(projection.metadata)),
3235
+ connections: projections.flatMap((projection) => RemoteConnections.get(projection.metadata))
3236
+ });
3237
+ /** Whether every `entity:id:fields` part of a live stream key was captured with those fields. */
3238
+ const liveCovered = (stream, entries) => stream.split("|").every((part) => {
3239
+ const split = part.lastIndexOf(":");
3240
+ const entry = entries.get(part.slice(0, split));
3241
+ const fields = part.slice(split + 1).split(",").filter((field) => field !== "");
3242
+ return entry !== void 0 && fields.every((field) => entry.present.has(field));
3243
+ });
3244
+ /**
3245
+ * Captures what some projections read from a store: the fields each
3246
+ * requirement names, through its relations; each connection whole, and the
3247
+ * fields its selection reads of each item; and each live cursor whose
3248
+ * entities were all captured with the fields it follows.
3249
+ */
3250
+ const captureRemote = (remote, projections) => {
3251
+ const store = remote.entities;
3252
+ const entries = /* @__PURE__ */ new Map();
3253
+ const walked = /* @__PURE__ */ new Map();
3254
+ const walk = (entity, id, requirement) => {
3255
+ const key = entityKey(entity, id);
3256
+ const seen = walked.get(key) ?? /* @__PURE__ */ new Set();
3257
+ if (seen.has(requirement)) return;
3258
+ seen.add(requirement);
3259
+ walked.set(key, seen);
3260
+ const stored = store[key];
3261
+ if (stored === void 0) return;
3262
+ const entry = entries.get(key) ?? {
3263
+ values: {},
3264
+ present: /* @__PURE__ */ new Set(),
3265
+ stale: /* @__PURE__ */ new Set(),
3266
+ unavailable: /* @__PURE__ */ new Set(),
3267
+ windows: {},
3268
+ tombstone: stored.tombstone,
3269
+ updatedAt: stored.updatedAt
3270
+ };
3271
+ entries.set(key, entry);
3272
+ for (const field of requirement.fields) {
3273
+ if (stored.unavailable.has(field)) entry.unavailable.add(field);
3274
+ if (!stored.present.has(field)) continue;
3275
+ entry.values[field] = stored.values[field];
3276
+ entry.present.add(field);
3277
+ if (stored.stale.has(field)) entry.stale.add(field);
3278
+ const window = stored.windows[field];
3279
+ if (window !== void 0) entry.windows[field] = window;
3280
+ const nested = requirement.relations?.[field];
3281
+ if (nested === void 0) continue;
3282
+ const value = readField(store, key, field);
3283
+ if (value._tag === "None") continue;
3284
+ for (const ref of targetsOf(value.value, nested)) walk(ref.entity, ref.id, nested);
3285
+ }
3286
+ };
3287
+ const asked = askedOf$1(projections);
3288
+ for (const requirement of asked.requirements) walk(requirement.entity, requirement.id, requirement);
3289
+ const connections = {};
3290
+ for (const { identity, select } of asked.connections) {
3291
+ const connection = remote.connections[identity];
3292
+ if (connection === void 0) continue;
3293
+ connections[identity] = connection;
3294
+ for (const segment of connection.segments) for (const edge of segment.edges) walk(edge.ref.entity, edge.ref.id, select);
3295
+ }
3296
+ const live = {};
3297
+ for (const [stream, state] of Object.entries(remote.live)) if (liveCovered(stream, entries)) live[stream] = state;
3298
+ return {
3299
+ entities: Object.fromEntries([...entries].map(([key, entry]) => [key, {
3300
+ values: entry.values,
3301
+ present: [...entry.present],
3302
+ stale: [...entry.stale],
3303
+ unavailable: [...entry.unavailable],
3304
+ windows: entry.windows,
3305
+ tombstone: entry.tombstone,
3306
+ updatedAt: entry.updatedAt
3307
+ }])),
3308
+ connections: Object.fromEntries(Object.entries(connections).map(([identity, connection]) => [identity, {
3309
+ segments: connection.segments.map((segment) => ({
3310
+ edges: segment.edges.map((edge) => ({
3311
+ key: edge.key,
3312
+ ref: edge.ref
3313
+ })),
3314
+ start: segment.start,
3315
+ end: segment.end
3316
+ })),
3317
+ stale: connection.stale
3318
+ }])),
3319
+ live
3320
+ };
3321
+ };
3322
+ /**
3323
+ * Sets a capture onto a store: its entities, connections and live cursors
3324
+ * replace any held under the same keys, and nothing else of the store changes.
3325
+ */
3326
+ const restoreRemote = (remote, capture) => ({
3327
+ ...remote,
3328
+ entities: {
3329
+ ...remote.entities,
3330
+ ...Object.fromEntries(Object.entries(capture.entities).map(([key, entry]) => [key, {
3331
+ values: entry.values,
3332
+ present: new Set(entry.present),
3333
+ stale: new Set(entry.stale),
3334
+ unavailable: new Set(entry.unavailable),
3335
+ windows: entry.windows,
3336
+ tombstone: entry.tombstone,
3337
+ updatedAt: entry.updatedAt
3338
+ }]))
3339
+ },
3340
+ connections: {
3341
+ ...remote.connections,
3342
+ ...Object.fromEntries(Object.entries(capture.connections).map(([identity, connection]) => [identity, {
3343
+ segments: connection.segments.map((segment) => ({
3344
+ edges: segment.edges,
3345
+ start: segment.start,
3346
+ end: segment.end
3347
+ })),
3348
+ stale: connection.stale
3349
+ }]))
3350
+ },
3351
+ live: {
3352
+ ...remote.live,
3353
+ ...capture.live
3354
+ }
3355
+ });
3356
+ const encodeCapture = Schema.encodeSync(RemoteCapture);
3357
+ const decodeCapture = Schema.decodeUnknownResult(RemoteCapture);
3358
+ /**
3359
+ * The resume part for a Remote domain, for `SSR.plan`'s `parts`: it sends
3360
+ * what the plan's active Surfaces read from this domain's store, and nothing
3361
+ * else of it. `id` tells two domains in one application apart.
3362
+ */
3363
+ const resumePart = (domain, options = {}) => ({
3364
+ id: options.id ?? "remote",
3365
+ covers: [RemoteRequirements.name, RemoteConnections.name],
3366
+ deferrable: (_key, entry) => typeof entry === "object" && entry !== null && DEFERRABLE in entry,
3367
+ capture: (model, projections) => encodeCapture(captureRemote(domain.store.get(model), projections)),
3368
+ restore: (model, value) => Result.match(decodeCapture(value), {
3369
+ onFailure: (error) => Result.fail(`Remote's state does not decode: ${error.message}`),
3370
+ onSuccess: (capture) => Result.succeed(domain.store.set(model, restoreRemote(domain.store.get(model), capture)))
3371
+ })
3372
+ });
3373
+ //#endregion
3015
3374
  //#region src/index.ts
3016
3375
  /**
3017
3376
  * `foldkit-remote` — normalized application-facing server state.
@@ -3061,6 +3420,22 @@ const visibleStoreOf = (entities, optimistic) => {
3061
3420
  }
3062
3421
  return visible;
3063
3422
  };
3423
+ /**
3424
+ * `Failed` naming a field the server settled without a value, when one is
3425
+ * among what `relation` reads of the entity; otherwise nothing, and the store
3426
+ * merely lacks the value. The reason stays with the server, so a view shows
3427
+ * the field it cannot have, not why.
3428
+ */
3429
+ const unavailableFailure = (store, key, relation) => {
3430
+ const field = unavailableOf(store, key, relation);
3431
+ return field === void 0 ? void 0 : {
3432
+ _tag: "Failed",
3433
+ error: {
3434
+ _tag: "Unavailable",
3435
+ message: `The server answered without ${field}, and will not answer with it: the field is not available to this client.`
3436
+ }
3437
+ };
3438
+ };
3064
3439
  const readResults = /* @__PURE__ */ new WeakMap();
3065
3440
  const memoRead = (snapshot, by, key, compute) => {
3066
3441
  let byScope = readResults.get(snapshot);
@@ -3171,10 +3546,13 @@ const planAsked = (remote, asked, options) => {
3171
3546
  const edges = visibleItems(known, connection.identity, remote.optimistic.overlays, remote.entities);
3172
3547
  items.push(...itemsOf(edges.map((edge) => edge.ref), connection.select));
3173
3548
  }
3174
- const planned = plan(visibleStoreOf(remote.entities, remote.optimistic), [...asked.requirements, ...items], options);
3549
+ const visible = visibleStoreOf(remote.entities, remote.optimistic);
3550
+ const read = [...asked.requirements, ...items];
3551
+ const planned = plan(visible, read, options);
3175
3552
  return {
3176
3553
  requirements: options.force === true ? planned : withoutFailedFields(remote, planned),
3177
- queries
3554
+ queries,
3555
+ expires: options.freshness === void 0 ? null : deadlineOf(visible, read, options.freshness) ?? null
3178
3556
  };
3179
3557
  };
3180
3558
  /**
@@ -3301,7 +3679,7 @@ const readMessage = (requirements, now) => Effect.gen(function* () {
3301
3679
  const client = yield* RemoteClient;
3302
3680
  const at = now();
3303
3681
  const result = yield* Effect.result(client.read({
3304
- version: 3,
3682
+ version: 4,
3305
3683
  requests: requirements
3306
3684
  }));
3307
3685
  return Result.isFailure(result) ? {
@@ -3348,7 +3726,11 @@ const observeEntry = (bound, askedOf, toMessage, options) => {
3348
3726
  dependenciesSchema: Schema.Struct({
3349
3727
  requirements: Schema.Array(ReadRequest),
3350
3728
  queries: Schema.Array(PlannedQuery),
3351
- refresh: Schema.Number
3729
+ refresh: Schema.Number,
3730
+ expires: Schema.NullOr(Schema.Struct({
3731
+ at: Schema.Number,
3732
+ due: Schema.Array(ReadRequest)
3733
+ }))
3352
3734
  }),
3353
3735
  modelToDependencies: (model) => {
3354
3736
  const remote = bound.store.get(model);
@@ -3360,10 +3742,11 @@ const observeEntry = (bound, askedOf, toMessage, options) => {
3360
3742
  identity,
3361
3743
  window,
3362
3744
  select
3363
- }))
3745
+ })),
3746
+ expires: planned.expires
3364
3747
  };
3365
3748
  },
3366
- dependenciesToStream: ({ requirements, queries, refresh }) => Stream.concat(Stream.fromIterable([...requirements.length === 0 ? [] : [toMessage({
3749
+ dependenciesToStream: ({ requirements, queries, refresh, expires }) => Stream.concat(Stream.fromIterable([...requirements.length === 0 ? [] : [toMessage({
3367
3750
  _tag: "ReadStarted",
3368
3751
  requests: requirements,
3369
3752
  refresh
@@ -3373,21 +3756,27 @@ const observeEntry = (bound, askedOf, toMessage, options) => {
3373
3756
  })] : []], ...queries.length === 0 ? [] : [toMessage({
3374
3757
  _tag: "QueryStarted",
3375
3758
  connections: queries.map((query) => query.identity)
3376
- })]]), Stream.mergeAll([...requirements.length === 0 ? [] : [Stream.fromEffect(read(requirements))], ...queries.map((query) => Stream.fromEffect(run(query)))], { concurrency: "unbounded" }))
3759
+ })]]), Stream.merge(Stream.mergeAll([...requirements.length === 0 ? [] : [Stream.fromEffect(read(requirements))], ...queries.map((query) => Stream.fromEffect(run(query)))], { concurrency: "unbounded" }), expires === null ? Stream.empty : Stream.fromEffect(Effect.as(Effect.sleep(Math.max(0, expires.at - now())), toMessage({
3760
+ _tag: "RefreshStarted",
3761
+ requests: expires.due
3762
+ })))))
3377
3763
  };
3378
3764
  };
3379
3765
  /** The live entry: subscribes to `requirementsOf(model)` from the Model's resume cursor. */
3380
3766
  const liveEntry = (bound, requirementsOf, toMessage, options) => ({
3381
3767
  dependenciesSchema: Schema.Struct({
3382
3768
  requirements: Schema.Array(ReadRequest),
3383
- cursor: Schema.Number
3769
+ cursor: Schema.Number,
3770
+ floor: Schema.Number
3384
3771
  }),
3385
3772
  modelToDependencies: (model) => {
3386
3773
  const requirements = requirementsOf(model);
3387
3774
  const stream = liveStreamKey(requirements);
3775
+ const remote = bound.store.get(model);
3388
3776
  return {
3389
3777
  requirements,
3390
- cursor: bound.store.get(model).live[stream]?.cursor ?? 0
3778
+ cursor: remote.live[stream]?.cursor ?? 0,
3779
+ floor: remote.refresh.floor
3391
3780
  };
3392
3781
  },
3393
3782
  dependenciesToStream: ({ requirements, cursor }) => requirements.length === 0 ? Stream.empty : Stream.unwrap(Effect.gen(function* () {
@@ -3440,6 +3829,52 @@ const Remote = {
3440
3829
  /** Whether a Message is one of Remote's, by tag. */
3441
3830
  reduces: isRemoteMessage,
3442
3831
  /**
3832
+ * Remote's Messages as one Schema, for a wrapper variant in the
3833
+ * application's union: `GotRemoteMessage: { message: Remote.Message }`. With
3834
+ * a wrapper, `update` matches the application's union exhaustively and
3835
+ * `Remote.fold` reduces what arrives inside it.
3836
+ */
3837
+ Message: defineMessageUnion(remoteMessageCases),
3838
+ /**
3839
+ * A bound domain under one wrapper variant of the application's union, the
3840
+ * same shape Foldkit gives a Submodel: `toParentMessage` wraps Remote's
3841
+ * Messages, the fold reduces them, and its `fetch`, `mutate`, and
3842
+ * `subscriptions` yield the wrapper. Spreading `Remote.messages` and
3843
+ * narrowing with `Remote.reduces` remains the shorter path for an
3844
+ * application whose `update` only reduces.
3845
+ *
3846
+ * @example
3847
+ * ```ts
3848
+ * const Message = defineMessageUnion({ GotRemoteMessage: { message: Remote.Message } })
3849
+ * const foldData = Remote.fold(Data, message => Message.GotRemoteMessage({ message }))
3850
+ *
3851
+ * const update = (model: Model, message: Message): Return =>
3852
+ * Message.match(message, {
3853
+ * GotRemoteMessage: ({ message }) => foldData(model, message),
3854
+ * })
3855
+ * const subscriptions = Subscription.make<Model, Message, RemoteClient>()(() =>
3856
+ * foldData.subscriptions({ page: Surface.at(ProjectPage, …) }),
3857
+ * )
3858
+ * ```
3859
+ */
3860
+ fold: (domain, toParentMessage) => {
3861
+ const fold = (model, message) => ({ model: domain.reduce(model, message) });
3862
+ return Object.assign(fold, {
3863
+ fetch: (ref) => mapMessage(domain.fetch(ref), toParentMessage),
3864
+ mutate: (...args) => {
3865
+ const started = domain.mutate(...args);
3866
+ return {
3867
+ ...started,
3868
+ command: mapMessage(started.command, toParentMessage)
3869
+ };
3870
+ },
3871
+ subscriptions: (active, options) => markAll(Subscription.lift(brandEntries(domain.subscriptions(active, options)))({
3872
+ toChildModel: (model) => model,
3873
+ toParentMessage
3874
+ }))
3875
+ });
3876
+ },
3877
+ /**
3443
3878
  * A patch of one entity's values, for a mutation's `optimistic` list or an
3444
3879
  * overlay. Takes a `foldkit-entity` Entity or a Remote descriptor alike, so
3445
3880
  * the domain you declared once is the one you patch:
@@ -3473,6 +3908,13 @@ const Remote = {
3473
3908
  */
3474
3909
  at: bindRemote,
3475
3910
  /**
3911
+ * The resume part for this domain, for `foldkit-ssr`'s `SSR.plan({ parts })`:
3912
+ * a server render sends what the plan's active Surfaces read from the store,
3913
+ * each connection with its boundaries and each live cursor with it, and
3914
+ * nothing else. `id` tells two domains in one application apart.
3915
+ */
3916
+ resume: resumePart,
3917
+ /**
3476
3918
  * A Projection node that reads a `RemoteData` value out of the store. The id
3477
3919
  * is supplied by the caller, usually from a Surface's params. The assembled
3478
3920
  * value is decoded against the Selection, so malformed server data surfaces
@@ -3496,7 +3938,7 @@ const Remote = {
3496
3938
  const present = memoRead(store, store, `${key}\u0000${stableStringify(relation)}`, () => {
3497
3939
  if (isTombstone(store, key)) return { _tag: "NotFound" };
3498
3940
  const assembled = assemble(store, key, relation);
3499
- if (assembled === void 0) return void 0;
3941
+ if (assembled === void 0) return unavailableFailure(store, key, relation);
3500
3942
  const decoded = Schema.decodeUnknownResult(selection.schema)(assembled.values);
3501
3943
  return Result.isFailure(decoded) ? {
3502
3944
  _tag: "Failed",
@@ -3564,7 +4006,7 @@ const Remote = {
3564
4006
  if (missing.length === 0) return store;
3565
4007
  yield* Effect.annotateCurrentSpan("requirementCount", missing.length);
3566
4008
  const result = yield* (yield* RemoteClient).read({
3567
- version: 3,
4009
+ version: 4,
3568
4010
  requests: missing
3569
4011
  });
3570
4012
  return writeRead(store, missing, result, at);
@@ -3617,6 +4059,17 @@ const Remote = {
3617
4059
  });
3618
4060
  },
3619
4061
  /**
4062
+ * Forgets everything the server told this Model: values, tombstones,
4063
+ * unavailable fields, connections, live cursors, failures. What is known is
4064
+ * known for a principal, and this is the one boundary for a login, a logout
4065
+ * or a switch of organization: called from `update`, it performs no I/O, and
4066
+ * every active Surface's read and live entries restart, so the screen asks
4067
+ * again as whoever the client now is. A read or stream begun before is
4068
+ * interrupted rather than landing after. A mutation in flight is treated as
4069
+ * applied: its answer writes nothing here.
4070
+ */
4071
+ forget: (bound, model) => bound.store.set(model, forgetRemote(bound.store.get(model))),
4072
+ /**
3620
4073
  * Writes a read result into the store, recording each field's applied window.
3621
4074
  * `requests` are the planned requirements the result answers.
3622
4075
  */
@@ -3633,7 +4086,7 @@ const Remote = {
3633
4086
  query: (request) => client.FoldkitRemoteQuery(request).pipe(Effect.provideContext(context)),
3634
4087
  mutate: (request) => client.FoldkitRemoteMutate(request).pipe(Effect.provideContext(context)),
3635
4088
  live: ({ requirements, after }) => client.FoldkitRemoteLive({
3636
- version: 3,
4089
+ version: 4,
3637
4090
  requirements,
3638
4091
  after
3639
4092
  }).pipe(Stream.map(liveEventOf), Stream.provideContext(context))
@@ -3824,6 +4277,7 @@ const bindDomain = (definition, store) => {
3824
4277
  roots: current
3825
4278
  }).pipe(Effect.delay(options.grace ?? 0)))
3826
4279
  };
4280
+ markAll(entries);
3827
4281
  return entries;
3828
4282
  },
3829
4283
  plan: (model, projection, options) => Remote.plan(bound, model, projection, options),
@@ -3842,7 +4296,7 @@ const bindDomain = (definition, store) => {
3842
4296
  if (requirements.length === 0) return current;
3843
4297
  const at = now();
3844
4298
  const result = yield* client.read({
3845
- version: 3,
4299
+ version: 4,
3846
4300
  requests: requirements
3847
4301
  });
3848
4302
  return reduce(current, {
@@ -3891,8 +4345,9 @@ const bindDomain = (definition, store) => {
3891
4345
  let refreshing = connection.stale;
3892
4346
  const edges = visibleItems(connection, ref.identity, remote.optimistic.overlays, remote.entities);
3893
4347
  for (const edge of edges) {
3894
- const assembled = assemble(visible, entityKey(edge.ref.entity, edge.ref.id), relation);
3895
- if (assembled === void 0) return void 0;
4348
+ const key = entityKey(edge.ref.entity, edge.ref.id);
4349
+ const assembled = assemble(visible, key, relation);
4350
+ if (assembled === void 0) return unavailableFailure(visible, key, relation);
3896
4351
  const decoded = Schema.decodeUnknownResult(select.schema)(assembled.values);
3897
4352
  if (Result.isFailure(decoded)) return {
3898
4353
  _tag: "Failed",
@@ -4048,7 +4503,7 @@ const bindDomain = (definition, store) => {
4048
4503
  };
4049
4504
  case "Failed": return {
4050
4505
  state: state._tag,
4051
- message: state.error._tag === "DecodeError" ? `What the server sent does not decode against the Selection: ${state.error.message}` : `Its request failed: ${state.error.message}. Nothing retries a failed read on its own; Data.refresh asks again.`,
4506
+ message: state.error._tag === "DecodeError" ? `What the server sent does not decode against the Selection: ${state.error.message}` : state.error._tag === "Unavailable" ? `${state.error.message} Select without it, or Data.refresh asks again.` : `Its request failed: ${state.error.message}. Nothing retries a failed read on its own; Data.refresh asks again.`,
4052
4507
  ...withSurfaces
4053
4508
  };
4054
4509
  case "Initial":
@@ -4101,6 +4556,7 @@ const bindDomain = (definition, store) => {
4101
4556
  };
4102
4557
  },
4103
4558
  refresh: (model, target) => Remote.refresh(bound, model, target),
4559
+ forget: (model) => Remote.forget(bound, model),
4104
4560
  overlay: (model, id, optimistic) => bound.store.set(model, updateRemote(bound.store.get(model), {
4105
4561
  _tag: "OverlayShown",
4106
4562
  id,
@@ -4193,6 +4649,6 @@ const pageSize = (window, key) => {
4193
4649
  return size === void 0 ? {} : { [key]: size };
4194
4650
  };
4195
4651
  //#endregion
4196
- export { ConnectionChange, ConnectionChangeSchema, Entity, IDENTITY_SEPARATOR, Live, LiveChange, LiveEdge, LiveRequirement, MAX_FIELDS_PER_REQUEST, MAX_RELATION_DEPTH, Mutate, Mutation, MutationRequest, MutationResult, NormalizedEntity, PageSize, Query, QueryEdge, QueryRequest, QueryResult, QueryRpc, RELATION_ALIAS, REMOTE_CACHE_VERSION, REMOTE_PROTOCOL_VERSION, Read, ReadBatch, ReadBatchResult, ReadRequest, RelationAnnotation, RelationEntityAnnotation, RelationRequest, Remote, RemoteClient, RemoteConnections, RemoteData, RemoteLiveError, RemoteMutationError, RemotePersistence, RemotePolicy, RemoteProtocolError, RemoteQueryError, RemoteReadError, RemoteRequirements, RemoteRpc, Requirement, Selection, WindowSchema, WireBoundary, addLayer, addOverlay, aliasedField, applyConnectionEvent, applyEntityEvent, assemble, beginMutation, beginOptimistic, belongsEncoded, classifyLive, clearStale, coalesceQueries, coalesceReads, coalescedLayer, connectionChangeOf, connectionIdentity, connectionsOf, cursor, edge, emptyConnection, emptyLiveState, emptyMutationState, emptyOptimistic, emptyRefresh, emptyStore, entityKey, entry, failMutation, failureOf, gc, hasField, hasNext, hasPrevious, initialRemoteModel, inspectEntity, inspectRemote, isFieldFailed, isFieldStale, isGapped, isLoading, isLoadingThrough, isQueryLoading, isRefPage, isRemoteMessage, isTombstone, items, liveEventOf, liveHasNext, liveHasPrevious, markStale, matching, merge, missingFields, mutateRemote, mutationStatus, noFailures, pageSchema, plan, pruneOverlays, reachable, readField, reconcileMutation, refParts, refreshIsInFlight, refreshedAt, refsIn, relationAlias, relationOf, relationShape, remoteDataSchema, remoteError, remoteErrorSchema, remoteMessageCases, remoteMessageSchema, remoteModelSchema, remove, removeLayer, removeOverlay, requirementKey, requirementsOf, retentionRootsSchema, schemaOf, segment, setStale, settleFailure, settleSuccess, shouldWake, stableStringify, targetsOf, terminal, tombstone, unknown, updateRemote, visibleItems, visibleStore, windowKey, withRefreshRequested, writeEntities, writeEntity, writeRead };
4652
+ export { ConnectionChange, ConnectionChangeSchema, Entity, IDENTITY_SEPARATOR, Live, LiveChange, LiveEdge, LiveRequirement, MAX_FIELDS_PER_REQUEST, MAX_RELATION_DEPTH, Mutate, Mutation, MutationRequest, MutationResult, NormalizedEntity, PageSize, Query, QueryEdge, QueryRequest, QueryResult, QueryRpc, RELATION_ALIAS, REMOTE_CACHE_VERSION, REMOTE_PROTOCOL_VERSION, Read, ReadBatch, ReadBatchResult, ReadRequest, RelationAnnotation, RelationEntityAnnotation, RelationRequest, Remote, RemoteClient, RemoteConnections, RemoteData, RemoteLiveError, RemoteMutationError, RemotePersistence, RemotePolicy, RemoteProtocolError, RemoteQueryError, RemoteReadError, RemoteRequirements, RemoteRpc, Requirement, Selection, SettledFields, WindowSchema, WireBoundary, addLayer, addOverlay, aliasedField, applyConnectionEvent, applyEntityEvent, assemble, beginMutation, beginOptimistic, belongsEncoded, classifyLive, clearStale, coalesceQueries, coalesceReads, coalescedLayer, connectionChangeOf, connectionIdentity, connectionsOf, cursor, deadlineOf, edge, emptyConnection, emptyLiveState, emptyMutationState, emptyOptimistic, emptyRefresh, emptyStore, entityKey, entry, failMutation, failureOf, forgetRemote, gc, hasField, hasNext, hasPrevious, initialRemoteModel, inspectEntity, inspectRemote, isFieldFailed, isFieldStale, isFieldUnavailable, isGapped, isLoading, isLoadingThrough, isQueryLoading, isRefPage, isRemoteMessage, isTombstone, items, liveEventOf, liveHasNext, liveHasPrevious, markStale, matching, merge, missingFields, mutateRemote, mutationStatus, noFailures, pageSchema, plan, pruneOverlays, reachable, readField, reconcileMutation, refParts, refreshIsInFlight, refreshedAt, refsIn, relationAlias, relationOf, relationShape, remoteDataSchema, remoteError, remoteErrorSchema, remoteMessageCases, remoteMessageSchema, remoteModelSchema, remove, removeLayer, removeOverlay, requirementKey, requirementsOf, retentionRootsSchema, schemaOf, segment, setStale, setUnavailable, settleFailure, settleSuccess, shouldWake, stableStringify, targetsOf, terminal, tombstone, unavailableOf, unknown, updateRemote, visibleItems, visibleStore, windowKey, withRefreshRequested, writeEntities, writeEntity, writeRead };
4197
4653
 
4198
4654
  //# sourceMappingURL=index.mjs.map