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,134 @@
1
+ import { r as RemoteData } from "./remoteData-mK1suYi1.js";
2
+ import { o as RemoteDiagnostic } from "./diagnostics-DHhVYflV.js";
3
+ import { l as QueryWindow } from "./query-BuIU93xA.js";
4
+ import { n as RelationRequirement } from "./requirement-D57S2LpH.js";
5
+ import { s as RemoteModel, t as Failures } from "./model-B6zk1NMB.js";
6
+ import { Dependencies } from "foldkit-entity";
7
+ /** A query body's `Dependencies` as plain data: each field by its Entity's name and key. */
8
+ interface ExplainedDependencies {
9
+ readonly fields: ReadonlyArray<{
10
+ readonly entity: string;
11
+ readonly key: string;
12
+ }>;
13
+ readonly inputs: Dependencies['inputs'];
14
+ readonly operations: Dependencies['operations'];
15
+ /** Each operator it calls, by its id and version. */
16
+ readonly operators: ReadonlyArray<{
17
+ readonly id: string;
18
+ readonly version: number;
19
+ }>;
20
+ }
21
+ /** A serializable summary of a RemoteModel for DevTools and diagnostics. */
22
+ interface RemoteInspection {
23
+ readonly entities: ReadonlyArray<{
24
+ readonly key: string;
25
+ readonly present: ReadonlyArray<string>;
26
+ readonly stale: ReadonlyArray<string>;
27
+ readonly unavailable: ReadonlyArray<string>;
28
+ readonly tombstone: boolean;
29
+ readonly updatedAt: number;
30
+ readonly windows: Readonly<Record<string, string>>;
31
+ }>;
32
+ readonly connections: ReadonlyArray<string>;
33
+ readonly live: ReadonlyArray<string>;
34
+ readonly gaps: ReadonlyArray<string>;
35
+ /** What the client met and kept going past, oldest first (`RemoteModel.diagnostics`). */
36
+ readonly diagnostics: ReadonlyArray<RemoteDiagnostic>;
37
+ /**
38
+ * The `entity\0id\0field` marks of reads in flight, and a
39
+ * `\0connection\0identity` mark for each query in flight. This is what "Remote is
40
+ * working" means here, and it is read from the Model rather than from the
41
+ * fibers doing the work: a tool that shows it shows something the Model can
42
+ * be replayed to, and nothing that needs the runtime to be asked.
43
+ */
44
+ readonly loading: ReadonlyArray<string>;
45
+ /**
46
+ * The reads that failed and are not yet settled: queries by connection
47
+ * identity, entity fields by `entity\0id\0field` mark, each with its error.
48
+ */
49
+ readonly failures: Failures;
50
+ readonly mutations: {
51
+ readonly pending: ReadonlyArray<string>;
52
+ readonly failed: ReadonlyArray<string>;
53
+ readonly applied: number;
54
+ };
55
+ }
56
+ /** A pure, serializable view of the whole cache. */
57
+ declare const inspectRemote: (model: RemoteModel) => RemoteInspection;
58
+ /** A pure, serializable view of one entity, or `undefined` if unknown. */
59
+ declare const inspectEntity: (model: RemoteModel, key: string) => RemoteInspection['entities'][number] | undefined;
60
+ /**
61
+ * What one query read is, and what it currently is — data-query-DESIGN §29.1.
62
+ *
63
+ * Every member is gathered from something that already existed: the read was
64
+ * always this many pieces, and nothing had ever been asked to put them in one
65
+ * place. It is plain serializable data, so a DevTools panel, a log line and a
66
+ * test assert on the same value.
67
+ *
68
+ * Two members of §29.1's sketch are **not** here, and their absence is the
69
+ * finding rather than an omission:
70
+ *
71
+ * - **Surface.** A Projection does not know which Surface reads it, and often
72
+ * several do — so it is not a property of the read, and `explain` cannot
73
+ * recover it alone. Given the active Surfaces (the same record
74
+ * `subscriptions` takes), it reports **every** Surface reading the connection
75
+ * rather than guessing one, and omits the member entirely when it was not
76
+ * given them.
77
+ * - **Executor.** The thing that answers a query is a `RemoteClient` Layer in
78
+ * the runtime, not a value in the Model, and a pure read of the Model cannot
79
+ * see it. That is the same boundary that makes this function pure and
80
+ * replayable, so it is worth more than the line of text.
81
+ *
82
+ * And one that the design proposed and this does not want: *expectation*
83
+ * (required versus optional). §11 left it unbuilt for want of a consumer, and
84
+ * the explanation was the likeliest consumer. It turns out not to need it — a
85
+ * query's result is a connection, so the shape is a `Page`, decided by the
86
+ * definition rather than by the read. Nothing here has an opinion about whether
87
+ * an empty one is an error, because nothing here has to have one.
88
+ */
89
+ interface QueryExplanation {
90
+ /** The bound Remote domain that answers it. */
91
+ readonly domain: string;
92
+ /** The definition's name. */
93
+ readonly query: string;
94
+ /** The input as encoded, which is what the identity is built from. */
95
+ readonly input: unknown;
96
+ /** The connection identity: definition plus canonical input, excluding the window. */
97
+ readonly identity: string;
98
+ /** How much of the connection this read asks for. */
99
+ readonly window: QueryWindow;
100
+ /** What it reads of each item, as the slice asked of the server. */
101
+ readonly select: RelationRequirement;
102
+ /**
103
+ * What the query means, as readable text — `Query.show`, and so neither SQL
104
+ * nor whatever the backend compiled. Absent for a definition made with
105
+ * `Query.make`, whose meaning lives on the server that answers it.
106
+ */
107
+ readonly body?: string | undefined;
108
+ /**
109
+ * The fields, inputs and operations the body reads; absent with the body.
110
+ * Plain data, so it serializes: a field is its Entity's name and key, which
111
+ * is unambiguous because a query reads one Entity.
112
+ */
113
+ readonly dependencies?: ExplainedDependencies | undefined;
114
+ /** What the read answers from this Model right now. */
115
+ readonly state: RemoteData<unknown>['_tag'];
116
+ /**
117
+ * The active Surfaces reading this connection at this Model, if the active
118
+ * record was supplied. Plural because several may, which is why a Projection
119
+ * cannot carry the answer itself.
120
+ */
121
+ readonly surfaces?: ReadonlyArray<string> | undefined;
122
+ /**
123
+ * Why each of those Surfaces is active, where it was placed with
124
+ * `Surface.when` — the Model path and the tag, without running anything. A
125
+ * Surface placed with `Surface.at` has no entry: a callback cannot be read.
126
+ */
127
+ readonly activation?: ReadonlyArray<{
128
+ readonly surface: string;
129
+ readonly path: readonly string[];
130
+ readonly tag: string;
131
+ }> | undefined;
132
+ }
133
+ export { inspectRemote as a, inspectEntity as i, QueryExplanation as n, RemoteInspection as r, ExplainedDependencies as t };
134
+ //# sourceMappingURL=inspect-DtRQGCRP.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inspect-DtRQGCRP.d.ts","names":[],"sources":["../src/inspect.ts"],"mappings":";;;;;;;UAIiB;WACN,QAAQ;aAAyB;aAAyB;;WAC1D,QAAQ;WACR,YAAY;;WAEZ,WAAW;aAAyB;aAAqB;;;;UAUnD;WACN,UAAU;aACR;aACA,SAAS;aACT,OAAO;aACP,aAAa;aACb;aACA;aACA,SAAS,SAAS;;WAEpB,aAAa;WACb,MAAM;WACN,MAAM;;WAEN,aAAa,cAAc;;;;;;;;WAQ3B,SAAS;;;;;WAKT,UAAU;WACV;aACE,SAAS;aACT,QAAQ;aACR;;;;cAeA,gBAAa,OAAW,gBAAc;;cAgBtC,gBAAa,OACjB,aAAW,gBAEjB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAkCc;;WAEN;;WAEA;;WAEA;;WAEA;;WAEA,QAAQ;;WAER,QAAQ;;;;;;WAMR;;;;;;WAMA,eAAe;;WAEf,OAAO;;;;;;WAMP,WAAW;;;;;;WAMX,aACL;aACW;aACA;aACA"}
@@ -0,0 +1,2 @@
1
+ import { a as inspectRemote, i as inspectEntity, n as QueryExplanation, r as RemoteInspection, t as ExplainedDependencies } from "./inspect-DtRQGCRP.js";
2
+ export { ExplainedDependencies, QueryExplanation, RemoteInspection, inspectEntity, inspectRemote };
@@ -0,0 +1,33 @@
1
+ // src/inspect.ts
2
+ const inspectEntry = (key, entry) => ({
3
+ key,
4
+ present: [...entry.present],
5
+ stale: [...entry.stale],
6
+ unavailable: [...entry.unavailable],
7
+ tombstone: entry.tombstone,
8
+ updatedAt: entry.updatedAt,
9
+ windows: { ...entry.windows }
10
+ });
11
+ /** A pure, serializable view of the whole cache. */
12
+ const inspectRemote = (model) => ({
13
+ entities: Object.entries(model.entities).map(([key, entry]) => inspectEntry(key, entry)),
14
+ connections: Object.keys(model.connections),
15
+ live: Object.keys(model.live),
16
+ gaps: [...model.gaps],
17
+ diagnostics: model.diagnostics,
18
+ loading: [...model.loading],
19
+ failures: model.failures,
20
+ mutations: {
21
+ pending: [...model.mutations.pending],
22
+ failed: [...model.mutations.failed],
23
+ applied: model.mutations.applied.size
24
+ }
25
+ });
26
+ /** A pure, serializable view of one entity, or `undefined` if unknown. */
27
+ const inspectEntity = (model, key) => {
28
+ const entry = model.entities[key];
29
+ return entry === void 0 ? void 0 : inspectEntry(key, entry);
30
+ };
31
+ export { inspectEntity, inspectRemote };
32
+
33
+ //# sourceMappingURL=inspect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inspect.js","names":[],"sources":["../src/inspect.ts"],"sourcesContent":["/** A serializable summary of a RemoteModel for DevTools and diagnostics. */\nimport type { Dependencies } from 'foldkit-entity'\n\n/** A query body's `Dependencies` as plain data: each field by its Entity's name and key. */\nexport interface ExplainedDependencies {\n readonly fields: ReadonlyArray<{ readonly entity: string; readonly key: string }>\n readonly inputs: Dependencies['inputs']\n readonly operations: Dependencies['operations']\n /** Each operator it calls, by its id and version. */\n readonly operators: ReadonlyArray<{ readonly id: string; readonly version: number }>\n}\nimport type { RemoteDiagnostic } from './diagnostics.js'\nimport type { Failures, RemoteModel } from './model.js'\nimport type { QueryWindow } from './query.js'\nimport type { RemoteData } from './remoteData.js'\nimport type { RelationRequirement } from './requirement.js'\nimport type { EntityEntry } from './store.js'\n\n/** A serializable summary of a RemoteModel for DevTools and diagnostics. */\nexport interface RemoteInspection {\n readonly entities: ReadonlyArray<{\n readonly key: string\n readonly present: ReadonlyArray<string>\n readonly stale: ReadonlyArray<string>\n readonly unavailable: ReadonlyArray<string>\n readonly tombstone: boolean\n readonly updatedAt: number\n readonly windows: Readonly<Record<string, string>>\n }>\n readonly connections: ReadonlyArray<string>\n readonly live: ReadonlyArray<string>\n readonly gaps: ReadonlyArray<string>\n /** What the client met and kept going past, oldest first (`RemoteModel.diagnostics`). */\n readonly diagnostics: ReadonlyArray<RemoteDiagnostic>\n /**\n * The `entity\\0id\\0field` marks of reads in flight, and a\n * `\\0connection\\0identity` mark for each query in flight. This is what \"Remote is\n * working\" means here, and it is read from the Model rather than from the\n * fibers doing the work: a tool that shows it shows something the Model can\n * be replayed to, and nothing that needs the runtime to be asked.\n */\n readonly loading: ReadonlyArray<string>\n /**\n * The reads that failed and are not yet settled: queries by connection\n * identity, entity fields by `entity\\0id\\0field` mark, each with its error.\n */\n readonly failures: Failures\n readonly mutations: {\n readonly pending: ReadonlyArray<string>\n readonly failed: ReadonlyArray<string>\n readonly applied: number\n }\n}\n\nconst inspectEntry = (key: string, entry: EntityEntry): RemoteInspection['entities'][number] => ({\n key,\n present: [...entry.present],\n stale: [...entry.stale],\n unavailable: [...entry.unavailable],\n tombstone: entry.tombstone,\n updatedAt: entry.updatedAt,\n windows: { ...entry.windows },\n})\n\n/** A pure, serializable view of the whole cache. */\nexport const inspectRemote = (model: RemoteModel): RemoteInspection => ({\n entities: Object.entries(model.entities).map(([key, entry]) => inspectEntry(key, entry)),\n connections: Object.keys(model.connections),\n live: Object.keys(model.live),\n gaps: [...model.gaps],\n diagnostics: model.diagnostics,\n loading: [...model.loading],\n failures: model.failures,\n mutations: {\n pending: [...model.mutations.pending],\n failed: [...model.mutations.failed],\n applied: model.mutations.applied.size,\n },\n})\n\n/** A pure, serializable view of one entity, or `undefined` if unknown. */\nexport const inspectEntity = (\n model: RemoteModel,\n key: string,\n): RemoteInspection['entities'][number] | undefined => {\n const entry = model.entities[key]\n return entry === undefined ? undefined : inspectEntry(key, entry)\n}\n\n/**\n * What one query read is, and what it currently is — data-query-DESIGN §29.1.\n *\n * Every member is gathered from something that already existed: the read was\n * always this many pieces, and nothing had ever been asked to put them in one\n * place. It is plain serializable data, so a DevTools panel, a log line and a\n * test assert on the same value.\n *\n * Two members of §29.1's sketch are **not** here, and their absence is the\n * finding rather than an omission:\n *\n * - **Surface.** A Projection does not know which Surface reads it, and often\n * several do — so it is not a property of the read, and `explain` cannot\n * recover it alone. Given the active Surfaces (the same record\n * `subscriptions` takes), it reports **every** Surface reading the connection\n * rather than guessing one, and omits the member entirely when it was not\n * given them.\n * - **Executor.** The thing that answers a query is a `RemoteClient` Layer in\n * the runtime, not a value in the Model, and a pure read of the Model cannot\n * see it. That is the same boundary that makes this function pure and\n * replayable, so it is worth more than the line of text.\n *\n * And one that the design proposed and this does not want: *expectation*\n * (required versus optional). §11 left it unbuilt for want of a consumer, and\n * the explanation was the likeliest consumer. It turns out not to need it — a\n * query's result is a connection, so the shape is a `Page`, decided by the\n * definition rather than by the read. Nothing here has an opinion about whether\n * an empty one is an error, because nothing here has to have one.\n */\nexport interface QueryExplanation {\n /** The bound Remote domain that answers it. */\n readonly domain: string\n /** The definition's name. */\n readonly query: string\n /** The input as encoded, which is what the identity is built from. */\n readonly input: unknown\n /** The connection identity: definition plus canonical input, excluding the window. */\n readonly identity: string\n /** How much of the connection this read asks for. */\n readonly window: QueryWindow\n /** What it reads of each item, as the slice asked of the server. */\n readonly select: RelationRequirement\n /**\n * What the query means, as readable text — `Query.show`, and so neither SQL\n * nor whatever the backend compiled. Absent for a definition made with\n * `Query.make`, whose meaning lives on the server that answers it.\n */\n readonly body?: string | undefined\n /**\n * The fields, inputs and operations the body reads; absent with the body.\n * Plain data, so it serializes: a field is its Entity's name and key, which\n * is unambiguous because a query reads one Entity.\n */\n readonly dependencies?: ExplainedDependencies | undefined\n /** What the read answers from this Model right now. */\n readonly state: RemoteData<unknown>['_tag']\n /**\n * The active Surfaces reading this connection at this Model, if the active\n * record was supplied. Plural because several may, which is why a Projection\n * cannot carry the answer itself.\n */\n readonly surfaces?: ReadonlyArray<string> | undefined\n /**\n * Why each of those Surfaces is active, where it was placed with\n * `Surface.when` — the Model path and the tag, without running anything. A\n * Surface placed with `Surface.at` has no entry: a callback cannot be read.\n */\n readonly activation?:\n | ReadonlyArray<{\n readonly surface: string\n readonly path: readonly string[]\n readonly tag: string\n }>\n | undefined\n}\n"],"mappings":";AAsDA,MAAM,gBAAgB,KAAa,WAA8D;CAC/F;CACA,SAAS,CAAC,GAAG,MAAM,OAAO;CAC1B,OAAO,CAAC,GAAG,MAAM,KAAK;CACtB,aAAa,CAAC,GAAG,MAAM,WAAW;CAClC,WAAW,MAAM;CACjB,WAAW,MAAM;CACjB,SAAS,EAAE,GAAG,MAAM,QAAQ;AAC9B;;AAGA,MAAa,iBAAiB,WAA0C;CACtE,UAAU,OAAO,QAAQ,MAAM,QAAQ,CAAC,CAAC,KAAK,CAAC,KAAK,WAAW,aAAa,KAAK,KAAK,CAAC;CACvF,aAAa,OAAO,KAAK,MAAM,WAAW;CAC1C,MAAM,OAAO,KAAK,MAAM,IAAI;CAC5B,MAAM,CAAC,GAAG,MAAM,IAAI;CACpB,aAAa,MAAM;CACnB,SAAS,CAAC,GAAG,MAAM,OAAO;CAC1B,UAAU,MAAM;CAChB,WAAW;EACT,SAAS,CAAC,GAAG,MAAM,UAAU,OAAO;EACpC,QAAQ,CAAC,GAAG,MAAM,UAAU,MAAM;EAClC,SAAS,MAAM,UAAU,QAAQ;CACnC;AACF;;AAGA,MAAa,iBACX,OACA,QACqD;CACrD,MAAM,QAAQ,MAAM,SAAS;CAC7B,OAAO,UAAU,KAAA,IAAY,KAAA,IAAY,aAAa,KAAK,KAAK;AAClE"}
@@ -0,0 +1,52 @@
1
+ import { n as RemoteJsonClient } from "./client-B055kQuP.js";
2
+ import { Schema } from "effect";
3
+ /** A request: which of Remote's calls, and its payload as the RPC protocol encodes it. */
4
+ declare const RemoteJsonRequest: Schema.Struct<{
5
+ readonly operation: Schema.Literals<readonly ["read", "query", "mutate"]>;
6
+ readonly payload: Schema.Unknown;
7
+ }>;
8
+ type RemoteJsonRequest = typeof RemoteJsonRequest.Type;
9
+ /** An answer: the call's result, or why there is none. */
10
+ declare const RemoteJsonAnswer: Schema.Union<readonly [Schema.Struct<{
11
+ readonly result: Schema.Unknown;
12
+ }>, Schema.Struct<{
13
+ readonly error: Schema.String;
14
+ readonly refusal: Schema.optional<Schema.Unknown>;
15
+ /** Who is asking was refused, with the application's reason when it gave one. */
16
+ readonly forbidden: Schema.optional<Schema.Struct<{
17
+ readonly reason: Schema.optional<Schema.String>;
18
+ }>>;
19
+ /** Who is asking could not be told, with the reason when one was given; a 401. */
20
+ readonly unauthenticated: Schema.optional<Schema.Struct<{
21
+ readonly reason: Schema.optional<Schema.String>;
22
+ }>>;
23
+ }>]>;
24
+ type RemoteJsonAnswer = typeof RemoteJsonAnswer.Type;
25
+ /**
26
+ * Sends one request's text and resolves with its answer as parsed JSON. It
27
+ * rejects when nothing came back, and the call fails with that message. A
28
+ * value that is not a `RemoteJsonAnswer` is no answer either: a mutation fails
29
+ * `RemoteUnreachable` for both, a read or a query its own error, which is
30
+ * retried like any failed read.
31
+ */
32
+ type RemoteJsonSend = (request: string) => Promise<unknown>;
33
+ /** Remote's client over `send`, for `Remote.clientLayer`. */
34
+ declare const json: (send: RemoteJsonSend) => RemoteJsonClient;
35
+ /**
36
+ * Remote's client over HTTP: each call a `POST` of the request to `url`, the
37
+ * answer its JSON body. A body that is not JSON, or one under 408, 429, 502,
38
+ * 503 or 504, is no answer: a mutation fails `RemoteUnreachable`, so a
39
+ * durable one is sent again. A 401 is answered as
40
+ * unauthenticated, for every call kind, and a 403 as forbidden, whatever the
41
+ * body says: the server's own says why, and middleware in front of Remote
42
+ * may say anything.
43
+ * `headers` is read per request, so a token that changes reaches the next
44
+ * call. `fetch` defaults to the global one; pass it to route calls through
45
+ * another implementation in tests.
46
+ */
47
+ declare const http: (url: string, options?: {
48
+ readonly headers?: (() => Readonly<Record<string, string>>) | undefined;
49
+ readonly fetch?: typeof fetch | undefined;
50
+ }) => RemoteJsonClient;
51
+ export { json as a, http as i, RemoteJsonRequest as n, RemoteJsonSend as r, RemoteJsonAnswer as t };
52
+ //# sourceMappingURL=json-DowLl1eB.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-DowLl1eB.d.ts","names":[],"sources":["../src/json.ts"],"mappings":";;;cAmBa,mBAAiB,OAAA;;;;KAIlB,2BAA2B,kBAAkB;;cAG5C,kBAAgB,OAAA,gBAAA,OAAA;;;;;;;;;;;;;;KAWjB,0BAA0B,iBAAiB;;;;;;;;KAa3C,kBAAkB,oBAAoB;;cAgDrC,OAAI,MAAU,mBAAiB;;;;;;;;;;;;;cAmF/B,OAAI,aACJ;WAEA,iBAAiB,SAAS;WAC1B,eAAe;MAEzB"}
package/dist/json.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ import { a as json, i as http, n as RemoteJsonRequest, r as RemoteJsonSend, t as RemoteJsonAnswer } from "./json-DowLl1eB.js";
2
+ export { RemoteJsonAnswer, RemoteJsonRequest, RemoteJsonSend, http, json };
package/dist/json.js ADDED
@@ -0,0 +1,123 @@
1
+ import { RemoteForbidden, RemoteMutationError, RemoteQueryError, RemoteReadError, RemoteUnauthenticated, RemoteUnreachable } from "./wire.js";
2
+ import { Effect, Option, Schema, Stream } from "effect";
3
+ // src/json.ts
4
+ /**
5
+ * Remote's three calls as JSON: one request, `{ operation, payload }`, and one
6
+ * answer, `{ result }` or `{ error }`. `RemoteServer.answer` is the other end.
7
+ * The client takes any way of sending the request text and receiving the
8
+ * answer, so the same client speaks HTTP (`http`), a worker, or a server
9
+ * running in the page. There is no live stream over it.
10
+ */
11
+ /** A request: which of Remote's calls, and its payload as the RPC protocol encodes it. */
12
+ const RemoteJsonRequest = Schema.Struct({
13
+ operation: Schema.Literals([
14
+ "read",
15
+ "query",
16
+ "mutate"
17
+ ]),
18
+ payload: Schema.Unknown
19
+ });
20
+ /** An answer: the call's result, or why there is none. */
21
+ const RemoteJsonAnswer = Schema.Union([Schema.Struct({ result: Schema.Unknown }), Schema.Struct({
22
+ error: Schema.String,
23
+ refusal: Schema.optional(Schema.Unknown),
24
+ /** Who is asking was refused, with the application's reason when it gave one. */
25
+ forbidden: Schema.optional(Schema.Struct({ reason: Schema.optional(Schema.String) })),
26
+ /** Who is asking could not be told, with the reason when one was given; a 401. */
27
+ unauthenticated: Schema.optional(Schema.Struct({ reason: Schema.optional(Schema.String) }))
28
+ })]);
29
+ const decodeAnswer = Schema.decodeUnknownEffect(RemoteJsonAnswer);
30
+ const call = (send, operation, payload, fail, unreached) => Effect.tryPromise({
31
+ try: () => send(JSON.stringify({
32
+ operation,
33
+ payload
34
+ })),
35
+ catch: (error) => unreached(error instanceof Error ? error.message : String(error))
36
+ }).pipe(Effect.flatMap((answer) => decodeAnswer(answer).pipe(Effect.mapError(() => unreached("The server answered with something that is not an answer")))), Effect.flatMap((answer) => "error" in answer ? Effect.fail(fail(answer)) : Effect.succeed(answer.result)));
37
+ /**
38
+ * An error answer as its call's failure: a refusal of who is asking, of
39
+ * every call kind alike, or the call's own error.
40
+ */
41
+ const failure = (own) => (answer) => answer.unauthenticated === void 0 ? own(answer) : new RemoteUnauthenticated({
42
+ message: answer.error,
43
+ ...answer.unauthenticated.reason === void 0 ? {} : { reason: answer.unauthenticated.reason }
44
+ });
45
+ /** Remote's client over `send`, for `Remote.clientLayer`. */
46
+ const json = (send) => ({
47
+ FoldkitRemoteRead: (payload) => call(send, "read", payload, failure(({ error }) => new RemoteReadError({ message: error })), (message) => new RemoteReadError({ message })),
48
+ FoldkitRemoteQuery: (payload) => call(send, "query", payload, failure(({ error }) => new RemoteQueryError({ message: error })), (message) => new RemoteQueryError({ message })),
49
+ FoldkitRemoteMutate: (payload) => call(send, "mutate", payload, failure(({ error, refusal, forbidden }) => forbidden === void 0 ? new RemoteMutationError({
50
+ message: error,
51
+ refusal
52
+ }) : new RemoteForbidden({
53
+ message: error,
54
+ ...forbidden.reason === void 0 ? {} : { reason: forbidden.reason }
55
+ })), (message) => new RemoteUnreachable({ message })),
56
+ FoldkitRemoteLive: () => Stream.empty
57
+ });
58
+ /** A body that did not parse, told apart from every value one can parse to, `null` included. */
59
+ const notJson = Symbol("not JSON");
60
+ const Reason = Schema.Struct({ reason: Schema.optional(Schema.String) });
61
+ /** A refusal in Remote's own words, by the key it is given under. */
62
+ const ownRefusal = {
63
+ unauthenticated: Schema.decodeUnknownOption(Schema.Struct({
64
+ error: Schema.String,
65
+ unauthenticated: Reason
66
+ })),
67
+ forbidden: Schema.decodeUnknownOption(Schema.Struct({
68
+ error: Schema.String,
69
+ forbidden: Reason
70
+ }))
71
+ };
72
+ /**
73
+ * A 401's or 403's answer, whatever its body: Remote's own refusal as it is,
74
+ * and anything else (`{ error }` from middleware, HTML, a shape of its own)
75
+ * as the status alone, so it never reaches a decoder that would call it a
76
+ * broken answer.
77
+ */
78
+ const refusalOf = (body, kind, status) => Option.getOrElse(ownRefusal[kind](body), () => ({
79
+ error: status,
80
+ [kind]: {}
81
+ }));
82
+ /**
83
+ * Statuses Remote's server never answers with, which say the request was not
84
+ * served (yet): a body under one is a gateway's or a limiter's, never an answer.
85
+ */
86
+ const unanswered = /* @__PURE__ */ new Set([
87
+ 408,
88
+ 429,
89
+ 502,
90
+ 503,
91
+ 504
92
+ ]);
93
+ /**
94
+ * Remote's client over HTTP: each call a `POST` of the request to `url`, the
95
+ * answer its JSON body. A body that is not JSON, or one under 408, 429, 502,
96
+ * 503 or 504, is no answer: a mutation fails `RemoteUnreachable`, so a
97
+ * durable one is sent again. A 401 is answered as
98
+ * unauthenticated, for every call kind, and a 403 as forbidden, whatever the
99
+ * body says: the server's own says why, and middleware in front of Remote
100
+ * may say anything.
101
+ * `headers` is read per request, so a token that changes reaches the next
102
+ * call. `fetch` defaults to the global one; pass it to route calls through
103
+ * another implementation in tests.
104
+ */
105
+ const http = (url, options = {}) => json(async (request) => {
106
+ const response = await (options.fetch ?? fetch)(url, {
107
+ method: "POST",
108
+ headers: {
109
+ "content-type": "application/json",
110
+ ...options.headers?.()
111
+ },
112
+ body: request
113
+ });
114
+ const status = `${response.status} ${response.statusText}`;
115
+ const body = await response.json().catch(() => notJson);
116
+ if (response.status === 401) return refusalOf(body, "unauthenticated", status);
117
+ if (response.status === 403) return refusalOf(body, "forbidden", status);
118
+ if (body === notJson || unanswered.has(response.status)) throw new Error(status);
119
+ return body;
120
+ });
121
+ export { RemoteJsonAnswer, RemoteJsonRequest, http, json };
122
+
123
+ //# sourceMappingURL=json.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.js","names":[],"sources":["../src/json.ts"],"sourcesContent":["/**\n * Remote's three calls as JSON: one request, `{ operation, payload }`, and one\n * answer, `{ result }` or `{ error }`. `RemoteServer.answer` is the other end.\n * The client takes any way of sending the request text and receiving the\n * answer, so the same client speaks HTTP (`http`), a worker, or a server\n * running in the page. There is no live stream over it.\n */\nimport { Effect, Option, Schema, Stream } from 'effect'\nimport type { RemoteJsonClient } from './client.js'\nimport {\n RemoteForbidden,\n RemoteMutationError,\n RemoteQueryError,\n RemoteReadError,\n RemoteUnauthenticated,\n RemoteUnreachable,\n} from './wire.js'\n\n/** A request: which of Remote's calls, and its payload as the RPC protocol encodes it. */\nexport const RemoteJsonRequest = Schema.Struct({\n operation: Schema.Literals(['read', 'query', 'mutate']),\n payload: Schema.Unknown,\n})\nexport type RemoteJsonRequest = typeof RemoteJsonRequest.Type\n\n/** An answer: the call's result, or why there is none. */\nexport const RemoteJsonAnswer = Schema.Union([\n Schema.Struct({ result: Schema.Unknown }),\n Schema.Struct({\n error: Schema.String,\n refusal: Schema.optional(Schema.Unknown),\n /** Who is asking was refused, with the application's reason when it gave one. */\n forbidden: Schema.optional(Schema.Struct({ reason: Schema.optional(Schema.String) })),\n /** Who is asking could not be told, with the reason when one was given; a 401. */\n unauthenticated: Schema.optional(Schema.Struct({ reason: Schema.optional(Schema.String) })),\n }),\n])\nexport type RemoteJsonAnswer = typeof RemoteJsonAnswer.Type\n\ntype ErrorAnswer = Extract<RemoteJsonAnswer, { readonly error: string }>\n\nconst decodeAnswer = Schema.decodeUnknownEffect(RemoteJsonAnswer)\n\n/**\n * Sends one request's text and resolves with its answer as parsed JSON. It\n * rejects when nothing came back, and the call fails with that message. A\n * value that is not a `RemoteJsonAnswer` is no answer either: a mutation fails\n * `RemoteUnreachable` for both, a read or a query its own error, which is\n * retried like any failed read.\n */\nexport type RemoteJsonSend = (request: string) => Promise<unknown>\n\nconst call = <A, E, U>(\n send: RemoteJsonSend,\n operation: RemoteJsonRequest['operation'],\n payload: unknown,\n fail: (answer: ErrorAnswer) => E,\n /** What no answer coming back is: the request was not sent, or what came back was no answer. */\n unreached: (message: string) => U,\n): Effect.Effect<A, E | U> =>\n Effect.tryPromise({\n try: () => send(JSON.stringify({ operation, payload })),\n catch: error => unreached(error instanceof Error ? error.message : String(error)),\n }).pipe(\n Effect.flatMap(answer =>\n decodeAnswer(answer).pipe(\n // JSON that is not an answer came from something in front of Remote\n // (a gateway's `{}`), so the request may have reached the server.\n Effect.mapError(() =>\n unreached('The server answered with something that is not an answer'),\n ),\n ),\n ),\n Effect.flatMap(answer =>\n 'error' in answer\n ? Effect.fail(fail(answer))\n : // The answer is to the request this call sent, so its result is this call's.\n Effect.succeed(answer.result as A),\n ),\n )\n\n/**\n * An error answer as its call's failure: a refusal of who is asking, of\n * every call kind alike, or the call's own error.\n */\nconst failure =\n <E>(own: (answer: ErrorAnswer) => E) =>\n (answer: ErrorAnswer): E | RemoteUnauthenticated =>\n answer.unauthenticated === undefined\n ? own(answer)\n : new RemoteUnauthenticated({\n message: answer.error,\n ...(answer.unauthenticated.reason === undefined\n ? {}\n : { reason: answer.unauthenticated.reason }),\n })\n\n/** Remote's client over `send`, for `Remote.clientLayer`. */\nexport const json = (send: RemoteJsonSend): RemoteJsonClient => ({\n FoldkitRemoteRead: payload =>\n call(\n send,\n 'read',\n payload,\n failure(({ error }) => new RemoteReadError({ message: error })),\n message => new RemoteReadError({ message }),\n ),\n FoldkitRemoteQuery: payload =>\n call(\n send,\n 'query',\n payload,\n failure(({ error }) => new RemoteQueryError({ message: error })),\n message => new RemoteQueryError({ message }),\n ),\n FoldkitRemoteMutate: payload =>\n call(\n send,\n 'mutate',\n payload,\n failure(({ error, refusal, forbidden }) =>\n forbidden === undefined\n ? new RemoteMutationError({ message: error, refusal })\n : new RemoteForbidden({\n message: error,\n ...(forbidden.reason === undefined ? {} : { reason: forbidden.reason }),\n }),\n ),\n // A mutation that may not have reached the server is told apart, so a\n // durable one is sent again rather than settled.\n message => new RemoteUnreachable({ message }),\n ),\n FoldkitRemoteLive: () => Stream.empty,\n})\n\n/** A body that did not parse, told apart from every value one can parse to, `null` included. */\nconst notJson = Symbol('not JSON')\n\nconst Reason = Schema.Struct({ reason: Schema.optional(Schema.String) })\n/** A refusal in Remote's own words, by the key it is given under. */\nconst ownRefusal: Record<\n 'unauthenticated' | 'forbidden',\n (body: unknown) => Option.Option<Partial<RemoteJsonAnswer>>\n> = {\n unauthenticated: Schema.decodeUnknownOption(\n Schema.Struct({ error: Schema.String, unauthenticated: Reason }),\n ),\n forbidden: Schema.decodeUnknownOption(Schema.Struct({ error: Schema.String, forbidden: Reason })),\n}\n\n/**\n * A 401's or 403's answer, whatever its body: Remote's own refusal as it is,\n * and anything else (`{ error }` from middleware, HTML, a shape of its own)\n * as the status alone, so it never reaches a decoder that would call it a\n * broken answer.\n */\nconst refusalOf = (\n body: unknown,\n kind: 'unauthenticated' | 'forbidden',\n status: string,\n): Partial<RemoteJsonAnswer> =>\n Option.getOrElse(ownRefusal[kind](body), () => ({ error: status, [kind]: {} }))\n\n/**\n * Statuses Remote's server never answers with, which say the request was not\n * served (yet): a body under one is a gateway's or a limiter's, never an answer.\n */\nconst unanswered: ReadonlySet<number> = new Set([408, 429, 502, 503, 504])\n\n/**\n * Remote's client over HTTP: each call a `POST` of the request to `url`, the\n * answer its JSON body. A body that is not JSON, or one under 408, 429, 502,\n * 503 or 504, is no answer: a mutation fails `RemoteUnreachable`, so a\n * durable one is sent again. A 401 is answered as\n * unauthenticated, for every call kind, and a 403 as forbidden, whatever the\n * body says: the server's own says why, and middleware in front of Remote\n * may say anything.\n * `headers` is read per request, so a token that changes reaches the next\n * call. `fetch` defaults to the global one; pass it to route calls through\n * another implementation in tests.\n */\nexport const http = (\n url: string,\n options: {\n readonly headers?: (() => Readonly<Record<string, string>>) | undefined\n readonly fetch?: typeof fetch | undefined\n } = {},\n): RemoteJsonClient =>\n json(async request => {\n const via = options.fetch ?? fetch\n const response = await via(url, {\n method: 'POST',\n headers: { 'content-type': 'application/json', ...options.headers?.() },\n body: request,\n })\n const status = `${response.status} ${response.statusText}`\n const body: unknown = await response.json().catch(() => notJson)\n if (response.status === 401) return refusalOf(body, 'unauthenticated', status)\n if (response.status === 403) return refusalOf(body, 'forbidden', status)\n if (body === notJson || unanswered.has(response.status)) throw new Error(status)\n return body\n })\n"],"mappings":"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"}
package/dist/law.d.ts ADDED
@@ -0,0 +1,56 @@
1
+ import { E as MutationDescriptor } from "./optimistic-CECsfNGY.js";
2
+ import { c as QueryRef } from "./query-BuIU93xA.js";
3
+ import { t as RemoteClient } from "./client-B055kQuP.js";
4
+ import { RemoteDomain } from "./index.js";
5
+ import { Effect } from "effect";
6
+ import { AnyEntity } from "foldkit-entity";
7
+ /** One part both sides answered, and what each said. */
8
+ export interface LawPart {
9
+ /** `Entity:id.field`, `Entity:id` for whether the row is there, or a list's identity. */
10
+ readonly part: string;
11
+ /** When the client said it: while the write was pending, or once its answer had landed. */
12
+ readonly when: 'pending' | 'answered';
13
+ readonly client: unknown;
14
+ readonly server: unknown;
15
+ }
16
+ /**
17
+ * What `Law.check` compared: the parts both sides agreed on, pending and
18
+ * answered, and those they did not.
19
+ */
20
+ export interface LawReport {
21
+ readonly agreed: ReadonlyArray<string>;
22
+ readonly disagreed: ReadonlyArray<LawPart>;
23
+ /** Parts the client left to the server: a list it did not place, a field it did not foresee. */
24
+ readonly unknown: ReadonlyArray<string>;
25
+ }
26
+ export interface LawConfig<AppModel> {
27
+ /** The application's Model before the client holds anything; `Remote.make`'s viewer reads it. */
28
+ readonly model: AppModel;
29
+ /** The rows the client holds, each read whole as the server's principal sees it. */
30
+ readonly holds: ReadonlyArray<{
31
+ readonly entity: AnyEntity;
32
+ readonly ids: ReadonlyArray<string>;
33
+ }>;
34
+ /** Lists the client holds, wholly loaded, whose placements are compared. */
35
+ readonly lists?: ReadonlyArray<QueryRef<string, unknown>> | undefined;
36
+ readonly mutation: MutationDescriptor<string, any, any, any>;
37
+ readonly input: unknown;
38
+ /** When the reader acted: what the client predicts with and the request sends. */
39
+ readonly now: number;
40
+ }
41
+ /**
42
+ * Runs `config.mutation` as the client would, through `Data.mutate` over a
43
+ * Model holding what the server sends this principal, and through the server
44
+ * (`RemoteClient`: `Remote.clientLayer(RemoteServer.handlers(server, principal))`),
45
+ * then compares each predicted field, each predicted deletion and each list's
46
+ * order with what the server answers after the write.
47
+ *
48
+ * Fails when nothing was compared: two sides that both said nothing agree,
49
+ * and that agreement is no evidence.
50
+ */
51
+ declare const check: <AppModel>(Data: RemoteDomain<AppModel, any, any, any, any>, config: LawConfig<AppModel>) => Effect.Effect<LawReport, unknown, RemoteClient>;
52
+ export declare const Law: {
53
+ check: typeof check;
54
+ };
55
+ export {};
56
+ //# sourceMappingURL=law.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"law.d.ts","names":[],"sources":["../src/law.ts"],"mappings":";;;;;;;iBAoBiB;;WAEN;;WAEA;WACA;WACA;;;;;;iBAOM;WACN,QAAQ;WACR,WAAW,cAAc;;WAEzB,SAAS;;iBAGH,UAAU;;WAEhB,OAAO;;WAEP,OAAO;aAAyB,QAAQ;aAAoB,KAAK;;;WAEjE,QAAQ,cAAc;WACtB,UAAU;WACV;;WAEA;;;;;;;;;;;;cAsCL,QAAS,UAAQ,MACf,aAAa,+BAA6B,QACxC,UAAU,cACjB,OAAO,OAAO,oBAAoB;qBAoLxB"}