zero-query 1.1.1 → 1.2.0

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 (154) 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 +443 -442
  5. package/cli/commands/build.js +254 -247
  6. package/cli/commands/bundle.js +1228 -1224
  7. package/cli/commands/create.js +137 -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 +295 -0
  73. package/cli/scaffold/webrtc/app/lib/room.js +252 -0
  74. package/cli/scaffold/webrtc/assets/.gitkeep +0 -0
  75. package/cli/scaffold/webrtc/global.css +250 -0
  76. package/cli/scaffold/webrtc/index.html +21 -0
  77. package/cli/utils.js +305 -287
  78. package/dist/API.md +661 -0
  79. package/dist/zquery.dist.zip +0 -0
  80. package/dist/zquery.js +10313 -6614
  81. package/dist/zquery.min.js +8 -631
  82. package/index.d.ts +570 -371
  83. package/index.js +311 -240
  84. package/package.json +76 -70
  85. package/src/component.js +1709 -1691
  86. package/src/core.js +921 -921
  87. package/src/diff.js +497 -497
  88. package/src/errors.js +209 -209
  89. package/src/expression.js +922 -922
  90. package/src/http.js +242 -242
  91. package/src/package.json +1 -1
  92. package/src/reactive.js +255 -255
  93. package/src/router.js +843 -843
  94. package/src/ssr.js +418 -418
  95. package/src/store.js +318 -318
  96. package/src/utils.js +515 -515
  97. package/src/webrtc/e2ee.js +351 -0
  98. package/src/webrtc/errors.js +116 -0
  99. package/src/webrtc/ice.js +301 -0
  100. package/src/webrtc/index.js +131 -0
  101. package/src/webrtc/joinToken.js +119 -0
  102. package/src/webrtc/observe.js +172 -0
  103. package/src/webrtc/peer.js +351 -0
  104. package/src/webrtc/reactive.js +268 -0
  105. package/src/webrtc/room.js +625 -0
  106. package/src/webrtc/sdp.js +302 -0
  107. package/src/webrtc/sfu/index.js +43 -0
  108. package/src/webrtc/sfu/livekit.js +131 -0
  109. package/src/webrtc/sfu/mediasoup.js +150 -0
  110. package/src/webrtc/signaling.js +373 -0
  111. package/src/webrtc/turn.js +237 -0
  112. package/tests/_helpers/webrtcFakes.js +289 -0
  113. package/tests/audit.test.js +4158 -4158
  114. package/tests/cli.test.js +1136 -1103
  115. package/tests/compare.test.js +497 -486
  116. package/tests/component.test.js +3969 -3938
  117. package/tests/core.test.js +1910 -1910
  118. package/tests/dev-server.test.js +489 -489
  119. package/tests/diff.test.js +1416 -1416
  120. package/tests/docs.test.js +1664 -1650
  121. package/tests/electron-features.test.js +864 -864
  122. package/tests/errors.test.js +619 -619
  123. package/tests/expression.test.js +1056 -1056
  124. package/tests/http.test.js +648 -648
  125. package/tests/reactive.test.js +819 -819
  126. package/tests/router.test.js +2327 -2327
  127. package/tests/ssr.test.js +870 -870
  128. package/tests/store.test.js +830 -830
  129. package/tests/test-minifier.js +153 -153
  130. package/tests/test-ssr.js +27 -27
  131. package/tests/utils.test.js +1377 -1377
  132. package/tests/webrtc/e2ee.test.js +283 -0
  133. package/tests/webrtc/ice.test.js +202 -0
  134. package/tests/webrtc/joinToken.test.js +89 -0
  135. package/tests/webrtc/observe.test.js +111 -0
  136. package/tests/webrtc/peer.test.js +373 -0
  137. package/tests/webrtc/reactive.test.js +235 -0
  138. package/tests/webrtc/room.test.js +406 -0
  139. package/tests/webrtc/sdp.test.js +151 -0
  140. package/tests/webrtc/sfu-livekit.test.js +119 -0
  141. package/tests/webrtc/sfu.test.js +160 -0
  142. package/tests/webrtc/signaling.test.js +251 -0
  143. package/tests/webrtc/turn.test.js +256 -0
  144. package/types/collection.d.ts +383 -383
  145. package/types/component.d.ts +186 -186
  146. package/types/errors.d.ts +135 -135
  147. package/types/http.d.ts +92 -92
  148. package/types/misc.d.ts +201 -201
  149. package/types/reactive.d.ts +98 -98
  150. package/types/router.d.ts +190 -190
  151. package/types/ssr.d.ts +102 -102
  152. package/types/store.d.ts +146 -146
  153. package/types/utils.d.ts +245 -245
  154. package/types/webrtc.d.ts +653 -0
