@orkestrel/pool 0.0.12 → 0.0.14
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 +8 -3
- package/dist/src/core/index.cjs +237 -35
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +233 -187
- package/dist/src/core/index.d.ts +233 -187
- package/dist/src/core/index.js +238 -36
- package/dist/src/core/index.js.map +1 -1
- package/package.json +13 -13
package/README.md
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
# @orkestrel/pool
|
|
2
2
|
|
|
3
|
-
> A typed resource pool with optional bounded capacity,
|
|
4
|
-
> validated reuse, caller-owned cancellation, explicit cleanup
|
|
5
|
-
> event-driven teardown barrier.
|
|
3
|
+
> A typed resource pool with optional bounded capacity, a warm floor, bounded loss recovery,
|
|
4
|
+
> unique ownership, FIFO settlement, validated reuse, caller-owned cancellation, and explicit cleanup.
|
|
6
5
|
|
|
7
6
|
Create a pool with the `createPool` function, hand it the hooks that make, check, and tear
|
|
8
7
|
down one resource, and `await pool.acquire()` wherever the work needs one. Release the token
|
|
@@ -10,6 +9,12 @@ in a `finally` block, and `await pool.destroy()` when the process is done with t
|
|
|
10
9
|
Environment-agnostic — no I/O, no browser or server assumptions. Part of the `@orkestrel`
|
|
11
10
|
line.
|
|
12
11
|
|
|
12
|
+
For eager resources, set `min` and the required `restarts` bound on failed refills and losses
|
|
13
|
+
of never-leased records between strike resets, then call `start()`.
|
|
14
|
+
The `watch(value, signal)` hook reports loss; a holder can also call its token's `destroy()`.
|
|
15
|
+
The pool disposes lost records before replacing them and keeps a record whose cleanup fails
|
|
16
|
+
counted against `max`.
|
|
17
|
+
|
|
13
18
|
## Install
|
|
14
19
|
|
|
15
20
|
```sh
|
package/dist/src/core/index.cjs
CHANGED
|
@@ -95,9 +95,9 @@ function isPoolSignal(value) {
|
|
|
95
95
|
//#endregion
|
|
96
96
|
//#region src/core/Pool.ts
|
|
97
97
|
/**
|
|
98
|
-
* Represents a
|
|
99
|
-
* settlement, cancellation, exact lease release, and
|
|
100
|
-
* hooks.
|
|
98
|
+
* Represents a resource pool with optional bounded capacity and a warm floor, whose opaque
|
|
99
|
+
* ownership records preserve FIFO settlement, cancellation, exact lease release, and
|
|
100
|
+
* deterministic teardown under concurrent hooks.
|
|
101
101
|
*
|
|
102
102
|
* @typeParam T - The pooled resource value
|
|
103
103
|
*
|
|
@@ -120,6 +120,15 @@ var Pool = class {
|
|
|
120
120
|
#destroy;
|
|
121
121
|
#validate;
|
|
122
122
|
#max;
|
|
123
|
+
#min;
|
|
124
|
+
#restarts;
|
|
125
|
+
#watch;
|
|
126
|
+
#error;
|
|
127
|
+
#watches = /* @__PURE__ */ new Map();
|
|
128
|
+
#used = /* @__PURE__ */ new WeakSet();
|
|
129
|
+
#settled = /* @__PURE__ */ new WeakSet();
|
|
130
|
+
#survivors = /* @__PURE__ */ new Map();
|
|
131
|
+
#owing = /* @__PURE__ */ new Set();
|
|
123
132
|
#emitter;
|
|
124
133
|
#resources = /* @__PURE__ */ new Map();
|
|
125
134
|
#available = [];
|
|
@@ -137,25 +146,51 @@ var Pool = class {
|
|
|
137
146
|
#ending;
|
|
138
147
|
#pumping = false;
|
|
139
148
|
#repump = false;
|
|
149
|
+
#started = false;
|
|
150
|
+
#starting;
|
|
151
|
+
#refilling = false;
|
|
152
|
+
#strikes = 0;
|
|
153
|
+
#owed = 0;
|
|
154
|
+
#cause;
|
|
140
155
|
/**
|
|
141
156
|
* Constructs a pool and synchronously validates its capacity contract.
|
|
142
157
|
*
|
|
143
|
-
* @param options - Resource hooks, observation hooks, and optional
|
|
144
|
-
* @throws {@link PoolError} Thrown when `
|
|
145
|
-
* integer, with `
|
|
158
|
+
* @param options - Resource hooks, observation hooks, capacity, and optional bounded warm floor
|
|
159
|
+
* @throws {@link PoolError} Thrown with `code: 'invalid'` when `min` or `max` is not a positive
|
|
160
|
+
* safe integer, their explicit values differ, `restarts` is absent with `min`, present without
|
|
161
|
+
* `min`, or not a non-negative safe integer, or `watch` is not callable.
|
|
146
162
|
*/
|
|
147
163
|
constructor(options) {
|
|
148
164
|
const max = options.max;
|
|
165
|
+
const min = options.min;
|
|
166
|
+
const restarts = options.restarts;
|
|
167
|
+
const watch = options.watch;
|
|
149
168
|
if (max !== void 0 && !isPoolMax(max)) throw new PoolError({
|
|
150
169
|
code: "invalid",
|
|
151
170
|
context: { value: max }
|
|
152
171
|
});
|
|
172
|
+
if (min !== void 0 && (!isPoolMax(min) || max !== void 0 && min !== max)) throw new PoolError({
|
|
173
|
+
code: "invalid",
|
|
174
|
+
context: { value: min }
|
|
175
|
+
});
|
|
176
|
+
if (min !== void 0 && restarts === void 0 || restarts !== void 0 && (min === void 0 || !(0, _orkestrel_contract.isNumber)(restarts) || !Number.isSafeInteger(restarts) || restarts < 0)) throw new PoolError({
|
|
177
|
+
code: "invalid",
|
|
178
|
+
context: { value: restarts }
|
|
179
|
+
});
|
|
180
|
+
if (watch !== void 0 && !(0, _orkestrel_contract.isFunction)(watch)) throw new PoolError({
|
|
181
|
+
code: "invalid",
|
|
182
|
+
context: { value: watch }
|
|
183
|
+
});
|
|
153
184
|
const on = options.on;
|
|
154
185
|
const error = options.error;
|
|
155
186
|
this.#create = options.create;
|
|
156
187
|
this.#destroy = options.destroy;
|
|
157
188
|
this.#validate = options.validate;
|
|
158
|
-
this.#max = max;
|
|
189
|
+
this.#max = max ?? min;
|
|
190
|
+
this.#min = min;
|
|
191
|
+
this.#restarts = restarts;
|
|
192
|
+
this.#watch = watch;
|
|
193
|
+
this.#error = error;
|
|
159
194
|
this.#emitter = new _orkestrel_emitter.Emitter({
|
|
160
195
|
...on === void 0 ? {} : { on },
|
|
161
196
|
...error === void 0 ? {} : { error }
|
|
@@ -165,7 +200,7 @@ var Pool = class {
|
|
|
165
200
|
get emitter() {
|
|
166
201
|
return this.#emitter;
|
|
167
202
|
}
|
|
168
|
-
/** Counts all owned records, including records validating or
|
|
203
|
+
/** Counts all owned records, including records validating, destroying, or retained after failed cleanup. */
|
|
169
204
|
get size() {
|
|
170
205
|
return this.#resources.size;
|
|
171
206
|
}
|
|
@@ -178,7 +213,27 @@ var Pool = class {
|
|
|
178
213
|
return this.#leased.size;
|
|
179
214
|
}
|
|
180
215
|
/**
|
|
181
|
-
*
|
|
216
|
+
* Fills the warm floor and resets the strikes of a spent floor; without a floor, resolves immediately.
|
|
217
|
+
*
|
|
218
|
+
* @returns A promise that resolves when the floor owns `min` live records
|
|
219
|
+
* @throws {@link PoolError} Thrown as a rejection with `code: 'create'` and the last cause
|
|
220
|
+
* when the bound is spent, `code: 'cleanup'` when retained failed cleanup prevents filling,
|
|
221
|
+
* or `code: 'destroyed'` when teardown begins.
|
|
222
|
+
*/
|
|
223
|
+
start() {
|
|
224
|
+
if (this.#ending !== void 0) return Promise.reject(new PoolError({ code: "destroyed" }));
|
|
225
|
+
if (this.#min === void 0) return Promise.resolve();
|
|
226
|
+
if (this.#starting !== void 0) return this.#starting.promise;
|
|
227
|
+
const starting = Promise.withResolvers();
|
|
228
|
+
this.#starting = starting;
|
|
229
|
+
this.#started = true;
|
|
230
|
+
if (this.#spent()) this.#strikes = 0;
|
|
231
|
+
this.#pump();
|
|
232
|
+
return starting.promise;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Queues the caller in FIFO order, validates an idle record or waits for a floor refill, and
|
|
236
|
+
* creates on demand only without a floor.
|
|
182
237
|
*
|
|
183
238
|
* @param signal - Optional native cancellation signal
|
|
184
239
|
* @returns A promise for the unique resource lease
|
|
@@ -186,9 +241,11 @@ var Pool = class {
|
|
|
186
241
|
* with `code: 'invalid'`. This throw is synchronous rather than a rejected promise, so a caller
|
|
187
242
|
* that handles failures with `.catch()` alone misses it.
|
|
188
243
|
* @throws {@link PoolError} Thrown as a rejection when `destroy()` has already begun, with
|
|
189
|
-
* `code: 'destroyed'`; when the create hook fails
|
|
190
|
-
*
|
|
191
|
-
* `
|
|
244
|
+
* `code: 'destroyed'`; when the create hook fails or the floor is spent without an idle record,
|
|
245
|
+
* with `code: 'create'` and the last cause; and when an invalid record's cleanup fails or
|
|
246
|
+
* a floor retains a record, owns `min` records, has nothing idle, and has no refill or disposal
|
|
247
|
+
* pending, even with live leases, with `code: 'cleanup'` and the retained cleanup failure as cause.
|
|
248
|
+
* A `signal` that aborts rejects with the caller's exact `signal.reason` instead.
|
|
192
249
|
*/
|
|
193
250
|
acquire(signal) {
|
|
194
251
|
if (signal !== void 0 && !isPoolSignal(signal)) throw new PoolError({
|
|
@@ -216,7 +273,7 @@ var Pool = class {
|
|
|
216
273
|
return waiter.promise;
|
|
217
274
|
}
|
|
218
275
|
/**
|
|
219
|
-
* Destroys the records that are idle at this call's synchronous snapshot.
|
|
276
|
+
* Destroys the records that are idle at this call's synchronous snapshot and restores a started floor.
|
|
220
277
|
*
|
|
221
278
|
* @returns A promise that settles after every snapshot cleanup attempt
|
|
222
279
|
* @throws {@link PoolError} Thrown when `destroy()` has already begun, with `code: 'destroyed'`.
|
|
@@ -239,7 +296,7 @@ var Pool = class {
|
|
|
239
296
|
* Tears down the pool permanently and returns its stable completion barrier.
|
|
240
297
|
*
|
|
241
298
|
* @returns The exact promise shared by every destroy call
|
|
242
|
-
* @throws {@link PoolError} Thrown when a destroy hook
|
|
299
|
+
* @throws {@link PoolError} Thrown when a destroy hook failure was retained or occurred during teardown, with
|
|
243
300
|
* `code: 'cleanup'` and every distinct failure in `context.failures`. The barrier rejects; it
|
|
244
301
|
* never throws synchronously, and a repeat call receives the same rejected promise.
|
|
245
302
|
*/
|
|
@@ -247,6 +304,8 @@ var Pool = class {
|
|
|
247
304
|
if (this.#ending !== void 0) return this.#ending.promise;
|
|
248
305
|
const ending = Promise.withResolvers();
|
|
249
306
|
this.#ending = ending;
|
|
307
|
+
this.#starting?.reject(new PoolError({ code: "destroyed" }));
|
|
308
|
+
this.#starting = void 0;
|
|
250
309
|
const waiters = this.#waiters.splice(0);
|
|
251
310
|
for (const waiter of waiters) {
|
|
252
311
|
this.#detach(waiter);
|
|
@@ -289,18 +348,31 @@ var Pool = class {
|
|
|
289
348
|
});
|
|
290
349
|
}
|
|
291
350
|
}
|
|
292
|
-
#
|
|
293
|
-
|
|
351
|
+
#token(record, value) {
|
|
352
|
+
const lease = {};
|
|
353
|
+
return {
|
|
354
|
+
value,
|
|
355
|
+
release: this.#createRelease(record, lease),
|
|
356
|
+
destroy: this.#createDestroy(record, lease)
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
#createRelease(record, lease) {
|
|
294
360
|
return () => {
|
|
295
|
-
if (
|
|
296
|
-
|
|
361
|
+
if (this.#settled.has(lease)) return;
|
|
362
|
+
this.#settled.add(lease);
|
|
297
363
|
this.#release(record);
|
|
298
364
|
};
|
|
299
365
|
}
|
|
300
|
-
#
|
|
301
|
-
return {
|
|
302
|
-
|
|
303
|
-
|
|
366
|
+
#createDestroy(record, lease) {
|
|
367
|
+
return () => {
|
|
368
|
+
if (this.#settled.has(lease)) return Promise.resolve();
|
|
369
|
+
this.#settled.add(lease);
|
|
370
|
+
return (this.#destroying.get(record) ?? this.#lose(record)).catch((error) => {
|
|
371
|
+
throw new PoolError({
|
|
372
|
+
code: "cleanup",
|
|
373
|
+
cause: error
|
|
374
|
+
});
|
|
375
|
+
});
|
|
304
376
|
};
|
|
305
377
|
}
|
|
306
378
|
#abort(waiter, reason) {
|
|
@@ -340,6 +412,30 @@ var Pool = class {
|
|
|
340
412
|
this.#startValidation(waiter, record);
|
|
341
413
|
continue;
|
|
342
414
|
}
|
|
415
|
+
if (this.#min !== void 0) {
|
|
416
|
+
if (!this.#refilling && this.#destroying.size === 0 && this.#resources.size >= this.#min && this.#survivors.size > 0) {
|
|
417
|
+
this.#ready.set(waiter, {
|
|
418
|
+
success: false,
|
|
419
|
+
error: new PoolError({
|
|
420
|
+
code: "cleanup",
|
|
421
|
+
cause: this.#survivors.values().next().value,
|
|
422
|
+
context: { failures: [...this.#failures] }
|
|
423
|
+
})
|
|
424
|
+
});
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
if (this.#spent() && !this.#refilling && this.#destroying.size === 0 && this.#owed === 0) {
|
|
428
|
+
this.#ready.set(waiter, {
|
|
429
|
+
success: false,
|
|
430
|
+
error: new PoolError({
|
|
431
|
+
code: "create",
|
|
432
|
+
cause: this.#cause
|
|
433
|
+
})
|
|
434
|
+
});
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
break;
|
|
438
|
+
}
|
|
343
439
|
if (this.#max === void 0 || this.#resources.size + this.#reservations.size < this.#max) {
|
|
344
440
|
this.#assigned.add(waiter);
|
|
345
441
|
this.#reservations.add(waiter);
|
|
@@ -348,8 +444,87 @@ var Pool = class {
|
|
|
348
444
|
}
|
|
349
445
|
break;
|
|
350
446
|
}
|
|
447
|
+
this.#commit();
|
|
351
448
|
} while (this.#repump);
|
|
352
449
|
this.#pumping = false;
|
|
450
|
+
this.#fill();
|
|
451
|
+
}
|
|
452
|
+
#spent() {
|
|
453
|
+
return this.#restarts !== void 0 && this.#strikes > this.#restarts;
|
|
454
|
+
}
|
|
455
|
+
#fill() {
|
|
456
|
+
if (this.#ending !== void 0 || !this.#started || this.#min === void 0) return;
|
|
457
|
+
if (this.#refilling) return;
|
|
458
|
+
if (this.#resources.size - this.#destroying.size - this.#survivors.size >= this.#min) {
|
|
459
|
+
this.#starting?.resolve();
|
|
460
|
+
this.#starting = void 0;
|
|
461
|
+
}
|
|
462
|
+
if (this.#resources.size < this.#min && (!this.#spent() || this.#owed > 0)) {
|
|
463
|
+
if (this.#owed > 0) this.#owed -= 1;
|
|
464
|
+
this.#refilling = true;
|
|
465
|
+
const operation = Promise.resolve().then(() => this.#refill());
|
|
466
|
+
this.#operations.add(operation);
|
|
467
|
+
operation.then(() => {
|
|
468
|
+
this.#refilling = false;
|
|
469
|
+
this.#completeOperation(operation);
|
|
470
|
+
});
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
if (this.#destroying.size > 0) return;
|
|
474
|
+
if (this.#spent() || this.#survivors.size > 0) {
|
|
475
|
+
this.#starting?.reject(this.#spent() ? new PoolError({
|
|
476
|
+
code: "create",
|
|
477
|
+
cause: this.#cause
|
|
478
|
+
}) : this.#cleanupError(this.#failures));
|
|
479
|
+
this.#starting = void 0;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
async #refill() {
|
|
483
|
+
if (this.#ending !== void 0) return;
|
|
484
|
+
let value;
|
|
485
|
+
try {
|
|
486
|
+
value = await this.#create();
|
|
487
|
+
} catch (error) {
|
|
488
|
+
this.#strikes += 1;
|
|
489
|
+
this.#cause = error;
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
const record = this.#insert(value);
|
|
493
|
+
this.#recycle(record);
|
|
494
|
+
}
|
|
495
|
+
#insert(value) {
|
|
496
|
+
const record = {};
|
|
497
|
+
this.#resources.set(record, value);
|
|
498
|
+
if (this.#watch !== void 0) {
|
|
499
|
+
const controller = new AbortController();
|
|
500
|
+
this.#watches.set(record, controller);
|
|
501
|
+
const watching = (0, _orkestrel_contract.attempt)(() => this.#watch?.(value, controller.signal));
|
|
502
|
+
(watching.success ? Promise.resolve(watching.value) : Promise.reject(watching.error)).then(() => this.#own(this.#lose(record)), (error) => {
|
|
503
|
+
if (controller.signal.aborted) return;
|
|
504
|
+
this.#own(this.#lose(record, error));
|
|
505
|
+
(0, _orkestrel_contract.attempt)(() => this.#error?.(error, "watch"));
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
this.#emitter.emit("create");
|
|
509
|
+
return record;
|
|
510
|
+
}
|
|
511
|
+
async #lose(record, cause) {
|
|
512
|
+
if (this.#ending !== void 0 || !this.#resources.has(record) || this.#destroying.has(record) || this.#survivors.has(record)) return;
|
|
513
|
+
if (this.#leased.has(record) && this.#min !== void 0) this.#owing.add(record);
|
|
514
|
+
this.#strike(record, cause);
|
|
515
|
+
try {
|
|
516
|
+
await this.#dispose(record);
|
|
517
|
+
} catch (error) {
|
|
518
|
+
this.#pump();
|
|
519
|
+
throw error;
|
|
520
|
+
}
|
|
521
|
+
this.#pump();
|
|
522
|
+
}
|
|
523
|
+
#strike(record, cause) {
|
|
524
|
+
if (this.#min !== void 0 && !this.#used.has(record)) {
|
|
525
|
+
this.#strikes += 1;
|
|
526
|
+
this.#cause = cause;
|
|
527
|
+
}
|
|
353
528
|
}
|
|
354
529
|
#startCreate(waiter) {
|
|
355
530
|
const operation = Promise.resolve().then(() => this.#createResource(waiter));
|
|
@@ -391,9 +566,7 @@ var Pool = class {
|
|
|
391
566
|
return;
|
|
392
567
|
}
|
|
393
568
|
this.#reservations.delete(waiter);
|
|
394
|
-
const record =
|
|
395
|
-
this.#resources.set(record, value);
|
|
396
|
-
this.#emitter.emit("create");
|
|
569
|
+
const record = this.#insert(value);
|
|
397
570
|
if (this.#ending !== void 0) {
|
|
398
571
|
this.#assigned.delete(waiter);
|
|
399
572
|
try {
|
|
@@ -419,6 +592,11 @@ var Pool = class {
|
|
|
419
592
|
valid = await this.#validate?.(value) === true;
|
|
420
593
|
} catch {}
|
|
421
594
|
this.#validating.delete(record);
|
|
595
|
+
if (!this.#resources.has(record) || this.#destroying.has(record) || this.#survivors.has(record)) {
|
|
596
|
+
this.#assigned.delete(waiter);
|
|
597
|
+
this.#pump();
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
422
600
|
if (this.#ending !== void 0) {
|
|
423
601
|
this.#assigned.delete(waiter);
|
|
424
602
|
try {
|
|
@@ -426,6 +604,7 @@ var Pool = class {
|
|
|
426
604
|
} catch {}
|
|
427
605
|
return;
|
|
428
606
|
}
|
|
607
|
+
if (!valid) this.#strike(record);
|
|
429
608
|
if (!this.#waiters.includes(waiter)) {
|
|
430
609
|
this.#assigned.delete(waiter);
|
|
431
610
|
if (valid) this.#recycle(record);
|
|
@@ -481,6 +660,12 @@ var Pool = class {
|
|
|
481
660
|
if (waiter === void 0) return;
|
|
482
661
|
const result = this.#ready.get(waiter);
|
|
483
662
|
if (result === void 0) return;
|
|
663
|
+
if (result.success && (!this.#resources.has(result.record) || this.#destroying.has(result.record) || this.#survivors.has(result.record))) {
|
|
664
|
+
this.#ready.delete(waiter);
|
|
665
|
+
this.#assigned.delete(waiter);
|
|
666
|
+
this.#repump = true;
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
484
669
|
this.#waiters.shift();
|
|
485
670
|
this.#repump = true;
|
|
486
671
|
this.#ready.delete(waiter);
|
|
@@ -491,6 +676,8 @@ var Pool = class {
|
|
|
491
676
|
continue;
|
|
492
677
|
}
|
|
493
678
|
this.#leased.add(result.record);
|
|
679
|
+
this.#used.add(result.record);
|
|
680
|
+
this.#strikes = 0;
|
|
494
681
|
waiter.resolve(result.token);
|
|
495
682
|
this.#emitter.emit("acquire");
|
|
496
683
|
}
|
|
@@ -521,6 +708,7 @@ var Pool = class {
|
|
|
521
708
|
this.#recycle(record);
|
|
522
709
|
}
|
|
523
710
|
#recycle(record) {
|
|
711
|
+
if (!this.#resources.has(record) || this.#destroying.has(record) || this.#survivors.has(record)) return;
|
|
524
712
|
if (this.#ending !== void 0) {
|
|
525
713
|
this.#own(this.#dispose(record));
|
|
526
714
|
return;
|
|
@@ -530,6 +718,7 @@ var Pool = class {
|
|
|
530
718
|
if (this.#available.includes(record)) this.#emitter.emit("release");
|
|
531
719
|
}
|
|
532
720
|
#dispose(record) {
|
|
721
|
+
if (this.#survivors.has(record)) return Promise.resolve();
|
|
533
722
|
const existing = this.#destroying.get(record);
|
|
534
723
|
if (existing !== void 0) return existing;
|
|
535
724
|
for (const [owned, value] of this.#resources) {
|
|
@@ -540,6 +729,8 @@ var Pool = class {
|
|
|
540
729
|
if (index >= 0) this.#available.splice(index, 1);
|
|
541
730
|
this.#validating.delete(record);
|
|
542
731
|
this.#leased.delete(record);
|
|
732
|
+
this.#watches.get(record)?.abort();
|
|
733
|
+
this.#watches.delete(record);
|
|
543
734
|
if (this.#ending !== void 0) this.#own(cleanup.promise);
|
|
544
735
|
this.#clean(record, value, cleanup);
|
|
545
736
|
return cleanup.promise;
|
|
@@ -547,26 +738,34 @@ var Pool = class {
|
|
|
547
738
|
return Promise.resolve();
|
|
548
739
|
}
|
|
549
740
|
async #clean(record, value, cleanup) {
|
|
550
|
-
let
|
|
741
|
+
let disposal;
|
|
551
742
|
try {
|
|
552
|
-
|
|
743
|
+
disposal = Promise.resolve(this.#destroy?.(value));
|
|
553
744
|
} catch (error) {
|
|
554
|
-
|
|
745
|
+
disposal = Promise.reject(error);
|
|
555
746
|
}
|
|
556
747
|
let failure;
|
|
557
748
|
let failed = false;
|
|
558
749
|
try {
|
|
559
|
-
await
|
|
750
|
+
await disposal;
|
|
560
751
|
} catch (error) {
|
|
561
752
|
failure = error;
|
|
562
753
|
failed = true;
|
|
563
754
|
}
|
|
564
|
-
this.#
|
|
755
|
+
if (failed && this.#min !== void 0) {
|
|
756
|
+
this.#survivors.set(record, failure);
|
|
757
|
+
this.#record(failure);
|
|
758
|
+
} else {
|
|
759
|
+
this.#resources.delete(record);
|
|
760
|
+
if (this.#owing.has(record)) this.#owed += 1;
|
|
761
|
+
}
|
|
762
|
+
this.#owing.delete(record);
|
|
565
763
|
if (failed) cleanup.reject(failure);
|
|
566
764
|
else cleanup.resolve();
|
|
567
765
|
await Promise.resolve();
|
|
568
766
|
this.#emitter.emit("destroy");
|
|
569
767
|
this.#destroying.delete(record);
|
|
768
|
+
if (this.#min !== void 0) this.#pump();
|
|
570
769
|
this.#finish();
|
|
571
770
|
}
|
|
572
771
|
async #settleClear(cleanups) {
|
|
@@ -604,11 +803,11 @@ var Pool = class {
|
|
|
604
803
|
if (ending === void 0 || this.#operations.size > 0 || this.#owned.size > 0) return;
|
|
605
804
|
let claimed = false;
|
|
606
805
|
for (const record of this.#resources.keys()) {
|
|
607
|
-
if (this.#validating.has(record) || this.#destroying.has(record)) continue;
|
|
806
|
+
if (this.#validating.has(record) || this.#destroying.has(record) || this.#survivors.has(record)) continue;
|
|
608
807
|
claimed = true;
|
|
609
808
|
this.#own(this.#dispose(record));
|
|
610
809
|
}
|
|
611
|
-
if (claimed || this.#resources.size >
|
|
810
|
+
if (claimed || this.#resources.size > this.#survivors.size || this.#destroying.size > 0) return;
|
|
612
811
|
this.#emitter.destroy();
|
|
613
812
|
if (this.#failures.length > 0) ending.reject(this.#cleanupError(this.#failures));
|
|
614
813
|
else ending.resolve();
|
|
@@ -625,12 +824,15 @@ var Pool = class {
|
|
|
625
824
|
* request order. `clear` owns its idle snapshot; `destroy` returns one stable barrier and
|
|
626
825
|
* waits for every in-flight create, validation, and destroy attempt before destroying the
|
|
627
826
|
* emitter last.
|
|
827
|
+
* Set `min` with its required `restarts` bound and call `start()` to warm resources and
|
|
828
|
+
* refill losses independently of queued acquires. `watch` observes loss and its signal
|
|
829
|
+
* aborts at disposal; a token can declare loss through `destroy()`.
|
|
628
830
|
*
|
|
629
831
|
* @typeParam T - The pooled resource type
|
|
630
|
-
* @param options - Lifecycle hooks, optional
|
|
832
|
+
* @param options - Lifecycle hooks, capacity, optional bounded warm floor, and observation hooks
|
|
631
833
|
* @returns A working {@link PoolInterface}
|
|
632
|
-
* @throws {@link PoolError} Thrown
|
|
633
|
-
*
|
|
834
|
+
* @throws {@link PoolError} Thrown with `code: 'invalid'` when capacity, restart bounds, or
|
|
835
|
+
* the watch hook are invalid. Construction validates them synchronously, before the pool exists.
|
|
634
836
|
*
|
|
635
837
|
* @example Create a pool
|
|
636
838
|
* ```ts
|