@objectstack/runtime 0.6.1 → 0.7.1

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.
@@ -0,0 +1,220 @@
1
+ import { Middleware, IHttpRequest, IHttpResponse } from '@objectstack/core';
2
+ import { MiddlewareConfig, MiddlewareType } from '@objectstack/spec/system';
3
+
4
+ /**
5
+ * Middleware Entry
6
+ * Internal representation of registered middleware
7
+ */
8
+ interface MiddlewareEntry {
9
+ name: string;
10
+ type: MiddlewareType;
11
+ middleware: Middleware;
12
+ order: number;
13
+ enabled: boolean;
14
+ paths?: {
15
+ include?: string[];
16
+ exclude?: string[];
17
+ };
18
+ }
19
+
20
+ /**
21
+ * MiddlewareManager
22
+ *
23
+ * Manages middleware registration, ordering, and execution.
24
+ * Provides fine-grained control over middleware chains with:
25
+ * - Execution order management
26
+ * - Path-based filtering
27
+ * - Enable/disable individual middleware
28
+ * - Middleware categorization by type
29
+ *
30
+ * @example
31
+ * const manager = new MiddlewareManager();
32
+ *
33
+ * // Register middleware with configuration
34
+ * manager.register({
35
+ * name: 'auth',
36
+ * type: 'authentication',
37
+ * order: 10,
38
+ * paths: { exclude: ['/health', '/metrics'] }
39
+ * }, authMiddleware);
40
+ *
41
+ * // Get sorted middleware chain
42
+ * const chain = manager.getMiddlewareChain();
43
+ * chain.forEach(mw => server.use(mw));
44
+ */
45
+ export class MiddlewareManager {
46
+ private middlewares: Map<string, MiddlewareEntry>;
47
+
48
+ constructor() {
49
+ this.middlewares = new Map();
50
+ }
51
+
52
+ /**
53
+ * Register middleware with configuration
54
+ * @param config - Middleware configuration
55
+ * @param middleware - Middleware function
56
+ */
57
+ register(config: MiddlewareConfig, middleware: Middleware): void {
58
+ const entry: MiddlewareEntry = {
59
+ name: config.name,
60
+ type: config.type,
61
+ middleware,
62
+ order: config.order ?? 100,
63
+ enabled: config.enabled ?? true,
64
+ paths: config.paths,
65
+ };
66
+
67
+ this.middlewares.set(config.name, entry);
68
+ }
69
+
70
+ /**
71
+ * Unregister middleware by name
72
+ * @param name - Middleware name
73
+ */
74
+ unregister(name: string): void {
75
+ this.middlewares.delete(name);
76
+ }
77
+
78
+ /**
79
+ * Enable middleware by name
80
+ * @param name - Middleware name
81
+ */
82
+ enable(name: string): void {
83
+ const entry = this.middlewares.get(name);
84
+ if (entry) {
85
+ entry.enabled = true;
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Disable middleware by name
91
+ * @param name - Middleware name
92
+ */
93
+ disable(name: string): void {
94
+ const entry = this.middlewares.get(name);
95
+ if (entry) {
96
+ entry.enabled = false;
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Get middleware entry by name
102
+ * @param name - Middleware name
103
+ */
104
+ get(name: string): MiddlewareEntry | undefined {
105
+ return this.middlewares.get(name);
106
+ }
107
+
108
+ /**
109
+ * Get all middleware entries
110
+ */
111
+ getAll(): MiddlewareEntry[] {
112
+ return Array.from(this.middlewares.values());
113
+ }
114
+
115
+ /**
116
+ * Get middleware by type
117
+ * @param type - Middleware type
118
+ */
119
+ getByType(type: MiddlewareType): MiddlewareEntry[] {
120
+ return this.getAll().filter(entry => entry.type === type);
121
+ }
122
+
123
+ /**
124
+ * Get middleware chain sorted by order
125
+ * Returns only enabled middleware
126
+ */
127
+ getMiddlewareChain(): Middleware[] {
128
+ return this.getAll()
129
+ .filter(entry => entry.enabled)
130
+ .sort((a, b) => a.order - b.order)
131
+ .map(entry => entry.middleware);
132
+ }
133
+
134
+ /**
135
+ * Get middleware chain with path filtering
136
+ * @param path - Request path to match against
137
+ */
138
+ getMiddlewareChainForPath(path: string): Middleware[] {
139
+ return this.getAll()
140
+ .filter(entry => {
141
+ if (!entry.enabled) return false;
142
+
143
+ // Check path filters
144
+ if (entry.paths) {
145
+ // Check exclude patterns
146
+ if (entry.paths.exclude) {
147
+ const excluded = entry.paths.exclude.some(pattern =>
148
+ this.matchPath(path, pattern)
149
+ );
150
+ if (excluded) return false;
151
+ }
152
+
153
+ // Check include patterns (if specified)
154
+ if (entry.paths.include) {
155
+ const included = entry.paths.include.some(pattern =>
156
+ this.matchPath(path, pattern)
157
+ );
158
+ if (!included) return false;
159
+ }
160
+ }
161
+
162
+ return true;
163
+ })
164
+ .sort((a, b) => a.order - b.order)
165
+ .map(entry => entry.middleware);
166
+ }
167
+
168
+ /**
169
+ * Match path against pattern (simple glob matching)
170
+ * @param path - Request path
171
+ * @param pattern - Pattern to match (supports * wildcard)
172
+ */
173
+ private matchPath(path: string, pattern: string): boolean {
174
+ // Convert glob pattern to regex
175
+ const regexPattern = pattern
176
+ .replace(/\*/g, '.*')
177
+ .replace(/\?/g, '.');
178
+
179
+ const regex = new RegExp(`^${regexPattern}$`);
180
+ return regex.test(path);
181
+ }
182
+
183
+ /**
184
+ * Clear all middleware
185
+ */
186
+ clear(): void {
187
+ this.middlewares.clear();
188
+ }
189
+
190
+ /**
191
+ * Get middleware count
192
+ */
193
+ count(): number {
194
+ return this.middlewares.size;
195
+ }
196
+
197
+ /**
198
+ * Create a composite middleware from the chain
199
+ * This can be used to apply all middleware at once
200
+ */
201
+ createCompositeMiddleware(): Middleware {
202
+ const chain = this.getMiddlewareChain();
203
+
204
+ return async (req: IHttpRequest, res: IHttpResponse, next: () => void | Promise<void>) => {
205
+ let index = 0;
206
+
207
+ const executeNext = async (): Promise<void> => {
208
+ if (index >= chain.length) {
209
+ await next();
210
+ return;
211
+ }
212
+
213
+ const middleware = chain[index++];
214
+ await middleware(req, res, executeNext);
215
+ };
216
+
217
+ await executeNext();
218
+ };
219
+ }
220
+ }