moost 0.5.32 → 0.6.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/README.md +21 -0
- package/dist/index.cjs +743 -271
- package/dist/index.d.ts +298 -116
- package/dist/index.mjs +680 -254
- package/package.json +39 -33
- package/scripts/setup-skills.js +76 -0
- package/skills/moost/SKILL.md +34 -0
- package/skills/moost/core.md +174 -0
- package/skills/moost/custom-adapters.md +744 -0
- package/skills/moost/decorators.md +185 -0
- package/skills/moost/di.md +161 -0
- package/skills/moost/interceptors.md +199 -0
- package/skills/moost/pipes.md +213 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# Decorators & Metadata — moost
|
|
2
|
+
|
|
3
|
+
> The decorator system, metadata storage via `@prostojs/mate`, handler registration, and how to create custom decorators.
|
|
4
|
+
|
|
5
|
+
## Concepts
|
|
6
|
+
|
|
7
|
+
Moost's decorator system is built on `@prostojs/mate`, a metadata management library. All decorators write to a shared metadata workspace (`'moost'`) via `getMoostMate()`. This metadata is then read during `app.init()` to bind handlers, resolve dependencies, and configure interceptors.
|
|
8
|
+
|
|
9
|
+
**Key types of decorators:**
|
|
10
|
+
- **Class decorators** — `@Controller()`, `@Injectable()`, `@ImportController()`
|
|
11
|
+
- **Method decorators** — `@Get()`, `@Cli()`, `@Intercept()`, handler registrations
|
|
12
|
+
- **Parameter decorators** — `@Param()`, `@Resolve()`, `@Inject()`, `@Body()`
|
|
13
|
+
- **Property decorators** — `@Inject()`, `@Provide()`, hooks
|
|
14
|
+
|
|
15
|
+
Metadata is stored per-class and per-method using TypeScript's legacy experimental decorators (`experimentalDecorators: true`, `emitDecoratorMetadata: true`).
|
|
16
|
+
|
|
17
|
+
## API Reference
|
|
18
|
+
|
|
19
|
+
### `getMoostMate()`
|
|
20
|
+
|
|
21
|
+
Returns the singleton `Mate` instance for the `'moost'` metadata workspace.
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { getMoostMate } from 'moost'
|
|
25
|
+
|
|
26
|
+
const mate = getMoostMate()
|
|
27
|
+
|
|
28
|
+
// Read class metadata
|
|
29
|
+
const classMeta = mate.read(MyController)
|
|
30
|
+
|
|
31
|
+
// Read method metadata
|
|
32
|
+
const methodMeta = mate.read(MyController.prototype, 'myMethod')
|
|
33
|
+
|
|
34
|
+
// Write custom metadata via decorator
|
|
35
|
+
mate.decorate('myKey', myValue) // Overwrites
|
|
36
|
+
mate.decorate('myArray', item, true) // Appends to array
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### `TMoostMetadata` (interface)
|
|
40
|
+
|
|
41
|
+
The shape of metadata stored per class/method:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
interface TMoostMetadata<H = {}> {
|
|
45
|
+
controller?: { prefix?: string }
|
|
46
|
+
importController?: Array<{ prefix?; typeResolver?; provide? }>
|
|
47
|
+
injectable?: true | 'FOR_EVENT' | 'SINGLETON'
|
|
48
|
+
interceptor?: { priority: TInterceptorPriority }
|
|
49
|
+
interceptors?: TInterceptorData[]
|
|
50
|
+
handlers?: TMoostHandler<H>[] // Set by adapter decorators
|
|
51
|
+
pipes?: TPipeData[]
|
|
52
|
+
provide?: TProvideRegistry
|
|
53
|
+
replace?: TReplaceRegistry
|
|
54
|
+
params: Array<TMateParamMeta & TMoostParamsMetadata>
|
|
55
|
+
returnType?: Function
|
|
56
|
+
loggerTopic?: string
|
|
57
|
+
id?: string // Handler ID for path builders
|
|
58
|
+
// ... and more
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### `TMoostHandler<H>` (type)
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
type TMoostHandler<H> = {
|
|
66
|
+
type: string // Adapter type identifier ('HTTP', 'WS_MESSAGE', 'CRON', etc.)
|
|
67
|
+
path?: string // Route/command path
|
|
68
|
+
} & H // Custom handler metadata fields
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### `@Controller(prefix?: string)`
|
|
72
|
+
|
|
73
|
+
Marks a class as a controller. The prefix is prepended to all handler paths.
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
@Controller('api/v1')
|
|
77
|
+
class ApiController { }
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### `@Injectable(scope?: 'SINGLETON' | 'FOR_EVENT')`
|
|
81
|
+
|
|
82
|
+
Marks a class for DI registration with a lifecycle scope.
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
@Injectable('SINGLETON') // One instance for the app lifetime
|
|
86
|
+
class DbService { }
|
|
87
|
+
|
|
88
|
+
@Injectable('FOR_EVENT') // New instance per event (request, command, etc.)
|
|
89
|
+
class RequestScoped { }
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### `@Description(text: string)`
|
|
93
|
+
|
|
94
|
+
Adds a description to a class or method (used by swagger, CLI help).
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
@Description('User management endpoints')
|
|
98
|
+
@Controller('users')
|
|
99
|
+
class UserController {
|
|
100
|
+
@Description('Get user by ID')
|
|
101
|
+
@Get(':id')
|
|
102
|
+
getUser() { }
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Common Patterns
|
|
107
|
+
|
|
108
|
+
### Pattern: Creating a Handler Decorator
|
|
109
|
+
|
|
110
|
+
All adapter-specific decorators follow the same pattern:
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
import type { TEmpty, TMoostMetadata } from 'moost'
|
|
114
|
+
import { getMoostMate } from 'moost'
|
|
115
|
+
|
|
116
|
+
function MyDecorator(path?: string): MethodDecorator {
|
|
117
|
+
return getMoostMate<TEmpty, TMoostMetadata<{ /* extra fields */ }>>().decorate(
|
|
118
|
+
'handlers', // Always 'handlers' for handler registration
|
|
119
|
+
{
|
|
120
|
+
path,
|
|
121
|
+
type: 'MY_TYPE', // Unique string your adapter filters by
|
|
122
|
+
// ...extra fields
|
|
123
|
+
},
|
|
124
|
+
true, // Array mode — accumulates multiple decorators
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Pattern: Reading Metadata in an Adapter
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
bindHandler<T extends object>(opts: TMoostAdapterOptions<TMyMeta, T>) {
|
|
133
|
+
const mate = getMoostMate()
|
|
134
|
+
|
|
135
|
+
for (const handler of opts.handlers) {
|
|
136
|
+
if (handler.type !== 'MY_TYPE') continue
|
|
137
|
+
|
|
138
|
+
// Read additional method-level metadata
|
|
139
|
+
const methodMeta = mate.read(opts.fakeInstance, opts.method as string)
|
|
140
|
+
|
|
141
|
+
// Read class-level metadata
|
|
142
|
+
const classMeta = mate.read(opts.fakeInstance)
|
|
143
|
+
|
|
144
|
+
// Access custom metadata set by your decorators
|
|
145
|
+
const myCustomData = methodMeta?.myCustomField
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Pattern: Dual Parameter/Property Decorator
|
|
151
|
+
|
|
152
|
+
Some decorators work on both parameters and class properties:
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
function MyValue(): ParameterDecorator & PropertyDecorator {
|
|
156
|
+
return Resolve(() => computeValue(), 'my-value')
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// As parameter:
|
|
160
|
+
@Get('test')
|
|
161
|
+
handler(@MyValue() val: string) { }
|
|
162
|
+
|
|
163
|
+
// As property:
|
|
164
|
+
@Controller()
|
|
165
|
+
class MyCtrl {
|
|
166
|
+
@MyValue()
|
|
167
|
+
val!: string
|
|
168
|
+
|
|
169
|
+
@Get('test')
|
|
170
|
+
handler() { return this.val }
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Best Practices
|
|
175
|
+
|
|
176
|
+
- Use `getMoostMate()` (not `new Mate()`) — all Moost metadata must share the same workspace
|
|
177
|
+
- Handler decorators must use array mode (`true` as third arg to `decorate`) so multiple decorators can coexist
|
|
178
|
+
- Use distinctive `type` strings to avoid collisions between adapters
|
|
179
|
+
- Keep metadata types lean — store only what `bindHandler` needs
|
|
180
|
+
|
|
181
|
+
## Gotchas
|
|
182
|
+
|
|
183
|
+
- `getMoostMate().read(instance, method)` requires the **prototype** instance, not a constructed one — use `opts.fakeInstance` in adapters
|
|
184
|
+
- Metadata is inherited from parent classes by default for class-level metadata (when `inherit: true` is set)
|
|
185
|
+
- TypeScript's `emitDecoratorMetadata` must be enabled for type reflection to work (`design:type`, `design:paramtypes`, `design:returntype`)
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# Dependency Injection — moost
|
|
2
|
+
|
|
3
|
+
> DI container powered by `@prostojs/infact`, scoping strategies, provider registration, and injection patterns.
|
|
4
|
+
|
|
5
|
+
## Concepts
|
|
6
|
+
|
|
7
|
+
Moost's DI is built on `@prostojs/infact`. It supports two scopes:
|
|
8
|
+
|
|
9
|
+
- **SINGLETON** — One instance per application. Created during `init()` or on first access.
|
|
10
|
+
- **FOR_EVENT** — One instance per event (HTTP request, CLI command, etc.). Cleaned up when the event scope is unregistered.
|
|
11
|
+
|
|
12
|
+
DI resolution happens automatically during the handler lifecycle — controller instances, interceptors, and injected dependencies are all resolved through the same container.
|
|
13
|
+
|
|
14
|
+
### Scope Lifecycle
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
app.init()
|
|
18
|
+
→ SINGLETON instances created (once)
|
|
19
|
+
|
|
20
|
+
Event arrives:
|
|
21
|
+
→ DI scope registered (useScopeId + registerEventScope)
|
|
22
|
+
→ FOR_EVENT instances created on demand
|
|
23
|
+
→ Handler executes
|
|
24
|
+
→ DI scope unregistered → FOR_EVENT instances released
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## API Reference
|
|
28
|
+
|
|
29
|
+
### `@Injectable(scope?: 'SINGLETON' | 'FOR_EVENT' | true)`
|
|
30
|
+
|
|
31
|
+
Marks a class as DI-managed. `true` is an alias for `'SINGLETON'`.
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
@Injectable('SINGLETON')
|
|
35
|
+
class DatabaseService {
|
|
36
|
+
constructor() { /* connects once */ }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@Injectable('FOR_EVENT')
|
|
40
|
+
class RequestContext {
|
|
41
|
+
// Fresh instance per request/event
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### `@Inject(token: string | symbol | Class)`
|
|
46
|
+
|
|
47
|
+
Explicit injection by token. Use when automatic type-based resolution isn't sufficient.
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
@Injectable()
|
|
51
|
+
class MyService {
|
|
52
|
+
constructor(@Inject('CONFIG') private config: AppConfig) {}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### `@Provide(token?, value?)`
|
|
57
|
+
|
|
58
|
+
Registers a value or factory in the DI container from a controller/class.
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
@Controller()
|
|
62
|
+
class AppController {
|
|
63
|
+
@Provide('APP_VERSION')
|
|
64
|
+
version = '1.0.0'
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### `@Circular(() => Type)`
|
|
69
|
+
|
|
70
|
+
Handles circular dependency references by deferring resolution.
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
@Injectable()
|
|
74
|
+
class ServiceA {
|
|
75
|
+
constructor(@Circular(() => ServiceB) private b: ServiceB) {}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### `createProvideRegistry(...entries)`
|
|
80
|
+
|
|
81
|
+
Creates a provider registry for adapter `getProvideRegistry()`.
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
import { createProvideRegistry } from 'moost'
|
|
85
|
+
|
|
86
|
+
const registry = createProvideRegistry(
|
|
87
|
+
[MyClass, () => myInstance],
|
|
88
|
+
['string-token', () => someValue],
|
|
89
|
+
)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### `createReplaceRegistry(...entries)`
|
|
93
|
+
|
|
94
|
+
Creates a replacement registry for overriding existing providers (useful for testing).
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
import { createReplaceRegistry } from 'moost'
|
|
98
|
+
|
|
99
|
+
const replacements = createReplaceRegistry(
|
|
100
|
+
[RealService, () => new MockService()],
|
|
101
|
+
)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### `getMoostInfact()`
|
|
105
|
+
|
|
106
|
+
Returns the singleton `Infact` DI container instance. Mainly used internally by adapters.
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
import { getMoostInfact } from 'moost'
|
|
110
|
+
|
|
111
|
+
const infact = getMoostInfact()
|
|
112
|
+
infact.registerScope(scopeId)
|
|
113
|
+
infact.unregisterScope(scopeId)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Common Patterns
|
|
117
|
+
|
|
118
|
+
### Pattern: Adapter DI Providers
|
|
119
|
+
|
|
120
|
+
Adapters expose their services to controllers via `getProvideRegistry()`:
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
class MoostHttp implements TMoostAdapter<THttpHandlerMeta> {
|
|
124
|
+
getProvideRegistry() {
|
|
125
|
+
return createProvideRegistry(
|
|
126
|
+
[WooksHttp, () => this.getHttpApp()],
|
|
127
|
+
[HttpServer, () => this.getHttpApp().getServer()],
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Pattern: Scope Cleanup for Long-lived Events
|
|
134
|
+
|
|
135
|
+
For events that outlive a handler call, adapters manually manage scope lifecycle:
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
const fn = defineMoostEventHandler({
|
|
139
|
+
manualUnscope: true,
|
|
140
|
+
hooks: {
|
|
141
|
+
init: ({ unscope }) => {
|
|
142
|
+
// Defer cleanup to when the event truly ends
|
|
143
|
+
onEventEnd(() => unscope())
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
})
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Best Practices
|
|
150
|
+
|
|
151
|
+
- Default to `SINGLETON` scope unless instances need per-event state
|
|
152
|
+
- Use `FOR_EVENT` for anything that holds request-specific data (user context, auth state)
|
|
153
|
+
- Adapters should provide both class tokens and string tokens in `getProvideRegistry()`
|
|
154
|
+
- Avoid manual `getMoostInfact()` calls in application code — use decorators instead
|
|
155
|
+
|
|
156
|
+
## Gotchas
|
|
157
|
+
|
|
158
|
+
- `SINGLETON` instances are created during `app.init()` inside a synthetic event context — composables may not behave as expected in constructors
|
|
159
|
+
- `FOR_EVENT` instances are only available during event processing — injecting them outside an event context will fail
|
|
160
|
+
- Circular dependencies require `@Circular(() => Type)` — without it, the container throws at resolution time
|
|
161
|
+
- Scope IDs are monotonic integers (not UUIDs) for performance — don't rely on their format
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# Interceptors — moost
|
|
2
|
+
|
|
3
|
+
> Interceptor lifecycle, priority levels, `@Intercept` decorator, `InterceptorHandler` internals, and creating custom interceptors.
|
|
4
|
+
|
|
5
|
+
## Concepts
|
|
6
|
+
|
|
7
|
+
Interceptors wrap handler execution in an onion-like pattern. They can:
|
|
8
|
+
- **Short-circuit** — Return a response before the handler runs
|
|
9
|
+
- **Modify responses** — Transform the handler's return value
|
|
10
|
+
- **Handle errors** — Catch and replace errors
|
|
11
|
+
- **Add side effects** — Logging, tracing, auth checks
|
|
12
|
+
|
|
13
|
+
### Priority Levels
|
|
14
|
+
|
|
15
|
+
Interceptors run in priority order (lowest first):
|
|
16
|
+
|
|
17
|
+
| Priority | Constant | Typical Use |
|
|
18
|
+
|----------|----------|-------------|
|
|
19
|
+
| 0 | `BEFORE_ALL` | Logging, tracing setup |
|
|
20
|
+
| 10 | `BEFORE_GUARD` | Pre-auth setup |
|
|
21
|
+
| 20 | `GUARD` | Authentication/authorization checks |
|
|
22
|
+
| 30 | `AFTER_GUARD` | Post-auth enrichment |
|
|
23
|
+
| 40 | `INTERCEPTOR` | General-purpose (default) |
|
|
24
|
+
| 50 | `CATCH_ERROR` | Error handling |
|
|
25
|
+
| 60 | `AFTER_ALL` | Response transformation, cleanup |
|
|
26
|
+
|
|
27
|
+
### Lifecycle Phases
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
before phase (all interceptors, in priority order)
|
|
31
|
+
└─ Each can return a value to short-circuit
|
|
32
|
+
handler execution
|
|
33
|
+
after phase (in LIFO/reverse order)
|
|
34
|
+
└─ Each receives the response, can modify it
|
|
35
|
+
onError phase (if handler threw)
|
|
36
|
+
└─ Each receives the error, can replace it
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The after/error phases use **LIFO order** (last registered runs first) — creating a wrap-around pattern where the outermost interceptor has first and last access.
|
|
40
|
+
|
|
41
|
+
## API Reference
|
|
42
|
+
|
|
43
|
+
### `@Intercept(handler, priority?)`
|
|
44
|
+
|
|
45
|
+
Registers an interceptor on a class or method. The handler can be a class or a functional interceptor definition.
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import { Intercept, TInterceptorPriority } from 'moost'
|
|
49
|
+
|
|
50
|
+
@Intercept(MyGuard, TInterceptorPriority.GUARD)
|
|
51
|
+
@Controller()
|
|
52
|
+
class ProtectedController { }
|
|
53
|
+
|
|
54
|
+
@Intercept(loggingInterceptor)
|
|
55
|
+
@Get('data')
|
|
56
|
+
getData() { }
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Functional Interceptor (TInterceptorDef)
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import type { TInterceptorDef } from 'moost'
|
|
63
|
+
|
|
64
|
+
const myInterceptor: TInterceptorDef = {
|
|
65
|
+
// Called before handler — return value to short-circuit
|
|
66
|
+
before(reply: (response: unknown) => void) {
|
|
67
|
+
// Optional: reply(value) to skip handler
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
// Called after handler (or after before's reply)
|
|
71
|
+
after(response: unknown, reply: (response: unknown) => void) {
|
|
72
|
+
// Optional: modify response via reply(newValue)
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
// Called if handler threw
|
|
76
|
+
onError(error: unknown, reply: (response: unknown) => void) {
|
|
77
|
+
// Optional: recover from error via reply(fallbackValue)
|
|
78
|
+
},
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Class-Based Interceptor
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
import { Injectable, Before, After, OnError } from 'moost'
|
|
86
|
+
|
|
87
|
+
@Injectable()
|
|
88
|
+
class LoggingInterceptor {
|
|
89
|
+
@Before()
|
|
90
|
+
before() {
|
|
91
|
+
console.log('Before handler')
|
|
92
|
+
// Return undefined to continue, or return a value to short-circuit
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
@After()
|
|
96
|
+
after(response: unknown) {
|
|
97
|
+
console.log('After handler:', response)
|
|
98
|
+
// Return undefined to keep original, or return new value
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
@OnError()
|
|
102
|
+
onError(error: unknown) {
|
|
103
|
+
console.error('Handler error:', error)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### `InterceptorHandler` (class)
|
|
109
|
+
|
|
110
|
+
Used internally by `defineMoostEventHandler`. You don't create these directly — they're provided via `opts.getIterceptorHandler()`.
|
|
111
|
+
|
|
112
|
+
Key properties:
|
|
113
|
+
- `count` — Total number of interceptors (0 means skip interceptor phase)
|
|
114
|
+
- `countAfter` — Number of after-phase handlers
|
|
115
|
+
- `countOnError` — Number of error-phase handlers
|
|
116
|
+
|
|
117
|
+
Key methods:
|
|
118
|
+
- `before()` — Runs all before interceptors. Returns early response or `undefined`
|
|
119
|
+
- `fireAfter(response)` — Runs after/error handlers in LIFO order. Returns final response
|
|
120
|
+
|
|
121
|
+
## Common Patterns
|
|
122
|
+
|
|
123
|
+
### Pattern: Auth Guard
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
const authGuard: TInterceptorDef = {
|
|
127
|
+
before(reply) {
|
|
128
|
+
const token = useHeaders().get('authorization')
|
|
129
|
+
if (!token) {
|
|
130
|
+
reply(new HttpError(401, 'Unauthorized'))
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
@Intercept(authGuard, TInterceptorPriority.GUARD)
|
|
136
|
+
@Controller('protected')
|
|
137
|
+
class ProtectedController { }
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Pattern: Response Wrapper
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
const wrapResponse: TInterceptorDef = {
|
|
144
|
+
after(response, reply) {
|
|
145
|
+
reply({ data: response, timestamp: Date.now() })
|
|
146
|
+
},
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Pattern: Global Error Handler
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
app.interceptor({
|
|
154
|
+
handler: {
|
|
155
|
+
onError(error, reply) {
|
|
156
|
+
if (error instanceof HttpError) {
|
|
157
|
+
reply({ error: error.message, status: error.statusCode })
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
priority: TInterceptorPriority.CATCH_ERROR,
|
|
162
|
+
})
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Integration
|
|
166
|
+
|
|
167
|
+
### With Adapters
|
|
168
|
+
|
|
169
|
+
Adapters receive `getIterceptorHandler` in `bindHandler()` options. Pass it through to `defineMoostEventHandler()` — interceptors are handled automatically.
|
|
170
|
+
|
|
171
|
+
For not-found handlers, use `moost.getGlobalInterceptorHandler()` to run global interceptors (CORS, logging) even on unmatched routes.
|
|
172
|
+
|
|
173
|
+
### With Context Types
|
|
174
|
+
|
|
175
|
+
Interceptors can check `contextType` to apply only to specific event types:
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
const httpOnly: TInterceptorDef = {
|
|
179
|
+
before() {
|
|
180
|
+
// This interceptor is registered globally but only meaningful for HTTP
|
|
181
|
+
// Check event type if needed
|
|
182
|
+
},
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Best Practices
|
|
187
|
+
|
|
188
|
+
- Use `GUARD` priority for auth checks — they run before general interceptors
|
|
189
|
+
- Use `AFTER_ALL` for response transformations — they run after everything else
|
|
190
|
+
- Use `CATCH_ERROR` for error recovery — keeps error handling separate from business logic
|
|
191
|
+
- Prefer functional interceptors for simple logic, class-based for complex logic requiring DI
|
|
192
|
+
- Return `undefined` from `before()` to continue to the handler; return a value to short-circuit
|
|
193
|
+
|
|
194
|
+
## Gotchas
|
|
195
|
+
|
|
196
|
+
- After-phase interceptors run in **LIFO** order (reverse of registration) — the last registered interceptor's `after()` runs first
|
|
197
|
+
- If `before()` returns a value, the handler is **skipped** but after-phase interceptors still run
|
|
198
|
+
- Error interceptors receive the error as the `response` parameter, not as a thrown exception
|
|
199
|
+
- `InterceptorHandler.count === 0` means no interceptors — the entire interceptor phase is skipped for performance
|