@atolljs/node 0.1.7 → 0.1.9

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.
@@ -1,6 +1,93 @@
1
+ // ../../src/devtools.ts
2
+ var THREAD = typeof WorkerGlobalScope !== "undefined" && self instanceof WorkerGlobalScope ? "worker" : "main";
3
+ var sink = null;
4
+ var emitDevtools = (event) => {
5
+ if (sink) sink({ ...event, at: performance.now(), thread: THREAD });
6
+ };
7
+ var forwardDevtools = (event) => {
8
+ if (sink) sink(event);
9
+ };
10
+ var devtoolsEnabled = () => sink !== null;
11
+ var previewValue = (value, max = 2048) => {
12
+ const seen = /* @__PURE__ */ new WeakSet();
13
+ let out;
14
+ try {
15
+ out = JSON.stringify(value, (_k, v) => {
16
+ if (typeof v === "function") return "[fn]";
17
+ if (typeof v === "bigint") return `${v}n`;
18
+ if (v instanceof ArrayBuffer) return `[ArrayBuffer ${v.byteLength}B]`;
19
+ if (ArrayBuffer.isView(v)) return `[${v.constructor.name} ${v.byteLength}B]`;
20
+ if (v instanceof Map) return { "[Map]": [...v.entries()].slice(0, 50) };
21
+ if (v instanceof Set) return { "[Set]": [...v].slice(0, 50) };
22
+ if (v && typeof v === "object") {
23
+ if (seen.has(v)) return "[cycle]";
24
+ seen.add(v);
25
+ }
26
+ return v;
27
+ }) ?? String(value);
28
+ } catch {
29
+ out = String(value);
30
+ }
31
+ return out.length > max ? `${out.slice(0, max)}\u2026` : out;
32
+ };
33
+ var estimateCloneBytes = (value, budget = 5e3) => {
34
+ let nodes = 0;
35
+ const seen = /* @__PURE__ */ new WeakSet();
36
+ const walk = (v) => {
37
+ if (++nodes > budget) return 0;
38
+ switch (typeof v) {
39
+ case "string":
40
+ return 4 + v.length * 2;
41
+ case "number":
42
+ return 8;
43
+ case "boolean":
44
+ return 4;
45
+ case "bigint":
46
+ return 16;
47
+ case "undefined":
48
+ return 1;
49
+ case "object": {
50
+ if (v === null) return 1;
51
+ if (v instanceof ArrayBuffer) return v.byteLength;
52
+ if (ArrayBuffer.isView(v)) return v.byteLength;
53
+ if (typeof SharedArrayBuffer !== "undefined" && v instanceof SharedArrayBuffer) return 8;
54
+ if (seen.has(v)) return 4;
55
+ seen.add(v);
56
+ let n = 8;
57
+ if (v instanceof Map) for (const [k, x] of v) n += walk(k) + walk(x);
58
+ else if (v instanceof Set) for (const x of v) n += walk(x);
59
+ else if (Array.isArray(v)) for (const x of v) n += walk(x);
60
+ else for (const k of Object.keys(v)) n += 4 + k.length * 2 + walk(v[k]);
61
+ return n;
62
+ }
63
+ default:
64
+ return 0;
65
+ }
66
+ };
67
+ return walk(value);
68
+ };
69
+ var commands = /* @__PURE__ */ new Map();
70
+ var registerDevtoolsCommand = (name, handler) => {
71
+ commands.set(name, handler);
72
+ return () => {
73
+ if (commands.get(name) === handler) commands.delete(name);
74
+ };
75
+ };
76
+ var BODY_PREVIEW = 8 * 1024;
77
+ var seq = 0;
78
+ var named = /* @__PURE__ */ new Map();
79
+ var nextDevtoolsId = (prefix, name) => {
80
+ const slug = name?.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
81
+ if (!slug) return `${prefix}-${++seq}`;
82
+ const base = `${slug}-${prefix === "pool" ? "p" : prefix === "worker" ? "w" : prefix}`;
83
+ const n = (named.get(base) ?? 0) + 1;
84
+ named.set(base, n);
85
+ return n === 1 ? base : `${base}${n}`;
86
+ };
87
+
1
88
  // ../../src/log.ts
