adaptive-concurrency 0.3.2 → 0.3.4

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 (47) hide show
  1. package/dist/LimitAllotment.d.ts +7 -1
  2. package/dist/LimitAllotment.d.ts.map +1 -1
  3. package/dist/Limiter.d.ts +15 -21
  4. package/dist/Limiter.d.ts.map +1 -1
  5. package/dist/Limiter.js +99 -56
  6. package/dist/RunResult.d.ts +12 -6
  7. package/dist/RunResult.d.ts.map +1 -1
  8. package/dist/RunResult.js +10 -3
  9. package/dist/index.d.ts +3 -2
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +4 -3
  12. package/dist/limiter/acquire-strategies/PartitionedStrategy.d.ts +34 -5
  13. package/dist/limiter/acquire-strategies/PartitionedStrategy.d.ts.map +1 -1
  14. package/dist/limiter/acquire-strategies/PartitionedStrategy.js +34 -9
  15. package/dist/limiter/acquire-strategies/SemaphoreStrategy.d.ts +13 -0
  16. package/dist/limiter/acquire-strategies/SemaphoreStrategy.d.ts.map +1 -0
  17. package/dist/limiter/acquire-strategies/SemaphoreStrategy.js +23 -0
  18. package/dist/limiter/allocation-unavailable-strategies/BlockingBacklogRejection.d.ts +36 -0
  19. package/dist/limiter/allocation-unavailable-strategies/BlockingBacklogRejection.d.ts.map +1 -0
  20. package/dist/limiter/allocation-unavailable-strategies/BlockingBacklogRejection.js +117 -0
  21. package/dist/limiter/allocation-unavailable-strategies/DelayedThenBlockingRejection.d.ts +2 -1
  22. package/dist/limiter/allocation-unavailable-strategies/DelayedThenBlockingRejection.d.ts.map +1 -1
  23. package/dist/limiter/allocation-unavailable-strategies/DelayedThenBlockingRejection.js +10 -2
  24. package/dist/limiter/allocation-unavailable-strategies/FifoBlockingRejection.d.ts +19 -15
  25. package/dist/limiter/allocation-unavailable-strategies/FifoBlockingRejection.d.ts.map +1 -1
  26. package/dist/limiter/allocation-unavailable-strategies/FifoBlockingRejection.js +13 -61
  27. package/dist/limiter/allocation-unavailable-strategies/LifoBlockingRejection.d.ts +4 -8
  28. package/dist/limiter/allocation-unavailable-strategies/LifoBlockingRejection.d.ts.map +1 -1
  29. package/dist/limiter/allocation-unavailable-strategies/LifoBlockingRejection.js +13 -49
  30. package/dist/limiter/factories/makeBlockingLimiter.d.ts +3 -1
  31. package/dist/limiter/factories/makeBlockingLimiter.d.ts.map +1 -1
  32. package/dist/limiter/factories/makeBlockingLimiter.js +3 -2
  33. package/dist/limiter/factories/makeLifoBlockingLimiter.d.ts +1 -1
  34. package/dist/limiter/factories/makeLifoBlockingLimiter.d.ts.map +1 -1
  35. package/dist/limiter/factories/makeLifoBlockingLimiter.js +1 -1
  36. package/dist/limiter/factories/makePartitionedBlockingLimiter.d.ts +3 -1
  37. package/dist/limiter/factories/makePartitionedBlockingLimiter.d.ts.map +1 -1
  38. package/dist/limiter/factories/makePartitionedBlockingLimiter.js +5 -4
  39. package/dist/limiter/factories/makeSimpleLimiter.d.ts.map +1 -1
  40. package/dist/limiter/factories/makeSimpleLimiter.js +2 -1
  41. package/dist/utils/LinkedWaiterQueue.d.ts +21 -0
  42. package/dist/utils/LinkedWaiterQueue.d.ts.map +1 -0
  43. package/dist/utils/LinkedWaiterQueue.js +81 -0
  44. package/dist/utils/index.d.ts +1 -0
  45. package/dist/utils/index.d.ts.map +1 -1
  46. package/dist/utils/index.js +1 -0
  47. package/package.json +4 -3
@@ -1,6 +1,12 @@
1
1
  /**
2
2
  * Handle returned when a concurrency slot is acquired. The caller must invoke
3
- * exactly one of the release-and-record methods when the operation completes.
3
+ * exactly one of the release-and-xxx methods when the operation completes.
4
+ *
5
+ * **Important for implementers:** the release methods below must never throw
6
+ * or produce a rejected promise. If an error occurs internally (e.g. notifying
7
+ * a strategy), the implementation is responsible for catching and handling it.
8
+ * Callers may invoke these methods in a fire-and-forget manner (without
9
+ * awaiting), so any rejection would become an unhandled promise rejection.
4
10
  */
