@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 ADDED
@@ -0,0 +1,48 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.1.0] - 2026-04-08
9
+
10
+ ### Changed
11
+ - Centralized stack trace capture logic for identifiers (`getCallerInfo`) into a single internal helper `src/core/caller.ts` resolving DRY violations.
12
+ - Restringed public API surface on `src/index.ts`. Internal utilities `loadConfig` and `DEFAULTS` are no longer exported.
13
+ - Simplified schema generation scaffolding avoiding hard dependency assumptions (`import { z } from 'zod'`).
14
+ - JSDoc explicitly addresses the inverse filtering logic behind the `includeFolders` flag inside `getAliases`.
15
+ - Rigorous isolation properties assigned strictly atop Vitest configuration (`pool: forks`, `testTimeout`).
16
+ - Renamed testing suite strings internally stripping hardcoded framework versions (`V1.0.0`) enhancing legibility.
17
+
18
+ ### Deprecated
19
+ - `ERROR_MESSAGES` en `errors.ts` ha sido marcado como deprecado y será eliminado en v2.0.0. Los mensajes reales se definen en el lugar donde se lanza la excepción.
20
+
21
+ ### Fixed
22
+ - Fixed bug causing misleading error mappings (`REGISTRY_MISSING_CONTEXT`) across non-express Identifiers when caller bounds fail. They correctly throw `INVALID_MODULE_DECLARATION`.
23
+ - Reorganized `activateAliasResolver` destructuring spread prioritizing user configuration aliases over auto-generated module ones.
24
+ - Removed unreachable condition branch inside `createApp.ts` unlocking proper stdout logs for explicitly disabled controllers.
25
+ - `sync-tsconfig` sweeps properly stale config aliases that follow the heuristic trailing completion logic.
26
+ - Resolved hardcoded versions in CLI metadata: `nodulus --version` properly pulls the underlying release version dynamically from `package.json`.
27
+
28
+ ## [1.0.0] - 2026-04-05
29
+
30
+ ### Added
31
+ - **Core structural layer**: Automatic module discovery and controller registration for Express apps.
32
+ - **Nodulus CLI**: Shipped the `nodulus` binary with `create-module` (scaffolding) and `sync-tsconfig` (IDE sync) commands.
33
+ - **Identifiers**: Added `Service()`, `Repository()`, and `Schema()` structural markers for registering domain concepts alongside `Controller()`.
34
+ - **Bootstrapping**: Robust `createApp()` pipeline with performance metrics and validation.
35
+ - **Logging System**: Color-coded, structured logging with `picocolors` and injectable handlers.
36
+ - **Isolation**: Per-execution registry isolation using `AsyncLocalStorage` to prevent state contamination.
37
+ - **ESM Aliases**: Seamless `@modules/*` and custom folder aliases via Node.js Hooks API.
38
+ - **Strict Mode**: Validation for circular dependencies and undeclared cross-module imports.
39
+
40
+ ### Changed
41
+ - Rebranded project from "Modular" to "Nodulus".
42
+ - **ESM-Only Architecture**: Dropped CommonJS support; Nodulus now requires `"type": "module"` in `package.json`.
43
+ - Updated minimum Node.js requirement to `v20.6.0+` for native ESM hook support.
44
+ - Refined `NodulusError` structure with clearer cause/solution messages.
45
+
46
+ ### Fixed
47
+ - Fixed race conditions and duplicate registration errors in hot-reloading scenarios.
48
+ - Fixed ESM module caching issues in high-frequency integration tests.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Keiver Luna
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,532 @@
1
+ # Nodulus
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@vlynk-studios/nodulus-core.svg)](https://www.npmjs.com/package/@vlynk-studios/nodulus-core)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![Node.js Version](https://img.shields.io/badge/node-%3E%3D20.6-brightgreen)](https://nodejs.org/)
6
+
7
+ A lightweight structural layer for Express. Nodulus lets you organise your Node.js application into self-contained modules — handling discovery, route mounting, import aliases, and dependency validation at bootstrap time, with zero overhead at runtime.
8
+
9
+ > **Node.js ≥ 20.6** · **Express 4.x / 5.x** · **ESM Only** · **TypeScript included**
10
+
11
+ ---
12
+
13
+ ## Why Nodulus?
14
+
15
+ Express is minimal by design. Nodulus keeps it that way while adding just enough structure to scale:
16
+
17
+ - **Module discovery** — point it at a glob `src/modules/*` and it finds, validates, and loads every module automatically.
18
+ - **Route mounting** — controllers declare their prefix; `createApp()` wires them to Express via `app.use()`.
19
+ - **Import aliases** — `@modules/users`, `@config/database` — no more `../../..` paths.
20
+ - **Dependency validation** — declare what your module imports and exports; Nodulus catches mismatches before a single request is handled.
21
+ - **No magic at runtime** — after bootstrap, Nodulus is out of the way. Express handles requests exactly as normal.
22
+
23
+ ---
24
+
25
+ ## Installation
26
+
27
+ ```bash
28
+ npm install @vlynk-studios/nodulus-core
29
+ ```
30
+
31
+ Express is a peer dependency:
32
+
33
+ ```bash
34
+ npm install express
35
+ ```
36
+
37
+ ---
38
+
39
+ ## Quick start
40
+
41
+ ```ts
42
+ // src/app.ts
43
+ import express from 'express'
44
+ import { createApp } from '@vlynk-studios/nodulus-core'
45
+
46
+ const app = express()
47
+ app.use(express.json())
48
+
49
+ const { routes } = await createApp(app, {
50
+ modules: 'src/modules/*',
51
+ prefix: '/api/v1',
52
+ aliases: {
53
+ '@config': './src/config',
54
+ '@middleware': './src/middleware',
55
+ '@shared': './src/shared',
56
+ },
57
+ strict: process.env.NODE_ENV !== 'production',
58
+ logger: (level, msg) => console[level](`[nodulus] ${msg}`),
59
+ })
60
+
61
+ app.use(errorHandler) // error middleware always last
62
+
63
+ console.log(`Mounted routes: ${routes.length}`)
64
+ export default app
65
+ ```
66
+
67
+ Then run your app with the `--import` flag so that aliases work at runtime:
68
+
69
+ ```bash
70
+ node --import @vlynk-studios/nodulus-core/register src/app.ts
71
+ ```
72
+
73
+ > This registers the ESM Hook that enables runtime alias resolution. Without this flag, `@modules/*` and folder aliases will not resolve at runtime.
74
+
75
+ ---
76
+
77
+ ## Project structure
78
+
79
+ Nodulus expects modules in a consistent layout:
80
+
81
+ ```
82
+ src/
83
+ ├── modules/
84
+ │ └── users/
85
+ │ ├── index.ts ← required — calls Module('users', ...)
86
+ │ ├── users.routes.ts ← controller (discovered automatically)
87
+ │ ├── users.service.ts ← private business logic
88
+ │ └── users.types.ts ← excluded from controller scan
89
+ └── app.ts
90
+ ```
91
+
92
+ ---
93
+
94
+ ## API
95
+
96
+ ### `createApp(app, options?)`
97
+
98
+ Bootstraps the entire application. Runs module discovery, alias resolution, controller mounting, and validation in a deterministic sequence. Throws a `NodulusError` before mounting any routes if anything is invalid — the app is never left in a partial state.
99
+
100
+ ```ts
101
+ createApp(app: Application, options?: CreateAppOptions): Promise<NodulusApp>
102
+ ```
103
+
104
+ | Option | Type | Default | Description |
105
+ |---|---|---|---|
106
+ | `modules` | `string` | `'src/modules/*'` | Glob pointing to module folders |
107
+ | `prefix` | `string` | `''` | Global route prefix (e.g. `'/api/v1'`) |
108
+ | `aliases` | `Record<string, string>` | `{}` | Folder aliases beyond the auto-generated `@modules/*` |
109
+ | `strict` | `boolean` | `true` in dev | Enables circular-dependency detection and undeclared-import errors |
110
+ | `resolveAliases` | `boolean` | `true` | Disable if you resolve aliases with a bundler |
111
+ | `logger` | `LogHandler` | `defaultLogHandler` | Custom log handler (supports Pino, Winston, etc.) |
112
+ | `logLevel` | `LogLevel` | `'info'` | Minimum severity for log events |
113
+
114
+ Returns `NodulusApp`:
115
+
116
+ ```ts
117
+ interface NodulusApp {
118
+ modules: RegisteredModule[]
119
+ routes: MountedRoute[]
120
+ registry: NodulusRegistry
121
+ }
122
+ ```
123
+
124
+ ---
125
+
126
+ ### `Module(name, options?)`
127
+
128
+ Declares a module and registers its metadata in the registry. **Must** be called from the module's `index.ts` (or `index.js`), and the `name` **must match the containing folder name exactly** — Nodulus enforces this as a structural rule.
129
+
130
+ ```ts
131
+ // src/modules/orders/index.ts
132
+ import { Module } from '@vlynk-studios/nodulus-core'
133
+
134
+ Module('orders', {
135
+ description: 'Purchase order management',
136
+ imports: ['users', 'payments'],
137
+ exports: ['OrderService', 'createOrderSchema'],
138
+ })
139
+
140
+ export { OrderService } from './orders.service.js'
141
+ export { createOrderSchema } from './orders.schema.js'
142
+ ```
143
+
144
+ | Option | Type | Description |
145
+ |---|---|---|
146
+ | `imports` | `string[]` | Modules this module depends on |
147
+ | `exports` | `string[]` | Public API names — validated against real exports at bootstrap |
148
+ | `description` | `string` | Documentation / future tooling |
149
+
150
+ > **Rule**: The name passed to `Module()` must equal the directory name. `Module('orders')` inside `src/modules/billing/` will throw `INVALID_MODULE_DECLARATION`.
151
+
152
+ ---
153
+
154
+ ### `Controller(prefix, options?)`
155
+
156
+ Declares a file as an Express controller. The controller name is derived automatically from the filename. The file **must** have a `default export` of an Express `Router`.
157
+
158
+ ```ts
159
+ // src/modules/users/users.routes.ts
160
+ import { Controller } from '@vlynk-studios/nodulus-core'
161
+ import { Router } from 'express'
162
+ import { requireAuth } from '@middleware/auth.js'
163
+ import { UserService } from './users.service.js'
164
+
165
+ Controller('/users', {
166
+ middlewares: [requireAuth],
167
+ })
168
+
169
+ const router = Router()
170
+
171
+ router.get('/', async (req, res, next) => {
172
+ try {
173
+ res.json(await UserService.findAll())
174
+ } catch (err) {
175
+ next(err)
176
+ }
177
+ })
178
+
179
+ router.post('/', async (req, res, next) => {
180
+ try {
181
+ res.status(201).json(await UserService.create(req.body))
182
+ } catch (err) {
183
+ next(err)
184
+ }
185
+ })
186
+
187
+ export default router
188
+ ```
189
+
190
+ | Parameter | Type | Description |
191
+ |---|---|---|
192
+ | `prefix` | `string` | Route prefix for this controller (e.g. `'/users'`) |
193
+ | `options.middlewares` | `RequestHandler[]` | Middlewares applied to all routes in this controller. Default: `[]` |
194
+ | `options.enabled` | `boolean` | If `false`, `createApp()` ignores this controller entirely. Default: `true` |
195
+
196
+ Nodulus mounts each controller as:
197
+
198
+ ```ts
199
+ app.use(globalPrefix + controllerPrefix, ...middlewares, router)
200
+ ```
201
+
202
+ ---
203
+
204
+ ### Domain Identifiers
205
+
206
+ To guarantee accurate error-tracing, structured logs, and framework-level validation, label your business logic with domain identifiers. They capture stack metadata to bind exports effectively to their parent module without any extra configuration.
207
+
208
+ ```ts
209
+ import { Service, Repository, Schema } from '@vlynk-studios/nodulus-core'
210
+ import { z } from 'zod'
211
+
212
+ Service('UserService')
213
+ Repository('UserRepository', { source: 'database' })
214
+ Schema('UserSchema', { library: 'zod' })
215
+ ```
216
+
217
+ Unlike `Controller` or `Module`, these identifiers do not alter runtime execution traces or wrap payloads—they simply announce presence and ownership into the `NodulusRegistry`.
218
+
219
+ > **Note:** Nodulus is validation-agnostic. While examples use Zod, you can use Joi, TypeBox, or any other library.
220
+
221
+ ---
222
+
223
+ ### Import aliases
224
+
225
+ Nodulus registers two kinds of aliases:
226
+
227
+ - **Module aliases** — auto-generated for every discovered module:
228
+ ```
229
+ @modules/<n> → src/modules/<n>/index.ts
230
+ ```
231
+ - **Folder aliases** — configured in `createApp()` or `nodulus.config.ts`:
232
+ ```
233
+ @config → src/config/
234
+ @middleware → src/middleware/
235
+ ```
236
+
237
+ Use them anywhere in your code:
238
+
239
+ ```ts
240
+ import { UserService } from '@modules/users'
241
+ import { db } from '@config/database.js'
242
+ ```
243
+
244
+ > [!IMPORTANT]
245
+ > Nodulus is an **ESM-only** framework. It requires `"type": "module"` in your `package.json`.
246
+ > Dynamic runtime alias resolution relies on the Node.js ESM Hooks API (`--import` or `register`).
247
+
248
+ For bundler-based projects (Vite, Esbuild, etc.), you can disable the runtime hook and inject `getAliases()` directly into your config:
249
+
250
+ ```ts
251
+ // vite.config.ts
252
+ import { getAliases } from '@vlynk-studios/nodulus-core'
253
+
254
+ const aliases = await getAliases()
255
+
256
+ export default {
257
+ resolve: { alias: aliases }
258
+ }
259
+ ```
260
+
261
+ ```ts
262
+ // esbuild.config.ts
263
+ import { getAliases } from '@vlynk-studios/nodulus-core'
264
+ import * as esbuild from 'esbuild'
265
+
266
+ const aliases = await getAliases()
267
+
268
+ await esbuild.build({
269
+ entryPoints: ['src/index.ts'],
270
+ alias: aliases,
271
+ bundle: true,
272
+ outfile: 'dist/app.js'
273
+ })
274
+ ```
275
+
276
+ `getAliases()` accepts a `GetAliasesOptions` object:
277
+
278
+ | Option | Type | Default | Description |
279
+ |---|---|---|---|
280
+ | `includeFolders` | `boolean` | `true` | If `false`, config-defined folder aliases are excluded (returns only auto-generated `@modules/*` aliases) |
281
+ | `absolute` | `boolean` | `false` | If `true`, returned paths are absolute |
282
+
283
+ ---
284
+
285
+ ### `nodulus.config.ts`
286
+
287
+ Centralise configuration in the project root. Options passed directly to `createApp()` take priority over the file.
288
+
289
+ ```ts
290
+ // nodulus.config.ts
291
+ import type { NodulusConfig } from '@vlynk-studios/nodulus-core'
292
+
293
+ const config: NodulusConfig = {
294
+ modules: 'src/modules/*',
295
+ prefix: '/api/v1',
296
+ strict: process.env.NODE_ENV !== 'production',
297
+ aliases: {
298
+ '@config': './src/config',
299
+ '@middleware': './src/middleware',
300
+ '@shared': './src/shared',
301
+ },
302
+ }
303
+
304
+ export default config
305
+ ```
306
+
307
+ Config file loading order (first match wins):
308
+
309
+ 1. `nodulus.config.ts` — development only
310
+ 2. `nodulus.config.js` — always
311
+
312
+ ---
313
+
314
+ ## CLI Tools
315
+
316
+ Nodulus provides a built-in CLI to enforce conventions effortlessly and improve developer experience without memorizing boilerplate.
317
+
318
+ ### `nodulus create-module <n>`
319
+
320
+ Scaffolds a perfectly structured module conforming to the framework constraints instantaneously.
321
+
322
+ ```bash
323
+ npx nodulus create-module payments
324
+ ```
325
+
326
+ ```text
327
+ ✔ Module 'payments' created successfully at src/modules/payments/
328
+ index.ts
329
+ payments.routes.ts
330
+ payments.service.ts
331
+ payments.repository.ts
332
+ payments.schema.ts
333
+ ```
334
+
335
+ | Option | Description |
336
+ |---|---|
337
+ | `--path <path>` | Sets a custom absolute or relative destination |
338
+ | `--no-repository` | Omits the repository file |
339
+ | `--no-schema` | Omits the schema file |
340
+
341
+ ### `nodulus sync-tsconfig`
342
+
343
+ Because nodulus dynamically discovers modules and configures `@modules/*` ES Hooks aliases, Node.js can recognize your code immediately. However, IDEs and TypeScript demand static assertions. This command bridges the gap by injecting your dynamic nodulus module aliases safely onto `compilerOptions.paths`.
344
+
345
+ ```bash
346
+ npx nodulus sync-tsconfig
347
+ ```
348
+
349
+ ```text
350
+ ✔ tsconfig.json updated — 3 module(s), 2 folder alias(es)
351
+ Added paths:
352
+ @modules/users → ./src/modules/users/index.ts
353
+ @modules/auth → ./src/modules/auth/index.ts
354
+ @config/* → ./src/config/*
355
+ ```
356
+
357
+ Run this command initially, and whenever you create, rename, or drop modules in the project. It behaves idempotently and automatically purges references to modules that were deleted.
358
+
359
+ ---
360
+
361
+ ## Logging
362
+
363
+ Nodulus emits structured, color-coded log events throughout the bootstrap pipeline using [picocolors](https://github.com/alexeyraspopov/picocolors).
364
+
365
+ ### Default behavior
366
+
367
+ | Environment | Default level | Output |
368
+ |---|---|---|
369
+ | Development | `info` | Modules loading, routes mounting, startup duration |
370
+ | Any | `warn` / `error` | Written to `stderr`; everything else to `stdout` |
371
+ | Debug | `debug` | Set `NODE_DEBUG=nodulus` to see file scans and alias registrations |
372
+
373
+ ### Using a custom logger (Pino)
374
+
375
+ The `LogHandler` signature is compatible with most modern loggers:
376
+
377
+ ```ts
378
+ import pino from 'pino'
379
+ const log = pino()
380
+
381
+ await createApp(app, {
382
+ logger: (level, message, meta) => {
383
+ log[level]({ ...meta, framework: 'nodulus' }, message)
384
+ }
385
+ })
386
+ ```
387
+
388
+ ### Total silence
389
+
390
+ ```ts
391
+ await createApp(app, {
392
+ logger: () => {} // Silences all output regardless of level
393
+ })
394
+ ```
395
+
396
+ ---
397
+
398
+ ## Error handling
399
+
400
+ All Nodulus errors are instances of `NodulusError` and carry a machine-readable `code`:
401
+
402
+ ```ts
403
+ import { NodulusError } from '@vlynk-studios/nodulus-core'
404
+
405
+ try {
406
+ await createApp(app, { modules: 'src/modules/*' })
407
+ } catch (err) {
408
+ if (err instanceof NodulusError) {
409
+ console.error(err.code) // 'EXPORT_MISMATCH'
410
+ console.error(err.message) // human-readable description
411
+ console.error(err.details) // additional context (path, module name, etc.)
412
+ }
413
+ process.exit(1)
414
+ }
415
+ ```
416
+
417
+ | Code | When it's thrown |
418
+ |---|---|
419
+ | `MODULE_NOT_FOUND` | Discovered folder has no `index.ts` / `index.js`, or `index.ts` does not call `Module()` |
420
+ | `INVALID_MODULE_DECLARATION` | `Module()` name doesn't match folder name, or an Identifier (Service, Schema, etc) is declared incorrectly or fails to detect caller bounds |
421
+ | `DUPLICATE_MODULE` | Two modules share the same name |
422
+ | `MISSING_IMPORT` | Module listed in `imports` does not exist in the registry |
423
+ | `UNDECLARED_IMPORT` | Module imports from another not listed in `imports` (strict only) |
424
+ | `CIRCULAR_DEPENDENCY` | A dependency cycle was detected (strict only) |
425
+ | `EXPORT_MISMATCH` | Name declared in `exports` is not an actual export of `index.ts` |
426
+ | `INVALID_CONTROLLER` | Controller file has no `default export` of an Express `Router` |
427
+ | `ALIAS_NOT_FOUND` | Configured alias points to a directory that does not exist |
428
+ | `DUPLICATE_ALIAS` | Two aliases resolve to the same name but different paths |
429
+ | `DUPLICATE_BOOTSTRAP` | `createApp()` called more than once with the same Express instance |
430
+ | `REGISTRY_MISSING_CONTEXT` | A Nodulus API was called outside of a `createApp()` async context |
431
+ | `INVALID_ESM_ENV` | `createApp()` called in a non-ESM environment (missing `"type": "module"` in `package.json`) |
432
+
433
+ ---
434
+
435
+ ## Advanced usage
436
+
437
+ ### `getRegistry()`
438
+
439
+ Returns the read-only registry bound to the current async execution context. Only callable within a `createApp()` scope.
440
+
441
+ > [!CAUTION]
442
+ > **@unstable API**: Intended for advanced framework integrations and debugging. Structure may change without a major version bump.
443
+
444
+ ```ts
445
+ import { getRegistry } from '@vlynk-studios/nodulus-core'
446
+
447
+ const registry = getRegistry()
448
+ const allModules = registry.getAllModules() // RegisteredModule[]
449
+ const alias = registry.resolveAlias('@modules/users')
450
+ ```
451
+
452
+ `NodulusRegistry` interface:
453
+
454
+ ```ts
455
+ interface NodulusRegistry {
456
+ hasModule(name: string): boolean
457
+ getModule(name: string): RegisteredModule | undefined
458
+ getAllModules(): RegisteredModule[]
459
+ resolveAlias(alias: string): string | undefined
460
+ getAllAliases(): Record<string, string>
461
+ }
462
+ ```
463
+
464
+ ---
465
+
466
+ ## Use cases
467
+
468
+ ### Microservices
469
+ Isolate each domain into a module and share types through `@modules/shared`. Each service stays lean with zero cross-cutting concerns.
470
+
471
+ ### Monoliths
472
+ Enforce clean module boundaries at bootstrap, not code review. Nodulus catches circular dependencies and missing imports before your server starts.
473
+
474
+ ### Fast prototyping
475
+ Scaffold a new feature by creating a folder and an `index.ts`. Nodulus handles all the boilerplate of wiring routes and middlewares.
476
+
477
+ ---
478
+
479
+ ## Requirements
480
+
481
+ | | Minimum |
482
+ |---|---|
483
+ | Node.js | 20.6.0 |
484
+ | Express | 4.x or 5.x |
485
+ | TypeScript | 5.0+ (optional) |
486
+
487
+ > **Why 20.6?** Nodulus uses the Node.js [ESM Hooks API](https://nodejs.org/api/module.html#customization-hooks) (`--import` / `register`) for runtime alias resolution. Native support without `--experimental-loader` requires Node 20.6+.
488
+
489
+ ---
490
+
491
+ ## ESM Only
492
+
493
+ Nodulus is built as a pure ESM package. It does not support CommonJS (`require()`).
494
+
495
+ ```ts
496
+ import { createApp, Module, Controller } from '@vlynk-studios/nodulus-core'
497
+ ```
498
+
499
+ > **Note:** Runtime alias resolution uses the ESM Hooks API. Ensure your `package.json` contains `"type": "module"`.
500
+
501
+ ---
502
+
503
+ ## TypeScript
504
+
505
+ Types are bundled — no `@types/nodulus` needed.
506
+
507
+ ```ts
508
+ import type {
509
+ CreateAppOptions,
510
+ NodulusApp,
511
+ NodulusRegistry,
512
+ NodulusConfig,
513
+ NodulusError,
514
+ ModuleOptions,
515
+ ControllerOptions,
516
+ RegisteredModule,
517
+ MountedRoute,
518
+ GetAliasesOptions,
519
+ LogLevel,
520
+ LogHandler,
521
+ } from '@vlynk-studios/nodulus-core'
522
+ ```
523
+
524
+ ---
525
+
526
+ ## License
527
+
528
+ MIT
529
+
530
+ ---
531
+
532
+ Developed and maintained by **Vlynk Studios**.
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node