almostnode 0.2.7 → 0.2.9

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 (64) hide show
  1. package/README.md +4 -2
  2. package/dist/CNAME +1 -0
  3. package/dist/__sw__.js +80 -84
  4. package/dist/assets/{runtime-worker-B8_LZkBX.js → runtime-worker-ujGAG2t7.js} +1278 -828
  5. package/dist/assets/runtime-worker-ujGAG2t7.js.map +1 -0
  6. package/dist/frameworks/code-transforms.d.ts.map +1 -1
  7. package/dist/frameworks/next-config-parser.d.ts +16 -0
  8. package/dist/frameworks/next-config-parser.d.ts.map +1 -0
  9. package/dist/frameworks/next-dev-server.d.ts +6 -6
  10. package/dist/frameworks/next-dev-server.d.ts.map +1 -1
  11. package/dist/frameworks/next-html-generator.d.ts +35 -0
  12. package/dist/frameworks/next-html-generator.d.ts.map +1 -0
  13. package/dist/frameworks/next-shims.d.ts +79 -0
  14. package/dist/frameworks/next-shims.d.ts.map +1 -0
  15. package/dist/index.cjs +3024 -2465
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.ts +3 -0
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.mjs +3336 -2787
  20. package/dist/index.mjs.map +1 -1
  21. package/dist/og-image.png +0 -0
  22. package/dist/runtime.d.ts +26 -0
  23. package/dist/runtime.d.ts.map +1 -1
  24. package/dist/server-bridge.d.ts +2 -0
  25. package/dist/server-bridge.d.ts.map +1 -1
  26. package/dist/shims/crypto.d.ts +2 -0
  27. package/dist/shims/crypto.d.ts.map +1 -1
  28. package/dist/shims/esbuild.d.ts.map +1 -1
  29. package/dist/shims/fs.d.ts.map +1 -1
  30. package/dist/shims/http.d.ts +29 -0
  31. package/dist/shims/http.d.ts.map +1 -1
  32. package/dist/shims/path.d.ts.map +1 -1
  33. package/dist/shims/stream.d.ts.map +1 -1
  34. package/dist/shims/vfs-adapter.d.ts.map +1 -1
  35. package/dist/shims/ws.d.ts +2 -0
  36. package/dist/shims/ws.d.ts.map +1 -1
  37. package/dist/types/package-json.d.ts +1 -0
  38. package/dist/types/package-json.d.ts.map +1 -1
  39. package/dist/utils/binary-encoding.d.ts +13 -0
  40. package/dist/utils/binary-encoding.d.ts.map +1 -0
  41. package/dist/virtual-fs.d.ts.map +1 -1
  42. package/package.json +4 -4
  43. package/src/convex-app-demo-entry.ts +229 -35
  44. package/src/frameworks/code-transforms.ts +5 -1
  45. package/src/frameworks/next-config-parser.ts +140 -0
  46. package/src/frameworks/next-dev-server.ts +76 -1675
  47. package/src/frameworks/next-html-generator.ts +597 -0
  48. package/src/frameworks/next-shims.ts +1050 -0
  49. package/src/frameworks/tailwind-config-loader.ts +1 -1
  50. package/src/index.ts +2 -0
  51. package/src/runtime.ts +271 -25
  52. package/src/server-bridge.ts +61 -28
  53. package/src/shims/crypto.ts +13 -0
  54. package/src/shims/esbuild.ts +4 -1
  55. package/src/shims/fs.ts +9 -11
  56. package/src/shims/http.ts +312 -3
  57. package/src/shims/path.ts +6 -13
  58. package/src/shims/stream.ts +12 -26
  59. package/src/shims/vfs-adapter.ts +5 -2
  60. package/src/shims/ws.ts +95 -2
  61. package/src/types/package-json.ts +1 -0
  62. package/src/utils/binary-encoding.ts +43 -0
  63. package/src/virtual-fs.ts +7 -15
  64. package/dist/assets/runtime-worker-B8_LZkBX.js.map +0 -1
