@ttsc/wasm 0.12.2 → 0.12.4

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.
Files changed (39) hide show
  1. package/cmd/ttsc-wasm/main.go +101 -89
  2. package/cmd/ttsc-wasm/main_wasm.go +6 -4
  3. package/dist/ttsc.wasm +0 -0
  4. package/host/api.go +154 -145
  5. package/host/doc.go +12 -12
  6. package/host/host.go +236 -225
  7. package/host/host_native.go +5 -2
  8. package/host/plugin.go +31 -31
  9. package/lib/src/MemFS.d.ts +35 -0
  10. package/lib/src/MemFS.js +57 -4
  11. package/lib/src/MemFS.js.map +1 -1
  12. package/lib/src/api.d.ts +46 -13
  13. package/lib/src/api.js +7 -4
  14. package/lib/src/api.js.map +1 -1
  15. package/lib/src/index.d.ts +1 -1
  16. package/lib/src/index.js +10 -3
  17. package/lib/src/index.js.map +1 -1
  18. package/lib/src/instantiate.d.ts +10 -7
  19. package/lib/src/instantiate.js +20 -3
  20. package/lib/src/instantiate.js.map +1 -1
  21. package/package.json +2 -2
  22. package/shim-vendor/shim/ast/go.mod +9 -0
  23. package/shim-vendor/shim/ast/go.sum +20 -0
  24. package/shim-vendor/shim/ast/safe_text.go +86 -0
  25. package/shim-vendor/shim/ast/shim.go +24 -0
  26. package/shim-vendor/shim/ast/test/node_text_joins_multi_hop_qualified_name_test.go +32 -0
  27. package/shim-vendor/shim/ast/test/node_text_joins_one_hop_qualified_name_test.go +31 -0
  28. package/shim-vendor/shim/ast/test/node_text_returns_empty_for_nil_test.go +23 -0
  29. package/shim-vendor/shim/ast/test/node_text_returns_identifier_text_test.go +27 -0
  30. package/shim-vendor/shim/ast/test/node_text_skips_empty_qualified_left_test.go +30 -0
  31. package/shim-vendor/shim/checker/shim.go +35 -0
  32. package/shim-vendor/shim/core/shim.go +17 -0
  33. package/shim-vendor/shim/diagnosticwriter/shim.go +4 -0
  34. package/shim-vendor/shim/lsp/shim.go +3 -3
  35. package/shim-vendor/shim/printer/shim.go +20 -0
  36. package/src/MemFS.ts +92 -7
  37. package/src/api.ts +46 -13
  38. package/src/index.ts +1 -5
  39. package/src/instantiate.ts +37 -14
package/src/MemFS.ts CHANGED
@@ -20,6 +20,15 @@ const S_IFREG = 0o100000;
20
20
  const DEFAULT_FILE_MODE = S_IFREG | 0o644;
21
21
  const DEFAULT_DIR_MODE = S_IFDIR | 0o755;
22
22
 
