zero-query 0.6.3 → 0.8.6

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 (72) hide show
  1. package/README.md +39 -29
  2. package/cli/commands/build.js +113 -4
  3. package/cli/commands/bundle.js +392 -29
  4. package/cli/commands/create.js +1 -1
  5. package/cli/commands/dev/devtools/index.js +56 -0
  6. package/cli/commands/dev/devtools/js/components.js +49 -0
  7. package/cli/commands/dev/devtools/js/core.js +409 -0
  8. package/cli/commands/dev/devtools/js/elements.js +413 -0
  9. package/cli/commands/dev/devtools/js/network.js +166 -0
  10. package/cli/commands/dev/devtools/js/performance.js +73 -0
  11. package/cli/commands/dev/devtools/js/router.js +105 -0
  12. package/cli/commands/dev/devtools/js/source.js +132 -0
  13. package/cli/commands/dev/devtools/js/stats.js +35 -0
  14. package/cli/commands/dev/devtools/js/tabs.js +79 -0
  15. package/cli/commands/dev/devtools/panel.html +95 -0
  16. package/cli/commands/dev/devtools/styles.css +244 -0
  17. package/cli/commands/dev/index.js +29 -4
  18. package/cli/commands/dev/logger.js +6 -1
  19. package/cli/commands/dev/overlay.js +428 -2
  20. package/cli/commands/dev/server.js +42 -5
  21. package/cli/commands/dev/watcher.js +59 -1
  22. package/cli/help.js +8 -5
  23. package/cli/scaffold/{scripts → app}/app.js +16 -23
  24. package/cli/scaffold/{scripts → app}/components/about.js +4 -4
  25. package/cli/scaffold/{scripts → app}/components/api-demo.js +1 -1
  26. package/cli/scaffold/{scripts → app}/components/contacts/contacts.css +0 -7
  27. package/cli/scaffold/{scripts → app}/components/contacts/contacts.html +3 -3
  28. package/cli/scaffold/app/components/home.js +137 -0
  29. package/cli/scaffold/{scripts → app}/routes.js +1 -1
  30. package/cli/scaffold/{scripts → app}/store.js +6 -6
  31. package/cli/scaffold/assets/.gitkeep +0 -0
  32. package/cli/scaffold/{styles/styles.css → global.css} +4 -2
  33. package/cli/scaffold/index.html +12 -11
  34. package/cli/utils.js +111 -6
  35. package/dist/zquery.dist.zip +0 -0
  36. package/dist/zquery.js +1122 -158
  37. package/dist/zquery.min.js +3 -16
  38. package/index.d.ts +129 -1290
  39. package/index.js +15 -10
  40. package/package.json +7 -6
  41. package/src/component.js +172 -49
  42. package/src/core.js +359 -18
  43. package/src/diff.js +256 -58
  44. package/src/expression.js +33 -3
  45. package/src/reactive.js +37 -5
  46. package/src/router.js +243 -7
  47. package/tests/component.test.js +886 -0
  48. package/tests/core.test.js +977 -0
  49. package/tests/diff.test.js +525 -0
  50. package/tests/errors.test.js +162 -0
  51. package/tests/expression.test.js +482 -0
  52. package/tests/http.test.js +289 -0
  53. package/tests/reactive.test.js +339 -0
  54. package/tests/router.test.js +649 -0
  55. package/tests/store.test.js +379 -0
  56. package/tests/utils.test.js +512 -0
  57. package/types/collection.d.ts +383 -0
  58. package/types/component.d.ts +217 -0
  59. package/types/errors.d.ts +103 -0
  60. package/types/http.d.ts +81 -0
  61. package/types/misc.d.ts +179 -0
  62. package/types/reactive.d.ts +76 -0
  63. package/types/router.d.ts +161 -0
  64. package/types/ssr.d.ts +49 -0
  65. package/types/store.d.ts +107 -0
  66. package/types/utils.d.ts +142 -0
  67. package/cli/commands/dev.old.js +0 -520
  68. package/cli/scaffold/scripts/components/home.js +0 -137
  69. /package/cli/scaffold/{scripts → app}/components/contacts/contacts.js +0 -0
  70. /package/cli/scaffold/{scripts → app}/components/counter.js +0 -0
  71. /package/cli/scaffold/{scripts → app}/components/not-found.js +0 -0
  72. /package/cli/scaffold/{scripts → app}/components/todos.js +0 -0
