foldkit-remote 0.4.0 → 0.5.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 +173 -9
- package/dist/index.d.mts +232 -12
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +384 -121
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Context, Deferred, Duration, Effect, Layer, Option, Request, RequestResolver, Result, Schema, SchemaGetter, Stream } from "effect";
|
|
2
2
|
import * as Domain from "foldkit-entity";
|
|
3
|
-
import { Entity as Entity$1, SelectionTypeId } from "foldkit-entity";
|
|
3
|
+
import { Entity as Entity$1, Expr, Query as Query$1, SelectionTypeId } from "foldkit-entity";
|
|
4
4
|
import * as Subscription from "foldkit/subscription";
|
|
5
5
|
import { Metadata } from "foldkit-surface";
|
|
6
6
|
import { Rpc, RpcGroup } from "effect/unstable/rpc";
|
|
@@ -108,6 +108,91 @@ const requirementsOf = (projection) => RemoteRequirements.get(projection.metadat
|
|
|
108
108
|
/** The query connections a Projection carries. */
|
|
109
109
|
const connectionsOf = (projection) => RemoteConnections.get(projection.metadata);
|
|
110
110
|
//#endregion
|
|
111
|
+
//#region src/relation.ts
|
|
112
|
+
/** Marks the codec `Entity.ref`/`Entity.refPage` produce, so a Selection can tell the field's shape. */
|
|
113
|
+
const RelationAnnotation = "foldkitRemoteRelation";
|
|
114
|
+
/** The target entity's name on the same codec, so a nested selection of another entity is refused. */
|
|
115
|
+
const RelationEntityAnnotation = "foldkitRemoteRelationEntity";
|
|
116
|
+
/**
|
|
117
|
+
* A page of a relation that is a whole list is read under a name of its own, the
|
|
118
|
+
* field's name and the page's size: `comments@first=10`. It is a field like any
|
|
119
|
+
* other to the store, the planner, and the wire, so the whole list and a page of
|
|
120
|
+
* it are held side by side, merged, marked stale, and persisted each on its own.
|
|
121
|
+
* The server reads the name apart, reads the relation with the window, and
|
|
122
|
+
* answers under the alias. A cursor is not part of the name: a page read from a
|
|
123
|
+
* cursor merges onto the page it continues.
|
|
124
|
+
*/
|
|
125
|
+
const RELATION_ALIAS = "@";
|
|
126
|
+
/** The alias a relation field is read under, for a window of this size. */
|
|
127
|
+
const relationAlias = (field, window) => `${field}@${window.last !== void 0 ? `last=${window.last}` : `first=${window.first ?? ""}`}`;
|
|
128
|
+
/** The field an alias reads; a name that is no alias is its own field. */
|
|
129
|
+
const aliasedField = (name) => {
|
|
130
|
+
const at = name.indexOf("@");
|
|
131
|
+
return at === -1 ? name : name.slice(0, at);
|
|
132
|
+
};
|
|
133
|
+
/** Splits a ref key (`"Entity:id"`, the store key too) back into its parts. */
|
|
134
|
+
const refParts = (encoded) => {
|
|
135
|
+
const separator = encoded.indexOf(":");
|
|
136
|
+
return separator === -1 ? {
|
|
137
|
+
entity: encoded,
|
|
138
|
+
id: ""
|
|
139
|
+
} : {
|
|
140
|
+
entity: encoded.slice(0, separator),
|
|
141
|
+
id: encoded.slice(separator + 1)
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
/**
|
|
145
|
+
* The refs a relation value carries, in order. Anything that is not a ref key,
|
|
146
|
+
* an array of them, or a page of them contributes nothing.
|
|
147
|
+
*/
|
|
148
|
+
const refsIn = (value) => {
|
|
149
|
+
if (typeof value === "string") return [refParts(value)];
|
|
150
|
+
if (Array.isArray(value)) return value.filter((item) => typeof item === "string").map(refParts);
|
|
151
|
+
if (value !== null && typeof value === "object") {
|
|
152
|
+
const refs = value.refs;
|
|
153
|
+
if (Array.isArray(refs)) return refs.filter((item) => typeof item === "string").map(refParts);
|
|
154
|
+
}
|
|
155
|
+
return [];
|
|
156
|
+
};
|
|
157
|
+
/** The refs in a relation value that name the relation's target entity, and no other. */
|
|
158
|
+
const targetsOf = (value, relation) => refsIn(value).filter((ref) => ref.entity === relation.entity);
|
|
159
|
+
const isRefPage = (value) => {
|
|
160
|
+
if (value === null || typeof value !== "object") return false;
|
|
161
|
+
const page = value;
|
|
162
|
+
return Array.isArray(page.refs) && page.refs.every((ref) => typeof ref === "string") && typeof page.hasNext === "boolean" && typeof page.hasPrevious === "boolean";
|
|
163
|
+
};
|
|
164
|
+
/**
|
|
165
|
+
* The relation shape an entity field schema declares: a ref (`one`), a nullable
|
|
166
|
+
* ref, an array of refs (`many`), or a page of refs (`page`). `undefined` for a
|
|
167
|
+
* scalar field, which cannot take a nested selection.
|
|
168
|
+
*/
|
|
169
|
+
const relationShape = (schema) => shapeOf(schema.ast, false);
|
|
170
|
+
const shapeOf = (ast, nullable) => {
|
|
171
|
+
const annotated = ast.annotations?.[RelationAnnotation];
|
|
172
|
+
if (annotated === "one" || annotated === "page") return {
|
|
173
|
+
kind: annotated,
|
|
174
|
+
nullable,
|
|
175
|
+
entity: String(ast.annotations?.[RelationEntityAnnotation])
|
|
176
|
+
};
|
|
177
|
+
if (ast._tag === "Arrays") {
|
|
178
|
+
const item = ast.rest?.[0];
|
|
179
|
+
const inner = item === void 0 ? void 0 : shapeOf(item, false);
|
|
180
|
+
return inner?.kind === "one" ? {
|
|
181
|
+
kind: "many",
|
|
182
|
+
nullable,
|
|
183
|
+
entity: inner.entity
|
|
184
|
+
} : void 0;
|
|
185
|
+
}
|
|
186
|
+
if (ast._tag === "Union") {
|
|
187
|
+
const members = ast.types ?? [];
|
|
188
|
+
const optional = members.some((member) => member._tag === "Null" || member._tag === "Undefined");
|
|
189
|
+
for (const member of members) {
|
|
190
|
+
const shape = shapeOf(member, optional);
|
|
191
|
+
if (shape !== void 0) return shape;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
//#endregion
|
|
111
196
|
//#region src/store.ts
|
|
112
197
|
/**
|
|
113
198
|
* The pure entity store: values, per-field presence, staleness, and tombstones.
|
|
@@ -142,6 +227,9 @@ const written = (previous, values, now, windows) => {
|
|
|
142
227
|
const requested = windows?.[field];
|
|
143
228
|
if (requested !== void 0 && requested !== "") nextWindows[field] = requested;
|
|
144
229
|
else delete nextWindows[field];
|
|
230
|
+
if (!field.includes("@")) {
|
|
231
|
+
for (const held of present) if (held.startsWith(`${field}@`) && !(held in values)) stale.add(held);
|
|
232
|
+
}
|
|
145
233
|
}
|
|
146
234
|
return {
|
|
147
235
|
values: {
|
|
@@ -361,7 +449,32 @@ const stableStringify = (value) => {
|
|
|
361
449
|
return `{${Object.keys(record).filter((key) => record[key] !== void 0).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`).join(",")}}`;
|
|
362
450
|
};
|
|
363
451
|
const isEntityName = (result) => typeof result === "object" && result !== null && typeof result.name === "string";
|
|
452
|
+
/**
|
|
453
|
+
* The relational half comes from `foldkit-entity`, so one `Query` namespace
|
|
454
|
+
* holds both what a query means (`from`, `where`, `orderBy`) and how this
|
|
455
|
+
* client addresses it (`make`, `connection`, the window steps). They are one
|
|
456
|
+
* vocabulary in use — a definition's body is composed and then named — and two
|
|
457
|
+
* namespaces of the same name in one file would be a trap.
|
|
458
|
+
*/
|
|
459
|
+
/**
|
|
460
|
+
* One placeholder per key of the input's schema, each carrying that key's own
|
|
461
|
+
* codec so a comparison against it is checked.
|
|
462
|
+
*
|
|
463
|
+
* A codec that is not a `Schema.Struct` exposes no keys to stand for, while its
|
|
464
|
+
* *type* may well have them — so the body would be handed an empty object,
|
|
465
|
+
* `input.slug` would be `undefined`, and the comparison would silently be
|
|
466
|
+
* against nothing while the types all agreed. That is a wrong query rather than
|
|
467
|
+
* a missing one, so it is refused where the query is declared.
|
|
468
|
+
*/
|
|
469
|
+
const inputsOf = (name, schema) => {
|
|
470
|
+
const fields = schema.fields;
|
|
471
|
+
if (fields === void 0) throw new Error(`[foldkit-remote] Query.define: the Input of "${name}" is not a Schema.Struct, so its body has no inputs to read. Declare it as fields (\`{ slug: Schema.String }\`) or a plain \`Schema.Struct\`.`);
|
|
472
|
+
const inputs = {};
|
|
473
|
+
for (const key of Object.keys(fields)) inputs[key] = Expr.input(key, fields[key]);
|
|
474
|
+
return Object.freeze(inputs);
|
|
475
|
+
};
|
|
364
476
|
const Query = {
|
|
477
|
+
...Query$1,
|
|
365
478
|
/** Declares the entity and options a query's result is a connection over. */
|
|
366
479
|
connection: (entity, options) => ({
|
|
367
480
|
entity: entity.name,
|
|
@@ -389,6 +502,41 @@ const Query = {
|
|
|
389
502
|
})
|
|
390
503
|
};
|
|
391
504
|
},
|
|
505
|
+
/**
|
|
506
|
+
* Declares a query by what it means. The body is built once, here, and the
|
|
507
|
+
* `input` it is given holds placeholders rather than values — there is
|
|
508
|
+
* nothing yet to branch on, so a condition that depends on what was passed is
|
|
509
|
+
* a comparison over the placeholder:
|
|
510
|
+
*
|
|
511
|
+
* ```ts
|
|
512
|
+
* const PostsBySlug = Query.define('PostsBySlug', { slug: Schema.String }, ({ input }) =>
|
|
513
|
+
* Query.from(Post).pipe(
|
|
514
|
+
* Query.where(Expr.eq(Post.fields.slug, input.slug)),
|
|
515
|
+
* Query.orderBy(Order.asc(Post.fields.id)),
|
|
516
|
+
* ),
|
|
517
|
+
* )
|
|
518
|
+
* ```
|
|
519
|
+
*
|
|
520
|
+
* What comes back is an ordinary `QueryDescriptor` — the same name, `Input`,
|
|
521
|
+
* `ref` and connection identity `Query.make` gives — carrying its `body`
|
|
522
|
+
* besides. A server can compile that body instead of being told the same
|
|
523
|
+
* thing again in its own dialect, and one that would rather answer the query
|
|
524
|
+
* its own way still can.
|
|
525
|
+
*
|
|
526
|
+
* The result is a connection over the Entity the body reads, so it is not
|
|
527
|
+
* named twice.
|
|
528
|
+
*/
|
|
529
|
+
define: (name, Input, body, options) => {
|
|
530
|
+
const schema = schemaOf(Input);
|
|
531
|
+
const built = body({ input: inputsOf(name, schema) });
|
|
532
|
+
return {
|
|
533
|
+
...Query.make(name, {
|
|
534
|
+
Input,
|
|
535
|
+
Result: Query.connection(built.entity, options)
|
|
536
|
+
}),
|
|
537
|
+
body: built
|
|
538
|
+
};
|
|
539
|
+
},
|
|
392
540
|
first: (count) => (ref) => ({
|
|
393
541
|
...ref,
|
|
394
542
|
window: {
|
|
@@ -1016,74 +1164,6 @@ const hasNext = (connection) => {
|
|
|
1016
1164
|
/** A connection with more than one segment has an explicit unknown gap. */
|
|
1017
1165
|
const isGapped = (connection) => connection.segments.length > 1;
|
|
1018
1166
|
//#endregion
|
|
1019
|
-
//#region src/relation.ts
|
|
1020
|
-
/** Marks the codec `Entity.ref`/`Entity.refPage` produce, so a Selection can tell the field's shape. */
|
|
1021
|
-
const RelationAnnotation = "foldkitRemoteRelation";
|
|
1022
|
-
/** The target entity's name on the same codec, so a nested selection of another entity is refused. */
|
|
1023
|
-
const RelationEntityAnnotation = "foldkitRemoteRelationEntity";
|
|
1024
|
-
/** Splits a ref key (`"Entity:id"`, the store key too) back into its parts. */
|
|
1025
|
-
const refParts = (encoded) => {
|
|
1026
|
-
const separator = encoded.indexOf(":");
|
|
1027
|
-
return separator === -1 ? {
|
|
1028
|
-
entity: encoded,
|
|
1029
|
-
id: ""
|
|
1030
|
-
} : {
|
|
1031
|
-
entity: encoded.slice(0, separator),
|
|
1032
|
-
id: encoded.slice(separator + 1)
|
|
1033
|
-
};
|
|
1034
|
-
};
|
|
1035
|
-
/**
|
|
1036
|
-
* The refs a relation value carries, in order. Anything that is not a ref key,
|
|
1037
|
-
* an array of them, or a page of them contributes nothing.
|
|
1038
|
-
*/
|
|
1039
|
-
const refsIn = (value) => {
|
|
1040
|
-
if (typeof value === "string") return [refParts(value)];
|
|
1041
|
-
if (Array.isArray(value)) return value.filter((item) => typeof item === "string").map(refParts);
|
|
1042
|
-
if (value !== null && typeof value === "object") {
|
|
1043
|
-
const refs = value.refs;
|
|
1044
|
-
if (Array.isArray(refs)) return refs.filter((item) => typeof item === "string").map(refParts);
|
|
1045
|
-
}
|
|
1046
|
-
return [];
|
|
1047
|
-
};
|
|
1048
|
-
/** The refs in a relation value that name the relation's target entity, and no other. */
|
|
1049
|
-
const targetsOf = (value, relation) => refsIn(value).filter((ref) => ref.entity === relation.entity);
|
|
1050
|
-
const isRefPage = (value) => {
|
|
1051
|
-
if (value === null || typeof value !== "object") return false;
|
|
1052
|
-
const page = value;
|
|
1053
|
-
return Array.isArray(page.refs) && page.refs.every((ref) => typeof ref === "string") && typeof page.hasNext === "boolean" && typeof page.hasPrevious === "boolean";
|
|
1054
|
-
};
|
|
1055
|
-
/**
|
|
1056
|
-
* The relation shape an entity field schema declares: a ref (`one`), a nullable
|
|
1057
|
-
* ref, an array of refs (`many`), or a page of refs (`page`). `undefined` for a
|
|
1058
|
-
* scalar field, which cannot take a nested selection.
|
|
1059
|
-
*/
|
|
1060
|
-
const relationShape = (schema) => shapeOf(schema.ast, false);
|
|
1061
|
-
const shapeOf = (ast, nullable) => {
|
|
1062
|
-
const annotated = ast.annotations?.[RelationAnnotation];
|
|
1063
|
-
if (annotated === "one" || annotated === "page") return {
|
|
1064
|
-
kind: annotated,
|
|
1065
|
-
nullable,
|
|
1066
|
-
entity: String(ast.annotations?.[RelationEntityAnnotation])
|
|
1067
|
-
};
|
|
1068
|
-
if (ast._tag === "Arrays") {
|
|
1069
|
-
const item = ast.rest?.[0];
|
|
1070
|
-
const inner = item === void 0 ? void 0 : shapeOf(item, false);
|
|
1071
|
-
return inner?.kind === "one" ? {
|
|
1072
|
-
kind: "many",
|
|
1073
|
-
nullable,
|
|
1074
|
-
entity: inner.entity
|
|
1075
|
-
} : void 0;
|
|
1076
|
-
}
|
|
1077
|
-
if (ast._tag === "Union") {
|
|
1078
|
-
const members = ast.types ?? [];
|
|
1079
|
-
const optional = members.some((member) => member._tag === "Null" || member._tag === "Undefined");
|
|
1080
|
-
for (const member of members) {
|
|
1081
|
-
const shape = shapeOf(member, optional);
|
|
1082
|
-
if (shape !== void 0) return shape;
|
|
1083
|
-
}
|
|
1084
|
-
}
|
|
1085
|
-
};
|
|
1086
|
-
//#endregion
|
|
1087
1167
|
//#region src/selection.ts
|
|
1088
1168
|
/**
|
|
1089
1169
|
* Selections: the fields a Surface picks of an entity (nested through
|
|
@@ -1109,15 +1189,20 @@ const relationOf = (selection) => ({
|
|
|
1109
1189
|
...selection.connections === void 0 ? {} : { windows: selection.connections },
|
|
1110
1190
|
...selection.relations === void 0 ? {} : { relations: selection.relations }
|
|
1111
1191
|
});
|
|
1192
|
+
const fromEntitySelections = /* @__PURE__ */ new WeakMap();
|
|
1112
1193
|
const Selection = {
|
|
1113
1194
|
/**
|
|
1114
1195
|
* The Remote Selection of a `foldkit-entity` Selection: the same members, read
|
|
1115
1196
|
* from Remote's store. `true` on a relation reads its refs, as it does here.
|
|
1116
1197
|
*/
|
|
1117
1198
|
from: (selection) => {
|
|
1199
|
+
const compiled = fromEntitySelections.get(selection);
|
|
1200
|
+
if (compiled !== void 0) return compiled;
|
|
1118
1201
|
const spec = {};
|
|
1119
1202
|
for (const [key, selected] of Object.entries(selection.members)) spec[key] = selected === true ? true : Domain.SelectionPageTypeId in selected ? Selection.connection(Entity.from(selected.selection.entity), selected.window, Selection.from(selected.selection)) : Selection.from(selected);
|
|
1120
|
-
|
|
1203
|
+
const made = Selection.make(Entity.from(selection.entity), spec);
|
|
1204
|
+
fromEntitySelections.set(selection, made);
|
|
1205
|
+
return made;
|
|
1121
1206
|
},
|
|
1122
1207
|
/**
|
|
1123
1208
|
* The fields to read of `entity`. A nested `Selection` on a relation field
|
|
@@ -1127,12 +1212,14 @@ const Selection = {
|
|
|
1127
1212
|
make: (entity, selection) => {
|
|
1128
1213
|
if (Object.keys(selection).length === 0) throw new Error(`Selection.make: a selection of "${entity.name}" picks at least one field`);
|
|
1129
1214
|
const picked = {};
|
|
1215
|
+
const names = [];
|
|
1130
1216
|
const connections = {};
|
|
1131
1217
|
const relations = {};
|
|
1132
1218
|
for (const key of Object.keys(selection)) {
|
|
1133
1219
|
const choice = selection[key];
|
|
1134
1220
|
if (choice === true) {
|
|
1135
1221
|
picked[key] = entity.fields[key];
|
|
1222
|
+
names.push(key);
|
|
1136
1223
|
continue;
|
|
1137
1224
|
}
|
|
1138
1225
|
const nested = choice;
|
|
@@ -1141,18 +1228,21 @@ const Selection = {
|
|
|
1141
1228
|
if (nested.entity !== shape.entity) throw new Error(`Selection.make: "${key}" on "${entity.name}" refers to "${shape.entity}", not "${nested.entity}"`);
|
|
1142
1229
|
const nullable = (codec) => shape.nullable ? Schema.NullOr(codec) : codec;
|
|
1143
1230
|
if (nested.window !== void 0) {
|
|
1144
|
-
|
|
1231
|
+
const name = shape.kind === "many" ? relationAlias(key, nested.window) : key;
|
|
1232
|
+
names.push(name);
|
|
1233
|
+
connections[name] = nested.window;
|
|
1145
1234
|
picked[key] = nullable(nested.schema);
|
|
1146
|
-
if (nested.fields.length > 0) relations[
|
|
1235
|
+
if (nested.fields.length > 0) relations[name] = relationOf(nested);
|
|
1147
1236
|
continue;
|
|
1148
1237
|
}
|
|
1238
|
+
names.push(key);
|
|
1149
1239
|
const item = nested.schema;
|
|
1150
1240
|
picked[key] = nullable(shape.kind === "many" ? Schema.Array(item) : shape.kind === "page" ? pageSchema(item) : item);
|
|
1151
1241
|
relations[key] = relationOf(nested);
|
|
1152
1242
|
}
|
|
1153
1243
|
return {
|
|
1154
1244
|
entity: entity.name,
|
|
1155
|
-
fields:
|
|
1245
|
+
fields: names,
|
|
1156
1246
|
schema: Schema.Struct(picked),
|
|
1157
1247
|
...Object.keys(connections).length === 0 ? {} : { connections },
|
|
1158
1248
|
...Object.keys(relations).length === 0 ? {} : { relations }
|
|
@@ -1189,12 +1279,12 @@ const assemble = (store, key, requirement) => {
|
|
|
1189
1279
|
refreshing ||= isFieldStale(store, key, field);
|
|
1190
1280
|
const relation = requirement.relations?.[field];
|
|
1191
1281
|
if (relation === void 0) {
|
|
1192
|
-
values[field] = value.value;
|
|
1282
|
+
values[aliasedField(field)] = value.value;
|
|
1193
1283
|
continue;
|
|
1194
1284
|
}
|
|
1195
1285
|
const nested = assembleRelation(store, value.value, relation);
|
|
1196
1286
|
if (nested === void 0) return void 0;
|
|
1197
|
-
values[field] = nested.values;
|
|
1287
|
+
values[aliasedField(field)] = nested.values;
|
|
1198
1288
|
refreshing ||= nested.refreshing;
|
|
1199
1289
|
}
|
|
1200
1290
|
return {
|
|
@@ -1291,6 +1381,8 @@ const Entity = {
|
|
|
1291
1381
|
return descriptor;
|
|
1292
1382
|
},
|
|
1293
1383
|
make: (name, schema) => {
|
|
1384
|
+
const reserved = Object.keys(schema.fields).find((field) => field.includes("@"));
|
|
1385
|
+
if (reserved !== void 0) throw new Error(`Entity.make: field "${reserved}" of "${name}" contains "@", which names a page of a relation`);
|
|
1294
1386
|
const descriptor = {
|
|
1295
1387
|
name,
|
|
1296
1388
|
schema,
|
|
@@ -1347,6 +1439,7 @@ const inspectRemote = (model) => ({
|
|
|
1347
1439
|
connections: Object.keys(model.connections),
|
|
1348
1440
|
live: Object.keys(model.live),
|
|
1349
1441
|
gaps: [...model.gaps],
|
|
1442
|
+
loading: [...model.loading],
|
|
1350
1443
|
mutations: {
|
|
1351
1444
|
pending: [...model.mutations.pending],
|
|
1352
1445
|
failed: [...model.mutations.failed],
|
|
@@ -1930,6 +2023,8 @@ const RemotePersistence = {
|
|
|
1930
2023
|
//#endregion
|
|
1931
2024
|
//#region src/remoteData.ts
|
|
1932
2025
|
/** `RemoteData`: the state of a value the store may not hold yet. */
|
|
2026
|
+
const fresh = { _tag: "Fresh" };
|
|
2027
|
+
const refreshing = { _tag: "Refreshing" };
|
|
1933
2028
|
const remoteErrorSchema = Schema.Struct({
|
|
1934
2029
|
_tag: Schema.String,
|
|
1935
2030
|
message: Schema.String
|
|
@@ -1977,6 +2072,43 @@ const RemoteData = {
|
|
|
1977
2072
|
case "NotFound": return cases.NotFound();
|
|
1978
2073
|
}
|
|
1979
2074
|
},
|
|
2075
|
+
/**
|
|
2076
|
+
* The view-oriented fold: the six states as the three things a view actually
|
|
2077
|
+
* draws, under the policy that useful data stays on screen. A value that is
|
|
2078
|
+
* being refetched, or that a later read failed to replace, still reaches
|
|
2079
|
+
* `data` — with `freshness` saying which — instead of being replaced by a
|
|
2080
|
+
* spinner or an error the reader cannot act on.
|
|
2081
|
+
*
|
|
2082
|
+
* ```ts
|
|
2083
|
+
* RemoteData.render(model.user, {
|
|
2084
|
+
* loading: () => UserSkeleton(),
|
|
2085
|
+
* notFound: () => NoSuchUser(),
|
|
2086
|
+
* failed: error => ErrorView(error),
|
|
2087
|
+
* data: (user, freshness) => UserView({ user, dimmed: freshness._tag !== 'Fresh' }),
|
|
2088
|
+
* })
|
|
2089
|
+
* ```
|
|
2090
|
+
*
|
|
2091
|
+
* `notFound` is its own branch and not optional: a row the server answered
|
|
2092
|
+
* for and does not have is neither loading nor a failure, and drawing it as
|
|
2093
|
+
* either is a spinner that never ends or an error nobody can fix.
|
|
2094
|
+
*
|
|
2095
|
+
* This does not replace `match`, which is the exhaustive fold over the states
|
|
2096
|
+
* themselves. Reach for `match` when the six states really do draw
|
|
2097
|
+
* differently, and for this when they draw the usual three ways.
|
|
2098
|
+
*/
|
|
2099
|
+
render: (data, cases) => {
|
|
2100
|
+
switch (data._tag) {
|
|
2101
|
+
case "Initial":
|
|
2102
|
+
case "Loading": return cases.loading();
|
|
2103
|
+
case "NotFound": return cases.notFound();
|
|
2104
|
+
case "Ready": return cases.data(data.value, fresh);
|
|
2105
|
+
case "Refreshing": return cases.data(data.value, refreshing);
|
|
2106
|
+
case "Failed": return data.previous === void 0 ? cases.failed(data.error) : cases.data(data.previous, {
|
|
2107
|
+
_tag: "Stale",
|
|
2108
|
+
error: data.error
|
|
2109
|
+
});
|
|
2110
|
+
}
|
|
2111
|
+
},
|
|
1980
2112
|
map: (data, f) => {
|
|
1981
2113
|
switch (data._tag) {
|
|
1982
2114
|
case "Ready": return {
|
|
@@ -2006,6 +2138,11 @@ const RemoteData = {
|
|
|
2006
2138
|
* optimistic layers an application keeps in its Model, the Messages that
|
|
2007
2139
|
* change it, and the pure reducer over them.
|
|
2008
2140
|
*/
|
|
2141
|
+
const emptyRefresh = {
|
|
2142
|
+
generation: 0,
|
|
2143
|
+
requested: /* @__PURE__ */ new Map(),
|
|
2144
|
+
started: /* @__PURE__ */ new Map()
|
|
2145
|
+
};
|
|
2009
2146
|
const initialRemoteModel = {
|
|
2010
2147
|
entities: emptyStore,
|
|
2011
2148
|
connections: {},
|
|
@@ -2014,10 +2151,7 @@ const initialRemoteModel = {
|
|
|
2014
2151
|
mutations: emptyMutationState,
|
|
2015
2152
|
gaps: /* @__PURE__ */ new Set(),
|
|
2016
2153
|
loading: /* @__PURE__ */ new Set(),
|
|
2017
|
-
refresh:
|
|
2018
|
-
requested: 0,
|
|
2019
|
-
started: 0
|
|
2020
|
-
}
|
|
2154
|
+
refresh: emptyRefresh
|
|
2021
2155
|
};
|
|
2022
2156
|
/** The entity store and the mutation ledger are runtime values, not wire shapes. */
|
|
2023
2157
|
const runtimeSchema = Schema.Unknown;
|
|
@@ -2029,10 +2163,7 @@ const remoteModelSchema = () => Schema.Struct({
|
|
|
2029
2163
|
mutations: runtimeSchema,
|
|
2030
2164
|
gaps: runtimeSchema,
|
|
2031
2165
|
loading: runtimeSchema,
|
|
2032
|
-
refresh:
|
|
2033
|
-
requested: Schema.Number,
|
|
2034
|
-
started: Schema.Number
|
|
2035
|
-
})
|
|
2166
|
+
refresh: runtimeSchema
|
|
2036
2167
|
});
|
|
2037
2168
|
const retentionRootsSchema = Schema.Struct({
|
|
2038
2169
|
requirements: Schema.Array(ReadRequest),
|
|
@@ -2082,6 +2213,11 @@ const remoteMessageCases = {
|
|
|
2082
2213
|
requestId: Schema.String,
|
|
2083
2214
|
error: remoteErrorSchema
|
|
2084
2215
|
},
|
|
2216
|
+
OverlayShown: {
|
|
2217
|
+
id: Schema.String,
|
|
2218
|
+
optimistic: Schema.Array(Schema.Unknown)
|
|
2219
|
+
},
|
|
2220
|
+
OverlayLifted: { id: Schema.String },
|
|
2085
2221
|
LiveReceived: {
|
|
2086
2222
|
stream: Schema.String,
|
|
2087
2223
|
event: Schema.Unknown,
|
|
@@ -2110,39 +2246,122 @@ const remoteMessageSchema = Schema.Union(Object.entries(remoteMessageCases).map(
|
|
|
2110
2246
|
_tag: Schema.Literal(tag),
|
|
2111
2247
|
...fields
|
|
2112
2248
|
})));
|
|
2249
|
+
/** An overlay's layer, named apart from every request's: a request id is the application's to choose too. */
|
|
2250
|
+
const overlayLayer = (id) => `overlay:${id}`;
|
|
2113
2251
|
const marksOf = (requests) => requests.map((request) => [entityKey(request.entity, request.id), request.fields]);
|
|
2114
2252
|
/**
|
|
2115
|
-
* One mark per requested field
|
|
2116
|
-
* a request id: two reads asking for one field share
|
|
2117
|
-
* answer clears it. Over-clearing shows `Initial` rather
|
|
2118
|
-
* is the safer way to be wrong.
|
|
2253
|
+
* One mark per requested field, `entity\0id\0field`. A read is identified by
|
|
2254
|
+
* what it asks for, not by a request id: two reads asking for one field share
|
|
2255
|
+
* its mark, so the first answer clears it. Over-clearing shows `Initial` rather
|
|
2256
|
+
* than a spinner, which is the safer way to be wrong. The refresh generations
|
|
2257
|
+
* below key off the same marks, so "what a read asks for" means one thing.
|
|
2119
2258
|
*/
|
|
2120
|
-
const
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
for (const field of request.fields) yield `${key}\u0000${field}`;
|
|
2124
|
-
}
|
|
2259
|
+
const fieldMark = (entity, id, field) => `${entityKey(entity, id)}\u0000${field}`;
|
|
2260
|
+
const fieldMarks = function* (requests) {
|
|
2261
|
+
for (const request of requests) for (const field of request.fields) yield fieldMark(request.entity, request.id, field);
|
|
2125
2262
|
};
|
|
2126
2263
|
const withLoading = (loading, requests) => {
|
|
2127
2264
|
const next = new Set(loading);
|
|
2128
|
-
for (const mark of
|
|
2265
|
+
for (const mark of fieldMarks(requests)) next.add(mark);
|
|
2129
2266
|
return next;
|
|
2130
2267
|
};
|
|
2131
2268
|
const withoutLoading = (loading, requests) => {
|
|
2132
2269
|
if (loading.size === 0) return loading;
|
|
2133
2270
|
const next = new Set(loading);
|
|
2134
|
-
for (const mark of
|
|
2271
|
+
for (const mark of fieldMarks(requests)) next.delete(mark);
|
|
2135
2272
|
return next;
|
|
2136
2273
|
};
|
|
2137
2274
|
/**
|
|
2275
|
+
* A connection's mark. The leading separator keeps it out of the field marks'
|
|
2276
|
+
* space, whatever a connection identity happens to spell.
|
|
2277
|
+
*/
|
|
2278
|
+
const connectionPrefix = "\0connection\0";
|
|
2279
|
+
const connectionMark = (identity) => `${connectionPrefix}${identity}`;
|
|
2280
|
+
/**
|
|
2281
|
+
* Drops the refresh marks of entities and connections that were collected.
|
|
2282
|
+
* Nothing observes a collected entity — the retention roots are the active
|
|
2283
|
+
* Surfaces — so no read entry's generation can fall back when its mark goes.
|
|
2284
|
+
* Without this the generations would be bounded by how many things the
|
|
2285
|
+
* application has ever refreshed rather than by what it currently holds, which
|
|
2286
|
+
* for a list refreshed on a timer is the same as not bounded at all.
|
|
2287
|
+
*/
|
|
2288
|
+
const prunedRefresh = (refresh, retained) => {
|
|
2289
|
+
const live = (mark) => mark.startsWith(connectionPrefix) ? retained.connections[mark.slice(12)] !== void 0 : retained.entities[mark.slice(0, mark.indexOf("\0"))] !== void 0;
|
|
2290
|
+
const keep = (marks) => {
|
|
2291
|
+
const next = /* @__PURE__ */ new Map();
|
|
2292
|
+
for (const [mark, generation] of marks) if (live(mark)) next.set(mark, generation);
|
|
2293
|
+
return next.size === marks.size ? marks : next;
|
|
2294
|
+
};
|
|
2295
|
+
const requested = keep(refresh.requested);
|
|
2296
|
+
const started = keep(refresh.started);
|
|
2297
|
+
return requested === refresh.requested && started === refresh.started ? refresh : {
|
|
2298
|
+
...refresh,
|
|
2299
|
+
requested,
|
|
2300
|
+
started
|
|
2301
|
+
};
|
|
2302
|
+
};
|
|
2303
|
+
/**
|
|
2304
|
+
* The generation a read is planned under: the highest any of the fields it
|
|
2305
|
+
* reads or connections it runs was refreshed at. A read entry carries this as a
|
|
2306
|
+
* dependency, so it restarts when what it observes is refreshed and not when
|
|
2307
|
+
* anything else is.
|
|
2308
|
+
*/
|
|
2309
|
+
const refreshedAt = (refresh, requests, connections = []) => {
|
|
2310
|
+
let at = 0;
|
|
2311
|
+
for (const mark of fieldMarks(requests)) at = Math.max(at, refresh.requested.get(mark) ?? 0);
|
|
2312
|
+
for (const identity of connections) at = Math.max(at, refresh.requested.get(connectionMark(identity)) ?? 0);
|
|
2313
|
+
return at;
|
|
2314
|
+
};
|
|
2315
|
+
/**
|
|
2316
|
+
* Marks every field of `requests` and every connection in `connections`
|
|
2317
|
+
* refreshed at the next generation.
|
|
2318
|
+
*/
|
|
2319
|
+
const withRefreshRequested = (refresh, requests, connections = []) => {
|
|
2320
|
+
const generation = refresh.generation + 1;
|
|
2321
|
+
const requested = new Map(refresh.requested);
|
|
2322
|
+
for (const mark of fieldMarks(requests)) requested.set(mark, generation);
|
|
2323
|
+
for (const identity of connections) requested.set(connectionMark(identity), generation);
|
|
2324
|
+
return {
|
|
2325
|
+
...refresh,
|
|
2326
|
+
generation,
|
|
2327
|
+
requested
|
|
2328
|
+
};
|
|
2329
|
+
};
|
|
2330
|
+
/**
|
|
2331
|
+
* Records that a read of `requests` began under `generation`. A read that
|
|
2332
|
+
* carries no generation began under none, and leaves the marks as they are.
|
|
2333
|
+
*/
|
|
2334
|
+
const withRefreshStarted = (refresh, requests, generation) => {
|
|
2335
|
+
if (generation === void 0 || generation === 0) return refresh;
|
|
2336
|
+
let started;
|
|
2337
|
+
for (const mark of fieldMarks(requests)) {
|
|
2338
|
+
if ((refresh.started.get(mark) ?? 0) >= generation) continue;
|
|
2339
|
+
started ??= new Map(refresh.started);
|
|
2340
|
+
started.set(mark, generation);
|
|
2341
|
+
}
|
|
2342
|
+
return started === void 0 ? refresh : {
|
|
2343
|
+
...refresh,
|
|
2344
|
+
started
|
|
2345
|
+
};
|
|
2346
|
+
};
|
|
2347
|
+
/**
|
|
2348
|
+
* Whether one field's read already began under the generation that field was
|
|
2349
|
+
* last refreshed at. Such a read is in flight and would otherwise outlive the
|
|
2350
|
+
* refresh, so the refresh takes a new generation to restart it even though the
|
|
2351
|
+
* field is already stale.
|
|
2352
|
+
*/
|
|
2353
|
+
const refreshIsInFlight = (refresh, request, field) => {
|
|
2354
|
+
const mark = fieldMark(request.entity, request.id, field);
|
|
2355
|
+
return (refresh.started.get(mark) ?? 0) === (refresh.requested.get(mark) ?? 0);
|
|
2356
|
+
};
|
|
2357
|
+
/**
|
|
2138
2358
|
* Whether a read is fetching any of `fields` for this entity. Consulted only
|
|
2139
2359
|
* when the store lacks the value, which is what separates `Loading` from
|
|
2140
2360
|
* `Initial`.
|
|
2141
2361
|
*/
|
|
2142
2362
|
const isLoading = (model, entity, id, fields) => {
|
|
2143
2363
|
if (model.loading.size === 0) return false;
|
|
2144
|
-
|
|
2145
|
-
return fields.some((field) => model.loading.has(`${key}\u0000${field}`));
|
|
2364
|
+
return fields.some((field) => model.loading.has(fieldMark(entity, id, field)));
|
|
2146
2365
|
};
|
|
2147
2366
|
const setConnectionStale = (connections, connection, stale) => {
|
|
2148
2367
|
const current = connections[connection] ?? emptyConnection;
|
|
@@ -2179,32 +2398,31 @@ const updateRemote = (model, message) => {
|
|
|
2179
2398
|
entities: setStale(model.entities, marksOf(message.requests), false),
|
|
2180
2399
|
loading: withoutLoading(model.loading, message.requests)
|
|
2181
2400
|
};
|
|
2182
|
-
case "RetentionChanged":
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2401
|
+
case "RetentionChanged": {
|
|
2402
|
+
const retained = gc(model, message.roots);
|
|
2403
|
+
return {
|
|
2404
|
+
...model,
|
|
2405
|
+
...retained,
|
|
2406
|
+
refresh: prunedRefresh(model.refresh, retained)
|
|
2407
|
+
};
|
|
2408
|
+
}
|
|
2186
2409
|
case "Hydrated": return {
|
|
2187
2410
|
...model,
|
|
2188
2411
|
entities: RemotePersistence.mergeStores(model.entities, message.entities, message.merge)
|
|
2189
2412
|
};
|
|
2190
2413
|
case "RefreshStarted": {
|
|
2191
|
-
const
|
|
2414
|
+
const asked = message.requests.reduce((store, request) => isTombstone(store, entityKey(request.entity, request.id)) ? remove(store, entityKey(request.entity, request.id)) : store, model.entities);
|
|
2415
|
+
const entities = setStale(asked, marksOf(message.requests), true);
|
|
2192
2416
|
return entities === model.entities ? model : {
|
|
2193
2417
|
...model,
|
|
2194
2418
|
entities
|
|
2195
2419
|
};
|
|
2196
2420
|
}
|
|
2197
|
-
case "ReadStarted": {
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
refresh: started === model.refresh.started ? model.refresh : {
|
|
2203
|
-
...model.refresh,
|
|
2204
|
-
started
|
|
2205
|
-
}
|
|
2206
|
-
};
|
|
2207
|
-
}
|
|
2421
|
+
case "ReadStarted": return {
|
|
2422
|
+
...model,
|
|
2423
|
+
loading: withLoading(model.loading, message.requests),
|
|
2424
|
+
refresh: withRefreshStarted(model.refresh, message.requests, message.refresh)
|
|
2425
|
+
};
|
|
2208
2426
|
case "MutationStarted": return {
|
|
2209
2427
|
...model,
|
|
2210
2428
|
mutations: beginMutation(model.mutations, message.requestId),
|
|
@@ -2219,6 +2437,17 @@ const updateRemote = (model, message) => {
|
|
|
2219
2437
|
optimistic: settled.optimistic
|
|
2220
2438
|
};
|
|
2221
2439
|
}
|
|
2440
|
+
case "OverlayShown": return {
|
|
2441
|
+
...model,
|
|
2442
|
+
optimistic: beginOptimistic(settleFailure(model.optimistic, overlayLayer(message.id)), overlayLayer(message.id), message.optimistic)
|
|
2443
|
+
};
|
|
2444
|
+
case "OverlayLifted": {
|
|
2445
|
+
const optimistic = settleFailure(model.optimistic, overlayLayer(message.id));
|
|
2446
|
+
return optimistic.layers.length === model.optimistic.layers.length && optimistic.overlays.length === model.optimistic.overlays.length ? model : {
|
|
2447
|
+
...model,
|
|
2448
|
+
optimistic
|
|
2449
|
+
};
|
|
2450
|
+
}
|
|
2222
2451
|
case "MutationFailed": return {
|
|
2223
2452
|
...model,
|
|
2224
2453
|
mutations: failMutation(model.mutations, message.requestId, message.error),
|
|
@@ -2414,6 +2643,22 @@ const storeOf = (bound, model) => {
|
|
|
2414
2643
|
const remote = bound.store.get(model);
|
|
2415
2644
|
return visibleStoreOf(remote.entities, remote.optimistic);
|
|
2416
2645
|
};
|
|
2646
|
+
/**
|
|
2647
|
+
* The Model as it would be with nothing optimistic pending. Every read reaches
|
|
2648
|
+
* the layers through the Model it is given, so dropping them here is what makes
|
|
2649
|
+
* a read confirmed — no read has to know it is being read confirmed.
|
|
2650
|
+
*/
|
|
2651
|
+
const withoutOptimistic = (bound, model) => {
|
|
2652
|
+
const remote = bound.store.get(model);
|
|
2653
|
+
return remote.optimistic.layers.length === 0 && remote.optimistic.overlays.length === 0 ? model : bound.store.set(model, {
|
|
2654
|
+
...remote,
|
|
2655
|
+
optimistic: emptyOptimistic
|
|
2656
|
+
});
|
|
2657
|
+
};
|
|
2658
|
+
const confirmed = (bound, projection) => ({
|
|
2659
|
+
...projection,
|
|
2660
|
+
read: (root) => projection.read(withoutOptimistic(bound, root))
|
|
2661
|
+
});
|
|
2417
2662
|
const visibleStores = /* @__PURE__ */ new WeakMap();
|
|
2418
2663
|
const visibleStoreOf = (entities, optimistic) => {
|
|
2419
2664
|
let byStore = visibleStores.get(optimistic);
|
|
@@ -2639,7 +2884,7 @@ const observeEntry = (bound, askedOf, toMessage, options) => {
|
|
|
2639
2884
|
const remote = bound.store.get(model);
|
|
2640
2885
|
const planned = planAsked(remote, askedOf(model), RemotePolicy.toPlan(policy, now()));
|
|
2641
2886
|
return {
|
|
2642
|
-
refresh: remote.refresh.
|
|
2887
|
+
refresh: refreshedAt(remote.refresh, planned.requirements, planned.queries.map((query) => query.identity)),
|
|
2643
2888
|
requirements: planned.requirements,
|
|
2644
2889
|
queries: planned.queries.map(({ identity, window, select }) => ({
|
|
2645
2890
|
identity,
|
|
@@ -2801,6 +3046,12 @@ const Remote = {
|
|
|
2801
3046
|
*/
|
|
2802
3047
|
storeOf: (bound, model) => storeOf(bound, model),
|
|
2803
3048
|
/**
|
|
3049
|
+
* The projection read against the server-derived store alone: the same
|
|
3050
|
+
* requirements, planned the same way, with the pending optimistic layers and
|
|
3051
|
+
* connection overlays left off. See the bound `Remote.confirmed`.
|
|
3052
|
+
*/
|
|
3053
|
+
confirmed: (bound, projection) => confirmed(bound, projection),
|
|
3054
|
+
/**
|
|
2804
3055
|
* Executes the plan against the `RemoteClient` and returns a new store. Used
|
|
2805
3056
|
* for SSR route prefetch, hover prefetch, and tests. Never called during render.
|
|
2806
3057
|
* `policy` decides what a present field means (default cache-first); `now`
|
|
@@ -2847,19 +3098,17 @@ const Remote = {
|
|
|
2847
3098
|
return known === void 0 ? [] : itemsOf(visibleItems(known, connection.identity, remote.optimistic.overlays, remote.entities).map((edge) => edge.ref), connection.select);
|
|
2848
3099
|
});
|
|
2849
3100
|
const requirements = plan(visibleStoreOf(remote.entities, remote.optimistic), [...asked.requirements, ...items], { force: true });
|
|
3101
|
+
const invalidated = connections.filter((connection) => remote.connections[connection.identity] !== void 0).map((connection) => connection.identity);
|
|
2850
3102
|
const marked = [...requirements.length === 0 ? [] : [{
|
|
2851
3103
|
_tag: "RefreshStarted",
|
|
2852
3104
|
requests: requirements
|
|
2853
|
-
}], ...
|
|
3105
|
+
}], ...invalidated.map((connection) => ({
|
|
2854
3106
|
_tag: "ConnectionInvalidated",
|
|
2855
|
-
connection
|
|
3107
|
+
connection
|
|
2856
3108
|
}))].reduce(updateRemote, remote);
|
|
2857
|
-
const inFlight =
|
|
3109
|
+
const inFlight = requirements.some((requirement) => requirement.fields.some((field) => remote.entities[entityKey(requirement.entity, requirement.id)]?.stale.has(field) === true && refreshIsInFlight(remote.refresh, requirement, field)));
|
|
2858
3110
|
if (marked === remote && !inFlight) return model;
|
|
2859
|
-
const refresh =
|
|
2860
|
-
...marked.refresh,
|
|
2861
|
-
requested: marked.refresh.requested + 1
|
|
2862
|
-
};
|
|
3111
|
+
const refresh = withRefreshRequested(marked.refresh, requirements, invalidated);
|
|
2863
3112
|
return bound.store.set(model, {
|
|
2864
3113
|
...marked,
|
|
2865
3114
|
refresh
|
|
@@ -3034,6 +3283,7 @@ const bindDomain = (definition, store) => {
|
|
|
3034
3283
|
},
|
|
3035
3284
|
plan: (model, projection, options) => Remote.plan(bound, model, projection, options),
|
|
3036
3285
|
storeOf: (model) => storeOf(bound, model),
|
|
3286
|
+
confirmed: (projection) => confirmed(bound, projection),
|
|
3037
3287
|
prefetch: (model, projection, options = {}) => Effect.gen(function* () {
|
|
3038
3288
|
const { policy = RemotePolicy.cacheFirst, now = Date.now } = options;
|
|
3039
3289
|
const client = yield* RemoteClient;
|
|
@@ -3143,6 +3393,19 @@ const bindDomain = (definition, store) => {
|
|
|
3143
3393
|
} : void 0;
|
|
3144
3394
|
},
|
|
3145
3395
|
refresh: (model, target) => Remote.refresh(bound, model, target),
|
|
3396
|
+
overlay: (model, id, optimistic) => bound.store.set(model, updateRemote(bound.store.get(model), {
|
|
3397
|
+
_tag: "OverlayShown",
|
|
3398
|
+
id,
|
|
3399
|
+
optimistic
|
|
3400
|
+
})),
|
|
3401
|
+
lift: (model, id) => {
|
|
3402
|
+
const remote = bound.store.get(model);
|
|
3403
|
+
const lifted = updateRemote(remote, {
|
|
3404
|
+
_tag: "OverlayLifted",
|
|
3405
|
+
id
|
|
3406
|
+
});
|
|
3407
|
+
return lifted === remote ? model : bound.store.set(model, lifted);
|
|
3408
|
+
},
|
|
3146
3409
|
fetch: (ref) => ({
|
|
3147
3410
|
name: `Remote.query(${ref.query})`,
|
|
3148
3411
|
args: {
|
|
@@ -3222,6 +3485,6 @@ const pageSize = (window, key) => {
|
|
|
3222
3485
|
return size === void 0 ? {} : { [key]: size };
|
|
3223
3486
|
};
|
|
3224
3487
|
//#endregion
|
|
3225
|
-
export { ConnectionChange, ConnectionChangeSchema, Entity, Live, LiveChange, LiveEdge, LiveRequirement, MAX_FIELDS_PER_REQUEST, MAX_RELATION_DEPTH, Mutate, Mutation, MutationRequest, MutationResult, NormalizedEntity, PageSize, Query, QueryEdge, QueryRequest, QueryResult, QueryRpc, REMOTE_CACHE_VERSION, REMOTE_PROTOCOL_VERSION, Read, ReadBatch, ReadBatchResult, ReadRequest, RelationAnnotation, RelationEntityAnnotation, RelationRequest, Remote, RemoteClient, RemoteConnections, RemoteData, RemoteLiveError, RemoteMutationError, RemotePersistence, RemotePolicy, RemoteProtocolError, RemoteQueryError, RemoteReadError, RemoteRequirements, RemoteRpc, Requirement, Selection, WindowSchema, WireBoundary, addLayer, addOverlay, applyConnectionEvent, applyEntityEvent, assemble, beginMutation, beginOptimistic, classifyLive, clearStale, coalesceQueries, coalesceReads, coalescedLayer, connectionChangeOf, connectionIdentity, connectionsOf, cursor, edge, emptyConnection, emptyLiveState, emptyMutationState, emptyOptimistic, emptyStore, entityKey, entry, failMutation, gc, hasField, hasNext, hasPrevious, initialRemoteModel, inspectEntity, inspectRemote, invalidateConnection, isFieldStale, isGapped, isLoading, isRefPage, isRemoteMessage, isStale, isTombstone, items, liveEventOf, liveHasNext, liveHasPrevious, markStale, merge, missingFields, mutateRemote, mutationStatus, pageSchema, plan, pruneOverlays, reachable, readField, reconcileMutation, refParts, refreshConnection, refsIn, relationOf, relationShape, remoteDataSchema, remoteError, remoteErrorSchema, remoteMessageCases, remoteMessageSchema, remoteModelSchema, remove, removeLayer, removeOverlay, requirementKey, requirementsOf, retentionRootsSchema, schemaOf, segment, setStale, settleFailure, settleSuccess, shouldWake, stableStringify, targetsOf, terminal, tombstone, unknown, updateRemote, visibleItems, visibleStore, windowKey, writeEntities, writeEntity, writeRead };
|
|
3488
|
+
export { ConnectionChange, ConnectionChangeSchema, Entity, Live, LiveChange, LiveEdge, LiveRequirement, MAX_FIELDS_PER_REQUEST, MAX_RELATION_DEPTH, Mutate, Mutation, MutationRequest, MutationResult, NormalizedEntity, PageSize, Query, QueryEdge, QueryRequest, QueryResult, QueryRpc, RELATION_ALIAS, REMOTE_CACHE_VERSION, REMOTE_PROTOCOL_VERSION, Read, ReadBatch, ReadBatchResult, ReadRequest, RelationAnnotation, RelationEntityAnnotation, RelationRequest, Remote, RemoteClient, RemoteConnections, RemoteData, RemoteLiveError, RemoteMutationError, RemotePersistence, RemotePolicy, RemoteProtocolError, RemoteQueryError, RemoteReadError, RemoteRequirements, RemoteRpc, Requirement, Selection, WindowSchema, WireBoundary, addLayer, addOverlay, aliasedField, applyConnectionEvent, applyEntityEvent, assemble, beginMutation, beginOptimistic, classifyLive, clearStale, coalesceQueries, coalesceReads, coalescedLayer, connectionChangeOf, connectionIdentity, connectionsOf, cursor, edge, emptyConnection, emptyLiveState, emptyMutationState, emptyOptimistic, emptyRefresh, emptyStore, entityKey, entry, failMutation, gc, hasField, hasNext, hasPrevious, initialRemoteModel, inspectEntity, inspectRemote, invalidateConnection, isFieldStale, isGapped, isLoading, isRefPage, isRemoteMessage, isStale, isTombstone, items, liveEventOf, liveHasNext, liveHasPrevious, markStale, merge, missingFields, mutateRemote, mutationStatus, pageSchema, plan, pruneOverlays, reachable, readField, reconcileMutation, refParts, refreshConnection, refreshIsInFlight, refreshedAt, refsIn, relationAlias, relationOf, relationShape, remoteDataSchema, remoteError, remoteErrorSchema, remoteMessageCases, remoteMessageSchema, remoteModelSchema, remove, removeLayer, removeOverlay, requirementKey, requirementsOf, retentionRootsSchema, schemaOf, segment, setStale, settleFailure, settleSuccess, shouldWake, stableStringify, targetsOf, terminal, tombstone, unknown, updateRemote, visibleItems, visibleStore, windowKey, withRefreshRequested, writeEntities, writeEntity, writeRead };
|
|
3226
3489
|
|
|
3227
3490
|
//# sourceMappingURL=index.mjs.map
|