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.
Files changed (219) hide show
  1. package/README.md +66 -6
  2. package/dist/FetchHttp.js +1 -1
  3. package/dist/FetchHttp.js.map +1 -1
  4. package/dist/PeerFactory.d.ts +13 -2
  5. package/dist/PeerFactory.d.ts.map +1 -1
  6. package/dist/PeerFactory.js.map +1 -1
  7. package/dist/PeerTypes.d.ts +2 -2
  8. package/dist/PeerTypes.d.ts.map +1 -1
  9. package/dist/Sequence.d.ts +18 -14
  10. package/dist/Sequence.d.ts.map +1 -1
  11. package/dist/Sequence.js +117 -122
  12. package/dist/Sequence.js.map +1 -1
  13. package/dist/SessionTypes.d.ts +172 -15
  14. package/dist/SessionTypes.d.ts.map +1 -1
  15. package/dist/SessionTypes.js +18 -1
  16. package/dist/SessionTypes.js.map +1 -1
  17. package/dist/bytes.js +3 -3
  18. package/dist/bytes.js.map +1 -1
  19. package/dist/contract.d.ts +120 -29
  20. package/dist/contract.d.ts.map +1 -1
  21. package/dist/contract.js +105 -73
  22. package/dist/contract.js.map +1 -1
  23. package/dist/coordinator/_internal/client.d.ts +111 -17
  24. package/dist/coordinator/_internal/client.d.ts.map +1 -1
  25. package/dist/coordinator/_internal/client.js +187 -74
  26. package/dist/coordinator/_internal/client.js.map +1 -1
  27. package/dist/coordinator/_internal/recording.d.ts +33 -0
  28. package/dist/coordinator/_internal/recording.d.ts.map +1 -0
  29. package/dist/coordinator/_internal/recording.js +123 -0
  30. package/dist/coordinator/_internal/recording.js.map +1 -0
  31. package/dist/coordinator/_internal/response.d.ts.map +1 -1
  32. package/dist/coordinator/_internal/response.js +5 -7
  33. package/dist/coordinator/_internal/response.js.map +1 -1
  34. package/dist/coordinator/_internal/schemas.d.ts +40 -6
  35. package/dist/coordinator/_internal/schemas.d.ts.map +1 -1
  36. package/dist/coordinator/_internal/schemas.js +94 -64
  37. package/dist/coordinator/_internal/schemas.js.map +1 -1
  38. package/dist/coordinator/index.d.ts +14 -2
  39. package/dist/coordinator/index.d.ts.map +1 -1
  40. package/dist/coordinator/index.js +14 -18
  41. package/dist/coordinator/index.js.map +1 -1
  42. package/dist/correlation.d.ts +15 -0
  43. package/dist/correlation.d.ts.map +1 -1
  44. package/dist/correlation.js +26 -8
  45. package/dist/correlation.js.map +1 -1
  46. package/dist/duration.d.ts +25 -0
  47. package/dist/duration.d.ts.map +1 -0
  48. package/dist/duration.js +47 -0
  49. package/dist/duration.js.map +1 -0
  50. package/dist/errors.d.ts +1264 -49
  51. package/dist/errors.d.ts.map +1 -1
  52. package/dist/errors.js +698 -46
  53. package/dist/errors.js.map +1 -1
  54. package/dist/h3/_internal/client.d.ts +2 -2
  55. package/dist/h3/_internal/client.d.ts.map +1 -1
  56. package/dist/h3/_internal/client.js +280 -146
  57. package/dist/h3/_internal/client.js.map +1 -1
  58. package/dist/h3/_internal/contracts.d.ts.map +1 -1
  59. package/dist/h3/_internal/contracts.js +2 -2
  60. package/dist/h3/_internal/contracts.js.map +1 -1
  61. package/dist/h3/_internal/deployment.d.ts.map +1 -1
  62. package/dist/h3/_internal/deployment.js +5 -4
  63. package/dist/h3/_internal/deployment.js.map +1 -1
  64. package/dist/h3/_internal/evidence.js +1 -1
  65. package/dist/h3/_internal/evidence.js.map +1 -1
  66. package/dist/h3/_internal/operations.d.ts +58 -0
  67. package/dist/h3/_internal/operations.d.ts.map +1 -0
  68. package/dist/h3/_internal/operations.js +268 -0
  69. package/dist/h3/_internal/operations.js.map +1 -0
  70. package/dist/h3/_internal/references.d.ts.map +1 -1
  71. package/dist/h3/_internal/references.js +3 -8
  72. package/dist/h3/_internal/references.js.map +1 -1
  73. package/dist/h3/_internal/request.js +1 -1
  74. package/dist/h3/_internal/request.js.map +1 -1
  75. package/dist/h3/_internal/retained.d.ts +22 -0
  76. package/dist/h3/_internal/retained.d.ts.map +1 -0
  77. package/dist/h3/_internal/retained.js +89 -0
  78. package/dist/h3/_internal/retained.js.map +1 -0
  79. package/dist/h3/_internal/state.d.ts +15 -1
  80. package/dist/h3/_internal/state.d.ts.map +1 -1
  81. package/dist/h3/_internal/state.js +69 -29
  82. package/dist/h3/_internal/state.js.map +1 -1
  83. package/dist/h3/index.d.ts +1 -1
  84. package/dist/h3/index.d.ts.map +1 -1
  85. package/dist/h3/messages.d.ts +118 -109
  86. package/dist/h3/messages.d.ts.map +1 -1
  87. package/dist/h3/messages.js +73 -51
  88. package/dist/h3/messages.js.map +1 -1
  89. package/dist/h3/types.d.ts +109 -13
  90. package/dist/h3/types.d.ts.map +1 -1
  91. package/dist/host.d.ts +6 -6
  92. package/dist/host.d.ts.map +1 -1
  93. package/dist/host.js +4 -5
  94. package/dist/host.js.map +1 -1
  95. package/dist/index.d.ts +11 -5
  96. package/dist/index.d.ts.map +1 -1
  97. package/dist/index.js +5 -1
  98. package/dist/index.js.map +1 -1
  99. package/dist/json.d.ts +13 -6
  100. package/dist/json.d.ts.map +1 -1
  101. package/dist/json.js +34 -14
  102. package/dist/json.js.map +1 -1
  103. package/dist/media-stream.d.ts.map +1 -1
  104. package/dist/media-stream.js +9 -11
  105. package/dist/media-stream.js.map +1 -1
  106. package/dist/observation.d.ts +9 -0
  107. package/dist/observation.d.ts.map +1 -1
  108. package/dist/observation.js +20 -18
  109. package/dist/observation.js.map +1 -1
  110. package/dist/orchestration/h3-source.d.ts +32 -4
  111. package/dist/orchestration/h3-source.d.ts.map +1 -1
  112. package/dist/orchestration/h3-source.js +47 -14
  113. package/dist/orchestration/h3-source.js.map +1 -1
  114. package/dist/orchestration/index.d.ts +9 -6
  115. package/dist/orchestration/index.d.ts.map +1 -1
  116. package/dist/orchestration/index.js +5 -3
  117. package/dist/orchestration/index.js.map +1 -1
  118. package/dist/orchestration/media-buffer.d.ts +12 -4
  119. package/dist/orchestration/media-buffer.d.ts.map +1 -1
  120. package/dist/orchestration/media-buffer.js +31 -11
  121. package/dist/orchestration/media-buffer.js.map +1 -1
  122. package/dist/orchestration/open-h3.d.ts +65 -0
  123. package/dist/orchestration/open-h3.d.ts.map +1 -0
  124. package/dist/orchestration/open-h3.js +65 -0
  125. package/dist/orchestration/open-h3.js.map +1 -0
  126. package/dist/orchestration/references.d.ts +5 -1
  127. package/dist/orchestration/references.d.ts.map +1 -1
  128. package/dist/orchestration/references.js +37 -34
  129. package/dist/orchestration/references.js.map +1 -1
  130. package/dist/orchestration/renewal-state.js +1 -1
  131. package/dist/orchestration/renewal-state.js.map +1 -1
  132. package/dist/orchestration/renewal.d.ts +64 -64
  133. package/dist/orchestration/renewal.d.ts.map +1 -1
  134. package/dist/orchestration/renewal.js +149 -88
  135. package/dist/orchestration/renewal.js.map +1 -1
  136. package/dist/orchestration/request.d.ts +11 -12
  137. package/dist/orchestration/request.d.ts.map +1 -1
  138. package/dist/orchestration/request.js +63 -66
  139. package/dist/orchestration/request.js.map +1 -1
  140. package/dist/orchestration/routing.d.ts.map +1 -1
  141. package/dist/orchestration/routing.js +17 -14
  142. package/dist/orchestration/routing.js.map +1 -1
  143. package/dist/orchestration/source-slot.d.ts +15 -5
  144. package/dist/orchestration/source-slot.d.ts.map +1 -1
  145. package/dist/orchestration/source-slot.js +40 -21
  146. package/dist/orchestration/source-slot.js.map +1 -1
  147. package/dist/orchestration/types.d.ts +594 -39
  148. package/dist/orchestration/types.d.ts.map +1 -1
  149. package/dist/orchestration/types.js +20 -0
  150. package/dist/orchestration/types.js.map +1 -1
  151. package/dist/protobuf.js +2 -2
  152. package/dist/protobuf.js.map +1 -1
  153. package/dist/session/_internal/acquire.d.ts +4 -6
  154. package/dist/session/_internal/acquire.d.ts.map +1 -1
  155. package/dist/session/_internal/acquire.js +40 -50
  156. package/dist/session/_internal/acquire.js.map +1 -1
  157. package/dist/session/_internal/cleanup.d.ts +2 -1
  158. package/dist/session/_internal/cleanup.d.ts.map +1 -1
  159. package/dist/session/_internal/cleanup.js +23 -15
  160. package/dist/session/_internal/cleanup.js.map +1 -1
  161. package/dist/session/_internal/lifecycle.d.ts +5 -0
  162. package/dist/session/_internal/lifecycle.d.ts.map +1 -1
  163. package/dist/session/_internal/lifecycle.js +16 -6
  164. package/dist/session/_internal/lifecycle.js.map +1 -1
  165. package/dist/session/_internal/remote.d.ts.map +1 -1
  166. package/dist/session/_internal/remote.js +16 -7
  167. package/dist/session/_internal/remote.js.map +1 -1
  168. package/dist/session/_internal/uploads.d.ts +4 -1
  169. package/dist/session/_internal/uploads.d.ts.map +1 -1
  170. package/dist/session/_internal/uploads.js +25 -27
  171. package/dist/session/_internal/uploads.js.map +1 -1
  172. package/dist/session/commands.d.ts +1 -21
  173. package/dist/session/commands.d.ts.map +1 -1
  174. package/dist/session/commands.js +1 -12
  175. package/dist/session/commands.js.map +1 -1
  176. package/dist/session/files.d.ts +10 -2
  177. package/dist/session/files.d.ts.map +1 -1
  178. package/dist/session/files.js +11 -16
  179. package/dist/session/files.js.map +1 -1
  180. package/dist/session/index.d.ts +6 -6
  181. package/dist/session/index.d.ts.map +1 -1
  182. package/dist/session/index.js.map +1 -1
  183. package/dist/session/media.d.ts +53 -6
  184. package/dist/session/media.d.ts.map +1 -1
  185. package/dist/session/recorder.d.ts +24 -0
  186. package/dist/session/recorder.d.ts.map +1 -0
  187. package/dist/session/recorder.js +19 -0
  188. package/dist/session/recorder.js.map +1 -0
  189. package/dist/session.d.ts +5 -4
  190. package/dist/session.d.ts.map +1 -1
  191. package/dist/session.js +213 -110
  192. package/dist/session.js.map +1 -1
  193. package/dist/simulation/_internal/source.d.ts.map +1 -1
  194. package/dist/simulation/_internal/source.js +57 -48
  195. package/dist/simulation/_internal/source.js.map +1 -1
  196. package/dist/simulation/index.d.ts +4 -5
  197. package/dist/simulation/index.d.ts.map +1 -1
  198. package/dist/simulation/index.js +3 -4
  199. package/dist/simulation/index.js.map +1 -1
  200. package/dist/simulation/types.d.ts +16 -2
  201. package/dist/simulation/types.d.ts.map +1 -1
  202. package/dist/stats.d.ts +19 -0
  203. package/dist/stats.d.ts.map +1 -1
  204. package/dist/stats.js +79 -8
  205. package/dist/stats.js.map +1 -1
  206. package/package.json +5 -4
  207. package/notices/owned-webrtc/Effect-MIT.txt +0 -21
  208. package/notices/owned-webrtc/IETF.txt +0 -43
  209. package/notices/owned-webrtc/LICENSE +0 -21
  210. package/notices/owned-webrtc/UPSTREAM.md +0 -20
  211. package/notices/owned-webrtc/VP8-ADAPTATION.md +0 -32
  212. package/notices/owned-webrtc/VP8-AUTHORS.txt +0 -42
  213. package/notices/owned-webrtc/VP8-LICENSE.txt +0 -25
  214. package/notices/owned-webrtc/VP8-PATENTS.txt +0 -21
  215. package/notices/owned-webrtc/Werift-MIT.txt +0 -21
  216. package/notices/owned-webrtc/libvpx-v1.12.0-LICENSE.txt +0 -28
  217. package/notices/owned-webrtc/libvpx-v1.12.0-PATENTS.txt +0 -22
  218. package/notices/owned-webrtc/rfc3711.txt +0 -3139
  219. 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
