reactor-effect-client 0.2.0 → 0.3.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 +66 -6
- package/dist/FetchHttp.js +1 -1
- package/dist/FetchHttp.js.map +1 -1
- package/dist/PeerFactory.d.ts +13 -2
- package/dist/PeerFactory.d.ts.map +1 -1
- package/dist/PeerFactory.js.map +1 -1
- package/dist/PeerTypes.d.ts +2 -2
- package/dist/PeerTypes.d.ts.map +1 -1
- package/dist/Sequence.d.ts +18 -14
- package/dist/Sequence.d.ts.map +1 -1
- package/dist/Sequence.js +117 -122
- package/dist/Sequence.js.map +1 -1
- package/dist/SessionTypes.d.ts +172 -15
- package/dist/SessionTypes.d.ts.map +1 -1
- package/dist/SessionTypes.js +18 -1
- package/dist/SessionTypes.js.map +1 -1
- package/dist/bytes.js +3 -3
- package/dist/bytes.js.map +1 -1
- package/dist/contract.d.ts +120 -29
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +105 -73
- package/dist/contract.js.map +1 -1
- package/dist/coordinator/_internal/client.d.ts +111 -17
- package/dist/coordinator/_internal/client.d.ts.map +1 -1
- package/dist/coordinator/_internal/client.js +187 -74
- package/dist/coordinator/_internal/client.js.map +1 -1
- package/dist/coordinator/_internal/recording.d.ts +33 -0
- package/dist/coordinator/_internal/recording.d.ts.map +1 -0
- package/dist/coordinator/_internal/recording.js +123 -0
- package/dist/coordinator/_internal/recording.js.map +1 -0
- package/dist/coordinator/_internal/response.d.ts.map +1 -1
- package/dist/coordinator/_internal/response.js +5 -7
- package/dist/coordinator/_internal/response.js.map +1 -1
- package/dist/coordinator/_internal/schemas.d.ts +40 -6
- package/dist/coordinator/_internal/schemas.d.ts.map +1 -1
- package/dist/coordinator/_internal/schemas.js +94 -64
- package/dist/coordinator/_internal/schemas.js.map +1 -1
- package/dist/coordinator/index.d.ts +14 -2
- package/dist/coordinator/index.d.ts.map +1 -1
- package/dist/coordinator/index.js +14 -18
- package/dist/coordinator/index.js.map +1 -1
- package/dist/correlation.d.ts +15 -0
- package/dist/correlation.d.ts.map +1 -1
- package/dist/correlation.js +26 -8
- package/dist/correlation.js.map +1 -1
- package/dist/duration.d.ts +25 -0
- package/dist/duration.d.ts.map +1 -0
- package/dist/duration.js +47 -0
- package/dist/duration.js.map +1 -0
- package/dist/errors.d.ts +1264 -49
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +698 -46
- package/dist/errors.js.map +1 -1
- package/dist/h3/_internal/client.d.ts +2 -2
- package/dist/h3/_internal/client.d.ts.map +1 -1
- package/dist/h3/_internal/client.js +280 -146
- package/dist/h3/_internal/client.js.map +1 -1
- package/dist/h3/_internal/contracts.d.ts.map +1 -1
- package/dist/h3/_internal/contracts.js +2 -2
- package/dist/h3/_internal/contracts.js.map +1 -1
- package/dist/h3/_internal/deployment.d.ts.map +1 -1
- package/dist/h3/_internal/deployment.js +5 -4
- package/dist/h3/_internal/deployment.js.map +1 -1
- package/dist/h3/_internal/evidence.js +1 -1
- package/dist/h3/_internal/evidence.js.map +1 -1
- package/dist/h3/_internal/operations.d.ts +58 -0
- package/dist/h3/_internal/operations.d.ts.map +1 -0
- package/dist/h3/_internal/operations.js +268 -0
- package/dist/h3/_internal/operations.js.map +1 -0
- package/dist/h3/_internal/references.d.ts.map +1 -1
- package/dist/h3/_internal/references.js +3 -8
- package/dist/h3/_internal/references.js.map +1 -1
- package/dist/h3/_internal/request.js +1 -1
- package/dist/h3/_internal/request.js.map +1 -1
- package/dist/h3/_internal/retained.d.ts +22 -0
- package/dist/h3/_internal/retained.d.ts.map +1 -0
- package/dist/h3/_internal/retained.js +89 -0
- package/dist/h3/_internal/retained.js.map +1 -0
- package/dist/h3/_internal/state.d.ts +15 -1
- package/dist/h3/_internal/state.d.ts.map +1 -1
- package/dist/h3/_internal/state.js +69 -29
- package/dist/h3/_internal/state.js.map +1 -1
- package/dist/h3/index.d.ts +1 -1
- package/dist/h3/index.d.ts.map +1 -1
- package/dist/h3/messages.d.ts +118 -109
- package/dist/h3/messages.d.ts.map +1 -1
- package/dist/h3/messages.js +73 -51
- package/dist/h3/messages.js.map +1 -1
- package/dist/h3/types.d.ts +109 -13
- package/dist/h3/types.d.ts.map +1 -1
- package/dist/host.d.ts +6 -6
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +4 -5
- package/dist/host.js.map +1 -1
- package/dist/index.d.ts +11 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/json.d.ts +13 -6
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +34 -14
- package/dist/json.js.map +1 -1
- package/dist/media-stream.d.ts.map +1 -1
- package/dist/media-stream.js +9 -11
- package/dist/media-stream.js.map +1 -1
- package/dist/observation.d.ts +9 -0
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +20 -18
- package/dist/observation.js.map +1 -1
- package/dist/orchestration/h3-source.d.ts +32 -4
- package/dist/orchestration/h3-source.d.ts.map +1 -1
- package/dist/orchestration/h3-source.js +47 -14
- package/dist/orchestration/h3-source.js.map +1 -1
- package/dist/orchestration/index.d.ts +9 -6
- package/dist/orchestration/index.d.ts.map +1 -1
- package/dist/orchestration/index.js +5 -3
- package/dist/orchestration/index.js.map +1 -1
- package/dist/orchestration/media-buffer.d.ts +12 -4
- package/dist/orchestration/media-buffer.d.ts.map +1 -1
- package/dist/orchestration/media-buffer.js +31 -11
- package/dist/orchestration/media-buffer.js.map +1 -1
- package/dist/orchestration/open-h3.d.ts +65 -0
- package/dist/orchestration/open-h3.d.ts.map +1 -0
- package/dist/orchestration/open-h3.js +65 -0
- package/dist/orchestration/open-h3.js.map +1 -0
- package/dist/orchestration/references.d.ts +5 -1
- package/dist/orchestration/references.d.ts.map +1 -1
- package/dist/orchestration/references.js +37 -34
- package/dist/orchestration/references.js.map +1 -1
- package/dist/orchestration/renewal-state.js +1 -1
- package/dist/orchestration/renewal-state.js.map +1 -1
- package/dist/orchestration/renewal.d.ts +64 -64
- package/dist/orchestration/renewal.d.ts.map +1 -1
- package/dist/orchestration/renewal.js +149 -88
- package/dist/orchestration/renewal.js.map +1 -1
- package/dist/orchestration/request.d.ts +11 -12
- package/dist/orchestration/request.d.ts.map +1 -1
- package/dist/orchestration/request.js +63 -66
- package/dist/orchestration/request.js.map +1 -1
- package/dist/orchestration/routing.d.ts.map +1 -1
- package/dist/orchestration/routing.js +17 -14
- package/dist/orchestration/routing.js.map +1 -1
- package/dist/orchestration/source-slot.d.ts +15 -5
- package/dist/orchestration/source-slot.d.ts.map +1 -1
- package/dist/orchestration/source-slot.js +40 -21
- package/dist/orchestration/source-slot.js.map +1 -1
- package/dist/orchestration/types.d.ts +594 -39
- package/dist/orchestration/types.d.ts.map +1 -1
- package/dist/orchestration/types.js +20 -0
- package/dist/orchestration/types.js.map +1 -1
- package/dist/protobuf.js +2 -2
- package/dist/protobuf.js.map +1 -1
- package/dist/session/_internal/acquire.d.ts +4 -6
- package/dist/session/_internal/acquire.d.ts.map +1 -1
- package/dist/session/_internal/acquire.js +40 -50
- package/dist/session/_internal/acquire.js.map +1 -1
- package/dist/session/_internal/cleanup.d.ts +2 -1
- package/dist/session/_internal/cleanup.d.ts.map +1 -1
- package/dist/session/_internal/cleanup.js +23 -15
- package/dist/session/_internal/cleanup.js.map +1 -1
- package/dist/session/_internal/lifecycle.d.ts +5 -0
- package/dist/session/_internal/lifecycle.d.ts.map +1 -1
- package/dist/session/_internal/lifecycle.js +16 -6
- package/dist/session/_internal/lifecycle.js.map +1 -1
- package/dist/session/_internal/remote.d.ts.map +1 -1
- package/dist/session/_internal/remote.js +16 -7
- package/dist/session/_internal/remote.js.map +1 -1
- package/dist/session/_internal/uploads.d.ts +4 -1
- package/dist/session/_internal/uploads.d.ts.map +1 -1
- package/dist/session/_internal/uploads.js +25 -27
- package/dist/session/_internal/uploads.js.map +1 -1
- package/dist/session/commands.d.ts +1 -21
- package/dist/session/commands.d.ts.map +1 -1
- package/dist/session/commands.js +1 -12
- package/dist/session/commands.js.map +1 -1
- package/dist/session/files.d.ts +10 -2
- package/dist/session/files.d.ts.map +1 -1
- package/dist/session/files.js +11 -16
- package/dist/session/files.js.map +1 -1
- package/dist/session/index.d.ts +6 -6
- package/dist/session/index.d.ts.map +1 -1
- package/dist/session/index.js.map +1 -1
- package/dist/session/media.d.ts +53 -6
- package/dist/session/media.d.ts.map +1 -1
- package/dist/session/recorder.d.ts +24 -0
- package/dist/session/recorder.d.ts.map +1 -0
- package/dist/session/recorder.js +19 -0
- package/dist/session/recorder.js.map +1 -0
- package/dist/session.d.ts +5 -4
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +213 -110
- package/dist/session.js.map +1 -1
- package/dist/simulation/_internal/source.d.ts.map +1 -1
- package/dist/simulation/_internal/source.js +57 -48
- package/dist/simulation/_internal/source.js.map +1 -1
- package/dist/simulation/index.d.ts +4 -5
- package/dist/simulation/index.d.ts.map +1 -1
- package/dist/simulation/index.js +3 -4
- package/dist/simulation/index.js.map +1 -1
- package/dist/simulation/types.d.ts +16 -2
- package/dist/simulation/types.d.ts.map +1 -1
- package/dist/stats.d.ts +19 -0
- package/dist/stats.d.ts.map +1 -1
- package/dist/stats.js +79 -8
- package/dist/stats.js.map +1 -1
- package/package.json +5 -4
- package/notices/owned-webrtc/Effect-MIT.txt +0 -21
- package/notices/owned-webrtc/IETF.txt +0 -43
- package/notices/owned-webrtc/LICENSE +0 -21
- package/notices/owned-webrtc/UPSTREAM.md +0 -20
- package/notices/owned-webrtc/VP8-ADAPTATION.md +0 -32
- package/notices/owned-webrtc/VP8-AUTHORS.txt +0 -42
- package/notices/owned-webrtc/VP8-LICENSE.txt +0 -25
- package/notices/owned-webrtc/VP8-PATENTS.txt +0 -21
- package/notices/owned-webrtc/Werift-MIT.txt +0 -21
- package/notices/owned-webrtc/libvpx-v1.12.0-LICENSE.txt +0 -28
- package/notices/owned-webrtc/libvpx-v1.12.0-PATENTS.txt +0 -22
- package/notices/owned-webrtc/rfc3711.txt +0 -3139
- package/notices/owned-webrtc/str0m-MIT.txt +0 -7
package/dist/errors.js
CHANGED
|
@@ -1,8 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The client's error algebra. Four wrapper classes, each with its own `_tag`,
|
|
3
|
+
* carry a tagged `reason` (handled with `Effect.catchReason`, `catchReasons`
|
|
4
|
+
* and `unwrapReason`) and a `context` of cross-cutting evidence: the operation
|
|
5
|
+
* and its dispatch outcome.
|
|
6
|
+
*
|
|
7
|
+
* Privacy: `message` is written by the library and never contains provider or
|
|
8
|
+
* payload text, so spans and logs that record it stay payload-free. Provider
|
|
9
|
+
* text lives only in fields for explicit inspection: `body` on `Http` and
|
|
10
|
+
* `Remote`, the Redacted `backendMessage` on `Native`, and `context.detail`.
|
|
11
|
+
* Diagnostic JSON (`toJSON`) leaves all three out. Only a cause the library
|
|
12
|
+
* authored becomes the native `cause`: each wrapper hoists its reason, and a
|
|
13
|
+
* reason never holds a raw cause, because exporters such as OtlpTracer render
|
|
14
|
+
* the whole cause chain. A response body, a wire message, native backend text
|
|
15
|
+
* or a `JSON.parse` SyntaxError (whose message quotes its input) stays in
|
|
16
|
+
* `detail` or a typed field.
|
|
17
|
+
*/
|
|
18
|
+
import * as Duration from "effect/Duration";
|
|
19
|
+
import * as Effect from "effect/Effect";
|
|
20
|
+
import * as Predicate from "effect/Predicate";
|
|
21
|
+
import * as Result from "effect/Result";
|
|
1
22
|
import * as Schema from "effect/Schema";
|
|
2
|
-
|
|
23
|
+
import * as SchemaIssue from "effect/SchemaIssue";
|
|
24
|
+
import * as SchemaTransformation from "effect/SchemaTransformation";
|
|
25
|
+
import { SequenceCode } from "./Sequence.js";
|
|
26
|
+
/**
|
|
27
|
+
* The client's four failure classes share this marker so one guard recognizes
|
|
28
|
+
* each of them, although each has its own `_tag` (the ClusterError precedent).
|
|
29
|
+
*/
|
|
30
|
+
const TypeId = "~reactor-effect-client/ReactorFailure";
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// ReactorError reasons
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
/** The codes whose reason carries nothing beyond a library-written message. */
|
|
35
|
+
export const FailureCode = Schema.Literals([
|
|
3
36
|
"InvalidInput",
|
|
4
37
|
"Protocol",
|
|
5
|
-
"Http",
|
|
6
38
|
"VersionMismatch",
|
|
7
39
|
"UnsupportedHost",
|
|
8
40
|
"UnsupportedCapability",
|
|
@@ -12,79 +44,699 @@ export const ErrorCode = Schema.Literals([
|
|
|
12
44
|
"Disconnected",
|
|
13
45
|
"Aborted",
|
|
14
46
|
"Overflow",
|
|
15
|
-
"Remote",
|
|
16
47
|
"UnexpectedReply",
|
|
17
48
|
"Upload",
|
|
18
|
-
"RecorderDisabled",
|
|
19
|
-
"Native",
|
|
20
49
|
"Closed",
|
|
21
50
|
"AlreadyReading",
|
|
22
51
|
"Shutdown",
|
|
52
|
+
"SdpRejected",
|
|
53
|
+
"ChannelClosed",
|
|
54
|
+
"Indeterminate",
|
|
23
55
|
]);
|
|
24
|
-
|
|
25
|
-
|
|
56
|
+
/** One reason class whose `_tag` is the code (the PlatformError.SystemError precedent). */
|
|
57
|
+
export class Failure extends Schema.Error("reactor-effect-client/ReactorError/Failure")({
|
|
58
|
+
_tag: FailureCode,
|
|
26
59
|
message: Schema.String,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
60
|
+
}) {
|
|
61
|
+
/** Local backpressure (`Overflow`) and a lost connection may succeed on a later attempt. */
|
|
62
|
+
get isRetryable() {
|
|
63
|
+
return (this._tag === "Overflow" || this._tag === "Disconnected" || this._tag === "ChannelClosed");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/** A coordinator HTTP failure; `status` is absent when no response arrived. */
|
|
67
|
+
export class Http extends Schema.Error("reactor-effect-client/ReactorError/Http")({
|
|
68
|
+
_tag: Schema.tag("Http"),
|
|
69
|
+
message: Schema.String,
|
|
70
|
+
status: Schema.optionalKey(Schema.Int),
|
|
71
|
+
/** The response's `Retry-After`, when it named a delay. */
|
|
72
|
+
retryAfter: Schema.optionalKey(Schema.Duration),
|
|
73
|
+
/** The response body, for explicit inspection only. */
|
|
74
|
+
body: Schema.optionalKey(Schema.String),
|
|
75
|
+
}) {
|
|
76
|
+
/**
|
|
77
|
+
* No response arrived, or the coordinator refused for now: 408, 429, 503, or
|
|
78
|
+
* any status that named a Retry-After delay.
|
|
79
|
+
*/
|
|
80
|
+
get isRetryable() {
|
|
81
|
+
return (this.status === undefined ||
|
|
82
|
+
this.status === 408 ||
|
|
83
|
+
this.status === 429 ||
|
|
84
|
+
this.status === 503 ||
|
|
85
|
+
this.retryAfter !== undefined);
|
|
86
|
+
}
|
|
87
|
+
/** Diagnostic JSON: the body stays out. */
|
|
88
|
+
toJSON() {
|
|
89
|
+
return reasonJson(this);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* The provider replied with an error or a refusal. `RecorderDisabled` is a
|
|
94
|
+
* clip failure recognized from its reason, the one documented classification
|
|
95
|
+
* of provider text.
|
|
96
|
+
*/
|
|
97
|
+
export class Remote extends Schema.Error("reactor-effect-client/ReactorError/Remote")({
|
|
98
|
+
_tag: Schema.Literals(["Remote", "RecorderDisabled"]),
|
|
99
|
+
message: Schema.String,
|
|
100
|
+
/** The provider's own error code, verbatim. */
|
|
101
|
+
remoteCode: Schema.optionalKey(Schema.String),
|
|
102
|
+
/** The provider's error text, for explicit inspection only. */
|
|
103
|
+
body: Schema.optionalKey(Schema.String),
|
|
104
|
+
}) {
|
|
105
|
+
/** The provider refused; the same request would be refused again. */
|
|
106
|
+
get isRetryable() {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
/** Diagnostic JSON: the body stays out. */
|
|
110
|
+
toJSON() {
|
|
111
|
+
return reasonJson(this);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/** A native bridge failure; `status` is the ABI's failure class when one was returned. */
|
|
115
|
+
export class Native extends Schema.Error("reactor-effect-client/ReactorError/Native")({
|
|
116
|
+
_tag: Schema.tag("Native"),
|
|
117
|
+
message: Schema.String,
|
|
118
|
+
status: Schema.optionalKey(Schema.Int),
|
|
119
|
+
/**
|
|
120
|
+
* libwebrtc's own text. It can contain peer SDP or caller-supplied signaling
|
|
121
|
+
* material, so it is Redacted and refuses JSON encoding.
|
|
122
|
+
*/
|
|
123
|
+
backendMessage: Schema.optionalKey(Schema.Redacted(Schema.String, { disallowJsonEncode: true })),
|
|
124
|
+
}) {
|
|
125
|
+
get isRetryable() {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
/** Diagnostic JSON: the backend text stays out. */
|
|
129
|
+
toJSON() {
|
|
130
|
+
return reasonJson(this);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/** No ICE candidate pair worked. */
|
|
134
|
+
export class IceFailed extends Schema.Error("reactor-effect-client/ReactorError/IceFailed")({
|
|
135
|
+
_tag: Schema.tag("IceFailed"),
|
|
136
|
+
message: Schema.String,
|
|
137
|
+
/** Candidate pairs the connection's statistics listed. */
|
|
138
|
+
pairs: Schema.Int,
|
|
139
|
+
/** The local candidate types gathered, such as `host` or `relay`. */
|
|
140
|
+
candidateTypes: Schema.Array(Schema.String),
|
|
141
|
+
}) {
|
|
142
|
+
get isRetryable() {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/** ICE connectivity succeeded and the DTLS or SCTP transport above it failed. */
|
|
147
|
+
export class TransportFailed extends Schema.Error("reactor-effect-client/ReactorError/TransportFailed")({
|
|
148
|
+
_tag: Schema.tag("TransportFailed"),
|
|
149
|
+
message: Schema.String,
|
|
150
|
+
/** Candidate pairs the connection's statistics listed. */
|
|
151
|
+
pairs: Schema.Int,
|
|
152
|
+
}) {
|
|
153
|
+
get isRetryable() {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* A clip ended before it reached the phase awaited: it failed, or it was
|
|
159
|
+
* popped from the queue, first.
|
|
160
|
+
*/
|
|
161
|
+
export class ClipEnded extends Schema.Error("reactor-effect-client/ReactorError/ClipEnded")({
|
|
162
|
+
_tag: Schema.tag("ClipEnded"),
|
|
163
|
+
message: Schema.String,
|
|
164
|
+
clipId: Schema.String,
|
|
165
|
+
/** The lifecycle message that ended the clip. */
|
|
166
|
+
lifecycle: Schema.Literals(["clip_failed", "clip_popped"]),
|
|
167
|
+
/** The transport generation whose evidence ended it. */
|
|
168
|
+
transportGeneration: Schema.BigInt,
|
|
169
|
+
}) {
|
|
170
|
+
get isRetryable() {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
export const ReactorErrorReason = Schema.Union([
|
|
175
|
+
Failure,
|
|
176
|
+
Http,
|
|
177
|
+
Remote,
|
|
178
|
+
Native,
|
|
179
|
+
IceFailed,
|
|
180
|
+
TransportFailed,
|
|
181
|
+
ClipEnded,
|
|
182
|
+
]);
|
|
183
|
+
/** Every reason tag a ReactorError, CommandFailure or AcquisitionFailure can carry. */
|
|
184
|
+
export const ErrorCode = Schema.Literals([
|
|
185
|
+
...FailureCode.literals,
|
|
186
|
+
"Http",
|
|
187
|
+
"Remote",
|
|
188
|
+
"RecorderDisabled",
|
|
189
|
+
"Native",
|
|
190
|
+
"IceFailed",
|
|
191
|
+
"TransportFailed",
|
|
192
|
+
"ClipEnded",
|
|
193
|
+
]);
|
|
194
|
+
const reasonFor = (code, message) => {
|
|
195
|
+
switch (code) {
|
|
196
|
+
case "Http":
|
|
197
|
+
return new Http({ message });
|
|
198
|
+
case "Remote":
|
|
199
|
+
case "RecorderDisabled":
|
|
200
|
+
return new Remote({ _tag: code, message });
|
|
201
|
+
case "Native":
|
|
202
|
+
return new Native({ message });
|
|
203
|
+
default:
|
|
204
|
+
return new Failure({ _tag: code, message });
|
|
205
|
+
}
|
|
206
|
+
};
|
|
32
207
|
export const ErrorContext = Schema.Struct({
|
|
33
208
|
operation: Schema.optionalKey(Schema.String),
|
|
34
209
|
requestId: Schema.optionalKey(Schema.String),
|
|
35
210
|
sessionId: Schema.optionalKey(Schema.String),
|
|
36
211
|
generation: Schema.optionalKey(Schema.BigInt),
|
|
37
212
|
outcome: Schema.optionalKey(Schema.Literals(["not-submitted", "unknown", "replied"])),
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
213
|
+
/**
|
|
214
|
+
* The underlying cause or diagnostic record, for explicit inspection only. It
|
|
215
|
+
* may hold provider or payload text, so it is never the native `cause` and
|
|
216
|
+
* diagnostic JSON leaves it out.
|
|
217
|
+
*/
|
|
43
218
|
detail: Schema.optionalKey(Schema.Unknown),
|
|
44
219
|
});
|
|
45
|
-
/**
|
|
46
|
-
export
|
|
47
|
-
|
|
220
|
+
/** Dispatch evidence proving that a request was never handed to the remote. */
|
|
221
|
+
export const NotSubmitted = Schema.Struct({
|
|
222
|
+
...ErrorContext.fields,
|
|
223
|
+
outcome: Schema.Literal("not-submitted"),
|
|
224
|
+
operation: Schema.String,
|
|
225
|
+
});
|
|
226
|
+
/** Dispatch evidence is independent of the failure's transport category. */
|
|
227
|
+
export const CommandContext = Schema.Union([
|
|
228
|
+
NotSubmitted,
|
|
229
|
+
Schema.Struct({
|
|
230
|
+
...ErrorContext.fields,
|
|
231
|
+
outcome: Schema.Literals(["unknown", "replied"]),
|
|
232
|
+
operation: Schema.String,
|
|
233
|
+
requestId: Schema.String,
|
|
234
|
+
generation: Schema.BigInt,
|
|
235
|
+
}),
|
|
236
|
+
]);
|
|
237
|
+
// ---------------------------------------------------------------------------
|
|
238
|
+
// PolicyFailure reasons
|
|
239
|
+
// ---------------------------------------------------------------------------
|
|
240
|
+
/** Local admission refusals that carry only a message. */
|
|
241
|
+
export const RefusalCode = Schema.Literals([
|
|
242
|
+
"QueueFull",
|
|
243
|
+
"SessionRecovering",
|
|
244
|
+
"SessionClosed",
|
|
245
|
+
"SessionRetired",
|
|
246
|
+
"NotFound",
|
|
247
|
+
"OwnerConflict",
|
|
248
|
+
"Busy",
|
|
249
|
+
"InvalidRequest",
|
|
250
|
+
"PositionConflict",
|
|
251
|
+
"QueueChanged",
|
|
252
|
+
"RouteChanged",
|
|
253
|
+
"AnchorUnavailable",
|
|
254
|
+
"ContinuationUnavailable",
|
|
255
|
+
"AnnotationCapacity",
|
|
256
|
+
"SubmissionCapacity",
|
|
257
|
+
"IdentityExhausted",
|
|
258
|
+
]);
|
|
259
|
+
/** A local refusal whose `_tag` is its code. */
|
|
260
|
+
export class Refusal extends Schema.Error("reactor-effect-client/PolicyFailure/Refusal")({
|
|
261
|
+
_tag: RefusalCode,
|
|
48
262
|
message: Schema.String,
|
|
49
|
-
context: ErrorContext,
|
|
50
|
-
nativeError: Schema.optionalKey(ProviderFailure),
|
|
51
263
|
}) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
264
|
+
/** A full queue and a recovering session are backpressure: the request can wait. */
|
|
265
|
+
get isRetryable() {
|
|
266
|
+
return this._tag === "QueueFull" || this._tag === "SessionRecovering";
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
/** A request field names a clip that no session is known to own. */
|
|
270
|
+
export class Missing extends Schema.Error("reactor-effect-client/PolicyFailure/Missing")({
|
|
271
|
+
_tag: Schema.tag("Missing"),
|
|
272
|
+
/** The field: `before` (anchor), `sameSessionAs` (session_anchor) or `continueFrom`. */
|
|
273
|
+
purpose: Schema.Literals(["anchor", "session_anchor", "continuation"]),
|
|
274
|
+
}) {
|
|
275
|
+
get message() {
|
|
276
|
+
return `The ${this.purpose} clip has no known owning session`;
|
|
277
|
+
}
|
|
278
|
+
get isRetryable() {
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
/** Sequence affinity refused the request; `code` is the SequenceError code. */
|
|
283
|
+
export class SequenceRefusal extends Schema.Error("reactor-effect-client/PolicyFailure/Sequence")({
|
|
284
|
+
_tag: Schema.tag("Sequence"),
|
|
285
|
+
sequenceId: Schema.String,
|
|
286
|
+
code: SequenceCode,
|
|
287
|
+
}) {
|
|
288
|
+
get message() {
|
|
289
|
+
return `Sequence ${this.sequenceId}: ${this.code}`;
|
|
290
|
+
}
|
|
291
|
+
get isRetryable() {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
export const PolicyReason = Schema.Union([Refusal, Missing, SequenceRefusal]);
|
|
296
|
+
// ---------------------------------------------------------------------------
|
|
297
|
+
// Diagnostic JSON
|
|
298
|
+
// ---------------------------------------------------------------------------
|
|
299
|
+
/** A reason's routing fields, without provider text. */
|
|
300
|
+
const reasonJson = (reason) => {
|
|
301
|
+
switch (reason._tag) {
|
|
302
|
+
case "Http":
|
|
303
|
+
return {
|
|
304
|
+
_tag: reason._tag,
|
|
305
|
+
message: reason.message,
|
|
306
|
+
...(reason.status === undefined ? {} : { status: reason.status }),
|
|
307
|
+
...(reason.retryAfter === undefined
|
|
308
|
+
? {}
|
|
309
|
+
: { retryAfterMillis: Duration.toMillis(reason.retryAfter) }),
|
|
310
|
+
};
|
|
311
|
+
case "Remote":
|
|
312
|
+
case "RecorderDisabled":
|
|
313
|
+
return {
|
|
314
|
+
_tag: reason._tag,
|
|
315
|
+
message: reason.message,
|
|
316
|
+
...(reason.remoteCode === undefined ? {} : { remoteCode: reason.remoteCode }),
|
|
317
|
+
};
|
|
318
|
+
case "Native":
|
|
319
|
+
return {
|
|
320
|
+
_tag: reason._tag,
|
|
321
|
+
message: reason.message,
|
|
322
|
+
...(reason.status === undefined ? {} : { status: reason.status }),
|
|
323
|
+
};
|
|
324
|
+
case "IceFailed":
|
|
325
|
+
return {
|
|
326
|
+
_tag: reason._tag,
|
|
327
|
+
message: reason.message,
|
|
328
|
+
pairs: reason.pairs,
|
|
329
|
+
candidateTypes: reason.candidateTypes,
|
|
330
|
+
};
|
|
331
|
+
case "TransportFailed":
|
|
332
|
+
return { _tag: reason._tag, message: reason.message, pairs: reason.pairs };
|
|
333
|
+
case "ClipEnded":
|
|
334
|
+
return {
|
|
335
|
+
_tag: reason._tag,
|
|
336
|
+
message: reason.message,
|
|
337
|
+
clipId: reason.clipId,
|
|
338
|
+
lifecycle: reason.lifecycle,
|
|
339
|
+
transportGeneration: String(reason.transportGeneration),
|
|
340
|
+
};
|
|
341
|
+
case "Missing":
|
|
342
|
+
return { _tag: reason._tag, message: reason.message, purpose: reason.purpose };
|
|
343
|
+
case "Sequence":
|
|
344
|
+
return {
|
|
345
|
+
_tag: reason._tag,
|
|
346
|
+
message: reason.message,
|
|
347
|
+
sequenceId: reason.sequenceId,
|
|
348
|
+
code: reason.code,
|
|
349
|
+
};
|
|
350
|
+
default:
|
|
351
|
+
return { _tag: reason._tag, message: reason.message };
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
const contextJson = ({ detail: _detail, generation, ...context }) => ({
|
|
355
|
+
...context,
|
|
356
|
+
...(generation === undefined ? {} : { generation: String(generation) }),
|
|
357
|
+
});
|
|
358
|
+
const diagnostic = (error) => ({
|
|
359
|
+
_tag: error._tag,
|
|
360
|
+
message: error.message,
|
|
361
|
+
reason: reasonJson(error.reason),
|
|
362
|
+
context: contextJson(error.context),
|
|
363
|
+
});
|
|
364
|
+
// ---------------------------------------------------------------------------
|
|
365
|
+
// Wrappers
|
|
366
|
+
// ---------------------------------------------------------------------------
|
|
367
|
+
/**
|
|
368
|
+
* A failure category never implies a mutation was rolled back. Inspect
|
|
369
|
+
* `context.outcome`, and route on `reason._tag`.
|
|
370
|
+
*
|
|
371
|
+
* Construct it from its fields, as with any Schema class (`context` defaults
|
|
372
|
+
* to empty), or with `ReactorError.fromCode`.
|
|
373
|
+
*/
|
|
374
|
+
export class ReactorError extends Schema.TaggedError("reactor-effect-client/ReactorError")("ReactorError", {
|
|
375
|
+
reason: ReactorErrorReason,
|
|
376
|
+
context: ErrorContext.pipe(Schema.withConstructorDefault(Effect.succeed({}))),
|
|
377
|
+
}) {
|
|
378
|
+
[TypeId] = TypeId;
|
|
379
|
+
/** The reason is library-authored, so it is the native cause (the AiError precedent). */
|
|
380
|
+
cause = this.reason;
|
|
381
|
+
get message() {
|
|
382
|
+
return this.reason.message;
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Whether a later attempt may succeed. Never true when `context.outcome` is
|
|
386
|
+
* "unknown": the remote may already have applied the first attempt.
|
|
387
|
+
*/
|
|
388
|
+
get isRetryable() {
|
|
389
|
+
return this.context.outcome !== "unknown" && this.reason.isRetryable;
|
|
390
|
+
}
|
|
391
|
+
/** The delay the remote asked for, when it named one (the AiError precedent). */
|
|
392
|
+
get retryAfter() {
|
|
393
|
+
return this.reason._tag === "Http" ? this.reason.retryAfter : undefined;
|
|
394
|
+
}
|
|
395
|
+
/** Whether `u` is a `ReactorError`, and not one of the other client failures. */
|
|
396
|
+
static is(u) {
|
|
397
|
+
return Predicate.hasProperty(u, TypeId) && Predicate.isTagged(u, "ReactorError");
|
|
398
|
+
}
|
|
399
|
+
/** A failure whose reason is `code` with a library-written `message`. */
|
|
400
|
+
static fromCode(code, message, context = {}) {
|
|
401
|
+
return new ReactorError({ reason: reasonFor(code, message), context });
|
|
402
|
+
}
|
|
403
|
+
toJSON() {
|
|
404
|
+
return diagnostic(this);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
/** Every failed command carries the dispatch evidence established by its owner. */
|
|
408
|
+
export class CommandFailure extends Schema.TaggedError("reactor-effect-client/CommandFailure")("CommandFailure", {
|
|
409
|
+
reason: ReactorErrorReason,
|
|
410
|
+
context: CommandContext,
|
|
411
|
+
}) {
|
|
412
|
+
[TypeId] = TypeId;
|
|
413
|
+
cause = this.reason;
|
|
414
|
+
get message() {
|
|
415
|
+
return this.reason.message;
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Whether a later attempt may succeed. Never true when `context.outcome` is
|
|
419
|
+
* "unknown": the remote may already have applied the first attempt.
|
|
420
|
+
*/
|
|
421
|
+
get isRetryable() {
|
|
422
|
+
return this.context.outcome !== "unknown" && this.reason.isRetryable;
|
|
423
|
+
}
|
|
424
|
+
/** The delay the remote asked for, when it named one (the AiError precedent). */
|
|
425
|
+
get retryAfter() {
|
|
426
|
+
return this.reason._tag === "Http" ? this.reason.retryAfter : undefined;
|
|
427
|
+
}
|
|
428
|
+
/** Whether `u` is a `CommandFailure`. */
|
|
429
|
+
static is(u) {
|
|
430
|
+
return Predicate.hasProperty(u, TypeId) && Predicate.isTagged(u, "CommandFailure");
|
|
431
|
+
}
|
|
432
|
+
/** `error`'s reason, with the dispatch evidence its command established. */
|
|
433
|
+
static from(error, context) {
|
|
434
|
+
return new CommandFailure({ reason: wrapperReason(error), context });
|
|
435
|
+
}
|
|
436
|
+
toJSON() {
|
|
437
|
+
return diagnostic(this);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
/** The report a lease produced, kept by reference rather than copied. */
|
|
441
|
+
const Cleanup = Schema.declare((input) => Predicate.isObject(input) && typeof input.localClosed === "boolean", { expected: "CloseReport" });
|
|
442
|
+
/** A failed acquisition still returns the lifetime evidence of its partial lease. */
|
|
443
|
+
export class AcquisitionFailure extends Schema.TaggedError("reactor-effect-client/AcquisitionFailure")("AcquisitionFailure", {
|
|
444
|
+
reason: ReactorErrorReason,
|
|
445
|
+
context: ErrorContext.pipe(Schema.withConstructorDefault(Effect.succeed({}))),
|
|
446
|
+
cleanup: Cleanup,
|
|
447
|
+
}) {
|
|
448
|
+
[TypeId] = TypeId;
|
|
449
|
+
cause = this.reason;
|
|
450
|
+
get message() {
|
|
451
|
+
return this.reason.message;
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Whether a later attempt may succeed. Never true when `context.outcome` is
|
|
455
|
+
* "unknown": the remote may already have applied the first attempt.
|
|
456
|
+
*/
|
|
457
|
+
get isRetryable() {
|
|
458
|
+
return this.context.outcome !== "unknown" && this.reason.isRetryable;
|
|
459
|
+
}
|
|
460
|
+
/** The delay the remote asked for, when it named one (the AiError precedent). */
|
|
461
|
+
get retryAfter() {
|
|
462
|
+
return this.reason._tag === "Http" ? this.reason.retryAfter : undefined;
|
|
463
|
+
}
|
|
464
|
+
/** Whether `u` is an `AcquisitionFailure`. */
|
|
465
|
+
static is(u) {
|
|
466
|
+
return Predicate.hasProperty(u, TypeId) && Predicate.isTagged(u, "AcquisitionFailure");
|
|
467
|
+
}
|
|
468
|
+
/** `error`'s reason and context, with the cleanup its partial lease reported. */
|
|
469
|
+
static from(error, cleanup) {
|
|
470
|
+
return new AcquisitionFailure({
|
|
471
|
+
reason: wrapperReason(error),
|
|
472
|
+
context: error.context,
|
|
473
|
+
cleanup,
|
|
474
|
+
});
|
|
55
475
|
}
|
|
56
476
|
toJSON() {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
477
|
+
return diagnostic(this);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
/** Local admission is a policy decision, with explicit proof of no dispatch. */
|
|
481
|
+
export class PolicyFailure extends Schema.TaggedError("reactor-effect-client/PolicyFailure")("PolicyFailure", {
|
|
482
|
+
reason: PolicyReason,
|
|
483
|
+
context: NotSubmitted,
|
|
484
|
+
}) {
|
|
485
|
+
[TypeId] = TypeId;
|
|
486
|
+
cause = this.reason;
|
|
487
|
+
get message() {
|
|
488
|
+
return this.reason.message;
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Whether a later attempt may succeed: a full queue or a recovering session.
|
|
492
|
+
* Nothing was dispatched, so the outcome never stands in the way.
|
|
493
|
+
*/
|
|
494
|
+
get isRetryable() {
|
|
495
|
+
return this.reason.isRetryable;
|
|
496
|
+
}
|
|
497
|
+
/** A local refusal names no delay. */
|
|
498
|
+
get retryAfter() {
|
|
499
|
+
return undefined;
|
|
500
|
+
}
|
|
501
|
+
/** Whether `u` is a `PolicyFailure`. */
|
|
502
|
+
static is(u) {
|
|
503
|
+
return Predicate.hasProperty(u, TypeId) && Predicate.isTagged(u, "PolicyFailure");
|
|
504
|
+
}
|
|
505
|
+
/** A local refusal of `operation`, which was therefore never dispatched. */
|
|
506
|
+
static refuse(code, message, operation = "enqueue", cause) {
|
|
507
|
+
return new PolicyFailure({
|
|
508
|
+
reason: new Refusal({ _tag: code, message }),
|
|
62
509
|
context: {
|
|
63
|
-
|
|
64
|
-
|
|
510
|
+
operation,
|
|
511
|
+
outcome: "not-submitted",
|
|
512
|
+
...(cause === undefined ? {} : { detail: cause }),
|
|
65
513
|
},
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
/** A refusal because the clip a request field names has no known owner. */
|
|
517
|
+
static missing(purpose, operation = "enqueue") {
|
|
518
|
+
return new PolicyFailure({
|
|
519
|
+
reason: new Missing({ purpose }),
|
|
520
|
+
context: { operation, outcome: "not-submitted" },
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
/** A refusal by sequence affinity, with its SequenceError code. */
|
|
524
|
+
static sequence(sequenceId, code, operation = "enqueue") {
|
|
525
|
+
return new PolicyFailure({
|
|
526
|
+
reason: new SequenceRefusal({ sequenceId, code }),
|
|
527
|
+
context: { operation, outcome: "not-submitted" },
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
toJSON() {
|
|
531
|
+
return diagnostic(this);
|
|
76
532
|
}
|
|
77
533
|
}
|
|
78
|
-
|
|
79
|
-
|
|
534
|
+
// ---------------------------------------------------------------------------
|
|
535
|
+
// Evidence codecs
|
|
536
|
+
// ---------------------------------------------------------------------------
|
|
537
|
+
/** A bigint as diagnostic JSON writes it. */
|
|
538
|
+
const Digits = Schema.String.check(Schema.isPattern(/^(?:0|[1-9][0-9]*)$/));
|
|
539
|
+
/** A reason's diagnostic JSON, as `toJSON` writes it. */
|
|
540
|
+
const ReactorReasonJson = Schema.Union([
|
|
541
|
+
Schema.Struct({ _tag: FailureCode, message: Schema.String }),
|
|
542
|
+
Schema.TaggedStruct("Http", {
|
|
543
|
+
message: Schema.String,
|
|
544
|
+
status: Schema.optionalKey(Schema.Int),
|
|
545
|
+
retryAfterMillis: Schema.optionalKey(Schema.Finite.check(Schema.isGreaterThanOrEqualTo(0))),
|
|
546
|
+
}),
|
|
547
|
+
Schema.Struct({
|
|
548
|
+
_tag: Remote.fields._tag,
|
|
549
|
+
message: Schema.String,
|
|
550
|
+
remoteCode: Schema.optionalKey(Schema.String),
|
|
551
|
+
}),
|
|
552
|
+
Schema.TaggedStruct("Native", {
|
|
553
|
+
message: Schema.String,
|
|
554
|
+
status: Schema.optionalKey(Schema.Int),
|
|
555
|
+
}),
|
|
556
|
+
Schema.TaggedStruct("IceFailed", {
|
|
557
|
+
message: Schema.String,
|
|
558
|
+
pairs: IceFailed.fields.pairs,
|
|
559
|
+
candidateTypes: IceFailed.fields.candidateTypes,
|
|
560
|
+
}),
|
|
561
|
+
Schema.TaggedStruct("TransportFailed", {
|
|
562
|
+
message: Schema.String,
|
|
563
|
+
pairs: TransportFailed.fields.pairs,
|
|
564
|
+
}),
|
|
565
|
+
Schema.TaggedStruct("ClipEnded", {
|
|
566
|
+
message: Schema.String,
|
|
567
|
+
clipId: ClipEnded.fields.clipId,
|
|
568
|
+
lifecycle: ClipEnded.fields.lifecycle,
|
|
569
|
+
transportGeneration: Digits,
|
|
570
|
+
}),
|
|
571
|
+
]);
|
|
572
|
+
const PolicyReasonJson = Schema.Union([
|
|
573
|
+
Schema.Struct({ _tag: RefusalCode, message: Schema.String }),
|
|
574
|
+
Schema.TaggedStruct("Missing", {
|
|
575
|
+
message: Schema.String,
|
|
576
|
+
purpose: Missing.fields.purpose,
|
|
577
|
+
}),
|
|
578
|
+
Schema.TaggedStruct("Sequence", {
|
|
579
|
+
message: Schema.String,
|
|
580
|
+
sequenceId: SequenceRefusal.fields.sequenceId,
|
|
581
|
+
code: SequenceRefusal.fields.code,
|
|
582
|
+
}),
|
|
583
|
+
]);
|
|
584
|
+
const ContextJson = Schema.Struct({
|
|
585
|
+
operation: Schema.optionalKey(Schema.String),
|
|
586
|
+
requestId: Schema.optionalKey(Schema.String),
|
|
587
|
+
sessionId: Schema.optionalKey(Schema.String),
|
|
588
|
+
generation: Schema.optionalKey(Digits),
|
|
589
|
+
outcome: ErrorContext.fields.outcome,
|
|
590
|
+
});
|
|
591
|
+
const reactorReason = (json) => {
|
|
592
|
+
switch (json._tag) {
|
|
593
|
+
case "Http":
|
|
594
|
+
return new Http({
|
|
595
|
+
message: json.message,
|
|
596
|
+
...(json.status === undefined ? {} : { status: json.status }),
|
|
597
|
+
...(json.retryAfterMillis === undefined
|
|
598
|
+
? {}
|
|
599
|
+
: { retryAfter: Duration.millis(json.retryAfterMillis) }),
|
|
600
|
+
});
|
|
601
|
+
case "Remote":
|
|
602
|
+
case "RecorderDisabled":
|
|
603
|
+
return new Remote({
|
|
604
|
+
_tag: json._tag,
|
|
605
|
+
message: json.message,
|
|
606
|
+
...(json.remoteCode === undefined ? {} : { remoteCode: json.remoteCode }),
|
|
607
|
+
});
|
|
608
|
+
case "Native":
|
|
609
|
+
return new Native({
|
|
610
|
+
message: json.message,
|
|
611
|
+
...(json.status === undefined ? {} : { status: json.status }),
|
|
612
|
+
});
|
|
613
|
+
case "IceFailed":
|
|
614
|
+
return new IceFailed({
|
|
615
|
+
message: json.message,
|
|
616
|
+
pairs: json.pairs,
|
|
617
|
+
candidateTypes: json.candidateTypes,
|
|
618
|
+
});
|
|
619
|
+
case "TransportFailed":
|
|
620
|
+
return new TransportFailed({ message: json.message, pairs: json.pairs });
|
|
621
|
+
case "ClipEnded":
|
|
622
|
+
return new ClipEnded({
|
|
623
|
+
message: json.message,
|
|
624
|
+
clipId: json.clipId,
|
|
625
|
+
lifecycle: json.lifecycle,
|
|
626
|
+
transportGeneration: BigInt(json.transportGeneration),
|
|
627
|
+
});
|
|
628
|
+
default:
|
|
629
|
+
return new Failure({ _tag: json._tag, message: json.message });
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
const policyReason = (json) => {
|
|
633
|
+
switch (json._tag) {
|
|
634
|
+
case "Missing":
|
|
635
|
+
return new Missing({ purpose: json.purpose });
|
|
636
|
+
case "Sequence":
|
|
637
|
+
return new SequenceRefusal({ sequenceId: json.sequenceId, code: json.code });
|
|
638
|
+
default:
|
|
639
|
+
return new Refusal({ _tag: json._tag, message: json.message });
|
|
640
|
+
}
|
|
641
|
+
};
|
|
642
|
+
const context = ({ generation, ...rest }) => ({
|
|
643
|
+
...rest,
|
|
644
|
+
...(generation === undefined ? {} : { generation: BigInt(generation) }),
|
|
645
|
+
});
|
|
646
|
+
/**
|
|
647
|
+
* A failure class encoded as its diagnostic JSON. Encoding is `toJSON`, so
|
|
648
|
+
* provider text (`body`), native backend text and `context.detail` never
|
|
649
|
+
* reach the encoded form, and a decoded failure is without them.
|
|
650
|
+
*/
|
|
651
|
+
const fromJson = (json, target, build) => json.pipe(Schema.decodeTo(Schema.declare((u) => u instanceof target, { expected: target.name }), SchemaTransformation.transformEffect({
|
|
652
|
+
decode: (input, options) => Effect.try({
|
|
653
|
+
try: () => build(input),
|
|
654
|
+
catch: () => new SchemaIssue.InvalidValue({ message: `not a valid ${target.name} context` }, input, options),
|
|
655
|
+
}),
|
|
656
|
+
encode: (error) => Effect.succeed(error.toJSON()),
|
|
657
|
+
})));
|
|
658
|
+
/** A `ReactorError` as its diagnostic JSON. */
|
|
659
|
+
export const ReactorErrorFromJson = fromJson(Schema.TaggedStruct("ReactorError", {
|
|
660
|
+
message: Schema.String,
|
|
661
|
+
reason: ReactorReasonJson,
|
|
662
|
+
context: ContextJson,
|
|
663
|
+
}), ReactorError, (json) => new ReactorError({ reason: reactorReason(json.reason), context: context(json.context) }));
|
|
664
|
+
/** A `CommandFailure` as its diagnostic JSON. */
|
|
665
|
+
export const CommandFailureFromJson = fromJson(Schema.TaggedStruct("CommandFailure", {
|
|
666
|
+
message: Schema.String,
|
|
667
|
+
reason: ReactorReasonJson,
|
|
668
|
+
context: ContextJson,
|
|
669
|
+
}), CommandFailure, (json) => new CommandFailure({
|
|
670
|
+
reason: reactorReason(json.reason),
|
|
671
|
+
context: context(json.context),
|
|
672
|
+
}));
|
|
673
|
+
/** A `PolicyFailure` as its diagnostic JSON. */
|
|
674
|
+
export const PolicyFailureFromJson = fromJson(Schema.TaggedStruct("PolicyFailure", {
|
|
675
|
+
message: Schema.String,
|
|
676
|
+
reason: PolicyReasonJson,
|
|
677
|
+
context: ContextJson,
|
|
678
|
+
}), PolicyFailure, (json) => new PolicyFailure({
|
|
679
|
+
reason: policyReason(json.reason),
|
|
680
|
+
context: context(json.context),
|
|
681
|
+
}));
|
|
682
|
+
/**
|
|
683
|
+
* Whether `u` is one of the client's failures. A passthrough that re-raises a
|
|
684
|
+
* known failure uses this guard or a class's `is`, never `instanceof
|
|
685
|
+
* ReactorError`, so the evidence a class carries (the dispatch outcome,
|
|
686
|
+
* `AcquisitionFailure.cleanup`) is kept instead of re-wrapped.
|
|
687
|
+
*/
|
|
688
|
+
export const isReactorFailure = (u) => Predicate.hasProperty(u, TypeId);
|
|
689
|
+
/** A failure's reason as a ReactorError reason: a local refusal is invalid input or state. */
|
|
690
|
+
const wrapperReason = (error) => PolicyFailure.is(error)
|
|
691
|
+
? new Failure({
|
|
692
|
+
_tag: error.reason._tag === "InvalidRequest" ? "InvalidInput" : "InvalidState",
|
|
693
|
+
message: error.message,
|
|
694
|
+
})
|
|
695
|
+
: error.reason;
|
|
696
|
+
/**
|
|
697
|
+
* Runs a synchronous parser or check that rejects by throwing a ReactorError,
|
|
698
|
+
* and returns that rejection as the Result's failure. Anything else it throws
|
|
699
|
+
* is a bug and propagates, so it stays a defect rather than becoming a typed
|
|
700
|
+
* failure.
|
|
701
|
+
*/
|
|
702
|
+
export const parse = (evaluate) => {
|
|
703
|
+
try {
|
|
704
|
+
return Result.succeed(evaluate());
|
|
705
|
+
}
|
|
706
|
+
catch (cause) {
|
|
707
|
+
if (ReactorError.is(cause))
|
|
708
|
+
return Result.fail(cause);
|
|
709
|
+
throw cause;
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
/** `parse`, lifted with `Effect.fromResult`: a rejection fails it and a bug is a defect. */
|
|
713
|
+
export const parsed = (evaluate) => Effect.suspend(() => Effect.fromResult(parse(evaluate)));
|
|
714
|
+
/**
|
|
715
|
+
* A rejected caller input: `InvalidInput`, never submitted, whatever category
|
|
716
|
+
* the parser that rejected it uses for remote data.
|
|
717
|
+
*/
|
|
718
|
+
export const invalidInput = (error, operation) => ReactorError.fromCode("InvalidInput", error.message, {
|
|
719
|
+
...error.context,
|
|
720
|
+
...(operation === undefined ? {} : { operation }),
|
|
721
|
+
outcome: "not-submitted",
|
|
722
|
+
});
|
|
723
|
+
/** `parsed` for caller input: a rejection is `invalidInput`. */
|
|
724
|
+
export const parsedInput = (evaluate, operation) => parsed(evaluate).pipe(Effect.mapError((error) => invalidInput(error, operation)));
|
|
725
|
+
/**
|
|
726
|
+
* A platform call's expected exception (a DOMException, a rejected browser
|
|
727
|
+
* promise, a native library call), as a failure of the category `code`; the
|
|
728
|
+
* exception stays in the inspection-only `detail`. Parsers and checks use
|
|
729
|
+
* `parse` instead, so a bug in them stays a defect.
|
|
730
|
+
*/
|
|
731
|
+
export const errorOf = (cause, code, operation) => ReactorError.is(cause)
|
|
80
732
|
? cause
|
|
81
|
-
:
|
|
733
|
+
: ReactorError.fromCode(code, operation === undefined ? code : `${operation} failed`, {
|
|
82
734
|
...(operation === undefined ? {} : { operation }),
|
|
83
735
|
detail: cause,
|
|
84
736
|
});
|
|
85
737
|
export const positiveLimit = (value, name, maximum = 0x7fffffff) => {
|
|
86
738
|
if (!Number.isSafeInteger(value) || value < 1 || value > maximum) {
|
|
87
|
-
throw
|
|
739
|
+
throw ReactorError.fromCode("InvalidInput", `${name} must be an integer in 1..${maximum}`, {
|
|
88
740
|
outcome: "not-submitted",
|
|
89
741
|
});
|
|
90
742
|
}
|