@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,291 @@
1
+ /**
2
+ * Explicit Data Exposure Analysis
3
+ *
4
+ * Phase 6: Analyzes expressions to detect data dependencies and ensure
5
+ * all data references are explicit (loader, props, stores) rather than implicit globals
6
+ */
7
+
8
+ import type { ExpressionIR } from '../ir/types'
9
+ import { CompilerError } from '../errors/compilerError'
10
+
11
+ /**
12
+ * Data dependency information for an expression
13
+ */
14
+ export interface ExpressionDataDependencies {
15
+ expressionId: string
16
+ usesLoaderData: boolean
17
+ usesProps: boolean
18
+ usesStores: boolean
19
+ usesState: boolean
20
+ loaderProperties: string[] // e.g., ['user', 'user.name']
21
+ propNames: string[] // e.g., ['title', 'showWelcome']
22
+ storeNames: string[] // e.g., ['cart', 'notifications']
23
+ stateProperties: string[] // e.g., ['count', 'isLoading']
24
+ }
25
+
26
+ /**
27
+ * Analyze an expression to detect its data dependencies
28
+ *
29
+ * This is a simple heuristic-based analyzer that looks for patterns like:
30
+ * - user.name, user.email → loader data
31
+ * - props.title, props.showWelcome → props
32
+ * - stores.cart, stores.notifications → stores
33
+ * - count, isLoading → state (top-level properties)
34
+ */
35
+ export function analyzeExpressionDependencies(
36
+ expr: ExpressionIR,
37
+ declaredLoaderProps: string[] = [],
38
+ declaredProps: string[] = [],
39
+ declaredStores: string[] = []
40
+ ): ExpressionDataDependencies {
41
+ const { id, code } = expr
42
+
43
+ const dependencies: ExpressionDataDependencies = {
44
+ expressionId: id,
45
+ usesLoaderData: false,
46
+ usesProps: false,
47
+ usesStores: false,
48
+ usesState: false,
49
+ loaderProperties: [],
50
+ propNames: [],
51
+ storeNames: [],
52
+ stateProperties: []
53
+ }
54
+
55
+ // Simple pattern matching (for Phase 6 - can be enhanced with proper AST parsing later)
56
+
57
+ // Check for loader data references (loaderData.property or direct property access)
58
+ // We assume properties not starting with props/stores/state are loader data
59
+ const loaderPattern = /\b(loaderData\.(\w+(?:\.\w+)*)|(?<!props\.|stores\.|state\.)(\w+)\.(\w+))/g
60
+ let match
61
+
62
+ // Check for explicit loaderData references
63
+ if (/loaderData\./.test(code)) {
64
+ dependencies.usesLoaderData = true
65
+ while ((match = loaderPattern.exec(code)) !== null) {
66
+ if (match[1]?.startsWith('loaderData.')) {
67
+ const propPath = match[1].replace('loaderData.', '')
68
+ if (!dependencies.loaderProperties.includes(propPath)) {
69
+ dependencies.loaderProperties.push(propPath)
70
+ }
71
+ }
72
+ }
73
+ }
74
+
75
+ // Check for props references
76
+ const propsPattern = /\bprops\.(\w+)(?:\.(\w+))*/g
77
+ if (/props\./.test(code)) {
78
+ dependencies.usesProps = true
79
+ while ((match = propsPattern.exec(code)) !== null) {
80
+ const propName = match[1]
81
+ if (propName && !dependencies.propNames.includes(propName)) {
82
+ dependencies.propNames.push(propName)
83
+ }
84
+ }
85
+ }
86
+
87
+ // Check for stores references
88
+ const storesPattern = /\bstores\.(\w+)(?:\.(\w+))*/g
89
+ if (/stores\./.test(code)) {
90
+ dependencies.usesStores = true
91
+ while ((match = storesPattern.exec(code)) !== null) {
92
+ const storeName = match[1]
93
+ if (storeName && !dependencies.storeNames.includes(storeName)) {
94
+ dependencies.storeNames.push(storeName)
95
+ }
96
+ }
97
+ }
98
+
99
+ // Check for state references (top-level properties)
100
+ // Simple identifiers that aren't part of props/stores/loaderData paths
101
+ const identifierPattern = /\b([a-zA-Z_$][a-zA-Z0-9_$]*)\b/g
102
+ const reserved = ['props', 'stores', 'loaderData', 'state', 'true', 'false', 'null', 'undefined', 'this', 'window']
103
+
104
+ const identifiers = new Set<string>()
105
+ while ((match = identifierPattern.exec(code)) !== null) {
106
+ const ident = match[1]
107
+ if (ident && !reserved.includes(ident) && !ident.includes('.')) {
108
+ identifiers.add(ident)
109
+ }
110
+ }
111
+
112
+ // If we have identifiers and no explicit data source, assume state
113
+ if (identifiers.size > 0 && !dependencies.usesLoaderData && !dependencies.usesProps && !dependencies.usesStores) {
114
+ dependencies.usesState = true
115
+ dependencies.stateProperties = Array.from(identifiers)
116
+ }
117
+
118
+ return dependencies
119
+ }
120
+
121
+ /**
122
+ * Validate that all referenced data exists
123
+ */
124
+ export function validateDataDependencies(
125
+ dependencies: ExpressionDataDependencies,
126
+ filePath: string,
127
+ declaredLoaderProps: string[] = [],
128
+ declaredProps: string[] = [],
129
+ declaredStores: string[] = []
130
+ ): void {
131
+ const errors: CompilerError[] = []
132
+
133
+ // Validate loader data properties
134
+ if (dependencies.usesLoaderData && dependencies.loaderProperties.length > 0) {
135
+ // For Phase 6, we'll allow any loader property (can be enhanced with type checking later)
136
+ // Just warn if property path is suspicious
137
+ for (const prop of dependencies.loaderProperties) {
138
+ if (!/^[a-zA-Z_$][a-zA-Z0-9_$.]*$/.test(prop)) {
139
+ errors.push(new CompilerError(
140
+ `Invalid loader data property reference: ${prop}`,
141
+ filePath,
142
+ 0,
143
+ 0
144
+ ))
145
+ }
146
+ }
147
+ }
148
+
149
+ // Validate props
150
+ if (dependencies.usesProps && dependencies.propNames.length > 0) {
151
+ for (const propName of dependencies.propNames) {
152
+ if (declaredProps.length > 0 && !declaredProps.includes(propName)) {
153
+ // This is a warning, not an error - props might be passed at runtime
154
+ console.warn(`[Zenith] Prop "${propName}" referenced but not declared in component`)
155
+ }
156
+ }
157
+ }
158
+
159
+ // Validate stores
160
+ if (dependencies.usesStores && dependencies.storeNames.length > 0) {
161
+ for (const storeName of dependencies.storeNames) {
162
+ if (declaredStores.length > 0 && !declaredStores.includes(storeName)) {
163
+ errors.push(new CompilerError(
164
+ `Store "${storeName}" referenced but not imported or declared`,
165
+ filePath,
166
+ 0,
167
+ 0
168
+ ))
169
+ }
170
+ }
171
+ }
172
+
173
+ if (errors.length > 0) {
174
+ throw errors[0] // Throw first error (can be enhanced to collect all)
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Transform expression code to use explicit data arguments
180
+ *
181
+ * Converts patterns like:
182
+ * - user.name → loaderData.user.name
183
+ * - title → props.title (if declared as prop)
184
+ * - cart.items → stores.cart.items
185
+ */
186
+ export function transformExpressionCode(
187
+ code: string,
188
+ dependencies: ExpressionDataDependencies,
189
+ declaredProps: string[] = []
190
+ ): string {
191
+ let transformed = code
192
+
193
+ // For Phase 6, we keep the code as-is but ensure expressions
194
+ // receive the right arguments. The actual transformation happens
195
+ // in the expression wrapper function signature.
196
+
197
+ // However, if the code references properties directly (without loaderData/props/stores prefix),
198
+ // we need to assume they're state properties (backwards compatibility)
199
+
200
+ return transformed
201
+ }
202
+
203
+ /**
204
+ * Generate expression wrapper with explicit data arguments
205
+ */
206
+ export function generateExplicitExpressionWrapper(
207
+ expr: ExpressionIR,
208
+ dependencies: ExpressionDataDependencies
209
+ ): string {
210
+ const { id, code } = expr
211
+
212
+ // Build function signature based on dependencies
213
+ const params: string[] = ['state']
214
+
215
+ if (dependencies.usesLoaderData) {
216
+ params.push('loaderData')
217
+ }
218
+ if (dependencies.usesProps) {
219
+ params.push('props')
220
+ }
221
+ if (dependencies.usesStores) {
222
+ params.push('stores')
223
+ }
224
+
225
+ const paramList = params.join(', ')
226
+
227
+ // Build evaluation context
228
+ const contextParts: string[] = []
229
+
230
+ if (dependencies.usesLoaderData) {
231
+ contextParts.push('loaderData')
232
+ }
233
+ if (dependencies.usesProps) {
234
+ contextParts.push('props')
235
+ }
236
+ if (dependencies.usesStores) {
237
+ contextParts.push('stores')
238
+ }
239
+ if (dependencies.usesState) {
240
+ contextParts.push('state')
241
+ }
242
+
243
+ // Create merged context for 'with' statement
244
+ const contextCode = contextParts.length > 0
245
+ ? `const __ctx = Object.assign({}, ${contextParts.join(', ')});\n with (__ctx) {`
246
+ : 'with (state) {'
247
+
248
+ const escapedCode = code.replace(/`/g, '\\`').replace(/\$/g, '\\$')
249
+
250
+ return `
251
+ // Expression: ${escapedCode}
252
+ // Dependencies: ${JSON.stringify({
253
+ loaderData: dependencies.usesLoaderData,
254
+ props: dependencies.usesProps,
255
+ stores: dependencies.usesStores,
256
+ state: dependencies.usesState
257
+ })}
258
+ const ${id} = (${paramList}) => {
259
+ try {
260
+ ${contextCode}
261
+ return ${code};
262
+ }
263
+ } catch (e) {
264
+ console.warn('[Zenith] Expression evaluation error:', ${JSON.stringify(code)}, e);
265
+ return undefined;
266
+ }
267
+ };`
268
+ }
269
+
270
+ /**
271
+ * Analyze all expressions in a template
272
+ */
273
+ export function analyzeAllExpressions(
274
+ expressions: ExpressionIR[],
275
+ filePath: string,
276
+ declaredLoaderProps: string[] = [],
277
+ declaredProps: string[] = [],
278
+ declaredStores: string[] = []
279
+ ): ExpressionDataDependencies[] {
280
+ const dependencies = expressions.map(expr =>
281
+ analyzeExpressionDependencies(expr, declaredLoaderProps, declaredProps, declaredStores)
282
+ )
283
+
284
+ // Validate all dependencies
285
+ for (const dep of dependencies) {
286
+ validateDataDependencies(dep, filePath, declaredLoaderProps, declaredProps, declaredStores)
287
+ }
288
+
289
+ return dependencies
290
+ }
291
+
@@ -0,0 +1,144 @@
1
+ /**
2
+ * DOM Generation
3
+ *
4
+ * Generates JavaScript code that creates DOM elements from template nodes
5
+ */
6
+
7
+ import type { TemplateNode, ElementNode, TextNode, ExpressionNode, ExpressionIR } from '../ir/types'
8
+
9
+ /**
10
+ * Generate DOM creation code from a template node
11
+ * Returns JavaScript code that creates and returns a DOM element or text node
12
+ */
13
+ export function generateDOMCode(
14
+ node: TemplateNode,
15
+ expressions: ExpressionIR[],
16
+ indent: string = ' ',
17
+ varCounter: { count: number } = { count: 0 }
18
+ ): { code: string; varName: string } {
19
+ const varName = `node_${varCounter.count++}`
20
+
21
+ switch (node.type) {
22
+ case 'text': {
23
+ const textNode = node as TextNode
24
+ const escapedValue = JSON.stringify(textNode.value)
25
+ return {
26
+ code: `${indent}const ${varName} = document.createTextNode(${escapedValue});`,
27
+ varName
28
+ }
29
+ }
30
+
31
+ case 'expression': {
32
+ const exprNode = node as ExpressionNode
33
+ const expr = expressions.find(e => e.id === exprNode.expression)
34
+ if (!expr) {
35
+ throw new Error(`Expression ${exprNode.expression} not found`)
36
+ }
37
+
38
+ // Create a span element to hold the expression result
39
+ return {
40
+ code: `${indent}const ${varName} = document.createElement('span');
41
+ ${indent}${varName}.textContent = String(${expr.id}(state) ?? '');
42
+ ${indent}${varName}.setAttribute('data-zen-expr', '${exprNode.expression}');`,
43
+ varName
44
+ }
45
+ }
46
+
47
+ case 'element': {
48
+ const elNode = node as ElementNode
49
+ const tag = elNode.tag
50
+
51
+ let code = `${indent}const ${varName} = document.createElement('${tag}');\n`
52
+
53
+ // Handle attributes
54
+ for (const attr of elNode.attributes) {
55
+ if (typeof attr.value === 'string') {
56
+ // Static attribute
57
+ const escapedValue = JSON.stringify(attr.value)
58
+ code += `${indent}${varName}.setAttribute('${attr.name}', ${escapedValue});\n`
59
+ } else {
60
+ // Expression attribute
61
+ const expr = attr.value as ExpressionIR
62
+ const attrName = attr.name === 'className' ? 'class' : attr.name
63
+
64
+ // Handle special attributes
65
+ if (attrName === 'class' || attrName === 'className') {
66
+ code += `${indent}${varName}.className = String(${expr.id}(state) ?? '');\n`
67
+ } else if (attrName === 'style') {
68
+ code += `${indent}const styleValue_${varCounter.count} = ${expr.id}(state);
69
+ ${indent}if (typeof styleValue_${varCounter.count} === 'string') {
70
+ ${indent} ${varName}.style.cssText = styleValue_${varCounter.count};
71
+ ${indent}}\n`
72
+ } else if (attrName.startsWith('on')) {
73
+ // Event handler - store handler name, will be bound later
74
+ const eventType = attrName.slice(2).toLowerCase() // Remove 'on' prefix
75
+ code += `${indent}${varName}.setAttribute('data-zen-${eventType}', ${JSON.stringify(attr.value as any)});\n`
76
+ } else {
77
+ const tempVar = `attr_${varCounter.count++}`
78
+ code += `${indent}const ${tempVar} = ${expr.id}(state);
79
+ ${indent}if (${tempVar} != null && ${tempVar} !== false) {
80
+ ${indent} ${varName}.setAttribute('${attrName}', String(${tempVar}));
81
+ ${indent}}\n`
82
+ }
83
+ }
84
+ }
85
+
86
+ // Handle children
87
+ if (elNode.children.length > 0) {
88
+ for (const child of elNode.children) {
89
+ const childResult = generateDOMCode(child, expressions, indent, varCounter)
90
+ code += `${childResult.code}\n`
91
+ code += `${indent}${varName}.appendChild(${childResult.varName});\n`
92
+ }
93
+ }
94
+
95
+ return { code, varName }
96
+ }
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Generate DOM creation code for multiple nodes
102
+ * Returns a function that creates DOM elements
103
+ */
104
+ export function generateDOMFunction(
105
+ nodes: TemplateNode[],
106
+ expressions: ExpressionIR[],
107
+ functionName: string = 'renderTemplate'
108
+ ): string {
109
+ if (nodes.length === 0) {
110
+ return `function ${functionName}(state) {
111
+ const fragment = document.createDocumentFragment();
112
+ return fragment;
113
+ }`
114
+ }
115
+
116
+ const varCounter = { count: 0 }
117
+ let code = `function ${functionName}(state) {
118
+ `
119
+
120
+ if (nodes.length === 1) {
121
+ const node = nodes[0]
122
+ if (!node) {
123
+ throw new Error('Empty nodes array passed to generateDOMFunction')
124
+ }
125
+ const result = generateDOMCode(node, expressions, ' ', varCounter)
126
+ code += result.code
127
+ code += `\n return ${result.varName};\n}`
128
+ return code
129
+ }
130
+
131
+ // Multiple nodes - create a fragment
132
+ code += ` const fragment = document.createDocumentFragment();\n`
133
+
134
+ for (const node of nodes) {
135
+ const result = generateDOMCode(node, expressions, ' ', varCounter)
136
+ code += `${result.code}\n`
137
+ code += ` fragment.appendChild(${result.varName});\n`
138
+ }
139
+
140
+ code += ` return fragment;
141
+ }`
142
+
143
+ return code
144
+ }