@carno.js/core 1.0.3 → 1.0.4
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/dist/Carno.js +269 -466
- package/dist/bun/index.js +2 -2
- package/dist/bun/index.js.map +5 -4
- package/dist/context/Context.js +2 -1
- package/dist/context/Context.mjs +2 -1
- package/dist/utils/parseQuery.js +84 -0
- package/dist/utils/parseQuery.mjs +63 -0
- package/package.json +2 -2
- package/src/context/Context.ts +3 -1
- package/src/utils/parseQuery.ts +161 -0
package/dist/Carno.js
CHANGED
|
@@ -1,474 +1,277 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const NOT_FOUND_RESPONSE = new Response('Not Found', { status: 404 });
|
|
19
|
-
/**
|
|
20
|
-
* Pre-computed response - frozen and reused.
|
|
21
|
-
*/
|
|
22
|
-
const TEXT_OPTS = Object.freeze({
|
|
23
|
-
status: 200,
|
|
24
|
-
headers: { 'Content-Type': 'text/plain' }
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: !0 });
|
|
8
|
+
}, __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from == "object" || typeof from == "function")
|
|
10
|
+
for (let key of __getOwnPropNames(from))
|
|
11
|
+
!__hasOwnProp.call(to, key) && key !== except && __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
12
|
+
return to;
|
|
13
|
+
};
|
|
14
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: !0 }), mod);
|
|
15
|
+
var Carno_exports = {};
|
|
16
|
+
__export(Carno_exports, {
|
|
17
|
+
Carno: () => Carno
|
|
25
18
|
});
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
* - No function calls in hot path
|
|
39
|
-
*/
|
|
19
|
+
module.exports = __toCommonJS(Carno_exports);
|
|
20
|
+
var import_reflect_metadata = require("reflect-metadata"), import_metadata = require('./metadata.js'), import_JITCompiler = require('./compiler/JITCompiler.js'), import_Context = require('./context/Context.js'), import_Container = require('./container/Container.js'), import_CorsHandler = require('./cors/CorsHandler.js'), import_HttpException = require('./exceptions/HttpException.js'), import_ZodAdapter = require('./validation/ZodAdapter.js'), import_Lifecycle = require('./events/Lifecycle.js'), import_CacheService = require('./cache/CacheService.js'), import_DefaultRoutes = require('./DefaultRoutes.js'), import_ZodAdapter2 = require('./validation/ZodAdapter.js');
|
|
21
|
+
const NOT_FOUND_RESPONSE = new Response("Not Found", { status: 404 }), TEXT_OPTS = Object.freeze({
|
|
22
|
+
status: 200,
|
|
23
|
+
headers: { "Content-Type": "text/plain" }
|
|
24
|
+
}), JSON_OPTS = Object.freeze({
|
|
25
|
+
status: 200,
|
|
26
|
+
headers: { "Content-Type": "application/json" }
|
|
27
|
+
}), INTERNAL_ERROR_RESPONSE = new Response(
|
|
28
|
+
'{"statusCode":500,"message":"Internal Server Error"}',
|
|
29
|
+
{ status: 500, headers: { "Content-Type": "application/json" } }
|
|
30
|
+
);
|
|
40
31
|
class Carno {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
// Instance passed directly
|
|
73
|
-
else if (this.config.validation) {
|
|
74
|
-
this.validator = this.config.validation;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* Use a Carno plugin.
|
|
79
|
-
* Imports controllers, services and global middlewares from another Carno instance.
|
|
80
|
-
*/
|
|
81
|
-
use(plugin) {
|
|
82
|
-
// Import controllers from plugin
|
|
83
|
-
if (plugin._controllers.length > 0) {
|
|
84
|
-
this._controllers.push(...plugin._controllers);
|
|
85
|
-
}
|
|
86
|
-
// Import services from plugin exports
|
|
87
|
-
for (const exported of plugin.config.exports || []) {
|
|
88
|
-
const existingService = this.findServiceInPlugin(plugin, exported);
|
|
89
|
-
const serviceToAdd = this.shouldCloneService(existingService)
|
|
90
|
-
? { ...existingService }
|
|
91
|
-
: exported;
|
|
92
|
-
this._services.push(serviceToAdd);
|
|
93
|
-
}
|
|
94
|
-
// Import services registered via .services() on the plugin
|
|
95
|
-
if (plugin._services.length > 0) {
|
|
96
|
-
this._services.push(...plugin._services);
|
|
97
|
-
}
|
|
98
|
-
// Import global middlewares
|
|
99
|
-
if (plugin.config.globalMiddlewares) {
|
|
100
|
-
this._middlewares.push(...plugin.config.globalMiddlewares);
|
|
101
|
-
}
|
|
102
|
-
// Import middlewares registered via .middlewares() on the plugin
|
|
103
|
-
if (plugin._middlewares.length > 0) {
|
|
104
|
-
this._middlewares.push(...plugin._middlewares);
|
|
105
|
-
}
|
|
106
|
-
return this;
|
|
107
|
-
}
|
|
108
|
-
findServiceInPlugin(plugin, exported) {
|
|
109
|
-
return plugin._services.find(s => this.getServiceToken(s) === this.getServiceToken(exported));
|
|
110
|
-
}
|
|
111
|
-
getServiceToken(service) {
|
|
112
|
-
return service?.token || service;
|
|
113
|
-
}
|
|
114
|
-
shouldCloneService(service) {
|
|
115
|
-
return !!(service?.useValue !== undefined || service?.useClass);
|
|
116
|
-
}
|
|
117
|
-
/**
|
|
118
|
-
* Register one or more services/providers.
|
|
119
|
-
*/
|
|
120
|
-
services(serviceClass) {
|
|
121
|
-
const items = Array.isArray(serviceClass) ? serviceClass : [serviceClass];
|
|
122
|
-
this._services.push(...items);
|
|
123
|
-
return this;
|
|
124
|
-
}
|
|
125
|
-
/**
|
|
126
|
-
* Register one or more global middlewares.
|
|
127
|
-
*/
|
|
128
|
-
middlewares(handler) {
|
|
129
|
-
const items = Array.isArray(handler) ? handler : [handler];
|
|
130
|
-
this._middlewares.push(...items);
|
|
131
|
-
return this;
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* Register one or more controllers.
|
|
135
|
-
*/
|
|
136
|
-
controllers(controllerClass) {
|
|
137
|
-
const items = Array.isArray(controllerClass) ? controllerClass : [controllerClass];
|
|
138
|
-
this._controllers.push(...items);
|
|
139
|
-
return this;
|
|
140
|
-
}
|
|
141
|
-
/**
|
|
142
|
-
* Get a service instance from the container.
|
|
143
|
-
*/
|
|
144
|
-
get(token) {
|
|
145
|
-
return this.container.get(token);
|
|
146
|
-
}
|
|
147
|
-
listen(port = 3000) {
|
|
148
|
-
this.bootstrap();
|
|
149
|
-
this.compileRoutes();
|
|
150
|
-
// All routes go through Bun's native SIMD-accelerated router
|
|
151
|
-
const config = {
|
|
152
|
-
port,
|
|
153
|
-
fetch: this.handleNotFound.bind(this),
|
|
154
|
-
error: this.handleError.bind(this),
|
|
155
|
-
routes: {
|
|
156
|
-
...DefaultRoutes_1.DEFAULT_STATIC_ROUTES,
|
|
157
|
-
...this.routes
|
|
158
|
-
}
|
|
159
|
-
};
|
|
160
|
-
this.server = Bun.serve(config);
|
|
161
|
-
// Execute BOOT hooks after server is ready
|
|
162
|
-
if (this.hasBootHooks) {
|
|
163
|
-
this.executeLifecycleHooks(Lifecycle_1.EventType.BOOT);
|
|
164
|
-
}
|
|
165
|
-
// Register shutdown handlers
|
|
166
|
-
if (this.hasShutdownHooks) {
|
|
167
|
-
this.registerShutdownHandlers();
|
|
168
|
-
}
|
|
169
|
-
if (!this.config.disableStartupLog) {
|
|
170
|
-
console.log(`Carno running on port ${port}`);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
bootstrap() {
|
|
174
|
-
// Cache lifecycle event flags
|
|
175
|
-
this.hasInitHooks = (0, Lifecycle_1.hasEventHandlers)(Lifecycle_1.EventType.INIT);
|
|
176
|
-
this.hasBootHooks = (0, Lifecycle_1.hasEventHandlers)(Lifecycle_1.EventType.BOOT);
|
|
177
|
-
this.hasShutdownHooks = (0, Lifecycle_1.hasEventHandlers)(Lifecycle_1.EventType.SHUTDOWN);
|
|
178
|
-
// Register Container itself so it can be injected
|
|
179
|
-
this.container.register({
|
|
180
|
-
token: Container_1.Container,
|
|
181
|
-
useValue: this.container
|
|
182
|
-
});
|
|
183
|
-
// Always register CacheService (Memory by default)
|
|
184
|
-
const cacheConfig = typeof this.config.cache === 'object' ? this.config.cache : {};
|
|
185
|
-
this.container.register({
|
|
186
|
-
token: CacheService_1.CacheService,
|
|
187
|
-
useValue: new CacheService_1.CacheService(cacheConfig)
|
|
188
|
-
});
|
|
189
|
-
for (const service of this._services) {
|
|
190
|
-
this.container.register(service);
|
|
191
|
-
}
|
|
192
|
-
for (const ControllerClass of this._controllers) {
|
|
193
|
-
this.container.register(ControllerClass);
|
|
194
|
-
}
|
|
195
|
-
if (this.hasInitHooks) {
|
|
196
|
-
this.executeLifecycleHooks(Lifecycle_1.EventType.INIT);
|
|
197
|
-
}
|
|
198
|
-
for (const service of this._services) {
|
|
199
|
-
const token = typeof service === 'function' ? service : service.token;
|
|
200
|
-
const serviceConfig = typeof service === 'function' ? null : service;
|
|
201
|
-
if (!serviceConfig || serviceConfig.scope !== Container_1.Scope.REQUEST) {
|
|
202
|
-
this.container.get(token);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
compileRoutes() {
|
|
207
|
-
for (const ControllerClass of this._controllers) {
|
|
208
|
-
this.compileController(ControllerClass);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
compileController(ControllerClass, parentPath = '', inheritedMiddlewares = []) {
|
|
212
|
-
const meta = Reflect.getMetadata(metadata_1.CONTROLLER_META, ControllerClass) || { path: '' };
|
|
213
|
-
const basePath = parentPath + (meta.path || '');
|
|
214
|
-
const routes = Reflect.getMetadata(metadata_1.ROUTES_META, ControllerClass) || [];
|
|
215
|
-
const middlewares = Reflect.getMetadata(metadata_1.MIDDLEWARE_META, ControllerClass) || [];
|
|
216
|
-
const instance = this.container.get(ControllerClass);
|
|
217
|
-
// Extract controller-level middlewares (applied to all routes of this controller)
|
|
218
|
-
const controllerMiddlewares = middlewares
|
|
219
|
-
.filter(m => !m.target)
|
|
220
|
-
.map(m => m.handler);
|
|
221
|
-
// Combine inherited middlewares with this controller's middlewares
|
|
222
|
-
// This combined list is passed down to children and applied to current routes
|
|
223
|
-
const scopedMiddlewares = [...inheritedMiddlewares, ...controllerMiddlewares];
|
|
224
|
-
for (const route of routes) {
|
|
225
|
-
const fullPath = this.normalizePath(basePath + route.path);
|
|
226
|
-
const params = Reflect.getMetadata(metadata_1.PARAMS_META, ControllerClass, route.handlerName) || [];
|
|
227
|
-
// Middlewares specific to this route handler
|
|
228
|
-
const routeMiddlewares = middlewares
|
|
229
|
-
.filter(m => m.target === route.handlerName)
|
|
230
|
-
.map(m => m.handler);
|
|
231
|
-
// Get parameter types for validation
|
|
232
|
-
const paramTypes = Reflect.getMetadata('design:paramtypes', ControllerClass.prototype, route.handlerName) || [];
|
|
233
|
-
// Find Body param with DTO that has @Schema for validation
|
|
234
|
-
let bodyDtoType = null;
|
|
235
|
-
for (const param of params) {
|
|
236
|
-
if (param.type === 'body' && !param.key) {
|
|
237
|
-
const dtoType = paramTypes[param.index];
|
|
238
|
-
if (dtoType && this.validator?.hasValidation(dtoType)) {
|
|
239
|
-
bodyDtoType = dtoType;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
const compiled = (0, JITCompiler_1.compileHandler)(instance, route.handlerName, params);
|
|
244
|
-
const allMiddlewares = [
|
|
245
|
-
...(this.config.globalMiddlewares || []),
|
|
246
|
-
...this._middlewares,
|
|
247
|
-
...scopedMiddlewares,
|
|
248
|
-
...routeMiddlewares
|
|
249
|
-
];
|
|
250
|
-
// Pre-resolve class-based middlewares at compile time for maximum performance
|
|
251
|
-
const resolvedMiddlewares = allMiddlewares.map(m => this.resolveMiddleware(m));
|
|
252
|
-
const hasMiddlewares = resolvedMiddlewares.length > 0;
|
|
253
|
-
const method = route.method.toUpperCase();
|
|
254
|
-
// Static response - no function needed
|
|
255
|
-
if (compiled.isStatic && !hasMiddlewares) {
|
|
256
|
-
this.registerRoute(fullPath, method, this.createStaticResponse(compiled.staticValue));
|
|
257
|
-
}
|
|
258
|
-
else {
|
|
259
|
-
// Dynamic handler - compile to Bun-compatible function
|
|
260
|
-
this.registerRoute(fullPath, method, this.createHandler(compiled, params, resolvedMiddlewares, bodyDtoType));
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
// Compile child controllers with parent path and inherited middlewares
|
|
264
|
-
if (meta.children) {
|
|
265
|
-
for (const ChildController of meta.children) {
|
|
266
|
-
if (!this.container.has(ChildController)) {
|
|
267
|
-
this.container.register(ChildController);
|
|
268
|
-
}
|
|
269
|
-
this.compileController(ChildController, basePath, scopedMiddlewares);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
/**
|
|
274
|
-
* Register a route with Bun's native router format.
|
|
275
|
-
* Path: "/users/:id", Method: "GET", Handler: Function or Response
|
|
276
|
-
*/
|
|
277
|
-
registerRoute(path, method, handler) {
|
|
278
|
-
if (!this.routes[path]) {
|
|
279
|
-
this.routes[path] = {};
|
|
280
|
-
}
|
|
281
|
-
this.routes[path][method] = handler;
|
|
282
|
-
}
|
|
283
|
-
createStaticResponse(value) {
|
|
284
|
-
const isString = typeof value === 'string';
|
|
285
|
-
const body = isString ? value : JSON.stringify(value);
|
|
286
|
-
const opts = isString ? TEXT_OPTS : JSON_OPTS;
|
|
287
|
-
return new Response(body, opts);
|
|
288
|
-
}
|
|
289
|
-
createHandler(compiled, params, middlewares, bodyDtoType) {
|
|
290
|
-
const handler = compiled.fn;
|
|
291
|
-
const hasMiddlewares = middlewares.length > 0;
|
|
292
|
-
const hasParams = params.length > 0;
|
|
293
|
-
const applyCors = this.hasCors ? this.applyCors.bind(this) : null;
|
|
294
|
-
const validator = bodyDtoType ? this.validator : null;
|
|
295
|
-
const needsValidation = !!validator;
|
|
296
|
-
// Force middleware path when validation is needed
|
|
297
|
-
const hasMiddlewaresOrValidation = hasMiddlewares || needsValidation;
|
|
298
|
-
// No middlewares, no params - fastest path
|
|
299
|
-
if (!hasMiddlewaresOrValidation && !hasParams) {
|
|
300
|
-
if (compiled.isAsync) {
|
|
301
|
-
return async (req) => {
|
|
302
|
-
const ctx = new Context_1.Context(req);
|
|
303
|
-
const result = await handler(ctx);
|
|
304
|
-
const response = this.buildResponse(result);
|
|
305
|
-
return applyCors ? applyCors(response, req) : response;
|
|
306
|
-
};
|
|
307
|
-
}
|
|
308
|
-
return (req) => {
|
|
309
|
-
const ctx = new Context_1.Context(req);
|
|
310
|
-
const result = handler(ctx);
|
|
311
|
-
const response = this.buildResponse(result);
|
|
312
|
-
return applyCors ? applyCors(response, req) : response;
|
|
313
|
-
};
|
|
314
|
-
}
|
|
315
|
-
// With params - use Bun's native req.params
|
|
316
|
-
if (!hasMiddlewaresOrValidation && hasParams) {
|
|
317
|
-
if (compiled.isAsync) {
|
|
318
|
-
return async (req) => {
|
|
319
|
-
const ctx = new Context_1.Context(req, req.params);
|
|
320
|
-
const result = await handler(ctx);
|
|
321
|
-
const response = this.buildResponse(result);
|
|
322
|
-
return applyCors ? applyCors(response, req) : response;
|
|
323
|
-
};
|
|
324
|
-
}
|
|
325
|
-
return (req) => {
|
|
326
|
-
const ctx = new Context_1.Context(req, req.params);
|
|
327
|
-
const result = handler(ctx);
|
|
328
|
-
const response = this.buildResponse(result);
|
|
329
|
-
return applyCors ? applyCors(response, req) : response;
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
// With middlewares - full pipeline
|
|
333
|
-
return async (req) => {
|
|
334
|
-
const ctx = new Context_1.Context(req, req.params || {});
|
|
335
|
-
for (const middleware of middlewares) {
|
|
336
|
-
const result = await middleware(ctx);
|
|
337
|
-
if (result instanceof Response) {
|
|
338
|
-
return applyCors ? applyCors(result, req) : result;
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
// Validate body if validator is configured
|
|
342
|
-
if (validator && bodyDtoType) {
|
|
343
|
-
await ctx.parseBody();
|
|
344
|
-
validator.validateOrThrow(bodyDtoType, ctx.body);
|
|
345
|
-
}
|
|
346
|
-
const result = compiled.isAsync
|
|
347
|
-
? await handler(ctx)
|
|
348
|
-
: handler(ctx);
|
|
349
|
-
const response = this.buildResponse(result);
|
|
350
|
-
return applyCors ? applyCors(response, req) : response;
|
|
351
|
-
};
|
|
352
|
-
}
|
|
353
|
-
resolveMiddleware(middleware) {
|
|
354
|
-
// Check if it's a class with a handle method
|
|
355
|
-
if (typeof middleware === 'function' && middleware.prototype?.handle) {
|
|
356
|
-
// Instantiate via Container and bind the handle method
|
|
357
|
-
const instance = this.container.get(middleware);
|
|
358
|
-
return (ctx) => instance.handle(ctx, () => { });
|
|
359
|
-
}
|
|
360
|
-
// Already a function
|
|
361
|
-
return middleware;
|
|
362
|
-
}
|
|
363
|
-
/**
|
|
364
|
-
* Apply CORS headers to a response.
|
|
365
|
-
*/
|
|
366
|
-
applyCors(response, req) {
|
|
367
|
-
const origin = req.headers.get('origin');
|
|
368
|
-
if (origin && this.corsHandler) {
|
|
369
|
-
return this.corsHandler.apply(response, origin);
|
|
370
|
-
}
|
|
371
|
-
return response;
|
|
32
|
+
constructor(config = {}) {
|
|
33
|
+
this.config = config;
|
|
34
|
+
this._controllers = [];
|
|
35
|
+
this._services = [];
|
|
36
|
+
this._middlewares = [];
|
|
37
|
+
this.routes = {};
|
|
38
|
+
this.container = new import_Container.Container();
|
|
39
|
+
this.corsHandler = null;
|
|
40
|
+
this.hasCors = !1;
|
|
41
|
+
this.validator = null;
|
|
42
|
+
// Cached lifecycle event flags - checked once at startup
|
|
43
|
+
this.hasInitHooks = !1;
|
|
44
|
+
this.hasBootHooks = !1;
|
|
45
|
+
this.hasShutdownHooks = !1;
|
|
46
|
+
if (this.config.exports = this.config.exports || [], this.config.globalMiddlewares = this.config.globalMiddlewares || [], this.config.cors && (this.corsHandler = new import_CorsHandler.CorsHandler(this.config.cors), this.hasCors = !0), this.config.validation === void 0 || this.config.validation === !0)
|
|
47
|
+
this.validator = new import_ZodAdapter2.ZodAdapter();
|
|
48
|
+
else if (typeof this.config.validation == "function") {
|
|
49
|
+
const AdapterClass = this.config.validation;
|
|
50
|
+
this.validator = new AdapterClass();
|
|
51
|
+
} else this.config.validation && (this.validator = this.config.validation);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Use a Carno plugin.
|
|
55
|
+
* Imports controllers, services and global middlewares from another Carno instance.
|
|
56
|
+
*/
|
|
57
|
+
use(plugin) {
|
|
58
|
+
plugin._controllers.length > 0 && this._controllers.push(...plugin._controllers);
|
|
59
|
+
for (const exported of plugin.config.exports || []) {
|
|
60
|
+
const existingService = this.findServiceInPlugin(plugin, exported), serviceToAdd = this.shouldCloneService(existingService) ? { ...existingService } : exported;
|
|
61
|
+
this._services.push(serviceToAdd);
|
|
372
62
|
}
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
63
|
+
return plugin._services.length > 0 && this._services.push(...plugin._services), plugin.config.globalMiddlewares && this._middlewares.push(...plugin.config.globalMiddlewares), plugin._middlewares.length > 0 && this._middlewares.push(...plugin._middlewares), this;
|
|
64
|
+
}
|
|
65
|
+
findServiceInPlugin(plugin, exported) {
|
|
66
|
+
return plugin._services.find(
|
|
67
|
+
(s) => this.getServiceToken(s) === this.getServiceToken(exported)
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
getServiceToken(service) {
|
|
71
|
+
return service?.token || service;
|
|
72
|
+
}
|
|
73
|
+
shouldCloneService(service) {
|
|
74
|
+
return !!(service?.useValue !== void 0 || service?.useClass);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Register one or more services/providers.
|
|
78
|
+
*/
|
|
79
|
+
services(serviceClass) {
|
|
80
|
+
const items = Array.isArray(serviceClass) ? serviceClass : [serviceClass];
|
|
81
|
+
return this._services.push(...items), this;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Register one or more global middlewares.
|
|
85
|
+
*/
|
|
86
|
+
middlewares(handler) {
|
|
87
|
+
const items = Array.isArray(handler) ? handler : [handler];
|
|
88
|
+
return this._middlewares.push(...items), this;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Register one or more controllers.
|
|
92
|
+
*/
|
|
93
|
+
controllers(controllerClass) {
|
|
94
|
+
const items = Array.isArray(controllerClass) ? controllerClass : [controllerClass];
|
|
95
|
+
return this._controllers.push(...items), this;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Get a service instance from the container.
|
|
99
|
+
*/
|
|
100
|
+
get(token) {
|
|
101
|
+
return this.container.get(token);
|
|
102
|
+
}
|
|
103
|
+
listen(port = 3e3) {
|
|
104
|
+
this.bootstrap(), this.compileRoutes();
|
|
105
|
+
const config = {
|
|
106
|
+
port,
|
|
107
|
+
fetch: this.handleNotFound.bind(this),
|
|
108
|
+
error: this.handleError.bind(this),
|
|
109
|
+
routes: {
|
|
110
|
+
...import_DefaultRoutes.DEFAULT_STATIC_ROUTES,
|
|
111
|
+
...this.routes
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
this.server = Bun.serve(config), this.hasBootHooks && this.executeLifecycleHooks(import_Lifecycle.EventType.BOOT), this.hasShutdownHooks && this.registerShutdownHandlers(), this.config.disableStartupLog || console.log(`Carno running on port ${port}`);
|
|
115
|
+
}
|
|
116
|
+
bootstrap() {
|
|
117
|
+
this.hasInitHooks = (0, import_Lifecycle.hasEventHandlers)(import_Lifecycle.EventType.INIT), this.hasBootHooks = (0, import_Lifecycle.hasEventHandlers)(import_Lifecycle.EventType.BOOT), this.hasShutdownHooks = (0, import_Lifecycle.hasEventHandlers)(import_Lifecycle.EventType.SHUTDOWN), this.container.register({
|
|
118
|
+
token: import_Container.Container,
|
|
119
|
+
useValue: this.container
|
|
120
|
+
});
|
|
121
|
+
const cacheConfig = typeof this.config.cache == "object" ? this.config.cache : {};
|
|
122
|
+
this.container.register({
|
|
123
|
+
token: import_CacheService.CacheService,
|
|
124
|
+
useValue: new import_CacheService.CacheService(cacheConfig)
|
|
125
|
+
});
|
|
126
|
+
for (const service of this._services)
|
|
127
|
+
this.container.register(service);
|
|
128
|
+
for (const ControllerClass of this._controllers)
|
|
129
|
+
this.container.register(ControllerClass);
|
|
130
|
+
this.hasInitHooks && this.executeLifecycleHooks(import_Lifecycle.EventType.INIT);
|
|
131
|
+
for (const service of this._services) {
|
|
132
|
+
const token = typeof service == "function" ? service : service.token, serviceConfig = typeof service == "function" ? null : service;
|
|
133
|
+
(!serviceConfig || serviceConfig.scope !== import_Container.Scope.REQUEST) && this.container.get(token);
|
|
409
134
|
}
|
|
410
|
-
|
|
411
|
-
|
|
135
|
+
}
|
|
136
|
+
compileRoutes() {
|
|
137
|
+
for (const ControllerClass of this._controllers)
|
|
138
|
+
this.compileController(ControllerClass);
|
|
139
|
+
}
|
|
140
|
+
compileController(ControllerClass, parentPath = "", inheritedMiddlewares = []) {
|
|
141
|
+
const meta = Reflect.getMetadata(import_metadata.CONTROLLER_META, ControllerClass) || { path: "" }, basePath = parentPath + (meta.path || ""), routes = Reflect.getMetadata(import_metadata.ROUTES_META, ControllerClass) || [], middlewares = Reflect.getMetadata(import_metadata.MIDDLEWARE_META, ControllerClass) || [], instance = this.container.get(ControllerClass), controllerMiddlewares = middlewares.filter((m) => !m.target).map((m) => m.handler), scopedMiddlewares = [...inheritedMiddlewares, ...controllerMiddlewares];
|
|
142
|
+
for (const route of routes) {
|
|
143
|
+
const fullPath = this.normalizePath(basePath + route.path), params = Reflect.getMetadata(import_metadata.PARAMS_META, ControllerClass, route.handlerName) || [], routeMiddlewares = middlewares.filter((m) => m.target === route.handlerName).map((m) => m.handler), paramTypes = Reflect.getMetadata("design:paramtypes", ControllerClass.prototype, route.handlerName) || [];
|
|
144
|
+
let bodyDtoType = null;
|
|
145
|
+
for (const param of params)
|
|
146
|
+
if (param.type === "body" && !param.key) {
|
|
147
|
+
const dtoType = paramTypes[param.index];
|
|
148
|
+
dtoType && this.validator?.hasValidation(dtoType) && (bodyDtoType = dtoType);
|
|
149
|
+
}
|
|
150
|
+
const compiled = (0, import_JITCompiler.compileHandler)(instance, route.handlerName, params), resolvedMiddlewares = [
|
|
151
|
+
...this.config.globalMiddlewares || [],
|
|
152
|
+
...this._middlewares,
|
|
153
|
+
...scopedMiddlewares,
|
|
154
|
+
...routeMiddlewares
|
|
155
|
+
].map((m) => this.resolveMiddleware(m)), hasMiddlewares = resolvedMiddlewares.length > 0, method = route.method.toUpperCase();
|
|
156
|
+
compiled.isStatic && !hasMiddlewares ? this.registerRoute(fullPath, method, this.createStaticResponse(compiled.staticValue)) : this.registerRoute(fullPath, method, this.createHandler(compiled, params, resolvedMiddlewares, bodyDtoType));
|
|
412
157
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
158
|
+
if (meta.children)
|
|
159
|
+
for (const ChildController of meta.children)
|
|
160
|
+
this.container.has(ChildController) || this.container.register(ChildController), this.compileController(ChildController, basePath, scopedMiddlewares);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Register a route with Bun's native router format.
|
|
164
|
+
* Path: "/users/:id", Method: "GET", Handler: Function or Response
|
|
165
|
+
*/
|
|
166
|
+
registerRoute(path, method, handler) {
|
|
167
|
+
this.routes[path] || (this.routes[path] = {}), this.routes[path][method] = handler;
|
|
168
|
+
}
|
|
169
|
+
createStaticResponse(value) {
|
|
170
|
+
const isString = typeof value == "string", body = isString ? value : JSON.stringify(value), opts = isString ? TEXT_OPTS : JSON_OPTS;
|
|
171
|
+
return new Response(body, opts);
|
|
172
|
+
}
|
|
173
|
+
createHandler(compiled, params, middlewares, bodyDtoType) {
|
|
174
|
+
const handler = compiled.fn, hasMiddlewares = middlewares.length > 0, hasParams = params.length > 0, applyCors = this.hasCors ? this.applyCors.bind(this) : null, validator = bodyDtoType ? this.validator : null, hasMiddlewaresOrValidation = hasMiddlewares || !!validator;
|
|
175
|
+
return !hasMiddlewaresOrValidation && !hasParams ? compiled.isAsync ? async (req) => {
|
|
176
|
+
const ctx = new import_Context.Context(req), result = await handler(ctx), response = this.buildResponse(result);
|
|
177
|
+
return applyCors ? applyCors(response, req) : response;
|
|
178
|
+
} : (req) => {
|
|
179
|
+
const ctx = new import_Context.Context(req), result = handler(ctx), response = this.buildResponse(result);
|
|
180
|
+
return applyCors ? applyCors(response, req) : response;
|
|
181
|
+
} : !hasMiddlewaresOrValidation && hasParams ? compiled.isAsync ? async (req) => {
|
|
182
|
+
const ctx = new import_Context.Context(req, req.params), result = await handler(ctx), response = this.buildResponse(result);
|
|
183
|
+
return applyCors ? applyCors(response, req) : response;
|
|
184
|
+
} : (req) => {
|
|
185
|
+
const ctx = new import_Context.Context(req, req.params), result = handler(ctx), response = this.buildResponse(result);
|
|
186
|
+
return applyCors ? applyCors(response, req) : response;
|
|
187
|
+
} : async (req) => {
|
|
188
|
+
const ctx = new import_Context.Context(req, req.params || {});
|
|
189
|
+
for (const middleware of middlewares) {
|
|
190
|
+
const result2 = await middleware(ctx);
|
|
191
|
+
if (result2 instanceof Response)
|
|
192
|
+
return applyCors ? applyCors(result2, req) : result2;
|
|
193
|
+
}
|
|
194
|
+
validator && bodyDtoType && (await ctx.parseBody(), validator.validateOrThrow(bodyDtoType, ctx.body));
|
|
195
|
+
const result = compiled.isAsync ? await handler(ctx) : handler(ctx), response = this.buildResponse(result);
|
|
196
|
+
return applyCors ? applyCors(response, req) : response;
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
resolveMiddleware(middleware) {
|
|
200
|
+
if (typeof middleware == "function" && middleware.prototype?.handle) {
|
|
201
|
+
const instance = this.container.get(middleware);
|
|
202
|
+
return (ctx) => instance.handle(ctx, () => {
|
|
203
|
+
});
|
|
437
204
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
catch (err) {
|
|
457
|
-
console.error(`Error in ${type} hook ${handler.methodName}:`, err);
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
/**
|
|
462
|
-
* Register SIGTERM/SIGINT handlers for graceful shutdown.
|
|
463
|
-
*/
|
|
464
|
-
registerShutdownHandlers() {
|
|
465
|
-
const shutdown = () => {
|
|
466
|
-
this.executeLifecycleHooks(Lifecycle_1.EventType.SHUTDOWN);
|
|
467
|
-
this.stop();
|
|
468
|
-
process.exit(0);
|
|
469
|
-
};
|
|
470
|
-
process.on('SIGTERM', shutdown);
|
|
471
|
-
process.on('SIGINT', shutdown);
|
|
205
|
+
return middleware;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Apply CORS headers to a response.
|
|
209
|
+
*/
|
|
210
|
+
applyCors(response, req) {
|
|
211
|
+
const origin = req.headers.get("origin");
|
|
212
|
+
return origin && this.corsHandler ? this.corsHandler.apply(response, origin) : response;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Fallback handler - only called for unmatched routes.
|
|
216
|
+
* All matched routes go through Bun's native router.
|
|
217
|
+
*/
|
|
218
|
+
handleNotFound(req) {
|
|
219
|
+
if (this.hasCors && req.method === "OPTIONS") {
|
|
220
|
+
const origin = req.headers.get("origin");
|
|
221
|
+
if (origin)
|
|
222
|
+
return this.corsHandler.preflight(origin);
|
|
472
223
|
}
|
|
224
|
+
return NOT_FOUND_RESPONSE;
|
|
225
|
+
}
|
|
226
|
+
buildResponse(result) {
|
|
227
|
+
return result instanceof Response ? result : typeof result == "string" ? new Response(result, TEXT_OPTS) : result === void 0 ? new Response(null, { status: 204 }) : Response.json(result);
|
|
228
|
+
}
|
|
229
|
+
normalizePath(path) {
|
|
230
|
+
return path.startsWith("/") || (path = "/" + path), path !== "/" && path.endsWith("/") && (path = path.slice(0, -1)), path.replace(/\/+/g, "/");
|
|
231
|
+
}
|
|
232
|
+
hasParams(path) {
|
|
233
|
+
return path.includes(":") || path.includes("*");
|
|
234
|
+
}
|
|
235
|
+
stop() {
|
|
236
|
+
this.server?.stop?.();
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Error handler for Bun.serve.
|
|
240
|
+
* Converts exceptions to proper HTTP responses.
|
|
241
|
+
*/
|
|
242
|
+
handleError(error) {
|
|
243
|
+
let response;
|
|
244
|
+
return error instanceof import_HttpException.HttpException || error instanceof import_ZodAdapter.ValidationException ? response = error.toResponse() : (console.error("Unhandled error:", error), response = INTERNAL_ERROR_RESPONSE), this.hasCors && this.corsHandler ? this.corsHandler.apply(response, "*") : response;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Execute lifecycle hooks for a specific event type.
|
|
248
|
+
*/
|
|
249
|
+
executeLifecycleHooks(type) {
|
|
250
|
+
const handlers = (0, import_Lifecycle.getEventHandlers)(type);
|
|
251
|
+
for (const handler of handlers)
|
|
252
|
+
try {
|
|
253
|
+
const instance = this.container.has(handler.target) ? this.container.get(handler.target) : null;
|
|
254
|
+
if (instance && typeof instance[handler.methodName] == "function") {
|
|
255
|
+
const result = instance[handler.methodName]();
|
|
256
|
+
result instanceof Promise && result.catch(
|
|
257
|
+
(err) => console.error(`Error in ${type} hook ${handler.methodName}:`, err)
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
} catch (err) {
|
|
261
|
+
console.error(`Error in ${type} hook ${handler.methodName}:`, err);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Register SIGTERM/SIGINT handlers for graceful shutdown.
|
|
266
|
+
*/
|
|
267
|
+
registerShutdownHandlers() {
|
|
268
|
+
const shutdown = () => {
|
|
269
|
+
this.executeLifecycleHooks(import_Lifecycle.EventType.SHUTDOWN), this.stop(), process.exit(0);
|
|
270
|
+
};
|
|
271
|
+
process.on("SIGTERM", shutdown), process.on("SIGINT", shutdown);
|
|
272
|
+
}
|
|
473
273
|
}
|
|
474
|
-
|
|
274
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
275
|
+
0 && (module.exports = {
|
|
276
|
+
Carno
|
|
277
|
+
});
|