@vereda/http 1.0.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 (98) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +604 -0
  3. package/dist/adapters/zod.d.ts +14 -0
  4. package/dist/adapters/zod.d.ts.map +1 -0
  5. package/dist/adapters/zod.js +14 -0
  6. package/dist/adapters/zod.js.map +1 -0
  7. package/dist/core/backoff.d.ts +9 -0
  8. package/dist/core/backoff.d.ts.map +1 -0
  9. package/dist/core/backoff.js +23 -0
  10. package/dist/core/backoff.js.map +1 -0
  11. package/dist/core/client.d.ts +98 -0
  12. package/dist/core/client.d.ts.map +1 -0
  13. package/dist/core/client.js +781 -0
  14. package/dist/core/client.js.map +1 -0
  15. package/dist/core/errors.d.ts +87 -0
  16. package/dist/core/errors.d.ts.map +1 -0
  17. package/dist/core/errors.js +140 -0
  18. package/dist/core/errors.js.map +1 -0
  19. package/dist/core/index.d.ts +15 -0
  20. package/dist/core/index.d.ts.map +1 -0
  21. package/dist/core/index.js +10 -0
  22. package/dist/core/index.js.map +1 -0
  23. package/dist/core/listeners.d.ts +14 -0
  24. package/dist/core/listeners.d.ts.map +1 -0
  25. package/dist/core/listeners.js +27 -0
  26. package/dist/core/listeners.js.map +1 -0
  27. package/dist/core/metrics.d.ts +33 -0
  28. package/dist/core/metrics.d.ts.map +1 -0
  29. package/dist/core/metrics.js +24 -0
  30. package/dist/core/metrics.js.map +1 -0
  31. package/dist/core/nanoid.d.ts +2 -0
  32. package/dist/core/nanoid.d.ts.map +1 -0
  33. package/dist/core/nanoid.js +11 -0
  34. package/dist/core/nanoid.js.map +1 -0
  35. package/dist/core/redact.d.ts +12 -0
  36. package/dist/core/redact.d.ts.map +1 -0
  37. package/dist/core/redact.js +42 -0
  38. package/dist/core/redact.js.map +1 -0
  39. package/dist/core/types.d.ts +261 -0
  40. package/dist/core/types.d.ts.map +1 -0
  41. package/dist/core/types.js +41 -0
  42. package/dist/core/types.js.map +1 -0
  43. package/dist/core/validate.d.ts +19 -0
  44. package/dist/core/validate.d.ts.map +1 -0
  45. package/dist/core/validate.js +135 -0
  46. package/dist/core/validate.js.map +1 -0
  47. package/dist/middleware/index.d.ts +26 -0
  48. package/dist/middleware/index.d.ts.map +1 -0
  49. package/dist/middleware/index.js +55 -0
  50. package/dist/middleware/index.js.map +1 -0
  51. package/dist/queue/bulkhead.d.ts +63 -0
  52. package/dist/queue/bulkhead.d.ts.map +1 -0
  53. package/dist/queue/bulkhead.js +192 -0
  54. package/dist/queue/bulkhead.js.map +1 -0
  55. package/dist/queue/circuit-breaker.d.ts +81 -0
  56. package/dist/queue/circuit-breaker.d.ts.map +1 -0
  57. package/dist/queue/circuit-breaker.js +283 -0
  58. package/dist/queue/circuit-breaker.js.map +1 -0
  59. package/dist/queue/executor.d.ts +67 -0
  60. package/dist/queue/executor.d.ts.map +1 -0
  61. package/dist/queue/executor.js +273 -0
  62. package/dist/queue/executor.js.map +1 -0
  63. package/dist/queue/policy.d.ts +26 -0
  64. package/dist/queue/policy.d.ts.map +1 -0
  65. package/dist/queue/policy.js +37 -0
  66. package/dist/queue/policy.js.map +1 -0
  67. package/dist/queue/retry.d.ts +58 -0
  68. package/dist/queue/retry.d.ts.map +1 -0
  69. package/dist/queue/retry.js +259 -0
  70. package/dist/queue/retry.js.map +1 -0
  71. package/dist/queue/semaphore.d.ts +32 -0
  72. package/dist/queue/semaphore.d.ts.map +1 -0
  73. package/dist/queue/semaphore.js +83 -0
  74. package/dist/queue/semaphore.js.map +1 -0
  75. package/dist/ticket/ticket.d.ts +77 -0
  76. package/dist/ticket/ticket.d.ts.map +1 -0
  77. package/dist/ticket/ticket.js +186 -0
  78. package/dist/ticket/ticket.js.map +1 -0
  79. package/package.json +85 -0
  80. package/src/adapters/zod.ts +16 -0
  81. package/src/core/backoff.ts +26 -0
  82. package/src/core/client.ts +1048 -0
  83. package/src/core/errors.ts +194 -0
  84. package/src/core/index.ts +56 -0
  85. package/src/core/listeners.ts +28 -0
  86. package/src/core/metrics.ts +42 -0
  87. package/src/core/nanoid.ts +11 -0
  88. package/src/core/redact.ts +46 -0
  89. package/src/core/types.ts +306 -0
  90. package/src/core/validate.ts +163 -0
  91. package/src/middleware/index.ts +63 -0
  92. package/src/queue/bulkhead.ts +243 -0
  93. package/src/queue/circuit-breaker.ts +373 -0
  94. package/src/queue/executor.ts +355 -0
  95. package/src/queue/policy.ts +49 -0
  96. package/src/queue/retry.ts +380 -0
  97. package/src/queue/semaphore.ts +91 -0
  98. package/src/ticket/ticket.ts +246 -0
