foldkit-remote 0.18.1 → 0.20.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 +40 -5
- package/dist/{index-86gaaCWm.d.mts → index-BEvVAL76.d.mts} +148 -75
- package/dist/index-BEvVAL76.d.mts.map +1 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +1 -1
- package/dist/port.d.mts +1 -3
- package/dist/port.d.mts.map +1 -1
- package/dist/port.mjs +2 -3
- package/dist/{src-Cp71fJac.mjs → src-BDqn7Ocr.mjs} +189 -104
- package/dist/src-BDqn7Ocr.mjs.map +1 -0
- package/package.json +3 -3
- package/dist/index-86gaaCWm.d.mts.map +0 -1
- package/dist/src-Cp71fJac.mjs.map +0 -1
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { Context, Data, Deferred, Duration, Effect, Layer, Match, Option, Predicate, Record, Request, RequestResolver, Result, Schema, SchemaAST, SchemaGetter, Stream } from "effect";
|
|
2
2
|
import { KeyValueStore } from "effect/persistence";
|
|
3
3
|
import * as Domain from "foldkit-entity";
|
|
4
|
-
import { Entity, Expr, Query, QueryEvaluateError, ReadFilter, SchemaShape, SelectionTypeId, Write, compareRows, dependenciesOf, evaluate } from "foldkit-entity";
|
|
4
|
+
import { Entity, Expr, Operators, Query, QueryEvaluateError, ReadFilter, SchemaShape, SelectionTypeId, Write, compareRows, dependenciesOf, evaluate } from "foldkit-entity";
|
|
5
5
|
import { mapMessage } from "foldkit/command";
|
|
6
6
|
import { defineMessageUnion } from "foldkit/message";
|
|
7
7
|
import * as Subscription from "foldkit/subscription";
|
|
8
8
|
import { Metadata } from "foldkit-surface";
|
|
9
9
|
import { Rpc, RpcGroup } from "effect/rpc";
|
|
10
10
|
import { defineTaggedUnion } from "foldkit/schema";
|
|
11
|
-
|
|
11
|
+
// src/requirement.ts
|
|
12
12
|
/**
|
|
13
13
|
* What a Remote Projection needs from the server: entity slices and query
|
|
14
14
|
* connections. They ride on `Projection.metadata` under Remote's own keys, so
|
|
@@ -110,8 +110,7 @@ const RemoteConnections = Metadata.key("remote.connection", {
|
|
|
110
110
|
const requirementsOf = (projection) => RemoteRequirements.get(projection.metadata);
|
|
111
111
|
/** The query connections a Projection carries. */
|
|
112
112
|
const connectionsOf = (projection) => RemoteConnections.get(projection.metadata);
|
|
113
|
-
|
|
114
|
-
//#region src/data.ts
|
|
113
|
+
// src/data.ts
|
|
115
114
|
/**
|
|
116
115
|
* Whether two values the server sent are the same data: equal primitives, or
|
|
117
116
|
* arrays and plain objects whose entries are. Anything else is compared by
|
|
@@ -129,8 +128,7 @@ const isPlain = (value) => {
|
|
|
129
128
|
const prototype = Object.getPrototypeOf(value);
|
|
130
129
|
return prototype === Object.prototype || prototype === null;
|
|
131
130
|
};
|
|
132
|
-
|
|
133
|
-
//#region src/relation.ts
|
|
131
|
+
// src/relation.ts
|
|
134
132
|
/**
|
|
135
133
|
* Relation values on the wire and in the store: a ref key (`"Entity:id"`), a
|
|
136
134
|
* nullable ref key, an array of ref keys, or a page of ref keys. The planner
|
|
@@ -218,8 +216,7 @@ const shapeOf = (ast, nullable) => {
|
|
|
218
216
|
}
|
|
219
217
|
}
|
|
220
218
|
};
|
|
221
|
-
|
|
222
|
-
//#region src/store.ts
|
|
219
|
+
// src/store.ts
|
|
223
220
|
/**
|
|
224
221
|
* The pure entity store: values, per-field presence, staleness, and tombstones.
|
|
225
222
|
*
|
|
@@ -406,8 +403,7 @@ const missingFields = (store, key, fields, windows) => {
|
|
|
406
403
|
return requested !== void 0 && stored !== void 0 && stored !== "" && stored !== requested;
|
|
407
404
|
});
|
|
408
405
|
};
|
|
409
|
-
|
|
410
|
-
//#region src/mutation.ts
|
|
406
|
+
// src/mutation.ts
|
|
411
407
|
/**
|
|
412
408
|
* Mutation status and idempotent reconciliation.
|
|
413
409
|
*
|
|
@@ -420,6 +416,7 @@ const emptyMutationState = {
|
|
|
420
416
|
applied: /* @__PURE__ */ new Set(),
|
|
421
417
|
failed: /* @__PURE__ */ new Set(),
|
|
422
418
|
errors: /* @__PURE__ */ new Map(),
|
|
419
|
+
operations: /* @__PURE__ */ new Map(),
|
|
423
420
|
sequence: 0
|
|
424
421
|
};
|
|
425
422
|
/**
|
|
@@ -442,17 +439,20 @@ const beginMutation = (state, requestId) => ({
|
|
|
442
439
|
pending: /* @__PURE__ */ new Set([...state.pending, requestId]),
|
|
443
440
|
sequence: state.sequence + 1
|
|
444
441
|
});
|
|
445
|
-
const failMutation = (state, requestId, error) => {
|
|
442
|
+
const failMutation = (state, requestId, error, operation) => {
|
|
446
443
|
const pending = new Set(state.pending);
|
|
447
444
|
pending.delete(requestId);
|
|
448
445
|
const failed = remember(state.failed, requestId);
|
|
449
446
|
const errors = new Map([...state.errors, [requestId, error]]);
|
|
447
|
+
const operations = new Map([...state.operations, [requestId, operation]]);
|
|
450
448
|
for (const id of errors.keys()) if (!failed.has(id)) errors.delete(id);
|
|
449
|
+
for (const id of operations.keys()) if (!failed.has(id)) operations.delete(id);
|
|
451
450
|
return {
|
|
452
451
|
...state,
|
|
453
452
|
pending,
|
|
454
453
|
failed,
|
|
455
|
-
errors
|
|
454
|
+
errors,
|
|
455
|
+
operations
|
|
456
456
|
};
|
|
457
457
|
};
|
|
458
458
|
const mutationStatus = (state, requestId) => {
|
|
@@ -487,6 +487,23 @@ const reconcileMutation = (store, state, requestId, answer) => {
|
|
|
487
487
|
};
|
|
488
488
|
};
|
|
489
489
|
const conflict = Schema.TaggedStruct("Conflict", {});
|
|
490
|
+
const unmet = Schema.TaggedStruct("Unmet", {});
|
|
491
|
+
/**
|
|
492
|
+
* The row the write would leave breaks one of its Entity's invariants: which
|
|
493
|
+
* one, what to tell the author, and the fields to show it beside. None and
|
|
494
|
+
* empty when the invariant reads a member hidden from whoever asked.
|
|
495
|
+
*/
|
|
496
|
+
const invalid = Schema.TaggedStruct("Invalid", {
|
|
497
|
+
rule: Schema.OptionFromNullOr(Schema.String),
|
|
498
|
+
message: Schema.String,
|
|
499
|
+
path: Schema.Array(Schema.String)
|
|
500
|
+
});
|
|
501
|
+
const conflictOrInvalid = Schema.Union([conflict, invalid]);
|
|
502
|
+
const conflictUnmetOrInvalid = Schema.Union([
|
|
503
|
+
conflict,
|
|
504
|
+
unmet,
|
|
505
|
+
invalid
|
|
506
|
+
]);
|
|
490
507
|
const anyField = Schema.TaggedStruct("Field", {
|
|
491
508
|
key: Schema.String,
|
|
492
509
|
reason: Schema.Unknown
|
|
@@ -500,10 +517,24 @@ const Refusal = {
|
|
|
500
517
|
}),
|
|
501
518
|
/** The row moved on since the client read it; the author decides what wins. */
|
|
502
519
|
conflict,
|
|
520
|
+
/** The row does not hold what the write's `when` asks of it, where it would commit. */
|
|
521
|
+
unmet,
|
|
522
|
+
/** The row the write would leave breaks an invariant of its Entity. */
|
|
523
|
+
invalid,
|
|
503
524
|
/** Whether a refusal, of whatever mutation, is a `Refusal.field`. */
|
|
504
525
|
isField: Schema.is(anyField),
|
|
505
526
|
/** Whether a refusal, of whatever mutation, is `Refusal.conflict`. */
|
|
506
|
-
isConflict: Schema.is(conflict)
|
|
527
|
+
isConflict: Schema.is(conflict),
|
|
528
|
+
/** Whether a refusal, of whatever mutation, is `Refusal.unmet`. */
|
|
529
|
+
isUnmet: Schema.is(unmet),
|
|
530
|
+
/** Whether a refusal, of whatever mutation, is `Refusal.invalid`. */
|
|
531
|
+
isInvalid: Schema.is(invalid),
|
|
532
|
+
/**
|
|
533
|
+
* Whether a refusal is `Refusal.invalid` naming this invariant: the one
|
|
534
|
+
* `Entity.invariant` returned, or one `Entity.invariants` reads. An invariant
|
|
535
|
+
* over a member hidden from the asker names none, so it matches nothing.
|
|
536
|
+
*/
|
|
537
|
+
isInvalidBy: (refusal, invariant) => Schema.is(invalid)(refusal) && Option.contains(refusal.rule, invariant.id)
|
|
507
538
|
};
|
|
508
539
|
/** The codec itself, or a `Schema.Struct` over the fields. */
|
|
509
540
|
const schemaOf = (shape) => Schema.isSchema(shape) ? shape : Schema.Struct(shape);
|
|
@@ -525,11 +556,18 @@ const patchOfWrite = (bound) => ({
|
|
|
525
556
|
/**
|
|
526
557
|
* What a declared write shows before the server answers: an update's or an
|
|
527
558
|
* insert's row as a patch (an update's only for the keys named, when some
|
|
528
|
-
* are). A delete shows nothing, since no operation removes a row from every
|
|
559
|
+
* are), and an update's increments as amounts over the value the store holds. A delete shows nothing, since no operation removes a row from every
|
|
529
560
|
* list it is in; its answer does.
|
|
530
561
|
*/
|
|
531
562
|
const optimisticOfWrite = (write, input, keys) => Match.value(write).pipe(Match.tagsExhaustive({
|
|
532
|
-
Update: () =>
|
|
563
|
+
Update: () => {
|
|
564
|
+
const bound = Write.bind(write, input, keys);
|
|
565
|
+
const patch = patchOfWrite(bound);
|
|
566
|
+
return [Object.keys(bound.increments).length === 0 ? patch : {
|
|
567
|
+
...patch,
|
|
568
|
+
adds: bound.increments
|
|
569
|
+
}];
|
|
570
|
+
},
|
|
533
571
|
Insert: () => {
|
|
534
572
|
const patch = patchOfWrite(Write.bind(write, input));
|
|
535
573
|
return [{
|
|
@@ -553,13 +591,14 @@ const Mutation = {
|
|
|
553
591
|
* A mutation that is a declared `Write` (`update`, `insert` or `delete`): its
|
|
554
592
|
* input is the write's, and it answers nothing but the row it changed, as a
|
|
555
593
|
* patch, or its deletion. It may be refused as a conflict, for a write that
|
|
556
|
-
* `expect`s a revision the row has moved past
|
|
594
|
+
* `expect`s a revision the row has moved past, for a write with a `when` as
|
|
595
|
+
* unmet, and as invalid when the row it would leave breaks an invariant.
|
|
557
596
|
*/
|
|
558
597
|
write: (name, write) => ({
|
|
559
598
|
name,
|
|
560
599
|
Input: write.input.schema,
|
|
561
600
|
Output: Schema.Struct({}),
|
|
562
|
-
Refusal:
|
|
601
|
+
Refusal: Option.isSome(write.when) ? conflictUnmetOrInvalid : conflictOrInvalid,
|
|
563
602
|
write
|
|
564
603
|
}),
|
|
565
604
|
make: (name, config) => ({
|
|
@@ -569,8 +608,7 @@ const Mutation = {
|
|
|
569
608
|
Refusal: config.Refusal ?? Schema.Never
|
|
570
609
|
})
|
|
571
610
|
};
|
|
572
|
-
|
|
573
|
-
//#region src/query.ts
|
|
611
|
+
// src/query.ts
|
|
574
612
|
/**
|
|
575
613
|
* Queries and connections. A connection's identity is its descriptor plus the
|
|
576
614
|
* canonical encoded filter/sort input — **never** the pagination window, so
|
|
@@ -712,8 +750,7 @@ const Query$1 = {
|
|
|
712
750
|
}
|
|
713
751
|
})
|
|
714
752
|
};
|
|
715
|
-
|
|
716
|
-
//#region src/wire.ts
|
|
753
|
+
// src/wire.ts
|
|
717
754
|
/**
|
|
718
755
|
* The Remote wire: Effect RPC semantics over `effect/rpc`. Remote owns
|
|
719
756
|
* the message shapes; Effect owns the transport. `Make`ing reads batch, mutations
|
|
@@ -822,9 +859,21 @@ const ReadBatchResult = Schema.Struct({
|
|
|
822
859
|
entities: Schema.Array(NormalizedEntity),
|
|
823
860
|
settled: Schema.Array(SettledFields)
|
|
824
861
|
});
|
|
862
|
+
/**
|
|
863
|
+
* One operation's identity across every client: minted where it is first sent
|
|
864
|
+
* and sent again with each retry of it. Bounded, since a server keys a record
|
|
865
|
+
* of the write by it.
|
|
866
|
+
*/
|
|
867
|
+
const OperationId = Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(128), Schema.isPattern(/^[A-Za-z0-9:._-]+$/));
|
|
825
868
|
const MutationRequest = Schema.Struct({
|
|
826
|
-
/**
|
|
869
|
+
/**
|
|
870
|
+
* This client's id for the request, the same on each retry of it. It is
|
|
871
|
+
* unique within one client's Model only (a counter there), so it names a
|
|
872
|
+
* request to that client; `operation` names it to a server.
|
|
873
|
+
*/
|
|
827
874
|
requestId: Schema.String,
|
|
875
|
+
/** The operation's identity across clients: what a server keys a record of the write by. */
|
|
876
|
+
operation: Schema.optional(OperationId),
|
|
828
877
|
mutation: Schema.String,
|
|
829
878
|
input: Schema.Unknown,
|
|
830
879
|
/** For a mutation that is a `Write`: the input keys to write, when not all of them. */
|
|
@@ -972,8 +1021,7 @@ const QueryRpc = Rpc.make("FoldkitRemoteQuery", {
|
|
|
972
1021
|
error: Schema.Union([RemoteQueryError, RemoteUnauthenticated])
|
|
973
1022
|
});
|
|
974
1023
|
const RemoteRpc = RpcGroup.make(Read, Mutate, QueryRpc, Live);
|
|
975
|
-
|
|
976
|
-
//#region src/coalesce.ts
|
|
1024
|
+
// src/coalesce.ts
|
|
977
1025
|
/**
|
|
978
1026
|
* Read coalescing: every requirement a client asks for goes through one
|
|
979
1027
|
* `RequestResolver`, so requirements issued together become one `ReadBatch`
|
|
@@ -1097,8 +1145,7 @@ const coalesceReads = (read, options = {}) => Effect.gen(function* () {
|
|
|
1097
1145
|
};
|
|
1098
1146
|
}));
|
|
1099
1147
|
});
|
|
1100
|
-
|
|
1101
|
-
//#region src/client.ts
|
|
1148
|
+
// src/client.ts
|
|
1102
1149
|
/**
|
|
1103
1150
|
* The transport seam: the `RemoteClient` Effect service, the RPC client it is
|
|
1104
1151
|
* built from, the wire-to-client reconstructions, and the mutation call.
|
|
@@ -1198,18 +1245,28 @@ const coalescedLayer = (layer, options = {}) => Layer.effect(RemoteClient, Effec
|
|
|
1198
1245
|
};
|
|
1199
1246
|
})).pipe(Layer.provide(layer));
|
|
1200
1247
|
/**
|
|
1248
|
+
* A new operation's identity: random, so unique across clients and reloads.
|
|
1249
|
+
* `randomUUID` exists only in a secure context, so a page served over plain
|
|
1250
|
+
* HTTP draws 128 random bits from `getRandomValues`, which exists in all.
|
|
1251
|
+
*/
|
|
1252
|
+
const mintOperation = () => typeof globalThis.crypto.randomUUID === "function" ? globalThis.crypto.randomUUID() : Array.from(globalThis.crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(16)), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
1253
|
+
/**
|
|
1201
1254
|
* `Remote.mutate` as a standalone effect, so `Remote.mutateInto` can reuse it
|
|
1202
1255
|
* without the object literal referencing itself.
|
|
1203
1256
|
*/
|
|
1204
|
-
const mutateRemote = Effect.fn("Remote.mutate")(function* (mutation, input, requestId,
|
|
1257
|
+
const mutateRemote = Effect.fn("Remote.mutate")(function* (mutation, input, requestId, options = {}) {
|
|
1258
|
+
const { keys } = options;
|
|
1259
|
+
const operation = options.operation ?? (yield* Effect.sync(mintOperation));
|
|
1205
1260
|
yield* Effect.annotateCurrentSpan({
|
|
1206
1261
|
mutation: mutation.name,
|
|
1207
|
-
requestId
|
|
1262
|
+
requestId,
|
|
1263
|
+
operation
|
|
1208
1264
|
});
|
|
1209
1265
|
const client = yield* RemoteClient;
|
|
1210
1266
|
const encoded = yield* Schema.encodeUnknownEffect(mutation.Input)(input).pipe(Effect.catchTag("SchemaError", (error) => Effect.fail(new RemoteMutationError({ message: error.message }))));
|
|
1211
1267
|
const result = yield* client.mutate({
|
|
1212
1268
|
requestId,
|
|
1269
|
+
operation,
|
|
1213
1270
|
mutation: mutation.name,
|
|
1214
1271
|
input: encoded,
|
|
1215
1272
|
...keys === void 0 ? {} : { keys }
|
|
@@ -1221,8 +1278,7 @@ const mutateRemote = Effect.fn("Remote.mutate")(function* (mutation, input, requ
|
|
|
1221
1278
|
deleted: result.deleted ?? []
|
|
1222
1279
|
};
|
|
1223
1280
|
});
|
|
1224
|
-
|
|
1225
|
-
//#region src/connection.ts
|
|
1281
|
+
// src/connection.ts
|
|
1226
1282
|
/**
|
|
1227
1283
|
* The pure connection model: an ordered normalized structure of edges with
|
|
1228
1284
|
* explicit known boundaries. Pages, live events, and optimistic inserts are all
|
|
@@ -1385,8 +1441,7 @@ const hasNext = (connection) => {
|
|
|
1385
1441
|
};
|
|
1386
1442
|
/** A connection with more than one segment has an explicit unknown gap. */
|
|
1387
1443
|
const isGapped = (connection) => connection.segments.length > 1;
|
|
1388
|
-
|
|
1389
|
-
//#region src/selection.ts
|
|
1444
|
+
// src/selection.ts
|
|
1390
1445
|
/**
|
|
1391
1446
|
* Selections: the fields a Surface picks of an entity (nested through
|
|
1392
1447
|
* relations, paged through connections), the requirement graph they imply,
|
|
@@ -1568,8 +1623,7 @@ const assembleRelation = (store, value, relation) => {
|
|
|
1568
1623
|
refreshing
|
|
1569
1624
|
};
|
|
1570
1625
|
};
|
|
1571
|
-
|
|
1572
|
-
//#region src/entity.ts
|
|
1626
|
+
// src/entity.ts
|
|
1573
1627
|
/**
|
|
1574
1628
|
* Entities: the descriptor an application declares, refs, ref pages, and
|
|
1575
1629
|
* patches, with the codecs that carry refs on the wire.
|
|
@@ -1657,8 +1711,7 @@ const Entity$1 = {
|
|
|
1657
1711
|
values: patch
|
|
1658
1712
|
})
|
|
1659
1713
|
};
|
|
1660
|
-
|
|
1661
|
-
//#region src/placement.ts
|
|
1714
|
+
// src/placement.ts
|
|
1662
1715
|
/**
|
|
1663
1716
|
* Where one row falls in a loaded list, by the list's own declared order:
|
|
1664
1717
|
* the decidable half of placing a row instead of fetching the list again.
|
|
@@ -1675,16 +1728,14 @@ const Entity$1 = {
|
|
|
1675
1728
|
*/
|
|
1676
1729
|
const RowPlace = Data.taggedEnum();
|
|
1677
1730
|
/**
|
|
1678
|
-
* The values a row holds of the fields an order reads,
|
|
1679
|
-
* one. An `id` no Selection stored is its key's, when the id is stored as
|
|
1731
|
+
* The values a row holds of the fields an order reads, a call's arguments
|
|
1732
|
+
* included, or none when it lacks one. An `id` no Selection stored is its key's, when the id is stored as
|
|
1680
1733
|
* text as a key holds it, so the id tie-break every order ends on does not
|
|
1681
1734
|
* make such a row unplaceable; a number id is never compared as text.
|
|
1682
1735
|
*/
|
|
1683
1736
|
const keysOf = (store, key, id, idIsText, terms) => {
|
|
1684
1737
|
const values = Object.create(null);
|
|
1685
|
-
for (const
|
|
1686
|
-
if (term.expr._tag !== "Field") return Option.none();
|
|
1687
|
-
const field = term.expr.key;
|
|
1738
|
+
for (const { key: field } of dependenciesOf(...terms).fields) {
|
|
1688
1739
|
const stored = readField(store, key, field);
|
|
1689
1740
|
const value = field === "id" && Option.isNone(stored) && idIsText ? id : stored;
|
|
1690
1741
|
if (Option.isNone(value)) return Option.none();
|
|
@@ -1694,12 +1745,12 @@ const keysOf = (store, key, id, idIsText, terms) => {
|
|
|
1694
1745
|
};
|
|
1695
1746
|
/**
|
|
1696
1747
|
* Where the row stored under `key` falls in `connection`, by `body`'s order
|
|
1697
|
-
* for `input` (encoded, as a connection's identity carries it)
|
|
1698
|
-
* edge, if the list holds it, is left out:
|
|
1699
|
-
* where it goes now.
|
|
1748
|
+
* for `input` (encoded, as a connection's identity carries it), a call in it
|
|
1749
|
+
* run by `operators`. The row's own edge, if the list holds it, is left out:
|
|
1750
|
+
* placing a row that moved asks where it goes now.
|
|
1700
1751
|
*/
|
|
1701
|
-
const placeIn = (store, connection, body, input, key) => {
|
|
1702
|
-
const placement = Query.placement(body, input);
|
|
1752
|
+
const placeIn = (store, connection, body, input, key, operators) => {
|
|
1753
|
+
const placement = Query.placement(body, input, { operators });
|
|
1703
1754
|
if (placement._tag === "NotPlaceable") return RowPlace.Unknown({ reason: placement.reason });
|
|
1704
1755
|
const terms = Query.orderFor(body, input);
|
|
1705
1756
|
const prefix = `${body.entity.name}:`;
|
|
@@ -1711,7 +1762,10 @@ const placeIn = (store, connection, body, input, key) => {
|
|
|
1711
1762
|
/** Negative when the row sorts before the edge; none when the edge cannot be compared. */
|
|
1712
1763
|
const against = (edge) => Option.flatMap(keysOf(store, entityKey(edge.ref.entity, edge.ref.id), Option.some(edge.ref.id), idIsText, terms), (values) => {
|
|
1713
1764
|
try {
|
|
1714
|
-
return Option.some(compareRows(terms, row.value, values, body.entity.name
|
|
1765
|
+
return Option.some(compareRows(terms, row.value, values, body.entity.name, {
|
|
1766
|
+
input,
|
|
1767
|
+
operators
|
|
1768
|
+
}));
|
|
1715
1769
|
} catch {
|
|
1716
1770
|
return Option.none();
|
|
1717
1771
|
}
|
|
@@ -1767,8 +1821,7 @@ const withEdge = (connection, added, place) => ({
|
|
|
1767
1821
|
};
|
|
1768
1822
|
})
|
|
1769
1823
|
});
|
|
1770
|
-
|
|
1771
|
-
//#region src/matching.ts
|
|
1824
|
+
// src/matching.ts
|
|
1772
1825
|
/**
|
|
1773
1826
|
* Running a query body against the rows the client already holds.
|
|
1774
1827
|
*
|
|
@@ -1855,7 +1908,7 @@ const matchingEncoded = (store, descriptor, encoded, options = {}) => {
|
|
|
1855
1908
|
});
|
|
1856
1909
|
}
|
|
1857
1910
|
return {
|
|
1858
|
-
matched: evaluate(body, encoded, rows).map((row) => row.__key),
|
|
1911
|
+
matched: evaluate(body, encoded, rows, { operators: options.operators }).map((row) => row.__key),
|
|
1859
1912
|
skipped
|
|
1860
1913
|
};
|
|
1861
1914
|
};
|
|
@@ -1868,7 +1921,7 @@ const matchingEncoded = (store, descriptor, encoded, options = {}) => {
|
|
|
1868
1921
|
* asymmetry with `matching`, which takes a decoded input, is the shape of the
|
|
1869
1922
|
* two real callers rather than an oversight.
|
|
1870
1923
|
*/
|
|
1871
|
-
const belongsEncoded = (store, descriptor, encoded, key) => {
|
|
1924
|
+
const belongsEncoded = (store, descriptor, encoded, key, operators) => {
|
|
1872
1925
|
const entry = store[key];
|
|
1873
1926
|
if (entry === void 0) return "unknown";
|
|
1874
1927
|
if (entry.tombstone) return "no";
|
|
@@ -1876,7 +1929,10 @@ const belongsEncoded = (store, descriptor, encoded, key) => {
|
|
|
1876
1929
|
if (body !== void 0 && decidableOn(body).some((field) => entry.stale.has(field))) return "unknown";
|
|
1877
1930
|
let judged;
|
|
1878
1931
|
try {
|
|
1879
|
-
judged = matchingEncoded(store, descriptor, encoded, {
|
|
1932
|
+
judged = matchingEncoded(store, descriptor, encoded, {
|
|
1933
|
+
among: [key],
|
|
1934
|
+
operators
|
|
1935
|
+
});
|
|
1880
1936
|
} catch (error) {
|
|
1881
1937
|
if (error instanceof QueryEvaluateError) return "unknown";
|
|
1882
1938
|
throw error;
|
|
@@ -1885,8 +1941,7 @@ const belongsEncoded = (store, descriptor, encoded, key) => {
|
|
|
1885
1941
|
if (judged.skipped.includes(key)) return "unknown";
|
|
1886
1942
|
return "no";
|
|
1887
1943
|
};
|
|
1888
|
-
|
|
1889
|
-
//#region src/impact.ts
|
|
1944
|
+
// src/impact.ts
|
|
1890
1945
|
/**
|
|
1891
1946
|
* What a write did to the lists the client holds: given one change and one
|
|
1892
1947
|
* loaded connection, whether the connection is still right as it stands.
|
|
@@ -1949,7 +2004,7 @@ const sameOrder = (left, right) => left.segments.length === right.segments.lengt
|
|
|
1949
2004
|
* What `change` does to `connection`, judged against `store`, the store after
|
|
1950
2005
|
* the write.
|
|
1951
2006
|
*/
|
|
1952
|
-
const impactOn = (connection, store, change) => {
|
|
2007
|
+
const impactOn = (connection, store, change, operators) => {
|
|
1953
2008
|
const { descriptor } = connection;
|
|
1954
2009
|
const body = descriptor.body;
|
|
1955
2010
|
if ((body?.entity.name ?? descriptor.Result.entity) !== change.entity) return kept;
|
|
@@ -1975,7 +2030,7 @@ const impactOn = (connection, store, change) => {
|
|
|
1975
2030
|
const refilters = touches(roles.predicate);
|
|
1976
2031
|
const unloaded = hasPrevious(pages) || hasNext(pages) || isGapped(pages);
|
|
1977
2032
|
if (!(held && reorders) && !refilters && !(!held && (created || reorders && unloaded))) return kept;
|
|
1978
|
-
const belongs = held && !refilters ? "yes" : belongsEncoded(store, descriptor, connection.encoded, key);
|
|
2033
|
+
const belongs = held && !refilters ? "yes" : belongsEncoded(store, descriptor, connection.encoded, key, operators);
|
|
1979
2034
|
if (belongs === "unknown") return Impact.Invalidated({ reason: `${key} changed a field "${descriptor.name}" filters on, and the client cannot judge it` });
|
|
1980
2035
|
if (!held && belongs === "no") return kept;
|
|
1981
2036
|
if (held && belongs === "yes" && !reorders) return kept;
|
|
@@ -1990,7 +2045,7 @@ const impactOn = (connection, store, change) => {
|
|
|
1990
2045
|
}), place);
|
|
1991
2046
|
return sameOrder(pages, next) ? kept : Impact.Placed({ connection: next });
|
|
1992
2047
|
};
|
|
1993
|
-
return RowPlace.$match(placeIn(store, pages, body, connection.encoded, key), {
|
|
2048
|
+
return RowPlace.$match(placeIn(store, pages, body, connection.encoded, key, operators), {
|
|
1994
2049
|
Before: placedAt,
|
|
1995
2050
|
End: placedAt,
|
|
1996
2051
|
Outside: () => held ? Impact.Placed({ connection: withoutEdge(pages, key) }) : kept,
|
|
@@ -1999,8 +2054,7 @@ const impactOn = (connection, store, change) => {
|
|
|
1999
2054
|
}
|
|
2000
2055
|
});
|
|
2001
2056
|
};
|
|
2002
|
-
|
|
2003
|
-
//#region src/inspect.ts
|
|
2057
|
+
// src/inspect.ts
|
|
2004
2058
|
const inspectEntry = (key, entry) => ({
|
|
2005
2059
|
key,
|
|
2006
2060
|
present: [...entry.present],
|
|
@@ -2030,8 +2084,7 @@ const inspectEntity = (model, key) => {
|
|
|
2030
2084
|
const entry = model.entities[key];
|
|
2031
2085
|
return entry === void 0 ? void 0 : inspectEntry(key, entry);
|
|
2032
2086
|
};
|
|
2033
|
-
|
|
2034
|
-
//#region src/optimistic.ts
|
|
2087
|
+
// src/optimistic.ts
|
|
2035
2088
|
/**
|
|
2036
2089
|
* Optimistic layers. Pending changes are held as ordered layers over a base
|
|
2037
2090
|
* store and as overlays over a connection; the visible value is recomputed, never
|
|
@@ -2137,7 +2190,7 @@ const removeOverlay = (optimistic, id) => {
|
|
|
2137
2190
|
* entity only a request holds (a temporary id) is not the server's to age, and
|
|
2138
2191
|
* is dated `Infinity`, so freshness never plans or times it.
|
|
2139
2192
|
*/
|
|
2140
|
-
const visibleStore = (base, optimistic) => writeEntities(base, optimistic
|
|
2193
|
+
const visibleStore = (base, optimistic) => writeEntities(base, resolvedPatches(base, optimistic).map((patch) => {
|
|
2141
2194
|
const key = entityKey(patch.entity, patch.id);
|
|
2142
2195
|
const under = base[key];
|
|
2143
2196
|
return {
|
|
@@ -2145,7 +2198,34 @@ const visibleStore = (base, optimistic) => writeEntities(base, optimistic.layers
|
|
|
2145
2198
|
values: patch.values,
|
|
2146
2199
|
updatedAt: under === void 0 || under.tombstone ? Infinity : under.updatedAt
|
|
2147
2200
|
};
|
|
2148
|
-
}))
|
|
2201
|
+
}));
|
|
2202
|
+
/**
|
|
2203
|
+
* Every pending patch in order, each amount added to the value under it: the
|
|
2204
|
+
* store's, or an earlier layer's. A prediction, so only over a number the
|
|
2205
|
+
* client holds: a field it has not read, or holds as anything else, keeps
|
|
2206
|
+
* what it shows until the server answers with the sum.
|
|
2207
|
+
*/
|
|
2208
|
+
const resolvedPatches = (base, optimistic) => {
|
|
2209
|
+
const shown = /* @__PURE__ */ new Map();
|
|
2210
|
+
return optimistic.layers.flatMap((layer) => layer.patches.map(({ adds, ...patch }) => {
|
|
2211
|
+
const key = entityKey(patch.entity, patch.id);
|
|
2212
|
+
const earlier = shown.get(key) ?? {};
|
|
2213
|
+
const values = { ...patch.values };
|
|
2214
|
+
const under = base[key];
|
|
2215
|
+
const valueUnder = (field) => Object.hasOwn(earlier, field) ? Option.some(earlier[field]) : Option.fromUndefinedOr(under?.values[field]);
|
|
2216
|
+
for (const [field, amount] of Object.entries(adds ?? {})) Option.map(Option.filter(valueUnder(field), Predicate.isNumber), (current) => {
|
|
2217
|
+
values[field] = current + amount;
|
|
2218
|
+
});
|
|
2219
|
+
shown.set(key, {
|
|
2220
|
+
...earlier,
|
|
2221
|
+
...values
|
|
2222
|
+
});
|
|
2223
|
+
return {
|
|
2224
|
+
...patch,
|
|
2225
|
+
values
|
|
2226
|
+
};
|
|
2227
|
+
}));
|
|
2228
|
+
};
|
|
2149
2229
|
/**
|
|
2150
2230
|
* A fresh page is newer than any settled overlay it covers: an edge the page
|
|
2151
2231
|
* carries no longer needs an insert overlay, and a `remove` overlay hiding it
|
|
@@ -2219,8 +2299,7 @@ const visibleItems = (connection, connectionId, overlays, store) => {
|
|
|
2219
2299
|
...take("append")
|
|
2220
2300
|
];
|
|
2221
2301
|
};
|
|
2222
|
-
|
|
2223
|
-
//#region src/live.ts
|
|
2302
|
+
// src/live.ts
|
|
2224
2303
|
const emptyLiveState = {
|
|
2225
2304
|
cursor: 0,
|
|
2226
2305
|
boundary: {}
|
|
@@ -2340,8 +2419,7 @@ const applyConnectionEvent = (state, optimistic, event, policy = {}) => {
|
|
|
2340
2419
|
const liveHasPrevious = (connection, state, connectionId) => hasPrevious(connection) || (state.boundary[connectionId]?.before ?? 0) > 0;
|
|
2341
2420
|
/** `hasNext` accounting for edges recorded outside the loaded boundary. */
|
|
2342
2421
|
const liveHasNext = (connection, state, connectionId) => hasNext(connection) || (state.boundary[connectionId]?.after ?? 0) > 0;
|
|
2343
|
-
|
|
2344
|
-
//#region src/plan.ts
|
|
2422
|
+
// src/plan.ts
|
|
2345
2423
|
/**
|
|
2346
2424
|
* The pure requirement planner.
|
|
2347
2425
|
*
|
|
@@ -2434,8 +2512,7 @@ const plan = (store, requirements, options = {}) => {
|
|
|
2434
2512
|
const nested = followed.length === 0 ? [] : plan(store, followed, options);
|
|
2435
2513
|
return [...Requirement.merge([...planned, ...nested])].sort((a, b) => entityKey(a.entity, a.id) < entityKey(b.entity, b.id) ? -1 : 1);
|
|
2436
2514
|
};
|
|
2437
|
-
|
|
2438
|
-
//#region src/retain.ts
|
|
2515
|
+
// src/retain.ts
|
|
2439
2516
|
/**
|
|
2440
2517
|
* The entity keys the roots reach: each root entity, the targets its retained
|
|
2441
2518
|
* fields refer to, and recursively the targets a nested relation selects.
|
|
@@ -2507,8 +2584,7 @@ const keptOf = (record, keep) => {
|
|
|
2507
2584
|
const kept = entries.filter(([key]) => keep(key));
|
|
2508
2585
|
return kept.length === entries.length ? record : Object.fromEntries(kept);
|
|
2509
2586
|
};
|
|
2510
|
-
|
|
2511
|
-
//#region src/persistence.ts
|
|
2587
|
+
// src/persistence.ts
|
|
2512
2588
|
/**
|
|
2513
2589
|
* Remote cache persistence: a snapshot of the entity store and nothing else.
|
|
2514
2590
|
* Runtime state (connections' live cursors, optimistic layers, the mutation
|
|
@@ -2705,8 +2781,7 @@ const RemotePersistence = {
|
|
|
2705
2781
|
return restored;
|
|
2706
2782
|
})
|
|
2707
2783
|
};
|
|
2708
|
-
|
|
2709
|
-
//#region src/remoteData.ts
|
|
2784
|
+
// src/remoteData.ts
|
|
2710
2785
|
/** `RemoteData`: the state of a value the store may not hold yet. */
|
|
2711
2786
|
/**
|
|
2712
2787
|
* The server would not answer who is asking: their session ended or was
|
|
@@ -2862,8 +2937,7 @@ const RemoteData = {
|
|
|
2862
2937
|
}
|
|
2863
2938
|
}
|
|
2864
2939
|
};
|
|
2865
|
-
|
|
2866
|
-
//#region src/model.ts
|
|
2940
|
+
// src/model.ts
|
|
2867
2941
|
/**
|
|
2868
2942
|
* The Remote submodel: the store, connections, mutations, live state, and
|
|
2869
2943
|
* optimistic layers an application keeps in its Model, the Messages that
|
|
@@ -2991,7 +3065,8 @@ const remoteMessageCases = {
|
|
|
2991
3065
|
},
|
|
2992
3066
|
MutationFailed: {
|
|
2993
3067
|
requestId: Schema.String,
|
|
2994
|
-
error: remoteErrorSchema
|
|
3068
|
+
error: remoteErrorSchema,
|
|
3069
|
+
operation: Schema.String
|
|
2995
3070
|
},
|
|
2996
3071
|
OverlayShown: {
|
|
2997
3072
|
id: Schema.String,
|
|
@@ -3432,7 +3507,7 @@ const reduceRemote = (model, message) => {
|
|
|
3432
3507
|
}
|
|
3433
3508
|
case "MutationFailed": return {
|
|
3434
3509
|
...model,
|
|
3435
|
-
mutations: failMutation(model.mutations, message.requestId, message.error),
|
|
3510
|
+
mutations: failMutation(model.mutations, message.requestId, message.error, message.operation),
|
|
3436
3511
|
optimistic: settleFailure(model.optimistic, message.requestId)
|
|
3437
3512
|
};
|
|
3438
3513
|
case "LiveReceived": {
|
|
@@ -3749,8 +3824,7 @@ const writeRead = (store, requests, result, now = 0) => {
|
|
|
3749
3824
|
}
|
|
3750
3825
|
return setUnavailable(writeEntities(answered, writes, now), result.settled.map((entry) => [entityKey(entry.entity, entry.id), entry.fields]), true);
|
|
3751
3826
|
};
|
|
3752
|
-
|
|
3753
|
-
//#region src/policy.ts
|
|
3827
|
+
// src/policy.ts
|
|
3754
3828
|
const RemotePolicy = {
|
|
3755
3829
|
/** Fetch only what the store lacks. */
|
|
3756
3830
|
cacheFirst: { _tag: "CacheFirst" },
|
|
@@ -3779,8 +3853,7 @@ const RemotePolicy = {
|
|
|
3779
3853
|
*/
|
|
3780
3854
|
refreshes: (policy) => policy._tag !== "CacheFirst"
|
|
3781
3855
|
};
|
|
3782
|
-
|
|
3783
|
-
//#region src/resume.ts
|
|
3856
|
+
// src/resume.ts
|
|
3784
3857
|
/**
|
|
3785
3858
|
* What of Remote's state crosses from a server render to the browser.
|
|
3786
3859
|
*
|
|
@@ -3994,8 +4067,7 @@ const resumePart = (domain, options = {}) => ({
|
|
|
3994
4067
|
onSuccess: (capture) => Result.succeed(domain.store.set(model, restoreRemote(domain.store.get(model), capture)))
|
|
3995
4068
|
})
|
|
3996
4069
|
});
|
|
3997
|
-
|
|
3998
|
-
//#region src/json.ts
|
|
4070
|
+
// src/json.ts
|
|
3999
4071
|
/**
|
|
4000
4072
|
* Remote's three calls as JSON: one request, `{ operation, payload }`, and one
|
|
4001
4073
|
* answer, `{ result }` or `{ error }`. `RemoteServer.answer` is the other end.
|
|
@@ -4100,8 +4172,7 @@ const http = (url, options = {}) => json(async (request) => {
|
|
|
4100
4172
|
if (body === notJson) throw new Error(status);
|
|
4101
4173
|
return body;
|
|
4102
4174
|
});
|
|
4103
|
-
|
|
4104
|
-
//#region src/sse.ts
|
|
4175
|
+
// src/sse.ts
|
|
4105
4176
|
/**
|
|
4106
4177
|
* Remote live over fetch with SSE framing: the other end of
|
|
4107
4178
|
* `foldkit-remote-server/fetch`'s live open. One `POST` carries the live
|
|
@@ -4221,8 +4292,7 @@ const httpWithLive = (url, options = {}) => ({
|
|
|
4221
4292
|
...http(url, options),
|
|
4222
4293
|
FoldkitRemoteLive: liveFetch(url, options)
|
|
4223
4294
|
});
|
|
4224
|
-
|
|
4225
|
-
//#region src/index.ts
|
|
4295
|
+
// src/index.ts
|
|
4226
4296
|
/**
|
|
4227
4297
|
* `foldkit-remote` — normalized application-facing server state.
|
|
4228
4298
|
*
|
|
@@ -4237,7 +4307,11 @@ const explainedDependencies = (dependencies) => ({
|
|
|
4237
4307
|
key
|
|
4238
4308
|
})),
|
|
4239
4309
|
inputs: dependencies.inputs,
|
|
4240
|
-
operations: dependencies.operations
|
|
4310
|
+
operations: dependencies.operations,
|
|
4311
|
+
operators: dependencies.operators.map(({ id, version }) => ({
|
|
4312
|
+
id,
|
|
4313
|
+
version
|
|
4314
|
+
}))
|
|
4241
4315
|
});
|
|
4242
4316
|
const descriptorOf = (entity) => Entity.is(entity) ? Entity$1.from(entity) : entity;
|
|
4243
4317
|
const selectionOf = (selection) => SelectionTypeId in selection ? Selection.from(selection) : selection;
|
|
@@ -4264,11 +4338,11 @@ const readContract = (source, shape, window) => {
|
|
|
4264
4338
|
* compared with neighbours that hold those fields. What the view reads, and so
|
|
4265
4339
|
* decodes, stays its Selection.
|
|
4266
4340
|
*/
|
|
4267
|
-
const placeableReadContract = (contract, body) => {
|
|
4341
|
+
const placeableReadContract = (contract, body, operators) => {
|
|
4268
4342
|
if (body === void 0) return contract;
|
|
4269
4343
|
const { requirement, ref } = contract;
|
|
4270
4344
|
const encoded = Schema.encodeSync(ref.Input)(ref.input);
|
|
4271
|
-
if (Query.placement(body, encoded)._tag !== "Placeable") return contract;
|
|
4345
|
+
if (Query.placement(body, encoded, { operators })._tag !== "Placeable") return contract;
|
|
4272
4346
|
const missing = Query.dependencies(body).fields.filter((field) => !requirement.select.fields.includes(field.key));
|
|
4273
4347
|
if (missing.length === 0) return contract;
|
|
4274
4348
|
return {
|
|
@@ -5029,7 +5103,10 @@ const Remote = {
|
|
|
5029
5103
|
* in one step; the result carries the application-facing operations. The
|
|
5030
5104
|
* descriptor alone is `Remote.define`, the binding alone `Remote.at`.
|
|
5031
5105
|
*/
|
|
5032
|
-
make: (config) => bindDomain(defineRemote(config), config.model, {
|
|
5106
|
+
make: (config) => bindDomain(defineRemote(config), config.model, {
|
|
5107
|
+
verifyPlacement: config.verifyPlacement,
|
|
5108
|
+
operators: config.operators
|
|
5109
|
+
}),
|
|
5033
5110
|
/**
|
|
5034
5111
|
* Declares a Remote domain without binding it: its entities, queries, and
|
|
5035
5112
|
* mutations, plus the submodel. For a domain reused across applications or
|
|
@@ -5391,6 +5468,7 @@ const failedReadWords = (error) => {
|
|
|
5391
5468
|
};
|
|
5392
5469
|
/** The bound domain: the descriptor, the binding, and the operations over them. */
|
|
5393
5470
|
const bindDomain = (definition, store, options = {}) => {
|
|
5471
|
+
const operators = options.operators ?? Operators.kernel;
|
|
5394
5472
|
const bound = bindRemote(definition, store);
|
|
5395
5473
|
const readers = /* @__PURE__ */ new Map();
|
|
5396
5474
|
const keptConnections = /* @__PURE__ */ new Set();
|
|
@@ -5423,7 +5501,7 @@ const bindDomain = (definition, store, options = {}) => {
|
|
|
5423
5501
|
const declared = (descriptor?.Result)?.live;
|
|
5424
5502
|
if (descriptor?.body === void 0) return declared;
|
|
5425
5503
|
const encoded = JSON.parse(identity.slice(identity.indexOf("\0") + 1));
|
|
5426
|
-
return belongsEncoded(storeOf(bound, model), descriptor, encoded, entityKey(event.edge.ref.entity, event.edge.ref.id)) === "no" ? {
|
|
5504
|
+
return belongsEncoded(storeOf(bound, model), descriptor, encoded, entityKey(event.edge.ref.entity, event.edge.ref.id), operators) === "no" ? {
|
|
5427
5505
|
prepend: "ignore",
|
|
5428
5506
|
append: "ignore"
|
|
5429
5507
|
} : declared;
|
|
@@ -5472,7 +5550,7 @@ const bindDomain = (definition, store, options = {}) => {
|
|
|
5472
5550
|
...query.value,
|
|
5473
5551
|
connection: placed,
|
|
5474
5552
|
holds
|
|
5475
|
-
}, store, change), {
|
|
5553
|
+
}, store, change, operators), {
|
|
5476
5554
|
Kept: () => placed,
|
|
5477
5555
|
Placed: ({ connection }) => connection,
|
|
5478
5556
|
Invalidated: () => placed
|
|
@@ -5492,7 +5570,7 @@ const bindDomain = (definition, store, options = {}) => {
|
|
|
5492
5570
|
pages,
|
|
5493
5571
|
layers,
|
|
5494
5572
|
remote.entities
|
|
5495
|
-
], `placed:${identity}`, () => placedHeld(identity, pages, changesOf({ entities:
|
|
5573
|
+
], `placed:${identity}`, () => placedHeld(identity, pages, changesOf({ entities: resolvedPatches(remote.entities, remote.optimistic) }, remote.entities), visibleStoreOf(remote.entities, remote.optimistic)));
|
|
5496
5574
|
};
|
|
5497
5575
|
/**
|
|
5498
5576
|
* The edges a loaded list shows: its pages with pending edits placed, and
|
|
@@ -5535,7 +5613,7 @@ const bindDomain = (definition, store, options = {}) => {
|
|
|
5535
5613
|
for (const edge of inserted.values()) {
|
|
5536
5614
|
if (held.has(edge.key)) continue;
|
|
5537
5615
|
const key = entityKey(edge.ref.entity, edge.ref.id);
|
|
5538
|
-
RowPlace.$match(placeIn(store, placed, body, query.value.encoded, key), {
|
|
5616
|
+
RowPlace.$match(placeIn(store, placed, body, query.value.encoded, key, operators), {
|
|
5539
5617
|
Before: (place) => void (placed = withEdge(placed, edge, place)),
|
|
5540
5618
|
End: (place) => void (placed = withEdge(placed, edge, place)),
|
|
5541
5619
|
Outside: () => void outside.push(edge),
|
|
@@ -5624,7 +5702,7 @@ const bindDomain = (definition, store, options = {}) => {
|
|
|
5624
5702
|
connection: pages,
|
|
5625
5703
|
holds
|
|
5626
5704
|
};
|
|
5627
|
-
const after = Impact.$match(impactOn(held, visible, change), {
|
|
5705
|
+
const after = Impact.$match(impactOn(held, visible, change, operators), {
|
|
5628
5706
|
Kept: () => Option.some(pages),
|
|
5629
5707
|
Placed: ({ connection }) => Option.some(connection),
|
|
5630
5708
|
Invalidated: () => Option.none()
|
|
@@ -5806,7 +5884,7 @@ const bindDomain = (definition, store, options = {}) => {
|
|
|
5806
5884
|
const listed = query.Result.entity;
|
|
5807
5885
|
if (listed === void 0) throw new Error(`Remote: query "${query.name}" is not a connection over an entity, so it has no page to select`);
|
|
5808
5886
|
if (select.entity !== listed) throw new Error(`Remote: the selection is of "${select.entity}", but query "${query.name}" lists "${listed}"`);
|
|
5809
|
-
const { ref, relation, requirement } = placeableReadContract(readContract(query.ref(input), select, window), query.body);
|
|
5887
|
+
const { ref, relation, requirement } = placeableReadContract(readContract(query.ref(input), select, window), query.body, operators);
|
|
5810
5888
|
const relationKey = stableStringify(relation);
|
|
5811
5889
|
const sizeOf = (remote) => windowSize(remote, ref);
|
|
5812
5890
|
const shown = (remote, connection) => cutToWindow(shownEdges(remote, ref.identity, connection), ref.window, sizeOf(remote));
|
|
@@ -6021,7 +6099,10 @@ const bindDomain = (definition, store, options = {}) => {
|
|
|
6021
6099
|
const among = (connection === void 0 ? [] : shownEdges(remote, over.ref.identity, connection)).map((edge) => entityKey(edge.ref.entity, edge.ref.id));
|
|
6022
6100
|
let judged;
|
|
6023
6101
|
try {
|
|
6024
|
-
judged = matching(visible, by, input, {
|
|
6102
|
+
judged = matching(visible, by, input, {
|
|
6103
|
+
among,
|
|
6104
|
+
operators
|
|
6105
|
+
});
|
|
6025
6106
|
} catch (error) {
|
|
6026
6107
|
if (error instanceof QueryEvaluateError) return {
|
|
6027
6108
|
items: [],
|
|
@@ -6071,6 +6152,7 @@ const bindDomain = (definition, store, options = {}) => {
|
|
|
6071
6152
|
const remote = store.get(model);
|
|
6072
6153
|
const requestId = options.requestId ?? `${bound.contract.name}-${remote.mutations.sequence + 1}`;
|
|
6073
6154
|
const tempId = `${requestId}.tmp`;
|
|
6155
|
+
const known = Option.fromUndefinedOr(remote.mutations.operations.get(requestId));
|
|
6074
6156
|
const optimistic = typeof options.optimistic === "function" ? options.optimistic({
|
|
6075
6157
|
requestId,
|
|
6076
6158
|
tempId
|
|
@@ -6087,11 +6169,15 @@ const bindDomain = (definition, store, options = {}) => {
|
|
|
6087
6169
|
command: {
|
|
6088
6170
|
name: `Remote.mutate(${mutation.name})`,
|
|
6089
6171
|
args: { requestId },
|
|
6090
|
-
effect: mutateRemote(mutation, input, requestId,
|
|
6172
|
+
effect: Effect.flatMap(Effect.sync(() => Option.getOrElse(known, mintOperation)), (operation) => mutateRemote(mutation, input, requestId, {
|
|
6173
|
+
keys: options.keys,
|
|
6174
|
+
operation
|
|
6175
|
+
}).pipe(Effect.match({
|
|
6091
6176
|
onFailure: (error) => ({
|
|
6092
6177
|
_tag: "MutationFailed",
|
|
6093
6178
|
requestId,
|
|
6094
|
-
error: remoteError(error)
|
|
6179
|
+
error: remoteError(error),
|
|
6180
|
+
operation
|
|
6095
6181
|
}),
|
|
6096
6182
|
onSuccess: (outcome) => ({
|
|
6097
6183
|
_tag: "MutationSucceeded",
|
|
@@ -6101,7 +6187,7 @@ const bindDomain = (definition, store, options = {}) => {
|
|
|
6101
6187
|
deleted: outcome.deleted,
|
|
6102
6188
|
now: (options.now ?? wallClock)()
|
|
6103
6189
|
})
|
|
6104
|
-
}))
|
|
6190
|
+
})))
|
|
6105
6191
|
}
|
|
6106
6192
|
};
|
|
6107
6193
|
},
|
|
@@ -6253,7 +6339,6 @@ const pickWindow = (window) => ({
|
|
|
6253
6339
|
...window.after === void 0 ? {} : { after: window.after },
|
|
6254
6340
|
...window.before === void 0 ? {} : { before: window.before }
|
|
6255
6341
|
});
|
|
6256
|
-
|
|
6257
|
-
export { emptyOptimistic as $, refParts as $n, QueryResult as $t, writeRead as A, schemaOf as An, RemoteClient as At, plan as B, missingFields as Bn, Live as Bt, remoteMessageCases as C, beginMutation as Cn, hasPrevious as Ct, updateRemote as D, optimisticOfWrite as Dn, segment as Dt, retentionRootsSchema as E, mutationStatus as En, merge as Et, REMOTE_CACHE_VERSION as F, hasField as Fn, remoteError as Ft, emptyLiveState as G, tombstone as Gn, MAX_RELATION_DEPTH as Gt, applyConnectionEvent as H, remove as Hn, LiveEdge as Ht, RemotePersistence as I, isFieldStale as In, coalesceQueries as It, ConnectionChange as J, RELATION_ALIAS as Jn, MutationResult as Jt, liveHasNext as K, writeEntities as Kn, Mutate as Kt, gc as L, isFieldUnavailable as Ln, coalesceReads as Lt, RemoteData as M, emptyStore as Mn, connectionChangeOf as Mt, remoteDataSchema as N, entityKey as Nn, liveEventOf as Nt, windowGrowthKey as O, patchOfWrite as On, terminal as Ot, remoteErrorSchema as P, entry as Pn, mutateRemote as Pt, connectionIdentity as Q, isRefPage as Qn, QueryRequest as Qt, reachable as R, isTombstone as Rn, requirementKey as Rt, refreshedAt as S, Refusal as Sn, hasNext as St, remoteModelSchema as T, failMutation as Tn, items as Tt, applyEntityEvent as U, setStale as Un, LiveRequirement as Ut, windowKey as V, readField as Vn, LiveChange as Vt, classifyLive as W, setUnavailable as Wn, MAX_FIELDS_PER_REQUEST as Wt, addOverlay as X, RelationEntityAnnotation as Xn, PageSize as Xt, addLayer as Y, RelationAnnotation as Yn, NormalizedEntity as Yt, beginOptimistic as Z, aliasedField as Zn, QueryEdge as Zt, isRemoteMessage as _, WireBoundary as _n, relationOf as _t, RemoteJsonAnswer as a, ReadRequest as an, RemoteRequirements as ar, visibleItems as at, overlaysOf as b, stableStringify as bn, edge as bt, emptyRefresh as c, RemoteLiveError as cn, requirementsOf as cr, inspectRemote as ct, initialRemoteModel as d, RemoteQueryError as dn, RowPlace as dt, QueryRpc as en, refsIn as er, pruneOverlays as et, invalidateConnections as f, RemoteReadError as fn, placeIn as ft, isQueryLoading as g, WindowSchema as gn, pageSchema as gt, isLoadingThrough as h, SettledFields as hn, assemble as ht, liveFetch as i, ReadBatchResult as in, RemoteConnections as ir, settleSuccess as it, Denied as j, clearStale as jn, coalescedLayer as jt, withRefreshRequested as k, reconcileMutation as kn, unknown as kt, failureOf as l, RemoteMutationError as ln, belongsEncoded as lt, isLoading as m, RemoteUnauthenticated as mn, Selection as mt, RemoteUnsatisfied as n, Read as nn, relationShape as nr, removeOverlay as nt, RemoteJsonRequest as o, RelationRequest as on, Requirement as or, visibleStore as ot, isFieldFailed as p, RemoteRpc as pn, Entity$1 as pt, liveHasPrevious as q, writeEntity as qn, MutationRequest as qt, httpWithLive as r, ReadBatch as rn, targetsOf as rr, settleFailure as rt, RemotePolicy as s, RemoteForbidden as sn, connectionsOf as sr, inspectEntity as st, Remote as t, REMOTE_PROTOCOL_VERSION as tn, relationAlias as tr, removeLayer as tt, forgetRemote as u, RemoteProtocolError as un, matching as ut, noFailures as v, IDENTITY_SEPARATOR as vn, unavailableOf as vt, remoteMessageSchema as w, emptyMutationState as wn, isGapped as wt, refreshIsInFlight as x, Mutation as xn, emptyConnection as xt, onlyOverlaid as y, Query$1 as yn, cursor as yt, deadlineOf as z, markStale as zn, ConnectionChangeSchema as zt };
|
|
6342
|
+
export { emptyOptimistic as $, RelationEntityAnnotation as $n, PageSize as $t, writeRead as A, optimisticOfWrite as An, unknown as At, plan as B, isFieldUnavailable as Bn, requirementKey as Bt, remoteMessageCases as C, stableStringify as Cn, hasNext as Ct, updateRemote as D, emptyMutationState as Dn, merge as Dt, retentionRootsSchema as E, beginMutation as En, items as Et, REMOTE_CACHE_VERSION as F, emptyStore as Fn, mintOperation as Ft, emptyLiveState as G, remove as Gn, LiveRequirement as Gt, applyConnectionEvent as H, markStale as Hn, Live as Ht, RemotePersistence as I, entityKey as In, mutateRemote as It, ConnectionChange as J, tombstone as Jn, Mutate as Jt, liveHasNext as K, setStale as Kn, MAX_FIELDS_PER_REQUEST as Kt, gc as L, entry as Ln, remoteError as Lt, RemoteData as M, reconcileMutation as Mn, coalescedLayer as Mt, remoteDataSchema as N, schemaOf as Nn, connectionChangeOf as Nt, windowGrowthKey as O, failMutation as On, segment as Ot, remoteErrorSchema as P, clearStale as Pn, liveEventOf as Pt, connectionIdentity as Q, RelationAnnotation as Qn, OperationId as Qt, reachable as R, hasField as Rn, coalesceQueries as Rt, refreshedAt as S, Query$1 as Sn, emptyConnection as St, remoteModelSchema as T, Refusal as Tn, isGapped as Tt, applyEntityEvent as U, missingFields as Un, LiveChange as Ut, windowKey as V, isTombstone as Vn, ConnectionChangeSchema as Vt, classifyLive as W, readField as Wn, LiveEdge as Wt, addOverlay as X, writeEntity as Xn, MutationResult as Xt, addLayer as Y, writeEntities as Yn, MutationRequest as Yt, beginOptimistic as Z, RELATION_ALIAS as Zn, NormalizedEntity as Zt, isRemoteMessage as _, RemoteUnauthenticated as _n, pageSchema as _t, RemoteJsonAnswer as a, Read as an, relationShape as ar, settleSuccess as at, overlaysOf as b, WireBoundary as bn, cursor as bt, emptyRefresh as c, ReadRequest as cn, RemoteRequirements as cr, inspectEntity as ct, initialRemoteModel as d, RemoteLiveError as dn, requirementsOf as dr, matching as dt, QueryEdge as en, aliasedField as er, pruneOverlays as et, invalidateConnections as f, RemoteMutationError as fn, RowPlace as ft, isQueryLoading as g, RemoteRpc as gn, assemble as gt, isLoadingThrough as h, RemoteReadError as hn, Selection as ht, liveFetch as i, REMOTE_PROTOCOL_VERSION as in, relationAlias as ir, settleFailure as it, Denied as j, patchOfWrite as jn, RemoteClient as jt, withRefreshRequested as k, mutationStatus as kn, terminal as kt, failureOf as l, RelationRequest as ln, Requirement as lr, inspectRemote as lt, isLoading as m, RemoteQueryError as mn, Entity$1 as mt, RemoteUnsatisfied as n, QueryResult as nn, refParts as nr, removeOverlay as nt, RemoteJsonRequest as o, ReadBatch as on, targetsOf as or, visibleItems as ot, isFieldFailed as p, RemoteProtocolError as pn, placeIn as pt, liveHasPrevious as q, setUnavailable as qn, MAX_RELATION_DEPTH as qt, httpWithLive as r, QueryRpc as rn, refsIn as rr, resolvedPatches as rt, RemotePolicy as s, ReadBatchResult as sn, RemoteConnections as sr, visibleStore as st, Remote as t, QueryRequest as tn, isRefPage as tr, removeLayer as tt, forgetRemote as u, RemoteForbidden as un, connectionsOf as ur, belongsEncoded as ut, noFailures as v, SettledFields as vn, relationOf as vt, remoteMessageSchema as w, Mutation as wn, hasPrevious as wt, refreshIsInFlight as x, IDENTITY_SEPARATOR as xn, edge as xt, onlyOverlaid as y, WindowSchema as yn, unavailableOf as yt, deadlineOf as z, isFieldStale as zn, coalesceReads as zt };
|
|
6258
6343
|
|
|
6259
|
-
//# sourceMappingURL=src-
|
|
6344
|
+
//# sourceMappingURL=src-BDqn7Ocr.mjs.map
|