23
+ /**
24
+ * Subset of the Node.js `fs` module that `wasm_exec.js` calls into.
25
+ *
26
+ * Go's js/wasm runtime routes all `syscall/js` filesystem operations through
27
+ * `globalThis.fs`. In a browser there is no real `fs`, so a MemFS
28
+ * implementation fulfils this interface. Only the operations that
29
+ * typescript-go's compiler exercises are required; the rest are no-ops or
30
+ * return `EPERM`/`EINVAL`.
31
+ */
23
32
  export interface IWasmExecFS {
24
33
  constants: Record<string, number>;
25
34
  writeSync(fd: number, buf: Uint8Array): number;
@@ -37,7 +46,10 @@ export interface IWasmExecFS {
37
46
  mode: number,
38
47
  callback: (err: NodeJS.ErrnoException | null, fd: number) => void,
39
48
  ): void;
40
- close(fd: number, callback: (err: NodeJS.ErrnoException | null) => void): void;
49
+ close(
50
+ fd: number,
51
+ callback: (err: NodeJS.ErrnoException | null) => void,
52
+ ): void;
41
53
  read(
42
54
  fd: number,
43
55
  buffer: Uint8Array,
@@ -67,7 +79,10 @@ export interface IWasmExecFS {
67
79
  fd: number,
68
80
  callback: (err: NodeJS.ErrnoException | null, stats: IFileStats) => void,
69
81
  ): void;
70
- fsync(fd: number, callback: (err: NodeJS.ErrnoException | null) => void): void;
82
+ fsync(
83
+ fd: number,
84
+ callback: (err: NodeJS.ErrnoException | null) => void,
85
+ ): void;
71
86
  unlink(
72
87
  path: string,
73
88
  callback: (err: NodeJS.ErrnoException | null) => void,
@@ -152,6 +167,12 @@ export interface IWasmExecFS {
152
167
  ): void;
153
168
  }
154
169
 
170
+ /**
171
+ * Stat-like object returned by `stat`, `lstat`, and `fstat`.
172
+ *
173
+ * Mirrors the subset of `fs.Stats` that `wasm_exec.js` reads. Fields not
174
+ * relevant to Go's `os.FileInfo` (e.g. ownership) are zeroed.
175
+ */
155
176
  export interface IFileStats {
156
177
  isDirectory(): boolean;
157
178
  isFile(): boolean;
@@ -170,12 +191,19 @@ export interface IFileStats {
170
191
  blocks: number;
171
192
  }
172
193
 
194
+ /** Internal filesystem tree node. Directories carry an empty `data` buffer. */
173
195
  interface INode {
174
196
  kind: "file" | "dir";
175
197
  data: Uint8Array;
176
198
  mtimeMs: number;
177
199
  }
178
200
 
201
+ /**
202
+ * Filesystem error with a POSIX error code and numeric `errno`.
203
+ *
204
+ * Matches the shape of `NodeJS.ErrnoException` so Go's os package interprets it
205
+ * as a proper `os.PathError` with a numeric error code.
206
+ */
179
207
  export class MemFSError extends Error {
180
208
  public code: string;
181
209
  public errno: number;
@@ -190,6 +218,7 @@ export class MemFSError extends Error {
190
218
  }
191
219
  }
192
220
 
221
+ /** Map a POSIX error name to its Linux numeric errno (negative by convention). */
193
222
  function errnoForCode(code: string): number {
194
223
  switch (code) {
195
224
  case "ENOENT":
@@ -207,6 +236,11 @@ function errnoForCode(code: string): number {
207
236
  }
208
237
  }
209
238
 
239
+ /**
240
+ * Handle returned by `createMemFS`. Provides the `fs` shim to install on
241
+ * `globalThis` plus convenience methods for seeding the virtual filesystem
242
+ * before booting the wasm.
243
+ */
210
244
  export interface IMemFSHost {
211
245
  fs: IWasmExecFS;
212
246
  writeFile(path: string, data: string | Uint8Array): void;
@@ -222,6 +256,12 @@ export interface IMemFSHost {
222
256
  const encoder = new TextEncoder();
223
257
  const decoder = new TextDecoder();
224
258
 
259
+ /**
260
+ * Resolve a path to an absolute, normalized POSIX path.
261
+ *
262
+ * Collapses `.` and `..` segments and converts backslashes. Returns `"/"` for
263
+ * empty input.
264
+ */
225
265
  function normalize(p: string): string {
226
266
  if (!p) return "/";
227
267
  const parts = p.replace(/\\/g, "/").split("/").filter(Boolean);
@@ -237,6 +277,15 @@ function normalize(p: string): string {
237
277
  return "/" + stack.join("/");
238
278
  }
239
279
 
280
+ /**
281
+ * Create an in-memory filesystem suitable for use as `globalThis.fs` inside a
282
+ * Go/wasm runtime.
283
+ *
284
+ * The returned host exposes the low-level `fs` object (install it on
285
+ * `globalThis.fs` before loading `wasm_exec.js`) and convenience helpers
286
+ * (`writeFile`, `readFile`, `mkdirp`, …) for seeding source files and reading
287
+ * compiler output without touching the real filesystem.
288
+ */
240
289
  export function createMemFS(): IMemFSHost {
241
290
  const nodes = new Map<string, INode>();
242
291
  nodes.set("/", { kind: "dir", data: new Uint8Array(), mtimeMs: Date.now() });
@@ -271,6 +320,10 @@ export function createMemFS(): IMemFSHost {
271
320
  }
272
321
  const pipes = new Map<number, IPipeState>();
273
322
 
323
+ /**
324
+ * Drain buffered pipe data into `buffer[offset..offset+length]`. Returns
325
+ * bytes copied.
326
+ */
274
327
  function drainPipeInto(
275
328
  state: IPipeState,
276
329
  buffer: Uint8Array,
@@ -289,19 +342,29 @@ export function createMemFS(): IMemFSHost {
289
342
  return written;
290
343
  }
291
344
 
345
+ /**
346
+ * Satisfy any pending blocked readers using available pipe data or EOF
347
+ * signal.
348
+ */
292
349
  function flushPipeReaders(state: IPipeState): void {
293
350
  while (
294
351
  state.pendingReaders.length > 0 &&
295
352
  (state.buffers.length > 0 || state.writeClosed)
296
353
  ) {
297
354
  const reader = state.pendingReaders.shift()!;
298
- const n = drainPipeInto(state, reader.buffer, reader.offset, reader.length);
355
+ const n = drainPipeInto(
356
+ state,
357
+ reader.buffer,
358
+ reader.offset,
359
+ reader.length,
360
+ );
299
361
  // Even at EOF (writeClosed && empty buffers) we satisfy with n=0 which
300
362
  // signals EOF to the Go-side caller.
301
363
  reader.callback(null, n);
302
364
  }
303
365
  }
304
366
 
367
+ /** Silently create any missing ancestor directories for path `p`. */
305
368
  function ensureParentDirs(p: string): void {
306
369
  const segments = normalize(p).split("/").filter(Boolean);
307
370
  segments.pop();
@@ -358,6 +421,7 @@ export function createMemFS(): IMemFSHost {
358
421
  return nodes.has(normalize(p));
359
422
  }
360
423
 
424
+ /** Synchronously stat `p`; throws `MemFSError("ENOENT")` if not found. */
361
425
  function statSync(p: string): IFileStats {
362
426
  const norm = normalize(p);
363
427
  const node = nodes.get(norm);
@@ -365,6 +429,7 @@ export function createMemFS(): IMemFSHost {
365
429
  return makeStats(node);
366
430
  }
367
431
 
432
+ /** Build an `IFileStats` object from a filesystem node. */
368
433
  function makeStats(node: INode): IFileStats {
369
434
  const isDir = node.kind === "dir";
370
435
  return {
@@ -386,6 +451,12 @@ export function createMemFS(): IMemFSHost {
386
451
  };
387
452
  }
388
453
 
454
+ /**
455
+ * Return immediate children of directory `p`, sorted alphabetically.
456
+ *
457
+ * Uses a linear scan over the node map and a `Set` to deduplicate nested
458
+ * paths into direct-child names — O(n) in the number of total nodes.
459
+ */
389
460
  function readdirSync(p: string): string[] {
390
461
  const norm = normalize(p);
391
462
  const node = nodes.get(norm);
@@ -438,9 +509,12 @@ export function createMemFS(): IMemFSHost {
438
509
  if (entry) {
439
510
  const node = nodes.get(entry.path);
440
511
  if (node && node.kind === "file") {
512
+ // subarray(0) is a zero-copy view over the full incoming buffer.
441
513
  const incoming = buf.subarray(0);
442
514
  const existing = node.data;
443
- const next = new Uint8Array(existing.byteLength + incoming.byteLength);
515
+ const next = new Uint8Array(
516
+ existing.byteLength + incoming.byteLength,
517
+ );
444
518
  next.set(existing, 0);
445
519
  next.set(incoming, existing.byteLength);
446
520
  node.data = next;
@@ -455,7 +529,11 @@ export function createMemFS(): IMemFSHost {
455
529
  stderr.buffer += decoder.decode(buf);
456
530
  // eslint-disable-next-line no-console
457
531
  console.error(
458
- "[wasm] writeSync to unknown fd " + fd + " (" + buf.byteLength + " bytes); routed to stderr buffer",
532
+ "[wasm] writeSync to unknown fd " +
533
+ fd +
534
+ " (" +
535
+ buf.byteLength +
536
+ " bytes); routed to stderr buffer",
459
537
  );
460
538
  return buf.length;
461
539
  },
@@ -599,7 +677,10 @@ export function createMemFS(): IMemFSHost {
599
677
  try {
600
678
  callback(null, statSync(p));
601
679
  } catch (err) {
602
- callback(err as NodeJS.ErrnoException, undefined as unknown as IFileStats);
680
+ callback(
681
+ err as NodeJS.ErrnoException,
682
+ undefined as unknown as IFileStats,
683
+ );
603
684
  }
604
685
  },
605
686
 
@@ -614,7 +695,11 @@ export function createMemFS(): IMemFSHost {
614
695
  if (pipes.has(fd)) {
615
696
  callback(
616
697
  null,
617
- makeStats({ kind: "file", data: new Uint8Array(), mtimeMs: Date.now() }),
698
+ makeStats({
699
+ kind: "file",
700
+ data: new Uint8Array(),
701
+ mtimeMs: Date.now(),
702
+ }),
618
703
  );
619
704
  return;
620
705
  }
package/src/api.ts CHANGED
@@ -3,28 +3,40 @@
3
3
  // `playground.wasm`, typia's `ttsc-typia.wasm`) exposes the same surface, so
4
4
  // one set of types covers them all.
5
5
 
6
+ /**
7
+ * API object that every host-built wasm binds to `globalThis[apiName]`.
8
+ *
9
+ * Obtain an instance via `bootTtsc`, which waits for the wasm to signal
10
+ * readiness and returns the typed handle together with its `IMemFSHost`.
11
+ */
6
12
  export interface ITtscApi {
7
13
  /** Build metadata reported by the wasm. Useful for diagnostics. */
8
14
  version(): ITtscVersion;
9
15
 
10
- /** Compile a project: typecheck + emit. `result` is JSON; `parseResult<ITtscCompileResult>` deserializes it. */
16
+ /**
17
+ * Compile a project: typecheck + emit. `result` is JSON;
18
+ * `parseResult<ITtscCompileResult>` deserializes it.
19
+ */
11
20
  build(opts: ITtscBuildOpts): Promise<ITtscResult>;
12
21
 
13
- /** Typecheck without emit. `result` is JSON; `parseResult<ITtscCompileResult>` deserializes it. */
22
+ /**
23
+ * Typecheck without emit. `result` is JSON; `parseResult<ITtscCompileResult>`
24
+ * deserializes it.
25
+ */
14
26
  check(opts: ITtscBuildOpts): Promise<ITtscResult>;
15
27
 
16
28
  /**
17
- * Return every source file the program saw, keyed by project-relative
18
- * path. Used by playgrounds that want to render the TypeScript view after
19
- * a source rewriter (e.g. paths) has run. `result` is JSON; use
29
+ * Return every source file the program saw, keyed by project-relative path.
30
+ * Used by playgrounds that want to render the TypeScript view after a source
31
+ * rewriter (e.g. paths) has run. `result` is JSON; use
20
32
  * `parseResult<ITtscTransformResult>` to deserialize.
21
33
  */
22
34
  transform(opts: ITtscBuildOpts): Promise<ITtscResult>;
23
35
 
24
36
  /**
25
37
  * Dispatch a registered plugin's subcommand. Returns the captured stdout /
26
- * stderr (the same streams the native sidecar binary would write to)
27
- * together with the exit code.
38
+ * stderr (the same streams the native sidecar binary would write to) together
39
+ * with the exit code.
28
40
  */
29
41
  plugin(opts: ITtscPluginOpts): Promise<ITtscResult>;
30
42
 
@@ -32,6 +44,7 @@ export interface ITtscApi {
32
44
  plugins(): string[];
33
45
  }
34
46
 
47
+ /** Build metadata embedded in the wasm by the Go linker at compile time. */
35
48
  export interface ITtscVersion {
36
49
  version: string;
37
50
  commit: string;
@@ -41,13 +54,15 @@ export interface ITtscVersion {
41
54
  goarch: string;
42
55
  }
43
56
 
57
+ /** Options shared by `build`, `check`, and `transform`. */
44
58
  export interface ITtscBuildOpts {
45
59
  /** Absolute virtual path the project lives at inside the MemFS. */
46
60
  cwd: string;
47
- /** tsconfig path, relative to `cwd`. Defaults to `tsconfig.json`. */
61
+ /** Tsconfig path, relative to `cwd`. Defaults to `tsconfig.json`. */
48
62
  tsconfig?: string;
49
63
  }
50
64
 
65
+ /** Options for dispatching a named plugin subcommand via `api.plugin`. */
51
66
  export interface ITtscPluginOpts {
52
67
  /** Plugin id registered with `host.Expose` (e.g. `@ttsc/banner`). */
53
68
  name: string;
@@ -61,6 +76,7 @@ export interface ITtscPluginOpts {
61
76
  [key: string]: string | boolean | number | undefined;
62
77
  }
63
78
 
79
+ /** Envelope returned by every `ITtscApi` method. */
64
80
  export interface ITtscResult {
65
81
  /** Exit code. 0 = success, 2 = usage error, 3 = runtime error. */
66
82
  code: number;
@@ -69,23 +85,40 @@ export interface ITtscResult {
69
85
  /** Anything the wasm wrote to its stderr stream. */
70
86
  stderr: string;
71
87
  /**
72
- * For the base endpoints, the JSON-encoded compile/transform result. For
73
- * the plugin endpoint, this is empty — the plugin's own output sits in
88
+ * For the base endpoints, the JSON-encoded compile/transform result. For the
89
+ * plugin endpoint, this is empty — the plugin's own output sits in
74
90
  * stdout/stderr. Use `parseResult<T>` to deserialize.
75
91
  */
76
92
  result: string;
77
93
  }
78
94
 
95
+ /**
96
+ * Structured payload inside `ITtscResult.result` for `build` and `check`.
97
+ *
98
+ * `output` maps emit-destination paths (relative to `outDir`) to file contents.
99
+ * It is empty when `check` is called without emit.
100
+ */
79
101
  export interface ITtscCompileResult {
80
102
  diagnostics?: ITtscDiagnostic[];
81
103
  output: Record<string, string>;
82
104
  }
83
105
 
106
+ /**
107
+ * Structured payload inside `ITtscResult.result` for `transform`.
108
+ *
109
+ * `typescript` maps source file paths (relative to `cwd`) to their
110
+ * post-transform TypeScript text — useful for playgrounds that want to show the
111
+ * rewritten source before it is emitted.
112
+ */
84
113
  export interface ITtscTransformResult {
85
114
  diagnostics?: ITtscDiagnostic[];
86
115
  typescript: Record<string, string>;
87
116
  }
88
117
 
118
+ /**
119
+ * A single TypeScript compiler diagnostic emitted during `build`, `check`, or
120
+ * `transform`. `line` and `character` are 0-based.
121
+ */
89
122
  export interface ITtscDiagnostic {
90
123
  file: string | null;
91
124
  category: "error" | "warning";
@@ -98,9 +131,9 @@ export interface ITtscDiagnostic {
98
131
  }
99
132
 
100
133
  /**
101
- * Parse the `result` field of an ITtscResult into the structured payload.
102
- * The wasm returns JSON as a string because js.ValueOf does not handle large
103
- * nested maps efficiently. Callers JSON.parse exactly once at the boundary.
134
+ * Parse the `result` field of an ITtscResult into the structured payload. The
135
+ * wasm returns JSON as a string because js.ValueOf does not handle large nested
136
+ * maps efficiently. Callers JSON.parse exactly once at the boundary.
104
137
  */
105
138
  export function parseResult<T>(result: ITtscResult): T | null {
106
139
  if (!result.result) return null;
package/src/index.ts CHANGED
@@ -9,11 +9,7 @@ export { bootTtsc } from "./instantiate";
9
9
  export type { IBootTtscOptions, IBootResult } from "./instantiate";
10
10
 
11
11
  export { createMemFS, MemFSError } from "./MemFS";
12
- export type {
13
- IMemFSHost,
14
- IWasmExecFS,
15
- IFileStats,
16
- } from "./MemFS";
12
+ export type { IMemFSHost, IWasmExecFS, IFileStats } from "./MemFS";
17
13
 
18
14
  export type {
19
15
  ITtscApi,
@@ -7,39 +7,43 @@
7
7
  // The boot helper is parameterized by `apiName` so any wasm built with
8
8
  // `host.Expose(...)` can be loaded the same way. The base wasm uses "ttsc";
9
9
  // downstream consumers pick their own (e.g. "ttscPlayground", "ttscTypia").
10
-
11
- import { createMemFS, type IMemFSHost } from "./MemFS";
10
+ import { type IMemFSHost, createMemFS } from "./MemFS";
12
11
  import type { ITtscApi } from "./api";
13
12
 
14
13
  declare const importScripts: (...urls: string[]) => void;
15
14
 
15
+ /** Options for `bootTtsc`. All fields except `wasmUrl` have sensible defaults. */
16
16
  export interface IBootTtscOptions {
17
17
  /** URL of the .wasm to fetch. */
18
18
  wasmUrl: string;
19
19
  /** URL of wasm_exec.js. Defaults to the same directory as wasmUrl. */
20
20
  wasmExecUrl?: string;
21
21
  /**
22
- * globalThis property name the wasm binds. Must match the value the wasm
23
- * was built with (the `apiName` passed to `host.Expose`). Defaults to
24
- * "ttsc".
22
+ * GlobalThis property name the wasm binds. Must match the value the wasm was
23
+ * built with (the `apiName` passed to `host.Expose`). Defaults to "ttsc".
25
24
  */
26
25
  apiName?: string;
27
26
  /**
28
- * Optional pre-existing MemFS host. When omitted, a fresh one is created
29
- * and stored on the returned BootResult. Pass an existing host when you
30
- * want to boot multiple wasms over the same filesystem (e.g. base ttsc +
31
- * a typia wasm) so they share project sources.
27
+ * Optional pre-existing MemFS host. When omitted, a fresh one is created and
28
+ * stored on the returned BootResult. Pass an existing host when you want to
29
+ * boot multiple wasms over the same filesystem (e.g. base ttsc + a typia
30
+ * wasm) so they share project sources.
32
31
  */
33
32
  host?: IMemFSHost;
34
33
  }
35
34
 
35
+ /** Handle returned by `bootTtsc` once the wasm is ready. */
36
36
  export interface IBootResult {
37
+ /** The typed API proxy bound by the wasm to `globalThis[apiName]`. */
37
38
  api: ITtscApi;
39
+ /** The MemFS instance shared with the wasm's virtual filesystem. */
38
40
  host: IMemFSHost;
39
41
  }
40
42
 
41
43
  /** Boot a host-built wasm. Re-entrant only if you reuse the same `host`. */
42
- export async function bootTtsc(options: IBootTtscOptions): Promise<IBootResult> {
44
+ export async function bootTtsc(
45
+ options: IBootTtscOptions,
46
+ ): Promise<IBootResult> {
43
47
  const wasmUrl = options.wasmUrl;
44
48
  const wasmExecUrl = options.wasmExecUrl ?? defaultWasmExecUrl(wasmUrl);
45
49
  const apiName = options.apiName ?? "ttsc";
@@ -64,11 +68,12 @@ export async function bootTtsc(options: IBootTtscOptions): Promise<IBootResult>
64
68
 
65
69
  const response = await fetch(wasmUrl);
66
70
  if (!response.ok) {
67
- throw new Error(
68
- `bootTtsc: failed to fetch ${wasmUrl}: ${response.status}`,
69
- );
71
+ throw new Error(`bootTtsc: failed to fetch ${wasmUrl}: ${response.status}`);
70
72
  }
71
- const wasm = await WebAssembly.instantiateStreaming(response, go.importObject);
73
+ const wasm = await WebAssembly.instantiateStreaming(
74
+ response,
75
+ go.importObject,
76
+ );
72
77
  // go.run never resolves until the wasm exits; we don't await it.
73
78
  void go.run(wasm.instance);
74
79
  await ready;
@@ -81,17 +86,35 @@ export async function bootTtsc(options: IBootTtscOptions): Promise<IBootResult>
81
86
  return { api, host };
82
87
  }
83
88
 
89
+ /**
90
+ * Derive the `wasm_exec.js` URL from the wasm URL by replacing the filename.
91
+ *
92
+ * If `wasmUrl` has no directory component, returns `"wasm_exec.js"` (same
93
+ * directory as the caller's base URL).
94
+ */
84
95
  function defaultWasmExecUrl(wasmUrl: string): string {
85
96
  const slash = wasmUrl.lastIndexOf("/");
86
97
  if (slash < 0) return "wasm_exec.js";
87
98
  return wasmUrl.slice(0, slash + 1) + "wasm_exec.js";
88
99
  }
89
100
 
101
+ /**
102
+ * Minimal shape of the `Go` constructor that `wasm_exec.js` exports on
103
+ * `globalThis`. Only the members we actually use are typed here.
104
+ */
90
105
  interface IGoInstance {
91
106
  importObject: WebAssembly.Imports;
92
107
  run(instance: WebAssembly.Instance): Promise<void>;
93
108
  }
94
109
 
110
+ /**
111
+ * Minimal `process` shim required by `wasm_exec.js` in non-Node environments.
112
+ *
113
+ * Go's js/wasm bridge reads `process.pid`, `process.ppid`, and calls
114
+ * `process.cwd()`. `getuid`/`getgid` and friends return `-1` (root-less).
115
+ * `umask` and `getgroups` are never exercised by the compiler but are included
116
+ * for completeness so unexpected calls surface as clear errors.
117
+ */
95
118
  function createProcessShim(): Record<string, unknown> {
96
119
  return {
97
120
  getuid: () => -1,