@@ -0,0 +1,81 @@
1
+ /**
2
+ * HTTP Client — fetch-based wrapper with interceptors and auto-JSON.
3
+ *
4
+ * @module http
5
+ */
6
+
7
+ /** The response object resolved by all HTTP request methods (except `raw`). */
8
+ export interface HttpResponse<T = any> {
9
+ /** `true` if status 200-299. */
10
+ ok: boolean;
11
+ /** HTTP status code. */
12
+ status: number;
13
+ /** HTTP status text. */
14
+ statusText: string;
15
+ /** Response headers as a plain object. */
16
+ headers: Record<string, string>;
17
+ /** Auto-parsed body (JSON, text, or Blob depending on content type). */
18
+ data: T;
19
+ /** Raw `fetch` Response object. */
20
+ response: Response;
21
+ }
22
+
23
+ /** Per-request options passed to HTTP methods. */
24
+ export interface HttpRequestOptions extends Omit<RequestInit, 'body' | 'method'> {
25
+ /** Additional headers (merged with defaults). */
26
+ headers?: Record<string, string>;
27
+ /** Override default timeout (ms). */
28
+ timeout?: number;
29
+ /** Abort signal for cancellation. */
30
+ signal?: AbortSignal;
31
+ }
32
+
33
+ /** Global HTTP configuration options. */
34
+ export interface HttpConfigureOptions {
35
+ /** Prepended to non-absolute URLs. */
36
+ baseURL?: string;
37
+ /** Default headers (merged, not replaced). */
38
+ headers?: Record<string, string>;
39
+ /** Default timeout in ms (default 30 000). Set `0` to disable. */
40
+ timeout?: number;
41
+ }
42
+
43
+ /** Request interceptor function. */
44
+ export type HttpRequestInterceptor = (
45
+ fetchOpts: RequestInit & { headers: Record<string, string> },
46
+ url: string,
47
+ ) => void | false | { url?: string; options?: RequestInit } | Promise<void | false | { url?: string; options?: RequestInit }>;
48
+
49
+ /** Response interceptor function. */
50
+ export type HttpResponseInterceptor = (result: HttpResponse) => void | Promise<void>;
51
+
52
+ /** The `$.http` namespace. */
53
+ export interface HttpClient {
54
+ /** GET request. `params` appended as query string. */
55
+ get<T = any>(url: string, params?: Record<string, any> | null, opts?: HttpRequestOptions): Promise<HttpResponse<T>>;
56
+ /** POST request. `data` sent as JSON body (or FormData). */
57
+ post<T = any>(url: string, data?: any, opts?: HttpRequestOptions): Promise<HttpResponse<T>>;
58
+ /** PUT request. */
59
+ put<T = any>(url: string, data?: any, opts?: HttpRequestOptions): Promise<HttpResponse<T>>;
60
+ /** PATCH request. */
61
+ patch<T = any>(url: string, data?: any, opts?: HttpRequestOptions): Promise<HttpResponse<T>>;
62
+ /** DELETE request. */
63
+ delete<T = any>(url: string, data?: any, opts?: HttpRequestOptions): Promise<HttpResponse<T>>;
64
+
65
+ /** Update default configuration for all subsequent requests. */
66
+ configure(options: HttpConfigureOptions): void;
67
+
68
+ /** Add a request interceptor (called before every request). */
69
+ onRequest(fn: HttpRequestInterceptor): void;
70
+
71
+ /** Add a response interceptor (called after every response, before error check). */
72
+ onResponse(fn: HttpResponseInterceptor): void;
73
+
74
+ /** Create a new `AbortController` for manual request cancellation. */
75
+ createAbort(): AbortController;
76
+
77
+ /** Direct passthrough to native `fetch()` — no JSON handling, no interceptors. */
78
+ raw(url: string, opts?: RequestInit): Promise<Response>;
79
+ }
80
+
81
+ export declare const http: HttpClient;
@@ -0,0 +1,179 @@
1
+ /**
2
+ * DOM diffing, safe expression evaluator, and directive metadata.
3
+ *
4
+ * @module misc
5
+ */
6
+
7
+ // ---------------------------------------------------------------------------
8
+ // DOM Diffing (Morphing)
9
+ // ---------------------------------------------------------------------------
10
+
11
+ /**
12
+ * Morph an existing DOM element's children to match new HTML.
13
+ * Only touches nodes that actually differ — preserves focus, scroll
14
+ * positions, video playback, and other live DOM state.
15
+ *
16
+ * Use `z-key="uniqueId"` attributes on list items for keyed reconciliation.
17
+ * Elements with `id`, `data-id`, or `data-key` attributes are auto-keyed.
18
+ *
19
+ * @param rootEl The live DOM container to patch.
20
+ * @param newHTML The desired HTML string.
21
+ */
22
+ export function morph(rootEl: Element, newHTML: string): void;
23
+
24
+ /**
25
+ * Morph a single element in place — diffs attributes and children
26
+ * without replacing the node reference. If the tag name matches, the
27
+ * element is patched in place (preserving identity). If the tag differs,
28
+ * the element is replaced.
29
+ *
30
+ * @param oldEl The live DOM element to patch.
31
+ * @param newHTML HTML string for the replacement element.
32
+ * @returns The resulting element (same ref if morphed, new if replaced).
33
+ */
34
+ export function morphElement(oldEl: Element, newHTML: string): Element;
35
+
36
+ // ---------------------------------------------------------------------------
37
+ // Safe Expression Evaluator
38
+ // ---------------------------------------------------------------------------
39
+
40
+ /**
41
+ * CSP-safe expression evaluator. Parses and evaluates JS expressions
42
+ * without `eval()` or `new Function()`. Used internally by directives.
43
+ *
44
+ * @param expr Expression string.
45
+ * @param scope Array of scope objects checked in order for identifier resolution.
46
+ * @returns Evaluation result, or `undefined` on error.
47
+ */
48
+ export function safeEval(expr: string, scope: object[]): any;
49
+
50
+ // ---------------------------------------------------------------------------
51
+ // Directive System
52
+ // ---------------------------------------------------------------------------
53
+ //
54
+ // Directives are special attributes processed by zQuery's component renderer.
55
+ // They work in both `render()` template literals and external `templateUrl`
56
+ // HTML files. All expressions evaluate in the component's state context
57
+ // (bare names resolve to `this.state.*`; `props` and `refs` also available).
58
+ //
59
+ // ─── Structural Directives ──────────────────────────────────────────────
60
+ //
61
+ // z-if="expression" Conditional rendering — element removed when falsy.
62
+ // z-else-if="expression" Else-if branch (must be immediate sibling of z-if).
63
+ // z-else Default branch (must follow z-if or z-else-if).
64
+ //
65
+ // z-for="item in items" List rendering — repeats the element per item.
66
+ // {{item.prop}} Use double-brace interpolation for item data.
67
+ // (item, index) in items Destructured index support.
68
+ // n in 5 Number range → [1, 2, 3, 4, 5].
69
+ // (val, key) in object Object iteration → {key, value} entries.
70
+ //
71
+ // z-key="uniqueId" Keyed reconciliation for list items.
72
+ // Preserves DOM nodes across reorders. Use inside
73
+ // z-for to give each item a stable identity.
74
+ // Example: <li z-for="item in items" z-key="{{item.id}}">
75
+ //
76
+ // z-show="expression" Toggle `display: none` (element stays in DOM).
77
+ //
78
+ // ─── Attribute Directives ───────────────────────────────────────────────
79
+ //
80
+ // z-bind:attr="expression" Dynamic attribute binding.
81
+ // :attr="expression" Shorthand for z-bind:attr.
82
+ // false/null/undefined → removes the attribute.
83
+ // true → sets empty attribute (e.g. disabled="").
84
+ //
85
+ // z-class="expression" Dynamic class binding.
86
+ // String: space-separated class names.
87
+ // Array: list of class names (falsy filtered).
88
+ // Object: { className: condition } map.
89
+ //
90
+ // z-style="expression" Dynamic inline styles.
91
+ // String: appended to existing cssText.
92
+ // Object: { property: value } map (camelCase keys).
93
+ //
94
+ // z-html="expression" Set innerHTML from expression (use trusted content only).
95
+ // z-text="expression" Set textContent from expression (safe, no HTML).
96
+ //
97
+ // ─── Form & Reference Directives ────────────────────────────────────────
98
+ //
99
+ // z-model="stateKey" Two-way binding for form elements.
100
+ // Supports: input, textarea, select, select[multiple], contenteditable.
101
+ // Nested keys: z-model="user.name" → this.state.user.name.
102
+ // Modifiers (boolean attributes on same element):
103
+ // z-lazy — update on 'change' instead of 'input' (update on blur).
104
+ // z-trim — auto .trim() whitespace before writing to state.
105
+ // z-number — force Number() conversion regardless of input type.
106
+ //
107
+ // z-ref="name" Element reference → this.refs.name.
108
+ //
109
+ // ─── Event Directives ───────────────────────────────────────────────────
110
+ //
111
+ // @event="method" Event binding with delegation (shorthand).
112
+ // z-on:event="method" Event binding with delegation (full syntax).
113
+ // @event="method(args)" Pass arguments: strings, numbers, booleans,
114
+ // null, $event, state.key references.
115
+ //
116
+ // Event Modifiers (chainable with dots):
117
+ // .prevent event.preventDefault()
118
+ // .stop event.stopPropagation()
119
+ // .self Only fire if event.target === element itself.
120
+ // .once Handler fires at most once per element.
121
+ // .capture addEventListener with { capture: true }.
122
+ // .passive addEventListener with { passive: true }.
123
+ // .debounce.{ms} Debounce: delay until {ms}ms idle (default 250).
124
+ // .throttle.{ms} Throttle: invoke at most once per {ms}ms (default 250).
125
+ //
126
+ // ─── Special Directives ─────────────────────────────────────────────────
127
+ //
128
+ // z-cloak Hidden until rendered (auto-removed after mount).
129
+ // Global CSS: [z-cloak] { display: none !important }.
130
+ // Also injects: *, *::before, *::after { -webkit-tap-highlight-color: transparent }.
131
+ //
132
+ // z-pre Skip all directive processing for this element
133
+ // and its descendants.
134
+ //
135
+ // ─── Slot System ────────────────────────────────────────────────────────
136
+ //
137
+ // <slot> Default slot — replaced with child content
138
+ // passed by the parent component.
139
+ // <slot name="header"> Named slot — replaced with child content that
140
+ // has a matching slot="header" attribute.
141
+ // <slot>fallback</slot> Fallback content shown when no slot content provided.
142
+ //
143
+ // Parent usage:
144
+ // <my-component>
145
+ // <h1 slot="header">Title</h1> (→ named slot "header")
146
+ // <p>Body text</p> (→ default slot)
147
+ // </my-component>
148
+ //
149
+ // ─── Processing Order ───────────────────────────────────────────────────
150
+ //
151
+ // 1. z-for (pre-innerHTML expansion)
152
+ // 2. z-if chain (DOM removal)
153
+ // 3. z-show (display toggle)
154
+ // 4. z-bind / : (dynamic attributes)
155
+ // 5. z-class (dynamic classes)
156
+ // 6. z-style (dynamic styles)
157
+ // 7. z-html/text (content injection)
158
+ // 8. @/z-on (event binding)
159
+ // 9. z-ref (element references)
160
+ // 10. z-model (two-way binding)
161
+ // 11. z-cloak (attribute removal)
162
+ //
163
+ // ---------------------------------------------------------------------------
164
+
165
+ /**
166
+ * Supported event modifier strings for `@event` and `z-on:event` bindings.
167
+ * Modifiers are appended to the event name with dots, e.g. `@click.prevent.stop`.
168
+ */
169
+ export type EventModifier =
170
+ | 'prevent'
171
+ | 'stop'
172
+ | 'self'
173
+ | 'once'
174
+ | 'capture'
175
+ | 'passive'
176
+ | `debounce`
177
+ | `debounce.${number}`
178
+ | `throttle`
179
+ | `throttle.${number}`;
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Reactive primitives — deep proxies and signals.
3
+ *
4
+ * @module reactive
5
+ */
6
+
7
+ /** Marker properties added to every reactive proxy. */
8
+ export interface ReactiveProxy<T extends object = object> {
9
+ /** Always `true` — indicates this object is wrapped in a reactive Proxy. */
10
+ readonly __isReactive: true;
11
+ /** The original un-proxied object. */
12
+ readonly __raw: T;
13
+ }
14
+
15
+ /**
16
+ * Wrap an object in a deep Proxy that fires `onChange` on every set / delete.
17
+ *
18
+ * @param target Plain object to make reactive.
19
+ * @param onChange Called with `(key, newValue, oldValue)` on every mutation.
20
+ * @returns A proxied version of `target` with `__isReactive` and `__raw` markers.
21
+ */
22
+ export function reactive<T extends object>(
23
+ target: T,
24
+ onChange: (key: string, value: any, oldValue: any) => void,
25
+ ): T & ReactiveProxy<T>;
26
+
27
+ /**
28
+ * A lightweight reactive primitive (inspired by Solid / Preact signals).
29
+ *
30
+ * Reading `.value` inside an `effect()` auto-subscribes to changes.
31
+ * Writing `.value` triggers all subscribers and dependent effects.
32
+ */
33
+ export class Signal<T = any> {
34
+ constructor(value: T);
35
+
36
+ /**
37
+ * Get or set the current value.
38
+ * The getter automatically tracks dependencies when accessed inside an `effect()`.
39
+ */
40
+ value: T;
41
+
42
+ /** Read the current value *without* creating a subscription (no tracking). */
43
+ peek(): T;
44
+
45
+ /**
46
+ * Manually subscribe to changes.
47
+ * @returns An unsubscribe function.
48
+ */
49
+ subscribe(fn: () => void): () => void;
50
+
51
+ toString(): string;
52
+ }
53
+
54
+ /** Create a new `Signal`. */
55
+ export function signal<T>(initial: T): Signal<T>;
56
+
57
+ /**
58
+ * Create a derived signal that recomputes when its dependencies change.
59
+ * The returned signal is effectively read-only.
60
+ */
61
+ export function computed<T>(fn: () => T): Signal<T>;
62
+
63
+ /**
64
+ * Create a side-effect that auto-subscribes to signals read during execution.
65
+ * Re-runs automatically when any tracked signal changes.
66
+ *
67
+ * @param fn The effect function. Executed immediately on creation, then on signal changes.
68
+ * @returns A dispose function — calling it stops tracking and prevents re-runs.
69
+ *
70
+ * @example
71
+ * const count = signal(0);
72
+ * const stop = effect(() => console.log(count.value)); // logs 0
73
+ * count.value = 1; // logs 1
74
+ * stop(); // effect no longer runs
75
+ */
76
+ export function effect(fn: () => void): () => void;
@@ -0,0 +1,161 @@
1
+ /**
2
+ * SPA Router — history and hash-based client-side routing.
3
+ *
4
+ * @module router
5
+ */
6
+
7
+ /** A single route definition. */
8
+ export interface RouteDefinition {
9
+ /**
10
+ * URL pattern. Supports `:param` segments and `*` wildcard.
11
+ * @example '/user/:id'
12
+ */
13
+ path: string;
14
+
15
+ /** Registered component name (auto-mounted), or a render function. */
16
+ component: string | ((route: NavigationContext) => string);
17
+
18
+ /** Async function called before mounting (for lazy-loading modules). */
19
+ load?: () => Promise<any>;
20
+
21
+ /**
22
+ * An additional path that also matches this route.
23
+ * Missing `:param` values will be `undefined`.
24
+ */
25
+ fallback?: string;
26
+ }
27
+
28
+ /** Navigation context passed to guards and `onChange` listeners. */
29
+ export interface NavigationContext {
30
+ /** The matched route definition. */
31
+ route: RouteDefinition;
32
+ /** Parsed `:param` values. */
33
+ params: Record<string, string>;
34
+ /** Parsed query-string values. */
35
+ query: Record<string, string>;
36
+ /** Matched path (base-stripped in history mode). */
37
+ path: string;
38
+ }
39
+
40
+ /** Router configuration. */
41
+ export interface RouterConfig {
42
+ /** Outlet element where route components are rendered. */
43
+ el?: string | Element;
44
+ /** Routing mode (default: `'history'`; `'hash'` for file:// or hash routing). */
45
+ mode?: 'history' | 'hash';
46
+ /**
47
+ * Base path prefix (e.g. `'/my-app'`).
48
+ * Auto-detected from `<base href>` or `window.__ZQ_BASE` if not set.
49
+ */
50
+ base?: string;
51
+ /** Route definitions. */
52
+ routes?: RouteDefinition[];
53
+ /** Component name to render when no route matches (404). */
54
+ fallback?: string;
55
+ }
56
+
57
+ /** The SPA router instance. */
58
+ export interface RouterInstance {
59
+ /**
60
+ * Push a new state and resolve the route.
61
+ * Supports `:param` interpolation when `options.params` is provided.
62
+ * Same-path navigation is deduplicated (skipped unless `options.force` is true).
63
+ * Hash-only changes on the same route use `replaceState` to avoid extra history entries.
64
+ * @example
65
+ * router.navigate('/user/:id', { params: { id: 42 } }); // navigates to /user/42
66
+ * router.navigate('/dashboard', { state: { from: 'login' } });
67
+ */
68
+ navigate(path: string, options?: { params?: Record<string, string | number>; state?: any; force?: boolean }): RouterInstance;
69
+ /**
70
+ * Replace the current state (no new history entry).
71
+ * Supports `:param` interpolation when `options.params` is provided.
72
+ * @example
73
+ * router.replace('/user/:id', { params: { id: 42 } }); // replaces with /user/42
74
+ */
75
+ replace(path: string, options?: { params?: Record<string, string | number>; state?: any }): RouterInstance;
76
+ /** `history.back()` */
77
+ back(): RouterInstance;
78
+ /** `history.forward()` */
79
+ forward(): RouterInstance;
80
+ /** `history.go(n)` */
81
+ go(n: number): RouterInstance;
82
+
83
+ /** Add a route dynamically. Chainable. */
84
+ add(route: RouteDefinition): RouterInstance;
85
+ /** Remove a route by path. */
86
+ remove(path: string): RouterInstance;
87
+
88
+ /**
89
+ * Navigation guard — runs before each route change.
90
+ * Return `false` to cancel, or a `string` to redirect.
91
+ */
92
+ beforeEach(
93
+ fn: (
94
+ to: NavigationContext,
95
+ from: NavigationContext | null,
96
+ ) => boolean | string | void | Promise<boolean | string | void>,
97
+ ): RouterInstance;
98
+
99
+ /** Post-navigation hook. */
100
+ afterEach(
101
+ fn: (to: NavigationContext, from: NavigationContext | null) => void | Promise<void>,
102
+ ): RouterInstance;
103
+
104
+ /**
105
+ * Subscribe to route changes.
106
+ * @returns An unsubscribe function.
107
+ */
108
+ onChange(
109
+ fn: (to: NavigationContext, from: NavigationContext | null) => void,
110
+ ): () => void;
111
+
112
+ /**
113
+ * Push a lightweight history entry for in-component UI state (modal, tab, panel).
114
+ * The URL does NOT change — only a history entry is added so the back button
115
+ * can undo the UI change before navigating away from the route.
116
+ * @param key — identifier for the substate (e.g. 'modal', 'tab')
117
+ * @param data — arbitrary serializable state
118
+ * @example
119
+ * router.pushSubstate('modal', { id: 'confirm-delete' });
120
+ */
121
+ pushSubstate(key: string, data?: any): RouterInstance;
122
+
123
+ /**
124
+ * Register a listener for substate pops (back button on a substate entry).
125
+ * The callback receives `(key, data, action)` and should return `true` if it
126
+ * handled the pop (prevents route resolution). If no listener returns `true`,
127
+ * normal route resolution proceeds.
128
+ * @returns An unsubscribe function.
129
+ * @example
130
+ * const unsub = router.onSubstate((key, data, action) => {
131
+ * if (action === 'reset') { resetDefaults(); return true; }
132
+ * if (key === 'modal') { closeModal(); return true; }
133
+ * });
134
+ */
135
+ onSubstate(
136
+ fn: (key: string | null, data: any, action: 'pop' | 'resolve' | 'reset') => boolean | void,
137
+ ): () => void;
138
+
139
+ /** Teardown the router and mounted component. */
140
+ destroy(): void;
141
+
142
+ /** Resolve a path applying the base prefix. */
143
+ resolve(path: string): string;
144
+
145
+ // -- Properties ----------------------------------------------------------
146
+
147
+ /** Current navigation context. */
148
+ readonly current: NavigationContext | null;
149
+ /** Current path (base-stripped in history mode). */
150
+ readonly path: string;
151
+ /** Parsed query-string object. */
152
+ readonly query: Record<string, string>;
153
+ /** Configured base path. */
154
+ readonly base: string;
155
+ }
156
+
157
+ /** Create and activate a client-side SPA router. */
158
+ export function createRouter(config: RouterConfig): RouterInstance;
159
+
160
+ /** Get the currently active router instance. */
161
+ export function getRouter(): RouterInstance | null;
package/types/ssr.d.ts ADDED
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Server-Side Rendering — render components to HTML strings.
3
+ *
4
+ * @module ssr
5
+ */
6
+
7
+ import type { ComponentDefinition } from './component';
8
+
9
+ /** SSR application instance for server-side component rendering. */
10
+ export interface SSRApp {
11
+ /** Register a component for SSR. */
12
+ component(name: string, definition: ComponentDefinition): SSRApp;
13
+
14
+ /**
15
+ * Render a component to an HTML string.
16
+ * @param componentName Registered component name.
17
+ * @param props Props to pass to the component.
18
+ * @param options Rendering options.
19
+ */
20
+ renderToString(
21
+ componentName: string,
22
+ props?: Record<string, any>,
23
+ options?: { hydrate?: boolean },
24
+ ): Promise<string>;
25
+
26
+ /**
27
+ * Render a full HTML page with a component mounted in a shell.
28
+ */
29
+ renderPage(options: {
30
+ component?: string;
31
+ props?: Record<string, any>;
32
+ title?: string;
33
+ styles?: string[];
34
+ scripts?: string[];
35
+ lang?: string;
36
+ meta?: string;
37
+ bodyAttrs?: string;
38
+ }): Promise<string>;
39
+ }
40
+
41
+ /** Create an SSR application instance. */
42
+ export function createSSRApp(): SSRApp;
43
+
44
+ /**
45
+ * Quick one-shot render of a component definition to an HTML string.
46
+ * @param definition Component definition object.
47
+ * @param props Props to pass.
48
+ */
49
+ export function renderToString(definition: ComponentDefinition, props?: Record<string, any>): string;
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Store — reactive global state management.
3
+ *
4
+ * @module store
5
+ */
6
+
7
+ import type { ReactiveProxy } from './reactive';
8
+
9
+ /** Store configuration. */
10
+ export interface StoreConfig<
11
+ S extends Record<string, any> = Record<string, any>,
12
+ A extends Record<string, (state: S, ...args: any[]) => any> = Record<string, (state: S, ...args: any[]) => any>,
13
+ G extends Record<string, (state: S) => any> = Record<string, (state: S) => any>,
14
+ > {
15
+ /** Initial state. Function form creates a fresh copy. */
16
+ state?: S | (() => S);
17
+
18
+ /** Named action functions. The first argument is always the reactive `state`. */
19
+ actions?: A;
20
+
21
+ /** Computed getters derived from state (lazily evaluated). */
22
+ getters?: G;
23
+
24
+ /** Log dispatched actions to the console. */
25
+ debug?: boolean;
26
+ }
27
+
28
+ /** A store action history entry. */
29
+ export interface StoreHistoryEntry {
30
+ action: string;
31
+ args: any[];
32
+ timestamp: number;
33
+ }
34
+
35
+ /** The reactive store instance. */
36
+ export interface StoreInstance<
37
+ S extends Record<string, any> = Record<string, any>,
38
+ A extends Record<string, (state: S, ...args: any[]) => any> = Record<string, (state: S, ...args: any[]) => any>,
39
+ G extends Record<string, (state: S) => any> = Record<string, (state: S) => any>,
40
+ > {
41
+ /** Reactive state proxy. Read / write triggers subscriptions. */
42
+ state: S & ReactiveProxy<S>;
43
+
44
+ /** Computed getters object. */
45
+ readonly getters: { readonly [K in keyof G]: ReturnType<G[K]> };
46
+
47
+ /** Log of all dispatched actions. */
48
+ readonly history: ReadonlyArray<StoreHistoryEntry>;
49
+
50
+ /**
51
+ * Execute a named action.
52
+ * @returns The action's return value.
53
+ */
54
+ dispatch<K extends keyof A>(
55
+ name: K,
56
+ ...args: A[K] extends (state: any, ...rest: infer P) => any ? P : any[]
57
+ ): ReturnType<A[K]>;
58
+ dispatch(name: string, ...args: any[]): any;
59
+
60
+ /**
61
+ * Subscribe to changes on a specific state key.
62
+ * Callback receives `(newValue, oldValue, key)`.
63
+ * @returns An unsubscribe function.
64
+ */
65
+ subscribe(key: string, fn: (value: any, oldValue: any, key: string) => void): () => void;
66
+
67
+ /**
68
+ * Subscribe to all state changes (wildcard).
69
+ * Callback receives `(key, newValue, oldValue)`.
70
+ * @returns An unsubscribe function.
71
+ */
72
+ subscribe(fn: (key: string, value: any, oldValue: any) => void): () => void;
73
+
74
+ /** Deep clone of the current state. */
75
+ snapshot(): S;
76
+
77
+ /** Replace the entire state. */
78
+ replaceState(newState: Partial<S>): void;
79
+
80
+ /**
81
+ * Add middleware. Return `false` from `fn` to block the action.
82
+ * Chainable.
83
+ */
84
+ use(fn: (actionName: string, args: any[], state: S) => boolean | void): StoreInstance<S, A, G>;
85
+
86
+ /** Replace state and clear action history. */
87
+ reset(initialState: Partial<S>): void;
88
+ }
89
+
90
+ /** Create a new global reactive store. */
91
+ export function createStore<
92
+ S extends Record<string, any>,
93
+ A extends Record<string, (state: S, ...args: any[]) => any>,
94
+ G extends Record<string, (state: S) => any>,
95
+ >(config: StoreConfig<S, A, G>): StoreInstance<S, A, G>;
96
+ export function createStore<
97
+ S extends Record<string, any>,
98
+ A extends Record<string, (state: S, ...args: any[]) => any>,
99
+ G extends Record<string, (state: S) => any>,
100
+ >(name: string, config: StoreConfig<S, A, G>): StoreInstance<S, A, G>;
101
+
102
+ /** Retrieve a previously created store by name (default: `'default'`). */
103
+ export function getStore<
104
+ S extends Record<string, any> = Record<string, any>,
105
+ A extends Record<string, (state: S, ...args: any[]) => any> = Record<string, (state: S, ...args: any[]) => any>,
106
+ G extends Record<string, (state: S) => any> = Record<string, (state: S) => any>,
107
+ >(name?: string): StoreInstance<S, A, G> | null;