@owlmeans/server-module 0.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/LICENSE +21 -0
- package/README.md +546 -0
- package/build/.gitkeep +0 -0
- package/build/helper.d.ts +5 -0
- package/build/helper.d.ts.map +1 -0
- package/build/helper.js +25 -0
- package/build/helper.js.map +1 -0
- package/build/index.d.ts +4 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +3 -0
- package/build/index.js.map +1 -0
- package/build/module.d.ts +6 -0
- package/build/module.d.ts.map +1 -0
- package/build/module.js +44 -0
- package/build/module.js.map +1 -0
- package/build/types.d.ts +27 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +2 -0
- package/build/types.js.map +1 -0
- package/build/utils/helper.d.ts +2 -0
- package/build/utils/helper.d.ts.map +1 -0
- package/build/utils/helper.js +2 -0
- package/build/utils/helper.js.map +1 -0
- package/build/utils/index.d.ts +3 -0
- package/build/utils/index.d.ts.map +1 -0
- package/build/utils/index.js +3 -0
- package/build/utils/index.js.map +1 -0
- package/build/utils/module.d.ts +3 -0
- package/build/utils/module.d.ts.map +1 -0
- package/build/utils/module.js +3 -0
- package/build/utils/module.js.map +1 -0
- package/package.json +37 -0
- package/src/helper.ts +38 -0
- package/src/index.ts +4 -0
- package/src/module.ts +57 -0
- package/src/types.ts +31 -0
- package/src/utils/helper.ts +2 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/module.ts +3 -0
- package/tsconfig.json +15 -0
- package/tsconfig.tsbuildinfo +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 OwlMeans Common — Fullstack typescript framework
|
|
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,546 @@
|
|
|
1
|
+
# @owlmeans/server-module
|
|
2
|
+
|
|
3
|
+
Server-side module system for OwlMeans Common applications. This package provides the infrastructure for building and managing server modules that handle HTTP requests, implement business logic, and integrate with the OwlMeans routing and context systems.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The `@owlmeans/server-module` package extends the base OwlMeans module system with server-specific functionality, providing:
|
|
8
|
+
|
|
9
|
+
- **Server Module Creation**: Factory functions for creating server-side modules
|
|
10
|
+
- **Route Integration**: Deep integration with server routing system
|
|
11
|
+
- **Request Handling**: Standardized request/response processing
|
|
12
|
+
- **Error Management**: Built-in error handling and fixer services
|
|
13
|
+
- **Module Elevation**: Converting common modules to server modules
|
|
14
|
+
- **Context Integration**: Seamless integration with server context
|
|
15
|
+
- **Guard Support**: Authentication and authorization integration
|
|
16
|
+
- **Reference Handling**: Module reference system for complex workflows
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @owlmeans/server-module
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Core Concepts
|
|
25
|
+
|
|
26
|
+
### Server Module
|
|
27
|
+
|
|
28
|
+
A `ServerModule` extends the common module interface with server-specific capabilities, including route models, request handlers, and error fixers.
|
|
29
|
+
|
|
30
|
+
### Module Handler
|
|
31
|
+
|
|
32
|
+
Functions that process incoming requests and generate responses, with access to the full OwlMeans context system.
|
|
33
|
+
|
|
34
|
+
### Fixer Service
|
|
35
|
+
|
|
36
|
+
Error handling services that can intercept and process errors before they're returned to clients.
|
|
37
|
+
|
|
38
|
+
### Module Elevation
|
|
39
|
+
|
|
40
|
+
The process of converting common modules to server modules with enhanced capabilities.
|
|
41
|
+
|
|
42
|
+
## API Reference
|
|
43
|
+
|
|
44
|
+
### Types
|
|
45
|
+
|
|
46
|
+
#### `ServerModule<R>`
|
|
47
|
+
Core server module interface with request handling capabilities.
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
interface ServerModule<R> extends CommonModule {
|
|
51
|
+
route: ServerRouteModel<R> // Server-specific route model
|
|
52
|
+
fixer?: string // Error fixer service alias
|
|
53
|
+
handle: ModuleHandler // Request handler function
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
#### `ModuleOptions<R>`
|
|
58
|
+
Configuration options for module creation.
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
interface ModuleOptions<R> extends CommonModuleOptions {
|
|
62
|
+
force?: boolean // Force elevation even if already elevated
|
|
63
|
+
fixer?: string // Error fixer service alias
|
|
64
|
+
intermediate?: boolean // Mark as intermediate module
|
|
65
|
+
routeOptions?: ServerRouteOptions<R> // Route-specific options
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
#### `FixerService`
|
|
70
|
+
Service interface for error handling.
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
interface FixerService extends Service {
|
|
74
|
+
handle: <R>(reply: R, error: Error) => void
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
#### `RefedModuleHandler<R>`
|
|
79
|
+
Handler function that receives module reference.
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
interface RefedModuleHandler<R = {}> {
|
|
83
|
+
(ref: ModuleRef<R>): ModuleHandler
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Factory Functions
|
|
88
|
+
|
|
89
|
+
#### `module<R>(arg, handler?, opts?): ServerModule<R>`
|
|
90
|
+
Creates a server module from various input types.
|
|
91
|
+
|
|
92
|
+
**Parameters:**
|
|
93
|
+
- `arg`: CommonModule, ServerRouteModel, or CommonRouteModel
|
|
94
|
+
- `handler`: Optional request handler function with module reference
|
|
95
|
+
- `opts`: Optional module configuration
|
|
96
|
+
|
|
97
|
+
**Returns:** Configured ServerModule instance
|
|
98
|
+
|
|
99
|
+
**Examples:**
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
// From route model
|
|
103
|
+
const userModule = module(userRoute, (ref) => async (request, reply) => {
|
|
104
|
+
const users = await getUserList()
|
|
105
|
+
reply.resolve(users)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
// From common module
|
|
109
|
+
const elevatedModule = module(commonModule, handler, {
|
|
110
|
+
fixer: 'error-handler',
|
|
111
|
+
force: true
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
// With options
|
|
115
|
+
const apiModule = module(apiRoute, handler, {
|
|
116
|
+
intermediate: true,
|
|
117
|
+
routeOptions: { cors: true }
|
|
118
|
+
})
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
#### `elevate<R>(modules, alias, handler?, opts?): ServerModule<R>[]`
|
|
122
|
+
Elevates common modules to server modules within a module collection.
|
|
123
|
+
|
|
124
|
+
**Parameters:**
|
|
125
|
+
- `modules`: Array of modules to search within
|
|
126
|
+
- `alias`: Alias of module to elevate
|
|
127
|
+
- `handler`: Optional new handler function
|
|
128
|
+
- `opts`: Optional elevation options
|
|
129
|
+
|
|
130
|
+
**Returns:** Updated modules array with elevated module
|
|
131
|
+
|
|
132
|
+
**Example:**
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
const modules = [commonModule1, commonModule2, commonModule3]
|
|
136
|
+
|
|
137
|
+
// Elevate specific module
|
|
138
|
+
const elevated = elevate(modules, 'user-api', (ref) => async (req, reply) => {
|
|
139
|
+
// New server-specific handler
|
|
140
|
+
const result = await processUserRequest(req)
|
|
141
|
+
reply.resolve(result)
|
|
142
|
+
}, { fixer: 'user-error-handler' })
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Module Creation Patterns
|
|
146
|
+
|
|
147
|
+
#### Basic Request Handler
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import { module } from '@owlmeans/server-module'
|
|
151
|
+
|
|
152
|
+
const userModule = module(userRoute, (ref) => async (request, reply) => {
|
|
153
|
+
try {
|
|
154
|
+
const { params, query, body } = request
|
|
155
|
+
|
|
156
|
+
// Business logic
|
|
157
|
+
const users = await userService.list(query)
|
|
158
|
+
|
|
159
|
+
// Successful response
|
|
160
|
+
reply.resolve(users, ModuleOutcome.Ok)
|
|
161
|
+
} catch (error) {
|
|
162
|
+
// Error handling
|
|
163
|
+
reply.reject(error)
|
|
164
|
+
}
|
|
165
|
+
})
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
#### Module with Context Access
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
const contextAwareModule = module(route, (ref) => async (request, reply) => {
|
|
172
|
+
const context = ref.ref?.ctx
|
|
173
|
+
|
|
174
|
+
// Access services from context
|
|
175
|
+
const dbService = context?.service('database')
|
|
176
|
+
const authService = context?.service('auth')
|
|
177
|
+
|
|
178
|
+
// Process request with context
|
|
179
|
+
const user = authService.user()
|
|
180
|
+
const data = await dbService.get(request.params.id)
|
|
181
|
+
|
|
182
|
+
reply.resolve(data)
|
|
183
|
+
})
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
#### Module with Error Fixer
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
const resilientModule = module(route, handler, {
|
|
190
|
+
fixer: 'error-fixer'
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
// Error fixer service
|
|
194
|
+
const errorFixer: FixerService = {
|
|
195
|
+
alias: 'error-fixer',
|
|
196
|
+
initialized: true,
|
|
197
|
+
|
|
198
|
+
handle: (reply, error) => {
|
|
199
|
+
if (error instanceof ValidationError) {
|
|
200
|
+
reply.reject(new BadRequestError(error.message))
|
|
201
|
+
} else if (error instanceof DatabaseError) {
|
|
202
|
+
reply.reject(new InternalServerError('Service temporarily unavailable'))
|
|
203
|
+
} else {
|
|
204
|
+
reply.reject(error)
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Usage Examples
|
|
211
|
+
|
|
212
|
+
### Basic CRUD Module
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
import { module } from '@owlmeans/server-module'
|
|
216
|
+
import { route } from '@owlmeans/server-route'
|
|
217
|
+
|
|
218
|
+
// Define route
|
|
219
|
+
const userRoute = route({
|
|
220
|
+
path: '/users/:id?',
|
|
221
|
+
method: RouteMethod.GET,
|
|
222
|
+
alias: 'user-crud'
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
// Create module
|
|
226
|
+
const userModule = module(userRoute, (ref) => async (request, reply) => {
|
|
227
|
+
const { params, query } = request
|
|
228
|
+
|
|
229
|
+
if (params.id) {
|
|
230
|
+
// Get single user
|
|
231
|
+
const user = await userService.get(params.id)
|
|
232
|
+
reply.resolve(user)
|
|
233
|
+
} else {
|
|
234
|
+
// List users
|
|
235
|
+
const users = await userService.list(query)
|
|
236
|
+
reply.resolve(users)
|
|
237
|
+
}
|
|
238
|
+
})
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Authenticated Module
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
const protectedModule = module(protectedRoute, (ref) => async (request, reply) => {
|
|
245
|
+
// Access authentication from context
|
|
246
|
+
const authService = ref.ref?.ctx?.service('auth')
|
|
247
|
+
const currentUser = authService?.user()
|
|
248
|
+
|
|
249
|
+
if (!currentUser) {
|
|
250
|
+
reply.reject(new UnauthorizedError('Authentication required'))
|
|
251
|
+
return
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Process authenticated request
|
|
255
|
+
const data = await processForUser(currentUser, request)
|
|
256
|
+
reply.resolve(data)
|
|
257
|
+
}, {
|
|
258
|
+
guards: ['auth-guard']
|
|
259
|
+
})
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### File Upload Module
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
const uploadModule = module(uploadRoute, (ref) => async (request, reply) => {
|
|
266
|
+
const { body, headers } = request
|
|
267
|
+
|
|
268
|
+
// Handle file upload
|
|
269
|
+
if (headers['content-type']?.includes('multipart/form-data')) {
|
|
270
|
+
const files = await parseMultipartFiles(body)
|
|
271
|
+
const saved = await fileService.saveFiles(files)
|
|
272
|
+
|
|
273
|
+
reply.resolve(saved, ModuleOutcome.Created)
|
|
274
|
+
} else {
|
|
275
|
+
reply.reject(new BadRequestError('Multipart data required'))
|
|
276
|
+
}
|
|
277
|
+
})
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Module with Database Transaction
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
const transactionalModule = module(route, (ref) => async (request, reply) => {
|
|
284
|
+
const dbService = ref.ref?.ctx?.service('database')
|
|
285
|
+
const transaction = await dbService.startTransaction()
|
|
286
|
+
|
|
287
|
+
try {
|
|
288
|
+
const user = await userService.create(request.body, { transaction })
|
|
289
|
+
const profile = await profileService.create(user.id, { transaction })
|
|
290
|
+
|
|
291
|
+
await transaction.commit()
|
|
292
|
+
reply.resolve({ user, profile }, ModuleOutcome.Created)
|
|
293
|
+
} catch (error) {
|
|
294
|
+
await transaction.rollback()
|
|
295
|
+
reply.reject(error)
|
|
296
|
+
}
|
|
297
|
+
})
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### Module Composition
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
// Base module
|
|
304
|
+
const baseModule = module(baseRoute, (ref) => async (request, reply) => {
|
|
305
|
+
// Base functionality
|
|
306
|
+
const result = await processBase(request)
|
|
307
|
+
reply.resolve(result)
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
// Elevated module with additional features
|
|
311
|
+
const elevatedModules = elevate([baseModule], 'base-route', (ref) => async (request, reply) => {
|
|
312
|
+
// Enhanced functionality
|
|
313
|
+
const enhanced = await enhanceProcessing(request)
|
|
314
|
+
const result = await processBase(enhanced)
|
|
315
|
+
reply.resolve(result)
|
|
316
|
+
}, {
|
|
317
|
+
fixer: 'enhanced-error-handler'
|
|
318
|
+
})
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Module Factory Pattern
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
const createCrudModule = <T>(
|
|
325
|
+
resource: string,
|
|
326
|
+
service: CrudService<T>
|
|
327
|
+
) => {
|
|
328
|
+
const crudRoute = route({
|
|
329
|
+
path: `/${resource}/:id?`,
|
|
330
|
+
method: RouteMethod.GET,
|
|
331
|
+
alias: `${resource}-crud`
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
return module(crudRoute, (ref) => async (request, reply) => {
|
|
335
|
+
const { params, query, body } = request
|
|
336
|
+
|
|
337
|
+
switch (request.method) {
|
|
338
|
+
case 'GET':
|
|
339
|
+
if (params.id) {
|
|
340
|
+
const item = await service.get(params.id)
|
|
341
|
+
reply.resolve(item)
|
|
342
|
+
} else {
|
|
343
|
+
const items = await service.list(query)
|
|
344
|
+
reply.resolve(items)
|
|
345
|
+
}
|
|
346
|
+
break
|
|
347
|
+
|
|
348
|
+
case 'POST':
|
|
349
|
+
const created = await service.create(body)
|
|
350
|
+
reply.resolve(created, ModuleOutcome.Created)
|
|
351
|
+
break
|
|
352
|
+
|
|
353
|
+
case 'PUT':
|
|
354
|
+
const updated = await service.update(params.id, body)
|
|
355
|
+
reply.resolve(updated)
|
|
356
|
+
break
|
|
357
|
+
|
|
358
|
+
case 'DELETE':
|
|
359
|
+
await service.delete(params.id)
|
|
360
|
+
reply.resolve(null, ModuleOutcome.Finished)
|
|
361
|
+
break
|
|
362
|
+
|
|
363
|
+
default:
|
|
364
|
+
reply.reject(new MethodNotAllowedError())
|
|
365
|
+
}
|
|
366
|
+
})
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Usage
|
|
370
|
+
const userModule = createCrudModule('users', userService)
|
|
371
|
+
const productModule = createCrudModule('products', productService)
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Middleware Integration
|
|
375
|
+
|
|
376
|
+
```typescript
|
|
377
|
+
const middlewareModule = module(route, (ref) => async (request, reply) => {
|
|
378
|
+
// Pre-processing middleware
|
|
379
|
+
await validateRequest(request)
|
|
380
|
+
await logRequest(request)
|
|
381
|
+
|
|
382
|
+
// Main processing
|
|
383
|
+
const result = await processRequest(request)
|
|
384
|
+
|
|
385
|
+
// Post-processing middleware
|
|
386
|
+
await logResponse(result)
|
|
387
|
+
await cacheResponse(request, result)
|
|
388
|
+
|
|
389
|
+
reply.resolve(result)
|
|
390
|
+
})
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
### Error Handling Patterns
|
|
394
|
+
|
|
395
|
+
```typescript
|
|
396
|
+
// Global error fixer
|
|
397
|
+
const globalFixer: FixerService = {
|
|
398
|
+
alias: 'global-error-fixer',
|
|
399
|
+
initialized: true,
|
|
400
|
+
|
|
401
|
+
handle: (reply, error) => {
|
|
402
|
+
// Log error
|
|
403
|
+
console.error('Module error:', error)
|
|
404
|
+
|
|
405
|
+
// Transform known errors
|
|
406
|
+
if (error.name === 'ValidationError') {
|
|
407
|
+
reply.reject(new BadRequestError(error.message))
|
|
408
|
+
} else if (error.name === 'NotFoundError') {
|
|
409
|
+
reply.reject(new NotFoundError(error.message))
|
|
410
|
+
} else {
|
|
411
|
+
// Generic error for unknown issues
|
|
412
|
+
reply.reject(new InternalServerError('An unexpected error occurred'))
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Module with error handling
|
|
418
|
+
const robustModule = module(route, (ref) => async (request, reply) => {
|
|
419
|
+
try {
|
|
420
|
+
const result = await riskyOperation(request)
|
|
421
|
+
reply.resolve(result)
|
|
422
|
+
} catch (error) {
|
|
423
|
+
// Let fixer handle the error
|
|
424
|
+
throw error
|
|
425
|
+
}
|
|
426
|
+
}, {
|
|
427
|
+
fixer: 'global-error-fixer'
|
|
428
|
+
})
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
## Advanced Features
|
|
432
|
+
|
|
433
|
+
### Module Chaining
|
|
434
|
+
|
|
435
|
+
```typescript
|
|
436
|
+
const chainModules = (modules: ServerModule<any>[]) => {
|
|
437
|
+
return modules.reduce((chain, module, index) => {
|
|
438
|
+
const nextModule = modules[index + 1]
|
|
439
|
+
|
|
440
|
+
if (nextModule) {
|
|
441
|
+
// Chain modules together
|
|
442
|
+
const originalHandler = module.handle
|
|
443
|
+
module.handle = async (request, reply) => {
|
|
444
|
+
const result = await originalHandler(request, reply)
|
|
445
|
+
|
|
446
|
+
// Pass result to next module
|
|
447
|
+
if (result) {
|
|
448
|
+
request.body = result
|
|
449
|
+
return nextModule.handle(request, reply)
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return result
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
return module
|
|
457
|
+
})
|
|
458
|
+
}
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
### Dynamic Module Loading
|
|
462
|
+
|
|
463
|
+
```typescript
|
|
464
|
+
const loadModuleFromConfig = async (config: ModuleConfig) => {
|
|
465
|
+
const route = await loadRoute(config.routeId)
|
|
466
|
+
const handler = await loadHandler(config.handlerId)
|
|
467
|
+
|
|
468
|
+
return module(route, handler, {
|
|
469
|
+
fixer: config.errorHandler,
|
|
470
|
+
guards: config.guards
|
|
471
|
+
})
|
|
472
|
+
}
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
### Module Testing
|
|
476
|
+
|
|
477
|
+
```typescript
|
|
478
|
+
import { createTestContext } from '@owlmeans/server-context'
|
|
479
|
+
|
|
480
|
+
describe('User Module', () => {
|
|
481
|
+
let context: ServerContext
|
|
482
|
+
let userModule: ServerModule<any>
|
|
483
|
+
|
|
484
|
+
beforeEach(() => {
|
|
485
|
+
context = createTestContext(testConfig)
|
|
486
|
+
userModule = module(userRoute, userHandler)
|
|
487
|
+
userModule.ctx = context
|
|
488
|
+
})
|
|
489
|
+
|
|
490
|
+
it('should handle user creation', async () => {
|
|
491
|
+
const request = {
|
|
492
|
+
method: 'POST',
|
|
493
|
+
body: { name: 'John Doe', email: 'john@example.com' },
|
|
494
|
+
params: {},
|
|
495
|
+
query: {},
|
|
496
|
+
headers: {}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const reply = createMockReply()
|
|
500
|
+
await userModule.handle(request, reply)
|
|
501
|
+
|
|
502
|
+
expect(reply.resolved).toBe(true)
|
|
503
|
+
expect(reply.value).toHaveProperty('id')
|
|
504
|
+
})
|
|
505
|
+
})
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
## Integration with OwlMeans Ecosystem
|
|
509
|
+
|
|
510
|
+
The `@owlmeans/server-module` package integrates with:
|
|
511
|
+
|
|
512
|
+
- **@owlmeans/module**: Base module system and interfaces
|
|
513
|
+
- **@owlmeans/server-route**: Server routing and path handling
|
|
514
|
+
- **@owlmeans/context**: Dependency injection and service access
|
|
515
|
+
- **@owlmeans/server-context**: Server-specific context features
|
|
516
|
+
- **@owlmeans/auth-common**: Authentication and authorization
|
|
517
|
+
- **@owlmeans/resource**: Data persistence and management
|
|
518
|
+
- **@owlmeans/error**: Error handling and transformation
|
|
519
|
+
|
|
520
|
+
## Best Practices
|
|
521
|
+
|
|
522
|
+
### Module Design
|
|
523
|
+
- Keep modules focused on single responsibilities
|
|
524
|
+
- Use descriptive aliases for easy identification
|
|
525
|
+
- Implement proper error handling in all handlers
|
|
526
|
+
- Validate input data before processing
|
|
527
|
+
|
|
528
|
+
### Performance
|
|
529
|
+
- Cache frequently accessed data
|
|
530
|
+
- Use streaming for large response payloads
|
|
531
|
+
- Implement proper pagination for list operations
|
|
532
|
+
- Monitor module execution times
|
|
533
|
+
|
|
534
|
+
### Security
|
|
535
|
+
- Always validate and sanitize input data
|
|
536
|
+
- Use appropriate authentication guards
|
|
537
|
+
- Implement proper authorization checks
|
|
538
|
+
- Log security-relevant events
|
|
539
|
+
|
|
540
|
+
### Testing
|
|
541
|
+
- Write unit tests for module handlers
|
|
542
|
+
- Use test contexts for isolated testing
|
|
543
|
+
- Mock external dependencies appropriately
|
|
544
|
+
- Test error scenarios thoroughly
|
|
545
|
+
|
|
546
|
+
Fixes #32.
|
package/build/.gitkeep
ADDED
|
File without changes
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ServerModule, ModuleOptions, RefedModuleHandler } from './types.js';
|
|
2
|
+
import type { CommonModule } from '@owlmeans/module';
|
|
3
|
+
export declare const elevate: <R>(modules: (CommonModule | ServerModule<R>)[], alias: string, handler?: RefedModuleHandler<R> | boolean | ModuleOptions<R>, opts?: boolean | ModuleOptions<R>) => ServerModule<R>[];
|
|
4
|
+
export declare const guard: <R>(guard: string, opts?: ModuleOptions<R>) => ModuleOptions<R>;
|
|
5
|
+
//# sourceMappingURL=helper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helper.d.ts","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAIjF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAEpD,eAAO,MAAM,OAAO,GAAI,CAAC,WACd,CAAC,YAAY,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,EAAE,SACpC,MAAM,YACH,kBAAkB,CAAC,CAAC,CAAC,GAAG,OAAO,GAAG,aAAa,CAAC,CAAC,CAAC,SACrD,OAAO,GAAG,aAAa,CAAC,CAAC,CAAC,KAChC,YAAY,CAAC,CAAC,CAAC,EAuBjB,CAAA;AAED,eAAO,MAAM,KAAK,GAAI,CAAC,SAAS,MAAM,SAAS,aAAa,CAAC,CAAC,CAAC,KAAG,aAAa,CAAC,CAAC,CACzC,CAAA"}
|
package/build/helper.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { module } from './module.js';
|
|
2
|
+
import { isServerRouteModel } from '@owlmeans/server-route';
|
|
3
|
+
import { createBasicGuard } from './utils/helper.js';
|
|
4
|
+
export const elevate = (modules, alias, handler, opts) => {
|
|
5
|
+
const idx = modules.findIndex(({ route }) => route.route.alias === alias);
|
|
6
|
+
if (idx === -1) {
|
|
7
|
+
throw new SyntaxError(`Module with alias ${alias} not present`);
|
|
8
|
+
}
|
|
9
|
+
if (typeof handler === 'boolean') {
|
|
10
|
+
opts = handler;
|
|
11
|
+
handler = undefined;
|
|
12
|
+
}
|
|
13
|
+
if (typeof handler === 'object' && typeof handler !== 'function') {
|
|
14
|
+
opts = handler;
|
|
15
|
+
handler = undefined;
|
|
16
|
+
}
|
|
17
|
+
const force = typeof opts === 'object' && opts.force;
|
|
18
|
+
if (isServerRouteModel(modules[idx].route) && !force) {
|
|
19
|
+
throw new SyntaxError(`Module with alias ${alias} is elready elevated`);
|
|
20
|
+
}
|
|
21
|
+
modules[idx] = module(modules[idx], handler, typeof opts === 'boolean' ? { intermediate: opts } : opts);
|
|
22
|
+
return modules;
|
|
23
|
+
};
|
|
24
|
+
export const guard = (guard, opts) => ({ ...createBasicGuard(guard, opts) });
|
|
25
|
+
//# sourceMappingURL=helper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helper.js","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AAGpD,MAAM,CAAC,MAAM,OAAO,GAAG,CACrB,OAA2C,EAC3C,KAAa,EACb,OAA4D,EAC5D,IAAiC,EACd,EAAE;IACrB,MAAM,GAAG,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,KAAK,KAAK,CAAC,CAAA;IACzE,IAAI,GAAG,KAAK,CAAC,CAAC,EAAE,CAAC;QACf,MAAM,IAAI,WAAW,CAAC,qBAAqB,KAAK,cAAc,CAAC,CAAA;IACjE,CAAC;IACD,IAAI,OAAO,OAAO,KAAK,SAAS,EAAE,CAAC;QACjC,IAAI,GAAG,OAAO,CAAA;QACd,OAAO,GAAG,SAAS,CAAA;IACrB,CAAC;IACD,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,OAAO,KAAK,UAAU,EAAE,CAAC;QACjE,IAAI,GAAG,OAAO,CAAA;QACd,OAAO,GAAG,SAAS,CAAA;IACrB,CAAC;IACD,MAAM,KAAK,GAAG,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,KAAK,CAAA;IACpD,IAAI,kBAAkB,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;QACrD,MAAM,IAAI,WAAW,CAAC,qBAAqB,KAAK,sBAAsB,CAAC,CAAA;IACzE,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,GAAG,MAAM,CACnB,OAAO,CAAC,GAAG,CAAC,EAAE,OAAO,EAAE,OAAO,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CACjF,CAAA;IAED,OAAO,OAA4B,CAAA;AACrC,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,KAAK,GAAG,CAAI,KAAa,EAAE,IAAuB,EAAoB,EAAE,CACnF,CAAC,EAAE,GAAG,gBAAgB,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC,CAAA"}
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,mBAAmB,YAAY,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA"}
|
package/build/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ServerRouteModel } from '@owlmeans/server-route';
|
|
2
|
+
import type { ServerModule, ModuleOptions, RefedModuleHandler } from './types.js';
|
|
3
|
+
import type { CommonModule } from '@owlmeans/module';
|
|
4
|
+
import type { CommonRouteModel } from '@owlmeans/route';
|
|
5
|
+
export declare const module: <R>(arg: CommonModule | ServerRouteModel<R> | CommonRouteModel, handler?: RefedModuleHandler<R>, opts?: ModuleOptions<R>) => ServerModule<R>;
|
|
6
|
+
//# sourceMappingURL=module.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAa,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAG5F,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AACpD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAIvD,eAAO,MAAM,MAAM,GAAI,CAAC,OACjB,YAAY,GAAG,gBAAgB,CAAC,CAAC,CAAC,GAAG,gBAAgB,YAAY,kBAAkB,CAAC,CAAC,CAAC,SAAS,aAAa,CAAC,CAAC,CAAC,KACnH,YAAY,CAAC,CAAC,CA6ChB,CAAA"}
|
package/build/module.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { isModule, makeCommonModule } from './utils/module.js';
|
|
2
|
+
import { isServerRouteModel, route } from '@owlmeans/server-route';
|
|
3
|
+
import { prependBase } from '@owlmeans/route/utils';
|
|
4
|
+
export const module = (arg, handler, opts) => {
|
|
5
|
+
const moduleHandle = { ref: undefined };
|
|
6
|
+
let _module;
|
|
7
|
+
if (isModule(arg)) {
|
|
8
|
+
const routeModel = route(arg.route, opts?.intermediate ?? false, opts?.routeOptions);
|
|
9
|
+
_module = arg;
|
|
10
|
+
// _module = makeCommonModule(routeModel, {
|
|
11
|
+
// ...opts,
|
|
12
|
+
// guards: [...(arg.guards ?? []), ...(opts?.guards ?? [])],
|
|
13
|
+
// gate: opts?.gate ?? arg.gate,
|
|
14
|
+
// gateParams: opts?.gateParams ?? arg.gateParams,
|
|
15
|
+
// }) as ServerModule<R>
|
|
16
|
+
_module.route = routeModel;
|
|
17
|
+
_module.filter = opts?.filter ?? arg.filter;
|
|
18
|
+
_module.guards = [...(arg.guards ?? []), ...(opts?.guards ?? [])];
|
|
19
|
+
_module.gate = opts?.gate ?? arg.gate;
|
|
20
|
+
_module.gateParams = opts?.gateParams ?? arg.gateParams;
|
|
21
|
+
}
|
|
22
|
+
else if (isServerRouteModel(arg)) {
|
|
23
|
+
_module = makeCommonModule(arg, { ...opts });
|
|
24
|
+
_module.route = arg;
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
const _route = route(arg, opts?.intermediate ?? false, opts?.routeOptions);
|
|
28
|
+
_module = makeCommonModule(_route, { ...opts });
|
|
29
|
+
_module.route = _route;
|
|
30
|
+
}
|
|
31
|
+
_module.fixer = opts?.fixer;
|
|
32
|
+
if (handler != null) {
|
|
33
|
+
_module.handle = handler(moduleHandle);
|
|
34
|
+
}
|
|
35
|
+
_module.reinitializeContext = (context) => {
|
|
36
|
+
const newModule = module(arg, handler, opts);
|
|
37
|
+
newModule.ctx = context;
|
|
38
|
+
return newModule;
|
|
39
|
+
};
|
|
40
|
+
_module.getPath = () => prependBase(_module.route.route);
|
|
41
|
+
moduleHandle.ref = _module;
|
|
42
|
+
return _module;
|
|
43
|
+
};
|
|
44
|
+
//# sourceMappingURL=module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module.js","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AAC9D,OAAO,EAAE,kBAAkB,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAA;AAIlE,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAEnD,MAAM,CAAC,MAAM,MAAM,GAAG,CACpB,GAA0D,EAAE,OAA+B,EAAE,IAAuB,EACnG,EAAE;IACnB,MAAM,YAAY,GAAiB,EAAE,GAAG,EAAE,SAAS,EAAE,CAAA;IAErD,IAAI,OAAwB,CAAA;IAE5B,IAAI,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAClB,MAAM,UAAU,GAAG,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,YAAY,IAAI,KAAK,EAAE,IAAI,EAAE,YAAY,CAAC,CAAA;QACpF,OAAO,GAAG,GAAsB,CAAA;QAChC,2CAA2C;QAC3C,aAAa;QACb,8DAA8D;QAC9D,kCAAkC;QAClC,oDAAoD;QACpD,wBAAwB;QACxB,OAAO,CAAC,KAAK,GAAG,UAAU,CAAA;QAC1B,OAAO,CAAC,MAAM,GAAG,IAAI,EAAE,MAAM,IAAI,GAAG,CAAC,MAAM,CAAA;QAC3C,OAAO,CAAC,MAAM,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,IAAI,EAAE,MAAM,IAAI,EAAE,CAAC,CAAC,CAAA;QACjE,OAAO,CAAC,IAAI,GAAG,IAAI,EAAE,IAAI,IAAI,GAAG,CAAC,IAAI,CAAA;QACrC,OAAO,CAAC,UAAU,GAAG,IAAI,EAAE,UAAU,IAAI,GAAG,CAAC,UAAU,CAAA;IACzD,CAAC;SAAM,IAAI,kBAAkB,CAAC,GAAG,CAAC,EAAE,CAAC;QACnC,OAAO,GAAG,gBAAgB,CAAC,GAAG,EAAE,EAAE,GAAG,IAAI,EAAE,CAAoB,CAAA;QAC/D,OAAO,CAAC,KAAK,GAAG,GAAG,CAAA;IACrB,CAAC;SAAM,CAAC;QACN,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE,YAAY,IAAI,KAAK,EAAE,IAAI,EAAE,YAAY,CAAC,CAAA;QAC1E,OAAO,GAAG,gBAAgB,CAAC,MAAM,EAAE,EAAE,GAAG,IAAI,EAAE,CAAoB,CAAA;QAClE,OAAO,CAAC,KAAK,GAAG,MAAM,CAAA;IACxB,CAAC;IAED,OAAO,CAAC,KAAK,GAAG,IAAI,EAAE,KAAK,CAAA;IAC3B,IAAI,OAAO,IAAI,IAAI,EAAE,CAAC;QACpB,OAAO,CAAC,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;IACxC,CAAC;IAED,OAAO,CAAC,mBAAmB,GAAG,CAAI,OAA0B,EAAE,EAAE;QAC9D,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,CAAC,CAAA;QAC5C,SAAS,CAAC,GAAG,GAAG,OAAO,CAAA;QAEvB,OAAO,SAAc,CAAA;IACvB,CAAC,CAAA;IAED,OAAO,CAAC,OAAO,GAAG,GAAG,EAAE,CAAC,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;IAExD,YAAY,CAAC,GAAG,GAAG,OAAO,CAAA;IAE1B,OAAO,OAAO,CAAA;AAChB,CAAC,CAAA"}
|
package/build/types.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ServerRouteModel, ServerRouteOptions } from '@owlmeans/server-route';
|
|
2
|
+
import type { CommonModule, ModuleHandler, CommonModuleOptions } from '@owlmeans/module';
|
|
3
|
+
import type { Service } from '@owlmeans/context';
|
|
4
|
+
export interface ServerModule<R> extends CommonModule {
|
|
5
|
+
route: ServerRouteModel<R>;
|
|
6
|
+
fixer?: string;
|
|
7
|
+
handle: ModuleHandler;
|
|
8
|
+
}
|
|
9
|
+
export interface ModuleOptions<R> extends CommonModuleOptions {
|
|
10
|
+
/**
|
|
11
|
+
* Force module to be elevated even if it is already elevated
|
|
12
|
+
*/
|
|
13
|
+
force?: boolean;
|
|
14
|
+
fixer?: string;
|
|
15
|
+
intermediate?: boolean;
|
|
16
|
+
routeOptions?: ServerRouteOptions<R>;
|
|
17
|
+
}
|
|
18
|
+
export interface FixerService extends Service {
|
|
19
|
+
handle: <R>(reply: R, error: Error) => void;
|
|
20
|
+
}
|
|
21
|
+
export interface ModuleRef<R> {
|
|
22
|
+
ref?: ServerModule<R>;
|
|
23
|
+
}
|
|
24
|
+
export interface RefedModuleHandler<R = {}> {
|
|
25
|
+
(ref: ModuleRef<R>): ModuleHandler;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAClF,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AACxF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAEhD,MAAM,WAAW,YAAY,CAAC,CAAC,CAAE,SAAQ,YAAY;IACnD,KAAK,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAA;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,aAAa,CAAA;CACtB;AAED,MAAM,WAAW,aAAa,CAAC,CAAC,CAAE,SAAQ,mBAAmB;IAC3D;;OAEG;IACH,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,YAAY,CAAC,EAAE,kBAAkB,CAAC,CAAC,CAAC,CAAA;CACrC;AAED,MAAM,WAAW,YAAa,SAAQ,OAAO;IAC3C,MAAM,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;CAC5C;AAED,MAAM,WAAW,SAAS,CAAC,CAAC;IAC1B,GAAG,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC,CAAA;CACtB;AAED,MAAM,WAAW,kBAAkB,CAAC,CAAC,GAAG,EAAE;IACxC,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,aAAa,CAAA;CACnC"}
|
package/build/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helper.d.ts","sourceRoot":"","sources":["../../src/utils/helper.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,IAAI,gBAAgB,EAAE,MAAM,kBAAkB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helper.js","sourceRoot":"","sources":["../../src/utils/helper.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,IAAI,gBAAgB,EAAE,MAAM,kBAAkB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AACA,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AACA,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../src/utils/module.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,IAAI,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAC7D,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module.js","sourceRoot":"","sources":["../../src/utils/module.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,IAAI,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAC7D,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAA"}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@owlmeans/server-module",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"build": "tsc -b",
|
|
7
|
+
"dev": "sleep 300 && nodemon -e ts,tsx,json --watch src --exec \"tsc -p ./tsconfig.json\"",
|
|
8
|
+
"watch": "tsc -b -w --preserveWatchOutput --pretty"
|
|
9
|
+
},
|
|
10
|
+
"main": "build/index.js",
|
|
11
|
+
"module": "build/index.js",
|
|
12
|
+
"types": "build/index.d.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"import": "./build/index.js",
|
|
16
|
+
"require": "./build/index.js",
|
|
17
|
+
"default": "./build/index.js",
|
|
18
|
+
"module": "./build/index.js",
|
|
19
|
+
"types": "./build/index.d.ts"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@owlmeans/context": "^0.1.0",
|
|
24
|
+
"@owlmeans/module": "^0.1.0",
|
|
25
|
+
"@owlmeans/route": "^0.1.0",
|
|
26
|
+
"@owlmeans/server-route": "^0.1.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^22.7.8",
|
|
30
|
+
"nodemon": "^3.1.7",
|
|
31
|
+
"typescript": "^5.6.3"
|
|
32
|
+
},
|
|
33
|
+
"private": false,
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
}
|
|
37
|
+
}
|
package/src/helper.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ServerModule, ModuleOptions, RefedModuleHandler } from './types.js'
|
|
2
|
+
import { module } from './module.js'
|
|
3
|
+
import { isServerRouteModel } from '@owlmeans/server-route'
|
|
4
|
+
import { createBasicGuard } from './utils/helper.js'
|
|
5
|
+
import type { CommonModule } from '@owlmeans/module'
|
|
6
|
+
|
|
7
|
+
export const elevate = <R>(
|
|
8
|
+
modules: (CommonModule | ServerModule<R>)[],
|
|
9
|
+
alias: string,
|
|
10
|
+
handler?: RefedModuleHandler<R> | boolean | ModuleOptions<R>,
|
|
11
|
+
opts?: boolean | ModuleOptions<R>
|
|
12
|
+
): ServerModule<R>[] => {
|
|
13
|
+
const idx = modules.findIndex(({ route }) => route.route.alias === alias)
|
|
14
|
+
if (idx === -1) {
|
|
15
|
+
throw new SyntaxError(`Module with alias ${alias} not present`)
|
|
16
|
+
}
|
|
17
|
+
if (typeof handler === 'boolean') {
|
|
18
|
+
opts = handler
|
|
19
|
+
handler = undefined
|
|
20
|
+
}
|
|
21
|
+
if (typeof handler === 'object' && typeof handler !== 'function') {
|
|
22
|
+
opts = handler
|
|
23
|
+
handler = undefined
|
|
24
|
+
}
|
|
25
|
+
const force = typeof opts === 'object' && opts.force
|
|
26
|
+
if (isServerRouteModel(modules[idx].route) && !force) {
|
|
27
|
+
throw new SyntaxError(`Module with alias ${alias} is elready elevated`)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
modules[idx] = module(
|
|
31
|
+
modules[idx], handler, typeof opts === 'boolean' ? { intermediate: opts } : opts
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
return modules as ServerModule<R>[]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const guard = <R>(guard: string, opts?: ModuleOptions<R>): ModuleOptions<R> =>
|
|
38
|
+
({ ...createBasicGuard(guard, opts) })
|
package/src/index.ts
ADDED
package/src/module.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { ServerRouteModel } from '@owlmeans/server-route'
|
|
2
|
+
import type { ServerModule, ModuleOptions, ModuleRef, RefedModuleHandler } from './types.js'
|
|
3
|
+
import { isModule, makeCommonModule } from './utils/module.js'
|
|
4
|
+
import { isServerRouteModel, route } from '@owlmeans/server-route'
|
|
5
|
+
import type { CommonModule } from '@owlmeans/module'
|
|
6
|
+
import type { CommonRouteModel } from '@owlmeans/route'
|
|
7
|
+
import type { BasicContext } from '@owlmeans/context'
|
|
8
|
+
import { prependBase } from '@owlmeans/route/utils'
|
|
9
|
+
|
|
10
|
+
export const module = <R>(
|
|
11
|
+
arg: CommonModule | ServerRouteModel<R> | CommonRouteModel, handler?: RefedModuleHandler<R>, opts?: ModuleOptions<R>
|
|
12
|
+
): ServerModule<R> => {
|
|
13
|
+
const moduleHandle: ModuleRef<R> = { ref: undefined }
|
|
14
|
+
|
|
15
|
+
let _module: ServerModule<R>
|
|
16
|
+
|
|
17
|
+
if (isModule(arg)) {
|
|
18
|
+
const routeModel = route(arg.route, opts?.intermediate ?? false, opts?.routeOptions)
|
|
19
|
+
_module = arg as ServerModule<R>
|
|
20
|
+
// _module = makeCommonModule(routeModel, {
|
|
21
|
+
// ...opts,
|
|
22
|
+
// guards: [...(arg.guards ?? []), ...(opts?.guards ?? [])],
|
|
23
|
+
// gate: opts?.gate ?? arg.gate,
|
|
24
|
+
// gateParams: opts?.gateParams ?? arg.gateParams,
|
|
25
|
+
// }) as ServerModule<R>
|
|
26
|
+
_module.route = routeModel
|
|
27
|
+
_module.filter = opts?.filter ?? arg.filter
|
|
28
|
+
_module.guards = [...(arg.guards ?? []), ...(opts?.guards ?? [])]
|
|
29
|
+
_module.gate = opts?.gate ?? arg.gate
|
|
30
|
+
_module.gateParams = opts?.gateParams ?? arg.gateParams
|
|
31
|
+
} else if (isServerRouteModel(arg)) {
|
|
32
|
+
_module = makeCommonModule(arg, { ...opts }) as ServerModule<R>
|
|
33
|
+
_module.route = arg
|
|
34
|
+
} else {
|
|
35
|
+
const _route = route(arg, opts?.intermediate ?? false, opts?.routeOptions)
|
|
36
|
+
_module = makeCommonModule(_route, { ...opts }) as ServerModule<R>
|
|
37
|
+
_module.route = _route
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
_module.fixer = opts?.fixer
|
|
41
|
+
if (handler != null) {
|
|
42
|
+
_module.handle = handler(moduleHandle)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
_module.reinitializeContext = <T>(context: BasicContext<any>) => {
|
|
46
|
+
const newModule = module(arg, handler, opts)
|
|
47
|
+
newModule.ctx = context
|
|
48
|
+
|
|
49
|
+
return newModule as T
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
_module.getPath = () => prependBase(_module.route.route)
|
|
53
|
+
|
|
54
|
+
moduleHandle.ref = _module
|
|
55
|
+
|
|
56
|
+
return _module
|
|
57
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ServerRouteModel, ServerRouteOptions } from '@owlmeans/server-route'
|
|
2
|
+
import type { CommonModule, ModuleHandler, CommonModuleOptions } from '@owlmeans/module'
|
|
3
|
+
import type { Service } from '@owlmeans/context'
|
|
4
|
+
|
|
5
|
+
export interface ServerModule<R> extends CommonModule {
|
|
6
|
+
route: ServerRouteModel<R>
|
|
7
|
+
fixer?: string
|
|
8
|
+
handle: ModuleHandler
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ModuleOptions<R> extends CommonModuleOptions {
|
|
12
|
+
/**
|
|
13
|
+
* Force module to be elevated even if it is already elevated
|
|
14
|
+
*/
|
|
15
|
+
force?: boolean
|
|
16
|
+
fixer?: string
|
|
17
|
+
intermediate?: boolean
|
|
18
|
+
routeOptions?: ServerRouteOptions<R>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface FixerService extends Service {
|
|
22
|
+
handle: <R>(reply: R, error: Error) => void
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ModuleRef<R> {
|
|
26
|
+
ref?: ServerModule<R>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface RefedModuleHandler<R = {}> {
|
|
30
|
+
(ref: ModuleRef<R>): ModuleHandler
|
|
31
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": [
|
|
3
|
+
"../tsconfig.default.json",
|
|
4
|
+
],
|
|
5
|
+
"compilerOptions": {
|
|
6
|
+
"rootDir": "./src/", /* Specify the root folder within your source files. */
|
|
7
|
+
"outDir": "./build/", /* Specify an output folder for all emitted files. */
|
|
8
|
+
"moduleResolution": "Bundler"
|
|
9
|
+
},
|
|
10
|
+
"exclude": [
|
|
11
|
+
"./dist/**/*",
|
|
12
|
+
"./build/**/*",
|
|
13
|
+
"./*.ts"
|
|
14
|
+
]
|
|
15
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"root":["./src/helper.ts","./src/index.ts","./src/module.ts","./src/types.ts","./src/utils/helper.ts","./src/utils/index.ts","./src/utils/module.ts"],"version":"5.6.3"}
|