@live-codes/odin-wasm 0.0.0-stage → 0.2.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Hatem Hosny
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,203 @@
1
- # Temporary Holding Version
1
+ # @live-codes/odin-wasm
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Run **Odin** in a browser with no server, through one API — on the Odin compiler compiled to
4
+ WebAssembly. The assets ship inside the package, so there is no toolchain to install.
5
+
6
+ ```js
7
+ import { createCompiler } from '@live-codes/odin-wasm';
8
+
9
+ const compiler = await createCompiler();
10
+
11
+ const { stdout, stderr, output, errors, exitCode } = await compiler.run(`
12
+ package main
13
+
14
+ import "core:fmt"
15
+
16
+ main :: proc() {
17
+ fmt.println("Hellope!")
18
+ }
19
+ `);
20
+
21
+ console.log(output); // "Hellope!\n" - stdout and stderr in the order the program wrote them
22
+ console.log(exitCode); // 0
23
+ ```
24
+
25
+ That is Node, where the packaged assets can be read off disk. **In a browser there is no filesystem**,
26
+ so a page has to be given a URL — copy the assets into whatever serves your page and pass it:
27
+
28
+ ```bash
29
+ npx --package @live-codes/odin-wasm odin-wasm-copy-assets public/odin
30
+ ```
31
+
32
+ ```js
33
+ const compiler = await createCompiler({ baseUrl: new URL('/odin/', location.href) });
34
+ ```
35
+
36
+ ## How it works
37
+
38
+ The compiler is a WASI command module, built by
39
+ [wodin](https://github.com/karl-zylinski/wodin) — the fork of Odin with a **direct WebAssembly
40
+ backend**, which emits wasm with no LLVM and no linker, and is what makes a browser-sized compiler
41
+ possible. The standard library is a single gzipped container the package reads into an in-memory file
42
+ system; the compiler never touches disk.
43
+
44
+ ```
45
+ your Odin source
46
+ → odin.wasm the compiler (a WASI module) → a wasm module
47
+ → run it wasi_wasm32: a WASI host, in this package
48
+ js_wasm32: the browser's DOM/canvas, through Odin's JS runtime → stdout / page
49
+ ```
50
+
51
+ ## API
52
+
53
+ ### `createCompiler(options)`
54
+
55
+ Returns a promise for a compiler. The heavy part — the compiler module and the packed standard
56
+ library, about 6 MB — is fetched here, so a bad `baseUrl` fails at this point rather than at the first
57
+ `run`. Compilers created against the same assets share one engine.
58
+
59
+ | Option | Meaning |
60
+ | --- | --- |
61
+ | `baseUrl` | Where the assets are served from. **Optional in Node**, where omitting it uses the files the package ships; required anywhere else, and absolute http(s) except in a browser, where it may be relative to the page. |
62
+ | `target` | `wasi_wasm32` (default) or `js_wasm32`. See [Targets](#targets). |
63
+ | `args` | Default program argv. |
64
+ | `compileArgs` | Extra compiler flags. |
65
+ | `fileName` | The name the source is compiled under. |
66
+ | `onProgress` | `({ name, loaded, total }) => {}` while the assets download. |
67
+
68
+ ### `compiler.run(code, input, runOptions?)`
69
+
70
+ Compiles and runs. `input` is stdin as a string or `Uint8Array`, handed to the program once.
71
+ `runOptions` may override `target`, `args`, `compileArgs` and `fileName` for that run.
72
+
73
+ | Field | Meaning |
74
+ | --- | --- |
75
+ | `stdout` | Everything the program wrote to fd 1. |
76
+ | `stderr` | Everything it wrote to fd 2. |
77
+ | `output` | Both, in the order the program wrote them. |
78
+ | `errors` | The compiler's diagnostics, one string per line. **Empty when the program compiled.** |
79
+ | `exitCode` | The program's exit status, or `null` if it never ran because it did not compile. |
80
+ | `compileMs` | Wall clock for the compile. |
81
+ | `runMs` | Wall clock for the run, or `null` if it did not run. |
82
+
83
+ `errors` is empty on success so that `errors.length` is a reliable failure test. On failure it holds
84
+ what the compiler said, and if the compiler said nothing it holds the underlying error's message, so a
85
+ failure is never silent.
86
+
87
+ ### `compiler.compile(code, options?)`
88
+
89
+ Compiles without running, for a host that runs the module itself. A page that needs the DOM cannot use
90
+ `run`: a worker has no `document`, and Odin's JS runtime is written for the page, so the module has to
91
+ be instantiated on the main thread. This hands it over.
92
+
93
+ | Field | Meaning |
94
+ | --- | --- |
95
+ | `ok` | Whether the program compiled. |
96
+ | `wasm` | The module (a `Uint8Array`), or `null` if it did not compile. |
97
+ | `errors` | The compiler's diagnostics, one string per line. |
98
+ | `compileMs` | Wall clock for the compile. |
99
+
100
+ ```js
101
+ const { ok, wasm, errors } = await compiler.compile(source, { target: 'js_wasm32' });
102
+ ```
103
+
104
+ `assets/odin.js` is Odin's own JS runtime — what such a module runs on. `importScripts` it, then
105
+ instantiate with `odin.setupDefaultImports(...)`, as the [demo page](https://github.com/live-codes/browser-odin)
106
+ does; the program then reaches the DOM and a canvas.
107
+
108
+ ### `compiler.dispose()`
109
+
110
+ Drops this compiler's hold on the shared engine. Further runs throw.
111
+
112
+ ### `TARGETS`
113
+
114
+ The two targets, for building a picker: `['wasi_wasm32', 'js_wasm32']`.
115
+
116
+ ## Targets
117
+
118
+ | target | runs on | what it reaches |
119
+ | --- | --- | --- |
120
+ | `wasi_wasm32` (default) | a WASI host in this package | `core:fmt`, **stdin**, `os.args`, exit codes — and it works in Node |
121
+ | `js_wasm32` | the Odin JS runtime, which a page provides | `core:fmt`, **stdin**, the DOM and a `<canvas>` |
122
+
123
+ `js_wasm32` is the browser target: a page runs it with Odin's own `odin.js` so the program can reach
124
+ the DOM and a canvas. Outside a page there is no DOM, so this package gives it a host that supports
125
+ the console, stdin and `js.evaluate` and stubs the rest — enough to run and test a console program,
126
+ and to draw through `js.evaluate` when a page embeds it.
127
+
128
+ Standard input works on **both**, which is not a given: upstream's `core:os` is deliberately inert on
129
+ the js target (a compile-time `#panic("core:os is unsupported on js/wasm")`), so the standard library
130
+ that ships here carries two small patches that give it a real `os.stdin` over the host's
131
+ `odin_env.read`. See the repository's `FINDINGS.md`.
132
+
133
+ ## Loading it without a bundler
134
+
135
+ `dist/odin-wasm.global.js` is a **minified IIFE bundle** — one classic script, 16.5 KB — for anywhere
136
+ an ES module cannot go: a classic (non-module) worker, a plain `<script>`, a CDN URL handed to
137
+ `importScripts()`. It sets `self.odinWasm` to the same two exports (`createCompiler`, `TARGETS`).
138
+
139
+ ```js
140
+ // worker.js - a classic worker: no { type: 'module' }, no imports
141
+ importScripts('odin-wasm.global.js');
142
+
143
+ const compiler = await self.odinWasm.createCompiler({ baseUrl: '/odin/' });
144
+ const { stdout, exitCode } = await compiler.run('package main\n\nimport "core:fmt"\n\nmain :: proc() { fmt.println("hi") }\n');
145
+ ```
146
+
147
+ It is reachable as `@live-codes/odin-wasm/iife`, and it is committed rather than built on install, so
148
+ a consumer never needs esbuild. Rebuild it with `npm run build:iife`.
149
+
150
+ ## Where the assets come from
151
+
152
+ ```
153
+ assets/odin.wasm 3.6 MB the compiler (a WASI module, built with wasi-sdk)
154
+ assets/odin-src.pack.gz 2.4 MB Odin's base/ and core/, gzipped
155
+ assets/odin.js 72 KB Odin's own JS runtime, for running a js_wasm32 module on a page
156
+ ```
157
+
158
+ Three ways to reach them:
159
+
160
+ - **In Node, nothing.** Omit `baseUrl` and the package reads those files directly.
161
+ - **In a browser, one command.** `odin-wasm-copy-assets <dir>` writes them into a directory you
162
+ already serve, plus an `asset-receipts.json` describing its own bytes.
163
+ - **Or point `baseUrl` at a host you already have** — a CDN, an S3 bucket — serving those two paths.
164
+
165
+ Every asset is checked against a pinned SHA-256 receipt (`src/asset-receipts.js`) before it is used,
166
+ so a truncated download or a host serving something else is an error rather than a wasm
167
+ instantiation failure a step later.
168
+
169
+ The pack is Odin's standard library with `core/rexcode` (the machine-code library, tens of MB of
170
+ binaries) and other-target assembly left out. The `vendor` collection is **not** shipped: the compiler
171
+ requires the directory to exist, so it is present and empty, but a program that imports a `vendor:`
172
+ package will not find it.
173
+
174
+ ## What does not work
175
+
176
+ | | why |
177
+ | --- | --- |
178
+ | **`vendor:` packages** (`vendor:raylib`, …) | not shipped: 91 MB, mostly DirectX/SDL and C libraries with prebuilt wasm objects |
179
+ | **the DOM through `run()`** | a worker — and Node — have no `document`. Use `compile()` and instantiate on a page with `assets/odin.js`; that is what the LiveCodes result page does |
180
+ | **`js_wasm32` exit codes** | Odin's js runtime exits by trapping, so a program's status is reported as 1 rather than the number it passed |
181
+ | **threads, files, sockets** | WASI preview 1 here is single-threaded, and a program is given an empty filesystem |
182
+ | **a runaway program** | the compiler and the program both run synchronously; there is no cancellation |
183
+
184
+ ## Development
185
+
186
+ ```bash
187
+ npm test # 17 checks: both targets, stdin, args, exit codes, diagnostics, the bundle, the receipts
188
+ npm run check-package # what `prepack` runs: every asset hashes to its receipt and the bundle is present
189
+ npm run sync-assets # copy ../../assets into this package and re-pin the receipts
190
+ npm run build:iife # rebuild dist/ (needs esbuild; `npm install --include=dev`)
191
+ ```
192
+
193
+ `src/wasi-host.js` is the same WASI host as the demo repository's `public/wasi.js`, carried here so
194
+ the package stands alone; the two are kept in step by hand.
195
+
196
+ The tests run against the packaged assets with no server. `test/iife.test.js` loads the committed
197
+ bundle in a `vm` context and checks its shape, so the shipped file cannot rot unnoticed.
198
+
199
+ ## License
200
+
201
+ **MIT.** The compiler module and the packed standard library are Odin's, under its **zlib** license —
202
+ see [THIRD-PARTY-NOTICES.md](THIRD-PARTY-NOTICES.md), which also records the two standard-library
203
+ files this package ships modified.
@@ -0,0 +1,41 @@
1
+ # Third-party notices
2
+
3
+ This package's own code is MIT (see [LICENSE](LICENSE)). What it ships and runs is other people's, and
4
+ this file records it.
5
+
6
+ ## The assets in `assets/`
7
+
8
+ Built, not written here; both come from a checkout of
9
+ [**wodin**](https://github.com/karl-zylinski/wodin), the fork of
10
+ [**Odin**](https://github.com/odin-lang/Odin) that adds a direct WebAssembly backend.
11
+
12
+ - **`assets/odin.wasm`** — the Odin compiler, compiled to `wasm32-wasip1`. Odin is under the **zlib**
13
+ license, and the module bundles what Odin's own sources do:
14
+ - **libtommath** — public domain.
15
+ - **utf8proc** — MIT.
16
+ - **ucg** — MIT.
17
+ - **`assets/odin-src.pack.gz`** — Odin's standard library (`base/` and `core/`), packed. Same
18
+ repository, same zlib license.
19
+
20
+ ### Modified standard-library sources
21
+
22
+ The pack is **not** a pristine copy of Odin's standard library. Two files replace the checkout's
23
+ copies so the `js_wasm32` target can have standard input; the modified versions are reproduced in the
24
+ repository this package is developed in, under `patches/`:
25
+
26
+ - `core/os/wasm.odin` — upstream's compile-time `#panic("core:os is unsupported on js/wasm")` is removed.
27
+ - `core/os/file_js.odin` — upstream's inert stubs are kept, but `os.stdin`, `os.stdout` and
28
+ `os.stderr` are given a real implementation over a host `odin_env.read` / `odin_env.write`.
29
+
30
+ Both are Odin source, under Odin's zlib license; the modifications are the package author's.
31
+
32
+ ## Toolchain used to build the compiler
33
+
34
+ - **wasi-sdk** (clang/lld and the WASI sysroot, including wasi-libc) — Apache-2.0 WITH
35
+ LLVM-exception; wasi-libc is separately MIT and Apache-2.0 WITH LLVM-exception.
36
+
37
+ ## Runtime
38
+
39
+ Nothing else is bundled. The WASI host, the js-target host, the pack reader and the IIFE bundle are
40
+ this package's own code, and the API uses only standard globals (`WebAssembly`, `fetch`,
41
+ `DecompressionStream`, `crypto.subtle`, `TextDecoder`) — there are no runtime dependencies.
Binary file