char/avril

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

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

Charlotte Somgenerate std typedefs from typescript impl38d7f6c

main
13.4 KiB276 linesraw
1import {
2  avril, pledge, env, process, runtime, inspect,
3  serve, serveFile, upgradeWebSocket, connect, listen, listenDatagram,
4  Conn, Listener, DatagramConn, FileHandle, FsWatcher, SeekMode, errors,
5  open, create, stat, lstat, readDir, watchFs, mkdir, remove, rename, copyFile,
6  symlink, readLink, realPath, chmod, chown, lchown, utime, lutime, link, truncate,
7  ChildProcess, ProcessError, OutputLimitError, ProcessTimeoutError, ProcessAbortError,
8  run, runSync, spawn, addSignalListener, removeSignalListener, stdin, stdout, stderr,
9  type TcpAddr, type UnixAddr, type UdpAddr, type StreamAddr, type Addr,
10  type ServeInfo, type ServeHandler, type ServeModule, type HttpServer,
11  type ProcessReason, type ProcessStatus, type ProcessOutput, type ProcessPromise,
12  type ProcessOptions, type SpawnOptions, type RunSyncOptions, type ProcessTerminal,
13  type ProcessStdio, type ExtraProcessStdio, type FileInfo, type DirEntry, type FsEvent,
14  type FsEventKind, type WatchOptions, type OpenOptions, type Pledge, type GrantRestriction,
15} from "avril:std";
16import * as sync from "avril:std/sync";
17// @ts-expect-error Facade support types are private.
18import type { ClassStatics } from "avril:std";
19
20const brand: symbol = avril;
21const branded: { readonly [avril]: true } = { [avril]: true };
22const connectionName: string = Conn.name;
23const listenerArity: number = Listener.length;
24const reflected: Function = ChildProcess;
25const prototype: Conn<StreamAddr> = Conn.prototype;
26// @ts-expect-error Class prototype properties cannot be replaced.
27Conn.prototype = prototype;
28const restriction: GrantRestriction<string | URL> = { allow: [new URL("file:///tmp/")] };
29const narrowed: Pledge = { read: restriction, net: false };
30pledge(narrowed);
31// @ts-expect-error Authority categories use the capability vocabulary.
32pledge({ filesystem: false });
33const variable: string | undefined = env.get("HOME");
34const snapshot: Record<string, string> = env.toObject();
35const pid: number = process.pid;
36const args: readonly string[] = process.args;
37const os: "linux" | "macos" | "windows" = runtime.os;
38const arch: "x86_64" | "aarch64" = runtime.arch;
39const version: string = runtime.version;
40const v8: string = runtime.v8;
41const cwd: string = process.cwd();
42const averages: [number, number, number] = process.loadavg();
43const formatted: string = inspect(snapshot, { depth: null, colors: false });
44// @ts-expect-error Frozen script arguments are not mutable.
45process.args.push("extra");
46// @ts-expect-error Runtime metadata is readonly.
47runtime.os = "linux";
48
49const tcpServer: HttpServer<TcpAddr> = serve((request, info) => {
50  const port: number = info.remoteAddr.port;
51  // @ts-expect-error TCP peer addresses have no Unix path.
52  info.remoteAddr.path;
53  return new Response(request.url + port);
54});
55serve({ port: 0, onListen(addr) { const port: number = addr.port; } }, async () => new Response());
56const unixServer: HttpServer<UnixAddr> = serve({
57  path: "/tmp/http.sock",
58  onListen(addr) { const path: string | null = addr.path; },
59}, (request, info) => {
60  const path: string | null = info.remoteAddr.path;
61  // @ts-expect-error Unix peer addresses have no TCP port.
62  info.remoteAddr.port;
63  return new Response(path);
64});
65// @ts-expect-error A Unix listener cannot also have a TCP port.
66serve({ path: "/tmp/http.sock", port: 80 }, () => new Response());
67// @ts-expect-error A handler must return a Response.
68serve(() => "not a response");
69const handler: ServeHandler = (request, info: ServeInfo) => new Response(info.remoteAddr.transport);
70const module: ServeModule = { fetch: handler };
71const shutdown: Promise<void> = unixServer.shutdown();
72const upgraded: { socket: WebSocket; response: Response } = upgradeWebSocket(new Request("http://localhost/"));
73
74const tcp = await connect({ port: 80 });
75const tcpAddress: TcpAddr = tcp.remoteAddr;
76const unix = await connect({ transport: "unix", path: "/tmp/socket" });
77const unixAddress: UnixAddr = unix.remoteAddr;
78const tcpListener = listen({ port: 0 });
79const accepted: Conn<TcpAddr> = await tcpListener.accept();
80const unixListener = listen({ transport: "unix", path: "/tmp/socket" });
81const unixAccepted: Conn<UnixAddr> = await unixListener.accept();
82const tcpConnections: AsyncIterable<Conn<TcpAddr>> = tcpListener;
83const unixConnections: AsyncIterable<Conn<UnixAddr>> = unixListener;
84const finishedWrite: Promise<void> = tcp.closeWrite();
85// @ts-expect-error closeWrite has no result.
86const nullShutdown: Promise<null> = tcp.closeWrite();
87// @ts-expect-error Unix connections cannot be used as TCP connections.
88const wrongConnection: Conn<TcpAddr> = unix;
89// @ts-expect-error Stream listeners do not use UDP.
90listen({ transport: "udp", port: 0 });
91// @ts-expect-error TCP connect requires a port.
92connect({ hostname: "localhost" });
93const datagram = listenDatagram({ port: 0 });
94const received: [Uint8Array, UdpAddr] = await datagram.receive();
95const packets: AsyncIterable<[Uint8Array, UdpAddr]> = datagram;
96const streamAddress: StreamAddr = unixAddress;
97const address: Addr = received[1];
98
99const options: OpenOptions = { read: true, write: true };
100const file: FileHandle = await open(new URL("file:///data"), options);
101const bytes = new Uint8Array(new SharedArrayBuffer(8));
102const count: number | null = await file.read(bytes);
103const written: number = await file.writev([bytes] as const);
104const writtenSync: number = file.writevSync([bytes] as const);
105const seek: Promise<number> = file.seek(0, SeekMode.Start);
106const metadata: FileInfo = await file.stat();
107const entries: AsyncIterable<DirEntry> = readDir(".");
108const watcherOptions: WatchOptions = { recursive: true, signal: new AbortController().signal };
109const watcher: FsWatcher = watchFs([".", new URL("file:///tmp")], watcherOptions);
110const events: AsyncIterable<FsEvent> = watcher;
111const kind: FsEventKind = "rescan";
112const disposable: Disposable = file;
113const watchDisposable: Disposable = watcher;
114const response: Promise<Response> = serveFile(new Request("http://localhost/"), file, { consume: false });
115// @ts-expect-error Seek modes are a closed set.
116file.seek(0, 3);
117// @ts-expect-error Reads require byte views.
118file.read(new Uint16Array());
119// @ts-expect-error writev requires an array, not an arbitrary iterator.
120file.writev(new Set([bytes]));
121
122const syncFile: FileHandle = sync.open("data");
123const syncType: sync.FileHandle = file;
124const syncOptions: sync.OpenOptions = options;
125const syncMetadata: sync.FileInfo = sync.stat("data");
126const syncEntries: Iterable<sync.DirEntry> = sync.readDir(".");
127const syncErrors: typeof errors = sync.errors;
128const syncHandle: typeof FileHandle = sync.FileHandle;
129const syncSeek: 2 = sync.SeekMode.End;
130const readCount: number | null = sync.stdin.read(bytes);
131const writeCount: number = sync.stdout.write(bytes);
132const errorCount: number = sync.stderr.write(bytes);
133// @ts-expect-error Sync operations do not return promises.
134const notAsync: Promise<FileInfo> = sync.stat("data");
135// @ts-expect-error Sync paths are strings or URLs.
136sync.open(42);
137
138const raw: ProcessPromise = run("tool");
139const text: ProcessPromise<string> = run("tool", { encoding: "utf-8" });
140const positional: ProcessPromise = run("tool", ["arg"], { check: false });
141const positionalText: ProcessPromise<string> = run("tool", undefined, { encoding: "utf-8" });
142const rawOutput: ProcessOutput = await raw;
143const textOutput: ProcessOutput<string> = await text;
144const outputBytes: Uint8Array | null = rawOutput.stdout;
145const outputText: string | null = textOutput.stdout;
146const shortcutBytes: Promise<Uint8Array> = text.bytes();
147const shortcutText: Promise<string> = raw.text();
148const json: Promise<unknown> = text.json();
149const lines: AsyncIterableIterator<string> = text.lines();
150const syncRaw: ProcessOutput = runSync("tool");
151const syncText: ProcessOutput<string> = runSync("tool", { encoding: "utf-8" });
152const syncPositional: ProcessOutput = runSync("tool", ["arg"]);
153const syncPositionalText: ProcessOutput<string> = runSync("tool", ["arg"], { encoding: "utf-8" });
154const child: ChildProcess = spawn("tool");
155const textChild: ChildProcess<string> = spawn("tool", { encoding: "utf-8" });
156const childPositional: ChildProcess = spawn("tool", ["arg"]);
157const textChildPositional: ChildProcess<string> = spawn("tool", undefined, { encoding: "utf-8" });
158const childOutput: ProcessOutput<string> = await textChild.output();
159const status: ProcessStatus = await child.status;
160const reason: ProcessReason | null = status.reason;
161const terminal: ProcessTerminal | null = child.terminal;
162const childDisposable: AsyncDisposable = child;
163const processOptions: ProcessOptions = { stdin: { handle: file }, input: new ReadableStream<Uint8Array>() };
164const spawnOptions: SpawnOptions = { terminal: { columns: 80 }, extraStdio: { 3: { pipe: "child-to-parent" } } };
165const syncRunOptions: RunSyncOptions = { input: bytes };
166const stdioOption: ProcessStdio = { file: "output", mode: "append" };
167const extra: ExtraProcessStdio = { handle: file, direction: "child-to-parent" };
168// @ts-expect-error Raw output is bytes, not decoded text.
169const wrongText: string | null = rawOutput.stdout;
170// @ts-expect-error Decoded output is text, not bytes.
171const wrongBytes: Uint8Array | null = textOutput.stderr;
172// @ts-expect-error Positional args cannot be repeated in options.
173run("tool", ["arg"], { args: ["again"] });
174// @ts-expect-error Sync runs do not accept streaming input.
175runSync("tool", { input: new ReadableStream<Uint8Array>() });
176// @ts-expect-error Sync runs do not accept an AbortSignal.
177runSync("tool", { signal: new AbortController().signal });
178// @ts-expect-error Spawn results are not promises.
179const notPromise: Promise<unknown> = child;
180// @ts-expect-error Native failure reasons have a closed vocabulary.
181const invalidReason: ProcessReason = "other";
182
183const failure = new ProcessError("failed", rawOutput, new Error());
184const limited = new OutputLimitError(textOutput);
185const timedOut = new ProcessTimeoutError(rawOutput);
186const aborted = new ProcessAbortError(null, "cancel");
187const parent: ProcessError = limited;
188const error: Error = failure;
189const isProcessError: boolean = ProcessError.isError(failure);
190const isOutputLimitError: boolean = OutputLimitError.isError(limited);
191const isProcessTimeoutError: boolean = ProcessTimeoutError.isError(timedOut);
192const isProcessAbortError: boolean = ProcessAbortError.isError(aborted);
193class CustomProcessError extends ProcessError {}
194
195declare const unknownValue: unknown;
196if (unknownValue instanceof Conn) {
197  const addr: StreamAddr = unknownValue.remoteAddr;
198  // @ts-expect-error instanceof alone cannot establish TCP transport.
199  const tcpOnly: Conn<TcpAddr> = unknownValue;
200}
201if (unknownValue instanceof Listener) {
202  const conn: Conn<StreamAddr> = await unknownValue.accept();
203}
204if (unknownValue instanceof DatagramConn) {
205  const addr: UdpAddr = unknownValue.addr;
206}
207if (unknownValue instanceof FileHandle) {
208  const result: FileInfo = await unknownValue.stat();
209}
210if (unknownValue instanceof sync.FileHandle) {
211  const handle: FileHandle = unknownValue;
212}
213if (unknownValue instanceof FsWatcher) {
214  const iterable: AsyncIterable<FsEvent> = unknownValue;
215}
216if (unknownValue instanceof ChildProcess) {
217  const pid: number = unknownValue.pid;
218}
219if (unknownValue instanceof ProcessError) {
220  const error: Error = unknownValue;
221  const output: ProcessOutput<Uint8Array | string> | null = unknownValue.result;
222}
223if (unknownValue instanceof OutputLimitError) {
224  const parent: ProcessError = unknownValue;
225  const error: Error = unknownValue;
226}
227if (unknownValue instanceof ProcessTimeoutError) {
228  const parent: ProcessError = unknownValue;
229}
230if (unknownValue instanceof ProcessAbortError) {
231  const parent: ProcessError = unknownValue;
232}
233// @ts-expect-error Connections cannot be constructed.
234new Conn();
235// @ts-expect-error Listeners cannot be constructed.
236new Listener();
237// @ts-expect-error Datagram sockets cannot be constructed.
238new DatagramConn();
239// @ts-expect-error File handles cannot be constructed.
240new FileHandle();
241// @ts-expect-error Watchers cannot be constructed.
242new FsWatcher();
243// @ts-expect-error Child processes must be spawned.
244new ChildProcess();
245// @ts-expect-error ProcessError's message is required.
246new ProcessError();
247// @ts-expect-error OutputLimitError requires a result.
248new OutputLimitError();
249
250const inputStream: ReadableStream<Uint8Array> = stdin;
251const outputStream: WritableStream<Uint8Array> = stdout;
252const errorStream: WritableStream<Uint8Array> = stderr;
253addSignalListener("SIGTERM", () => {});
254removeSignalListener("SIGTERM", () => {});
255// @ts-expect-error Signal listeners take callbacks.
256addSignalListener("SIGTERM", 42);
257
258// Keep the ordinary filesystem operations represented alongside their sync siblings.
259const created: Promise<FileHandle> = create("file");
260const stats: Promise<FileInfo>[] = [stat("."), lstat(".")];
261const changes: Promise<void>[] = [
262  mkdir("dir"), remove("dir"), rename("a", "b"), copyFile("a", "b"),
263  symlink("target", "link"), chmod("file", 0o600), chown("file", null, null),
264  lchown("link", null, null), utime("file", new Date(), 0), lutime("link", 0, 0),
265  link("a", "b"), truncate("file"),
266];
267const paths: Promise<string>[] = [readLink("link"), realPath(".")];
268const syncCreated: FileHandle = sync.create("file");
269const syncStats: FileInfo[] = [sync.stat("."), sync.lstat(".")];
270const syncChanges: void[] = [
271  sync.mkdir("dir"), sync.remove("dir"), sync.rename("a", "b"), sync.copyFile("a", "b"),
272  sync.symlink("target", "link"), sync.chmod("file", 0o600), sync.chown("file", null, null),
273  sync.lchown("link", null, null), sync.utime("file", new Date(), 0), sync.lutime("link", 0, 0),
274  sync.link("a", "b"), sync.truncate("file"),
275];
276const syncPaths: string[] = [sync.readLink("link"), sync.realPath(".")];