zero-query 1.1.1 → 1.2.2

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 (156) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +2 -0
  3. package/cli/args.js +33 -33
  4. package/cli/commands/build-api.js +442 -442
  5. package/cli/commands/build.js +253 -247
  6. package/cli/commands/bundle.js +1226 -1224
  7. package/cli/commands/create.js +178 -121
  8. package/cli/commands/dev/devtools/index.js +56 -56
  9. package/cli/commands/dev/devtools/js/components.js +49 -49
  10. package/cli/commands/dev/devtools/js/core.js +423 -423
  11. package/cli/commands/dev/devtools/js/elements.js +421 -421
  12. package/cli/commands/dev/devtools/js/network.js +166 -166
  13. package/cli/commands/dev/devtools/js/performance.js +73 -73
  14. package/cli/commands/dev/devtools/js/router.js +105 -105
  15. package/cli/commands/dev/devtools/js/source.js +132 -132
  16. package/cli/commands/dev/devtools/js/stats.js +35 -35
  17. package/cli/commands/dev/devtools/js/tabs.js +79 -79
  18. package/cli/commands/dev/devtools/panel.html +95 -95
  19. package/cli/commands/dev/devtools/styles.css +244 -244
  20. package/cli/commands/dev/index.js +107 -107
  21. package/cli/commands/dev/logger.js +75 -75
  22. package/cli/commands/dev/overlay.js +858 -858
  23. package/cli/commands/dev/server.js +220 -220
  24. package/cli/commands/dev/validator.js +94 -94
  25. package/cli/commands/dev/watcher.js +172 -172
  26. package/cli/help.js +114 -112
  27. package/cli/index.js +52 -52
  28. package/cli/scaffold/default/LICENSE +21 -21
  29. package/cli/scaffold/default/app/app.js +207 -207
  30. package/cli/scaffold/default/app/components/about.js +201 -201
  31. package/cli/scaffold/default/app/components/api-demo.js +143 -143
  32. package/cli/scaffold/default/app/components/contact-card.js +231 -231
  33. package/cli/scaffold/default/app/components/contacts/contacts.css +706 -706
  34. package/cli/scaffold/default/app/components/contacts/contacts.html +200 -200
  35. package/cli/scaffold/default/app/components/contacts/contacts.js +196 -196
  36. package/cli/scaffold/default/app/components/counter.js +127 -127
  37. package/cli/scaffold/default/app/components/home.js +249 -249
  38. package/cli/scaffold/default/app/components/not-found.js +16 -16
  39. package/cli/scaffold/default/app/components/playground/playground.css +115 -115
  40. package/cli/scaffold/default/app/components/playground/playground.html +161 -161
  41. package/cli/scaffold/default/app/components/playground/playground.js +116 -116
  42. package/cli/scaffold/default/app/components/todos.js +225 -225
  43. package/cli/scaffold/default/app/components/toolkit/toolkit.css +97 -97
  44. package/cli/scaffold/default/app/components/toolkit/toolkit.html +146 -146
  45. package/cli/scaffold/default/app/components/toolkit/toolkit.js +280 -280
  46. package/cli/scaffold/default/app/routes.js +15 -15
  47. package/cli/scaffold/default/app/store.js +101 -101
  48. package/cli/scaffold/default/global.css +552 -552
  49. package/cli/scaffold/default/index.html +99 -99
  50. package/cli/scaffold/minimal/app/app.js +85 -85
  51. package/cli/scaffold/minimal/app/components/about.js +68 -68
  52. package/cli/scaffold/minimal/app/components/counter.js +122 -122
  53. package/cli/scaffold/minimal/app/components/home.js +68 -68
  54. package/cli/scaffold/minimal/app/components/not-found.js +16 -16
  55. package/cli/scaffold/minimal/app/routes.js +9 -9
  56. package/cli/scaffold/minimal/app/store.js +36 -36
  57. package/cli/scaffold/minimal/global.css +300 -300
  58. package/cli/scaffold/minimal/index.html +44 -44
  59. package/cli/scaffold/ssr/app/app.js +41 -41
  60. package/cli/scaffold/ssr/app/components/about.js +55 -55
  61. package/cli/scaffold/ssr/app/components/blog/index.js +65 -65
  62. package/cli/scaffold/ssr/app/components/blog/post.js +86 -86
  63. package/cli/scaffold/ssr/app/components/home.js +37 -37
  64. package/cli/scaffold/ssr/app/components/not-found.js +15 -15
  65. package/cli/scaffold/ssr/app/routes.js +8 -8
  66. package/cli/scaffold/ssr/global.css +228 -228
  67. package/cli/scaffold/ssr/index.html +37 -37
  68. package/cli/scaffold/ssr/package.json +8 -8
  69. package/cli/scaffold/ssr/server/data/posts.js +144 -144
  70. package/cli/scaffold/ssr/server/index.js +213 -213
  71. package/cli/scaffold/webrtc/app/app.js +11 -0
  72. package/cli/scaffold/webrtc/app/components/video-room.js +441 -0
  73. package/cli/scaffold/webrtc/assets/.gitkeep +0 -0
  74. package/cli/scaffold/webrtc/global.css +250 -0
  75. package/cli/scaffold/webrtc/index.html +21 -0
  76. package/cli/scaffold/webrtc/package.json +16 -0
  77. package/cli/scaffold/webrtc/server/index.js +198 -0
  78. package/cli/utils.js +305 -287
  79. package/dist/API.md +661 -0
  80. package/dist/zquery.dist.zip +0 -0
  81. package/dist/zquery.js +10454 -6614
  82. package/dist/zquery.min.js +8 -631
  83. package/index.d.ts +570 -371
  84. package/index.js +311 -240
  85. package/package.json +77 -70
  86. package/src/component.js +1758 -1691
  87. package/src/core.js +929 -921
  88. package/src/diff.js +497 -497
  89. package/src/errors.js +209 -209
  90. package/src/expression.js +929 -922
  91. package/src/http.js +242 -242
  92. package/src/package.json +1 -1
  93. package/src/reactive.js +271 -255
  94. package/src/router.js +878 -843
  95. package/src/ssr.js +421 -418
  96. package/src/store.js +325 -318
  97. package/src/utils.js +534 -515
  98. package/src/webrtc/e2ee.js +351 -0
  99. package/src/webrtc/errors.js +116 -0
  100. package/src/webrtc/ice.js +301 -0
  101. package/src/webrtc/index.js +131 -0
  102. package/src/webrtc/joinToken.js +119 -0
  103. package/src/webrtc/observe.js +172 -0
  104. package/src/webrtc/peer.js +351 -0
  105. package/src/webrtc/reactive.js +268 -0
  106. package/src/webrtc/room.js +625 -0
  107. package/src/webrtc/sdp.js +302 -0
  108. package/src/webrtc/sfu/index.js +43 -0
  109. package/src/webrtc/sfu/livekit.js +131 -0
  110. package/src/webrtc/sfu/mediasoup.js +150 -0
  111. package/src/webrtc/signaling.js +373 -0
  112. package/src/webrtc/turn.js +237 -0
  113. package/tests/_helpers/webrtcFakes.js +289 -0
  114. package/tests/audit.test.js +4158 -4158
  115. package/tests/bench/render.bench.js +77 -0
  116. package/tests/cli.test.js +1136 -1103
  117. package/tests/compare.test.js +497 -486
  118. package/tests/component.test.js +4076 -3938
  119. package/tests/core.test.js +1926 -1910
  120. package/tests/dev-server.test.js +489 -489
  121. package/tests/diff.test.js +1416 -1416
  122. package/tests/docs.test.js +1664 -1650
  123. package/tests/electron-features.test.js +864 -864
  124. package/tests/errors.test.js +619 -619
  125. package/tests/expression.test.js +1087 -1056
  126. package/tests/http.test.js +693 -648
  127. package/tests/reactive.test.js +844 -819
  128. package/tests/router.test.js +2423 -2327
  129. package/tests/ssr.test.js +916 -870
  130. package/tests/store.test.js +840 -830
  131. package/tests/test-minifier.js +153 -153
  132. package/tests/test-ssr.js +27 -27
  133. package/tests/utils.test.js +1411 -1377
  134. package/tests/webrtc/e2ee.test.js +283 -0
  135. package/tests/webrtc/ice.test.js +202 -0
  136. package/tests/webrtc/joinToken.test.js +89 -0
  137. package/tests/webrtc/observe.test.js +111 -0
  138. package/tests/webrtc/peer.test.js +373 -0
  139. package/tests/webrtc/reactive.test.js +235 -0
  140. package/tests/webrtc/room.test.js +406 -0
  141. package/tests/webrtc/sdp.test.js +151 -0
  142. package/tests/webrtc/sfu-livekit.test.js +119 -0
  143. package/tests/webrtc/sfu.test.js +160 -0
  144. package/tests/webrtc/signaling.test.js +251 -0
  145. package/tests/webrtc/turn.test.js +256 -0
  146. package/types/collection.d.ts +383 -383
  147. package/types/component.d.ts +186 -186
  148. package/types/errors.d.ts +135 -135
  149. package/types/http.d.ts +92 -92
  150. package/types/misc.d.ts +201 -201
  151. package/types/reactive.d.ts +98 -98
  152. package/types/router.d.ts +190 -190
  153. package/types/ssr.d.ts +102 -102
  154. package/types/store.d.ts +146 -146
  155. package/types/utils.d.ts +245 -245
  156. package/types/webrtc.d.ts +653 -0
