@carno.js/core 0.2.9 → 0.2.11

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.d.ts CHANGED
@@ -12,6 +12,7 @@ export interface ApplicationConfig<TAdapter extends ValidatorAdapterConstructor
12
12
  providers?: any[];
13
13
  cors?: CorsConfig;
14
14
  globalMiddlewares?: any[];
15
+ disableStartupLog?: boolean;
15
16
  }
16
17
  export declare class Carno<TAdapter extends ValidatorAdapterConstructor = ValidatorAdapterConstructor> {
17
18
  config: ApplicationConfig<TAdapter>;
@@ -20,6 +21,8 @@ export declare class Carno<TAdapter extends ValidatorAdapterConstructor = Valida
20
21
  private corsCache?;
21
22
  private readonly emptyLocals;
22
23
  private validatorAdapter;
24
+ private corsEnabled;
25
+ private hasOnRequestHook;
23
26
  private fetch;
24
27
  private server;
25
28
  constructor(config?: ApplicationConfig<TAdapter>);
@@ -53,15 +56,16 @@ export declare class Carno<TAdapter extends ValidatorAdapterConstructor = Valida
53
56
  getHttpServer(): Server<any>;
54
57
  getInjector(): import("./container").InjectorService;
55
58
  private createHttpServer;
56
- private fetcher;
59
+ private fetcherAsync;
57
60
  private catcher;
58
61
  private bootstrapApplication;
59
62
  private handleShutdownHook;
60
63
  private closeHttpServer;
61
64
  private exitProcess;
62
65
  private reportHookFailure;
66
+ private resolveLogger;
63
67
  private isCorsEnabled;
64
- private isOriginAllowed;
68
+ private errorResponse;
65
69
  private handlePreflightRequest;
66
70
  private applyCorsHeaders;
67
71
  close(closeActiveConnections?: boolean): void;
package/dist/Carno.js CHANGED
@@ -20,42 +20,56 @@ const HttpException_1 = require("./exceptions/HttpException");
20
20
  const RouteExecutor_1 = require("./route/RouteExecutor");
21
21
  const memoirist_1 = __importDefault(require("./route/memoirist"));
22
22
  const CompiledRoute_1 = require("./route/CompiledRoute");
23
- const FastPathExecutor_1 = require("./route/FastPathExecutor");
24
23
  const logger_service_1 = require("./services/logger.service");
25
- const parseUrl = require("parseurl-fast");
26
- // todo: change console.log for LoggerService.
24
+ const METHOD_MAP = {
25
+ GET: 'get',
26
+ POST: 'post',
27
+ PUT: 'put',
28
+ DELETE: 'delete',
29
+ PATCH: 'patch',
30
+ HEAD: 'head',
31
+ OPTIONS: 'options'
32
+ };
27
33
  class Carno {
28
34
  constructor(config = {}) {
29
35
  this.config = config;
30
36
  this.router = new memoirist_1.default();
31
37
  this.injector = (0, createInjector_1.createInjector)();
32
38
  this.emptyLocals = new LocalsContainer_1.LocalsContainer();
33
- this.fetch = async (request, server) => {
34
- try {
35
- return await this.fetcher(request, server);
36
- }
37
- catch (error) {
38
- if (error instanceof HttpException_1.HttpException) {
39
- let response = new Response(JSON.stringify({
40
- message: error.getResponse(),
41
- statusCode: error.getStatus(),
42
- }), {
43
- status: error.statusCode,
44
- headers: { "Content-Type": "application/json" },
45
- });
46
- if (this.isCorsEnabled()) {
47
- const origin = request.headers.get("origin");
48
- if (origin && this.isOriginAllowed(origin)) {
49
- response = this.applyCorsHeaders(response, origin);
50
- }
51
- }
52
- return response;
39
+ this.corsEnabled = false;
40
+ this.hasOnRequestHook = false;
41
+ this.fetch = (request, server) => {
42
+ const method = request.method;
43
+ if (this.corsEnabled) {
44
+ const origin = request.headers.get("origin");
45
+ if (method === "OPTIONS" && origin) {
46
+ return this.handlePreflightRequest(request);
53
47
  }
54
- throw error;
55
48
  }
49
+ const url = request.url;
50
+ const startIndex = url.indexOf('/', 12);
51
+ const queryIndex = url.indexOf('?', startIndex);
52
+ const pathname = queryIndex === -1
53
+ ? (startIndex === -1 ? '/' : url.slice(startIndex))
54
+ : url.slice(startIndex, queryIndex);
55
+ const methodLower = METHOD_MAP[method] || method.toLowerCase();
56
+ const route = this.router.find(methodLower, pathname);
57
+ if (!route) {
58
+ return this.errorResponse(request, "Method not allowed", 404);
59
+ }
60
+ const compiled = route.store;
61
+ const isCompiledRoute = compiled.routeType !== undefined;
62
+ const isSimpleRoute = isCompiledRoute && compiled.routeType === CompiledRoute_1.RouteType.SIMPLE;
63
+ const isGetOrHead = method === 'GET' || method === 'HEAD';
64
+ const hasQuery = queryIndex !== -1;
65
+ if (isSimpleRoute && isGetOrHead && !this.corsEnabled && !hasQuery && !compiled.isAsync) {
66
+ const context = Context_1.Context.createFastContext(request, route.params);
67
+ return compiled.boundHandler(context);
68
+ }
69
+ return this.fetcherAsync(request, server, route, compiled, isSimpleRoute, hasQuery, queryIndex, url);
56
70
  };
57
71
  this.catcher = (error) => {
58
- console.error("Unhandled error:", error);
72
+ this.resolveLogger().error("Unhandled error", error);
59
73
  return new Response("Internal Server Error", { status: 500 });
60
74
  };
61
75
  this.validatorAdapter = this.resolveValidatorAdapter();
@@ -163,6 +177,8 @@ class Carno {
163
177
  (0, ValidationCache_1.setValidatorAdapter)(this.validatorAdapter);
164
178
  this.loadProvidersAndControllers();
165
179
  await this.injector.loadModule((0, createContainer_1.createContainer)(), this.config, this.router, this.validatorAdapter);
180
+ this.corsEnabled = !!this.config.cors;
181
+ this.hasOnRequestHook = this.injector.hasOnRequestHook();
166
182
  }
167
183
  async listen(port = 3000) {
168
184
  this.registerShutdownHandlers();
@@ -171,7 +187,7 @@ class Carno {
171
187
  }
172
188
  registerShutdownHandlers() {
173
189
  const shutdown = async (signal) => {
174
- console.log(`\nReceived ${signal}, starting graceful shutdown...`);
190
+ this.resolveLogger().info(`Received ${signal}, starting graceful shutdown...`);
175
191
  await this.handleShutdownHook();
176
192
  };
177
193
  node_process_1.default.on("SIGTERM", () => shutdown("SIGTERM"));
@@ -185,47 +201,48 @@ class Carno {
185
201
  }
186
202
  createHttpServer(port) {
187
203
  this.server = Bun.serve({ port, fetch: this.fetch, error: this.catcher });
188
- console.log(`Server running on port ${port}`);
204
+ if (!this.config.disableStartupLog) {
205
+ this.resolveLogger().info(`Server running on port ${port}`);
206
+ }
189
207
  }
190
- async fetcher(request, server) {
191
- if (this.isCorsEnabled()) {
192
- const origin = request.headers.get("origin");
193
- if (request.method === "OPTIONS" && origin) {
194
- return this.handlePreflightRequest(request);
208
+ async fetcherAsync(request, server, route, compiled, isSimpleRoute, hasQuery, queryIndex, url) {
209
+ try {
210
+ let response;
211
+ const query = hasQuery ? url.slice(queryIndex + 1) : undefined;
212
+ const context = Context_1.Context.createFromRequestSync({ query }, request, server);
213
+ context.param = route.params;
214
+ if (isSimpleRoute) {
215
+ response = compiled.isAsync
216
+ ? await compiled.boundHandler(context)
217
+ : compiled.boundHandler(context);
195
218
  }
196
- }
197
- const urlParsed = parseUrl(request);
198
- const routePath = this.discoverRoutePath(urlParsed);
199
- const route = this.router.find(request.method.toLowerCase(), routePath);
200
- if (!route) {
201
- throw new HttpException_1.HttpException("Method not allowed", 404);
202
- }
203
- const compiled = route.store;
204
- const context = Context_1.Context.createFromRequestSync(urlParsed, request, server);
205
- context.param = route.params;
206
- let response;
207
- const isCompiledRoute = compiled.routeType !== undefined;
208
- if (isCompiledRoute && compiled.routeType === CompiledRoute_1.RouteType.SIMPLE) {
209
- const result = await (0, FastPathExecutor_1.executeSimpleRoute)(compiled, context);
210
- response = RouteExecutor_1.RouteExecutor.mountResponse(result, context);
211
- }
212
- else {
213
- const needsLocalsContainer = isCompiledRoute
214
- ? compiled.needsLocalsContainer
215
- : true;
216
- const locals = this.resolveLocalsContainer(needsLocalsContainer, context);
217
- if (this.injector.hasOnRequestHook()) {
218
- await this.injector.callHook(on_event_1.EventType.OnRequest, { context });
219
+ else {
220
+ const needsLocalsContainer = compiled.routeType !== undefined
221
+ ? compiled.needsLocalsContainer
222
+ : true;
223
+ const locals = this.resolveLocalsContainer(needsLocalsContainer, context);
224
+ if (this.hasOnRequestHook) {
225
+ await this.injector.callHook(on_event_1.EventType.OnRequest, { context });
226
+ }
227
+ response = await RouteExecutor_1.RouteExecutor.executeRoute(compiled, this.injector, context, locals);
228
+ }
229
+ if (this.corsEnabled) {
230
+ const origin = request.headers.get("origin");
231
+ if (origin && this.corsCache.isOriginAllowed(origin)) {
232
+ if (!(response instanceof Response)) {
233
+ response = RouteExecutor_1.RouteExecutor.mountResponse(response, context);
234
+ }
235
+ return this.applyCorsHeaders(response, origin);
236
+ }
219
237
  }
220
- response = await RouteExecutor_1.RouteExecutor.executeRoute(compiled, this.injector, context, locals);
238
+ return response;
221
239
  }
222
- if (this.isCorsEnabled()) {
223
- const origin = request.headers.get("origin");
224
- if (origin && this.isOriginAllowed(origin)) {
225
- response = this.applyCorsHeaders(response, origin);
240
+ catch (error) {
241
+ if (error instanceof HttpException_1.HttpException) {
242
+ return this.errorResponse(request, error.getResponse(), error.getStatus());
226
243
  }
244
+ throw error;
227
245
  }
228
- return response;
229
246
  }
230
247
  async bootstrapApplication() {
231
248
  try {
@@ -248,42 +265,44 @@ class Carno {
248
265
  }
249
266
  closeHttpServer() {
250
267
  if (this.server) {
251
- console.log("Closing HTTP server...");
268
+ this.resolveLogger().info("Closing HTTP server...");
252
269
  this.server.stop(true);
253
270
  }
254
271
  }
255
272
  exitProcess(code = 0) {
256
- console.log("Shutdown complete.");
273
+ this.resolveLogger().info("Shutdown complete.");
257
274
  node_process_1.default.exit(code);
258
275
  }
259
276
  reportHookFailure(event, error) {
260
- console.error(`Lifecycle hook ${event} failed`, error);
277
+ this.resolveLogger().error(`Lifecycle hook ${event} failed`, error);
278
+ }
279
+ resolveLogger() {
280
+ const provider = this.injector.get(logger_service_1.LoggerService);
281
+ const instance = provider?.instance;
282
+ return instance ?? new logger_service_1.LoggerService(this.injector);
261
283
  }
262
284
  isCorsEnabled() {
263
285
  return !!this.config.cors;
264
286
  }
265
- isOriginAllowed(origin) {
266
- if (!origin || !this.config.cors) {
267
- return false;
268
- }
269
- const { origins } = this.config.cors;
270
- if (typeof origins === "string") {
271
- return origins === "*" || origins === origin;
272
- }
273
- if (Array.isArray(origins)) {
274
- return origins.includes(origin);
275
- }
276
- if (origins instanceof RegExp) {
277
- return origins.test(origin);
278
- }
279
- if (typeof origins === "function") {
280
- return origins(origin);
287
+ errorResponse(request, message, statusCode) {
288
+ let response = new Response(JSON.stringify({
289
+ message,
290
+ statusCode,
291
+ }), {
292
+ status: statusCode,
293
+ headers: { "Content-Type": "application/json" },
294
+ });
295
+ if (this.corsEnabled) {
296
+ const origin = request.headers.get("origin");
297
+ if (origin && this.corsCache.isOriginAllowed(origin)) {
298
+ response = this.applyCorsHeaders(response, origin);
299
+ }
281
300
  }
282
- return false;
301
+ return response;
283
302
  }
284
303
  handlePreflightRequest(request) {
285
304
  const origin = request.headers.get("origin");
286
- if (!this.isOriginAllowed(origin)) {
305
+ if (!origin || !this.corsCache.isOriginAllowed(origin)) {
287
306
  return new Response(null, { status: 403 });
288
307
  }
289
308
  const corsHeaders = this.corsCache.get(origin);
@@ -310,10 +329,10 @@ class Carno {
310
329
  return locals;
311
330
  }
312
331
  discoverRoutePath(url) {
313
- if (url?.pathname) {
314
- return url.pathname;
332
+ if (typeof url === 'string') {
333
+ return url;
315
334
  }
316
- return url?.path || "/";
335
+ return url?.pathname || url?.path || '/';
317
336
  }
318
337
  }
319
338
  exports.Carno = Carno;
@@ -99,10 +99,12 @@ let InjectorService = InjectorService_1 = class InjectorService {
99
99
  }
100
100
  return this.invoke(token, locals);
101
101
  }
102
- async invokeRoute(route, context, locals, instance) {
103
- await middleware_resolver_1.MiddlewareRes.resolveMiddlewares(route, this, locals);
104
- const result = await this.methodInvoker.invoke(instance, route.methodName, locals, context, (t, l) => this.invoke(t, l));
105
- return result;
102
+ invokeRoute(route, context, locals, instance) {
103
+ const middlewarePromise = middleware_resolver_1.MiddlewareRes.resolveMiddlewares(route, this, locals);
104
+ if (middlewarePromise instanceof Promise) {
105
+ return middlewarePromise.then(() => this.methodInvoker.invoke(instance, route.methodName, locals, context, (t, l) => this.invoke(t, l)));
106
+ }
107
+ return this.methodInvoker.invoke(instance, route.methodName, locals, context, (t, l) => this.invoke(t, l));
106
108
  }
107
109
  scopeOf(provider) {
108
110
  return provider.scope || provider_scope_1.ProviderScope.SINGLETON;
@@ -14,21 +14,21 @@ class MiddlewareResolver {
14
14
  let currentIndex = 0;
15
15
  const next = async () => {
16
16
  if (currentIndex >= middlewares.length) {
17
- // Se processamos todos os middlewares, não faz nada.
18
- // Isso evita o erro "Middleware stack exhausted" se um middleware chamar `next()`
19
- // quando não mais middlewares.
17
+ // If all middlewares are already processed, do nothing.
18
+ // This avoids "Middleware stack exhausted" if a middleware calls `next()`
19
+ // when there are no more middlewares.
20
20
  return;
21
21
  }
22
22
  const middleware = middlewares[currentIndex++];
23
23
  // @ts-ignore
24
24
  const instance = injector.invoke(middleware, local);
25
- // Await a execução do middleware.
26
- // Se o middleware lançar uma exceção, ela será propagada.
25
+ // Await the middleware execution.
26
+ // If the middleware throws, the exception will propagate.
27
27
  await instance.handle(context, next);
28
28
  };
29
29
  if (middlewares.length === 0)
30
30
  return;
31
- // Inicia a execução dos middlewares
31
+ // Start the middleware execution
32
32
  await next();
33
33
  }
34
34
  }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * BaseContext - Interface mínima que todo contexto deve implementar.
3
+ *
4
+ * Define o contrato básico para FastContext e Context,
5
+ * permitindo que handlers compilados trabalhem com ambos.
6
+ */
7
+ export interface BaseContext {
8
+ readonly req: Request;
9
+ param: Record<string, string>;
10
+ readonly headers: Headers;
11
+ readonly query: Record<string, string>;
12
+ status: number;
13
+ setResponseStatus(status: number): void;
14
+ getResponseStatus(): number;
15
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,36 +1,58 @@
1
1
  import { Server } from 'bun';
2
+ /**
3
+ * Context otimizado com shape mínimo e lazy loading.
4
+ *
5
+ * Shape fixo mínimo (sempre alocado):
6
+ * - req: Request
7
+ * - param: Record<string, string>
8
+ * - status: number
9
+ *
10
+ * Lazy loading (só aloca quando usado):
11
+ * - query: Record<string, string> (getter lazy)
12
+ * - headers: Headers (getter que retorna req.headers)
13
+ * - body: Record<string, any> (getter lazy)
14
+ * - locals: Record<string, any> (getter lazy)
15
+ * - rawBody: ArrayBuffer (lazy)
16
+ *
17
+ * V8/JSC otimiza shape consistente. Propriedades lazy não quebram
18
+ * monomorfismo porque são getters, não props dinâmicas.
19
+ */
2
20
  export declare class Context {
3
- query: Record<string, any>;
4
- private _body;
5
- rawBody?: ArrayBuffer;
6
- param: Record<string, any>;
7
21
  req: Request;
8
- headers: Request["headers"];
9
- locals: Record<string, any>;
10
- private resultStatus;
11
- private _pendingRequest;
22
+ param: Record<string, string>;
23
+ status: number;
24
+ private _queryString;
25
+ private _query;
26
+ private _locals;
27
+ private _body;
28
+ private _rawBody;
12
29
  private _bodyParsed;
13
30
  private constructor();
31
+ get headers(): Headers;
32
+ get query(): Record<string, string>;
33
+ set query(value: Record<string, string>);
34
+ get locals(): Record<string, any>;
35
+ set locals(value: Record<string, any>);
14
36
  get body(): Record<string, any>;
15
37
  set body(value: Record<string, any>);
38
+ get rawBody(): ArrayBuffer | undefined;
39
+ set rawBody(value: ArrayBuffer | undefined);
16
40
  getBody(): Promise<Record<string, any>>;
17
41
  isBodyParsed(): boolean;
18
- static createFromRequest(url: any, request: Request, server: Server<any>): Promise<Context>;
19
- static createFromRequestSync(url: any, request: Request, server: Server<any>): Context;
20
- static createFromRequestWithBody(url: any, request: Request, server: Server<any>): Promise<Context>;
21
- static createFromJob(job: any): Context;
22
- private setQuery;
23
- private setBody;
24
- private setReq;
25
- private setHeaders;
26
- setParam(param: Record<string, any>): void;
27
42
  setResponseStatus(status: number): void;
28
43
  getResponseStatus(): number;
29
- private buildQueryObject;
30
- private resolveBody;
31
- private parseJsonFromBuffer;
32
- private parseJsonText;
33
- private isEmptyBuffer;
34
- private parseUrlEncodedFromBuffer;
35
- private decodeBuffer;
44
+ setParam(param: Record<string, string>): void;
45
+ static createFromRequestSync(url: {
46
+ query?: string;
47
+ }, request: Request, server: Server<any>): Context;
48
+ static createFastContext(request: Request, params: Record<string, any>): Context;
49
+ static createFromRequest(url: {
50
+ query?: string;
51
+ }, request: Request, server: Server<any>): Promise<Context>;
52
+ static createFromJob(job: any): Context;
53
+ private parseQueryString;
54
+ private parseBody;
55
+ private parseJsonBody;
56
+ private parseFormDataBody;
57
+ private parseUrlEncodedBody;
36
58
  }