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.
Files changed (198) hide show
  1. package/README.md +987 -198
  2. package/dist/answerGaps.d.ts +16 -0
  3. package/dist/answerGaps.d.ts.map +1 -0
  4. package/dist/answerGaps.js +60 -0
  5. package/dist/answerGaps.js.map +1 -0
  6. package/dist/client-B055kQuP.d.ts +110 -0
  7. package/dist/client-B055kQuP.d.ts.map +1 -0
  8. package/dist/client.d.ts +2 -0
  9. package/dist/client.js +142 -0
  10. package/dist/client.js.map +1 -0
  11. package/dist/coalesce-CLAr92Nk.d.ts +34 -0
  12. package/dist/coalesce-CLAr92Nk.d.ts.map +1 -0
  13. package/dist/coalesce.d.ts +2 -0
  14. package/dist/coalesce.js +131 -0
  15. package/dist/coalesce.js.map +1 -0
  16. package/dist/connection-BRyaJy89.d.ts +45 -0
  17. package/dist/connection-BRyaJy89.d.ts.map +1 -0
  18. package/dist/connection.d.ts +2 -0
  19. package/dist/connection.js +170 -0
  20. package/dist/connection.js.map +1 -0
  21. package/dist/data.d.ts +7 -0
  22. package/dist/data.d.ts.map +1 -0
  23. package/dist/data.js +21 -0
  24. package/dist/data.js.map +1 -0
  25. package/dist/diagnostics-DHhVYflV.d.ts +125 -0
  26. package/dist/diagnostics-DHhVYflV.d.ts.map +1 -0
  27. package/dist/diagnostics.d.ts +2 -0
  28. package/dist/diagnostics.js +60 -0
  29. package/dist/diagnostics.js.map +1 -0
  30. package/dist/entity-CgsG5Uel.d.ts +196 -0
  31. package/dist/entity-CgsG5Uel.d.ts.map +1 -0
  32. package/dist/entity-DuDxaa3n.js +279 -0
  33. package/dist/entity-DuDxaa3n.js.map +1 -0
  34. package/dist/entity.d.ts +2 -0
  35. package/dist/entity.js +1 -0
  36. package/dist/held.d.ts +19 -0
  37. package/dist/held.d.ts.map +1 -0
  38. package/dist/held.js +70 -0
  39. package/dist/held.js.map +1 -0
  40. package/dist/impact.d.ts +79 -0
  41. package/dist/impact.d.ts.map +1 -0
  42. package/dist/impact.js +166 -0
  43. package/dist/impact.js.map +1 -0
  44. package/dist/index.d.ts +1542 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.js +2791 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/inspect-DtRQGCRP.d.ts +134 -0
  49. package/dist/inspect-DtRQGCRP.d.ts.map +1 -0
  50. package/dist/inspect.d.ts +2 -0
  51. package/dist/inspect.js +33 -0
  52. package/dist/inspect.js.map +1 -0
  53. package/dist/json-DowLl1eB.d.ts +52 -0
  54. package/dist/json-DowLl1eB.d.ts.map +1 -0
  55. package/dist/json.d.ts +2 -0
  56. package/dist/json.js +123 -0
  57. package/dist/json.js.map +1 -0
  58. package/dist/law.d.ts +56 -0
  59. package/dist/law.d.ts.map +1 -0
  60. package/dist/law.js +201 -0
  61. package/dist/law.js.map +1 -0
  62. package/dist/live-BpTzlCnY.d.ts +79 -0
  63. package/dist/live-BpTzlCnY.d.ts.map +1 -0
  64. package/dist/live.d.ts +2 -0
  65. package/dist/live.js +126 -0
  66. package/dist/live.js.map +1 -0
  67. package/dist/matching-DF5F9Us8.d.ts +100 -0
  68. package/dist/matching-DF5F9Us8.d.ts.map +1 -0
  69. package/dist/matching.d.ts +2 -0
  70. package/dist/matching.js +172 -0
  71. package/dist/matching.js.map +1 -0
  72. package/dist/model-B6zk1NMB.d.ts +710 -0
  73. package/dist/model-B6zk1NMB.d.ts.map +1 -0
  74. package/dist/model.d.ts +2 -0
  75. package/dist/model.js +1308 -0
  76. package/dist/model.js.map +1 -0
  77. package/dist/mutation.d.ts +2 -0
  78. package/dist/mutation.js +612 -0
  79. package/dist/mutation.js.map +1 -0
  80. package/dist/optimistic-CECsfNGY.d.ts +523 -0
  81. package/dist/optimistic-CECsfNGY.d.ts.map +1 -0
  82. package/dist/optimistic.d.ts +2 -0
  83. package/dist/optimistic.js +336 -0
  84. package/dist/optimistic.js.map +1 -0
  85. package/dist/outbox-DNuc4kZp.d.ts +259 -0
  86. package/dist/outbox-DNuc4kZp.d.ts.map +1 -0
  87. package/dist/outbox.d.ts +2 -0
  88. package/dist/outbox.js +326 -0
  89. package/dist/outbox.js.map +1 -0
  90. package/dist/patchValues.d.ts +10 -0
  91. package/dist/patchValues.d.ts.map +1 -0
  92. package/dist/patchValues.js +57 -0
  93. package/dist/patchValues.js.map +1 -0
  94. package/dist/persistence-CCMnvg7-.d.ts +114 -0
  95. package/dist/persistence-CCMnvg7-.d.ts.map +1 -0
  96. package/dist/persistence.d.ts +2 -0
  97. package/dist/persistence.js +205 -0
  98. package/dist/persistence.js.map +1 -0
  99. package/dist/placement-jZdsIpmj.d.ts +50 -0
  100. package/dist/placement-jZdsIpmj.d.ts.map +1 -0
  101. package/dist/placement.d.ts +2 -0
  102. package/dist/placement.js +116 -0
  103. package/dist/placement.js.map +1 -0
  104. package/dist/plan-D9cftxC_.d.ts +35 -0
  105. package/dist/plan-D9cftxC_.d.ts.map +1 -0
  106. package/dist/plan.d.ts +2 -0
  107. package/dist/plan.js +100 -0
  108. package/dist/plan.js.map +1 -0
  109. package/dist/policy-DsWHmIAq.d.ts +42 -0
  110. package/dist/policy-DsWHmIAq.d.ts.map +1 -0
  111. package/dist/policy.d.ts +2 -0
  112. package/dist/policy.js +32 -0
  113. package/dist/policy.js.map +1 -0
  114. package/dist/{port.d.mts → port.d.ts} +3 -2
  115. package/dist/port.d.ts.map +1 -0
  116. package/dist/{port.mjs → port.js} +3 -2
  117. package/dist/port.js.map +1 -0
  118. package/dist/query-BuIU93xA.d.ts +141 -0
  119. package/dist/query-BuIU93xA.d.ts.map +1 -0
  120. package/dist/query.d.ts +2 -0
  121. package/dist/query.js +0 -0
  122. package/dist/query.js.map +1 -0
  123. package/dist/record.d.ts +11 -0
  124. package/dist/record.d.ts.map +1 -0
  125. package/dist/record.js +21 -0
  126. package/dist/record.js.map +1 -0
  127. package/dist/relation-BLBGOZom.d.ts +59 -0
  128. package/dist/relation-BLBGOZom.d.ts.map +1 -0
  129. package/dist/relation.d.ts +2 -0
  130. package/dist/relation.js +93 -0
  131. package/dist/relation.js.map +1 -0
  132. package/dist/remoteData-mK1suYi1.d.ts +148 -0
  133. package/dist/remoteData-mK1suYi1.d.ts.map +1 -0
  134. package/dist/remoteData.d.ts +2 -0
  135. package/dist/remoteData.js +179 -0
  136. package/dist/remoteData.js.map +1 -0
  137. package/dist/reporting.d.ts +12 -0
  138. package/dist/reporting.d.ts.map +1 -0
  139. package/dist/reporting.js +45 -0
  140. package/dist/reporting.js.map +1 -0
  141. package/dist/requirement-D57S2LpH.d.ts +64 -0
  142. package/dist/requirement-D57S2LpH.d.ts.map +1 -0
  143. package/dist/requirement.d.ts +2 -0
  144. package/dist/requirement.js +114 -0
  145. package/dist/requirement.js.map +1 -0
  146. package/dist/resume-Ct5R6yli.d.ts +92 -0
  147. package/dist/resume-Ct5R6yli.d.ts.map +1 -0
  148. package/dist/resume.d.ts +2 -0
  149. package/dist/resume.js +222 -0
  150. package/dist/resume.js.map +1 -0
  151. package/dist/retain-BfNTvBRb.d.ts +48 -0
  152. package/dist/retain-BfNTvBRb.d.ts.map +1 -0
  153. package/dist/retain.d.ts +2 -0
  154. package/dist/retain.js +107 -0
  155. package/dist/retain.js.map +1 -0
  156. package/dist/selection.d.ts +2 -0
  157. package/dist/selection.js +1 -0
  158. package/dist/shown.d.ts +20 -0
  159. package/dist/shown.d.ts.map +1 -0
  160. package/dist/shown.js +40 -0
  161. package/dist/shown.js.map +1 -0
  162. package/dist/sse-C7LhdbiQ.d.ts +17 -0
  163. package/dist/sse-C7LhdbiQ.d.ts.map +1 -0
  164. package/dist/sse.d.ts +2 -0
  165. package/dist/sse.js +127 -0
  166. package/dist/sse.js.map +1 -0
  167. package/dist/store-ah2qjcJW.d.ts +100 -0
  168. package/dist/store-ah2qjcJW.d.ts.map +1 -0
  169. package/dist/store.d.ts +2 -0
  170. package/dist/store.js +218 -0
  171. package/dist/store.js.map +1 -0
  172. package/dist/testing.d.ts +133 -0
  173. package/dist/testing.d.ts.map +1 -0
  174. package/dist/testing.js +0 -0
  175. package/dist/testing.js.map +1 -0
  176. package/dist/why-kgp_Wr3z.d.ts +483 -0
  177. package/dist/why-kgp_Wr3z.d.ts.map +1 -0
  178. package/dist/why.d.ts +2 -0
  179. package/dist/why.js +293 -0
  180. package/dist/why.js.map +1 -0
  181. package/dist/whyReasons.d.ts +42 -0
  182. package/dist/whyReasons.d.ts.map +1 -0
  183. package/dist/whyReasons.js +211 -0
  184. package/dist/whyReasons.js.map +1 -0
  185. package/dist/wire-DthPcTM6.d.ts +787 -0
  186. package/dist/wire-DthPcTM6.d.ts.map +1 -0
  187. package/dist/wire.d.ts +2 -0
  188. package/dist/wire.js +329 -0
  189. package/dist/wire.js.map +1 -0
  190. package/package.json +19 -14
  191. package/dist/index-BEvVAL76.d.mts +0 -4097
  192. package/dist/index-BEvVAL76.d.mts.map +0 -1
  193. package/dist/index.d.mts +0 -2
  194. package/dist/index.mjs +0 -1
  195. package/dist/port.d.mts.map +0 -1
  196. package/dist/port.mjs.map +0 -1
  197. package/dist/src-BDqn7Ocr.mjs +0 -6344
  198. 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"}
@@ -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"}
@@ -0,0 +1,2 @@
1
+ import { a as gc, i as departedLimit, n as Retained, o as reachable, r as RetentionRoots, s as recentRoots, t as ConnectionRoot } from "./retain-BfNTvBRb.js";
2
+ export { ConnectionRoot, Retained, RetentionRoots, departedLimit, gc, reachable, recentRoots };
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";
@@ -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"}