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