@zenithbuild/bundler 1.3.10 → 1.3.16

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,844 @@
1
+ import { readFileSync, existsSync } from 'fs'
2
+ import path from 'path'
3
+
4
+ /**
5
+ * Zenith Bundle Generator
6
+ *
7
+ * Generates the shared client runtime bundle that gets served as:
8
+ * - /assets/bundle.js in production
9
+ * - /runtime.js in development
10
+ *
11
+ * This is a cacheable, versioned file that contains:
12
+ * - Reactivity primitives (zenSignal, zenState, zenEffect, etc.)
13
+ * - Lifecycle hooks (zenOnMount, zenOnUnmount)
14
+ * - Hydration functions (zenithHydrate)
15
+ * - Event binding utilities
16
+ */
17
+
18
+ /**
19
+ * Generate the complete client runtime bundle
20
+ * This is served as an external JS file, not inlined
21
+ */
22
+ export function generateBundleJS(pluginData?: Record<string, any>): string {
23
+ // Serialize plugin data blindly - CLI never inspects what's inside.
24
+ // We escape </script> sequences just in case this bundle is ever inlined (unlikely but safe).
25
+ const serializedData = pluginData
26
+ ? JSON.stringify(pluginData).replace(/<\/script/g, '<\\/script')
27
+ : '{}';
28
+
29
+ // Resolve core runtime paths - assumes sibling directory or relative in node_modules
30
+ const rootDir = process.cwd()
31
+ let coreRuntimePath = path.join(rootDir, '../zenith-core/dist/runtime')
32
+ if (!existsSync(coreRuntimePath)) {
33
+ coreRuntimePath = path.join(rootDir, '../zenith-core/core')
34
+ }
35
+
36
+ let reactivityJS = ''
37
+ let lifecycleJS = ''
38
+
39
+ try {
40
+ let reactivityFile = path.join(coreRuntimePath, 'reactivity/index.js')
41
+ if (!existsSync(reactivityFile)) reactivityFile = path.join(coreRuntimePath, 'reactivity/index.ts')
42
+
43
+ let lifecycleFile = path.join(coreRuntimePath, 'lifecycle/index.js')
44
+ if (!existsSync(lifecycleFile)) lifecycleFile = path.join(coreRuntimePath, 'lifecycle/index.ts')
45
+
46
+ if (existsSync(reactivityFile) && reactivityFile.endsWith('.js')) {
47
+ reactivityJS = transformExportsToGlobal(readFileSync(reactivityFile, 'utf-8'));
48
+ }
49
+ if (existsSync(lifecycleFile) && lifecycleFile.endsWith('.js')) {
50
+ lifecycleJS = transformExportsToGlobal(readFileSync(lifecycleFile, 'utf-8'));
51
+ }
52
+ } catch (e) {
53
+ if (process.env.ZENITH_DEBUG === 'true') {
54
+ console.warn('[Zenith] Could not load runtime from core, falling back to internal', e);
55
+ }
56
+ }
57
+
58
+ // Fallback to internal hydration_runtime.js from native compiler source
59
+ // Use the compiler's own location to find the file, not process.cwd()
60
+ if (!reactivityJS || !lifecycleJS) {
61
+ // Resolve relative to this bundle-generator.ts file's location
62
+ // In compiled form, this will be in dist/runtime/, so we go up to find native/
63
+ // ADAPTATION: zenith-bundler is sibling to zenith-compiler
64
+ const compilerRoot = path.resolve(path.dirname(import.meta.url.replace('file://', '')), '../../zenith-compiler');
65
+ const nativeRuntimePath = path.join(compilerRoot, 'native/compiler-native/src/hydration_runtime.js');
66
+ if (existsSync(nativeRuntimePath)) {
67
+ const nativeJS = readFileSync(nativeRuntimePath, 'utf-8');
68
+ // IMPORTANT: Include the FULL IIFE - do NOT strip the wrapper!
69
+ // The runtime has its own bootstrap guard and idempotency check.
70
+ // It will install primitives to window and create window.__ZENITH_RUNTIME__
71
+ reactivityJS = nativeJS;
72
+ lifecycleJS = ' '; // Ensure it doesn't trigger the "not found" message
73
+ }
74
+ }
75
+
76
+ return `/*!
77
+ * Zenith Runtime v1.0.1
78
+ * Shared client-side runtime for hydration and reactivity
79
+ */
80
+ (function(global) {
81
+ 'use strict';
82
+
83
+ // Initialize plugin data envelope
84
+ global.__ZENITH_PLUGIN_DATA__ = ${serializedData};
85
+
86
+ ${reactivityJS ? ` // ============================================
87
+ // Core Reactivity (Injected from @zenithbuild/core)
88
+ // ============================================
89
+ ${reactivityJS}` : ` // Fallback: Reactivity not found`}
90
+
91
+ ${lifecycleJS ? ` // ============================================
92
+ // Lifecycle Hooks (Injected from @zenithbuild/core)
93
+ // ============================================
94
+ ${lifecycleJS}` : ` // Fallback: Lifecycle not found`}
95
+
96
+ // ----------------------------------------------------------------------
97
+ // COMPAT: Expose internal exports as globals
98
+ // ----------------------------------------------------------------------
99
+ // The code above was stripped of "export { ... }" but assigned to internal variables.
100
+ // We need to map them back to global scope if they weren't attached by the code itself.
101
+
102
+ // Reactivity primitives map (internal name -> global alias)
103
+ // Based on zenith-core/core/reactivity/index.ts re-exports:
104
+ // export { zenSignal, zenState, ... }
105
+
106
+ // Since we stripped exports, we rely on the fact that the bundled code
107
+ // defines variables like "var zenSignal = ..." or "function zenSignal...".
108
+ // Note: Minified code variables might be renamed (e.g., "var P=...").
109
+ // Ideally, @zenithbuild/core should export an IIFE build for this purpose.
110
+ // For now, we assume the code above already does "global.zenSignal = ..."
111
+ // OR we rely on the Aliases section below to do the mapping if the names match.
112
+
113
+ // ============================================
114
+ // Lifecycle Hooks (Required for hydration)
115
+ // ============================================
116
+ // These functions are required by the runtime - define them if not injected from core
117
+
118
+ const mountCallbacks = [];
119
+ const unmountCallbacks = [];
120
+ let isMounted = false;
121
+
122
+ function zenOnMount(fn) {
123
+ if (isMounted) {
124
+ // Already mounted, run immediately
125
+ const cleanup = fn();
126
+ if (typeof cleanup === 'function') {
127
+ unmountCallbacks.push(cleanup);
128
+ }
129
+ } else {
130
+ mountCallbacks.push(fn);
131
+ }
132
+ }
133
+
134
+ function zenOnUnmount(fn) {
135
+ unmountCallbacks.push(fn);
136
+ }
137
+
138
+ // Called by hydration when page mounts
139
+ function triggerMount() {
140
+ isMounted = true;
141
+ for (let i = 0; i < mountCallbacks.length; i++) {
142
+ try {
143
+ const cleanup = mountCallbacks[i]();
144
+ if (typeof cleanup === 'function') {
145
+ unmountCallbacks.push(cleanup);
146
+ }
147
+ } catch(e) {
148
+ console.error('[Zenith] Mount callback error:', e);
149
+ }
150
+ }
151
+ mountCallbacks.length = 0;
152
+ }
153
+
154
+ // Called by router when page unmounts
155
+ function triggerUnmount() {
156
+ isMounted = false;
157
+ for (let i = 0; i < unmountCallbacks.length; i++) {
158
+ try { unmountCallbacks[i](); } catch(e) { console.error('[Zenith] Unmount error:', e); }
159
+ }
160
+ unmountCallbacks.length = 0;
161
+ }
162
+
163
+ // ============================================
164
+ // Component Instance System
165
+ // ============================================
166
+ // Each component instance gets isolated state, effects, and lifecycles
167
+ // Instances are tied to DOM elements via hydration markers
168
+
169
+ const componentRegistry = {};
170
+
171
+ function createComponentInstance(componentName, rootElement) {
172
+ const instanceMountCallbacks = [];
173
+ const instanceUnmountCallbacks = [];
174
+ const instanceEffects = [];
175
+ let instanceMounted = false;
176
+
177
+ return {
178
+ // DOM reference
179
+ root: rootElement,
180
+
181
+ // Lifecycle hooks (instance-scoped)
182
+ onMount: function(fn) {
183
+ if (instanceMounted) {
184
+ const cleanup = fn();
185
+ if (typeof cleanup === 'function') {
186
+ instanceUnmountCallbacks.push(cleanup);
187
+ }
188
+ } else {
189
+ instanceMountCallbacks.push(fn);
190
+ }
191
+ },
192
+ onUnmount: function(fn) {
193
+ instanceUnmountCallbacks.push(fn);
194
+ },
195
+
196
+ // Reactivity (uses global primitives but tracks for cleanup)
197
+ signal: function(initial) {
198
+ return global.zenSignal(initial);
199
+ },
200
+ state: function(initial) {
201
+ return global.zenState(initial);
202
+ },
203
+ ref: function(initial) {
204
+ return global.zenRef(initial);
205
+ },
206
+ effect: function(fn) {
207
+ const cleanup = global.zenEffect(fn);
208
+ instanceEffects.push(cleanup);
209
+ return cleanup;
210
+ },
211
+ memo: function(fn) {
212
+ return global.zenMemo(fn);
213
+ },
214
+ batch: function(fn) {
215
+ global.zenBatch(fn);
216
+ },
217
+ untrack: function(fn) {
218
+ return global.zenUntrack(fn);
219
+ },
220
+
221
+ // Lifecycle execution
222
+ mount: function() {
223
+ instanceMounted = true;
224
+ for (let i = 0; i < instanceMountCallbacks.length; i++) {
225
+ try {
226
+ const cleanup = instanceMountCallbacks[i]();
227
+ if (typeof cleanup === 'function') {
228
+ instanceUnmountCallbacks.push(cleanup);
229
+ }
230
+ } catch(e) {
231
+ console.error('[Zenith] Component mount error:', componentName, e);
232
+ }
233
+ }
234
+ instanceMountCallbacks.length = 0;
235
+ },
236
+ unmount: function() {
237
+ instanceMounted = false;
238
+ // Cleanup effects
239
+ for (let i = 0; i < instanceEffects.length; i++) {
240
+ try {
241
+ if (typeof instanceEffects[i] === 'function') instanceEffects[i]();
242
+ } catch(e) {
243
+ console.error('[Zenith] Effect cleanup error:', e);
244
+ }
245
+ }
246
+ instanceEffects.length = 0;
247
+ // Run unmount callbacks
248
+ for (let i = 0; i < instanceUnmountCallbacks.length; i++) {
249
+ try { instanceUnmountCallbacks[i](); } catch(e) { console.error('[Zenith] Unmount error:', e); }
250
+ }
251
+ instanceUnmountCallbacks.length = 0;
252
+ }
253
+ };
254
+ }
255
+
256
+ function defineComponent(name, factory) {
257
+ componentRegistry[name] = factory;
258
+ }
259
+
260
+ function instantiateComponent(name, props, rootElement) {
261
+ const factory = componentRegistry[name];
262
+ if (!factory) {
263
+ if (name === 'ErrorPage') {
264
+ // Built-in fallback for ErrorPage if not registered by user
265
+ return fallbackErrorPage(props, rootElement);
266
+ }
267
+ console.warn('[Zenith] Component not found:', name);
268
+ return null;
269
+ }
270
+ return factory(props, rootElement);
271
+ }
272
+
273
+ function renderErrorPage(error, metadata) {
274
+ console.error('[Zenith Runtime Error]', error, metadata);
275
+
276
+ // In production, we might want a simpler page, but for now let's use the high-fidelity one
277
+ // if it's available.
278
+ const container = document.getElementById('app') || document.body;
279
+
280
+ // If we've already rendered an error page, don't do it again to avoid infinite loops
281
+ if (window.__ZENITH_ERROR_RENDERED__) return;
282
+ window.__ZENITH_ERROR_RENDERED__ = true;
283
+
284
+ const errorProps = {
285
+ message: error.message || 'Unknown Error',
286
+ stack: error.stack,
287
+ file: metadata.file || (error.file),
288
+ line: metadata.line || (error.line),
289
+ column: metadata.column || (error.column),
290
+ errorType: metadata.errorType || error.name || 'RuntimeError',
291
+ code: metadata.code || 'ERR500',
292
+ context: metadata.context || (metadata.expressionId ? 'Expression: ' + metadata.expressionId : ''),
293
+ hints: metadata.hints || [],
294
+ isProd: false // Check env here if possible
295
+ };
296
+
297
+ // Try to instantiate the user's ErrorPage
298
+ const instance = instantiateComponent('ErrorPage', errorProps, container);
299
+ if (instance) {
300
+ container.innerHTML = '';
301
+ instance.mount();
302
+ } else {
303
+ // Fallback to basic HTML if ErrorPage component fails or is missing
304
+ container.innerHTML = \`
305
+ <div style="padding: 4rem; font-family: system-ui, sans-serif; background: #000; color: #fff; min-h: 100vh;">
306
+ <h1 style="font-size: 3rem; margin-bottom: 1rem; color: #ef4444;">Zenith Runtime Error</h1>
307
+ <p style="font-size: 1.5rem; opacity: 0.8;">\${errorProps.message}</p>
308
+ <pre style="margin-top: 2rem; padding: 1rem; background: #111; border-radius: 8px; overflow: auto; font-size: 0.8rem; color: #888;">\${errorProps.stack}</pre>
309
+ </div>
310
+ \`;
311
+ }
312
+ }
313
+
314
+ function fallbackErrorPage(props, el) {
315
+ // This could be a more complex fallback, but for now we just return null
316
+ // to trigger the basic HTML fallback in renderErrorPage.
317
+ return null;
318
+ }
319
+
320
+ /**
321
+ * Hydrate components by discovering data-zen-component markers
322
+ * This is the ONLY place component instantiation should happen
323
+ */
324
+ function hydrateComponents(container) {
325
+ try {
326
+ const componentElements = container.querySelectorAll('[data-zen-component]');
327
+
328
+ for (let i = 0; i < componentElements.length; i++) {
329
+ const el = componentElements[i];
330
+ const componentName = el.getAttribute('data-zen-component');
331
+
332
+ // Skip if already hydrated OR if handled by instance script (data-zen-inst)
333
+ if (el.__zenith_instance || el.hasAttribute('data-zen-inst')) continue;
334
+
335
+ // Parse props from data attribute if present
336
+ const propsJson = el.getAttribute('data-zen-props') || '{}';
337
+ let props = {};
338
+ try {
339
+ props = JSON.parse(propsJson);
340
+ } catch(e) {
341
+ console.warn('[Zenith] Invalid props JSON for', componentName);
342
+ }
343
+
344
+ try {
345
+ // Instantiate component and bind to DOM element
346
+ const instance = instantiateComponent(componentName, props, el);
347
+
348
+ if (instance) {
349
+ el.__zenith_instance = instance;
350
+ }
351
+ } catch (e) {
352
+ renderErrorPage(e, { component: componentName, props: props });
353
+ }
354
+ }
355
+ } catch (e) {
356
+ renderErrorPage(e, { activity: 'hydrateComponents' });
357
+ }
358
+ }
359
+
360
+ // ============================================
361
+ // Expression Registry & Hydration
362
+ // ============================================
363
+
364
+ const expressionRegistry = new Map();
365
+
366
+ function registerExpression(id, fn) {
367
+ expressionRegistry.set(id, fn);
368
+ }
369
+
370
+ function getExpression(id) {
371
+ return expressionRegistry.get(id);
372
+ }
373
+
374
+ function updateNode(node, exprId, pageState) {
375
+ const expr = getExpression(exprId);
376
+ if (!expr) return;
377
+
378
+ zenEffect(function() {
379
+ try {
380
+ const result = expr(pageState);
381
+
382
+ if (node.hasAttribute('data-zen-text')) {
383
+ // Handle complex text/children results
384
+ if (result === null || result === undefined || result === false) {
385
+ node.textContent = '';
386
+ } else if (typeof result === 'string') {
387
+ if (result.trim().startsWith('<') && result.trim().endsWith('>')) {
388
+ node.innerHTML = result;
389
+ } else {
390
+ node.textContent = result;
391
+ }
392
+ } else if (result instanceof Node) {
393
+ node.innerHTML = '';
394
+ node.appendChild(result);
395
+ } else if (Array.isArray(result)) {
396
+ node.innerHTML = '';
397
+ const fragment = document.createDocumentFragment();
398
+ result.flat(Infinity).forEach(item => {
399
+ if (item instanceof Node) fragment.appendChild(item);
400
+ else if (item != null && item !== false) fragment.appendChild(document.createTextNode(String(item)));
401
+ });
402
+ node.appendChild(fragment);
403
+ } else {
404
+ node.textContent = String(result);
405
+ }
406
+ } else {
407
+ // Attribute update
408
+ const attrNames = ['class', 'style', 'src', 'href', 'disabled', 'checked'];
409
+ for (const attr of attrNames) {
410
+ if (node.hasAttribute('data-zen-attr-' + attr)) {
411
+ if (attr === 'class' || attr === 'className') {
412
+ node.className = String(result || '');
413
+ } else if (attr === 'disabled' || attr === 'checked') {
414
+ if (result) node.setAttribute(attr, '');
415
+ else node.removeAttribute(attr);
416
+ } else {
417
+ if (result != null && result !== false) node.setAttribute(attr, String(result));
418
+ else node.removeAttribute(attr);
419
+ }
420
+ }
421
+ }
422
+ }
423
+ } catch (e) {
424
+ renderErrorPage(e, { expressionId: exprId, node: node });
425
+ }
426
+ });
427
+ }
428
+
429
+ /**
430
+ * Hydrate a page with reactive bindings
431
+ * Called after page HTML is in DOM
432
+ */
433
+ function updateLoopBinding(template, exprId, pageState) {
434
+ const expr = getExpression(exprId);
435
+ if (!expr) return;
436
+
437
+ const itemVar = template.getAttribute('data-zen-item');
438
+ const indexVar = template.getAttribute('data-zen-index');
439
+
440
+ // Use a marker or a container next to the template to hold instances
441
+ let container = template.__zen_container;
442
+ if (!container) {
443
+ container = document.createElement('div');
444
+ container.style.display = 'contents';
445
+ template.parentNode.insertBefore(container, template.nextSibling);
446
+ template.__zen_container = container;
447
+ }
448
+
449
+ zenEffect(function() {
450
+ try {
451
+ const items = expr(pageState);
452
+ if (!Array.isArray(items)) return;
453
+
454
+ // Simple reconciliation: clear and redraw
455
+ container.innerHTML = '';
456
+
457
+ items.forEach(function(item, index) {
458
+ const fragment = template.content.cloneNode(true);
459
+
460
+ // Create child scope
461
+ const childState = Object.assign({}, pageState);
462
+ if (itemVar) childState[itemVar] = item;
463
+ if (indexVar) childState[indexVar] = index;
464
+
465
+ // Recursive hydration for the fragment
466
+ zenithHydrate(childState, fragment);
467
+
468
+ container.appendChild(fragment);
469
+ });
470
+ } catch (e) {
471
+ renderErrorPage(e, { expressionId: exprId, activity: 'loopReconciliation' });
472
+ }
473
+ });
474
+ }
475
+
476
+ /**
477
+ * Hydrate static HTML with dynamic expressions
478
+ */
479
+ /**
480
+ * Hydrate static HTML with dynamic expressions (Comment-based)
481
+ */
482
+ function zenithHydrate(pageState, container) {
483
+ try {
484
+ container = container || document;
485
+
486
+ // Walker to find comment nodes efficiently
487
+ const walker = document.createTreeWalker(
488
+ container,
489
+ NodeFilter.SHOW_COMMENT,
490
+ null,
491
+ false
492
+ );
493
+
494
+ const exprLocationMap = new Map();
495
+ let node;
496
+
497
+ while(node = walker.nextNode()) {
498
+ const content = node.nodeValue || '';
499
+ if (content.startsWith('zen:expr_')) {
500
+ const exprId = content.replace('zen:expr_', '');
501
+ exprLocationMap.set(node, exprId);
502
+ }
503
+ }
504
+
505
+ // Process expressions
506
+ for (const [commentNode, exprId] of exprLocationMap) {
507
+ updateNode(commentNode, exprId, pageState);
508
+ }
509
+
510
+ // Wire up event handlers (still attribute based, usually safe)
511
+ const eventTypes = ['click', 'change', 'input', 'submit', 'focus', 'blur', 'keyup', 'keydown'];
512
+ eventTypes.forEach(eventType => {
513
+ const elements = container.querySelectorAll('[data-zen-' + eventType + ']');
514
+ elements.forEach(el => {
515
+ const handlerName = el.getAttribute('data-zen-' + eventType);
516
+ // Check global scope (window) or expression registry
517
+ if (handlerName) {
518
+ el.addEventListener(eventType, function(e) {
519
+ // Resolve handler at runtime to allow for late definition
520
+ const handler = global[handlerName] || getExpression(handlerName);
521
+ if (typeof handler === 'function') {
522
+ handler(e, el);
523
+ } else {
524
+ console.warn('[Zenith] Handler not found:', handlerName);
525
+ }
526
+ });
527
+ }
528
+ });
529
+ });
530
+
531
+ // Trigger mount
532
+ if (container === document || container.id === 'app' || container.tagName === 'BODY') {
533
+ triggerMount();
534
+ }
535
+ } catch (e) {
536
+ renderErrorPage(e, { activity: 'zenithHydrate' });
537
+ }
538
+ }
539
+
540
+ // Update logic for comment placeholders
541
+ function updateNode(placeholder, exprId, pageState) {
542
+ const expr = getExpression(exprId);
543
+ if (!expr) return;
544
+
545
+ // Store reference to current nodes for cleanup
546
+ let currentNodes = [];
547
+
548
+ zenEffect(function() {
549
+ try {
550
+ const result = expr(pageState);
551
+
552
+ // Cleanup old nodes
553
+ currentNodes.forEach(n => n.remove());
554
+ currentNodes = [];
555
+
556
+ if (result == null || result === false) {
557
+ // Render nothing
558
+ } else if (result instanceof Node) {
559
+ placeholder.parentNode.insertBefore(result, placeholder);
560
+ currentNodes.push(result);
561
+ } else if (Array.isArray(result)) {
562
+ result.flat(Infinity).forEach(item => {
563
+ const n = item instanceof Node ? item : document.createTextNode(String(item));
564
+ placeholder.parentNode.insertBefore(n, placeholder);
565
+ currentNodes.push(n);
566
+ });
567
+ } else {
568
+ // Primitive
569
+ const n = document.createTextNode(String(result));
570
+ placeholder.parentNode.insertBefore(n, placeholder);
571
+ currentNodes.push(n);
572
+ }
573
+ } catch (e) {
574
+ renderErrorPage(e, { expressionId: exprId });
575
+ }
576
+ });
577
+ }
578
+
579
+ // ============================================
580
+ // zenith:content - Content Engine
581
+ // ============================================
582
+
583
+ const schemaRegistry = new Map();
584
+ const builtInEnhancers = {
585
+ readTime: (item) => {
586
+ const wordsPerMinute = 200;
587
+ const text = item.content || '';
588
+ const wordCount = text.split(/\\s+/).length;
589
+ const minutes = Math.ceil(wordCount / wordsPerMinute);
590
+ return Object.assign({}, item, { readTime: minutes + ' min' });
591
+ },
592
+ wordCount: (item) => {
593
+ const text = item.content || '';
594
+ const wordCount = text.split(/\\s+/).length;
595
+ return Object.assign({}, item, { wordCount: wordCount });
596
+ }
597
+ };
598
+
599
+ async function applyEnhancers(item, enhancers) {
600
+ let enrichedItem = Object.assign({}, item);
601
+ for (const enhancer of enhancers) {
602
+ if (typeof enhancer === 'string') {
603
+ const fn = builtInEnhancers[enhancer];
604
+ if (fn) enrichedItem = await fn(enrichedItem);
605
+ } else if (typeof enhancer === 'function') {
606
+ enrichedItem = await enhancer(enrichedItem);
607
+ }
608
+ }
609
+ return enrichedItem;
610
+ }
611
+
612
+ class ZenCollection {
613
+ constructor(items) {
614
+ this.items = [...items];
615
+ this.filters = [];
616
+ this.sortField = null;
617
+ this.sortOrder = 'desc';
618
+ this.limitCount = null;
619
+ this.selectedFields = null;
620
+ this.enhancers = [];
621
+ this._groupByFolder = false;
622
+ }
623
+ where(fn) { this.filters.push(fn); return this; }
624
+ sortBy(field, order = 'desc') { this.sortField = field; this.sortOrder = order; return this; }
625
+ limit(n) { this.limitCount = n; return this; }
626
+ fields(f) { this.selectedFields = f; return this; }
627
+ enhanceWith(e) { this.enhancers.push(e); return this; }
628
+ groupByFolder() { this._groupByFolder = true; return this; }
629
+ get() {
630
+ let results = [...this.items];
631
+ for (const filter of this.filters) results = results.filter(filter);
632
+ if (this.sortField) {
633
+ results.sort((a, b) => {
634
+ const valA = a[this.sortField];
635
+ const valB = b[this.sortField];
636
+ if (valA < valB) return this.sortOrder === 'asc' ? -1 : 1;
637
+ if (valA > valB) return this.sortOrder === 'asc' ? 1 : -1;
638
+ return 0;
639
+ });
640
+ }
641
+ if (this.limitCount !== null) results = results.slice(0, this.limitCount);
642
+
643
+ // Apply enhancers synchronously if possible
644
+ if (this.enhancers.length > 0) {
645
+ results = results.map(item => {
646
+ let enrichedItem = Object.assign({}, item);
647
+ for (const enhancer of this.enhancers) {
648
+ if (typeof enhancer === 'string') {
649
+ const fn = builtInEnhancers[enhancer];
650
+ if (fn) enrichedItem = fn(enrichedItem);
651
+ } else if (typeof enhancer === 'function') {
652
+ enrichedItem = enhancer(enrichedItem);
653
+ }
654
+ }
655
+ return enrichedItem;
656
+ });
657
+ }
658
+
659
+ if (this.selectedFields) {
660
+ results = results.map(item => {
661
+ const newItem = {};
662
+ this.selectedFields.forEach(f => { newItem[f] = item[f]; });
663
+ return newItem;
664
+ });
665
+ }
666
+
667
+ // Group by folder if requested
668
+ if (this._groupByFolder) {
669
+ const groups = {};
670
+ const groupOrder = [];
671
+ for (const item of results) {
672
+ // Extract folder from slug (e.g., "getting-started/installation" -> "getting-started")
673
+ const slug = item.slug || item.id || '';
674
+ const parts = slug.split('/');
675
+ const folder = parts.length > 1 ? parts[0] : 'root';
676
+
677
+ if (!groups[folder]) {
678
+ groups[folder] = {
679
+ id: folder,
680
+ title: folder.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '),
681
+ items: []
682
+ };
683
+ groupOrder.push(folder);
684
+ }
685
+ groups[folder].items.push(item);
686
+ }
687
+ return groupOrder.map(f => groups[f]);
688
+ }
689
+
690
+ return results;
691
+ }
692
+ }
693
+
694
+ function defineSchema(name, schema) { schemaRegistry.set(name, schema); }
695
+
696
+ function zenCollection(collectionName) {
697
+ // Access plugin data from the neutral envelope
698
+ // Content plugin stores all items under 'content' namespace
699
+ const pluginData = global.__ZENITH_PLUGIN_DATA__ || {};
700
+ const contentItems = pluginData.content || [];
701
+
702
+ // Filter by collection name (plugin owns data structure, runtime just filters)
703
+ const data = Array.isArray(contentItems)
704
+ ? contentItems.filter(item => item && item.collection === collectionName)
705
+ : [];
706
+
707
+ return new ZenCollection(data);
708
+ }
709
+
710
+ // ============================================
711
+ // useZenOrder - Documentation ordering & navigation
712
+ // ============================================
713
+
714
+ function slugify(text) {
715
+ return String(text || '')
716
+ .toLowerCase()
717
+ .replace(/[^\\w\\s-]/g, '')
718
+ .replace(/\\s+/g, '-')
719
+ .replace(/-+/g, '-')
720
+ .trim();
721
+ }
722
+
723
+ function getDocSlug(doc) {
724
+ const slugOrId = String(doc.slug || doc.id || '');
725
+ const parts = slugOrId.split('/');
726
+ const filename = parts[parts.length - 1];
727
+ return filename ? slugify(filename) : slugify(doc.title || 'untitled');
728
+ }
729
+
730
+ function processRawSections(rawSections) {
731
+ const sections = (rawSections || []).map(function(rawSection) {
732
+ const sectionSlug = slugify(rawSection.title || rawSection.id || 'section');
733
+ const items = (rawSection.items || []).map(function(item) {
734
+ return Object.assign({}, item, {
735
+ slug: getDocSlug(item),
736
+ sectionSlug: sectionSlug,
737
+ isIntro: item.intro === true || (item.tags && item.tags.includes && item.tags.includes('intro'))
738
+ });
739
+ });
740
+
741
+ // Sort items: intro first, then order, then alphabetical
742
+ items.sort(function(a, b) {
743
+ if (a.isIntro && !b.isIntro) return -1;
744
+ if (!a.isIntro && b.isIntro) return 1;
745
+ if (a.order !== undefined && b.order !== undefined) return a.order - b.order;
746
+ if (a.order !== undefined) return -1;
747
+ if (b.order !== undefined) return 1;
748
+ return (a.title || '').localeCompare(b.title || '');
749
+ });
750
+
751
+ return {
752
+ id: rawSection.id || sectionSlug,
753
+ title: rawSection.title || 'Untitled',
754
+ slug: sectionSlug,
755
+ order: rawSection.order !== undefined ? rawSection.order : (rawSection.meta && rawSection.meta.order),
756
+ hasIntro: items.some(function(i) { return i.isIntro; }),
757
+ items: items
758
+ };
759
+ });
760
+
761
+ // Sort sections: order → hasIntro → alphabetical
762
+ sections.sort(function(a, b) {
763
+ if (a.order !== undefined && b.order !== undefined) return a.order - b.order;
764
+ if (a.order !== undefined) return -1;
765
+ if (b.order !== undefined) return 1;
766
+ if (a.hasIntro && !b.hasIntro) return -1;
767
+ if (!a.hasIntro && b.hasIntro) return 1;
768
+ return a.title.localeCompare(b.title);
769
+ });
770
+
771
+ return sections;
772
+ }
773
+
774
+ function createZenOrder(rawSections) {
775
+ const sections = processRawSections(rawSections);
776
+
777
+ return {
778
+ sections: sections,
779
+ selectedSection: sections[0] || null,
780
+ selectedDoc: sections[0] && sections[0].items[0] || null,
781
+
782
+ getSectionBySlug: function(sectionSlug) {
783
+ return sections.find(function(s) { return s.slug === sectionSlug; }) || null;
784
+ },
785
+
786
+ getDocBySlug: function(sectionSlug, docSlug) {
787
+ var section = sections.find(function(s) { return s.slug === sectionSlug; });
788
+ if (!section) return null;
789
+ return section.items.find(function(d) { return d.slug === docSlug; }) || null;
790
+ },
791
+
792
+ getNextDoc: function(currentDoc) {
793
+ if (!currentDoc) return null;
794
+ var currentSection = sections.find(function(s) { return s.slug === currentDoc.sectionSlug; });
795
+ if (!currentSection) return null;
796
+ var idx = currentSection.items.findIndex(function(d) { return d.slug === currentDoc.slug; });
797
+ if (idx < currentSection.items.length - 1) return currentSection.items[idx + 1];
798
+ var secIdx = sections.findIndex(function(s) { return s.slug === currentSection.slug; });
799
+ if (secIdx < sections.length - 1) return sections[secIdx + 1].items[0] || null;
800
+ return null;
801
+ },
802
+
803
+ getPrevDoc: function(currentDoc) {
804
+ if (!currentDoc) return null;
805
+ var currentSection = sections.find(function(s) { return s.slug === currentDoc.sectionSlug; });
806
+ if (!currentSection) return null;
807
+ var idx = currentSection.items.findIndex(function(d) { return d.slug === currentDoc.slug; });
808
+ if (idx > 0) return currentSection.items[idx - 1];
809
+ var secIdx = sections.findIndex(function(s) { return s.slug === currentSection.slug; });
810
+ if (secIdx > 0) {
811
+ var prevSec = sections[secIdx - 1];
812
+ return prevSec.items[prevSec.items.length - 1] || null;
813
+ }
814
+ return null;
815
+ }
816
+ };
817
+ }
818
+
819
+ // ============================================
820
+ // Export to global window
821
+ // ============================================
822
+
823
+ global.defineComponent = defineComponent;
824
+ global.hydrateComponents = hydrateComponents;
825
+ global.zenithHydrate = zenithHydrate;
826
+ global.registerExpression = registerExpression;
827
+ global.getExpression = getExpression;
828
+ global.updateNode = updateNode;
829
+ global.updateLoopBinding = updateLoopBinding;
830
+ global.zenCollection = zenCollection;
831
+ global.defineSchema = defineSchema;
832
+ global.createZenOrder = createZenOrder;
833
+
834
+ // Initialize component registry
835
+ global.componentRegistry = componentRegistry;
836
+
837
+ })(typeof window !== 'undefined' ? window : globalThis);
838
+ `;
839
+ }
840
+
841
+ // Helpers
842
+ function transformExportsToGlobal(source: string): string {
843
+ return source.replace(/export\s+(const|function|class)\s+(\w+)/g, 'var $2');
844
+ }