zero-query 1.0.9 → 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 -0
  5. package/cli/commands/build.js +254 -216
  6. package/cli/commands/bundle.js +1228 -1183
  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 -167
  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 +7264 -0
  79. package/dist/zquery.dist.zip +0 -0
  80. package/dist/zquery.js +10313 -6252
  81. package/dist/zquery.min.js +8 -601
  82. package/index.d.ts +570 -365
  83. package/index.js +311 -232
  84. package/package.json +76 -69
  85. package/src/component.js +1709 -1454
  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 -254
  93. package/src/router.js +843 -773
  94. package/src/ssr.js +418 -418
  95. package/src/store.js +318 -272
  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 -1023
  115. package/tests/compare.test.js +497 -0
  116. package/tests/component.test.js +3969 -3938
  117. package/tests/core.test.js +1910 -1910
  118. package/tests/dev-server.test.js +489 -0
  119. package/tests/diff.test.js +1416 -1416
  120. package/tests/docs.test.js +1664 -0
  121. package/tests/electron-features.test.js +864 -0
  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 -145
  153. package/types/utils.d.ts +245 -245
  154. package/types/webrtc.d.ts +653 -0
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Tony Wiedman "molex"
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tony Wiedman "molex"
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -6,6 +6,7 @@
6
6
 
7
7
  <p align="center">
8
8
 
9
+ [![CI](https://github.com/tonywied17/zero-query/actions/workflows/ci.yml/badge.svg)](https://github.com/tonywied17/zero-query/actions/workflows/ci.yml)
9
10
  [![npm version](https://img.shields.io/npm/v/zero-query.svg)](https://www.npmjs.com/package/zero-query)
10
11
  [![npm downloads](https://img.shields.io/npm/dm/zero-query.svg)](https://www.npmjs.com/package/zero-query)
11
12
  [![GitHub](https://img.shields.io/badge/GitHub-zero--query-blue.svg)](https://github.com/tonywied17/zero-query)
@@ -32,6 +33,7 @@
32
33
  | **Security** | XSS-safe template expressions (`{{}}` auto-escaping), sandboxed expression evaluator (blocks `window`, `Function`, `eval`, `RegExp`, `Error`, prototype chains), prototype pollution prevention in `deepMerge`/`setPath`, `z-link` protocol validation, SSR error sanitization, `renderShell()` metadata injection hardening (script-tag breakout prevention, ReDoS-safe OG keys, safe `.replace()` patterns) |
33
34
  | **Dev Tools** | CLI dev server with live-reload, CSS hot-swap, full-screen error overlay, floating toolbar, dark-themed inspector panel (Router view, DOM tree, network log, component viewer, performance dashboard), fetch interceptor, render instrumentation, CLI bundler for single-file production builds |
34
35
  | **SSR** | Server-side rendering to HTML strings in Node.js - `createSSRApp()`, `renderToString()`, `renderPage()` with SEO/Open Graph support, `renderShell()` for injecting SSR into custom HTML shells, `renderBatch()` for parallel rendering, fragment mode, hydration markers, graceful error handling, `escapeHtml()` utility |
36
+ | **WebRTC** | `SignalingClient` + `Peer` (perfect negotiation) + multi-peer `Room` speaking the `@zero-server/webrtc` wire protocol &mdash; `$.webrtc.join(url, opts)`, reactive `useRoom` / `usePeer` / `useTracks` / `useDataChannel` / `useConnectionQuality` composables, `z-stream` directive for binding remote `MediaStream`s to `<video>` / `<audio>`, SDP + ICE parsers, exponential-backoff reconnect, coalesced ICE trickle, TURN credential fetcher + auto-refresher, SFrame E2EE worker, SFU peer-dep adapters for `mediasoup-client` and `livekit-client`, join-token decoder, `getStats()` sampler with quality classification, typed error family (`WebRtcError`, `SignalingError`, `IceError`, `SdpError`, `TurnError`, `E2eeError`); scaffold a one-page demo with `npx zero-query create my-app --webrtc-demo` |
35
37
 
36
38
  ---
37
39
 
package/cli/args.js CHANGED
@@ -1,33 +1,33 @@
1
- /**
2
- * cli/args.js - CLI argument parsing helpers
3
- *
4
- * Provides the raw args array and two helper functions for reading
5
- * named flags (--verbose, -v) and valued options (--port 8080, -p 8080).
6
- */
7
-
8
- 'use strict';
9
-
10
- const args = process.argv.slice(2);
11
-
12
- /**
13
- * Check whether a boolean flag is present.
14
- * flag('verbose', 'v') → true if --verbose or -v appears
15
- */
16
- function flag(name, short) {
17
- const i = args.indexOf(`--${name}`);
18
- const j = short ? args.indexOf(`-${short}`) : -1;
19
- return i !== -1 || j !== -1;
20
- }
21
-
22
- /**
23
- * Read a string-valued option.
24
- * option('port', 'p', '3100') → the value after --port / -p, or '3100'
25
- */
26
- function option(name, short, fallback) {
27
- let i = args.indexOf(`--${name}`);
28
- if (i === -1 && short) i = args.indexOf(`-${short}`);
29
- if (i !== -1 && i + 1 < args.length) return args[i + 1];
30
- return fallback;
31
- }
32
-
33
- module.exports = { args, flag, option };
1
+ /**
2
+ * cli/args.js - CLI argument parsing helpers
3
+ *
4
+ * Provides the raw args array and two helper functions for reading
5
+ * named flags (--verbose, -v) and valued options (--port 8080, -p 8080).
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ const args = process.argv.slice(2);
11
+
12
+ /**
13
+ * Check whether a boolean flag is present.
14
+ * flag('verbose', 'v') → true if --verbose or -v appears
15
+ */
16
+ function flag(name, short) {
17
+ const i = args.indexOf(`--${name}`);
18
+ const j = short ? args.indexOf(`-${short}`) : -1;
19
+ return i !== -1 || j !== -1;
20
+ }
21
+
22
+ /**
23
+ * Read a string-valued option.
24
+ * option('port', 'p', '3100') → the value after --port / -p, or '3100'
25
+ */
26
+ function option(name, short, fallback) {
27
+ let i = args.indexOf(`--${name}`);
28
+ if (i === -1 && short) i = args.indexOf(`-${short}`);
29
+ if (i !== -1 && i + 1 < args.length) return args[i + 1];
30
+ return fallback;
31
+ }
32
+
33
+ module.exports = { args, flag, option };
@@ -0,0 +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
+ { 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;