@weconjs/core 1.2.2 → 1.2.6
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/README.md +460 -259
- package/dist/config.d.ts +7 -7
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +21 -43
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +5 -0
- package/dist/server/index.js.map +1 -1
- package/dist/types.d.ts +10 -12
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/dist/devtools/client/assets/ModuleDetail-BoiZYmmO.js +0 -1
- package/dist/devtools/client/assets/TranslationEditor-Dz2BHJgQ.js +0 -6
- package/dist/devtools/client/assets/api-CvtLmLuv.js +0 -11
- package/dist/devtools/client/assets/arrow-left-C26_8YjI.js +0 -6
- package/dist/devtools/client/assets/chevron-right-ByQ2jwEZ.js +0 -6
- package/dist/devtools/client/assets/index-BFzJyDrn.css +0 -1
- package/dist/devtools/client/assets/index-BI-oiBt-.js +0 -1
- package/dist/devtools/client/assets/index-BMd21Cuf.js +0 -1
- package/dist/devtools/client/assets/index-BjgISQg5.js +0 -181
- package/dist/devtools/client/assets/index-D825pKz_.js +0 -6
- package/dist/devtools/client/assets/index-DS8GcC6h.js +0 -6
- package/dist/devtools/client/assets/index-DaglUNul.js +0 -1
- package/dist/devtools/client/index.html +0 -15
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @weconjs/core
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Convention-over-configuration Node.js framework built on Express.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@weconjs/core)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
@@ -8,64 +8,56 @@
|
|
|
8
8
|
## Table of Contents
|
|
9
9
|
|
|
10
10
|
- [Installation](#installation)
|
|
11
|
-
- [Features](#features)
|
|
12
11
|
- [Quick Start](#quick-start)
|
|
13
|
-
- [Configuration
|
|
14
|
-
- [
|
|
15
|
-
- [
|
|
16
|
-
- [Database
|
|
12
|
+
- [Configuration](#configuration)
|
|
13
|
+
- [Modules](#modules)
|
|
14
|
+
- [Routing & RBAC](#routing--rbac)
|
|
15
|
+
- [Database](#database)
|
|
17
16
|
- [Logging](#logging)
|
|
17
|
+
- [i18n](#i18n)
|
|
18
|
+
- [Socket.IO](#socketio)
|
|
19
|
+
- [Context & Services](#context--services)
|
|
20
|
+
- [Authentication](#authentication)
|
|
18
21
|
- [API Reference](#api-reference)
|
|
19
22
|
- [Testing](#testing)
|
|
23
|
+
- [Requirements](#requirements)
|
|
24
|
+
- [License](#license)
|
|
20
25
|
|
|
21
26
|
## Installation
|
|
22
27
|
|
|
23
28
|
```bash
|
|
24
29
|
npm install @weconjs/core
|
|
25
|
-
# or
|
|
26
|
-
yarn add @weconjs/core
|
|
27
30
|
```
|
|
28
31
|
|
|
29
|
-
|
|
32
|
+
Express and Mongoose are peer dependencies:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install express mongoose
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Optional peer dependencies:
|
|
30
39
|
|
|
31
40
|
```bash
|
|
32
41
|
# Winston logging with daily file rotation
|
|
33
42
|
npm install winston winston-daily-rotate-file
|
|
34
43
|
|
|
44
|
+
# Socket.IO real-time support
|
|
45
|
+
npm install socket.io
|
|
46
|
+
|
|
35
47
|
# Field-level access control for Mongoose
|
|
36
48
|
npm install @weconjs/mongoose-field-shield
|
|
37
49
|
```
|
|
38
50
|
|
|
39
|
-
## Features
|
|
40
|
-
|
|
41
|
-
| Feature | Description |
|
|
42
|
-
|---------|-------------|
|
|
43
|
-
| **Configuration System** | Mode-based configuration with inheritance (development, staging, production) |
|
|
44
|
-
| **Module System** | Auto-discovery, dependency resolution, and lifecycle hooks |
|
|
45
|
-
| **i18n Support** | Automatic translation file loading from module directories |
|
|
46
|
-
| **Socket.IO Integration** | Auto-discover and register socket handlers and middleware |
|
|
47
|
-
| **Database Connection** | MongoDB/Mongoose with URI builders, plugins, retry logic |
|
|
48
|
-
| **Server Factory** | Complete Express bootstrap with HTTPS, graceful shutdown |
|
|
49
|
-
| **Winston Logger** | Production-ready logging with console and file rotation |
|
|
50
|
-
| **Response Helpers** | Standardized API responses via `res.respond()` |
|
|
51
|
-
| **HTTPS Support** | Built-in SSL/TLS certificate loading and validation |
|
|
52
|
-
| **Graceful Shutdown** | Proper SIGTERM/SIGINT handling with cleanup hooks |
|
|
53
|
-
|
|
54
|
-
---
|
|
55
|
-
|
|
56
51
|
## Quick Start
|
|
57
52
|
|
|
58
|
-
### 1.
|
|
53
|
+
### 1. Define your configuration
|
|
59
54
|
|
|
60
55
|
```typescript
|
|
61
56
|
// wecon.config.ts
|
|
62
57
|
import { defineConfig } from '@weconjs/core';
|
|
63
58
|
|
|
64
59
|
export default defineConfig({
|
|
65
|
-
app: {
|
|
66
|
-
name: 'my-api',
|
|
67
|
-
version: '1.0.0',
|
|
68
|
-
},
|
|
60
|
+
app: { name: 'my-api', version: '1.0.0' },
|
|
69
61
|
modes: {
|
|
70
62
|
development: {
|
|
71
63
|
port: 3000,
|
|
@@ -86,53 +78,50 @@ export default defineConfig({
|
|
|
86
78
|
protocol: 'mongodb+srv',
|
|
87
79
|
host: process.env.DB_HOST,
|
|
88
80
|
database: 'myapp',
|
|
89
|
-
|
|
90
|
-
|
|
81
|
+
auth: {
|
|
82
|
+
username: process.env.DB_USER,
|
|
83
|
+
password: process.env.DB_PASSWORD,
|
|
84
|
+
},
|
|
91
85
|
},
|
|
92
86
|
},
|
|
93
|
-
logging: {
|
|
94
|
-
level: 'info',
|
|
95
|
-
enableFile: true,
|
|
96
|
-
directory: './logs',
|
|
97
|
-
},
|
|
98
|
-
https: {
|
|
99
|
-
enabled: true,
|
|
100
|
-
keyPath: './certs/privkey.pem',
|
|
101
|
-
certPath: './certs/fullchain.pem',
|
|
102
|
-
},
|
|
103
|
-
},
|
|
104
|
-
},
|
|
105
|
-
modules: ['./src/modules/users', './src/modules/auth'],
|
|
106
|
-
features: {
|
|
107
|
-
i18n: {
|
|
108
|
-
enabled: true,
|
|
109
|
-
defaultLocale: 'en',
|
|
110
|
-
supported: ['en', 'es', 'fr'],
|
|
111
|
-
},
|
|
112
|
-
fieldShield: {
|
|
113
|
-
enabled: true,
|
|
114
|
-
strict: true,
|
|
87
|
+
logging: { level: 'info', enableFile: true },
|
|
115
88
|
},
|
|
116
89
|
},
|
|
90
|
+
modules: ['./src/modules/auth', './src/modules/users'],
|
|
117
91
|
});
|
|
118
92
|
```
|
|
119
93
|
|
|
120
|
-
### 2. Define a
|
|
94
|
+
### 2. Define a module
|
|
121
95
|
|
|
122
96
|
```typescript
|
|
123
97
|
// src/modules/users/users.module.ts
|
|
124
|
-
import { defineModule } from '@weconjs/core';
|
|
125
|
-
import { Routes, Route } from '@weconjs/lib';
|
|
98
|
+
import { defineModule, Routes, Route } from '@weconjs/core';
|
|
126
99
|
import { userController } from './controllers/user.controller.js';
|
|
127
100
|
|
|
128
101
|
const usersRoutes = new Routes({
|
|
129
102
|
prefix: '/users',
|
|
130
103
|
routes: [
|
|
131
|
-
new Route({
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
104
|
+
new Route({
|
|
105
|
+
method: 'GET',
|
|
106
|
+
path: '/',
|
|
107
|
+
rai: 'users:list',
|
|
108
|
+
roles: ['admin', 'user'],
|
|
109
|
+
middlewares: [userController.findAll],
|
|
110
|
+
}),
|
|
111
|
+
new Route({
|
|
112
|
+
method: 'GET',
|
|
113
|
+
path: '/:id',
|
|
114
|
+
rai: 'users:read',
|
|
115
|
+
roles: ['admin', 'user'],
|
|
116
|
+
middlewares: [userController.findById],
|
|
117
|
+
}),
|
|
118
|
+
new Route({
|
|
119
|
+
method: 'POST',
|
|
120
|
+
path: '/',
|
|
121
|
+
rai: 'users:create',
|
|
122
|
+
roles: ['admin'],
|
|
123
|
+
middlewares: [userController.create],
|
|
124
|
+
}),
|
|
136
125
|
],
|
|
137
126
|
});
|
|
138
127
|
|
|
@@ -140,75 +129,60 @@ export default defineModule({
|
|
|
140
129
|
name: 'users',
|
|
141
130
|
description: 'User management module',
|
|
142
131
|
routes: usersRoutes,
|
|
143
|
-
|
|
132
|
+
imports: ['auth'],
|
|
144
133
|
onInit: async (ctx) => {
|
|
145
134
|
ctx.logger.info('Users module initialized');
|
|
146
|
-
// Setup module-specific resources
|
|
147
135
|
},
|
|
148
136
|
});
|
|
149
137
|
```
|
|
150
138
|
|
|
151
|
-
### 3.
|
|
139
|
+
### 3. Set up routing with RBAC
|
|
152
140
|
|
|
153
141
|
```typescript
|
|
154
|
-
// src/
|
|
155
|
-
import
|
|
156
|
-
import
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
142
|
+
// src/bootstrap.ts
|
|
143
|
+
import { Wecon, Routes } from '@weconjs/core';
|
|
144
|
+
import usersModule from './modules/users/users.module.js';
|
|
145
|
+
import authModule from './modules/auth/auth.module.js';
|
|
146
|
+
|
|
147
|
+
const roles = ['admin', 'user', 'guest'] as const;
|
|
148
|
+
|
|
149
|
+
export const wecon = new Wecon()
|
|
150
|
+
.roles(roles)
|
|
151
|
+
.guestRole('guest')
|
|
152
|
+
.routes(
|
|
153
|
+
new Routes({
|
|
154
|
+
prefix: '/api',
|
|
155
|
+
routes: [authModule.routes!, usersModule.routes!],
|
|
156
|
+
})
|
|
157
|
+
)
|
|
158
|
+
.dev({ helpfulErrors: true, logRoutes: true })
|
|
159
|
+
.build();
|
|
160
|
+
|
|
161
|
+
export const modules = [authModule, usersModule];
|
|
162
|
+
```
|
|
162
163
|
|
|
163
|
-
|
|
164
|
-
// Load configuration with mode resolution
|
|
165
|
-
const config = await loadConfig(
|
|
166
|
-
path.resolve(process.cwd(), 'wecon.config.ts'),
|
|
167
|
-
process.env.NODE_ENV
|
|
168
|
-
);
|
|
164
|
+
### 4. Create the application entry point
|
|
169
165
|
|
|
170
|
-
|
|
171
|
-
|
|
166
|
+
```typescript
|
|
167
|
+
// src/main.ts
|
|
168
|
+
import { createWecon, loadConfig, buildUriFromConfig } from '@weconjs/core';
|
|
169
|
+
import { wecon, modules } from './bootstrap.js';
|
|
172
170
|
|
|
173
|
-
|
|
174
|
-
const
|
|
171
|
+
async function main() {
|
|
172
|
+
const config = await loadConfig('./wecon.config.ts', process.env.NODE_ENV);
|
|
175
173
|
|
|
176
|
-
// Create and configure the application
|
|
177
174
|
const app = await createWecon({
|
|
178
175
|
config,
|
|
179
|
-
modules
|
|
176
|
+
modules,
|
|
180
177
|
wecon,
|
|
181
|
-
middleware: [], // Your custom middleware array
|
|
182
178
|
database: {
|
|
183
179
|
enabled: true,
|
|
184
180
|
uri: buildUriFromConfig(config.database),
|
|
185
|
-
plugins: [], // Global Mongoose plugins
|
|
186
|
-
debug: config.mode === 'development',
|
|
187
|
-
},
|
|
188
|
-
plugins: {
|
|
189
|
-
fieldShield: config.features?.fieldShield?.enabled
|
|
190
|
-
? { strict: config.features.fieldShield.strict ?? true }
|
|
191
|
-
: false,
|
|
192
|
-
},
|
|
193
|
-
i18n: {
|
|
194
|
-
enabled: config.features?.i18n?.enabled ?? false,
|
|
195
|
-
modulesDir: './src/modules',
|
|
196
|
-
},
|
|
197
|
-
logger: {
|
|
198
|
-
useWinston: true,
|
|
199
|
-
level: config.logging?.level ?? 'info',
|
|
200
|
-
appName: config.app.name,
|
|
201
|
-
enableFile: config.logging?.enableFile ?? false,
|
|
202
|
-
logsDir: config.logging?.directory ?? './logs',
|
|
203
181
|
},
|
|
182
|
+
logger: { useWinston: true, enableFile: false },
|
|
204
183
|
hooks: {
|
|
205
|
-
onBoot:
|
|
206
|
-
|
|
207
|
-
},
|
|
208
|
-
onShutdown: async (ctx: WeconContext) => {
|
|
209
|
-
ctx.logger.info('Application shutting down...');
|
|
210
|
-
// Cleanup resources, close connections
|
|
211
|
-
},
|
|
184
|
+
onBoot: (ctx) => ctx.logger.info('Server ready'),
|
|
185
|
+
onShutdown: (ctx) => ctx.logger.info('Shutting down...'),
|
|
212
186
|
},
|
|
213
187
|
});
|
|
214
188
|
|
|
@@ -216,98 +190,124 @@ async function main() {
|
|
|
216
190
|
}
|
|
217
191
|
|
|
218
192
|
main().catch((err) => {
|
|
219
|
-
console.error('Failed to start
|
|
193
|
+
console.error('Failed to start:', err);
|
|
220
194
|
process.exit(1);
|
|
221
195
|
});
|
|
222
196
|
```
|
|
223
197
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
## Configuration System
|
|
198
|
+
## Configuration
|
|
227
199
|
|
|
228
|
-
### Mode-
|
|
200
|
+
### Mode-based configuration
|
|
229
201
|
|
|
230
|
-
Define environment-specific settings that
|
|
202
|
+
Define environment-specific settings that deep-merge with a base configuration. Modes can inherit from other modes with `extends`:
|
|
231
203
|
|
|
232
204
|
```typescript
|
|
233
205
|
import { defineConfig } from '@weconjs/core';
|
|
234
206
|
|
|
235
207
|
export default defineConfig({
|
|
236
|
-
// Base configuration (shared across all modes)
|
|
237
208
|
app: { name: 'my-api', version: '1.0.0' },
|
|
238
|
-
|
|
239
|
-
// Mode-specific overrides
|
|
240
209
|
modes: {
|
|
241
210
|
development: {
|
|
242
211
|
port: 3000,
|
|
243
212
|
logging: { level: 'debug' },
|
|
244
213
|
},
|
|
245
214
|
staging: {
|
|
215
|
+
extends: 'development',
|
|
246
216
|
port: 3000,
|
|
247
217
|
logging: { level: 'info' },
|
|
248
218
|
},
|
|
249
219
|
production: {
|
|
250
220
|
port: 8080,
|
|
251
221
|
logging: { level: 'warn', enableFile: true },
|
|
222
|
+
https: {
|
|
223
|
+
enabled: true,
|
|
224
|
+
keyPath: './certs/privkey.pem',
|
|
225
|
+
certPath: './certs/fullchain.pem',
|
|
226
|
+
},
|
|
252
227
|
},
|
|
253
228
|
},
|
|
254
229
|
});
|
|
255
230
|
```
|
|
256
231
|
|
|
257
|
-
### Loading
|
|
232
|
+
### Loading & resolving configuration
|
|
258
233
|
|
|
259
234
|
```typescript
|
|
260
235
|
import { loadConfig, resolveConfig } from '@weconjs/core';
|
|
261
236
|
|
|
262
|
-
// Load and resolve for current environment
|
|
237
|
+
// Load and resolve for the current environment
|
|
263
238
|
const config = await loadConfig('./wecon.config.ts', process.env.NODE_ENV);
|
|
264
239
|
|
|
265
|
-
// Or
|
|
240
|
+
// Or resolve separately
|
|
266
241
|
const rawConfig = await loadConfig('./wecon.config.ts');
|
|
267
242
|
const resolved = resolveConfig(rawConfig, 'production');
|
|
268
243
|
```
|
|
269
244
|
|
|
270
|
-
|
|
245
|
+
### Per-module configuration
|
|
271
246
|
|
|
272
|
-
|
|
247
|
+
Modules can declare a Zod schema for their configuration. Values are validated at startup and accessible at runtime through the context:
|
|
273
248
|
|
|
274
|
-
|
|
249
|
+
```typescript
|
|
250
|
+
import { z } from 'zod';
|
|
251
|
+
import { defineModule } from '@weconjs/core';
|
|
252
|
+
|
|
253
|
+
export default defineModule({
|
|
254
|
+
name: 'mail',
|
|
255
|
+
config: {
|
|
256
|
+
schema: z.object({
|
|
257
|
+
from: z.string().email(),
|
|
258
|
+
provider: z.enum(['ses', 'sendgrid']),
|
|
259
|
+
}),
|
|
260
|
+
defaults: { provider: 'ses' },
|
|
261
|
+
},
|
|
262
|
+
onInit: async (ctx) => {
|
|
263
|
+
const mailConfig = ctx.getModuleConfig<{ from: string; provider: string }>('mail');
|
|
264
|
+
ctx.logger.info(`Mail provider: ${mailConfig.provider}`);
|
|
265
|
+
},
|
|
266
|
+
});
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Provide values in your config file:
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
export default defineConfig({
|
|
273
|
+
app: { name: 'my-api' },
|
|
274
|
+
moduleConfigs: {
|
|
275
|
+
mail: { from: 'hello@example.com' },
|
|
276
|
+
},
|
|
277
|
+
// ...
|
|
278
|
+
});
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Modules
|
|
282
|
+
|
|
283
|
+
### Defining modules
|
|
275
284
|
|
|
276
285
|
```typescript
|
|
277
|
-
import { defineModule
|
|
286
|
+
import { defineModule } from '@weconjs/core';
|
|
278
287
|
|
|
279
288
|
export default defineModule({
|
|
280
289
|
name: 'auth',
|
|
281
290
|
description: 'Authentication and authorization',
|
|
282
|
-
routes: authRoutes,
|
|
283
|
-
|
|
284
|
-
//
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
// Called when module is registered
|
|
290
|
-
ctx.logger.debug(`${module.name} module initializing`);
|
|
291
|
-
},
|
|
292
|
-
|
|
293
|
-
onBoot: async (module, ctx: WeconContext) => {
|
|
294
|
-
// Called after all modules initialized, before server starts
|
|
295
|
-
await seedDefaultRoles(ctx);
|
|
291
|
+
routes: authRoutes, // Routes instance
|
|
292
|
+
imports: ['database-seeds'], // Dependencies (loaded first)
|
|
293
|
+
exports: ['authService'], // Exported services
|
|
294
|
+
path: __dirname, // Enables per-module package.json
|
|
295
|
+
|
|
296
|
+
onInit: async (ctx) => {
|
|
297
|
+
// Called when the module is initialized
|
|
296
298
|
},
|
|
297
|
-
|
|
298
|
-
onShutdown: async (module, ctx: WeconContext) => {
|
|
299
|
+
onDestroy: async (ctx) => {
|
|
299
300
|
// Called on graceful shutdown
|
|
300
|
-
await cleanupSessions();
|
|
301
301
|
},
|
|
302
302
|
});
|
|
303
303
|
```
|
|
304
304
|
|
|
305
|
-
###
|
|
305
|
+
### Dependency resolution
|
|
306
306
|
|
|
307
|
-
|
|
307
|
+
Modules are sorted using topological sort, with circular dependency detection:
|
|
308
308
|
|
|
309
309
|
```typescript
|
|
310
|
-
import {
|
|
310
|
+
import { loadModule, resolveModuleDependencies } from '@weconjs/core';
|
|
311
311
|
|
|
312
312
|
const modules = await Promise.all([
|
|
313
313
|
loadModule('./src/modules/users'),
|
|
@@ -319,110 +319,132 @@ const modules = await Promise.all([
|
|
|
319
319
|
const sorted = resolveModuleDependencies(modules);
|
|
320
320
|
```
|
|
321
321
|
|
|
322
|
-
|
|
322
|
+
### Per-module dependencies
|
|
323
323
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
### Basic Usage
|
|
324
|
+
Each module can have its own `package.json` for isolated dependencies. The framework can auto-install missing dependencies in development:
|
|
327
325
|
|
|
328
326
|
```typescript
|
|
329
|
-
import { createWecon } from '@weconjs/core';
|
|
330
|
-
|
|
331
327
|
const app = await createWecon({
|
|
332
328
|
config,
|
|
333
329
|
modules,
|
|
334
|
-
|
|
330
|
+
moduleDeps: {
|
|
331
|
+
autoInstall: true,
|
|
332
|
+
rootDir: process.cwd(),
|
|
333
|
+
paths: {
|
|
334
|
+
auth: './src/modules/auth',
|
|
335
|
+
users: './src/modules/users',
|
|
336
|
+
},
|
|
337
|
+
},
|
|
335
338
|
});
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
## Routing & RBAC
|
|
342
|
+
|
|
343
|
+
Wecon uses a two-layer architecture for request processing:
|
|
344
|
+
|
|
345
|
+
1. **Intelligence Layer** — `RaiMatcher` resolves the request to a RAI (Route Access Identifier) and checks authorization against the user's roles
|
|
346
|
+
2. **Execution Layer** — a single compiled Express Router handles the request
|
|
347
|
+
|
|
348
|
+
### Route Access Identifiers (RAI)
|
|
349
|
+
|
|
350
|
+
Every route has a unique RAI string (e.g. `users:list`, `orders:create`). The RAI is used for authorization checks and route introspection.
|
|
336
351
|
|
|
337
|
-
|
|
352
|
+
### Defining routes
|
|
353
|
+
|
|
354
|
+
```typescript
|
|
355
|
+
import { Route, Routes, RoutesParam } from '@weconjs/core';
|
|
356
|
+
|
|
357
|
+
const routes = new Routes({
|
|
358
|
+
prefix: '/api/orders',
|
|
359
|
+
middlewares: [authMiddleware],
|
|
360
|
+
routes: [
|
|
361
|
+
new Route({
|
|
362
|
+
method: 'GET',
|
|
363
|
+
path: '/',
|
|
364
|
+
rai: 'orders:list',
|
|
365
|
+
roles: ['admin', 'user'],
|
|
366
|
+
middlewares: [orderController.list],
|
|
367
|
+
}),
|
|
368
|
+
new Route({
|
|
369
|
+
method: 'POST',
|
|
370
|
+
path: '/',
|
|
371
|
+
rai: 'orders:create',
|
|
372
|
+
roles: ['admin', 'user'],
|
|
373
|
+
middlewares: [validateOrder, orderController.create],
|
|
374
|
+
}),
|
|
375
|
+
new Route({
|
|
376
|
+
method: 'DELETE',
|
|
377
|
+
path: '/:id',
|
|
378
|
+
rai: 'orders:delete',
|
|
379
|
+
roles: ['admin'],
|
|
380
|
+
middlewares: [orderController.delete],
|
|
381
|
+
meta: { audit: true },
|
|
382
|
+
}),
|
|
383
|
+
],
|
|
384
|
+
});
|
|
338
385
|
```
|
|
339
386
|
|
|
340
|
-
|
|
387
|
+
Routes can be nested. Prefixes, middleware, and params accumulate from parent groups:
|
|
341
388
|
|
|
342
389
|
```typescript
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
// Custom Express middleware
|
|
349
|
-
middleware: [cors(), helmet(), rateLimit()],
|
|
350
|
-
|
|
351
|
-
// Database configuration
|
|
352
|
-
database: {
|
|
353
|
-
enabled: true,
|
|
354
|
-
uri: 'mongodb://localhost/myapp',
|
|
355
|
-
plugins: [timestampPlugin, auditPlugin],
|
|
356
|
-
debug: true,
|
|
357
|
-
retryAttempts: 5,
|
|
358
|
-
retryDelay: 2000,
|
|
359
|
-
},
|
|
360
|
-
|
|
361
|
-
// FieldShield integration
|
|
362
|
-
plugins: {
|
|
363
|
-
fieldShield: { strict: true },
|
|
364
|
-
},
|
|
365
|
-
|
|
366
|
-
// i18n configuration
|
|
367
|
-
i18n: {
|
|
368
|
-
enabled: true,
|
|
369
|
-
modulesDir: './src/modules',
|
|
370
|
-
},
|
|
371
|
-
|
|
372
|
-
// Logger configuration
|
|
373
|
-
logger: {
|
|
374
|
-
useWinston: true,
|
|
375
|
-
level: 'info',
|
|
376
|
-
appName: 'my-api',
|
|
377
|
-
enableFile: true,
|
|
378
|
-
logsDir: './logs',
|
|
379
|
-
},
|
|
380
|
-
|
|
381
|
-
// Lifecycle hooks
|
|
382
|
-
hooks: {
|
|
383
|
-
onBoot: async (ctx) => { /* Server starting */ },
|
|
384
|
-
onShutdown: async (ctx) => { /* Cleanup */ },
|
|
385
|
-
onModuleInit: async (module, ctx) => { /* Module loaded */ },
|
|
386
|
-
},
|
|
390
|
+
const apiRoutes = new Routes({
|
|
391
|
+
prefix: '/api',
|
|
392
|
+
middlewares: [corsMiddleware, rateLimiter],
|
|
393
|
+
routes: [authRoutes, userRoutes, orderRoutes],
|
|
387
394
|
});
|
|
388
395
|
```
|
|
389
396
|
|
|
390
|
-
###
|
|
397
|
+
### Building the Wecon instance
|
|
391
398
|
|
|
392
|
-
|
|
399
|
+
```typescript
|
|
400
|
+
import { Wecon } from '@weconjs/core';
|
|
401
|
+
|
|
402
|
+
const wecon = new Wecon()
|
|
403
|
+
.roles(['admin', 'user', 'guest'] as const)
|
|
404
|
+
.guestRole('guest')
|
|
405
|
+
.routes(apiRoutes)
|
|
406
|
+
.dev({
|
|
407
|
+
debugMode: true,
|
|
408
|
+
helpfulErrors: true, // Detailed 401/404 messages in development
|
|
409
|
+
logRoutes: true,
|
|
410
|
+
})
|
|
411
|
+
.onRoutesPrepared((routes) => {
|
|
412
|
+
console.log(`Registered ${routes.length} routes`);
|
|
413
|
+
})
|
|
414
|
+
.build();
|
|
415
|
+
|
|
416
|
+
// Mount as Express middleware
|
|
417
|
+
app.use(wecon.handler());
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
### Type-safe roles
|
|
421
|
+
|
|
422
|
+
Augment the global `Wecon.Roles` type for compile-time role checking:
|
|
393
423
|
|
|
394
424
|
```typescript
|
|
395
|
-
//
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
success: true,
|
|
401
|
-
data: users,
|
|
402
|
-
meta: { total: users.length },
|
|
403
|
-
});
|
|
425
|
+
// wecon.d.ts
|
|
426
|
+
declare global {
|
|
427
|
+
namespace Wecon {
|
|
428
|
+
type Roles = 'admin' | 'user' | 'guest';
|
|
429
|
+
}
|
|
404
430
|
}
|
|
405
|
-
|
|
406
|
-
// Error response
|
|
407
|
-
res.respond({
|
|
408
|
-
success: false,
|
|
409
|
-
message: req.t('user_not_found'),
|
|
410
|
-
errors: [{ code: 'NOT_FOUND', field: 'id' }],
|
|
411
|
-
}, 404);
|
|
431
|
+
export {};
|
|
412
432
|
```
|
|
413
433
|
|
|
414
|
-
|
|
434
|
+
### Route introspection
|
|
415
435
|
|
|
416
|
-
|
|
436
|
+
```typescript
|
|
437
|
+
const allRoutes = wecon.getRoutes();
|
|
438
|
+
const route = wecon.getRoute('users:read');
|
|
439
|
+
```
|
|
417
440
|
|
|
418
|
-
|
|
441
|
+
## Database
|
|
419
442
|
|
|
420
|
-
|
|
443
|
+
### URI builder
|
|
421
444
|
|
|
422
445
|
```typescript
|
|
423
446
|
import { buildMongoUri, buildUriFromConfig } from '@weconjs/core';
|
|
424
447
|
|
|
425
|
-
// From individual parts
|
|
426
448
|
const uri = buildMongoUri({
|
|
427
449
|
protocol: 'mongodb+srv',
|
|
428
450
|
host: 'cluster.mongodb.net',
|
|
@@ -432,11 +454,11 @@ const uri = buildMongoUri({
|
|
|
432
454
|
options: { retryWrites: 'true', w: 'majority' },
|
|
433
455
|
});
|
|
434
456
|
|
|
435
|
-
//
|
|
457
|
+
// Or build from a resolved config
|
|
436
458
|
const uri = buildUriFromConfig(config.database);
|
|
437
459
|
```
|
|
438
460
|
|
|
439
|
-
###
|
|
461
|
+
### Connection with retry logic
|
|
440
462
|
|
|
441
463
|
```typescript
|
|
442
464
|
import { createDatabaseConnection } from '@weconjs/core';
|
|
@@ -445,103 +467,282 @@ const db = await createDatabaseConnection({
|
|
|
445
467
|
uri: 'mongodb://localhost/myapp',
|
|
446
468
|
plugins: [timestampPlugin],
|
|
447
469
|
debug: process.env.NODE_ENV === 'development',
|
|
448
|
-
|
|
449
|
-
retryDelay: 2000,
|
|
470
|
+
fieldShield: { enabled: true, strict: true },
|
|
450
471
|
});
|
|
472
|
+
|
|
473
|
+
await db.connect();
|
|
451
474
|
```
|
|
452
475
|
|
|
453
|
-
|
|
476
|
+
The connection retries with configurable exponential backoff on failure.
|
|
454
477
|
|
|
455
478
|
## Logging
|
|
456
479
|
|
|
457
|
-
|
|
480
|
+
Two logging backends are supported: Winston (with daily file rotation) and a console-only fallback.
|
|
458
481
|
|
|
459
482
|
```typescript
|
|
460
483
|
import { createWinstonLogger, createConsoleLogger } from '@weconjs/core';
|
|
461
484
|
|
|
462
|
-
// Winston
|
|
485
|
+
// Winston (requires winston + winston-daily-rotate-file)
|
|
463
486
|
const logger = await createWinstonLogger({
|
|
464
487
|
level: 'info',
|
|
465
488
|
appName: 'my-api',
|
|
466
489
|
enableFile: true,
|
|
467
|
-
|
|
490
|
+
logDir: './logs',
|
|
468
491
|
});
|
|
469
492
|
|
|
470
|
-
// Console
|
|
493
|
+
// Console fallback (zero dependencies)
|
|
471
494
|
const logger = createConsoleLogger({
|
|
472
495
|
level: 'debug',
|
|
473
496
|
appName: 'my-api',
|
|
474
497
|
});
|
|
475
498
|
|
|
476
499
|
logger.info('Server started', { port: 3000 });
|
|
477
|
-
logger.error('
|
|
500
|
+
logger.error('Connection failed', { error: err.message });
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
When creating an app with `createWecon`, the framework tries Winston first and falls back to the console logger automatically.
|
|
504
|
+
|
|
505
|
+
## i18n
|
|
506
|
+
|
|
507
|
+
Translation files are auto-discovered from module directories. Each module can have a `locales/` folder with JSON files:
|
|
508
|
+
|
|
509
|
+
```
|
|
510
|
+
src/modules/users/locales/en.json
|
|
511
|
+
src/modules/users/locales/es.json
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
Enable i18n in your config:
|
|
515
|
+
|
|
516
|
+
```typescript
|
|
517
|
+
const app = await createWecon({
|
|
518
|
+
config,
|
|
519
|
+
modules,
|
|
520
|
+
i18n: { enabled: true, modulesDir: './src/modules' },
|
|
521
|
+
});
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
Use translations in handlers via `req.t()`:
|
|
525
|
+
|
|
526
|
+
```typescript
|
|
527
|
+
app.get('/greeting', (req, res) => {
|
|
528
|
+
res.json({ message: req.t('users:welcome', { name: 'Alice' }) });
|
|
529
|
+
});
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
Interpolation uses `{{key}}` syntax in translation files:
|
|
533
|
+
|
|
534
|
+
```json
|
|
535
|
+
{ "welcome": "Welcome, {{name}}!" }
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
## Socket.IO
|
|
539
|
+
|
|
540
|
+
Socket handlers and middleware are auto-discovered from module directories:
|
|
541
|
+
|
|
542
|
+
```typescript
|
|
543
|
+
import { setupSocketIO, createSocketServer } from '@weconjs/core';
|
|
544
|
+
|
|
545
|
+
// Attach to existing HTTP server
|
|
546
|
+
const io = createSocketServer(httpServer, {
|
|
547
|
+
cors: { origin: 'http://localhost:3000' },
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
// Auto-discover handlers from modules
|
|
551
|
+
await setupSocketIO(io, './src/modules');
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
Convention: place socket handlers in `src/modules/<name>/sockets/` and middleware in `src/modules/<name>/sockets/middleware/`.
|
|
555
|
+
|
|
556
|
+
## Context & Services
|
|
557
|
+
|
|
558
|
+
The `WeconContext` is passed to all module hooks, handlers, and middleware. It provides access to configuration, logging, and a service registry.
|
|
559
|
+
|
|
560
|
+
```typescript
|
|
561
|
+
// Register a service
|
|
562
|
+
ctx.registerService('mailer', new MailerService());
|
|
563
|
+
|
|
564
|
+
// Retrieve a service
|
|
565
|
+
const mailer = ctx.getService<MailerService>('mailer');
|
|
566
|
+
|
|
567
|
+
// Access module config (validated at startup)
|
|
568
|
+
const mailConfig = ctx.getModuleConfig<MailConfig>('mail');
|
|
569
|
+
|
|
570
|
+
// Update module config at runtime (re-validates against Zod schema)
|
|
571
|
+
ctx.setModuleConfig('mail', { from: 'new@example.com', provider: 'sendgrid' });
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
## Authentication
|
|
575
|
+
|
|
576
|
+
Wecon provides an `Authenticable` interface for any model used in authentication. The `WeconRequest` generic lets you type `req.user`:
|
|
577
|
+
|
|
578
|
+
```typescript
|
|
579
|
+
import type { Authenticable, WeconRequest, WeconResponse } from '@weconjs/core';
|
|
580
|
+
|
|
581
|
+
interface User extends Authenticable {
|
|
582
|
+
email: string;
|
|
583
|
+
name: { first: string; last: string };
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function getProfile(req: WeconRequest<User>, res: WeconResponse) {
|
|
587
|
+
const user = req.user!;
|
|
588
|
+
res.respond({ data: { email: user.email, roles: user.roles } });
|
|
589
|
+
}
|
|
478
590
|
```
|
|
479
591
|
|
|
480
|
-
|
|
592
|
+
### Response helpers
|
|
593
|
+
|
|
594
|
+
The `res.respond()` helper produces standardized API responses:
|
|
595
|
+
|
|
596
|
+
```typescript
|
|
597
|
+
// Success
|
|
598
|
+
res.respond({ data: users, meta: { total: users.length } });
|
|
599
|
+
// → { success: true, data: [...], errors: null, meta: { total: 10 } }
|
|
600
|
+
|
|
601
|
+
// Error
|
|
602
|
+
res.status(404).respond({
|
|
603
|
+
errors: [{ code: 'NOT_FOUND', message: 'User not found' }],
|
|
604
|
+
});
|
|
605
|
+
// → { success: false, data: null, errors: [...], meta: null }
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
## Server Factory
|
|
609
|
+
|
|
610
|
+
`createWecon` is the main entry point. It wires together all framework features and returns a `WeconApp` instance:
|
|
611
|
+
|
|
612
|
+
```typescript
|
|
613
|
+
import { createWecon } from '@weconjs/core';
|
|
614
|
+
|
|
615
|
+
const app = await createWecon({
|
|
616
|
+
config,
|
|
617
|
+
modules,
|
|
618
|
+
wecon, // Wecon routing instance (optional)
|
|
619
|
+
middleware: [cors(), helmet()], // Custom Express middleware
|
|
620
|
+
database: { enabled: true, uri: '...' },
|
|
621
|
+
plugins: { fieldShield: { strict: true } },
|
|
622
|
+
i18n: { enabled: true, modulesDir: './src/modules' },
|
|
623
|
+
logger: { useWinston: true, enableFile: true, logDir: './logs' },
|
|
624
|
+
moduleDeps: { autoInstall: true, paths: { auth: './src/modules/auth' } },
|
|
625
|
+
hooks: {
|
|
626
|
+
onBoot: async (ctx) => { /* before server starts */ },
|
|
627
|
+
onShutdown: async (ctx) => { /* cleanup */ },
|
|
628
|
+
onModuleInit: async (module, ctx) => { /* after each module init */ },
|
|
629
|
+
},
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
const server = await app.start(); // Start listening
|
|
633
|
+
await app.shutdown(); // Graceful shutdown
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
Features included automatically:
|
|
637
|
+
- `express.json()` and `express.urlencoded()` parsing
|
|
638
|
+
- `/health` endpoint
|
|
639
|
+
- HTTPS support (falls back to HTTP if certs are missing)
|
|
640
|
+
- Graceful shutdown on SIGTERM, SIGINT, SIGUSR2
|
|
641
|
+
- Global error handler with standardized responses
|
|
481
642
|
|
|
482
643
|
## API Reference
|
|
483
644
|
|
|
484
645
|
### Configuration
|
|
485
646
|
|
|
486
|
-
|
|
|
487
|
-
|
|
647
|
+
| Export | Description |
|
|
648
|
+
|--------|-------------|
|
|
488
649
|
| `defineConfig(config)` | Define application configuration with TypeScript support |
|
|
489
650
|
| `resolveConfig(config, mode)` | Resolve configuration for a specific mode |
|
|
490
651
|
| `loadConfig(path, mode?)` | Load configuration file and optionally resolve mode |
|
|
491
652
|
|
|
492
653
|
### Modules
|
|
493
654
|
|
|
494
|
-
|
|
|
495
|
-
|
|
655
|
+
| Export | Description |
|
|
656
|
+
|--------|-------------|
|
|
496
657
|
| `defineModule(definition)` | Define a module with routes and lifecycle hooks |
|
|
497
658
|
| `loadModule(path)` | Load a module from file path |
|
|
498
|
-
| `resolveModuleDependencies(modules)` | Sort modules by dependencies |
|
|
659
|
+
| `resolveModuleDependencies(modules)` | Sort modules by dependencies (topological sort) |
|
|
660
|
+
| `readModulePackageJson(path)` | Read a module's package.json |
|
|
661
|
+
| `checkModuleDeps(path, rootDir)` | Check if module dependencies are installed |
|
|
662
|
+
| `installModuleDeps(path, rootDir)` | Install missing module dependencies |
|
|
663
|
+
| `resolveAllModuleDeps(modules, rootDir, logger, autoInstall)` | Check/install deps for all modules |
|
|
664
|
+
| `detectPackageManager()` | Detect npm/yarn/pnpm |
|
|
499
665
|
|
|
500
666
|
### Server
|
|
501
667
|
|
|
502
|
-
|
|
|
503
|
-
|
|
504
|
-
| `createWecon(options)` | Create and configure
|
|
668
|
+
| Export | Description |
|
|
669
|
+
|--------|-------------|
|
|
670
|
+
| `createWecon(options)` | Create and configure the application |
|
|
671
|
+
|
|
672
|
+
### Routing
|
|
673
|
+
|
|
674
|
+
| Export | Description |
|
|
675
|
+
|--------|-------------|
|
|
676
|
+
| `Wecon` | Main routing class with fluent API and RBAC |
|
|
677
|
+
| `Route` | Single endpoint definition (method, path, RAI, roles, middlewares) |
|
|
678
|
+
| `Routes` | Hierarchical route group with prefix, middleware, and params |
|
|
679
|
+
| `RoutesParam` | Route parameter definition with validation |
|
|
680
|
+
| `RaiMatcher` | RAI resolution engine (maps request path + method to a RAI) |
|
|
681
|
+
| `ErrorCatcher` | Base class for configuration error reporting |
|
|
505
682
|
|
|
506
683
|
### Database
|
|
507
684
|
|
|
508
|
-
|
|
|
509
|
-
|
|
685
|
+
| Export | Description |
|
|
686
|
+
|--------|-------------|
|
|
510
687
|
| `createDatabaseConnection(options)` | Create MongoDB connection with retry logic |
|
|
511
688
|
| `buildMongoUri(parts)` | Build MongoDB URI from parts |
|
|
512
689
|
| `buildUriFromConfig(config)` | Build URI from database config object |
|
|
513
690
|
|
|
514
691
|
### Logging
|
|
515
692
|
|
|
516
|
-
|
|
|
517
|
-
|
|
693
|
+
| Export | Description |
|
|
694
|
+
|--------|-------------|
|
|
518
695
|
| `createWinstonLogger(options)` | Create Winston logger with file rotation |
|
|
519
|
-
| `createConsoleLogger(options)` | Create console-based logger |
|
|
696
|
+
| `createConsoleLogger(options)` | Create console-based fallback logger |
|
|
697
|
+
|
|
698
|
+
### i18n
|
|
699
|
+
|
|
700
|
+
| Export | Description |
|
|
701
|
+
|--------|-------------|
|
|
702
|
+
| `initI18n(modulesDir, defaultLocale)` | Initialize i18n and return Express middleware |
|
|
703
|
+
| `loadI18nResources(modulesDir)` | Load translation files from module directories |
|
|
704
|
+
| `createI18nMiddleware(resources, defaultLocale)` | Create the Express middleware |
|
|
520
705
|
|
|
521
706
|
### Socket.IO
|
|
522
707
|
|
|
523
|
-
|
|
|
524
|
-
|
|
525
|
-
| `
|
|
526
|
-
| `
|
|
527
|
-
| `
|
|
708
|
+
| Export | Description |
|
|
709
|
+
|--------|-------------|
|
|
710
|
+
| `createSocketServer(httpServer, options)` | Create Socket.IO server |
|
|
711
|
+
| `setupSocketIO(io, modulesDir)` | Auto-discover and register handlers |
|
|
712
|
+
| `initializeSocket(server, modulesDir, options)` | Combined setup helper |
|
|
713
|
+
| `discoverSocketHandlers(modulesDir)` | Find socket handler files in modules |
|
|
714
|
+
| `discoverSocketMiddleware(modulesDir)` | Find socket middleware files in modules |
|
|
715
|
+
|
|
716
|
+
### Context
|
|
717
|
+
|
|
718
|
+
| Export | Description |
|
|
719
|
+
|--------|-------------|
|
|
720
|
+
| `createContext(options)` | Create application context with service registry |
|
|
721
|
+
| `createLogger(options)` | Create a logger instance |
|
|
722
|
+
|
|
723
|
+
### Errors
|
|
528
724
|
|
|
529
|
-
|
|
725
|
+
| Export | Description |
|
|
726
|
+
|--------|-------------|
|
|
727
|
+
| `ConfigError` | Configuration error with code and metadata |
|
|
728
|
+
| `RequestError` | Request error with code and metadata |
|
|
530
729
|
|
|
531
730
|
## Testing
|
|
532
731
|
|
|
533
732
|
```bash
|
|
534
|
-
|
|
535
|
-
|
|
733
|
+
npm test # Run all tests
|
|
734
|
+
npm run test:watch # Watch mode
|
|
735
|
+
npm run test:coverage # With coverage report
|
|
736
|
+
npm run build # Build TypeScript to dist/
|
|
536
737
|
```
|
|
537
738
|
|
|
538
739
|
## Requirements
|
|
539
740
|
|
|
540
741
|
- Node.js >= 18.0.0
|
|
541
742
|
- TypeScript >= 5.0.0
|
|
542
|
-
- Express 5.x
|
|
543
|
-
- Mongoose 8.x
|
|
743
|
+
- Express 5.x (peer dependency)
|
|
744
|
+
- Mongoose 8.x (peer dependency)
|
|
544
745
|
|
|
545
746
|
## License
|
|
546
747
|
|
|
547
|
-
MIT
|
|
748
|
+
MIT
|