@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.
@@ -15,144 +15,171 @@ 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.status = 200;
45
+ }
46
+ get headers() {
47
+ return this.req.headers;
48
+ }
49
+ get query() {
50
+ if (this._query === null) {
51
+ this._query = this.parseQueryString();
52
+ }
53
+ return this._query;
54
+ }
55
+ set query(value) {
56
+ this._query = value;
57
+ }
58
+ get locals() {
59
+ if (this._locals === null) {
60
+ this._locals = {};
61
+ }
62
+ return this._locals;
63
+ }
64
+ set locals(value) {
65
+ this._locals = value;
27
66
  }
28
67
  get body() {
68
+ if (this._body === null) {
69
+ return {};
70
+ }
29
71
  return this._body;
30
72
  }
31
73
  set body(value) {
32
74
  this._body = value;
33
75
  this._bodyParsed = true;
34
76
  }
77
+ get rawBody() {
78
+ return this._rawBody ?? undefined;
79
+ }
80
+ set rawBody(value) {
81
+ this._rawBody = value ?? null;
82
+ }
35
83
  async getBody() {
36
- if (!this._bodyParsed && this._pendingRequest) {
37
- await this.resolveBody(this._pendingRequest);
38
- this._pendingRequest = null;
39
- this._bodyParsed = true;
84
+ if (!this._bodyParsed) {
85
+ await this.parseBody();
40
86
  }
41
- return this._body;
87
+ return this._body ?? {};
42
88
  }
43
89
  isBodyParsed() {
44
90
  return this._bodyParsed;
45
91
  }
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;
92
+ setResponseStatus(status) {
93
+ this.status = status;
94
+ }
95
+ getResponseStatus() {
96
+ return this.status;
97
+ }
98
+ setParam(param) {
99
+ this.param = param;
52
100
  }
53
101
  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;
102
+ const ctx = new Context_1();
103
+ ctx.req = request;
104
+ ctx.param = {};
105
+ ctx._queryString = url.query;
106
+ const method = request.method;
107
+ if (method !== 'GET' && method !== 'HEAD') {
108
+ ctx._bodyParsed = false;
61
109
  }
62
110
  else {
63
- context._bodyParsed = true;
111
+ ctx._bodyParsed = true;
64
112
  }
65
- return context;
113
+ return ctx;
114
+ }
115
+ static createFastContext(request, params) {
116
+ const ctx = new Context_1();
117
+ ctx.req = request;
118
+ ctx.param = params;
119
+ ctx._bodyParsed = true;
120
+ return ctx;
66
121
  }
67
- static async createFromRequestWithBody(url, request, server) {
68
- const context = Context_1.createFromRequestSync(url, request, server);
69
- if (context._pendingRequest) {
70
- await context.getBody();
122
+ static async createFromRequest(url, request, server) {
123
+ const ctx = Context_1.createFromRequestSync(url, request, server);
124
+ if (!ctx._bodyParsed) {
125
+ await ctx.getBody();
71
126
  }
72
- return context;
127
+ return ctx;
73
128
  }
74
129
  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);
130
+ return new Context_1();
81
131
  }
82
- setBody(body) {
83
- for (const [key, value] of body.entries()) {
84
- this._body[key] = value;
132
+ parseQueryString() {
133
+ if (!this._queryString) {
134
+ return {};
85
135
  }
136
+ return Object.fromEntries(new URLSearchParams(this._queryString));
86
137
  }
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());
138
+ async parseBody() {
139
+ this._bodyParsed = true;
140
+ const contentType = this.req.headers.get('content-type') ?? '';
141
+ if (contentType.includes('application/json')) {
142
+ this._body = await this.parseJsonBody();
114
143
  return;
115
144
  }
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);
145
+ if (contentType.includes('multipart/form-data')) {
146
+ this._body = await this.parseFormDataBody();
120
147
  return;
121
148
  }
122
149
  if (contentType.includes('application/x-www-form-urlencoded')) {
123
- this._body = this.parseUrlEncodedFromBuffer(this.rawBody);
150
+ this._body = await this.parseUrlEncodedBody();
124
151
  return;
125
152
  }
126
- // Plain text or unknown content type
127
- this._body = { body: this.decodeBuffer(this.rawBody) };
153
+ this._body = {};
128
154
  }
