@swifttui/web 0.0.6

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/AGENTS.md +52 -0
  2. package/README.md +116 -0
  3. package/cli.ts +168 -0
  4. package/index.html +50 -0
  5. package/index.ts +8 -0
  6. package/manifest.ts +1 -0
  7. package/package.json +33 -0
  8. package/src/AccessibilityTree.ts +262 -0
  9. package/src/BoxDrawingRenderer.ts +585 -0
  10. package/src/PublicEntrypointBoundary.test.ts +20 -0
  11. package/src/WebHostApp.test.ts +222 -0
  12. package/src/WebHostApp.ts +269 -0
  13. package/src/WebHostSceneManifest.test.ts +38 -0
  14. package/src/WebHostSceneManifest.ts +156 -0
  15. package/src/WebHostSceneRuntime.test.ts +1752 -0
  16. package/src/WebHostSceneRuntime.ts +955 -0
  17. package/src/WebHostSurfaceTransport.test.ts +362 -0
  18. package/src/WebHostSurfaceTransport.ts +648 -0
  19. package/src/WebHostTerminalStyle.test.ts +123 -0
  20. package/src/WebHostTerminalStyle.ts +471 -0
  21. package/src/WebHostTestFixtures.ts +10 -0
  22. package/src/WebSocketSceneBridge.test.ts +198 -0
  23. package/src/WebSocketSceneBridge.ts +233 -0
  24. package/src/browser.ts +59 -0
  25. package/src/wasi/BrowserWASIBridge.test.ts +168 -0
  26. package/src/wasi/BrowserWASIBridge.ts +167 -0
  27. package/src/wasi/SharedInputQueue.test.ts +146 -0
  28. package/src/wasi/SharedInputQueue.ts +199 -0
  29. package/src/wasi/StdIOPipe.ts +72 -0
  30. package/src/wasi/WasiPollScheduler.test.ts +176 -0
  31. package/src/wasi/WasiPollScheduler.ts +305 -0
  32. package/src/wasi/WasmSceneRuntime.ts +205 -0
  33. package/src/wasi/WasmSceneWorker.ts +182 -0
  34. package/style.css +15 -0
  35. package/testing.ts +1 -0
  36. package/tsconfig.json +29 -0
  37. package/wasi-worker.ts +1 -0
  38. package/wasi.ts +4 -0
  39. package/websocket.ts +1 -0
