@noego/stitch 1.0.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-schema.md +139 -0
- package/bin/stitch.js +156 -0
- package/dist/browser/index.d.ts +4 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/cli/StitchCLI.d.ts +12 -0
- package/dist/cli/StitchCLI.d.ts.map +1 -0
- package/dist/client.cjs +2 -0
- package/dist/client.cjs.map +1 -0
- package/dist/client.mjs +83 -0
- package/dist/client.mjs.map +1 -0
- package/dist/core/ConfigParser.d.ts +16 -0
- package/dist/core/ConfigParser.d.ts.map +1 -0
- package/dist/core/StitchEngine.d.ts +31 -0
- package/dist/core/StitchEngine.d.ts.map +1 -0
- package/dist/core/Validator.d.ts +18 -0
- package/dist/core/Validator.d.ts.map +1 -0
- package/dist/core/ViteHelper.d.ts +33 -0
- package/dist/core/ViteHelper.d.ts.map +1 -0
- package/dist/core/YamlMerger.d.ts +29 -0
- package/dist/core/YamlMerger.d.ts.map +1 -0
- package/dist/core/YamlMergerBrowser.d.ts +18 -0
- package/dist/core/YamlMergerBrowser.d.ts.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/types/StitchTypes.d.ts +38 -0
- package/dist/types/StitchTypes.d.ts.map +1 -0
- package/dist-ssr/browser/index.d.ts +4 -0
- package/dist-ssr/browser/index.d.ts.map +1 -0
- package/dist-ssr/cli/StitchCLI.d.ts +12 -0
- package/dist-ssr/cli/StitchCLI.d.ts.map +1 -0
- package/dist-ssr/core/ConfigParser.d.ts +16 -0
- package/dist-ssr/core/ConfigParser.d.ts.map +1 -0
- package/dist-ssr/core/StitchEngine.d.ts +31 -0
- package/dist-ssr/core/StitchEngine.d.ts.map +1 -0
- package/dist-ssr/core/Validator.d.ts +18 -0
- package/dist-ssr/core/Validator.d.ts.map +1 -0
- package/dist-ssr/core/ViteHelper.d.ts +33 -0
- package/dist-ssr/core/ViteHelper.d.ts.map +1 -0
- package/dist-ssr/core/YamlMerger.d.ts +29 -0
- package/dist-ssr/core/YamlMerger.d.ts.map +1 -0
- package/dist-ssr/core/YamlMergerBrowser.d.ts +18 -0
- package/dist-ssr/core/YamlMergerBrowser.d.ts.map +1 -0
- package/dist-ssr/index.d.ts +15 -0
- package/dist-ssr/index.d.ts.map +1 -0
- package/dist-ssr/server.cjs +574 -0
- package/dist-ssr/server.cjs.map +1 -0
- package/dist-ssr/server.d.ts +2 -0
- package/dist-ssr/server.js +554 -0
- package/dist-ssr/server.js.map +1 -0
- package/dist-ssr/types/StitchTypes.d.ts +38 -0
- package/dist-ssr/types/StitchTypes.d.ts.map +1 -0
- package/docs/advanced_validation.md +188 -0
- package/docs/middleware.md +507 -0
- package/docs/modules.md +175 -0
- package/docs/project.md +856 -0
- package/docs/validation.md +77 -0
- package/package.json +67 -0
- package/readme.md +159 -0
- package/schemas/schema.json +360 -0
- package/schemas/stitch-schema.json +38 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# OpenAPI Validation Cheatsheet
|
|
2
|
+
|
|
3
|
+
When defining endpoints in OpenAPI (using JSON Schema–style validation), you can constrain your data in **several** ways. This guide provides a quick overview of **common validation** approaches, such as **string constraints**, **patterns (regex)**, **numerical constraints**, and more.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. String Constraints
|
|
8
|
+
|
|
9
|
+
**OpenAPI** leverages standard JSON Schema properties to validate strings.
|
|
10
|
+
|
|
11
|
+
### Basic Example
|
|
12
|
+
|
|
13
|
+
```yaml
|
|
14
|
+
components:
|
|
15
|
+
schemas:
|
|
16
|
+
UserName:
|
|
17
|
+
type: string
|
|
18
|
+
description: A simple username
|
|
19
|
+
minLength: 3
|
|
20
|
+
maxLength: 50
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
- **`minLength`**: The string must be at least 3 characters.
|
|
24
|
+
- **`maxLength`**: The string must be at most 50 characters.
|
|
25
|
+
|
|
26
|
+
### Format Examples
|
|
27
|
+
|
|
28
|
+
- **`format: email`**
|
|
29
|
+
Validates that a string is an email (e.g., `john@example.com`).
|
|
30
|
+
- **`format: uri`**
|
|
31
|
+
Validates that a string is a URI (e.g., `https://example.com`).
|
|
32
|
+
- **`format: date`** / **`format: date-time`**
|
|
33
|
+
Checks that a string is in a valid date/time format.
|
|
34
|
+
|
|
35
|
+
```yaml
|
|
36
|
+
components:
|
|
37
|
+
schemas:
|
|
38
|
+
Profile:
|
|
39
|
+
type: object
|
|
40
|
+
properties:
|
|
41
|
+
email:
|
|
42
|
+
type: string
|
|
43
|
+
format: email
|
|
44
|
+
website:
|
|
45
|
+
type: string
|
|
46
|
+
format: uri
|
|
47
|
+
birthday:
|
|
48
|
+
type: string
|
|
49
|
+
format: date
|
|
50
|
+
required: [ email, website ]
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
> Note: The `format` checks are not bulletproof but are a quick way to ensure a minimal level of validity.
|
|
54
|
+
|
|
55
|
+
### Pattern (Regex)
|
|
56
|
+
|
|
57
|
+
For **custom rules**, add a `"pattern"` (regex). For example:
|
|
58
|
+
|
|
59
|
+
```yaml
|
|
60
|
+
components:
|
|
61
|
+
schemas:
|
|
62
|
+
StrictUserId:
|
|
63
|
+
type: string
|
|
64
|
+
pattern: '^[A-Z0-9]{8}$'
|
|
65
|
+
description: Must be exactly 8 characters of uppercase letters or digits
|
|
66
|
+
```
|
|
67
|
+
- The pattern `'^[A-Z0-9]{8}$'` enforces exactly 8 uppercase alpha-numerics.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## 2. Numeric Constraints
|
|
72
|
+
|
|
73
|
+
You can validate numbers by specifying **minimum**, **maximum**, and whether the number is an **integer**.
|
|
74
|
+
|
|
75
|
+
```yaml
|
|
76
|
+
components:
|
|
77
|
+
schemas:
|
|
78
|
+
Product:
|
|
79
|
+
type: object
|
|
80
|
+
properties:
|
|
81
|
+
price:
|
|
82
|
+
type: number
|
|
83
|
+
minimum: 0.01
|
|
84
|
+
quantity:
|
|
85
|
+
type: integer
|
|
86
|
+
minimum: 1
|
|
87
|
+
maximum: 9999
|
|
88
|
+
required: [ price, quantity ]
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
- **`type: number`** vs. **`type: integer`**
|
|
92
|
+
- `number` can have decimals; `integer` cannot.
|
|
93
|
+
- **`minimum`** / **`maximum`**
|
|
94
|
+
- Price must be ≥ 0.01.
|
|
95
|
+
- Quantity must be between 1 and 9999.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## 3. Array Constraints
|
|
100
|
+
|
|
101
|
+
For arrays, use keywords like **items**, **minItems**, and **maxItems**:
|
|
102
|
+
|
|
103
|
+
```yaml
|
|
104
|
+
components:
|
|
105
|
+
schemas:
|
|
106
|
+
TagList:
|
|
107
|
+
type: array
|
|
108
|
+
minItems: 1
|
|
109
|
+
maxItems: 10
|
|
110
|
+
items:
|
|
111
|
+
type: string
|
|
112
|
+
maxLength: 20
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
- **`items`**: schema each array element must follow.
|
|
116
|
+
- **`minItems`** / **`maxItems`**: ensures array length constraints.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## 4. Required Fields
|
|
121
|
+
|
|
122
|
+
If you have **object** properties that must be provided:
|
|
123
|
+
|
|
124
|
+
```yaml
|
|
125
|
+
components:
|
|
126
|
+
schemas:
|
|
127
|
+
RegisterUser:
|
|
128
|
+
type: object
|
|
129
|
+
properties:
|
|
130
|
+
username:
|
|
131
|
+
type: string
|
|
132
|
+
password:
|
|
133
|
+
type: string
|
|
134
|
+
required: [ username, password ]
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Those fields become mandatory. Missing them triggers a **400 Bad Request** from the validator.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## 5. Combining Validation Approaches
|
|
142
|
+
|
|
143
|
+
You can combine multiple validations. For example, ensure a **username** is at least 3 chars and also matches a pattern:
|
|
144
|
+
|
|
145
|
+
```yaml
|
|
146
|
+
components:
|
|
147
|
+
schemas:
|
|
148
|
+
StrictUsername:
|
|
149
|
+
type: string
|
|
150
|
+
minLength: 3
|
|
151
|
+
maxLength: 12
|
|
152
|
+
pattern: '^[a-zA-Z0-9._-]+$'
|
|
153
|
+
description: 3–12 characters, only letters, digits, '.', '_' or '-'
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## 6. How the Validation Happens
|
|
159
|
+
|
|
160
|
+
- **OpenAPI** spec includes your schemas.
|
|
161
|
+
- **Fastify** uses **Ajv** behind the scenes to auto-validate requests.
|
|
162
|
+
- If a request body (or query param, path param, etc.) fails to match the schema, a **400** is returned automatically.
|
|
163
|
+
- No manual coding needed to check lengths, patterns, or formats—**the schema does the job**.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## 7. Quick Tips
|
|
168
|
+
|
|
169
|
+
1. **Start Simple**
|
|
170
|
+
Use `type`, `required`, and `format` if that’s all you need.
|
|
171
|
+
|
|
172
|
+
2. **Regex for Power**
|
|
173
|
+
`"pattern"` is your friend for custom validations, from strict username rules to domain-specific checks.
|
|
174
|
+
|
|
175
|
+
3. **Min/Max**
|
|
176
|
+
Avoid weird input by bounding string length, array length, or numeric ranges.
|
|
177
|
+
|
|
178
|
+
4. **Keep Schemas Organized**
|
|
179
|
+
Put them in `components.schemas`. Reuse by referencing them across your OpenAPI spec.
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## 8. Further Reading
|
|
184
|
+
|
|
185
|
+
- **Fastify** validation docs: <https://www.fastify.io/docs/latest/Reference/Validation-and-Serialization/>
|
|
186
|
+
- **Ajv** official docs: <https://ajv.js.org/>
|
|
187
|
+
- **OpenAPI** specification about schema: <https://swagger.io/docs/specification/data-models/data-types/>
|
|
188
|
+
|
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
# Middleware Documentation
|
|
2
|
+
|
|
3
|
+
## Current Implementation
|
|
4
|
+
|
|
5
|
+
### How Middleware Works
|
|
6
|
+
|
|
7
|
+
The framework implements route-specific middleware through OpenAPI configuration. Middleware executes sequentially before controller actions using dynamic imports and Promise-based execution.
|
|
8
|
+
|
|
9
|
+
**Current Architecture Issue**: Middleware logic is embedded directly in the ExpressServerImplementation class, making it tightly coupled and harder to maintain.
|
|
10
|
+
|
|
11
|
+
**Desired Architecture**: Separate middleware functionality into a dedicated `MiddlewareAdapter` class (see forge implementation: `express_server_adapter.ts:22`, `middleware_adapter.ts`).
|
|
12
|
+
|
|
13
|
+
## Improved Architecture Pattern (From Forge)
|
|
14
|
+
|
|
15
|
+
### Separation of Concerns
|
|
16
|
+
|
|
17
|
+
The forge implementation demonstrates proper separation:
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
// express_server_adapter.ts:22
|
|
21
|
+
export class ExpressServerAdapter extends ServerAdapter {
|
|
22
|
+
constructor(
|
|
23
|
+
private server: ReturnType<typeof express>,
|
|
24
|
+
private manager: ComponentManager,
|
|
25
|
+
private htmlRender: IHTMLRender,
|
|
26
|
+
private api_adapter: ApiAdapter,
|
|
27
|
+
private middleware_adapter: MiddlewareAdapter // Injected dependency
|
|
28
|
+
) {
|
|
29
|
+
super()
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Middleware Adapter Interface
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
// middleware_adapter.ts:19
|
|
37
|
+
async handleMiddleware(req: any, reply: any, route: IRoute, request_data: RequestData, callback: Callback): Promise<any>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Execution Flow in Server Adapter
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
// express_server_adapter.ts:107-118
|
|
44
|
+
try {
|
|
45
|
+
await new Promise((resolve: any, reject: any) => {
|
|
46
|
+
this.middleware_adapter.handleMiddleware(req, res, route, request_data, resolve).catch(reject)
|
|
47
|
+
})
|
|
48
|
+
} catch(e) {
|
|
49
|
+
console.log('Error in middleware', e)
|
|
50
|
+
res.status(500).send(`
|
|
51
|
+
<h1>500 Internal Server Error</h1>
|
|
52
|
+
<pre>${e}</pre>
|
|
53
|
+
`)
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Middleware Adapter Implementation Features
|
|
59
|
+
|
|
60
|
+
#### Component-Based Middleware (`middleware_adapter.ts:20-26`)
|
|
61
|
+
```typescript
|
|
62
|
+
let components = await this.manager.getLayoutComponents(route)
|
|
63
|
+
let middleware_stack = await Promise.all(components.map(async (component) => {
|
|
64
|
+
const middleware = [ ...(component.middleware||[]), ...(component.middlewares||[]) ]
|
|
65
|
+
return middleware
|
|
66
|
+
}))
|
|
67
|
+
const layout_middlewares = middleware_stack.flat() || []
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
#### Sequential Execution with Deduplication (`middleware_adapter.ts:52-76`)
|
|
71
|
+
```typescript
|
|
72
|
+
private runMiddleware<K>(middleware: K[], req: any, reply: any, callback: Callback): void {
|
|
73
|
+
middleware = this.dedupe(middleware) // Remove duplicates
|
|
74
|
+
let index = 0;
|
|
75
|
+
|
|
76
|
+
let next = () => {
|
|
77
|
+
if (index >= middleware.length) {
|
|
78
|
+
callback()
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
const mw = middleware[index]
|
|
82
|
+
index++
|
|
83
|
+
if (typeof mw === 'function') {
|
|
84
|
+
if(!reply.sent)
|
|
85
|
+
mw(req, reply, next)
|
|
86
|
+
} else {
|
|
87
|
+
if (!reply.sent)
|
|
88
|
+
next()
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
next()
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
#### Layout + View Middleware Support (`middleware_adapter.ts:38-47`)
|
|
96
|
+
- **Layout Middleware**: Executed first from layout components
|
|
97
|
+
- **View Middleware**: Executed second from view components
|
|
98
|
+
- Both support `middleware` and `middlewares` array properties
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Current Server Implementation (Needs Refactoring)
|
|
103
|
+
|
|
104
|
+
### Configuration & Structure
|
|
105
|
+
|
|
106
|
+
#### Server Configuration
|
|
107
|
+
```typescript
|
|
108
|
+
// ServerOptions interface
|
|
109
|
+
interface ServerOptions {
|
|
110
|
+
middleware_path?: string; // Optional absolute path to middleware directory
|
|
111
|
+
controllers_base_path: string; // Fallback path if middleware_path not set
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
#### OpenAPI Route Configuration
|
|
116
|
+
```yaml
|
|
117
|
+
# Middleware assigned per route using x-middleware array
|
|
118
|
+
/user/{id}:
|
|
119
|
+
get:
|
|
120
|
+
x-controller: controllers/user.controller
|
|
121
|
+
x-action: get
|
|
122
|
+
x-middleware:
|
|
123
|
+
- user/auth # Default export only
|
|
124
|
+
- auth:* # All exported functions
|
|
125
|
+
- auth:default,cookie,parsing # Specific functions
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
#### Module-Level Middleware (Implementation Required First)
|
|
129
|
+
|
|
130
|
+
> **⚠️ Implementation Required**: Module support does not currently exist in this server implementation. The module system exists in the forge project but needs to be ported to support module-level middleware.
|
|
131
|
+
|
|
132
|
+
**Planned Module Configuration with Top-Level BasePath**
|
|
133
|
+
```yaml
|
|
134
|
+
# This functionality requires implementing module support first
|
|
135
|
+
openapi: 3.0.0
|
|
136
|
+
info:
|
|
137
|
+
title: My API
|
|
138
|
+
version: 1.0.0
|
|
139
|
+
|
|
140
|
+
basePath: "/api" # Top-level basePath applied to ALL routes
|
|
141
|
+
|
|
142
|
+
module:
|
|
143
|
+
users:
|
|
144
|
+
basePath: "/users" # Module basePath (combined with top-level)
|
|
145
|
+
x-middleware: # Module-level middleware (NOT YET SUPPORTED)
|
|
146
|
+
- auth # Applied to all routes in this module
|
|
147
|
+
- logging:request # Applied to all routes in this module
|
|
148
|
+
paths:
|
|
149
|
+
"/": # Becomes /api/users/
|
|
150
|
+
get:
|
|
151
|
+
x-controller: controllers/user.controller
|
|
152
|
+
x-action: getAll
|
|
153
|
+
x-middleware: # Route-specific middleware
|
|
154
|
+
- validation:users # Combined with module middleware
|
|
155
|
+
"/{id}": # Becomes /api/users/{id}
|
|
156
|
+
get:
|
|
157
|
+
x-controller: controllers/user.controller
|
|
158
|
+
x-action: get
|
|
159
|
+
x-middleware:
|
|
160
|
+
- validation:user-id
|
|
161
|
+
posts:
|
|
162
|
+
basePath: "/posts" # Becomes /api/posts/*
|
|
163
|
+
paths:
|
|
164
|
+
"/":
|
|
165
|
+
get:
|
|
166
|
+
x-controller: controllers/post.controller
|
|
167
|
+
x-action: getAll
|
|
168
|
+
|
|
169
|
+
# Direct paths also get top-level basePath
|
|
170
|
+
paths:
|
|
171
|
+
"/health": # Becomes /api/health
|
|
172
|
+
get:
|
|
173
|
+
x-controller: controllers/health.controller
|
|
174
|
+
x-action: check
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
**Implementation Requirements**:
|
|
178
|
+
1. **Top-Level BasePath Support**: Add support for top-level `basePath` configuration
|
|
179
|
+
- Parse top-level `basePath` from OpenAPI config
|
|
180
|
+
- Apply to all routes (both direct paths and module-generated paths)
|
|
181
|
+
2. **OpenAPI Parser Extension**: Add module parsing support (see `forge/src/parser/openapi.ts:19-61`)
|
|
182
|
+
- Parse module configurations with `basePath` and module-level `x-middleware`
|
|
183
|
+
- Inherit module middleware to all routes within the module
|
|
184
|
+
- Flatten modules into regular `paths` structure for easier processing
|
|
185
|
+
3. **Route Normalization**: Implement module-to-paths normalization (`forge/src/parser/openapi.ts:94-123`)
|
|
186
|
+
- Combine top-level basePath + module basePath + route path
|
|
187
|
+
- Combine module routes with direct path routes
|
|
188
|
+
- Write all routes back to standard `config.paths[path][method]` structure
|
|
189
|
+
- Delete `config.modules` section after normalization
|
|
190
|
+
4. **Middleware Inheritance**: During module parsing, prepend module middleware to route middleware
|
|
191
|
+
5. **Flat Route Processing**: Current `ExpressServerImplementation` can remain unchanged since routes are flattened
|
|
192
|
+
|
|
193
|
+
**Module Normalization Example**:
|
|
194
|
+
```yaml
|
|
195
|
+
# Input: Configuration with top-level basePath and modules
|
|
196
|
+
basePath: "/api" # Top-level basePath
|
|
197
|
+
|
|
198
|
+
module:
|
|
199
|
+
users:
|
|
200
|
+
basePath: "/users" # Module basePath
|
|
201
|
+
x-middleware: [auth, logging:request]
|
|
202
|
+
paths:
|
|
203
|
+
"/":
|
|
204
|
+
get:
|
|
205
|
+
x-controller: controllers/user.controller
|
|
206
|
+
x-action: getAll
|
|
207
|
+
x-middleware: [validation:users]
|
|
208
|
+
"/{id}":
|
|
209
|
+
get:
|
|
210
|
+
x-controller: controllers/user.controller
|
|
211
|
+
x-action: get
|
|
212
|
+
|
|
213
|
+
paths:
|
|
214
|
+
"/health": # Direct path
|
|
215
|
+
get:
|
|
216
|
+
x-controller: controllers/health.controller
|
|
217
|
+
x-action: check
|
|
218
|
+
|
|
219
|
+
# Output: Normalized to standard paths structure
|
|
220
|
+
paths:
|
|
221
|
+
"/api/users/": # top-level + module + route
|
|
222
|
+
get:
|
|
223
|
+
x-controller: controllers/user.controller
|
|
224
|
+
x-action: getAll
|
|
225
|
+
x-middleware: [auth, logging:request, validation:users] # Module middleware prepended
|
|
226
|
+
"/api/users/{id}": # top-level + module + route
|
|
227
|
+
get:
|
|
228
|
+
x-controller: controllers/user.controller
|
|
229
|
+
x-action: get
|
|
230
|
+
x-middleware: [auth, logging:request] # Module middleware inherited
|
|
231
|
+
"/api/health": # top-level + direct path
|
|
232
|
+
get:
|
|
233
|
+
x-controller: controllers/health.controller
|
|
234
|
+
x-action: check
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
**Planned Module Middleware Features**:
|
|
238
|
+
- All routes within a module inherit the module's `x-middleware`
|
|
239
|
+
- Module middleware is prepended to route-specific middleware
|
|
240
|
+
- Module middleware applies to every path within that module
|
|
241
|
+
- Nested modules inherit parent module middleware (when supported)
|
|
242
|
+
|
|
243
|
+
#### Middleware Function Structure
|
|
244
|
+
|
|
245
|
+
**Default Export (Current Behavior)**
|
|
246
|
+
```typescript
|
|
247
|
+
// auth.ts - uses default export only
|
|
248
|
+
export default function UserAuth(req: any, res: any, next: any) {
|
|
249
|
+
// Middleware logic here
|
|
250
|
+
next();
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
**Multiple Function Exports (Extended Behavior)**
|
|
255
|
+
```typescript
|
|
256
|
+
// auth.ts - multiple middleware functions
|
|
257
|
+
export default function UserAuth(req: any, res: any, next: any) {
|
|
258
|
+
// Default authentication
|
|
259
|
+
next();
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function cookie(req: any, res: any, next: any) {
|
|
263
|
+
// Cookie handling middleware
|
|
264
|
+
next();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export function parsing(req: any, res: any, next: any) {
|
|
268
|
+
// Request parsing middleware
|
|
269
|
+
next();
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
#### Middleware Resolution Patterns
|
|
274
|
+
|
|
275
|
+
| Pattern | Behavior | Example |
|
|
276
|
+
|---------|----------|---------|
|
|
277
|
+
| `auth` | Load default export only | `middleware_module.default` |
|
|
278
|
+
| `auth:*` | Load all exported functions | `Object.values(middleware_module).filter(fn => typeof fn === 'function')` |
|
|
279
|
+
| `auth:default,cookie,parsing` | Load specific named functions | `[middleware_module.default, middleware_module.cookie, middleware_module.parsing]` |
|
|
280
|
+
|
|
281
|
+
#### Extended Middleware Examples
|
|
282
|
+
|
|
283
|
+
**Example 1: Using all functions from a middleware file**
|
|
284
|
+
```yaml
|
|
285
|
+
x-middleware:
|
|
286
|
+
- auth:* # Executes all exported functions in order: default, cookie, parsing
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
**Example 2: Selective function execution**
|
|
290
|
+
```yaml
|
|
291
|
+
x-middleware:
|
|
292
|
+
- auth:cookie,parsing # Only executes cookie and parsing functions, skips default
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
**Example 3: Mixed middleware patterns**
|
|
296
|
+
```yaml
|
|
297
|
+
x-middleware:
|
|
298
|
+
- logging # Default export only
|
|
299
|
+
- auth:* # All functions from auth.ts
|
|
300
|
+
- validation:schema # Only schema function from validation.ts
|
|
301
|
+
- security:default,cors # Default and cors functions from security.ts
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
**Execution Order**: Functions execute in the exact order specified:
|
|
305
|
+
1. `logging.default`
|
|
306
|
+
2. `auth.default`, `auth.cookie`, `auth.parsing` (in export order)
|
|
307
|
+
3. `validation.schema`
|
|
308
|
+
4. `security.default`, `security.cors`
|
|
309
|
+
|
|
310
|
+
### Execution Flow
|
|
311
|
+
|
|
312
|
+
1. **Route Matching**: Request matches OpenAPI route definition
|
|
313
|
+
2. **Middleware Extraction**: Extract middleware from route configuration:
|
|
314
|
+
- **Current**: Only route-level `x-middleware` from OpenAPI route definition
|
|
315
|
+
- **Future**: Module middleware consolidation when module support is added
|
|
316
|
+
3. **Middleware Resolution**: Each middleware in the array is resolved:
|
|
317
|
+
- **Path Parsing**: Split middleware string on `:` to separate file path from function selector
|
|
318
|
+
- **File Resolution**: `(middleware_path || controllers_base_path) + filePath`
|
|
319
|
+
- **Dynamic Import**: `await import(middleware_full_path)`
|
|
320
|
+
- **Function Extraction**: Based on pattern:
|
|
321
|
+
- `auth` → `[middleware_module.default]` (validate is function)
|
|
322
|
+
- `auth:*` → `Object.values(middleware_module).filter(fn => typeof fn === 'function')`
|
|
323
|
+
- `auth:default,cookie,parsing` → `[middleware_module.default, middleware_module.cookie, middleware_module.parsing]` (validate each is function)
|
|
324
|
+
4. **Function Flattening**: All resolved functions are flattened into a single execution array
|
|
325
|
+
5. **Sequential Execution**: For each middleware function:
|
|
326
|
+
- Wrapped in Promise for async handling
|
|
327
|
+
- Called with `(req, res, next)` parameters
|
|
328
|
+
- Must complete before next middleware executes
|
|
329
|
+
6. **Error Handling**: Any middleware error halts execution with 500 response
|
|
330
|
+
- **File Not Found**: Middleware file doesn't exist at resolved path
|
|
331
|
+
- **Function Not Found**: Specified function doesn't exist in module
|
|
332
|
+
- **Runtime Errors**: Middleware throws exception or calls `next(error)`
|
|
333
|
+
7. **Controller Execution**: After all middleware succeeds, controller action runs
|
|
334
|
+
|
|
335
|
+
### Current Implementation Details
|
|
336
|
+
|
|
337
|
+
#### Path Resolution (`ExpressServerImplentation.ts:183-211`)
|
|
338
|
+
```typescript
|
|
339
|
+
// Use middleware_path if provided, otherwise fall back to controller_base
|
|
340
|
+
const middleware_base = this.options.middleware_path || controller_base;
|
|
341
|
+
const middleware_full_path = path.resolve(process.cwd(), path.join(middleware_base, `${middlewarePath}`));
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
#### Middleware Execution Loop (`ExpressServerImplentation.ts:183-211`)
|
|
345
|
+
|
|
346
|
+
The middleware execution follows this detailed process for each middleware in the `x-middleware` array:
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
for (const middlewarePath of middleware) {
|
|
350
|
+
try {
|
|
351
|
+
// 1. Path Resolution
|
|
352
|
+
// Use middleware_path if provided, otherwise fall back to controller_base
|
|
353
|
+
const middleware_base = this.options.middleware_path || controller_base;
|
|
354
|
+
const middleware_full_path = path.resolve(process.cwd(), path.join(middleware_base, `${middlewarePath}`));
|
|
355
|
+
|
|
356
|
+
// 2. Dynamic Module Import
|
|
357
|
+
const middleware_module = (await import(middleware_full_path));
|
|
358
|
+
const middleware_function = middleware_module.default;
|
|
359
|
+
|
|
360
|
+
// 3. Function Validation & Execution
|
|
361
|
+
if (middleware_function && typeof middleware_function === "function") {
|
|
362
|
+
await new Promise<void>((resolve, reject) => {
|
|
363
|
+
middleware_function(req, res, (error?: any) => {
|
|
364
|
+
if (error) {
|
|
365
|
+
reject(error);
|
|
366
|
+
} else {
|
|
367
|
+
resolve();
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
} catch (error) {
|
|
373
|
+
// 4. Error Handling
|
|
374
|
+
console.error(`Middleware execution failed for ${middlewarePath}:`, error);
|
|
375
|
+
res.status(500).send({
|
|
376
|
+
error: true,
|
|
377
|
+
message: `Middleware execution failed: ${middlewarePath}`,
|
|
378
|
+
statusCode: 500,
|
|
379
|
+
});
|
|
380
|
+
return // Stops execution immediately
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
##### Step-by-Step Breakdown:
|
|
386
|
+
|
|
387
|
+
**1. Path Resolution**
|
|
388
|
+
- Constructs absolute path using `middleware_path` option or falls back to `controller_base`
|
|
389
|
+
- Uses `path.resolve(process.cwd(), path.join(base, middlewarePath))`
|
|
390
|
+
- Example: `middlewarePath: "user/auth"` → `/absolute/path/to/middleware/user/auth`
|
|
391
|
+
|
|
392
|
+
**2. Dynamic Module Import**
|
|
393
|
+
- Uses ES6 dynamic import: `await import(middleware_full_path)`
|
|
394
|
+
- Automatically resolves file extensions (`.js`, `.ts`, etc.)
|
|
395
|
+
- Extracts default export: `middleware_module.default`
|
|
396
|
+
|
|
397
|
+
**3. Function Validation & Execution**
|
|
398
|
+
- Validates middleware is a function before execution
|
|
399
|
+
- Wraps execution in Promise for async control flow
|
|
400
|
+
- Provides Express-style callback: `(error?: any) => void`
|
|
401
|
+
- Middleware must call callback to continue: `next()` or `next(error)`
|
|
402
|
+
|
|
403
|
+
**4. Error Handling**
|
|
404
|
+
- **Import Errors**: Module not found, syntax errors, etc.
|
|
405
|
+
- **Runtime Errors**: Middleware throws exception or calls `next(error)`
|
|
406
|
+
- **Response**: Sets 500 status with structured error message
|
|
407
|
+
- **Flow Control**: `return` statement halts entire request processing
|
|
408
|
+
|
|
409
|
+
##### Execution Characteristics:
|
|
410
|
+
|
|
411
|
+
- **Sequential**: Middleware executes one at a time, not in parallel
|
|
412
|
+
- **Blocking**: Each middleware must complete before next one starts
|
|
413
|
+
- **Fail-Fast**: First error stops all subsequent middleware and controller execution
|
|
414
|
+
- **No Caching**: Modules are imported fresh on every request
|
|
415
|
+
- **Stateless**: No shared state between middleware executions
|
|
416
|
+
|
|
417
|
+
#### Error Response Format
|
|
418
|
+
```json
|
|
419
|
+
{
|
|
420
|
+
"error": true,
|
|
421
|
+
"message": "Middleware execution failed: user/auth",
|
|
422
|
+
"statusCode": 500
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
#### Extended Error Handling
|
|
427
|
+
|
|
428
|
+
**File Not Found Errors**
|
|
429
|
+
```json
|
|
430
|
+
{
|
|
431
|
+
"error": true,
|
|
432
|
+
"message": "Middleware file not found: auth.ts",
|
|
433
|
+
"statusCode": 500
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
**Function Not Found Errors**
|
|
438
|
+
```json
|
|
439
|
+
{
|
|
440
|
+
"error": true,
|
|
441
|
+
"message": "Middleware function 'cookie' not found in auth.ts",
|
|
442
|
+
"statusCode": 500
|
|
443
|
+
}
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
**Multiple Function Errors**
|
|
447
|
+
```json
|
|
448
|
+
{
|
|
449
|
+
"error": true,
|
|
450
|
+
"message": "Middleware functions 'cookie,parsing' not found in auth.ts",
|
|
451
|
+
"statusCode": 500
|
|
452
|
+
}
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
**Non-Function Export Errors**
|
|
456
|
+
```json
|
|
457
|
+
{
|
|
458
|
+
"error": true,
|
|
459
|
+
"message": "Middleware export 'cookie' in auth.ts is not a function",
|
|
460
|
+
"statusCode": 500
|
|
461
|
+
}
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
**Multiple Non-Function Errors**
|
|
465
|
+
```json
|
|
466
|
+
{
|
|
467
|
+
"error": true,
|
|
468
|
+
"message": "Middleware exports 'cookie,config' in auth.ts are not functions",
|
|
469
|
+
"statusCode": 500
|
|
470
|
+
}
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
**Default Export Missing**
|
|
474
|
+
```json
|
|
475
|
+
{
|
|
476
|
+
"error": true,
|
|
477
|
+
"message": "Middleware file auth.ts has no default export",
|
|
478
|
+
"statusCode": 500
|
|
479
|
+
}
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
**Default Export Not Function**
|
|
483
|
+
```json
|
|
484
|
+
{
|
|
485
|
+
"error": true,
|
|
486
|
+
"message": "Middleware default export in auth.ts is not a function",
|
|
487
|
+
"statusCode": 500
|
|
488
|
+
}
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
**Validation Rules**
|
|
492
|
+
- **File Resolution**: Must throw error if middleware file doesn't exist at resolved path
|
|
493
|
+
- **Function Resolution**: Must throw error if any specified function doesn't exist in module
|
|
494
|
+
- **Function Type Validation**: Must throw error if any resolved export is not a function
|
|
495
|
+
- **Default Export Validation**:
|
|
496
|
+
- `auth` pattern must fail if module has no default export or if default export is not a function
|
|
497
|
+
- `auth:default` pattern must fail if module has no default export or if default export is not a function
|
|
498
|
+
- **Wildcard Validation**: `auth:*` should only fail if file doesn't exist, not if no functions found
|
|
499
|
+
|
|
500
|
+
### Current Limitations
|
|
501
|
+
|
|
502
|
+
- **Route-Specific Only**: No global middleware support
|
|
503
|
+
- **Basic Error Handling**: Generic 500 responses for all middleware failures
|
|
504
|
+
- **No Dependency Injection**: Middleware cannot access IoC container
|
|
505
|
+
- **Simple Path Resolution**: No middleware aliases or advanced routing
|
|
506
|
+
- **No Caching**: Middleware modules imported on every request
|
|
507
|
+
- **Limited Context**: No framework context or shared state between middleware
|