@orkestrel/pool 0.0.14 → 0.0.16
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 +2 -2
- package/dist/src/core/index.cjs +72 -39
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +42 -29
- package/dist/src/core/index.d.ts +42 -29
- package/dist/src/core/index.js +72 -39
- package/dist/src/core/index.js.map +1 -1
- package/package.json +5 -5
|
@@ -4,7 +4,7 @@ import type { EmitterInterface } from '@orkestrel/emitter';
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Creates a distinct {@link PoolInterface} from resource lifecycle hooks, with optional bounded
|
|
7
|
-
* capacity,
|
|
7
|
+
* capacity, exclusive leases by default, and FIFO settlement.
|
|
8
8
|
*
|
|
9
9
|
* @remarks
|
|
10
10
|
* Concurrent create and validation hooks may overlap, while acquire promises settle in
|
|
@@ -57,18 +57,18 @@ import type { EmitterInterface } from '@orkestrel/emitter';
|
|
|
57
57
|
export declare function isPoolError(value: unknown): value is PoolError;
|
|
58
58
|
|
|
59
59
|
/**
|
|
60
|
-
* Tests whether a value is a positive safe integer
|
|
60
|
+
* Tests whether a value is a positive safe integer for a pool record or lease limit.
|
|
61
61
|
*
|
|
62
|
-
* @param value - The unknown
|
|
62
|
+
* @param value - The unknown record or lease limit candidate
|
|
63
63
|
* @returns True if the value is a positive safe integer; false otherwise
|
|
64
64
|
*
|
|
65
65
|
* @example
|
|
66
66
|
* ```ts
|
|
67
|
-
*
|
|
68
|
-
*
|
|
67
|
+
* isPoolLimit(8) // true
|
|
68
|
+
* isPoolLimit(Infinity) // false
|
|
69
69
|
* ```
|
|
70
70
|
*/
|
|
71
|
-
export declare function
|
|
71
|
+
export declare function isPoolLimit(value: unknown): value is number;
|
|
72
72
|
|
|
73
73
|
/**
|
|
74
74
|
* Tests whether a value is a native `AbortSignal` for the acquire boundary, returning `false`
|
|
@@ -112,8 +112,8 @@ import type { EmitterInterface } from '@orkestrel/emitter';
|
|
|
112
112
|
* Constructs a pool and synchronously validates its capacity contract.
|
|
113
113
|
*
|
|
114
114
|
* @param options - Resource hooks, observation hooks, capacity, and optional bounded warm floor
|
|
115
|
-
* @throws {@link PoolError} Thrown with `code: 'invalid'` when `min
|
|
116
|
-
* safe integer,
|
|
115
|
+
* @throws {@link PoolError} Thrown with `code: 'invalid'` when `capacity`, `min`, or `max` is not a positive
|
|
116
|
+
* safe integer, explicit `min` and `max` differ, `restarts` is absent with `min`, present without
|
|
117
117
|
* `min`, or not a non-negative safe integer, or `watch` is not callable.
|
|
118
118
|
*/
|
|
119
119
|
constructor(options: PoolOptions<T>);
|
|
@@ -121,9 +121,9 @@ import type { EmitterInterface } from '@orkestrel/emitter';
|
|
|
121
121
|
get emitter(): EmitterInterface<PoolEventMap>;
|
|
122
122
|
/** Counts all owned records, including records validating, destroying, or retained after failed cleanup. */
|
|
123
123
|
get size(): number;
|
|
124
|
-
/** Counts
|
|
124
|
+
/** Counts available records with no live lease or pending handout. */
|
|
125
125
|
get idle(): number;
|
|
126
|
-
/** Counts
|
|
126
|
+
/** Counts records with live leases, counting a shared record once rather than counting its leases. */
|
|
127
127
|
get active(): number;
|
|
128
128
|
/**
|
|
129
129
|
* Fills the warm floor and resets the strikes of a spent floor; without a floor, resolves immediately.
|
|
@@ -135,8 +135,8 @@ import type { EmitterInterface } from '@orkestrel/emitter';
|
|
|
135
135
|
*/
|
|
136
136
|
start(): Promise<void>;
|
|
137
137
|
/**
|
|
138
|
-
* Queues the caller in FIFO order,
|
|
139
|
-
* creates on demand
|
|
138
|
+
* Queues the caller in FIFO order, shares an occupied record or validates an idle record, and
|
|
139
|
+
* waits for a floor refill or creates on demand without a floor.
|
|
140
140
|
*
|
|
141
141
|
* @param signal - Optional native cancellation signal
|
|
142
142
|
* @returns A promise for the unique resource lease
|
|
@@ -144,9 +144,9 @@ import type { EmitterInterface } from '@orkestrel/emitter';
|
|
|
144
144
|
* with `code: 'invalid'`. This throw is synchronous rather than a rejected promise, so a caller
|
|
145
145
|
* that handles failures with `.catch()` alone misses it.
|
|
146
146
|
* @throws {@link PoolError} Thrown as a rejection when `destroy()` has already begun, with
|
|
147
|
-
* `code: 'destroyed'`; when the create hook fails or the floor is spent without an
|
|
147
|
+
* `code: 'destroyed'`; when the create hook fails or the floor is spent without an eligible record,
|
|
148
148
|
* with `code: 'create'` and the last cause; and when an invalid record's cleanup fails or
|
|
149
|
-
* a floor retains a record, owns `min` records, has nothing
|
|
149
|
+
* a floor retains a record, owns `min` records, has nothing eligible, and has no refill or disposal
|
|
150
150
|
* pending, even with live leases, with `code: 'cleanup'` and the retained cleanup failure as cause.
|
|
151
151
|
* A `signal` that aborts rejects with the caller's exact `signal.reason` instead.
|
|
152
152
|
*/
|
|
@@ -252,9 +252,9 @@ import type { EmitterInterface } from '@orkestrel/emitter';
|
|
|
252
252
|
readonly emitter: EmitterInterface<PoolEventMap>;
|
|
253
253
|
/** Counts all owned records, including records validating, destroying, or retained after failed cleanup. */
|
|
254
254
|
readonly size: number;
|
|
255
|
-
/** Counts
|
|
255
|
+
/** Counts available records with no live lease or pending handout. */
|
|
256
256
|
readonly idle: number;
|
|
257
|
-
/** Counts
|
|
257
|
+
/** Counts records with live leases, counting a shared record once rather than counting its leases. */
|
|
258
258
|
readonly active: number;
|
|
259
259
|
/**
|
|
260
260
|
* Fills the warm floor and resets the strikes of a spent floor; without a floor, resolves immediately.
|
|
@@ -266,8 +266,8 @@ import type { EmitterInterface } from '@orkestrel/emitter';
|
|
|
266
266
|
*/
|
|
267
267
|
start(): Promise<void>;
|
|
268
268
|
/**
|
|
269
|
-
* Queues the caller in FIFO order,
|
|
270
|
-
* creates on demand
|
|
269
|
+
* Queues the caller in FIFO order, shares an occupied record or validates an idle record, and
|
|
270
|
+
* waits for a floor refill or creates on demand without a floor.
|
|
271
271
|
*
|
|
272
272
|
* @param signal - Optional native cancellation signal
|
|
273
273
|
* @returns A promise for the unique resource lease
|
|
@@ -275,9 +275,9 @@ import type { EmitterInterface } from '@orkestrel/emitter';
|
|
|
275
275
|
* with `code: 'invalid'`. This throw is synchronous rather than a rejected promise, so a caller
|
|
276
276
|
* that handles failures with `.catch()` alone misses it.
|
|
277
277
|
* @throws {@link PoolError} Thrown as a rejection when `destroy()` has already begun, with
|
|
278
|
-
* `code: 'destroyed'`; when the create hook fails or the floor is spent without an
|
|
278
|
+
* `code: 'destroyed'`; when the create hook fails or the floor is spent without an eligible record,
|
|
279
279
|
* with `code: 'create'` and the last cause; and when an invalid record's cleanup fails or
|
|
280
|
-
* a floor retains a record, owns `min` records, has nothing
|
|
280
|
+
* a floor retains a record, owns `min` records, has nothing eligible, and has no refill or disposal
|
|
281
281
|
* pending, even with live leases, with `code: 'cleanup'` and the retained cleanup failure as cause.
|
|
282
282
|
* A `signal` that aborts rejects with the caller's exact `signal.reason` instead.
|
|
283
283
|
*/
|
|
@@ -309,13 +309,25 @@ import type { EmitterInterface } from '@orkestrel/emitter';
|
|
|
309
309
|
*
|
|
310
310
|
* @remarks
|
|
311
311
|
* `create` produces resources on demand, or only to restore `min` after `start()`.
|
|
312
|
-
* `destroy` tears down a claimed resource. `validate` checks
|
|
312
|
+
* `destroy` tears down a claimed resource. `validate` checks only idle records before reuse;
|
|
313
|
+
* additional leases on occupied records skip validation, so validation cannot dispose co-holders.
|
|
314
|
+
* With `capacity` greater than 1, an occupied record is never revalidated. Only `watch` detects
|
|
315
|
+
* its loss: without `watch`, a dead record can keep receiving leases until it becomes idle;
|
|
316
|
+
* with `watch`, that exposure lasts until the watch settles.
|
|
317
|
+
* `capacity` bounds simultaneous leases per record, including pending handouts, and is a positive
|
|
318
|
+
* safe integer. Default: 1. Selection prefers the least occupied eligible record, with idle ties
|
|
319
|
+
* in release order and occupied ties in creation order. Reservations precede awaited hooks;
|
|
320
|
+
* waiter settlement stays FIFO.
|
|
313
321
|
* `min` and `max` are positive safe integers and must be equal when both are set; `max`
|
|
314
|
-
* defaults to `min`. Omitting both leaves
|
|
322
|
+
* defaults to `min`. Omitting both leaves the record count unbounded. `restarts` is required with
|
|
315
323
|
* `min`, is refused without `min`, has no default, and is a non-negative safe integer.
|
|
316
|
-
* A failed refill or loss of a
|
|
317
|
-
* strikes
|
|
318
|
-
*
|
|
324
|
+
* A failed refill or loss of a record with no live lease adds a strike, even after prior use.
|
|
325
|
+
* A lease grant resets the strikes only when its record was created after the last strike;
|
|
326
|
+
* creation alone does not.
|
|
327
|
+
* Exceeding `restarts` stops ordinary refills until `start()` or a qualifying grant.
|
|
328
|
+
* Loss or one lease's `destroy()` invalidates every lease on that record with one disposal.
|
|
329
|
+
* Each lost leased record earns one refill attempt after successful disposal, even when
|
|
330
|
+
* the bound is spent. That credit does not reset strikes; a failed attempt adds a strike.
|
|
319
331
|
* `watch` settles on loss and receives a signal aborted when disposal begins. Its rejection
|
|
320
332
|
* reaches `error` with event `watch` only while the record is live. Any settlement after
|
|
321
333
|
* the signal aborts is ignored. `on` installs initial emitter listeners; `error`
|
|
@@ -328,6 +340,7 @@ import type { EmitterInterface } from '@orkestrel/emitter';
|
|
|
328
340
|
readonly destroy?: (value: T) => Promise<void> | void;
|
|
329
341
|
readonly validate?: (value: T) => Promise<boolean> | boolean;
|
|
330
342
|
readonly watch?: (value: T, signal: AbortSignal) => Promise<unknown>;
|
|
343
|
+
readonly capacity?: number;
|
|
331
344
|
readonly max?: number;
|
|
332
345
|
readonly min?: number;
|
|
333
346
|
readonly restarts?: number;
|
|
@@ -341,13 +354,13 @@ import type { EmitterInterface } from '@orkestrel/emitter';
|
|
|
341
354
|
/** Holds the leased value. Duplicate values still belong to independent records. */
|
|
342
355
|
readonly value: T;
|
|
343
356
|
/**
|
|
344
|
-
*
|
|
345
|
-
*
|
|
357
|
+
* Ends this exact lease once; a repeat call, and a call after loss or teardown took ownership,
|
|
358
|
+
* are no-ops. Other leases on the record remain live.
|
|
346
359
|
*/
|
|
347
360
|
release(): void;
|
|
348
361
|
/**
|
|
349
|
-
* Destroys this exact record
|
|
350
|
-
* are no-ops.
|
|
362
|
+
* Destroys this exact record for every co-holder and invalidates their leases; a repeat call,
|
|
363
|
+
* and a call after release, are no-ops.
|
|
351
364
|
*
|
|
352
365
|
* @returns A promise for this record's cleanup attempt, including an attempt already in progress
|
|
353
366
|
* @throws {@link PoolError} Thrown as a rejection with `code: 'cleanup'` when disposal fails.
|
package/dist/src/core/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { EmitterInterface } from '@orkestrel/emitter';
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Creates a distinct {@link PoolInterface} from resource lifecycle hooks, with optional bounded
|
|
7
|
-
* capacity,
|
|
7
|
+
* capacity, exclusive leases by default, and FIFO settlement.
|
|
8
8
|
*
|
|
9
9
|
* @remarks
|
|
10
10
|
* Concurrent create and validation hooks may overlap, while acquire promises settle in
|
|
@@ -57,18 +57,18 @@ import type { EmitterInterface } from '@orkestrel/emitter';
|
|
|
57
57
|
export declare function isPoolError(value: unknown): value is PoolError;
|
|
58
58
|
|
|
59
59
|
/**
|
|
60
|
-
* Tests whether a value is a positive safe integer
|
|
60
|
+
* Tests whether a value is a positive safe integer for a pool record or lease limit.
|
|
61
61
|
*
|
|
62
|
-
* @param value - The unknown
|
|
62
|
+
* @param value - The unknown record or lease limit candidate
|
|
63
63
|
* @returns True if the value is a positive safe integer; false otherwise
|
|
64
64
|
*
|
|
65
65
|
* @example
|
|
66
66
|
* ```ts
|
|
67
|
-
*
|
|
68
|
-
*
|
|
67
|
+
* isPoolLimit(8) // true
|
|
68
|
+
* isPoolLimit(Infinity) // false
|
|
69
69
|
* ```
|
|
70
70
|
*/
|
|
71
|
-
export declare function
|
|
71
|
+
export declare function isPoolLimit(value: unknown): value is number;
|
|
72
72
|
|
|
73
73
|
/**
|
|
74
74
|
* Tests whether a value is a native `AbortSignal` for the acquire boundary, returning `false`
|
|
@@ -112,8 +112,8 @@ import type { EmitterInterface } from '@orkestrel/emitter';
|
|
|
112
112
|
* Constructs a pool and synchronously validates its capacity contract.
|
|
113
113
|
*
|
|
114
114
|
* @param options - Resource hooks, observation hooks, capacity, and optional bounded warm floor
|
|
115
|
-
* @throws {@link PoolError} Thrown with `code: 'invalid'` when `min
|
|
116
|
-
* safe integer,
|
|
115
|
+
* @throws {@link PoolError} Thrown with `code: 'invalid'` when `capacity`, `min`, or `max` is not a positive
|
|
116
|
+
* safe integer, explicit `min` and `max` differ, `restarts` is absent with `min`, present without
|
|
117
117
|
* `min`, or not a non-negative safe integer, or `watch` is not callable.
|
|
118
118
|
*/
|
|
119
119
|
constructor(options: PoolOptions<T>);
|
|
@@ -121,9 +121,9 @@ import type { EmitterInterface } from '@orkestrel/emitter';
|
|
|
121
121
|
get emitter(): EmitterInterface<PoolEventMap>;
|
|
122
122
|
/** Counts all owned records, including records validating, destroying, or retained after failed cleanup. */
|
|
123
123
|
get size(): number;
|
|
124
|
-
/** Counts
|
|
124
|
+
/** Counts available records with no live lease or pending handout. */
|
|
125
125
|
get idle(): number;
|
|
126
|
-
/** Counts
|
|
126
|
+
/** Counts records with live leases, counting a shared record once rather than counting its leases. */
|
|
127
127
|
get active(): number;
|
|
128
128
|
/**
|
|
129
129
|
* Fills the warm floor and resets the strikes of a spent floor; without a floor, resolves immediately.
|
|
@@ -135,8 +135,8 @@ import type { EmitterInterface } from '@orkestrel/emitter';
|
|
|
135
135
|
*/
|
|
136
136
|
start(): Promise<void>;
|
|
137
137
|
/**
|
|
138
|
-
* Queues the caller in FIFO order,
|
|
139
|
-
* creates on demand
|
|
138
|
+
* Queues the caller in FIFO order, shares an occupied record or validates an idle record, and
|
|
139
|
+
* waits for a floor refill or creates on demand without a floor.
|
|
140
140
|
*
|
|
141
141
|
* @param signal - Optional native cancellation signal
|
|
142
142
|
* @returns A promise for the unique resource lease
|
|
@@ -144,9 +144,9 @@ import type { EmitterInterface } from '@orkestrel/emitter';
|
|
|
144
144
|
* with `code: 'invalid'`. This throw is synchronous rather than a rejected promise, so a caller
|
|
145
145
|
* that handles failures with `.catch()` alone misses it.
|
|
146
146
|
* @throws {@link PoolError} Thrown as a rejection when `destroy()` has already begun, with
|
|
147
|
-
* `code: 'destroyed'`; when the create hook fails or the floor is spent without an
|
|
147
|
+
* `code: 'destroyed'`; when the create hook fails or the floor is spent without an eligible record,
|
|
148
148
|
* with `code: 'create'` and the last cause; and when an invalid record's cleanup fails or
|
|
149
|
-
* a floor retains a record, owns `min` records, has nothing
|
|
149
|
+
* a floor retains a record, owns `min` records, has nothing eligible, and has no refill or disposal
|
|
150
150
|
* pending, even with live leases, with `code: 'cleanup'` and the retained cleanup failure as cause.
|
|
151
151
|
* A `signal` that aborts rejects with the caller's exact `signal.reason` instead.
|
|
152
152
|
*/
|
|
@@ -252,9 +252,9 @@ import type { EmitterInterface } from '@orkestrel/emitter';
|
|
|
252
252
|
readonly emitter: EmitterInterface<PoolEventMap>;
|
|
253
253
|
/** Counts all owned records, including records validating, destroying, or retained after failed cleanup. */
|
|
254
254
|
readonly size: number;
|
|
255
|
-
/** Counts
|
|
255
|
+
/** Counts available records with no live lease or pending handout. */
|
|
256
256
|
readonly idle: number;
|
|
257
|
-
/** Counts
|
|
257
|
+
/** Counts records with live leases, counting a shared record once rather than counting its leases. */
|
|
258
258
|
readonly active: number;
|
|
259
259
|
/**
|
|
260
260
|
* Fills the warm floor and resets the strikes of a spent floor; without a floor, resolves immediately.
|
|
@@ -266,8 +266,8 @@ import type { EmitterInterface } from '@orkestrel/emitter';
|
|
|
266
266
|
*/
|
|
267
267
|
start(): Promise<void>;
|
|
268
268
|
/**
|
|
269
|
-
* Queues the caller in FIFO order,
|
|
270
|
-
* creates on demand
|
|
269
|
+
* Queues the caller in FIFO order, shares an occupied record or validates an idle record, and
|
|
270
|
+
* waits for a floor refill or creates on demand without a floor.
|
|
271
271
|
*
|
|
272
272
|
* @param signal - Optional native cancellation signal
|
|
273
273
|
* @returns A promise for the unique resource lease
|
|
@@ -275,9 +275,9 @@ import type { EmitterInterface } from '@orkestrel/emitter';
|
|
|
275
275
|
* with `code: 'invalid'`. This throw is synchronous rather than a rejected promise, so a caller
|
|
276
276
|
* that handles failures with `.catch()` alone misses it.
|
|
277
277
|
* @throws {@link PoolError} Thrown as a rejection when `destroy()` has already begun, with
|
|
278
|
-
* `code: 'destroyed'`; when the create hook fails or the floor is spent without an
|
|
278
|
+
* `code: 'destroyed'`; when the create hook fails or the floor is spent without an eligible record,
|
|
279
279
|
* with `code: 'create'` and the last cause; and when an invalid record's cleanup fails or
|
|
280
|
-
* a floor retains a record, owns `min` records, has nothing
|
|
280
|
+
* a floor retains a record, owns `min` records, has nothing eligible, and has no refill or disposal
|
|
281
281
|
* pending, even with live leases, with `code: 'cleanup'` and the retained cleanup failure as cause.
|
|
282
282
|
* A `signal` that aborts rejects with the caller's exact `signal.reason` instead.
|
|
283
283
|
*/
|
|
@@ -309,13 +309,25 @@ import type { EmitterInterface } from '@orkestrel/emitter';
|
|
|
309
309
|
*
|
|
310
310
|
* @remarks
|
|
311
311
|
* `create` produces resources on demand, or only to restore `min` after `start()`.
|
|
312
|
-
* `destroy` tears down a claimed resource. `validate` checks
|
|
312
|
+
* `destroy` tears down a claimed resource. `validate` checks only idle records before reuse;
|
|
313
|
+
* additional leases on occupied records skip validation, so validation cannot dispose co-holders.
|
|
314
|
+
* With `capacity` greater than 1, an occupied record is never revalidated. Only `watch` detects
|
|
315
|
+
* its loss: without `watch`, a dead record can keep receiving leases until it becomes idle;
|
|
316
|
+
* with `watch`, that exposure lasts until the watch settles.
|
|
317
|
+
* `capacity` bounds simultaneous leases per record, including pending handouts, and is a positive
|
|
318
|
+
* safe integer. Default: 1. Selection prefers the least occupied eligible record, with idle ties
|
|
319
|
+
* in release order and occupied ties in creation order. Reservations precede awaited hooks;
|
|
320
|
+
* waiter settlement stays FIFO.
|
|
313
321
|
* `min` and `max` are positive safe integers and must be equal when both are set; `max`
|
|
314
|
-
* defaults to `min`. Omitting both leaves
|
|
322
|
+
* defaults to `min`. Omitting both leaves the record count unbounded. `restarts` is required with
|
|
315
323
|
* `min`, is refused without `min`, has no default, and is a non-negative safe integer.
|
|
316
|
-
* A failed refill or loss of a
|
|
317
|
-
* strikes
|
|
318
|
-
*
|
|
324
|
+
* A failed refill or loss of a record with no live lease adds a strike, even after prior use.
|
|
325
|
+
* A lease grant resets the strikes only when its record was created after the last strike;
|
|
326
|
+
* creation alone does not.
|
|
327
|
+
* Exceeding `restarts` stops ordinary refills until `start()` or a qualifying grant.
|
|
328
|
+
* Loss or one lease's `destroy()` invalidates every lease on that record with one disposal.
|
|
329
|
+
* Each lost leased record earns one refill attempt after successful disposal, even when
|
|
330
|
+
* the bound is spent. That credit does not reset strikes; a failed attempt adds a strike.
|
|
319
331
|
* `watch` settles on loss and receives a signal aborted when disposal begins. Its rejection
|
|
320
332
|
* reaches `error` with event `watch` only while the record is live. Any settlement after
|
|
321
333
|
* the signal aborts is ignored. `on` installs initial emitter listeners; `error`
|
|
@@ -328,6 +340,7 @@ import type { EmitterInterface } from '@orkestrel/emitter';
|
|
|
328
340
|
readonly destroy?: (value: T) => Promise<void> | void;
|
|
329
341
|
readonly validate?: (value: T) => Promise<boolean> | boolean;
|
|
330
342
|
readonly watch?: (value: T, signal: AbortSignal) => Promise<unknown>;
|
|
343
|
+
readonly capacity?: number;
|
|
331
344
|
readonly max?: number;
|
|
332
345
|
readonly min?: number;
|
|
333
346
|
readonly restarts?: number;
|
|
@@ -341,13 +354,13 @@ import type { EmitterInterface } from '@orkestrel/emitter';
|
|
|
341
354
|
/** Holds the leased value. Duplicate values still belong to independent records. */
|
|
342
355
|
readonly value: T;
|
|
343
356
|
/**
|
|
344
|
-
*
|
|
345
|
-
*
|
|
357
|
+
* Ends this exact lease once; a repeat call, and a call after loss or teardown took ownership,
|
|
358
|
+
* are no-ops. Other leases on the record remain live.
|
|
346
359
|
*/
|
|
347
360
|
release(): void;
|
|
348
361
|
/**
|
|
349
|
-
* Destroys this exact record
|
|
350
|
-
* are no-ops.
|
|
362
|
+
* Destroys this exact record for every co-holder and invalidates their leases; a repeat call,
|
|
363
|
+
* and a call after release, are no-ops.
|
|
351
364
|
*
|
|
352
365
|
* @returns A promise for this record's cleanup attempt, including an attempt already in progress
|
|
353
366
|
* @throws {@link PoolError} Thrown as a rejection with `code: 'cleanup'` when disposal fails.
|
package/dist/src/core/index.js
CHANGED
|
@@ -56,18 +56,18 @@ function isPoolError(value) {
|
|
|
56
56
|
//#endregion
|
|
57
57
|
//#region src/core/validators.ts
|
|
58
58
|
/**
|
|
59
|
-
* Tests whether a value is a positive safe integer
|
|
59
|
+
* Tests whether a value is a positive safe integer for a pool record or lease limit.
|
|
60
60
|
*
|
|
61
|
-
* @param value - The unknown
|
|
61
|
+
* @param value - The unknown record or lease limit candidate
|
|
62
62
|
* @returns True if the value is a positive safe integer; false otherwise
|
|
63
63
|
*
|
|
64
64
|
* @example
|
|
65
65
|
* ```ts
|
|
66
|
-
*
|
|
67
|
-
*
|
|
66
|
+
* isPoolLimit(8) // true
|
|
67
|
+
* isPoolLimit(Infinity) // false
|
|
68
68
|
* ```
|
|
69
69
|
*/
|
|
70
|
-
function
|
|
70
|
+
function isPoolLimit(value) {
|
|
71
71
|
return isNumber(value) && Number.isSafeInteger(value) && value > 0;
|
|
72
72
|
}
|
|
73
73
|
/**
|
|
@@ -119,12 +119,13 @@ var Pool = class {
|
|
|
119
119
|
#destroy;
|
|
120
120
|
#validate;
|
|
121
121
|
#max;
|
|
122
|
+
#capacity;
|
|
122
123
|
#min;
|
|
123
124
|
#restarts;
|
|
124
125
|
#watch;
|
|
125
126
|
#error;
|
|
126
127
|
#watches = /* @__PURE__ */ new Map();
|
|
127
|
-
#
|
|
128
|
+
#births = /* @__PURE__ */ new WeakMap();
|
|
128
129
|
#settled = /* @__PURE__ */ new WeakSet();
|
|
129
130
|
#survivors = /* @__PURE__ */ new Map();
|
|
130
131
|
#owing = /* @__PURE__ */ new Set();
|
|
@@ -132,7 +133,7 @@ var Pool = class {
|
|
|
132
133
|
#resources = /* @__PURE__ */ new Map();
|
|
133
134
|
#available = [];
|
|
134
135
|
#validating = /* @__PURE__ */ new Set();
|
|
135
|
-
#leased = /* @__PURE__ */ new
|
|
136
|
+
#leased = /* @__PURE__ */ new Map();
|
|
136
137
|
#destroying = /* @__PURE__ */ new Map();
|
|
137
138
|
#waiters = [];
|
|
138
139
|
#assigned = /* @__PURE__ */ new Set();
|
|
@@ -149,26 +150,32 @@ var Pool = class {
|
|
|
149
150
|
#starting;
|
|
150
151
|
#refilling = false;
|
|
151
152
|
#strikes = 0;
|
|
153
|
+
#epoch = {};
|
|
152
154
|
#owed = 0;
|
|
153
155
|
#cause;
|
|
154
156
|
/**
|
|
155
157
|
* Constructs a pool and synchronously validates its capacity contract.
|
|
156
158
|
*
|
|
157
159
|
* @param options - Resource hooks, observation hooks, capacity, and optional bounded warm floor
|
|
158
|
-
* @throws {@link PoolError} Thrown with `code: 'invalid'` when `min
|
|
159
|
-
* safe integer,
|
|
160
|
+
* @throws {@link PoolError} Thrown with `code: 'invalid'` when `capacity`, `min`, or `max` is not a positive
|
|
161
|
+
* safe integer, explicit `min` and `max` differ, `restarts` is absent with `min`, present without
|
|
160
162
|
* `min`, or not a non-negative safe integer, or `watch` is not callable.
|
|
161
163
|
*/
|
|
162
164
|
constructor(options) {
|
|
163
165
|
const max = options.max;
|
|
166
|
+
const capacity = options.capacity;
|
|
164
167
|
const min = options.min;
|
|
165
168
|
const restarts = options.restarts;
|
|
166
169
|
const watch = options.watch;
|
|
167
|
-
if (max !== void 0 && !
|
|
170
|
+
if (max !== void 0 && !isPoolLimit(max)) throw new PoolError({
|
|
168
171
|
code: "invalid",
|
|
169
172
|
context: { value: max }
|
|
170
173
|
});
|
|
171
|
-
if (
|
|
174
|
+
if (capacity !== void 0 && !isPoolLimit(capacity)) throw new PoolError({
|
|
175
|
+
code: "invalid",
|
|
176
|
+
context: { value: capacity }
|
|
177
|
+
});
|
|
178
|
+
if (min !== void 0 && (!isPoolLimit(min) || max !== void 0 && min !== max)) throw new PoolError({
|
|
172
179
|
code: "invalid",
|
|
173
180
|
context: { value: min }
|
|
174
181
|
});
|
|
@@ -186,6 +193,7 @@ var Pool = class {
|
|
|
186
193
|
this.#destroy = options.destroy;
|
|
187
194
|
this.#validate = options.validate;
|
|
188
195
|
this.#max = max ?? min;
|
|
196
|
+
this.#capacity = capacity ?? 1;
|
|
189
197
|
this.#min = min;
|
|
190
198
|
this.#restarts = restarts;
|
|
191
199
|
this.#watch = watch;
|
|
@@ -203,11 +211,11 @@ var Pool = class {
|
|
|
203
211
|
get size() {
|
|
204
212
|
return this.#resources.size;
|
|
205
213
|
}
|
|
206
|
-
/** Counts
|
|
214
|
+
/** Counts available records with no live lease or pending handout. */
|
|
207
215
|
get idle() {
|
|
208
216
|
return this.#available.length;
|
|
209
217
|
}
|
|
210
|
-
/** Counts
|
|
218
|
+
/** Counts records with live leases, counting a shared record once rather than counting its leases. */
|
|
211
219
|
get active() {
|
|
212
220
|
return this.#leased.size;
|
|
213
221
|
}
|
|
@@ -231,8 +239,8 @@ var Pool = class {
|
|
|
231
239
|
return starting.promise;
|
|
232
240
|
}
|
|
233
241
|
/**
|
|
234
|
-
* Queues the caller in FIFO order,
|
|
235
|
-
* creates on demand
|
|
242
|
+
* Queues the caller in FIFO order, shares an occupied record or validates an idle record, and
|
|
243
|
+
* waits for a floor refill or creates on demand without a floor.
|
|
236
244
|
*
|
|
237
245
|
* @param signal - Optional native cancellation signal
|
|
238
246
|
* @returns A promise for the unique resource lease
|
|
@@ -240,9 +248,9 @@ var Pool = class {
|
|
|
240
248
|
* with `code: 'invalid'`. This throw is synchronous rather than a rejected promise, so a caller
|
|
241
249
|
* that handles failures with `.catch()` alone misses it.
|
|
242
250
|
* @throws {@link PoolError} Thrown as a rejection when `destroy()` has already begun, with
|
|
243
|
-
* `code: 'destroyed'`; when the create hook fails or the floor is spent without an
|
|
251
|
+
* `code: 'destroyed'`; when the create hook fails or the floor is spent without an eligible record,
|
|
244
252
|
* with `code: 'create'` and the last cause; and when an invalid record's cleanup fails or
|
|
245
|
-
* a floor retains a record, owns `min` records, has nothing
|
|
253
|
+
* a floor retains a record, owns `min` records, has nothing eligible, and has no refill or disposal
|
|
246
254
|
* pending, even with live leases, with `code: 'cleanup'` and the retained cleanup failure as cause.
|
|
247
255
|
* A `signal` that aborts rejects with the caller's exact `signal.reason` instead.
|
|
248
256
|
*/
|
|
@@ -347,8 +355,7 @@ var Pool = class {
|
|
|
347
355
|
});
|
|
348
356
|
}
|
|
349
357
|
}
|
|
350
|
-
#token(record, value) {
|
|
351
|
-
const lease = {};
|
|
358
|
+
#token(record, value, lease) {
|
|
352
359
|
return {
|
|
353
360
|
value,
|
|
354
361
|
release: this.#createRelease(record, lease),
|
|
@@ -359,7 +366,7 @@ var Pool = class {
|
|
|
359
366
|
return () => {
|
|
360
367
|
if (this.#settled.has(lease)) return;
|
|
361
368
|
this.#settled.add(lease);
|
|
362
|
-
this.#release(record);
|
|
369
|
+
this.#release(record, lease);
|
|
363
370
|
};
|
|
364
371
|
}
|
|
365
372
|
#createDestroy(record, lease) {
|
|
@@ -404,10 +411,12 @@ var Pool = class {
|
|
|
404
411
|
this.#repump = false;
|
|
405
412
|
for (const waiter of this.#waiters) {
|
|
406
413
|
if (this.#assigned.has(waiter) || this.#ready.has(waiter)) continue;
|
|
407
|
-
const record = this.#
|
|
414
|
+
const record = this.#select();
|
|
408
415
|
if (record !== void 0) {
|
|
409
416
|
this.#assigned.add(waiter);
|
|
410
|
-
this.#
|
|
417
|
+
const index = this.#available.indexOf(record);
|
|
418
|
+
if (index >= 0) this.#available.splice(index, 1);
|
|
419
|
+
if (this.#occupancy(record) === 0) this.#validating.add(record);
|
|
411
420
|
this.#startValidation(waiter, record);
|
|
412
421
|
continue;
|
|
413
422
|
}
|
|
@@ -448,6 +457,25 @@ var Pool = class {
|
|
|
448
457
|
this.#pumping = false;
|
|
449
458
|
this.#fill();
|
|
450
459
|
}
|
|
460
|
+
#occupancy(record) {
|
|
461
|
+
let count = this.#leased.get(record)?.size ?? 0;
|
|
462
|
+
for (const result of this.#ready.values()) if (result.success && result.record === record) count += 1;
|
|
463
|
+
return count;
|
|
464
|
+
}
|
|
465
|
+
#select() {
|
|
466
|
+
let selected;
|
|
467
|
+
let least = this.#capacity;
|
|
468
|
+
for (const record of this.#resources.keys()) {
|
|
469
|
+
if (this.#validating.has(record) || this.#destroying.has(record) || this.#survivors.has(record)) continue;
|
|
470
|
+
const count = this.#occupancy(record);
|
|
471
|
+
if (count === 0 && !this.#available.includes(record)) continue;
|
|
472
|
+
if (count < least) {
|
|
473
|
+
selected = record;
|
|
474
|
+
least = count;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
return this.#available[0] ?? selected;
|
|
478
|
+
}
|
|
451
479
|
#spent() {
|
|
452
480
|
return this.#restarts !== void 0 && this.#strikes > this.#restarts;
|
|
453
481
|
}
|
|
@@ -484,8 +512,7 @@ var Pool = class {
|
|
|
484
512
|
try {
|
|
485
513
|
value = await this.#create();
|
|
486
514
|
} catch (error) {
|
|
487
|
-
this.#
|
|
488
|
-
this.#cause = error;
|
|
515
|
+
this.#addStrike(error);
|
|
489
516
|
return;
|
|
490
517
|
}
|
|
491
518
|
const record = this.#insert(value);
|
|
@@ -493,6 +520,7 @@ var Pool = class {
|
|
|
493
520
|
}
|
|
494
521
|
#insert(value) {
|
|
495
522
|
const record = {};
|
|
523
|
+
this.#births.set(record, this.#epoch);
|
|
496
524
|
this.#resources.set(record, value);
|
|
497
525
|
if (this.#watch !== void 0) {
|
|
498
526
|
const controller = new AbortController();
|
|
@@ -520,10 +548,12 @@ var Pool = class {
|
|
|
520
548
|
this.#pump();
|
|
521
549
|
}
|
|
522
550
|
#strike(record, cause) {
|
|
523
|
-
if (this.#min !== void 0 && !this.#
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
551
|
+
if (this.#min !== void 0 && !this.#leased.has(record)) this.#addStrike(cause);
|
|
552
|
+
}
|
|
553
|
+
#addStrike(cause) {
|
|
554
|
+
this.#strikes += 1;
|
|
555
|
+
this.#cause = cause;
|
|
556
|
+
this.#epoch = {};
|
|
527
557
|
}
|
|
528
558
|
#startCreate(waiter) {
|
|
529
559
|
const operation = Promise.resolve().then(() => this.#createResource(waiter));
|
|
@@ -533,7 +563,7 @@ var Pool = class {
|
|
|
533
563
|
#startValidation(waiter, record) {
|
|
534
564
|
for (const [owned, value] of this.#resources) {
|
|
535
565
|
if (owned !== record) continue;
|
|
536
|
-
if (this.#validate === void 0) {
|
|
566
|
+
if (!this.#validating.has(record) || this.#validate === void 0) {
|
|
537
567
|
this.#validating.delete(record);
|
|
538
568
|
this.#prepare(waiter, record, value);
|
|
539
569
|
return;
|
|
@@ -581,7 +611,7 @@ var Pool = class {
|
|
|
581
611
|
this.#ready.set(waiter, {
|
|
582
612
|
success: true,
|
|
583
613
|
record,
|
|
584
|
-
token: this.#token(record, value)
|
|
614
|
+
token: this.#token(record, value, waiter)
|
|
585
615
|
});
|
|
586
616
|
this.#commit();
|
|
587
617
|
}
|
|
@@ -649,7 +679,7 @@ var Pool = class {
|
|
|
649
679
|
this.#ready.set(waiter, {
|
|
650
680
|
success: true,
|
|
651
681
|
record,
|
|
652
|
-
token: this.#token(record, value)
|
|
682
|
+
token: this.#token(record, value, waiter)
|
|
653
683
|
});
|
|
654
684
|
this.#commit();
|
|
655
685
|
}
|
|
@@ -674,9 +704,10 @@ var Pool = class {
|
|
|
674
704
|
waiter.reject(result.error);
|
|
675
705
|
continue;
|
|
676
706
|
}
|
|
677
|
-
this.#leased.
|
|
678
|
-
|
|
679
|
-
this.#
|
|
707
|
+
const leases = this.#leased.get(result.record) ?? /* @__PURE__ */ new Set();
|
|
708
|
+
leases.add(waiter);
|
|
709
|
+
this.#leased.set(result.record, leases);
|
|
710
|
+
if (this.#births.get(result.record) === this.#epoch) this.#strikes = 0;
|
|
680
711
|
waiter.resolve(result.token);
|
|
681
712
|
this.#emitter.emit("acquire");
|
|
682
713
|
}
|
|
@@ -702,8 +733,10 @@ var Pool = class {
|
|
|
702
733
|
this.#pump();
|
|
703
734
|
this.#finish();
|
|
704
735
|
}
|
|
705
|
-
#release(record) {
|
|
706
|
-
|
|
736
|
+
#release(record, lease) {
|
|
737
|
+
const leases = this.#leased.get(record);
|
|
738
|
+
if (leases === void 0 || !leases.delete(lease)) return;
|
|
739
|
+
if (leases.size === 0) this.#leased.delete(record);
|
|
707
740
|
this.#recycle(record);
|
|
708
741
|
}
|
|
709
742
|
#recycle(record) {
|
|
@@ -712,7 +745,7 @@ var Pool = class {
|
|
|
712
745
|
this.#own(this.#dispose(record));
|
|
713
746
|
return;
|
|
714
747
|
}
|
|
715
|
-
this.#available.push(record);
|
|
748
|
+
if (this.#occupancy(record) === 0 && !this.#validating.has(record) && !this.#available.includes(record)) this.#available.push(record);
|
|
716
749
|
this.#pump();
|
|
717
750
|
if (this.#available.includes(record)) this.#emitter.emit("release");
|
|
718
751
|
}
|
|
@@ -816,7 +849,7 @@ var Pool = class {
|
|
|
816
849
|
//#region src/core/factories.ts
|
|
817
850
|
/**
|
|
818
851
|
* Creates a distinct {@link PoolInterface} from resource lifecycle hooks, with optional bounded
|
|
819
|
-
* capacity,
|
|
852
|
+
* capacity, exclusive leases by default, and FIFO settlement.
|
|
820
853
|
*
|
|
821
854
|
* @remarks
|
|
822
855
|
* Concurrent create and validation hooks may overlap, while acquire promises settle in
|
|
@@ -856,6 +889,6 @@ function createPool(options) {
|
|
|
856
889
|
return new Pool(options);
|
|
857
890
|
}
|
|
858
891
|
//#endregion
|
|
859
|
-
export { Pool, PoolError, createPool, isPoolError,
|
|
892
|
+
export { Pool, PoolError, createPool, isPoolError, isPoolLimit, isPoolSignal };
|
|
860
893
|
|
|
861
894
|
//# sourceMappingURL=index.js.map
|