zero-query 0.9.8 → 1.0.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 (99) hide show
  1. package/README.md +55 -31
  2. package/cli/args.js +1 -1
  3. package/cli/commands/build.js +2 -2
  4. package/cli/commands/bundle.js +15 -15
  5. package/cli/commands/create.js +41 -7
  6. package/cli/commands/dev/devtools/index.js +1 -1
  7. package/cli/commands/dev/devtools/js/core.js +14 -14
  8. package/cli/commands/dev/devtools/js/elements.js +4 -4
  9. package/cli/commands/dev/devtools/js/stats.js +1 -1
  10. package/cli/commands/dev/devtools/styles.css +2 -2
  11. package/cli/commands/dev/index.js +2 -2
  12. package/cli/commands/dev/logger.js +1 -1
  13. package/cli/commands/dev/overlay.js +21 -14
  14. package/cli/commands/dev/server.js +5 -5
  15. package/cli/commands/dev/validator.js +7 -7
  16. package/cli/commands/dev/watcher.js +6 -6
  17. package/cli/help.js +4 -2
  18. package/cli/index.js +2 -2
  19. package/cli/scaffold/default/app/app.js +17 -18
  20. package/cli/scaffold/default/app/components/about.js +9 -9
  21. package/cli/scaffold/default/app/components/api-demo.js +6 -6
  22. package/cli/scaffold/default/app/components/contact-card.js +4 -4
  23. package/cli/scaffold/default/app/components/contacts/contacts.css +2 -2
  24. package/cli/scaffold/default/app/components/contacts/contacts.html +3 -3
  25. package/cli/scaffold/default/app/components/contacts/contacts.js +11 -11
  26. package/cli/scaffold/default/app/components/counter.js +8 -8
  27. package/cli/scaffold/default/app/components/home.js +13 -13
  28. package/cli/scaffold/default/app/components/not-found.js +1 -1
  29. package/cli/scaffold/default/app/components/playground/playground.css +1 -1
  30. package/cli/scaffold/default/app/components/playground/playground.html +11 -11
  31. package/cli/scaffold/default/app/components/playground/playground.js +11 -11
  32. package/cli/scaffold/default/app/components/todos.js +8 -8
  33. package/cli/scaffold/default/app/components/toolkit/toolkit.css +1 -1
  34. package/cli/scaffold/default/app/components/toolkit/toolkit.html +4 -4
  35. package/cli/scaffold/default/app/components/toolkit/toolkit.js +7 -7
  36. package/cli/scaffold/default/app/routes.js +1 -1
  37. package/cli/scaffold/default/app/store.js +1 -1
  38. package/cli/scaffold/default/global.css +2 -2
  39. package/cli/scaffold/default/index.html +2 -2
  40. package/cli/scaffold/minimal/app/app.js +6 -7
  41. package/cli/scaffold/minimal/app/components/about.js +5 -5
  42. package/cli/scaffold/minimal/app/components/counter.js +6 -6
  43. package/cli/scaffold/minimal/app/components/home.js +8 -8
  44. package/cli/scaffold/minimal/app/components/not-found.js +1 -1
  45. package/cli/scaffold/minimal/app/routes.js +1 -1
  46. package/cli/scaffold/minimal/app/store.js +1 -1
  47. package/cli/scaffold/minimal/global.css +2 -2
  48. package/cli/scaffold/minimal/index.html +1 -1
  49. package/cli/scaffold/ssr/app/app.js +29 -0
  50. package/cli/scaffold/ssr/app/components/about.js +28 -0
  51. package/cli/scaffold/ssr/app/components/home.js +37 -0
  52. package/cli/scaffold/ssr/app/components/not-found.js +15 -0
  53. package/cli/scaffold/ssr/app/routes.js +6 -0
  54. package/cli/scaffold/ssr/global.css +113 -0
  55. package/cli/scaffold/ssr/index.html +31 -0
  56. package/cli/scaffold/ssr/package.json +8 -0
  57. package/cli/scaffold/ssr/server/index.js +118 -0
  58. package/cli/utils.js +6 -6
  59. package/dist/zquery.dist.zip +0 -0
  60. package/dist/zquery.js +565 -228
  61. package/dist/zquery.min.js +2 -2
  62. package/index.d.ts +25 -12
  63. package/index.js +11 -7
  64. package/package.json +9 -3
  65. package/src/component.js +64 -63
  66. package/src/core.js +15 -15
  67. package/src/diff.js +38 -38
  68. package/src/errors.js +72 -18
  69. package/src/expression.js +15 -17
  70. package/src/http.js +4 -4
  71. package/src/package.json +1 -0
  72. package/src/reactive.js +75 -9
  73. package/src/router.js +104 -24
  74. package/src/ssr.js +133 -39
  75. package/src/store.js +103 -21
  76. package/src/utils.js +64 -12
  77. package/tests/audit.test.js +143 -15
  78. package/tests/cli.test.js +20 -20
  79. package/tests/component.test.js +121 -121
  80. package/tests/core.test.js +56 -56
  81. package/tests/diff.test.js +42 -42
  82. package/tests/errors.test.js +425 -147
  83. package/tests/expression.test.js +58 -53
  84. package/tests/http.test.js +20 -20
  85. package/tests/reactive.test.js +185 -24
  86. package/tests/router.test.js +501 -74
  87. package/tests/ssr.test.js +444 -10
  88. package/tests/store.test.js +264 -23
  89. package/tests/utils.test.js +163 -26
  90. package/types/collection.d.ts +2 -2
  91. package/types/component.d.ts +5 -5
  92. package/types/errors.d.ts +36 -4
  93. package/types/http.d.ts +3 -3
  94. package/types/misc.d.ts +9 -9
  95. package/types/reactive.d.ts +25 -3
  96. package/types/router.d.ts +10 -6
  97. package/types/ssr.d.ts +22 -2
  98. package/types/store.d.ts +40 -5
  99. package/types/utils.d.ts +1 -1
package/README.md CHANGED
@@ -15,21 +15,23 @@
15
15
 
16
16
  </p>
17
17
 
