@shd101wyy/yo 0.1.17 → 0.1.19

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.
@@ -0,0 +1,398 @@
1
+ # Yo Syntax Cheatsheet
2
+
3
+ These are baseline syntax rules for portable Yo code.
4
+
5
+ ## Mental model
6
+
7
+ - Everything is an expression.
8
+ - Separators change meaning:
9
+ - commas build tuples, arrays, or struct literals
10
+ - semicolons create sequencing or type shapes
11
+ - Prefer explicit syntax over relying on parser guesswork.
12
+
13
+ ## Common declaration forms
14
+
15
+ ```rust
16
+ { println } :: import "std/fmt";
17
+
18
+ app_name :: "yo-demo";
19
+
20
+ main :: (fn() -> unit)({
21
+ value := i32(1);
22
+ (message : str) = "hello";
23
+ println(message);
24
+ });
25
+
26
+ export main;
27
+ ```
28
+
29
+ - Top-level binding: `name :: expr;`
30
+ - Local binding: `name := expr;`
31
+ - Typed binding: `(name : Type) = expr;`
32
+ - Function definition: `name :: (fn(args...) -> ReturnType)(body);`
33
+
34
+ ## Blocks and expressions
35
+
36
+ | Goal | Write | Avoid |
37
+ | ----------------- | -------------------------- | -------------------------- |
38
+ | Single expression | `cond(...)` | `{ cond(...) }` |
39
+ | Begin block | `{ x := i32(1); x }` | `{ x := i32(1), x }` |
40
+ | Struct literal | `{ name: "yo", ok: true }` | `{ name: "yo"; ok: true }` |
41
+
42
+ ```rust
43
+ result := cond(
44
+ ready => .Ok(()),
45
+ true => .Err(`not ready`)
46
+ );
47
+
48
+ total := {
49
+ base := i32(40);
50
+ (base + i32(2))
51
+ };
52
+ ```
53
+
54
+ Remember: `{ expr }` without semicolons is a struct literal, not a block.
55
+
56
+ ## Control flow
57
+
58
+ ```rust
59
+ value := cond(
60
+ (x < i32(0)) => i32(-1),
61
+ (x == i32(0)) => i32(0),
62
+ true => i32(1)
63
+ );
64
+
65
+ label := match(token,
66
+ .Identifier(name) => name,
67
+ .Number(_) => "number",
68
+ .Eof => "eof"
69
+ );
70
+
71
+ if(done, println("done"), println("pending"));
72
+ ```
73
+
74
+ - Always write `cond(...)`, never bare `cond ...`
75
+ - Always write `match(...)`, never bare `match ...`
76
+ - `if(a, b)` and `if(a, b, c)` are macro forms over `cond`
77
+
78
+ ## String types
79
+
80
+ | Syntax | Type | Context |
81
+ | ------------------ | ----------------- | -------------------------------- |
82
+ | `"hello"` | `str` | Runtime contexts (most code) |
83
+ | `"hello"` | `comptime_string` | Inside `comptime` functions |
84
+ | `` `hello ${x}` `` | `String` | Always (template string) |
85
+ | `` `hello` `` | `String` | Always (template without interp) |
86
+ | `*(u8)("hello")` | `*(u8)` | Pointer cast for C interop |
87
+
88
+ Key rules:
89
+
90
+ - In **runtime** code, `"hello"` is `str`. Mixing literals and variables in `cond`/`match` branches is fine.
91
+ - In **comptime** functions (return type `comptime(...)`), `"hello"` is `comptime_string` — it does NOT auto-convert to `str`.
92
+ - For `String` constants, prefer `` `hello` `` over `String.from("hello")`.
93
+
94
+ ## Calls, operators, and whitespace
95
+
96
+ ```rust
97
+ sum := add(i32(1), i32(2));
98
+ flag := ((a > b) && (b > c));
99
+ masked := ((A | B) | C);
100
+ ```
101
+
102
+ - Prefer parenthesized calls: `func(arg1, arg2)`
103
+ - `func (a, b)` is a different parse shape than `func(a, b)`
104
+ - Yo has no operator precedence; fully parenthesize binary expressions
105
+ - Parenthesize unary operands: `!(ready)`, `-(value)`
106
+
107
+ ## Functions and methods
108
+
109
+ ```rust
110
+ double :: (fn(x : i32) -> i32)(
111
+ (x * i32(2))
112
+ );
113
+
114
+ Counter :: struct(current : i32);
115
+
116
+ impl(Counter,
117
+ next : (fn(self : Self) -> i32)({
118
+ self.current = (self.current + i32(1));
119
+ self.current
120
+ })
121
+ );
122
+ ```
123
+
124
+ - No space between a function type and its body: `(fn(...) -> T)(...)`
125
+ - Use `Self` in method signatures
126
+ - Wrap `fn` types in parentheses when they appear after `:`
127
+
128
+ ### Named arguments and default values
129
+
130
+ ```rust
131
+ create_user :: (fn(
132
+ name : String,
133
+ (age : i32) ?= 18
134
+ ) -> User)(
135
+ User(name: name, age: age)
136
+ );
137
+
138
+ create_user(name: `Alice`);
139
+ create_user(name: `Bob`, age: 30);
140
+ ```
141
+
142
+ - Named arguments must keep the same order as the definition
143
+ - Default values use `?=` and must be compile-time known
144
+
145
+ ### Implicit parameters (`using` / `given`)
146
+
147
+ ```rust
148
+ Raise :: (fn(msg : String) -> i32);
149
+
150
+ safe_divide :: (fn(x : i32, y : i32, using(raise : Raise)) -> i32)(
151
+ cond(
152
+ (y == i32(0)) => raise(`divide by zero`),
153
+ true => (x / y)
154
+ )
155
+ );
156
+
157
+ caller :: (fn() -> i32)({
158
+ (given(raise) : Raise) = (fn(msg : String) -> i32)({
159
+ return i32(0);
160
+ });
161
+
162
+ safe_divide(i32(10), i32(0))
163
+ });
164
+ ```
165
+
166
+ - `using(name : Type)` declares an implicit parameter (effect)
167
+ - `given(name) := Type(fields...)` installs a handler in the caller's scope
168
+ - Effects are matched by **type**, not by name
169
+ - The handler is auto-resolved at call sites; pass explicitly with `using(name)`
170
+
171
+ ### Closures and anonymous functions
172
+
173
+ ```rust
174
+ (closure : Impl(Fn(x : i32) -> i32)) = ((x) => (x + i32(1)));
175
+
176
+ result := closure(i32(5));
177
+
178
+ transform :: (fn(list : ArrayList(i32), f : Impl(Fn(x : i32) -> i32)) -> unit)({
179
+ for list.iter(), (ptr) => {
180
+ ptr.* = f(ptr.*);
181
+ };
182
+ });
183
+ ```
184
+
185
+ - `(params) => expr` — lambda / closure syntax
186
+ - `Impl(Fn(params) -> ReturnType)` — closure type
187
+ - Value types are captured by copy; object types by reference
188
+ - Each closure has a unique type; you cannot assign different closures to the same variable
189
+
190
+ ## Imports and modules
191
+
192
+ ```rust
193
+ { Parser } :: import "./parser.yo";
194
+ parser_module :: import "./parser.yo";
195
+
196
+ open import "std/string";
197
+ { ArrayList } :: import "std/collections/array_list";
198
+ ```
199
+
200
+ - Use relative imports for nearby `.yo` files
201
+ - Use `open import "std/module"` for standard-library modules you want fully in scope
202
+ - Do not write `import "./file.yo" as name`
203
+ - Do not import `std/prelude`
204
+
205
+ ## Enums and pattern matching
206
+
207
+ ```rust
208
+ Option :: (fn(comptime(T) : Type) -> comptime(Type))(
209
+ enum(None, Some(value : T))
210
+ );
211
+
212
+ (value : Option(i32)) = .Some(i32(42));
213
+
214
+ text := match(value,
215
+ .Some(inner) => "present",
216
+ .None => "missing"
217
+ );
218
+ ```
219
+
220
+ - Enum definitions omit the leading `.`
221
+ - Construction and match branches use the leading `.`
222
+ - Nested destructuring is not supported; match one layer at a time
223
+
224
+ ## Generics and compile-time
225
+
226
+ ```rust
227
+ identity :: (fn(forall(T : Type), value : T) -> T)(value);
228
+
229
+ max :: (fn(comptime(a) : i32, comptime(b) : i32) -> comptime(i32))(
230
+ cond((a > b) => a, true => b)
231
+ );
232
+
233
+ show :: (fn(forall(T : Type), value : T, where(T <: ToString)) -> unit)(
234
+ println(value)
235
+ );
236
+ ```
237
+
238
+ - `forall(T : Type)` introduces a generic type parameter
239
+ - `comptime(x) : T` makes a parameter compile-time only
240
+ - `where(T <: Trait)` constrains a type parameter
241
+ - Functions returning `comptime(...)` are evaluated at compile time
242
+
243
+ ## Exports
244
+
245
+ ```rust
246
+ main :: (fn() -> unit)(());
247
+ export main;
248
+
249
+ export
250
+ helper,
251
+ Config
252
+ ;
253
+ ```
254
+
255
+ - `export name;` exports a single binding
256
+ - Block form exports multiple bindings separated by commas
257
+ - Every executable needs `export main;`
258
+
259
+ ## Static and dynamic dispatch types
260
+
261
+ ```rust
262
+ show :: (fn(value : Impl(ToString)) -> unit)(
263
+ println(value)
264
+ );
265
+
266
+ (erased : Dyn(ToString)) = dyn(i32(42));
267
+ println(erased);
268
+ ```
269
+
270
+ - `Impl(Trait)` — static dispatch; concrete type chosen at compile time
271
+ - `Dyn(Trait)` — dynamic dispatch via trait object
272
+ - `dyn(expr)` wraps a concrete value into its `Dyn(Trait)` form
273
+
274
+ ## Naming conventions
275
+
276
+ | Kind | Style | Example |
277
+ | --------------------------- | ------------------ | ------------------ |
278
+ | File / directory / module | `snake_case` | `array_list` |
279
+ | Function / variable | `snake_case` | `safe_divide` |
280
+ | Trait / type / enum variant | `PascalCase` | `ToString`, `Some` |
281
+ | Constant | `UPPER_SNAKE_CASE` | `MAX_SIZE` |
282
+
283
+ Use 2-space indentation.
284
+
285
+ ## Recursion and loops
286
+
287
+ ```rust
288
+ factorial :: (fn(n : i32) -> i32)(
289
+ cond(
290
+ (n <= i32(1)) => i32(1),
291
+ true => (n * recur((n - i32(1))))
292
+ )
293
+ );
294
+
295
+ while runtime(true), {
296
+ work();
297
+ };
298
+ ```
299
+
300
+ - Use `recur(...)` for self-recursion
301
+ - `while true` runs at compile time
302
+ - Use `while runtime(true), { ... }` for open-ended runtime loops
303
+
304
+ ## Return and branch safety
305
+
306
+ ```rust
307
+ // WRONG — return consumes the comma, capturing the next match branch:
308
+ match(opt,
309
+ .Some(v) => return v, // parsed as return(v, .None => ...)
310
+ .None => default_value()
311
+ );
312
+
313
+ // CORRECT — begin blocks isolate return from the comma:
314
+ match(opt,
315
+ .Some(v) => {
316
+ return v;
317
+ },
318
+ .None => {
319
+ return default_value();
320
+ }
321
+ );
322
+
323
+ // BEST — expression-bodied function, no return needed:
324
+ get_value :: (fn(opt : Option(i32)) -> i32)(
325
+ match(opt,
326
+ .Some(v) => v,
327
+ .None => i32(0)
328
+ )
329
+ );
330
+ ```
331
+
332
+ - `return expr1, expr2` parses as a single function call: `return(expr1, expr2)`
333
+ - In `cond` or `match` branches, **always use begin blocks** when you need `return`
334
+ - If the whole function is one expression, prefer expression-bodied style and skip `return` entirely
335
+ - The same trap applies to any function call without parens in match branches
336
+
337
+ ## Iterator and for loop
338
+
339
+ ```rust
340
+ { ArrayList } :: import "std/collections/array_list";
341
+
342
+ list := ArrayList(i32).new();
343
+ list.push(i32(10));
344
+ list.push(i32(20));
345
+
346
+ for list.iter(), (ptr) => {
347
+ println(ptr.*);
348
+ };
349
+
350
+ for list.into_iter(), (value) => {
351
+ println(value);
352
+ };
353
+ ```
354
+
355
+ - `for collection, (variable) => { body }` iterates via the `Iterator` trait
356
+ - `.iter()` borrows the collection and yields pointers
357
+ - `.into_iter()` takes ownership and yields values
358
+
359
+ ## Testing
360
+
361
+ ```rust
362
+ test "Addition works", {
363
+ assert(((i32(1) + i32(1)) == i32(2)), "1+1 should be 2");
364
+ };
365
+
366
+ test "Compile-time check", {
367
+ comptime_assert((2 + 2) == 4);
368
+ comptime_expect_error({ x :: (1 / 0); });
369
+ };
370
+
371
+ test "Async test", {
372
+ io.await(yield());
373
+ };
374
+ ```
375
+
376
+ - `test "description", { body }` defines a test — `io : IO` is automatically available
377
+ - All tests can use `io.async(...)`, `io.await(...)`, etc. without a `using` clause
378
+ - `assert(condition, "message")` — runtime assertion (always include a message)
379
+ - `comptime_assert(condition)` — compile-time assertion
380
+ - `comptime_expect_error(expr)` — verify code produces a compile error
381
+
382
+ ## Advanced features (reference)
383
+
384
+ These features are powerful but less commonly used. Consult the linked docs for full details.
385
+
386
+ | Feature | Syntax hint | Documentation |
387
+ | ---------------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
388
+ | Higher-Kinded Types | `forall(F : (fn(comptime(T) : Type) -> comptime(Type)))` | [DESIGN.md § HKT](https://github.com/shd101wyy/Yo/blob/develop/docs/en-US/DESIGN.md#higher-kinded-types-hkt) |
389
+ | GADTs | `enum(IntVal(i : i32) -> recur(i32))` | [GADTS.md](https://github.com/shd101wyy/Yo/blob/develop/docs/en-US/GADTS.md) |
390
+ | Derive traits | `derive(MyType, Eq, Hash, Clone, Ord, ToString)` | [DERIVE_TRAITS.md](https://github.com/shd101wyy/Yo/blob/develop/docs/en-US/DERIVE_TRAITS.md) |
391
+ | Type reflection | `Type.get_info(T)` returns `TypeInfo` | [TYPE_REFLECTION.md](https://github.com/shd101wyy/Yo/blob/develop/docs/en-US/TYPE_REFLECTION.md) |
392
+ | Inline assembly | `asm("mov {0}, #42", out(reg, i32))` | [INLINE_ASSEMBLY.md](https://github.com/shd101wyy/Yo/blob/develop/docs/en-US/INLINE_ASSEMBLY.md) |
393
+ | Metaprogramming | `quote(...)`, `unquote(...)`, `unquote_splicing(...)` | [DESIGN.md § Meta](https://github.com/shd101wyy/Yo/blob/develop/docs/en-US/DESIGN.md#meta-programming) |
394
+ | Effect row variables | `forall(...(E))` with `using(...(E))` | [ALGEBRAIC_EFFECTS.md](https://github.com/shd101wyy/Yo/blob/develop/docs/en-US/ALGEBRAIC_EFFECTS.md) |
395
+ | Custom derive rules | `derive_rule(MyTrait, (fn(...) -> unquote(Expr)){...})` | [DERIVE_TRAITS.md](https://github.com/shd101wyy/Yo/blob/develop/docs/en-US/DERIVE_TRAITS.md#user-defined-derive-rules) |
396
+ | Isolated types | `Iso(T)` for data-race-free parallelism | [ISOLATED.md](https://github.com/shd101wyy/Yo/blob/develop/docs/en-US/ISOLATED.md) |
397
+ | Arc (atomic ref count) | `arc(value)`, `shared.(*)` for cross-thread sharing | [ARC.md](https://github.com/shd101wyy/Yo/blob/develop/docs/en-US/ARC.md) |
398
+ | Parallelism | Thread pool, `io.spawn` for parallel work | [PARALLELISM.md](https://github.com/shd101wyy/Yo/blob/develop/docs/en-US/PARALLELISM.md) |
@@ -0,0 +1,46 @@
1
+ ---
2
+ name: yo-wasm-integration
3
+ description: Build Yo libraries for WebAssembly and publish as npm packages. Use this when working with WASM targets, Emscripten, WASI, npm packaging, JavaScript/TypeScript wrappers, or browser/Node.js integration.
4
+ argument-hint: "[WASM task, target, or integration question]"
5
+ ---
6
+
7
+ # Yo WASM Integration
8
+
9
+ Use this skill for compiling Yo projects to WebAssembly and packaging them for JavaScript/TypeScript consumption via npm.
10
+
11
+ ## When to use this skill
12
+
13
+ Use this skill when you need to:
14
+
15
+ - compile a Yo project to WebAssembly (Emscripten or WASI)
16
+ - set up an `npm/` package directory with JavaScript/TypeScript wrappers
17
+ - expose Yo functions to JavaScript via WASM memory and C ABI
18
+ - integrate a Yo WASM module into a Node.js or browser project
19
+ - debug WASM-specific compilation or runtime issues
20
+ - publish a Yo-backed npm package
21
+
22
+ ## Workflow
23
+
24
+ 1. Decide on target: Emscripten (browser + Node.js) or WASI (standalone/server).
25
+ 2. Define WASM build steps in `build.yo` using `build.executable` with `target: build.CompilationTarget.Wasm32_Emscripten` and `add_c_flags(...)` for Emscripten settings.
26
+ 3. Create a JavaScript wrapper in `npm/` that loads the WASM module and exposes a clean API.
27
+ 4. Add TypeScript declarations (`index.d.ts`) for type safety.
28
+ 5. Test the WASM module from both Node.js and browser contexts.
29
+ 6. Consult the [WASM integration cheatsheet](./wasm-integration-cheatsheet.md) for patterns.
30
+
31
+ ## High-signal rules
32
+
33
+ - In `build.yo`, use `target: build.CompilationTarget.Wasm32_Emscripten` for Emscripten WASM builds. Use `step.add_c_flags(...)` for Emscripten `-s` options.
34
+ - For single-file compilation, use `yo compile --cc emcc` or `yo compile --target wasm-wasi`.
35
+ - WASM functions communicate through linear memory — pass pointers and lengths, not Yo types.
36
+ - JavaScript wrappers handle the string encoding/decoding boundary (`TextEncoder`/`TextDecoder`).
37
+ - Use bitmask flags (powers of 2) to pass option sets through a single `i32` parameter.
38
+ - Yo's `str` and `String` types are internal — export raw `*(u8)` + `usize` pairs for WASM APIs.
39
+ - Always test with `--sanitize address` on native first, then verify WASM behavior matches.
40
+ - Emscripten exports are controlled by `-sEXPORTED_FUNCTIONS` and `-sEXPORTED_RUNTIME_METHODS`.
41
+ - WASM modules can be loaded in Node.js using `WebAssembly.instantiate` or Emscripten's generated JS glue.
42
+ - Errno values differ on WASM — use constants from `std/libc/errno`, never hardcode numbers.
43
+
44
+ ## Resource
45
+
46
+ - [WASM integration cheatsheet](./wasm-integration-cheatsheet.md)
@@ -0,0 +1,250 @@
1
+ # Yo WASM Integration Cheatsheet
2
+
3
+ Patterns for building Yo libraries as WebAssembly modules and consuming them from JavaScript/TypeScript.
4
+
5
+ ## Compilation targets
6
+
7
+ | Target | Command | Output |
8
+ | -------------------- | -------------------------------------------------- | ----------------------------- |
9
+ | Emscripten (browser) | `yo compile src/api.yo --cc emcc --release -o api` | `.js` + `.wasm` |
10
+ | WASI (standalone) | `yo compile src/api.yo --target wasm-wasi -o api` | `.wasm` (runs via `wasmtime`) |
11
+ | Native (testing) | `yo compile src/api.yo --release -o api` | Native binary |
12
+
13
+ ## WASM API design pattern
14
+
15
+ Export C-compatible functions that operate on linear memory:
16
+
17
+ ```rust
18
+ // src/wasm_api.yo
19
+ open import "std/string";
20
+
21
+ // Allocate WASM memory for the caller
22
+ wasm_alloc :: (fn(size : usize) -> *(u8))(
23
+ malloc(size)
24
+ );
25
+
26
+ // Free WASM memory
27
+ wasm_free :: (fn(ptr : *(u8)) -> unit)(
28
+ free(ptr)
29
+ );
30
+
31
+ // Process input and return result pointer + length
32
+ render :: (fn(input_ptr : *(u8), input_len : usize, flags : i32) -> *(u8))({
33
+ (input : str) = str.from_raw_parts(input_ptr, input_len);
34
+ // ... process input ...
35
+ result := do_work(input);
36
+ // Write length to a known location, return pointer
37
+ result_ptr
38
+ });
39
+
40
+ export wasm_alloc;
41
+ export wasm_free;
42
+ export render;
43
+ ```
44
+
45
+ Key rules:
46
+
47
+ - Export only `fn` functions with primitive or pointer types
48
+ - Use `*(u8)` + `usize` for strings across the boundary
49
+ - Use `i32` bitmask flags for option sets (powers of 2)
50
+ - Always provide `wasm_alloc` / `wasm_free` for the JavaScript side
51
+
52
+ ## Bitmask flags pattern
53
+
54
+ Pass multiple boolean options as a single `i32` using bit flags:
55
+
56
+ ```rust
57
+ // Each option is a power of 2
58
+ // bit 0 = 1 : feature_a
59
+ // bit 1 = 2 : feature_b
60
+ // bit 2 = 4 : feature_c
61
+ // bit 3 = 8 : feature_d
62
+
63
+ parse_flags :: (fn(flags : i32) -> Options)({
64
+ (opts : Options) = Options.default();
65
+ opts.feature_a = ((flags & i32(1)) != i32(0));
66
+ opts.feature_b = ((flags & i32(2)) != i32(0));
67
+ opts.feature_c = ((flags & i32(4)) != i32(0));
68
+ opts.feature_d = ((flags & i32(8)) != i32(0));
69
+ opts
70
+ });
71
+ ```
72
+
73
+ JavaScript side:
74
+
75
+ ```javascript
76
+ function buildFlags(options) {
77
+ let flags = 0;
78
+ if (options.featureA) flags |= 1;
79
+ if (options.featureB) flags |= 2;
80
+ if (options.featureC) flags |= 4;
81
+ if (options.featureD) flags |= 8;
82
+ return flags;
83
+ }
84
+ ```
85
+
86
+ ## build.yo WASM target
87
+
88
+ The `Executable` struct accepts: `name`, `root`, `target`, `optimize`, `allocator`, `sanitize`.
89
+ Emscripten-specific flags go in `add_c_flags(...)` after creating the step.
90
+
91
+ ```rust
92
+ build :: import "std/build";
93
+
94
+ wasm_api :: build.executable({
95
+ name: "my_lib_wasm_api",
96
+ root: "./src/wasm_api.yo",
97
+ target: build.CompilationTarget.Wasm32_Emscripten,
98
+ optimize: build.Optimize.ReleaseSmall,
99
+ allocator: build.Allocator.Libc
100
+ });
101
+ wasm_api.add_c_flags("-O3 -flto -mbulk-memory -sALLOW_MEMORY_GROWTH -sENVIRONMENT=web,node -sMODULARIZE=1 -sEXPORT_NAME=createModule -sEXPORTED_FUNCTIONS=_wasm_alloc,_wasm_free,_render -sEXPORTED_RUNTIME_METHODS=HEAPU8");
102
+
103
+ install :: build.step("install", "Build WASM module");
104
+ install.depend_on(wasm_api);
105
+ ```
106
+
107
+ Available targets: `Wasm32_Emscripten`, `Wasm32_Wasi`, `X86_64_Linux_Gnu`, `Aarch64_Macos`, etc.
108
+ Available optimizations: `Debug`, `ReleaseSafe`, `ReleaseFast`, `ReleaseSmall`.
109
+ Available allocators: `Mimalloc` (default), `Libc`.
110
+
111
+ ## npm package structure
112
+
113
+ ```text
114
+ npm/
115
+ ├── package.json # npm package metadata
116
+ ├── index.js # JavaScript wrapper (loads WASM, exposes clean API)
117
+ ├── index.d.ts # TypeScript declarations
118
+ ├── my_lib_wasm_api.js # Emscripten-generated JS glue (build artifact)
119
+ └── my_lib_wasm_api.wasm # WASM binary (build artifact)
120
+ ```
121
+
122
+ ## JavaScript wrapper pattern
123
+
124
+ ```javascript
125
+ // npm/index.js
126
+ const createModule = require("./my_lib_wasm_api.js");
127
+
128
+ let moduleInstance = null;
129
+
130
+ async function initModule() {
131
+ if (!moduleInstance) {
132
+ moduleInstance = await createModule();
133
+ }
134
+ return moduleInstance;
135
+ }
136
+
137
+ function createRenderer() {
138
+ let mod = null;
139
+
140
+ return {
141
+ async render(input, options = {}) {
142
+ if (!mod) mod = await initModule();
143
+
144
+ const flags = buildFlags(options);
145
+ const encoder = new TextEncoder();
146
+ const inputBytes = encoder.encode(input);
147
+ const inputLen = inputBytes.length;
148
+
149
+ // Allocate WASM memory and copy input
150
+ const inputPtr = mod._wasm_alloc(inputLen);
151
+ mod.HEAPU8.set(inputBytes, inputPtr);
152
+
153
+ // Call the WASM function
154
+ const resultPtr = mod._render(inputPtr, inputLen, flags);
155
+
156
+ // Read result (assuming null-terminated or length-prefixed)
157
+ const result = readString(mod, resultPtr);
158
+
159
+ // Free memory
160
+ mod._wasm_free(inputPtr);
161
+ mod._wasm_free(resultPtr);
162
+
163
+ return result;
164
+ },
165
+ };
166
+ }
167
+
168
+ function buildFlags(options) {
169
+ let flags = 0;
170
+ if (options.featureA) flags |= 1;
171
+ if (options.featureB) flags |= 2;
172
+ return flags;
173
+ }
174
+
175
+ module.exports = { createRenderer };
176
+ ```
177
+
178
+ ## TypeScript declarations
179
+
180
+ ```typescript
181
+ // npm/index.d.ts
182
+ export interface RenderOptions {
183
+ /** Enable feature A */
184
+ featureA?: boolean;
185
+ /** Enable feature B */
186
+ featureB?: boolean;
187
+ }
188
+
189
+ export interface Renderer {
190
+ render(input: string, options?: RenderOptions): Promise<string>;
191
+ }
192
+
193
+ export function createRenderer(): Renderer;
194
+ ```
195
+
196
+ ## String passing across WASM boundary
197
+
198
+ JavaScript → WASM:
199
+
200
+ ```javascript
201
+ const encoder = new TextEncoder();
202
+ const bytes = encoder.encode(str);
203
+ const ptr = mod._wasm_alloc(bytes.length);
204
+ mod.HEAPU8.set(bytes, ptr);
205
+ // Pass ptr and bytes.length to WASM function
206
+ ```
207
+
208
+ WASM → JavaScript:
209
+
210
+ ```javascript
211
+ // If result is pointer + length
212
+ function readString(mod, ptr, len) {
213
+ const bytes = mod.HEAPU8.subarray(ptr, ptr + len);
214
+ return new TextDecoder().decode(bytes);
215
+ }
216
+
217
+ // If result stores length at a known offset
218
+ function readStringWithStoredLength(mod, ptr, lenPtr) {
219
+ const len = mod.HEAPU32[lenPtr >> 2];
220
+ return readString(mod, ptr, len);
221
+ }
222
+ ```
223
+
224
+ ## Testing WASM modules
225
+
226
+ ```bash
227
+ # Test native first (fast iteration, AddressSanitizer)
228
+ yo compile src/wasm_api.yo --release --sanitize address -o test && ./test
229
+
230
+ # Test Emscripten WASM
231
+ yo compile src/wasm_api.yo --cc emcc --release -o npm/my_lib_wasm_api
232
+
233
+ # Test WASI
234
+ yo compile src/wasm_api.yo --target wasm-wasi -o test.wasm && wasmtime test.wasm
235
+
236
+ # Run npm package tests
237
+ cd npm && node -e "const m = require('.'); m.createRenderer().render('hello').then(console.log)"
238
+ ```
239
+
240
+ ## Common WASM pitfalls
241
+
242
+ - **Memory leaks**: Always `_wasm_free` every `_wasm_alloc` on the JavaScript side.
243
+ - **String encoding**: WASM only sees bytes — use `TextEncoder`/`TextDecoder` for UTF-8.
244
+ - **Errno differences**: WASI errno values differ from POSIX. Use `std/libc/errno` constants.
245
+ - **No `main` needed**: Library WASM modules don't need `main` or `export main;` — just export the API functions.
246
+ - **Emscripten environment**: Set `-sENVIRONMENT='web,node'` to support both contexts, or `'node'` for Node.js only.
247
+ - **Module initialization is async**: Emscripten's `createModule()` returns a Promise. Initialize once and reuse.
248
+ - **WASM memory growth**: Always use `-sALLOW_MEMORY_GROWTH=1` for dynamic allocations.
249
+ - **Trailing null bytes**: If your Yo code writes null-terminated strings, the JavaScript reader must know to stop at `\0` or use explicit length.
250
+ - **Native testing first**: Always debug with `--sanitize address` on native before switching to WASM — error messages are much clearer.