@atolljs/core 0.1.0 → 0.1.2
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 +167 -61
- package/dist/contract/listSchema.d.ts +47 -0
- package/dist/contract/msgpackCodec.d.ts +7 -0
- package/dist/contract/msgpackrCodec.d.ts +2 -0
- package/dist/contract/mz.d.ts +43 -0
- package/dist/contract/sharedMemory.d.ts +350 -0
- package/dist/contract/types.d.ts +131 -0
- package/dist/contract/zod.d.ts +53 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +18 -18
- package/dist/index10.js +14 -178
- package/dist/index11.js +188 -47
- package/dist/index12.js +45 -59
- package/dist/index13.js +59 -20
- package/dist/index14.js +24 -60
- package/dist/index15.js +56 -43
- package/dist/index16.js +45 -41
- package/dist/index17.js +42 -38
- package/dist/index18.js +36 -55
- package/dist/index19.js +52 -47
- package/dist/index20.js +48 -51
- package/dist/index21.js +59 -0
- package/dist/index3.js +24 -117
- package/dist/index4.js +116 -34
- package/dist/index5.js +32 -473
- package/dist/index6.js +489 -19
- package/dist/index7.js +19 -6
- package/dist/index8.js +6 -33
- package/dist/index9.js +29 -14
- package/dist/log.d.ts +35 -0
- package/dist/observable.d.ts +30 -0
- package/dist/pool/errors.d.ts +16 -0
- package/dist/pool/memory.d.ts +19 -0
- package/dist/pool/workerClient.d.ts +80 -0
- package/dist/pool/workerPool.d.ts +115 -0
- package/dist/reactive.d.ts +46 -0
- package/dist/service.d.ts +181 -0
- package/dist/shared/sharedWorkerClient.d.ts +35 -0
- package/dist/shared/sharedWorkerHost.d.ts +21 -0
- package/dist/task.d.ts +40 -0
- package/dist/worker/defineWorker.d.ts +63 -0
- package/dist/worker/registry.d.ts +8 -0
- package/dist/worker/workerBootstrap.d.ts +1 -0
- package/package.json +17 -2
- package/src/contract/listSchema.ts +13 -8
- package/src/contract/mz.ts +1 -1
- package/src/contract/sharedMemory.ts +28 -1
- package/src/contract/types.ts +21 -0
- package/src/contract/zod.ts +44 -0
- package/src/index.ts +2 -1
- package/src/pool/workerPool.ts +27 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atolljs/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Typed shared-memory worker pools — defineWorker/connectWorker, SharedArrayBuffer contracts, cross-thread reactivity",
|
|
5
|
+
"homepage": "https://jwhenry3.github.io/atolljs/consumer/quickstart/",
|
|
4
6
|
"type": "module",
|
|
5
7
|
"main": "./dist/index.js",
|
|
6
8
|
"types": "./dist/index.d.ts",
|
|
@@ -28,8 +30,13 @@
|
|
|
28
30
|
"serve:all": "node scripts/serve-all.mjs",
|
|
29
31
|
"build": "tsc --noEmit && vite build",
|
|
30
32
|
"test": "vitest run",
|
|
33
|
+
"test:coverage": "vitest run --coverage",
|
|
34
|
+
"stats": "node scripts/bundle-stats.mjs",
|
|
35
|
+
"stats:islands": "node scripts/island-perf.mjs",
|
|
31
36
|
"build:pages": "node scripts/build-pages.mjs",
|
|
32
|
-
"assemble:pages": "node scripts/assemble-pages.mjs"
|
|
37
|
+
"assemble:pages": "node scripts/assemble-pages.mjs",
|
|
38
|
+
"serve:pages": "node scripts/serve-pages.mjs",
|
|
39
|
+
"serve:static": "node scripts/serve-static.mjs"
|
|
33
40
|
},
|
|
34
41
|
"devDependencies": {
|
|
35
42
|
"@angular/core": "^22.2.0",
|
|
@@ -40,6 +47,7 @@
|
|
|
40
47
|
"@sveltejs/vite-plugin-svelte": "^7.3.1",
|
|
41
48
|
"@types/react": "^19.3.0",
|
|
42
49
|
"@types/react-dom": "^19.3.0",
|
|
50
|
+
"@vitejs/plugin-vue": "^6.0.9",
|
|
43
51
|
"@vitest/coverage-v8": "^4.1.11",
|
|
44
52
|
"fast-json-stringify": "^7.0.1",
|
|
45
53
|
"happy-dom": "^20.14.5",
|
|
@@ -64,5 +72,12 @@
|
|
|
64
72
|
"repository": {
|
|
65
73
|
"type": "git",
|
|
66
74
|
"url": "https://github.com/jwhenry3/atolljs"
|
|
75
|
+
},
|
|
76
|
+
"publishConfig": {
|
|
77
|
+
"access": "public",
|
|
78
|
+
"registry": "https://registry.npmjs.org"
|
|
79
|
+
},
|
|
80
|
+
"bugs": {
|
|
81
|
+
"url": "https://github.com/jwhenry3/atolljs/issues"
|
|
67
82
|
}
|
|
68
83
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
1
|
+
import { z } from './zod';
|
|
2
2
|
import type {
|
|
3
3
|
ScalarKind,
|
|
4
4
|
ListFieldSpec,
|
|
@@ -71,7 +71,7 @@ export function listSchema<F extends ListFields>(fields: F): z.ZodObject<ListZod
|
|
|
71
71
|
function memberToSchema(member: ListMember): z.ZodTypeAny {
|
|
72
72
|
if (member instanceof z.ZodType) {
|
|
73
73
|
const bytes =
|
|
74
|
-
member.def?.type === 'string' ? (member
|
|
74
|
+
member._zod.def?.type === 'string' ? zodMetaBytes(member) : undefined;
|
|
75
75
|
return typeof bytes === 'number'
|
|
76
76
|
? member.refine((s) => utf8.encode(s as string).byteLength <= bytes, `exceeds ${bytes} UTF-8 bytes`)
|
|
77
77
|
: member;
|
|
@@ -160,12 +160,12 @@ export function memberToSpec(member: ListMember, name: string): ListFieldSpec {
|
|
|
160
160
|
if ((member as { bool?: unknown }).bool === true) return member as { bool: true };
|
|
161
161
|
throw new TypeError(`member "${name}": expected a scalar token, { string: n }, { bool: true }, or a zod schema`);
|
|
162
162
|
}
|
|
163
|
-
const def = member.def as ZodMemberDef;
|
|
163
|
+
const def = member._zod.def as unknown as ZodMemberDef;
|
|
164
164
|
if (def.type === 'number') return numberMemberToSpec(def);
|
|
165
165
|
if (def.type === 'bigint') return BIGINT_FORMATS[def.format ?? ''] ?? 'i64';
|
|
166
166
|
if (def.type === 'boolean') return { bool: true };
|
|
167
167
|
if (def.type === 'string') {
|
|
168
|
-
const bytes = (member
|
|
168
|
+
const bytes = zodMetaBytes(member);
|
|
169
169
|
if (typeof bytes === 'number') return { string: bytes };
|
|
170
170
|
throw new TypeError(
|
|
171
171
|
`member "${name}": z.string() needs .meta({ bytes: n }) — inline strings are fixed-width`
|
|
@@ -183,7 +183,7 @@ export function memberToSpec(member: ListMember, name: string): ListFieldSpec {
|
|
|
183
183
|
/** The declared member map of a zod object, or null when `schema` isn't one. */
|
|
184
184
|
export function zodObjectShape(schema: unknown): ListFields | null {
|
|
185
185
|
if (!(schema instanceof z.ZodObject)) return null;
|
|
186
|
-
return (schema.def as { shape: ListFields }).shape;
|
|
186
|
+
return (schema._zod.def as unknown as { shape: ListFields }).shape;
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
/**
|
|
@@ -193,7 +193,7 @@ export function zodObjectShape(schema: unknown): ListFields | null {
|
|
|
193
193
|
*/
|
|
194
194
|
export function zodArrayInfo(schema: unknown): { element: z.ZodTypeAny; capacity: number | null } | null {
|
|
195
195
|
if (!(schema instanceof z.ZodArray)) return null;
|
|
196
|
-
const def = schema.def as ZodMemberDef & { element: z.ZodTypeAny };
|
|
196
|
+
const def = schema._zod.def as unknown as ZodMemberDef & { element: z.ZodTypeAny };
|
|
197
197
|
let capacity: number | null = null;
|
|
198
198
|
for (const c of def.checks ?? []) {
|
|
199
199
|
const cd = checkDef(c);
|
|
@@ -203,9 +203,14 @@ export function zodArrayInfo(schema: unknown): { element: z.ZodTypeAny; capacity
|
|
|
203
203
|
return { element: def.element, capacity };
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
+
/** `.meta()?.bytes` — optional-call: mini schemas ($ZodString too) have no `.meta` method. */
|
|
207
|
+
function zodMetaBytes(schema: unknown): number | null {
|
|
208
|
+
const meta = (schema as { meta?: () => { bytes?: unknown } }).meta?.();
|
|
209
|
+
return typeof meta?.bytes === 'number' ? meta.bytes : null;
|
|
210
|
+
}
|
|
211
|
+
|
|
206
212
|
/** The byte budget carried by `mz.string(n)` / `z.string().meta({ bytes: n })` — null otherwise. */
|
|
207
213
|
export function zodStringBytes(schema: unknown): number | null {
|
|
208
214
|
if (!(schema instanceof z.ZodString)) return null;
|
|
209
|
-
|
|
210
|
-
return typeof bytes === 'number' ? bytes : null;
|
|
215
|
+
return zodMetaBytes(schema);
|
|
211
216
|
}
|
package/src/contract/mz.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { Prettify, Schema } from './types';
|
|
|
2
2
|
import { msgpackrCodec } from './msgpackrCodec';
|
|
3
3
|
import { memberToSpec, zodArrayInfo, zodObjectShape, zodStringBytes } from './listSchema';
|
|
4
4
|
import { fmtBytes, scoped } from '../log';
|
|
5
|
-
import { z } from 'zod';
|
|
5
|
+
import { z } from './zod';
|
|
6
6
|
|
|
7
7
|
const memLog = scoped('memory');
|
|
8
8
|
|
|
@@ -768,6 +768,7 @@ export class SharedMemory<S extends SharedSpec = SharedSpec> {
|
|
|
768
768
|
private readonly connectors = new Map<string, Connector<any>>();
|
|
769
769
|
private readonly boundListeners = new Set<() => void>();
|
|
770
770
|
private isBound = false;
|
|
771
|
+
private boundBuffer?: SharedArrayBuffer;
|
|
771
772
|
|
|
772
773
|
constructor(spec: S, options: SharedMemoryOptions = {}) {
|
|
773
774
|
this.spec = spec;
|
|
@@ -818,6 +819,7 @@ export class SharedMemory<S extends SharedSpec = SharedSpec> {
|
|
|
818
819
|
});
|
|
819
820
|
});
|
|
820
821
|
this.isBound = true;
|
|
822
|
+
this.boundBuffer = buffer;
|
|
821
823
|
for (const cb of [...this.boundListeners]) cb();
|
|
822
824
|
}
|
|
823
825
|
|
|
@@ -826,6 +828,31 @@ export class SharedMemory<S extends SharedSpec = SharedSpec> {
|
|
|
826
828
|
return this.isBound;
|
|
827
829
|
}
|
|
828
830
|
|
|
831
|
+
/**
|
|
832
|
+
* The buffer this contract is bound to on this thread — the raw backing
|
|
833
|
+
* store persistence adapters and snapshot tooling read/write through.
|
|
834
|
+
* Throws while unbound.
|
|
835
|
+
*/
|
|
836
|
+
public get buffer(): SharedArrayBuffer {
|
|
837
|
+
if (!this.boundBuffer) {
|
|
838
|
+
throw new Error('Shared memory contract is not bound to a buffer on this thread.');
|
|
839
|
+
}
|
|
840
|
+
return this.boundBuffer;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
/**
|
|
844
|
+
* Every field's address in the layout — `{ path, byteOffset, byteLength }`.
|
|
845
|
+
* Persistence adapters use this to read/write field regions directly and
|
|
846
|
+
* to pair each region with its `connector(path)._version` slot.
|
|
847
|
+
*/
|
|
848
|
+
public fields(): { path: string; byteOffset: number; byteLength: number }[] {
|
|
849
|
+
return this.entries.map(({ path, descriptor }) => ({
|
|
850
|
+
path,
|
|
851
|
+
byteOffset: this.offsets.get(path)!,
|
|
852
|
+
byteLength: descriptor.byteLength,
|
|
853
|
+
}));
|
|
854
|
+
}
|
|
855
|
+
|
|
829
856
|
/**
|
|
830
857
|
* Fires when the contract is bound on this thread — immediately if already
|
|
831
858
|
* bound. Returns an unsubscribe function.
|
package/src/contract/types.ts
CHANGED
|
@@ -26,6 +26,21 @@ export interface MemoryConfig {
|
|
|
26
26
|
growthFactor?: number;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Runtime handle for a memory-persistence adapter — e.g. the Redis adapter
|
|
31
|
+
* in '@atolljs/node/redis'. The adapter mirrors the bound contract's field
|
|
32
|
+
* regions to external storage; the buffer stays the synchronous source of
|
|
33
|
+
* truth.
|
|
34
|
+
*/
|
|
35
|
+
export interface MemoryPersistence {
|
|
36
|
+
/** Resolves when initial state has been restored into the local buffer. */
|
|
37
|
+
readonly ready?: Promise<unknown>;
|
|
38
|
+
/** Push dirty fields now — most adapters also flush on an interval. */
|
|
39
|
+
flush?(): Promise<unknown>;
|
|
40
|
+
/** Final flush + release external resources. Invoked by pool.terminate(). */
|
|
41
|
+
stop(): void | Promise<void>;
|
|
42
|
+
}
|
|
43
|
+
|
|
29
44
|
export interface WorkerPoolConfig<S extends SharedSpec = SharedSpec, T extends TaskMap = TaskMap> {
|
|
30
45
|
/** Worker entry as a URL — e.g. `new URL('./worker.ts', import.meta.url)`. */
|
|
31
46
|
workerUrl?: URL;
|
|
@@ -42,6 +57,12 @@ export interface WorkerPoolConfig<S extends SharedSpec = SharedSpec, T extends T
|
|
|
42
57
|
* skipped and workers receive a plain `{ type: 'INIT' }` handshake.
|
|
43
58
|
*/
|
|
44
59
|
sharedMemory?: SharedMemory<S> & Prettify<SharedAccess<S>>;
|
|
60
|
+
/**
|
|
61
|
+
* Persistence adapter for `sharedMemory` — invoked with the contract right
|
|
62
|
+
* after the pool binds it, and its `stop()` runs on `terminate()`.
|
|
63
|
+
* `redisMemoryAdapter(client)` from '@atolljs/node/redis' produces one.
|
|
64
|
+
*/
|
|
65
|
+
persistence?: (memory: SharedMemory<S>) => MemoryPersistence | undefined;
|
|
45
66
|
/** Worker count — a number, or 'auto' (the default) for navigator.hardwareConcurrency ?? 4. */
|
|
46
67
|
poolSize?: number | 'auto';
|
|
47
68
|
memory?: MemoryConfig;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The SDK's vendored zod surface — everything the contract layer needs,
|
|
3
|
+
* narrowed.
|
|
4
|
+
*
|
|
5
|
+
* `import { z } from 'zod'` bundles the ENTIRE classic namespace in consumer
|
|
6
|
+
* builds (~440KB minified): the `z` object can't be property-shaken, so it
|
|
7
|
+
* drags in every locale, the JSON-schema processors, and the compile
|
|
8
|
+
* pipeline. Named imports tree-shake (~93KB for the full set below).
|
|
9
|
+
*
|
|
10
|
+
* `instanceof` guards use the `$Zod*` base classes from `zod/v4/core` —
|
|
11
|
+
* classic AND mini schemas both extend them, so user-supplied schemas match
|
|
12
|
+
* either way. Constructors stay classic (`zod` named imports) because `mz`
|
|
13
|
+
* output and `memory.schemas` are documented as ordinary zod — `.meta()`,
|
|
14
|
+
* `.refine()`, `.parse()` must all work and compose with the consumer's own
|
|
15
|
+
* `z.object(...)`.
|
|
16
|
+
*/
|
|
17
|
+
import {
|
|
18
|
+
array, bigint, boolean, instanceof as instanceof_,
|
|
19
|
+
int32, int64, float32, float64, number, object, string, uint32, uint64,
|
|
20
|
+
unknown as unknown_,
|
|
21
|
+
} from 'zod';
|
|
22
|
+
import { $ZodArray, $ZodObject, $ZodString, $ZodType } from 'zod/v4/core';
|
|
23
|
+
import type * as zod from 'zod';
|
|
24
|
+
|
|
25
|
+
export const z = {
|
|
26
|
+
number, string, boolean, bigint, object, array,
|
|
27
|
+
instanceof: instanceof_, unknown: unknown_,
|
|
28
|
+
int32, uint32, float32, float64, int64, uint64,
|
|
29
|
+
/** Core classes — instanceof-matching for user schemas (classic or mini). */
|
|
30
|
+
ZodType: $ZodType, ZodObject: $ZodObject, ZodArray: $ZodArray, ZodString: $ZodString,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export namespace z {
|
|
34
|
+
/* Type surface — the classic zod types the contract layer annotates with.
|
|
35
|
+
* Value positions above are the runtime; these are the types the public
|
|
36
|
+
* signatures speak in (users pass real classic schemas). */
|
|
37
|
+
export type ZodTypeAny = zod.ZodTypeAny;
|
|
38
|
+
export type ZodType<Output = unknown, Input = unknown> = zod.ZodType<Output, Input>;
|
|
39
|
+
export type ZodString = zod.ZodString;
|
|
40
|
+
export type ZodArray<T extends zod.ZodTypeAny = zod.ZodTypeAny> = zod.ZodArray<T>;
|
|
41
|
+
export type ZodObject<S extends zod.ZodRawShape = zod.ZodRawShape> = zod.ZodObject<S>;
|
|
42
|
+
export type ZodRawShape = zod.ZodRawShape;
|
|
43
|
+
export type output<T extends zod.ZodTypeAny> = zod.output<T>;
|
|
44
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* ── Contract — shared memory declarations (both threads) ───────────────── */
|
|
2
2
|
|
|
3
|
-
export { defineSharedMemory, field, getDefinedSharedMemoryCount, jsonCodec, registerConnectorFactory } from './contract/sharedMemory';
|
|
3
|
+
export { bindSharedMemories, defineSharedMemory, field, getDefinedSharedMemoryCount, jsonCodec, registerConnectorFactory } from './contract/sharedMemory';
|
|
4
4
|
export { mz } from './contract/mz';
|
|
5
5
|
export type {
|
|
6
6
|
Codec,
|
|
@@ -44,6 +44,7 @@ export type {
|
|
|
44
44
|
TaskMap,
|
|
45
45
|
TaskMessage,
|
|
46
46
|
TaskResult,
|
|
47
|
+
MemoryPersistence,
|
|
47
48
|
SharedWorkerConfig,
|
|
48
49
|
ViewType,
|
|
49
50
|
WorkerPoolConfig,
|
package/src/pool/workerPool.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { MemoryManager } from './memory';
|
|
2
2
|
import { PoolQueueFullError, TaskAbortedError, TaskTimeoutError, WorkerCrashedError } from './errors';
|
|
3
3
|
import { SharedAccess, SharedMemory, SharedSpec } from '../contract/sharedMemory';
|
|
4
|
-
import { PoolTasks, TaskContract, TaskMap, TaskResult, WorkerPoolConfig } from '../contract/types';
|
|
4
|
+
import { MemoryPersistence, PoolTasks, TaskContract, TaskMap, TaskResult, WorkerPoolConfig } from '../contract/types';
|
|
5
5
|
import { fmtBytes, scoped } from '../log';
|
|
6
6
|
|
|
7
7
|
const poolLog = scoped('pool');
|
|
@@ -101,6 +101,8 @@ class WorkerPoolImpl<S extends SharedSpec = SharedSpec> {
|
|
|
101
101
|
runMs: agg(),
|
|
102
102
|
};
|
|
103
103
|
public readonly sharedMemory: (SharedMemory<S> & SharedAccess<S>) | undefined;
|
|
104
|
+
/** The attached memory-persistence adapter, if the config supplied one. */
|
|
105
|
+
public readonly persistence: MemoryPersistence | undefined;
|
|
104
106
|
|
|
105
107
|
constructor(config: WorkerPoolConfig<S, TaskMap>) {
|
|
106
108
|
if (config.sharedMemory) {
|
|
@@ -125,6 +127,7 @@ class WorkerPoolImpl<S extends SharedSpec = SharedSpec> {
|
|
|
125
127
|
this.memoryManager = new MemoryManager(config.memory);
|
|
126
128
|
this.memoryManager.ensureCapacity(this.sharedMemory.totalBytes);
|
|
127
129
|
this.sharedMemory.bind(this.memoryManager.getBuffer());
|
|
130
|
+
this.persistence = config.persistence?.(config.sharedMemory);
|
|
128
131
|
}
|
|
129
132
|
|
|
130
133
|
const requestedSize = config.poolSize ?? 'auto';
|
|
@@ -367,6 +370,25 @@ class WorkerPoolImpl<S extends SharedSpec = SharedSpec> {
|
|
|
367
370
|
return this.dispatch(contract, args);
|
|
368
371
|
}
|
|
369
372
|
|
|
373
|
+
/**
|
|
374
|
+
* The shared buffer handed to workers via INIT_MEMORY — undefined on
|
|
375
|
+
* message-only pools. Useful for passing the same buffer to auxiliaries
|
|
376
|
+
* (e.g. a second pool of HTTP workers via withSharedBuffer).
|
|
377
|
+
*/
|
|
378
|
+
public get sharedBuffer(): SharedArrayBuffer | undefined {
|
|
379
|
+
return this.memoryManager?.getBuffer();
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Live snapshot of the pool's worker slots — for auxiliary messaging that
|
|
384
|
+
* isn't task dispatch (e.g. routing accepted sockets into workers). The
|
|
385
|
+
* array is a copy; respawned workers appear on the next read. Do NOT use
|
|
386
|
+
* this to dispatch tasks — runTask/dispatch own the scheduling protocol.
|
|
387
|
+
*/
|
|
388
|
+
public get workers(): readonly Worker[] {
|
|
389
|
+
return this.slots.map((s) => s.worker);
|
|
390
|
+
}
|
|
391
|
+
|
|
370
392
|
/** Running aggregates — no histograms, just counts and means. */
|
|
371
393
|
public stats(): PoolStats {
|
|
372
394
|
return {
|
|
@@ -398,6 +420,10 @@ class WorkerPoolImpl<S extends SharedSpec = SharedSpec> {
|
|
|
398
420
|
|
|
399
421
|
public terminate(): void {
|
|
400
422
|
this.closed = true;
|
|
423
|
+
// Final flush + release — fire-and-forget; terminate() stays synchronous.
|
|
424
|
+
void this.persistence?.stop()?.catch?.((e: unknown) =>
|
|
425
|
+
poolLog.warn('memory persistence stop failed', e),
|
|
426
|
+
);
|
|
401
427
|
poolLog.info(`terminating ${this.slots.length} worker(s)`);
|
|
402
428
|
const crashed = new WorkerCrashedError('pool terminated');
|
|
403
429
|
for (const [, entry] of this.pending) {
|