package/README.md CHANGED
@@ -8,6 +8,8 @@ A lightweight, browser-native Node.js runtime environment. Run Node.js code, ins
8
8
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.0-blue.svg)](https://www.typescriptlang.org/)
9
9
  [![Node.js](https://img.shields.io/badge/Node.js-%3E%3D20-green.svg)](https://nodejs.org/)
10
10
 
11
+ Built by the creators of [Macaly.com](https://macaly.com) — a tool that lets anyone build websites and web apps, even without coding experience. Think Claude Code for non-developers.
12
+
11
13
  > **Warning:** This project is experimental and may contain bugs. Use with caution in production environments.
12
14
 
13
15
  ---
@@ -856,7 +858,7 @@ triggerHMR('/app/page.tsx', iframe);
856
858
  ### Setup
857
859
 
858
860
  ```bash
859
- git clone https://github.com/Macaly/almostnode.git
861
+ git clone https://github.com/macaly/almostnode.git
860
862
  cd almostnode
861
863
  npm install
862
864
  ```
@@ -909,5 +911,5 @@ MIT License - see [LICENSE](LICENSE) for details.
909
911
  ---
910
912
 
911
913
  <p align="center">
912
- Made with care for the browser
914
+ Built by the creators of <a href="https://macaly.com">Macaly.com</a>
913
915
  </p>
package/dist/CNAME ADDED
@@ -0,0 +1 @@
1
+ almostnode.dev
package/dist/__sw__.js CHANGED
@@ -1,9 +1,11 @@
1
1
  /**
2
2
  * Service Worker for Mini WebContainers
3
3
  * Intercepts fetch requests and routes them to virtual servers
4
- * Version: 14 - forward ALL requests from virtual context (images, scripts, navigation) to virtual server
4
+ * Version: 15 - cleanup: extract helpers, gate debug logs, remove test endpoints
5
5
  */
6
6
 
7
+ const DEBUG = false;
8
+
7
9
  // Communication port with main thread
8
10
  let mainPort = null;
9
11
 
@@ -14,30 +16,45 @@ let requestId = 0;
14
16
  // Registered virtual server ports
15
17
  const registeredPorts = new Set();
16
18
 
19
+ /**
20
+ * Decode base64 string to Uint8Array
21
+ */
22
+ function base64ToBytes(base64) {
23
+ const binary = atob(base64);
24
+ const bytes = new Uint8Array(binary.length);
25
+ for (let i = 0; i < binary.length; i++) {
26
+ bytes[i] = binary.charCodeAt(i);
27
+ }
28
+ return bytes;
29
+ }
30
+
17
31
  /**
18
32
  * Handle messages from main thread
19
33
  */
20
34
  self.addEventListener('message', (event) => {
21
35
  const { type, data } = event.data;
22
36
 
23
- console.log('[SW] Received message:', type, 'hasPort in event.ports:', event.ports?.length > 0);
37
+ DEBUG && console.log('[SW] Received message:', type, 'hasPort in event.ports:', event.ports?.length > 0);
24
38
 
25
39
  // When a MessagePort is transferred, it's in event.ports[0], not event.data.port
26
40
  if (type === 'init' && event.ports && event.ports[0]) {
27
41
  // Initialize communication channel
28
42
  mainPort = event.ports[0];
29
43
  mainPort.onmessage = handleMainMessage;
30
- console.log('[SW] Initialized communication channel with transferred port');
44
+ DEBUG && console.log('[SW] Initialized communication channel with transferred port');
45
+ // Re-claim clients so that pages opened after SW activation get controlled.
46
+ // Without this, controllerchange never fires for late-arriving pages.
47
+ self.clients.claim();
31
48
  }
32
49
 
33
50
  if (type === 'server-registered' && data) {
34
51
  registeredPorts.add(data.port);
35
- console.log(`[SW] Server registered on port ${data.port}`);
52
+ DEBUG && console.log(`[SW] Server registered on port ${data.port}`);
36
53
  }
37
54
 
38
55
  if (type === 'server-unregistered' && data) {
39
56
  registeredPorts.delete(data.port);
40
- console.log(`[SW] Server unregistered from port ${data.port}`);
57
+ DEBUG && console.log(`[SW] Server unregistered from port ${data.port}`);
41
58
  }
42
59
  });
43
60
 
@@ -47,20 +64,20 @@ self.addEventListener('message', (event) => {
47
64
  function handleMainMessage(event) {
48
65
  const { type, id, data, error } = event.data;
49
66
 
50
- console.log('[SW] Received message from main:', type, 'id:', id);
67
+ DEBUG && console.log('[SW] Received message from main:', type, 'id:', id);
51
68
 
52
69
  if (type === 'response') {
53
70
  const pending = pendingRequests.get(id);
54
- console.log('[SW] Looking for pending request:', id, 'found:', !!pending);
71
+ DEBUG && console.log('[SW] Looking for pending request:', id, 'found:', !!pending);
55
72
 
56
73
  if (pending) {
57
74
  pendingRequests.delete(id);
58
75
 
59
76
  if (error) {
60
- console.log('[SW] Response error:', error);
77
+ DEBUG && console.log('[SW] Response error:', error);
61
78
  pending.reject(new Error(error));
62
79
  } else {
63
- console.log('[SW] Response data:', {
80
+ DEBUG && console.log('[SW] Response data:', {
64
81
  statusCode: data?.statusCode,
65
82
  statusMessage: data?.statusMessage,
66
83
  headers: data?.headers,
@@ -74,50 +91,46 @@ function handleMainMessage(event) {
74
91
 
75
92
  // Handle streaming responses
76
93
  if (type === 'stream-start') {
77
- console.log('[SW] 🟢 stream-start received, id:', id);
94
+ DEBUG && console.log('[SW] stream-start received, id:', id);
78
95
  const pending = pendingRequests.get(id);
79
96
  if (pending && pending.streamController) {
80
97
  // Store headers/status for the streaming response
81
98
  pending.streamData = data;
82
99
  pending.resolveHeaders(data);
83
- console.log('[SW] 🟢 headers resolved for stream', id);
100
+ DEBUG && console.log('[SW] headers resolved for stream', id);
84
101
  } else {
85
- console.log('[SW] 🔴 No pending request or controller for stream-start', id, !!pending, pending?.streamController);
102
+ DEBUG && console.log('[SW] No pending request or controller for stream-start', id, !!pending, pending?.streamController);
86
103
  }
87
104
  }
88
105
 
89
106
  if (type === 'stream-chunk') {
90
- console.log('[SW] 🟡 stream-chunk received, id:', id, 'size:', data?.chunkBase64?.length);
107
+ DEBUG && console.log('[SW] stream-chunk received, id:', id, 'size:', data?.chunkBase64?.length);
91
108
  const pending = pendingRequests.get(id);
92
109
  if (pending && pending.streamController) {
93
110
  try {
94
111
  // Decode base64 chunk and enqueue
95
112
  if (data.chunkBase64) {
96
- const binary = atob(data.chunkBase64);
97
- const bytes = new Uint8Array(binary.length);
98
- for (let i = 0; i < binary.length; i++) {
99
- bytes[i] = binary.charCodeAt(i);
100
- }
113
+ const bytes = base64ToBytes(data.chunkBase64);
101
114
  pending.streamController.enqueue(bytes);
102
- console.log('[SW] 🟡 chunk enqueued, bytes:', bytes.length);
115
+ DEBUG && console.log('[SW] chunk enqueued, bytes:', bytes.length);
103
116
  }
104
117
  } catch (e) {
105
118
  console.error('[SW] Error enqueueing chunk:', e);
106
119
  }
107
120
  } else {
108
- console.log('[SW] 🔴 No pending request or controller for stream-chunk', id);
121
+ DEBUG && console.log('[SW] No pending request or controller for stream-chunk', id);
109
122
  }
110
123
  }
111
124
 
112
125
  if (type === 'stream-end') {
113
- console.log('[SW] 🟢 stream-end received, id:', id);
126
+ DEBUG && console.log('[SW] stream-end received, id:', id);
114
127
  const pending = pendingRequests.get(id);
115
128
  if (pending && pending.streamController) {
116
129
  try {
117
130
  pending.streamController.close();
118
- console.log('[SW] 🟢 stream closed');
131
+ DEBUG && console.log('[SW] stream closed');
119
132
  } catch (e) {
120
- console.log('[SW] stream already closed');
133
+ DEBUG && console.log('[SW] stream already closed');
121
134
  }
122
135
  pendingRequests.delete(id);
123
136
  }
@@ -128,11 +141,23 @@ function handleMainMessage(event) {
128
141
  * Send request to main thread and wait for response
129
142
  */
130
143
  async function sendRequest(port, method, url, headers, body) {
131
- console.log('[SW] sendRequest called, mainPort:', !!mainPort, 'url:', url);
144
+ DEBUG && console.log('[SW] sendRequest called, mainPort:', !!mainPort, 'url:', url);
132
145
 
133
146
  if (!mainPort) {
134
- console.error('[SW] No mainPort available! Service Worker not connected to main thread.');
135
- throw new Error('Service Worker not initialized - no connection to main thread');
147
+ // Ask all clients to re-send the init message
148
+ const allClients = await self.clients.matchAll({ type: 'window' });
149
+ for (const client of allClients) {
150
+ client.postMessage({ type: 'sw-needs-init' });
151
+ }
152
+ // Wait up to 5s for a client to re-initialize the port
153
+ // (main thread may be busy with heavy operations like CLI execution)
154
+ await new Promise(resolve => {
155
+ const check = setInterval(() => { if (mainPort) { clearInterval(check); resolve(); } }, 50);
156
+ setTimeout(() => { clearInterval(check); resolve(); }, 5000);
157
+ });
158
+ if (!mainPort) {
159
+ throw new Error('Service Worker not initialized - no connection to main thread');
160
+ }
136
161
  }
137
162
 
138
163
  const id = ++requestId;
@@ -160,11 +185,22 @@ async function sendRequest(port, method, url, headers, body) {
160
185
  * Send streaming request to main thread
161
186
  * Returns a ReadableStream that receives chunks from main thread
162
187
  */
163
- function sendStreamingRequest(port, method, url, headers, body) {
164
- console.log('[SW] sendStreamingRequest called, url:', url);
188
+ async function sendStreamingRequest(port, method, url, headers, body) {
189
+ DEBUG && console.log('[SW] sendStreamingRequest called, url:', url);
165
190
 
166
191
  if (!mainPort) {
167
- throw new Error('Service Worker not initialized');
192
+ // Ask all clients to re-send the init message
193
+ const allClients = await self.clients.matchAll({ type: 'window' });
194
+ for (const client of allClients) {
195
+ client.postMessage({ type: 'sw-needs-init' });
196
+ }
197
+ await new Promise(resolve => {
198
+ const check = setInterval(() => { if (mainPort) { clearInterval(check); resolve(); } }, 50);
199
+ setTimeout(() => { clearInterval(check); resolve(); }, 5000);
200
+ });
201
+ if (!mainPort) {
202
+ throw new Error('Service Worker not initialized');
203
+ }
168
204
  }
169
205
 
170
206
  const id = ++requestId;
@@ -206,7 +242,7 @@ function sendStreamingRequest(port, method, url, headers, body) {
206
242
  self.addEventListener('fetch', (event) => {
207
243
  const url = new URL(event.request.url);
208
244
 
209
- console.log('[SW] Fetch:', url.pathname, 'mainPort:', !!mainPort);
245
+ DEBUG && console.log('[SW] Fetch:', url.pathname, 'mainPort:', !!mainPort);
210
246
 
211
247
  // Check if this is a virtual server request
212
248
  const match = url.pathname.match(/^\/__virtual__\/(\d+)(\/.*)?$/);
@@ -229,12 +265,12 @@ self.addEventListener('fetch', (event) => {
229
265
  if (event.request.mode === 'navigate') {
230
266
  // Navigation requests: redirect to include the virtual prefix
231
267
  const redirectUrl = url.origin + virtualPrefix + targetPath;
232
- console.log('[SW] Redirecting navigation from virtual context:', url.pathname, '->', redirectUrl);
268
+ DEBUG && console.log('[SW] Redirecting navigation from virtual context:', url.pathname, '->', redirectUrl);
233
269
  event.respondWith(Response.redirect(redirectUrl, 302));
234
270
  return;
235
271
  } else {
236
272
  // Non-navigation requests (images, scripts, etc.): forward to virtual server
237
- console.log('[SW] Forwarding resource from virtual context:', url.pathname);
273
+ DEBUG && console.log('[SW] Forwarding resource from virtual context:', url.pathname);
238
274
  event.respondWith(handleVirtualRequest(event.request, virtualPort, targetPath));
239
275
  return;
240
276
  }
@@ -247,45 +283,11 @@ self.addEventListener('fetch', (event) => {
247
283
  return;
248
284
  }
249
285
 
250
- console.log('[SW] Virtual request:', url.pathname);
286
+ DEBUG && console.log('[SW] Virtual request:', url.pathname);
251
287
 
252
288
  const port = parseInt(match[1], 10);
253
289
  const path = match[2] || '/';
254
290
 
255
- // TEST MODE: Return hardcoded response to verify SW is working
256
- if (url.searchParams.has('__sw_test__')) {
257
- event.respondWith(new Response(
258
- '<!DOCTYPE html><html><body><h1>SW Test OK</h1><div id="root">Service Worker is responding correctly!</div></body></html>',
259
- {
260
- status: 200,
261
- headers: { 'Content-Type': 'text/html' },
262
- }
263
- ));
264
- return;
265
- }
266
-
267
- // DEBUG MODE: Return info about what SW receives
268
- if (url.searchParams.has('__sw_debug__')) {
269
- event.respondWith((async () => {
270
- try {
271
- const response = await sendRequest(port, 'GET', path, {}, null);
272
- return new Response(
273
- `<!DOCTYPE html><html><body><h1>SW Debug</h1><pre>${JSON.stringify({
274
- statusCode: response.statusCode,
275
- statusMessage: response.statusMessage,
276
- headers: response.headers,
277
- bodyBase64Length: response.bodyBase64?.length,
278
- bodyBase64Start: response.bodyBase64?.substring(0, 100),
279
- }, null, 2)}</pre></body></html>`,
280
- { status: 200, headers: { 'Content-Type': 'text/html' } }
281
- );
282
- } catch (error) {
283
- return new Response(`Error: ${error.message}`, { status: 500 });
284
- }
285
- })());
286
- return;
287
- }
288
-
289
291
  event.respondWith(handleVirtualRequest(event.request, port, path + url.search));
290
292
  });
291
293
 
@@ -310,17 +312,15 @@ async function handleVirtualRequest(request, port, path) {
310
312
  const isStreamingCandidate = request.method === 'POST' && path.startsWith('/api/');
311
313
 
312
314
  if (isStreamingCandidate) {
313
- console.log('[SW] 🔴 Using streaming mode for:', path);
315
+ DEBUG && console.log('[SW] Using streaming mode for:', path);
314
316
  return handleStreamingRequest(port, request.method, path, headers, body);
315
317
  }
316
- console.log('[SW] Using non-streaming mode for:', request.method, path);
317
-
318
- console.log('[SW] Sending request to main thread:', port, request.method, path);
318
+ DEBUG && console.log('[SW] Using non-streaming mode for:', request.method, path);
319
319
 
320
320
  // Send to main thread
321
321
  const response = await sendRequest(port, request.method, path, headers, body);
322
322
 
323
- console.log('[SW] Got response from main thread:', {
323
+ DEBUG && console.log('[SW] Got response from main thread:', {
324
324
  statusCode: response.statusCode,
325
325
  headersKeys: response.headers ? Object.keys(response.headers) : [],
326
326
  bodyBase64Length: response.bodyBase64?.length,
@@ -330,16 +330,12 @@ async function handleVirtualRequest(request, port, path) {
330
330
  let finalResponse;
331
331
  if (response.bodyBase64 && response.bodyBase64.length > 0) {
332
332
  try {
333
- const binary = atob(response.bodyBase64);
334
- const bytes = new Uint8Array(binary.length);
335
- for (let i = 0; i < binary.length; i++) {
336
- bytes[i] = binary.charCodeAt(i);
337
- }
338
- console.log('[SW] Decoded body length:', bytes.length);
333
+ const bytes = base64ToBytes(response.bodyBase64);
334
+ DEBUG && console.log('[SW] Decoded body length:', bytes.length);
339
335
 
340
336
  // Use Blob to ensure proper body handling
341
337
  const blob = new Blob([bytes], { type: response.headers['Content-Type'] || 'application/octet-stream' });
342
- console.log('[SW] Created blob size:', blob.size);
338
+ DEBUG && console.log('[SW] Created blob size:', blob.size);
343
339
 
344
340
  // Merge response headers with CORP/COEP headers to allow iframe embedding
345
341
  // The parent page has COEP: credentialless, so we need matching headers
@@ -370,7 +366,7 @@ async function handleVirtualRequest(request, port, path) {
370
366
  });
371
367
  }
372
368
 
373
- console.log('[SW] Final Response created, status:', finalResponse.status);
369
+ DEBUG && console.log('[SW] Final Response created, status:', finalResponse.status);
374
370
 
375
371
  return finalResponse;
376
372
  } catch (error) {
@@ -387,12 +383,12 @@ async function handleVirtualRequest(request, port, path) {
387
383
  * Handle a streaming request
388
384
  */
389
385
  async function handleStreamingRequest(port, method, path, headers, body) {
390
- const { stream, headersPromise, id } = sendStreamingRequest(port, method, path, headers, body);
386
+ const { stream, headersPromise, id } = await sendStreamingRequest(port, method, path, headers, body);
391
387
 
392
388
  // Wait for headers to arrive
393
389
  const responseData = await headersPromise;
394
390
 
395
- console.log('[SW] Streaming response started:', responseData?.statusCode);
391
+ DEBUG && console.log('[SW] Streaming response started:', responseData?.statusCode);
396
392
 
397
393
  // Build response headers
398
394
  const respHeaders = new Headers(responseData?.headers || {});
@@ -412,7 +408,7 @@ async function handleStreamingRequest(port, method, path, headers, body) {
412
408
  * Activate immediately
413
409
  */
414
410
  self.addEventListener('install', (event) => {
415
- console.log('[SW] Installing...');
411
+ DEBUG && console.log('[SW] Installing...');
416
412
  event.waitUntil(self.skipWaiting());
417
413
  });
418
414
 
@@ -420,6 +416,6 @@ self.addEventListener('install', (event) => {
420
416
  * Claim all clients immediately
421
417
  */
422
418
  self.addEventListener('activate', (event) => {
423
- console.log('[SW] Activated');
419
+ DEBUG && console.log('[SW] Activated');
424
420
  event.waitUntil(self.clients.claim());
425
421
  });