foldkit-remote 0.18.0 → 0.19.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 +34 -5
- package/dist/{index-86gaaCWm.d.mts → index-Bx_lCC85.d.mts} +125 -16
- package/dist/index-Bx_lCC85.d.mts.map +1 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +1 -1
- package/dist/port.d.mts +1 -1
- package/dist/port.mjs +1 -1
- package/dist/{src-Cp71fJac.mjs → src-fsYu0YoT.mjs} +118 -20
- package/dist/src-fsYu0YoT.mjs.map +1 -0
- package/package.json +8 -8
- package/dist/index-86gaaCWm.d.mts.map +0 -1
- package/dist/src-Cp71fJac.mjs.map +0 -1
|
@@ -420,6 +420,7 @@ const emptyMutationState = {
|
|
|
420
420
|
applied: /* @__PURE__ */ new Set(),
|
|
421
421
|
failed: /* @__PURE__ */ new Set(),
|
|
422
422
|
errors: /* @__PURE__ */ new Map(),
|
|
423
|
+
operations: /* @__PURE__ */ new Map(),
|
|
423
424
|
sequence: 0
|
|
424
425
|
};
|
|
425
426
|
/**
|
|
@@ -442,17 +443,20 @@ const beginMutation = (state, requestId) => ({
|
|
|
442
443
|
pending: /* @__PURE__ */ new Set([...state.pending, requestId]),
|
|
443
444
|
sequence: state.sequence + 1
|
|
444
445
|
});
|
|
445
|
-
const failMutation = (state, requestId, error) => {
|
|
446
|
+
const failMutation = (state, requestId, error, operation) => {
|
|
446
447
|
const pending = new Set(state.pending);
|
|
447
448
|
pending.delete(requestId);
|
|
448
449
|
const failed = remember(state.failed, requestId);
|
|
449
450
|
const errors = new Map([...state.errors, [requestId, error]]);
|
|
451
|
+
const operations = new Map([...state.operations, [requestId, operation]]);
|
|
450
452
|
for (const id of errors.keys()) if (!failed.has(id)) errors.delete(id);
|
|
453
|
+
for (const id of operations.keys()) if (!failed.has(id)) operations.delete(id);
|
|
451
454
|
return {
|
|
452
455
|
...state,
|
|
453
456
|
pending,
|
|
454
457
|
failed,
|
|
455
|
-
errors
|
|
458
|
+
errors,
|
|
459
|
+
operations
|
|
456
460
|
};
|
|
457
461
|
};
|
|
458
462
|
const mutationStatus = (state, requestId) => {
|
|
@@ -487,6 +491,23 @@ const reconcileMutation = (store, state, requestId, answer) => {
|
|
|
487
491
|
};
|
|
488
492
|
};
|
|
489
493
|
const conflict = Schema.TaggedStruct("Conflict", {});
|
|
494
|
+
const unmet = Schema.TaggedStruct("Unmet", {});
|
|
495
|
+
/**
|
|
496
|
+
* The row the write would leave breaks one of its Entity's invariants: which
|
|
497
|
+
* one, what to tell the author, and the fields to show it beside. None and
|
|
498
|
+
* empty when the invariant reads a member hidden from whoever asked.
|
|
499
|
+
*/
|
|
500
|
+
const invalid = Schema.TaggedStruct("Invalid", {
|
|
501
|
+
rule: Schema.OptionFromNullOr(Schema.String),
|
|
502
|
+
message: Schema.String,
|
|
503
|
+
path: Schema.Array(Schema.String)
|
|
504
|
+
});
|
|
505
|
+
const conflictOrInvalid = Schema.Union([conflict, invalid]);
|
|
506
|
+
const conflictUnmetOrInvalid = Schema.Union([
|
|
507
|
+
conflict,
|
|
508
|
+
unmet,
|
|
509
|
+
invalid
|
|
510
|
+
]);
|
|
490
511
|
const anyField = Schema.TaggedStruct("Field", {
|
|
491
512
|
key: Schema.String,
|
|
492
513
|
reason: Schema.Unknown
|
|
@@ -500,10 +521,24 @@ const Refusal = {
|
|
|
500
521
|
}),
|
|
501
522
|
/** The row moved on since the client read it; the author decides what wins. */
|
|
502
523
|
conflict,
|
|
524
|
+
/** The row does not hold what the write's `when` asks of it, where it would commit. */
|
|
525
|
+
unmet,
|
|
526
|
+
/** The row the write would leave breaks an invariant of its Entity. */
|
|
527
|
+
invalid,
|
|
503
528
|
/** Whether a refusal, of whatever mutation, is a `Refusal.field`. */
|
|
504
529
|
isField: Schema.is(anyField),
|
|
505
530
|
/** Whether a refusal, of whatever mutation, is `Refusal.conflict`. */
|
|
506
|
-
isConflict: Schema.is(conflict)
|
|
531
|
+
isConflict: Schema.is(conflict),
|
|
532
|
+
/** Whether a refusal, of whatever mutation, is `Refusal.unmet`. */
|
|
533
|
+
isUnmet: Schema.is(unmet),
|
|
534
|
+
/** Whether a refusal, of whatever mutation, is `Refusal.invalid`. */
|
|
535
|
+
isInvalid: Schema.is(invalid),
|
|
536
|
+
/**
|
|
537
|
+
* Whether a refusal is `Refusal.invalid` naming this invariant: the one
|
|
538
|
+
* `Entity.invariant` returned, or one `Entity.invariants` reads. An invariant
|
|
539
|
+
* over a member hidden from the asker names none, so it matches nothing.
|
|
540
|
+
*/
|
|
541
|
+
isInvalidBy: (refusal, invariant) => Schema.is(invalid)(refusal) && Option.contains(refusal.rule, invariant.id)
|
|
507
542
|
};
|
|
508
543
|
/** The codec itself, or a `Schema.Struct` over the fields. */
|
|
509
544
|
const schemaOf = (shape) => Schema.isSchema(shape) ? shape : Schema.Struct(shape);
|
|
@@ -525,11 +560,18 @@ const patchOfWrite = (bound) => ({
|
|
|
525
560
|
/**
|
|
526
561
|
* What a declared write shows before the server answers: an update's or an
|
|
527
562
|
* 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
|
|
563
|
+
* 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
564
|
* list it is in; its answer does.
|
|
530
565
|
*/
|
|
531
566
|
const optimisticOfWrite = (write, input, keys) => Match.value(write).pipe(Match.tagsExhaustive({
|
|
532
|
-
Update: () =>
|
|
567
|
+
Update: () => {
|
|
568
|
+
const bound = Write.bind(write, input, keys);
|
|
569
|
+
const patch = patchOfWrite(bound);
|
|
570
|
+
return [Object.keys(bound.increments).length === 0 ? patch : {
|
|
571
|
+
...patch,
|
|
572
|
+
adds: bound.increments
|
|
573
|
+
}];
|
|
574
|
+
},
|
|
533
575
|
Insert: () => {
|
|
534
576
|
const patch = patchOfWrite(Write.bind(write, input));
|
|
535
577
|
return [{
|
|
@@ -553,13 +595,14 @@ const Mutation = {
|
|
|
553
595
|
* A mutation that is a declared `Write` (`update`, `insert` or `delete`): its
|
|
554
596
|
* input is the write's, and it answers nothing but the row it changed, as a
|
|
555
597
|
* 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
|
|
598
|
+
* `expect`s a revision the row has moved past, for a write with a `when` as
|
|
599
|
+
* unmet, and as invalid when the row it would leave breaks an invariant.
|
|
557
600
|
*/
|
|
558
601
|
write: (name, write) => ({
|
|
559
602
|
name,
|
|
560
603
|
Input: write.input.schema,
|
|
561
604
|
Output: Schema.Struct({}),
|
|
562
|
-
Refusal:
|
|
605
|
+
Refusal: Option.isSome(write.when) ? conflictUnmetOrInvalid : conflictOrInvalid,
|
|
563
606
|
write
|
|
564
607
|
}),
|
|
565
608
|
make: (name, config) => ({
|
|
@@ -822,9 +865,21 @@ const ReadBatchResult = Schema.Struct({
|
|
|
822
865
|
entities: Schema.Array(NormalizedEntity),
|
|
823
866
|
settled: Schema.Array(SettledFields)
|
|
824
867
|
});
|
|
868
|
+
/**
|
|
869
|
+
* One operation's identity across every client: minted where it is first sent
|
|
870
|
+
* and sent again with each retry of it. Bounded, since a server keys a record
|
|
871
|
+
* of the write by it.
|
|
872
|
+
*/
|
|
873
|
+
const OperationId = Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(128), Schema.isPattern(/^[A-Za-z0-9:._-]+$/));
|
|
825
874
|
const MutationRequest = Schema.Struct({
|
|
826
|
-
/**
|
|
875
|
+
/**
|
|
876
|
+
* This client's id for the request, the same on each retry of it. It is
|
|
877
|
+
* unique within one client's Model only (a counter there), so it names a
|
|
878
|
+
* request to that client; `operation` names it to a server.
|
|
879
|
+
*/
|
|
827
880
|
requestId: Schema.String,
|
|
881
|
+
/** The operation's identity across clients: what a server keys a record of the write by. */
|
|
882
|
+
operation: Schema.optional(OperationId),
|
|
828
883
|
mutation: Schema.String,
|
|
829
884
|
input: Schema.Unknown,
|
|
830
885
|
/** For a mutation that is a `Write`: the input keys to write, when not all of them. */
|
|
@@ -1198,18 +1253,28 @@ const coalescedLayer = (layer, options = {}) => Layer.effect(RemoteClient, Effec
|
|
|
1198
1253
|
};
|
|
1199
1254
|
})).pipe(Layer.provide(layer));
|
|
1200
1255
|
/**
|
|
1256
|
+
* A new operation's identity: random, so unique across clients and reloads.
|
|
1257
|
+
* `randomUUID` exists only in a secure context, so a page served over plain
|
|
1258
|
+
* HTTP draws 128 random bits from `getRandomValues`, which exists in all.
|
|
1259
|
+
*/
|
|
1260
|
+
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("");
|
|
1261
|
+
/**
|
|
1201
1262
|
* `Remote.mutate` as a standalone effect, so `Remote.mutateInto` can reuse it
|
|
1202
1263
|
* without the object literal referencing itself.
|
|
1203
1264
|
*/
|
|
1204
|
-
const mutateRemote = Effect.fn("Remote.mutate")(function* (mutation, input, requestId,
|
|
1265
|
+
const mutateRemote = Effect.fn("Remote.mutate")(function* (mutation, input, requestId, options = {}) {
|
|
1266
|
+
const { keys } = options;
|
|
1267
|
+
const operation = options.operation ?? (yield* Effect.sync(mintOperation));
|
|
1205
1268
|
yield* Effect.annotateCurrentSpan({
|
|
1206
1269
|
mutation: mutation.name,
|
|
1207
|
-
requestId
|
|
1270
|
+
requestId,
|
|
1271
|
+
operation
|
|
1208
1272
|
});
|
|
1209
1273
|
const client = yield* RemoteClient;
|
|
1210
1274
|
const encoded = yield* Schema.encodeUnknownEffect(mutation.Input)(input).pipe(Effect.catchTag("SchemaError", (error) => Effect.fail(new RemoteMutationError({ message: error.message }))));
|
|
1211
1275
|
const result = yield* client.mutate({
|
|
1212
1276
|
requestId,
|
|
1277
|
+
operation,
|
|
1213
1278
|
mutation: mutation.name,
|
|
1214
1279
|
input: encoded,
|
|
1215
1280
|
...keys === void 0 ? {} : { keys }
|
|
@@ -2137,7 +2202,7 @@ const removeOverlay = (optimistic, id) => {
|
|
|
2137
2202
|
* entity only a request holds (a temporary id) is not the server's to age, and
|
|
2138
2203
|
* is dated `Infinity`, so freshness never plans or times it.
|
|
2139
2204
|
*/
|
|
2140
|
-
const visibleStore = (base, optimistic) => writeEntities(base, optimistic
|
|
2205
|
+
const visibleStore = (base, optimistic) => writeEntities(base, resolvedPatches(base, optimistic).map((patch) => {
|
|
2141
2206
|
const key = entityKey(patch.entity, patch.id);
|
|
2142
2207
|
const under = base[key];
|
|
2143
2208
|
return {
|
|
@@ -2145,7 +2210,34 @@ const visibleStore = (base, optimistic) => writeEntities(base, optimistic.layers
|
|
|
2145
2210
|
values: patch.values,
|
|
2146
2211
|
updatedAt: under === void 0 || under.tombstone ? Infinity : under.updatedAt
|
|
2147
2212
|
};
|
|
2148
|
-
}))
|
|
2213
|
+
}));
|
|
2214
|
+
/**
|
|
2215
|
+
* Every pending patch in order, each amount added to the value under it: the
|
|
2216
|
+
* store's, or an earlier layer's. A prediction, so only over a number the
|
|
2217
|
+
* client holds: a field it has not read, or holds as anything else, keeps
|
|
2218
|
+
* what it shows until the server answers with the sum.
|
|
2219
|
+
*/
|
|
2220
|
+
const resolvedPatches = (base, optimistic) => {
|
|
2221
|
+
const shown = /* @__PURE__ */ new Map();
|
|
2222
|
+
return optimistic.layers.flatMap((layer) => layer.patches.map(({ adds, ...patch }) => {
|
|
2223
|
+
const key = entityKey(patch.entity, patch.id);
|
|
2224
|
+
const earlier = shown.get(key) ?? {};
|
|
2225
|
+
const values = { ...patch.values };
|
|
2226
|
+
const under = base[key];
|
|
2227
|
+
const valueUnder = (field) => Object.hasOwn(earlier, field) ? Option.some(earlier[field]) : Option.fromUndefinedOr(under?.values[field]);
|
|
2228
|
+
for (const [field, amount] of Object.entries(adds ?? {})) Option.map(Option.filter(valueUnder(field), Predicate.isNumber), (current) => {
|
|
2229
|
+
values[field] = current + amount;
|
|
2230
|
+
});
|
|
2231
|
+
shown.set(key, {
|
|
2232
|
+
...earlier,
|
|
2233
|
+
...values
|
|
2234
|
+
});
|
|
2235
|
+
return {
|
|
2236
|
+
...patch,
|
|
2237
|
+
values
|
|
2238
|
+
};
|
|
2239
|
+
}));
|
|
2240
|
+
};
|
|
2149
2241
|
/**
|
|
2150
2242
|
* A fresh page is newer than any settled overlay it covers: an edge the page
|
|
2151
2243
|
* carries no longer needs an insert overlay, and a `remove` overlay hiding it
|
|
@@ -2991,7 +3083,8 @@ const remoteMessageCases = {
|
|
|
2991
3083
|
},
|
|
2992
3084
|
MutationFailed: {
|
|
2993
3085
|
requestId: Schema.String,
|
|
2994
|
-
error: remoteErrorSchema
|
|
3086
|
+
error: remoteErrorSchema,
|
|
3087
|
+
operation: Schema.String
|
|
2995
3088
|
},
|
|
2996
3089
|
OverlayShown: {
|
|
2997
3090
|
id: Schema.String,
|
|
@@ -3432,7 +3525,7 @@ const reduceRemote = (model, message) => {
|
|
|
3432
3525
|
}
|
|
3433
3526
|
case "MutationFailed": return {
|
|
3434
3527
|
...model,
|
|
3435
|
-
mutations: failMutation(model.mutations, message.requestId, message.error),
|
|
3528
|
+
mutations: failMutation(model.mutations, message.requestId, message.error, message.operation),
|
|
3436
3529
|
optimistic: settleFailure(model.optimistic, message.requestId)
|
|
3437
3530
|
};
|
|
3438
3531
|
case "LiveReceived": {
|
|
@@ -5492,7 +5585,7 @@ const bindDomain = (definition, store, options = {}) => {
|
|
|
5492
5585
|
pages,
|
|
5493
5586
|
layers,
|
|
5494
5587
|
remote.entities
|
|
5495
|
-
], `placed:${identity}`, () => placedHeld(identity, pages, changesOf({ entities:
|
|
5588
|
+
], `placed:${identity}`, () => placedHeld(identity, pages, changesOf({ entities: resolvedPatches(remote.entities, remote.optimistic) }, remote.entities), visibleStoreOf(remote.entities, remote.optimistic)));
|
|
5496
5589
|
};
|
|
5497
5590
|
/**
|
|
5498
5591
|
* The edges a loaded list shows: its pages with pending edits placed, and
|
|
@@ -6071,6 +6164,7 @@ const bindDomain = (definition, store, options = {}) => {
|
|
|
6071
6164
|
const remote = store.get(model);
|
|
6072
6165
|
const requestId = options.requestId ?? `${bound.contract.name}-${remote.mutations.sequence + 1}`;
|
|
6073
6166
|
const tempId = `${requestId}.tmp`;
|
|
6167
|
+
const known = Option.fromUndefinedOr(remote.mutations.operations.get(requestId));
|
|
6074
6168
|
const optimistic = typeof options.optimistic === "function" ? options.optimistic({
|
|
6075
6169
|
requestId,
|
|
6076
6170
|
tempId
|
|
@@ -6087,11 +6181,15 @@ const bindDomain = (definition, store, options = {}) => {
|
|
|
6087
6181
|
command: {
|
|
6088
6182
|
name: `Remote.mutate(${mutation.name})`,
|
|
6089
6183
|
args: { requestId },
|
|
6090
|
-
effect: mutateRemote(mutation, input, requestId,
|
|
6184
|
+
effect: Effect.flatMap(Effect.sync(() => Option.getOrElse(known, mintOperation)), (operation) => mutateRemote(mutation, input, requestId, {
|
|
6185
|
+
keys: options.keys,
|
|
6186
|
+
operation
|
|
6187
|
+
}).pipe(Effect.match({
|
|
6091
6188
|
onFailure: (error) => ({
|
|
6092
6189
|
_tag: "MutationFailed",
|
|
6093
6190
|
requestId,
|
|
6094
|
-
error: remoteError(error)
|
|
6191
|
+
error: remoteError(error),
|
|
6192
|
+
operation
|
|
6095
6193
|
}),
|
|
6096
6194
|
onSuccess: (outcome) => ({
|
|
6097
6195
|
_tag: "MutationSucceeded",
|
|
@@ -6101,7 +6199,7 @@ const bindDomain = (definition, store, options = {}) => {
|
|
|
6101
6199
|
deleted: outcome.deleted,
|
|
6102
6200
|
now: (options.now ?? wallClock)()
|
|
6103
6201
|
})
|
|
6104
|
-
}))
|
|
6202
|
+
})))
|
|
6105
6203
|
}
|
|
6106
6204
|
};
|
|
6107
6205
|
},
|
|
@@ -6254,6 +6352,6 @@ const pickWindow = (window) => ({
|
|
|
6254
6352
|
...window.before === void 0 ? {} : { before: window.before }
|
|
6255
6353
|
});
|
|
6256
6354
|
//#endregion
|
|
6257
|
-
export { emptyOptimistic as $,
|
|
6355
|
+
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
6356
|
|
|
6259
|
-
//# sourceMappingURL=src-
|
|
6357
|
+
//# sourceMappingURL=src-fsYu0YoT.mjs.map
|