@orkestrel/pool 0.0.15 → 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 +62 -33
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +39 -28
- package/dist/src/core/index.d.ts +39 -28
- package/dist/src/core/index.js +62 -33
- package/dist/src/core/index.js.map +1 -1
- package/package.json +4 -4
|
@@ -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,23 @@ 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 only when its record was created after the last strike;
|
|
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.
|
|
318
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.
|
|
319
329
|
* Each lost leased record earns one refill attempt after successful disposal, even when
|
|
320
330
|
* the bound is spent. That credit does not reset strikes; a failed attempt adds a strike.
|
|
321
331
|
* `watch` settles on loss and receives a signal aborted when disposal begins. Its rejection
|
|
@@ -330,6 +340,7 @@ import type { EmitterInterface } from '@orkestrel/emitter';
|
|
|
330
340
|
readonly destroy?: (value: T) => Promise<void> | void;
|
|
331
341
|
readonly validate?: (value: T) => Promise<boolean> | boolean;
|
|
332
342
|
readonly watch?: (value: T, signal: AbortSignal) => Promise<unknown>;
|
|
343
|
+
readonly capacity?: number;
|
|
333
344
|
readonly max?: number;
|
|
334
345
|
readonly min?: number;
|
|
335
346
|
readonly restarts?: number;
|
|
@@ -343,13 +354,13 @@ import type { EmitterInterface } from '@orkestrel/emitter';
|
|
|
343
354
|
/** Holds the leased value. Duplicate values still belong to independent records. */
|
|
344
355
|
readonly value: T;
|
|
345
356
|
/**
|
|
346
|
-
*
|
|
347
|
-
*
|
|
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.
|
|
348
359
|
*/
|
|
349
360
|
release(): void;
|
|
350
361
|
/**
|
|
351
|
-
* Destroys this exact record
|
|
352
|
-
* 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.
|
|
353
364
|
*
|
|
354
365
|
* @returns A promise for this record's cleanup attempt, including an attempt already in progress
|
|
355
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,23 @@ 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 only when its record was created after the last strike;
|
|
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.
|
|
318
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.
|
|
319
329
|
* Each lost leased record earns one refill attempt after successful disposal, even when
|
|
320
330
|
* the bound is spent. That credit does not reset strikes; a failed attempt adds a strike.
|
|
321
331
|
* `watch` settles on loss and receives a signal aborted when disposal begins. Its rejection
|
|
@@ -330,6 +340,7 @@ import type { EmitterInterface } from '@orkestrel/emitter';
|
|
|
330
340
|
readonly destroy?: (value: T) => Promise<void> | void;
|
|
331
341
|
readonly validate?: (value: T) => Promise<boolean> | boolean;
|
|
332
342
|
readonly watch?: (value: T, signal: AbortSignal) => Promise<unknown>;
|
|
343
|
+
readonly capacity?: number;
|
|
333
344
|
readonly max?: number;
|
|
334
345
|
readonly min?: number;
|
|
335
346
|
readonly restarts?: number;
|
|
@@ -343,13 +354,13 @@ import type { EmitterInterface } from '@orkestrel/emitter';
|
|
|
343
354
|
/** Holds the leased value. Duplicate values still belong to independent records. */
|
|
344
355
|
readonly value: T;
|
|
345
356
|
/**
|
|
346
|
-
*
|
|
347
|
-
*
|
|
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.
|
|
348
359
|
*/
|
|
349
360
|
release(): void;
|
|
350
361
|
/**
|
|
351
|
-
* Destroys this exact record
|
|
352
|
-
* 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.
|
|
353
364
|
*
|
|
354
365
|
* @returns A promise for this record's cleanup attempt, including an attempt already in progress
|
|
355
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,12 @@ 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
|
-
#used = /* @__PURE__ */ new WeakSet();
|
|
128
128
|
#births = /* @__PURE__ */ new WeakMap();
|
|
129
129
|
#settled = /* @__PURE__ */ new WeakSet();
|
|
130
130
|
#survivors = /* @__PURE__ */ new Map();
|
|
@@ -133,7 +133,7 @@ var Pool = class {
|
|
|
133
133
|
#resources = /* @__PURE__ */ new Map();
|
|
134
134
|
#available = [];
|
|
135
135
|
#validating = /* @__PURE__ */ new Set();
|
|
136
|
-
#leased = /* @__PURE__ */ new
|
|
136
|
+
#leased = /* @__PURE__ */ new Map();
|
|
137
137
|
#destroying = /* @__PURE__ */ new Map();
|
|
138
138
|
#waiters = [];
|
|
139
139
|
#assigned = /* @__PURE__ */ new Set();
|
|
@@ -157,20 +157,25 @@ var Pool = class {
|
|
|
157
157
|
* Constructs a pool and synchronously validates its capacity contract.
|
|
158
158
|
*
|
|
159
159
|
* @param options - Resource hooks, observation hooks, capacity, and optional bounded warm floor
|
|
160
|
-
* @throws {@link PoolError} Thrown with `code: 'invalid'` when `min
|
|
161
|
-
* 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
|
|
162
162
|
* `min`, or not a non-negative safe integer, or `watch` is not callable.
|
|
163
163
|
*/
|
|
164
164
|
constructor(options) {
|
|
165
165
|
const max = options.max;
|
|
166
|
+
const capacity = options.capacity;
|
|
166
167
|
const min = options.min;
|
|
167
168
|
const restarts = options.restarts;
|
|
168
169
|
const watch = options.watch;
|
|
169
|
-
if (max !== void 0 && !
|
|
170
|
+
if (max !== void 0 && !isPoolLimit(max)) throw new PoolError({
|
|
170
171
|
code: "invalid",
|
|
171
172
|
context: { value: max }
|
|
172
173
|
});
|
|
173
|
-
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({
|
|
174
179
|
code: "invalid",
|
|
175
180
|
context: { value: min }
|
|
176
181
|
});
|
|
@@ -188,6 +193,7 @@ var Pool = class {
|
|
|
188
193
|
this.#destroy = options.destroy;
|
|
189
194
|
this.#validate = options.validate;
|
|
190
195
|
this.#max = max ?? min;
|
|
196
|
+
this.#capacity = capacity ?? 1;
|
|
191
197
|
this.#min = min;
|
|
192
198
|
this.#restarts = restarts;
|
|
193
199
|
this.#watch = watch;
|
|
@@ -205,11 +211,11 @@ var Pool = class {
|
|
|
205
211
|
get size() {
|
|
206
212
|
return this.#resources.size;
|
|
207
213
|
}
|
|
208
|
-
/** Counts
|
|
214
|
+
/** Counts available records with no live lease or pending handout. */
|
|
209
215
|
get idle() {
|
|
210
216
|
return this.#available.length;
|
|
211
217
|
}
|
|
212
|
-
/** Counts
|
|
218
|
+
/** Counts records with live leases, counting a shared record once rather than counting its leases. */
|
|
213
219
|
get active() {
|
|
214
220
|
return this.#leased.size;
|
|
215
221
|
}
|
|
@@ -233,8 +239,8 @@ var Pool = class {
|
|
|
233
239
|
return starting.promise;
|
|
234
240
|
}
|
|
235
241
|
/**
|
|
236
|
-
* Queues the caller in FIFO order,
|
|
237
|
-
* 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.
|
|
238
244
|
*
|
|
239
245
|
* @param signal - Optional native cancellation signal
|
|
240
246
|
* @returns A promise for the unique resource lease
|
|
@@ -242,9 +248,9 @@ var Pool = class {
|
|
|
242
248
|
* with `code: 'invalid'`. This throw is synchronous rather than a rejected promise, so a caller
|
|
243
249
|
* that handles failures with `.catch()` alone misses it.
|
|
244
250
|
* @throws {@link PoolError} Thrown as a rejection when `destroy()` has already begun, with
|
|
245
|
-
* `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,
|
|
246
252
|
* with `code: 'create'` and the last cause; and when an invalid record's cleanup fails or
|
|
247
|
-
* 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
|
|
248
254
|
* pending, even with live leases, with `code: 'cleanup'` and the retained cleanup failure as cause.
|
|
249
255
|
* A `signal` that aborts rejects with the caller's exact `signal.reason` instead.
|
|
250
256
|
*/
|
|
@@ -349,8 +355,7 @@ var Pool = class {
|
|
|
349
355
|
});
|
|
350
356
|
}
|
|
351
357
|
}
|
|
352
|
-
#token(record, value) {
|
|
353
|
-
const lease = {};
|
|
358
|
+
#token(record, value, lease) {
|
|
354
359
|
return {
|
|
355
360
|
value,
|
|
356
361
|
release: this.#createRelease(record, lease),
|
|
@@ -361,7 +366,7 @@ var Pool = class {
|
|
|
361
366
|
return () => {
|
|
362
367
|
if (this.#settled.has(lease)) return;
|
|
363
368
|
this.#settled.add(lease);
|
|
364
|
-
this.#release(record);
|
|
369
|
+
this.#release(record, lease);
|
|
365
370
|
};
|
|
366
371
|
}
|
|
367
372
|
#createDestroy(record, lease) {
|
|
@@ -406,10 +411,12 @@ var Pool = class {
|
|
|
406
411
|
this.#repump = false;
|
|
407
412
|
for (const waiter of this.#waiters) {
|
|
408
413
|
if (this.#assigned.has(waiter) || this.#ready.has(waiter)) continue;
|
|
409
|
-
const record = this.#
|
|
414
|
+
const record = this.#select();
|
|
410
415
|
if (record !== void 0) {
|
|
411
416
|
this.#assigned.add(waiter);
|
|
412
|
-
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);
|
|
413
420
|
this.#startValidation(waiter, record);
|
|
414
421
|
continue;
|
|
415
422
|
}
|
|
@@ -450,6 +457,25 @@ var Pool = class {
|
|
|
450
457
|
this.#pumping = false;
|
|
451
458
|
this.#fill();
|
|
452
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
|
+
}
|
|
453
479
|
#spent() {
|
|
454
480
|
return this.#restarts !== void 0 && this.#strikes > this.#restarts;
|
|
455
481
|
}
|
|
@@ -522,7 +548,7 @@ var Pool = class {
|
|
|
522
548
|
this.#pump();
|
|
523
549
|
}
|
|
524
550
|
#strike(record, cause) {
|
|
525
|
-
if (this.#min !== void 0 && !this.#
|
|
551
|
+
if (this.#min !== void 0 && !this.#leased.has(record)) this.#addStrike(cause);
|
|
526
552
|
}
|
|
527
553
|
#addStrike(cause) {
|
|
528
554
|
this.#strikes += 1;
|
|
@@ -537,7 +563,7 @@ var Pool = class {
|
|
|
537
563
|
#startValidation(waiter, record) {
|
|
538
564
|
for (const [owned, value] of this.#resources) {
|
|
539
565
|
if (owned !== record) continue;
|
|
540
|
-
if (this.#validate === void 0) {
|
|
566
|
+
if (!this.#validating.has(record) || this.#validate === void 0) {
|
|
541
567
|
this.#validating.delete(record);
|
|
542
568
|
this.#prepare(waiter, record, value);
|
|
543
569
|
return;
|
|
@@ -585,7 +611,7 @@ var Pool = class {
|
|
|
585
611
|
this.#ready.set(waiter, {
|
|
586
612
|
success: true,
|
|
587
613
|
record,
|
|
588
|
-
token: this.#token(record, value)
|
|
614
|
+
token: this.#token(record, value, waiter)
|
|
589
615
|
});
|
|
590
616
|
this.#commit();
|
|
591
617
|
}
|
|
@@ -653,7 +679,7 @@ var Pool = class {
|
|
|
653
679
|
this.#ready.set(waiter, {
|
|
654
680
|
success: true,
|
|
655
681
|
record,
|
|
656
|
-
token: this.#token(record, value)
|
|
682
|
+
token: this.#token(record, value, waiter)
|
|
657
683
|
});
|
|
658
684
|
this.#commit();
|
|
659
685
|
}
|
|
@@ -678,8 +704,9 @@ var Pool = class {
|
|
|
678
704
|
waiter.reject(result.error);
|
|
679
705
|
continue;
|
|
680
706
|
}
|
|
681
|
-
this.#leased.
|
|
682
|
-
|
|
707
|
+
const leases = this.#leased.get(result.record) ?? /* @__PURE__ */ new Set();
|
|
708
|
+
leases.add(waiter);
|
|
709
|
+
this.#leased.set(result.record, leases);
|
|
683
710
|
if (this.#births.get(result.record) === this.#epoch) this.#strikes = 0;
|
|
684
711
|
waiter.resolve(result.token);
|
|
685
712
|
this.#emitter.emit("acquire");
|
|
@@ -706,8 +733,10 @@ var Pool = class {
|
|
|
706
733
|
this.#pump();
|
|
707
734
|
this.#finish();
|
|
708
735
|
}
|
|
709
|
-
#release(record) {
|
|
710
|
-
|
|
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);
|
|
711
740
|
this.#recycle(record);
|
|
712
741
|
}
|
|
713
742
|
#recycle(record) {
|
|
@@ -716,7 +745,7 @@ var Pool = class {
|
|
|
716
745
|
this.#own(this.#dispose(record));
|
|
717
746
|
return;
|
|
718
747
|
}
|
|
719
|
-
this.#available.push(record);
|
|
748
|
+
if (this.#occupancy(record) === 0 && !this.#validating.has(record) && !this.#available.includes(record)) this.#available.push(record);
|
|
720
749
|
this.#pump();
|
|
721
750
|
if (this.#available.includes(record)) this.#emitter.emit("release");
|
|
722
751
|
}
|
|
@@ -820,7 +849,7 @@ var Pool = class {
|
|
|
820
849
|
//#region src/core/factories.ts
|
|
821
850
|
/**
|
|
822
851
|
* Creates a distinct {@link PoolInterface} from resource lifecycle hooks, with optional bounded
|
|
823
|
-
* capacity,
|
|
852
|
+
* capacity, exclusive leases by default, and FIFO settlement.
|
|
824
853
|
*
|
|
825
854
|
* @remarks
|
|
826
855
|
* Concurrent create and validation hooks may overlap, while acquire promises settle in
|
|
@@ -860,6 +889,6 @@ function createPool(options) {
|
|
|
860
889
|
return new Pool(options);
|
|
861
890
|
}
|
|
862
891
|
//#endregion
|
|
863
|
-
export { Pool, PoolError, createPool, isPoolError,
|
|
892
|
+
export { Pool, PoolError, createPool, isPoolError, isPoolLimit, isPoolSignal };
|
|
864
893
|
|
|
865
894
|
//# sourceMappingURL=index.js.map
|