jsql-neo 5.4.2 → 5.5.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 (72) hide show
  1. package/LICENSE +202 -202
  2. package/README.md +8097 -7971
  3. package/bin/jsql +320 -320
  4. package/bin/jsql-server +90 -90
  5. package/bin/jsql-server-control.js +177 -177
  6. package/index.d.ts +385 -357
  7. package/index.js +124 -118
  8. package/lib/ast.js +214 -0
  9. package/lib/btree.js +413 -413
  10. package/lib/cache.js +328 -328
  11. package/lib/client.js +125 -125
  12. package/lib/database.js +1744 -1744
  13. package/lib/date-types.js +182 -182
  14. package/lib/errors.js +89 -89
  15. package/lib/jsql_format.js +497 -497
  16. package/lib/migrate.js +254 -254
  17. package/lib/mod.js +292 -292
  18. package/lib/mongo_server.js +607 -607
  19. package/lib/multiserver.js +140 -140
  20. package/lib/mysql_compat.js +413 -413
  21. package/lib/mysql_server.js +1237 -1237
  22. package/lib/native_client.js +732 -732
  23. package/lib/nedb_compat.js +506 -506
  24. package/lib/pg_server.js +864 -864
  25. package/lib/plugin.js +34 -34
  26. package/lib/query.js +815 -815
  27. package/lib/redis_server.js +571 -571
  28. package/lib/sql.js +3460 -2925
  29. package/lib/sqlite_compat.js +323 -323
  30. package/lib/sqlite_worker.js +264 -264
  31. package/lib/table.js +1304 -1304
  32. package/lib/tui.js +502 -502
  33. package/lib/wasm.js +1 -1
  34. package/lib/wasm_client.js +718 -718
  35. package/lib/web_ui.js +257 -257
  36. package/native/jsql-neo-native.node +0 -0
  37. package/nativesrc/jsql-neo-core/Cargo.lock +455 -455
  38. package/nativesrc/jsql-neo-core/Cargo.toml +24 -24
  39. package/nativesrc/jsql-neo-core/src/engine/hybrid.rs +455 -455
  40. package/nativesrc/jsql-neo-core/src/engine/memory.rs +168 -168
  41. package/nativesrc/jsql-neo-core/src/engine/mod.rs +62 -62
  42. package/nativesrc/jsql-neo-core/src/engine/table.rs +700 -700
  43. package/nativesrc/jsql-neo-core/src/lib.rs +2 -2
  44. package/nativesrc/jsql-neo-core/src/storage/mod.rs +2 -2
  45. package/nativesrc/jsql-neo-core/src/storage/persistent.rs +1 -1
  46. package/nativesrc/jsql-neo-core/src/storage/wal.rs +85 -85
  47. package/nativesrc/jsql-neo-core/src/types.rs +93 -93
  48. package/nativesrc/jsql-neo-native/Cargo.lock +606 -606
  49. package/nativesrc/jsql-neo-native/Cargo.toml +16 -16
  50. package/nativesrc/jsql-neo-native/build.rs +3 -3
  51. package/nativesrc/jsql-neo-native/package.json +7 -7
  52. package/nativesrc/jsql-neo-native/src/lib.rs +269 -269
  53. package/nativesrc/jsql-neo-wasm/Cargo.lock +465 -465
  54. package/nativesrc/jsql-neo-wasm/Cargo.toml +15 -15
  55. package/nativesrc/jsql-neo-wasm/src/lib.rs +447 -447
  56. package/package.json +87 -86
  57. package/postinstall.js +158 -158
  58. package/test/btree.test.js +128 -128
  59. package/test/coverage.js +187 -187
  60. package/test/join.test.js +110 -110
  61. package/test/native.test.js +158 -158
  62. package/test/regress-5.1.0.js +271 -271
  63. package/test/smoke.js +58 -58
  64. package/test/sql-parser.test.js +273 -0
  65. package/test/wasm.test.js +110 -110
  66. package/wasm/browser.d.ts +89 -89
  67. package/wasm/browser.mjs +569 -569
  68. package/wasm/browser_bg.mjs +597 -597
  69. package/wasm/jsql_neo_wasm.d.ts +38 -38
  70. package/wasm/jsql_neo_wasm.js +520 -520
  71. package/wasm/jsql_neo_wasm_bg.wasm.d.ts +26 -26
  72. package/wasm/package.json +11 -11