18
- > **Lightweight, zero-dependency frontend library that combines jQuery-style DOM manipulation with a modern reactive component system, SPA router, global state management, HTTP client, and utility toolkit all in a single ~100 KB minified browser bundle. Works out of the box with ES modules. An optional CLI bundler is available for single-file production builds.**
18
+ > **Lightweight, zero-dependency frontend library that combines jQuery-style DOM manipulation with a modern reactive component system, SPA router, global state management, HTTP client, and utility toolkit - all in a single ~91 KB minified browser bundle. Works out of the box with ES modules. An optional CLI bundler is available for single-file production builds.**
19
19
 
20
20
  ## Features
21
21
 
22
22
  | Module | Highlights |
23
23
  | --- | --- |
24
- | **Components** | Reactive state, template literals, `@event` delegation (22 modifiers key filters, system keys, `.outside`, timing, and more), `z-model` two-way binding (with `z-trim`, `z-number`, `z-lazy`, `z-debounce`, `z-uppercase`, `z-lowercase`), computed properties, watch callbacks, slot-based content projection, directives (`z-if`/`z-else-if`/`z-else`, `z-for`, `z-show`, `z-bind`/`:attr`, `z-class`, `z-style`, `z-text`, `z-html`, `z-ref`, `z-cloak`, `z-pre`, `z-key`, `z-skip`), DOM morphing engine with LIS-based keyed reconciliation (no innerHTML rebuild), CSP-safe expression evaluation with AST caching, scoped styles, external templates (`templateUrl` / `styleUrl`), lifecycle hooks, auto-injected base styles |
25
- | **Router** | History & hash mode, route params (`:id`), wildcards, guards (`beforeEach`/`afterEach`), lazy loading, `z-link` navigation, `z-to-top` scroll modifier (`instant`/`smooth`), sub-route history substates (`pushSubstate`/`onSubstate`) |
26
- | **Directives** | `z-if`, `z-for`, `z-model`, `z-show`, `z-bind`, `z-class`, `z-style`, `z-text`, `z-html`, `z-ref`, `z-cloak`, `z-pre`, `z-key`, `z-skip` &mdash; 17 built-in template directives |
27
- | **Reactive** | Deep proxy reactivity, Signals (`.value`, `.peek()`), computed values, effects (auto-tracked with dispose) |
28
- | **Store** | Reactive global state, named actions, computed getters, middleware, subscriptions, action history, snapshots |
24
+ | **Components** | Reactive state, template literals, `@event` delegation (22 modifiers - key filters, system keys, `.outside`, timing, and more), `z-model` two-way binding (with `z-trim`, `z-number`, `z-lazy`, `z-debounce`, `z-uppercase`, `z-lowercase`), computed properties, watch callbacks, slot-based content projection, directives (`z-if`/`z-else-if`/`z-else`, `z-for`, `z-show`, `z-bind`/`:attr`, `z-class`, `z-style`, `z-text`, `z-html`, `z-ref`, `z-cloak`, `z-pre`, `z-key`, `z-skip`), DOM morphing engine with LIS-based keyed reconciliation (no innerHTML rebuild), CSP-safe expression evaluation with AST caching, scoped styles, external templates (`templateUrl` / `styleUrl`), lifecycle hooks, auto-injected base styles |
25
+ | **Router** | History & hash mode, route params (`:id`), wildcards, guards (`beforeEach`/`afterEach`), lazy loading, `z-link` navigation with `z-link-params`, `z-to-top` scroll modifier (`instant`/`smooth`), `z-active-route` active-link class directive, `<z-outlet>` declarative mount point, sub-route history substates (`pushSubstate`/`onSubstate`) |
26
+ | **Directives** | `z-if`, `z-else-if`, `z-else`, `z-for`, `z-model`, `z-show`, `z-bind`/`:attr`, `z-class`, `z-style`, `z-text`, `z-html`, `z-ref`, `z-cloak`, `z-pre`, `z-key`, `z-skip`, `@event`/`z-on` &mdash; 17 built-in template directives |
27
+ | **Reactive** | Deep proxy reactivity, Signals (`.value`, `.peek()`), computed values, effects (auto-tracked with dispose), `batch()` for deferred notifications, `untracked()` for dependency-free reads |
28
+ | **Store** | Reactive global state, named actions, computed getters, middleware, subscriptions, `batch()` grouped mutations, `checkpoint()`/`undo()`/`redo()` with configurable stack, action history, snapshots |
29
29
  | **Selectors & DOM** | jQuery-like chainable selectors, traversal, DOM manipulation, events, animation |
30
30
  | **HTTP** | Fetch wrapper with auto-JSON, interceptors (with unsubscribe & clear), HEAD requests, parallel requests (`http.all`), config inspection (`getConfig`), timeout/abort, base URL |
31
- | **Utils** | debounce, throttle, pipe, once, sleep, memoize, escapeHtml, stripHtml, uuid, capitalize, truncate, range, chunk, groupBy, unique, pick, omit, getPath/setPath, isEmpty, clamp, retry, timeout, deepClone, deepMerge, storage/session wrappers, event bus |
31
+ | **Utils** | debounce, throttle, pipe, once, sleep, memoize (LRU), escapeHtml, stripHtml, uuid, capitalize, truncate, range, chunk, groupBy, unique, pick, omit, getPath/setPath, isEmpty, clamp, retry, timeout, deepClone (enhanced fallback), deepMerge (prototype-pollution safe), storage/session wrappers, event bus |
32
+ | **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 |
32
33
  | **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
+ | **SSR** | Server-side rendering to HTML strings in Node.js - `createSSRApp()`, `renderToString()`, `renderPage()` with SEO/Open Graph support, `renderBatch()` for parallel rendering, fragment mode, hydration markers, graceful error handling, `escapeHtml()` utility |
33
35
 
34
36
  ---
35
37
 
@@ -37,7 +39,7 @@
37
39
 
38
40
  ### Recommended: CLI Dev Server
39
41
 
40
- The fastest way to develop with zQuery is via the built-in **CLI dev server** with **live-reload**. It serves your ES modules as-is and automatically resolves the library no manual downloads required.
42
+ The fastest way to develop with zQuery is via the built-in **CLI dev server** with **live-reload**. It serves your ES modules as-is and automatically resolves the library - no manual downloads required.
41
43
 
