@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/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
|
+
[](https://www.npmjs.com/package/@vlynk-studios/nodulus-core)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](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
|