- export const ErrorCode = Schema.Literals([
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
- export const ProviderFailure = Schema.Struct({
25
- code: Schema.String,
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
- recoverable: Schema.Boolean,
28
- operation: Schema.optionalKey(Schema.String),
29
- status: Schema.optionalKey(Schema.Number),
30
- retryAfterMs: Schema.optionalKey(Schema.Number),
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
- status: Schema.optionalKey(Schema.Number),
39
- retryAfterMs: Schema.optionalKey(Schema.Number),
40
- remoteCode: Schema.optionalKey(Schema.String),
41
- /** Explicit inspection only. Diagnostic serialization excludes response bodies and causes. */
42
- body: Schema.optionalKey(Schema.String),
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
- /** A failure category never implies a mutation was rolled back. Inspect context.outcome. */
46
- export class ReactorError extends Schema.TaggedError("reactor-effect-client/ReactorError")("ReactorError", {
47
- code: ErrorCode,
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
- constructor(code, message, context = {}) {
53
- const fields = typeof code === "string" ? { code, message: message ?? code, context } : code;
54
- super({ ...fields, context: fields.context ?? {} });
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
- const { body: _body, detail: _detail, generation, ...context } = this.context;
58
- return {
59
- _tag: this._tag,
60
- code: this.code,
61
- message: this.message,
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
- ...context,
64
- ...(generation === undefined ? {} : { generation: String(generation) }),
510
+ operation,
511
+ outcome: "not-submitted",
512
+ ...(cause === undefined ? {} : { detail: cause }),
65
513
  },
66
- ...(this.nativeError === undefined
67
- ? {}
68
- : {
69
- provider: {
70
- code: this.nativeError.code,
71
- operation: this.nativeError.operation,
72
- status: this.nativeError.status,
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
- /** Keep the original cause for deliberate inspection without including it in the message. */
79
- export const errorOf = (cause, code = "Protocol", operation) => cause instanceof ReactorError
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
- : new ReactorError(code, operation === undefined ? code : `${operation} failed`, {
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 new ReactorError("InvalidInput", `${name} must be an integer in 1..${maximum}`, {
739
+ throw ReactorError.fromCode("InvalidInput", `${name} must be an integer in 1..${maximum}`, {
88
740
  outcome: "not-submitted",
89
741
  });
90
742
  }