@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.
- package/LICENSE +21 -0
- package/README.md +662 -0
- package/examples/basic-usage.js +217 -0
- package/examples/dist/basic-usage-result.html +31 -0
- package/examples/dist/layout-example-result.html +210 -0
- package/examples/dist/templates/layouts/main.aether +58 -0
- package/examples/dist/templates/pages/home.aether +116 -0
- package/examples/layout-example.js +404 -0
- package/examples/ssr-example.js +180 -0
- package/index.js +179 -0
- package/package.json +42 -0
- package/src/core/CacheManager.js +245 -0
- package/src/core/EngineRegistry.js +148 -0
- package/src/core/ModeManager.js +231 -0
- package/src/core/TemplateEngineFactory.js +373 -0
- package/src/engines/AetherEngine.js +582 -0
- package/src/engines/BaseEngine.js +101 -0
- package/src/engines/SSRModeEngine.js +139 -0
- package/src/engines/TemplateModeEngine.js +320 -0
- package/src/utils/ConfigLoader.js +279 -0
- package/src/utils/ErrorHandler.js +276 -0
package/index.js
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aether Template Engine - Main Entry Point
|
|
3
|
+
* Provides a unified interface to access the Template Engine Factory
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import TemplateEngineFactory from './src/core/TemplateEngineFactory.js';
|
|
7
|
+
import ConfigLoader from './src/utils/ConfigLoader.js';
|
|
8
|
+
|
|
9
|
+
// Export main factory class
|
|
10
|
+
export { TemplateEngineFactory };
|
|
11
|
+
|
|
12
|
+
// Export utility classes
|
|
13
|
+
export { ConfigLoader };
|
|
14
|
+
|
|
15
|
+
// Export engine classes
|
|
16
|
+
export { default as AetherEngine } from './src/engines/AetherEngine.js';
|
|
17
|
+
export { default as SSRModeEngine } from './src/engines/SSRModeEngine.js';
|
|
18
|
+
export { default as TemplateModeEngine } from './src/engines/TemplateModeEngine.js';
|
|
19
|
+
export { default as BaseEngine } from './src/engines/BaseEngine.js';
|
|
20
|
+
|
|
21
|
+
// Export core components
|
|
22
|
+
export { default as EngineRegistry } from './src/core/EngineRegistry.js';
|
|
23
|
+
export { default as ModeManager } from './src/core/ModeManager.js';
|
|
24
|
+
export { default as CacheManager } from './src/core/CacheManager.js';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Create a template engine factory with default configuration
|
|
28
|
+
* @param {Object} options - Configuration options
|
|
29
|
+
* @returns {Promise<TemplateEngineFactory>} Template engine factory instance
|
|
30
|
+
*/
|
|
31
|
+
export async function createEngine(options = {}) {
|
|
32
|
+
// Create factory instance
|
|
33
|
+
const factory = new TemplateEngineFactory(options);
|
|
34
|
+
|
|
35
|
+
// Initialize the factory (this will register engines)
|
|
36
|
+
await factory.initialize();
|
|
37
|
+
|
|
38
|
+
return factory;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Create a template engine factory from environment configuration
|
|
43
|
+
* @returns {Promise<TemplateEngineFactory>} Template engine factory instance
|
|
44
|
+
*/
|
|
45
|
+
export async function createEngineFromEnv() {
|
|
46
|
+
const configLoader = new ConfigLoader();
|
|
47
|
+
const config = await configLoader.load();
|
|
48
|
+
|
|
49
|
+
// Validate configuration
|
|
50
|
+
const validation = configLoader.validate();
|
|
51
|
+
if (!validation.valid) {
|
|
52
|
+
console.warn('⚠️ Configuration validation warnings:', validation.warnings);
|
|
53
|
+
if (validation.errors.length > 0) {
|
|
54
|
+
throw new Error(`Configuration errors: ${validation.errors.join(', ')}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const factory = new TemplateEngineFactory(config);
|
|
59
|
+
await factory.initialize();
|
|
60
|
+
|
|
61
|
+
return factory;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Quick start function for common use cases
|
|
66
|
+
* @param {Object} options - Quick start options
|
|
67
|
+
* @returns {Promise<Object>} Engine instance and utilities
|
|
68
|
+
*/
|
|
69
|
+
export async function quickStart(options = {}) {
|
|
70
|
+
const defaultOptions = {
|
|
71
|
+
mode: 'template',
|
|
72
|
+
templateDir: './templates',
|
|
73
|
+
cacheEnabled: true,
|
|
74
|
+
debug: process.env.NODE_ENV === 'development'
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const config = { ...defaultOptions, ...options };
|
|
78
|
+
const factory = new TemplateEngineFactory(config);
|
|
79
|
+
await factory.initialize();
|
|
80
|
+
|
|
81
|
+
// Create default directory structure
|
|
82
|
+
const fs = await import('fs-extra');
|
|
83
|
+
const dirs = [
|
|
84
|
+
config.templateDir,
|
|
85
|
+
`${config.templateDir}/layouts`,
|
|
86
|
+
`${config.templateDir}/pages`,
|
|
87
|
+
`${config.templateDir}/components`,
|
|
88
|
+
`${config.templateDir}/partials`
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
for (const dir of dirs) {
|
|
92
|
+
await fs.ensureDir(dir);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Create default layout
|
|
96
|
+
const defaultLayout = `<!DOCTYPE html>
|
|
97
|
+
<html lang="zh-CN">
|
|
98
|
+
<head>
|
|
99
|
+
<meta charset="UTF-8">
|
|
100
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
101
|
+
<title>@yield('title', 'Aether Framework')</title>
|
|
102
|
+
<meta name="description" content="@yield('description', '下一代轻量级全栈解决方案')">
|
|
103
|
+
<meta name="keywords" content="@yield('keywords', 'Aether,Framework,Web')">
|
|
104
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
|
105
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
106
|
+
@yield('head')
|
|
107
|
+
</head>
|
|
108
|
+
<body class="antialiased bg-[#020617] text-white">
|
|
109
|
+
@yield('content')
|
|
110
|
+
@yield('scripts')
|
|
111
|
+
</body>
|
|
112
|
+
</html>`;
|
|
113
|
+
|
|
114
|
+
const layoutPath = `${config.templateDir}/layouts/default.aether`;
|
|
115
|
+
await fs.writeFile(layoutPath, defaultLayout, 'utf-8');
|
|
116
|
+
|
|
117
|
+
// Create example page
|
|
118
|
+
const examplePage = `@extends('layouts/default')
|
|
119
|
+
|
|
120
|
+
@section('title', 'Welcome to Aether Framework')
|
|
121
|
+
|
|
122
|
+
@section('description', 'A modern template engine for Node.js applications')
|
|
123
|
+
|
|
124
|
+
@section('keywords', 'template, engine, nodejs, javascript')
|
|
125
|
+
|
|
126
|
+
@section('content')
|
|
127
|
+
<div class="min-h-screen flex items-center justify-center">
|
|
128
|
+
<div class="text-center">
|
|
129
|
+
<h1 class="text-4xl font-bold mb-6">Welcome to Aether Framework</h1>
|
|
130
|
+
<p class="text-xl text-gray-300 mb-8">Your template engine is ready to use!</p>
|
|
131
|
+
|
|
132
|
+
<div class="bg-gray-800 rounded-lg p-6 max-w-md mx-auto">
|
|
133
|
+
<h2 class="text-2xl font-semibold mb-4">Quick Start</h2>
|
|
134
|
+
<p class="mb-4">This is an example page created with Aether Template Engine.</p>
|
|
135
|
+
|
|
136
|
+
<div class="space-y-3">
|
|
137
|
+
<div class="flex items-center justify-between">
|
|
138
|
+
<span>Mode:</span>
|
|
139
|
+
<code class="bg-gray-900 px-2 py-1 rounded">{{mode}}</code>
|
|
140
|
+
</div>
|
|
141
|
+
<div class="flex items-center justify-between">
|
|
142
|
+
<span>Cache:</span>
|
|
143
|
+
<span class="{{cacheEnabled ? 'text-green-500' : 'text-red-500'}}">{{cacheEnabled ? 'Enabled' : 'Disabled'}}</span>
|
|
144
|
+
</div>
|
|
145
|
+
<div class="flex items-center justify-between">
|
|
146
|
+
<span>Template Dir:</span>
|
|
147
|
+
<code class="bg-gray-900 px-2 py-1 rounded">{{templateDir}}</code>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
@endsection
|
|
154
|
+
|
|
155
|
+
@section('scripts')
|
|
156
|
+
<script>
|
|
157
|
+
console.log('Aether Template Engine loaded successfully!');
|
|
158
|
+
</script>
|
|
159
|
+
@endsection`;
|
|
160
|
+
|
|
161
|
+
const pagePath = `${config.templateDir}/pages/welcome.aether`;
|
|
162
|
+
await fs.writeFile(pagePath, examplePage, 'utf-8');
|
|
163
|
+
|
|
164
|
+
// Create renderer
|
|
165
|
+
const renderer = factory.createRenderer('aether');
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
factory,
|
|
169
|
+
renderer,
|
|
170
|
+
config
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export default {
|
|
175
|
+
createEngine,
|
|
176
|
+
createEngineFromEnv,
|
|
177
|
+
quickStart,
|
|
178
|
+
TemplateEngineFactory
|
|
179
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aetherframework/template-engine",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A lightweight, high-performance template engine with SSR support and custom syntax",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"start": "node examples/basic-usage.js",
|
|
9
|
+
"test:basic": "node examples/basic-usage.js",
|
|
10
|
+
"test:layout": "node examples/layout-example.js",
|
|
11
|
+
"test:ssr": "node examples/ssr-example.js",
|
|
12
|
+
"clean": "rm -rf templates/cache"
|
|
13
|
+
},
|
|
14
|
+
"author": "Aether Framework Team",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "https://github.com/aetherjs/aetherframework-template-engine.git"
|
|
19
|
+
},
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/aetherjs/aetherframework-template-engine/issues",
|
|
22
|
+
"email": "support@aetherjs.org"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://www.aetherjs.org",
|
|
25
|
+
"files": [
|
|
26
|
+
"index.js",
|
|
27
|
+
"README.md",
|
|
28
|
+
"src",
|
|
29
|
+
"examples",
|
|
30
|
+
"LICENSE"
|
|
31
|
+
],
|
|
32
|
+
"keywords": [
|
|
33
|
+
"template",
|
|
34
|
+
"engine",
|
|
35
|
+
"ssr",
|
|
36
|
+
"aether",
|
|
37
|
+
"html"
|
|
38
|
+
],
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"fs-extra": "^11.2.0"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license MIT
|
|
3
|
+
* Copyright (c) 2026-present AetherFramework Contributors.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
* @module @aetherframework/template-engine/src/core/CacheManager
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Cache Manager - Manages template and compilation cache
|
|
9
|
+
*/
|
|
10
|
+
class CacheManager {
|
|
11
|
+
constructor(options = {}) {
|
|
12
|
+
this.options = {
|
|
13
|
+
enabled: options.enabled !== false,
|
|
14
|
+
ttl: options.ttl || 300000, // 5 minutes default
|
|
15
|
+
maxSize: options.maxSize || 1000,
|
|
16
|
+
...options
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
this.cache = new Map();
|
|
20
|
+
this.stats = {
|
|
21
|
+
hits: 0,
|
|
22
|
+
misses: 0,
|
|
23
|
+
sets: 0,
|
|
24
|
+
deletes: 0,
|
|
25
|
+
size: 0
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Set cache entry
|
|
31
|
+
* @param {string} key - Cache key
|
|
32
|
+
* @param {any} value - Cache value
|
|
33
|
+
* @param {number} ttl - Time to live in milliseconds
|
|
34
|
+
* @returns {CacheManager} This instance for chaining
|
|
35
|
+
*/
|
|
36
|
+
set(key, value, ttl = this.options.ttl) {
|
|
37
|
+
if (!this.options.enabled) return this;
|
|
38
|
+
|
|
39
|
+
// Check cache size limit
|
|
40
|
+
if (this.cache.size >= this.options.maxSize) {
|
|
41
|
+
this.evictOldest();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const entry = {
|
|
45
|
+
value,
|
|
46
|
+
timestamp: Date.now(),
|
|
47
|
+
ttl,
|
|
48
|
+
expiresAt: Date.now() + ttl
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
this.cache.set(key, entry);
|
|
52
|
+
this.stats.sets++;
|
|
53
|
+
this.stats.size = this.cache.size;
|
|
54
|
+
|
|
55
|
+
return this;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get cache entry
|
|
60
|
+
* @param {string} key - Cache key
|
|
61
|
+
* @returns {any} Cached value or undefined
|
|
62
|
+
*/
|
|
63
|
+
get(key) {
|
|
64
|
+
if (!this.options.enabled) return undefined;
|
|
65
|
+
|
|
66
|
+
const entry = this.cache.get(key);
|
|
67
|
+
|
|
68
|
+
if (!entry) {
|
|
69
|
+
this.stats.misses++;
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Check if entry has expired
|
|
74
|
+
if (Date.now() > entry.expiresAt) {
|
|
75
|
+
this.cache.delete(key);
|
|
76
|
+
this.stats.misses++;
|
|
77
|
+
this.stats.size = this.cache.size;
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
this.stats.hits++;
|
|
82
|
+
return entry.value;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Check if cache has key
|
|
87
|
+
* @param {string} key - Cache key
|
|
88
|
+
* @returns {boolean} True if key exists and is not expired
|
|
89
|
+
*/
|
|
90
|
+
has(key) {
|
|
91
|
+
if (!this.options.enabled) return false;
|
|
92
|
+
|
|
93
|
+
const entry = this.cache.get(key);
|
|
94
|
+
if (!entry) return false;
|
|
95
|
+
|
|
96
|
+
if (Date.now() > entry.expiresAt) {
|
|
97
|
+
this.cache.delete(key);
|
|
98
|
+
this.stats.size = this.cache.size;
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Delete cache entry
|
|
107
|
+
* @param {string} key - Cache key
|
|
108
|
+
* @returns {boolean} True if entry was deleted
|
|
109
|
+
*/
|
|
110
|
+
delete(key) {
|
|
111
|
+
const deleted = this.cache.delete(key);
|
|
112
|
+
if (deleted) {
|
|
113
|
+
this.stats.deletes++;
|
|
114
|
+
this.stats.size = this.cache.size;
|
|
115
|
+
}
|
|
116
|
+
return deleted;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Clear all cache entries
|
|
121
|
+
* @returns {CacheManager} This instance for chaining
|
|
122
|
+
*/
|
|
123
|
+
clear() {
|
|
124
|
+
this.cache.clear();
|
|
125
|
+
this.stats.size = 0;
|
|
126
|
+
this.stats.deletes++;
|
|
127
|
+
return this;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Get cache size
|
|
132
|
+
* @returns {number} Number of cache entries
|
|
133
|
+
*/
|
|
134
|
+
size() {
|
|
135
|
+
return this.cache.size;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Get cache statistics
|
|
140
|
+
* @returns {Object} Cache statistics
|
|
141
|
+
*/
|
|
142
|
+
getStats() {
|
|
143
|
+
return {
|
|
144
|
+
...this.stats,
|
|
145
|
+
enabled: this.options.enabled,
|
|
146
|
+
ttl: this.options.ttl,
|
|
147
|
+
maxSize: this.options.maxSize,
|
|
148
|
+
currentSize: this.cache.size,
|
|
149
|
+
hitRate: this.stats.hits + this.stats.misses > 0
|
|
150
|
+
? (this.stats.hits / (this.stats.hits + this.stats.misses) * 100).toFixed(2) + '%'
|
|
151
|
+
: '0%'
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Evict oldest cache entries
|
|
157
|
+
* @private
|
|
158
|
+
*/
|
|
159
|
+
evictOldest() {
|
|
160
|
+
if (this.cache.size === 0) return;
|
|
161
|
+
|
|
162
|
+
// Find oldest entry
|
|
163
|
+
let oldestKey = null;
|
|
164
|
+
let oldestTime = Infinity;
|
|
165
|
+
|
|
166
|
+
for (const [key, entry] of this.cache) {
|
|
167
|
+
if (entry.timestamp < oldestTime) {
|
|
168
|
+
oldestTime = entry.timestamp;
|
|
169
|
+
oldestKey = key;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (oldestKey) {
|
|
174
|
+
this.cache.delete(oldestKey);
|
|
175
|
+
this.stats.deletes++;
|
|
176
|
+
this.stats.size = this.cache.size;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Clean expired cache entries
|
|
182
|
+
* @returns {number} Number of entries cleaned
|
|
183
|
+
*/
|
|
184
|
+
cleanExpired() {
|
|
185
|
+
let cleaned = 0;
|
|
186
|
+
const now = Date.now();
|
|
187
|
+
|
|
188
|
+
for (const [key, entry] of this.cache) {
|
|
189
|
+
if (now > entry.expiresAt) {
|
|
190
|
+
this.cache.delete(key);
|
|
191
|
+
cleaned++;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
this.stats.size = this.cache.size;
|
|
196
|
+
return cleaned;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Enable cache
|
|
201
|
+
* @returns {CacheManager} This instance for chaining
|
|
202
|
+
*/
|
|
203
|
+
enable() {
|
|
204
|
+
this.options.enabled = true;
|
|
205
|
+
return this;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Disable cache
|
|
210
|
+
* @returns {CacheManager} This instance for chaining
|
|
211
|
+
*/
|
|
212
|
+
disable() {
|
|
213
|
+
this.options.enabled = false;
|
|
214
|
+
this.clear();
|
|
215
|
+
return this;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Set cache TTL
|
|
220
|
+
* @param {number} ttl - Time to live in milliseconds
|
|
221
|
+
* @returns {CacheManager} This instance for chaining
|
|
222
|
+
*/
|
|
223
|
+
setTTL(ttl) {
|
|
224
|
+
this.options.ttl = ttl;
|
|
225
|
+
return this;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Set cache max size
|
|
230
|
+
* @param {number} maxSize - Maximum cache size
|
|
231
|
+
* @returns {CacheManager} This instance for chaining
|
|
232
|
+
*/
|
|
233
|
+
setMaxSize(maxSize) {
|
|
234
|
+
this.options.maxSize = maxSize;
|
|
235
|
+
|
|
236
|
+
// Evict entries if current size exceeds new max
|
|
237
|
+
while (this.cache.size > maxSize) {
|
|
238
|
+
this.evictOldest();
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return this;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export default CacheManager;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license MIT
|
|
3
|
+
* Copyright (c) 2026-present AetherFramework Contributors.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
* @module @aetherframework/template-engine/src/core/EngineRegistry
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Engine Registry - Manages template engine registration and retrieval
|
|
10
|
+
*/
|
|
11
|
+
class EngineRegistry {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.engines = new Map();
|
|
14
|
+
this.defaultEngine = null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Register a template engine
|
|
19
|
+
* @param {string} name - Engine name
|
|
20
|
+
* @param {BaseEngine} engine - Engine instance
|
|
21
|
+
* @param {boolean} isDefault - Whether to set as default engine
|
|
22
|
+
*/
|
|
23
|
+
register(name, engine, isDefault = false) {
|
|
24
|
+
if (this.engines.has(name)) {
|
|
25
|
+
console.warn(`⚠️ Engine "${name}" already registered, overwriting...`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
this.engines.set(name, engine);
|
|
29
|
+
|
|
30
|
+
if (isDefault || this.engines.size === 1) {
|
|
31
|
+
this.defaultEngine = name;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return this;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Get engine by name
|
|
39
|
+
* @param {string} name - Engine name
|
|
40
|
+
* @returns {BaseEngine} Engine instance
|
|
41
|
+
*/
|
|
42
|
+
get(name) {
|
|
43
|
+
if (!this.engines.has(name)) {
|
|
44
|
+
throw new Error(`Engine "${name}" not found. Available engines: ${Array.from(this.engines.keys()).join(', ')}`);
|
|
45
|
+
}
|
|
46
|
+
return this.engines.get(name);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Check if engine exists
|
|
51
|
+
* @param {string} name - Engine name
|
|
52
|
+
* @returns {boolean} True if engine exists
|
|
53
|
+
*/
|
|
54
|
+
has(name) {
|
|
55
|
+
return this.engines.has(name);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Remove engine by name
|
|
60
|
+
* @param {string} name - Engine name
|
|
61
|
+
* @returns {boolean} True if engine was removed
|
|
62
|
+
*/
|
|
63
|
+
unregister(name) {
|
|
64
|
+
const removed = this.engines.delete(name);
|
|
65
|
+
|
|
66
|
+
if (removed && this.defaultEngine === name) {
|
|
67
|
+
this.defaultEngine = this.engines.keys().next().value || null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return removed;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get default engine
|
|
75
|
+
* @returns {BaseEngine} Default engine instance
|
|
76
|
+
*/
|
|
77
|
+
getDefault() {
|
|
78
|
+
if (!this.defaultEngine) {
|
|
79
|
+
throw new Error('No default engine set');
|
|
80
|
+
}
|
|
81
|
+
return this.get(this.defaultEngine);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Set default engine
|
|
86
|
+
* @param {string} name - Engine name
|
|
87
|
+
*/
|
|
88
|
+
setDefault(name) {
|
|
89
|
+
if (!this.engines.has(name)) {
|
|
90
|
+
throw new Error(`Cannot set default: Engine "${name}" not found`);
|
|
91
|
+
}
|
|
92
|
+
this.defaultEngine = name;
|
|
93
|
+
return this;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* List all registered engines
|
|
98
|
+
* @returns {Array} List of engine names
|
|
99
|
+
*/
|
|
100
|
+
listEngines() {
|
|
101
|
+
return Array.from(this.engines.keys());
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get engine metadata
|
|
106
|
+
* @param {string} name - Engine name
|
|
107
|
+
* @returns {Object} Engine metadata
|
|
108
|
+
*/
|
|
109
|
+
getEngineInfo(name) {
|
|
110
|
+
const engine = this.get(name);
|
|
111
|
+
return engine ? engine.getMetadata() : null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Clear all engine caches
|
|
116
|
+
*/
|
|
117
|
+
clearCaches() {
|
|
118
|
+
for (const [name, engine] of this.engines) {
|
|
119
|
+
if (typeof engine.clearCache === 'function') {
|
|
120
|
+
engine.clearCache();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Get engine statistics
|
|
127
|
+
* @returns {Object} Statistics
|
|
128
|
+
*/
|
|
129
|
+
getStats() {
|
|
130
|
+
const stats = {
|
|
131
|
+
totalEngines: this.engines.size,
|
|
132
|
+
defaultEngine: this.defaultEngine,
|
|
133
|
+
engines: []
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
for (const [name, engine] of this.engines) {
|
|
137
|
+
stats.engines.push({
|
|
138
|
+
name,
|
|
139
|
+
initialized: engine.initialized || false,
|
|
140
|
+
metadata: engine.getMetadata ? engine.getMetadata() : {}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return stats;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export default EngineRegistry;
|