129
- parseJsonFromBuffer(buffer) {
130
- if (this.isEmptyBuffer(buffer)) {
155
+ async parseJsonBody() {
156
+ const contentLength = this.req.headers.get('content-length');
157
+ if (contentLength === '0') {
131
158
  return {};
132
159
  }
133
- return this.parseJsonText(this.decodeBuffer(buffer));
134
- }
135
- parseJsonText(text) {
136
160
  try {
137
- return JSON.parse(text);
161
+ const payload = await this.req.json();
162
+ return payload;
138
163
  }
139
164
  catch {
140
- throw new HttpException_1.HttpException("Invalid JSON body", http_code_enum_1.HttpCode.BAD_REQUEST);
165
+ throw new HttpException_1.HttpException('Invalid JSON body', http_code_enum_1.HttpCode.BAD_REQUEST);
141
166
  }
142
167
  }
143
- isEmptyBuffer(buffer) {
144
- return buffer.byteLength === 0;
168
+ async parseFormDataBody() {
169
+ const formData = await this.req.formData();
170
+ const result = {};
171
+ for (const [key, value] of formData.entries()) {
172
+ result[key] = value;
173
+ }
174
+ return result;
145
175
  }
146
- parseUrlEncodedFromBuffer(buffer) {
147
- if (buffer.byteLength === 0) {
176
+ async parseUrlEncodedBody() {
177
+ const text = await this.req.text();
178
+ if (!text) {
148
179
  return {};
149
180
  }
150
- const text = this.decodeBuffer(buffer);
151
181
  return Object.fromEntries(new URLSearchParams(text));
152
182
  }
153
- decodeBuffer(buffer) {
154
- return new TextDecoder().decode(buffer);
155
- }
156
183
  };
157
184
  exports.Context = Context;
158
185
  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
  }
@@ -2,6 +2,46 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.CorsHeadersCache = void 0;
4
4
  const cors_config_1 = require("./cors-config");
5
+ const allowAnyOrigin = () => {
6
+ return true;
7
+ };
8
+ const denyAnyOrigin = () => {
9
+ return false;
10
+ };
11
+ function buildOriginAllowed(origins) {
12
+ if (origins === '*') {
13
+ return allowAnyOrigin;
14
+ }
15
+ if (typeof origins === 'string') {
16
+ return createExactOriginMatcher(origins);
17
+ }
18
+ if (Array.isArray(origins)) {
19
+ return createSetOriginMatcher(origins);
20
+ }
21
+ if (origins instanceof RegExp) {
22
+ return createRegexOriginMatcher(origins);
23
+ }
24
+ if (typeof origins === 'function') {
25
+ return origins;
26
+ }
27
+ return denyAnyOrigin;
28
+ }
29
+ function createExactOriginMatcher(origin) {
30
+ return (value) => {
31
+ return value === origin;
32
+ };
33
+ }
34
+ function createSetOriginMatcher(origins) {
35
+ const originSet = new Set(origins);
36
+ return (value) => {
37
+ return originSet.has(value);
38
+ };
39
+ }
40
+ function createRegexOriginMatcher(origins) {
41
+ return (value) => {
42
+ return origins.test(value);
43
+ };
44
+ }
5
45
  class CorsHeadersCache {
6
46
  constructor(config) {
7
47
  this.config = config;
@@ -18,6 +58,7 @@ class CorsHeadersCache {
18
58
  : null;
19
59
  this.hasCredentials = !!config.credentials;
20
60
  this.isWildcard = config.origins === '*';
61
+ this.originAllowed = buildOriginAllowed(config.origins);
21
62
  }
22
63
  get(origin) {
23
64
  const cacheKey = this.isWildcard ? '*' : origin;
@@ -53,5 +94,8 @@ class CorsHeadersCache {
53
94
  }
54
95
  return response;
55
96
  }
97
+ isOriginAllowed(origin) {
98
+ return this.originAllowed(origin);
99
+ }
56
100
  }
57
101
  exports.CorsHeadersCache = CorsHeadersCache;
@@ -1,4 +1,12 @@
1
1
  import type { Context } from '../domain/Context';
2
2
  import type { CompiledRoute } from './CompiledRoute';
3
- export declare function executeSimpleRoute(compiled: CompiledRoute, context: Context): Promise<any>;
4
- export declare function executeSimpleRouteSync(compiled: CompiledRoute, context: Context): any;
3
+ /**
4
+ * Fast path executor completo - executa handler e monta response inline.
5
+ * Máximo 2 type checks no fast path.
6
+ */
7
+ export declare function executeFastPath(compiled: CompiledRoute, context: Context): Promise<Response>;
8
+ /**
9
+ * Fast path executor síncrono - para handlers que não retornam Promise.
10
+ * Elimina overhead de async/await quando handler é síncrono.
11
+ */
12
+ export declare function executeFastPathSync(compiled: CompiledRoute, context: Context): Response;
@@ -1,19 +1,50 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.executeSimpleRoute = executeSimpleRoute;
4
- exports.executeSimpleRouteSync = executeSimpleRouteSync;
5
- async function executeSimpleRoute(compiled, context) {
6
- if (!compiled.boundHandler) {
7
- throw new Error('Simple route must have a bound handler');
3
+ exports.executeFastPath = executeFastPath;
4
+ exports.executeFastPathSync = executeFastPathSync;
5
+ /**
6
+ * Frozen header objects - V8 otimiza acesso a objetos frozen.
7
+ * Criados uma vez no startup, reutilizados em todas requests.
8
+ */
9
+ const JSON_HEADERS = Object.freeze({
10
+ 'Content-Type': 'application/json'
11
+ });
12
+ const TEXT_HEADERS = Object.freeze({
13
+ 'Content-Type': 'text/html'
14
+ });
15
+ /**
16
+ * Fast path executor completo - executa handler e monta response inline.
17
+ * Máximo 2 type checks no fast path.
18
+ */
19
+ async function executeFastPath(compiled, context) {
20
+ const result = await compiled.boundHandler(context);
21
+ const status = context.getResponseStatus() || 200;
22
+ if (typeof result === 'string') {
23
+ return new Response(result, { status, headers: TEXT_HEADERS });
8
24
  }
9
- if (compiled.isAsync) {
10
- return compiled.boundHandler(context);
25
+ if (result instanceof Response) {
26
+ return result;
11
27
  }
12
- return compiled.boundHandler(context);
28
+ if (result === null || result === undefined) {
29
+ return new Response('', { status, headers: TEXT_HEADERS });
30
+ }
31
+ return new Response(JSON.stringify(result), { status, headers: JSON_HEADERS });
13
32
  }
14
- function executeSimpleRouteSync(compiled, context) {
15
- if (!compiled.boundHandler) {
16
- throw new Error('Simple route must have a bound handler');
33
+ /**
34
+ * Fast path executor síncrono - para handlers que não retornam Promise.
35
+ * Elimina overhead de async/await quando handler é síncrono.
36
+ */
37
+ function executeFastPathSync(compiled, context) {
38
+ const result = compiled.boundHandler(context);
39
+ const status = context.getResponseStatus() || 200;
40
+ if (typeof result === 'string') {
41
+ return new Response(result, { status, headers: TEXT_HEADERS });
42
+ }
43
+ if (result instanceof Response) {
44
+ return result;
45
+ }
46
+ if (result === null || result === undefined) {
47
+ return new Response('', { status, headers: TEXT_HEADERS });
17
48
  }
18
- return compiled.boundHandler(context);
49
+ return new Response(JSON.stringify(result), { status, headers: JSON_HEADERS });
19
50
  }
@@ -1,4 +1,28 @@
1
1
  import type { ParamInfo } from './ParamResolverFactory';
2
2
  import type { CompiledHandler, AsyncCompiledHandler } from './CompiledRoute';
3
+ /**
4
+ * Compila route handler em função otimizada.
5
+ *
6
+ * Estratégias de otimização:
7
+ * - Inline de acesso a parâmetros
8
+ * - Bind do handler no compile time
9
+ * - Código gerado monomórfico
10
+ * - Sem overhead de resolvers array
11
+ */
12
+ /**
13
+ * Compila route handler em função otimizada que retorna Response inline.
14
+ *
15
+ * Estratégias de otimização:
16
+ * - Inline de acesso a parâmetros
17
+ * - Inline de response building (elimina executeFastPath)
18
+ * - Bind do handler no compile time
19
+ * - Código gerado monomórfico
20
+ * - Headers pré-frozen para otimização V8
21
+ */
3
22
  export declare function compileRouteHandler(instance: any, methodName: string, paramInfos: ParamInfo[]): CompiledHandler | AsyncCompiledHandler;
4
- export declare function compileValidatedHandler(instance: any, methodName: string, paramInfos: ParamInfo[], validators: ((value: any) => any)[]): CompiledHandler | AsyncCompiledHandler;
23
+ /**
24
+ * Compila handler com validação inline.
25
+ */
26
+ export declare function compileValidatedHandler(instance: any, methodName: string, paramInfos: ParamInfo[], validatorAdapter: {
27
+ validateAndTransform: (token: any, value: any) => any;
28
+ }): CompiledHandler | AsyncCompiledHandler;