@@ -0,0 +1,259 @@
1
+ import { buildBackoffFn, DEFAULT_MAX_DELAY_MS } from "../core/backoff.js";
2
+ import { CancelledError, CircuitOpenError, DeadlineExceededError, MaxRetriesExceededError, NO_TIMEOUT_CONFIGURED, QueueFullError, RetryableStatusError, TimeoutError, } from "../core/errors.js";
3
+ import { DEFAULT_MAX_RETRIES, isBoundedMs, } from "../core/types.js";
4
+ import { executeRequest } from "./executor.js";
5
+ import { defaultRetryPolicy, shouldRetry } from "./policy.js";
6
+ export async function runRetryLoop(job) {
7
+ const { url, displayUrl, requestOptions, timeoutConfig, retryConfig, deadlineAt, ticket, controller, middleware, bulkhead, semaphore, circuitBreaker, partition, firstError, initialQueuedMs, onRetry, onSuccess, onFailure, onCancelled, onCleanup, fetch: customFetch, } = job;
8
+ const maxRetries = retryConfig.maxRetries ?? DEFAULT_MAX_RETRIES;
9
+ const backoffFn = buildBackoffFn(retryConfig.backoff);
10
+ const backoffCap = retryConfig.backoff && typeof retryConfig.backoff === "object"
11
+ ? (retryConfig.backoff.maxDelayMs ?? DEFAULT_MAX_DELAY_MS)
12
+ : DEFAULT_MAX_DELAY_MS;
13
+ let lastError = firstError;
14
+ let totalAttempts = 1; // first attempt already fired client-side
15
+ let totalQueuedMs = initialQueuedMs; // cumulative bulkhead/semaphore wait across all attempts
16
+ // Attempt 0's error was already vetted (default policy + retryWhen) by
17
+ // client.ts's vetoed() before this loop was ever entered — that's what
18
+ // decided to queue the ticket for retry in the first place. `ctx` is
19
+ // constant across the loop (derived only from requestOptions/retryConfig),
20
+ // so it's built once here and reused both by the loop's gate below and by
21
+ // the final-attempt check after the loop.
22
+ const ctx = {
23
+ method: requestOptions.method ?? "GET",
24
+ headers: requestOptions.headers,
25
+ idempotent: retryConfig.idempotent,
26
+ };
27
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
28
+ if (ticket.isCancelled) {
29
+ onCleanup?.();
30
+ onCancelled?.(totalAttempts, totalQueuedMs);
31
+ controller.markDone({
32
+ success: false,
33
+ error: new CancelledError(),
34
+ });
35
+ return;
36
+ }
37
+ const permit = circuitBreaker.tryAcquire();
38
+ if (!permit) {
39
+ onCleanup?.();
40
+ const error = new CircuitOpenError(partition);
41
+ onFailure?.(error, totalAttempts, totalQueuedMs);
42
+ controller.markDone({ success: false, error });
43
+ return;
44
+ }
45
+ // Every exit from this iteration settles the permit: an outcome below,
46
+ // or release() for the veto/cancel/deadline/queue-full paths (B1).
47
+ try {
48
+ // Consult the default policy + retryWhen for every retry iteration
49
+ // *except the first* (attempt 0): that error is always `firstError`,
50
+ // already vetted by client.ts's vetoed() before this loop was ever
51
+ // entered. Re-checking it here would call a user-provided retryWhen
52
+ // twice for the same failed attempt. From attempt 1 on, `lastError`
53
+ // is a fresh error produced by a retry this loop just ran, so the
54
+ // gate must run — it's what lets retryWhen limit retries to N+1
55
+ // attempts total.
56
+ if (attempt > 0 && !shouldRetry(lastError, attempt, ctx, retryConfig.retryWhen)) {
57
+ onCleanup?.();
58
+ onFailure?.(lastError, totalAttempts, totalQueuedMs);
59
+ controller.markDone({ success: false, error: lastError });
60
+ return;
61
+ }
62
+ // All retries get backoff. Every retry is gated by the policy check above.
63
+ const delayMs = resolveRetryDelay(lastError, backoffFn, attempt, backoffCap);
64
+ onRetry?.(attempt, delayMs, lastError);
65
+ controller.markRetrying(attempt, delayMs);
66
+ try {
67
+ await sleep(delayMs, ticket.signal);
68
+ }
69
+ catch {
70
+ // The deadline timer aborts the ticket signal. Distinguish deadline from
71
+ // user cancellation: deadline only aborts the signal (abortSignal()),
72
+ // while user cancellation sets _cancelled = true via cancel().
73
+ onCleanup?.();
74
+ if (!ticket.isCancelled && isBoundedMs(timeoutConfig.totalMs)) {
75
+ const error = new DeadlineExceededError(displayUrl, timeoutConfig.totalMs);
76
+ onFailure?.(error, totalAttempts, totalQueuedMs);
77
+ controller.markDone({
78
+ success: false,
79
+ error,
80
+ });
81
+ }
82
+ else {
83
+ onCancelled?.(totalAttempts, totalQueuedMs);
84
+ controller.markDone({
85
+ success: false,
86
+ error: new CancelledError(),
87
+ });
88
+ }
89
+ return;
90
+ }
91
+ if (ticket.isCancelled) {
92
+ onCleanup?.();
93
+ onCancelled?.(totalAttempts, totalQueuedMs);
94
+ controller.markDone({
95
+ success: false,
96
+ error: new CancelledError(),
97
+ });
98
+ return;
99
+ }
100
+ totalAttempts++;
101
+ // Per-attempt bulkhead scheduling: each retry acquires its own slot,
102
+ // releases it after execution, so other tickets aren't blocked (#5, D4).
103
+ // The global semaphore is acquired after the partition slot (D4).
104
+ let result;
105
+ try {
106
+ result = await bulkhead.run(() => executeRequest({
107
+ url,
108
+ options: requestOptions,
109
+ timeoutConfig,
110
+ retryConfig,
111
+ deadlineAt,
112
+ displayUrl,
113
+ signal: ticket.signal,
114
+ attempt: attempt + 1,
115
+ ticketId: ticket.id,
116
+ partition: bulkhead.name,
117
+ fetch: customFetch,
118
+ }, middleware), semaphore, (queuedMs) => {
119
+ totalQueuedMs += queuedMs;
120
+ }, ticket.signal);
121
+ }
122
+ catch (err) {
123
+ if (err instanceof CancelledError) {
124
+ // Cancelled (or deadline-aborted) while still queued for a slot or
125
+ // permit (B9): nothing was dispatched, so back out the optimistic
126
+ // totalAttempts++ and let the "cancelled" case settle it.
127
+ totalAttempts--;
128
+ result = { kind: "cancelled" };
129
+ }
130
+ else if (err instanceof QueueFullError) {
131
+ // Queue is at capacity — bulkhead.run() rejected before task()
132
+ // ever ran, so this iteration's optimistic totalAttempts++ above
133
+ // must be backed out; it reports attempts actually dispatched.
134
+ const attemptsMade = totalAttempts - 1;
135
+ onCleanup?.();
136
+ if (ticket.isCancelled) {
137
+ // cancel() may have resolved the ticket directly (ticket.ts),
138
+ // bypassing markDone, while this bulkhead/semaphore acquisition
139
+ // was still pending. Match every other cancellation checkpoint
140
+ // in this loop: notify via onCancelled, not onFailure, so the
141
+ // lifecycle-event stream agrees with the ticket's actual outcome.
142
+ onCancelled?.(attemptsMade, totalQueuedMs);
143
+ }
144
+ else {
145
+ // Emit failure here (not in the client's outer .catch) because
146
+ // totalQueuedMs — the wait accumulated by retries that already
147
+ // ran — only exists in this closure; the outer catch only has
148
+ // the first attempt's queuedMs.
149
+ onFailure?.(err, attemptsMade, totalQueuedMs);
150
+ }
151
+ controller.markDone({ success: false, error: err });
152
+ throw err;
153
+ }
154
+ else {
155
+ throw err;
156
+ }
157
+ }
158
+ switch (result.kind) {
159
+ case "success": {
160
+ permit.success();
161
+ onCleanup?.();
162
+ if (result.result.success) {
163
+ onSuccess?.(result.result.raw.status, totalAttempts, totalQueuedMs);
164
+ }
165
+ controller.markDone(result.result);
166
+ return;
167
+ }
168
+ case "cancelled":
169
+ onCleanup?.();
170
+ if (!ticket.isCancelled && isBoundedMs(timeoutConfig.totalMs)) {
171
+ const error = new DeadlineExceededError(displayUrl, timeoutConfig.totalMs);
172
+ onFailure?.(error, totalAttempts, totalQueuedMs);
173
+ controller.markDone({
174
+ success: false,
175
+ error,
176
+ });
177
+ }
178
+ else {
179
+ onCancelled?.(totalAttempts, totalQueuedMs);
180
+ controller.markDone({
181
+ success: false,
182
+ error: new CancelledError(),
183
+ });
184
+ }
185
+ return;
186
+ case "timeout":
187
+ lastError = new TimeoutError(displayUrl, timeoutConfig.attemptMs ?? NO_TIMEOUT_CONFIGURED);
188
+ permit.failure(lastError);
189
+ break;
190
+ case "error":
191
+ lastError = result.error;
192
+ permit.failure(lastError);
193
+ break;
194
+ }
195
+ }
196
+ finally {
197
+ permit.release();
198
+ }
199
+ }
200
+ if (maxRetries === 0) {
201
+ // Zero retries configured/executed — surface the underlying error raw,
202
+ // never wrapped in MaxRetriesExceededError.
203
+ onCleanup?.();
204
+ onFailure?.(lastError, totalAttempts, totalQueuedMs);
205
+ controller.markDone({ success: false, error: lastError });
206
+ return;
207
+ }
208
+ // All retries exhausted — total attempts = 1 (first) + maxRetries (loop)
209
+ totalAttempts = 1 + maxRetries;
210
+ onCleanup?.();
211
+ // The final attempt's own error was never run through the gate above —
212
+ // the loop only checks the *previous* error at the top of each iteration,
213
+ // and there is no next iteration for this one. If it wouldn't have been
214
+ // retried anyway (e.g. a non-retryable HttpError, or a ValidationError
215
+ // from a failed parse), it's terminal on its own merits: surface it raw,
216
+ // same as a mid-loop veto, instead of wrapping it in
217
+ // MaxRetriesExceededError. Only genuinely transient exhaustion — the
218
+ // final error was itself retryable, retries just ran out — gets wrapped.
219
+ // This intentionally checks the default policy only, not retryWhen:
220
+ // retryWhen is never consulted for an attempt that has no retries left.
221
+ if (!defaultRetryPolicy(lastError, maxRetries, ctx)) {
222
+ onFailure?.(lastError, totalAttempts, totalQueuedMs);
223
+ controller.markDone({ success: false, error: lastError });
224
+ return;
225
+ }
226
+ const exhaustedError = new MaxRetriesExceededError(totalAttempts, lastError);
227
+ onFailure?.(exhaustedError, totalAttempts, totalQueuedMs);
228
+ controller.markDone({
229
+ success: false,
230
+ error: exhaustedError,
231
+ });
232
+ }
233
+ function sleep(ms, signal) {
234
+ return new Promise((resolve, reject) => {
235
+ if (signal?.aborted) {
236
+ reject(signal.reason ?? new DOMException("The operation was aborted", "AbortError"));
237
+ return;
238
+ }
239
+ const timer = setTimeout(() => {
240
+ signal?.removeEventListener("abort", onAbort);
241
+ resolve();
242
+ }, ms);
243
+ const onAbort = () => {
244
+ clearTimeout(timer);
245
+ reject(signal?.reason ?? new DOMException("The operation was aborted", "AbortError"));
246
+ };
247
+ signal?.addEventListener("abort", onAbort, { once: true });
248
+ });
249
+ }
250
+ /** Resolve the delay before a retry (decision D3): a Retry-After-derived
251
+ * `retryAfterMs` wins when present (capped at `cap`, no jitter), otherwise
252
+ * the configured backoff drives the delay. */
253
+ function resolveRetryDelay(lastError, backoffFn, attempt, cap) {
254
+ if (lastError instanceof RetryableStatusError && lastError.retryAfterMs !== undefined) {
255
+ return Math.min(lastError.retryAfterMs, cap);
256
+ }
257
+ return backoffFn(attempt);
258
+ }
259
+ //# sourceMappingURL=retry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry.js","sourceRoot":"","sources":["../../src/queue/retry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAE1E,OAAO,EACN,cAAc,EACd,gBAAgB,EAChB,qBAAqB,EACrB,uBAAuB,EACvB,qBAAqB,EACrB,cAAc,EACd,oBAAoB,EACpB,YAAY,GACZ,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAEN,mBAAmB,EACnB,WAAW,GAIX,MAAM,kBAAkB,CAAC;AAI1B,OAAO,EAAE,cAAc,EAAqB,MAAM,eAAe,CAAC;AAClE,OAAO,EAAE,kBAAkB,EAA2B,WAAW,EAAE,MAAM,aAAa,CAAC;AAqDvF,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,GAAoB;IACtD,MAAM,EACL,GAAG,EACH,UAAU,EACV,cAAc,EACd,aAAa,EACb,WAAW,EACX,UAAU,EACV,MAAM,EACN,UAAU,EACV,UAAU,EACV,QAAQ,EACR,SAAS,EACT,cAAc,EACd,SAAS,EACT,UAAU,EACV,eAAe,EACf,OAAO,EACP,SAAS,EACT,SAAS,EACT,WAAW,EACX,SAAS,EACT,KAAK,EAAE,WAAW,GAClB,GAAG,GAAG,CAAC;IAER,MAAM,UAAU,GAAG,WAAW,CAAC,UAAU,IAAI,mBAAmB,CAAC;IACjE,MAAM,SAAS,GAAG,cAAc,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IACtD,MAAM,UAAU,GACf,WAAW,CAAC,OAAO,IAAI,OAAO,WAAW,CAAC,OAAO,KAAK,QAAQ;QAC7D,CAAC,CAAC,CAAE,WAAW,CAAC,OAA0B,CAAC,UAAU,IAAI,oBAAoB,CAAC;QAC9E,CAAC,CAAC,oBAAoB,CAAC;IAEzB,IAAI,SAAS,GAAa,UAAU,CAAC;IACrC,IAAI,aAAa,GAAG,CAAC,CAAC,CAAC,0CAA0C;IACjE,IAAI,aAAa,GAAG,eAAe,CAAC,CAAC,yDAAyD;IAE9F,uEAAuE;IACvE,uEAAuE;IACvE,qEAAqE;IACrE,2EAA2E;IAC3E,0EAA0E;IAC1E,0CAA0C;IAC1C,MAAM,GAAG,GAAuB;QAC/B,MAAM,EAAE,cAAc,CAAC,MAAM,IAAI,KAAK;QACtC,OAAO,EAAE,cAAc,CAAC,OAAO;QAC/B,UAAU,EAAE,WAAW,CAAC,UAAU;KAClC,CAAC;IAEF,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,UAAU,EAAE,OAAO,EAAE,EAAE,CAAC;QACvD,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;YACxB,SAAS,EAAE,EAAE,CAAC;YACd,WAAW,EAAE,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;YAC5C,UAAU,CAAC,QAAQ,CAAC;gBACnB,OAAO,EAAE,KAAK;gBACd,KAAK,EAAE,IAAI,cAAc,EAAE;aAClB,CAAC,CAAC;YACZ,OAAO;QACR,CAAC;QAED,MAAM,MAAM,GAAG,cAAc,CAAC,UAAU,EAAE,CAAC;QAC3C,IAAI,CAAC,MAAM,EAAE,CAAC;YACb,SAAS,EAAE,EAAE,CAAC;YACd,MAAM,KAAK,GAAG,IAAI,gBAAgB,CAAC,SAAS,CAAC,CAAC;YAC9C,SAAS,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,aAAa,CAAC,CAAC;YACjD,UAAU,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAW,CAAC,CAAC;YACxD,OAAO;QACR,CAAC;QAED,uEAAuE;QACvE,mEAAmE;QACnE,IAAI,CAAC;YACJ,mEAAmE;YACnE,qEAAqE;YACrE,mEAAmE;YACnE,oEAAoE;YACpE,oEAAoE;YACpE,kEAAkE;YAClE,gEAAgE;YAChE,kBAAkB;YAClB,IAAI,OAAO,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,SAAS,EAAE,OAAO,EAAE,GAAG,EAAE,WAAW,CAAC,SAAS,CAAC,EAAE,CAAC;gBACjF,SAAS,EAAE,EAAE,CAAC;gBACd,SAAS,EAAE,CAAC,SAAS,EAAE,aAAa,EAAE,aAAa,CAAC,CAAC;gBACrD,UAAU,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAW,CAAC,CAAC;gBACnE,OAAO;YACR,CAAC;YAED,2EAA2E;YAC3E,MAAM,OAAO,GAAG,iBAAiB,CAAC,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC;YAC7E,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC;YACvC,UAAU,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAE1C,IAAI,CAAC;gBACJ,MAAM,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;YACrC,CAAC;YAAC,MAAM,CAAC;gBACR,yEAAyE;gBACzE,sEAAsE;gBACtE,+DAA+D;gBAC/D,SAAS,EAAE,EAAE,CAAC;gBACd,IAAI,CAAC,MAAM,CAAC,WAAW,IAAI,WAAW,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC;oBAC/D,MAAM,KAAK,GAAG,IAAI,qBAAqB,CAAC,UAAU,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;oBAC3E,SAAS,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,aAAa,CAAC,CAAC;oBACjD,UAAU,CAAC,QAAQ,CAAC;wBACnB,OAAO,EAAE,KAAK;wBACd,KAAK;qBACI,CAAC,CAAC;gBACb,CAAC;qBAAM,CAAC;oBACP,WAAW,EAAE,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;oBAC5C,UAAU,CAAC,QAAQ,CAAC;wBACnB,OAAO,EAAE,KAAK;wBACd,KAAK,EAAE,IAAI,cAAc,EAAE;qBAClB,CAAC,CAAC;gBACb,CAAC;gBACD,OAAO;YACR,CAAC;YAED,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;gBACxB,SAAS,EAAE,EAAE,CAAC;gBACd,WAAW,EAAE,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;gBAC5C,UAAU,CAAC,QAAQ,CAAC;oBACnB,OAAO,EAAE,KAAK;oBACd,KAAK,EAAE,IAAI,cAAc,EAAE;iBAClB,CAAC,CAAC;gBACZ,OAAO;YACR,CAAC;YAED,aAAa,EAAE,CAAC;YAEhB,qEAAqE;YACrE,yEAAyE;YACzE,kEAAkE;YAClE,IAAI,MAAkD,CAAC;YACvD,IAAI,CAAC;gBACJ,MAAM,GAAG,MAAM,QAAQ,CAAC,GAAG,CAC1B,GAAG,EAAE,CACJ,cAAc,CACb;oBACC,GAAG;oBACH,OAAO,EAAE,cAAc;oBACvB,aAAa;oBACb,WAAW;oBACX,UAAU;oBACV,UAAU;oBACV,MAAM,EAAE,MAAM,CAAC,MAAM;oBACrB,OAAO,EAAE,OAAO,GAAG,CAAC;oBACpB,QAAQ,EAAE,MAAM,CAAC,EAAE;oBACnB,SAAS,EAAE,QAAQ,CAAC,IAAI;oBACxB,KAAK,EAAE,WAAW;iBAClB,EACD,UAAU,CACV,EACF,SAAS,EACT,CAAC,QAAQ,EAAE,EAAE;oBACZ,aAAa,IAAI,QAAQ,CAAC;gBAC3B,CAAC,EACD,MAAM,CAAC,MAAM,CACb,CAAC;YACH,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACd,IAAI,GAAG,YAAY,cAAc,EAAE,CAAC;oBACnC,mEAAmE;oBACnE,kEAAkE;oBAClE,0DAA0D;oBAC1D,aAAa,EAAE,CAAC;oBAChB,MAAM,GAAG,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;gBAChC,CAAC;qBAAM,IAAI,GAAG,YAAY,cAAc,EAAE,CAAC;oBAC1C,+DAA+D;oBAC/D,iEAAiE;oBACjE,+DAA+D;oBAC/D,MAAM,YAAY,GAAG,aAAa,GAAG,CAAC,CAAC;oBACvC,SAAS,EAAE,EAAE,CAAC;oBACd,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;wBACxB,8DAA8D;wBAC9D,gEAAgE;wBAChE,+DAA+D;wBAC/D,8DAA8D;wBAC9D,kEAAkE;wBAClE,WAAW,EAAE,CAAC,YAAY,EAAE,aAAa,CAAC,CAAC;oBAC5C,CAAC;yBAAM,CAAC;wBACP,+DAA+D;wBAC/D,+DAA+D;wBAC/D,8DAA8D;wBAC9D,gCAAgC;wBAChC,SAAS,EAAE,CAAC,GAAG,EAAE,YAAY,EAAE,aAAa,CAAC,CAAC;oBAC/C,CAAC;oBACD,UAAU,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAW,CAAC,CAAC;oBAC7D,MAAM,GAAG,CAAC;gBACX,CAAC;qBAAM,CAAC;oBACP,MAAM,GAAG,CAAC;gBACX,CAAC;YACF,CAAC;YAED,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;gBACrB,KAAK,SAAS,CAAC,CAAC,CAAC;oBAChB,MAAM,CAAC,OAAO,EAAE,CAAC;oBACjB,SAAS,EAAE,EAAE,CAAC;oBACd,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;wBAC3B,SAAS,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,aAAa,EAAE,aAAa,CAAC,CAAC;oBACrE,CAAC;oBACD,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;oBACnC,OAAO;gBACR,CAAC;gBAED,KAAK,WAAW;oBACf,SAAS,EAAE,EAAE,CAAC;oBACd,IAAI,CAAC,MAAM,CAAC,WAAW,IAAI,WAAW,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC;wBAC/D,MAAM,KAAK,GAAG,IAAI,qBAAqB,CAAC,UAAU,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;wBAC3E,SAAS,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,aAAa,CAAC,CAAC;wBACjD,UAAU,CAAC,QAAQ,CAAC;4BACnB,OAAO,EAAE,KAAK;4BACd,KAAK;yBACI,CAAC,CAAC;oBACb,CAAC;yBAAM,CAAC;wBACP,WAAW,EAAE,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;wBAC5C,UAAU,CAAC,QAAQ,CAAC;4BACnB,OAAO,EAAE,KAAK;4BACd,KAAK,EAAE,IAAI,cAAc,EAAE;yBAClB,CAAC,CAAC;oBACb,CAAC;oBACD,OAAO;gBAER,KAAK,SAAS;oBACb,SAAS,GAAG,IAAI,YAAY,CAAC,UAAU,EAAE,aAAa,CAAC,SAAS,IAAI,qBAAqB,CAAC,CAAC;oBAC3F,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;oBAC1B,MAAM;gBAEP,KAAK,OAAO;oBACX,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC;oBACzB,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;oBAC1B,MAAM;YACR,CAAC;QACF,CAAC;gBAAS,CAAC;YACV,MAAM,CAAC,OAAO,EAAE,CAAC;QAClB,CAAC;IACF,CAAC;IAED,IAAI,UAAU,KAAK,CAAC,EAAE,CAAC;QACtB,uEAAuE;QACvE,4CAA4C;QAC5C,SAAS,EAAE,EAAE,CAAC;QACd,SAAS,EAAE,CAAC,SAAS,EAAE,aAAa,EAAE,aAAa,CAAC,CAAC;QACrD,UAAU,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAW,CAAC,CAAC;QACnE,OAAO;IACR,CAAC;IAED,yEAAyE;IACzE,aAAa,GAAG,CAAC,GAAG,UAAU,CAAC;IAC/B,SAAS,EAAE,EAAE,CAAC;IAEd,uEAAuE;IACvE,0EAA0E;IAC1E,wEAAwE;IACxE,uEAAuE;IACvE,yEAAyE;IACzE,qDAAqD;IACrD,qEAAqE;IACrE,yEAAyE;IACzE,oEAAoE;IACpE,wEAAwE;IACxE,IAAI,CAAC,kBAAkB,CAAC,SAAS,EAAE,UAAU,EAAE,GAAG,CAAC,EAAE,CAAC;QACrD,SAAS,EAAE,CAAC,SAAS,EAAE,aAAa,EAAE,aAAa,CAAC,CAAC;QACrD,UAAU,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAW,CAAC,CAAC;QACnE,OAAO;IACR,CAAC;IAED,MAAM,cAAc,GAAG,IAAI,uBAAuB,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;IAC7E,SAAS,EAAE,CAAC,cAAc,EAAE,aAAa,EAAE,aAAa,CAAC,CAAC;IAC1D,UAAU,CAAC,QAAQ,CAAC;QACnB,OAAO,EAAE,KAAK;QACd,KAAK,EAAE,cAAc;KACZ,CAAC,CAAC;AACb,CAAC;AAED,SAAS,KAAK,CAAC,EAAU,EAAE,MAAoB;IAC9C,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACtC,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;YACrB,MAAM,CAAC,MAAM,CAAC,MAAM,IAAI,IAAI,YAAY,CAAC,2BAA2B,EAAE,YAAY,CAAC,CAAC,CAAC;YACrF,OAAO;QACR,CAAC;QACD,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC7B,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC9C,OAAO,EAAE,CAAC;QACX,CAAC,EAAE,EAAE,CAAC,CAAC;QACP,MAAM,OAAO,GAAG,GAAG,EAAE;YACpB,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,MAAM,CAAC,MAAM,EAAE,MAAM,IAAI,IAAI,YAAY,CAAC,2BAA2B,EAAE,YAAY,CAAC,CAAC,CAAC;QACvF,CAAC,CAAC;QACF,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;AACJ,CAAC;AAED;;+CAE+C;AAC/C,SAAS,iBAAiB,CACzB,SAAmB,EACnB,SAAsC,EACtC,OAAe,EACf,GAAW;IAEX,IAAI,SAAS,YAAY,oBAAoB,IAAI,SAAS,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;QACvF,OAAO,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,YAAY,EAAE,GAAG,CAAC,CAAC;IAC9C,CAAC;IACD,OAAO,SAAS,CAAC,OAAO,CAAC,CAAC;AAC3B,CAAC"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * A counting semaphore that limits total concurrent executions across all
3
+ * partitions (the global concurrency cap — decision D1, see
4
+ * `ClientConfig.concurrency` / `DEFAULT_GLOBAL_CONCURRENCY` for the default).
5
+ *
6
+ * Each `acquire()` returns a `release` callback. When no permit is available
7
+ * the caller is queued (up to `maxQueueSize`, see `ClientConfig.maxQueueSize` /
8
+ * `DEFAULT_GLOBAL_QUEUE_SIZE`) and resolved when a permit is released.
9
+ * Exceeding the queue limit rejects immediately with QueueFullError.
10
+ */
11
+ export declare class Semaphore {
12
+ private available;
13
+ private readonly waitQueue;
14
+ private readonly maxQueueSize;
15
+ constructor(permits: number, maxQueueSize?: number);
16
+ /** Number of callers currently waiting for a permit. */
17
+ get queueLength(): number;
18
+ /** Number of permits currently free (not held by an in-flight request). */
19
+ get availablePermits(): number;
20
+ /** Acquire a permit. Resolves to a `release` function when granted;
21
+ * calling `release` more than once is a no-op.
22
+ *
23
+ * If `signal` aborts while the caller is still queued, the waiter is
24
+ * removed from the queue — freeing its place for live callers — and the
25
+ * promise rejects with `CancelledError` (B9). An already-aborted signal
26
+ * rejects immediately without taking a permit. */
27
+ acquire(signal?: AbortSignal): Promise<() => void>;
28
+ private releaseOnce;
29
+ private release;
30
+ private _drain;
31
+ }
32
+ //# sourceMappingURL=semaphore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"semaphore.d.ts","sourceRoot":"","sources":["../../src/queue/semaphore.ts"],"names":[],"mappings":"AAGA;;;;;;;;;GASG;AACH,qBAAa,SAAS;IACrB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAyB;IACnD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;gBAE1B,OAAO,EAAE,MAAM,EAAE,YAAY,SAA4B;IAKrE,wDAAwD;IACxD,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,2EAA2E;IAC3E,IAAI,gBAAgB,IAAI,MAAM,CAE7B;IAED;;;;;;uDAMmD;IACnD,OAAO,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC;IA8BlD,OAAO,CAAC,WAAW;IASnB,OAAO,CAAC,OAAO;IAKf,OAAO,CAAC,MAAM;CAMd"}
@@ -0,0 +1,83 @@
1
+ import { CancelledError, QueueFullError } from "../core/errors.js";
2
+ import { DEFAULT_GLOBAL_QUEUE_SIZE } from "../core/types.js";
3
+ /**
4
+ * A counting semaphore that limits total concurrent executions across all
5
+ * partitions (the global concurrency cap — decision D1, see
6
+ * `ClientConfig.concurrency` / `DEFAULT_GLOBAL_CONCURRENCY` for the default).
7
+ *
8
+ * Each `acquire()` returns a `release` callback. When no permit is available
9
+ * the caller is queued (up to `maxQueueSize`, see `ClientConfig.maxQueueSize` /
10
+ * `DEFAULT_GLOBAL_QUEUE_SIZE`) and resolved when a permit is released.
11
+ * Exceeding the queue limit rejects immediately with QueueFullError.
12
+ */
13
+ export class Semaphore {
14
+ available;
15
+ waitQueue = [];
16
+ maxQueueSize;
17
+ constructor(permits, maxQueueSize = DEFAULT_GLOBAL_QUEUE_SIZE) {
18
+ this.available = permits;
19
+ this.maxQueueSize = maxQueueSize;
20
+ }
21
+ /** Number of callers currently waiting for a permit. */
22
+ get queueLength() {
23
+ return this.waitQueue.length;
24
+ }
25
+ /** Number of permits currently free (not held by an in-flight request). */
26
+ get availablePermits() {
27
+ return this.available;
28
+ }
29
+ /** Acquire a permit. Resolves to a `release` function when granted;
30
+ * calling `release` more than once is a no-op.
31
+ *
32
+ * If `signal` aborts while the caller is still queued, the waiter is
33
+ * removed from the queue — freeing its place for live callers — and the
34
+ * promise rejects with `CancelledError` (B9). An already-aborted signal
35
+ * rejects immediately without taking a permit. */
36
+ acquire(signal) {
37
+ if (signal?.aborted) {
38
+ return Promise.reject(new CancelledError());
39
+ }
40
+ if (this.available > 0) {
41
+ this.available--;
42
+ return Promise.resolve(this.releaseOnce());
43
+ }
44
+ if (this.waitQueue.length >= this.maxQueueSize) {
45
+ return Promise.reject(new QueueFullError("global", this.waitQueue.length, this.maxQueueSize));
46
+ }
47
+ return new Promise((resolve, reject) => {
48
+ const onAbort = () => {
49
+ const idx = this.waitQueue.indexOf(grant);
50
+ if (idx !== -1)
51
+ this.waitQueue.splice(idx, 1);
52
+ reject(new CancelledError());
53
+ };
54
+ const grant = () => {
55
+ signal?.removeEventListener("abort", onAbort);
56
+ this.available--;
57
+ resolve(this.releaseOnce());
58
+ };
59
+ signal?.addEventListener("abort", onAbort, { once: true });
60
+ this.waitQueue.push(grant);
61
+ });
62
+ }
63
+ releaseOnce() {
64
+ let released = false;
65
+ return () => {
66
+ if (released)
67
+ return;
68
+ released = true;
69
+ this.release();
70
+ };
71
+ }
72
+ release() {
73
+ this.available++;
74
+ this._drain();
75
+ }
76
+ _drain() {
77
+ while (this.available > 0 && this.waitQueue.length > 0) {
78
+ const next = this.waitQueue.shift();
79
+ next();
80
+ }
81
+ }
82
+ }
83
+ //# sourceMappingURL=semaphore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"semaphore.js","sourceRoot":"","sources":["../../src/queue/semaphore.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,77 @@
1
+ import type { AppError } from "../core/errors.ts";
2
+ import type { Result } from "../core/types.ts";
3
+ export type TicketStatus = {
4
+ state: "pending";
5
+ } | {
6
+ state: "queued";
7
+ } | {
8
+ state: "retrying";
9
+ attempt: number;
10
+ } | {
11
+ state: "done";
12
+ result: Result<unknown>;
13
+ } | {
14
+ state: "cancelled";
15
+ };
16
+ export type TicketUpdate = {
17
+ type: "queued";
18
+ } | {
19
+ type: "retrying";
20
+ attempt: number;
21
+ delayMs: number;
22
+ } | {
23
+ type: "done";
24
+ result: Result<unknown>;
25
+ } | {
26
+ type: "cancelled";
27
+ };
28
+ export interface TicketController<T> {
29
+ markQueued(): void;
30
+ markRetrying(attempt: number, delayMs: number): void;
31
+ markDone(result: Result<T>): void;
32
+ /** @internal — abort the signal without resolving the ticket. Used by the
33
+ * deadline timer so the retry loop can resolve with DeadlineExceededError
34
+ * instead of CancelledError. */
35
+ abortSignal(): void;
36
+ }
37
+ export declare class Ticket<T> {
38
+ readonly id: string;
39
+ private readonly emitter;
40
+ private _status;
41
+ private _cancelled;
42
+ private _abortController;
43
+ private _resolve;
44
+ private _promise;
45
+ /** @internal — use `createTicket()` to obtain a ticket and its controller. */
46
+ constructor(id: string);
47
+ get status(): TicketStatus;
48
+ /** Validate and apply a state transition. Returns false (and leaves the
49
+ * current state untouched) when `next` is not reachable from the current
50
+ * state, so illegal transitions are ignored rather than corrupting state. */
51
+ private applyTransition;
52
+ get signal(): AbortSignal;
53
+ on(event: "done", listener: (result: Result<T>) => void): this;
54
+ on(event: "error", listener: (error: AppError) => void): this;
55
+ on(event: "update", listener: (update: TicketUpdate) => void): this;
56
+ off(event: "done", listener: (result: Result<T>) => void): this;
57
+ off(event: "error", listener: (error: AppError) => void): this;
58
+ off(event: "update", listener: (update: TicketUpdate) => void): this;
59
+ cancel(): void;
60
+ toPromise(): Promise<Result<T>>;
61
+ subscribe(): AsyncGenerator<TicketUpdate>;
62
+ get isCancelled(): boolean;
63
+ /** True once this ticket has reached a terminal state by any path — a
64
+ * normal `done` transition or `cancel()` (which resolves the ticket
65
+ * directly, bypassing `done`). A terminal-event emitter must check this
66
+ * before firing, or a result racing a concurrent cancel() can emit a
67
+ * second, contradictory terminal event for the same ticket. */
68
+ get isSettled(): boolean;
69
+ private markQueued;
70
+ private markRetrying;
71
+ private markDone;
72
+ }
73
+ export declare function createTicket<T>(id: string): {
74
+ ticket: Ticket<T>;
75
+ controller: TicketController<T>;
76
+ };
77
+ //# sourceMappingURL=ticket.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ticket.d.ts","sourceRoot":"","sources":["../../src/ticket/ticket.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAGlD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE/C,MAAM,MAAM,YAAY,GACrB;IAAE,KAAK,EAAE,SAAS,CAAA;CAAE,GACpB;IAAE,KAAK,EAAE,QAAQ,CAAA;CAAE,GACnB;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACtC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,CAAA;CAAE,GAC1C;IAAE,KAAK,EAAE,WAAW,CAAA;CAAE,CAAC;AAE1B,MAAM,MAAM,YAAY,GACrB;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAClB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACtD;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,CAAA;CAAE,GACzC;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC;AAuBzB,MAAM,WAAW,gBAAgB,CAAC,CAAC;IAClC,UAAU,IAAI,IAAI,CAAC;IACnB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACrD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAClC;;qCAEiC;IACjC,WAAW,IAAI,IAAI,CAAC;CACpB;AAMD,qBAAa,MAAM,CAAC,CAAC;IACpB,SAAgB,EAAE,EAAE,MAAM,CAAC;IAE3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;IAC9C,OAAO,CAAC,OAAO,CAAsC;IACrD,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,gBAAgB,CAAyB;IACjD,OAAO,CAAC,QAAQ,CAA+B;IAC/C,OAAO,CAAC,QAAQ,CAAqB;IAErC,8EAA8E;gBAClE,EAAE,EAAE,MAAM;IAStB,IAAI,MAAM,IAAI,YAAY,CAEzB;IAED;;kFAE8E;IAC9E,OAAO,CAAC,eAAe;IAQvB,IAAI,MAAM,IAAI,WAAW,CAExB;IAID,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,IAAI;IAC9D,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,GAAG,IAAI;IAC7D,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,GAAG,IAAI;IAQnE,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,IAAI;IAC/D,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,GAAG,IAAI;IAC9D,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,GAAG,IAAI;IAUpE,MAAM,IAAI,IAAI;IAad,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAIxB,SAAS,IAAI,cAAc,CAAC,YAAY,CAAC;IA0ChD,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED;;;;oEAIgE;IAChE,IAAI,SAAS,IAAI,OAAO,CAEvB;IAKD,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,YAAY;IAUpB,OAAO,CAAC,QAAQ;CAShB;AAMD,wBAAgB,YAAY,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,GAAG;IAC5C,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;IAClB,UAAU,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC;CAChC,CAmBA"}