@securitydept/client 0.2.0 → 0.3.0-beta.10
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 +6 -0
- package/dist/create-i5VyYgN6.mjs +989 -0
- package/dist/create-i5VyYgN6.mjs.map +1 -0
- package/dist/handle-DwMgqe7R.mjs +638 -0
- package/dist/handle-DwMgqe7R.mjs.map +1 -0
- package/dist/index-Bo1BtjZH.d.mts +132 -0
- package/dist/index-CFAJ969E.d.mts +674 -0
- package/dist/index-CbeZ3AfB.d.mts +1680 -0
- package/dist/index.d.mts +3 -353
- package/dist/index.mjs +1196 -401
- package/dist/index.mjs.map +1 -1
- package/dist/router-at5bTdJQ.mjs +245 -0
- package/dist/router-at5bTdJQ.mjs.map +1 -0
- package/dist/rx/index.d.mts +68 -0
- package/dist/rx/index.mjs +2 -0
- package/dist/rx-DM6Ouv-m.mjs +1119 -0
- package/dist/rx-DM6Ouv-m.mjs.map +1 -0
- package/dist/server/index.d.mts +20 -0
- package/dist/server/index.mjs +29 -0
- package/dist/server/index.mjs.map +1 -0
- package/dist/test/index.d.mts +101 -0
- package/dist/test/index.mjs +270 -0
- package/dist/test/index.mjs.map +1 -0
- package/dist/uri-string-BPNbIMgv.mjs +300 -0
- package/dist/uri-string-BPNbIMgv.mjs.map +1 -0
- package/dist/web/index.d.mts +82 -337
- package/dist/web/index.mjs +334 -337
- package/dist/web/index.mjs.map +1 -1
- package/dist/webext/index.d.mts +73 -0
- package/dist/webext/index.mjs +231 -0
- package/dist/webext/index.mjs.map +1 -0
- package/package.json +28 -21
- package/dist/auth-coordination/index.d.mts +0 -198
- package/dist/auth-coordination/index.mjs +0 -107
- package/dist/auth-coordination/index.mjs.map +0 -1
- package/dist/events/index.d.mts +0 -2
- package/dist/events/index.mjs +0 -2
- package/dist/events-BUlfsRfl.mjs +0 -127
- package/dist/events-BUlfsRfl.mjs.map +0 -1
- package/dist/fetch-transport-Bd5RyWj2.d.mts +0 -339
- package/dist/fetch-transport-DtnSRWAH.mjs +0 -631
- package/dist/fetch-transport-DtnSRWAH.mjs.map +0 -1
- package/dist/index-C_wgl6dW.d.mts +0 -102
- package/dist/index-DbYSFHDe.d.mts +0 -39
- package/dist/persistence/index.d.mts +0 -3
- package/dist/persistence/index.mjs +0 -2
- package/dist/persistence/web/index.d.mts +0 -16
- package/dist/persistence/web/index.mjs +0 -51
- package/dist/persistence/web/index.mjs.map +0 -1
- package/dist/persistence-CSB8770k.mjs +0 -108
- package/dist/persistence-CSB8770k.mjs.map +0 -1
- package/dist/planner-host-CPyGNyQV.d.mts +0 -190
- package/dist/planner-host-CV38bMSn.mjs +0 -195
- package/dist/planner-host-CV38bMSn.mjs.map +0 -1
- package/dist/types-C59fcM5W.d.mts +0 -52
- package/dist/types-DQc088ZP.d.mts +0 -32
- package/dist/web-router/index.d.mts +0 -259
- package/dist/web-router/index.mjs +0 -395
- package/dist/web-router/index.mjs.map +0 -1
|
@@ -0,0 +1,1119 @@
|
|
|
1
|
+
import { BehaviorSubject, EMPTY, NEVER, Observable, ReplaySubject, Subject, animationFrameScheduler, asapScheduler, audit, auditTime, catchError, concat, concatAll, defer, distinctUntilChanged, filter, finalize, firstValueFrom, from, fromEventPattern, lastValueFrom, map, merge, of, share, shareReplay, switchMap, take, takeLast, takeUntil, takeWhile, toArray } from "rxjs";
|
|
2
|
+
import { AsyncAction } from "rxjs/internal/scheduler/AsyncAction";
|
|
3
|
+
import { AsyncScheduler } from "rxjs/internal/scheduler/AsyncScheduler";
|
|
4
|
+
import { BehaviorSubject as BehaviorSubject$1 } from "rxjs/internal/BehaviorSubject";
|
|
5
|
+
//#region src/errors/types.ts
|
|
6
|
+
/** Recovery actions the user might take. Aligned with server-side `UserRecovery`. */
|
|
7
|
+
const UserRecovery = {
|
|
8
|
+
None: "none",
|
|
9
|
+
Retry: "retry",
|
|
10
|
+
RestartFlow: "restart_flow",
|
|
11
|
+
Reauthenticate: "reauthenticate",
|
|
12
|
+
ContactSupport: "contact_support"
|
|
13
|
+
};
|
|
14
|
+
const ErrorPresentationTone = {
|
|
15
|
+
Neutral: "neutral",
|
|
16
|
+
Warning: "warning",
|
|
17
|
+
Danger: "danger"
|
|
18
|
+
};
|
|
19
|
+
/** Machine-facing error kind discriminator. */
|
|
20
|
+
const ClientErrorKind = {
|
|
21
|
+
Authorization: "authorization",
|
|
22
|
+
Transport: "transport",
|
|
23
|
+
Server: "server",
|
|
24
|
+
Protocol: "protocol",
|
|
25
|
+
Storage: "storage",
|
|
26
|
+
Configuration: "configuration",
|
|
27
|
+
Unauthenticated: "unauthenticated",
|
|
28
|
+
Unauthorized: "unauthorized",
|
|
29
|
+
Cancelled: "cancelled",
|
|
30
|
+
Timeout: "timeout",
|
|
31
|
+
Internal: "internal"
|
|
32
|
+
};
|
|
33
|
+
/** Stable source vocabulary for common client error producers. */
|
|
34
|
+
const ClientErrorSource = {
|
|
35
|
+
Transport: "transport",
|
|
36
|
+
Server: "server",
|
|
37
|
+
Client: "client"
|
|
38
|
+
};
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/errors/client-error.ts
|
|
41
|
+
const CLIENT_ERROR_SPAN_ATTRIBUTE_PROVIDER_ID = "@securitydept/client/error";
|
|
42
|
+
const userRecoveryValues = new Set(Object.values(UserRecovery));
|
|
43
|
+
function readUserRecovery(value) {
|
|
44
|
+
return typeof value === "string" && userRecoveryValues.has(value) ? value : void 0;
|
|
45
|
+
}
|
|
46
|
+
function mapServerErrorKind(kind, status) {
|
|
47
|
+
switch (kind) {
|
|
48
|
+
case "unauthenticated": return ClientErrorKind.Unauthenticated;
|
|
49
|
+
case "unauthorized": return ClientErrorKind.Unauthorized;
|
|
50
|
+
case "unavailable":
|
|
51
|
+
case "internal": return ClientErrorKind.Server;
|
|
52
|
+
case "invalid_request":
|
|
53
|
+
case "conflict": return ClientErrorKind.Protocol;
|
|
54
|
+
default: return status === 401 ? ClientErrorKind.Unauthenticated : status === 403 ? ClientErrorKind.Unauthorized : status === 408 ? ClientErrorKind.Timeout : status >= 500 ? ClientErrorKind.Server : ClientErrorKind.Protocol;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function readServerErrorBody(body) {
|
|
58
|
+
if (!body || typeof body !== "object") return;
|
|
59
|
+
const record = body;
|
|
60
|
+
const error = record.error && typeof record.error === "object" ? record.error : record;
|
|
61
|
+
return {
|
|
62
|
+
kind: typeof error.kind === "string" ? error.kind : void 0,
|
|
63
|
+
code: typeof error.code === "string" ? error.code : void 0,
|
|
64
|
+
message: typeof error.message === "string" ? error.message : void 0,
|
|
65
|
+
recovery: typeof error.recovery === "string" ? error.recovery : void 0,
|
|
66
|
+
presentation: error.presentation && typeof error.presentation === "object" ? {
|
|
67
|
+
code: typeof error.presentation.code === "string" ? error.presentation.code : void 0,
|
|
68
|
+
message: typeof error.presentation.message === "string" ? error.presentation.message : void 0,
|
|
69
|
+
recovery: typeof error.presentation.recovery === "string" ? error.presentation.recovery : void 0
|
|
70
|
+
} : void 0
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Base client error carrying both machine-facing context and an optional
|
|
75
|
+
* user-facing presentation layer.
|
|
76
|
+
*
|
|
77
|
+
* Top-level `code` and `recovery` provide stable machine contracts for
|
|
78
|
+
* policy decisions without requiring callers to unwrap `presentation`.
|
|
79
|
+
*/
|
|
80
|
+
var ClientError = class ClientError extends Error {
|
|
81
|
+
_spanContext;
|
|
82
|
+
kind;
|
|
83
|
+
/**
|
|
84
|
+
* Stable machine-readable error code.
|
|
85
|
+
* Matches `presentation.code` when present, otherwise a default
|
|
86
|
+
* derived from `kind`.
|
|
87
|
+
*/
|
|
88
|
+
code;
|
|
89
|
+
/** Recovery hint at the machine level. */
|
|
90
|
+
recovery;
|
|
91
|
+
/** Whether this error is safe to retry. */
|
|
92
|
+
retryable;
|
|
93
|
+
/** Structured user-facing error presentation from the server. */
|
|
94
|
+
presentation;
|
|
95
|
+
/** Source subsystem or component that produced the error. */
|
|
96
|
+
source;
|
|
97
|
+
get spanContext() {
|
|
98
|
+
return this._spanContext;
|
|
99
|
+
}
|
|
100
|
+
constructor(options) {
|
|
101
|
+
super(options.message, { cause: options.cause });
|
|
102
|
+
this.name = "ClientError";
|
|
103
|
+
this.kind = options.kind;
|
|
104
|
+
this.code = options.code ?? options.presentation?.code ?? `client.${options.kind}`;
|
|
105
|
+
this.recovery = options.recovery ?? options.presentation?.recovery ?? UserRecovery.None;
|
|
106
|
+
this.retryable = options.retryable ?? this.recovery === UserRecovery.Retry;
|
|
107
|
+
this.presentation = options.presentation;
|
|
108
|
+
this.source = options.source;
|
|
109
|
+
if (options.span) this.captureSpanContext(options.span);
|
|
110
|
+
}
|
|
111
|
+
captureSpanContext(span) {
|
|
112
|
+
this._spanContext ??= span.getRootToNodeAttributes({ providerId: CLIENT_ERROR_SPAN_ATTRIBUTE_PROVIDER_ID });
|
|
113
|
+
return this;
|
|
114
|
+
}
|
|
115
|
+
/** Create a `ClientError` from a server error response body. */
|
|
116
|
+
static fromServerError(body, overrides) {
|
|
117
|
+
const rawPresentation = body.presentation;
|
|
118
|
+
const recovery = readUserRecovery(body.recovery) ?? readUserRecovery(rawPresentation?.recovery) ?? UserRecovery.None;
|
|
119
|
+
const presentation = rawPresentation?.code && rawPresentation.message ? {
|
|
120
|
+
code: rawPresentation.code,
|
|
121
|
+
message: rawPresentation.message,
|
|
122
|
+
recovery: readUserRecovery(rawPresentation.recovery) ?? recovery
|
|
123
|
+
} : void 0;
|
|
124
|
+
return new ClientError({
|
|
125
|
+
kind: overrides?.kind ?? mapServerErrorKind(body.kind, 400),
|
|
126
|
+
message: body.message ?? presentation?.message ?? "Unknown server error",
|
|
127
|
+
code: body.code ?? presentation?.code,
|
|
128
|
+
recovery,
|
|
129
|
+
retryable: recovery === UserRecovery.Retry,
|
|
130
|
+
presentation,
|
|
131
|
+
source: overrides?.source,
|
|
132
|
+
span: overrides?.span
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
static fromUnknown(error, options) {
|
|
136
|
+
if (error instanceof ClientError) return options.span ? error.captureSpanContext(options.span) : error;
|
|
137
|
+
return new ClientError({
|
|
138
|
+
kind: ClientErrorKind.Internal,
|
|
139
|
+
message: options.message,
|
|
140
|
+
code: options.code,
|
|
141
|
+
source: options.source,
|
|
142
|
+
cause: error,
|
|
143
|
+
span: options.span
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Create a `ClientError` from an HTTP response status and optional body.
|
|
148
|
+
* Used when a non-success status is unexpected.
|
|
149
|
+
*/
|
|
150
|
+
static fromHttpResponse(options) {
|
|
151
|
+
const { status, body } = options;
|
|
152
|
+
const serverBody = readServerErrorBody(body);
|
|
153
|
+
const kind = mapServerErrorKind(serverBody?.kind, status);
|
|
154
|
+
const source = options.source ?? ClientErrorSource.Server;
|
|
155
|
+
const fallbackRecovery = kind === ClientErrorKind.Unauthenticated ? UserRecovery.Reauthenticate : status === 408 || status >= 500 ? UserRecovery.Retry : UserRecovery.None;
|
|
156
|
+
if (serverBody?.code && serverBody?.message) {
|
|
157
|
+
const recovery = readUserRecovery(serverBody.presentation?.recovery) ?? readUserRecovery(serverBody.recovery) ?? fallbackRecovery;
|
|
158
|
+
return ClientError.fromServerError({
|
|
159
|
+
...serverBody,
|
|
160
|
+
recovery,
|
|
161
|
+
presentation: serverBody.presentation ? {
|
|
162
|
+
...serverBody.presentation,
|
|
163
|
+
recovery
|
|
164
|
+
} : void 0
|
|
165
|
+
}, {
|
|
166
|
+
kind,
|
|
167
|
+
source,
|
|
168
|
+
span: options.span
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
return new ClientError({
|
|
172
|
+
kind,
|
|
173
|
+
message: serverBody?.message ?? `HTTP ${status}`,
|
|
174
|
+
code: serverBody?.code ?? `http.${status}`,
|
|
175
|
+
recovery: fallbackRecovery,
|
|
176
|
+
retryable: status === 408 || status >= 500,
|
|
177
|
+
source,
|
|
178
|
+
span: options.span
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
//#endregion
|
|
183
|
+
//#region src/compat/disposable.ts
|
|
184
|
+
const SYMBOL_DISPOSE = typeof Symbol !== "undefined" && Symbol.dispose ? Symbol.dispose : "@@dispose";
|
|
185
|
+
const SYMBOL_ASYNC_DISPOSE = typeof Symbol !== "undefined" && Symbol.asyncDispose ? Symbol.asyncDispose : "@@asyncDispose";
|
|
186
|
+
const SYMBOL_DISPOSABLE_STACK = "@@disposableStack";
|
|
187
|
+
function injectDisposableStackFrom(arg, isOptions = false) {
|
|
188
|
+
return isOptions && typeof arg === "object" && arg !== null ? arg[SYMBOL_DISPOSABLE_STACK] : arg;
|
|
189
|
+
}
|
|
190
|
+
var SimpleDisposableStack = class {
|
|
191
|
+
stack = [];
|
|
192
|
+
disposed = false;
|
|
193
|
+
use(disposable) {
|
|
194
|
+
if (this.disposed) throw new ReferenceError("Disposable stack is already disposed");
|
|
195
|
+
this.stack.push(disposable);
|
|
196
|
+
return disposable;
|
|
197
|
+
}
|
|
198
|
+
dispose() {
|
|
199
|
+
if (this.disposed) return;
|
|
200
|
+
this.disposed = true;
|
|
201
|
+
const errors = [];
|
|
202
|
+
for (let disposable = this.stack.pop(); disposable; disposable = this.stack.pop()) try {
|
|
203
|
+
disposable.dispose();
|
|
204
|
+
} catch (error) {
|
|
205
|
+
errors.push(error);
|
|
206
|
+
}
|
|
207
|
+
if (errors.length === 1) throw errors[0];
|
|
208
|
+
if (errors.length > 1) throw new AggregateError(errors, "Failed to dispose disposable stack");
|
|
209
|
+
}
|
|
210
|
+
[SYMBOL_DISPOSE]() {
|
|
211
|
+
this.dispose();
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
var StdDisposableStack = class {
|
|
215
|
+
stack;
|
|
216
|
+
constructor(Stack = globalThis.DisposableStack) {
|
|
217
|
+
this.stack = new Stack();
|
|
218
|
+
}
|
|
219
|
+
get disposed() {
|
|
220
|
+
return this.stack.disposed;
|
|
221
|
+
}
|
|
222
|
+
use(disposable) {
|
|
223
|
+
return this.stack.use(disposable);
|
|
224
|
+
}
|
|
225
|
+
dispose() {
|
|
226
|
+
this.stack.dispose();
|
|
227
|
+
}
|
|
228
|
+
[SYMBOL_DISPOSE]() {
|
|
229
|
+
this.dispose();
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
function createDisposableStack() {
|
|
233
|
+
const Stack = globalThis.DisposableStack;
|
|
234
|
+
return typeof Stack === "function" ? new StdDisposableStack(Stack) : new SimpleDisposableStack();
|
|
235
|
+
}
|
|
236
|
+
function withDisposableStack(index, isOptions = false) {
|
|
237
|
+
return (target, _context) => function(...args) {
|
|
238
|
+
const disposableStack = createDisposableStack();
|
|
239
|
+
const injectedArgs = [...args];
|
|
240
|
+
if (isOptions) {
|
|
241
|
+
const value = injectedArgs[index];
|
|
242
|
+
injectedArgs[index] = Object.assign({}, typeof value === "object" && value !== null ? value : void 0, { [SYMBOL_DISPOSABLE_STACK]: disposableStack });
|
|
243
|
+
} else injectedArgs[index] = disposableStack;
|
|
244
|
+
let result;
|
|
245
|
+
try {
|
|
246
|
+
result = target.call(this, ...injectedArgs);
|
|
247
|
+
} catch (error) {
|
|
248
|
+
disposableStack.dispose();
|
|
249
|
+
throw error;
|
|
250
|
+
}
|
|
251
|
+
if (typeof result === "object" && result !== null && "then" in result && typeof result.then === "function") return Promise.resolve(result).finally(() => {
|
|
252
|
+
disposableStack.dispose();
|
|
253
|
+
});
|
|
254
|
+
disposableStack.dispose();
|
|
255
|
+
return result;
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
//#endregion
|
|
259
|
+
//#region src/compat/observable.ts
|
|
260
|
+
const SYMBOL_OBSERVABLE = typeof Symbol !== "undefined" && Symbol.observable ? Symbol.observable : "@@observable";
|
|
261
|
+
function isInteropObservableTrait(value) {
|
|
262
|
+
return typeof value === "object" && value !== null && SYMBOL_OBSERVABLE in value && typeof value[SYMBOL_OBSERVABLE] === "function";
|
|
263
|
+
}
|
|
264
|
+
//#endregion
|
|
265
|
+
//#region src/rx/event.ts
|
|
266
|
+
var RxEventStream = class RxEventStream extends Observable {
|
|
267
|
+
constructor(source) {
|
|
268
|
+
super((subscriber) => {
|
|
269
|
+
if (typeof source === "function") return source(subscriber);
|
|
270
|
+
if (isInteropObservableTrait(source)) {
|
|
271
|
+
const subscription = source[SYMBOL_OBSERVABLE]().subscribe(subscriber);
|
|
272
|
+
return () => {
|
|
273
|
+
subscription.unsubscribe();
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
if (typeof source === "object" && source !== null && "subscribe" in source && typeof source.subscribe === "function") {
|
|
277
|
+
const subscription = source.subscribe(subscriber);
|
|
278
|
+
return () => {
|
|
279
|
+
subscription.unsubscribe();
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
const subscription = from(source).subscribe(subscriber);
|
|
283
|
+
return () => {
|
|
284
|
+
subscription.unsubscribe();
|
|
285
|
+
};
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
[SYMBOL_OBSERVABLE]() {
|
|
289
|
+
return this;
|
|
290
|
+
}
|
|
291
|
+
static fromObservableInput(input) {
|
|
292
|
+
if (input instanceof RxEventStream) return input;
|
|
293
|
+
return new RxEventStream(input);
|
|
294
|
+
}
|
|
295
|
+
pipe(...args) {
|
|
296
|
+
const piped = super.pipe(...args);
|
|
297
|
+
return RxEventStream.fromObservableInput(piped);
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
var RxEventSubject = class extends Subject {
|
|
301
|
+
[SYMBOL_OBSERVABLE]() {
|
|
302
|
+
return this.asObservable();
|
|
303
|
+
}
|
|
304
|
+
asObservable() {
|
|
305
|
+
return RxEventStream.fromObservableInput(super.asObservable());
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
var RxEventReplaySubject = class extends ReplaySubject {
|
|
309
|
+
[SYMBOL_OBSERVABLE]() {
|
|
310
|
+
return this.asObservable();
|
|
311
|
+
}
|
|
312
|
+
asObservable() {
|
|
313
|
+
return RxEventStream.fromObservableInput(super.asObservable());
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
//#endregion
|
|
317
|
+
//#region src/rx/signal.ts
|
|
318
|
+
function watchOptionsToAuditOperator(options) {
|
|
319
|
+
if (options.schedule === globalThis.queueMicrotask) return auditTime(0, asapScheduler);
|
|
320
|
+
if (options.schedule === globalThis.requestAnimationFrame && options.cancel === globalThis.cancelAnimationFrame) return auditTime(0, animationFrameScheduler);
|
|
321
|
+
return audit(() => fromEventPattern((handler) => options.schedule(() => handler()), (_handler, handle) => {
|
|
322
|
+
if (options.cancel) options.cancel(handle);
|
|
323
|
+
else if (typeof handle === "function") handle();
|
|
324
|
+
}));
|
|
325
|
+
}
|
|
326
|
+
var RxSignal = class {
|
|
327
|
+
static globalEpoch = 0;
|
|
328
|
+
static currentContext = null;
|
|
329
|
+
valueEqVersion = 0;
|
|
330
|
+
equals;
|
|
331
|
+
constructor(options) {
|
|
332
|
+
this.equals = options?.equals ?? Object.is;
|
|
333
|
+
}
|
|
334
|
+
watchStream(options = { schedule: globalThis.queueMicrotask }) {
|
|
335
|
+
return RxEventStream.fromObservableInput(this.dirtyObservable().pipe(watchOptionsToAuditOperator(options), map(() => void 0), shareReplay({
|
|
336
|
+
bufferSize: 1,
|
|
337
|
+
refCount: true
|
|
338
|
+
})));
|
|
339
|
+
}
|
|
340
|
+
[SYMBOL_OBSERVABLE]() {
|
|
341
|
+
return RxEventStream.fromObservableInput(defer(() => concat(of(this.get()), this.dirtyObservable().pipe(map(() => this.get())))));
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
var RxStateSignal = class RxStateSignal extends RxSignal {
|
|
345
|
+
dirtySubject = new Subject();
|
|
346
|
+
value;
|
|
347
|
+
constructor(initialValue, options) {
|
|
348
|
+
super(options);
|
|
349
|
+
this.value = new BehaviorSubject(initialValue);
|
|
350
|
+
}
|
|
351
|
+
set(input) {
|
|
352
|
+
const changed = !this.equals(this.value.getValue(), input);
|
|
353
|
+
this.value.next(input);
|
|
354
|
+
if (changed) this.valueEqVersion += 1;
|
|
355
|
+
RxSignal.globalEpoch += 1;
|
|
356
|
+
this.dirtySubject.next({
|
|
357
|
+
epoch: RxSignal.globalEpoch,
|
|
358
|
+
signal: this
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
static fromInitialValue(initialValue, options) {
|
|
362
|
+
return new RxStateSignal(initialValue, options);
|
|
363
|
+
}
|
|
364
|
+
get() {
|
|
365
|
+
const value = this.value.getValue();
|
|
366
|
+
RxSignal.currentContext?.markDependency(this);
|
|
367
|
+
return value;
|
|
368
|
+
}
|
|
369
|
+
validateForEpoch() {}
|
|
370
|
+
dirtyObservable() {
|
|
371
|
+
return this.dirtySubject.asObservable();
|
|
372
|
+
}
|
|
373
|
+
[SYMBOL_OBSERVABLE]() {
|
|
374
|
+
return RxEventStream.fromObservableInput(this.value.asObservable());
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
function haveSameSourceKeys(left, right) {
|
|
378
|
+
if (left.size !== right.size) return false;
|
|
379
|
+
const leftSources = left.keys();
|
|
380
|
+
const rightSources = right.keys();
|
|
381
|
+
while (true) {
|
|
382
|
+
const leftSource = leftSources.next();
|
|
383
|
+
const rightSource = rightSources.next();
|
|
384
|
+
if (leftSource.done || rightSource.done) return leftSource.done === rightSource.done;
|
|
385
|
+
if (leftSource.value !== rightSource.value) return false;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
var RxComputedSignal = class RxComputedSignal extends RxSignal {
|
|
389
|
+
computeFn;
|
|
390
|
+
cacheState = { kind: "empty" };
|
|
391
|
+
sources = /* @__PURE__ */ new Map();
|
|
392
|
+
sourcesSubject = new BehaviorSubject(this.sources);
|
|
393
|
+
validatedEpoch = -1;
|
|
394
|
+
isComputing = false;
|
|
395
|
+
collectingSources = null;
|
|
396
|
+
dirty$;
|
|
397
|
+
constructor(computeFn, options) {
|
|
398
|
+
super(options);
|
|
399
|
+
this.computeFn = computeFn;
|
|
400
|
+
}
|
|
401
|
+
markDependency(signal) {
|
|
402
|
+
const source = signal;
|
|
403
|
+
this.collectingSources?.set(source, source.valueEqVersion);
|
|
404
|
+
}
|
|
405
|
+
cacheStatesEqual(left, right) {
|
|
406
|
+
if (left.kind !== right.kind) return false;
|
|
407
|
+
if (left.kind === "empty" || right.kind === "empty") return false;
|
|
408
|
+
if (left.kind === "value" && right.kind === "value") return this.equals(left.value, right.value);
|
|
409
|
+
if (left.kind === "error" && right.kind === "error") return Object.is(left.error, right.error);
|
|
410
|
+
return false;
|
|
411
|
+
}
|
|
412
|
+
recompute() {
|
|
413
|
+
if (this.isComputing) throw new Error("Circular computed dependency detected");
|
|
414
|
+
const prevContext = RxSignal.currentContext;
|
|
415
|
+
const prevCollectingSources = this.collectingSources;
|
|
416
|
+
const nextSources = /* @__PURE__ */ new Map();
|
|
417
|
+
RxSignal.currentContext = this;
|
|
418
|
+
this.collectingSources = nextSources;
|
|
419
|
+
this.isComputing = true;
|
|
420
|
+
let nextCacheState;
|
|
421
|
+
try {
|
|
422
|
+
nextCacheState = {
|
|
423
|
+
kind: "value",
|
|
424
|
+
value: this.computeFn()
|
|
425
|
+
};
|
|
426
|
+
} catch (error) {
|
|
427
|
+
nextCacheState = {
|
|
428
|
+
kind: "error",
|
|
429
|
+
error
|
|
430
|
+
};
|
|
431
|
+
} finally {
|
|
432
|
+
this.collectingSources = prevCollectingSources;
|
|
433
|
+
RxSignal.currentContext = prevContext;
|
|
434
|
+
this.isComputing = false;
|
|
435
|
+
}
|
|
436
|
+
const sourcesChanged = !haveSameSourceKeys(this.sources, nextSources);
|
|
437
|
+
let cacheStateChanged;
|
|
438
|
+
try {
|
|
439
|
+
cacheStateChanged = !this.cacheStatesEqual(this.cacheState, nextCacheState);
|
|
440
|
+
} catch (error) {
|
|
441
|
+
nextCacheState = {
|
|
442
|
+
kind: "error",
|
|
443
|
+
error
|
|
444
|
+
};
|
|
445
|
+
cacheStateChanged = !this.cacheStatesEqual(this.cacheState, nextCacheState);
|
|
446
|
+
}
|
|
447
|
+
if (cacheStateChanged) this.valueEqVersion += 1;
|
|
448
|
+
this.cacheState = nextCacheState;
|
|
449
|
+
this.sources = nextSources;
|
|
450
|
+
if (sourcesChanged) this.sourcesSubject.next(nextSources);
|
|
451
|
+
if (nextCacheState.kind === "error") throw nextCacheState.error;
|
|
452
|
+
}
|
|
453
|
+
validateForEpoch(epoch) {
|
|
454
|
+
if (this.validatedEpoch === epoch) return;
|
|
455
|
+
try {
|
|
456
|
+
if (this.cacheState.kind === "empty") {
|
|
457
|
+
this.recompute();
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
for (const [source, version] of this.sources) {
|
|
461
|
+
try {
|
|
462
|
+
source.validateForEpoch(epoch);
|
|
463
|
+
} catch {
|
|
464
|
+
this.recompute();
|
|
465
|
+
break;
|
|
466
|
+
}
|
|
467
|
+
if (source.valueEqVersion !== version) {
|
|
468
|
+
this.recompute();
|
|
469
|
+
break;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
} finally {
|
|
473
|
+
this.validatedEpoch = epoch;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
get() {
|
|
477
|
+
this.validateForEpoch(RxSignal.globalEpoch);
|
|
478
|
+
RxSignal.currentContext?.markDependency(this);
|
|
479
|
+
switch (this.cacheState.kind) {
|
|
480
|
+
case "value": return this.cacheState.value;
|
|
481
|
+
case "error": throw this.cacheState.error;
|
|
482
|
+
case "empty": throw new Error("Computed signal has not been initialized");
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
dirtyObservable() {
|
|
486
|
+
if (!this.dirty$) this.dirty$ = defer(() => {
|
|
487
|
+
this.validateForEpoch(RxSignal.globalEpoch);
|
|
488
|
+
return this.sourcesSubject.asObservable().pipe(switchMap((sources) => sources.size === 0 ? EMPTY : merge(...[...sources.keys()].map((source) => source.dirtyObservable())).pipe(distinctUntilChanged((left, right) => left.epoch === right.epoch), map((dirtyDepEvent) => {
|
|
489
|
+
return {
|
|
490
|
+
epoch: dirtyDepEvent.epoch,
|
|
491
|
+
signal: this
|
|
492
|
+
};
|
|
493
|
+
}))));
|
|
494
|
+
}).pipe(share());
|
|
495
|
+
return this.dirty$;
|
|
496
|
+
}
|
|
497
|
+
static computed(fn, options) {
|
|
498
|
+
return new RxComputedSignal(fn, options);
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
//#endregion
|
|
502
|
+
//#region src/rx/command.ts
|
|
503
|
+
/** biome-ignore-all lint/complexity/noBannedTypes: false negative */
|
|
504
|
+
const CommandResponseType = {
|
|
505
|
+
Data: "data",
|
|
506
|
+
Success: "success",
|
|
507
|
+
Error: "error",
|
|
508
|
+
Cancel: "cancel",
|
|
509
|
+
Rejected: "rejected"
|
|
510
|
+
};
|
|
511
|
+
function createCommandCancellationError(command) {
|
|
512
|
+
return new ClientError({
|
|
513
|
+
kind: ClientErrorKind.Cancelled,
|
|
514
|
+
message: "Command was cancelled before completion",
|
|
515
|
+
cause: { command }
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
function createCommandRejectedError(command, reason) {
|
|
519
|
+
return new ClientError({
|
|
520
|
+
kind: ClientErrorKind.Cancelled,
|
|
521
|
+
message: `Command was rejected before execution: ${reason}`,
|
|
522
|
+
cause: {
|
|
523
|
+
command,
|
|
524
|
+
reason
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
function isCommandDataResponse(response) {
|
|
529
|
+
return response.type === CommandResponseType.Data;
|
|
530
|
+
}
|
|
531
|
+
function isCommandTerminalResponse(response) {
|
|
532
|
+
return response.type !== CommandResponseType.Data;
|
|
533
|
+
}
|
|
534
|
+
function dispatchCommand(dispatch, matches) {
|
|
535
|
+
return (source$) => new Observable((subscriber) => {
|
|
536
|
+
const subscription = source$.pipe(filter(matches), takeWhile(isCommandDataResponse, true)).subscribe(subscriber);
|
|
537
|
+
if (subscription.closed) return subscription;
|
|
538
|
+
try {
|
|
539
|
+
dispatch();
|
|
540
|
+
} catch (error) {
|
|
541
|
+
if (!subscription.closed) subscriber.error(error);
|
|
542
|
+
}
|
|
543
|
+
return subscription;
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
function unwrapCommandTerminalResponse(response) {
|
|
547
|
+
switch (response.type) {
|
|
548
|
+
case CommandResponseType.Success: return;
|
|
549
|
+
case CommandResponseType.Error: throw response.error;
|
|
550
|
+
case CommandResponseType.Cancel: throw createCommandCancellationError(response.command);
|
|
551
|
+
case CommandResponseType.Rejected: throw createCommandRejectedError(response.command, response.reason);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
function commandResponseData() {
|
|
555
|
+
return (source$) => source$.pipe(filter((response) => {
|
|
556
|
+
if (isCommandDataResponse(response)) return true;
|
|
557
|
+
unwrapCommandTerminalResponse(response);
|
|
558
|
+
return false;
|
|
559
|
+
}), map((response) => response.data));
|
|
560
|
+
}
|
|
561
|
+
function dispatchCommandLocallyToStream(options) {
|
|
562
|
+
const command = {
|
|
563
|
+
payload: options.payload,
|
|
564
|
+
...options.createCommandExtra?.(options.payload) ?? {}
|
|
565
|
+
};
|
|
566
|
+
return from(options.responseStream).pipe(dispatchCommand(() => {
|
|
567
|
+
options.requestStream.next(command);
|
|
568
|
+
}, (response) => response.command === command));
|
|
569
|
+
}
|
|
570
|
+
function dispatchCommandByIdToStream(options) {
|
|
571
|
+
const areIdsEqual = options.areIdsEqual ?? Object.is;
|
|
572
|
+
const commandId = options.selectCommandId(options.command);
|
|
573
|
+
return from(options.responseStream).pipe(dispatchCommand(() => {
|
|
574
|
+
options.requestStream.next(options.command);
|
|
575
|
+
}, (response) => areIdsEqual(options.selectCommandId(response.command), commandId)));
|
|
576
|
+
}
|
|
577
|
+
async function dispatchCommandLocallyToPromise(options) {
|
|
578
|
+
const [data, terminal] = await lastValueFrom(dispatchCommandLocallyToStream(options).pipe(takeLast(2), toArray()), { defaultValue: [] });
|
|
579
|
+
if (data === void 0) throw new ClientError({
|
|
580
|
+
kind: ClientErrorKind.Configuration,
|
|
581
|
+
message: "the executor configuration of dispatchCommandLocallyToPromise should produce a valid data response."
|
|
582
|
+
});
|
|
583
|
+
if (!isCommandDataResponse(data)) throw new ClientError({
|
|
584
|
+
kind: ClientErrorKind.Configuration,
|
|
585
|
+
message: "the executor configuration of dispatchCommandLocallyToPromise should produce a valid data response."
|
|
586
|
+
});
|
|
587
|
+
if (terminal === void 0) throw new ClientError({
|
|
588
|
+
kind: ClientErrorKind.Internal,
|
|
589
|
+
message: "the execution of dispatchCommandLocallyToPromise must produce a valid terminal response."
|
|
590
|
+
});
|
|
591
|
+
if (!isCommandTerminalResponse(terminal)) throw new ClientError({
|
|
592
|
+
kind: ClientErrorKind.Internal,
|
|
593
|
+
message: "the execution of dispatchCommandLocallyToPromise must produce a valid terminal response."
|
|
594
|
+
});
|
|
595
|
+
unwrapCommandTerminalResponse(terminal);
|
|
596
|
+
return data;
|
|
597
|
+
}
|
|
598
|
+
async function dispatchCommandByIdToPromise(options) {
|
|
599
|
+
const [data, terminal] = await lastValueFrom(dispatchCommandByIdToStream(options).pipe(takeLast(2), toArray()), { defaultValue: [] });
|
|
600
|
+
if (data === void 0) throw new ClientError({
|
|
601
|
+
kind: ClientErrorKind.Configuration,
|
|
602
|
+
message: "the executor configuration of dispatchCommandLocallyToPromise should produce a valid data response."
|
|
603
|
+
});
|
|
604
|
+
if (!isCommandDataResponse(data)) throw new ClientError({
|
|
605
|
+
kind: ClientErrorKind.Configuration,
|
|
606
|
+
message: "the executor configuration of dispatchCommandLocallyToPromise should produce a valid data response."
|
|
607
|
+
});
|
|
608
|
+
if (terminal === void 0) throw new ClientError({
|
|
609
|
+
kind: ClientErrorKind.Internal,
|
|
610
|
+
message: "the execution of dispatchCommandLocallyToPromise must produce a valid terminal response."
|
|
611
|
+
});
|
|
612
|
+
if (!isCommandTerminalResponse(terminal)) throw new ClientError({
|
|
613
|
+
kind: ClientErrorKind.Internal,
|
|
614
|
+
message: "the execution of dispatchCommandLocallyToPromise must produce a valid terminal response."
|
|
615
|
+
});
|
|
616
|
+
unwrapCommandTerminalResponse(terminal);
|
|
617
|
+
return data;
|
|
618
|
+
}
|
|
619
|
+
function createCommandExecutionStream(options) {
|
|
620
|
+
return new Observable((subscriber) => {
|
|
621
|
+
let didEmitTerminal = false;
|
|
622
|
+
const emitTerminal = (response) => {
|
|
623
|
+
if (didEmitTerminal) return;
|
|
624
|
+
didEmitTerminal = true;
|
|
625
|
+
subscriber.next(response);
|
|
626
|
+
subscriber.complete();
|
|
627
|
+
};
|
|
628
|
+
const subscription = from(options.execute(options.command)).subscribe({
|
|
629
|
+
next: (data) => {
|
|
630
|
+
subscriber.next({
|
|
631
|
+
type: CommandResponseType.Data,
|
|
632
|
+
command: options.command,
|
|
633
|
+
data
|
|
634
|
+
});
|
|
635
|
+
},
|
|
636
|
+
error: (error) => {
|
|
637
|
+
emitTerminal({
|
|
638
|
+
type: CommandResponseType.Error,
|
|
639
|
+
command: options.command,
|
|
640
|
+
error
|
|
641
|
+
});
|
|
642
|
+
},
|
|
643
|
+
complete: () => {
|
|
644
|
+
emitTerminal({
|
|
645
|
+
type: CommandResponseType.Success,
|
|
646
|
+
command: options.command
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
});
|
|
650
|
+
return () => {
|
|
651
|
+
const wasTerminalEmitted = didEmitTerminal;
|
|
652
|
+
subscription.unsubscribe();
|
|
653
|
+
options.onStateEnd?.(options.command);
|
|
654
|
+
if (!wasTerminalEmitted) subscriber.next({
|
|
655
|
+
type: CommandResponseType.Cancel,
|
|
656
|
+
command: options.command
|
|
657
|
+
});
|
|
658
|
+
};
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
function switchCommand(execute) {
|
|
662
|
+
return (source$) => new Observable((destination) => {
|
|
663
|
+
let innerSubscription = null;
|
|
664
|
+
let currentCommand = null;
|
|
665
|
+
let isComplete = false;
|
|
666
|
+
const checkComplete = () => {
|
|
667
|
+
if (isComplete && !innerSubscription) destination.complete();
|
|
668
|
+
};
|
|
669
|
+
const sourceSubscription = source$.subscribe({
|
|
670
|
+
next: (command) => {
|
|
671
|
+
const cancelledCommand = currentCommand;
|
|
672
|
+
innerSubscription?.unsubscribe();
|
|
673
|
+
innerSubscription = null;
|
|
674
|
+
currentCommand = null;
|
|
675
|
+
if (cancelledCommand) destination.next({
|
|
676
|
+
type: CommandResponseType.Cancel,
|
|
677
|
+
command: cancelledCommand
|
|
678
|
+
});
|
|
679
|
+
currentCommand = command;
|
|
680
|
+
let didCompleteSynchronously = false;
|
|
681
|
+
const nextInnerSubscription = createCommandExecutionStream({
|
|
682
|
+
command,
|
|
683
|
+
execute
|
|
684
|
+
}).subscribe({
|
|
685
|
+
next: (response) => {
|
|
686
|
+
destination.next(response);
|
|
687
|
+
},
|
|
688
|
+
error: (error) => {
|
|
689
|
+
destination.error(error);
|
|
690
|
+
},
|
|
691
|
+
complete: () => {
|
|
692
|
+
didCompleteSynchronously = true;
|
|
693
|
+
innerSubscription = null;
|
|
694
|
+
currentCommand = null;
|
|
695
|
+
checkComplete();
|
|
696
|
+
}
|
|
697
|
+
});
|
|
698
|
+
if (!didCompleteSynchronously) innerSubscription = nextInnerSubscription;
|
|
699
|
+
},
|
|
700
|
+
error: (error) => {
|
|
701
|
+
destination.error(error);
|
|
702
|
+
},
|
|
703
|
+
complete: () => {
|
|
704
|
+
isComplete = true;
|
|
705
|
+
checkComplete();
|
|
706
|
+
}
|
|
707
|
+
});
|
|
708
|
+
return () => {
|
|
709
|
+
sourceSubscription.unsubscribe();
|
|
710
|
+
innerSubscription?.unsubscribe();
|
|
711
|
+
};
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
function concatCommand(execute) {
|
|
715
|
+
return (source$) => source$.pipe(map((command) => createCommandExecutionStream({
|
|
716
|
+
command,
|
|
717
|
+
execute
|
|
718
|
+
})), concatAll());
|
|
719
|
+
}
|
|
720
|
+
const exhaustCommand = (execute) => (source$) => new Observable((destination) => {
|
|
721
|
+
let innerSubscription = null;
|
|
722
|
+
let isComplete = false;
|
|
723
|
+
const checkComplete = () => {
|
|
724
|
+
if (isComplete && !innerSubscription) destination.complete();
|
|
725
|
+
};
|
|
726
|
+
const sourceSubscription = source$.subscribe({
|
|
727
|
+
next: (command) => {
|
|
728
|
+
if (innerSubscription) {
|
|
729
|
+
destination.next({
|
|
730
|
+
type: CommandResponseType.Rejected,
|
|
731
|
+
command,
|
|
732
|
+
reason: "Pessimistic lock active."
|
|
733
|
+
});
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
let didCompleteSynchronously = false;
|
|
737
|
+
const nextInnerSubscription = createCommandExecutionStream({
|
|
738
|
+
command,
|
|
739
|
+
execute
|
|
740
|
+
}).subscribe({
|
|
741
|
+
next: (response) => {
|
|
742
|
+
destination.next(response);
|
|
743
|
+
},
|
|
744
|
+
error: (error) => {
|
|
745
|
+
destination.error(error);
|
|
746
|
+
},
|
|
747
|
+
complete: () => {
|
|
748
|
+
didCompleteSynchronously = true;
|
|
749
|
+
innerSubscription = null;
|
|
750
|
+
checkComplete();
|
|
751
|
+
}
|
|
752
|
+
});
|
|
753
|
+
if (!didCompleteSynchronously) innerSubscription = nextInnerSubscription;
|
|
754
|
+
},
|
|
755
|
+
error: (error) => {
|
|
756
|
+
destination.error(error);
|
|
757
|
+
},
|
|
758
|
+
complete: () => {
|
|
759
|
+
isComplete = true;
|
|
760
|
+
checkComplete();
|
|
761
|
+
}
|
|
762
|
+
});
|
|
763
|
+
return () => {
|
|
764
|
+
sourceSubscription.unsubscribe();
|
|
765
|
+
innerSubscription?.unsubscribe();
|
|
766
|
+
};
|
|
767
|
+
});
|
|
768
|
+
//#endregion
|
|
769
|
+
//#region src/rx/environment.ts
|
|
770
|
+
function createAsyncSchedulerWithTimestampProvider(time) {
|
|
771
|
+
class TimeTraitAsyncAction extends AsyncAction {
|
|
772
|
+
requestAsyncId(scheduler, _id, delay = 0) {
|
|
773
|
+
return time.setTimeout(() => {
|
|
774
|
+
scheduler.flush(this);
|
|
775
|
+
}, delay);
|
|
776
|
+
}
|
|
777
|
+
recycleAsyncId(_scheduler, id, delay = 0) {
|
|
778
|
+
if (delay != null && this.delay === delay && this.pending === false) return id;
|
|
779
|
+
if (id !== void 0) time.clearTimeout(id);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
return new AsyncScheduler(TimeTraitAsyncAction, time.now);
|
|
783
|
+
}
|
|
784
|
+
//#endregion
|
|
785
|
+
//#region src/cancellation/create.ts
|
|
786
|
+
var CancellationToken = class {
|
|
787
|
+
cancellationState = new BehaviorSubject$1(void 0);
|
|
788
|
+
get isCancellationRequested() {
|
|
789
|
+
return this.cancellationState.getValue()?.isCancelled === true;
|
|
790
|
+
}
|
|
791
|
+
get reason() {
|
|
792
|
+
return this.cancellationState.getValue()?.reason;
|
|
793
|
+
}
|
|
794
|
+
/** @internal — called by `CancellationTokenSource`. */
|
|
795
|
+
_cancel(reason) {
|
|
796
|
+
if (this.isCancellationRequested) return;
|
|
797
|
+
this.cancellationState.next({
|
|
798
|
+
isCancelled: true,
|
|
799
|
+
reason,
|
|
800
|
+
cancellationError: new ClientError({
|
|
801
|
+
kind: ClientErrorKind.Cancelled,
|
|
802
|
+
message: "Operation was cancelled",
|
|
803
|
+
code: "client.cancelled",
|
|
804
|
+
source: ClientErrorSource.Client,
|
|
805
|
+
cause: reason
|
|
806
|
+
})
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
onCancellationRequested(listener) {
|
|
810
|
+
const cancellationState = this.cancellationState.getValue();
|
|
811
|
+
if (cancellationState?.isCancelled) {
|
|
812
|
+
listener({
|
|
813
|
+
cancellationError: cancellationState.cancellationError,
|
|
814
|
+
reason: cancellationState.reason
|
|
815
|
+
});
|
|
816
|
+
return {
|
|
817
|
+
dispose() {},
|
|
818
|
+
[SYMBOL_DISPOSE]() {}
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
const subscription = this.asObservable().subscribe((errorData) => {
|
|
822
|
+
listener(errorData);
|
|
823
|
+
});
|
|
824
|
+
return {
|
|
825
|
+
dispose() {
|
|
826
|
+
subscription.unsubscribe();
|
|
827
|
+
},
|
|
828
|
+
[SYMBOL_DISPOSE]: () => {
|
|
829
|
+
subscription.unsubscribe();
|
|
830
|
+
}
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
throwIfCancellationRequested() {
|
|
834
|
+
const cancellationState = this.cancellationState.getValue();
|
|
835
|
+
if (cancellationState?.cancellationError) throw cancellationState.cancellationError;
|
|
836
|
+
}
|
|
837
|
+
get cancellationError() {
|
|
838
|
+
return this.cancellationState.getValue()?.cancellationError;
|
|
839
|
+
}
|
|
840
|
+
asObservable() {
|
|
841
|
+
return this.cancellationState.pipe(filter((state) => !!state?.isCancelled), take(1), map((state) => ({
|
|
842
|
+
cancellationError: state.cancellationError,
|
|
843
|
+
reason: state.reason
|
|
844
|
+
})));
|
|
845
|
+
}
|
|
846
|
+
[SYMBOL_OBSERVABLE]() {
|
|
847
|
+
return this.asObservable();
|
|
848
|
+
}
|
|
849
|
+
};
|
|
850
|
+
var CancellationTokenSource = class {
|
|
851
|
+
token = new CancellationToken();
|
|
852
|
+
cancel(reason) {
|
|
853
|
+
this.token._cancel(reason);
|
|
854
|
+
}
|
|
855
|
+
};
|
|
856
|
+
/**
|
|
857
|
+
* Create a `CancellationTokenSource` that produces a cooperative cancellation token.
|
|
858
|
+
*/
|
|
859
|
+
function createCancellationTokenSource() {
|
|
860
|
+
return new CancellationTokenSource();
|
|
861
|
+
}
|
|
862
|
+
function isCancellationTokenTrait(obj) {
|
|
863
|
+
return typeof obj === "object" && obj !== null && typeof obj.isCancellationRequested === "boolean" && typeof obj.onCancellationRequested === "function" && typeof obj.throwIfCancellationRequested === "function" && isInteropObservableTrait(obj);
|
|
864
|
+
}
|
|
865
|
+
//#endregion
|
|
866
|
+
//#region src/signals/error.ts
|
|
867
|
+
const ResourceErrorCode = { ValueUnavailable: "resource.value_unavailable" };
|
|
868
|
+
var ResourceError = class ResourceError extends Error {
|
|
869
|
+
name = "ResourceError";
|
|
870
|
+
code;
|
|
871
|
+
status;
|
|
872
|
+
constructor(message, options) {
|
|
873
|
+
super(message);
|
|
874
|
+
this.code = options.code;
|
|
875
|
+
this.status = options.status;
|
|
876
|
+
}
|
|
877
|
+
static valueUnavailable(options) {
|
|
878
|
+
return new ResourceError(`Resource value is not available while status is "${options.status}".`, {
|
|
879
|
+
code: ResourceErrorCode.ValueUnavailable,
|
|
880
|
+
status: options.status
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
};
|
|
884
|
+
//#endregion
|
|
885
|
+
//#region src/signals/types.ts
|
|
886
|
+
const ResourceStatus = {
|
|
887
|
+
Idle: "idle",
|
|
888
|
+
Loading: "loading",
|
|
889
|
+
LoadingError: "loading_error",
|
|
890
|
+
Reloading: "reloading",
|
|
891
|
+
Resolved: "resolved",
|
|
892
|
+
Error: "error"
|
|
893
|
+
};
|
|
894
|
+
//#endregion
|
|
895
|
+
//#region src/signals/resource-snapshot.ts
|
|
896
|
+
async function whenResourceSnapshotValue(snapshotSignal, options) {
|
|
897
|
+
options?.cancellationToken?.throwIfCancellationRequested();
|
|
898
|
+
const staleValueWhenError = options?.staleValueWhenError ?? true;
|
|
899
|
+
const current = snapshotSignal.get();
|
|
900
|
+
if (current.status === ResourceStatus.Resolved || current.status === ResourceStatus.Reloading || current.status === ResourceStatus.Error && staleValueWhenError) return current.value;
|
|
901
|
+
if (current.status === ResourceStatus.LoadingError || current.status === ResourceStatus.Error) throw current.error;
|
|
902
|
+
const snapshot = await firstValueFrom(merge(from(snapshotSignal).pipe(filter((snapshot) => snapshot.status === ResourceStatus.Resolved || snapshot.status === ResourceStatus.Reloading || snapshot.status === ResourceStatus.LoadingError || snapshot.status === ResourceStatus.Error)), (options?.cancellationToken ? from(options.cancellationToken) : NEVER).pipe(map(({ cancellationError }) => {
|
|
903
|
+
throw cancellationError;
|
|
904
|
+
}))));
|
|
905
|
+
if (snapshot.status === ResourceStatus.LoadingError) throw snapshot.error;
|
|
906
|
+
if (snapshot.status === ResourceStatus.Error && !staleValueWhenError) throw snapshot.error;
|
|
907
|
+
return snapshot.value;
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* Flatten nested resource snapshots while tracking each selected snapshot signal.
|
|
911
|
+
* Ancestor reload and error states remain visible whenever the final snapshot has
|
|
912
|
+
* a value; an ancestor error without a final value becomes a loading error.
|
|
913
|
+
*/
|
|
914
|
+
function flattenResourceSnapshot(sourceSnapshot, ...selectors) {
|
|
915
|
+
let snapshot = sourceSnapshot;
|
|
916
|
+
let reloading = false;
|
|
917
|
+
let hasError = false;
|
|
918
|
+
let error;
|
|
919
|
+
for (const select of selectors) switch (snapshot.status) {
|
|
920
|
+
case ResourceStatus.Idle:
|
|
921
|
+
case ResourceStatus.Loading: return hasError ? {
|
|
922
|
+
status: ResourceStatus.LoadingError,
|
|
923
|
+
error
|
|
924
|
+
} : snapshot;
|
|
925
|
+
case ResourceStatus.LoadingError: return hasError ? {
|
|
926
|
+
status: ResourceStatus.LoadingError,
|
|
927
|
+
error
|
|
928
|
+
} : snapshot;
|
|
929
|
+
case ResourceStatus.Reloading:
|
|
930
|
+
reloading = true;
|
|
931
|
+
snapshot = select(snapshot.value).get();
|
|
932
|
+
break;
|
|
933
|
+
case ResourceStatus.Resolved:
|
|
934
|
+
snapshot = select(snapshot.value).get();
|
|
935
|
+
break;
|
|
936
|
+
case ResourceStatus.Error:
|
|
937
|
+
if (!hasError) {
|
|
938
|
+
hasError = true;
|
|
939
|
+
error = snapshot.error;
|
|
940
|
+
}
|
|
941
|
+
snapshot = select(snapshot.value).get();
|
|
942
|
+
}
|
|
943
|
+
switch (snapshot.status) {
|
|
944
|
+
case ResourceStatus.Idle:
|
|
945
|
+
case ResourceStatus.Loading:
|
|
946
|
+
case ResourceStatus.LoadingError: return hasError ? {
|
|
947
|
+
status: ResourceStatus.LoadingError,
|
|
948
|
+
error
|
|
949
|
+
} : snapshot;
|
|
950
|
+
case ResourceStatus.Reloading: return hasError ? {
|
|
951
|
+
status: ResourceStatus.Error,
|
|
952
|
+
value: snapshot.value,
|
|
953
|
+
error
|
|
954
|
+
} : snapshot;
|
|
955
|
+
case ResourceStatus.Resolved: return hasError ? {
|
|
956
|
+
status: ResourceStatus.Error,
|
|
957
|
+
value: snapshot.value,
|
|
958
|
+
error
|
|
959
|
+
} : reloading ? {
|
|
960
|
+
status: ResourceStatus.Reloading,
|
|
961
|
+
value: snapshot.value
|
|
962
|
+
} : snapshot;
|
|
963
|
+
case ResourceStatus.Error: return hasError ? {
|
|
964
|
+
status: ResourceStatus.Error,
|
|
965
|
+
value: snapshot.value,
|
|
966
|
+
error
|
|
967
|
+
} : snapshot;
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
const ResourceSnapshotUpdateKind = {
|
|
971
|
+
Load: "load",
|
|
972
|
+
Resolve: "resolve",
|
|
973
|
+
Fail: "fail",
|
|
974
|
+
FailWithValue: "fail_with_value"
|
|
975
|
+
};
|
|
976
|
+
function reduceResourceSnapshot(previous, update) {
|
|
977
|
+
switch (update.kind) {
|
|
978
|
+
case ResourceSnapshotUpdateKind.Load: return previous.status === ResourceStatus.Reloading || previous.status === ResourceStatus.Resolved || previous.status === ResourceStatus.Error ? {
|
|
979
|
+
status: ResourceStatus.Reloading,
|
|
980
|
+
value: previous.value
|
|
981
|
+
} : { status: ResourceStatus.Loading };
|
|
982
|
+
case ResourceSnapshotUpdateKind.Resolve: return {
|
|
983
|
+
status: ResourceStatus.Resolved,
|
|
984
|
+
value: update.value
|
|
985
|
+
};
|
|
986
|
+
case ResourceSnapshotUpdateKind.Fail: return previous.status === ResourceStatus.Reloading || previous.status === ResourceStatus.Resolved || previous.status === ResourceStatus.Error ? {
|
|
987
|
+
status: ResourceStatus.Error,
|
|
988
|
+
value: previous.value,
|
|
989
|
+
error: update.error
|
|
990
|
+
} : {
|
|
991
|
+
status: ResourceStatus.LoadingError,
|
|
992
|
+
error: update.error
|
|
993
|
+
};
|
|
994
|
+
case ResourceSnapshotUpdateKind.FailWithValue: return {
|
|
995
|
+
status: ResourceStatus.Error,
|
|
996
|
+
value: update.value,
|
|
997
|
+
error: update.error
|
|
998
|
+
};
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
//#endregion
|
|
1002
|
+
//#region src/rx/resource.ts
|
|
1003
|
+
var RxResourceBase = class {
|
|
1004
|
+
value;
|
|
1005
|
+
status;
|
|
1006
|
+
error;
|
|
1007
|
+
isLoading;
|
|
1008
|
+
destroyed = RxStateSignal.fromInitialValue(false);
|
|
1009
|
+
destroyed$ = from(this.destroyed).pipe(filter((value) => value), take(1));
|
|
1010
|
+
constructor() {
|
|
1011
|
+
this.value = RxComputedSignal.computed(() => {
|
|
1012
|
+
const snapshot = this.snapshot.get();
|
|
1013
|
+
if (snapshot.status === ResourceStatus.Resolved || snapshot.status === ResourceStatus.Reloading) return snapshot.value;
|
|
1014
|
+
if (snapshot.status === ResourceStatus.LoadingError || snapshot.status === ResourceStatus.Error) throw snapshot.error;
|
|
1015
|
+
throw ResourceError.valueUnavailable({ status: snapshot.status });
|
|
1016
|
+
});
|
|
1017
|
+
this.status = RxComputedSignal.computed(() => this.snapshot.get().status);
|
|
1018
|
+
this.error = RxComputedSignal.computed(() => {
|
|
1019
|
+
const snapshot = this.snapshot.get();
|
|
1020
|
+
return snapshot.status === ResourceStatus.LoadingError || snapshot.status === ResourceStatus.Error ? snapshot.error : void 0;
|
|
1021
|
+
});
|
|
1022
|
+
this.isLoading = RxComputedSignal.computed(() => {
|
|
1023
|
+
const status = this.snapshot.get().status;
|
|
1024
|
+
return status === ResourceStatus.Loading || status === ResourceStatus.Reloading;
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
hasValue() {
|
|
1028
|
+
const status = this.snapshot.get().status;
|
|
1029
|
+
return status === ResourceStatus.Resolved || status === ResourceStatus.Reloading;
|
|
1030
|
+
}
|
|
1031
|
+
async whenValue(options) {
|
|
1032
|
+
return whenResourceSnapshotValue(this.snapshot, options);
|
|
1033
|
+
}
|
|
1034
|
+
dispose() {
|
|
1035
|
+
this.destroyed.set(true);
|
|
1036
|
+
}
|
|
1037
|
+
[SYMBOL_DISPOSE]() {
|
|
1038
|
+
this.dispose();
|
|
1039
|
+
}
|
|
1040
|
+
[SYMBOL_OBSERVABLE]() {
|
|
1041
|
+
return this.snapshot[SYMBOL_OBSERVABLE]();
|
|
1042
|
+
}
|
|
1043
|
+
};
|
|
1044
|
+
var RxResource = class RxResource extends RxResourceBase {
|
|
1045
|
+
options;
|
|
1046
|
+
snapshot;
|
|
1047
|
+
constructor(options) {
|
|
1048
|
+
super();
|
|
1049
|
+
this.options = options;
|
|
1050
|
+
this.snapshot = RxStateSignal.fromInitialValue({ status: ResourceStatus.Idle });
|
|
1051
|
+
this.destroyed$.subscribe(() => {
|
|
1052
|
+
this.snapshot.set({ status: ResourceStatus.Idle });
|
|
1053
|
+
});
|
|
1054
|
+
this.connect();
|
|
1055
|
+
}
|
|
1056
|
+
connect() {
|
|
1057
|
+
const configuredRequest = this.options.request;
|
|
1058
|
+
const requestObservable = configuredRequest && typeof configuredRequest !== "function" ? from(configuredRequest).pipe(distinctUntilChanged(configuredRequest.equals)) : of(configuredRequest && typeof configuredRequest === "function" ? configuredRequest() : void 0);
|
|
1059
|
+
const requestControlsLoading = configuredRequest !== void 0;
|
|
1060
|
+
requestObservable.pipe(switchMap((request) => {
|
|
1061
|
+
if (requestControlsLoading && request === void 0) return of({ status: ResourceStatus.Idle });
|
|
1062
|
+
const previous = this.snapshot.get();
|
|
1063
|
+
const loadingSnapshot = reduceResourceSnapshot(previous, { kind: ResourceSnapshotUpdateKind.Load });
|
|
1064
|
+
this.snapshot.set(loadingSnapshot);
|
|
1065
|
+
const loadCancellation = createCancellationTokenSource();
|
|
1066
|
+
return from(this.options.stream({
|
|
1067
|
+
request,
|
|
1068
|
+
previous,
|
|
1069
|
+
cancellationToken: loadCancellation.token
|
|
1070
|
+
})).pipe(map((value) => reduceResourceSnapshot(loadingSnapshot, {
|
|
1071
|
+
kind: ResourceSnapshotUpdateKind.Resolve,
|
|
1072
|
+
value
|
|
1073
|
+
})), catchError((error) => of(reduceResourceSnapshot(loadingSnapshot, {
|
|
1074
|
+
kind: ResourceSnapshotUpdateKind.Fail,
|
|
1075
|
+
error
|
|
1076
|
+
}))), finalize(() => loadCancellation.cancel()));
|
|
1077
|
+
}), takeUntil(this.destroyed$)).subscribe((snapshot) => this.snapshot.set(snapshot));
|
|
1078
|
+
}
|
|
1079
|
+
static create(options) {
|
|
1080
|
+
return new RxResource(options);
|
|
1081
|
+
}
|
|
1082
|
+
};
|
|
1083
|
+
var RxSnapshotResource = class extends RxResourceBase {
|
|
1084
|
+
snapshot;
|
|
1085
|
+
constructor(source) {
|
|
1086
|
+
super();
|
|
1087
|
+
this.snapshot = RxComputedSignal.computed(() => this.destroyed.get() ? { status: ResourceStatus.Idle } : source());
|
|
1088
|
+
}
|
|
1089
|
+
};
|
|
1090
|
+
function resourceFromSnapshots(source) {
|
|
1091
|
+
return new RxSnapshotResource(source);
|
|
1092
|
+
}
|
|
1093
|
+
function mapResource(source, mapValue) {
|
|
1094
|
+
return resourceFromSnapshots(() => {
|
|
1095
|
+
const snapshot = source.snapshot.get();
|
|
1096
|
+
switch (snapshot.status) {
|
|
1097
|
+
case ResourceStatus.Idle:
|
|
1098
|
+
case ResourceStatus.Loading:
|
|
1099
|
+
case ResourceStatus.LoadingError: return snapshot;
|
|
1100
|
+
case ResourceStatus.Reloading:
|
|
1101
|
+
case ResourceStatus.Resolved: return {
|
|
1102
|
+
status: snapshot.status,
|
|
1103
|
+
value: mapValue(snapshot.value)
|
|
1104
|
+
};
|
|
1105
|
+
case ResourceStatus.Error: return {
|
|
1106
|
+
status: ResourceStatus.Error,
|
|
1107
|
+
value: mapValue(snapshot.value),
|
|
1108
|
+
error: snapshot.error
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
function rxResource(options) {
|
|
1114
|
+
return new RxResource(options);
|
|
1115
|
+
}
|
|
1116
|
+
//#endregion
|
|
1117
|
+
export { RxEventSubject as A, withDisposableStack as B, exhaustCommand as C, RxStateSignal as D, RxComputedSignal as E, SYMBOL_DISPOSE as F, ErrorPresentationTone as G, ClientError as H, SimpleDisposableStack as I, UserRecovery as K, StdDisposableStack as L, isInteropObservableTrait as M, SYMBOL_ASYNC_DISPOSE as N, RxEventReplaySubject as O, SYMBOL_DISPOSABLE_STACK as P, createDisposableStack as R, dispatchCommandLocallyToStream as S, unwrapCommandTerminalResponse as T, ClientErrorKind as U, CLIENT_ERROR_SPAN_ATTRIBUTE_PROVIDER_ID as V, ClientErrorSource as W, commandResponseData as _, ResourceSnapshotUpdateKind as a, dispatchCommandByIdToStream as b, whenResourceSnapshotValue as c, ResourceErrorCode as d, CancellationToken as f, CommandResponseType as g, createAsyncSchedulerWithTimestampProvider as h, rxResource as i, SYMBOL_OBSERVABLE as j, RxEventStream as k, ResourceStatus as l, isCancellationTokenTrait as m, mapResource as n, flattenResourceSnapshot as o, createCancellationTokenSource as p, resourceFromSnapshots as r, reduceResourceSnapshot as s, RxResource as t, ResourceError as u, concatCommand as v, switchCommand as w, dispatchCommandLocallyToPromise as x, dispatchCommandByIdToPromise as y, injectDisposableStackFrom as z };
|
|
1118
|
+
|
|
1119
|
+
//# sourceMappingURL=rx-DM6Ouv-m.mjs.map
|