2
89
  var LEVELS = { trace: 0, debug: 1, info: 2, warn: 3, error: 4, off: 5 };
3
- var THREAD = typeof WorkerGlobalScope !== "undefined" && self instanceof WorkerGlobalScope ? "worker" : "main";
90
+ var THREAD2 = typeof WorkerGlobalScope !== "undefined" && self instanceof WorkerGlobalScope ? "worker" : "main";
4
91
  var consoleSink = ({ level, scope, message, data, thread, at }) => {
5
92
  const prefix = `[${(at / 1e3).toFixed(2)}s ${thread}:${scope}]`;
6
93
  const fn = level === "error" ? console.error : level === "warn" ? console.warn : level === "info" ? console.info : console.debug;
@@ -8,10 +95,25 @@ var consoleSink = ({ level, scope, message, data, thread, at }) => {
8
95
  else fn(`${prefix} ${message}`);
9
96
  };
10
97
  var minLevel = "info";
11
- var sink = consoleSink;
98
+ var sink2 = consoleSink;
99
+ var teeing = false;
12
100
  function log(level, scope, message, data) {
13
101
  if (LEVELS[level] < LEVELS[minLevel]) return;
14
- sink({ level, scope, message, data, thread: THREAD, at: performance.now() });
102
+ sink2({ level, scope, message, data, thread: THREAD2, at: performance.now() });
103
+ if (!teeing && devtoolsEnabled()) {
104
+ teeing = true;
105
+ try {
106
+ emitDevtools({
107
+ type: "log",
108
+ level,
109
+ scope,
110
+ message,
111
+ ...data !== void 0 ? { data: previewValue(data, 1024) } : {}
112
+ });
113
+ } finally {
114
+ teeing = false;
115
+ }
116
+ }
15
117
  }
16
118
  function scoped(scope) {
17
119
  return {
@@ -24,20 +126,6 @@ function scoped(scope) {
24
126
  }
25
127
  var fmtBytes = (bytes) => bytes >= 1 << 20 ? `${(bytes / (1 << 20)).toFixed(1)}MB` : bytes >= 1024 ? `${(bytes / 1024).toFixed(1)}KB` : `${bytes}B`;
26
128
 
27
- // ../../src/devtools.ts
28
- var THREAD2 = typeof WorkerGlobalScope !== "undefined" && self instanceof WorkerGlobalScope ? "worker" : "main";
29
- var sink2 = null;
30
- var emitDevtools = (event) => {
31
- if (sink2) sink2({ ...event, at: performance.now(), thread: THREAD2 });
32
- };
33
- var forwardDevtools = (event) => {
34
- if (sink2) sink2(event);
35
- };
36
- var devtoolsEnabled = () => sink2 !== null;
37
- var BODY_PREVIEW = 8 * 1024;
38
- var seq = 0;
39
- var nextDevtoolsId = (prefix) => `${prefix}-${++seq}`;
40
-
41
129
  // ../../src/contract/sharedMemory.ts
42
130
  var memLog = scoped("memory");
43
131
  var codecTextEncoder = new TextEncoder();
@@ -366,6 +454,177 @@ var WorkerCrashedError = class extends Error {
366
454
  }
367
455
  };
368
456
 
457
+ // ../../src/userTiming.ts
458
+ var measureAtoll = (name, start, end, t) => {
459
+ const perf = globalThis.performance;
460
+ if (typeof perf?.measure !== "function") return;
461
+ try {
462
+ perf.measure(name, {
463
+ start,
464
+ end: Math.max(end, start),
465
+ detail: {
466
+ devtools: {
467
+ dataType: "track-entry",
468
+ track: t.track,
469
+ trackGroup: "atoll",
470
+ color: t.color ?? "primary",
471
+ properties: t.properties,
472
+ tooltipText: t.tooltipText
473
+ }
474
+ }
475
+ });
476
+ perf.clearMeasures?.(name);
477
+ } catch {
478
+ }
479
+ };
480
+ var measureTaskCall = (runnerId, taskId, callId, slot, start, end, ok) => measureAtoll(`atoll task ${taskId}`, start, end, {
481
+ track: runnerId,
482
+ color: ok ? "primary" : "error",
483
+ properties: [["runner", runnerId], ["slot", slot], ["call", callId], ["outcome", ok ? "ok" : "error"]],
484
+ tooltipText: `${taskId} on ${runnerId}#${slot}: ${(end - start).toFixed(1)}ms`
485
+ });
486
+
487
+ // ../../src/pool/workerChannel.ts
488
+ function setupChannelMemory(config, owner) {
489
+ if (config.sharedMemory || config.sharedBuffer !== void 0) {
490
+ if (typeof SharedArrayBuffer === "undefined" || typeof crossOriginIsolated !== "undefined" && !crossOriginIsolated) {
491
+ throw new Error(
492
+ `${owner} requires SharedArrayBuffer in a cross-origin isolated context \u2014 serve the page with COOP/COEP headers.`
493
+ );
494
+ }
495
+ }
496
+ let injectedBuffer;
497
+ if (config.sharedBuffer !== void 0) {
498
+ injectedBuffer = typeof config.sharedBuffer === "function" ? config.sharedBuffer() : config.sharedBuffer;
499
+ if (!injectedBuffer) {
500
+ throw new Error(
501
+ `${owner}: \`sharedBuffer\` resolved to an empty buffer \u2014 the producing pool or contract has no bound memory yet.`
502
+ );
503
+ }
504
+ }
505
+ if (!config.sharedMemory) return { sharedMemory: void 0, injectedBuffer, persistence: void 0 };
506
+ const sharedMemory = config.sharedMemory;
507
+ let memoryManager;
508
+ if (injectedBuffer) {
509
+ sharedMemory.bind(injectedBuffer);
510
+ } else {
511
+ memoryManager = new MemoryManager(config.memory);
512
+ memoryManager.ensureCapacity(sharedMemory.totalBytes);
513
+ sharedMemory.bind(memoryManager.getBuffer());
514
+ }
515
+ return { sharedMemory, memoryManager, injectedBuffer, persistence: config.persistence?.(sharedMemory) };
516
+ }
517
+ function sendInit(worker, mem) {
518
+ if (mem.memoryManager) {
519
+ worker.postMessage({ type: "INIT_MEMORY", memory: mem.memoryManager.memory, devtools: devtoolsEnabled() });
520
+ return "INIT_MEMORY";
521
+ }
522
+ if (mem.injectedBuffer) {
523
+ worker.postMessage({ type: "INIT_MEMORY", buffer: mem.injectedBuffer, devtools: devtoolsEnabled() });
524
+ return "INIT_MEMORY";
525
+ }
526
+ worker.postMessage({ type: "INIT", devtools: devtoolsEnabled() });
527
+ return "INIT";
528
+ }
529
+ function forwardWorkerDevtools(runnerId, slot, ev) {
530
+ const qualify = (id) => `${runnerId}#${slot}~${id}`;
531
+ forwardDevtools({
532
+ ...ev,
533
+ ...ev.poolId !== void 0 ? { poolId: qualify(ev.poolId) } : {},
534
+ worker: ev.worker === void 0 ? { poolId: runnerId, slot } : { poolId: qualify(ev.worker.poolId), slot: ev.worker.slot }
535
+ });
536
+ }
537
+
538
+ // ../../src/pool/devtoolsCommands.ts
539
+ var CHAOS_FAILURE = "chaos: injected failure";
540
+ var KILL_MESSAGE = "killed from devtools";
541
+ var runners = /* @__PURE__ */ new Map();
542
+ var uninstall = null;
543
+ var runnerOf = (args) => {
544
+ const id = args.poolId;
545
+ if (typeof id !== "string") throw new Error("poolId (string) is required");
546
+ const r = runners.get(id);
547
+ if (!r) {
548
+ throw new Error(
549
+ id.includes("~") ? `runner '${id}' lives inside a worker: nested pools are not controllable from the dashboard` : `no live runner '${id}' on this thread`
550
+ );
551
+ }
552
+ return r;
553
+ };
554
+ var rate = (v, name) => {
555
+ if (v === void 0 || v === null) return void 0;
556
+ if (typeof v !== "number" || !Number.isFinite(v) || v < 0 || v > 1) {
557
+ throw new Error(`${name} must be a number in 0..1`);
558
+ }
559
+ return v || void 0;
560
+ };
561
+ var parseChaos = (args) => {
562
+ const { delayMs } = args;
563
+ if (delayMs !== void 0 && delayMs !== null && (typeof delayMs !== "number" || !Number.isFinite(delayMs) || delayMs < 0)) {
564
+ throw new Error("delayMs must be a non-negative number");
565
+ }
566
+ const failRate = rate(args.failRate, "failRate");
567
+ const timeoutRate = rate(args.timeoutRate, "timeoutRate");
568
+ if ((failRate ?? 0) + (timeoutRate ?? 0) > 1) throw new Error("failRate + timeoutRate must not exceed 1");
569
+ const chaos = {};
570
+ if (delayMs) chaos.delayMs = delayMs;
571
+ if (failRate) chaos.failRate = failRate;
572
+ if (timeoutRate) chaos.timeoutRate = timeoutRate;
573
+ return Object.keys(chaos).length ? chaos : null;
574
+ };
575
+ var listRunners = () => [...runners.values()].map((r) => ({
576
+ poolId: r.poolId,
577
+ label: r.label ?? r.poolId,
578
+ size: r.size(),
579
+ dedicated: r.dedicated,
580
+ chaos: r.getChaos()
581
+ }));
582
+ var install = () => {
583
+ const offs = [
584
+ registerDevtoolsCommand("pool.list", () => listRunners()),
585
+ registerDevtoolsCommand("pool.stats", (args) => runnerOf(args).stats()),
586
+ registerDevtoolsCommand("worker.kill", (args) => {
587
+ const r = runnerOf(args);
588
+ const slot = args.slot ?? 0;
589
+ if (typeof slot !== "number" || !Number.isInteger(slot) || slot < 0) {
590
+ throw new Error("slot must be a non-negative integer");
591
+ }
592
+ r.kill(slot);
593
+ return { poolId: r.poolId, slot, killed: true };
594
+ }),
595
+ registerDevtoolsCommand("pool.chaos", (args) => {
596
+ const r = runnerOf(args);
597
+ r.setChaos(parseChaos(args));
598
+ return r.getChaos();
599
+ })
600
+ ];
601
+ return () => offs.forEach((off) => off());
602
+ };
603
+ var registerDevtoolsRunner = (handle) => {
604
+ runners.set(handle.poolId, handle);
605
+ uninstall ??= install();
606
+ return () => {
607
+ if (runners.get(handle.poolId) !== handle) return;
608
+ runners.delete(handle.poolId);
609
+ if (runners.size === 0 && uninstall) {
610
+ uninstall();
611
+ uninstall = null;
612
+ }
613
+ };
614
+ };
615
+ var applyChaos = (chaos, call) => {
616
+ const r = Math.random();
617
+ const fail = r < (chaos.failRate ?? 0);
618
+ const timeout = !fail && r < (chaos.failRate ?? 0) + (chaos.timeoutRate ?? 0);
619
+ if (!chaos.delayMs && !fail && !timeout) return call.post();
620
+ setTimeout(() => {
621
+ if (!call.live()) return;
622
+ if (fail) return call.fail();
623
+ if (timeout) call.timeout();
624
+ call.post();
625
+ }, chaos.delayMs ?? 0);
626
+ };
627
+
369
628
  // ../../src/pool/workerPool.ts
370
629
  var poolLog = scoped("pool");
371
630
  var taskLog = scoped("task");
@@ -384,7 +643,10 @@ var WorkerPoolImpl = class {
384
643
  pending = /* @__PURE__ */ new Map();
385
644
  nextMessageId = 0;
386
645
  queue = [];
646
+ mem;
387
647
  memoryManager;
648
+ /** A caller-supplied buffer shipped via INIT_MEMORY (sub-worker / second-pool sharing). */
649
+ injectedBuffer;
388
650
  workerUrl;
389
651
  spawn;
390
652
  concurrency;
@@ -392,6 +654,9 @@ var WorkerPoolImpl = class {
392
654
  respawn;
393
655
  taskTimeout;
394
656
  closed = false;
657
+ /** Dashboard fault injection (`pool.chaos`); null unless devtools set it. */
658
+ chaos = null;
659
+ devtoolsOff;
395
660
  drainWaiters = [];
396
661
  statsAgg = {
397
662
  completed: 0,
@@ -401,21 +666,18 @@ var WorkerPoolImpl = class {
401
666
  waitMs: agg(),
402
667
  runMs: agg()
403
668
  };
404
- /** Devtools correlation id ('pool-N') — stamped on every event this pool emits. */
405
- poolId = nextDevtoolsId("pool");
669
+ /**
670
+ * Devtools correlation id, stamped on every event this pool emits:
671
+ * '<name>-p' when the config has a `name`, else 'pool-N'.
672
+ */
673
+ poolId;
406
674
  label;
407
675
  nextCallId = 0;
408
676
  sharedMemory;
409
677
  /** The attached memory-persistence adapter, if the config supplied one. */
410
678
  persistence;
411
679
  constructor(config) {
412
- if (config.sharedMemory) {
413
- if (typeof SharedArrayBuffer === "undefined" || typeof crossOriginIsolated !== "undefined" && !crossOriginIsolated) {
414
- throw new Error(
415
- "WorkerPool requires SharedArrayBuffer in a cross-origin isolated context \u2014 serve the page with COOP/COEP headers."
416
- );
417
- }
418
- }
680
+ const mem = setupChannelMemory(config, "WorkerPool");
419
681
  if (!config.workerUrl && !config.createWorker) {
420
682
  throw new Error("WorkerPool requires either `workerUrl` or `createWorker` in its config.");
421
683
  }
@@ -426,17 +688,16 @@ var WorkerPoolImpl = class {
426
688
  this.respawn = config.respawn ?? true;
427
689
  this.taskTimeout = config.taskTimeout;
428
690
  this.label = config.name;
429
- if (config.sharedMemory) {
430
- this.sharedMemory = config.sharedMemory;
431
- this.memoryManager = new MemoryManager(config.memory);
432
- this.memoryManager.ensureCapacity(this.sharedMemory.totalBytes);
433
- this.sharedMemory.bind(this.memoryManager.getBuffer());
434
- this.persistence = config.persistence?.(config.sharedMemory);
435
- }
691
+ this.poolId = nextDevtoolsId("pool", config.name);
692
+ this.mem = mem;
693
+ this.sharedMemory = mem.sharedMemory;
694
+ this.memoryManager = mem.memoryManager;
695
+ this.injectedBuffer = mem.injectedBuffer;
696
+ this.persistence = mem.persistence;
436
697
  const requestedSize = config.poolSize ?? "auto";
437
- const poolSize = requestedSize === "auto" ? navigator.hardwareConcurrency ?? 4 : requestedSize;
698
+ const poolSize = requestedSize === "auto" ? typeof navigator !== "undefined" ? navigator.hardwareConcurrency ?? 4 : 4 : requestedSize;
438
699
  poolLog.info(
439
- this.sharedMemory ? `spawning ${poolSize} worker(s) with ${fmtBytes(this.sharedMemory.totalBytes)} shared buffer` : `spawning ${poolSize} worker(s), message-only`,
700
+ this.sharedMemory ? `spawning ${poolSize} worker(s) with ${fmtBytes(this.sharedMemory.totalBytes)} shared buffer` : this.injectedBuffer ? `spawning ${poolSize} worker(s) with ${fmtBytes(this.injectedBuffer.byteLength)} inherited buffer` : `spawning ${poolSize} worker(s), message-only`,
440
701
  { workerUrl: config.workerUrl ? String(config.workerUrl) : "createWorker()" }
441
702
  );
442
703
  poolLog.debug(`tip: setLogLevel('trace') for per-task detail`);
@@ -446,11 +707,29 @@ var WorkerPoolImpl = class {
446
707
  label: this.label,
447
708
  poolSize,
448
709
  concurrency: this.concurrency,
449
- memoryBytes: this.sharedMemory?.totalBytes
710
+ memoryBytes: this.sharedMemory?.totalBytes ?? this.injectedBuffer?.byteLength
450
711
  });
451
712
  for (let i = 0; i < poolSize; i++) {
452
713
  this.addWorker(this.spawn());
453
714
  }
715
+ if (devtoolsEnabled()) {
716
+ this.devtoolsOff = registerDevtoolsRunner({
717
+ poolId: this.poolId,
718
+ label: this.label,
719
+ dedicated: false,
720
+ size: () => this.slots.length,
721
+ stats: () => this.stats(),
722
+ kill: (slot) => {
723
+ const s = this.slots[slot];
724
+ if (!s) throw new Error(`${this.poolId} has no worker slot ${slot}`);
725
+ this.handleWorkerError(s, { message: KILL_MESSAGE });
726
+ },
727
+ getChaos: () => this.chaos,
728
+ setChaos: (chaos) => {
729
+ this.chaos = chaos;
730
+ }
731
+ });
732
+ }
454
733
  const tasks = config.tasks ?? {};
455
734
  for (const [name, contract] of Object.entries(tasks)) {
456
735
  if (name in this) {
@@ -469,10 +748,7 @@ var WorkerPoolImpl = class {
469
748
  worker.addEventListener("message", (event) => {
470
749
  const data = event.data;
471
750
  if (data.type === "ATOLL_DEVTOOLS" && data.event) {
472
- forwardDevtools({
473
- ...data.event,
474
- worker: { poolId: this.poolId, slot: this.slots.indexOf(slot) }
475
- });
751
+ forwardWorkerDevtools(this.poolId, this.slots.indexOf(slot), data.event);
476
752
  return;
477
753
  }
478
754
  this.handleReply(slot, data);
@@ -480,13 +756,9 @@ var WorkerPoolImpl = class {
480
756
  worker.addEventListener("error", (event) => {
481
757
  this.handleWorkerError(slot, event);
482
758
  });
483
- if (this.memoryManager) {
484
- worker.postMessage({ type: "INIT_MEMORY", memory: this.memoryManager.memory, devtools: devtoolsEnabled() });
485
- } else {
486
- worker.postMessage({ type: "INIT", devtools: devtoolsEnabled() });
487
- }
759
+ const init = sendInit(worker, this.mem);
488
760
  this.slots.push(slot);
489
- poolLog.debug(`worker spawned, ${this.memoryManager ? "INIT_MEMORY" : "INIT"} sent`);
761
+ poolLog.debug(`worker spawned, ${init} sent`);
490
762
  emitDevtools({ type: "worker:spawn", poolId: this.poolId, slot: this.slots.indexOf(slot) });
491
763
  return slot;
492
764
  }
@@ -498,17 +770,24 @@ var WorkerPoolImpl = class {
498
770
  this.detach(entry);
499
771
  if (!entry.done) {
500
772
  entry.done = true;
501
- const runMs = performance.now() - (entry.sentAt ?? entry.enqueuedAt);
773
+ const start = entry.sentAt ?? entry.enqueuedAt;
774
+ const runMs = performance.now() - start;
502
775
  this.statsAgg.runMs.add(runMs);
503
776
  if (data.success) {
504
777
  this.statsAgg.completed++;
505
778
  taskLog.debug(`\u2190 ${entry.contract.taskId} ok`);
506
- emitDevtools({ type: "task:settle", poolId: this.poolId, callId: entry.callId, taskId: entry.contract.taskId, outcome: "ok", runMs });
779
+ if (devtoolsEnabled()) {
780
+ emitDevtools({ type: "task:settle", poolId: this.poolId, callId: entry.callId, taskId: entry.contract.taskId, outcome: "ok", runMs, resultBytes: estimateCloneBytes(data.result) });
781
+ measureTaskCall(this.poolId, entry.contract.taskId, entry.callId, this.slots.indexOf(slot), start, start + runMs, true);
782
+ }
507
783
  entry.resolve(data.result);
508
784
  } else {
509
785
  this.statsAgg.failed++;
510
786
  taskLog.warn(`\u2190 ${entry.contract.taskId} failed: ${data.error}`);
511
- emitDevtools({ type: "task:settle", poolId: this.poolId, callId: entry.callId, taskId: entry.contract.taskId, outcome: "error", runMs, error: data.error });
787
+ if (devtoolsEnabled()) {
788
+ emitDevtools({ type: "task:settle", poolId: this.poolId, callId: entry.callId, taskId: entry.contract.taskId, outcome: "error", runMs, error: data.error });
789
+ measureTaskCall(this.poolId, entry.contract.taskId, entry.callId, this.slots.indexOf(slot), start, start + runMs, false);
790
+ }
512
791
  entry.reject(new Error(data.error));
513
792
  }
514
793
  }
@@ -579,13 +858,20 @@ var WorkerPoolImpl = class {
579
858
  this.pending.set(messageId, entry);
580
859
  const slotIndex = this.slots.indexOf(slot);
581
860
  taskLog.debug(`\u2192 ${entry.contract.taskId}`, { worker: slotIndex, args: entry.args });
582
- emitDevtools({ type: "task:dispatch", poolId: this.poolId, callId: entry.callId, taskId: entry.contract.taskId, slot: slotIndex, waitMs });
583
- slot.worker.postMessage({
584
- type: "EXECUTE_TASK",
585
- messageId,
586
- taskId: entry.contract.taskId,
587
- args: entry.args
588
- });
861
+ if (devtoolsEnabled()) {
862
+ emitDevtools({ type: "task:dispatch", poolId: this.poolId, callId: entry.callId, taskId: entry.contract.taskId, slot: slotIndex, waitMs, argBytes: estimateCloneBytes(entry.args) });
863
+ }
864
+ const msg = { type: "EXECUTE_TASK", messageId, taskId: entry.contract.taskId, args: entry.args };
865
+ if (this.chaos) {
866
+ applyChaos(this.chaos, {
867
+ live: () => this.pending.get(messageId) === entry,
868
+ fail: () => this.handleReply(slot, { messageId, taskId: msg.taskId, success: false, error: CHAOS_FAILURE }),
869
+ timeout: () => this.cancel(entry, new TaskTimeoutError()),
870
+ post: () => slot.worker.postMessage(msg)
871
+ });
872
+ } else {
873
+ slot.worker.postMessage(msg);
874
+ }
589
875
  }
590
876
  /** Detaches signal/timeout listeners from a finalized entry. */
591
877
  detach(entry) {
@@ -675,7 +961,7 @@ var WorkerPoolImpl = class {
675
961
  * (e.g. a second pool of HTTP workers via withSharedBuffer).
676
962
  */
677
963
  get sharedBuffer() {
678
- return this.memoryManager?.getBuffer();
964
+ return this.memoryManager?.getBuffer() ?? this.injectedBuffer;
679
965
  }
680
966
  /**
681
967
  * Live snapshot of the pool's worker slots — for auxiliary messaging that
@@ -715,6 +1001,7 @@ var WorkerPoolImpl = class {
715
1001
  }
716
1002
  terminate() {
717
1003
  this.closed = true;
1004
+ this.devtoolsOff?.();
718
1005
  void this.persistence?.stop()?.catch?.(
719
1006
  (e) => poolLog.warn("memory persistence stop failed", e)
720
1007
  );
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  WorkerPool,
3
3
  bindSharedMemories
4
- } from "./chunk-SHWEILEH.js";
4
+ } from "./chunk-VEJT6OVD.js";
5
5
 
6
6
  // src/worker.ts
7
7
  import { Worker as NodeWorker } from "node:worker_threads";
package/dist/redis.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  scoped
3
- } from "./chunk-SHWEILEH.js";
3
+ } from "./chunk-VEJT6OVD.js";
4
4
 
5
5
  // src/redis.ts
6
6
  var log = scoped("redis-mem");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atolljs/node",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Node runtime adapter — node:worker_threads pools, no framework",
5
5
  "homepage": "https://jwhenry3.github.io/atolljs/consumer/quickstart/",
6
6
  "type": "module",
@@ -34,7 +34,7 @@
34
34
  "registry": "https://registry.npmjs.org"
35
35
  },
36
36
  "peerDependencies": {
37
- "@atolljs/core": "0.1.7"
37
+ "@atolljs/core": "0.1.9"
38
38
  },
39
39
  "repository": {
40
40
  "type": "git",