42
44
  ```bash
43
45
  # Install (per-project or globally)
@@ -53,29 +55,29 @@ npx zquery dev my-app
53
55
 
54
56
  > **Tip:** Stay in the project root (where `node_modules` lives) instead of `cd`-ing into `my-app`. This keeps `index.d.ts` accessible to your IDE for full type/intellisense support.
55
57
 
56
- The `create` command generates a ready-to-run project with a sidebar layout, router, multiple components (including folder components with external templates and styles), and responsive styles. Use `--minimal` (or `-m`) to scaffold a lightweight 3-page starter instead. The dev server watches for file changes, hot-swaps CSS in-place, full-reloads on other changes, and handles SPA fallback routing.
58
+ The `create` command generates a ready-to-run project with a sidebar layout, router, multiple components (including folder components with external templates and styles), and responsive styles. Use `--minimal` (or `-m`) to scaffold a lightweight 3-page starter instead. Use `--ssr` (or `-s`) to scaffold a project with a Node.js server-side rendering example. The dev server watches for file changes, hot-swaps CSS in-place, full-reloads on other changes, and handles SPA fallback routing.
57
59
 
58
60
  #### Error Overlay
59
61
 
60
- The dev server includes a **full-screen error overlay** that surfaces errors directly in the browser similar to Vite or Angular:
62
+ The dev server includes a **full-screen error overlay** that surfaces errors directly in the browser - similar to Vite or Angular:
61
63
 
62
- - **Syntax errors** JS files are validated on every save *before* the reload is triggered. If a syntax error is found the page stays intact and a dark overlay appears with the error message, file path, line:column, and a code frame pointing to the exact location.
63
- - **Runtime errors** uncaught exceptions and unhandled promise rejections are captured and displayed in the same overlay with a cleaned-up stack trace.
64
+ - **Syntax errors** - JS files are validated on every save *before* the reload is triggered. If a syntax error is found the page stays intact and a dark overlay appears with the error message, file path, line:column, and a code frame pointing to the exact location.
65
+ - **Runtime errors** - uncaught exceptions and unhandled promise rejections are captured and displayed in the same overlay with a cleaned-up stack trace.
64
66
  - The overlay **auto-clears** when you fix the error and save. Press `Esc` or click `×` to dismiss manually.
65
67
 
66
68
  #### Floating Toolbar & Inspector
67
69
 
68
- A compact expandable toolbar appears in the bottom-right corner. In its **collapsed** state it shows live render and request counters. Click the chevron to **expand** and reveal the route indicator (color-coded by the last navigation event navigate, pop, replace, hashchange, substate), registered component count, and DOM element count. Click any stat to open a **dark-themed DevTools inspector** as a popup or visit `http://localhost:<port>/_devtools` for a standalone split-view panel with five tabs: **Router** (live route state, guards, history timeline), **Components** (live state cards), **Performance** (render timeline with timing metrics), **Network** (fetch log with JSON viewer), and **Elements** (live DOM tree with component badges, source viewer, expand/collapse).
70
+ A compact expandable toolbar appears in the bottom-right corner. In its **collapsed** state it shows live render and request counters. Click the chevron to **expand** and reveal the route indicator (color-coded by the last navigation event - navigate, pop, replace, hashchange, substate), registered component count, and DOM element count. Click any stat to open a **dark-themed DevTools inspector** as a popup - or visit `http://localhost:<port>/_devtools` for a standalone split-view panel with five tabs: **Router** (live route state, guards, history timeline), **Components** (live state cards), **Performance** (render timeline with timing metrics), **Network** (fetch log with JSON viewer), and **Elements** (live DOM tree with component badges, source viewer, expand/collapse).
69
71
 
70
72
  ### Alternative: Manual Setup (No npm)
71
73
 
