@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,338 @@
1
+ // compiler/bindings.ts
2
+ // Phase 2: Object-style dynamic attribute bindings with quoted expressions
3
+ // Supports :class and :value attributes with synchronous, deterministic updates
4
+
5
+ export function generateAttributeBindingRuntime(bindings: Array<{ type: 'class' | 'value'; expression: string }>): string {
6
+ if (bindings.length === 0) {
7
+ return ''; // No bindings, no runtime needed
8
+ }
9
+
10
+ // Generate unique expression IDs for tracking
11
+ const expressionIds = bindings.map((_, i) => `expr_${i}`);
12
+ const expressionsStr = JSON.stringify(bindings.map(b => b.expression));
13
+ const typesStr = JSON.stringify(bindings.map(b => b.type));
14
+
15
+ return `
16
+ // Phase 2: Attribute binding runtime - synchronous, deterministic updates
17
+ // Note: 'use strict' is omitted to allow 'with' statement for expression evaluation
18
+ (function() {
19
+
20
+ // Store all binding elements and their expressions
21
+ const bindingExpressions = ${expressionsStr};
22
+ const bindingTypes = ${typesStr};
23
+ const bindingElements = [];
24
+
25
+ // Reactive state proxy - tracks property access and updates DOM synchronously
26
+ // Initialize with empty object - properties will be added dynamically
27
+ const stateTarget = {};
28
+ const stateProxy = new Proxy(stateTarget, {
29
+ set(target, prop, value) {
30
+ const oldValue = target[prop];
31
+ target[prop] = value;
32
+
33
+ // Log state change for debugging
34
+ // console.log('[Zenith] State change:', prop, '=', value);
35
+
36
+ // Synchronously update all affected bindings
37
+ bindingElements.forEach(binding => {
38
+ try {
39
+ // Re-evaluate expression in context of current state
40
+ // Pass target (the state object) as parameter to the evaluator function
41
+ // If this binding has instance state, merge it with global state
42
+ const mergedState = binding.instanceState
43
+ ? Object.assign({}, target, binding.instanceState)
44
+ : target;
45
+ const result = binding.fn(mergedState);
46
+
47
+ if (binding.type === 'class') {
48
+ updateClassBinding(binding.el, result);
49
+ // console.log('[Zenith] Updated :class binding for element:', binding.el, 'result:', result);
50
+ } else if (binding.type === 'value') {
51
+ updateValueBinding(binding.el, result);
52
+ // console.log('[Zenith] Updated :value binding for element:', binding.el, 'result:', result);
53
+ }
54
+ } catch (e) {
55
+ // Log errors for debugging (Phase 2: graceful degradation)
56
+ console.warn('[Zenith] Binding evaluation error:', e, 'for expression:', binding.expression);
57
+ }
58
+ });
59
+
60
+ return true;
61
+ },
62
+ get(target, prop) {
63
+ // Return undefined for missing properties (don't throw errors)
64
+ return target[prop];
65
+ }
66
+ });
67
+
68
+ // Make stateProxy available globally as 'state'
69
+ window.state = stateProxy;
70
+
71
+ // Function to update attribute bindings for a specific component instance
72
+ // Called when instance-scoped state changes
73
+ function updateAttributeBindingsForInstance(instanceId) {
74
+ const instanceRoot = document.querySelector('[data-zen-instance="' + instanceId + '"]');
75
+ if (!instanceRoot) return;
76
+
77
+ // Update all bindings within this instance
78
+ bindingElements.forEach(binding => {
79
+ // Check if this binding belongs to the instance
80
+ const bindingInstanceRoot = findInstanceRoot(binding.el);
81
+ if (bindingInstanceRoot === instanceRoot) {
82
+ try {
83
+ const instanceState = getInstanceStateForElement(binding.el);
84
+ const mergedState = instanceState
85
+ ? Object.assign({}, stateProxy, instanceState)
86
+ : stateProxy;
87
+ const result = binding.fn(mergedState);
88
+
89
+ if (binding.type === 'class') {
90
+ updateClassBinding(binding.el, result);
91
+ } else if (binding.type === 'value') {
92
+ updateValueBinding(binding.el, result);
93
+ }
94
+ } catch (e) {
95
+ console.warn('[Zenith] Attribute binding evaluation error:', e, 'for expression:', binding.expression);
96
+ }
97
+ }
98
+ });
99
+ }
100
+
101
+ // Expose update function globally so text binding runtime can trigger it
102
+ window.__zen_update_attribute_bindings = updateAttributeBindingsForInstance;
103
+
104
+ // Helper: Evaluate class binding expression
105
+ // Handles: objects, strings, empty objects, falsy values
106
+ // Preserves existing static classes from the class attribute
107
+ function updateClassBinding(el, result) {
108
+ // Store static classes on first update (from class attribute)
109
+ if (!el._zenStaticClasses) {
110
+ const staticClasses = el.getAttribute('class') || '';
111
+ el._zenStaticClasses = staticClasses.split(/\\s+/).filter(c => c);
112
+ }
113
+ const staticClassList = el._zenStaticClasses;
114
+
115
+ if (typeof result === 'string') {
116
+ // String value: treat as raw class names, merge with static classes
117
+ const dynamicClasses = result.split(/\\s+/).filter(c => c);
118
+ el.className = [...staticClassList, ...dynamicClasses].join(' ').trim();
119
+ } else if (result && typeof result === 'object' && !Array.isArray(result)) {
120
+ // Object value: extract keys with true values
121
+ const dynamicClasses = [];
122
+ for (const key in result) {
123
+ if (result.hasOwnProperty(key) && result[key] === true) {
124
+ dynamicClasses.push(key);
125
+ }
126
+ }
127
+ // Merge static and dynamic classes
128
+ el.className = [...staticClassList, ...dynamicClasses].join(' ').trim();
129
+ } else {
130
+ // Falsy, null, undefined, or non-object: keep only static classes
131
+ el.className = staticClassList.join(' ').trim();
132
+ }
133
+ }
134
+
135
+ // Helper: Evaluate value binding expression
136
+ // Handles: primitives, falsy values
137
+ function updateValueBinding(el, result) {
138
+ if (result === null || result === undefined) {
139
+ el.value = '';
140
+ } else {
141
+ el.value = String(result);
142
+ }
143
+ }
144
+
145
+ // Helper: Safely evaluate expression string
146
+ // Creates a function that evaluates the expression with state properties in scope
147
+ // Supports both global state (via state object) and instance-scoped state (via window)
148
+ function createEvaluator(expression) {
149
+ // Trim whitespace from expression
150
+ let trimmed = expression.trim();
151
+
152
+ // Check if expression is a quoted string (single or double quotes)
153
+ // If so, unquote it and check if it's a simple identifier
154
+ let isSimpleIdentifier = false;
155
+ let evalExpression = trimmed;
156
+
157
+ // Handle quoted strings: "username" or 'username' -> username
158
+ if ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
159
+ (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
160
+ // Extract the unquoted value
161
+ const unquoted = trimmed.slice(1, -1);
162
+ // Check if unquoted value is a simple identifier
163
+ if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(unquoted)) {
164
+ isSimpleIdentifier = true;
165
+ evalExpression = unquoted; // Use unquoted identifier for evaluation
166
+ }
167
+ // Otherwise, treat as string literal (e.g., "'static-class'" -> "static-class")
168
+ } else {
169
+ // Not quoted: check if it's a simple identifier
170
+ isSimpleIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(trimmed);
171
+ if (isSimpleIdentifier) {
172
+ evalExpression = trimmed; // Already unquoted identifier
173
+ }
174
+ }
175
+
176
+ try {
177
+ // Create a function that evaluates the expression
178
+ // The expression is written as if state properties are directly accessible
179
+ // We use Function constructor with 'with' statement to make state properties available
180
+ // Note: 'with' is deprecated but necessary for this use case in non-strict mode
181
+ return function(state) {
182
+ try {
183
+ // Use window.__zen_eval_expr for consistent expression evaluation
184
+ // This handles window properties (state variables) correctly
185
+ if (typeof window !== 'undefined' && window.__zen_eval_expr) {
186
+ return window.__zen_eval_expr(evalExpression);
187
+ }
188
+
189
+ // Fallback: Merge state with window to access instance-scoped state variables
190
+ // This allows expressions to reference both global state and instance-scoped state
191
+ // Copy window properties that look like instance-scoped state to the merged context
192
+ const mergedContext = Object.assign({}, state);
193
+ for (const key in window) {
194
+ if (key.startsWith('__zen_comp_') && !(key in mergedContext)) {
195
+ mergedContext[key] = window[key];
196
+ }
197
+ }
198
+
199
+ // Use Function constructor to create evaluator
200
+ // The 'with' statement makes state properties and instance-scoped state available as variables
201
+ // This allows expressions like "{ active: isActive }" where isActive refers to state.isActive
202
+ // or instance-scoped variables like __zen_comp_0_clicks
203
+ const func = new Function('state',
204
+ 'try {' +
205
+ ' with (state) {' +
206
+ ' return (' + evalExpression + ');' +
207
+ ' }' +
208
+ '} catch (e) {' +
209
+ ' console.warn("[Zenith] Expression evaluation error:", ' + JSON.stringify(trimmed) + ', e);' +
210
+ ' return null;' +
211
+ '}'
212
+ );
213
+ const result = func(mergedContext);
214
+ // console.log('[Zenith] Evaluated expression:', trimmed, 'result:', result, 'state:', state);
215
+ return result;
216
+ } catch (e) {
217
+ // Last resort: return safe default
218
+ console.warn('Expression evaluation error:', trimmed, e);
219
+ const bindingIndex = bindingExpressions.indexOf(expression);
220
+ return bindingIndex >= 0 && bindingTypes[bindingIndex] === 'class' ? {} : '';
221
+ }
222
+ };
223
+ } catch (e) {
224
+ // If expression is invalid, return a function that returns empty string/object
225
+ console.warn('Invalid binding expression:', expression, e);
226
+ const bindingIndex = bindingExpressions.indexOf(expression);
227
+ return function() {
228
+ return bindingIndex >= 0 && bindingTypes[bindingIndex] === 'class' ? {} : '';
229
+ };
230
+ }
231
+ }
232
+
233
+ // Helper: Find component instance root for an element
234
+ function findInstanceRoot(el) {
235
+ let current = el;
236
+ while (current) {
237
+ if (current.hasAttribute && current.hasAttribute('data-zen-instance')) {
238
+ return current;
239
+ }
240
+ current = current.parentElement;
241
+ }
242
+ return null;
243
+ }
244
+
245
+ // Helper: Get instance-scoped state for an element
246
+ function getInstanceStateForElement(el) {
247
+ const instanceRoot = findInstanceRoot(el);
248
+ if (instanceRoot) {
249
+ const instanceId = instanceRoot.getAttribute('data-zen-instance');
250
+ if (instanceId && window.__zen_instances && window.__zen_instances[instanceId]) {
251
+ return window.__zen_instances[instanceId];
252
+ }
253
+ }
254
+ return null;
255
+ }
256
+
257
+ // Enhanced evaluator that supports both global and instance-scoped state
258
+ function createEnhancedEvaluator(expression, instanceState) {
259
+ const baseEvaluator = createEvaluator(expression);
260
+ return function(state) {
261
+ // Merge global state and instance state
262
+ const mergedState = Object.assign({}, state);
263
+ if (instanceState) {
264
+ Object.assign(mergedState, instanceState);
265
+ }
266
+ // Also check window for instance-scoped state variables (e.g., __zen_comp_0_clicks)
267
+ // These are set up by the binding runtime
268
+ return baseEvaluator(mergedState);
269
+ };
270
+ }
271
+
272
+ // Initialize bindings after DOM is ready
273
+ function initializeBindings() {
274
+ // console.log('[Zenith] Initializing attribute bindings...');
275
+
276
+ // Find all elements with data-zen-class or data-zen-value attributes
277
+ const classElements = document.querySelectorAll('[data-zen-class]');
278
+ const valueElements = document.querySelectorAll('[data-zen-value]');
279
+
280
+ // console.log('[Zenith] Found', classElements.length, ':class bindings and', valueElements.length, ':value bindings');
281
+
282
+ // Process :class bindings
283
+ classElements.forEach((el) => {
284
+ const expression = el.getAttribute('data-zen-class');
285
+ if (expression) {
286
+ // console.log('[Zenith] Setting up :class binding:', expression, 'for element:', el);
287
+ const instanceState = getInstanceStateForElement(el);
288
+ const fn = createEnhancedEvaluator(expression, instanceState);
289
+
290
+ // Use merged state for initial evaluation
291
+ const mergedState = Object.assign({}, stateProxy);
292
+ if (instanceState) {
293
+ Object.assign(mergedState, instanceState);
294
+ }
295
+ const result = fn(mergedState);
296
+ updateClassBinding(el, result);
297
+ bindingElements.push({ el: el, type: 'class', expression, fn, instanceState });
298
+ // console.log('[Zenith] Initial :class result:', result, 'applied classes:', el.className);
299
+ }
300
+ });
301
+
302
+ // Process :value bindings
303
+ valueElements.forEach((el) => {
304
+ const expression = el.getAttribute('data-zen-value');
305
+ if (expression) {
306
+ // console.log('[Zenith] Setting up :value binding:', expression, 'for element:', el);
307
+ const instanceState = getInstanceStateForElement(el);
308
+ const fn = createEnhancedEvaluator(expression, instanceState);
309
+
310
+ // Use merged state for initial evaluation
311
+ const mergedState = Object.assign({}, stateProxy);
312
+ if (instanceState) {
313
+ Object.assign(mergedState, instanceState);
314
+ }
315
+ const result = fn(mergedState);
316
+ updateValueBinding(el, result);
317
+ bindingElements.push({ el: el, type: 'value', expression, fn, instanceState });
318
+ // console.log('[Zenith] Initial :value result:', result, 'applied value:', el.value);
319
+ }
320
+ });
321
+
322
+ // Instance state proxies are set up by the text binding runtime
323
+ // Attribute binding updates are triggered via window.__zen_update_attribute_bindings
324
+ // when instance-scoped state changes
325
+
326
+ // console.log('[Zenith] Initialized', bindingElements.length, 'bindings. State object:', stateProxy);
327
+ }
328
+
329
+ // Initialize when DOM is ready
330
+ if (document.readyState === 'loading') {
331
+ document.addEventListener('DOMContentLoaded', initializeBindings);
332
+ } else {
333
+ initializeBindings();
334
+ }
335
+ })();
336
+ `;
337
+ }
338
+