foldkit-remote 0.20.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +987 -198
- package/dist/answerGaps.d.ts +16 -0
- package/dist/answerGaps.d.ts.map +1 -0
- package/dist/answerGaps.js +60 -0
- package/dist/answerGaps.js.map +1 -0
- package/dist/client-B055kQuP.d.ts +110 -0
- package/dist/client-B055kQuP.d.ts.map +1 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.js +142 -0
- package/dist/client.js.map +1 -0
- package/dist/coalesce-CLAr92Nk.d.ts +34 -0
- package/dist/coalesce-CLAr92Nk.d.ts.map +1 -0
- package/dist/coalesce.d.ts +2 -0
- package/dist/coalesce.js +131 -0
- package/dist/coalesce.js.map +1 -0
- package/dist/connection-BRyaJy89.d.ts +45 -0
- package/dist/connection-BRyaJy89.d.ts.map +1 -0
- package/dist/connection.d.ts +2 -0
- package/dist/connection.js +170 -0
- package/dist/connection.js.map +1 -0
- package/dist/data.d.ts +7 -0
- package/dist/data.d.ts.map +1 -0
- package/dist/data.js +21 -0
- package/dist/data.js.map +1 -0
- package/dist/diagnostics-DHhVYflV.d.ts +125 -0
- package/dist/diagnostics-DHhVYflV.d.ts.map +1 -0
- package/dist/diagnostics.d.ts +2 -0
- package/dist/diagnostics.js +60 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/entity-CgsG5Uel.d.ts +196 -0
- package/dist/entity-CgsG5Uel.d.ts.map +1 -0
- package/dist/entity-DuDxaa3n.js +279 -0
- package/dist/entity-DuDxaa3n.js.map +1 -0
- package/dist/entity.d.ts +2 -0
- package/dist/entity.js +1 -0
- package/dist/held.d.ts +19 -0
- package/dist/held.d.ts.map +1 -0
- package/dist/held.js +70 -0
- package/dist/held.js.map +1 -0
- package/dist/impact.d.ts +79 -0
- package/dist/impact.d.ts.map +1 -0
- package/dist/impact.js +166 -0
- package/dist/impact.js.map +1 -0
- package/dist/index.d.ts +1542 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2791 -0
- package/dist/index.js.map +1 -0
- package/dist/inspect-DtRQGCRP.d.ts +134 -0
- package/dist/inspect-DtRQGCRP.d.ts.map +1 -0
- package/dist/inspect.d.ts +2 -0
- package/dist/inspect.js +33 -0
- package/dist/inspect.js.map +1 -0
- package/dist/json-DowLl1eB.d.ts +52 -0
- package/dist/json-DowLl1eB.d.ts.map +1 -0
- package/dist/json.d.ts +2 -0
- package/dist/json.js +123 -0
- package/dist/json.js.map +1 -0
- package/dist/law.d.ts +56 -0
- package/dist/law.d.ts.map +1 -0
- package/dist/law.js +201 -0
- package/dist/law.js.map +1 -0
- package/dist/live-BpTzlCnY.d.ts +79 -0
- package/dist/live-BpTzlCnY.d.ts.map +1 -0
- package/dist/live.d.ts +2 -0
- package/dist/live.js +126 -0
- package/dist/live.js.map +1 -0
- package/dist/matching-DF5F9Us8.d.ts +100 -0
- package/dist/matching-DF5F9Us8.d.ts.map +1 -0
- package/dist/matching.d.ts +2 -0
- package/dist/matching.js +172 -0
- package/dist/matching.js.map +1 -0
- package/dist/model-B6zk1NMB.d.ts +710 -0
- package/dist/model-B6zk1NMB.d.ts.map +1 -0
- package/dist/model.d.ts +2 -0
- package/dist/model.js +1308 -0
- package/dist/model.js.map +1 -0
- package/dist/mutation.d.ts +2 -0
- package/dist/mutation.js +612 -0
- package/dist/mutation.js.map +1 -0
- package/dist/optimistic-CECsfNGY.d.ts +523 -0
- package/dist/optimistic-CECsfNGY.d.ts.map +1 -0
- package/dist/optimistic.d.ts +2 -0
- package/dist/optimistic.js +336 -0
- package/dist/optimistic.js.map +1 -0
- package/dist/outbox-DNuc4kZp.d.ts +259 -0
- package/dist/outbox-DNuc4kZp.d.ts.map +1 -0
- package/dist/outbox.d.ts +2 -0
- package/dist/outbox.js +326 -0
- package/dist/outbox.js.map +1 -0
- package/dist/patchValues.d.ts +10 -0
- package/dist/patchValues.d.ts.map +1 -0
- package/dist/patchValues.js +57 -0
- package/dist/patchValues.js.map +1 -0
- package/dist/persistence-CCMnvg7-.d.ts +114 -0
- package/dist/persistence-CCMnvg7-.d.ts.map +1 -0
- package/dist/persistence.d.ts +2 -0
- package/dist/persistence.js +205 -0
- package/dist/persistence.js.map +1 -0
- package/dist/placement-jZdsIpmj.d.ts +50 -0
- package/dist/placement-jZdsIpmj.d.ts.map +1 -0
- package/dist/placement.d.ts +2 -0
- package/dist/placement.js +116 -0
- package/dist/placement.js.map +1 -0
- package/dist/plan-D9cftxC_.d.ts +35 -0
- package/dist/plan-D9cftxC_.d.ts.map +1 -0
- package/dist/plan.d.ts +2 -0
- package/dist/plan.js +100 -0
- package/dist/plan.js.map +1 -0
- package/dist/policy-DsWHmIAq.d.ts +42 -0
- package/dist/policy-DsWHmIAq.d.ts.map +1 -0
- package/dist/policy.d.ts +2 -0
- package/dist/policy.js +32 -0
- package/dist/policy.js.map +1 -0
- package/dist/{port.d.mts → port.d.ts} +3 -2
- package/dist/port.d.ts.map +1 -0
- package/dist/{port.mjs → port.js} +3 -2
- package/dist/port.js.map +1 -0
- package/dist/query-BuIU93xA.d.ts +141 -0
- package/dist/query-BuIU93xA.d.ts.map +1 -0
- package/dist/query.d.ts +2 -0
- package/dist/query.js +0 -0
- package/dist/query.js.map +1 -0
- package/dist/record.d.ts +11 -0
- package/dist/record.d.ts.map +1 -0
- package/dist/record.js +21 -0
- package/dist/record.js.map +1 -0
- package/dist/relation-BLBGOZom.d.ts +59 -0
- package/dist/relation-BLBGOZom.d.ts.map +1 -0
- package/dist/relation.d.ts +2 -0
- package/dist/relation.js +93 -0
- package/dist/relation.js.map +1 -0
- package/dist/remoteData-mK1suYi1.d.ts +148 -0
- package/dist/remoteData-mK1suYi1.d.ts.map +1 -0
- package/dist/remoteData.d.ts +2 -0
- package/dist/remoteData.js +179 -0
- package/dist/remoteData.js.map +1 -0
- package/dist/reporting.d.ts +12 -0
- package/dist/reporting.d.ts.map +1 -0
- package/dist/reporting.js +45 -0
- package/dist/reporting.js.map +1 -0
- package/dist/requirement-D57S2LpH.d.ts +64 -0
- package/dist/requirement-D57S2LpH.d.ts.map +1 -0
- package/dist/requirement.d.ts +2 -0
- package/dist/requirement.js +114 -0
- package/dist/requirement.js.map +1 -0
- package/dist/resume-Ct5R6yli.d.ts +92 -0
- package/dist/resume-Ct5R6yli.d.ts.map +1 -0
- package/dist/resume.d.ts +2 -0
- package/dist/resume.js +222 -0
- package/dist/resume.js.map +1 -0
- package/dist/retain-BfNTvBRb.d.ts +48 -0
- package/dist/retain-BfNTvBRb.d.ts.map +1 -0
- package/dist/retain.d.ts +2 -0
- package/dist/retain.js +107 -0
- package/dist/retain.js.map +1 -0
- package/dist/selection.d.ts +2 -0
- package/dist/selection.js +1 -0
- package/dist/shown.d.ts +20 -0
- package/dist/shown.d.ts.map +1 -0
- package/dist/shown.js +40 -0
- package/dist/shown.js.map +1 -0
- package/dist/sse-C7LhdbiQ.d.ts +17 -0
- package/dist/sse-C7LhdbiQ.d.ts.map +1 -0
- package/dist/sse.d.ts +2 -0
- package/dist/sse.js +127 -0
- package/dist/sse.js.map +1 -0
- package/dist/store-ah2qjcJW.d.ts +100 -0
- package/dist/store-ah2qjcJW.d.ts.map +1 -0
- package/dist/store.d.ts +2 -0
- package/dist/store.js +218 -0
- package/dist/store.js.map +1 -0
- package/dist/testing.d.ts +133 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +0 -0
- package/dist/testing.js.map +1 -0
- package/dist/why-kgp_Wr3z.d.ts +483 -0
- package/dist/why-kgp_Wr3z.d.ts.map +1 -0
- package/dist/why.d.ts +2 -0
- package/dist/why.js +293 -0
- package/dist/why.js.map +1 -0
- package/dist/whyReasons.d.ts +42 -0
- package/dist/whyReasons.d.ts.map +1 -0
- package/dist/whyReasons.js +211 -0
- package/dist/whyReasons.js.map +1 -0
- package/dist/wire-DthPcTM6.d.ts +787 -0
- package/dist/wire-DthPcTM6.d.ts.map +1 -0
- package/dist/wire.d.ts +2 -0
- package/dist/wire.js +329 -0
- package/dist/wire.js.map +1 -0
- package/package.json +19 -14
- package/dist/index-BEvVAL76.d.mts +0 -4097
- package/dist/index-BEvVAL76.d.mts.map +0 -1
- package/dist/index.d.mts +0 -2
- package/dist/index.mjs +0 -1
- package/dist/port.d.mts.map +0 -1
- package/dist/port.mjs.map +0 -1
- package/dist/src-BDqn7Ocr.mjs +0 -6344
- package/dist/src-BDqn7Ocr.mjs.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entity-CgsG5Uel.d.ts","names":["EntityRef","EntityRef","Entity"],"sources":["../src/selection.ts","../src/entity.ts"],"mappings":"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"}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { own, put } from "./record.js";
|
|
2
|
+
import { RelationAnnotation, RelationEntityAnnotation, aliasedField, isRefPage, refParts, relationAlias, relationShape, targetsOf } from "./relation.js";
|
|
3
|
+
import { entityKey, isFieldStale, isFieldUnavailable, isTombstone, readField } from "./store.js";
|
|
4
|
+
import { Option, Schema, SchemaGetter } from "effect";
|
|
5
|
+
import * as Domain from "foldkit-entity";
|
|
6
|
+
// src/selection.ts
|
|
7
|
+
/**
|
|
8
|
+
* Selections: the fields a Surface picks of an entity (nested through
|
|
9
|
+
* relations, paged through connections), the requirement graph they imply,
|
|
10
|
+
* and their assembly from the normalized store.
|
|
11
|
+
*/
|
|
12
|
+
const pageSchemas = /* @__PURE__ */ new WeakMap();
|
|
13
|
+
const pageSchema = (item) => {
|
|
14
|
+
const cached = pageSchemas.get(item);
|
|
15
|
+
if (cached !== void 0) return cached;
|
|
16
|
+
const built = Schema.Struct({
|
|
17
|
+
items: Schema.Array(item),
|
|
18
|
+
hasNext: Schema.Boolean,
|
|
19
|
+
hasPrevious: Schema.Boolean
|
|
20
|
+
});
|
|
21
|
+
pageSchemas.set(item, built);
|
|
22
|
+
return built;
|
|
23
|
+
};
|
|
24
|
+
/** The requirement a nested selection contributes for its relation's target. */
|
|
25
|
+
const relationOf = (selection) => ({
|
|
26
|
+
entity: selection.entity,
|
|
27
|
+
fields: selection.fields,
|
|
28
|
+
...selection.connections === void 0 ? {} : { windows: selection.connections },
|
|
29
|
+
...selection.relations === void 0 ? {} : { relations: selection.relations }
|
|
30
|
+
});
|
|
31
|
+
const fromEntitySelections = /* @__PURE__ */ new WeakMap();
|
|
32
|
+
const Selection = {
|
|
33
|
+
/**
|
|
34
|
+
* The Remote Selection of a `foldkit-entity` Selection: the same members, read
|
|
35
|
+
* from Remote's store. `true` on a relation reads its refs, as it does here.
|
|
36
|
+
*/
|
|
37
|
+
from: (selection) => {
|
|
38
|
+
const compiled = fromEntitySelections.get(selection);
|
|
39
|
+
if (compiled !== void 0) return compiled;
|
|
40
|
+
const spec = {};
|
|
41
|
+
for (const [key, selected] of Object.entries(selection.members)) put(spec, key, selected === true ? true : Domain.SelectionPageTypeId in selected ? Selection.connection(Entity$1.from(selected.selection.entity), selected.window, Selection.from(selected.selection)) : Selection.from(selected));
|
|
42
|
+
const made = Selection.make(Entity$1.from(selection.entity), spec);
|
|
43
|
+
fromEntitySelections.set(selection, made);
|
|
44
|
+
return made;
|
|
45
|
+
},
|
|
46
|
+
/**
|
|
47
|
+
* The fields to read of `entity`. A nested `Selection` on a relation field
|
|
48
|
+
* reads through the ref (or refs, or page of refs) the field holds into the
|
|
49
|
+
* target's fields; a scalar field cannot take one.
|
|
50
|
+
*/
|
|
51
|
+
make: (entity, selection) => {
|
|
52
|
+
if (Object.keys(selection).length === 0) throw new Error(`Selection.make: a selection of "${entity.name}" picks at least one field`);
|
|
53
|
+
const picked = {};
|
|
54
|
+
const names = [];
|
|
55
|
+
const connections = {};
|
|
56
|
+
const relations = {};
|
|
57
|
+
for (const key of Object.keys(selection)) {
|
|
58
|
+
const choice = selection[key];
|
|
59
|
+
if (choice === true) {
|
|
60
|
+
put(picked, key, entity.fields[key]);
|
|
61
|
+
names.push(key);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const nested = choice;
|
|
65
|
+
const shape = relationShape(entity.fields[key]);
|
|
66
|
+
if (shape === void 0) throw new Error(`Selection.make: "${key}" on "${entity.name}" is not a relation field, so it cannot take a nested selection`);
|
|
67
|
+
if (nested.entity !== shape.entity) throw new Error(`Selection.make: "${key}" on "${entity.name}" refers to "${shape.entity}", not "${nested.entity}"`);
|
|
68
|
+
const nullable = (codec) => shape.nullable ? Schema.NullOr(codec) : codec;
|
|
69
|
+
if (nested.window !== void 0) {
|
|
70
|
+
const name = shape.kind === "many" ? relationAlias(key, nested.window) : key;
|
|
71
|
+
names.push(name);
|
|
72
|
+
put(connections, name, nested.window);
|
|
73
|
+
put(picked, key, nullable(nested.schema));
|
|
74
|
+
if (nested.fields.length > 0) put(relations, name, relationOf(nested));
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
names.push(key);
|
|
78
|
+
const item = nested.schema;
|
|
79
|
+
const read = shape.kind === "many" ? Schema.Array(item) : shape.kind === "page" ? pageSchema(item) : item;
|
|
80
|
+
put(picked, key, nullable(read));
|
|
81
|
+
put(relations, key, relationOf(nested));
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
entity: entity.name,
|
|
85
|
+
fields: names,
|
|
86
|
+
schema: Schema.Struct(picked),
|
|
87
|
+
...Object.keys(connections).length === 0 ? {} : { connections },
|
|
88
|
+
...Object.keys(relations).length === 0 ? {} : { relations }
|
|
89
|
+
};
|
|
90
|
+
},
|
|
91
|
+
/**
|
|
92
|
+
* A paginated relation: one page of refs to `entity` with a window, or, with
|
|
93
|
+
* a nested selection, one page of targets assembled through it.
|
|
94
|
+
*/
|
|
95
|
+
connection: ((entity, window, selection) => {
|
|
96
|
+
if (selection !== void 0 && selection.entity !== entity.name) throw new Error(`Selection.connection: a page of "${entity.name}" cannot select "${selection.entity}"`);
|
|
97
|
+
return {
|
|
98
|
+
entity: entity.name,
|
|
99
|
+
fields: selection?.fields ?? [],
|
|
100
|
+
schema: selection === void 0 ? Entity$1.refPage(entity) : pageSchema(selection.schema),
|
|
101
|
+
window,
|
|
102
|
+
...selection?.connections === void 0 ? {} : { connections: selection.connections },
|
|
103
|
+
...selection?.relations === void 0 ? {} : { relations: selection.relations }
|
|
104
|
+
};
|
|
105
|
+
})
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Reads an entity's selected fields out of the store, following each nested
|
|
109
|
+
* relation into its targets. `undefined` means some field, at any depth, is
|
|
110
|
+
* not present yet. A tombstoned target reads as `null` (or is dropped from a
|
|
111
|
+
* list), so the Selection's codec decides whether that is a failure.
|
|
112
|
+
*/
|
|
113
|
+
const assemble = (store, key, requirement) => {
|
|
114
|
+
const values = {};
|
|
115
|
+
let refreshing = false;
|
|
116
|
+
for (const field of requirement.fields) {
|
|
117
|
+
const value = readField(store, key, field);
|
|
118
|
+
if (Option.isNone(value)) return void 0;
|
|
119
|
+
refreshing ||= isFieldStale(store, key, field);
|
|
120
|
+
const relation = own(requirement.relations, field);
|
|
121
|
+
if (relation === void 0) {
|
|
122
|
+
put(values, aliasedField(field), value.value);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const nested = assembleRelation(store, value.value, relation);
|
|
126
|
+
if (nested === void 0) return void 0;
|
|
127
|
+
put(values, aliasedField(field), nested.values);
|
|
128
|
+
refreshing ||= nested.refreshing;
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
values,
|
|
132
|
+
refreshing
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
/**
|
|
136
|
+
* The first field `requirement` reads, at any depth, that the server settled
|
|
137
|
+
* without a value, as `Entity:id.field`. A read that consults it says why it
|
|
138
|
+
* cannot assemble rather than waiting on a field nothing will fetch.
|
|
139
|
+
*/
|
|
140
|
+
const unavailableOf = (store, key, requirement) => {
|
|
141
|
+
for (const field of requirement.fields) {
|
|
142
|
+
if (isFieldUnavailable(store, key, field)) return `${key}.${field}`;
|
|
143
|
+
const relation = requirement.relations?.[field];
|
|
144
|
+
if (relation === void 0) continue;
|
|
145
|
+
const value = readField(store, key, field);
|
|
146
|
+
if (Option.isNone(value) || value.value === null || value.value === void 0) continue;
|
|
147
|
+
for (const ref of targetsOf(value.value, relation)) {
|
|
148
|
+
const found = unavailableOf(store, entityKey(ref.entity, ref.id), relation);
|
|
149
|
+
if (found !== void 0) return found;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
const assembleTarget = (store, ref, relation) => {
|
|
154
|
+
const key = entityKey(ref.entity, ref.id);
|
|
155
|
+
return isTombstone(store, key) ? "absent" : assemble(store, key, relation);
|
|
156
|
+
};
|
|
157
|
+
/** Assembles the targets a relation value refers to, in the value's own shape. */
|
|
158
|
+
const assembleRelation = (store, value, relation) => {
|
|
159
|
+
if (value === null || value === void 0) return {
|
|
160
|
+
values: null,
|
|
161
|
+
refreshing: false
|
|
162
|
+
};
|
|
163
|
+
const targets = [];
|
|
164
|
+
let refreshing = false;
|
|
165
|
+
for (const ref of targetsOf(value, relation)) {
|
|
166
|
+
const target = assembleTarget(store, ref, relation);
|
|
167
|
+
if (target === void 0) return void 0;
|
|
168
|
+
if (target === "absent") continue;
|
|
169
|
+
targets.push(target.values);
|
|
170
|
+
refreshing ||= target.refreshing;
|
|
171
|
+
}
|
|
172
|
+
if (typeof value === "string") return {
|
|
173
|
+
values: targets.length === 0 ? null : targets[0],
|
|
174
|
+
refreshing
|
|
175
|
+
};
|
|
176
|
+
if (isRefPage(value)) return {
|
|
177
|
+
values: {
|
|
178
|
+
items: targets,
|
|
179
|
+
hasNext: value.hasNext,
|
|
180
|
+
hasPrevious: value.hasPrevious
|
|
181
|
+
},
|
|
182
|
+
refreshing
|
|
183
|
+
};
|
|
184
|
+
return {
|
|
185
|
+
values: targets,
|
|
186
|
+
refreshing
|
|
187
|
+
};
|
|
188
|
+
};
|
|
189
|
+
// src/entity.ts
|
|
190
|
+
/**
|
|
191
|
+
* Entities: the descriptor an application declares, refs, ref pages, and
|
|
192
|
+
* patches, with the codecs that carry refs on the wire.
|
|
193
|
+
*/
|
|
194
|
+
const refCodec = (entity) => Schema.Struct({
|
|
195
|
+
entity: Schema.String,
|
|
196
|
+
id: Schema.String
|
|
197
|
+
}).pipe(Schema.encodeTo(Schema.String, {
|
|
198
|
+
decode: SchemaGetter.transform(refParts),
|
|
199
|
+
encode: SchemaGetter.transform((ref) => entityKey(ref.entity, ref.id))
|
|
200
|
+
})).annotate({
|
|
201
|
+
[RelationAnnotation]: "one",
|
|
202
|
+
[RelationEntityAnnotation]: entity
|
|
203
|
+
});
|
|
204
|
+
const refPageCodec = (entity) => Schema.Struct({
|
|
205
|
+
refs: Schema.Array(refCodec(entity)),
|
|
206
|
+
hasNext: Schema.Boolean,
|
|
207
|
+
hasPrevious: Schema.Boolean
|
|
208
|
+
}).annotate({
|
|
209
|
+
[RelationAnnotation]: "page",
|
|
210
|
+
[RelationEntityAnnotation]: entity
|
|
211
|
+
});
|
|
212
|
+
const descriptors = /* @__PURE__ */ new WeakMap();
|
|
213
|
+
const relationField = (relation) => {
|
|
214
|
+
const ref = refCodec(relation.target().name);
|
|
215
|
+
return relation.cardinality === "many" ? Schema.Array(ref) : relation.optional ? Schema.NullOr(ref) : ref;
|
|
216
|
+
};
|
|
217
|
+
const Entity$1 = {
|
|
218
|
+
/**
|
|
219
|
+
* The Remote descriptor of a `foldkit-entity` Entity. Relations declared with
|
|
220
|
+
* `Entity.relate` become ref fields, so the store, the planner, and the server
|
|
221
|
+
* treat it as they treat an `Entity.make` descriptor.
|
|
222
|
+
*/
|
|
223
|
+
from: (entity) => {
|
|
224
|
+
const cached = descriptors.get(entity);
|
|
225
|
+
if (cached !== void 0) return cached;
|
|
226
|
+
const members = entity.members;
|
|
227
|
+
if (members.id?._tag !== "Field") throw new Error(`Entity.from: "${entity.name}" needs an "id" field for Remote to key it by`);
|
|
228
|
+
const fields = {};
|
|
229
|
+
for (const [key, member] of Object.entries(members)) put(fields, key, member._tag === "Relation" ? relationField(member) : member.schema);
|
|
230
|
+
const descriptor = Entity$1.make(entity.name, Schema.Struct(fields));
|
|
231
|
+
descriptors.set(entity, descriptor);
|
|
232
|
+
return descriptor;
|
|
233
|
+
},
|
|
234
|
+
make: (name, schema) => {
|
|
235
|
+
const reserved = Object.keys(schema.fields).find((field) => field.includes("@"));
|
|
236
|
+
if (reserved !== void 0) throw new Error(`Entity.make: field "${reserved}" of "${name}" contains "@", which names a page of a relation`);
|
|
237
|
+
const descriptor = {
|
|
238
|
+
name,
|
|
239
|
+
schema,
|
|
240
|
+
fields: schema.fields,
|
|
241
|
+
ref: (id) => ({
|
|
242
|
+
entity: name,
|
|
243
|
+
id: String(id)
|
|
244
|
+
}),
|
|
245
|
+
select: (selection) => Selection.make(descriptor, selection),
|
|
246
|
+
patch: (id, values) => ({
|
|
247
|
+
entity: name,
|
|
248
|
+
id: String(id),
|
|
249
|
+
values
|
|
250
|
+
})
|
|
251
|
+
};
|
|
252
|
+
return descriptor;
|
|
253
|
+
},
|
|
254
|
+
/** A relation to a known entity, decoded as a reference. */
|
|
255
|
+
ref: (entity) => refCodec(entity.name),
|
|
256
|
+
/** A relation by name, for recursive or forward references. */
|
|
257
|
+
refTo: (name) => refCodec(name),
|
|
258
|
+
/**
|
|
259
|
+
* The wire key a relation field carries (`"Entity:id"`). Adapters that read a
|
|
260
|
+
* foreign key emit this so the ref codec can decode it on the client.
|
|
261
|
+
*/
|
|
262
|
+
refKey: (ref) => entityKey(ref.entity, ref.id),
|
|
263
|
+
/** Splits a ref key back into its entity and id. */
|
|
264
|
+
refParts,
|
|
265
|
+
/** A relation value of one authoritative page of refs. */
|
|
266
|
+
refPage: (entity) => refPageCodec(entity.name),
|
|
267
|
+
/**
|
|
268
|
+
* A normalized patch for an entity, typed against its fields' wire shape:
|
|
269
|
+
* write a relation as its ref key (`Entity.refKey`), as the server does.
|
|
270
|
+
*/
|
|
271
|
+
patch: (ref, patch) => ({
|
|
272
|
+
entity: ref.entity,
|
|
273
|
+
id: ref.id,
|
|
274
|
+
values: patch
|
|
275
|
+
})
|
|
276
|
+
};
|
|
277
|
+
export { relationOf as a, pageSchema as i, Selection as n, unavailableOf as o, assemble as r, Entity$1 as t };
|
|
278
|
+
|
|
279
|
+
//# sourceMappingURL=entity-DuDxaa3n.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entity-DuDxaa3n.js","names":["Entity","Entity"],"sources":["../src/selection.ts","../src/entity.ts"],"sourcesContent":["/**\n * Selections: the fields a Surface picks of an entity (nested through\n * relations, paged through connections), the requirement graph they imply,\n * and their assembly from the normalized store.\n */\nimport { Option, Schema } from 'effect'\nimport * as Domain from 'foldkit-entity'\nimport type { RelationRequirement } from './requirement.js'\nimport {\n Entity,\n type AnySchema,\n type EntityDescriptor,\n type EntityRef,\n type RefPage,\n} from './entity.js'\nimport type { QueryWindow } from './query.js'\nimport { own, put } from './record.js'\nimport { aliasedField, isRefPage, relationAlias, relationShape, targetsOf } from './relation.js'\nimport {\n entityKey,\n isFieldStale,\n isFieldUnavailable,\n isTombstone,\n readField,\n type EntityStore,\n} from './store.js'\n\n/** One page of a paginated relation with each target assembled through a nested selection. */\nexport interface Page<Item> {\n readonly items: ReadonlyArray<Item>\n readonly hasNext: boolean\n readonly hasPrevious: boolean\n}\n\n/**\n * The fields a projection reads of one entity, plus the slice it reads of each\n * relation's target. `Shape` tells a parent selection whether this one is a\n * paginated relation (`connection`) or a plain entity slice (`entity`).\n */\nexport interface Selection<\n Value,\n Name extends string = string,\n Shape extends 'entity' | 'connection' = 'entity' | 'connection',\n> {\n readonly entity: Name\n readonly fields: readonly string[]\n /** A pure codec: entity fields carry no decoding or encoding services. */\n readonly schema: Schema.Codec<Value, unknown, never, never>\n /** Pagination windows for nested relation fields, keyed by field name. */\n readonly connections?: Readonly<Record<string, QueryWindow>> | undefined\n /** The slice required of each nested relation's target, keyed by field name. */\n readonly relations?: Readonly<Record<string, RelationRequirement>> | undefined\n /** Present when this selection is itself a paginated relation. */\n readonly window?: QueryWindow | undefined\n /** Phantom: see `Shape`. */\n readonly shape?: Shape | undefined\n}\n\n/** Flattens a mapped type so hovers show the picked fields, not the machinery. */\ntype Simplify<T> = T extends infer O ? { readonly [K in keyof O]: O[K] } : never\n\n/** The value a Selection reads: `Selection.Value<typeof ProjectCard>`. */\nexport type SelectionValueOf<S> = S extends Selection<infer V, any, any> ? V : never\n\n/** The nested selections a relation field admits: of its target entity, in the shapes its value takes. */\ntype NestedFor<Field> =\n Exclude<Field, null | undefined> extends ReadonlyArray<EntityRef<infer Name, any>>\n ? Selection<unknown, Name, 'entity' | 'connection'>\n : Exclude<Field, null | undefined> extends RefPage<infer Name, any>\n ? Selection<unknown, Name, 'entity' | 'connection'>\n : Exclude<Field, null | undefined> extends EntityRef<infer Name, any>\n ? Selection<unknown, Name, 'entity'>\n : never\n\n/** What a selection of an entity with fields `F` may pick: `true` per scalar, a nested selection per relation. */\nexport type SelectionOf<F extends Schema.Struct.Fields> = {\n readonly [K in keyof F]?: true | NestedFor<Schema.Schema.Type<F[K]>>\n}\n\n/** `T | null` when the field admits `null` or `undefined` (both assemble to `null`). */\ntype Nullable<Field, T> = [Extract<Field, null | undefined>] extends [never] ? T : T | null\n\n/** A nested selection's value takes the shape of the field it selects through. */\ntype NestedValue<Field, Sel> =\n Sel extends Selection<infer V, string, 'connection'>\n ? Nullable<Field, V>\n : Sel extends Selection<infer V, string, 'entity'>\n ? Nullable<\n Field,\n Exclude<Field, null | undefined> extends ReadonlyArray<EntityRef<any, any>>\n ? ReadonlyArray<V>\n : Exclude<Field, null | undefined> extends RefPage<any, any>\n ? Page<V>\n : V\n >\n : never\n\n/** The value a selection `Sel` of fields `F` reads. */\nexport type SelectionValue<F extends Schema.Struct.Fields, Sel> = Simplify<{\n readonly [K in keyof Sel & keyof F]: Sel[K] extends true\n ? Schema.Schema.Type<F[K]>\n : NestedValue<Schema.Schema.Type<F[K]>, Sel[K]>\n}>\n\n// Built once per item schema, as `remoteDataSchema` is: a query projection is\n// rebuilt on every Model change.\nconst pageSchemas = new WeakMap<object, AnySchema>()\n\nexport const pageSchema = (item: AnySchema): AnySchema => {\n const cached = pageSchemas.get(item)\n if (cached !== undefined) return cached\n const built = Schema.Struct({\n items: Schema.Array(item),\n hasNext: Schema.Boolean,\n hasPrevious: Schema.Boolean,\n }) as unknown as AnySchema\n pageSchemas.set(item, built)\n return built\n}\n\n/** The requirement a nested selection contributes for its relation's target. */\nexport const relationOf = (selection: Selection<unknown>): RelationRequirement => ({\n entity: selection.entity,\n fields: selection.fields,\n ...(selection.connections === undefined ? {} : { windows: selection.connections }),\n ...(selection.relations === undefined ? {} : { relations: selection.relations }),\n})\n\nconst fromEntitySelections = new WeakMap<object, Selection<unknown>>()\n\nexport const Selection = {\n /**\n * The Remote Selection of a `foldkit-entity` Selection: the same members, read\n * from Remote's store. `true` on a relation reads its refs, as it does here.\n */\n from: <Name extends string, S extends Schema.Constraint>(\n selection: Domain.Selection<Name, unknown, S>,\n ): Selection<S['Type'], Name, 'entity'> => {\n // Compiled once per Entity Selection: `Data.get` and `Data.query` are called\n // with one on every render, and Remote caches its reads by the Selection's identity.\n const compiled = fromEntitySelections.get(selection)\n if (compiled !== undefined) return compiled as never\n const spec: Record<string, true | Selection<unknown>> = {}\n for (const [key, selected] of Object.entries(\n selection.members as Readonly<\n Record<\n string,\n | true\n | Domain.Selection<string, unknown, Schema.Constraint>\n | Domain.SelectionPage<string, Schema.Constraint>\n >\n >,\n ))\n put(\n spec,\n key,\n selected === true\n ? true\n : Domain.SelectionPageTypeId in selected\n ? // A page of a `many` relation is Remote's relation connection.\n Selection.connection(\n Entity.from(selected.selection.entity as never),\n selected.window,\n Selection.from(selected.selection) as never,\n )\n : Selection.from(selected),\n )\n const made = Selection.make(Entity.from(selection.entity as never), spec as never)\n fromEntitySelections.set(selection, made as Selection<unknown>)\n return made as never\n },\n\n /**\n * The fields to read of `entity`. A nested `Selection` on a relation field\n * reads through the ref (or refs, or page of refs) the field holds into the\n * target's fields; a scalar field cannot take one.\n */\n make: <Name extends string, F extends Schema.Struct.Fields, const Sel extends SelectionOf<F>>(\n entity: EntityDescriptor<Name, F>,\n selection: Sel,\n ): Selection<SelectionValue<F, Sel>, Name, 'entity'> => {\n if (Object.keys(selection).length === 0) {\n throw new Error(`Selection.make: a selection of \"${entity.name}\" picks at least one field`)\n }\n const picked: Record<string, AnySchema> = {}\n // The names read: a field's own, or the alias a page of a list is read under.\n const names: string[] = []\n const connections: Record<string, QueryWindow> = {}\n const relations: Record<string, RelationRequirement> = {}\n for (const key of Object.keys(selection)) {\n const choice = (selection as Record<string, unknown>)[key]\n if (choice === true) {\n put(picked, key, entity.fields[key] as AnySchema)\n names.push(key)\n continue\n }\n const nested = choice as Selection<unknown>\n const shape = relationShape(entity.fields[key] as Schema.Top)\n if (shape === undefined) {\n throw new Error(\n `Selection.make: \"${key}\" on \"${entity.name}\" is not a relation field, so it cannot take a nested selection`,\n )\n }\n if (nested.entity !== shape.entity) {\n throw new Error(\n `Selection.make: \"${key}\" on \"${entity.name}\" refers to \"${shape.entity}\", not \"${nested.entity}\"`,\n )\n }\n const nullable = (codec: AnySchema): AnySchema =>\n shape.nullable ? (Schema.NullOr(codec) as unknown as AnySchema) : codec\n if (nested.window !== undefined) {\n // A connection: the nested selection already carries its page codec. A page\n // of a whole list is read under its alias, so the list can be read beside it;\n // a field that is always a page has nothing to be beside.\n const name = shape.kind === 'many' ? relationAlias(key, nested.window) : key\n names.push(name)\n put(connections, name, nested.window)\n put(picked, key, nullable(nested.schema as AnySchema))\n if (nested.fields.length > 0) put(relations, name, relationOf(nested))\n continue\n }\n names.push(key)\n const item = nested.schema as AnySchema\n const read =\n shape.kind === 'many'\n ? (Schema.Array(item) as unknown as AnySchema)\n : shape.kind === 'page'\n ? pageSchema(item)\n : item\n put(picked, key, nullable(read))\n put(relations, key, relationOf(nested))\n }\n return {\n entity: entity.name,\n fields: names,\n schema: Schema.Struct(picked) as unknown as Schema.Codec<\n SelectionValue<F, Sel>,\n unknown,\n never,\n never\n >,\n ...(Object.keys(connections).length === 0 ? {} : { connections }),\n ...(Object.keys(relations).length === 0 ? {} : { relations }),\n }\n },\n\n /**\n * A paginated relation: one page of refs to `entity` with a window, or, with\n * a nested selection, one page of targets assembled through it.\n */\n connection: (<Name extends string, F extends Schema.Struct.Fields, Item = never>(\n entity: EntityDescriptor<Name, F>,\n window: QueryWindow,\n selection?: Selection<Item, Name, 'entity'>,\n ): Selection<RefPage<Name, F> | Page<Item>, Name, 'connection'> => {\n if (selection !== undefined && selection.entity !== entity.name) {\n throw new Error(\n `Selection.connection: a page of \"${entity.name}\" cannot select \"${selection.entity}\"`,\n )\n }\n return {\n entity: entity.name,\n fields: selection?.fields ?? [],\n schema: (selection === undefined\n ? Entity.refPage(entity)\n : pageSchema(selection.schema as AnySchema)) as unknown as Schema.Codec<\n RefPage<Name, F> | Page<Item>,\n unknown,\n never,\n never\n >,\n window,\n ...(selection?.connections === undefined ? {} : { connections: selection.connections }),\n ...(selection?.relations === undefined ? {} : { relations: selection.relations }),\n }\n }) as {\n <Name extends string, F extends Schema.Struct.Fields>(\n entity: EntityDescriptor<Name, F>,\n window: QueryWindow,\n ): Selection<RefPage<Name, F>, Name, 'connection'>\n <Name extends string, F extends Schema.Struct.Fields, Item>(\n entity: EntityDescriptor<Name, F>,\n window: QueryWindow,\n selection: Selection<Item, NoInfer<Name>, 'entity'>,\n ): Selection<Page<Item>, Name, 'connection'>\n },\n}\n\ninterface Assembled {\n readonly values: unknown\n readonly refreshing: boolean\n}\n\n/**\n * Reads an entity's selected fields out of the store, following each nested\n * relation into its targets. `undefined` means some field, at any depth, is\n * not present yet. A tombstoned target reads as `null` (or is dropped from a\n * list), so the Selection's codec decides whether that is a failure.\n */\nexport const assemble = (\n store: EntityStore,\n key: string,\n requirement: RelationRequirement,\n): Assembled | undefined => {\n const values: Record<string, unknown> = {}\n let refreshing = false\n for (const field of requirement.fields) {\n const value = readField(store, key, field)\n if (Option.isNone(value)) return undefined\n refreshing ||= isFieldStale(store, key, field)\n // A page read under an alias is the value of the field the alias reads.\n const relation = own(requirement.relations, field)\n if (relation === undefined) {\n put(values, aliasedField(field), value.value)\n continue\n }\n const nested = assembleRelation(store, value.value, relation)\n if (nested === undefined) return undefined\n put(values, aliasedField(field), nested.values)\n refreshing ||= nested.refreshing\n }\n return { values, refreshing }\n}\n\n/**\n * The first field `requirement` reads, at any depth, that the server settled\n * without a value, as `Entity:id.field`. A read that consults it says why it\n * cannot assemble rather than waiting on a field nothing will fetch.\n */\nexport const unavailableOf = (\n store: EntityStore,\n key: string,\n requirement: RelationRequirement,\n): string | undefined => {\n for (const field of requirement.fields) {\n if (isFieldUnavailable(store, key, field)) return `${key}.${field}`\n const relation = requirement.relations?.[field]\n if (relation === undefined) continue\n const value = readField(store, key, field)\n if (Option.isNone(value) || value.value === null || value.value === undefined) continue\n for (const ref of targetsOf(value.value, relation)) {\n const found = unavailableOf(store, entityKey(ref.entity, ref.id), relation)\n if (found !== undefined) return found\n }\n }\n return undefined\n}\n\nconst assembleTarget = (\n store: EntityStore,\n ref: { readonly entity: string; readonly id: string },\n relation: RelationRequirement,\n): Assembled | 'absent' | undefined => {\n const key = entityKey(ref.entity, ref.id)\n return isTombstone(store, key) ? 'absent' : assemble(store, key, relation)\n}\n\n/** Assembles the targets a relation value refers to, in the value's own shape. */\nconst assembleRelation = (\n store: EntityStore,\n value: unknown,\n relation: RelationRequirement,\n): Assembled | undefined => {\n if (value === null || value === undefined) return { values: null, refreshing: false }\n const targets: unknown[] = []\n let refreshing = false\n for (const ref of targetsOf(value, relation)) {\n const target = assembleTarget(store, ref, relation)\n if (target === undefined) return undefined\n if (target === 'absent') continue\n targets.push(target.values)\n refreshing ||= target.refreshing\n }\n if (typeof value === 'string') {\n return { values: targets.length === 0 ? null : targets[0], refreshing }\n }\n if (isRefPage(value)) {\n return {\n values: { items: targets, hasNext: value.hasNext, hasPrevious: value.hasPrevious },\n refreshing,\n }\n }\n return { values: targets, refreshing }\n}\n","/**\n * Entities: the descriptor an application declares, refs, ref pages, and\n * patches, with the codecs that carry refs on the wire.\n */\nimport { Schema, SchemaGetter } from 'effect'\nimport type * as Domain from 'foldkit-entity'\nimport {\n RelationAnnotation,\n RelationEntityAnnotation,\n refParts,\n RELATION_ALIAS,\n} from './relation.js'\n// selection.ts imports this module too; both only use the other inside\n// function bodies, so the cycle is never observed at module evaluation.\nimport { put } from './record.js'\nimport { Selection, type SelectionOf, type SelectionValue } from './selection.js'\nimport { entityKey } from './store.js'\n\nexport type AnySchema = Schema.Schema<unknown>\n\ndeclare const entityRefFields: unique symbol\n\n/**\n * A normalized reference to an entity. `F` is a phantom carrying the entity's\n * fields so `Entity.patch` can type its patch; it is absent at runtime.\n */\nexport interface EntityRef<\n Name extends string,\n F extends Schema.Struct.Fields = Schema.Struct.Fields,\n> {\n readonly entity: Name\n readonly id: string\n readonly [entityRefFields]?: F\n}\n\nexport interface EntityDescriptor<Name extends string, F extends Schema.Struct.Fields> {\n readonly name: Name\n readonly schema: Schema.Struct<F>\n readonly fields: F\n // Methods, not properties: method signatures are bivariant, so a concrete\n // descriptor stays assignable to `EntityDescriptor<any, any>` (which appears\n // in every heterogeneous collection, e.g. `Remote.make`'s entities).\n ref(id: Schema.Schema.Type<F['id']>): EntityRef<Name, F>\n /** The fields a Surface reads of this entity; `Selection.make(entity, …)` with the entity as receiver. */\n select<const Sel extends SelectionOf<F>>(\n selection: Sel,\n ): Selection<SelectionValue<F, Sel>, Name, 'entity'>\n /** A patch of this entity's wire-shaped values; `Entity.patch(entity.ref(id), values)`. */\n patch(\n id: Schema.Schema.Type<F['id']>,\n values: Partial<Schema.Struct.Encoded<F>>,\n ): EntityPatch<Name, F>\n}\n\n/**\n * Relations are **references**, never inline target schemas: a ref cannot\n * reconstruct a full entity, and dereferencing is a store concern. Because the\n * target schema is not inlined, recursive relations cannot arise through the\n * schema graph.\n */\n/**\n * A ref as the wire carries it: the target's name, a colon, and its id. Typed\n * with the name, so a relation to `User` takes `'User:u1'` and not a ref to\n * some other entity, which would read as a row that does not exist.\n */\nexport type RefKey<Name extends string> = `${Name}:${string}`\n\nconst refCodec = <Name extends string, F extends Schema.Struct.Fields>(\n entity: Name,\n): Schema.Codec<EntityRef<Name, F>, RefKey<Name>> =>\n Schema.Struct({ entity: Schema.String, id: Schema.String })\n .pipe(\n Schema.encodeTo(Schema.String, {\n decode: SchemaGetter.transform(refParts),\n encode: SchemaGetter.transform((ref: EntityRef<string>) => entityKey(ref.entity, ref.id)),\n }),\n )\n .annotate({\n [RelationAnnotation]: 'one',\n [RelationEntityAnnotation]: entity,\n }) as unknown as Schema.Codec<EntityRef<Name, F>, RefKey<Name>>\n\n/**\n * A patch in wire shape: the values the store holds, so a relation is its ref\n * key (`Entity.refKey`), not an `EntityRef`.\n */\nexport interface EntityPatch<Name extends string, F extends Schema.Struct.Fields> {\n readonly entity: Name\n readonly id: string\n readonly values: Partial<Schema.Struct.Encoded<F>>\n}\n\n/** What a relation field decodes to: a ref, refs, or a page of refs, perhaps null. */\ntype IsRelation<T> = [NonNullable<T>] extends [never]\n ? false\n : [NonNullable<T>] extends [\n EntityRef<string, any> | ReadonlyArray<EntityRef<string, any>> | RefPage<string, any>,\n ]\n ? true\n : false\n\n/**\n * `Remote.patch`'s values: each field's wire shape, except a relation, which\n * takes what it decodes to (`Remote.ref(User, id)`, an array of refs, a page\n * of refs, or `null` where it is optional), never a ref key typed by hand.\n */\nexport type PatchValues<F extends Schema.Struct.Fields> = {\n readonly [K in keyof Schema.Struct.Encoded<F>]?: K extends keyof Schema.Struct.Type<F>\n ? IsRelation<Schema.Struct.Type<F>[K]> extends true\n ? Schema.Struct.Type<F>[K]\n : Schema.Struct.Encoded<F>[K]\n : Schema.Struct.Encoded<F>[K]\n}\n\n/**\n * One authoritative page of a paginated relation: refs (never inline entities)\n * plus whether more exist. The adapter does not hold segmentation state; the\n * client merges pages.\n */\nexport interface RefPage<\n Name extends string,\n F extends Schema.Struct.Fields = Schema.Struct.Fields,\n> {\n readonly refs: ReadonlyArray<EntityRef<Name, F>>\n readonly hasNext: boolean\n readonly hasPrevious: boolean\n}\n\nconst refPageCodec = <Name extends string, F extends Schema.Struct.Fields>(\n entity: Name,\n): Schema.Codec<\n RefPage<Name, F>,\n {\n readonly refs: ReadonlyArray<string>\n readonly hasNext: boolean\n readonly hasPrevious: boolean\n }\n> =>\n Schema.Struct({\n refs: Schema.Array(refCodec<Name, F>(entity)),\n hasNext: Schema.Boolean,\n hasPrevious: Schema.Boolean,\n }).annotate({\n [RelationAnnotation]: 'page',\n [RelationEntityAnnotation]: entity,\n }) as unknown as Schema.Codec<\n RefPage<Name, F>,\n {\n readonly refs: ReadonlyArray<string>\n readonly hasNext: boolean\n readonly hasPrevious: boolean\n }\n >\n\n/** The ref codec a `foldkit-entity` relation becomes: one ref, a nullable ref, or an array of refs. */\ntype RelationField<Spec> =\n Spec extends Domain.RelationSpec<infer Target, infer Cardinality, infer Optional>\n ? Cardinality extends 'many'\n ? Schema.$Array<Schema.Codec<EntityRef<Target['name']>, RefKey<Target['name']>>>\n : Optional extends true\n ? Schema.NullOr<Schema.Codec<EntityRef<Target['name']>, RefKey<Target['name']>>>\n : Schema.Codec<EntityRef<Target['name']>, RefKey<Target['name']>>\n : never\n\n/**\n * The fields Remote stores for a `foldkit-entity` Entity: its own fields, its\n * derived members (the server supplies them like any field), and each relation\n * as refs.\n */\nexport type FieldsFrom<E> =\n E extends Domain.Entity<any, infer Fields, infer Relations, infer Derived>\n ? Fields & { readonly [K in keyof Derived]: Derived[K]['schema'] } & {\n readonly [K in keyof Relations]: RelationField<Relations[K]>\n }\n : never\n\n// One descriptor per Entity version: `Selection.from` looks a Selection's Entity up again.\nconst descriptors = new WeakMap<Domain.AnyEntity, EntityDescriptor<any, any>>()\n\nconst relationField = (\n relation: Domain.EntityMember & { readonly _tag: 'Relation' },\n): AnySchema => {\n const ref = refCodec(relation.target().name) as unknown as AnySchema\n return (relation.cardinality === 'many'\n ? Schema.Array(ref)\n : relation.optional\n ? Schema.NullOr(ref)\n : ref) as unknown as AnySchema\n}\n\nexport const Entity = {\n /**\n * The Remote descriptor of a `foldkit-entity` Entity. Relations declared with\n * `Entity.relate` become ref fields, so the store, the planner, and the server\n * treat it as they treat an `Entity.make` descriptor.\n */\n from: <E extends Domain.AnyEntity>(\n entity: E & { readonly fields: { readonly id: unknown } },\n ): EntityDescriptor<E['name'], FieldsFrom<E>> => {\n const cached = descriptors.get(entity)\n if (cached !== undefined) return cached\n const members: Readonly<Record<string, Domain.EntityMember>> = entity.members\n if (members.id?._tag !== 'Field')\n throw new Error(`Entity.from: \"${entity.name}\" needs an \"id\" field for Remote to key it by`)\n const fields: Record<string, AnySchema> = {}\n for (const [key, member] of Object.entries(members))\n put(\n fields,\n key,\n member._tag === 'Relation' ? relationField(member) : (member.schema as AnySchema),\n )\n const descriptor = Entity.make(entity.name, Schema.Struct(fields) as never)\n descriptors.set(entity, descriptor)\n return descriptor as never\n },\n\n make: <\n const Name extends string,\n const F extends Schema.Struct.Fields & { readonly id: Schema.Schema<unknown> },\n >(\n name: Name,\n schema: Schema.Struct<F>,\n ): EntityDescriptor<Name, F> => {\n // A page of a relation is read as `field@size`, so a field may not look like one.\n const reserved = Object.keys(schema.fields).find(field => field.includes(RELATION_ALIAS))\n if (reserved !== undefined) {\n throw new Error(\n `Entity.make: field \"${reserved}\" of \"${name}\" contains \"${RELATION_ALIAS}\", which names a page of a relation`,\n )\n }\n const descriptor: EntityDescriptor<Name, F> = {\n name,\n schema,\n fields: schema.fields,\n ref: id => ({ entity: name, id: String(id) }) as EntityRef<Name, F>,\n select: selection => Selection.make(descriptor, selection),\n patch: (id, values) => ({ entity: name, id: String(id), values }),\n }\n return descriptor\n },\n\n /** A relation to a known entity, decoded as a reference. */\n ref: <Name extends string, F extends Schema.Struct.Fields>(\n entity: EntityDescriptor<Name, F>,\n ): Schema.Codec<EntityRef<Name, F>, RefKey<Name>> => refCodec<Name, F>(entity.name),\n\n /** A relation by name, for recursive or forward references. */\n refTo: <Name extends string>(name: Name): Schema.Codec<EntityRef<Name>, RefKey<Name>> =>\n refCodec<Name, Schema.Struct.Fields>(name),\n\n /**\n * The wire key a relation field carries (`\"Entity:id\"`). Adapters that read a\n * foreign key emit this so the ref codec can decode it on the client.\n */\n refKey: (ref: { readonly entity: string; readonly id: string }): string =>\n entityKey(ref.entity, ref.id),\n\n /** Splits a ref key back into its entity and id. */\n refParts,\n\n /** A relation value of one authoritative page of refs. */\n refPage: <Name extends string, F extends Schema.Struct.Fields>(\n entity: EntityDescriptor<Name, F>,\n ): Schema.Codec<\n RefPage<Name, F>,\n {\n readonly refs: ReadonlyArray<string>\n readonly hasNext: boolean\n readonly hasPrevious: boolean\n }\n > => refPageCodec<Name, F>(entity.name),\n\n /**\n * A normalized patch for an entity, typed against its fields' wire shape:\n * write a relation as its ref key (`Entity.refKey`), as the server does.\n */\n patch: <Name extends string, F extends Schema.Struct.Fields>(\n ref: EntityRef<Name, F>,\n patch: Partial<Schema.Struct.Encoded<F>>,\n ): EntityPatch<Name, F> => ({ entity: ref.entity, id: ref.id, values: patch }),\n}\n"],"mappings":"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"}
|
package/dist/entity.d.ts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as EntityRef, c as RefKey, i as EntityPatch, l as RefPage, n as Entity, o as FieldsFrom, r as EntityDescriptor, s as PatchValues, t as AnySchema } from "./entity-CgsG5Uel.js";
|
|
2
|
+
export { AnySchema, Entity, EntityDescriptor, EntityPatch, EntityRef, FieldsFrom, PatchValues, RefKey, RefPage };
|
package/dist/entity.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { t as Entity } from "./entity-DuDxaa3n.js";
|
package/dist/held.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { r as EntityStore } from "./store-ah2qjcJW.js";
|
|
2
|
+
import { AnyEntity, HeldRow, HeldRows } from "foldkit-entity";
|
|
3
|
+
/** The store as a holder, and the row a write leaves in it. */
|
|
4
|
+
export interface StoreHolder {
|
|
5
|
+
readonly rows: HeldRows;
|
|
6
|
+
/**
|
|
7
|
+
* The row of `entity` with `id` as the store holds it, with a write's
|
|
8
|
+
* `values` over it, and the fields it `moves` (an increment, a computed
|
|
9
|
+
* value the server works out) not known.
|
|
10
|
+
*/
|
|
11
|
+
readonly written: (entity: AnyEntity, id: string, values: Readonly<Record<string, unknown>>, moves: ReadonlyArray<string>) => HeldRow;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* `store` as `Judge` reads it. Links are kept as ref keys (`'User:ada'`);
|
|
15
|
+
* a row is read with each `one` relation as the id it points at, as
|
|
16
|
+
* `Held.rows` takes it.
|
|
17
|
+
*/
|
|
18
|
+
export declare const holderOf: (store: EntityStore) => StoreHolder;
|
|
19
|
+
//# sourceMappingURL=held.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"held.d.ts","names":[],"sources":["../src/held.ts"],"mappings":";;;iBAyBiB;WACN,MAAM;;;;;;WAMN,UACP,QAAQ,WACR,YACA,QAAQ,SAAS,0BACjB,OAAO,0BACJ;;;;;;;qBAQM,WAAQ,OAAW,gBAAc"}
|
package/dist/held.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { entityKey } from "./store.js";
|
|
2
|
+
import { Option } from "effect";
|
|
3
|
+
import { Held, idOfRef } from "foldkit-entity";
|
|
4
|
+
// src/held.ts
|
|
5
|
+
/**
|
|
6
|
+
* The store as a holder of rows (`HeldRows`), which `Judge` reads to foresee
|
|
7
|
+
* a write's rules: a field the store was not sent is unheld, a row it never
|
|
8
|
+
* read is unheld, a tombstone is known absent. It never holds every row
|
|
9
|
+
* pointing at one, so an exists over the store is unknown unless a pointing
|
|
10
|
+
* row it holds decides it.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* `store` as `Judge` reads it. Links are kept as ref keys (`'User:ada'`);
|
|
14
|
+
* a row is read with each `one` relation as the id it points at, as
|
|
15
|
+
* `Held.rows` takes it.
|
|
16
|
+
*/
|
|
17
|
+
const holderOf = (store) => {
|
|
18
|
+
const rowOf = (entity, row) => {
|
|
19
|
+
const field = (key) => {
|
|
20
|
+
if (!row.present.has(key)) return Held.unheld(`${entity.name}.${key} is not held`);
|
|
21
|
+
const value = row.values[key];
|
|
22
|
+
return (Object.hasOwn(entity.relations, key) ? entity.relations[key] : void 0)?.cardinality === "one" && value !== null && value !== void 0 ? Held.of(idOfRef(value, `${entity.name}.${key}`)) : Held.of(value);
|
|
23
|
+
};
|
|
24
|
+
return {
|
|
25
|
+
field,
|
|
26
|
+
follow: (relation) => Held.match(field(relation.key), {
|
|
27
|
+
onHeld: (link) => link === null || link === void 0 ? Held.of(Option.none()) : find(relation.target(), link),
|
|
28
|
+
onUnheld: (reason) => Held.unheld(reason)
|
|
29
|
+
}),
|
|
30
|
+
pointing: (relation) => ({
|
|
31
|
+
rows: [],
|
|
32
|
+
missing: Option.some(`the store may not hold every ${relation.owner.name} pointing through ${relation.key}`)
|
|
33
|
+
})
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
const live = (entity, id) => {
|
|
37
|
+
const entry = store[entityKey(entity.name, String(id))];
|
|
38
|
+
return entry === void 0 || entry.tombstone ? void 0 : entry;
|
|
39
|
+
};
|
|
40
|
+
const find = (entity, id) => {
|
|
41
|
+
const entry = store[entityKey(entity.name, String(id))];
|
|
42
|
+
if (entry === void 0) return Held.unheld(`${entity.name} ${String(id)} is not held`);
|
|
43
|
+
return Held.of(entry.tombstone ? Option.none() : Option.some(rowOf(entity, entry)));
|
|
44
|
+
};
|
|
45
|
+
return {
|
|
46
|
+
rows: {
|
|
47
|
+
find,
|
|
48
|
+
row: (entity, id) => Option.fromUndefinedOr(live(entity, id)).pipe(Option.map((entry) => rowOf(entity, entry))),
|
|
49
|
+
rows: (entity) => {
|
|
50
|
+
const prefix = `${entity.name}:`;
|
|
51
|
+
return Object.entries(store).flatMap(([key, entry]) => key.startsWith(prefix) && !entry.tombstone ? [rowOf(entity, entry)] : []);
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
written: (entity, id, values, moves) => {
|
|
55
|
+
const before = live(entity, id);
|
|
56
|
+
const present = /* @__PURE__ */ new Set([...before?.present ?? [], ...Object.keys(values)]);
|
|
57
|
+
for (const key of moves) present.delete(key);
|
|
58
|
+
return rowOf(entity, {
|
|
59
|
+
values: {
|
|
60
|
+
...before?.values,
|
|
61
|
+
...values
|
|
62
|
+
},
|
|
63
|
+
present
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
export { holderOf };
|
|
69
|
+
|
|
70
|
+
//# sourceMappingURL=held.js.map
|
package/dist/held.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"held.js","names":[],"sources":["../src/held.ts"],"sourcesContent":["/**\n * The store as a holder of rows (`HeldRows`), which `Judge` reads to foresee\n * a write's rules: a field the store was not sent is unheld, a row it never\n * read is unheld, a tombstone is known absent. It never holds every row\n * pointing at one, so an exists over the store is unknown unless a pointing\n * row it holds decides it.\n */\nimport { Option } from 'effect'\nimport {\n Held,\n idOfRef,\n type AnyEntity,\n type HeldRow,\n type HeldRows,\n type OneRelation,\n} from 'foldkit-entity'\nimport { entityKey, type EntityEntry, type EntityStore } from './store.js'\n\n/** One row's values: the store's, or the row a write leaves, which holds what it sets besides. */\ninterface Values {\n readonly values: Readonly<Record<string, unknown>>\n readonly present: ReadonlySet<string>\n}\n\n/** The store as a holder, and the row a write leaves in it. */\nexport interface StoreHolder {\n readonly rows: HeldRows\n /**\n * The row of `entity` with `id` as the store holds it, with a write's\n * `values` over it, and the fields it `moves` (an increment, a computed\n * value the server works out) not known.\n */\n readonly written: (\n entity: AnyEntity,\n id: string,\n values: Readonly<Record<string, unknown>>,\n moves: ReadonlyArray<string>,\n ) => HeldRow\n}\n\n/**\n * `store` as `Judge` reads it. Links are kept as ref keys (`'User:ada'`);\n * a row is read with each `one` relation as the id it points at, as\n * `Held.rows` takes it.\n */\nexport const holderOf = (store: EntityStore): StoreHolder => {\n const rowOf = (entity: AnyEntity, row: Values): HeldRow => {\n const field = (key: string): Held<unknown> => {\n if (!row.present.has(key)) return Held.unheld(`${entity.name}.${key} is not held`)\n const value = row.values[key]\n const relation = Object.hasOwn(entity.relations, key) ? entity.relations[key] : undefined\n return relation?.cardinality === 'one' && value !== null && value !== undefined\n ? Held.of(idOfRef(value, `${entity.name}.${key}`))\n : Held.of(value)\n }\n return {\n field,\n follow: (relation: OneRelation) =>\n Held.match(field(relation.key), {\n onHeld: link =>\n link === null || link === undefined\n ? Held.of(Option.none<HeldRow>())\n : find(relation.target(), link),\n onUnheld: reason => Held.unheld<Option.Option<HeldRow>>(reason),\n }),\n pointing: relation => ({\n rows: [],\n missing: Option.some(\n `the store may not hold every ${relation.owner.name} pointing through ${relation.key}`,\n ),\n }),\n }\n }\n\n const live = (entity: AnyEntity, id: unknown): EntityEntry | undefined => {\n const entry = store[entityKey(entity.name, String(id))]\n return entry === undefined || entry.tombstone ? undefined : entry\n }\n\n const find = (entity: AnyEntity, id: unknown): Held<Option.Option<HeldRow>> => {\n const entry = store[entityKey(entity.name, String(id))]\n if (entry === undefined) return Held.unheld(`${entity.name} ${String(id)} is not held`)\n return Held.of(entry.tombstone ? Option.none() : Option.some(rowOf(entity, entry)))\n }\n\n return {\n rows: {\n find,\n row: (entity, id) =>\n Option.fromUndefinedOr(live(entity, id)).pipe(Option.map(entry => rowOf(entity, entry))),\n rows: entity => {\n const prefix = `${entity.name}:`\n return Object.entries(store).flatMap(([key, entry]) =>\n key.startsWith(prefix) && !entry.tombstone ? [rowOf(entity, entry)] : [],\n )\n },\n },\n written: (entity, id, values, moves) => {\n const before = live(entity, id)\n const present = new Set([...(before?.present ?? []), ...Object.keys(values)])\n for (const key of moves) present.delete(key)\n return rowOf(entity, { values: { ...before?.values, ...values }, present })\n },\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;AA6CA,MAAa,YAAY,UAAoC;CAC3D,MAAM,SAAS,QAAmB,QAAyB;EACzD,MAAM,SAAS,QAA+B;GAC5C,IAAI,CAAC,IAAI,QAAQ,IAAI,GAAG,GAAG,OAAO,KAAK,OAAO,GAAG,OAAO,KAAK,GAAG,IAAI,aAAa;GACjF,MAAM,QAAQ,IAAI,OAAO;GAEzB,QADiB,OAAO,OAAO,OAAO,WAAW,GAAG,IAAI,OAAO,UAAU,OAAO,KAAA,EAAA,EAC/D,gBAAgB,SAAS,UAAU,QAAQ,UAAU,KAAA,IAClE,KAAK,GAAG,QAAQ,OAAO,GAAG,OAAO,KAAK,GAAG,KAAK,CAAC,IAC/C,KAAK,GAAG,KAAK;EACnB;EACA,OAAO;GACL;GACA,SAAS,aACP,KAAK,MAAM,MAAM,SAAS,GAAG,GAAG;IAC9B,SAAQ,SACN,SAAS,QAAQ,SAAS,KAAA,IACtB,KAAK,GAAG,OAAO,KAAc,CAAC,IAC9B,KAAK,SAAS,OAAO,GAAG,IAAI;IAClC,WAAU,WAAU,KAAK,OAA+B,MAAM;GAChE,CAAC;GACH,WAAU,cAAa;IACrB,MAAM,CAAC;IACP,SAAS,OAAO,KACd,gCAAgC,SAAS,MAAM,KAAK,oBAAoB,SAAS,KACnF;GACF;EACF;CACF;CAEA,MAAM,QAAQ,QAAmB,OAAyC;EACxE,MAAM,QAAQ,MAAM,UAAU,OAAO,MAAM,OAAO,EAAE,CAAC;EACrD,OAAO,UAAU,KAAA,KAAa,MAAM,YAAY,KAAA,IAAY;CAC9D;CAEA,MAAM,QAAQ,QAAmB,OAA8C;EAC7E,MAAM,QAAQ,MAAM,UAAU,OAAO,MAAM,OAAO,EAAE,CAAC;EACrD,IAAI,UAAU,KAAA,GAAW,OAAO,KAAK,OAAO,GAAG,OAAO,KAAK,GAAG,OAAO,EAAE,EAAE,aAAa;EACtF,OAAO,KAAK,GAAG,MAAM,YAAY,OAAO,KAAK,IAAI,OAAO,KAAK,MAAM,QAAQ,KAAK,CAAC,CAAC;CACpF;CAEA,OAAO;EACL,MAAM;GACJ;GACA,MAAM,QAAQ,OACZ,OAAO,gBAAgB,KAAK,QAAQ,EAAE,CAAC,CAAC,CAAC,KAAK,OAAO,KAAI,UAAS,MAAM,QAAQ,KAAK,CAAC,CAAC;GACzF,OAAM,WAAU;IACd,MAAM,SAAS,GAAG,OAAO,KAAK;IAC9B,OAAO,OAAO,QAAQ,KAAK,CAAC,CAAC,SAAS,CAAC,KAAK,WAC1C,IAAI,WAAW,MAAM,KAAK,CAAC,MAAM,YAAY,CAAC,MAAM,QAAQ,KAAK,CAAC,IAAI,CAAC,CACzE;GACF;EACF;EACA,UAAU,QAAQ,IAAI,QAAQ,UAAU;GACtC,MAAM,SAAS,KAAK,QAAQ,EAAE;GAC9B,MAAM,0BAAU,IAAI,IAAI,CAAC,GAAI,QAAQ,WAAW,CAAC,GAAI,GAAG,OAAO,KAAK,MAAM,CAAC,CAAC;GAC5E,KAAK,MAAM,OAAO,OAAO,QAAQ,OAAO,GAAG;GAC3C,OAAO,MAAM,QAAQ;IAAE,QAAQ;KAAE,GAAG,QAAQ;KAAQ,GAAG;IAAO;IAAG;GAAQ,CAAC;EAC5E;CACF;AACF"}
|
package/dist/impact.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { n as Connection } from "./connection-BRyaJy89.js";
|
|
2
|
+
import { n as EntityKey, r as EntityStore } from "./store-ah2qjcJW.js";
|
|
3
|
+
import { k as NormalizedPatch } from "./optimistic-CECsfNGY.js";
|
|
4
|
+
import { o as QueryDescriptor } from "./query-BuIU93xA.js";
|
|
5
|
+
import { Data, Schema } from "effect";
|
|
6
|
+
import { ExprContext, MemoryOperators } from "foldkit-entity";
|
|
7
|
+
/**
|
|
8
|
+
* One row a write changed: the fields it wrote, and whether the client held
|
|
9
|
+
* no values of the row before (`created`), or its deletion.
|
|
10
|
+
*/
|
|
11
|
+
export declare const EntityChange: import("foldkit/schema").TaggedUnion<{
|
|
12
|
+
readonly Changed: {
|
|
13
|
+
entity: Schema.String;
|
|
14
|
+
id: Schema.String;
|
|
15
|
+
fields: Schema.$Array<Schema.String>;
|
|
16
|
+
created: Schema.Boolean;
|
|
17
|
+
};
|
|
18
|
+
readonly Deleted: {
|
|
19
|
+
entity: Schema.String;
|
|
20
|
+
id: Schema.String;
|
|
21
|
+
};
|
|
22
|
+
}>;
|
|
23
|
+
export type EntityChange = typeof EntityChange.Type;
|
|
24
|
+
/**
|
|
25
|
+
* What a mutation's answer changed, against `before`, the store as the server
|
|
26
|
+
* last said it: each patch's fields whose value is new, and each deletion. An
|
|
27
|
+
* answer returns more than it changed (a whole row, say), and counting an
|
|
28
|
+
* unchanged ordering field would refetch every list on every save.
|
|
29
|
+
*/
|
|
30
|
+
export declare const changesOf: (answer: {
|
|
31
|
+
readonly entities: ReadonlyArray<NormalizedPatch>;
|
|
32
|
+
readonly deleted?: ReadonlyArray<{
|
|
33
|
+
readonly entity: string;
|
|
34
|
+
readonly id: string;
|
|
35
|
+
}> | undefined;
|
|
36
|
+
}, before: EntityStore) => ReadonlyArray<EntityChange>;
|
|
37
|
+
/**
|
|
38
|
+
* What a change does to a connection: nothing it shows, a change the client
|
|
39
|
+
* places itself (`Placed`, the connection as it now is), or a change it
|
|
40
|
+
* cannot place, for its read entry to fetch again.
|
|
41
|
+
*/
|
|
42
|
+
export type Impact = Data.TaggedEnum<{
|
|
43
|
+
Kept: {};
|
|
44
|
+
Placed: {
|
|
45
|
+
readonly connection: Connection;
|
|
46
|
+
};
|
|
47
|
+
Invalidated: {
|
|
48
|
+
readonly reason: string;
|
|
49
|
+
};
|
|
50
|
+
}>;
|
|
51
|
+
export declare const Impact: Data.TaggedEnum.Constructor<{
|
|
52
|
+
readonly _tag: "Invalidated";
|
|
53
|
+
readonly reason: string;
|
|
54
|
+
} | {
|
|
55
|
+
readonly _tag: "Kept";
|
|
56
|
+
} | {
|
|
57
|
+
readonly _tag: "Placed";
|
|
58
|
+
readonly connection: Connection;
|
|
59
|
+
}>;
|
|
60
|
+
/** A loaded connection, as impact needs it. */
|
|
61
|
+
export interface HeldConnection {
|
|
62
|
+
readonly descriptor: QueryDescriptor<string, unknown, unknown>;
|
|
63
|
+
/** The connection's input, encoded, as its identity carries it. */
|
|
64
|
+
readonly encoded: Readonly<Record<string, unknown>>;
|
|
65
|
+
/** The rows it shows now, overlays included. */
|
|
66
|
+
readonly holds: ReadonlySet<EntityKey>;
|
|
67
|
+
/** Its pages as the server sent them, which a placement edits. */
|
|
68
|
+
readonly connection: Connection;
|
|
69
|
+
/** The members its Entity's server filters rows by under a rule the client does not run. */
|
|
70
|
+
readonly filtered: ReadonlyArray<string>;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* What `change` does to `connection`, judged against `store`, the store after
|
|
74
|
+
* the write.
|
|
75
|
+
*/
|
|
76
|
+
export declare const impactOn: (connection: HeldConnection, store: EntityStore, change: EntityChange, operators: MemoryOperators,
|
|
77
|
+
/** What a body's `Expr.viewer` and `Expr.now` read; see `MatchingOptions.context`. */
|
|
78
|
+
context?: ExprContext | undefined) => Impact;
|
|
79
|
+
//# sourceMappingURL=impact.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"impact.d.ts","names":[],"sources":["../src/impact.ts"],"mappings":";;;;;;;;;;qBAuCa,uCAAY;;IAErB,QAAM,OAAA;IACN,IAAE,OAAA;IACF,QAAM,OAAA,OAAA,OAAA;IACN,SAAO,OAAA;;;IAEE,QAAM,OAAA;IAAiB,IAAE,OAAA;;;YAE1B,sBAAsB,aAAa;;;;;;;qBAQlC,YAAS;WAET,UAAU,cAAc;WACxB,UAAU;aAAyB;aAAyB;;GACtE,QACO,gBACP,cAAc;;;;;;YA4BL,SAAS,KAAK;EACxB;EACA;aAAmB,YAAY;;EAC/B;aAAwB;;;qBAEb,QAAM,KAAA,WAAA;;WAFe;;;;;WADD,YAAA;;;iBAMhB;WACN,YAAY;;WAEZ,SAAS,SAAS;;WAElB,OAAO,YAAY;;WAEnB,YAAY;;WAEZ,UAAU;;;;;;qBA0ER,WAAQ,YACP,gBAAc,OACnB,aAAW,QACV,cAAY,WACT;;AAED,UAAA,4BACT"}
|