@zenithbuild/runtime 0.2.1 → 0.5.0-beta.2.15

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.
@@ -0,0 +1,115 @@
1
+ # Zenith Runtime V0 Hydration Contract
2
+
3
+ Canonical public docs: `../zenith-docs/documentation/contracts/hydration-contract.md`
4
+
5
+
6
+ Status: FROZEN (V0)
7
+
8
+ This contract locks hydration and reactivity boundaries for Zenith V0.
9
+
10
+ ## 1. No Runtime Discovery
11
+
12
+ Runtime must not discover bindings by scanning unknown DOM structure.
13
+
14
+ Allowed:
15
+ - Resolve selectors from bundler-provided marker/event tables.
16
+
17
+ Forbidden:
18
+ - Global `querySelectorAll('*')` discovery passes.
19
+ - Runtime inference of binding intent from HTML shape.
20
+
21
+ ## 2. No Implicit Reactivity
22
+
23
+ Runtime primitives are explicit:
24
+ - `signal(initial)`
25
+ - `state(initialObject)`
26
+ - `zeneffect(dependencies, fn)`
27
+
28
+ Forbidden:
29
+ - Proxy-based tracking.
30
+ - Implicit dependency capture contexts.
31
+ - Scheduler/queue abstractions.
32
+
33
+ ## 3. Deterministic Ordering
34
+
35
+ Compiler expression ordering is canonical.
36
+ Bundler and runtime must preserve index semantics exactly.
37
+
38
+ Required:
39
+ - Marker indices are sequential `0..N-1`.
40
+ - Marker count equals expression table length.
41
+ - Runtime never remaps indices.
42
+
43
+ ## 4. Explicit Bootstrap
44
+
45
+ Hydration is explicit and called by bundler bootstrap only:
46
+
47
+ ```js
48
+ hydrate({
49
+ ir_version: 1,
50
+ root: document,
51
+ expressions: __zenith_expr,
52
+ markers: __zenith_markers,
53
+ events: __zenith_events,
54
+ state_values: __zenith_state_values,
55
+ signals: __zenith_signals,
56
+ components: __zenith_components
57
+ });
58
+ ```
59
+
60
+ Runtime must export functions only. Runtime must not auto-run.
61
+
62
+ ## 5. Component Factory Payload
63
+
64
+ Component scripts are compile-time hoisted and emitted as factory modules.
65
+ Runtime receives only explicit component references in `payload.components`.
66
+
67
+ Required:
68
+ - Component host selectors are provided by bundler.
69
+ - Runtime instantiates factories only from payload.
70
+ - Runtime merges returned `bindings` into hydration state by instance key.
71
+
72
+ Forbidden:
73
+ - Runtime component discovery by scanning unknown DOM shape.
74
+ - Runtime-generated component wrappers or lifecycle registries.
75
+
76
+ ## 6. Hard Fail Semantics
77
+
78
+ Runtime must throw for contract drift:
79
+ - Missing or unsupported `ir_version`.
80
+ - Duplicate marker indices.
81
+ - Missing index in sequence.
82
+ - Out-of-order expression table entries.
83
+ - Out-of-order marker table entries.
84
+ - Marker index out of bounds.
85
+ - Marker/expression count mismatch.
86
+ - Event index out of bounds.
87
+
88
+ No fallback behavior is allowed for broken payload contracts.
89
+
90
+ ## 7. Determinism and Purity
91
+
92
+ Forbidden in runtime/bundler output:
93
+ - `eval(`
94
+ - `new Function(`
95
+ - `require(`
96
+ - `Date(`
97
+ - `Math.random(`
98
+ - `crypto.randomUUID(`
99
+ - `process.env`
100
+
101
+ Compile-time guarantees override runtime flexibility.
102
+
103
+ ## 8. Freeze Boundary Contract
104
+
105
+ Runtime payload freezing is allowed only for deterministic internal tables and
106
+ plain JSON-like containers controlled by runtime (`Object` / `Array`).
107
+
108
+ Runtime MUST NOT freeze:
109
+ - `ref()` objects (objects with writable `.current`)
110
+ - function values (handlers/callbacks)
111
+ - host/platform objects (`Node`, `EventTarget`, `URL`, `Request`, `Response`, etc.)
112
+
113
+ Rationale:
114
+ - hydration and `zenMount` must be able to assign `ref.current` without throwing
115
+ - host objects preserve platform mutability semantics
package/README.md CHANGED
@@ -1,7 +1,16 @@
1
1
  # @zenith/runtime
