@vlynk-studios/nodulus-core 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -0
- package/LICENSE +21 -0
- package/README.md +532 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +331 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.d.ts +267 -0
- package/dist/index.js +880 -0
- package/dist/index.js.map +1 -0
- package/package.json +79 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,880 @@
|
|
|
1
|
+
// src/core/registry.ts
|
|
2
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
3
|
+
|
|
4
|
+
// src/core/errors.ts
|
|
5
|
+
var NodulusError = class extends Error {
|
|
6
|
+
code;
|
|
7
|
+
details;
|
|
8
|
+
constructor(code, message, details) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "NodulusError";
|
|
11
|
+
this.code = code;
|
|
12
|
+
this.details = details;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
var ERROR_MESSAGES = {
|
|
16
|
+
MODULE_NOT_FOUND: "This folder was discovered but index.ts does not call Module(). Add Module('name') to the top of index.ts.",
|
|
17
|
+
DUPLICATE_MODULE: "A module with this name or path already exists. Ensure every module name is unique across the app.",
|
|
18
|
+
MISSING_IMPORT: "A module listed in 'imports' does not exist in the registry. Verify the module name exists and its index.ts calls Module().",
|
|
19
|
+
UNDECLARED_IMPORT: "Attempted to import a module not listed in this module's 'imports' field. Add the missing dependency to Module() options.",
|
|
20
|
+
CIRCULAR_DEPENDENCY: "A circular dependency chain was detected. Extract shared logic into a third module to break the cycle.",
|
|
21
|
+
EXPORT_MISMATCH: "A name declared in 'exports' is not a real export of index.ts. Ensure you 'export { ... }' the matching member.",
|
|
22
|
+
INVALID_CONTROLLER: "Controller has no default export of an Express Router. Add 'export default router;' to the controller file.",
|
|
23
|
+
ALIAS_NOT_FOUND: "An alias points to a target directory that does not exist. Verify the path in nodulus.config.ts or createApp() options.",
|
|
24
|
+
DUPLICATE_ALIAS: "An alias with this name is already registered to a different path. Check for naming collisions in your config.",
|
|
25
|
+
DUPLICATE_BOOTSTRAP: "createApp() was called more than once with the same Express instance. Reuse the existing NodulusApp instead.",
|
|
26
|
+
REGISTRY_MISSING_CONTEXT: "No active registry found in the current async context. Ensure Nodulus API calls run within a createApp() scope.",
|
|
27
|
+
INVALID_MODULE_DECLARATION: "The Module() call violates architectural rules. Ensure it's called at the top level of index.ts.",
|
|
28
|
+
DUPLICATE_SERVICE: "A service with this name is already registered. Ensure every Service() name is unique within the same module.",
|
|
29
|
+
DUPLICATE_REPOSITORY: "A repository with this name is already registered. Ensure every Repository() name is unique within the same module.",
|
|
30
|
+
DUPLICATE_SCHEMA: "A schema with this name is already registered. Ensure every Schema() name is unique within the same module.",
|
|
31
|
+
INVALID_ESM_ENV: `Nodulus requires an ESM environment. Please ensure '"type": "module"' is present heavily in your root package.json file.`
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// src/core/registry.ts
|
|
35
|
+
var toRegisteredModule = (entry) => ({
|
|
36
|
+
name: entry.name,
|
|
37
|
+
path: entry.path,
|
|
38
|
+
imports: entry.imports,
|
|
39
|
+
exports: entry.exports,
|
|
40
|
+
controllers: entry.controllers.map((c) => c.name)
|
|
41
|
+
});
|
|
42
|
+
function createRegistry() {
|
|
43
|
+
const modules = /* @__PURE__ */ new Map();
|
|
44
|
+
const aliases = /* @__PURE__ */ new Map();
|
|
45
|
+
const controllers = /* @__PURE__ */ new Map();
|
|
46
|
+
const services = /* @__PURE__ */ new Map();
|
|
47
|
+
const repositories = /* @__PURE__ */ new Map();
|
|
48
|
+
const schemas = /* @__PURE__ */ new Map();
|
|
49
|
+
return {
|
|
50
|
+
hasModule(name) {
|
|
51
|
+
return modules.has(name);
|
|
52
|
+
},
|
|
53
|
+
getModule(name) {
|
|
54
|
+
const entry = modules.get(name);
|
|
55
|
+
return entry ? toRegisteredModule(entry) : void 0;
|
|
56
|
+
},
|
|
57
|
+
getAllModules() {
|
|
58
|
+
return Array.from(modules.values()).map(toRegisteredModule);
|
|
59
|
+
},
|
|
60
|
+
resolveAlias(alias) {
|
|
61
|
+
return aliases.get(alias);
|
|
62
|
+
},
|
|
63
|
+
getAllAliases() {
|
|
64
|
+
return Object.fromEntries(aliases.entries());
|
|
65
|
+
},
|
|
66
|
+
getDependencyGraph() {
|
|
67
|
+
const graph = /* @__PURE__ */ new Map();
|
|
68
|
+
for (const [name, entry] of modules.entries()) {
|
|
69
|
+
graph.set(name, entry.imports);
|
|
70
|
+
}
|
|
71
|
+
return graph;
|
|
72
|
+
},
|
|
73
|
+
findCircularDependencies() {
|
|
74
|
+
const cycles = [];
|
|
75
|
+
const visited = /* @__PURE__ */ new Set();
|
|
76
|
+
const recStack = /* @__PURE__ */ new Set();
|
|
77
|
+
const path10 = [];
|
|
78
|
+
const dfs = (node) => {
|
|
79
|
+
visited.add(node);
|
|
80
|
+
recStack.add(node);
|
|
81
|
+
path10.push(node);
|
|
82
|
+
const deps = modules.get(node)?.imports || [];
|
|
83
|
+
for (const neighbor of deps) {
|
|
84
|
+
if (!visited.has(neighbor)) {
|
|
85
|
+
dfs(neighbor);
|
|
86
|
+
} else if (recStack.has(neighbor)) {
|
|
87
|
+
const cycleStart = path10.indexOf(neighbor);
|
|
88
|
+
cycles.push([...path10.slice(cycleStart), neighbor]);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
recStack.delete(node);
|
|
92
|
+
path10.pop();
|
|
93
|
+
};
|
|
94
|
+
for (const node of modules.keys()) {
|
|
95
|
+
if (!visited.has(node)) {
|
|
96
|
+
dfs(node);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return cycles;
|
|
100
|
+
},
|
|
101
|
+
registerModule(name, options, dirPath, indexPath) {
|
|
102
|
+
if (modules.has(name)) {
|
|
103
|
+
throw new NodulusError(
|
|
104
|
+
"DUPLICATE_MODULE",
|
|
105
|
+
`A module with this name already exists. Each module must have a unique name.`,
|
|
106
|
+
`Module name: ${name}`
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
const existing = Array.from(modules.values()).find((m) => m.path === dirPath);
|
|
110
|
+
if (existing) {
|
|
111
|
+
throw new NodulusError(
|
|
112
|
+
"DUPLICATE_MODULE",
|
|
113
|
+
`A module is already registered for this folder. Call Module() only once per directory.`,
|
|
114
|
+
`Existing: ${existing.name}, New: ${name}, Folder: ${dirPath}`
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
const entry = {
|
|
118
|
+
name,
|
|
119
|
+
path: dirPath,
|
|
120
|
+
indexPath,
|
|
121
|
+
imports: options.imports || [],
|
|
122
|
+
exports: options.exports || [],
|
|
123
|
+
controllers: []
|
|
124
|
+
};
|
|
125
|
+
modules.set(name, entry);
|
|
126
|
+
},
|
|
127
|
+
registerAlias(alias, targetPath) {
|
|
128
|
+
const existing = aliases.get(alias);
|
|
129
|
+
if (existing && existing !== targetPath) {
|
|
130
|
+
throw new NodulusError(
|
|
131
|
+
"DUPLICATE_ALIAS",
|
|
132
|
+
`An alias with this name is already registered to a different target path.`,
|
|
133
|
+
`Alias: ${alias}, Existing: ${existing}, New: ${targetPath}`
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
aliases.set(alias, targetPath);
|
|
137
|
+
},
|
|
138
|
+
registerControllerMetadata(entry) {
|
|
139
|
+
if (controllers.has(entry.path)) {
|
|
140
|
+
throw new NodulusError(
|
|
141
|
+
"INVALID_CONTROLLER",
|
|
142
|
+
`Controller() was called more than once in the same file.`,
|
|
143
|
+
`File: ${entry.path}`
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
controllers.set(entry.path, entry);
|
|
147
|
+
},
|
|
148
|
+
getControllerMetadata(filePath) {
|
|
149
|
+
return controllers.get(filePath);
|
|
150
|
+
},
|
|
151
|
+
getAllControllersMetadata() {
|
|
152
|
+
return Array.from(controllers.values());
|
|
153
|
+
},
|
|
154
|
+
getRawModule(name) {
|
|
155
|
+
return modules.get(name);
|
|
156
|
+
},
|
|
157
|
+
registerFileMetadata(entry) {
|
|
158
|
+
if (entry.type === "service") {
|
|
159
|
+
if (services.has(entry.name)) {
|
|
160
|
+
throw new NodulusError(
|
|
161
|
+
"DUPLICATE_SERVICE",
|
|
162
|
+
`A service named "${entry.name}" is already registered. Each Service() name must be unique within the registry.`,
|
|
163
|
+
`Duplicate name: ${entry.name}`
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
services.set(entry.name, entry);
|
|
167
|
+
} else if (entry.type === "repository") {
|
|
168
|
+
if (repositories.has(entry.name)) {
|
|
169
|
+
throw new NodulusError(
|
|
170
|
+
"DUPLICATE_REPOSITORY",
|
|
171
|
+
`A repository named "${entry.name}" is already registered. Each Repository() name must be unique within the registry.`,
|
|
172
|
+
`Duplicate name: ${entry.name}`
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
repositories.set(entry.name, entry);
|
|
176
|
+
} else if (entry.type === "schema") {
|
|
177
|
+
if (schemas.has(entry.name)) {
|
|
178
|
+
throw new NodulusError(
|
|
179
|
+
"DUPLICATE_SCHEMA",
|
|
180
|
+
`A schema named "${entry.name}" is already registered. Each Schema() name must be unique within the registry.`,
|
|
181
|
+
`Duplicate name: ${entry.name}`
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
schemas.set(entry.name, entry);
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
getAllServices() {
|
|
188
|
+
return Array.from(services.values());
|
|
189
|
+
},
|
|
190
|
+
getService(name) {
|
|
191
|
+
return services.get(name);
|
|
192
|
+
},
|
|
193
|
+
getAllRepositories() {
|
|
194
|
+
return Array.from(repositories.values());
|
|
195
|
+
},
|
|
196
|
+
getRepository(name) {
|
|
197
|
+
return repositories.get(name);
|
|
198
|
+
},
|
|
199
|
+
getAllSchemas() {
|
|
200
|
+
return Array.from(schemas.values());
|
|
201
|
+
},
|
|
202
|
+
getSchema(name) {
|
|
203
|
+
return schemas.get(name);
|
|
204
|
+
},
|
|
205
|
+
clearRegistry() {
|
|
206
|
+
modules.clear();
|
|
207
|
+
aliases.clear();
|
|
208
|
+
controllers.clear();
|
|
209
|
+
services.clear();
|
|
210
|
+
repositories.clear();
|
|
211
|
+
schemas.clear();
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
var registryContext = new AsyncLocalStorage();
|
|
216
|
+
function getActiveRegistry() {
|
|
217
|
+
const store = registryContext.getStore();
|
|
218
|
+
if (!store) {
|
|
219
|
+
throw new NodulusError(
|
|
220
|
+
"REGISTRY_MISSING_CONTEXT",
|
|
221
|
+
"No active registry found in the current async context. Ensure code runs inside a createApp() execution scope."
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
return store;
|
|
225
|
+
}
|
|
226
|
+
var getRegistry = () => getActiveRegistry();
|
|
227
|
+
|
|
228
|
+
// src/identifiers/module.ts
|
|
229
|
+
import path2 from "path";
|
|
230
|
+
|
|
231
|
+
// src/core/caller.ts
|
|
232
|
+
import path from "path";
|
|
233
|
+
import { fileURLToPath } from "url";
|
|
234
|
+
function resolveCallerFile(identifierName) {
|
|
235
|
+
const originalFunc = Error.prepareStackTrace;
|
|
236
|
+
let callerFile = null;
|
|
237
|
+
try {
|
|
238
|
+
const err = new Error();
|
|
239
|
+
Error.prepareStackTrace = (_, stack2) => stack2;
|
|
240
|
+
const stack = err.stack;
|
|
241
|
+
if (stack && stack.length > 3) {
|
|
242
|
+
callerFile = stack[3].getFileName() || null;
|
|
243
|
+
}
|
|
244
|
+
} catch {
|
|
245
|
+
} finally {
|
|
246
|
+
Error.prepareStackTrace = originalFunc;
|
|
247
|
+
}
|
|
248
|
+
if (!callerFile) {
|
|
249
|
+
throw new NodulusError(
|
|
250
|
+
"INVALID_MODULE_DECLARATION",
|
|
251
|
+
`${identifierName} could not determine caller path. Stack trace unavailable.`,
|
|
252
|
+
"Ensure you are using Node.js >= 20.6 with ESM and no bundler obfuscation."
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
if (callerFile.startsWith("file://")) {
|
|
256
|
+
callerFile = fileURLToPath(callerFile);
|
|
257
|
+
}
|
|
258
|
+
return callerFile;
|
|
259
|
+
}
|
|
260
|
+
function getModuleCallerInfo(identifierName) {
|
|
261
|
+
const filePath = resolveCallerFile(identifierName);
|
|
262
|
+
return { filePath, dirPath: path.dirname(filePath) };
|
|
263
|
+
}
|
|
264
|
+
function getFileCallerInfo(identifierName) {
|
|
265
|
+
return { filePath: resolveCallerFile(identifierName) };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// src/identifiers/module.ts
|
|
269
|
+
function Module(name, options = {}) {
|
|
270
|
+
if (typeof name !== "string") {
|
|
271
|
+
throw new TypeError(`Module name must be a string, received ${typeof name}`);
|
|
272
|
+
}
|
|
273
|
+
const { filePath: indexPath, dirPath } = getModuleCallerInfo("Module()");
|
|
274
|
+
if (dirPath) {
|
|
275
|
+
const folderName = path2.basename(dirPath);
|
|
276
|
+
if (folderName && folderName !== name) {
|
|
277
|
+
throw new NodulusError(
|
|
278
|
+
"INVALID_MODULE_DECLARATION",
|
|
279
|
+
`Module name "${name}" does not match its containing folder "${folderName}".`,
|
|
280
|
+
`The module name in Module() MUST match the folder name exactly.`
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
const fileName = path2.basename(indexPath);
|
|
285
|
+
const isIndexFile = /index\.(ts|js|mts|mjs)$/.test(fileName);
|
|
286
|
+
if (!isIndexFile) {
|
|
287
|
+
throw new NodulusError(
|
|
288
|
+
"INVALID_MODULE_DECLARATION",
|
|
289
|
+
`Module() was called from "${fileName}", but it must be called only from the module's index file.`,
|
|
290
|
+
`File: ${indexPath}`
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
getActiveRegistry().registerModule(name, options, dirPath, indexPath);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// src/identifiers/controller.ts
|
|
297
|
+
import path3 from "path";
|
|
298
|
+
function Controller(prefix, options = {}) {
|
|
299
|
+
if (typeof prefix !== "string") {
|
|
300
|
+
throw new TypeError(`Controller prefix must be a string, received ${typeof prefix}`);
|
|
301
|
+
}
|
|
302
|
+
const { filePath } = getFileCallerInfo("Controller()");
|
|
303
|
+
const name = path3.parse(filePath).name;
|
|
304
|
+
getActiveRegistry().registerControllerMetadata({
|
|
305
|
+
name,
|
|
306
|
+
path: filePath,
|
|
307
|
+
prefix,
|
|
308
|
+
middlewares: options.middlewares ?? [],
|
|
309
|
+
enabled: options.enabled ?? true
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// src/identifiers/service.ts
|
|
314
|
+
import path4 from "path";
|
|
315
|
+
function Service(name, options = {}) {
|
|
316
|
+
if (typeof name !== "string" || name.trim() === "") {
|
|
317
|
+
throw new TypeError(`Service name must be a non-empty string, received ${typeof name}`);
|
|
318
|
+
}
|
|
319
|
+
const { filePath } = getFileCallerInfo("Service()");
|
|
320
|
+
const inferredModule = options.module ?? path4.basename(path4.dirname(filePath));
|
|
321
|
+
getActiveRegistry().registerFileMetadata({
|
|
322
|
+
name,
|
|
323
|
+
path: filePath,
|
|
324
|
+
type: "service",
|
|
325
|
+
module: inferredModule,
|
|
326
|
+
description: options.description
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// src/identifiers/repository.ts
|
|
331
|
+
import path5 from "path";
|
|
332
|
+
function Repository(name, options = {}) {
|
|
333
|
+
if (typeof name !== "string" || name.trim() === "") {
|
|
334
|
+
throw new TypeError(`Repository name must be a non-empty string, received ${typeof name}`);
|
|
335
|
+
}
|
|
336
|
+
const { filePath } = getFileCallerInfo("Repository()");
|
|
337
|
+
const inferredModule = options.module ?? path5.basename(path5.dirname(filePath));
|
|
338
|
+
getActiveRegistry().registerFileMetadata({
|
|
339
|
+
name,
|
|
340
|
+
path: filePath,
|
|
341
|
+
type: "repository",
|
|
342
|
+
module: inferredModule,
|
|
343
|
+
description: options.description,
|
|
344
|
+
source: options.source
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// src/identifiers/schema.ts
|
|
349
|
+
import path6 from "path";
|
|
350
|
+
function Schema(name, options = {}) {
|
|
351
|
+
if (typeof name !== "string" || name.trim() === "") {
|
|
352
|
+
throw new TypeError(`Schema name must be a non-empty string, received ${typeof name}`);
|
|
353
|
+
}
|
|
354
|
+
const { filePath } = getFileCallerInfo("Schema()");
|
|
355
|
+
const inferredModule = options.module ?? path6.basename(path6.dirname(filePath));
|
|
356
|
+
getActiveRegistry().registerFileMetadata({
|
|
357
|
+
name,
|
|
358
|
+
path: filePath,
|
|
359
|
+
type: "schema",
|
|
360
|
+
module: inferredModule,
|
|
361
|
+
description: options.description,
|
|
362
|
+
library: options.library
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// src/bootstrap/createApp.ts
|
|
367
|
+
import fs2 from "fs";
|
|
368
|
+
import path8 from "path";
|
|
369
|
+
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
370
|
+
import fg from "fast-glob";
|
|
371
|
+
|
|
372
|
+
// src/core/config.ts
|
|
373
|
+
import fs from "fs";
|
|
374
|
+
import path7 from "path";
|
|
375
|
+
import { pathToFileURL } from "url";
|
|
376
|
+
|
|
377
|
+
// src/core/logger.ts
|
|
378
|
+
import pc from "picocolors";
|
|
379
|
+
var LEVEL_ORDER = {
|
|
380
|
+
debug: 0,
|
|
381
|
+
info: 1,
|
|
382
|
+
warn: 2,
|
|
383
|
+
error: 3
|
|
384
|
+
};
|
|
385
|
+
var LEVEL_STYLE = {
|
|
386
|
+
debug: (msg) => pc.gray(msg),
|
|
387
|
+
info: (msg) => pc.cyan(msg),
|
|
388
|
+
warn: (msg) => pc.yellow(msg),
|
|
389
|
+
error: (msg) => pc.red(msg)
|
|
390
|
+
};
|
|
391
|
+
var LEVEL_LABELS = {
|
|
392
|
+
debug: "debug",
|
|
393
|
+
info: "info ",
|
|
394
|
+
// trailing space for alignment
|
|
395
|
+
warn: "warn ",
|
|
396
|
+
error: "error"
|
|
397
|
+
};
|
|
398
|
+
var defaultLogHandler = (level, message) => {
|
|
399
|
+
const prefix = pc.gray("[Nodulus]");
|
|
400
|
+
const label = LEVEL_STYLE[level](LEVEL_LABELS[level]);
|
|
401
|
+
const line = `${prefix} ${label} ${message}`;
|
|
402
|
+
if (level === "warn" || level === "error") {
|
|
403
|
+
process.stderr.write(line + "\n");
|
|
404
|
+
} else {
|
|
405
|
+
process.stdout.write(line + "\n");
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
function resolveLogLevel(explicit) {
|
|
409
|
+
if (explicit) return explicit;
|
|
410
|
+
const nodeDebug = process.env.NODE_DEBUG ?? "";
|
|
411
|
+
if (nodeDebug.split(",").map((s) => s.trim()).includes("nodulus")) {
|
|
412
|
+
return "debug";
|
|
413
|
+
}
|
|
414
|
+
return "info";
|
|
415
|
+
}
|
|
416
|
+
function createLogger(handler, minLevel) {
|
|
417
|
+
const minOrder = LEVEL_ORDER[minLevel];
|
|
418
|
+
const emit = (level, message, meta) => {
|
|
419
|
+
if (LEVEL_ORDER[level] >= minOrder) {
|
|
420
|
+
handler(level, message, meta);
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
return {
|
|
424
|
+
debug: (msg, meta) => emit("debug", msg, meta),
|
|
425
|
+
info: (msg, meta) => emit("info", msg, meta),
|
|
426
|
+
warn: (msg, meta) => emit("warn", msg, meta),
|
|
427
|
+
error: (msg, meta) => emit("error", msg, meta)
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// src/core/config.ts
|
|
432
|
+
var defaultStrict = typeof process !== "undefined" && process.env?.NODE_ENV !== "production";
|
|
433
|
+
var DEFAULTS = {
|
|
434
|
+
modules: "src/modules/*",
|
|
435
|
+
prefix: "",
|
|
436
|
+
aliases: {},
|
|
437
|
+
strict: defaultStrict,
|
|
438
|
+
resolveAliases: true,
|
|
439
|
+
logger: defaultLogHandler,
|
|
440
|
+
logLevel: resolveLogLevel()
|
|
441
|
+
};
|
|
442
|
+
var loadConfig = async (options = {}) => {
|
|
443
|
+
const cwd = process.cwd();
|
|
444
|
+
let fileConfig = {};
|
|
445
|
+
const tsPath = path7.join(cwd, "nodulus.config.ts");
|
|
446
|
+
const jsPath = path7.join(cwd, "nodulus.config.js");
|
|
447
|
+
const isProduction = process.env.NODE_ENV === "production";
|
|
448
|
+
const hasTsLoader = process.execArgv.some((arg) => arg.includes("ts-node") || arg.includes("tsx")) || process._preload_modules?.some((m) => m.includes("ts-node") || m.includes("tsx"));
|
|
449
|
+
const candidates = [];
|
|
450
|
+
if (!isProduction || hasTsLoader) {
|
|
451
|
+
candidates.push(tsPath);
|
|
452
|
+
}
|
|
453
|
+
candidates.push(jsPath);
|
|
454
|
+
let configPathToLoad = null;
|
|
455
|
+
for (const candidate of candidates) {
|
|
456
|
+
if (fs.existsSync(candidate)) {
|
|
457
|
+
configPathToLoad = candidate;
|
|
458
|
+
break;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
if (configPathToLoad) {
|
|
462
|
+
try {
|
|
463
|
+
const importUrl = pathToFileURL(configPathToLoad).href;
|
|
464
|
+
const mod = await import(importUrl);
|
|
465
|
+
fileConfig = mod.default || mod.config || mod;
|
|
466
|
+
} catch (error) {
|
|
467
|
+
if (configPathToLoad.endsWith(".ts") && error.code === "ERR_UNKNOWN_FILE_EXTENSION") {
|
|
468
|
+
throw new Error(
|
|
469
|
+
`[Nodulus] Found "nodulus.config.ts" but your environment cannot load raw TypeScript files.
|
|
470
|
+
- In production: Run "npm run build" to generate a .js config OR use nodulus.config.js.
|
|
471
|
+
- In development: Ensure you are running with a loader like "tsx" or "ts-node".`,
|
|
472
|
+
{ cause: error }
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
throw new Error(`[Nodulus] Failed to parse or evaluate config file at ${configPathToLoad}: ${error.message}`, { cause: error });
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
return {
|
|
479
|
+
modules: options.modules ?? fileConfig.modules ?? DEFAULTS.modules,
|
|
480
|
+
prefix: options.prefix ?? fileConfig.prefix ?? DEFAULTS.prefix,
|
|
481
|
+
aliases: {
|
|
482
|
+
...DEFAULTS.aliases,
|
|
483
|
+
...fileConfig.aliases || {},
|
|
484
|
+
...options.aliases || {}
|
|
485
|
+
// Options override file aliases
|
|
486
|
+
},
|
|
487
|
+
strict: options.strict ?? fileConfig.strict ?? DEFAULTS.strict,
|
|
488
|
+
resolveAliases: options.resolveAliases ?? fileConfig.resolveAliases ?? DEFAULTS.resolveAliases,
|
|
489
|
+
logger: options.logger ?? fileConfig.logger ?? DEFAULTS.logger,
|
|
490
|
+
logLevel: resolveLogLevel(options.logLevel ?? fileConfig.logLevel)
|
|
491
|
+
};
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
// src/aliases/resolver.ts
|
|
495
|
+
import { register } from "module";
|
|
496
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
497
|
+
var isHookRegistered = false;
|
|
498
|
+
function activateAliasResolver(moduleAliases, folderAliases, log) {
|
|
499
|
+
if (isHookRegistered) return;
|
|
500
|
+
const combinedAliases = { ...moduleAliases, ...folderAliases };
|
|
501
|
+
for (const [alias, target] of Object.entries(folderAliases)) {
|
|
502
|
+
log.debug(`Alias registered: ${alias} \u2192 ${target}`, { alias, target, source: "config" });
|
|
503
|
+
}
|
|
504
|
+
for (const [alias, target] of Object.entries(moduleAliases)) {
|
|
505
|
+
log.debug(`Alias registered: ${alias} \u2192 ${target}`, { alias, target, source: "module" });
|
|
506
|
+
}
|
|
507
|
+
const serialisedAliases = JSON.stringify(combinedAliases);
|
|
508
|
+
const loaderCode = `
|
|
509
|
+
import { pathToFileURL } from 'node:url';
|
|
510
|
+
import path from 'node:path';
|
|
511
|
+
|
|
512
|
+
const aliases = ${serialisedAliases};
|
|
513
|
+
|
|
514
|
+
export async function resolve(specifier, context, nextResolve) {
|
|
515
|
+
for (const alias of Object.keys(aliases)) {
|
|
516
|
+
if (specifier === alias || specifier.startsWith(alias + '/')) {
|
|
517
|
+
const target = aliases[alias];
|
|
518
|
+
const resolvedPath = specifier.replace(alias, target);
|
|
519
|
+
return nextResolve(pathToFileURL(path.resolve(resolvedPath)).href, context);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return nextResolve(specifier, context);
|
|
523
|
+
}
|
|
524
|
+
`;
|
|
525
|
+
try {
|
|
526
|
+
const dataUrl = `data:text/javascript,${encodeURIComponent(loaderCode)}`;
|
|
527
|
+
const parentUrl = typeof __filename === "undefined" ? import.meta.url : pathToFileURL2(__filename).href;
|
|
528
|
+
if (typeof register === "function") {
|
|
529
|
+
register(dataUrl, { parentURL: parentUrl });
|
|
530
|
+
isHookRegistered = true;
|
|
531
|
+
log.info(`ESM alias hook activated (${Object.keys(combinedAliases).length} alias(es))`, {
|
|
532
|
+
aliasCount: Object.keys(combinedAliases).length
|
|
533
|
+
});
|
|
534
|
+
} else {
|
|
535
|
+
log.warn("ESM alias hook could not be registered \u2014 upgrade to Node.js >= 20.6.0 for runtime alias support", {
|
|
536
|
+
nodeVersion: process.version
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
} catch (err) {
|
|
540
|
+
log.warn("ESM alias hook registration threw an unexpected error \u2014 aliases may not resolve at runtime", {
|
|
541
|
+
error: err?.message ?? String(err)
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// src/aliases/cache.ts
|
|
547
|
+
var aliasCache = {};
|
|
548
|
+
function updateAliasCache(aliases) {
|
|
549
|
+
aliasCache = { ...aliases };
|
|
550
|
+
}
|
|
551
|
+
function getAliasCache() {
|
|
552
|
+
return aliasCache;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// src/bootstrap/createApp.ts
|
|
556
|
+
import { performance } from "perf_hooks";
|
|
557
|
+
import pc2 from "picocolors";
|
|
558
|
+
async function createApp(app, options = {}) {
|
|
559
|
+
if (app.__nodulusBootstrapped) {
|
|
560
|
+
throw new NodulusError(
|
|
561
|
+
"DUPLICATE_BOOTSTRAP",
|
|
562
|
+
"createApp() was called more than once with the same Express instance."
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
let isEsm = false;
|
|
566
|
+
try {
|
|
567
|
+
const pkgPath = path8.resolve(process.cwd(), "package.json");
|
|
568
|
+
if (fs2.existsSync(pkgPath)) {
|
|
569
|
+
const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf8"));
|
|
570
|
+
if (pkg.type === "module") {
|
|
571
|
+
isEsm = true;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
} catch (_e) {
|
|
575
|
+
}
|
|
576
|
+
if (!isEsm) {
|
|
577
|
+
throw new NodulusError(
|
|
578
|
+
"INVALID_ESM_ENV",
|
|
579
|
+
'Nodulus requires an ESM environment. Please ensure "type": "module" is present in your root package.json file.'
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
const registry = createRegistry();
|
|
583
|
+
return registryContext.run(registry, async () => {
|
|
584
|
+
const startTime = performance.now();
|
|
585
|
+
try {
|
|
586
|
+
const config = await loadConfig(options);
|
|
587
|
+
const log = createLogger(config.logger, config.logLevel);
|
|
588
|
+
log.info("Bootstrap started", {
|
|
589
|
+
modules: pc2.cyan(config.modules),
|
|
590
|
+
prefix: pc2.cyan(config.prefix || "(none)"),
|
|
591
|
+
strict: pc2.yellow(String(config.strict)),
|
|
592
|
+
nodeVersion: pc2.gray(process.version)
|
|
593
|
+
});
|
|
594
|
+
const globPattern = config.modules.replace(/\\/g, "/");
|
|
595
|
+
const moduleDirs = await fg(globPattern, {
|
|
596
|
+
onlyDirectories: true,
|
|
597
|
+
absolute: true,
|
|
598
|
+
cwd: process.cwd()
|
|
599
|
+
});
|
|
600
|
+
moduleDirs.sort();
|
|
601
|
+
const resolvedModules = [];
|
|
602
|
+
for (const dirPath of moduleDirs) {
|
|
603
|
+
log.debug(`Discovered module directory: ${dirPath}`, { dirPath });
|
|
604
|
+
const tsPath = path8.join(dirPath, "index.ts");
|
|
605
|
+
const jsPath = path8.join(dirPath, "index.js");
|
|
606
|
+
let indexPath = null;
|
|
607
|
+
if (fs2.existsSync(tsPath)) {
|
|
608
|
+
indexPath = tsPath;
|
|
609
|
+
} else if (fs2.existsSync(jsPath)) {
|
|
610
|
+
indexPath = jsPath;
|
|
611
|
+
}
|
|
612
|
+
if (!indexPath) {
|
|
613
|
+
throw new NodulusError(
|
|
614
|
+
"MODULE_NOT_FOUND",
|
|
615
|
+
`No index.ts or index.js found for module. A module directory must have an index file mapping its dependencies.`,
|
|
616
|
+
`Directory: ${dirPath}`
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
resolvedModules.push({ dirPath, indexPath });
|
|
620
|
+
}
|
|
621
|
+
if (config.resolveAliases !== false) {
|
|
622
|
+
const pureModuleAliases = {};
|
|
623
|
+
for (const mod of resolvedModules) {
|
|
624
|
+
const modName = path8.basename(mod.dirPath);
|
|
625
|
+
const aliasKey = `@modules/${modName}`;
|
|
626
|
+
pureModuleAliases[aliasKey] = mod.dirPath;
|
|
627
|
+
registry.registerAlias(aliasKey, mod.dirPath);
|
|
628
|
+
}
|
|
629
|
+
activateAliasResolver(pureModuleAliases, config.aliases, log);
|
|
630
|
+
updateAliasCache(registry.getAllAliases());
|
|
631
|
+
}
|
|
632
|
+
for (const mod of resolvedModules) {
|
|
633
|
+
const imported = await import(pathToFileURL3(mod.indexPath).href);
|
|
634
|
+
const allRegistered = registry.getAllModules();
|
|
635
|
+
const registeredMod = allRegistered.find((m) => path8.normalize(m.path) === path8.normalize(mod.dirPath));
|
|
636
|
+
if (!registeredMod) {
|
|
637
|
+
throw new NodulusError(
|
|
638
|
+
"MODULE_NOT_FOUND",
|
|
639
|
+
`No index.ts found calling Module(). Add Module() to the module's index.ts.`,
|
|
640
|
+
`File: ${mod.indexPath}`
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
log.info(`Module loaded: ${pc2.green(registeredMod.name)}`, {
|
|
644
|
+
name: registeredMod.name,
|
|
645
|
+
imports: registeredMod.imports,
|
|
646
|
+
exports: registeredMod.exports,
|
|
647
|
+
path: registeredMod.path
|
|
648
|
+
});
|
|
649
|
+
const actualExports = Object.keys(imported).filter((key) => key !== "default");
|
|
650
|
+
const declaredExports = registeredMod.exports || [];
|
|
651
|
+
for (const declared of declaredExports) {
|
|
652
|
+
if (!actualExports.includes(declared)) {
|
|
653
|
+
throw new NodulusError(
|
|
654
|
+
"EXPORT_MISMATCH",
|
|
655
|
+
`A name declared in exports does not exist as a real export of index.ts.`,
|
|
656
|
+
`Module: ${registeredMod.name}, Missing Export: ${declared}`
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
if (config.strict) {
|
|
661
|
+
for (const actual of actualExports) {
|
|
662
|
+
if (!declaredExports.includes(actual)) {
|
|
663
|
+
log.warn(
|
|
664
|
+
`Module "${registeredMod.name}" exports "${actual}" but it is not declared in Module() options "exports" array.`,
|
|
665
|
+
{ name: registeredMod.name, exportName: actual }
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
const allModules = registry.getAllModules();
|
|
672
|
+
for (const mod of allModules) {
|
|
673
|
+
for (const importName of mod.imports) {
|
|
674
|
+
if (!registry.hasModule(importName)) {
|
|
675
|
+
throw new NodulusError(
|
|
676
|
+
"MISSING_IMPORT",
|
|
677
|
+
`A module declared in imports does not exist in the registry.`,
|
|
678
|
+
`Module "${mod.name}" is trying to import missing module "${importName}"`
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
if (config.strict) {
|
|
684
|
+
const cycles = registry.findCircularDependencies();
|
|
685
|
+
if (cycles.length > 0) {
|
|
686
|
+
const cycleStrings = cycles.map((cycle) => cycle.join(" -> ")).join(" | ");
|
|
687
|
+
throw new NodulusError(
|
|
688
|
+
"CIRCULAR_DEPENDENCY",
|
|
689
|
+
`Circular dependency detected. Extract the shared dependency into a separate module.`,
|
|
690
|
+
`Cycles found: ${cycleStrings}`
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
for (const mod of allModules) {
|
|
695
|
+
const rawMod = registry.getRawModule(mod.name);
|
|
696
|
+
if (!rawMod) continue;
|
|
697
|
+
const files = await fg("**/*.{ts,js,mts,mjs,cjs}", {
|
|
698
|
+
cwd: mod.path,
|
|
699
|
+
absolute: true,
|
|
700
|
+
ignore: [
|
|
701
|
+
"**/*.types.*",
|
|
702
|
+
"**/*.d.ts",
|
|
703
|
+
"**/*.spec.*",
|
|
704
|
+
"**/*.test.*",
|
|
705
|
+
"index.*"
|
|
706
|
+
// Escapes root index.ts/js
|
|
707
|
+
]
|
|
708
|
+
});
|
|
709
|
+
files.sort();
|
|
710
|
+
for (let file of files) {
|
|
711
|
+
log.debug(`Scanning controller file: ${file}`, { filePath: file, module: mod.name });
|
|
712
|
+
file = path8.normalize(file);
|
|
713
|
+
let imported;
|
|
714
|
+
try {
|
|
715
|
+
imported = await import(pathToFileURL3(file).href);
|
|
716
|
+
} catch (err) {
|
|
717
|
+
throw new NodulusError(
|
|
718
|
+
"INVALID_CONTROLLER",
|
|
719
|
+
`Failed to import controller file. Check for syntax errors or missing dependencies.`,
|
|
720
|
+
`File: ${file} \u2014 ${err.message}`
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
const resolvedFile = path8.normalize(file);
|
|
724
|
+
const ctrlMeta = registry.getControllerMetadata(resolvedFile) || registry.getAllControllersMetadata().find((c) => path8.normalize(c.path) === resolvedFile);
|
|
725
|
+
if (ctrlMeta) {
|
|
726
|
+
const isRouter = imported.default && typeof imported.default === "function" && typeof imported.default.use === "function";
|
|
727
|
+
if (!isRouter) {
|
|
728
|
+
throw new NodulusError(
|
|
729
|
+
"INVALID_CONTROLLER",
|
|
730
|
+
`Controller has no default export of a Router. Add export default router.`,
|
|
731
|
+
`File: ${file}`
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
log.debug(`Controller registered: ${pc2.green(ctrlMeta.name)} \u2192 ${pc2.cyan(ctrlMeta.prefix)}`, {
|
|
735
|
+
name: ctrlMeta.name,
|
|
736
|
+
prefix: ctrlMeta.prefix,
|
|
737
|
+
module: mod.name,
|
|
738
|
+
middlewareCount: ctrlMeta.middlewares.length
|
|
739
|
+
});
|
|
740
|
+
ctrlMeta.router = imported.default;
|
|
741
|
+
rawMod.controllers.push(ctrlMeta);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
if (rawMod.controllers.length === 0) {
|
|
745
|
+
log.warn(`Module "${mod.name}" has no controllers \u2014 no routes will be mounted from it`, {
|
|
746
|
+
name: mod.name,
|
|
747
|
+
path: mod.path
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
const mountedRoutes = [];
|
|
752
|
+
for (const mod of allModules) {
|
|
753
|
+
const rawMod = registry.getRawModule(mod.name);
|
|
754
|
+
if (!rawMod) continue;
|
|
755
|
+
for (const ctrl of rawMod.controllers) {
|
|
756
|
+
if (!ctrl.enabled) {
|
|
757
|
+
log.info(`Controller "${ctrl.name}" is disabled \u2014 skipping mount`, {
|
|
758
|
+
name: ctrl.name,
|
|
759
|
+
module: mod.name,
|
|
760
|
+
prefix: ctrl.prefix
|
|
761
|
+
});
|
|
762
|
+
continue;
|
|
763
|
+
}
|
|
764
|
+
const fullPath = (config.prefix + ctrl.prefix).replace(/\/+/g, "/").replace(/\/$/, "") || "/";
|
|
765
|
+
if (ctrl.router) {
|
|
766
|
+
if (ctrl.middlewares && ctrl.middlewares.length > 0) {
|
|
767
|
+
app.use(fullPath, ...ctrl.middlewares, ctrl.router);
|
|
768
|
+
} else {
|
|
769
|
+
app.use(fullPath, ctrl.router);
|
|
770
|
+
}
|
|
771
|
+
let foundRoutes = false;
|
|
772
|
+
const extractedRoutes = [];
|
|
773
|
+
if (ctrl.router.stack && Array.isArray(ctrl.router.stack)) {
|
|
774
|
+
for (const layer of ctrl.router.stack) {
|
|
775
|
+
const routeObj = layer.route;
|
|
776
|
+
if (routeObj && routeObj.methods) {
|
|
777
|
+
foundRoutes = true;
|
|
778
|
+
const routePath = routeObj.path;
|
|
779
|
+
const methods = Object.keys(routeObj.methods).filter((m) => routeObj.methods[m]).map((m) => m.toUpperCase());
|
|
780
|
+
for (const method of methods) {
|
|
781
|
+
const fullRoutePath = (fullPath + (routePath === "/" ? "" : routePath)).replace(/\/+/g, "/");
|
|
782
|
+
extractedRoutes.push({ method, path: fullRoutePath });
|
|
783
|
+
mountedRoutes.push({
|
|
784
|
+
method,
|
|
785
|
+
path: fullRoutePath,
|
|
786
|
+
module: mod.name,
|
|
787
|
+
controller: ctrl.name
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
if (!foundRoutes) {
|
|
794
|
+
extractedRoutes.push({ method: "USE", path: fullPath });
|
|
795
|
+
mountedRoutes.push({
|
|
796
|
+
method: "USE",
|
|
797
|
+
path: fullPath,
|
|
798
|
+
module: mod.name,
|
|
799
|
+
controller: ctrl.name
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
const methodColors = {
|
|
803
|
+
GET: pc2.green,
|
|
804
|
+
POST: pc2.yellow,
|
|
805
|
+
PUT: pc2.cyan,
|
|
806
|
+
PATCH: pc2.magenta,
|
|
807
|
+
DELETE: pc2.red,
|
|
808
|
+
USE: pc2.gray
|
|
809
|
+
};
|
|
810
|
+
for (const route of extractedRoutes) {
|
|
811
|
+
const colorFn = methodColors[route.method] || pc2.white;
|
|
812
|
+
log.info(` ${colorFn(route.method.padEnd(6))} ${pc2.white(route.path)} ${pc2.gray(`(${ctrl.name})`)}`, {
|
|
813
|
+
method: route.method,
|
|
814
|
+
path: route.path,
|
|
815
|
+
module: mod.name,
|
|
816
|
+
controller: ctrl.name
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
app.__nodulusBootstrapped = true;
|
|
823
|
+
const safeRegisteredModules = allModules.map((m) => registry.getModule(m.name));
|
|
824
|
+
const durationMs = Math.round(performance.now() - startTime);
|
|
825
|
+
log.info(`${pc2.green("Bootstrap complete")} \u2014 ${pc2.cyan(allModules.length)} module(s), ${pc2.cyan(mountedRoutes.length)} route(s) in ${pc2.yellow(`${durationMs}ms`)}`, {
|
|
826
|
+
moduleCount: allModules.length,
|
|
827
|
+
routeCount: mountedRoutes.length,
|
|
828
|
+
durationMs
|
|
829
|
+
});
|
|
830
|
+
return {
|
|
831
|
+
modules: safeRegisteredModules,
|
|
832
|
+
routes: mountedRoutes,
|
|
833
|
+
registry
|
|
834
|
+
};
|
|
835
|
+
} catch (err) {
|
|
836
|
+
registry.clearRegistry();
|
|
837
|
+
throw err;
|
|
838
|
+
}
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
// src/aliases/getAliases.ts
|
|
843
|
+
import path9 from "path";
|
|
844
|
+
async function getAliases(options = {}) {
|
|
845
|
+
const includeFolders = options.includeFolders ?? true;
|
|
846
|
+
const absolute = options.absolute ?? false;
|
|
847
|
+
const allAliases = getAliasCache();
|
|
848
|
+
const result = {};
|
|
849
|
+
const cwd = process.cwd();
|
|
850
|
+
for (const [alias, target] of Object.entries(allAliases)) {
|
|
851
|
+
if (!includeFolders && !alias.startsWith("@modules/")) {
|
|
852
|
+
continue;
|
|
853
|
+
}
|
|
854
|
+
let resolvedPath;
|
|
855
|
+
if (absolute) {
|
|
856
|
+
resolvedPath = path9.isAbsolute(target) ? target : path9.resolve(cwd, target);
|
|
857
|
+
} else {
|
|
858
|
+
resolvedPath = path9.isAbsolute(target) ? path9.relative(cwd, target) : target;
|
|
859
|
+
resolvedPath = resolvedPath.replace(/\\/g, "/");
|
|
860
|
+
if (!resolvedPath.startsWith(".") && !resolvedPath.startsWith("/")) {
|
|
861
|
+
resolvedPath = "./" + resolvedPath;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
result[alias] = resolvedPath;
|
|
865
|
+
}
|
|
866
|
+
return result;
|
|
867
|
+
}
|
|
868
|
+
export {
|
|
869
|
+
Controller,
|
|
870
|
+
ERROR_MESSAGES,
|
|
871
|
+
Module,
|
|
872
|
+
NodulusError,
|
|
873
|
+
Repository,
|
|
874
|
+
Schema,
|
|
875
|
+
Service,
|
|
876
|
+
createApp,
|
|
877
|
+
getAliases,
|
|
878
|
+
getRegistry
|
|
879
|
+
};
|
|
880
|
+
//# sourceMappingURL=index.js.map
|