@@ -0,0 +1,182 @@
1
+ import { ConsoleStdout, Fd, WASI, wasi } from "@bjorn3/browser_wasi_shim";
2
+ import {
3
+ SharedInputQueueReader,
4
+ type SharedInputQueueBuffers,
5
+ } from "./SharedInputQueue.ts";
6
+ import { WasiPollScheduler } from "./WasiPollScheduler.ts";
7
+
8
+ export interface StartWasmSceneWorkerMessage {
9
+ type: "start";
10
+ wasmURL: string;
11
+ environment: Record<string, string>;
12
+ inputQueue: SharedInputQueueBuffers;
13
+ }
14
+
15
+ export interface OutputWasmSceneWorkerMessage {
16
+ type: "stdout" | "stderr";
17
+ chunk: Uint8Array;
18
+ }
19
+
20
+ export interface ExitWasmSceneWorkerMessage {
21
+ type: "exit";
22
+ code: number;
23
+ }
24
+
25
+ export interface ErrorWasmSceneWorkerMessage {
26
+ type: "error";
27
+ message: string;
28
+ }
29
+
30
+ export type WasmSceneWorkerMessage = StartWasmSceneWorkerMessage;
31
+ export type WasmSceneWorkerResponse =
32
+ | OutputWasmSceneWorkerMessage
33
+ | ExitWasmSceneWorkerMessage
34
+ | ErrorWasmSceneWorkerMessage;
35
+
36
+ export function startWasmSceneWorker(): void {
37
+ globalThis.addEventListener("message", (event: MessageEvent<WasmSceneWorkerMessage>) => {
38
+ if (event.data.type !== "start") {
39
+ return;
40
+ }
41
+
42
+ void startWasmScene(event.data);
43
+ });
44
+ }
45
+
46
+ class BlockingInputFileDescriptor extends Fd {
47
+ private readonly reader: SharedInputQueueReader;
48
+ private readonly fdstat = (() => {
49
+ const fdstat = new wasi.Fdstat(wasi.FILETYPE_CHARACTER_DEVICE, 0);
50
+ fdstat.fs_rights_base = BigInt(wasi.RIGHTS_FD_READ);
51
+ return fdstat;
52
+ })();
53
+
54
+ constructor(inputQueue: SharedInputQueueBuffers) {
55
+ super();
56
+ this.reader = new SharedInputQueueReader(inputQueue);
57
+ }
58
+
59
+ override fd_fdstat_get(): { ret: number; fdstat: wasi.Fdstat } {
60
+ return {
61
+ ret: wasi.ERRNO_SUCCESS,
62
+ fdstat: this.fdstat,
63
+ };
64
+ }
65
+
66
+ override fd_filestat_get(): { ret: number; filestat: wasi.Filestat } {
67
+ return {
68
+ ret: wasi.ERRNO_SUCCESS,
69
+ filestat: new wasi.Filestat(0n, wasi.FILETYPE_CHARACTER_DEVICE, 0n),
70
+ };
71
+ }
72
+
73
+ override fd_read(size: number): { ret: number; data: Uint8Array } {
74
+ const chunk = this.reader.readAvailable(size);
75
+ if (chunk) {
76
+ return {
77
+ ret: wasi.ERRNO_SUCCESS,
78
+ data: chunk,
79
+ };
80
+ }
81
+
82
+ if (this.reader.isClosed()) {
83
+ return {
84
+ ret: wasi.ERRNO_SUCCESS,
85
+ data: new Uint8Array(),
86
+ };
87
+ }
88
+
89
+ return {
90
+ ret: wasi.ERRNO_AGAIN,
91
+ data: new Uint8Array(),
92
+ };
93
+ }
94
+
95
+ availableBytes(): number {
96
+ return this.reader.availableBytes();
97
+ }
98
+
99
+ waitForReadable(
100
+ timeoutMilliseconds?: number
101
+ ) {
102
+ return this.reader.waitForReadable(timeoutMilliseconds);
103
+ }
104
+
105
+ isClosed(): boolean {
106
+ return this.reader.isClosed();
107
+ }
108
+ }
109
+
110
+ function installWasiPollScheduler(
111
+ wasiBridge: WASI,
112
+ stdin: BlockingInputFileDescriptor
113
+ ): void {
114
+ const originalPoll = wasiBridge.wasiImport.poll_oneoff;
115
+ if (typeof originalPoll !== "function") {
116
+ return;
117
+ }
118
+
119
+ const scheduler = new WasiPollScheduler({
120
+ memory: () => wasiBridge.inst?.exports.memory as WebAssembly.Memory | undefined,
121
+ stdin,
122
+ fallbackPoll: (inPtr, outPtr, nsubscriptions, neventsPtr) =>
123
+ originalPoll(inPtr, outPtr, nsubscriptions, neventsPtr),
124
+ });
125
+ wasiBridge.wasiImport.poll_oneoff = (inPtr, outPtr, nsubscriptions, neventsPtr) =>
126
+ scheduler.pollOneOff(inPtr, outPtr, nsubscriptions, neventsPtr);
127
+ }
128
+
129
+ async function startWasmScene(
130
+ message: StartWasmSceneWorkerMessage
131
+ ): Promise<void> {
132
+ try {
133
+ const stdin = new BlockingInputFileDescriptor(message.inputQueue);
134
+ const wasiBridge = new WASI(
135
+ ["app.wasm"],
136
+ Object.entries(message.environment).map(([key, value]) => `${key}=${value}`),
137
+ [
138
+ stdin,
139
+ new ConsoleStdout((chunk) => {
140
+ postWorkerMessage({
141
+ type: "stdout",
142
+ chunk,
143
+ });
144
+ }),
145
+ new ConsoleStdout((chunk) => {
146
+ postWorkerMessage({
147
+ type: "stderr",
148
+ chunk,
149
+ });
150
+ }),
151
+ ]
152
+ );
153
+ installWasiPollScheduler(wasiBridge, stdin);
154
+
155
+ const response = await fetch(message.wasmURL);
156
+ if (!response.ok) {
157
+ throw new Error(`failed to load ${message.wasmURL}: ${response.status} ${response.statusText}`);
158
+ }
159
+
160
+ const module = await WebAssembly.compile(await response.arrayBuffer());
161
+ const instance = await WebAssembly.instantiate(module, {
162
+ wasi_snapshot_preview1: wasiBridge.wasiImport,
163
+ });
164
+
165
+ const exitCode = wasiBridge.start(instance as WebAssembly.Instance);
166
+ postWorkerMessage({
167
+ type: "exit",
168
+ code: exitCode,
169
+ });
170
+ } catch (error) {
171
+ postWorkerMessage({
172
+ type: "error",
173
+ message: error instanceof Error ? error.message : String(error),
174
+ });
175
+ }
176
+ }
177
+
178
+ function postWorkerMessage(
179
+ message: WasmSceneWorkerResponse
180
+ ): void {
181
+ globalThis.postMessage(message);
182
+ }
package/style.css ADDED
@@ -0,0 +1,15 @@
1
+ .webhost-scene-root,
2
+ .webhost-scene {
3
+ min-width: 0;
4
+ min-height: 0;
5
+ }
6
+
7
+ .webhost-scene__terminal {
8
+ min-width: 0;
9
+ min-height: 0;
10
+ overflow: hidden;
11
+ }
12
+
13
+ .webhost-scene__surface {
14
+ display: block;
15
+ }
package/testing.ts ADDED
@@ -0,0 +1 @@
1
+ export * from "./src/WebHostTestFixtures.ts";
package/tsconfig.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "compilerOptions": {
3
+ // Environment setup & latest features
4
+ "lib": ["ESNext"],
5
+ "target": "ESNext",
6
+ "module": "Preserve",
7
+ "moduleDetection": "force",
8
+ "jsx": "react-jsx",
9
+ "allowJs": true,
10
+
11
+ // Bundler mode
12
+ "moduleResolution": "bundler",
13
+ "allowImportingTsExtensions": true,
14
+ "verbatimModuleSyntax": true,
15
+ "noEmit": true,
16
+
17
+ // Best practices
18
+ "strict": true,
19
+ "skipLibCheck": true,
20
+ "noFallthroughCasesInSwitch": true,
21
+ "noUncheckedIndexedAccess": true,
22
+ "noImplicitOverride": true,
23
+
24
+ // Some stricter flags (disabled by default)
25
+ "noUnusedLocals": false,
26
+ "noUnusedParameters": false,
27
+ "noPropertyAccessFromIndexSignature": false
28
+ }
29
+ }
package/wasi-worker.ts ADDED
@@ -0,0 +1 @@
1
+ export * from "./src/wasi/WasmSceneWorker.ts";
package/wasi.ts ADDED
@@ -0,0 +1,4 @@
1
+ export * from "./src/wasi/BrowserWASIBridge.ts";
2
+ export * from "./src/wasi/StdIOPipe.ts";
3
+ export * from "./src/wasi/SharedInputQueue.ts";
4
+ export * from "./src/wasi/WasmSceneRuntime.ts";
package/websocket.ts ADDED
@@ -0,0 +1 @@
1
+ export * from "./src/WebSocketSceneBridge.ts";