2
2
 
3
+ > **⚠️ Internal API:** This package is an internal implementation detail of the Zenith framework. It is not intended for public use and its API may break without warning. Please use `@zenithbuild/core` instead.
4
+
5
+
3
6
  The core runtime library for the Zenith framework.
4
7
 
8
+ ## Canonical Docs
9
+
10
+ - Runtime contract: `../zenith-docs/documentation/contracts/runtime-contract.md`
11
+ - Hydration contract: `../zenith-docs/documentation/contracts/hydration-contract.md`
12
+ - Reactive binding model: `../zenith-docs/documentation/reference/reactive-binding-model.md`
13
+
5
14
  ## Overview
6
15
  This package provides the reactivity system, hydration logic, and Virtual DOM primitives used by Zenith applications. It is designed to be lightweight, fast, and tree-shakeable.
7
16
 
@@ -0,0 +1,186 @@
1
+ # RUNTIME_CONTRACT.md — Sealed Runtime Interface
2
+
3
+ Canonical public docs: `../zenith-docs/documentation/contracts/runtime-contract.md`
4
+
5
+
6
+ > **This document is a legal boundary.**
7
+ > The runtime is a consumer of bundler output.
8
+ > It does not reinterpret, normalize, or extend.
9
+
10
+ ## Status: FROZEN (V0)
11
+
12
+ ---
13
+
14
+ ## 1. Input Surface
15
+
16
+ The runtime receives exactly one module per page:
17
+
18
+ | Export | Type | Contract |
19
+ |---|---|---|
20
+ | `__zenith_html` | `string` | Template literal, immutable |
21
+ | `__zenith_expr` | `(() => any)[]` | Pre-bound expression functions, immutable, ordered |
22
+ | `__zenith_page()` | `function` | Returns `{ html, expressions }` |
23
+
24
+ ### Expression Functions
25
+
26
+ Each entry in `__zenith_expr` is a **pre-bound zero-argument function** emitted by the bundler. The runtime never resolves expression strings, never performs key lookups, and never interprets JavaScript.
27
+
28
+ ```js
29
+ // Bundler emits:
30
+ export const __zenith_expr = [
31
+ () => count(),
32
+ () => count() + 1,
33
+ () => increment()
34
+ ];
35
+
36
+ export default function __zenith_page() {
37
+ return {
38
+ html: __zenith_html,
39
+ expressions: __zenith_expr
40
+ };
41
+ }
42
+ ```
43
+
44
+ The runtime simply executes: `expressions[index]()`.
45
+
46
+ ---
47
+
48
+ ## 2. Runtime Responsibilities (Allowed)
49
+
50
+ | Action | Method |
51
+ |---|---|
52
+ | Insert HTML into container | `container.innerHTML = html` |
53
+ | Locate expression bindings | `querySelectorAll('[data-zx-e]')` |
54
+ | Locate event bindings | `querySelectorAll('[data-zx-on-*]')` |
55
+ | Bind expressions to DOM | `effect(() => node.textContent = expressions[index]())` |
56
+ | Bind event listeners | `node.addEventListener(event, expressions[index])` |
57
+ | Update DOM on signal change | Via effect re-execution |
58
+ | Clean up on unmount | Remove effects + listeners |
59
+
60
+ ---
61
+
62
+ ## 3. Runtime Prohibitions (Forbidden)
63
+
64
+ The runtime **must never**:
65
+
66
+ - Parse JavaScript expressions
67
+ - Resolve expression strings against a scope object
68
+ - Normalize expression strings
69
+ - Modify expression content
70
+ - Introduce component abstractions
71
+ - Perform virtual DOM diffing
72
+ - Re-render full subtrees
73
+ - Implement lifecycle hooks beyond mount/unmount
74
+ - Access or mutate `window` globals
75
+ - Reorder binding indices
76
+ - Interpret import semantics
77
+ - Add framework-level abstractions (routing, stores, context)
78
+
79
+ ---
80
+
81
+ ## 4. Data Attribute Contract
82
+
83
+ Inherited from `BUNDLER_CONTRACT.md`:
84
+
85
+ | Attribute | Format | Runtime Action |
86
+ |---|---|---|
87
+ | `data-zx-e` | `"<index>"` or `"<i0> <i1> ..."` | Execute `expressions[index]()`, bind result to node |
88
+ | `data-zx-on-<event>` | `"<index>"` | Call `addEventListener(event, expressions[index])` |
89
+
90
+ Index values are 0-based integers matching `__zenith_expr` array positions.
91
+
92
+ ---
93
+
94
+ ## 5. Reactivity Model
95
+
96
+ ### Signal Primitive
97
+
98
+ ```js
99
+ const count = signal(0); // Create
100
+ count(); // Read (tracks dependency)
101
+ count.set(1); // Write (notifies subscribers)
102
+ ```
103
+
104
+ Internals:
105
+ - Each signal maintains a `Set<Effect>` of subscribers
106
+ - Reading a signal during effect execution registers the effect as a subscriber
107
+ - Writing a signal notifies all subscribers synchronously
108
+
109
+ ### Effect Primitive
110
+
111
+ ```js
112
+ effect(() => {
113
+ node.textContent = count();
114
+ });
115
+ ```
116
+
117
+ Internals:
118
+ - On execution, sets itself as the "current tracking context"
119
+ - Any signal read during execution adds this effect to its subscriber set
120
+ - When a dependency signal changes, the effect re-runs
121
+
122
+ ### Constraints
123
+
124
+ - No batching
125
+ - No scheduler / microtask queue
126
+ - No async effects
127
+ - No suspense / lazy loading
128
+ - No lifecycle hooks beyond `cleanup()`
129
+
130
+ ---
131
+
132
+ ## 6. Hydration Algorithm
133
+
134
+ Single-pass, deterministic:
135
+
136
+ 1. Call `__zenith_page()` → receive `{ html, expressions }`
137
+ 2. Set `container.innerHTML = html`
138
+ 3. Walk DOM once: `querySelectorAll('[data-zx-e], [data-zx-on-click], ...')`
139
+ 4. For each node with `data-zx-e`:
140
+ - Parse space-separated indices
141
+ - For each index: create `effect(() => node.textContent = expressions[index]())`
142
+ 5. For each node with `data-zx-on-*`:
143
+ - Extract event name from attribute suffix
144
+ - `node.addEventListener(eventName, expressions[index])`
145
+ 6. Store all effects and listeners in a cleanup registry
146
+
147
+ No recursive diffing. No re-render cycle. No component tree.
148
+
149
+ ---
150
+
151
+ ## 7. Cleanup Contract
152
+
153
+ The runtime exposes `cleanup()`:
154
+
155
+ - Disposes all active effects (clears subscriber sets)
156
+ - Removes all event listeners
157
+ - Clears the binding registry
158
+ - Leaves the DOM intact (caller decides whether to clear container)
159
+
160
+ Cleanup is deterministic — calling it twice is a no-op.
161
+
162
+ ---
163
+
164
+ ## 8. Public API Surface
165
+
166
+ Total exports (exhaustive):
167
+
168
+ ```js
169
+ export { signal } // Create reactive signal
170
+ export { effect } // Create reactive effect
171
+ export { mount } // Mount page module into container
172
+ export { cleanup } // Tear down all bindings
173
+ ```
174
+
175
+ Four functions. No more.
176
+
177
+ ---
178
+
179
+ ## 9. Alignment Verification
180
+
181
+ This contract is valid if and only if:
182
+
183
+ - [ ] `data-zx-e` indices match `__zenith_expr` array positions (from `BUNDLER_CONTRACT.md` §2)
184
+ - [ ] Expression functions are pre-bound at bundle time — runtime never resolves strings
185
+ - [ ] HMR footer is ignored by runtime (from `BUNDLER_CONTRACT.md` §7)
186
+ - [ ] No symbol beyond `__zenith_html`, `__zenith_expr`, `__zenith_page` is consumed (from `BUNDLER_CONTRACT.md` §1)
@@ -0,0 +1,88 @@
1
+ // ---------------------------------------------------------------------------
2
+ // cleanup.js — Zenith Runtime V0
3
+ // ---------------------------------------------------------------------------
4
+ // Deterministic teardown system.
5
+ //
6
+ // Tracks:
7
+ // - Active effect disposers
8
+ // - Active event listeners
9
+ //
10
+ // cleanup() removes everything.
11
+ // Calling cleanup() twice is a no-op.
12
+ // ---------------------------------------------------------------------------
13
+
14
+ import { resetGlobalSideEffects } from './zeneffect.js';
15
+
16
+ /** @type {function[]} */
17
+ const _disposers = [];
18
+
19
+ /** @type {{ element: Element, event: string, handler: function }[]} */
20
+ const _listeners = [];
21
+
22
+ let _cleaned = false;
23
+
24
+ /**
25
+ * Register an effect disposer for later cleanup.
26
+ *
27
+ * @param {function} dispose
28
+ */
29
+ export function _registerDisposer(dispose) {
30
+ _disposers.push(dispose);
31
+ _cleaned = false;
32
+ }
33
+
34
+ /**
35
+ * Register an event listener for later cleanup.
36
+ *
37
+ * @param {Element} element
38
+ * @param {string} event
39
+ * @param {function} handler
40
+ */
41
+ export function _registerListener(element, event, handler) {
42
+ _listeners.push({ element, event, handler });
43
+ _cleaned = false;
44
+ }
45
+
46
+ /**
47
+ * Tear down all active effects and event listeners.
48
+ *
49
+ * - Disposes all effects (clears subscriber sets)
50
+ * - Removes all event listeners
51
+ * - Clears registries
52
+ * - Idempotent: calling twice is a no-op
53
+ */
54
+ export function cleanup() {
55
+ // Global zenMount/zenEffect registrations can be created outside hydrate's
56
+ // disposer table (e.g. page-level component bootstraps). Even when cleanup
57
+ // has already run once, we still need to reset that scope deterministically.
58
+ if (_cleaned) {
59
+ resetGlobalSideEffects();
60
+ return;
61
+ }
62
+
63
+ // 1. Dispose all effects
64
+ for (let i = 0; i < _disposers.length; i++) {
65
+ _disposers[i]();
66
+ }
67
+ _disposers.length = 0;
68
+
69
+ // 2. Remove all event listeners
70
+ for (let i = 0; i < _listeners.length; i++) {
71
+ const { element, event, handler } = _listeners[i];
72
+ element.removeEventListener(event, handler);
73
+ }
74
+ _listeners.length = 0;
75
+
76
+ _cleaned = true;
77
+ }
78
+
79
+ /**
80
+ * Get counts for testing/debugging.
81
+ * @returns {{ effects: number, listeners: number }}
82
+ */
83
+ export function _getCounts() {
84
+ return {
85
+ effects: _disposers.length,
86
+ listeners: _listeners.length
87
+ };
88
+ }