foldkit-remote 0.20.0 → 0.22.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 +987 -198
- package/dist/answerGaps.d.ts +16 -0
- package/dist/answerGaps.d.ts.map +1 -0
- package/dist/answerGaps.js +60 -0
- package/dist/answerGaps.js.map +1 -0
- package/dist/client-B055kQuP.d.ts +110 -0
- package/dist/client-B055kQuP.d.ts.map +1 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.js +142 -0
- package/dist/client.js.map +1 -0
- package/dist/coalesce-CLAr92Nk.d.ts +34 -0
- package/dist/coalesce-CLAr92Nk.d.ts.map +1 -0
- package/dist/coalesce.d.ts +2 -0
- package/dist/coalesce.js +131 -0
- package/dist/coalesce.js.map +1 -0
- package/dist/connection-BRyaJy89.d.ts +45 -0
- package/dist/connection-BRyaJy89.d.ts.map +1 -0
- package/dist/connection.d.ts +2 -0
- package/dist/connection.js +170 -0
- package/dist/connection.js.map +1 -0
- package/dist/data.d.ts +7 -0
- package/dist/data.d.ts.map +1 -0
- package/dist/data.js +21 -0
- package/dist/data.js.map +1 -0
- package/dist/diagnostics-DHhVYflV.d.ts +125 -0
- package/dist/diagnostics-DHhVYflV.d.ts.map +1 -0
- package/dist/diagnostics.d.ts +2 -0
- package/dist/diagnostics.js +60 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/entity-CgsG5Uel.d.ts +196 -0
- package/dist/entity-CgsG5Uel.d.ts.map +1 -0
- package/dist/entity-DuDxaa3n.js +279 -0
- package/dist/entity-DuDxaa3n.js.map +1 -0
- package/dist/entity.d.ts +2 -0
- package/dist/entity.js +1 -0
- package/dist/held.d.ts +19 -0
- package/dist/held.d.ts.map +1 -0
- package/dist/held.js +70 -0
- package/dist/held.js.map +1 -0
- package/dist/impact.d.ts +79 -0
- package/dist/impact.d.ts.map +1 -0
- package/dist/impact.js +166 -0
- package/dist/impact.js.map +1 -0
- package/dist/index.d.ts +1542 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2791 -0
- package/dist/index.js.map +1 -0
- package/dist/inspect-DtRQGCRP.d.ts +134 -0
- package/dist/inspect-DtRQGCRP.d.ts.map +1 -0
- package/dist/inspect.d.ts +2 -0
- package/dist/inspect.js +33 -0
- package/dist/inspect.js.map +1 -0
- package/dist/json-DowLl1eB.d.ts +52 -0
- package/dist/json-DowLl1eB.d.ts.map +1 -0
- package/dist/json.d.ts +2 -0
- package/dist/json.js +123 -0
- package/dist/json.js.map +1 -0
- package/dist/law.d.ts +56 -0
- package/dist/law.d.ts.map +1 -0
- package/dist/law.js +201 -0
- package/dist/law.js.map +1 -0
- package/dist/live-BpTzlCnY.d.ts +79 -0
- package/dist/live-BpTzlCnY.d.ts.map +1 -0
- package/dist/live.d.ts +2 -0
- package/dist/live.js +126 -0
- package/dist/live.js.map +1 -0
- package/dist/matching-DF5F9Us8.d.ts +100 -0
- package/dist/matching-DF5F9Us8.d.ts.map +1 -0
- package/dist/matching.d.ts +2 -0
- package/dist/matching.js +172 -0
- package/dist/matching.js.map +1 -0
- package/dist/model-B6zk1NMB.d.ts +710 -0
- package/dist/model-B6zk1NMB.d.ts.map +1 -0
- package/dist/model.d.ts +2 -0
- package/dist/model.js +1308 -0
- package/dist/model.js.map +1 -0
- package/dist/mutation.d.ts +2 -0
- package/dist/mutation.js +612 -0
- package/dist/mutation.js.map +1 -0
- package/dist/optimistic-CECsfNGY.d.ts +523 -0
- package/dist/optimistic-CECsfNGY.d.ts.map +1 -0
- package/dist/optimistic.d.ts +2 -0
- package/dist/optimistic.js +336 -0
- package/dist/optimistic.js.map +1 -0
- package/dist/outbox-DNuc4kZp.d.ts +259 -0
- package/dist/outbox-DNuc4kZp.d.ts.map +1 -0
- package/dist/outbox.d.ts +2 -0
- package/dist/outbox.js +326 -0
- package/dist/outbox.js.map +1 -0
- package/dist/patchValues.d.ts +10 -0
- package/dist/patchValues.d.ts.map +1 -0
- package/dist/patchValues.js +57 -0
- package/dist/patchValues.js.map +1 -0
- package/dist/persistence-CCMnvg7-.d.ts +114 -0
- package/dist/persistence-CCMnvg7-.d.ts.map +1 -0
- package/dist/persistence.d.ts +2 -0
- package/dist/persistence.js +205 -0
- package/dist/persistence.js.map +1 -0
- package/dist/placement-jZdsIpmj.d.ts +50 -0
- package/dist/placement-jZdsIpmj.d.ts.map +1 -0
- package/dist/placement.d.ts +2 -0
- package/dist/placement.js +116 -0
- package/dist/placement.js.map +1 -0
- package/dist/plan-D9cftxC_.d.ts +35 -0
- package/dist/plan-D9cftxC_.d.ts.map +1 -0
- package/dist/plan.d.ts +2 -0
- package/dist/plan.js +100 -0
- package/dist/plan.js.map +1 -0
- package/dist/policy-DsWHmIAq.d.ts +42 -0
- package/dist/policy-DsWHmIAq.d.ts.map +1 -0
- package/dist/policy.d.ts +2 -0
- package/dist/policy.js +32 -0
- package/dist/policy.js.map +1 -0
- package/dist/{port.d.mts → port.d.ts} +3 -2
- package/dist/port.d.ts.map +1 -0
- package/dist/{port.mjs → port.js} +3 -2
- package/dist/port.js.map +1 -0
- package/dist/query-BuIU93xA.d.ts +141 -0
- package/dist/query-BuIU93xA.d.ts.map +1 -0
- package/dist/query.d.ts +2 -0
- package/dist/query.js +0 -0
- package/dist/query.js.map +1 -0
- package/dist/record.d.ts +11 -0
- package/dist/record.d.ts.map +1 -0
- package/dist/record.js +21 -0
- package/dist/record.js.map +1 -0
- package/dist/relation-BLBGOZom.d.ts +59 -0
- package/dist/relation-BLBGOZom.d.ts.map +1 -0
- package/dist/relation.d.ts +2 -0
- package/dist/relation.js +93 -0
- package/dist/relation.js.map +1 -0
- package/dist/remoteData-mK1suYi1.d.ts +148 -0
- package/dist/remoteData-mK1suYi1.d.ts.map +1 -0
- package/dist/remoteData.d.ts +2 -0
- package/dist/remoteData.js +179 -0
- package/dist/remoteData.js.map +1 -0
- package/dist/reporting.d.ts +12 -0
- package/dist/reporting.d.ts.map +1 -0
- package/dist/reporting.js +45 -0
- package/dist/reporting.js.map +1 -0
- package/dist/requirement-D57S2LpH.d.ts +64 -0
- package/dist/requirement-D57S2LpH.d.ts.map +1 -0
- package/dist/requirement.d.ts +2 -0
- package/dist/requirement.js +114 -0
- package/dist/requirement.js.map +1 -0
- package/dist/resume-Ct5R6yli.d.ts +92 -0
- package/dist/resume-Ct5R6yli.d.ts.map +1 -0
- package/dist/resume.d.ts +2 -0
- package/dist/resume.js +222 -0
- package/dist/resume.js.map +1 -0
- package/dist/retain-BfNTvBRb.d.ts +48 -0
- package/dist/retain-BfNTvBRb.d.ts.map +1 -0
- package/dist/retain.d.ts +2 -0
- package/dist/retain.js +107 -0
- package/dist/retain.js.map +1 -0
- package/dist/selection.d.ts +2 -0
- package/dist/selection.js +1 -0
- package/dist/shown.d.ts +20 -0
- package/dist/shown.d.ts.map +1 -0
- package/dist/shown.js +40 -0
- package/dist/shown.js.map +1 -0
- package/dist/sse-C7LhdbiQ.d.ts +17 -0
- package/dist/sse-C7LhdbiQ.d.ts.map +1 -0
- package/dist/sse.d.ts +2 -0
- package/dist/sse.js +127 -0
- package/dist/sse.js.map +1 -0
- package/dist/store-ah2qjcJW.d.ts +100 -0
- package/dist/store-ah2qjcJW.d.ts.map +1 -0
- package/dist/store.d.ts +2 -0
- package/dist/store.js +218 -0
- package/dist/store.js.map +1 -0
- package/dist/testing.d.ts +133 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +0 -0
- package/dist/testing.js.map +1 -0
- package/dist/why-kgp_Wr3z.d.ts +483 -0
- package/dist/why-kgp_Wr3z.d.ts.map +1 -0
- package/dist/why.d.ts +2 -0
- package/dist/why.js +293 -0
- package/dist/why.js.map +1 -0
- package/dist/whyReasons.d.ts +42 -0
- package/dist/whyReasons.d.ts.map +1 -0
- package/dist/whyReasons.js +211 -0
- package/dist/whyReasons.js.map +1 -0
- package/dist/wire-DthPcTM6.d.ts +787 -0
- package/dist/wire-DthPcTM6.d.ts.map +1 -0
- package/dist/wire.d.ts +2 -0
- package/dist/wire.js +329 -0
- package/dist/wire.js.map +1 -0
- package/package.json +19 -14
- package/dist/index-BEvVAL76.d.mts +0 -4097
- package/dist/index-BEvVAL76.d.mts.map +0 -1
- package/dist/index.d.mts +0 -2
- package/dist/index.mjs +0 -1
- package/dist/port.d.mts.map +0 -1
- package/dist/port.mjs.map +0 -1
- package/dist/src-BDqn7Ocr.mjs +0 -6344
- package/dist/src-BDqn7Ocr.mjs.map +0 -1
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { s as RemoteModel } from "./model-B6zk1NMB.js";
|
|
2
|
+
import { Result, Schema } from "effect";
|
|
3
|
+
import { Focus, Metadata } from "foldkit-surface";
|
|
4
|
+
/**
|
|
5
|
+
* Marks a Subscription entry Remote built as one that may start late under
|
|
6
|
+
* `foldkit-ssr`'s deferred boot (its decision 10): a read with resumed data
|
|
7
|
+
* plans nothing, a live subscription resumes from the cursor the envelope
|
|
8
|
+
* carries, and retention running late collects late. Non-enumerable, so the
|
|
9
|
+
* runtime's iteration of an entry never sees it.
|
|
10
|
+
*/
|
|
11
|
+
declare const DEFERRABLE: unique symbol;
|
|
12
|
+
/** Marks every entry of a record Remote built, lifted or not. */
|
|
13
|
+
declare const markAll: <Entries extends object>(entries: Entries) => Entries;
|
|
14
|
+
/** The captured part of a Remote store, as JSON. */
|
|
15
|
+
declare const RemoteCapture: Schema.Struct<{
|
|
16
|
+
readonly entities: Schema.$Record<Schema.String, Schema.Struct<{
|
|
17
|
+
readonly values: Schema.$Record<Schema.String, Schema.Unknown>;
|
|
18
|
+
readonly present: Schema.$Array<Schema.String>;
|
|
19
|
+
readonly stale: Schema.$Array<Schema.String>;
|
|
20
|
+
readonly unavailable: Schema.$Array<Schema.String>;
|
|
21
|
+
readonly windows: Schema.$Record<Schema.String, Schema.String>;
|
|
22
|
+
readonly tombstone: Schema.Boolean;
|
|
23
|
+
readonly updatedAt: Schema.Number;
|
|
24
|
+
}>>;
|
|
25
|
+
readonly connections: Schema.$Record<Schema.String, Schema.Struct<{
|
|
26
|
+
readonly segments: Schema.$Array<Schema.Struct<{
|
|
27
|
+
readonly edges: Schema.$Array<Schema.Struct<{
|
|
28
|
+
readonly key: Schema.String;
|
|
29
|
+
readonly ref: Schema.Struct<{
|
|
30
|
+
readonly entity: Schema.String;
|
|
31
|
+
readonly id: Schema.String;
|
|
32
|
+
}>;
|
|
33
|
+
}>>;
|
|
34
|
+
readonly start: Schema.Union<readonly [Schema.TaggedStruct<"Terminal", {}>, Schema.TaggedStruct<"Cursor", {
|
|
35
|
+
readonly cursor: Schema.String;
|
|
36
|
+
}>, Schema.TaggedStruct<"Unknown", {}>]>;
|
|
37
|
+
readonly end: Schema.Union<readonly [Schema.TaggedStruct<"Terminal", {}>, Schema.TaggedStruct<"Cursor", {
|
|
38
|
+
readonly cursor: Schema.String;
|
|
39
|
+
}>, Schema.TaggedStruct<"Unknown", {}>]>;
|
|
40
|
+
}>>;
|
|
41
|
+
readonly stale: Schema.Boolean;
|
|
42
|
+
}>>;
|
|
43
|
+
readonly live: Schema.$Record<Schema.String, Schema.Struct<{
|
|
44
|
+
readonly cursor: Schema.Number;
|
|
45
|
+
readonly boundary: Schema.$Record<Schema.String, Schema.Struct<{
|
|
46
|
+
readonly before: Schema.Number;
|
|
47
|
+
readonly after: Schema.Number;
|
|
48
|
+
}>>;
|
|
49
|
+
}>>;
|
|
50
|
+
}>;
|
|
51
|
+
type RemoteCapture = typeof RemoteCapture.Type;
|
|
52
|
+
/**
|
|
53
|
+
* Captures what some projections read from a store: the fields each
|
|
54
|
+
* requirement names, through its relations; each connection whole, and the
|
|
55
|
+
* fields its selection reads of each item; and each live cursor whose
|
|
56
|
+
* entities were all captured with the fields it follows.
|
|
57
|
+
*/
|
|
58
|
+
declare const captureRemote: (remote: RemoteModel, projections: ReadonlyArray<{
|
|
59
|
+
readonly metadata: Metadata;
|
|
60
|
+
}>) => RemoteCapture;
|
|
61
|
+
/**
|
|
62
|
+
* Sets a capture onto a store: its entities, connections and live cursors
|
|
63
|
+
* replace any held under the same keys, and nothing else of the store changes.
|
|
64
|
+
*/
|
|
65
|
+
declare const restoreRemote: <Store extends RemoteModel>(remote: Store, capture: RemoteCapture) => Store;
|
|
66
|
+
/**
|
|
67
|
+
* A resume part, in the shape `foldkit-ssr`'s `SSR.plan` takes in `parts`.
|
|
68
|
+
* Declared here structurally, so neither package depends on the other.
|
|
69
|
+
*/
|
|
70
|
+
interface RemoteResumePart<AppModel> {
|
|
71
|
+
readonly id: string;
|
|
72
|
+
/** The metadata key names whose reads this part resumes. */
|
|
73
|
+
readonly covers: ReadonlyArray<string>;
|
|
74
|
+
readonly capture: (model: AppModel, projections: ReadonlyArray<{
|
|
75
|
+
readonly metadata: Metadata;
|
|
76
|
+
}>) => unknown;
|
|
77
|
+
readonly restore: (model: AppModel, value: unknown) => Result.Result<AppModel, string>;
|
|
78
|
+
/** Whether a Subscription entry is one of this domain's, which may start late. */
|
|
79
|
+
readonly deferrable: (key: string, entry: unknown) => boolean;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The resume part for a Remote domain, for `SSR.plan`'s `parts`: it sends
|
|
83
|
+
* what the plan's active Surfaces read from this domain's store, and nothing
|
|
84
|
+
* else of it. `id` tells two domains in one application apart.
|
|
85
|
+
*/
|
|
86
|
+
declare const resumePart: <AppModel, Store extends RemoteModel>(domain: {
|
|
87
|
+
readonly store: Focus<AppModel, Store>;
|
|
88
|
+
}, options?: {
|
|
89
|
+
readonly id?: string | undefined;
|
|
90
|
+
}) => RemoteResumePart<AppModel>;
|
|
91
|
+
export { markAll as a, captureRemote as i, RemoteCapture as n, restoreRemote as o, RemoteResumePart as r, resumePart as s, DEFERRABLE as t };
|
|
92
|
+
//# sourceMappingURL=resume-Ct5R6yli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resume-Ct5R6yli.d.ts","names":[],"sources":["../src/resume.ts"],"mappings":";;;;;;;;;;cAwEa;;cAGA,UAAW,wBAAsB,SAAW,YAAU;;cAQtD,eAAa,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAKd,uBAAuB,cAAc;;;;;;;cAoCpC,gBAAa,QAChB,aAAW,aACN;WAAyB,UAAU;OAC/C;;;;;cA+FU,gBAAiB,cAAc,aAAW,QAC7C,OAAK,SACJ,kBACR;;;;;UA0Cc,iBAAiB;WACvB;;WAEA,QAAQ;WACR,UACP,OAAO,UACP,aAAa;aAAyB,UAAU;;WAEzC,UAAU,OAAO,UAAU,mBAAmB,OAAO,OAAO;;WAE5D,aAAa,aAAa;;;;;;;cAWxB,aAAc,UAAU,cAAc,aAAW;WACzC,OAAO,MAAM,UAAU;GAAQ;WAC9B;MACnB,iBAAiB"}
|
package/dist/resume.d.ts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as markAll, i as captureRemote, n as RemoteCapture, o as restoreRemote, r as RemoteResumePart, s as resumePart, t as DEFERRABLE } from "./resume-Ct5R6yli.js";
|
|
2
|
+
export { DEFERRABLE, RemoteCapture, RemoteResumePart, captureRemote, markAll, restoreRemote, resumePart };
|
package/dist/resume.js
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { own, put } from "./record.js";
|
|
2
|
+
import { RemoteConnections, RemoteRequirements } from "./requirement.js";
|
|
3
|
+
import { targetsOf } from "./relation.js";
|
|
4
|
+
import { entityKey, readField } from "./store.js";
|
|
5
|
+
import { Result, Schema } from "effect";
|
|
6
|
+
// src/resume.ts
|
|
7
|
+
/**
|
|
8
|
+
* What of Remote's state crosses from a server render to the browser.
|
|
9
|
+
*
|
|
10
|
+
* A page rendered a moment ago sends exactly what its active Surfaces read:
|
|
11
|
+
* for each requirement the fields it names, following relations through the
|
|
12
|
+
* refs the store holds, and each connection whole, with its boundaries, so
|
|
13
|
+
* "load more" knows where the server stopped. A live subscription's cursor
|
|
14
|
+
* crosses with the entities it covers, so a subscription started in the
|
|
15
|
+
* browser resumes where the server's left off.
|
|
16
|
+
*
|
|
17
|
+
* This is not a `Snapshot`, which is built for a cache that survived a reload
|
|
18
|
+
* and deliberately keeps no cursors. Nothing else of `RemoteModel` crosses:
|
|
19
|
+
* loading marks, failures, the mutation ledger, optimistic layers, gaps and
|
|
20
|
+
* retention start from their initial values in the browser.
|
|
21
|
+
*/
|
|
22
|
+
const CapturedEntry = Schema.Struct({
|
|
23
|
+
values: Schema.Record(Schema.String, Schema.Unknown),
|
|
24
|
+
present: Schema.Array(Schema.String),
|
|
25
|
+
stale: Schema.Array(Schema.String),
|
|
26
|
+
unavailable: Schema.Array(Schema.String),
|
|
27
|
+
windows: Schema.Record(Schema.String, Schema.String),
|
|
28
|
+
tombstone: Schema.Boolean,
|
|
29
|
+
updatedAt: Schema.Number
|
|
30
|
+
});
|
|
31
|
+
const CapturedBoundary = Schema.Union([
|
|
32
|
+
Schema.TaggedStruct("Terminal", {}),
|
|
33
|
+
Schema.TaggedStruct("Cursor", { cursor: Schema.String }),
|
|
34
|
+
Schema.TaggedStruct("Unknown", {})
|
|
35
|
+
]);
|
|
36
|
+
const CapturedConnection = Schema.Struct({
|
|
37
|
+
segments: Schema.Array(Schema.Struct({
|
|
38
|
+
edges: Schema.Array(Schema.Struct({
|
|
39
|
+
key: Schema.String,
|
|
40
|
+
ref: Schema.Struct({
|
|
41
|
+
entity: Schema.String,
|
|
42
|
+
id: Schema.String
|
|
43
|
+
})
|
|
44
|
+
})),
|
|
45
|
+
start: CapturedBoundary,
|
|
46
|
+
end: CapturedBoundary
|
|
47
|
+
})),
|
|
48
|
+
stale: Schema.Boolean
|
|
49
|
+
});
|
|
50
|
+
const CapturedLive = Schema.Struct({
|
|
51
|
+
cursor: Schema.Number,
|
|
52
|
+
boundary: Schema.Record(Schema.String, Schema.Struct({
|
|
53
|
+
before: Schema.Number,
|
|
54
|
+
after: Schema.Number
|
|
55
|
+
}))
|
|
56
|
+
});
|
|
57
|
+
/**
|
|
58
|
+
* Marks a Subscription entry Remote built as one that may start late under
|
|
59
|
+
* `foldkit-ssr`'s deferred boot (its decision 10): a read with resumed data
|
|
60
|
+
* plans nothing, a live subscription resumes from the cursor the envelope
|
|
61
|
+
* carries, and retention running late collects late. Non-enumerable, so the
|
|
62
|
+
* runtime's iteration of an entry never sees it.
|
|
63
|
+
*/
|
|
64
|
+
const DEFERRABLE = Symbol.for("foldkit-remote/deferrable");
|
|
65
|
+
/** Marks every entry of a record Remote built, lifted or not. */
|
|
66
|
+
const markAll = (entries) => {
|
|
67
|
+
for (const entry of Object.values(entries)) Object.defineProperty(entry, DEFERRABLE, {
|
|
68
|
+
value: true,
|
|
69
|
+
enumerable: false
|
|
70
|
+
});
|
|
71
|
+
return entries;
|
|
72
|
+
};
|
|
73
|
+
/** The captured part of a Remote store, as JSON. */
|
|
74
|
+
const RemoteCapture = Schema.Struct({
|
|
75
|
+
entities: Schema.Record(Schema.String, CapturedEntry),
|
|
76
|
+
connections: Schema.Record(Schema.String, CapturedConnection),
|
|
77
|
+
live: Schema.Record(Schema.String, CapturedLive)
|
|
78
|
+
});
|
|
79
|
+
/** What some projections read, from their metadata. */
|
|
80
|
+
const askedOf = (projections) => ({
|
|
81
|
+
requirements: projections.flatMap((projection) => RemoteRequirements.get(projection.metadata)),
|
|
82
|
+
connections: projections.flatMap((projection) => RemoteConnections.get(projection.metadata))
|
|
83
|
+
});
|
|
84
|
+
/** Whether every `entity:id:fields` part of a live stream key was captured with those fields. */
|
|
85
|
+
const liveCovered = (stream, entries) => stream.split("|").every((part) => {
|
|
86
|
+
const split = part.lastIndexOf(":");
|
|
87
|
+
const entry = entries.get(part.slice(0, split));
|
|
88
|
+
const fields = part.slice(split + 1).split(",").filter((field) => field !== "");
|
|
89
|
+
return entry !== void 0 && fields.every((field) => entry.present.has(field));
|
|
90
|
+
});
|
|
91
|
+
/**
|
|
92
|
+
* Captures what some projections read from a store: the fields each
|
|
93
|
+
* requirement names, through its relations; each connection whole, and the
|
|
94
|
+
* fields its selection reads of each item; and each live cursor whose
|
|
95
|
+
* entities were all captured with the fields it follows.
|
|
96
|
+
*/
|
|
97
|
+
const captureRemote = (remote, projections) => {
|
|
98
|
+
const store = remote.entities;
|
|
99
|
+
const entries = /* @__PURE__ */ new Map();
|
|
100
|
+
const walked = /* @__PURE__ */ new Map();
|
|
101
|
+
const walk = (entity, id, requirement) => {
|
|
102
|
+
const key = entityKey(entity, id);
|
|
103
|
+
const seen = walked.get(key) ?? /* @__PURE__ */ new Set();
|
|
104
|
+
if (seen.has(requirement)) return;
|
|
105
|
+
seen.add(requirement);
|
|
106
|
+
walked.set(key, seen);
|
|
107
|
+
const stored = store[key];
|
|
108
|
+
if (stored === void 0) return;
|
|
109
|
+
const entry = entries.get(key) ?? {
|
|
110
|
+
values: {},
|
|
111
|
+
present: /* @__PURE__ */ new Set(),
|
|
112
|
+
stale: /* @__PURE__ */ new Set(),
|
|
113
|
+
unavailable: /* @__PURE__ */ new Set(),
|
|
114
|
+
windows: {},
|
|
115
|
+
tombstone: stored.tombstone,
|
|
116
|
+
updatedAt: stored.updatedAt
|
|
117
|
+
};
|
|
118
|
+
entries.set(key, entry);
|
|
119
|
+
for (const field of requirement.fields) {
|
|
120
|
+
if (stored.unavailable.has(field)) entry.unavailable.add(field);
|
|
121
|
+
if (!stored.present.has(field)) continue;
|
|
122
|
+
put(entry.values, field, stored.values[field]);
|
|
123
|
+
entry.present.add(field);
|
|
124
|
+
if (stored.stale.has(field)) entry.stale.add(field);
|
|
125
|
+
const window = own(stored.windows, field);
|
|
126
|
+
if (window !== void 0) put(entry.windows, field, window);
|
|
127
|
+
const nested = requirement.relations?.[field];
|
|
128
|
+
if (nested === void 0) continue;
|
|
129
|
+
const value = readField(store, key, field);
|
|
130
|
+
if (value._tag === "None") continue;
|
|
131
|
+
for (const ref of targetsOf(value.value, nested)) walk(ref.entity, ref.id, nested);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
const asked = askedOf(projections);
|
|
135
|
+
for (const requirement of asked.requirements) walk(requirement.entity, requirement.id, requirement);
|
|
136
|
+
const connections = {};
|
|
137
|
+
for (const { identity, select } of asked.connections) {
|
|
138
|
+
const connection = remote.connections[identity];
|
|
139
|
+
if (connection === void 0) continue;
|
|
140
|
+
connections[identity] = connection;
|
|
141
|
+
for (const segment of connection.segments) for (const edge of segment.edges) walk(edge.ref.entity, edge.ref.id, select);
|
|
142
|
+
}
|
|
143
|
+
const live = {};
|
|
144
|
+
for (const [stream, state] of Object.entries(remote.live)) if (liveCovered(stream, entries)) live[stream] = state;
|
|
145
|
+
return {
|
|
146
|
+
entities: Object.fromEntries([...entries].map(([key, entry]) => [key, {
|
|
147
|
+
values: entry.values,
|
|
148
|
+
present: [...entry.present],
|
|
149
|
+
stale: [...entry.stale],
|
|
150
|
+
unavailable: [...entry.unavailable],
|
|
151
|
+
windows: entry.windows,
|
|
152
|
+
tombstone: entry.tombstone,
|
|
153
|
+
updatedAt: entry.updatedAt
|
|
154
|
+
}])),
|
|
155
|
+
connections: Object.fromEntries(Object.entries(connections).map(([identity, connection]) => [identity, {
|
|
156
|
+
segments: connection.segments.map((segment) => ({
|
|
157
|
+
edges: segment.edges.map((edge) => ({
|
|
158
|
+
key: edge.key,
|
|
159
|
+
ref: edge.ref
|
|
160
|
+
})),
|
|
161
|
+
start: segment.start,
|
|
162
|
+
end: segment.end
|
|
163
|
+
})),
|
|
164
|
+
stale: connection.stale
|
|
165
|
+
}])),
|
|
166
|
+
live
|
|
167
|
+
};
|
|
168
|
+
};
|
|
169
|
+
/**
|
|
170
|
+
* Sets a capture onto a store: its entities, connections and live cursors
|
|
171
|
+
* replace any held under the same keys, and nothing else of the store changes.
|
|
172
|
+
*/
|
|
173
|
+
const restoreRemote = (remote, capture) => ({
|
|
174
|
+
...remote,
|
|
175
|
+
entities: {
|
|
176
|
+
...remote.entities,
|
|
177
|
+
...Object.fromEntries(Object.entries(capture.entities).map(([key, entry]) => [key, {
|
|
178
|
+
values: entry.values,
|
|
179
|
+
present: new Set(entry.present),
|
|
180
|
+
stale: new Set(entry.stale),
|
|
181
|
+
unavailable: new Set(entry.unavailable),
|
|
182
|
+
windows: entry.windows,
|
|
183
|
+
tombstone: entry.tombstone,
|
|
184
|
+
updatedAt: entry.updatedAt
|
|
185
|
+
}]))
|
|
186
|
+
},
|
|
187
|
+
connections: {
|
|
188
|
+
...remote.connections,
|
|
189
|
+
...Object.fromEntries(Object.entries(capture.connections).map(([identity, connection]) => [identity, {
|
|
190
|
+
segments: connection.segments.map((segment) => ({
|
|
191
|
+
edges: segment.edges,
|
|
192
|
+
start: segment.start,
|
|
193
|
+
end: segment.end
|
|
194
|
+
})),
|
|
195
|
+
stale: connection.stale
|
|
196
|
+
}]))
|
|
197
|
+
},
|
|
198
|
+
live: {
|
|
199
|
+
...remote.live,
|
|
200
|
+
...capture.live
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
const encodeCapture = Schema.encodeSync(RemoteCapture);
|
|
204
|
+
const decodeCapture = Schema.decodeUnknownResult(RemoteCapture);
|
|
205
|
+
/**
|
|
206
|
+
* The resume part for a Remote domain, for `SSR.plan`'s `parts`: it sends
|
|
207
|
+
* what the plan's active Surfaces read from this domain's store, and nothing
|
|
208
|
+
* else of it. `id` tells two domains in one application apart.
|
|
209
|
+
*/
|
|
210
|
+
const resumePart = (domain, options = {}) => ({
|
|
211
|
+
id: options.id ?? "remote",
|
|
212
|
+
covers: [RemoteRequirements.name, RemoteConnections.name],
|
|
213
|
+
deferrable: (_key, entry) => typeof entry === "object" && entry !== null && DEFERRABLE in entry,
|
|
214
|
+
capture: (model, projections) => encodeCapture(captureRemote(domain.store.get(model), projections)),
|
|
215
|
+
restore: (model, value) => Result.match(decodeCapture(value), {
|
|
216
|
+
onFailure: (error) => Result.fail(`Remote's state does not decode: ${error.message}`),
|
|
217
|
+
onSuccess: (capture) => Result.succeed(domain.store.set(model, restoreRemote(domain.store.get(model), capture)))
|
|
218
|
+
})
|
|
219
|
+
});
|
|
220
|
+
export { DEFERRABLE, RemoteCapture, captureRemote, markAll, restoreRemote, resumePart };
|
|
221
|
+
|
|
222
|
+
//# sourceMappingURL=resume.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resume.js","names":[],"sources":["../src/resume.ts"],"sourcesContent":["/**\n * What of Remote's state crosses from a server render to the browser.\n *\n * A page rendered a moment ago sends exactly what its active Surfaces read:\n * for each requirement the fields it names, following relations through the\n * refs the store holds, and each connection whole, with its boundaries, so\n * \"load more\" knows where the server stopped. A live subscription's cursor\n * crosses with the entities it covers, so a subscription started in the\n * browser resumes where the server's left off.\n *\n * This is not a `Snapshot`, which is built for a cache that survived a reload\n * and deliberately keeps no cursors. Nothing else of `RemoteModel` crosses:\n * loading marks, failures, the mutation ledger, optimistic layers, gaps and\n * retention start from their initial values in the browser.\n */\nimport { Result, Schema } from 'effect'\nimport type { Focus, Metadata } from 'foldkit-surface'\nimport type { Boundary, Connection, Segment } from './connection.js'\nimport type { LiveState } from './live.js'\nimport type { RemoteModel } from './model.js'\nimport { own, put } from './record.js'\nimport { targetsOf } from './relation.js'\nimport { RemoteConnections, RemoteRequirements, type RelationRequirement } from './requirement.js'\nimport { entityKey, readField, type EntityEntry } from './store.js'\n\nconst CapturedEntry = Schema.Struct({\n values: Schema.Record(Schema.String, Schema.Unknown),\n present: Schema.Array(Schema.String),\n stale: Schema.Array(Schema.String),\n unavailable: Schema.Array(Schema.String),\n windows: Schema.Record(Schema.String, Schema.String),\n tombstone: Schema.Boolean,\n updatedAt: Schema.Number,\n})\n\nconst CapturedBoundary = Schema.Union([\n Schema.TaggedStruct('Terminal', {}),\n Schema.TaggedStruct('Cursor', { cursor: Schema.String }),\n Schema.TaggedStruct('Unknown', {}),\n])\n\nconst CapturedConnection = Schema.Struct({\n segments: Schema.Array(\n Schema.Struct({\n edges: Schema.Array(\n Schema.Struct({\n key: Schema.String,\n ref: Schema.Struct({ entity: Schema.String, id: Schema.String }),\n }),\n ),\n start: CapturedBoundary,\n end: CapturedBoundary,\n }),\n ),\n stale: Schema.Boolean,\n})\n\nconst CapturedLive = Schema.Struct({\n cursor: Schema.Number,\n boundary: Schema.Record(\n Schema.String,\n Schema.Struct({ before: Schema.Number, after: Schema.Number }),\n ),\n})\n\n/**\n * Marks a Subscription entry Remote built as one that may start late under\n * `foldkit-ssr`'s deferred boot (its decision 10): a read with resumed data\n * plans nothing, a live subscription resumes from the cursor the envelope\n * carries, and retention running late collects late. Non-enumerable, so the\n * runtime's iteration of an entry never sees it.\n */\nexport const DEFERRABLE: unique symbol = Symbol.for('foldkit-remote/deferrable')\n\n/** Marks every entry of a record Remote built, lifted or not. */\nexport const markAll = <Entries extends object>(entries: Entries): Entries => {\n for (const entry of Object.values(entries)) {\n Object.defineProperty(entry, DEFERRABLE, { value: true, enumerable: false })\n }\n return entries\n}\n\n/** The captured part of a Remote store, as JSON. */\nexport const RemoteCapture = Schema.Struct({\n entities: Schema.Record(Schema.String, CapturedEntry),\n connections: Schema.Record(Schema.String, CapturedConnection),\n live: Schema.Record(Schema.String, CapturedLive),\n})\nexport type RemoteCapture = typeof RemoteCapture.Type\n\ntype MutableEntry = {\n values: Record<string, unknown>\n present: Set<string>\n stale: Set<string>\n unavailable: Set<string>\n windows: Record<string, string>\n tombstone: boolean\n updatedAt: number\n}\n\n/** What some projections read, from their metadata. */\nconst askedOf = (projections: ReadonlyArray<{ readonly metadata: Metadata }>) => ({\n requirements: projections.flatMap(projection => RemoteRequirements.get(projection.metadata)),\n connections: projections.flatMap(projection => RemoteConnections.get(projection.metadata)),\n})\n\n/** Whether every `entity:id:fields` part of a live stream key was captured with those fields. */\nconst liveCovered = (stream: string, entries: ReadonlyMap<string, MutableEntry>): boolean =>\n stream.split('|').every(part => {\n const split = part.lastIndexOf(':')\n const entry = entries.get(part.slice(0, split))\n const fields = part\n .slice(split + 1)\n .split(',')\n .filter(field => field !== '')\n return entry !== undefined && fields.every(field => entry.present.has(field))\n })\n\n/**\n * Captures what some projections read from a store: the fields each\n * requirement names, through its relations; each connection whole, and the\n * fields its selection reads of each item; and each live cursor whose\n * entities were all captured with the fields it follows.\n */\nexport const captureRemote = (\n remote: RemoteModel,\n projections: ReadonlyArray<{ readonly metadata: Metadata }>,\n): RemoteCapture => {\n const store = remote.entities\n const entries = new Map<string, MutableEntry>()\n const walked = new Map<string, Set<RelationRequirement>>()\n\n const walk = (entity: string, id: string, requirement: RelationRequirement): void => {\n const key = entityKey(entity, id)\n const seen = walked.get(key) ?? new Set()\n if (seen.has(requirement)) return\n seen.add(requirement)\n walked.set(key, seen)\n const stored: EntityEntry | undefined = store[key]\n if (stored === undefined) return\n const entry = entries.get(key) ?? {\n values: {},\n present: new Set<string>(),\n stale: new Set<string>(),\n unavailable: new Set<string>(),\n windows: {},\n tombstone: stored.tombstone,\n updatedAt: stored.updatedAt,\n }\n entries.set(key, entry)\n for (const field of requirement.fields) {\n // A field the server settled without a value is knowledge too: the\n // browser would otherwise ask for it once more.\n if (stored.unavailable.has(field)) entry.unavailable.add(field)\n if (!stored.present.has(field)) continue\n put(entry.values, field, stored.values[field])\n entry.present.add(field)\n if (stored.stale.has(field)) entry.stale.add(field)\n const window = own(stored.windows, field)\n if (window !== undefined) put(entry.windows, field, window)\n const nested = requirement.relations?.[field]\n if (nested === undefined) continue\n const value = readField(store, key, field)\n if (value._tag === 'None') continue\n for (const ref of targetsOf(value.value, nested)) walk(ref.entity, ref.id, nested)\n }\n }\n\n const asked = askedOf(projections)\n for (const requirement of asked.requirements) {\n walk(requirement.entity, requirement.id, requirement)\n }\n const connections: Record<string, Connection> = {}\n for (const { identity, select } of asked.connections) {\n const connection = remote.connections[identity]\n if (connection === undefined) continue\n connections[identity] = connection\n for (const segment of connection.segments) {\n for (const edge of segment.edges) walk(edge.ref.entity, edge.ref.id, select)\n }\n }\n const live: Record<string, LiveState> = {}\n for (const [stream, state] of Object.entries(remote.live)) {\n if (liveCovered(stream, entries)) live[stream] = state\n }\n\n return {\n entities: Object.fromEntries(\n [...entries].map(([key, entry]) => [\n key,\n {\n values: entry.values,\n present: [...entry.present],\n stale: [...entry.stale],\n unavailable: [...entry.unavailable],\n windows: entry.windows,\n tombstone: entry.tombstone,\n updatedAt: entry.updatedAt,\n },\n ]),\n ),\n connections: Object.fromEntries(\n Object.entries(connections).map(([identity, connection]) => [\n identity,\n {\n segments: connection.segments.map(segment => ({\n edges: segment.edges.map(edge => ({ key: edge.key, ref: edge.ref })),\n start: segment.start,\n end: segment.end,\n })),\n stale: connection.stale,\n },\n ]),\n ),\n live,\n }\n}\n\n/**\n * Sets a capture onto a store: its entities, connections and live cursors\n * replace any held under the same keys, and nothing else of the store changes.\n */\nexport const restoreRemote = <Store extends RemoteModel>(\n remote: Store,\n capture: RemoteCapture,\n): Store => ({\n ...remote,\n entities: {\n ...remote.entities,\n ...Object.fromEntries(\n Object.entries(capture.entities).map(([key, entry]) => [\n key,\n {\n values: entry.values,\n present: new Set(entry.present),\n stale: new Set(entry.stale),\n unavailable: new Set(entry.unavailable),\n windows: entry.windows,\n tombstone: entry.tombstone,\n updatedAt: entry.updatedAt,\n } satisfies EntityEntry,\n ]),\n ),\n },\n connections: {\n ...remote.connections,\n ...Object.fromEntries(\n Object.entries(capture.connections).map(([identity, connection]) => [\n identity,\n {\n segments: connection.segments.map((segment): Segment => ({\n edges: segment.edges,\n start: segment.start as Boundary,\n end: segment.end as Boundary,\n })),\n stale: connection.stale,\n } satisfies Connection,\n ]),\n ),\n },\n live: { ...remote.live, ...capture.live },\n})\n\n/**\n * A resume part, in the shape `foldkit-ssr`'s `SSR.plan` takes in `parts`.\n * Declared here structurally, so neither package depends on the other.\n */\nexport interface RemoteResumePart<AppModel> {\n readonly id: string\n /** The metadata key names whose reads this part resumes. */\n readonly covers: ReadonlyArray<string>\n readonly capture: (\n model: AppModel,\n projections: ReadonlyArray<{ readonly metadata: Metadata }>,\n ) => unknown\n readonly restore: (model: AppModel, value: unknown) => Result.Result<AppModel, string>\n /** Whether a Subscription entry is one of this domain's, which may start late. */\n readonly deferrable: (key: string, entry: unknown) => boolean\n}\n\nconst encodeCapture = Schema.encodeSync(RemoteCapture)\nconst decodeCapture = Schema.decodeUnknownResult(RemoteCapture)\n\n/**\n * The resume part for a Remote domain, for `SSR.plan`'s `parts`: it sends\n * what the plan's active Surfaces read from this domain's store, and nothing\n * else of it. `id` tells two domains in one application apart.\n */\nexport const resumePart = <AppModel, Store extends RemoteModel>(\n domain: { readonly store: Focus<AppModel, Store> },\n options: { readonly id?: string | undefined } = {},\n): RemoteResumePart<AppModel> => ({\n id: options.id ?? 'remote',\n covers: [RemoteRequirements.name, RemoteConnections.name],\n deferrable: (_key, entry) => typeof entry === 'object' && entry !== null && DEFERRABLE in entry,\n capture: (model, projections) =>\n encodeCapture(captureRemote(domain.store.get(model), projections)),\n restore: (model, value) =>\n Result.match(decodeCapture(value), {\n onFailure: error => Result.fail(`Remote's state does not decode: ${error.message}`),\n onSuccess: capture =>\n Result.succeed(domain.store.set(model, restoreRemote(domain.store.get(model), capture))),\n }),\n})\n"],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { n as Connection } from "./connection-BRyaJy89.js";
|
|
2
|
+
import { n as EntityKey, r as EntityStore } from "./store-ah2qjcJW.js";
|
|
3
|
+
import { D as MutationState, o as OptimisticState } from "./optimistic-CECsfNGY.js";
|
|
4
|
+
import { a as Requirement, n as RelationRequirement } from "./requirement-D57S2LpH.js";
|
|
5
|
+
/** A connection to keep: its identity, and what a page of it selects of each item, if a projection reads it. */
|
|
6
|
+
interface ConnectionRoot {
|
|
7
|
+
readonly identity: string;
|
|
8
|
+
readonly select?: RelationRequirement | undefined;
|
|
9
|
+
}
|
|
10
|
+
interface RetentionRoots {
|
|
11
|
+
readonly requirements: ReadonlyArray<Requirement>;
|
|
12
|
+
/** Connections (`QueryRef.identity`) to keep, with their edges' targets and what `select` reaches through them. */
|
|
13
|
+
readonly connections: ReadonlyArray<ConnectionRoot>;
|
|
14
|
+
}
|
|
15
|
+
interface Retained {
|
|
16
|
+
readonly entities: EntityStore;
|
|
17
|
+
readonly connections: Readonly<Record<string, Connection>>;
|
|
18
|
+
readonly optimistic: OptimisticState;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The entity keys the roots reach: each root entity, the targets its retained
|
|
22
|
+
* fields refer to, and recursively the targets a nested relation selects.
|
|
23
|
+
*/
|
|
24
|
+
declare const reachable: (store: EntityStore, roots: RetentionRoots, connections: Readonly<Record<string, Connection>>, optimistic: OptimisticState, pending: ReadonlySet<string>) => ReadonlySet<EntityKey>;
|
|
25
|
+
/**
|
|
26
|
+
* How many lists, and how many entity reads, stay held after the Surface
|
|
27
|
+
* showing them goes inactive. Going back to one shows what it held while the
|
|
28
|
+
* list refetches, rather than reading as if nothing had ever loaded.
|
|
29
|
+
*/
|
|
30
|
+
declare const departedLimit: {
|
|
31
|
+
readonly connections: 16;
|
|
32
|
+
readonly requirements: 256;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* The roots retention keeps: the active ones, after the most recently
|
|
36
|
+
* departed ones `previous` held (oldest first), up to `departedLimit`.
|
|
37
|
+
*/
|
|
38
|
+
declare const recentRoots: (previous: RetentionRoots, active: RetentionRoots) => RetentionRoots;
|
|
39
|
+
/**
|
|
40
|
+
* Drops every entity the roots do not reach, every connection they do not
|
|
41
|
+
* name, and every settled overlay on a dropped connection, keeping whatever a
|
|
42
|
+
* pending request's layer or overlays touch. Pure.
|
|
43
|
+
*/
|
|
44
|
+
declare const gc: (state: Retained & {
|
|
45
|
+
readonly mutations: MutationState;
|
|
46
|
+
}, roots: RetentionRoots) => Retained;
|
|
47
|
+
export { gc as a, departedLimit as i, Retained as n, reachable as o, RetentionRoots as r, recentRoots as s, ConnectionRoot as t };
|
|
48
|
+
//# sourceMappingURL=retain-BfNTvBRb.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retain-BfNTvBRb.d.ts","names":[],"sources":["../src/retain.ts"],"mappings":";;;;;UAciB;WACN;WACA,SAAS;;UAGH;WACN,cAAc,cAAc;;WAE5B,aAAa,cAAc;;UAGrB;WACN,UAAU;WACV,aAAa,SAAS,eAAe;WACrC,YAAY;;;;;;cAOV,YAAS,OACb,aAAW,OACX,gBAAc,aACR,SAAS,eAAe,cAAY,YACrC,iBAAe,SAClB,wBACR,YAAY;;;;;;cA2DF;WAAkB;WAAiB;;;;;;cAqBnC,cAAW,UAAc,gBAAc,QAAU,mBAAiB;;;;;;cA4BlE,KAAE,OACN;WAAsB,WAAW;GAAe,OAChD,mBACN"}
|
package/dist/retain.d.ts
ADDED
package/dist/retain.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { refsIn } from "./relation.js";
|
|
2
|
+
import { entityKey, readField } from "./store.js";
|
|
3
|
+
// src/retain.ts
|
|
4
|
+
/**
|
|
5
|
+
* The entity keys the roots reach: each root entity, the targets its retained
|
|
6
|
+
* fields refer to, and recursively the targets a nested relation selects.
|
|
7
|
+
*/
|
|
8
|
+
const reachable = (store, roots, connections, optimistic, pending) => {
|
|
9
|
+
const kept = /* @__PURE__ */ new Set();
|
|
10
|
+
const rootConnections = new Set(roots.connections.map((root) => root.identity));
|
|
11
|
+
const walked = /* @__PURE__ */ new Map();
|
|
12
|
+
const visit = (key, requirement) => {
|
|
13
|
+
kept.add(key);
|
|
14
|
+
const seen = walked.get(key) ?? /* @__PURE__ */ new Set();
|
|
15
|
+
if (seen.has(requirement)) return;
|
|
16
|
+
seen.add(requirement);
|
|
17
|
+
walked.set(key, seen);
|
|
18
|
+
for (const field of requirement.fields) {
|
|
19
|
+
const value = readField(store, key, field);
|
|
20
|
+
if (value._tag === "None") continue;
|
|
21
|
+
const relation = requirement.relations?.[field];
|
|
22
|
+
for (const ref of refsIn(value.value)) {
|
|
23
|
+
const target = entityKey(ref.entity, ref.id);
|
|
24
|
+
if (relation === void 0 || ref.entity !== relation.entity) {
|
|
25
|
+
kept.add(target);
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
visit(target, relation);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
for (const root of roots.requirements) visit(entityKey(root.entity, root.id), root);
|
|
33
|
+
for (const { identity, select } of roots.connections) for (const segment of connections[identity]?.segments ?? []) for (const edge of segment.edges) {
|
|
34
|
+
const target = entityKey(edge.ref.entity, edge.ref.id);
|
|
35
|
+
if (select === void 0 || edge.ref.entity !== select.entity) kept.add(target);
|
|
36
|
+
else visit(target, select);
|
|
37
|
+
}
|
|
38
|
+
for (const layer of optimistic.layers) {
|
|
39
|
+
if (!pending.has(layer.id)) continue;
|
|
40
|
+
for (const patch of layer.patches) kept.add(entityKey(patch.entity, patch.id));
|
|
41
|
+
for (const gone of layer.deletes) kept.add(entityKey(gone.entity, gone.id));
|
|
42
|
+
}
|
|
43
|
+
for (const overlay of optimistic.overlays) {
|
|
44
|
+
if (!pending.has(overlay.id) && !rootConnections.has(overlay.connection)) continue;
|
|
45
|
+
for (const edge of overlay.edges) kept.add(entityKey(edge.ref.entity, edge.ref.id));
|
|
46
|
+
}
|
|
47
|
+
return kept;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* How many lists, and how many entity reads, stay held after the Surface
|
|
51
|
+
* showing them goes inactive. Going back to one shows what it held while the
|
|
52
|
+
* list refetches, rather than reading as if nothing had ever loaded.
|
|
53
|
+
*/
|
|
54
|
+
const departedLimit = {
|
|
55
|
+
connections: 16,
|
|
56
|
+
requirements: 256
|
|
57
|
+
};
|
|
58
|
+
const requirementKey = (requirement) => JSON.stringify(requirement);
|
|
59
|
+
const connectionKey = (root) => JSON.stringify(root);
|
|
60
|
+
/** The last `limit` of `previous` that `active` does not hold, then all of `active`. */
|
|
61
|
+
const departedThen = (previous, active, key, limit) => {
|
|
62
|
+
const current = new Set(active.map(key));
|
|
63
|
+
const departed = previous.filter((value) => !current.has(key(value)));
|
|
64
|
+
return [...departed.slice(Math.max(0, departed.length - limit)), ...active];
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* The roots retention keeps: the active ones, after the most recently
|
|
68
|
+
* departed ones `previous` held (oldest first), up to `departedLimit`.
|
|
69
|
+
*/
|
|
70
|
+
const recentRoots = (previous, active) => {
|
|
71
|
+
const next = {
|
|
72
|
+
requirements: departedThen(previous.requirements, active.requirements, requirementKey, departedLimit.requirements),
|
|
73
|
+
connections: departedThen(previous.connections, active.connections, connectionKey, departedLimit.connections)
|
|
74
|
+
};
|
|
75
|
+
const same = (a, b, key) => a.length === b.length && a.every((value, i) => key(value) === key(b[i]));
|
|
76
|
+
return same(previous.requirements, next.requirements, requirementKey) && same(previous.connections, next.connections, connectionKey) ? previous : next;
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Drops every entity the roots do not reach, every connection they do not
|
|
80
|
+
* name, and every settled overlay on a dropped connection, keeping whatever a
|
|
81
|
+
* pending request's layer or overlays touch. Pure.
|
|
82
|
+
*/
|
|
83
|
+
const gc = (state, roots) => {
|
|
84
|
+
const pending = state.mutations.pending;
|
|
85
|
+
const kept = reachable(state.entities, roots, state.connections, state.optimistic, pending);
|
|
86
|
+
const entities = keptOf(state.entities, (key) => kept.has(key));
|
|
87
|
+
const keptConnections = /* @__PURE__ */ new Set([...roots.connections.map((root) => root.identity), ...state.optimistic.overlays.filter((overlay) => pending.has(overlay.id)).map((overlay) => overlay.connection)]);
|
|
88
|
+
const connections = keptOf(state.connections, (identity) => keptConnections.has(identity));
|
|
89
|
+
const overlays = state.optimistic.overlays.filter((overlay) => pending.has(overlay.id) || keptConnections.has(overlay.connection));
|
|
90
|
+
return {
|
|
91
|
+
entities,
|
|
92
|
+
connections,
|
|
93
|
+
optimistic: overlays.length === state.optimistic.overlays.length ? state.optimistic : {
|
|
94
|
+
layers: state.optimistic.layers,
|
|
95
|
+
overlays
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
/** The entries whose key is kept; `record` itself when that is all of them. */
|
|
100
|
+
const keptOf = (record, keep) => {
|
|
101
|
+
const entries = Object.entries(record);
|
|
102
|
+
const kept = entries.filter(([key]) => keep(key));
|
|
103
|
+
return kept.length === entries.length ? record : Object.fromEntries(kept);
|
|
104
|
+
};
|
|
105
|
+
export { departedLimit, gc, reachable, recentRoots };
|
|
106
|
+
|
|
107
|
+
//# sourceMappingURL=retain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retain.js","names":[],"sources":["../src/retain.ts"],"sourcesContent":["/**\n * Cache lifetime. Retention roots are the requirements (and connections) the\n * application's active Surfaces observe; `gc` keeps what they reach through\n * the store's refs plus every pending optimistic change, and drops the rest.\n * Roots live outside the Model, so GC arrives as a Message.\n */\nimport type { RelationRequirement, Requirement } from './requirement.js'\nimport { refsIn } from './relation.js'\nimport { entityKey, readField, type EntityKey, type EntityStore } from './store.js'\nimport type { Connection } from './connection.js'\nimport type { OptimisticState } from './optimistic.js'\nimport type { MutationState } from './mutation.js'\n\n/** A connection to keep: its identity, and what a page of it selects of each item, if a projection reads it. */\nexport interface ConnectionRoot {\n readonly identity: string\n readonly select?: RelationRequirement | undefined\n}\n\nexport interface RetentionRoots {\n readonly requirements: ReadonlyArray<Requirement>\n /** Connections (`QueryRef.identity`) to keep, with their edges' targets and what `select` reaches through them. */\n readonly connections: ReadonlyArray<ConnectionRoot>\n}\n\nexport interface Retained {\n readonly entities: EntityStore\n readonly connections: Readonly<Record<string, Connection>>\n readonly optimistic: OptimisticState\n}\n\n/**\n * The entity keys the roots reach: each root entity, the targets its retained\n * fields refer to, and recursively the targets a nested relation selects.\n */\nexport const reachable = (\n store: EntityStore,\n roots: RetentionRoots,\n connections: Readonly<Record<string, Connection>>,\n optimistic: OptimisticState,\n pending: ReadonlySet<string>,\n): ReadonlySet<EntityKey> => {\n const kept = new Set<EntityKey>()\n const rootConnections = new Set(roots.connections.map(root => root.identity))\n // A target is walked once per relation spec that reaches it: two specs may\n // select different nested relations of the same entity, and a spec tree is\n // finite, so this terminates on cyclic data too.\n const walked = new Map<EntityKey, Set<Omit<Requirement, 'id'>>>()\n const visit = (key: EntityKey, requirement: Omit<Requirement, 'id'>): void => {\n kept.add(key)\n const seen = walked.get(key) ?? new Set()\n if (seen.has(requirement)) return\n seen.add(requirement)\n walked.set(key, seen)\n for (const field of requirement.fields) {\n const value = readField(store, key, field)\n if (value._tag === 'None') continue\n const relation = requirement.relations?.[field]\n for (const ref of refsIn(value.value)) {\n const target = entityKey(ref.entity, ref.id)\n if (relation === undefined || ref.entity !== relation.entity) {\n kept.add(target)\n continue\n }\n visit(target, relation)\n }\n }\n }\n for (const root of roots.requirements) visit(entityKey(root.entity, root.id), root)\n // A connection root keeps its edges' targets, and what a page's `select`\n // reaches through them (an item's owner, say), so the page stays readable.\n for (const { identity, select } of roots.connections) {\n for (const segment of connections[identity]?.segments ?? []) {\n for (const edge of segment.edges) {\n const target = entityKey(edge.ref.entity, edge.ref.id)\n if (select === undefined || edge.ref.entity !== select.entity) kept.add(target)\n else visit(target, select)\n }\n }\n }\n // A pending request's changes are kept whole; a settled overlay (a live or\n // confirmed insert) is kept only with a retained connection, below.\n for (const layer of optimistic.layers) {\n if (!pending.has(layer.id)) continue\n for (const patch of layer.patches) kept.add(entityKey(patch.entity, patch.id))\n // A failure brings the row back, as it was.\n for (const gone of layer.deletes) kept.add(entityKey(gone.entity, gone.id))\n }\n for (const overlay of optimistic.overlays) {\n if (!pending.has(overlay.id) && !rootConnections.has(overlay.connection)) continue\n for (const edge of overlay.edges) kept.add(entityKey(edge.ref.entity, edge.ref.id))\n }\n return kept\n}\n\n/**\n * How many lists, and how many entity reads, stay held after the Surface\n * showing them goes inactive. Going back to one shows what it held while the\n * list refetches, rather than reading as if nothing had ever loaded.\n */\nexport const departedLimit = { connections: 16, requirements: 256 } as const\n\nconst requirementKey = (requirement: Requirement): string => JSON.stringify(requirement)\nconst connectionKey = (root: ConnectionRoot): string => JSON.stringify(root)\n\n/** The last `limit` of `previous` that `active` does not hold, then all of `active`. */\nconst departedThen = <A>(\n previous: ReadonlyArray<A>,\n active: ReadonlyArray<A>,\n key: (value: A) => string,\n limit: number,\n): ReadonlyArray<A> => {\n const current = new Set(active.map(key))\n const departed = previous.filter(value => !current.has(key(value)))\n return [...departed.slice(Math.max(0, departed.length - limit)), ...active]\n}\n\n/**\n * The roots retention keeps: the active ones, after the most recently\n * departed ones `previous` held (oldest first), up to `departedLimit`.\n */\nexport const recentRoots = (previous: RetentionRoots, active: RetentionRoots): RetentionRoots => {\n const next = {\n requirements: departedThen(\n previous.requirements,\n active.requirements,\n requirementKey,\n departedLimit.requirements,\n ),\n connections: departedThen(\n previous.connections,\n active.connections,\n connectionKey,\n departedLimit.connections,\n ),\n }\n const same = <A>(a: ReadonlyArray<A>, b: ReadonlyArray<A>, key: (value: A) => string) =>\n a.length === b.length && a.every((value, i) => key(value) === key(b[i]!))\n return same(previous.requirements, next.requirements, requirementKey) &&\n same(previous.connections, next.connections, connectionKey)\n ? previous\n : next\n}\n\n/**\n * Drops every entity the roots do not reach, every connection they do not\n * name, and every settled overlay on a dropped connection, keeping whatever a\n * pending request's layer or overlays touch. Pure.\n */\nexport const gc = (\n state: Retained & { readonly mutations: MutationState },\n roots: RetentionRoots,\n): Retained => {\n const pending = state.mutations.pending\n const kept = reachable(state.entities, roots, state.connections, state.optimistic, pending)\n const entities = keptOf(state.entities, key => kept.has(key))\n const keptConnections = new Set([\n ...roots.connections.map(root => root.identity),\n ...state.optimistic.overlays\n .filter(overlay => pending.has(overlay.id))\n .map(overlay => overlay.connection),\n ])\n const connections = keptOf(state.connections, identity => keptConnections.has(identity))\n const overlays = state.optimistic.overlays.filter(\n overlay => pending.has(overlay.id) || keptConnections.has(overlay.connection),\n )\n const optimistic: OptimisticState =\n overlays.length === state.optimistic.overlays.length\n ? state.optimistic\n : { layers: state.optimistic.layers, overlays }\n return { entities, connections, optimistic }\n}\n\n/** The entries whose key is kept; `record` itself when that is all of them. */\nconst keptOf = <T>(\n record: Readonly<Record<string, T>>,\n keep: (key: string) => boolean,\n): Readonly<Record<string, T>> => {\n const entries = Object.entries(record)\n const kept = entries.filter(([key]) => keep(key))\n return kept.length === entries.length ? record : Object.fromEntries(kept)\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { _ as relationOf, d as Selection, f as SelectionOf, g as pageSchema, h as assemble, m as SelectionValueOf, p as SelectionValue, u as Page, v as unavailableOf } from "./entity-CgsG5Uel.js";
|
|
2
|
+
export { Page, Selection, SelectionOf, SelectionValue, SelectionValueOf, assemble, pageSchema, relationOf, unavailableOf };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { n as Selection, r as assemble, i as pageSchema, a as relationOf, o as unavailableOf } from "./entity-DuDxaa3n.js";
|
package/dist/shown.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { a as OptimisticOperation, j as PendingPatch } from "./optimistic-CECsfNGY.js";
|
|
2
|
+
import { a as Requirement, n as RelationRequirement, t as ConnectionRequirement } from "./requirement-D57S2LpH.js";
|
|
3
|
+
/** A patch is the one optimistic operation with no tag. */
|
|
4
|
+
export declare const isPatch: (operation: OptimisticOperation) => operation is PendingPatch;
|
|
5
|
+
/** Every field a pending patch shows: those it sets, adds to, and computes. */
|
|
6
|
+
export declare const patchedFields: (patch: PendingPatch) => ReadonlyArray<string>;
|
|
7
|
+
/**
|
|
8
|
+
* The union, per entity, of every slice the reads ask: each requirement's,
|
|
9
|
+
* each connection's item `select`, and every relation's target slice under
|
|
10
|
+
* its own entity too, so a row the answer names is read the way any read
|
|
11
|
+
* reaches it. Every field the write's own patches show is added: the
|
|
12
|
+
* prediction is released when the answer lands, so a field it moved that no
|
|
13
|
+
* Surface reads now (a count on a page left behind) would otherwise read the
|
|
14
|
+
* value from before the write (#265).
|
|
15
|
+
*/
|
|
16
|
+
export declare const shownOf: (asked: {
|
|
17
|
+
readonly requirements: ReadonlyArray<Requirement>;
|
|
18
|
+
readonly connections: ReadonlyArray<ConnectionRequirement>;
|
|
19
|
+
}, predicted: ReadonlyArray<OptimisticOperation>) => ReadonlyArray<RelationRequirement>;
|
|
20
|
+
//# sourceMappingURL=shown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shown.d.ts","names":[],"sources":["../src/shown.ts"],"mappings":";;;qBAWa,UAAO,WAAe,wBAAsB,aAAa;;qBAIzD,gBAAa,OAAW,iBAAe;;;;;;;;;;qBAevC,UAAO;WAEP,cAAc,cAAc;WAC5B,aAAa,cAAc;GACrC,WACU,cAAc,yBACxB,cAAc"}
|
package/dist/shown.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Requirement } from "./requirement.js";
|
|
2
|
+
// src/shown.ts
|
|
3
|
+
/** A patch is the one optimistic operation with no tag. */
|
|
4
|
+
const isPatch = (operation) => !("_tag" in operation);
|
|
5
|
+
/** Every field a pending patch shows: those it sets, adds to, and computes. */
|
|
6
|
+
const patchedFields = (patch) => [
|
|
7
|
+
...Object.keys(patch.values),
|
|
8
|
+
...Object.keys(patch.adds ?? {}),
|
|
9
|
+
...Object.keys(patch.computes ?? {})
|
|
10
|
+
];
|
|
11
|
+
/**
|
|
12
|
+
* The union, per entity, of every slice the reads ask: each requirement's,
|
|
13
|
+
* each connection's item `select`, and every relation's target slice under
|
|
14
|
+
* its own entity too, so a row the answer names is read the way any read
|
|
15
|
+
* reaches it. Every field the write's own patches show is added: the
|
|
16
|
+
* prediction is released when the answer lands, so a field it moved that no
|
|
17
|
+
* Surface reads now (a count on a page left behind) would otherwise read the
|
|
18
|
+
* value from before the write (#265).
|
|
19
|
+
*/
|
|
20
|
+
const shownOf = (asked, predicted) => {
|
|
21
|
+
const byEntity = /* @__PURE__ */ new Map();
|
|
22
|
+
const add = (slice) => {
|
|
23
|
+
const current = byEntity.get(slice.entity) ?? {
|
|
24
|
+
entity: slice.entity,
|
|
25
|
+
fields: []
|
|
26
|
+
};
|
|
27
|
+
byEntity.set(slice.entity, Requirement.mergeRelation(current, slice));
|
|
28
|
+
for (const relation of Object.values(slice.relations ?? {})) add(relation);
|
|
29
|
+
};
|
|
30
|
+
for (const requirement of asked.requirements) add(requirement);
|
|
31
|
+
for (const connection of asked.connections) add(connection.select);
|
|
32
|
+
for (const patch of predicted.filter(isPatch)) add({
|
|
33
|
+
entity: patch.entity,
|
|
34
|
+
fields: [...patchedFields(patch)]
|
|
35
|
+
});
|
|
36
|
+
return [...byEntity.values()];
|
|
37
|
+
};
|
|
38
|
+
export { isPatch, patchedFields, shownOf };
|
|
39
|
+
|
|
40
|
+
//# sourceMappingURL=shown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shown.js","names":[],"sources":["../src/shown.ts"],"sourcesContent":["/**\n * What a client shows of each entity, sent with a mutation so its answer\n * carries those fields (#245). Per entity rather than per row: the server\n * knows which rows a write changed, the client what it reads of a row of\n * each kind.\n */\nimport type { PendingPatch } from './mutation.js'\nimport type { OptimisticOperation } from './optimistic.js'\nimport { Requirement, type ConnectionRequirement, type RelationRequirement } from './requirement.js'\n\n/** A patch is the one optimistic operation with no tag. */\nexport const isPatch = (operation: OptimisticOperation): operation is PendingPatch =>\n !('_tag' in operation)\n\n/** Every field a pending patch shows: those it sets, adds to, and computes. */\nexport const patchedFields = (patch: PendingPatch): ReadonlyArray<string> => [\n ...Object.keys(patch.values),\n ...Object.keys(patch.adds ?? {}),\n ...Object.keys(patch.computes ?? {}),\n]\n\n/**\n * The union, per entity, of every slice the reads ask: each requirement's,\n * each connection's item `select`, and every relation's target slice under\n * its own entity too, so a row the answer names is read the way any read\n * reaches it. Every field the write's own patches show is added: the\n * prediction is released when the answer lands, so a field it moved that no\n * Surface reads now (a count on a page left behind) would otherwise read the\n * value from before the write (#265).\n */\nexport const shownOf = (\n asked: {\n readonly requirements: ReadonlyArray<Requirement>\n readonly connections: ReadonlyArray<ConnectionRequirement>\n },\n predicted: ReadonlyArray<OptimisticOperation>,\n): ReadonlyArray<RelationRequirement> => {\n const byEntity = new Map<string, RelationRequirement>()\n const add = (slice: RelationRequirement): void => {\n // A merge keeps a slice's own keys only, so a requirement's `id` and `live` stay behind.\n const current = byEntity.get(slice.entity) ?? { entity: slice.entity, fields: [] }\n byEntity.set(slice.entity, Requirement.mergeRelation(current, slice))\n for (const relation of Object.values(slice.relations ?? {})) add(relation)\n }\n for (const requirement of asked.requirements) add(requirement)\n for (const connection of asked.connections) add(connection.select)\n for (const patch of predicted.filter(isPatch))\n add({ entity: patch.entity, fields: [...patchedFields(patch)] })\n return [...byEntity.values()]\n}\n"],"mappings":";;;AAWA,MAAa,WAAW,cACtB,EAAE,UAAU;;AAGd,MAAa,iBAAiB,UAA+C;CAC3E,GAAG,OAAO,KAAK,MAAM,MAAM;CAC3B,GAAG,OAAO,KAAK,MAAM,QAAQ,CAAC,CAAC;CAC/B,GAAG,OAAO,KAAK,MAAM,YAAY,CAAC,CAAC;AACrC;;;;;;;;;;AAWA,MAAa,WACX,OAIA,cACuC;CACvC,MAAM,2BAAW,IAAI,IAAiC;CACtD,MAAM,OAAO,UAAqC;EAEhD,MAAM,UAAU,SAAS,IAAI,MAAM,MAAM,KAAK;GAAE,QAAQ,MAAM;GAAQ,QAAQ,CAAC;EAAE;EACjF,SAAS,IAAI,MAAM,QAAQ,YAAY,cAAc,SAAS,KAAK,CAAC;EACpE,KAAK,MAAM,YAAY,OAAO,OAAO,MAAM,aAAa,CAAC,CAAC,GAAG,IAAI,QAAQ;CAC3E;CACA,KAAK,MAAM,eAAe,MAAM,cAAc,IAAI,WAAW;CAC7D,KAAK,MAAM,cAAc,MAAM,aAAa,IAAI,WAAW,MAAM;CACjE,KAAK,MAAM,SAAS,UAAU,OAAO,OAAO,GAC1C,IAAI;EAAE,QAAQ,MAAM;EAAQ,QAAQ,CAAC,GAAG,cAAc,KAAK,CAAC;CAAE,CAAC;CACjE,OAAO,CAAC,GAAG,SAAS,OAAO,CAAC;AAC9B"}
|