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