char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somchore: use lowercase avril branding consistentlyb862289

main
3.5 KiB95 linesraw

Module workers

Each worker runs on its own thread and V8 isolate. Workers have the same module loader, TypeScript support, built-in modules, and network APIs as the parent.

import { avril } from "avril:std";

const worker = new Worker(new URL("./worker.ts", import.meta.url), {
  type: "module",
  name: "background",
  [avril]: {
    caps: {
      env: false,
      write: false,
      net: { allow: ["example.com:443"] },
    },
  },
});

worker.onmessage = ({ data }) => {
  console.log(data);
  worker.terminate();
};
worker.onerror = (event) => {
  event.preventDefault();
  console.error(event.message);
};
worker.postMessage({ value: 42 });

In worker.ts:

onmessage = ({ data }) => {
  postMessage({ doubled: data.value * 2 });
  close();
};

Only type: "module" is supported. Relative worker URLs resolve against the parent's entry URL; new URL("./worker.ts", import.meta.url) is preferable when the constructor is called from an imported module. A worker exposes self, name, read-only location, postMessage, onmessage, onmessageerror, and close. Workers may create nested workers.

Capabilities and environment

The avril export is Symbol.for("runtime:avril") and keys avril-specific worker options. Omitting caps inherits the parent's capabilities. The object uses the same restrictions as pledge: net, env, sys, read, write, jsExec, and run, each either false or { allow, deny }. Restrictions apply before the entry module loads. They cannot grant authority that the parent lacks.

A worker receives an independent snapshot of its parent's effective capabilities at construction; later parent or ancestor pledges do not restrict existing workers or their descendants, while workers spawned later from the pledged context inherit the narrowed policy. A worker's own pledge does not restrict its parent or siblings.

Workers receive an independent snapshot of the parent's runtime environment at construction time. Environment mutations are local, and access remains subject to capabilities. Worker standard input is unavailable; console and standard output/error remain available.

Messages and transfers

postMessage(value, transfer) and postMessage(value, { transfer }) support:

  • Structured-clone graphs, including cycles, maps, sets, typed arrays, errors, blobs, files, DOM exceptions, crypto keys, and WebAssembly modules.
  • ArrayBuffer transfers, which detach the sender's buffer.
  • SharedArrayBuffer and Atomics for shared memory.
  • MessagePort transfers, including queued messages and retransfers.
  • ReadableStream, WritableStream, and TransformStream transfers.

Stream transfers lock the sender's stream. Chunks and rejection/cancellation reasons are cloned across the message channel; demand and write acknowledgments preserve backpressure. Byte streams become default readable streams at the destination. Closing an endpoint or terminating its owner rejects outstanding remote operations.

const channel = new MessageChannel();
channel.port1.onmessage = ({ data }) => console.log(data);
worker.postMessage({ port: channel.port2 }, [channel.port2]);

const bytes = new Uint8Array([1, 2, 3]);
worker.postMessage(bytes, [bytes.buffer]);

Workers keep their parent alive until they close, fail, or are terminated. close() finishes the current turn and discards future incoming tasks; terminate() interrupts execution, including CPU-bound JavaScript. Parent teardown terminates and joins its workers recursively.

1# Module workers
2
3Each worker runs on its own thread and V8 isolate. Workers have the same module
4loader, TypeScript support, built-in modules, and network APIs as the parent.
5
6```ts
7import { avril } from "avril:std";
8
9const worker = new Worker(new URL("./worker.ts", import.meta.url), {
10  type: "module",
11  name: "background",
12  [avril]: {
13    caps: {
14      env: false,
15      write: false,
16      net: { allow: ["example.com:443"] },
17    },
18  },
19});
20
21worker.onmessage = ({ data }) => {
22  console.log(data);
23  worker.terminate();
24};
25worker.onerror = (event) => {
26  event.preventDefault();
27  console.error(event.message);
28};
29worker.postMessage({ value: 42 });
30```
31
32In `worker.ts`:
33
34```ts
35onmessage = ({ data }) => {
36  postMessage({ doubled: data.value * 2 });
37  close();
38};
39```
40
41Only `type: "module"` is supported. Relative worker URLs resolve against the
42parent's entry URL; `new URL("./worker.ts", import.meta.url)` is preferable when
43the constructor is called from an imported module. A worker exposes `self`,
44`name`, read-only `location`, `postMessage`, `onmessage`, `onmessageerror`, and
45`close`. Workers may create nested workers.
46
47## Capabilities and environment
48
49The `avril` export is `Symbol.for("runtime:avril")` and keys avril-specific
50worker options. Omitting `caps` inherits the parent's capabilities. The object
51uses the same restrictions as `pledge`: `net`, `env`, `sys`, `read`, `write`, `jsExec`, and `run`, each
52either `false` or `{ allow, deny }`. Restrictions apply before the entry module
53loads. They cannot grant authority that the parent lacks.
54
55A worker receives an independent snapshot of its parent's effective
56capabilities at construction; later parent or ancestor pledges do not restrict
57existing workers or their descendants, while workers spawned later from the
58pledged context inherit the narrowed policy. A worker's own pledge does not
59restrict its parent or siblings.
60
61Workers receive an independent snapshot of the parent's runtime environment at
62construction time. Environment mutations are local, and access remains subject
63to capabilities. Worker standard input is unavailable; console and standard
64output/error remain available.
65
66## Messages and transfers
67
68`postMessage(value, transfer)` and `postMessage(value, { transfer })` support:
69
70- Structured-clone graphs, including cycles, maps, sets, typed arrays, errors,
71  blobs, files, DOM exceptions, crypto keys, and WebAssembly modules.
72- `ArrayBuffer` transfers, which detach the sender's buffer.
73- `SharedArrayBuffer` and `Atomics` for shared memory.
74- `MessagePort` transfers, including queued messages and retransfers.
75- `ReadableStream`, `WritableStream`, and `TransformStream` transfers.
76
77Stream transfers lock the sender's stream. Chunks and rejection/cancellation
78reasons are cloned across the message channel; demand and write acknowledgments
79preserve backpressure. Byte streams become default readable streams at the
80destination. Closing an endpoint or terminating its owner rejects outstanding
81remote operations.
82
83```ts
84const channel = new MessageChannel();
85channel.port1.onmessage = ({ data }) => console.log(data);
86worker.postMessage({ port: channel.port2 }, [channel.port2]);
87
88const bytes = new Uint8Array([1, 2, 3]);
89worker.postMessage(bytes, [bytes.buffer]);
90```
91
92Workers keep their parent alive until they close, fail, or are terminated.
93`close()` finishes the current turn and discards future incoming tasks;
94`terminate()` interrupts execution, including CPU-bound JavaScript. Parent
95teardown terminates and joins its workers recursively.