@aetherframework/template-engine 1.0.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.
@@ -0,0 +1,231 @@
1
+ /**
2
+ * @license MIT
3
+ * Copyright (c) 2026-present AetherFramework Contributors.
4
+ * SPDX-License-Identifier: MIT
5
+ * @module @aetherframework/template-engine/src/core/ModeManager
6
+ */
7
+ /**
8
+ * Mode Manager - Manages rendering modes (SSR/Template)
9
+ */
10
+ class ModeManager {
11
+ constructor(options = {}) {
12
+ this.options = {
13
+ defaultMode: options.defaultMode || 'template',
14
+ supportedModes: ['ssr', 'template', 'disabled'],
15
+ ...options
16
+ };
17
+
18
+ this.currentMode = this.options.defaultMode;
19
+ this.modes = new Map();
20
+
21
+ // Initialize modes
22
+ this.initializeModes();
23
+ }
24
+
25
+ /**
26
+ * Initialize supported modes
27
+ * @private
28
+ */
29
+ initializeModes() {
30
+ // SSR Mode
31
+ this.modes.set('ssr', {
32
+ name: 'ssr',
33
+ description: 'Server-Side Rendering mode',
34
+ features: ['hydration', 'seo-friendly', 'fast-initial-load'],
35
+ renderer: null // Will be set when SSR engine is loaded
36
+ });
37
+
38
+ // Template Mode
39
+ this.modes.set('template', {
40
+ name: 'template',
41
+ description: 'Template rendering mode',
42
+ features: ['fast-render', 'caching', 'layout-support'],
43
+ renderer: null // Will be set when Template engine is loaded
44
+ });
45
+
46
+ // Disabled Mode (fallback)
47
+ this.modes.set('disabled', {
48
+ name: 'disabled',
49
+ description: 'Disabled mode - returns raw template',
50
+ features: ['no-processing', 'fallback'],
51
+ renderer: this.getDisabledRenderer()
52
+ });
53
+ }
54
+
55
+ /**
56
+ * Set current rendering mode
57
+ * @param {string} mode - Mode name ('ssr', 'template', or 'disabled')
58
+ * @returns {ModeManager} This instance for chaining
59
+ */
60
+ setMode(mode) {
61
+ if (!this.modes.has(mode)) {
62
+ throw new Error(`Unsupported mode: ${mode}. Supported modes: ${Array.from(this.modes.keys()).join(', ')}`);
63
+ }
64
+
65
+ this.currentMode = mode;
66
+ console.log(`🔄 Mode changed to: ${mode}`);
67
+ return this;
68
+ }
69
+
70
+ /**
71
+ * Get current mode
72
+ * @returns {string} Current mode name
73
+ */
74
+ getMode() {
75
+ return this.currentMode;
76
+ }
77
+
78
+ /**
79
+ * Get available modes
80
+ * @returns {Array} List of available modes
81
+ */
82
+ getAvailableModes() {
83
+ return Array.from(this.modes.keys());
84
+ }
85
+
86
+ /**
87
+ * Get mode information
88
+ * @param {string} mode - Mode name
89
+ * @returns {Object} Mode information
90
+ */
91
+ getModeInfo(mode) {
92
+ return this.modes.get(mode) || null;
93
+ }
94
+
95
+ /**
96
+ * Get renderer for current mode
97
+ * @returns {Function} Renderer function
98
+ */
99
+ getRenderer() {
100
+ const modeInfo = this.modes.get(this.currentMode);
101
+ if (!modeInfo || !modeInfo.renderer) {
102
+ throw new Error(`No renderer available for mode: ${this.currentMode}`);
103
+ }
104
+ return modeInfo.renderer;
105
+ }
106
+
107
+ /**
108
+ * Get SSR renderer
109
+ * @returns {Function} SSR renderer function
110
+ */
111
+ getSSRRenderer() {
112
+ const ssrMode = this.modes.get('ssr');
113
+ if (!ssrMode || !ssrMode.renderer) {
114
+ throw new Error('SSR renderer not available. Make sure SSR engine is registered.');
115
+ }
116
+ return ssrMode.renderer;
117
+ }
118
+
119
+ /**
120
+ * Get template renderer
121
+ * @returns {Function} Template renderer function
122
+ */
123
+ getTemplateRenderer() {
124
+ const templateMode = this.modes.get('template');
125
+ if (!templateMode || !templateMode.renderer) {
126
+ throw new Error('Template renderer not available. Make sure Template engine is registered.');
127
+ }
128
+ return templateMode.renderer;
129
+ }
130
+
131
+ /**
132
+ * Get disabled mode renderer (fallback)
133
+ * @returns {Function} Disabled mode renderer
134
+ */
135
+ getDisabledRenderer() {
136
+ return (template, data) => {
137
+ // Simple renderer that returns template as-is
138
+ if (typeof template === 'string') {
139
+ return Promise.resolve(template);
140
+ }
141
+ return Promise.resolve(String(template));
142
+ };
143
+ }
144
+
145
+ /**
146
+ * Register a renderer for a specific mode
147
+ * @param {string} mode - Mode name
148
+ * @param {Function} renderer - Renderer function
149
+ * @returns {ModeManager} This instance for chaining
150
+ */
151
+ registerRenderer(mode, renderer) {
152
+ if (!this.modes.has(mode)) {
153
+ throw new Error(`Cannot register renderer for unknown mode: ${mode}`);
154
+ }
155
+
156
+ this.modes.get(mode).renderer = renderer;
157
+ console.log(`✅ Renderer registered for mode: ${mode}`);
158
+ return this;
159
+ }
160
+
161
+ /**
162
+ * Register an adapter for mode switching
163
+ * @param {Object} adapter - Mode adapter
164
+ * @returns {ModeManager} This instance for chaining
165
+ */
166
+ registerAdapter(adapter) {
167
+ if (!adapter || typeof adapter !== 'object') {
168
+ throw new Error('Adapter must be an object');
169
+ }
170
+
171
+ if (!adapter.name) {
172
+ throw new Error('Adapter must have a name property');
173
+ }
174
+
175
+ if (!adapter.supportedModes || !Array.isArray(adapter.supportedModes)) {
176
+ throw new Error('Adapter must have supportedModes array');
177
+ }
178
+
179
+ // Register adapter for each supported mode
180
+ adapter.supportedModes.forEach(mode => {
181
+ if (this.modes.has(mode)) {
182
+ const modeInfo = this.modes.get(mode);
183
+ modeInfo.adapter = adapter;
184
+ console.log(`✅ Adapter "${adapter.name}" registered for mode: ${mode}`);
185
+ }
186
+ });
187
+
188
+ return this;
189
+ }
190
+
191
+ /**
192
+ * List registered adapters
193
+ * @returns {Array} List of adapter names
194
+ */
195
+ listAdapters() {
196
+ const adapters = new Set();
197
+ for (const [mode, modeInfo] of this.modes) {
198
+ if (modeInfo.adapter) {
199
+ adapters.add(modeInfo.adapter.name);
200
+ }
201
+ }
202
+ return Array.from(adapters);
203
+ }
204
+
205
+ /**
206
+ * Get mode statistics
207
+ * @returns {Object} Mode statistics
208
+ */
209
+ getStats() {
210
+ const stats = {
211
+ currentMode: this.currentMode,
212
+ availableModes: this.getAvailableModes(),
213
+ adapters: this.listAdapters(),
214
+ modes: {}
215
+ };
216
+
217
+ for (const [name, modeInfo] of this.modes) {
218
+ stats.modes[name] = {
219
+ name: modeInfo.name,
220
+ description: modeInfo.description,
221
+ hasRenderer: !!modeInfo.renderer,
222
+ hasAdapter: !!modeInfo.adapter,
223
+ features: modeInfo.features || []
224
+ };
225
+ }
226
+
227
+ return stats;
228
+ }
229
+ }
230
+
231
+ export default ModeManager;
@@ -0,0 +1,373 @@
1
+ /**
2
+ * @license MIT
3
+ * Copyright (c) 2026-present AetherFramework Contributors.
4
+ * SPDX-License-Identifier: MIT
5
+ * @module @aetherframework/template-engine/src/core/TemplateEngineFactory
6
+ */
7
+
8
+ /**
9
+ * Template Engine Factory - Core factory class for creating and managing template engines
10
+ * Supports factory pattern with multiple rendering modes (SSR/Template)
11
+ */
12
+ import EngineRegistry from './EngineRegistry.js';
13
+ import ModeManager from './ModeManager.js';
14
+ import CacheManager from './CacheManager.js';
15
+ import ConfigLoader from '../utils/ConfigLoader.js';
16
+ import ErrorHandler from '../utils/ErrorHandler.js';
17
+
18
+ class TemplateEngineFactory {
19
+ constructor(options = {}) {
20
+ // Load configuration from environment variables
21
+ this.config = {
22
+ // Mode selection: 'ssr' or 'template'
23
+ mode: process.env.TEMPLATE_ENGINE_MODE || options.mode || 'template',
24
+
25
+ // Default engine to use
26
+ defaultEngine: process.env.TEMPLATE_ENGINE || options.defaultEngine || 'aether',
27
+
28
+ // Cache configuration
29
+ cacheEnabled: process.env.CACHE_ENABLED !== 'false' && (options.cacheEnabled !== false),
30
+ cacheTTL: parseInt(process.env.CACHE_TTL) || options.cacheTTL || 300000, // 5 minutes
31
+
32
+ // Template directory
33
+ templateDir: process.env.TEMPLATE_DIR || options.templateDir || './templates',
34
+
35
+ // Debug mode
36
+ debug: process.env.NODE_ENV === 'development' || options.debug || false,
37
+
38
+ // Merge with user options
39
+ ...options
40
+ };
41
+
42
+ // Initialize core components
43
+ this.registry = new EngineRegistry();
44
+ this.modeManager = new ModeManager(this.config);
45
+ this.cacheManager = new CacheManager(this.config);
46
+
47
+ // Track initialization state
48
+ this.initialized = false;
49
+
50
+ console.log(`🚀 Template Engine Factory created in ${this.config.mode} mode`);
51
+ }
52
+
53
+ /**
54
+ * Initialize the factory and register engines
55
+ * This method should be called before using the factory
56
+ * @returns {Promise<TemplateEngineFactory>} This factory instance for chaining
57
+ */
58
+ async initialize() {
59
+ if (this.initialized) {
60
+ return this;
61
+ }
62
+
63
+ try {
64
+ // Initialize built-in engines
65
+ await this.initializeEngines();
66
+
67
+ this.initialized = true;
68
+ console.log(`✅ Template Engine Factory initialized in ${this.config.mode} mode`);
69
+
70
+ return this;
71
+ } catch (error) {
72
+ console.error('❌ Factory initialization failed:', error.message);
73
+ throw error;
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Initialize built-in engines
79
+ * @private
80
+ */
81
+ async initializeEngines() {
82
+ try {
83
+ // Import and register Aether Engine (your custom syntax)
84
+ const { default: AetherEngine } = await import('./../engines/AetherEngine.js');
85
+ this.registerEngine('aether', new AetherEngine(this.config));
86
+ console.log('✅ Aether engine registered');
87
+
88
+ // Import and register SSR Mode Engine
89
+ const { default: SSRModeEngine } = await import('./../engines/SSRModeEngine.js');
90
+ this.registerEngine('ssr-mode', new SSRModeEngine(this.config));
91
+ console.log('✅ SSR Mode engine registered');
92
+
93
+ // Import and register Template Mode Engine
94
+ const { default: TemplateModeEngine } = await import('./../engines/TemplateModeEngine.js');
95
+ this.registerEngine('template-mode', new TemplateModeEngine(this.config));
96
+ console.log('✅ Template Mode engine registered');
97
+
98
+ } catch (error) {
99
+ console.error('❌ Engine initialization failed:', error.message);
100
+ throw error;
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Register a template engine
106
+ * @param {string} name - Engine name
107
+ * @param {BaseEngine} engine - Engine instance
108
+ * @returns {TemplateEngineFactory} This factory instance for chaining
109
+ */
110
+ registerEngine(name, engine) {
111
+ this.registry.register(name, engine);
112
+ return this;
113
+ }
114
+
115
+ /**
116
+ * Get engine by name
117
+ * @param {string} name - Engine name
118
+ * @returns {BaseEngine} Engine instance
119
+ */
120
+ getEngine(name) {
121
+ return this.registry.get(name);
122
+ }
123
+
124
+ /**
125
+ * Create a renderer with specific engine
126
+ * @param {string} engineName - Engine name
127
+ * @param {Object} options - Engine options
128
+ * @returns {Object} Renderer instance
129
+ */
130
+ createRenderer(engineName = this.config.defaultEngine, options = {}) {
131
+ if (!this.initialized) {
132
+ throw new Error('Factory must be initialized before creating a renderer. Call factory.initialize() first.');
133
+ }
134
+
135
+ const engine = this.getEngine(engineName);
136
+ if (!engine) {
137
+ throw new Error(`Engine "${engineName}" not found. Available: ${this.registry.listEngines().join(', ')}`);
138
+ }
139
+
140
+ // Initialize engine if needed
141
+ if (!engine.initialized) {
142
+ engine.initialize(options);
143
+ }
144
+
145
+ // Store references to avoid context issues
146
+ const cacheManager = this.cacheManager;
147
+ const modeManager = this.modeManager;
148
+ const config = this.config;
149
+ const factory = this; // Store factory reference
150
+
151
+ return {
152
+ engine,
153
+ engineName,
154
+ cache: cacheManager,
155
+ options: { ...config, ...options },
156
+
157
+ /**
158
+ * Render template with data
159
+ * @param {string|Function} template - Template content or name
160
+ * @param {Object} data - Template data
161
+ * @param {Object} renderOptions - Render options
162
+ * @returns {Promise<string>} Rendered HTML
163
+ */
164
+ async render(template, data = {}, renderOptions = {}) {
165
+ const startTime = Date.now();
166
+ const mode = config.mode;
167
+
168
+ // Generate cache key
169
+ const cacheKey = config.cacheEnabled
170
+ ? `${mode}:${engineName}:${typeof template === 'string' ? template : 'function'}:${JSON.stringify(data)}`
171
+ : null;
172
+
173
+ // Check cache
174
+ if (cacheKey && cacheManager.has(cacheKey)) {
175
+ const cached = cacheManager.get(cacheKey);
176
+ if (Date.now() - cached.timestamp < config.cacheTTL) {
177
+ console.log(`📦 Cache hit for key: ${cacheKey.substring(0, 50)}...`);
178
+ return cached.html;
179
+ }
180
+ }
181
+
182
+ let html;
183
+ try {
184
+ // Render based on mode
185
+ if (mode === 'ssr') {
186
+ // FIX: Use factory.getEngine instead of this.engine.getEngine
187
+ const ssrEngine = factory.getEngine('ssr-mode'); // Fixed line 179
188
+ if (!ssrEngine) {
189
+ throw new Error('SSR engine not found. Make sure SSRModeEngine is registered.');
190
+ }
191
+ html = await ssrEngine.render(template, data, {
192
+ ...renderOptions,
193
+ engine: engineName
194
+ });
195
+ } else if (mode === 'template') {
196
+ // Use template mode engine
197
+ html = await engine.render(template, data, renderOptions);
198
+ } else {
199
+ throw new Error(`Unsupported mode: ${mode}`);
200
+ }
201
+
202
+ // Cache result
203
+ if (cacheKey && config.cacheEnabled) {
204
+ cacheManager.set(cacheKey, {
205
+ html,
206
+ timestamp: Date.now(),
207
+ mode,
208
+ engine: engineName,
209
+ renderTime: Date.now() - startTime
210
+ });
211
+ console.log(`💾 Cached result for key: ${cacheKey.substring(0, 50)}...`);
212
+ }
213
+
214
+ return html;
215
+
216
+ } catch (error) {
217
+ // Enhanced error handling
218
+ const errorContext = {
219
+ template: typeof template === 'string' ? template.substring(0, 100) + '...' : 'Function',
220
+ engine: engineName,
221
+ mode: mode,
222
+ data: Object.keys(data)
223
+ };
224
+
225
+ const handledError = ErrorHandler.handle(error, errorContext);
226
+
227
+ // Fallback to default mode if configured
228
+ if (renderOptions.fallbackOnError !== false && mode !== 'template') {
229
+ console.warn(`⚠️ Render failed in ${mode} mode, falling back to template mode`);
230
+ // FIX: Use factory.getEngine instead of this.engine.getEngine
231
+ const fallbackEngine = factory.getEngine('template-mode'); // Fixed line 219
232
+ return fallbackEngine.render(template, data, renderOptions);
233
+ }
234
+
235
+ throw handledError;
236
+ }
237
+ },
238
+
239
+ /**
240
+ * Compile template for reuse
241
+ * @param {string} template - Template content
242
+ * @param {Object} compileOptions - Compile options
243
+ * @returns {Function} Compiled function
244
+ */
245
+ compile(template, compileOptions = {}) {
246
+ return engine.compile(template, compileOptions);
247
+ },
248
+
249
+ /**
250
+ * Clear engine cache
251
+ */
252
+ clearCache() {
253
+ engine.clearCache();
254
+ cacheManager.clear();
255
+ }
256
+ };
257
+ }
258
+
259
+ /**
260
+ * Set rendering mode
261
+ * @param {string} mode - 'ssr' or 'template'
262
+ * @returns {TemplateEngineFactory} This factory instance for chaining
263
+ */
264
+ setMode(mode) {
265
+ if (!['ssr', 'template'].includes(mode)) {
266
+ throw new Error(`Invalid mode: ${mode}. Must be 'ssr' or 'template'`);
267
+ }
268
+ this.config.mode = mode;
269
+ console.log(`🔄 Rendering mode changed to: ${mode}`);
270
+ return this;
271
+ }
272
+
273
+ /**
274
+ * Get current mode
275
+ * @returns {string} Current mode
276
+ */
277
+ getMode() {
278
+ return this.config.mode;
279
+ }
280
+
281
+ /**
282
+ * Render with auto-detected engine and mode
283
+ * @param {string} template - Template content or name
284
+ * @param {Object} data - Template data
285
+ * @param {Object} options - Render options
286
+ * @returns {Promise<string>} Rendered HTML
287
+ */
288
+ async renderAuto(template, data = {}, options = {}) {
289
+ const mode = this.getMode();
290
+ const engineName = this.detectEngine(template, options);
291
+ const renderer = this.createRenderer(engineName, options);
292
+ return renderer.render(template, data, options);
293
+ }
294
+
295
+ /**
296
+ * Auto-detect engine based on template content
297
+ * @param {string} template - Template content or file path
298
+ * @param {Object} options - Detection options
299
+ * @returns {string} Engine name
300
+ */
301
+ detectEngine(template, options = {}) {
302
+ // Check by file extension
303
+ if (template.includes('.')) {
304
+ const ext = template.split('.').pop().toLowerCase();
305
+ const extensionMap = {
306
+ 'aether': 'aether',
307
+ 'html': 'aether',
308
+ 'htm': 'aether'
309
+ };
310
+
311
+ if (extensionMap[ext] && this.registry.has(extensionMap[ext])) {
312
+ return extensionMap[ext];
313
+ }
314
+ }
315
+
316
+ // Check by template syntax
317
+ if (typeof template === 'string') {
318
+ // Your custom syntax detection
319
+ if (template.includes('@yield') || template.includes('@section') || template.includes('@extends')) {
320
+ return 'aether';
321
+ } else if (template.includes('{{#') || template.includes('{{/')) {
322
+ return 'aether'; // Fallback to Aether for handlebars-like syntax
323
+ }
324
+ }
325
+
326
+ return options.engine || this.config.defaultEngine;
327
+ }
328
+
329
+ /**
330
+ * Get list of available engines
331
+ * @returns {Array} List of engine names
332
+ */
333
+ listEngines() {
334
+ return this.registry.listEngines();
335
+ }
336
+
337
+ /**
338
+ * Get engine information
339
+ * @param {string} name - Engine name
340
+ * @returns {Object} Engine metadata
341
+ */
342
+ getEngineInfo(name) {
343
+ const engine = this.getEngine(name);
344
+ return engine ? engine.getMetadata() : null;
345
+ }
346
+
347
+ /**
348
+ * Clear all caches
349
+ */
350
+ clearAllCaches() {
351
+ this.cacheManager.clear();
352
+ this.registry.clearCaches();
353
+ console.log('🗑️ All caches cleared');
354
+ }
355
+
356
+ /**
357
+ * Get factory statistics
358
+ * @returns {Object} Statistics
359
+ */
360
+ getStats() {
361
+ return {
362
+ mode: this.getMode(),
363
+ engines: this.listEngines(),
364
+ cacheSize: this.cacheManager.size(),
365
+ cacheEnabled: this.config.cacheEnabled,
366
+ cacheTTL: this.config.cacheTTL,
367
+ templateDir: this.config.templateDir,
368
+ debug: this.config.debug
369
+ };
370
+ }
371
+ }
372
+
373
+ export default TemplateEngineFactory;