5
11
  export interface LimitAllotment {
6
12
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"LimitAllotment.d.ts","sourceRoot":"","sources":["../src/LimitAllotment.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,uBAAuB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzC;;;OAGG;IACH,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAElC;;;;OAIG;IACH,uBAAuB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1C"}
1
+ {"version":3,"file":"LimitAllotment.d.ts","sourceRoot":"","sources":["../src/LimitAllotment.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,uBAAuB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzC;;;OAGG;IACH,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAElC;;;;OAIG;IACH,uBAAuB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1C"}
package/dist/Limiter.d.ts CHANGED
@@ -9,7 +9,7 @@ export type MaybePromise<T> = T | Promise<T>;
9
9
  export type AcquireResult = Promise<LimitAllotment | undefined>;
10
10
  export interface AcquireOptions<ContextT = void> {
11
11
  context?: ContextT;
12
- signal?: AbortSignal;
12
+ signal?: AbortSignal | undefined;
13
13
  }
14
14
  /**
15
15
  * Read-only view of the limiter's current state, provided to strategies so
@@ -40,13 +40,13 @@ export interface AcquireStrategy<ContextT> {
40
40
  * Called when the adaptive limit changes. The strategy can react
41
41
  * (e.g. adjust available permits by the delta, update partition sub-limits).
42
42
  */
43
- onLimitChanged?(oldLimit: number, newLimit: number): MaybePromise<void>;
43
+ onLimitChanged?(oldLimit: number, newLimit: number): void;
44
44
  }
45
45
  /**
46
46
  * Determines what happens when a request is rejected by the
47
- * {@link AcquireStrategy}. The type parameter `ResultT` flows through to
48
- * {@link Limiter.acquire}'s return type, enabling the type system to
49
- * distinguish sync limiters (no promise) from async/blocking ones.
47
+ * {@link AcquireStrategy} (e.g. queue the caller, reject immediately, or block
48
+ * until a slot is available). Implementations must return an
49
+ * {@link AcquireResult}, which is always a `Promise`.
50
50
  */
51
51
  export interface AllotmentUnavailableStrategy<ContextT> {
52
52
  /**
@@ -66,6 +66,12 @@ export interface AllotmentUnavailableStrategy<ContextT> {
66
66
  * Blocking strategies use this to wake queued waiters.
67
67
  */
68
68
  onAllotmentReleased(): MaybePromise<void>;
69
+ /**
70
+ * Called when the adaptive limit changes. Blocking/rejection strategies can
71
+ * use this to proactively react to newly available capacity (e.g. drain
72
+ * backlog).
73
+ */
74
+ onLimitChanged?(oldLimit: number, newLimit: number): MaybePromise<void>;
69
75
  }
70
76
  export interface LimiterOptions<ContextT> {
71
77
  limit?: AdaptiveLimit;
@@ -97,8 +103,6 @@ export interface LimiterOptions<ContextT> {
97
103
  * Concurrency limiter with pluggable strategies for gating decisions and
98
104
  * rejection handling.
99
105
  *
100
- * `acquire()` always returns a `Promise<LimitAllotment | undefined>`.
101
- *
102
106
  * @typeParam ContextT Request context type (e.g. partition key).
103
107
  */
104
108
  export declare class Limiter<Context = void> {
@@ -122,25 +126,13 @@ export declare class Limiter<Context = void> {
122
126
  getLimit(): number;
123
127
  getInflight(): number;
124
128
  }
125
- /**
126
- * Simple semaphore-based acquire strategy. Tracks a permits counter that is
127
- * decremented when an allotment is taken and incremented on release. When
128
- * permits reach zero, {@link tryAcquireAllotment} returns false.
129
- */
130
- export declare class SemaphoreStrategy {
131
- private permits;
132
- constructor(initialLimit: number);
133
- tryAcquireAllotment(): boolean;
134
- onAllotmentReleased(): void;
135
- onLimitChanged(oldLimit: number, newLimit: number): void;
136
- }
137
129
  export type RunCallbackArgs<ContextT> = {
138
130
  context: ContextT | undefined;
139
131
  signal: AbortSignal | undefined;
140
132
  };
141
133
  export interface LimitedFunction<ContextT> {
142
- <T, E extends Error = Error>(fn: (args: RunCallbackArgs<ContextT>) => RunResult<T, E> | Promise<RunResult<T, E>>): Promise<T | typeof QuotaNotAvailable>;
143
- <T, E extends Error = Error>(options: AcquireOptions<ContextT>, fn: (args: RunCallbackArgs<ContextT>) => RunResult<T, E> | Promise<RunResult<T, E>>): Promise<T | typeof QuotaNotAvailable>;
134
+ <T, E extends Error = Error>(fn: (args: RunCallbackArgs<ContextT>) => T | RunResult<T, E> | Promise<T | RunResult<T, E>>): Promise<T | typeof QuotaNotAvailable>;
135
+ <T, E extends Error = Error>(options: AcquireOptions<ContextT>, fn: (args: RunCallbackArgs<ContextT>) => T | RunResult<T, E> | Promise<T | RunResult<T, E>>): Promise<T | typeof QuotaNotAvailable>;
144
136
  }
145
137
  /**
146
138
  * Creates a helper that runs callbacks under acquired limiter allotments.
@@ -150,6 +142,8 @@ export interface LimitedFunction<ContextT> {
150
142
  * - On {@link RunResult} `success` / `ignore`, returns the carried value after
151
143
  * recording success/ignore to the allotment.
152
144
  * - On `dropped`, records drop and throws the carried error.
145
+ * - If callback returns a non-{@link RunResult} value, it is treated as
146
+ * implicit success and returned as-is.
153
147
  * - On uncaught exceptions from `fn`, records ignore and rethrows, except for
154
148
  * {@link AdaptiveTimeoutError}, which records drop and rethrows.
155
149
  * - Callback receives `{ context, signal }` from acquire options.
@@ -1 +1 @@
1
- {"version":3,"file":"Limiter.d.ts","sourceRoot":"","sources":["../src/Limiter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Limiter.d.ts","sourceRoot":"","sources":["../src/Limiter.ts"],"names":[],"mappings":"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"}
package/dist/Limiter.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { GradientLimit } from "./limit/GradientLimit.js";
2
+ import { SemaphoreStrategy } from "./limiter/acquire-strategies/SemaphoreStrategy.js";
2
3
  import { MetricIds, NoopMetricRegistry } from "./MetricRegistry.js";
3
- import { isAdaptiveTimeoutError, QuotaNotAvailable, } from "./RunResult.js";
4
+ import { isAdaptiveTimeoutError, isRunResult, QuotaNotAvailable, } from "./RunResult.js";
4
5
  // ---------------------------------------------------------------------------
5
6
  // Limiter options
6
7
  // ---------------------------------------------------------------------------
@@ -14,8 +15,6 @@ let idCounter = 0;
14
15
  * Concurrency limiter with pluggable strategies for gating decisions and
15
16
  * rejection handling.
16
17
  *
17
- * `acquire()` always returns a `Promise<LimitAllotment | undefined>`.
18
- *
19
18
  * @typeParam ContextT Request context type (e.g. partition key).
20
19
  */
21
20
  export class Limiter {
@@ -45,7 +44,8 @@ export class Limiter {
45
44
  this.limitAlgorithm.subscribe((newLimit) => {
46
45
  const oldLimit = this._limit;
47
46
  this._limit = newLimit;
48
- void Promise.resolve(this.acquireStrategy.onLimitChanged?.(oldLimit, newLimit));
47
+ this.acquireStrategy.onLimitChanged?.(oldLimit, newLimit);
48
+ void this.rejectionStrategy?.onLimitChanged?.(oldLimit, newLimit);
49
49
  });
50
50
  const registry = options.metricRegistry ?? NoopMetricRegistry;
51
51
  const limiterName = options.name ?? `unnamed-${++idCounter}`;
@@ -85,12 +85,27 @@ export class Limiter {
85
85
  };
86
86
  if (!(await this.acquireStrategy.tryAcquireAllotment(ctx, state))) {
87
87
  this.rejectedCounter.increment();
88
- if (this.rejectionStrategy) {
89
- return this.rejectionStrategy.onAllotmentUnavailable(ctx, (retryCtx) => this.tryAcquireCore(retryCtx), options?.signal);
88
+ if (!this.rejectionStrategy) {
89
+ return undefined;
90
+ }
91
+ // if signal aborted here, nothing to cleanup, as we didn't acquire anything.
92
+ if (options?.signal?.aborted) {
93
+ return undefined;
90
94
  }
95
+ return this.rejectionStrategy.onAllotmentUnavailable(ctx, async (retryCtx) => {
96
+ if (options?.signal?.aborted) {
97
+ return undefined;
98
+ }
99
+ return this.tryAcquireCore(retryCtx);
100
+ }, options?.signal);
101
+ }
102
+ const allotment = this.createAllotment(ctx);
103
+ if (options?.signal?.aborted) {
104
+ // here, we did acquire, so we need to try to cleanup.
105
+ await allotment.releaseAndIgnore();
91
106
  return undefined;
92
107
  }
93
- return this.createAllotment(ctx);
108
+ return allotment;
94
109
  }
95
110
  async tryAcquireCore(ctx) {
96
111
  const state = {
@@ -105,26 +120,77 @@ export class Limiter {
105
120
  createAllotment(ctx) {
106
121
  const startTime = this.clock();
107
122
  const currentInflight = ++this._inflight;
123
+ // Make sure an allotment can only be released once; future calls become a
124
+ // no-op. This simplifies a lot of cleanup handling etc that'd otherwise be
125
+ // much racier/more complicated. It could hide subtle correctness issue, but
126
+ // should be more valuable as defense-in-depth.
127
+ let releaseStarted = false;
108
128
  return {
109
129
  releaseAndRecordSuccess: async () => {
130
+ if (releaseStarted)
131
+ return;
132
+ releaseStarted = true;
133
+ const endTime = this.clock();
134
+ const rtt = endTime - startTime;
110
135
  this._inflight--;
111
- await this.acquireStrategy.onAllotmentReleased(ctx);
112
136
  this.successCounter.increment();
113
- this.limitAlgorithm.addSample(startTime, this.clock() - startTime, currentInflight, false);
114
- await this.rejectionStrategy?.onAllotmentReleased();
137
+ // If one onAllotmentReleased call fails, hard to know what to do here.
138
+ // We're in some kind of inconsistent state, but we probably have to
139
+ // soldier on.
140
+ try {
141
+ await this.acquireStrategy.onAllotmentReleased(ctx);
142
+ }
143
+ catch { }
144
+ try {
145
+ this.limitAlgorithm.addSample(startTime, rtt, currentInflight, false);
146
+ }
147
+ catch { }
148
+ try {
149
+ await this.rejectionStrategy?.onAllotmentReleased();
150
+ }
151
+ catch { }
115
152
  },
116
153
  releaseAndIgnore: async () => {
154
+ if (releaseStarted)
155
+ return;
156
+ releaseStarted = true;
117
157
  this._inflight--;
118
- await this.acquireStrategy.onAllotmentReleased(ctx);
119
158
  this.ignoredCounter.increment();
120
- await this.rejectionStrategy?.onAllotmentReleased();
159
+ // If one onAllotmentReleased call fails, hard to know what to do here.
160
+ // We're in some kind of inconsistent state, but we probably have to
161
+ // soldier on.
162
+ try {
163
+ await this.acquireStrategy.onAllotmentReleased(ctx);
164
+ }
165
+ catch { }
166
+ try {
167
+ await this.rejectionStrategy?.onAllotmentReleased();
168
+ }
169
+ catch { }
121
170
  },
122
171
  releaseAndRecordDropped: async () => {
172
+ if (releaseStarted)
173
+ return;
174
+ releaseStarted = true;
175
+ const endTime = this.clock();
176
+ const rtt = endTime - startTime;
123
177
  this._inflight--;
124
- await this.acquireStrategy.onAllotmentReleased(ctx);
125
178
  this.droppedCounter.increment();
126
- this.limitAlgorithm.addSample(startTime, this.clock() - startTime, currentInflight, true);
127
- await this.rejectionStrategy?.onAllotmentReleased();
179
+ // If one onAllotmentReleased call fails, hard to know what to do here.
180
+ // We're in some kind of inconsistent state, but we probably have to
181
+ // soldier on.
182
+ try {
183
+ await this.acquireStrategy.onAllotmentReleased(ctx);
184
+ }
185
+ catch { }
186
+ try {
187
+ this.limitAlgorithm.addSample(startTime, rtt, currentInflight, true);
188
+ }
189
+ catch { }
190
+ try {
191
+ await this.rejectionStrategy?.onAllotmentReleased();
192
+ }
193
+ catch { }
128
194
  },
129
195
  };
130
196
  }
@@ -135,32 +201,6 @@ export class Limiter {
135
201
  return this._inflight;
136
202
  }
137
203
  }
138
- // ---------------------------------------------------------------------------
139
- // Built-in acquire strategy: Semaphore
140
- // ---------------------------------------------------------------------------
141
- /**
142
- * Simple semaphore-based acquire strategy. Tracks a permits counter that is
143
- * decremented when an allotment is taken and incremented on release. When
144
- * permits reach zero, {@link tryAcquireAllotment} returns false.
145
- */
146
- export class SemaphoreStrategy {
147
- permits;
148
- constructor(initialLimit) {
149
- this.permits = initialLimit;
150
- }
151
- tryAcquireAllotment() {
152
- if (this.permits <= 0)
153
- return false;
154
- this.permits--;
155
- return true;
156
- }
157
- onAllotmentReleased() {
158
- this.permits++;
159
- }
160
- onLimitChanged(oldLimit, newLimit) {
161
- this.permits += newLimit - oldLimit;
162
- }
163
- }
164
204
  /**
165
205
  * Creates a helper that runs callbacks under acquired limiter allotments.
166
206
  *
@@ -169,28 +209,26 @@ export class SemaphoreStrategy {
169
209
  * - On {@link RunResult} `success` / `ignore`, returns the carried value after
170
210
  * recording success/ignore to the allotment.
171
211
  * - On `dropped`, records drop and throws the carried error.
212
+ * - If callback returns a non-{@link RunResult} value, it is treated as
213
+ * implicit success and returned as-is.
172
214
  * - On uncaught exceptions from `fn`, records ignore and rethrows, except for
173
215
  * {@link AdaptiveTimeoutError}, which records drop and rethrows.
174
216
  * - Callback receives `{ context, signal }` from acquire options.
175
217
  */
176
218
  export function withLimiter(limiter) {
177
219
  async function limited(optionsOrFn, maybeFn) {
178
- const hasOptions = maybeFn !== undefined;
179
- const options = hasOptions
180
- ? optionsOrFn
181
- : undefined;
182
- const fn = hasOptions
183
- ? maybeFn
184
- : optionsOrFn;
220
+ const [options, fn] = typeof optionsOrFn === "function"
221
+ ? [undefined, optionsOrFn]
222
+ : [optionsOrFn, maybeFn];
223
+ if (!fn) {
224
+ throw new Error("No function provided");
225
+ }
185
226
  const allotment = await limiter.acquire(options);
186
227
  if (!allotment) {
187
228
  return QuotaNotAvailable;
188
229
  }
189
230
  const [result] = await Promise.allSettled([
190
- fn({
191
- context: options?.context,
192
- signal: options?.signal,
193
- }),
231
+ fn({ context: options?.context, signal: options?.signal }),
194
232
  ]);
195
233
  if (result.status === "rejected") {
196
234
  if (isAdaptiveTimeoutError(result.reason)) {
@@ -202,16 +240,21 @@ export function withLimiter(limiter) {
202
240
  throw result.reason;
203
241
  }
204
242
  const outcome = result.value;
205
- switch (outcome.kind) {
243
+ if (!isRunResult(outcome)) {
244
+ await allotment.releaseAndRecordSuccess();
245
+ return outcome;
246
+ }
247
+ const outcomeCast = outcome;
248
+ switch (outcomeCast.kind) {
206
249
  case "success":
207
250
  await allotment.releaseAndRecordSuccess();
208
- return outcome.value;
251
+ return outcomeCast.value;
209
252
  case "ignore":
210
253
  await allotment.releaseAndIgnore();
211
- return outcome.value;
254
+ return outcomeCast.value;
212
255
  case "dropped":
213
256
  await allotment.releaseAndRecordDropped();
214
- throw outcome.error;
257
+ throw outcomeCast.error;
215
258
  }
216
259
  }
217
260
  return limited;
@@ -14,20 +14,26 @@ export declare class AdaptiveTimeoutError extends Error {
14
14
  constructor(message?: string);
15
15
  }
16
16
  export declare function isAdaptiveTimeoutError(error: unknown): error is AdaptiveTimeoutError;
17
- export interface RunSuccess<T> {
17
+ declare const isRunResultTag: unique symbol;
18
+ export type RunSuccess<T> = {
18
19
  readonly kind: "success";
20
+ readonly [isRunResultTag]: true;
19
21
  readonly value: T;
20
- }
21
- export interface RunIgnore<T> {
22
+ };
23
+ export type RunIgnore<T> = {
22
24
  readonly kind: "ignore";
25
+ readonly [isRunResultTag]: true;
23
26
  readonly value: T;
24
- }
25
- export interface RunDropped<E extends Error = Error> {
27
+ };
28
+ export type RunDropped<E extends Error = Error> = {
26
29
  readonly kind: "dropped";
30
+ readonly [isRunResultTag]: true;
27
31
  readonly error: E;
28
- }
32
+ };
29
33
  export type RunResult<T, E extends Error = Error> = RunSuccess<T> | RunIgnore<T> | RunDropped<E>;
34
+ export declare function isRunResult<T extends unknown>(value: T): value is T & RunResult<unknown, Error>;
30
35
  export declare function success<T>(value: T): RunSuccess<T>;
31
36
  export declare function ignore<T>(value: T): RunIgnore<T>;
32
37
  export declare function dropped<E extends Error>(error: E): RunDropped<E>;
38
+ export {};
33
39
  //# sourceMappingURL=RunResult.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"RunResult.d.ts","sourceRoot":"","sources":["../src/RunResult.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,eAA8B,CAAC;AAE7D;;;;GAIG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,IAAI,qBAA+B;gBAEhC,OAAO,CAAC,EAAE,MAAM;CAI7B;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,oBAAoB,CAQ/B;AAED,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;CACnB;AAED,MAAM,WAAW,SAAS,CAAC,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;CACnB;AAED,MAAM,WAAW,UAAU,CAAC,CAAC,SAAS,KAAK,GAAG,KAAK;IACjD,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;CACnB;AAED,MAAM,MAAM,SAAS,CAAC,CAAC,EAAE,CAAC,SAAS,KAAK,GAAG,KAAK,IAC5C,UAAU,CAAC,CAAC,CAAC,GACb,SAAS,CAAC,CAAC,CAAC,GACZ,UAAU,CAAC,CAAC,CAAC,CAAC;AAElB,wBAAgB,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAElD;AAED,wBAAgB,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAEhD;AAED,wBAAgB,OAAO,CAAC,CAAC,SAAS,KAAK,EAAE,KAAK,EAAE,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAEhE"}
1
+ {"version":3,"file":"RunResult.d.ts","sourceRoot":"","sources":["../src/RunResult.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,eAA8B,CAAC;AAE7D;;;;GAIG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,IAAI,qBAA+B;gBAEhC,OAAO,CAAC,EAAE,MAAM;CAI7B;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,oBAAoB,CAQ/B;AAED,QAAA,MAAM,cAAc,eAAwB,CAAC;AAE7C,MAAM,MAAM,UAAU,CAAC,CAAC,IAAI;IAC1B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,CAAC,cAAc,CAAC,EAAE,IAAI,CAAC;IAChC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI;IACzB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,CAAC,cAAc,CAAC,EAAE,IAAI,CAAC;IAChC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,KAAK,GAAG,KAAK,IAAI;IAChD,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,CAAC,cAAc,CAAC,EAAE,IAAI,CAAC;IAChC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,SAAS,CAAC,CAAC,EAAE,CAAC,SAAS,KAAK,GAAG,KAAK,IAC5C,UAAU,CAAC,CAAC,CAAC,GACb,SAAS,CAAC,CAAC,CAAC,GACZ,UAAU,CAAC,CAAC,CAAC,CAAC;AAElB,wBAAgB,WAAW,CAAC,CAAC,SAAS,OAAO,EAC3C,KAAK,EAAE,CAAC,GACP,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC,OAAO,EAAE,KAAK,CAAC,CAOxC;AAED,wBAAgB,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAElD;AAED,wBAAgB,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAEhD;AAED,wBAAgB,OAAO,CAAC,CAAC,SAAS,KAAK,EAAE,KAAK,EAAE,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAEhE"}
package/dist/RunResult.js CHANGED
@@ -24,12 +24,19 @@ export function isAdaptiveTimeoutError(error) {
24
24
  "code" in error &&
25
25
  error.code === ADAPTIVE_TIMEOUT_ERROR_CODE));
26
26
  }
27
+ const isRunResultTag = Symbol("isRunResult");
28
+ export function isRunResult(value) {
29
+ return (typeof value === "object" &&
30
+ value !== null &&
31
+ isRunResultTag in value &&
32
+ value[isRunResultTag] === true);
33
+ }
27
34
  export function success(value) {
28
- return { kind: "success", value };
35
+ return { kind: "success", [isRunResultTag]: true, value };
29
36
  }
30
37
  export function ignore(value) {
31
- return { kind: "ignore", value };
38
+ return { kind: "ignore", [isRunResultTag]: true, value };
32
39
  }
33
40
  export function dropped(error) {
34
- return { kind: "dropped", error };
41
+ return { kind: "dropped", [isRunResultTag]: true, error };
35
42
  }
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  export type { AdaptiveLimit } from "./limit/StreamingLimit.js";
2
2
  export type { LimitAllotment } from "./LimitAllotment.js";
3
- export { Limiter, SemaphoreStrategy, withLimiter, type AcquireOptions, type AcquireResult, type AcquireStrategy, type AllotmentUnavailableStrategy, type LimitedFunction, type LimiterOptions, type LimiterState, type RunCallbackArgs, type MaybePromise, } from "./Limiter.js";
3
+ export { Limiter, withLimiter, type AcquireOptions, type AcquireResult, type AcquireStrategy, type AllotmentUnavailableStrategy, type LimitedFunction, type LimiterOptions, type LimiterState, type MaybePromise, type RunCallbackArgs, } from "./Limiter.js";
4
4
  export { ListenerSet } from "./ListenerSet.js";
5
5
  export { MetricIds, NoopMetricRegistry, type Counter, type DistributionMetric, type GaugeMetric, type MetricRegistry, } from "./MetricRegistry.js";
6
- export { AdaptiveTimeoutError, dropped, ignore, isAdaptiveTimeoutError, QuotaNotAvailable, success, type RunDropped, type RunIgnore, type RunResult, type RunSuccess, } from "./RunResult.js";
6
+ export { AdaptiveTimeoutError, dropped, ignore, isAdaptiveTimeoutError, isRunResult, QuotaNotAvailable, success, type RunDropped, type RunIgnore, type RunResult, type RunSuccess, } from "./RunResult.js";
7
7
  export { AIMDLimit, type AIMDLimitOptions } from "./limit/AIMDLimit.js";
8
8
  export { FixedLimit } from "./limit/FixedLimit.js";
9
9
  export { GradientLimit, type Gradient2LimitOptions, } from "./limit/GradientLimit.js";
@@ -20,6 +20,7 @@ export type { SampleWindow } from "./limit/window/SampleWindow.js";
20
20
  export { squareRoot, squareRootWithBaseline } from "./utils/index.js";
21
21
  export * from "./limiter/factories/index.js";
22
22
  export { PartitionedStrategy, type PartitionConfig, } from "./limiter/acquire-strategies/PartitionedStrategy.js";
23
+ export { SemaphoreStrategy } from "./limiter/acquire-strategies/SemaphoreStrategy.js";
23
24
  export { DelayedRejectStrategy, type DelayedRejectStrategyOptions, } from "./limiter/allocation-unavailable-strategies/DelayedRejectStrategy.js";
24
25
  export { DelayedThenBlockingRejection } from "./limiter/allocation-unavailable-strategies/DelayedThenBlockingRejection.js";
25
26
  export { FifoBlockingRejection } from "./limiter/allocation-unavailable-strategies/FifoBlockingRejection.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC/D,YAAY,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EACL,OAAO,EACP,iBAAiB,EACjB,WAAW,EACX,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,4BAA4B,EACjC,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,KAAK,YAAY,GAClB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EACL,SAAS,EACT,kBAAkB,EAClB,KAAK,OAAO,EACZ,KAAK,kBAAkB,EACvB,KAAK,WAAW,EAChB,KAAK,cAAc,GACpB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,oBAAoB,EACpB,OAAO,EACP,MAAM,EACN,sBAAsB,EACtB,iBAAiB,EACjB,OAAO,EACP,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,UAAU,GAChB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxE,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EACL,aAAa,EACb,KAAK,qBAAqB,GAC3B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAC;AACzE,OAAO,EACL,UAAU,EACV,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,GACtB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,aAAa,EACb,KAAK,oBAAoB,GAC1B,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAC5D,YAAY,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AAG7E,OAAO,EAAE,uBAAuB,EAAE,MAAM,uCAAuC,CAAC;AAChF,OAAO,EAAE,4BAA4B,EAAE,MAAM,0CAA0C,CAAC;AACxF,YAAY,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAGnE,OAAO,EAAE,UAAU,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAGtE,cAAc,8BAA8B,CAAC;AAC7C,OAAO,EACL,mBAAmB,EACnB,KAAK,eAAe,GACrB,MAAM,qDAAqD,CAAC;AAG7D,OAAO,EACL,qBAAqB,EACrB,KAAK,4BAA4B,GAClC,MAAM,sEAAsE,CAAC;AAC9E,OAAO,EAAE,4BAA4B,EAAE,MAAM,6EAA6E,CAAC;AAC3H,OAAO,EAAE,qBAAqB,EAAE,MAAM,sEAAsE,CAAC;AAC7G,OAAO,EACL,qBAAqB,EACrB,KAAK,4BAA4B,GAClC,MAAM,sEAAsE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC/D,YAAY,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EACL,OAAO,EACP,WAAW,EACX,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,4BAA4B,EACjC,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,eAAe,GACrB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EACL,SAAS,EACT,kBAAkB,EAClB,KAAK,OAAO,EACZ,KAAK,kBAAkB,EACvB,KAAK,WAAW,EAChB,KAAK,cAAc,GACpB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,oBAAoB,EACpB,OAAO,EACP,MAAM,EACN,sBAAsB,EACtB,WAAW,EACX,iBAAiB,EACjB,OAAO,EACP,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,UAAU,GAChB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxE,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EACL,aAAa,EACb,KAAK,qBAAqB,GAC3B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAC;AACzE,OAAO,EACL,UAAU,EACV,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,GACtB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,aAAa,EACb,KAAK,oBAAoB,GAC1B,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAC5D,YAAY,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AAG7E,OAAO,EAAE,uBAAuB,EAAE,MAAM,uCAAuC,CAAC;AAChF,OAAO,EAAE,4BAA4B,EAAE,MAAM,0CAA0C,CAAC;AACxF,YAAY,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAGnE,OAAO,EAAE,UAAU,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAEtE,cAAc,8BAA8B,CAAC;AAG7C,OAAO,EACL,mBAAmB,EACnB,KAAK,eAAe,GACrB,MAAM,qDAAqD,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,mDAAmD,CAAC;AAGtF,OAAO,EACL,qBAAqB,EACrB,KAAK,4BAA4B,GAClC,MAAM,sEAAsE,CAAC;AAC9E,OAAO,EAAE,4BAA4B,EAAE,MAAM,6EAA6E,CAAC;AAC3H,OAAO,EAAE,qBAAqB,EAAE,MAAM,sEAAsE,CAAC;AAC7G,OAAO,EACL,qBAAqB,EACrB,KAAK,4BAA4B,GAClC,MAAM,sEAAsE,CAAC"}
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
- export { Limiter, SemaphoreStrategy, withLimiter, } from "./Limiter.js";
1
+ export { Limiter, withLimiter, } from "./Limiter.js";
2
2
  export { ListenerSet } from "./ListenerSet.js";
3
3
  export { MetricIds, NoopMetricRegistry, } from "./MetricRegistry.js";
4
- export { AdaptiveTimeoutError, dropped, ignore, isAdaptiveTimeoutError, QuotaNotAvailable, success, } from "./RunResult.js";
4
+ export { AdaptiveTimeoutError, dropped, ignore, isAdaptiveTimeoutError, isRunResult, QuotaNotAvailable, success, } from "./RunResult.js";
5
5
  // Limit algorithms
6
6
  export { AIMDLimit } from "./limit/AIMDLimit.js";
7
7
  export { FixedLimit } from "./limit/FixedLimit.js";
@@ -18,9 +18,10 @@ export { makeAverageSampleWindow } from "./limit/window/AverageSampleWindow.js";
18
18
  export { createPercentileSampleWindow } from "./limit/window/PercentileSampleWindow.js";
19
19
  // Limit functions
20
20
  export { squareRoot, squareRootWithBaseline } from "./utils/index.js";
21
- // Acquire strategies
22
21
  export * from "./limiter/factories/index.js";
22
+ // Acquire strategies
23
23
  export { PartitionedStrategy, } from "./limiter/acquire-strategies/PartitionedStrategy.js";
24
+ export { SemaphoreStrategy } from "./limiter/acquire-strategies/SemaphoreStrategy.js";
24
25
  // Rejection strategies
25
26
  export { DelayedRejectStrategy, } from "./limiter/allocation-unavailable-strategies/DelayedRejectStrategy.js";
26
27
  export { DelayedThenBlockingRejection } from "./limiter/allocation-unavailable-strategies/DelayedThenBlockingRejection.js";
@@ -6,6 +6,25 @@ export type PartitionConfig = {
6
6
  * [0.0, 1.0].
7
7
  */
8
8
  percent: number;
9
+ /**
10
+ * Controls whether this partition may exceed its limit-at-global-saturation
11
+ * while global inflight is still below the global limit.
12
+ *
13
+ * - `{ kind: "unbounded" }` (default): current behavior; any partition may
14
+ * consume all global slack.
15
+ * - `{ kind: "capped", maxBurstMultiplier }`: partition may burst, but only
16
+ * up to `ceil(limitAtGlobalSaturation * maxBurstMultiplier)`.
17
+ * - `{ kind: "none" }`: no bursting; partition cannot exceed its
18
+ * `limitAtGlobalSaturation`, even when global slack exists.
19
+ */
20
+ burstMode?: {
21
+ kind: "unbounded";
22
+ } | {
23
+ kind: "capped";
24
+ maxBurstMultiplier: number;
25
+ } | {
26
+ kind: "none";
27
+ } | undefined;
9
28
  };
10
29
  /**
11
30
  * ## Partitioned acquire strategy (guaranteed share + bursting)
@@ -16,9 +35,12 @@ export type PartitionConfig = {
16
35
  * max(1, ceil(globalLimit * percent))`.
17
36
  *
18
37
  * Admission policy:
19
- * - If `globalInflight < globalLimit`, the request is admitted regardless of
20
- * its partition's current limitAtGlobalSaturation usage (bursting while slack
21
- * exists).
38
+ * - If `globalInflight < globalLimit`, admission is controlled by partition
39
+ * burst policy:
40
+ * - `unbounded` (default): always admit while global slack exists.
41
+ * - `capped`: admit up to
42
+ * `ceil(limitAtGlobalSaturation * maxBurstMultiplier)`.
43
+ * - `none`: admit only up to `limitAtGlobalSaturation`.
22
44
  *
23
45
  * - If `globalInflight >= globalLimit`, admission is checked against the
24
46
  * resolved partition's limitAtGlobalSaturation only (`tryAcquire` on that
@@ -30,8 +52,7 @@ export type PartitionConfig = {
30
52
  *
31
53
  * Examples (global limit = 10, A=50%, B=50% => limitsAtGlobalSaturation: A=5,
32
54
  * B=5):
33
- * - **Bursting:** A can reach inflight 10 while B is 0 (below-saturation
34
- * admissions do not enforce per-partition limits at global saturation).
55
+ * - **Unbounded bursting:** A can reach inflight 10 while B is 0.
35
56
  *
36
57
  * - **Limit-at-global-saturation catch-up at saturation:** if A=10 and B=0,
37
58
  * then B requests can still be admitted up to B=5 via `tryAcquire`, so global
@@ -81,6 +102,14 @@ export declare class PartitionedStrategy<ContextT, PartitionName extends string
81
102
  getPartition(name: PartitionName): {
82
103
  name: PartitionName;
83
104
  percent: number;
105
+ burstMode: {
106
+ kind: "unbounded";
107
+ } | {
108
+ kind: "capped";
109
+ maxBurstMultiplier: number;
110
+ } | {
111
+ kind: "none";
112
+ };
84
113
  limitAtGlobalSaturation: number;
85
114
  inFlight: number;
86
115
  isLimitAtGlobalSaturationExceeded: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"PartitionedStrategy.d.ts","sourceRoot":"","sources":["../../../src/limiter/acquire-strategies/PartitionedStrategy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,KAAK,EAGV,cAAc,EACf,MAAM,yBAAyB,CAAC;AAKjC,MAAM,MAAM,eAAe,GAAG;IAC5B;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,qBAAa,mBAAmB,CAC9B,QAAQ,EACR,aAAa,SAAS,MAAM,GAAG,MAAM,CACrC,YAAW,eAAe,CAAC,QAAQ,CAAC;IACpC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAG9B;IACF,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAuB;IACxD,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAEH;gBAEnB,OAAO,EAAE;QACnB;;;WAGG;QACH,YAAY,EAAE,MAAM,CAAC;QAErB;;;;;WAKG;QACH,iBAAiB,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,aAAa,GAAG,SAAS,CAAC;QACpE,UAAU,EAAE,MAAM,CAAC,aAAa,EAAE,eAAe,CAAC,CAAC;QAEnD,cAAc,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;KAC7C;IAkDD,mBAAmB,CAAC,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO;IAUpE,mBAAmB,CAAC,OAAO,EAAE,QAAQ,GAAG,IAAI;IAI5C,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAMzD;;;OAGG;IACH,YAAY,CAAC,IAAI,EAAE,aAAa;;;;;;;IAehC,OAAO,CAAC,gBAAgB;CAUzB"}
1
+ {"version":3,"file":"PartitionedStrategy.d.ts","sourceRoot":"","sources":["../../../src/limiter/acquire-strategies/PartitionedStrategy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,KAAK,EAGV,cAAc,EACf,MAAM,yBAAyB,CAAC;AAKjC,MAAM,MAAM,eAAe,GAAG;IAC5B;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;;;;;;;;;OAUG;IACH,SAAS,CAAC,EACN;QAAE,IAAI,EAAE,WAAW,CAAA;KAAE,GACrB;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,kBAAkB,EAAE,MAAM,CAAA;KAAE,GAC9C;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAChB,SAAS,CAAC;CACf,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,qBAAa,mBAAmB,CAC9B,QAAQ,EACR,aAAa,SAAS,MAAM,GAAG,MAAM,CACrC,YAAW,eAAe,CAAC,QAAQ,CAAC;IACpC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAG9B;IACF,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAuB;IACxD,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAEH;gBAEnB,OAAO,EAAE;QACnB;;;WAGG;QACH,YAAY,EAAE,MAAM,CAAC;QAErB;;;;;WAKG;QACH,iBAAiB,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,aAAa,GAAG,SAAS,CAAC;QACpE,UAAU,EAAE,MAAM,CAAC,aAAa,EAAE,eAAe,CAAC,CAAC;QAEnD,cAAc,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;KAC7C;IAmDD,mBAAmB,CAAC,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO;IASpE,mBAAmB,CAAC,OAAO,EAAE,QAAQ,GAAG,IAAI;IAI5C,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAMzD;;;OAGG;IACH,YAAY,CAAC,IAAI,EAAE,aAAa;;;;kBAuCpB,WAAW;;kBACX,QAAQ;gCAAsB,MAAM;;kBACpC,MAAM;;;;;;IAzBlB,OAAO,CAAC,gBAAgB;CAUzB"}