@carno.js/core 0.2.9 → 0.2.10

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
@@ -60,8 +60,8 @@ export declare class Carno<TAdapter extends ValidatorAdapterConstructor = Valida
60
60
  private closeHttpServer;
61
61
  private exitProcess;
62
62
  private reportHookFailure;
63
+ private resolveLogger;
63
64
  private isCorsEnabled;
64
- private isOriginAllowed;
65
65
  private handlePreflightRequest;
66
66
  private applyCorsHeaders;
67
67
  close(closeActiveConnections?: boolean): void;
package/dist/Carno.js CHANGED
@@ -20,10 +20,8 @@ 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
24
  const parseUrl = require("parseurl-fast");
26
- // todo: change console.log for LoggerService.
27
25
  class Carno {
28
26
  constructor(config = {}) {
29
27
  this.config = config;
@@ -45,7 +43,7 @@ class Carno {
45
43
  });
46
44
  if (this.isCorsEnabled()) {
47
45
  const origin = request.headers.get("origin");
48
- if (origin && this.isOriginAllowed(origin)) {
46
+ if (origin && this.corsCache.isOriginAllowed(origin)) {
49
47
  response = this.applyCorsHeaders(response, origin);
50
48
  }
51
49
  }
@@ -55,7 +53,7 @@ class Carno {
55
53
  }
56
54
  };
57
55
  this.catcher = (error) => {
58
- console.error("Unhandled error:", error);
56
+ this.resolveLogger().error("Unhandled error", error);
59
57
  return new Response("Internal Server Error", { status: 500 });
60
58
  };
61
59
  this.validatorAdapter = this.resolveValidatorAdapter();
@@ -171,7 +169,7 @@ class Carno {
171
169
  }
172
170
  registerShutdownHandlers() {
173
171
  const shutdown = async (signal) => {
174
- console.log(`\nReceived ${signal}, starting graceful shutdown...`);
172
+ this.resolveLogger().info(`Received ${signal}, starting graceful shutdown...`);
175
173
  await this.handleShutdownHook();
176
174
  };
177
175
  node_process_1.default.on("SIGTERM", () => shutdown("SIGTERM"));
@@ -185,7 +183,7 @@ class Carno {
185
183
  }
186
184
  createHttpServer(port) {
187
185
  this.server = Bun.serve({ port, fetch: this.fetch, error: this.catcher });
188
- console.log(`Server running on port ${port}`);
186
+ this.resolveLogger().info(`Server running on port ${port}`);
189
187
  }
190
188
  async fetcher(request, server) {
191
189
  if (this.isCorsEnabled()) {
@@ -206,8 +204,9 @@ class Carno {
206
204
  let response;
207
205
  const isCompiledRoute = compiled.routeType !== undefined;
208
206
  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);
207
+ response = compiled.isAsync
208
+ ? await compiled.boundHandler(context)
209
+ : compiled.boundHandler(context);
211
210
  }
212
211
  else {
213
212
  const needsLocalsContainer = isCompiledRoute
@@ -221,7 +220,7 @@ class Carno {
221
220
  }
222
221
  if (this.isCorsEnabled()) {
223
222
  const origin = request.headers.get("origin");
224
- if (origin && this.isOriginAllowed(origin)) {
223
+ if (origin && this.corsCache.isOriginAllowed(origin)) {
225
224
  response = this.applyCorsHeaders(response, origin);
226
225
  }
227
226
  }
@@ -248,42 +247,28 @@ class Carno {
248
247
  }
249
248
  closeHttpServer() {
250
249
  if (this.server) {
251
- console.log("Closing HTTP server...");
250
+ this.resolveLogger().info("Closing HTTP server...");
252
251
  this.server.stop(true);
253
252
  }
254
253
  }
255
254
  exitProcess(code = 0) {
256
- console.log("Shutdown complete.");
255
+ this.resolveLogger().info("Shutdown complete.");
257
256
  node_process_1.default.exit(code);
258
257
  }
259
258
  reportHookFailure(event, error) {
260
- console.error(`Lifecycle hook ${event} failed`, error);
259
+ this.resolveLogger().error(`Lifecycle hook ${event} failed`, error);
260
+ }
261
+ resolveLogger() {
262
+ const provider = this.injector.get(logger_service_1.LoggerService);
263
+ const instance = provider?.instance;
264
+ return instance ?? new logger_service_1.LoggerService(this.injector);
261
265
  }
262
266
  isCorsEnabled() {
263
267
  return !!this.config.cors;
264
268
  }
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);
281
- }
282
- return false;
283
- }
284
269
  handlePreflightRequest(request) {
285
270
  const origin = request.headers.get("origin");
286
- if (!this.isOriginAllowed(origin)) {
271
+ if (!origin || !this.corsCache.isOriginAllowed(origin)) {
287
272
  return new Response(null, { status: 403 });
288
273
  }
289
274
  const corsHeaders = this.corsCache.get(origin);
@@ -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,57 @@
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 createFromRequest(url: {
49
+ query?: string;
50
+ }, request: Request, server: Server<any>): Promise<Context>;
51
+ static createFromJob(job: any): Context;
52
+ private parseQueryString;
53
+ private parseBody;
54
+ private parseJsonBody;
55
+ private parseFormDataBody;
56
+ private parseUrlEncodedBody;
36
57
  }
@@ -15,144 +15,165 @@ const http_code_enum_1 = require("../commons/http-code.enum");
15
15
  const Injectable_decorator_1 = require("../commons/decorators/Injectable.decorator");
16
16
  const HttpException_1 = require("../exceptions/HttpException");
17
17
  const provider_scope_1 = require("./provider-scope");
18
+ /**
19
+ * Context otimizado com shape mínimo e lazy loading.
20
+ *
21
+ * Shape fixo mínimo (sempre alocado):
22
+ * - req: Request
23
+ * - param: Record<string, string>
24
+ * - status: number
25
+ *
26
+ * Lazy loading (só aloca quando usado):
27
+ * - query: Record<string, string> (getter lazy)
28
+ * - headers: Headers (getter que retorna req.headers)
29
+ * - body: Record<string, any> (getter lazy)
30
+ * - locals: Record<string, any> (getter lazy)
31
+ * - rawBody: ArrayBuffer (lazy)
32
+ *
33
+ * V8/JSC otimiza shape consistente. Propriedades lazy não quebram
34
+ * monomorfismo porque são getters, não props dinâmicas.
35
+ */
18
36
  let Context = Context_1 = class Context {
19
37
  constructor() {
20
- this.query = {};
21
- this._body = {};
22
- this.param = {};
23
- this.headers = new Headers();
24
- this.locals = {};
25
- this._pendingRequest = null;
38
+ this._query = null;
39
+ this._locals = null;
40
+ this._body = null;
41
+ this._rawBody = null;
26
42
  this._bodyParsed = false;
43
+ this.req = undefined;
44
+ this.param = {};
45
+ this.status = 200;
46
+ }
47
+ get headers() {
48
+ return this.req.headers;
49
+ }
50
+ get query() {
51
+ if (this._query === null) {
52
+ this._query = this.parseQueryString();
53
+ }
54
+ return this._query;
55
+ }
56
+ set query(value) {
57
+ this._query = value;
58
+ }
59
+ get locals() {
60
+ if (this._locals === null) {
61
+ this._locals = {};
62
+ }
63
+ return this._locals;
64
+ }
65
+ set locals(value) {
66
+ this._locals = value;
27
67
  }
28
68
  get body() {
69
+ if (this._body === null) {
70
+ return {};
71
+ }
29
72
  return this._body;
30
73
  }
31
74
  set body(value) {
32
75
  this._body = value;
33
76
  this._bodyParsed = true;
34
77
  }
78
+ get rawBody() {
79
+ return this._rawBody ?? undefined;
80
+ }
81
+ set rawBody(value) {
82
+ this._rawBody = value ?? null;
83
+ }
35
84
  async getBody() {
36
- if (!this._bodyParsed && this._pendingRequest) {
37
- await this.resolveBody(this._pendingRequest);
38
- this._pendingRequest = null;
39
- this._bodyParsed = true;
85
+ if (!this._bodyParsed) {
86
+ await this.parseBody();
40
87
  }
41
- return this._body;
88
+ return this._body ?? {};
42
89
  }
43
90
  isBodyParsed() {
44
91
  return this._bodyParsed;
45
92
  }
46
- static async createFromRequest(url, request, server) {
47
- const context = Context_1.createFromRequestSync(url, request, server);
48
- if (context._pendingRequest) {
49
- await context.getBody();
50
- }
51
- return context;
93
+ setResponseStatus(status) {
94
+ this.status = status;
95
+ }
96
+ getResponseStatus() {
97
+ return this.status;
98
+ }
99
+ setParam(param) {
100
+ this.param = param;
52
101
  }
53
102
  static createFromRequestSync(url, request, server) {
54
- const context = new Context_1();
55
- context.setQuery(url);
56
- context.setReq(request);
57
- // @ts-ignore
58
- context.setHeaders(request.headers);
59
- if (request.method !== 'GET' && request.method !== 'HEAD') {
60
- context._pendingRequest = request;
103
+ const ctx = new Context_1();
104
+ ctx.req = request;
105
+ ctx.param = {};
106
+ ctx._queryString = url.query;
107
+ const method = request.method;
108
+ if (method !== 'GET' && method !== 'HEAD') {
109
+ ctx._bodyParsed = false;
61
110
  }
62
111
  else {
63
- context._bodyParsed = true;
112
+ ctx._bodyParsed = true;
64
113
  }
65
- return context;
114
+ return ctx;
66
115
  }
67
- static async createFromRequestWithBody(url, request, server) {
68
- const context = Context_1.createFromRequestSync(url, request, server);
69
- if (context._pendingRequest) {
70
- await context.getBody();
116
+ static async createFromRequest(url, request, server) {
117
+ const ctx = Context_1.createFromRequestSync(url, request, server);
118
+ if (!ctx._bodyParsed) {
119
+ await ctx.getBody();
71
120
  }
72
- return context;
121
+ return ctx;
73
122
  }
74
123
  static createFromJob(job) {
75
- const context = new Context_1();
76
- return context;
77
- }
78
- // @ts-ignore
79
- setQuery({ query }) {
80
- this.query = this.buildQueryObject(query);
124
+ return new Context_1();
81
125
  }
82
- setBody(body) {
83
- for (const [key, value] of body.entries()) {
84
- this._body[key] = value;
126
+ parseQueryString() {
127
+ if (!this._queryString) {
128
+ return {};
85
129
  }
130
+ return Object.fromEntries(new URLSearchParams(this._queryString));
86
131
  }
87
- setReq(req) {
88
- this.req = req;
89
- }
90
- setHeaders(headers) {
91
- this.headers = headers;
92
- }
93
- setParam(param) {
94
- this.param = param;
95
- }
96
- setResponseStatus(status) {
97
- this.resultStatus = status;
98
- }
99
- getResponseStatus() {
100
- return this.resultStatus;
101
- }
102
- buildQueryObject(query) {
103
- return query ? Object.fromEntries(new URLSearchParams(query)) : {};
104
- }
105
- async resolveBody(request) {
106
- const contentType = request.headers.get('content-type') || '';
107
- // Clone request once - preserve original request untouched
108
- const clonedRequest = request.clone();
109
- // FormData multipart requires consuming as formData
110
- if (contentType.includes('multipart/form-data')) {
111
- // Need separate clone for rawBody since formData() consumes the body
112
- this.rawBody = await request.clone().arrayBuffer();
113
- this.setBody(await clonedRequest.formData());
132
+ async parseBody() {
133
+ this._bodyParsed = true;
134
+ const contentType = this.req.headers.get('content-type') ?? '';
135
+ if (contentType.includes('application/json')) {
136
+ this._body = await this.parseJsonBody();
114
137
  return;
115
138
  }
116
- // For all other content types, consume body once as ArrayBuffer from clone
117
- this.rawBody = await clonedRequest.arrayBuffer();
118
- if (contentType.includes('application/json')) {
119
- this._body = this.parseJsonFromBuffer(this.rawBody);
139
+ if (contentType.includes('multipart/form-data')) {
140
+ this._body = await this.parseFormDataBody();
120
141
  return;
121
142
  }
122
143
  if (contentType.includes('application/x-www-form-urlencoded')) {
123
- this._body = this.parseUrlEncodedFromBuffer(this.rawBody);
144
+ this._body = await this.parseUrlEncodedBody();
124
145
  return;
125
146
  }
126
- // Plain text or unknown content type
127
- this._body = { body: this.decodeBuffer(this.rawBody) };
147
+ this._body = {};
128
148
  }
129
- parseJsonFromBuffer(buffer) {
130
- if (this.isEmptyBuffer(buffer)) {
149
+ async parseJsonBody() {
150
+ const contentLength = this.req.headers.get('content-length');
151
+ if (contentLength === '0') {
131
152
  return {};
132
153
  }
133
- return this.parseJsonText(this.decodeBuffer(buffer));
134
- }
135
- parseJsonText(text) {
136
154
  try {
137
- return JSON.parse(text);
155
+ const payload = await this.req.json();
156
+ return payload;
138
157
  }
139
158
  catch {
140
- throw new HttpException_1.HttpException("Invalid JSON body", http_code_enum_1.HttpCode.BAD_REQUEST);
159
+ throw new HttpException_1.HttpException('Invalid JSON body', http_code_enum_1.HttpCode.BAD_REQUEST);
141
160
  }
142
161
  }
143
- isEmptyBuffer(buffer) {
144
- return buffer.byteLength === 0;
162
+ async parseFormDataBody() {
163
+ const formData = await this.req.formData();
164
+ const result = {};
165
+ for (const [key, value] of formData.entries()) {
166
+ result[key] = value;
167
+ }
168
+ return result;
145
169
  }
146
- parseUrlEncodedFromBuffer(buffer) {
147
- if (buffer.byteLength === 0) {
170
+ async parseUrlEncodedBody() {
171
+ const text = await this.req.text();
172
+ if (!text) {
148
173
  return {};
149
174
  }
150
- const text = this.decodeBuffer(buffer);
151
175
  return Object.fromEntries(new URLSearchParams(text));
152
176
  }
153
- decodeBuffer(buffer) {
154
- return new TextDecoder().decode(buffer);
155
- }
156
177
  };
157
178
  exports.Context = Context;
158
179
  exports.Context = Context = Context_1 = __decorate([
@@ -0,0 +1,34 @@
1
+ import type { BaseContext } from './BaseContext';
2
+ /**
3
+ * FastContext - Contexto mínimo monomórfico para fast path.
4
+ *
5
+ * Shape fixo otimizado para V8/JSC JIT:
6
+ * - Apenas 3 propriedades diretas: req, param, status
7
+ * - Lazy getters para headers e query (zero alocação se não usados)
8
+ * - Sem locals, sem body, sem flags extras
9
+ * - Monomórfico: sempre mesmo shape, JIT otimiza agressivamente
10
+ *
11
+ * Usado quando:
12
+ * - Rota SIMPLE (RouteType.SIMPLE)
13
+ * - Sem middlewares
14
+ * - Sem DI
15
+ * - Sem body parsing
16
+ */
17
+ export declare class FastContext implements BaseContext {
18
+ readonly req: Request;
19
+ param: Record<string, string>;
20
+ status: number;
21
+ private _query;
22
+ private _queryString;
23
+ constructor(req: Request, param: Record<string, string>, queryString: string | undefined);
24
+ get headers(): Headers;
25
+ get query(): Record<string, string>;
26
+ setResponseStatus(status: number): void;
27
+ getResponseStatus(): number;
28
+ private parseQueryString;
29
+ }
30
+ /**
31
+ * Factory otimizado para criar FastContext.
32
+ * Extrai queryString inline sem overhead de parseUrl.
33
+ */
34
+ export declare function createFastContext(req: Request, param: Record<string, string>, url: string): FastContext;
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FastContext = void 0;
4
+ exports.createFastContext = createFastContext;
5
+ /**
6
+ * FastContext - Contexto mínimo monomórfico para fast path.
7
+ *
8
+ * Shape fixo otimizado para V8/JSC JIT:
9
+ * - Apenas 3 propriedades diretas: req, param, status
10
+ * - Lazy getters para headers e query (zero alocação se não usados)
11
+ * - Sem locals, sem body, sem flags extras
12
+ * - Monomórfico: sempre mesmo shape, JIT otimiza agressivamente
13
+ *
14
+ * Usado quando:
15
+ * - Rota SIMPLE (RouteType.SIMPLE)
16
+ * - Sem middlewares
17
+ * - Sem DI
18
+ * - Sem body parsing
19
+ */
20
+ class FastContext {
21
+ constructor(req, param, queryString) {
22
+ this.req = req;
23
+ this.param = param;
24
+ this.status = 200;
25
+ this._query = null;
26
+ this._queryString = queryString;
27
+ }
28
+ get headers() {
29
+ return this.req.headers;
30
+ }
31
+ get query() {
32
+ if (this._query === null) {
33
+ this._query = this.parseQueryString();
34
+ }
35
+ return this._query;
36
+ }
37
+ setResponseStatus(status) {
38
+ this.status = status;
39
+ }
40
+ getResponseStatus() {
41
+ return this.status;
42
+ }
43
+ parseQueryString() {
44
+ if (!this._queryString) {
45
+ return {};
46
+ }
47
+ return Object.fromEntries(new URLSearchParams(this._queryString));
48
+ }
49
+ }
50
+ exports.FastContext = FastContext;
51
+ /**
52
+ * Factory otimizado para criar FastContext.
53
+ * Extrai queryString inline sem overhead de parseUrl.
54
+ */
55
+ function createFastContext(req, param, url) {
56
+ const queryIdx = url.indexOf('?');
57
+ const queryString = queryIdx !== -1 ? url.slice(queryIdx + 1) : undefined;
58
+ return new FastContext(req, param, queryString);
59
+ }
@@ -8,8 +8,10 @@ export declare class CorsHeadersCache {
8
8
  private readonly maxAgeString;
9
9
  private readonly hasCredentials;
10
10
  private readonly isWildcard;
11
+ private readonly originAllowed;
11
12
  constructor(config: CorsConfig);
12
13
  get(origin: string): Record<string, string>;
13
14
  private buildHeaders;
14
15
  applyToResponse(response: Response, origin: string): Response;
16
+ isOriginAllowed(origin: string): boolean;
15
17
  }