# API ```js import { transform, transformSync, UnsupportedSyntaxError } from "@nibling/typestrip"; ``` ## `transform(src, options?)` Transforms TypeScript asynchronously. - With a **string**, it returns a `Promise` of the whole output. - With an **iterable or async iterable of chunks**, it returns an `AsyncGenerator`. The yielded pieces concatenate to the same output a whole-string call gives. ```js await transform("let x: number = 1;"); // "let x = 1;" let out = ""; for await (const piece of transform(["let x: numb", "er = 1;"])) out += piece; ``` ## `transformSync(src, options?)` The synchronous form. It takes a string or a sync iterable of chunks, and returns a `string` or a `Generator`. An async iterable throws a `TypeError` that points at `transform`. ```js transformSync("let x: number = 1;"); for (const piece of transformSync(["let x: numb", "er = 1;"])) out += piece; ``` ## Chunks A chunk is a `string` or a `Uint8Array` of UTF-8. A byte stream may split a multi-byte character across chunks. typestrip holds the partial sequence until the next chunk completes it. Node `Readable` streams and web `ReadableStream`s are async iterables of chunks, so you can pass them to `transform` as they are. ```js import fs from "node:fs"; for await (const piece of transform(fs.createReadStream("input.ts"))) process.stdout.write(piece); const res = await fetch("/app.ts"); for await (const piece of transform(res.body)) out += piece; ``` Memory stays flat at any input size. The engine keeps only the state it needs to resume at the next chunk. ## Options | Option | Type | Default | Meaning | |---|---|---|---| | `mode` | `"erase" \| "downlevel"` | `"erase"` | Which [mode](modes.md) to run. Any other value throws a `TypeError` before any engine loads. | | `onError` | `({ start, end }) => void` | throw | Called for each construct the mode can't handle. `start` and `end` are UTF-16 offsets into the whole input. The construct stays in the output. | ## Errors Without `onError`, the first unsupported construct throws an `UnsupportedSyntaxError`: - a whole-string `transform` call rejects its `Promise`; - a whole-string `transformSync` call throws; - a generator throws from `next()`. ```js try { transformSync("enum E { A }"); } catch (e) { e instanceof UnsupportedSyntaxError; // true e.code; // "ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX", the same code Node uses e.start; // 0 } ``` With `onError`, nothing throws for unsupported syntax. For chunked input, `onError` fires as each chunk's output comes through the generator. ```js await transform(src, { onError: ({ start, end }) => report(start, end) }); ``` Input the engine can't parse at all throws a `SyntaxError` ("typestrip: could not parse input"). `UnsupportedSyntaxError` is a subclass of `SyntaxError`, so check for it first. ## Loading Neither engine loads at import. Each mode's engine compiles the first time a call needs it, and only once. `transform` and `transformSync` share the compiled module, whichever loads it first. ### Node and other hosts `package.json` picks the entry by export condition, not by runtime detection. | Condition | Entry | Loads the engine with | |---|---|---| | `node` | `src/index.js` | `fs`, so `transformSync` works on the first call | | `default` (browsers, workers, Deno, workerd) | `src/browser.js` | `fetch` and `WebAssembly.compileStreaming` | There is no synchronous `fetch`. In the browser entry, `transformSync` throws until `transform` has loaded that mode's engine once. Await one `transform` call at startup if you need the sync API later. Serve `build/*.wasm` as `application/wasm`. A bundler that resolves `new URL("../build/erase.wasm", import.meta.url)` picks the binaries up; Vite, esbuild with a file loader, and webpack 5 all do. To serve or precache them yourself, import them as `@nibling/typestrip/erase.wasm` and `@nibling/typestrip/downlevel.wasm` (for example, `import.meta.resolve("@nibling/typestrip/erase.wasm")`). [`examples/service-worker`](../examples/service-worker) strips `.ts` modules in a service worker. Both engines need WasmGC and fixed-width SIMD. Every engine that ships WasmGC also ships SIMD. ## Concurrency Streams can run at the same time, interleaved or not. An engine instance keeps its parse state in wasm globals, so it runs one stream at a time. Each call or stream takes an instance from a per-mode pool and returns it when done. A new instance comes from the compiled module, which costs about 0.04 ms (0.2 ms for the first) and 256 KiB of memory on Node 24. The pool keeps every instance it gets back, so repeated bursts of concurrent streams reuse instances instead of creating new ones. One to two seconds after the last stream opens, a timer trims each mode's pool back to 4 idle instances. The timer never keeps a Node, Bun, or Deno process alive. ## `engine(mode?)` Returns the raw wasm exports of `mode`'s first pooled instance, creating it if needed. Tests and profiling scripts use it. It isn't part of the supported API. ## Environment overrides (Node entry) `TYPESTRIP_ERASE_WASM` and `TYPESTRIP_DOWNLEVEL_WASM` point one mode at another wasm file. Each override affects only its own mode. The development tools use them to compare builds.