@carno.js/core 0.2.3 → 0.2.5

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
@@ -4,6 +4,7 @@ import * as pino from "pino";
4
4
  import { TokenRouteWithProvider } from "./container/ContainerConfiguration";
5
5
  import { CorsConfig } from "./domain/cors-config";
6
6
  import Memoirist from "./route/memoirist";
7
+ import { type CompiledRoute } from "./route/CompiledRoute";
7
8
  export interface ApplicationConfig {
8
9
  validation?: ValidatorOptions;
9
10
  logger?: pino.LoggerOptions;
@@ -14,8 +15,10 @@ export interface ApplicationConfig {
14
15
  }
15
16
  export declare class Carno {
16
17
  config: ApplicationConfig;
17
- router: Memoirist<TokenRouteWithProvider>;
18
+ router: Memoirist<CompiledRoute | TokenRouteWithProvider>;
18
19
  private injector;
20
+ private corsCache?;
21
+ private readonly emptyLocals;
19
22
  private fetch;
20
23
  private server;
21
24
  constructor(config?: ApplicationConfig);
@@ -57,9 +60,10 @@ export declare class Carno {
57
60
  private reportHookFailure;
58
61
  private isCorsEnabled;
59
62
  private isOriginAllowed;
60
- private buildCorsHeaders;
61
63
  private handlePreflightRequest;
62
64
  private applyCorsHeaders;
63
65
  close(closeActiveConnections?: boolean): void;
66
+ private resolveLocalsContainer;
67
+ private buildLocalsContainer;
64
68
  private discoverRoutePath;
65
69
  }
package/dist/Carno.js CHANGED
@@ -12,11 +12,13 @@ const createInjector_1 = require("./container/createInjector");
12
12
  const domain_1 = require("./domain");
13
13
  const Context_1 = require("./domain/Context");
14
14
  const LocalsContainer_1 = require("./domain/LocalsContainer");
15
- const cors_config_1 = require("./domain/cors-config");
15
+ const cors_headers_cache_1 = require("./domain/cors-headers-cache");
16
16
  const on_event_1 = require("./events/on-event");
17
17
  const HttpException_1 = require("./exceptions/HttpException");
18
18
  const RouteExecutor_1 = require("./route/RouteExecutor");
19
19
  const memoirist_1 = __importDefault(require("./route/memoirist"));
20
+ const CompiledRoute_1 = require("./route/CompiledRoute");
21
+ const FastPathExecutor_1 = require("./route/FastPathExecutor");
20
22
  const logger_service_1 = require("./services/logger.service");
21
23
  const parseUrl = require("parseurl-fast");
22
24
  // todo: change console.log for LoggerService.
@@ -25,6 +27,7 @@ class Carno {
25
27
  this.config = config;
26
28
  this.router = new memoirist_1.default();
27
29
  this.injector = (0, createInjector_1.createInjector)();
30
+ this.emptyLocals = new LocalsContainer_1.LocalsContainer();
28
31
  this.fetch = async (request, server) => {
29
32
  try {
30
33
  return await this.fetcher(request, server);
@@ -53,6 +56,9 @@ class Carno {
53
56
  console.error("Unhandled error:", error);
54
57
  return new Response("Internal Server Error", { status: 500 });
55
58
  };
59
+ if (config.cors) {
60
+ this.corsCache = new cors_headers_cache_1.CorsHeadersCache(config.cors);
61
+ }
56
62
  void this.bootstrapApplication();
57
63
  }
58
64
  /**
@@ -176,17 +182,30 @@ class Carno {
176
182
  }
177
183
  }
178
184
  const urlParsed = parseUrl(request);
179
- const context = await Context_1.Context.createFromRequest(urlParsed, request, server);
180
- await this.injector.callHook(on_event_1.EventType.OnRequest, { context });
181
- const local = new LocalsContainer_1.LocalsContainer();
182
185
  const routePath = this.discoverRoutePath(urlParsed);
183
186
  const route = this.router.find(request.method.toLowerCase(), routePath);
184
187
  if (!route) {
185
188
  throw new HttpException_1.HttpException("Method not allowed", 404);
186
189
  }
190
+ const compiled = route.store;
191
+ const context = Context_1.Context.createFromRequestSync(urlParsed, request, server);
187
192
  context.param = route.params;
188
- local.set(Context_1.Context, context);
189
- let response = await RouteExecutor_1.RouteExecutor.executeRoute(route.store, this.injector, context, local);
193
+ let response;
194
+ const isCompiledRoute = compiled.routeType !== undefined;
195
+ if (isCompiledRoute && compiled.routeType === CompiledRoute_1.RouteType.SIMPLE) {
196
+ const result = await (0, FastPathExecutor_1.executeSimpleRoute)(compiled, context);
197
+ response = RouteExecutor_1.RouteExecutor.mountResponse(result, context);
198
+ }
199
+ else {
200
+ const needsLocalsContainer = isCompiledRoute
201
+ ? compiled.needsLocalsContainer
202
+ : true;
203
+ const locals = this.resolveLocalsContainer(needsLocalsContainer, context);
204
+ if (this.injector.hasOnRequestHook()) {
205
+ await this.injector.callHook(on_event_1.EventType.OnRequest, { context });
206
+ }
207
+ response = await RouteExecutor_1.RouteExecutor.executeRoute(compiled, this.injector, context, locals);
208
+ }
190
209
  if (this.isCorsEnabled()) {
191
210
  const origin = request.headers.get("origin");
192
211
  if (origin && this.isOriginAllowed(origin)) {
@@ -249,54 +268,34 @@ class Carno {
249
268
  }
250
269
  return false;
251
270
  }
252
- buildCorsHeaders(origin) {
253
- const cors = this.config.cors;
254
- const headers = {};
255
- const allowedOrigin = typeof cors.origins === "string" && cors.origins === "*"
256
- ? "*"
257
- : origin;
258
- headers["Access-Control-Allow-Origin"] = allowedOrigin;
259
- if (cors.credentials) {
260
- headers["Access-Control-Allow-Credentials"] = "true";
261
- }
262
- const methods = cors.methods || cors_config_1.DEFAULT_CORS_METHODS;
263
- headers["Access-Control-Allow-Methods"] = methods.join(", ");
264
- const allowedHeaders = cors.allowedHeaders || cors_config_1.DEFAULT_CORS_ALLOWED_HEADERS;
265
- headers["Access-Control-Allow-Headers"] = allowedHeaders.join(", ");
266
- if (cors.exposedHeaders && cors.exposedHeaders.length > 0) {
267
- headers["Access-Control-Expose-Headers"] = cors.exposedHeaders.join(", ");
268
- }
269
- if (cors.maxAge !== undefined) {
270
- headers["Access-Control-Max-Age"] = cors.maxAge.toString();
271
- }
272
- return headers;
273
- }
274
271
  handlePreflightRequest(request) {
275
272
  const origin = request.headers.get("origin");
276
273
  if (!this.isOriginAllowed(origin)) {
277
274
  return new Response(null, { status: 403 });
278
275
  }
279
- const corsHeaders = this.buildCorsHeaders(origin);
276
+ const corsHeaders = this.corsCache.get(origin);
280
277
  return new Response(null, {
281
278
  status: 204,
282
279
  headers: corsHeaders,
283
280
  });
284
281
  }
285
282
  applyCorsHeaders(response, origin) {
286
- const corsHeaders = this.buildCorsHeaders(origin);
287
- const newHeaders = new Headers(response.headers);
288
- for (const [key, value] of Object.entries(corsHeaders)) {
289
- newHeaders.set(key, value);
290
- }
291
- return new Response(response.body, {
292
- status: response.status,
293
- statusText: response.statusText,
294
- headers: newHeaders,
295
- });
283
+ return this.corsCache.applyToResponse(response, origin);
296
284
  }
297
285
  close(closeActiveConnections = false) {
298
286
  this.server?.stop(closeActiveConnections);
299
287
  }
288
+ resolveLocalsContainer(needsLocalsContainer, context) {
289
+ if (!needsLocalsContainer) {
290
+ return this.emptyLocals;
291
+ }
292
+ return this.buildLocalsContainer(context);
293
+ }
294
+ buildLocalsContainer(context) {
295
+ const locals = new LocalsContainer_1.LocalsContainer();
296
+ locals.set(Context_1.Context, context);
297
+ return locals;
298
+ }
300
299
  discoverRoutePath(url) {
301
300
  if (url?.pathname) {
302
301
  return url.pathname;
@@ -1,6 +1,7 @@
1
1
  import { TokenProvider } from "../commons/registries/ProviderControl";
2
2
  import { LocalsContainer } from "../domain/LocalsContainer";
3
3
  import { Provider } from "../domain/provider";
4
+ import { ProviderScope } from "../domain/provider-scope";
4
5
  import { Container } from "./container";
5
6
  export declare class DependencyResolver {
6
7
  private container;
@@ -8,6 +9,7 @@ export declare class DependencyResolver {
8
9
  resolve(provider: Provider, locals: LocalsContainer, invokeCallback: (token: TokenProvider, locals: LocalsContainer) => any): any;
9
10
  private validateProvider;
10
11
  private determineScope;
12
+ resolveScope(provider: Provider): ProviderScope;
11
13
  private hasRequestScopeDependency;
12
14
  private createInstance;
13
15
  private resolveServices;
@@ -30,6 +30,10 @@ class DependencyResolver {
30
30
  const hasRequestDep = this.hasRequestScopeDependency(deps);
31
31
  return hasRequestDep ? provider_scope_1.ProviderScope.REQUEST : provider_scope_1.ProviderScope.SINGLETON;
32
32
  }
33
+ resolveScope(provider) {
34
+ const scope = this.determineScope(provider);
35
+ return scope;
36
+ }
33
37
  hasRequestScopeDependency(deps) {
34
38
  if (deps.length === 0)
35
39
  return false;
@@ -13,6 +13,10 @@ export declare class InjectorService {
13
13
  container: Container;
14
14
  applicationConfig: ApplicationConfig;
15
15
  router: Memoirist<TokenRouteWithProvider>;
16
+ private hooksByEvent;
17
+ private _hasOnRequestHook;
18
+ private _hasOnResponseHook;
19
+ private controllerScopes;
16
20
  private routeResolver;
17
21
  private dependencyResolver;
18
22
  private methodInvoker;
@@ -21,10 +25,28 @@ export declare class InjectorService {
21
25
  private ensureProvider;
22
26
  get(token: TokenProvider): Provider | undefined;
23
27
  invoke(token: TokenProvider, locals?: LocalsContainer): any;
24
- invokeRoute(route: TokenRouteWithProvider, context: Context, locals: LocalsContainer): Promise<any>;
28
+ resolveControllerInstance(token: TokenProvider, locals: LocalsContainer): any;
29
+ invokeRoute(route: TokenRouteWithProvider, context: Context, locals: LocalsContainer, instance: any): Promise<any>;
25
30
  scopeOf(provider: Provider): ProviderScope | undefined;
26
31
  callHook(event: EventType, data?: unknown): Promise<void>;
27
- private getHooksByEvent;
32
+ private getCachedHooks;
33
+ private cacheHooks;
34
+ hasHook(event: EventType): boolean;
35
+ hasOnRequestHook(): boolean;
36
+ hasOnResponseHook(): boolean;
37
+ private cacheControllerScopes;
38
+ private buildControllerScopeMap;
39
+ private getControllers;
40
+ private getControllerScope;
41
+ private preInstantiateSingletonControllers;
42
+ private compileRoutes;
43
+ private ensureControllerInstance;
44
+ private buildHooksByEvent;
45
+ private readAllHooks;
46
+ private groupHooksByEvent;
47
+ private appendHook;
48
+ private sortHookMap;
49
+ private sortAndStore;
28
50
  private sortHooksByPriority;
29
51
  private runHookHandlers;
30
52
  private executeHook;
@@ -17,6 +17,7 @@ const Context_1 = require("../domain/Context");
17
17
  const LocalsContainer_1 = require("../domain/LocalsContainer");
18
18
  const Metadata_1 = require("../domain/Metadata");
19
19
  const provider_scope_1 = require("../domain/provider-scope");
20
+ const provider_type_1 = require("../domain/provider-type");
20
21
  const on_event_1 = require("../events/on-event");
21
22
  const logger_service_1 = require("../services/logger.service");
22
23
  const cache_service_1 = require("../cache/cache.service");
@@ -28,12 +29,16 @@ const middleware_resolver_1 = require("./middleware.resolver");
28
29
  const RouteResolver_1 = require("./RouteResolver");
29
30
  const DependencyResolver_1 = require("./DependencyResolver");
30
31
  const MethodInvoker_1 = require("./MethodInvoker");
31
- const request_logger_service_1 = require("../services/request-logger.service");
32
+ const RouteCompiler_1 = require("../route/RouteCompiler");
32
33
  let InjectorService = InjectorService_1 = class InjectorService {
33
34
  constructor() {
34
35
  this.settings = new ContainerConfiguration_1.ContainerConfiguration();
35
36
  this.container = new container_1.Container();
36
37
  this.applicationConfig = {};
38
+ this.hooksByEvent = new Map();
39
+ this._hasOnRequestHook = false;
40
+ this._hasOnResponseHook = false;
41
+ this.controllerScopes = new Map();
37
42
  }
38
43
  async loadModule(container, applicationConfig, router) {
39
44
  this.container = container;
@@ -43,6 +48,10 @@ let InjectorService = InjectorService_1 = class InjectorService {
43
48
  this.removeUnknownProviders();
44
49
  this.saveInjector();
45
50
  this.routeResolver.resolveControllers();
51
+ this.cacheControllerScopes();
52
+ this.preInstantiateSingletonControllers();
53
+ this.cacheHooks();
54
+ this.compileRoutes();
46
55
  await this.callHook(on_event_1.EventType.OnApplicationInit);
47
56
  }
48
57
  initializeResolvers() {
@@ -73,27 +82,145 @@ let InjectorService = InjectorService_1 = class InjectorService {
73
82
  }
74
83
  return this.dependencyResolver.resolve(provider, locals, (t, l) => this.invoke(t, l));
75
84
  }
76
- async invokeRoute(route, context, locals) {
85
+ resolveControllerInstance(token, locals) {
86
+ const provider = this.ensureProvider(token);
87
+ if (!provider) {
88
+ const message = `Provider not found for: ${(0, nameOf_1.nameOf)(token)}, check if it was ` +
89
+ `imported into the module or imported without type-only import.`;
90
+ throw new Error(message);
91
+ }
92
+ const scope = this.getControllerScope(provider);
93
+ if (scope !== provider_scope_1.ProviderScope.SINGLETON) {
94
+ return this.invoke(token, locals);
95
+ }
96
+ if (provider.instance) {
97
+ return provider.instance;
98
+ }
99
+ return this.invoke(token, locals);
100
+ }
101
+ async invokeRoute(route, context, locals, instance) {
77
102
  await middleware_resolver_1.MiddlewareRes.resolveMiddlewares(route, this, locals);
78
- return this.methodInvoker.invoke(route.provider.instance, route.methodName, locals, context, (t, l) => this.invoke(t, l));
103
+ const result = await this.methodInvoker.invoke(instance, route.methodName, locals, context, (t, l) => this.invoke(t, l));
104
+ return result;
79
105
  }
80
106
  scopeOf(provider) {
81
107
  return provider.scope || provider_scope_1.ProviderScope.SINGLETON;
82
108
  }
83
109
  async callHook(event, data = null) {
84
- const hooks = this.getHooksByEvent(event);
110
+ const hooks = this.getCachedHooks(event);
85
111
  if (hooks.length === 0) {
86
112
  return;
87
113
  }
88
114
  await this.runHookHandlers(hooks, data ?? {});
89
115
  }
90
- getHooksByEvent(event) {
91
- const hooks = Metadata_1.Metadata.get(constants_1.CONTROLLER_EVENTS, Reflect);
92
- if (!hooks) {
93
- return [];
116
+ getCachedHooks(event) {
117
+ return this.hooksByEvent.get(event) ?? [];
118
+ }
119
+ cacheHooks() {
120
+ this.hooksByEvent = this.buildHooksByEvent();
121
+ this._hasOnRequestHook = (this.hooksByEvent.get(on_event_1.EventType.OnRequest)?.length ?? 0) > 0;
122
+ this._hasOnResponseHook = (this.hooksByEvent.get(on_event_1.EventType.OnResponse)?.length ?? 0) > 0;
123
+ }
124
+ hasHook(event) {
125
+ if (event === on_event_1.EventType.OnRequest) {
126
+ return this._hasOnRequestHook;
127
+ }
128
+ if (event === on_event_1.EventType.OnResponse) {
129
+ return this._hasOnResponseHook;
130
+ }
131
+ return (this.hooksByEvent.get(event)?.length ?? 0) > 0;
132
+ }
133
+ hasOnRequestHook() {
134
+ return this._hasOnRequestHook;
135
+ }
136
+ hasOnResponseHook() {
137
+ return this._hasOnResponseHook;
138
+ }
139
+ cacheControllerScopes() {
140
+ const controllers = this.getControllers();
141
+ this.controllerScopes = this.buildControllerScopeMap(controllers);
142
+ }
143
+ buildControllerScopeMap(controllers) {
144
+ const scoped = new Map();
145
+ controllers.forEach((controller) => {
146
+ const scope = this.dependencyResolver.resolveScope(controller);
147
+ scoped.set(controller.token, scope);
148
+ });
149
+ return scoped;
150
+ }
151
+ getControllers() {
152
+ const controllers = ProviderControl_1.GlobalProvider.getByType(provider_type_1.ProviderType.CONTROLLER);
153
+ return controllers;
154
+ }
155
+ getControllerScope(controller) {
156
+ const cached = this.controllerScopes.get(controller.token);
157
+ if (cached) {
158
+ return cached;
94
159
  }
95
- const filtered = hooks.filter((hook) => hook.eventName === event);
96
- return this.sortHooksByPriority(filtered);
160
+ const scope = this.dependencyResolver.resolveScope(controller);
161
+ this.controllerScopes.set(controller.token, scope);
162
+ return scope;
163
+ }
164
+ preInstantiateSingletonControllers() {
165
+ const controllers = this.getControllers();
166
+ for (const controller of controllers) {
167
+ const scope = this.getControllerScope(controller);
168
+ if (scope !== provider_scope_1.ProviderScope.SINGLETON) {
169
+ continue;
170
+ }
171
+ this.ensureControllerInstance(controller);
172
+ }
173
+ }
174
+ compileRoutes() {
175
+ const compiler = new RouteCompiler_1.RouteCompiler({
176
+ container: this.container,
177
+ controllerScopes: this.controllerScopes,
178
+ validationConfig: this.applicationConfig.validation,
179
+ hasOnRequestHook: this._hasOnRequestHook,
180
+ hasOnResponseHook: this._hasOnResponseHook,
181
+ });
182
+ const routesToCompile = [...this.router.history];
183
+ for (const [method, path, store] of routesToCompile) {
184
+ if (!store || store.routeType !== undefined) {
185
+ continue;
186
+ }
187
+ const compiled = compiler.compile(store);
188
+ if (!compiled) {
189
+ continue;
190
+ }
191
+ this.router.updateStore(method, path, store, compiled);
192
+ }
193
+ }
194
+ ensureControllerInstance(controller) {
195
+ if (controller.instance) {
196
+ return;
197
+ }
198
+ this.invoke(controller.token);
199
+ }
200
+ buildHooksByEvent() {
201
+ const hooks = this.readAllHooks();
202
+ return this.groupHooksByEvent(hooks);
203
+ }
204
+ readAllHooks() {
205
+ return Metadata_1.Metadata.get(constants_1.CONTROLLER_EVENTS, Reflect) ?? [];
206
+ }
207
+ groupHooksByEvent(hooks) {
208
+ const grouped = new Map();
209
+ hooks.forEach((hook) => this.appendHook(grouped, hook));
210
+ return this.sortHookMap(grouped);
211
+ }
212
+ appendHook(grouped, hook) {
213
+ const list = grouped.get(hook.eventName) ?? [];
214
+ list.push(hook);
215
+ grouped.set(hook.eventName, list);
216
+ }
217
+ sortHookMap(grouped) {
218
+ const sorted = new Map();
219
+ grouped.forEach((hooks, event) => this.sortAndStore(sorted, event, hooks));
220
+ return sorted;
221
+ }
222
+ sortAndStore(target, event, hooks) {
223
+ target.set(event, this.sortHooksByPriority(hooks));
97
224
  }
98
225
  sortHooksByPriority(hooks) {
99
226
  return hooks.slice().sort((first, second) => {
@@ -123,8 +250,7 @@ let InjectorService = InjectorService_1 = class InjectorService {
123
250
  Context_1.Context,
124
251
  logger_service_1.LoggerService,
125
252
  default_routes_carno_1.DefaultRoutesCarno,
126
- cache_service_1.CacheService,
127
- request_logger_service_1.RequestLogger
253
+ cache_service_1.CacheService
128
254
  ];
129
255
  this.applicationConfig.providers = this.applicationConfig.providers || [];
130
256
  this.applicationConfig.providers.push(...defaults);
@@ -1,26 +1,36 @@
1
1
  import { Server } from 'bun';
2
2
  export declare class Context {
3
3
  query: Record<string, any>;
4
- body: Record<string, any>;
4
+ private _body;
5
5
  rawBody?: ArrayBuffer;
6
6
  param: Record<string, any>;
7
7
  req: Request;
8
8
  headers: Request["headers"];
9
9
  locals: Record<string, any>;
10
- trackingId: string;
11
10
  private resultStatus;
11
+ private _pendingRequest;
12
+ private _bodyParsed;
12
13
  private constructor();
14
+ get body(): Record<string, any>;
15
+ set body(value: Record<string, any>);
16
+ getBody(): Promise<Record<string, any>>;
17
+ isBodyParsed(): boolean;
13
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>;
14
21
  static createFromJob(job: any): Context;
15
22
  private setQuery;
16
23
  private setBody;
17
24
  private setReq;
18
25
  private setHeaders;
19
- private setTrackingId;
20
- private setTrackingIdFromJob;
21
26
  setParam(param: Record<string, any>): void;
22
27
  setResponseStatus(status: number): void;
23
28
  getResponseStatus(): number;
24
29
  private buildQueryObject;
25
30
  private resolveBody;
31
+ private parseJsonFromBuffer;
32
+ private parseJsonText;
33
+ private isEmptyBuffer;
34
+ private parseUrlEncodedFromBuffer;
35
+ private decodeBuffer;
26
36
  }
@@ -11,31 +11,68 @@ var __metadata = (this && this.__metadata) || function (k, v) {
11
11
  var Context_1;
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
13
  exports.Context = void 0;
14
+ const http_code_enum_1 = require("../commons/http-code.enum");
14
15
  const Injectable_decorator_1 = require("../commons/decorators/Injectable.decorator");
16
+ const HttpException_1 = require("../exceptions/HttpException");
15
17
  const provider_scope_1 = require("./provider-scope");
16
18
  let Context = Context_1 = class Context {
17
19
  constructor() {
18
20
  this.query = {};
19
- this.body = {};
21
+ this._body = {};
20
22
  this.param = {};
21
23
  this.headers = new Headers();
22
24
  this.locals = {};
25
+ this._pendingRequest = null;
26
+ this._bodyParsed = false;
27
+ }
28
+ get body() {
29
+ return this._body;
30
+ }
31
+ set body(value) {
32
+ this._body = value;
33
+ this._bodyParsed = true;
34
+ }
35
+ async getBody() {
36
+ if (!this._bodyParsed && this._pendingRequest) {
37
+ await this.resolveBody(this._pendingRequest);
38
+ this._pendingRequest = null;
39
+ this._bodyParsed = true;
40
+ }
41
+ return this._body;
42
+ }
43
+ isBodyParsed() {
44
+ return this._bodyParsed;
23
45
  }
24
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;
52
+ }
53
+ static createFromRequestSync(url, request, server) {
25
54
  const context = new Context_1();
26
55
  context.setQuery(url);
27
- if (request.method !== 'GET') {
28
- await context.resolveBody(request);
29
- }
30
56
  context.setReq(request);
31
57
  // @ts-ignore
32
58
  context.setHeaders(request.headers);
33
- context.setTrackingId(request);
59
+ if (request.method !== 'GET' && request.method !== 'HEAD') {
60
+ context._pendingRequest = request;
61
+ }
62
+ else {
63
+ context._bodyParsed = true;
64
+ }
65
+ return context;
66
+ }
67
+ static async createFromRequestWithBody(url, request, server) {
68
+ const context = Context_1.createFromRequestSync(url, request, server);
69
+ if (context._pendingRequest) {
70
+ await context.getBody();
71
+ }
34
72
  return context;
35
73
  }
36
74
  static createFromJob(job) {
37
75
  const context = new Context_1();
38
- context.setTrackingIdFromJob(job);
39
76
  return context;
40
77
  }
41
78
  // @ts-ignore
@@ -44,7 +81,7 @@ let Context = Context_1 = class Context {
44
81
  }
45
82
  setBody(body) {
46
83
  for (const [key, value] of body.entries()) {
47
- this.body[key] = value;
84
+ this._body[key] = value;
48
85
  }
49
86
  }
50
87
  setReq(req) {
@@ -53,27 +90,6 @@ let Context = Context_1 = class Context {
53
90
  setHeaders(headers) {
54
91
  this.headers = headers;
55
92
  }
56
- setTrackingId(request) {
57
- const headerTrackingId = request.headers.get('x-tracking-id');
58
- if (headerTrackingId) {
59
- this.trackingId = headerTrackingId;
60
- return;
61
- }
62
- this.trackingId = crypto.randomUUID();
63
- }
64
- setTrackingIdFromJob(job) {
65
- const trackingIdFromData = job.data?.__trackingId;
66
- if (trackingIdFromData) {
67
- this.trackingId = trackingIdFromData;
68
- return;
69
- }
70
- const trackingIdFromProperty = job.trackingId;
71
- if (trackingIdFromProperty) {
72
- this.trackingId = trackingIdFromProperty;
73
- return;
74
- }
75
- this.trackingId = crypto.randomUUID();
76
- }
77
93
  setParam(param) {
78
94
  this.param = param;
79
95
  }
@@ -88,16 +104,54 @@ let Context = Context_1 = class Context {
88
104
  }
89
105
  async resolveBody(request) {
90
106
  const contentType = request.headers.get('content-type') || '';
91
- this.rawBody = await request.clone().arrayBuffer();
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());
114
+ return;
115
+ }
116
+ // For all other content types, consume body once as ArrayBuffer from clone
117
+ this.rawBody = await clonedRequest.arrayBuffer();
92
118
  if (contentType.includes('application/json')) {
93
- this.body = await request.clone().json();
119
+ this._body = this.parseJsonFromBuffer(this.rawBody);
94
120
  return;
95
121
  }
96
- if (contentType.includes('application/x-www-form-urlencoded') || contentType.includes('multipart/form-data')) {
97
- this.setBody(await request.clone().formData());
122
+ if (contentType.includes('application/x-www-form-urlencoded')) {
123
+ this._body = this.parseUrlEncodedFromBuffer(this.rawBody);
98
124
  return;
99
125
  }
100
- this.body = { body: await request.clone().text() };
126
+ // Plain text or unknown content type
127
+ this._body = { body: this.decodeBuffer(this.rawBody) };
128
+ }
129
+ parseJsonFromBuffer(buffer) {
130
+ if (this.isEmptyBuffer(buffer)) {
131
+ return {};
132
+ }
133
+ return this.parseJsonText(this.decodeBuffer(buffer));
134
+ }
135
+ parseJsonText(text) {
136
+ try {
137
+ return JSON.parse(text);
138
+ }
139
+ catch {
140
+ throw new HttpException_1.HttpException("Invalid JSON body", http_code_enum_1.HttpCode.BAD_REQUEST);
141
+ }
142
+ }
143
+ isEmptyBuffer(buffer) {
144
+ return buffer.byteLength === 0;
145
+ }
146
+ parseUrlEncodedFromBuffer(buffer) {
147
+ if (buffer.byteLength === 0) {
148
+ return {};
149
+ }
150
+ const text = this.decodeBuffer(buffer);
151
+ return Object.fromEntries(new URLSearchParams(text));
152
+ }
153
+ decodeBuffer(buffer) {
154
+ return new TextDecoder().decode(buffer);
101
155
  }
102
156
  };
103
157
  exports.Context = Context;
@@ -0,0 +1,15 @@
1
+ import { CorsConfig } from './cors-config';
2
+ export declare class CorsHeadersCache {
3
+ private readonly config;
4
+ private readonly cache;
5
+ private readonly methodsString;
6
+ private readonly allowedHeadersString;
7
+ private readonly exposedHeadersString;
8
+ private readonly maxAgeString;
9
+ private readonly hasCredentials;
10
+ private readonly isWildcard;
11
+ constructor(config: CorsConfig);
12
+ get(origin: string): Record<string, string>;
13
+ private buildHeaders;
14
+ applyToResponse(response: Response, origin: string): Response;
15
+ }