@@ -1,442 +1,443 @@
1
- /**
2
- * cli/commands/build-api.js - auto-generate API.md from docs section data
3
- *
4
- * Reads the structured docs sections (zquery-website/app/components/docs/sections),
5
- * converts their HTML content to clean Markdown, and writes API.md.
6
- *
7
- * Usage: node cli/commands/build-api.js
8
- * npm run build:api
9
- */
10
-
11
- 'use strict';
12
-
13
- const fs = require('fs');
14
- const path = require('path');
15
- const { pathToFileURL } = require('url');
16
-
17
- // Sections to include in API.md and their order.
18
- // Maps section ID → { file, export } for direct import (avoids pulling in
19
- // getting-started.js which depends on the website store / globalThis.$).
20
- const API_SECTIONS = [
21
- { id: 'router', file: 'router.js', exportName: 'routerSec' },
22
- { id: 'components', file: 'components.js', exportName: 'components' },
23
- { id: 'directives', file: 'directives.js', exportName: 'directives' },
24
- { id: 'store', file: 'store.js', exportName: 'storeSec' },
25
- { id: 'http', file: 'http.js', exportName: 'http' },
26
- { id: 'reactive', file: 'reactive.js', exportName: 'reactive' },
27
- { id: 'selectors', file: 'selectors.js', exportName: 'selectors' },
28
- { id: 'utils', file: 'utils.js', exportName: 'utils' },
29
- { id: 'error-handling', file: 'error-handling.js', exportName: 'errorHandling'},
30
- { id: 'ssr', file: 'ssr.js', exportName: 'ssr' },
31
- { id: 'security', file: 'security.js', exportName: 'security' },
32
- { id: 'environment', file: 'environment.js', exportName: 'environment' },
33
- ];
34
-
35
-
36
- // ---------------------------------------------------------------------------
37
- // HTML entity decoding
38
- // ---------------------------------------------------------------------------
39
-
40
- function unesc(str) {
41
- return str
42
- .replace(/&/g, '&')
43
- .replace(/&lt;/g, '<')
44
- .replace(/&gt;/g, '>')
45
- .replace(/&quot;/g, '"')
46
- .replace(/&#39;/g, "'")
47
- .replace(/&mdash;/g, '\u2014')
48
- .replace(/&ndash;/g, '\u2013')
49
- .replace(/&rarr;/g, '\u2192')
50
- .replace(/&larr;/g, '\u2190')
51
- .replace(/&nbsp;/g, ' ')
52
- .replace(/&ensp;/g, ' ')
53
- .replace(/&emsp;/g, ' ')
54
- .replace(/&hellip;/g, '\u2026')
55
- .replace(/&times;/g, '\u00D7')
56
- .replace(/&copy;/g, '\u00A9')
57
- .replace(/&rsquo;/g, '\u2019')
58
- .replace(/&lsquo;/g, '\u2018')
59
- .replace(/&rdquo;/g, '\u201D')
60
- .replace(/&ldquo;/g, '\u201C')
61
- .replace(/&bull;/g, '\u2022')
62
- .replace(/&middot;/g, '\u00B7')
63
- .replace(/&trade;/g, '\u2122')
64
- .replace(/&rsaquo;/g, '\u203A')
65
- .replace(/&lsaquo;/g, '\u2039')
66
- .replace(/&harr;/g, '\u2194')
67
- .replace(/&apos;/g, "'")
68
- .replace(/&hairsp;/g, '\u200A')
69
- .replace(/&thinsp;/g, '\u2009')
70
- .replace(/&#(\d+);/g, (_, n) => String.fromCharCode(parseInt(n, 10)));
71
- }
72
-
73
-
74
- // ---------------------------------------------------------------------------
75
- // Inline HTML → Markdown
76
- // ---------------------------------------------------------------------------
77
-
78
- /**
79
- * Convert inline HTML elements to their Markdown equivalents.
80
- * Handles: code, strong/b, em/i, a, br, spans (dots, badges, etc.)
81
- */
82
- function inlineMd(html) {
83
- if (!html) return '';
84
- let md = html;
85
-
86
- // Strip decorative dots (colored circles in table cells)
87
- md = md.replace(/<span class="docs-dot"[^>]*><\/span>\s*/g, '');
88
- md = md.replace(/<span class="docs-legend-dot"[^>]*><\/span>\s*/g, '');
89
- md = md.replace(/<span class="docs-legend-item">[\s\S]*?<\/span>/g, '');
90
-
91
- // ZQueryCollection SVG badge → backtick text
92
- md = md.replace(/<span class="zq-badge">[\s\S]*?<\/span>/g, '`ZQueryCollection`');
93
-
94
- // Status badges (colored pills) → plain text
95
- md = md.replace(/<span\s+style="[^"]*background:[^"]*"[^>]*>([\s\S]*?)<\/span>/g, '$1');
96
-
97
- // Convert <a> links (before code, since links can wrap code elements)
98
- md = md.replace(/<a[^>]*href="([^"]*)"[^>]*>([\s\S]*?)<\/a>/g, '[$2]($1)');
99
-
100
- // Convert <code> (both inline and zq-inline variants)
101
- md = md.replace(/<code[^>]*>([\s\S]*?)<\/code>/g, (_, c) => '`' + unesc(c) + '`');
102
-
103
- // Convert <strong>/<b>
104
- md = md.replace(/<(?:strong|b)>([\s\S]*?)<\/(?:strong|b)>/g, '**$1**');
105
-
106
- // Convert <em>/<i>
107
- md = md.replace(/<(?:em|i)>([\s\S]*?)<\/(?:em|i)>/g, '*$1*');
108
-
109
- // Convert <br>
110
- md = md.replace(/<br\s*\/?>/g, '\n');
111
-
112
- // Strip remaining <span> (keep content)
113
- md = md.replace(/<span[^>]*>([\s\S]*?)<\/span>/g, '$1');
114
-
115
- return md;
116
- }
117
-
118
-
119
- // ---------------------------------------------------------------------------
120
- // HTML table → Markdown table
121
- // ---------------------------------------------------------------------------
122
-
123
- function convertTable(tableHtml) {
124
- let html = tableHtml;
125
-
126
- // Strip legend rows (decorative colored-dot rows)
127
- html = html.replace(/<tr class="docs-table-legend-row">[\s\S]*?<\/tr>/g, '');
128
-
129
- const headers = [];
130
- const rows = [];
131
-
132
- // --- Extract header cells from <thead> ---
133
- const theadMatch = html.match(/<thead>([\s\S]*?)<\/thead>/);
134
- if (theadMatch) {
135
- const thRe = /<th[^>]*>([\s\S]*?)<\/th>/g;
136
- let m;
137
- while ((m = thRe.exec(theadMatch[1])) !== null) {
138
- headers.push(unesc(inlineMd(m[1])).trim());
139
- }
140
- }
141
-
142
- // --- Extract body rows from <tbody> ---
143
- const tbodyMatch = html.match(/<tbody>([\s\S]*?)<\/tbody>/);
144
- if (tbodyMatch) {
145
- const trRe = /<tr>([\s\S]*?)<\/tr>/g;
146
- let m;
147
- while ((m = trRe.exec(tbodyMatch[1])) !== null) {
148
- const cells = [];
149
- const tdRe = /<td>([\s\S]*?)<\/td>/g;
150
- let td;
151
- while ((td = tdRe.exec(m[1])) !== null) {
152
- let cell = unesc(inlineMd(td[1])).trim();
153
- cell = cell.replace(/\n+/g, ' '); // flatten to single line
154
- cell = cell.replace(/\|/g, '\\|'); // escape pipe chars
155
- cells.push(cell);
156
- }
157
- rows.push(cells);
158
- }
159
- }
160
-
161
- if (!headers.length && !rows.length) return '';
162
-
163
- const colCount = Math.max(headers.length, rows[0]?.length || 0);
164
-
165
- // Pad short rows
166
- while (headers.length < colCount) headers.push('');
167
- rows.forEach(r => { while (r.length < colCount) r.push(''); });
168
-
169
- const lines = [];
170
- lines.push('| ' + headers.join(' | ') + ' |');
171
- lines.push('| ' + headers.map(() => '---').join(' | ') + ' |');
172
- rows.forEach(r => lines.push('| ' + r.join(' | ') + ' |'));
173
-
174
- return '\n' + lines.join('\n') + '\n';
175
- }
176
-
177
-
178
- // ---------------------------------------------------------------------------
179
- // Full section HTML → Markdown
180
- // ---------------------------------------------------------------------------
181
-
182
- function htmlToMarkdown(html) {
183
- let md = html;
184
-
185
- // ---- Pass 1: Extract code blocks → placeholders ----
186
- // Matches: optional code-header div, then <pre z-skip><code class="language-xxx">...</code></pre>
187
- const codeBlocks = [];
188
- md = md.replace(
189
- /(?:<div class="code-header">[\s\S]*?<\/div>\s*)?<pre[^>]*>\s*<code class="language-(\w+)">([\s\S]*?)<\/code>\s*<\/pre>/g,
190
- (_, lang, src) => {
191
- const idx = codeBlocks.length;
192
- codeBlocks.push({ lang, src: unesc(src).trim() });
193
- return `\n__CODEBLOCK_${idx}__\n`;
194
- }
195
- );
196
-
197
- // ---- Pass 2: Convert tables ----
198
- md = md.replace(
199
- /<div class="docs-table-wrap">\s*<table[^>]*>([\s\S]*?)<\/table>\s*<\/div>/g,
200
- (_, content) => convertTable(content)
201
- );
202
-
203
- // ---- Pass 3: Convert callouts, tips, warnings ----
204
- md = md.replace(/<div class="docs-callout"[^>]*>([\s\S]*?)<\/div>/g, (_, c) => {
205
- const text = unesc(inlineMd(c)).trim();
206
- const lines = text.split('\n').map(l => '> ' + l);
207
- return '\n' + lines.join('\n') + '\n';
208
- });
209
- md = md.replace(/<div class="docs-tip">([\s\S]*?)<\/div>/g, (_, c) => {
210
- return '\n> **Tip:** ' + unesc(inlineMd(c)).trim() + '\n';
211
- });
212
- md = md.replace(/<div class="docs-warning">([\s\S]*?)<\/div>/g, (_, c) => {
213
- return '\n> **Warning:** ' + unesc(inlineMd(c)).trim() + '\n';
214
- });
215
-
216
- // ---- Pass 4: Strip legend bars & file trees ----
217
- md = md.replace(/<div class="docs-legend-bar">[\s\S]*?<\/div>/g, '');
218
- // File trees (only in non-API sections, but strip just in case)
219
- md = md.replace(/<div class="file-tree">[\s\S]*?<\/div>(?:\s*<\/div>)*/g, '');
220
-
221
- // ---- Pass 5: Convert headings ----
222
- md = md.replace(/<h2[^>]*>([\s\S]*?)<\/h2>/g, (_, c) => '\n## ' + unesc(inlineMd(c)).trim() + '\n');
223
- md = md.replace(/<h3[^>]*>([\s\S]*?)<\/h3>/g, (_, c) => '\n### ' + unesc(inlineMd(c)).trim() + '\n');
224
- md = md.replace(/<h4[^>]*>([\s\S]*?)<\/h4>/g, (_, c) => '\n#### ' + unesc(inlineMd(c)).trim() + '\n');
225
-
226
- // ---- Pass 6: Convert lists ----
227
- md = md.replace(/<ul>([\s\S]*?)<\/ul>/g, (_, content) => {
228
- let result = '\n';
229
- const liRe = /<li>([\s\S]*?)<\/li>/g;
230
- let li;
231
- while ((li = liRe.exec(content)) !== null) {
232
- result += '- ' + unesc(inlineMd(li[1])).trim() + '\n';
233
- }
234
- return result;
235
- });
236
- md = md.replace(/<ol>([\s\S]*?)<\/ol>/g, (_, content) => {
237
- let result = '\n';
238
- let i = 1;
239
- const liRe = /<li>([\s\S]*?)<\/li>/g;
240
- let li;
241
- while ((li = liRe.exec(content)) !== null) {
242
- result += `${i++}. ` + unesc(inlineMd(li[1])).trim() + '\n';
243
- }
244
- return result;
245
- });
246
-
247
- // ---- Pass 7: Convert paragraphs ----
248
- md = md.replace(/<p>([\s\S]*?)<\/p>/g, (_, c) => '\n' + unesc(inlineMd(c)).trim() + '\n');
249
-
250
- // ---- Pass 8: Convert <hr> ----
251
- md = md.replace(/<hr\s*\/?>/g, '\n---\n');
252
-
253
- // ---- Pass 9: Remaining inline conversion ----
254
- md = inlineMd(md);
255
-
256
- // ---- Pass 10: Strip remaining HTML tags ----
257
- md = md.replace(/<[^>]+>/g, '');
258
-
259
- // ---- Pass 11: Decode remaining entities ----
260
- md = unesc(md);
261
-
262
- // ---- Pass 12: Clean up whitespace ----
263
- md = md.replace(/\n{3,}/g, '\n\n');
264
- md = md.trim();
265
-
266
- // ---- Pass 13: Restore code blocks ----
267
- codeBlocks.forEach((block, i) => {
268
- md = md.replace(
269
- `__CODEBLOCK_${i}__`,
270
- '\n```' + block.lang + '\n' + block.src + '\n```\n'
271
- );
272
- });
273
-
274
- // Final whitespace cleanup
275
- md = md.replace(/\n{3,}/g, '\n\n');
276
-
277
- return md;
278
- }
279
-
280
-
281
- // ---------------------------------------------------------------------------
282
- // TOC builder
283
- // ---------------------------------------------------------------------------
284
-
285
- function slugify(text) {
286
- return text
287
- .toLowerCase()
288
- .replace(/`/g, '')
289
- .replace(/\$/g, '')
290
- .replace(/\(\)/g, '')
291
- .replace(/[^a-z0-9\s-]/g, '')
292
- .replace(/\s+/g, '-')
293
- .replace(/-+/g, '-')
294
- .replace(/^-|-$/g, '');
295
- }
296
-
297
- function buildToc(sections) {
298
- const lines = [];
299
- for (const section of sections) {
300
- // Get the section title from the first <h2> in content
301
- const html = section.content();
302
- const h2Match = html.match(/<h2[^>]*>([\s\S]*?)<\/h2>/);
303
- const title = h2Match ? unesc(inlineMd(h2Match[1])).trim() : section.label;
304
- lines.push(`- [${title}](#${slugify(title)})`);
305
- for (const h of section.headings) {
306
- const hText = unesc(h.text);
307
- lines.push(` - [${hText}](#${slugify(hText)})`);
308
- }
309
- }
310
- return lines.join('\n');
311
- }
312
-
313
-
314
- // ---------------------------------------------------------------------------
315
- // ES Module Exports section (generated from index.js)
316
- // ---------------------------------------------------------------------------
317
-
318
- function buildEsmSection(root) {
319
- const indexSrc = fs.readFileSync(path.join(root, 'index.js'), 'utf-8');
320
-
321
- // Extract the export block
322
- const exportMatch = indexSrc.match(/export\s*\{([\s\S]*?)\}/);
323
- if (!exportMatch) return '';
324
-
325
- const exports = exportMatch[1]
326
- .split(',')
327
- .map(e => e.trim())
328
- .filter(Boolean)
329
- .map(e => {
330
- const parts = e.split(/\s+as\s+/);
331
- return parts.length > 1
332
- ? { name: parts[1].trim(), alias: parts[0].trim() }
333
- : { name: parts[0].trim() };
334
- });
335
-
336
- const importNames = exports.map(e => {
337
- return e.alias ? `${e.alias} as ${e.name}` : e.name;
338
- });
339
-
340
- return [
341
- '## ES Module Exports (for npm/bundler usage)',
342
- '',
343
- 'When used as an ES module (not the built bundle), the library provides named exports for every public API:',
344
- '',
345
- '```js',
346
- 'import {',
347
- ' ' + importNames.join(',\n '),
348
- "} from 'zero-query';",
349
- '```',
350
- '',
351
- '> The SSR module has its own entry point: `import { createSSRApp, renderToString } from \'zero-query/ssr\';`',
352
- ].join('\n');
353
- }
354
-
355
-
356
- // ---------------------------------------------------------------------------
357
- // Main
358
- // ---------------------------------------------------------------------------
359
-
360
- async function buildApi() {
361
- const root = process.cwd();
362
- const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf-8'));
363
-
364
- console.log('\n zQuery API.md Generator\n');
365
-
366
- // Import sections individually (ESM dynamic import)
367
- // We avoid importing sections/index.js because it pulls in getting-started.js
368
- // which depends on the website store ($.libSize).
369
- const sectionsDir = path.join(root, 'zquery-website', 'app', 'components', 'docs', 'sections');
370
-
371
- const apiSections = [];
372
- for (const entry of API_SECTIONS) {
373
- const fileUrl = pathToFileURL(path.join(sectionsDir, entry.file)).href;
374
- const mod = await import(fileUrl);
375
- const section = mod[entry.exportName];
376
- if (section) {
377
- apiSections.push(section);
378
- } else {
379
- console.warn(` ⚠ Section "${entry.id}" not found in ${entry.file} (export: ${entry.exportName})`);
380
- }
381
- }
382
-
383
- console.log(` Processing ${apiSections.length} sections...`);
384
-
385
- // --- Header ---
386
- const header = [
387
- '# zQuery (zeroQuery) - Full API Reference',
388
- '',
389
- 'Complete API documentation for every module, method, option, and type in zQuery. All examples assume the global `$` is available via the built `zquery.min.js` bundle. For getting started, project setup, the dev server, and the CLI bundler, see [README.md](README.md).',
390
- '',
391
- '> **Editor Support:** Install the [zQuery for VS Code](https://marketplace.visualstudio.com/items?itemName=zQuery.zquery-vs-code) extension for autocomplete, hover docs, directive support, and 185+ code snippets.',
392
- '',
393
- '---',
394
- ].join('\n');
395
-
396
- // --- Table of Contents ---
397
- const toc = buildToc(apiSections);
398
-
399
- // --- Convert each section ---
400
- const sectionMds = apiSections.map(s => htmlToMarkdown(s.content()));
401
-
402
- // --- ES Module Exports ---
403
- const esm = buildEsmSection(root);
404
-
405
- // --- Assemble ---
406
- const parts = [header, '', '## Table of Contents', '', toc, '', '---'];
407
-
408
- for (const md of sectionMds) {
409
- parts.push('');
410
- parts.push(md);
411
- parts.push('');
412
- parts.push('---');
413
- }
414
-
415
- parts.push('');
416
- parts.push(esm);
417
-
418
- let output = parts.join('\n');
419
- output = output.replace(/\n{4,}/g, '\n\n\n');
420
- output = output.replace(/\n+$/, '\n');
421
-
422
- // --- Write ---
423
- const outPath = path.join(root, 'API.md');
424
- fs.writeFileSync(outPath, output, 'utf-8');
425
-
426
- const lines = output.split('\n').length;
427
- const kb = Math.round(Buffer.from(output).byteLength / 1024);
428
- console.log(` \u2713 API.md generated (${lines} lines, ${kb} KB)`);
429
- console.log();
430
- }
431
-
432
-
433
- // Allow both require() (from CLI) and direct execution
434
- if (require.main === module) {
435
- buildApi().catch(err => {
436
- console.error(' \u2717 Failed to generate API.md:', err.message);
437
- console.error(err.stack);
438
- process.exit(1);
439
- });
440
- }
441
-
442
- module.exports = buildApi;
1
+ /**
2
+ * cli/commands/build-api.js - auto-generate API.md from docs section data
3
+ *
4
+ * Reads the structured docs sections (zquery-website/app/components/docs/sections),
5
+ * converts their HTML content to clean Markdown, and writes API.md.
6
+ *
7
+ * Usage: node cli/commands/build-api.js
8
+ * npm run build:api
9
+ */
10
+
11
+ 'use strict';
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+ const { pathToFileURL } = require('url');
16
+
17
+ // Sections to include in API.md and their order.
18
+ // Maps section ID → { file, export } for direct import (avoids pulling in
19
+ // getting-started.js which depends on the website store / globalThis.$).
20
+ const API_SECTIONS = [
21
+ { id: 'router', file: 'router.js', exportName: 'routerSec' },
22
+ { id: 'components', file: 'components.js', exportName: 'components' },
23
+ { id: 'directives', file: 'directives.js', exportName: 'directives' },
24
+ { id: 'store', file: 'store.js', exportName: 'storeSec' },
25
+ { id: 'http', file: 'http.js', exportName: 'http' },
26
+ { id: 'reactive', file: 'reactive.js', exportName: 'reactive' },
27
+ { id: 'selectors', file: 'selectors.js', exportName: 'selectors' },
28
+ { id: 'utils', file: 'utils.js', exportName: 'utils' },
29
+ { id: 'error-handling', file: 'error-handling.js', exportName: 'errorHandling'},
30
+ { id: 'ssr', file: 'ssr.js', exportName: 'ssr' },
31
+ { id: 'security', file: 'security.js', exportName: 'security' },
32
+ { id: 'environment', file: 'environment.js', exportName: 'environment' },
33
+ { id: 'webrtc', file: 'webrtc.js', exportName: 'webrtc' },
34
+ ];
35
+
36
+
37
+ // ---------------------------------------------------------------------------
38
+ // HTML entity decoding
39
+ // ---------------------------------------------------------------------------
40
+
41
+ function unesc(str) {
42
+ return str
43
+ .replace(/&amp;/g, '&')
44
+ .replace(/&lt;/g, '<')
45
+ .replace(/&gt;/g, '>')
46
+ .replace(/&quot;/g, '"')
47
+ .replace(/&#39;/g, "'")
48
+ .replace(/&mdash;/g, '\u2014')
49
+ .replace(/&ndash;/g, '\u2013')
50
+ .replace(/&rarr;/g, '\u2192')
51
+ .replace(/&larr;/g, '\u2190')
52
+ .replace(/&nbsp;/g, ' ')
53
+ .replace(/&ensp;/g, ' ')
54
+ .replace(/&emsp;/g, ' ')
55
+ .replace(/&hellip;/g, '\u2026')
56
+ .replace(/&times;/g, '\u00D7')
57
+ .replace(/&copy;/g, '\u00A9')
58
+ .replace(/&rsquo;/g, '\u2019')
59
+ .replace(/&lsquo;/g, '\u2018')
60
+ .replace(/&rdquo;/g, '\u201D')
61
+ .replace(/&ldquo;/g, '\u201C')
62
+ .replace(/&bull;/g, '\u2022')
63
+ .replace(/&middot;/g, '\u00B7')
64
+ .replace(/&trade;/g, '\u2122')
65
+ .replace(/&rsaquo;/g, '\u203A')
66
+ .replace(/&lsaquo;/g, '\u2039')
67
+ .replace(/&harr;/g, '\u2194')
68
+ .replace(/&apos;/g, "'")
69
+ .replace(/&hairsp;/g, '\u200A')
70
+ .replace(/&thinsp;/g, '\u2009')
71
+ .replace(/&#(\d+);/g, (_, n) => String.fromCharCode(parseInt(n, 10)));
72
+ }
73
+
74
+
75
+ // ---------------------------------------------------------------------------
76
+ // Inline HTML → Markdown
77
+ // ---------------------------------------------------------------------------
78
+
79
+ /**
80
+ * Convert inline HTML elements to their Markdown equivalents.
81
+ * Handles: code, strong/b, em/i, a, br, spans (dots, badges, etc.)
82
+ */
83
+ function inlineMd(html) {
84
+ if (!html) return '';
85
+ let md = html;
86
+
87
+ // Strip decorative dots (colored circles in table cells)
88
+ md = md.replace(/<span class="docs-dot"[^>]*><\/span>\s*/g, '');
89
+ md = md.replace(/<span class="docs-legend-dot"[^>]*><\/span>\s*/g, '');
90
+ md = md.replace(/<span class="docs-legend-item">[\s\S]*?<\/span>/g, '');
91
+
92
+ // ZQueryCollection SVG badge → backtick text
93
+ md = md.replace(/<span class="zq-badge">[\s\S]*?<\/span>/g, '`ZQueryCollection`');
94
+
95
+ // Status badges (colored pills) → plain text
96
+ md = md.replace(/<span\s+style="[^"]*background:[^"]*"[^>]*>([\s\S]*?)<\/span>/g, '$1');
97
+
98
+ // Convert <a> links (before code, since links can wrap code elements)
99
+ md = md.replace(/<a[^>]*href="([^"]*)"[^>]*>([\s\S]*?)<\/a>/g, '[$2]($1)');
100
+
101
+ // Convert <code> (both inline and zq-inline variants)
102
+ md = md.replace(/<code[^>]*>([\s\S]*?)<\/code>/g, (_, c) => '`' + unesc(c) + '`');
103
+
104
+ // Convert <strong>/<b>
105
+ md = md.replace(/<(?:strong|b)>([\s\S]*?)<\/(?:strong|b)>/g, '**$1**');
106
+
107
+ // Convert <em>/<i>
108
+ md = md.replace(/<(?:em|i)>([\s\S]*?)<\/(?:em|i)>/g, '*$1*');
109
+
110
+ // Convert <br>
111
+ md = md.replace(/<br\s*\/?>/g, '\n');
112
+
113
+ // Strip remaining <span> (keep content)
114
+ md = md.replace(/<span[^>]*>([\s\S]*?)<\/span>/g, '$1');
115
+
116
+ return md;
117
+ }
118
+
119
+
120
+ // ---------------------------------------------------------------------------
121
+ // HTML table → Markdown table
122
+ // ---------------------------------------------------------------------------
123
+
124
+ function convertTable(tableHtml) {
125
+ let html = tableHtml;
126
+
127
+ // Strip legend rows (decorative colored-dot rows)
128
+ html = html.replace(/<tr class="docs-table-legend-row">[\s\S]*?<\/tr>/g, '');
129
+
130
+ const headers = [];
131
+ const rows = [];
132
+
133
+ // --- Extract header cells from <thead> ---
134
+ const theadMatch = html.match(/<thead>([\s\S]*?)<\/thead>/);
135
+ if (theadMatch) {
136
+ const thRe = /<th[^>]*>([\s\S]*?)<\/th>/g;
137
+ let m;
138
+ while ((m = thRe.exec(theadMatch[1])) !== null) {
139
+ headers.push(unesc(inlineMd(m[1])).trim());
140
+ }
141
+ }
142
+
143
+ // --- Extract body rows from <tbody> ---
144
+ const tbodyMatch = html.match(/<tbody>([\s\S]*?)<\/tbody>/);
145
+ if (tbodyMatch) {
146
+ const trRe = /<tr>([\s\S]*?)<\/tr>/g;
147
+ let m;
148
+ while ((m = trRe.exec(tbodyMatch[1])) !== null) {
149
+ const cells = [];
150
+ const tdRe = /<td>([\s\S]*?)<\/td>/g;
151
+ let td;
152
+ while ((td = tdRe.exec(m[1])) !== null) {
153
+ let cell = unesc(inlineMd(td[1])).trim();
154
+ cell = cell.replace(/\n+/g, ' '); // flatten to single line
155
+ cell = cell.replace(/\|/g, '\\|'); // escape pipe chars
156
+ cells.push(cell);
157
+ }
158
+ rows.push(cells);
159
+ }
160
+ }
161
+
162
+ if (!headers.length && !rows.length) return '';
163
+
164
+ const colCount = Math.max(headers.length, rows[0]?.length || 0);
165
+
166
+ // Pad short rows
167
+ while (headers.length < colCount) headers.push('');
168
+ rows.forEach(r => { while (r.length < colCount) r.push(''); });
169
+
170
+ const lines = [];
171
+ lines.push('| ' + headers.join(' | ') + ' |');
172
+ lines.push('| ' + headers.map(() => '---').join(' | ') + ' |');
173
+ rows.forEach(r => lines.push('| ' + r.join(' | ') + ' |'));
174
+
175
+ return '\n' + lines.join('\n') + '\n';
176
+ }
177
+
178
+
179
+ // ---------------------------------------------------------------------------
180
+ // Full section HTML → Markdown
181
+ // ---------------------------------------------------------------------------
182
+
183
+ function htmlToMarkdown(html) {
184
+ let md = html;
185
+
186
+ // ---- Pass 1: Extract code blocks placeholders ----
187
+ // Matches: optional code-header div, then <pre z-skip><code class="language-xxx">...</code></pre>
188
+ const codeBlocks = [];
189
+ md = md.replace(
190
+ /(?:<div class="code-header">[\s\S]*?<\/div>\s*)?<pre[^>]*>\s*<code class="language-(\w+)">([\s\S]*?)<\/code>\s*<\/pre>/g,
191
+ (_, lang, src) => {
192
+ const idx = codeBlocks.length;
193
+ codeBlocks.push({ lang, src: unesc(src).trim() });
194
+ return `\n__CODEBLOCK_${idx}__\n`;
195
+ }
196
+ );
197
+
198
+ // ---- Pass 2: Convert tables ----
199
+ md = md.replace(
200
+ /<div class="docs-table-wrap">\s*<table[^>]*>([\s\S]*?)<\/table>\s*<\/div>/g,
201
+ (_, content) => convertTable(content)
202
+ );
203
+
204
+ // ---- Pass 3: Convert callouts, tips, warnings ----
205
+ md = md.replace(/<div class="docs-callout"[^>]*>([\s\S]*?)<\/div>/g, (_, c) => {
206
+ const text = unesc(inlineMd(c)).trim();
207
+ const lines = text.split('\n').map(l => '> ' + l);
208
+ return '\n' + lines.join('\n') + '\n';
209
+ });
210
+ md = md.replace(/<div class="docs-tip">([\s\S]*?)<\/div>/g, (_, c) => {
211
+ return '\n> **Tip:** ' + unesc(inlineMd(c)).trim() + '\n';
212
+ });
213
+ md = md.replace(/<div class="docs-warning">([\s\S]*?)<\/div>/g, (_, c) => {
214
+ return '\n> **Warning:** ' + unesc(inlineMd(c)).trim() + '\n';
215
+ });
216
+
217
+ // ---- Pass 4: Strip legend bars & file trees ----
218
+ md = md.replace(/<div class="docs-legend-bar">[\s\S]*?<\/div>/g, '');
219
+ // File trees (only in non-API sections, but strip just in case)
220
+ md = md.replace(/<div class="file-tree">[\s\S]*?<\/div>(?:\s*<\/div>)*/g, '');
221
+
222
+ // ---- Pass 5: Convert headings ----
223
+ md = md.replace(/<h2[^>]*>([\s\S]*?)<\/h2>/g, (_, c) => '\n## ' + unesc(inlineMd(c)).trim() + '\n');
224
+ md = md.replace(/<h3[^>]*>([\s\S]*?)<\/h3>/g, (_, c) => '\n### ' + unesc(inlineMd(c)).trim() + '\n');
225
+ md = md.replace(/<h4[^>]*>([\s\S]*?)<\/h4>/g, (_, c) => '\n#### ' + unesc(inlineMd(c)).trim() + '\n');
226
+
227
+ // ---- Pass 6: Convert lists ----
228
+ md = md.replace(/<ul>([\s\S]*?)<\/ul>/g, (_, content) => {
229
+ let result = '\n';
230
+ const liRe = /<li>([\s\S]*?)<\/li>/g;
231
+ let li;
232
+ while ((li = liRe.exec(content)) !== null) {
233
+ result += '- ' + unesc(inlineMd(li[1])).trim() + '\n';
234
+ }
235
+ return result;
236
+ });
237
+ md = md.replace(/<ol>([\s\S]*?)<\/ol>/g, (_, content) => {
238
+ let result = '\n';
239
+ let i = 1;
240
+ const liRe = /<li>([\s\S]*?)<\/li>/g;
241
+ let li;
242
+ while ((li = liRe.exec(content)) !== null) {
243
+ result += `${i++}. ` + unesc(inlineMd(li[1])).trim() + '\n';
244
+ }
245
+ return result;
246
+ });
247
+
248
+ // ---- Pass 7: Convert paragraphs ----
249
+ md = md.replace(/<p>([\s\S]*?)<\/p>/g, (_, c) => '\n' + unesc(inlineMd(c)).trim() + '\n');
250
+
251
+ // ---- Pass 8: Convert <hr> ----
252
+ md = md.replace(/<hr\s*\/?>/g, '\n---\n');
253
+
254
+ // ---- Pass 9: Remaining inline conversion ----
255
+ md = inlineMd(md);
256
+
257
+ // ---- Pass 10: Strip remaining HTML tags ----
258
+ md = md.replace(/<[^>]+>/g, '');
259
+
260
+ // ---- Pass 11: Decode remaining entities ----
261
+ md = unesc(md);
262
+
263
+ // ---- Pass 12: Clean up whitespace ----
264
+ md = md.replace(/\n{3,}/g, '\n\n');
265
+ md = md.trim();
266
+
267
+ // ---- Pass 13: Restore code blocks ----
268
+ codeBlocks.forEach((block, i) => {
269
+ md = md.replace(
270
+ `__CODEBLOCK_${i}__`,
271
+ '\n```' + block.lang + '\n' + block.src + '\n```\n'
272
+ );
273
+ });
274
+
275
+ // Final whitespace cleanup
276
+ md = md.replace(/\n{3,}/g, '\n\n');
277
+
278
+ return md;
279
+ }
280
+
281
+
282
+ // ---------------------------------------------------------------------------
283
+ // TOC builder
284
+ // ---------------------------------------------------------------------------
285
+
286
+ function slugify(text) {
287
+ return text
288
+ .toLowerCase()
289
+ .replace(/`/g, '')
290
+ .replace(/\$/g, '')
291
+ .replace(/\(\)/g, '')
292
+ .replace(/[^a-z0-9\s-]/g, '')
293
+ .replace(/\s+/g, '-')
294
+ .replace(/-+/g, '-')
295
+ .replace(/^-|-$/g, '');
296
+ }
297
+
298
+ function buildToc(sections) {
299
+ const lines = [];
300
+ for (const section of sections) {
301
+ // Get the section title from the first <h2> in content
302
+ const html = section.content();
303
+ const h2Match = html.match(/<h2[^>]*>([\s\S]*?)<\/h2>/);
304
+ const title = h2Match ? unesc(inlineMd(h2Match[1])).trim() : section.label;
305
+ lines.push(`- [${title}](#${slugify(title)})`);
306
+ for (const h of section.headings) {
307
+ const hText = unesc(h.text);
308
+ lines.push(` - [${hText}](#${slugify(hText)})`);
309
+ }
310
+ }
311
+ return lines.join('\n');
312
+ }
313
+
314
+
315
+ // ---------------------------------------------------------------------------
316
+ // ES Module Exports section (generated from index.js)
317
+ // ---------------------------------------------------------------------------
318
+
319
+ function buildEsmSection(root) {
320
+ const indexSrc = fs.readFileSync(path.join(root, 'index.js'), 'utf-8');
321
+
322
+ // Extract the export block
323
+ const exportMatch = indexSrc.match(/export\s*\{([\s\S]*?)\}/);
324
+ if (!exportMatch) return '';
325
+
326
+ const exports = exportMatch[1]
327
+ .split(',')
328
+ .map(e => e.trim())
329
+ .filter(Boolean)
330
+ .map(e => {
331
+ const parts = e.split(/\s+as\s+/);
332
+ return parts.length > 1
333
+ ? { name: parts[1].trim(), alias: parts[0].trim() }
334
+ : { name: parts[0].trim() };
335
+ });
336
+
337
+ const importNames = exports.map(e => {
338
+ return e.alias ? `${e.alias} as ${e.name}` : e.name;
339
+ });
340
+
341
+ return [
342
+ '## ES Module Exports (for npm/bundler usage)',
343
+ '',
344
+ 'When used as an ES module (not the built bundle), the library provides named exports for every public API:',
345
+ '',
346
+ '```js',
347
+ 'import {',
348
+ ' ' + importNames.join(',\n '),
349
+ "} from 'zero-query';",
350
+ '```',
351
+ '',
352
+ '> The SSR module has its own entry point: `import { createSSRApp, renderToString } from \'zero-query/ssr\';`',
353
+ ].join('\n');
354
+ }
355
+
356
+
357
+ // ---------------------------------------------------------------------------
358
+ // Main
359
+ // ---------------------------------------------------------------------------
360
+
361
+ async function buildApi() {
362
+ const root = process.cwd();
363
+ const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf-8'));
364
+
365
+ console.log('\n zQuery API.md Generator\n');
366
+
367
+ // Import sections individually (ESM dynamic import)
368
+ // We avoid importing sections/index.js because it pulls in getting-started.js
369
+ // which depends on the website store ($.libSize).
370
+ const sectionsDir = path.join(root, 'zquery-website', 'app', 'components', 'docs', 'sections');
371
+
372
+ const apiSections = [];
373
+ for (const entry of API_SECTIONS) {
374
+ const fileUrl = pathToFileURL(path.join(sectionsDir, entry.file)).href;
375
+ const mod = await import(fileUrl);
376
+ const section = mod[entry.exportName];
377
+ if (section) {
378
+ apiSections.push(section);
379
+ } else {
380
+ console.warn(` ⚠ Section "${entry.id}" not found in ${entry.file} (export: ${entry.exportName})`);
381
+ }
382
+ }
383
+
384
+ console.log(` Processing ${apiSections.length} sections...`);
385
+
386
+ // --- Header ---
387
+ const header = [
388
+ '# zQuery (zeroQuery) - Full API Reference',
389
+ '',
390
+ 'Complete API documentation for every module, method, option, and type in zQuery. All examples assume the global `$` is available via the built `zquery.min.js` bundle. For getting started, project setup, the dev server, and the CLI bundler, see [README.md](README.md).',
391
+ '',
392
+ '> **Editor Support:** Install the [zQuery for VS Code](https://marketplace.visualstudio.com/items?itemName=zQuery.zquery-vs-code) extension for autocomplete, hover docs, directive support, and 185+ code snippets.',
393
+ '',
394
+ '---',
395
+ ].join('\n');
396
+
397
+ // --- Table of Contents ---
398
+ const toc = buildToc(apiSections);
399
+
400
+ // --- Convert each section ---
401
+ const sectionMds = apiSections.map(s => htmlToMarkdown(s.content()));
402
+
403
+ // --- ES Module Exports ---
404
+ const esm = buildEsmSection(root);
405
+
406
+ // --- Assemble ---
407
+ const parts = [header, '', '## Table of Contents', '', toc, '', '---'];
408
+
409
+ for (const md of sectionMds) {
410
+ parts.push('');
411
+ parts.push(md);
412
+ parts.push('');
413
+ parts.push('---');
414
+ }
415
+
416
+ parts.push('');
417
+ parts.push(esm);
418
+
419
+ let output = parts.join('\n');
420
+ output = output.replace(/\n{4,}/g, '\n\n\n');
421
+ output = output.replace(/\n+$/, '\n');
422
+
423
+ // --- Write ---
424
+ const outPath = path.join(root, 'API.md');
425
+ fs.writeFileSync(outPath, output, 'utf-8');
426
+
427
+ const lines = output.split('\n').length;
428
+ const kb = Math.round(Buffer.from(output).byteLength / 1024);
429
+ console.log(` \u2713 API.md generated (${lines} lines, ${kb} KB)`);
430
+ console.log();
431
+ }
432
+
433
+
434
+ // Allow both require() (from CLI) and direct execution
435
+ if (require.main === module) {
436
+ buildApi().catch(err => {
437
+ console.error(' \u2717 Failed to generate API.md:', err.message);
438
+ console.error(err.stack);
439
+ process.exit(1);
440
+ });
441
+ }
442
+
443
+ module.exports = buildApi;