@@ -1,220 +1,220 @@
1
- /**
2
- * cli/commands/dev/server.js - HTTP server & SSE broadcasting
3
- *
4
- * Creates the zero-http app, serves static files, injects the
5
- * error-overlay snippet into HTML responses, and manages the
6
- * SSE connection pool for live-reload events.
7
- *
8
- * Uses zero-http middleware:
9
- * - helmet() → security headers (relaxed CSP for dev inline scripts)
10
- * - compress() → brotli/gzip/deflate response compression
11
- * - cors() → allow cross-origin requests in development
12
- * - serveStatic() → static file serving with ETag & Cache-Control
13
- * - SSE with keepAlive, retry, pad for proxy compatibility
14
- */
15
-
16
- 'use strict';
17
-
18
- const fs = require('fs');
19
- const path = require('path');
20
- const OVERLAY_SCRIPT = require('./overlay');
21
- const DEVTOOLS_HTML = require('./devtools');
22
-
23
- // ---------------------------------------------------------------------------
24
- // SSE client pool
25
- // ---------------------------------------------------------------------------
26
-
27
- class SSEPool {
28
- constructor() {
29
- this._clients = new Set();
30
- }
31
-
32
- /** @param {import('zero-http').SSEStream} sse */
33
- add(sse) {
34
- this._clients.add(sse);
35
- sse.on('close', () => this._clients.delete(sse));
36
- }
37
-
38
- broadcast(eventType, data) {
39
- for (const sse of this._clients) {
40
- try { sse.event(eventType, data || ''); } catch { this._clients.delete(sse); }
41
- }
42
- }
43
-
44
- /** Number of connected SSE clients. */
45
- get size() { return this._clients.size; }
46
-
47
- closeAll() {
48
- for (const sse of this._clients) {
49
- try { sse.close(); } catch { /* ignore */ }
50
- }
51
- this._clients.clear();
52
- }
53
- }
54
-
55
- // ---------------------------------------------------------------------------
56
- // Server factory
57
- // ---------------------------------------------------------------------------
58
-
59
- /**
60
- * Prompt the user to auto-install zero-http when it isn't found.
61
- * Resolves `true` if the user accepts, `false` otherwise.
62
- */
63
- function promptInstall() {
64
- const rl = require('readline').createInterface({
65
- input: process.stdin,
66
- output: process.stdout,
67
- });
68
- return new Promise((resolve) => {
69
- rl.question(
70
- '\n The local dev server requires zero-http, which is not installed.\n' +
71
- ' This package is only used during development and is not needed\n' +
72
- ' for building, bundling, or production.\n' +
73
- ' Install it now? (y/n): ',
74
- (answer) => {
75
- rl.close();
76
- resolve(answer.trim().toLowerCase() === 'y');
77
- }
78
- );
79
- });
80
- }
81
-
82
- /**
83
- * @param {object} opts
84
- * @param {string} opts.root - absolute path to project root
85
- * @param {string} opts.htmlEntry - e.g. 'index.html'
86
- * @param {number} opts.port
87
- * @param {boolean} opts.noIntercept - skip zquery.min.js auto-resolve
88
- * @returns {Promise<{ app, pool: SSEPool, listen: Function }>}
89
- */
90
- async function createServer({ root, htmlEntry, port, noIntercept }) {
91
- let zeroHttp;
92
- try {
93
- zeroHttp = require('zero-http');
94
- } catch {
95
- const ok = await promptInstall();
96
- if (!ok) {
97
- console.error('\n ✖ Cannot start dev server without zero-http.\n');
98
- process.exit(1);
99
- }
100
- const { execSync } = require('child_process');
101
- console.log('\n Installing zero-http...\n');
102
- execSync('npm install zero-http --save-dev', { stdio: 'inherit' });
103
- zeroHttp = require('zero-http');
104
- }
105
-
106
- const {
107
- createApp,
108
- static: serveStatic,
109
- helmet,
110
- compress,
111
- cors,
112
- debug,
113
- } = zeroHttp;
114
-
115
- debug.level('silent');
116
-
117
- const app = createApp();
118
- const pool = new SSEPool();
119
-
120
- // ---- Security headers (dev-friendly CSP) ----
121
- app.use(helmet({
122
- contentSecurityPolicy: {
123
- directives: {
124
- defaultSrc: ["'self'"],
125
- scriptSrc: ["'self'", "'unsafe-inline'", "'unsafe-eval'"],
126
- styleSrc: ["'self'", "'unsafe-inline'"],
127
- imgSrc: ["'self'", 'data:', 'blob:'],
128
- connectSrc: ["'self'", 'ws:', 'wss:'],
129
- fontSrc: ["'self'", 'data:'],
130
- },
131
- },
132
- // SPA dev server runs over plain HTTP
133
- hsts: false,
134
- // Allow framing for devtools panel
135
- frameguard: false,
136
- }));
137
-
138
- // ---- CORS (allow cross-origin during development) ----
139
- app.use(cors());
140
-
141
- // ---- Compression (brotli > gzip > deflate) ----
142
- app.use(compress({
143
- threshold: 1024,
144
- }));
145
-
146
- // ---- SSE endpoint ----
147
- app.get('/__zq_reload', (req, res) => {
148
- const sse = res.sse({
149
- keepAlive: 30000,
150
- keepAliveComment: 'ping',
151
- retry: 3000,
152
- pad: 2048,
153
- });
154
- pool.add(sse);
155
- });
156
-
157
- // ---- DevTools panel ----
158
- app.get('/_devtools', (req, res) => {
159
- res.set('Content-Type', 'text/html; charset=utf-8');
160
- res.set('Cache-Control', 'no-cache');
161
- res.send(DEVTOOLS_HTML);
162
- });
163
-
164
- // ---- Auto-resolve zquery.min.js ----
165
- const pkgRoot = path.resolve(__dirname, '..', '..', '..');
166
-
167
- app.use((req, res, next) => {
168
- if (noIntercept) return next();
169
- const basename = path.basename(req.url.split('?')[0]).toLowerCase();
170
- if (basename !== 'zquery.min.js') return next();
171
-
172
- const candidates = [
173
- path.join(pkgRoot, 'dist', 'zquery.min.js'),
174
- path.join(root, 'node_modules', 'zero-query', 'dist', 'zquery.min.js'),
175
- ];
176
- for (const p of candidates) {
177
- if (fs.existsSync(p)) {
178
- res.set('Content-Type', 'application/javascript; charset=utf-8');
179
- res.set('Cache-Control', 'no-cache');
180
- res.send(fs.readFileSync(p, 'utf-8'));
181
- return;
182
- }
183
- }
184
- next();
185
- });
186
-
187
- // ---- Static files ----
188
- app.use(serveStatic(root, { index: false, dotfiles: 'ignore' }));
189
-
190
- // ---- SPA fallback - inject overlay/SSE snippet ----
191
- app.get('*', (req, res) => {
192
- if (path.extname(req.url) && path.extname(req.url) !== '.html') {
193
- res.status(404).send('Not Found');
194
- return;
195
- }
196
- const indexPath = path.join(root, htmlEntry);
197
- if (!fs.existsSync(indexPath)) {
198
- res.status(404).send(`${htmlEntry} not found`);
199
- return;
200
- }
201
- let html = fs.readFileSync(indexPath, 'utf-8');
202
- if (html.includes('</body>')) {
203
- html = html.replace('</body>', OVERLAY_SCRIPT + '\n</body>');
204
- } else {
205
- html += OVERLAY_SCRIPT;
206
- }
207
- res.html(html);
208
- });
209
-
210
- function listen(cb) {
211
- const server = app.listen(port, cb);
212
- server.keepAliveTimeout = 65000;
213
- server.headersTimeout = 66000;
214
- return server;
215
- }
216
-
217
- return { app, pool, listen };
218
- }
219
-
220
- module.exports = { createServer, SSEPool };
1
+ /**
2
+ * cli/commands/dev/server.js - HTTP server & SSE broadcasting
3
+ *
4
+ * Creates the zero-http app, serves static files, injects the
5
+ * error-overlay snippet into HTML responses, and manages the
6
+ * SSE connection pool for live-reload events.
7
+ *
8
+ * Uses zero-http middleware:
9
+ * - helmet() → security headers (relaxed CSP for dev inline scripts)
10
+ * - compress() → brotli/gzip/deflate response compression
11
+ * - cors() → allow cross-origin requests in development
12
+ * - serveStatic() → static file serving with ETag & Cache-Control
13
+ * - SSE with keepAlive, retry, pad for proxy compatibility
14
+ */
15
+
16
+ 'use strict';
17
+
18
+ const fs = require('fs');
19
+ const path = require('path');
20
+ const OVERLAY_SCRIPT = require('./overlay');
21
+ const DEVTOOLS_HTML = require('./devtools');
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // SSE client pool
25
+ // ---------------------------------------------------------------------------
26
+
27
+ class SSEPool {
28
+ constructor() {
29
+ this._clients = new Set();
30
+ }
31
+
32
+ /** @param {import('zero-http').SSEStream} sse */
33
+ add(sse) {
34
+ this._clients.add(sse);
35
+ sse.on('close', () => this._clients.delete(sse));
36
+ }
37
+
38
+ broadcast(eventType, data) {
39
+ for (const sse of this._clients) {
40
+ try { sse.event(eventType, data || ''); } catch { this._clients.delete(sse); }
41
+ }
42
+ }
43
+
44
+ /** Number of connected SSE clients. */
45
+ get size() { return this._clients.size; }
46
+
47
+ closeAll() {
48
+ for (const sse of this._clients) {
49
+ try { sse.close(); } catch { /* ignore */ }
50
+ }
51
+ this._clients.clear();
52
+ }
53
+ }
54
+
55
+ // ---------------------------------------------------------------------------
56
+ // Server factory
57
+ // ---------------------------------------------------------------------------
58
+
59
+ /**
60
+ * Prompt the user to auto-install zero-http when it isn't found.
61
+ * Resolves `true` if the user accepts, `false` otherwise.
62
+ */
63
+ function promptInstall() {
64
+ const rl = require('readline').createInterface({
65
+ input: process.stdin,
66
+ output: process.stdout,
67
+ });
68
+ return new Promise((resolve) => {
69
+ rl.question(
70
+ '\n The local dev server requires zero-http, which is not installed.\n' +
71
+ ' This package is only used during development and is not needed\n' +
72
+ ' for building, bundling, or production.\n' +
73
+ ' Install it now? (y/n): ',
74
+ (answer) => {
75
+ rl.close();
76
+ resolve(answer.trim().toLowerCase() === 'y');
77
+ }
78
+ );
79
+ });
80
+ }
81
+
82
+ /**
83
+ * @param {object} opts
84
+ * @param {string} opts.root - absolute path to project root
85
+ * @param {string} opts.htmlEntry - e.g. 'index.html'
86
+ * @param {number} opts.port
87
+ * @param {boolean} opts.noIntercept - skip zquery.min.js auto-resolve
88
+ * @returns {Promise<{ app, pool: SSEPool, listen: Function }>}
89
+ */
90
+ async function createServer({ root, htmlEntry, port, noIntercept }) {
91
+ let zeroHttp;
92
+ try {
93
+ zeroHttp = require('zero-http');
94
+ } catch {
95
+ const ok = await promptInstall();
96
+ if (!ok) {
97
+ console.error('\n ✖ Cannot start dev server without zero-http.\n');
98
+ process.exit(1);
99
+ }
100
+ const { execSync } = require('child_process');
101
+ console.log('\n Installing zero-http...\n');
102
+ execSync('npm install zero-http --save-dev', { stdio: 'inherit' });
103
+ zeroHttp = require('zero-http');
104
+ }
105
+
106
+ const {
107
+ createApp,
108
+ static: serveStatic,
109
+ helmet,
110
+ compress,
111
+ cors,
112
+ debug,
113
+ } = zeroHttp;
114
+
115
+ debug.level('silent');
116
+
117
+ const app = createApp();
118
+ const pool = new SSEPool();
119
+
120
+ // ---- Security headers (dev-friendly CSP) ----
121
+ app.use(helmet({
122
+ contentSecurityPolicy: {
123
+ directives: {
124
+ defaultSrc: ["'self'"],
125
+ scriptSrc: ["'self'", "'unsafe-inline'", "'unsafe-eval'"],
126
+ styleSrc: ["'self'", "'unsafe-inline'"],
127
+ imgSrc: ["'self'", 'data:', 'blob:'],
128
+ connectSrc: ["'self'", 'ws:', 'wss:'],
129
+ fontSrc: ["'self'", 'data:'],
130
+ },
131
+ },
132
+ // SPA dev server runs over plain HTTP
133
+ hsts: false,
134
+ // Allow framing for devtools panel
135
+ frameguard: false,
136
+ }));
137
+
138
+ // ---- CORS (allow cross-origin during development) ----
139
+ app.use(cors());
140
+
141
+ // ---- Compression (brotli > gzip > deflate) ----
142
+ app.use(compress({
143
+ threshold: 1024,
144
+ }));
145
+
146
+ // ---- SSE endpoint ----
147
+ app.get('/__zq_reload', (req, res) => {
148
+ const sse = res.sse({
149
+ keepAlive: 30000,
150
+ keepAliveComment: 'ping',
151
+ retry: 3000,
152
+ pad: 2048,
153
+ });
154
+ pool.add(sse);
155
+ });
156
+
157
+ // ---- DevTools panel ----
158
+ app.get('/_devtools', (req, res) => {
159
+ res.set('Content-Type', 'text/html; charset=utf-8');
160
+ res.set('Cache-Control', 'no-cache');
161
+ res.send(DEVTOOLS_HTML);
162
+ });
163
+
164
+ // ---- Auto-resolve zquery.min.js ----
165
+ const pkgRoot = path.resolve(__dirname, '..', '..', '..');
166
+
167
+ app.use((req, res, next) => {
168
+ if (noIntercept) return next();
169
+ const basename = path.basename(req.url.split('?')[0]).toLowerCase();
170
+ if (basename !== 'zquery.min.js') return next();
171
+
172
+ const candidates = [
173
+ path.join(pkgRoot, 'dist', 'zquery.min.js'),
174
+ path.join(root, 'node_modules', 'zero-query', 'dist', 'zquery.min.js'),
175
+ ];
176
+ for (const p of candidates) {
177
+ if (fs.existsSync(p)) {
178
+ res.set('Content-Type', 'application/javascript; charset=utf-8');
179
+ res.set('Cache-Control', 'no-cache');
180
+ res.send(fs.readFileSync(p, 'utf-8'));
181
+ return;
182
+ }
183
+ }
184
+ next();
185
+ });
186
+
187
+ // ---- Static files ----
188
+ app.use(serveStatic(root, { index: false, dotfiles: 'ignore' }));
189
+
190
+ // ---- SPA fallback - inject overlay/SSE snippet ----
191
+ app.get('*', (req, res) => {
192
+ if (path.extname(req.url) && path.extname(req.url) !== '.html') {
193
+ res.status(404).send('Not Found');
194
+ return;
195
+ }
196
+ const indexPath = path.join(root, htmlEntry);
197
+ if (!fs.existsSync(indexPath)) {
198
+ res.status(404).send(`${htmlEntry} not found`);
199
+ return;
200
+ }
201
+ let html = fs.readFileSync(indexPath, 'utf-8');
202
+ if (html.includes('</body>')) {
203
+ html = html.replace('</body>', OVERLAY_SCRIPT + '\n</body>');
204
+ } else {
205
+ html += OVERLAY_SCRIPT;
206
+ }
207
+ res.html(html);
208
+ });
209
+
210
+ function listen(cb) {
211
+ const server = app.listen(port, cb);
212
+ server.keepAliveTimeout = 65000;
213
+ server.headersTimeout = 66000;
214
+ return server;
215
+ }
216
+
217
+ return { app, pool, listen };
218
+ }
219
+
220
+ module.exports = { createServer, SSEPool };
@@ -1,94 +1,94 @@
1
- /**
2
- * cli/commands/dev/validator.js - JS syntax validation
3
- *
4
- * Pre-validates JavaScript files on save using Node's VM module.
5
- * Returns structured error descriptors with code frames compatible
6
- * with the browser error overlay.
7
- */
8
-
9
- 'use strict';
10
-
11
- const fs = require('fs');
12
- const vm = require('vm');
13
-
14
- // ---------------------------------------------------------------------------
15
- // Code frame generator
16
- // ---------------------------------------------------------------------------
17
-
18
- /**
19
- * Build a code frame showing ~4 lines of context around the error
20
- * with a caret pointer at the offending column.
21
- *
22
- * @param {string} source - full file contents
23
- * @param {number} line - 1-based line number
24
- * @param {number} column - 1-based column number
25
- * @returns {string}
26
- */
27
- function generateCodeFrame(source, line, column) {
28
- const lines = source.split('\n');
29
- const start = Math.max(0, line - 4);
30
- const end = Math.min(lines.length, line + 3);
31
- const pad = String(end).length;
32
- const frame = [];
33
-
34
- for (let i = start; i < end; i++) {
35
- const num = String(i + 1).padStart(pad);
36
- const marker = i === line - 1 ? '>' : ' ';
37
- frame.push(`${marker} ${num} | ${lines[i]}`);
38
- if (i === line - 1 && column > 0) {
39
- frame.push(` ${' '.repeat(pad)} | ${' '.repeat(column - 1)}^`);
40
- }
41
- }
42
- return frame.join('\n');
43
- }
44
-
45
- // ---------------------------------------------------------------------------
46
- // JS validation
47
- // ---------------------------------------------------------------------------
48
-
49
- /**
50
- * Validate a JavaScript file for syntax errors.
51
- *
52
- * Strips ESM import/export statements (preserving line numbers) so the
53
- * VM can parse module-style code, then compiles via vm.Script.
54
- *
55
- * @param {string} filePath - absolute path to the file
56
- * @param {string} relPath - display-friendly relative path
57
- * @returns {object|null} - error descriptor, or null if valid
58
- */
59
- function validateJS(filePath, relPath) {
60
- let source;
61
- try { source = fs.readFileSync(filePath, 'utf-8'); } catch { return null; }
62
-
63
- const normalized = source.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
64
- const stripped = normalized.split('\n').map(line => {
65
- if (/^\s*import\s+.*from\s+['"]/.test(line)) return ' '.repeat(line.length);
66
- if (/^\s*import\s+['"]/.test(line)) return ' '.repeat(line.length);
67
- if (/^\s*export\s*\{/.test(line)) return ' '.repeat(line.length);
68
- line = line.replace(/^(\s*)export\s+default\s+/, '$1');
69
- line = line.replace(/^(\s*)export\s+(const|let|var|function|class|async\s+function)\s/, '$1$2 ');
70
- line = line.replace(/import\.meta\.url/g, "'__meta__'");
71
- line = line.replace(/import\.meta/g, '({})');
72
- return line;
73
- }).join('\n');
74
-
75
- try {
76
- new vm.Script(stripped, { filename: relPath });
77
- return null;
78
- } catch (err) {
79
- const line = err.stack ? parseInt((err.stack.match(/:(\d+)/) || [])[1]) || 0 : 0;
80
- const col = err.stack ? parseInt((err.stack.match(/:(\d+):(\d+)/) || [])[2]) || 0 : 0;
81
- const frame = line > 0 ? generateCodeFrame(source, line, col) : '';
82
- return {
83
- code: 'ZQ_DEV_SYNTAX',
84
- type: err.constructor.name || 'SyntaxError',
85
- message: err.message,
86
- file: relPath,
87
- line,
88
- column: col,
89
- frame,
90
- };
91
- }
92
- }
93
-
94
- module.exports = { generateCodeFrame, validateJS };
1
+ /**
2
+ * cli/commands/dev/validator.js - JS syntax validation
3
+ *
4
+ * Pre-validates JavaScript files on save using Node's VM module.
5
+ * Returns structured error descriptors with code frames compatible
6
+ * with the browser error overlay.
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const fs = require('fs');
12
+ const vm = require('vm');
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // Code frame generator
16
+ // ---------------------------------------------------------------------------
17
+
18
+ /**
19
+ * Build a code frame showing ~4 lines of context around the error
20
+ * with a caret pointer at the offending column.
21
+ *
22
+ * @param {string} source - full file contents
23
+ * @param {number} line - 1-based line number
24
+ * @param {number} column - 1-based column number
25
+ * @returns {string}
26
+ */
27
+ function generateCodeFrame(source, line, column) {
28
+ const lines = source.split('\n');
29
+ const start = Math.max(0, line - 4);
30
+ const end = Math.min(lines.length, line + 3);
31
+ const pad = String(end).length;
32
+ const frame = [];
33
+
34
+ for (let i = start; i < end; i++) {
35
+ const num = String(i + 1).padStart(pad);
36
+ const marker = i === line - 1 ? '>' : ' ';
37
+ frame.push(`${marker} ${num} | ${lines[i]}`);
38
+ if (i === line - 1 && column > 0) {
39
+ frame.push(` ${' '.repeat(pad)} | ${' '.repeat(column - 1)}^`);
40
+ }
41
+ }
42
+ return frame.join('\n');
43
+ }
44
+
45
+ // ---------------------------------------------------------------------------
46
+ // JS validation
47
+ // ---------------------------------------------------------------------------
48
+
49
+ /**
50
+ * Validate a JavaScript file for syntax errors.
51
+ *
52
+ * Strips ESM import/export statements (preserving line numbers) so the
53
+ * VM can parse module-style code, then compiles via vm.Script.
54
+ *
55
+ * @param {string} filePath - absolute path to the file
56
+ * @param {string} relPath - display-friendly relative path
57
+ * @returns {object|null} - error descriptor, or null if valid
58
+ */
59
+ function validateJS(filePath, relPath) {
60
+ let source;
61
+ try { source = fs.readFileSync(filePath, 'utf-8'); } catch { return null; }
62
+
63
+ const normalized = source.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
64
+ const stripped = normalized.split('\n').map(line => {
65
+ if (/^\s*import\s+.*from\s+['"]/.test(line)) return ' '.repeat(line.length);
66
+ if (/^\s*import\s+['"]/.test(line)) return ' '.repeat(line.length);
67
+ if (/^\s*export\s*\{/.test(line)) return ' '.repeat(line.length);
68
+ line = line.replace(/^(\s*)export\s+default\s+/, '$1');
69
+ line = line.replace(/^(\s*)export\s+(const|let|var|function|class|async\s+function)\s/, '$1$2 ');
70
+ line = line.replace(/import\.meta\.url/g, "'__meta__'");
71
+ line = line.replace(/import\.meta/g, '({})');
72
+ return line;
73
+ }).join('\n');
74
+
75
+ try {
76
+ new vm.Script(stripped, { filename: relPath });
77
+ return null;
78
+ } catch (err) {
79
+ const line = err.stack ? parseInt((err.stack.match(/:(\d+)/) || [])[1]) || 0 : 0;
80
+ const col = err.stack ? parseInt((err.stack.match(/:(\d+):(\d+)/) || [])[2]) || 0 : 0;
81
+ const frame = line > 0 ? generateCodeFrame(source, line, col) : '';
82
+ return {
83
+ code: 'ZQ_DEV_SYNTAX',
84
+ type: err.constructor.name || 'SyntaxError',
85
+ message: err.message,
86
+ file: relPath,
87
+ line,
88
+ column: col,
89
+ frame,
90
+ };
91
+ }
92
+ }
93
+
94
+ module.exports = { generateCodeFrame, validateJS };