foldkit-remote 0.16.0 → 0.18.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.
@@ -740,6 +740,16 @@ var RemoteForbidden = class extends Schema.TaggedError()("Forbidden", {
740
740
  message: Schema.String,
741
741
  reason: Schema.optional(Schema.String)
742
742
  }) {};
743
+ /**
744
+ * The server could not tell who is asking: a credential was sent and no
745
+ * longer holds, or none was sent where one is needed. Asking again as the
746
+ * same caller is refused again; signing in again is the remedy. `reason` is
747
+ * words for the person who asked; without it the refusal says nothing of why.
748
+ */
749
+ var RemoteUnauthenticated = class extends Schema.TaggedError()("Unauthenticated", {
750
+ message: Schema.String,
751
+ reason: Schema.optional(Schema.String)
752
+ }) {};
743
753
  var RemoteLiveError = class extends Schema.TaggedError()("RemoteLiveError", { message: Schema.String }) {};
744
754
  /** The peer speaks another protocol version; nothing was read. */
745
755
  var RemoteProtocolError = class extends Schema.TaggedError()("RemoteProtocolError", {
@@ -894,17 +904,29 @@ const LiveChange = Schema.Union([
894
904
  const Read = Rpc.make("FoldkitRemoteRead", {
895
905
  payload: ReadBatch,
896
906
  success: ReadBatchResult,
897
- error: Schema.Union([RemoteReadError, RemoteProtocolError])
907
+ error: Schema.Union([
908
+ RemoteReadError,
909
+ RemoteProtocolError,
910
+ RemoteUnauthenticated
911
+ ])
898
912
  });
899
913
  const Mutate = Rpc.make("FoldkitRemoteMutate", {
900
914
  payload: MutationRequest,
901
915
  success: MutationResult,
902
- error: Schema.Union([RemoteMutationError, RemoteForbidden])
916
+ error: Schema.Union([
917
+ RemoteMutationError,
918
+ RemoteForbidden,
919
+ RemoteUnauthenticated
920
+ ])
903
921
  });
904
922
  const Live = Rpc.make("FoldkitRemoteLive", {
905
923
  payload: LiveRequirement,
906
924
  success: LiveChange,
907
- error: Schema.Union([RemoteLiveError, RemoteProtocolError]),
925
+ error: Schema.Union([
926
+ RemoteLiveError,
927
+ RemoteProtocolError,
928
+ RemoteUnauthenticated
929
+ ]),
908
930
  stream: true
909
931
  });
910
932
  var RemoteQueryError = class extends Schema.TaggedError()("RemoteQueryError", { message: Schema.String }) {};
@@ -947,7 +969,7 @@ const QueryResult = Schema.Struct({
947
969
  const QueryRpc = Rpc.make("FoldkitRemoteQuery", {
948
970
  payload: QueryRequest,
949
971
  success: QueryResult,
950
- error: RemoteQueryError
972
+ error: Schema.Union([RemoteQueryError, RemoteUnauthenticated])
951
973
  });
952
974
  const RemoteRpc = RpcGroup.make(Read, Mutate, QueryRpc, Live);
953
975
  //#endregion
@@ -2686,6 +2708,23 @@ const RemotePersistence = {
2686
2708
  //#endregion
2687
2709
  //#region src/remoteData.ts
2688
2710
  /** `RemoteData`: the state of a value the store may not hold yet. */
2711
+ /**
2712
+ * The server would not answer who is asking: their session ended or was
2713
+ * never there (`Unauthenticated`, a 401), or they may not (`Forbidden`, a
2714
+ * 403). `reason` is the server's sentence, when it gave one. Not a mutation's
2715
+ * declared `refusal`, which the server answered and `Data.refusal` reads.
2716
+ */
2717
+ const Denied = defineTaggedUnion({
2718
+ Unauthenticated: { reason: Schema.Option(Schema.String) },
2719
+ Forbidden: { reason: Schema.Option(Schema.String) }
2720
+ });
2721
+ /** The tags `denied` reads: a `RemoteError`'s `_tag` is open, for any failure a transport reports. */
2722
+ const denials = /* @__PURE__ */ new Set(["Unauthenticated", "Forbidden"]);
2723
+ const isDenial = (tag) => denials.has(tag);
2724
+ const deny = {
2725
+ Unauthenticated: Denied.Unauthenticated,
2726
+ Forbidden: Denied.Forbidden
2727
+ };
2689
2728
  const fresh = { _tag: "Fresh" };
2690
2729
  const refreshing = { _tag: "Refreshing" };
2691
2730
  const remoteErrorSchema = Schema.Struct({
@@ -2786,6 +2825,19 @@ const RemoteData = {
2786
2825
  });
2787
2826
  }
2788
2827
  },
2828
+ /**
2829
+ * The denial a failure was, typed, or none for any other failure (a
2830
+ * network error, a protocol mismatch): what to read before telling a reader
2831
+ * to sign in again.
2832
+ *
2833
+ * ```ts
2834
+ * Option.match(RemoteData.denied(error), {
2835
+ * onNone: () => 'Could not load',
2836
+ * onSome: Denied.match({ Unauthenticated: () => 'Sign in again', Forbidden: () => 'Not yours' }),
2837
+ * })
2838
+ * ```
2839
+ */
2840
+ denied: (error) => isDenial(error._tag) ? Option.some(deny[error._tag]({ reason: Option.fromUndefinedOr(error.reason) })) : Option.none(),
2789
2841
  map: (data, f) => {
2790
2842
  switch (data._tag) {
2791
2843
  case "Ready": return {
@@ -3965,7 +4017,9 @@ const RemoteJsonAnswer = Schema.Union([Schema.Struct({ result: Schema.Unknown })
3965
4017
  error: Schema.String,
3966
4018
  refusal: Schema.optional(Schema.Unknown),
3967
4019
  /** Who is asking was refused, with the application's reason when it gave one. */
3968
- forbidden: Schema.optional(Schema.Struct({ reason: Schema.optional(Schema.String) }))
4020
+ forbidden: Schema.optional(Schema.Struct({ reason: Schema.optional(Schema.String) })),
4021
+ /** Who is asking could not be told, with the reason when one was given; a 401. */
4022
+ unauthenticated: Schema.optional(Schema.Struct({ reason: Schema.optional(Schema.String) }))
3969
4023
  })]);
3970
4024
  const decodeAnswer = Schema.decodeUnknownEffect(RemoteJsonAnswer);
3971
4025
  const call = (send, operation, payload, fail) => Effect.tryPromise({
@@ -3975,26 +4029,57 @@ const call = (send, operation, payload, fail) => Effect.tryPromise({
3975
4029
  })),
3976
4030
  catch: (error) => fail({ error: error instanceof Error ? error.message : String(error) })
3977
4031
  }).pipe(Effect.flatMap((answer) => decodeAnswer(answer).pipe(Effect.mapError(() => fail({ error: "The server answered with something that is not an answer" })))), Effect.flatMap((answer) => "error" in answer ? Effect.fail(fail(answer)) : Effect.succeed(answer.result)));
4032
+ /**
4033
+ * An error answer as its call's failure: a refusal of who is asking, of
4034
+ * every call kind alike, or the call's own error.
4035
+ */
4036
+ const failure = (own) => (answer) => answer.unauthenticated === void 0 ? own(answer) : new RemoteUnauthenticated({
4037
+ message: answer.error,
4038
+ ...answer.unauthenticated.reason === void 0 ? {} : { reason: answer.unauthenticated.reason }
4039
+ });
3978
4040
  /** Remote's client over `send`, for `Remote.clientLayer`. */
3979
4041
  const json = (send) => ({
3980
- FoldkitRemoteRead: (payload) => call(send, "read", payload, ({ error }) => new RemoteReadError({ message: error })),
3981
- FoldkitRemoteQuery: (payload) => call(send, "query", payload, ({ error }) => new RemoteQueryError({ message: error })),
3982
- FoldkitRemoteMutate: (payload) => call(send, "mutate", payload, ({ error, refusal, forbidden }) => forbidden === void 0 ? new RemoteMutationError({
4042
+ FoldkitRemoteRead: (payload) => call(send, "read", payload, failure(({ error }) => new RemoteReadError({ message: error }))),
4043
+ FoldkitRemoteQuery: (payload) => call(send, "query", payload, failure(({ error }) => new RemoteQueryError({ message: error }))),
4044
+ FoldkitRemoteMutate: (payload) => call(send, "mutate", payload, failure(({ error, refusal, forbidden }) => forbidden === void 0 ? new RemoteMutationError({
3983
4045
  message: error,
3984
4046
  refusal
3985
4047
  }) : new RemoteForbidden({
3986
4048
  message: error,
3987
4049
  ...forbidden.reason === void 0 ? {} : { reason: forbidden.reason }
3988
- })),
4050
+ }))),
3989
4051
  FoldkitRemoteLive: () => Stream.empty
3990
4052
  });
3991
4053
  /** A body that did not parse, told apart from every value one can parse to, `null` included. */
3992
4054
  const notJson = Symbol("not JSON");
4055
+ const Reason = Schema.Struct({ reason: Schema.optional(Schema.String) });
4056
+ /** A refusal in Remote's own words, by the key it is given under. */
4057
+ const ownRefusal = {
4058
+ unauthenticated: Schema.decodeUnknownOption(Schema.Struct({
4059
+ error: Schema.String,
4060
+ unauthenticated: Reason
4061
+ })),
4062
+ forbidden: Schema.decodeUnknownOption(Schema.Struct({
4063
+ error: Schema.String,
4064
+ forbidden: Reason
4065
+ }))
4066
+ };
4067
+ /**
4068
+ * A 401's or 403's answer, whatever its body: Remote's own refusal as it is,
4069
+ * and anything else (`{ error }` from middleware, HTML, a shape of its own)
4070
+ * as the status alone, so it never reaches a decoder that would call it a
4071
+ * broken answer.
4072
+ */
4073
+ const refusalOf = (body, kind, status) => Option.getOrElse(ownRefusal[kind](body), () => ({
4074
+ error: status,
4075
+ [kind]: {}
4076
+ }));
3993
4077
  /**
3994
4078
  * Remote's client over HTTP: each call a `POST` of the request to `url`, the
3995
- * answer its JSON body whatever its status. A 403 is answered as forbidden
3996
- * unless its body says so itself, as the server's own does: one from
3997
- * middleware in front of Remote (a refused session) may say only `{ error }`.
4079
+ * answer its JSON body whatever its status. A 401 is answered as
4080
+ * unauthenticated, for every call kind, and a 403 as forbidden, whatever the
4081
+ * body says: the server's own says why, and middleware in front of Remote
4082
+ * may say anything.
3998
4083
  * `headers` is read per request, so a token that changes reaches the next
3999
4084
  * call. `fetch` defaults to the global one; pass it to route calls through
4000
4085
  * another implementation in tests.
@@ -4010,11 +4095,8 @@ const http = (url, options = {}) => json(async (request) => {
4010
4095
  });
4011
4096
  const status = `${response.status} ${response.statusText}`;
4012
4097
  const body = await response.json().catch(() => notJson);
4013
- const saysForbidden = typeof body === "object" && body !== null && Object.hasOwn(body, "forbidden");
4014
- if (response.status === 403 && !saysForbidden) return {
4015
- error: status,
4016
- forbidden: {}
4017
- };
4098
+ if (response.status === 401) return refusalOf(body, "unauthenticated", status);
4099
+ if (response.status === 403) return refusalOf(body, "forbidden", status);
4018
4100
  if (body === notJson) throw new Error(status);
4019
4101
  return body;
4020
4102
  });
@@ -4079,13 +4161,21 @@ async function* changes(body) {
4079
4161
  if (frame.data.length === 0) continue;
4080
4162
  const text = frame.data.join("\n");
4081
4163
  if (frame.event === "error") {
4082
- const message = JSON.parse(text).message;
4083
- throw new RemoteLiveError({ message: typeof message === "string" ? message : text });
4164
+ const said = JSON.parse(text);
4165
+ const message = Predicate.isObject(said) && typeof said.message === "string" ? said.message : text;
4166
+ if (Predicate.isObject(said) && Predicate.isObject(said.unauthenticated)) {
4167
+ const reason = said.unauthenticated.reason;
4168
+ throw new RemoteUnauthenticated({
4169
+ message,
4170
+ ...typeof reason === "string" ? { reason } : {}
4171
+ });
4172
+ }
4173
+ throw new RemoteLiveError({ message });
4084
4174
  }
4085
4175
  yield decodeChange(JSON.parse(text));
4086
4176
  }
4087
4177
  }
4088
- const liveError = (error) => error instanceof RemoteLiveError ? error : new RemoteLiveError({ message: error instanceof Error ? error.message : String(error) });
4178
+ const liveError = (error) => error instanceof RemoteLiveError || error instanceof RemoteUnauthenticated ? error : new RemoteLiveError({ message: error instanceof Error ? error.message : String(error) });
4089
4179
  /** Remote's live stream over fetch: `serveFetch`'s live open as a `Stream`. */
4090
4180
  const liveFetch = (url, options = {}) => (payload) => Stream.unwrap(Effect.gen(function* () {
4091
4181
  const via = options.fetch ?? fetch;
@@ -4110,7 +4200,15 @@ const liveFetch = (url, options = {}) => (payload) => Stream.unwrap(Effect.gen(f
4110
4200
  try: () => response.json().catch(() => null),
4111
4201
  catch: liveError
4112
4202
  });
4113
- return yield* new RemoteLiveError({ message: Predicate.isObject(answered) && "error" in answered ? String(answered.error) : `HTTP ${response.status} ${response.statusText}` });
4203
+ const message = Predicate.isObject(answered) && "error" in answered ? String(answered.error) : `HTTP ${response.status} ${response.statusText}`;
4204
+ if (response.status === 401) {
4205
+ const said = Predicate.isObject(answered) && Predicate.isObject(answered.unauthenticated) ? answered.unauthenticated.reason : void 0;
4206
+ return yield* new RemoteUnauthenticated({
4207
+ message,
4208
+ ...typeof said === "string" ? { reason: said } : {}
4209
+ });
4210
+ }
4211
+ return yield* new RemoteLiveError({ message });
4114
4212
  }
4115
4213
  if (response.body === null) return yield* new RemoteLiveError({ message: "The server answered live with no body" });
4116
4214
  return Stream.fromAsyncIterable(changes(response.body), liveError);
@@ -6112,7 +6210,7 @@ const bindDomain = (definition, store, options = {}) => {
6112
6210
  },
6113
6211
  wiring: (active, options) => ({
6114
6212
  key: `remote:${bound.contract.name}`,
6115
- handles: Object.keys(remoteMessageCases),
6213
+ handles: Record.keys(remoteMessageCases),
6116
6214
  route: (model, message) => isRemoteMessage(message) ? Option.some({ model: reduce(model, message) }) : Option.none(),
6117
6215
  subscriptions: brandEntries(domain.subscriptions(active, options)),
6118
6216
  contract: bound.contract
@@ -6156,6 +6254,6 @@ const pickWindow = (window) => ({
6156
6254
  ...window.before === void 0 ? {} : { before: window.before }
6157
6255
  });
6158
6256
  //#endregion
6159
- export { pruneOverlays as $, relationAlias as $n, QueryRpc as $t, writeRead as A, emptyStore as An, coalescedLayer as At, windowKey as B, remove as Bn, LiveChange as Bt, remoteMessageCases as C, failMutation as Cn, isGapped as Ct, updateRemote as D, reconcileMutation as Dn, terminal as Dt, retentionRootsSchema as E, patchOfWrite as En, segment as Et, RemotePersistence as F, isFieldUnavailable as Fn, coalesceQueries as Ft, liveHasNext as G, writeEntity as Gn, Mutate as Gt, applyEntityEvent as H, setUnavailable as Hn, LiveRequirement as Ht, gc as I, isTombstone as In, coalesceReads as It, addLayer as J, RelationEntityAnnotation as Jn, NormalizedEntity as Jt, liveHasPrevious as K, RELATION_ALIAS as Kn, MutationRequest as Kt, reachable as L, markStale as Ln, requirementKey as Lt, remoteDataSchema as M, entry as Mn, liveEventOf as Mt, remoteErrorSchema as N, hasField as Nn, mutateRemote as Nt, windowGrowthKey as O, schemaOf as On, unknown as Ot, REMOTE_CACHE_VERSION as P, isFieldStale as Pn, remoteError as Pt, emptyOptimistic as Q, refsIn as Qn, QueryResult as Qt, deadlineOf as R, missingFields as Rn, ConnectionChangeSchema as Rt, refreshedAt as S, emptyMutationState as Sn, hasPrevious as St, remoteModelSchema as T, optimisticOfWrite as Tn, merge as Tt, classifyLive as U, tombstone as Un, MAX_FIELDS_PER_REQUEST as Ut, applyConnectionEvent as V, setStale as Vn, LiveEdge as Vt, emptyLiveState as W, writeEntities as Wn, MAX_RELATION_DEPTH as Wt, beginOptimistic as X, isRefPage as Xn, QueryEdge as Xt, addOverlay as Y, aliasedField as Yn, PageSize as Yt, connectionIdentity as Z, refParts as Zn, QueryRequest as Zt, isRemoteMessage as _, Query$1 as _n, unavailableOf as _t, RemoteJsonAnswer as a, RelationRequest as an, connectionsOf as ar, visibleStore as at, overlaysOf as b, Refusal as bn, emptyConnection as bt, emptyRefresh as c, RemoteMutationError as cn, belongsEncoded as ct, initialRemoteModel as d, RemoteReadError as dn, placeIn as dt, REMOTE_PROTOCOL_VERSION as en, relationShape as er, removeLayer as et, invalidateConnections as f, RemoteRpc as fn, Entity$1 as ft, isQueryLoading as g, IDENTITY_SEPARATOR as gn, relationOf as gt, isLoadingThrough as h, WireBoundary as hn, pageSchema as ht, liveFetch as i, ReadRequest as in, Requirement as ir, visibleItems as it, RemoteData as j, entityKey as jn, connectionChangeOf as jt, withRefreshRequested as k, clearStale as kn, RemoteClient as kt, failureOf as l, RemoteProtocolError as ln, matching as lt, isLoading as m, WindowSchema as mn, assemble as mt, RemoteUnsatisfied as n, ReadBatch as nn, RemoteConnections as nr, settleFailure as nt, RemoteJsonRequest as o, RemoteForbidden as on, requirementsOf as or, inspectEntity as ot, isFieldFailed as p, SettledFields as pn, Selection as pt, ConnectionChange as q, RelationAnnotation as qn, MutationResult as qt, httpWithLive as r, ReadBatchResult as rn, RemoteRequirements as rr, settleSuccess as rt, RemotePolicy as s, RemoteLiveError as sn, inspectRemote as st, Remote as t, Read as tn, targetsOf as tr, removeOverlay as tt, forgetRemote as u, RemoteQueryError as un, RowPlace as ut, noFailures as v, stableStringify as vn, cursor as vt, remoteMessageSchema as w, mutationStatus as wn, items as wt, refreshIsInFlight as x, beginMutation as xn, hasNext as xt, onlyOverlaid as y, Mutation as yn, edge as yt, plan as z, readField as zn, Live as zt };
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 };
6160
6258
 
6161
- //# sourceMappingURL=src-oVKXbiTu.mjs.map
6259
+ //# sourceMappingURL=src-Cp71fJac.mjs.map