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