@zenithbuild/core 0.1.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 (101) hide show
  1. package/.eslintignore +15 -0
  2. package/.gitattributes +2 -0
  3. package/.github/ISSUE_TEMPLATE/compiler-errors-for-invalid-state-declarations.md +25 -0
  4. package/.github/ISSUE_TEMPLATE/new_ticket.yaml +34 -0
  5. package/.github/pull_request_template.md +15 -0
  6. package/.github/workflows/discord-changelog.yml +141 -0
  7. package/.github/workflows/discord-notify.yml +242 -0
  8. package/.github/workflows/discord-version.yml +195 -0
  9. package/.prettierignore +13 -0
  10. package/.prettierrc +21 -0
  11. package/.zen.d.ts +15 -0
  12. package/LICENSE +21 -0
  13. package/README.md +55 -0
  14. package/app/components/Button.zen +46 -0
  15. package/app/components/Link.zen +11 -0
  16. package/app/favicon.ico +0 -0
  17. package/app/layouts/Main.zen +59 -0
  18. package/app/pages/about.zen +23 -0
  19. package/app/pages/blog/[id].zen +53 -0
  20. package/app/pages/blog/index.zen +32 -0
  21. package/app/pages/dynamic-dx.zen +712 -0
  22. package/app/pages/dynamic-primitives.zen +453 -0
  23. package/app/pages/index.zen +154 -0
  24. package/app/pages/navigation-demo.zen +229 -0
  25. package/app/pages/posts/[...slug].zen +61 -0
  26. package/app/pages/primitives-demo.zen +273 -0
  27. package/assets/logos/0E3B5DDD-605C-4839-BB2E-DFCA8ADC9604.PNG +0 -0
  28. package/assets/logos/760971E5-79A1-44F9-90B9-925DF30F4278.PNG +0 -0
  29. package/assets/logos/8A06ED80-9ED2-4689-BCBD-13B2E95EE8E4.JPG +0 -0
  30. package/assets/logos/C691FF58-ED13-4E8D-B6A3-02E835849340.PNG +0 -0
  31. package/assets/logos/C691FF58-ED13-4E8D-B6A3-02E835849340.svg +601 -0
  32. package/assets/logos/README.md +54 -0
  33. package/assets/logos/zen.icns +0 -0
  34. package/bun.lock +39 -0
  35. package/compiler/README.md +380 -0
  36. package/compiler/errors/compilerError.ts +24 -0
  37. package/compiler/finalize/finalizeOutput.ts +163 -0
  38. package/compiler/finalize/generateFinalBundle.ts +82 -0
  39. package/compiler/index.ts +44 -0
  40. package/compiler/ir/types.ts +83 -0
  41. package/compiler/legacy/binding.ts +254 -0
  42. package/compiler/legacy/bindings.ts +338 -0
  43. package/compiler/legacy/component-process.ts +1208 -0
  44. package/compiler/legacy/component.ts +301 -0
  45. package/compiler/legacy/event.ts +50 -0
  46. package/compiler/legacy/expression.ts +1149 -0
  47. package/compiler/legacy/mutation.ts +280 -0
  48. package/compiler/legacy/parse.ts +299 -0
  49. package/compiler/legacy/split.ts +608 -0
  50. package/compiler/legacy/types.ts +32 -0
  51. package/compiler/output/types.ts +34 -0
  52. package/compiler/parse/detectMapExpressions.ts +102 -0
  53. package/compiler/parse/parseScript.ts +22 -0
  54. package/compiler/parse/parseTemplate.ts +425 -0
  55. package/compiler/parse/parseZenFile.ts +66 -0
  56. package/compiler/parse/trackLoopContext.ts +82 -0
  57. package/compiler/runtime/dataExposure.ts +291 -0
  58. package/compiler/runtime/generateDOM.ts +144 -0
  59. package/compiler/runtime/generateHydrationBundle.ts +383 -0
  60. package/compiler/runtime/hydration.ts +309 -0
  61. package/compiler/runtime/navigation.ts +432 -0
  62. package/compiler/runtime/thinRuntime.ts +160 -0
  63. package/compiler/runtime/transformIR.ts +256 -0
  64. package/compiler/runtime/wrapExpression.ts +84 -0
  65. package/compiler/runtime/wrapExpressionWithLoop.ts +77 -0
  66. package/compiler/spa-build.ts +1000 -0
  67. package/compiler/test/validate-test.ts +104 -0
  68. package/compiler/transform/generateBindings.ts +47 -0
  69. package/compiler/transform/generateHTML.ts +28 -0
  70. package/compiler/transform/transformNode.ts +126 -0
  71. package/compiler/transform/transformTemplate.ts +38 -0
  72. package/compiler/validate/validateExpressions.ts +168 -0
  73. package/core/index.ts +135 -0
  74. package/core/lifecycle/index.ts +49 -0
  75. package/core/lifecycle/zen-mount.ts +182 -0
  76. package/core/lifecycle/zen-unmount.ts +88 -0
  77. package/core/reactivity/index.ts +54 -0
  78. package/core/reactivity/tracking.ts +167 -0
  79. package/core/reactivity/zen-batch.ts +57 -0
  80. package/core/reactivity/zen-effect.ts +139 -0
  81. package/core/reactivity/zen-memo.ts +146 -0
  82. package/core/reactivity/zen-ref.ts +52 -0
  83. package/core/reactivity/zen-signal.ts +121 -0
  84. package/core/reactivity/zen-state.ts +180 -0
  85. package/core/reactivity/zen-untrack.ts +44 -0
  86. package/docs/COMMENTS.md +111 -0
  87. package/docs/COMMITS.md +36 -0
  88. package/docs/CONTRIBUTING.md +116 -0
  89. package/docs/STYLEGUIDE.md +62 -0
  90. package/package.json +44 -0
  91. package/router/index.ts +76 -0
  92. package/router/manifest.ts +314 -0
  93. package/router/navigation/ZenLink.zen +231 -0
  94. package/router/navigation/index.ts +78 -0
  95. package/router/navigation/zen-link.ts +584 -0
  96. package/router/runtime.ts +458 -0
  97. package/router/types.ts +168 -0
  98. package/runtime/build.ts +17 -0
  99. package/runtime/serve.ts +93 -0
  100. package/scripts/webhook-proxy.ts +213 -0
  101. package/tsconfig.json +28 -0
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Generate Final Bundle
3
+ *
4
+ * Phase 8/9/10: Generate final browser-ready bundle
5
+ *
6
+ * Combines:
7
+ * - Compiled HTML
8
+ * - Runtime JS
9
+ * - Expression functions
10
+ * - Event bindings
11
+ * - Style injection
12
+ */
13
+
14
+ import type { FinalizedOutput } from './finalizeOutput'
15
+ import type { RuntimeCode } from '../runtime/transformIR'
16
+
17
+ /**
18
+ * Generate final bundle code
19
+ *
20
+ * This is the complete JavaScript bundle that will execute in the browser.
21
+ * All expressions are pre-compiled - no template parsing at runtime.
22
+ */
23
+ export function generateFinalBundle(finalized: FinalizedOutput): string {
24
+ return `// Zenith Compiled Bundle (Phase 8/9/10)
25
+ // Generated at compile time - no .zen parsing in browser
26
+ // All expressions are pre-compiled - deterministic output
27
+
28
+ ${finalized.js}
29
+
30
+ // Bundle complete - ready for browser execution
31
+ `
32
+ }
33
+
34
+ /**
35
+ * Generate HTML with inline script
36
+ */
37
+ export function generateHTMLWithScript(
38
+ html: string,
39
+ jsBundle: string,
40
+ styles: string[]
41
+ ): string {
42
+ // Inject styles as <style> tags
43
+ const styleTags = styles.map(style => `<style>${escapeHTML(style)}</style>`).join('\n')
44
+
45
+ // Inject JS bundle as inline script
46
+ const scriptTag = `<script>${jsBundle}</script>`
47
+
48
+ // Find </head> or <body> to inject styles
49
+ // Find </body> to inject script
50
+ let result = html
51
+
52
+ if (styleTags) {
53
+ if (result.includes('</head>')) {
54
+ result = result.replace('</head>', `${styleTags}\n</head>`)
55
+ } else if (result.includes('<body')) {
56
+ result = result.replace('<body', `${styleTags}\n<body`)
57
+ }
58
+ }
59
+
60
+ if (scriptTag) {
61
+ if (result.includes('</body>')) {
62
+ result = result.replace('</body>', `${scriptTag}\n</body>`)
63
+ } else {
64
+ result += scriptTag
65
+ }
66
+ }
67
+
68
+ return result
69
+ }
70
+
71
+ /**
72
+ * Escape HTML for safe embedding
73
+ */
74
+ function escapeHTML(str: string): string {
75
+ return str
76
+ .replace(/&/g, '&amp;')
77
+ .replace(/</g, '&lt;')
78
+ .replace(/>/g, '&gt;')
79
+ .replace(/"/g, '&quot;')
80
+ .replace(/'/g, '&#039;')
81
+ }
82
+
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Zenith Compiler
3
+ *
4
+ * Phase 1: Parse & Extract
5
+ * Phase 2: Transform IR → Static HTML + Runtime Bindings
6
+ * Phase 8/9/10: Finalize Output with Validation
7
+ *
8
+ * This compiler observes .zen files, extracts their structure,
9
+ * transforms them into static HTML with explicit bindings,
10
+ * and validates/finalizes output for browser execution.
11
+ */
12
+
13
+ import { parseZenFile } from './parse/parseZenFile'
14
+ import { transformTemplate } from './transform/transformTemplate'
15
+ import { finalizeOutputOrThrow } from './finalize/finalizeOutput'
16
+ import type { ZenIR } from './ir/types'
17
+ import type { CompiledTemplate } from './output/types'
18
+ import type { FinalizedOutput } from './finalize/finalizeOutput'
19
+
20
+ /**
21
+ * Compile a .zen file into IR and CompiledTemplate
22
+ *
23
+ * Phase 1: Parses and extracts structure
24
+ * Phase 2: Transforms IR into static HTML with bindings
25
+ * Phase 8/9/10: Validates and finalizes output
26
+ */
27
+ export function compileZen(filePath: string): {
28
+ ir: ZenIR
29
+ compiled: CompiledTemplate
30
+ finalized?: FinalizedOutput
31
+ } {
32
+ const ir = parseZenFile(filePath)
33
+ const compiled = transformTemplate(ir)
34
+
35
+ // Phase 8/9/10: Finalize output with validation
36
+ // This ensures build fails on invalid expressions
37
+ try {
38
+ const finalized = finalizeOutputOrThrow(ir, compiled)
39
+ return { ir, compiled, finalized }
40
+ } catch (error: any) {
41
+ // Re-throw with context
42
+ throw new Error(`Failed to finalize output for ${filePath}:\n${error.message}`)
43
+ }
44
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Zenith Intermediate Representation (IR)
3
+ *
4
+ * Phase 1: Parse & Extract
5
+ * This IR represents the parsed structure of a .zen file
6
+ * without any runtime execution or transformation.
7
+ */
8
+
9
+ export type ZenIR = {
10
+ filePath: string
11
+ template: TemplateIR
12
+ script: ScriptIR | null
13
+ styles: StyleIR[]
14
+ }
15
+
16
+ export type TemplateIR = {
17
+ raw: string
18
+ nodes: TemplateNode[]
19
+ expressions: ExpressionIR[]
20
+ }
21
+
22
+ export type TemplateNode =
23
+ | ElementNode
24
+ | TextNode
25
+ | ExpressionNode
26
+
27
+ export type ElementNode = {
28
+ type: 'element'
29
+ tag: string
30
+ attributes: AttributeIR[]
31
+ children: TemplateNode[]
32
+ location: SourceLocation
33
+ loopContext?: LoopContext // Phase 7: Inherited loop context from parent map expressions
34
+ }
35
+
36
+ export type TextNode = {
37
+ type: 'text'
38
+ value: string
39
+ location: SourceLocation
40
+ }
41
+
42
+ export type ExpressionNode = {
43
+ type: 'expression'
44
+ expression: string
45
+ location: SourceLocation
46
+ loopContext?: LoopContext // Phase 7: Loop context for expressions inside map iterations
47
+ }
48
+
49
+ export type AttributeIR = {
50
+ name: string
51
+ value: string | ExpressionIR
52
+ location: SourceLocation
53
+ loopContext?: LoopContext // Phase 7: Loop context for expressions inside map iterations
54
+ }
55
+
56
+ /**
57
+ * Loop context for expressions inside map iterations
58
+ * Phase 7: Tracks loop variables (e.g., todo, index) for expressions inside .map() calls
59
+ */
60
+ export type LoopContext = {
61
+ variables: string[] // e.g., ['todo', 'index'] for todoItems.map((todo, index) => ...)
62
+ mapSource?: string // The array being mapped, e.g., 'todoItems'
63
+ }
64
+
65
+ export type ExpressionIR = {
66
+ id: string
67
+ code: string
68
+ location: SourceLocation
69
+ }
70
+
71
+ export type ScriptIR = {
72
+ raw: string
73
+ }
74
+
75
+ export type StyleIR = {
76
+ raw: string
77
+ }
78
+
79
+ export type SourceLocation = {
80
+ line: number
81
+ column: number
82
+ }
83
+
@@ -0,0 +1,254 @@
1
+ // compiler/binding.ts
2
+ // Phase 2: Reactive text bindings runtime generator
3
+ // Generates code to update DOM when state values change
4
+ // Extended to support component instance-scoped state
5
+
6
+ import type { StateBinding } from "./types"
7
+
8
+ // Extract instance ID and base state name from instance-scoped state name
9
+ // e.g., "__zen_comp_0_clicks" -> { instanceId: "comp-0", baseState: "clicks" }
10
+ function parseInstanceState(stateName: string): { instanceId: string; baseState: string } | null {
11
+ const match = stateName.match(/^__zen_comp_(\d+)_(.+)$/);
12
+ if (match) {
13
+ const instanceNum = match[1];
14
+ const baseState = match[2];
15
+ return { instanceId: `comp-${instanceNum}`, baseState };
16
+ }
17
+ return null;
18
+ }
19
+
20
+ export function generateBindingRuntime(
21
+ stateBindings: StateBinding[],
22
+ stateDeclarations: Map<string, string>
23
+ ): string {
24
+ if (stateBindings.length === 0 && stateDeclarations.size === 0) {
25
+ return "";
26
+ }
27
+
28
+ const stateNames = Array.from(stateDeclarations.keys());
29
+
30
+ // Separate global state and instance-scoped state
31
+ const globalStates = new Set<string>();
32
+ const instanceStates = new Map<string, Map<string, string[]>>(); // instanceId -> baseState -> fullStateNames
33
+
34
+ for (const stateName of stateNames) {
35
+ const instanceInfo = parseInstanceState(stateName);
36
+ if (instanceInfo) {
37
+ if (!instanceStates.has(instanceInfo.instanceId)) {
38
+ instanceStates.set(instanceInfo.instanceId, new Map());
39
+ }
40
+ const instanceMap = instanceStates.get(instanceInfo.instanceId)!;
41
+ if (!instanceMap.has(instanceInfo.baseState)) {
42
+ instanceMap.set(instanceInfo.baseState, []);
43
+ }
44
+ instanceMap.get(instanceInfo.baseState)!.push(stateName);
45
+ } else {
46
+ globalStates.add(stateName);
47
+ }
48
+ }
49
+
50
+ // Generate binding update map - collect all nodes for each state
51
+ // Order is preserved: bindings are processed in the order they appear in stateBindings array
52
+ // (which matches DOM traversal order from compilation)
53
+ const bindingMapEntries: string[] = [];
54
+ const bindingMap = new Map<string, string[]>();
55
+
56
+ // Iterate over stateBindings array to preserve compile-time order
57
+ // Maps preserve insertion order, so this maintains deterministic ordering
58
+ for (const stateBinding of stateBindings) {
59
+ if (!bindingMap.has(stateBinding.stateName)) {
60
+ bindingMap.set(stateBinding.stateName, []);
61
+ }
62
+ const selectors = bindingMap.get(stateBinding.stateName)!;
63
+ // Push bindings in the order they appear in stateBinding.bindings array
64
+ // This preserves the DOM traversal order from compilation
65
+ for (const binding of stateBinding.bindings) {
66
+ const bindId = `bind-${binding.nodeIndex}`;
67
+
68
+ // Check if this is an instance-scoped binding
69
+ const instanceInfo = parseInstanceState(stateBinding.stateName);
70
+ if (instanceInfo) {
71
+ // Scope selector to component instance root
72
+ selectors.push(`[data-zen-instance="${instanceInfo.instanceId}"] span[data-zen-bind="${stateBinding.stateName}"][data-zen-bind-id="${bindId}"]`);
73
+ } else {
74
+ // Global binding
75
+ selectors.push(`span[data-zen-bind="${stateBinding.stateName}"][data-zen-bind-id="${bindId}"]`);
76
+ }
77
+ }
78
+ }
79
+
80
+ // Generate update functions for each binding
81
+ // Each function captures a DOM node reference directly
82
+ // Map.entries() preserves insertion order, maintaining compile-time binding order
83
+ for (const [stateName, selectors] of bindingMap.entries()) {
84
+ if (selectors.length > 0) {
85
+ // Generate an array of update functions, each capturing a node reference
86
+ const updateFunctions = selectors.map((selector, index) => {
87
+ const escapedSelector = selector.replace(/"/g, '\\"');
88
+ // Create a function that captures the node and updates it
89
+ // We'll query the node once during init, then the function captures it
90
+ return `(function() {
91
+ const node = document.querySelector("${escapedSelector}");
92
+ return function(value) {
93
+ if (node) node.textContent = String(value);
94
+ };
95
+ })()`;
96
+ }).join(",\n ");
97
+
98
+ bindingMapEntries.push(
99
+ ` "${stateName}": [\n ${updateFunctions}\n ]`
100
+ );
101
+ }
102
+ }
103
+
104
+ const bindingMapCode = bindingMapEntries.length > 0
105
+ ? `__zen_bindings = {\n${bindingMapEntries.join(",\n")}\n };`
106
+ : `__zen_bindings = {};`;
107
+
108
+ // Generate global state initialization code
109
+ const globalStateInitCode = Array.from(globalStates).map(name => {
110
+ const initialValue = stateDeclarations.get(name) || "undefined";
111
+ return `
112
+ // Initialize global state: ${name}
113
+ (function() {
114
+ let __zen_${name} = ${initialValue};
115
+ Object.defineProperty(window, "${name}", {
116
+ get: function() { return __zen_${name}; },
117
+ set: function(value) {
118
+ __zen_${name} = value;
119
+ // Immediately trigger synchronous updates - no batching, no async
120
+ __zen_update_bindings("${name}", value);
121
+ // Also trigger dynamic expression updates
122
+ if (window.__zen_trigger_expression_updates) {
123
+ window.__zen_trigger_expression_updates("${name}");
124
+ }
125
+ },
126
+ enumerable: true,
127
+ configurable: true
128
+ });
129
+ })();`;
130
+ }).join("");
131
+
132
+ // Generate instance state initialization code
133
+ const instanceStateInitCode: string[] = [];
134
+ for (const [instanceId, baseStateMap] of instanceStates.entries()) {
135
+ const safeInstanceId = instanceId.replace(/-/g, '_');
136
+
137
+ for (const [baseState, fullStateNames] of baseStateMap.entries()) {
138
+ // For each instance, create a state proxy scoped to that instance
139
+ // Only process the first fullStateName (they should all have the same instance)
140
+ const fullStateName = fullStateNames[0];
141
+ const initialValue = stateDeclarations.get(fullStateName) || "undefined";
142
+
143
+ instanceStateInitCode.push(`
144
+ // Initialize instance-scoped state: ${fullStateName} (${instanceId}.${baseState})
145
+ (function() {
146
+ const instanceRoot = document.querySelector('[data-zen-instance="${instanceId}"]');
147
+ if (!instanceRoot) {
148
+ console.warn('[Zenith] Component instance "${instanceId}" not found in DOM');
149
+ return;
150
+ }
151
+
152
+ let __zen_${safeInstanceId}_${baseState} = ${initialValue};
153
+
154
+ // Create instance-scoped state proxy
155
+ const instanceState = new Proxy({}, {
156
+ get(target, prop) {
157
+ if (prop === '${baseState}') {
158
+ return __zen_${safeInstanceId}_${baseState};
159
+ }
160
+ return undefined;
161
+ },
162
+ set(target, prop, value) {
163
+ if (prop === '${baseState}') {
164
+ __zen_${safeInstanceId}_${baseState} = value;
165
+ // Trigger updates only for bindings within this instance
166
+ __zen_update_bindings("${fullStateName}", value);
167
+ return true;
168
+ }
169
+ return false;
170
+ }
171
+ });
172
+
173
+ // Store instance state on window for component access
174
+ if (!window.__zen_instances) {
175
+ window.__zen_instances = {};
176
+ }
177
+ if (!window.__zen_instances["${instanceId}"]) {
178
+ window.__zen_instances["${instanceId}"] = instanceState;
179
+ } else {
180
+ window.__zen_instances["${instanceId}"].${baseState} = __zen_${safeInstanceId}_${baseState};
181
+ }
182
+
183
+ // Create global property accessor for instance-scoped state
184
+ Object.defineProperty(window, "${fullStateName}", {
185
+ get: function() { return __zen_${safeInstanceId}_${baseState}; },
186
+ set: function(value) {
187
+ __zen_${safeInstanceId}_${baseState} = value;
188
+ // Update instance state object
189
+ if (window.__zen_instances && window.__zen_instances["${instanceId}"]) {
190
+ window.__zen_instances["${instanceId}"].${baseState} = value;
191
+ }
192
+ __zen_update_bindings("${fullStateName}", value);
193
+ // Also trigger attribute binding updates for this instance
194
+ if (window.__zen_update_attribute_bindings) {
195
+ window.__zen_update_attribute_bindings("${instanceId}");
196
+ }
197
+ // Also trigger dynamic expression updates
198
+ if (window.__zen_trigger_expression_updates) {
199
+ window.__zen_trigger_expression_updates("${fullStateName}");
200
+ }
201
+ },
202
+ enumerable: true,
203
+ configurable: true
204
+ });
205
+ })();`);
206
+ }
207
+ }
208
+
209
+ // Generate initialization call (after DOM is ready)
210
+ const initBindingsCode = stateNames.map(name => {
211
+ return ` __zen_update_bindings("${name}", ${name});`;
212
+ }).join("\n");
213
+
214
+ return `
215
+ // Phase 2: Reactive text bindings runtime (with component instance support)
216
+ (function() {
217
+ let __zen_bindings = {};
218
+
219
+ // Update function for a specific state
220
+ // Calls all registered update functions for the given state property
221
+ // Executes synchronously, immediately, in compile-order (array order)
222
+ // No batching, no async scheduling, no reordering
223
+ function __zen_update_bindings(stateName, value) {
224
+ const updaters = __zen_bindings[stateName];
225
+ if (updaters) {
226
+ // Execute update functions in deterministic order (compile-time order)
227
+ // forEach executes synchronously, preserving array order
228
+ updaters.forEach(updateFn => {
229
+ if (typeof updateFn === 'function') {
230
+ updateFn(value);
231
+ }
232
+ });
233
+ }
234
+ }
235
+
236
+ ${globalStateInitCode}
237
+ ${instanceStateInitCode.join('')}
238
+
239
+ // Initialize binding map and bindings after DOM is ready
240
+ function __zen_init_bindings() {
241
+ ${bindingMapCode.split('\n').map(line => ' ' + line).join('\n')}
242
+ ${initBindingsCode.split('\n').map(line => ' ' + line).join('\n')}
243
+ }
244
+
245
+ // Initialize bindings when DOM is ready (scripts are deferred, so DOM should be ready)
246
+ if (document.readyState === 'loading') {
247
+ document.addEventListener('DOMContentLoaded', __zen_init_bindings);
248
+ } else {
249
+ __zen_init_bindings();
250
+ }
251
+ })();
252
+ `;
253
+ }
254
+