package/lib/mod.js CHANGED
@@ -1,292 +1,292 @@
1
- const fs = require('fs');
2
- const os = require('os');
3
- const path = require('path');
4
- const Module = require('module');
5
-
6
- const DEFAULT_CONFIG_PATH = path.join(os.homedir(), '.config', 'jsql', 'mod.config');
7
- const CONFIG_PATH = process.env.JSQL_MOD_CONFIG || DEFAULT_CONFIG_PATH;
8
-
9
- class ModApi {
10
- constructor(collector) {
11
- this._collector = collector;
12
- }
13
-
14
- nameset(name) {
15
- if (typeof name !== 'string' || !name) {
16
- throw new Error('nameset: module name must be a non-empty string');
17
- }
18
- if (this._collector.nameSet) {
19
- throw new Error('nameset: module name already declared');
20
- }
21
- this._collector.name = name;
22
- this._collector.nameSet = true;
23
- }
24
-
25
- priority(n) {
26
- this._collector.priority = n;
27
- return this;
28
- }
29
-
30
- depends(name) {
31
- if (typeof name === 'string') name = [name];
32
- for (const d of name) {
33
- if (typeof d !== 'string' || !d) {
34
- throw new Error('depends: dependency names must be non-empty strings');
35
- }
36
- }
37
- this._collector.depends = [...(this._collector.depends || []), ...name];
38
- return this;
39
- }
40
-
41
- api(obj) {
42
- if (typeof obj !== 'object' || obj === null) {
43
- throw new Error('api: must be an object');
44
- }
45
- this._collector.api = obj;
46
- return this;
47
- }
48
-
49
- on(hook, fn) {
50
- if (typeof fn !== 'function') throw new Error('on: hook must be a function');
51
- if (!this._collector.hooks[hook]) this._collector.hooks[hook] = [];
52
- this._collector.hooks[hook].push(fn);
53
- return this;
54
- }
55
-
56
- onEvent(fn) {
57
- if (typeof fn !== 'function') throw new Error('onEvent: must be a function');
58
- this._collector.onEvent = fn;
59
- return this;
60
- }
61
-
62
- install(fn) {
63
- if (typeof fn !== 'function') throw new Error('install: must be a function');
64
- this._collector.install = fn;
65
- return this;
66
- }
67
- }
68
-
69
- class ModuleManager {
70
- constructor(configPath = CONFIG_PATH) {
71
- this._configPath = configPath;
72
- this._modules = [];
73
- this._load();
74
- }
75
-
76
- _load() {
77
- try {
78
- if (!fs.existsSync(this._configPath)) return;
79
- const data = JSON.parse(fs.readFileSync(this._configPath, 'utf8'));
80
- if (Array.isArray(data)) {
81
- this._modules = data;
82
- } else if (Array.isArray(data.modules)) {
83
- this._modules = data.modules;
84
- }
85
- } catch (e) {
86
- this._modules = [];
87
- }
88
- }
89
-
90
- save() {
91
- const dir = path.dirname(this._configPath);
92
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
93
- fs.writeFileSync(this._configPath, JSON.stringify({ modules: this._modules }, null, 2), 'utf8');
94
- try { fs.chmodSync(this._configPath, 0o600); } catch (e) {}
95
- }
96
-
97
- list() {
98
- return this._modules.map(m => ({ ...m }));
99
- }
100
-
101
- find(name) {
102
- return this._modules.find(m => m.name === name) || null;
103
- }
104
-
105
- add(address, opts = {}) {
106
- const resolved = path.resolve(address);
107
- if (!fs.existsSync(resolved)) {
108
- throw new Error(`Module file not found: ${resolved}`);
109
- }
110
- if (fs.statSync(resolved).isDirectory()) {
111
- throw new Error(`Expected a module file, got directory: ${resolved}`);
112
- }
113
-
114
- const plugin = this._loadModuleFile(resolved);
115
- const name = plugin.name;
116
-
117
- if (this.find(name)) {
118
- throw new Error(`Module already registered: ${name}`);
119
- }
120
-
121
- const entry = {
122
- name,
123
- path: resolved,
124
- enabled: false,
125
- priority: plugin.priority || 0,
126
- depends: plugin.depends || [],
127
- addedAt: Date.now()
128
- };
129
- this._modules.push(entry);
130
- this.save();
131
- return { ...entry };
132
- }
133
-
134
- enable(name, opts = {}) {
135
- const m = this.find(name);
136
- if (!m) throw new Error(`Module not found: ${name}. Use 'jsql mod add --address <path>' first.`);
137
- if (m.enabled) return { ...m, ok: true, message: 'already enabled' };
138
-
139
- const enabling = this._enablingStack || (this._enablingStack = new Set());
140
- if (enabling.has(name)) throw new Error(`Circular module dependency: ${[...enabling, name].join(' -> ')}`);
141
- enabling.add(name);
142
-
143
- try {
144
- const plugin = this._loadModuleFile(m.path);
145
-
146
- if (plugin.depends && plugin.depends.length > 0) {
147
- for (const dep of plugin.depends) {
148
- const depMod = this.find(dep);
149
- if (!depMod) throw new Error(`Module '${name}' depends on '${dep}' which is not registered`);
150
- if (!depMod.enabled && opts.withDeps !== false) {
151
- this.enable(dep, opts);
152
- }
153
- }
154
- }
155
-
156
- m.enabled = true;
157
- m.priority = plugin.priority || m.priority || 0;
158
- this.save();
159
- return { ...m, ok: true };
160
- } finally {
161
- enabling.delete(name);
162
- }
163
- }
164
-
165
- disable(name) {
166
- const m = this.find(name);
167
- if (!m) throw new Error(`Module not found: ${name}`);
168
- if (!m.enabled) return { ...m, ok: true, message: 'already disabled' };
169
- for (const other of this._modules) {
170
- const plugin = this._loadModuleFile(other.path);
171
- if (plugin.depends && plugin.depends.includes(name) && other.enabled) {
172
- this.disable(other.name);
173
- }
174
- }
175
- m.enabled = false;
176
- this.save();
177
- return { ...m, ok: true };
178
- }
179
-
180
- enableAll() {
181
- const names = this._modules.map(m => m.name);
182
- for (const name of names) {
183
- try { this.enable(name); } catch (e) { /* skip failed */ }
184
- }
185
- this.save();
186
- return this.list().filter(m => m.enabled).length;
187
- }
188
-
189
- disableAll() {
190
- for (const m of this._modules) m.enabled = false;
191
- this.save();
192
- return this._modules.length;
193
- }
194
-
195
- applyTo(db) {
196
- const enabled = this._sortEnabled();
197
- for (const entry of enabled) {
198
- const plugin = this._loadModuleFile(entry.path);
199
- db.use(plugin);
200
- }
201
- return db;
202
- }
203
-
204
- api(name) {
205
- const m = this.find(name);
206
- if (!m) throw new Error(`Module not found: ${name}`);
207
- const plugin = this._loadModuleFile(m.path);
208
- return plugin.api || null;
209
- }
210
-
211
- _sortEnabled() {
212
- const enabled = this._modules.filter(m => m.enabled).map(m => ({ ...m }));
213
- const resolved = new Set();
214
- const order = [];
215
- const cache = new Map();
216
-
217
- const resolve = (name, chain) => {
218
- if (resolved.has(name)) return 0;
219
- const entry = enabled.find(m => m.name === name);
220
- if (!entry) return 0;
221
- if (chain.has(name)) throw new Error(`Circular module dependency: ${[...chain, name].join(' -> ')}`);
222
-
223
- let plugin;
224
- if (cache.has(entry.path)) {
225
- plugin = cache.get(entry.path);
226
- } else {
227
- plugin = this._loadModuleFile(entry.path);
228
- cache.set(entry.path, plugin);
229
- }
230
-
231
- let maxDepDepth = -1;
232
- for (const dep of (plugin.depends || [])) {
233
- const d = resolve(dep, new Set([...chain, name]));
234
- maxDepDepth = Math.max(maxDepDepth, d);
235
- }
236
- resolved.add(name);
237
- order.push({ entry, depth: maxDepDepth + 1 });
238
- return maxDepDepth + 1;
239
- };
240
-
241
- for (const entry of enabled) resolve(entry.name, new Set());
242
-
243
- // 依赖深度优先,同层按 priority 排序:保证被依赖方始终先应用
244
- order.sort((a, b) => a.depth - b.depth || (a.entry.priority || 0) - (b.entry.priority || 0));
245
- return order.map(o => o.entry);
246
- }
247
-
248
- remove(name) {
249
- const idx = this._modules.findIndex(m => m.name === name);
250
- if (idx === -1) throw new Error(`Module not found: ${name}`);
251
- const [removed] = this._modules.splice(idx, 1);
252
- this.save();
253
- return removed;
254
- }
255
-
256
- get(name) {
257
- const m = this.find(name);
258
- if (!m) throw new Error(`Module not found: ${name}`);
259
- return this._loadModuleFile(m.path);
260
- }
261
-
262
- _loadModuleFile(filePath) {
263
- const ext = path.extname(filePath).toLowerCase();
264
-
265
- if (ext === '.json') {
266
- const obj = JSON.parse(fs.readFileSync(filePath, 'utf8'));
267
- return {
268
- name: obj.name || path.basename(filePath, ext),
269
- ...obj
270
- };
271
- }
272
-
273
- // JS 模块使用 Node.js require 直接加载(信任模型——模块由管理员通过 CLI 管理,
274
- // 与 npm 包模型相同,无安全隔离假设)。
275
- // 如需防止非预期代码执行,请使用 JSON-only 模块(只声明 hooks/api 无代码执行能力)。
276
- const resolved = path.isAbsolute(filePath) ? filePath : require.resolve(filePath);
277
- delete require.cache[resolved];
278
- let plugin;
279
- try {
280
- plugin = require(filePath);
281
- } catch (e) {
282
- throw new Error(`Failed to load module ${filePath}: ${e.message}`);
283
- }
284
- if (typeof plugin === 'function') {
285
- return { install: plugin, name: path.basename(filePath, ext) };
286
- }
287
- if (!plugin.name) plugin.name = path.basename(filePath, ext);
288
- return plugin;
289
- }
290
- }
291
-
292
- module.exports = { ModuleManager, ModApi, CONFIG_PATH };
1
+ const fs = require('fs');
2
+ const os = require('os');
3
+ const path = require('path');
4
+ const Module = require('module');
5
+
6
+ const DEFAULT_CONFIG_PATH = path.join(os.homedir(), '.config', 'jsql', 'mod.config');
7
+ const CONFIG_PATH = process.env.JSQL_MOD_CONFIG || DEFAULT_CONFIG_PATH;
8
+
9
+ class ModApi {
10
+ constructor(collector) {
11
+ this._collector = collector;
12
+ }
13
+
14
+ nameset(name) {
15
+ if (typeof name !== 'string' || !name) {
16
+ throw new Error('nameset: module name must be a non-empty string');
17
+ }
18
+ if (this._collector.nameSet) {
19
+ throw new Error('nameset: module name already declared');
20
+ }
21
+ this._collector.name = name;
22
+ this._collector.nameSet = true;
23
+ }
24
+
25
+ priority(n) {
26
+ this._collector.priority = n;
27
+ return this;
28
+ }
29
+
30
+ depends(name) {
31
+ if (typeof name === 'string') name = [name];
32
+ for (const d of name) {
33
+ if (typeof d !== 'string' || !d) {
34
+ throw new Error('depends: dependency names must be non-empty strings');
35
+ }
36
+ }
37
+ this._collector.depends = [...(this._collector.depends || []), ...name];
38
+ return this;
39
+ }
40
+
41
+ api(obj) {
42
+ if (typeof obj !== 'object' || obj === null) {
43
+ throw new Error('api: must be an object');
44
+ }
45
+ this._collector.api = obj;
46
+ return this;
47
+ }
48
+
49
+ on(hook, fn) {
50
+ if (typeof fn !== 'function') throw new Error('on: hook must be a function');
51
+ if (!this._collector.hooks[hook]) this._collector.hooks[hook] = [];
52
+ this._collector.hooks[hook].push(fn);
53
+ return this;
54
+ }
55
+
56
+ onEvent(fn) {
57
+ if (typeof fn !== 'function') throw new Error('onEvent: must be a function');
58
+ this._collector.onEvent = fn;
59
+ return this;
60
+ }
61
+
62
+ install(fn) {
63
+ if (typeof fn !== 'function') throw new Error('install: must be a function');
64
+ this._collector.install = fn;
65
+ return this;
66
+ }
67
+ }
68
+
69
+ class ModuleManager {
70
+ constructor(configPath = CONFIG_PATH) {
71
+ this._configPath = configPath;
72
+ this._modules = [];
73
+ this._load();
74
+ }
75
+
76
+ _load() {
77
+ try {
78
+ if (!fs.existsSync(this._configPath)) return;
79
+ const data = JSON.parse(fs.readFileSync(this._configPath, 'utf8'));
80
+ if (Array.isArray(data)) {
81
+ this._modules = data;
82
+ } else if (Array.isArray(data.modules)) {
83
+ this._modules = data.modules;
84
+ }
85
+ } catch (e) {
86
+ this._modules = [];
87
+ }
88
+ }
89
+
90
+ save() {
91
+ const dir = path.dirname(this._configPath);
92
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
93
+ fs.writeFileSync(this._configPath, JSON.stringify({ modules: this._modules }, null, 2), 'utf8');
94
+ try { fs.chmodSync(this._configPath, 0o600); } catch (e) {}
95
+ }
96
+
97
+ list() {
98
+ return this._modules.map(m => ({ ...m }));
99
+ }
100
+
101
+ find(name) {
102
+ return this._modules.find(m => m.name === name) || null;
103
+ }
104
+
105
+ add(address, opts = {}) {
106
+ const resolved = path.resolve(address);
107
+ if (!fs.existsSync(resolved)) {
108
+ throw new Error(`Module file not found: ${resolved}`);
109
+ }
110
+ if (fs.statSync(resolved).isDirectory()) {
111
+ throw new Error(`Expected a module file, got directory: ${resolved}`);
112
+ }
113
+
114
+ const plugin = this._loadModuleFile(resolved);
115
+ const name = plugin.name;
116
+
117
+ if (this.find(name)) {
118
+ throw new Error(`Module already registered: ${name}`);
119
+ }
120
+
121
+ const entry = {
122
+ name,
123
+ path: resolved,
124
+ enabled: false,
125
+ priority: plugin.priority || 0,
126
+ depends: plugin.depends || [],
127
+ addedAt: Date.now()
128
+ };
129
+ this._modules.push(entry);
130
+ this.save();
131
+ return { ...entry };
132
+ }
133
+
134
+ enable(name, opts = {}) {
135
+ const m = this.find(name);
136
+ if (!m) throw new Error(`Module not found: ${name}. Use 'jsql mod add --address <path>' first.`);
137
+ if (m.enabled) return { ...m, ok: true, message: 'already enabled' };
138
+
139
+ const enabling = this._enablingStack || (this._enablingStack = new Set());
140
+ if (enabling.has(name)) throw new Error(`Circular module dependency: ${[...enabling, name].join(' -> ')}`);
141
+ enabling.add(name);
142
+
143
+ try {
144
+ const plugin = this._loadModuleFile(m.path);
145
+
146
+ if (plugin.depends && plugin.depends.length > 0) {
147
+ for (const dep of plugin.depends) {
148
+ const depMod = this.find(dep);
149
+ if (!depMod) throw new Error(`Module '${name}' depends on '${dep}' which is not registered`);
150
+ if (!depMod.enabled && opts.withDeps !== false) {
151
+ this.enable(dep, opts);
152
+ }
153
+ }
154
+ }
155
+
156
+ m.enabled = true;
157
+ m.priority = plugin.priority || m.priority || 0;
158
+ this.save();
159
+ return { ...m, ok: true };
160
+ } finally {
161
+ enabling.delete(name);
162
+ }
163
+ }
164
+
165
+ disable(name) {
166
+ const m = this.find(name);
167
+ if (!m) throw new Error(`Module not found: ${name}`);
168
+ if (!m.enabled) return { ...m, ok: true, message: 'already disabled' };
169
+ for (const other of this._modules) {
170
+ const plugin = this._loadModuleFile(other.path);
171
+ if (plugin.depends && plugin.depends.includes(name) && other.enabled) {
172
+ this.disable(other.name);
173
+ }
174
+ }
175
+ m.enabled = false;
176
+ this.save();
177
+ return { ...m, ok: true };
178
+ }
179
+
180
+ enableAll() {
181
+ const names = this._modules.map(m => m.name);
182
+ for (const name of names) {
183
+ try { this.enable(name); } catch (e) { /* skip failed */ }
184
+ }
185
+ this.save();
186
+ return this.list().filter(m => m.enabled).length;
187
+ }
188
+
189
+ disableAll() {
190
+ for (const m of this._modules) m.enabled = false;
191
+ this.save();
192
+ return this._modules.length;
193
+ }
194
+
195
+ applyTo(db) {
196
+ const enabled = this._sortEnabled();
197
+ for (const entry of enabled) {
198
+ const plugin = this._loadModuleFile(entry.path);
199
+ db.use(plugin);
200
+ }
201
+ return db;
202
+ }
203
+
204
+ api(name) {
205
+ const m = this.find(name);
206
+ if (!m) throw new Error(`Module not found: ${name}`);
207
+ const plugin = this._loadModuleFile(m.path);
208
+ return plugin.api || null;
209
+ }
210
+
211
+ _sortEnabled() {
212
+ const enabled = this._modules.filter(m => m.enabled).map(m => ({ ...m }));
213
+ const resolved = new Set();
214
+ const order = [];
215
+ const cache = new Map();
216
+
217
+ const resolve = (name, chain) => {
218
+ if (resolved.has(name)) return 0;
219
+ const entry = enabled.find(m => m.name === name);
220
+ if (!entry) return 0;
221
+ if (chain.has(name)) throw new Error(`Circular module dependency: ${[...chain, name].join(' -> ')}`);
222
+
223
+ let plugin;
224
+ if (cache.has(entry.path)) {
225
+ plugin = cache.get(entry.path);
226
+ } else {
227
+ plugin = this._loadModuleFile(entry.path);
228
+ cache.set(entry.path, plugin);
229
+ }
230
+
231
+ let maxDepDepth = -1;
232
+ for (const dep of (plugin.depends || [])) {
233
+ const d = resolve(dep, new Set([...chain, name]));
234
+ maxDepDepth = Math.max(maxDepDepth, d);
235
+ }
236
+ resolved.add(name);
237
+ order.push({ entry, depth: maxDepDepth + 1 });
238
+ return maxDepDepth + 1;
239
+ };
240
+
241
+ for (const entry of enabled) resolve(entry.name, new Set());
242
+
243
+ // 依赖深度优先,同层按 priority 排序:保证被依赖方始终先应用
244
+ order.sort((a, b) => a.depth - b.depth || (a.entry.priority || 0) - (b.entry.priority || 0));
245
+ return order.map(o => o.entry);
246
+ }
247
+
248
+ remove(name) {
249
+ const idx = this._modules.findIndex(m => m.name === name);
250
+ if (idx === -1) throw new Error(`Module not found: ${name}`);
251
+ const [removed] = this._modules.splice(idx, 1);
252
+ this.save();
253
+ return removed;
254
+ }
255
+
256
+ get(name) {
257
+ const m = this.find(name);
258
+ if (!m) throw new Error(`Module not found: ${name}`);
259
+ return this._loadModuleFile(m.path);
260
+ }
261
+
262
+ _loadModuleFile(filePath) {
263
+ const ext = path.extname(filePath).toLowerCase();
264
+
265
+ if (ext === '.json') {
266
+ const obj = JSON.parse(fs.readFileSync(filePath, 'utf8'));
267
+ return {
268
+ name: obj.name || path.basename(filePath, ext),
269
+ ...obj
270
+ };
271
+ }
272
+
273
+ // JS 模块使用 Node.js require 直接加载(信任模型——模块由管理员通过 CLI 管理,
274
+ // 与 npm 包模型相同,无安全隔离假设)。
275
+ // 如需防止非预期代码执行,请使用 JSON-only 模块(只声明 hooks/api 无代码执行能力)。
276
+ const resolved = path.isAbsolute(filePath) ? filePath : require.resolve(filePath);
277
+ delete require.cache[resolved];
278
+ let plugin;
279
+ try {
280
+ plugin = require(filePath);
281
+ } catch (e) {
282
+ throw new Error(`Failed to load module ${filePath}: ${e.message}`);
283
+ }
284
+ if (typeof plugin === 'function') {
285
+ return { install: plugin, name: path.basename(filePath, ext) };
286
+ }
287
+ if (!plugin.name) plugin.name = path.basename(filePath, ext);
288
+ return plugin;
289
+ }
290
+ }
291
+
292
+ module.exports = { ModuleManager, ModApi, CONFIG_PATH };