72
- If you prefer **zero tooling**, download `dist/zquery.min.js` from the [dist/ folder](https://github.com/tonywied17/zero-query/tree/main/dist) and drop it into your project root or `assets/scripts/`. Then open `index.html` directly in a browser no Node.js required.
74
+ If you prefer **zero tooling**, download `dist/zquery.min.js` from the [dist/ folder](https://github.com/tonywied17/zero-query/tree/main/dist) and drop it into your project root or `assets/scripts/`. Then open `index.html` directly in a browser - no Node.js required.
73
75
 
74
76
  ```bash
75
77
  git clone https://github.com/tonywied17/zero-query.git
76
78
  cd zero-query
77
79
  npx zquery build
78
- # → dist/zquery.min.js (~100 KB)
80
+ # → dist/zquery.min.js (~91 KB)
79
81
  ```
80
82
 
81
83
  ### Include in HTML
@@ -109,7 +111,7 @@ import './components/about.js';
109
111
  import './components/contacts/contacts.js';
110
112
  import { routes } from './routes.js';
111
113
 
112
- $.router({ el: '#app', routes, fallback: 'not-found' });
114
+ $.router({ routes, fallback: 'not-found' });
113
115
  ```
114
116
 
115
117
  ### Define a Component
@@ -129,7 +131,7 @@ $.component('home-page', {
129
131
  });
130
132
  ```
131
133
 
132
- That's it a fully working SPA with the dev server's live-reload.
134
+ That's it - a fully working SPA with the dev server's live-reload.
133
135
 
134
136
  ---
135
137
 
@@ -186,10 +188,30 @@ my-app/ ← minimal scaffold (npx zquery create my-app
186
188
  assets/
187
189
  ```
188
190
 
191
+ Use `--ssr` for a project with server-side rendering:
192
+
193
+ ```
194
+ my-app/ ← SSR scaffold (npx zquery create my-app --ssr)
195
+ index.html ← client HTML shell
196
+ global.css
197
+ app/
198
+ app.js ← client entry - registers shared components
199
+ routes.js ← shared route definitions
200
+ components/
201
+ home.js ← shared component (SSR + client)
202
+ about.js
203
+ not-found.js
204
+ server/
205
+ index.js ← SSR HTTP server
206
+ assets/
207
+ ```
208
+
209
+ Components in `app/components/` export plain definition objects - the client registers them with `$.component()`, the server with `app.component()`. The `--ssr` flag handles everything automatically - installs dependencies, starts the server at `http://localhost:3000`, and opens the browser.
210
+
189
211
  - One component per file inside `components/`.
190
212
  - Names **must contain a hyphen** (Web Component convention): `home-page`, `app-counter`, etc.
191
213
  - Components with external templates or styles can use a subfolder (e.g. `contacts/contacts.js` + `contacts.html` + `contacts.css`).
192
- - `app.js` is the single entry point import components, create the store, and boot the router.
214
+ - `app.js` is the single entry point - import components, create the store, and boot the router.
193
215
  - `global.css` lives next to `index.html` for easy access; the bundler hashes it into `global.<hash>.min.css` for production.
194
216
  - `assets/` holds static files that get copied to `dist/` as-is.
195
217
 
@@ -197,7 +219,7 @@ my-app/ ← minimal scaffold (npx zquery create my-app
197
219
 
198
220
  ## CLI Bundler
199
221
 
200
- The CLI compiles your entire app ES modules, the library, external templates, and assets into a **single production-ready bundle**. It outputs two builds in one step: a `server/` build for deploying to any web server, and a `local/` build that works straight from disk. No config, no flags just point it at your app.
222
+ The CLI compiles your entire app - ES modules, the library, external templates, and assets - into a **single production-ready bundle**. It outputs two builds in one step: a `server/` build for deploying to any web server, and a `local/` build that works straight from disk. No config, no flags - just point it at your app.
201
223
 
202
224
  ```bash
203
225
  # Auto-detect entry from any .html with a module script
@@ -219,7 +241,7 @@ dist/
219
241
  z-app.<hash>.min.js
220
242
  global.<hash>.min.css
221
243
  assets/
222
- local/ ← open from disk (file://) no server needed
244
+ local/ ← open from disk (file://) - no server needed
223
245
  index.html
224
246
  z-app.<hash>.min.js
225
247
  ...
@@ -236,11 +258,11 @@ dist/
236
258
 
237
259
  ### What the Bundler Does
238
260
 
239
- 1. **Entry detection** a strict precedence order ensures the correct file is chosen:
240
- 1. **HTML files** `index.html` is checked first, then other `.html` files (root + one level deep).
241
- 2. **Module scripts within HTML** within each HTML file, a `<script type="module">` whose `src` resolves to `app.js` wins; otherwise the first module script tag is used.
242
- 3. **JS file scan** if no HTML match, JS files (up to 2 levels deep) are scanned in two passes: first for `$.router(` (the canonical app entry point), then for `$.mount(`, `$.store(`, or `mountAll(`.
243
- 4. **Convention fallbacks** `app/app.js`, `scripts/app.js`, `src/app.js`, `js/app.js`, `app.js`, `main.js`.
261
+ 1. **Entry detection** - a strict precedence order ensures the correct file is chosen:
262
+ 1. **HTML files** - `index.html` is checked first, then other `.html` files (root + one level deep).
263
+ 2. **Module scripts within HTML** - within each HTML file, a `<script type="module">` whose `src` resolves to `app.js` wins; otherwise the first module script tag is used.
264
+ 3. **JS file scan** - if no HTML match, JS files (up to 2 levels deep) are scanned in two passes: first for `$.router(` (the canonical app entry point), then for `$.mount(`, `$.store(`, or `mountAll(`.
265
+ 4. **Convention fallbacks** - `app/app.js`, `scripts/app.js`, `src/app.js`, `js/app.js`, `app.js`, `main.js`.
244
266
  2. Resolves all `import` statements and topologically sorts dependencies
245
267
  3. Strips `import`/`export` syntax, wraps in an IIFE
246
268
  4. Embeds zQuery library and inlines `templateUrl` / `styleUrl` files
@@ -268,7 +290,7 @@ location / {
268
290
  }
269
291
  ```
270
292
 
271
- **Sub-path deployment** (e.g. `/my-app/`): set `<base href="/my-app/">` in your HTML the router auto-detects it.
293
+ **Sub-path deployment** (e.g. `/my-app/`): set `<base href="/my-app/">` in your HTML - the router auto-detects it.
272
294
 
273
295
  ---
274
296
 
@@ -277,13 +299,13 @@ location / {
277
299
  | Namespace | Methods |
278
300
  | --- | --- |
279
301
  | `$()` | Chainable selector → `ZQueryCollection` (CSS selectors, elements, NodeLists, HTML strings) |
280
- | `$.all()` | Alias for `$()` identical behavior |
302
+ | `$.all()` | Alias for `$()` - identical behavior |
281
303
  | `$.id` `$.class` `$.classes` `$.tag` `$.name` `$.children` `$.qs` `$.qsa` | Quick DOM refs |
282
304
  | `$.create` | Element factory |
283
305
  | `$.ready` `$.on` `$.off` | DOM ready, global event delegation & direct listeners |
284
306
  | `$.fn` | Collection prototype (extend it) |
285
307
  | `$.component` `$.mount` `$.mountAll` `$.getInstance` `$.destroy` `$.components` `$.prefetch` | Component system |
286
- | `$.morph` `$.morphElement` | DOM morphing engine LIS-based keyed reconciliation, `isEqualNode()` bail-outs, `z-skip` opt-out. Patches existing DOM to match new HTML without destroying unchanged nodes. Auto-key detection (`id`, `data-id`, `data-key`) no `z-key` required. `$().html()` and `$().replaceWith()` auto-morph existing content; `$().morph()` for explicit morph |
308
+ | `$.morph` `$.morphElement` | DOM morphing engine - LIS-based keyed reconciliation, `isEqualNode()` bail-outs, `z-skip` opt-out. Patches existing DOM to match new HTML without destroying unchanged nodes. Auto-key detection (`id`, `data-id`, `data-key`) - no `z-key` required. `$().html()` and `$().replaceWith()` auto-morph existing content; `$().morph()` for explicit morph |
287
309
  | `$.safeEval` | CSP-safe expression evaluator (replaces `eval` / `new Function`) |
288
310
  | `$.style` | Dynamically load global stylesheet file(s) at runtime |
289
311
  | `$.router` `$.getRouter` | SPA router |
@@ -298,14 +320,16 @@ location / {
298
320
  | `$.retry` `$.timeout` | Async utils |
299
321
  | `$.param` `$.parseQuery` | URL utils |
300
322
  | `$.storage` `$.session` | Storage wrappers |
301
- | `$.EventBus` `$.bus` | Event bus || `$.onError` `$.ZQueryError` `$.ErrorCode` `$.guardCallback` `$.validate` | Error handling || `$.version` | Library version |\n| `$.libSize` | Minified bundle size string (e.g. `\"~100 KB\"`) |
323
+ | `$.EventBus` `$.bus` | Event bus |
324
+ | `$.onError` `$.ZQueryError` `$.ErrorCode` `$.guardCallback` `$.guardAsync` `$.formatError` `$.validate` | Error handling |
325
+ | `$.version` | Library version |\n| `$.libSize` | Minified bundle size string (e.g. `\"~91 KB\"`) |
302
326
  | `$.unitTests` | Build-time test results `{ passed, failed, total, suites, duration, ok }` |
303
327
  | `$.meta` | Build metadata (populated by CLI bundler) |
304
328
  | `$.noConflict` | Release `$` global |
305
329
 
306
330
  | CLI Command | Description |
307
331
  | --- | --- |
308
- | `zquery create [dir]` | Scaffold a new project. Default: full-featured app. `--minimal` / `-m`: lightweight 3-page starter with 404 fallback. |
332
+ | `zquery create [dir]` | Scaffold a new project. Default: full-featured app. `--minimal` / `-m`: lightweight 3-page starter. `--ssr` / `-s`: SSR project with shared components and HTTP server. |
309
333
  | `zquery dev [root]` | Dev server with live-reload, CSS hot-swap, error overlay, expandable floating toolbar &amp; five-tab inspector panel (port 3100). Visit `/_devtools` for the standalone panel. `--index` for custom HTML, `--bundle` for bundled mode, `--no-intercept` to skip CDN intercept. |
310
334
  | `zquery bundle [dir\|file]` | Bundle app into a single IIFE file. Accepts dir or direct entry file. |
311
335
  | `zquery build` | Build the zQuery library (`dist/zquery.min.js`) |
@@ -323,4 +347,4 @@ The official **[zQuery for VS Code](https://marketplace.visualstudio.com/items?i
323
347
 
324
348
  ## License
325
349
 
326
- MIT [Anthony Wiedman / Molex](https://github.com/tonywied17)
350
+ MIT - [Anthony Wiedman / Molex](https://github.com/tonywied17)
package/cli/args.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * cli/args.js CLI argument parsing helpers
2
+ * cli/args.js - CLI argument parsing helpers
3
3
  *
4
4
  * Provides the raw args array and two helper functions for reading
5
5
  * named flags (--verbose, -v) and valued options (--port 8080, -p 8080).
@@ -1,5 +1,5 @@
1
1
  /**
2
- * cli/commands/build.js library build command
2
+ * cli/commands/build.js - library build command
3
3
  *
4
4
  * Concatenates the zQuery source modules into dist/zquery.js and
5
5
  * dist/zquery.min.js.
@@ -208,7 +208,7 @@ function buildLibrary() {
208
208
  const zipBuf = buildZip(entries);
209
209
  const zipPath = path.join(DIST, 'zquery.dist.zip');
210
210
  fs.writeFileSync(zipPath, zipBuf);
211
- console.log(` ✓ dist/zquery.dist.zip (${sizeKB(zipBuf)} KB) ${entries.length} files`);
211
+ console.log(` ✓ dist/zquery.dist.zip (${sizeKB(zipBuf)} KB) - ${entries.length} files`);
212
212
 
213
213
  return { DIST, OUT_FILE, MIN_FILE };
214
214
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * cli/commands/bundle.js app bundler command
2
+ * cli/commands/bundle.js - app bundler command
3
3
  *
4
4
  * Walks the ES module import graph starting from an entry file,
5
5
  * strips import/export syntax, concatenates everything into a single
@@ -59,7 +59,7 @@ function extractImports(code) {
59
59
  return specifiers;
60
60
  }
61
61
 
62
- /** Walk the import graph topological sort (leaves first). */
62
+ /** Walk the import graph - topological sort (leaves first). */
63
63
  function walkImportGraph(entry) {
64
64
  const visited = new Set();
65
65
  const order = [];
@@ -119,7 +119,7 @@ function rewriteResourceUrls(code, filePath, projectRoot) {
119
119
  (match, prefix, quote, url) => {
120
120
  if (url.startsWith('/') || url.includes('://')) return match;
121
121
  const abs = path.resolve(fileDir, url);
122
- // Only rewrite if the file actually exists avoids mangling code examples
122
+ // Only rewrite if the file actually exists - avoids mangling code examples
123
123
  if (!fs.existsSync(abs)) return match;
124
124
  const rel = path.relative(projectRoot, abs).replace(/\\/g, '/');
125
125
  return `${prefix}${quote}${rel}${quote}`;
@@ -128,7 +128,7 @@ function rewriteResourceUrls(code, filePath, projectRoot) {
128
128
  }
129
129
 
130
130
  /**
131
- * Minify HTML for inlining strips indentation and collapses whitespace
131
+ * Minify HTML for inlining - strips indentation and collapses whitespace
132
132
  * between tags. Preserves content inside <pre>, <code>, and <textarea>
133
133
  * blocks verbatim so syntax-highlighted code samples survive.
134
134
  */
@@ -164,7 +164,7 @@ function minifyHTML(html) {
164
164
  }
165
165
 
166
166
  /**
167
- * Minify CSS for inlining strips comments, collapses whitespace,
167
+ * Minify CSS for inlining - strips comments, collapses whitespace,
168
168
  * removes unnecessary spaces around punctuation.
169
169
  */
170
170
  function minifyCSS(css) {
@@ -396,7 +396,7 @@ function collectInlineResources(files, projectRoot) {
396
396
  inlineMap[relKey] = fs.readFileSync(tmplPath, 'utf-8');
397
397
  }
398
398
  } else if (/templateUrl\s*:/.test(code)) {
399
- // Dynamic templateUrl (e.g. Object.fromEntries, computed map)
399
+ // Dynamic templateUrl (e.g. Object.fromEntries, computed map) -
400
400
  // inline all .html files in the component's directory tree so
401
401
  // the runtime __zqInline lookup can resolve them by suffix.
402
402
  (function scanHtml(dir) {
@@ -412,7 +412,7 @@ function collectInlineResources(files, projectRoot) {
412
412
  scanHtml(full);
413
413
  }
414
414
  }
415
- } catch { /* permission error skip */ }
415
+ } catch { /* permission error - skip */ }
416
416
  })(fileDir);
417
417
  }
418
418
  }
@@ -429,7 +429,7 @@ function collectInlineResources(files, projectRoot) {
429
429
  /**
430
430
  * Auto-detect the app entry point.
431
431
  *
432
- * Strategy ordered by precedence (first match wins):
432
+ * Strategy - ordered by precedence (first match wins):
433
433
  * 1. HTML discovery: index.html first, then other .html files
434
434
  * (root level + one directory deep).
435
435
  * 2. Within each HTML file, prefer a module <script> whose src
@@ -456,7 +456,7 @@ function detectEntry(projectRoot) {
456
456
  htmlFiles.push(path.join(sub, child.name));
457
457
  }
458
458
  }
459
- } catch { /* permission error skip */ }
459
+ } catch { /* permission error - skip */ }
460
460
  }
461
461
  }
462
462
 
@@ -490,8 +490,8 @@ function detectEntry(projectRoot) {
490
490
  }
491
491
 
492
492
  // 2. Search JS files for entry-point patterns.
493
- // Pass 1 $.router (the canonical entry point).
494
- // Pass 2 $.mount, $.store, mountAll (component-level, lower confidence).
493
+ // Pass 1 - $.router (the canonical entry point).
494
+ // Pass 2 - $.mount, $.store, mountAll (component-level, lower confidence).
495
495
  const routerRe = /\$\.router\s*\(/;
496
496
  const otherRe = /\$\.(mount|store)\s*\(|mountAll\s*\(/;
497
497
 
@@ -541,8 +541,8 @@ function detectEntry(projectRoot) {
541
541
  /**
542
542
  * Rewrite an HTML file to replace the module <script> with the bundle.
543
543
  * Produces two variants:
544
- * server/index.html <base href="/"> for SPA deep routes
545
- * local/index.html relative paths for file:// access
544
+ * server/index.html - <base href="/"> for SPA deep routes
545
+ * local/index.html - relative paths for file:// access
546
546
  */
547
547
  function rewriteHtml(projectRoot, htmlRelPath, bundleFile, includeLib, bundledFiles, serverDir, localDir, globalCssOrigHref, globalCssHash) {
548
548
  const htmlPath = path.resolve(projectRoot, htmlRelPath);
@@ -737,7 +737,7 @@ function bundleApp() {
737
737
  const minimal = flag('minimal', 'm');
738
738
  const globalCssOverride = option('global-css', null, null);
739
739
 
740
- // Entry point positional arg (directory or file) or auto-detection
740
+ // Entry point - positional arg (directory or file) or auto-detection
741
741
  let entry = null;
742
742
  let targetDir = null;
743
743
  for (let i = 1; i < args.length; i++) {
@@ -984,7 +984,7 @@ function bundleApp() {
984
984
  console.log(`\n ✓ ${minBase} (${sizeKB(fs.readFileSync(minFile))} KB)`);
985
985
 
986
986
  // ------------------------------------------------------------------
987
- // Global CSS bundling extract from index.html <link> or --global-css
987
+ // Global CSS bundling - extract from index.html <link> or --global-css
988
988
  // ------------------------------------------------------------------
989
989
  let globalCssHash = null;
990
990
  let globalCssOrigHref = null;
@@ -1,4 +1,4 @@
1
- // cli/commands/create.js scaffold a new zQuery project
1
+ // cli/commands/create.js - scaffold a new zQuery project
2
2
  //
3
3
  // Templates live in cli/scaffold/<variant>/ (default or minimal).
4
4
  // Reads template files, replaces {{NAME}} with the project name,
@@ -6,6 +6,7 @@
6
6
 
7
7
  const fs = require('fs');
8
8
  const path = require('path');
9
+ const { execSync, spawn } = require('child_process');
9
10
  const { flag } = require('../args');
10
11
 
11
12
  /**
@@ -30,11 +31,15 @@ function createProject(args) {
30
31
  const target = dirArg ? path.resolve(dirArg) : process.cwd();
31
32
  const name = path.basename(target);
32
33
 
33
- // Determine scaffold variant: --minimal / -m or default
34
- const variant = flag('minimal', 'm') ? 'minimal' : 'default';
34
+ // Determine scaffold variant: --minimal / -m or --ssr / -s or default
35
+ const variant = flag('minimal', 'm') ? 'minimal'
36
+ : flag('ssr', 's') ? 'ssr'
37
+ : 'default';
35
38
 
36
39
  // Guard: refuse to overwrite existing files
37
- const conflicts = ['index.html', 'global.css', 'app', 'assets'].filter(f =>
40
+ const checkFiles = ['index.html', 'global.css', 'app', 'assets'];
41
+ if (variant === 'ssr') checkFiles.push('server');
42
+ const conflicts = checkFiles.filter(f =>
38
43
  fs.existsSync(path.join(target, f))
39
44
  );
40
45
  if (conflicts.length) {
@@ -43,7 +48,7 @@ function createProject(args) {
43
48
  process.exit(1);
44
49
  }
45
50
 
46
- console.log(`\n zQuery Create Project (${variant})\n`);
51
+ console.log(`\n zQuery - Create Project (${variant})\n`);
47
52
  console.log(` Scaffolding into ${target}\n`);
48
53
 
49
54
  // Resolve the scaffold template directory for the chosen variant
@@ -70,11 +75,40 @@ function createProject(args) {
70
75
  console.log(` ✓ ${rel}`);
71
76
  }
72
77
 
73
- console.log(`
78
+ const devCmd = `npx zquery dev${target !== process.cwd() ? ` ${dirArg}` : ''}`;
79
+
80
+ if (variant === 'ssr') {
81
+ console.log(`\n Installing dependencies...\n`);
82
+ // Install zero-query from the same package that provides this CLI
83
+ // (works both in local dev and when installed from npm)
84
+ const zqRoot = path.resolve(__dirname, '..', '..');
85
+ try {
86
+ execSync(`npm install "${zqRoot}"`, { cwd: target, stdio: 'inherit' });
87
+ } catch {
88
+ console.error(`\n ✗ npm install failed. Run it manually:\n\n cd ${dirArg || '.'}\n npm install\n node server/index.js\n`);
89
+ process.exit(1);
90
+ }
91
+
92
+ console.log(`\n Starting SSR server...\n`);
93
+ const child = spawn('node', ['server/index.js'], { cwd: target, stdio: 'inherit' });
94
+
95
+ setTimeout(() => {
96
+ const open = process.platform === 'win32' ? 'start'
97
+ : process.platform === 'darwin' ? 'open' : 'xdg-open';
98
+ try { execSync(`${open} http://localhost:3000`, { stdio: 'ignore' }); } catch {}
99
+ }, 500);
100
+
101
+ process.on('SIGINT', () => { child.kill(); process.exit(); });
102
+ process.on('SIGTERM', () => { child.kill(); process.exit(); });
103
+ child.on('exit', (code) => process.exit(code || 0));
104
+ return; // keep process alive for the server
105
+ } else {
106
+ console.log(`
74
107
  Done! Next steps:
75
108
 
76
- npx zquery dev${target !== process.cwd() ? ` ${dirArg}` : ''}
109
+ ${devCmd}
77
110
  `);
111
+ }
78
112
  }
79
113
 
80
114
  module.exports = createProject;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * cli/commands/dev/devtools/index.js DevTools HTML assembler
2
+ * cli/commands/dev/devtools/index.js - DevTools HTML assembler
3
3
  *
4
4
  * Reads CSS, HTML, and JS partials from this folder and concatenates them
5
5
  * into a single self-contained HTML page served at /_devtools.
@@ -39,7 +39,7 @@ function formatTime(ts) {
39
39
  }
40
40
 
41
41
  // ===================================================================
42
- // Connection find target window (opener popup → iframe fallback)
42
+ // Connection - find target window (opener popup → iframe fallback)
43
43
  // ===================================================================
44
44
  function isConnected() {
45
45
  try { return targetWin && (targetWin === window.opener ? !targetWin.closed : true) && targetWin.document; }
@@ -48,11 +48,11 @@ function isConnected() {
48
48
 
49
49
  function detectMode() {
50
50
  if (window.opener) {
51
- // Opened as popup hide iframe, use opener
51
+ // Opened as popup - hide iframe, use opener
52
52
  mode = 'popup';
53
53
  targetWin = window.opener;
54
54
  } else {
55
- // Standalone tab embed app in iframe
55
+ // Standalone tab - embed app in iframe
56
56
  mode = 'split-h';
57
57
  targetWin = null; // will set from iframe.contentWindow
58
58
  }
@@ -110,11 +110,11 @@ var tbDragging = false;
110
110
 
111
111
  var isH = mode === 'split-h';
112
112
  if (isH) {
113
- // Vertical column divider drag toolbar up/down
113
+ // Vertical column divider - drag toolbar up/down
114
114
  startPos = e.clientY;
115
115
  startOffset = parseInt(toolbar.style.top, 10) || toolbar.offsetTop;
116
116
  } else {
117
- // Horizontal row divider drag toolbar left/right
117
+ // Horizontal row divider - drag toolbar left/right
118
118
  startPos = e.clientX;
119
119
  startOffset = parseInt(toolbar.style.left, 10) || toolbar.offsetLeft;
120
120
  }
@@ -180,7 +180,7 @@ divider.addEventListener('mousedown', function(e) {
180
180
  });
181
181
 
182
182
  // ===================================================================
183
- // Refresh button reload the embedded iframe
183
+ // Refresh button - reload the embedded iframe
184
184
  // ===================================================================
185
185
  document.getElementById('btn-refresh').addEventListener('click', function(e) {
186
186
  e.stopPropagation();
@@ -192,7 +192,7 @@ document.getElementById('btn-refresh').addEventListener('click', function(e) {
192
192
  });
193
193
 
194
194
  // ===================================================================
195
- // Viewport preset buttons resize browser pane to mobile/tablet/desktop
195
+ // Viewport preset buttons - resize browser pane to mobile/tablet/desktop
196
196
  // ===================================================================
197
197
  var viewportBtns = document.querySelectorAll('.viewport-btn');
198
198
 
@@ -219,10 +219,10 @@ viewportBtns.forEach(function(btn) {
219
219
  var total = rootEl.offsetWidth;
220
220
 
221
221
  if (targetWidth === 0) {
222
- // Desktop reset to default CSS proportions
222
+ // Desktop - reset to default CSS proportions
223
223
  rootEl.style.gridTemplateColumns = '';
224
224
  } else {
225
- // Mobile/Tablet set iframe column to exact pixel width
225
+ // Mobile/Tablet - set iframe column to exact pixel width
226
226
  var pct = Math.min(85, Math.max(15, (targetWidth / total) * 100));
227
227
  rootEl.style.gridTemplateColumns = pct.toFixed(1) + '% 4px 1fr';
228
228
  }
@@ -233,7 +233,7 @@ viewportBtns.forEach(function(btn) {
233
233
  });
234
234
 
235
235
  // ===================================================================
236
- // Route indicator toggle label showing current path + hash
236
+ // Route indicator - toggle label showing current path + hash
237
237
  // ===================================================================
238
238
  var routeBtn = document.getElementById('btn-route');
239
239
  var routeLabel = document.getElementById('route-label');
@@ -266,7 +266,7 @@ setInterval(function() {
266
266
  }, 500);
267
267
 
268
268
  // ===================================================================
269
- // init connect to target window (popup or iframe)
269
+ // init - connect to target window (popup or iframe)
270
270
  // ===================================================================
271
271
  function init() {
272
272
  // If popup mode, targetWin is already set
@@ -310,7 +310,7 @@ function init() {
310
310
  }
311
311
 
312
312
  // ===================================================================
313
- // connectToTarget read existing data, start listeners, periodic sync
313
+ // connectToTarget - read existing data, start listeners, periodic sync
314
314
  // ===================================================================
315
315
  function connectToTarget() {
316
316
 
@@ -372,7 +372,7 @@ function connectToTarget() {
372
372
  // Periodic refresh for components + perf (fast when tab is visible)
373
373
  setInterval(function() {
374
374
  if (!isConnected()) {
375
- // Retry connection opener may be mid-mutation, not truly gone
375
+ // Retry connection - opener may be mid-mutation, not truly gone
376
376
  try {
377
377
  if (mode === 'popup' && window.opener && !window.opener.closed) {
378
378
  targetWin = window.opener;
@@ -389,7 +389,7 @@ function connectToTarget() {
389
389
  return;
390
390
  }
391
391
  }
392
- // Keep targetDoc fresh the opener may have reloaded (live-reload)
392
+ // Keep targetDoc fresh - the opener may have reloaded (live-reload)
393
393
  try {
394
394
  var freshDoc = targetWin.document;
395
395
  if (freshDoc !== targetDoc) {
@@ -72,7 +72,7 @@ function buildNode(node, depth) {
72
72
  var nodePath = getNodePath(node);
73
73
  var hasChildren = false;
74
74
  var childNodes = node.childNodes;
75
- // style/script content is shown inline via the peek button treat as leaf
75
+ // style/script content is shown inline via the peek button - treat as leaf
76
76
  if (tag !== 'style' && tag !== 'script') {
77
77
  for (var i = 0; i < childNodes.length; i++) {
78
78
  var cn = childNodes[i];
@@ -238,7 +238,7 @@ function buildNode(node, depth) {
238
238
  toggleNested(childContainer, 0);
239
239
  });
240
240
 
241
- // Row click select element (not toggle)
241
+ // Row click - select element (not toggle)
242
242
  row.addEventListener('click', function(e) {
243
243
  // Don't select when clicking toggle arrow, badge, action buttons, or peek
244
244
  if (e.target.closest('.tree-toggle') || e.target.closest('.tree-badge') || e.target.closest('.tree-action') || e.target.closest('.tree-peek')) return;
@@ -351,7 +351,7 @@ function showDetail(node) {
351
351
  }
352
352
 
353
353
  // ===================================================================
354
- // MutationObserver watch target document for live DOM changes
354
+ // MutationObserver - watch target document for live DOM changes
355
355
  // ===================================================================
356
356
  function startObserver() {
357
357
  if (!targetDoc || observer) return;
@@ -394,7 +394,7 @@ function startObserver() {
394
394
  }
395
395
  }
396
396
 
397
- if (!dominated) return; // All mutations were from devtools highlight skip rebuild
397
+ if (!dominated) return; // All mutations were from devtools highlight - skip rebuild
398
398
 
399
399
  // Debounce tree rebuild
400
400
  clearTimeout(startObserver._timer);
@@ -9,7 +9,7 @@ function updateStats() {
9
9
  document.getElementById('morph-count').textContent = morphCount + ' renders';
10
10
  document.getElementById('req-count').textContent = requests.length + ' requests';
11
11
 
12
- // Route stat show current path
12
+ // Route stat - show current path
13
13
  try {
14
14
  var router = targetWin && targetWin.$ && targetWin.$.getRouter();
15
15
  var routeStat = document.getElementById('route-stat');
@@ -20,7 +20,7 @@ button{font:inherit;cursor:pointer;border:none;background:none;color:inherit}
20
20
  .divider:hover{background:var(--accent)}
21
21
 
22
22
  /* Divider toolbar */
23
- /* split-h: divider is a tall 4px column toolbar sits to its LEFT, overlaying the browser */
23
+ /* split-h: divider is a tall 4px column - toolbar sits to its LEFT, overlaying the browser */
24
24
  .divider-toolbar{position:absolute;display:flex;flex-direction:column;align-items:center;gap:2px;
25
25
  padding:4px 2px;background:var(--bg2);border:1px solid var(--border);border-radius:6px;
26
26
  z-index:11;top:12px;right:100%;margin-right:2px;cursor:grab}
@@ -36,7 +36,7 @@ border:1px solid var(--border);border-radius:4px;font-size:11px;color:var(--acce
36
36
  max-width:0;overflow:hidden;opacity:0;transition:max-width .25s ease,opacity .2s ease,padding .2s ease;
37
37
  padding:0;pointer-events:none;line-height:22px;text-overflow:ellipsis}
38
38
  .route-label.open{max-width:50vw;opacity:1;padding:2px 8px;pointer-events:auto}
39
- /* split-v: divider is a wide 4px row toolbar sits ABOVE it as a horizontal bar */
39
+ /* split-v: divider is a wide 4px row - toolbar sits ABOVE it as a horizontal bar */
40
40
  #root.split-v .divider-toolbar{flex-direction:row;gap:4px;padding:2px 4px;
41
41
  top:auto;right:auto;bottom:100%;left:12px;margin-right:0;margin-bottom:2px}
42
42
  #root.split-v .divider-sep{width:1px;height:14px;margin:0 1px}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * cli/commands/dev/index.js Dev server orchestrator
2
+ * cli/commands/dev/index.js - Dev server orchestrator
3
3
  *
4
4
  * Ties together the HTTP server, file watcher, logger, and overlay
5
5
  * to provide a complete development environment with live-reload,
@@ -44,7 +44,7 @@ function resolveRoot(htmlEntry) {
44
44
  }
45
45
 
46
46
  // ---------------------------------------------------------------------------
47
- // devServer main entry point (called from cli/index.js)
47
+ // devServer - main entry point (called from cli/index.js)
48
48
  // ---------------------------------------------------------------------------
49
49
 
50
50
  async function devServer() {