@carno.js/core 0.2.4 → 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,9 +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;
19
20
  private corsCache?;
21
+ private readonly emptyLocals;
20
22
  private fetch;
21
23
  private server;
22
24
  constructor(config?: ApplicationConfig);
@@ -61,5 +63,7 @@ export declare class Carno {
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
@@ -17,6 +17,8 @@ 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);
@@ -179,17 +182,30 @@ class Carno {
179
182
  }
180
183
  }
181
184
  const urlParsed = parseUrl(request);
182
- const context = await Context_1.Context.createFromRequest(urlParsed, request, server);
183
- await this.injector.callHook(on_event_1.EventType.OnRequest, { context });
184
- const local = new LocalsContainer_1.LocalsContainer();
185
185
  const routePath = this.discoverRoutePath(urlParsed);
186
186
  const route = this.router.find(request.method.toLowerCase(), routePath);
187
187
  if (!route) {
188
188
  throw new HttpException_1.HttpException("Method not allowed", 404);
189
189
  }
190
+ const compiled = route.store;
191
+ const context = Context_1.Context.createFromRequestSync(urlParsed, request, server);
190
192
  context.param = route.params;
191
- local.set(Context_1.Context, context);
192
- 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
+ }
193
209
  if (this.isCorsEnabled()) {
194
210
  const origin = request.headers.get("origin");
195
211
  if (origin && this.isOriginAllowed(origin)) {
@@ -269,6 +285,17 @@ class Carno {
269
285
  close(closeActiveConnections = false) {
270
286
  this.server?.stop(closeActiveConnections);
271
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
+ }
272
299
  discoverRoutePath(url) {
273
300
  if (url?.pathname) {
274
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,23 +1,28 @@
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;
@@ -18,26 +18,61 @@ const provider_scope_1 = require("./provider-scope");
18
18
  let Context = Context_1 = class Context {
19
19
  constructor() {
20
20
  this.query = {};
21
- this.body = {};
21
+ this._body = {};
22
22
  this.param = {};
23
23
  this.headers = new Headers();
24
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;
25
45
  }
26
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) {
27
54
  const context = new Context_1();
28
55
  context.setQuery(url);
29
- if (request.method !== 'GET') {
30
- await context.resolveBody(request);
31
- }
32
56
  context.setReq(request);
33
57
  // @ts-ignore
34
58
  context.setHeaders(request.headers);
35
- 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
+ }
36
72
  return context;
37
73
  }
38
74
  static createFromJob(job) {
39
75
  const context = new Context_1();
40
- context.setTrackingIdFromJob(job);
41
76
  return context;
42
77
  }
43
78
  // @ts-ignore
@@ -46,7 +81,7 @@ let Context = Context_1 = class Context {
46
81
  }
47
82
  setBody(body) {
48
83
  for (const [key, value] of body.entries()) {
49
- this.body[key] = value;
84
+ this._body[key] = value;
50
85
  }
51
86
  }
52
87
  setReq(req) {
@@ -55,27 +90,6 @@ let Context = Context_1 = class Context {
55
90
  setHeaders(headers) {
56
91
  this.headers = headers;
57
92
  }
58
- setTrackingId(request) {
59
- const headerTrackingId = request.headers.get('x-tracking-id');
60
- if (headerTrackingId) {
61
- this.trackingId = headerTrackingId;
62
- return;
63
- }
64
- this.trackingId = crypto.randomUUID();
65
- }
66
- setTrackingIdFromJob(job) {
67
- const trackingIdFromData = job.data?.__trackingId;
68
- if (trackingIdFromData) {
69
- this.trackingId = trackingIdFromData;
70
- return;
71
- }
72
- const trackingIdFromProperty = job.trackingId;
73
- if (trackingIdFromProperty) {
74
- this.trackingId = trackingIdFromProperty;
75
- return;
76
- }
77
- this.trackingId = crypto.randomUUID();
78
- }
79
93
  setParam(param) {
80
94
  this.param = param;
81
95
  }
@@ -102,15 +116,15 @@ let Context = Context_1 = class Context {
102
116
  // For all other content types, consume body once as ArrayBuffer from clone
103
117
  this.rawBody = await clonedRequest.arrayBuffer();
104
118
  if (contentType.includes('application/json')) {
105
- this.body = this.parseJsonFromBuffer(this.rawBody);
119
+ this._body = this.parseJsonFromBuffer(this.rawBody);
106
120
  return;
107
121
  }
108
122
  if (contentType.includes('application/x-www-form-urlencoded')) {
109
- this.body = this.parseUrlEncodedFromBuffer(this.rawBody);
123
+ this._body = this.parseUrlEncodedFromBuffer(this.rawBody);
110
124
  return;
111
125
  }
112
126
  // Plain text or unknown content type
113
- this.body = { body: this.decodeBuffer(this.rawBody) };
127
+ this._body = { body: this.decodeBuffer(this.rawBody) };
114
128
  }
115
129
  parseJsonFromBuffer(buffer) {
116
130
  if (this.isEmptyBuffer(buffer)) {
package/dist/index.d.ts CHANGED
@@ -10,7 +10,6 @@ export * from "./constants";
10
10
  export * from "./utils";
11
11
  export * from "./default-routes-carno";
12
12
  export * from "./services/logger.service";
13
- export * from "./services/request-logger.service";
14
13
  export * from "./cache/cache.service";
15
14
  export * from "./cache/bento-cache.driver";
16
15
  export * from "./testing";
package/dist/index.js CHANGED
@@ -26,7 +26,6 @@ __exportStar(require("./constants"), exports);
26
26
  __exportStar(require("./utils"), exports);
27
27
  __exportStar(require("./default-routes-carno"), exports);
28
28
  __exportStar(require("./services/logger.service"), exports);
29
- __exportStar(require("./services/request-logger.service"), exports);
30
29
  __exportStar(require("./cache/cache.service"), exports);
31
30
  __exportStar(require("./cache/bento-cache.driver"), exports);
32
31
  __exportStar(require("./testing"), exports);
@@ -0,0 +1,23 @@
1
+ import type { Context } from '../domain/Context';
2
+ import type { TokenRouteWithProvider } from '../container/ContainerConfiguration';
3
+ export declare enum RouteType {
4
+ SIMPLE = 0,
5
+ STANDARD = 1,
6
+ COMPLEX = 2
7
+ }
8
+ export type ParamResolver = (context: Context) => any;
9
+ export type AsyncParamResolver = (context: Context) => Promise<any>;
10
+ export type CompiledHandler = (context: Context) => any;
11
+ export type AsyncCompiledHandler = (context: Context) => Promise<any>;
12
+ export interface CompiledRoute {
13
+ routeType: RouteType;
14
+ controllerInstance: any | null;
15
+ boundHandler: CompiledHandler | AsyncCompiledHandler | null;
16
+ paramResolvers: (ParamResolver | AsyncParamResolver | null)[];
17
+ needsLocalsContainer: boolean;
18
+ hasMiddlewares: boolean;
19
+ hasValidation: boolean;
20
+ validationIndices: number[];
21
+ isAsync: boolean;
22
+ original: TokenRouteWithProvider;
23
+ }
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RouteType = void 0;
4
+ var RouteType;
5
+ (function (RouteType) {
6
+ RouteType[RouteType["SIMPLE"] = 0] = "SIMPLE";
7
+ RouteType[RouteType["STANDARD"] = 1] = "STANDARD";
8
+ RouteType[RouteType["COMPLEX"] = 2] = "COMPLEX";
9
+ })(RouteType || (exports.RouteType = RouteType = {}));
@@ -0,0 +1,4 @@
1
+ import type { Context } from '../domain/Context';
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;
@@ -0,0 +1,19 @@
1
+ "use strict";
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');
8
+ }
9
+ if (compiled.isAsync) {
10
+ return compiled.boundHandler(context);
11
+ }
12
+ return compiled.boundHandler(context);
13
+ }
14
+ function executeSimpleRouteSync(compiled, context) {
15
+ if (!compiled.boundHandler) {
16
+ throw new Error('Simple route must have a bound handler');
17
+ }
18
+ return compiled.boundHandler(context);
19
+ }
@@ -0,0 +1,4 @@
1
+ import type { ParamInfo } from './ParamResolverFactory';
2
+ import type { CompiledHandler, AsyncCompiledHandler } from './CompiledRoute';
3
+ 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;
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.compileRouteHandler = compileRouteHandler;
4
+ exports.compileValidatedHandler = compileValidatedHandler;
5
+ function escapeKey(key) {
6
+ return key.replace(/['"\\]/g, '\\$&');
7
+ }
8
+ function buildArgExpression(param, index) {
9
+ const key = param.key ? escapeKey(param.key) : undefined;
10
+ switch (param.type) {
11
+ case 'param':
12
+ return key ? `ctx.param['${key}']` : 'ctx.param';
13
+ case 'query':
14
+ return key ? `ctx.query['${key}']` : 'ctx.query';
15
+ case 'body':
16
+ return key ? `ctx.body['${key}']` : 'ctx.body';
17
+ case 'headers':
18
+ return key
19
+ ? `ctx.headers.get('${key}')`
20
+ : 'ctx.headers';
21
+ case 'req':
22
+ return 'ctx.req';
23
+ case 'locals':
24
+ return 'ctx.locals';
25
+ case 'di':
26
+ return `resolvers[${index}](ctx)`;
27
+ default:
28
+ return `resolvers[${index}](ctx)`;
29
+ }
30
+ }
31
+ function compileRouteHandler(instance, methodName, paramInfos) {
32
+ const handler = instance[methodName].bind(instance);
33
+ if (paramInfos.length === 0) {
34
+ return () => handler();
35
+ }
36
+ const hasBodyParam = paramInfos.some((p) => p.type === 'body');
37
+ const hasDIParam = paramInfos.some((p) => p.type === 'di');
38
+ if (hasDIParam) {
39
+ return createFallbackHandler(handler, paramInfos);
40
+ }
41
+ const argExpressions = paramInfos.map(buildArgExpression);
42
+ if (hasBodyParam) {
43
+ return createAsyncHandler(handler, argExpressions);
44
+ }
45
+ return createSyncHandler(handler, argExpressions);
46
+ }
47
+ function createSyncHandler(handler, argExpressions) {
48
+ const code = `
49
+ return function compiledHandler(ctx) {
50
+ return handler(${argExpressions.join(', ')});
51
+ }
52
+ `;
53
+ return new Function('handler', code)(handler);
54
+ }
55
+ function createAsyncHandler(handler, argExpressions) {
56
+ const code = `
57
+ return async function compiledHandler(ctx) {
58
+ await ctx.getBody();
59
+ return handler(${argExpressions.join(', ')});
60
+ }
61
+ `;
62
+ return new Function('handler', code)(handler);
63
+ }
64
+ function createFallbackHandler(handler, paramInfos) {
65
+ return (ctx) => {
66
+ const args = [];
67
+ for (const param of paramInfos) {
68
+ switch (param.type) {
69
+ case 'param':
70
+ args.push(param.key ? ctx.param[param.key] : ctx.param);
71
+ break;
72
+ case 'query':
73
+ args.push(param.key ? ctx.query[param.key] : ctx.query);
74
+ break;
75
+ case 'body':
76
+ args.push(param.key ? ctx.body[param.key] : ctx.body);
77
+ break;
78
+ case 'headers':
79
+ args.push(param.key ? ctx.headers.get(param.key) : ctx.headers);
80
+ break;
81
+ case 'req':
82
+ args.push(ctx.req);
83
+ break;
84
+ case 'locals':
85
+ args.push(ctx.locals);
86
+ break;
87
+ default:
88
+ args.push(undefined);
89
+ break;
90
+ }
91
+ }
92
+ return handler(...args);
93
+ };
94
+ }
95
+ function compileValidatedHandler(instance, methodName, paramInfos, validators) {
96
+ const handler = instance[methodName].bind(instance);
97
+ const hasBodyParam = paramInfos.some((p) => p.type === 'body');
98
+ const resolveArg = (ctx, param, index) => {
99
+ let value;
100
+ switch (param.type) {
101
+ case 'param':
102
+ value = param.key ? ctx.param[param.key] : ctx.param;
103
+ break;
104
+ case 'query':
105
+ value = param.key ? ctx.query[param.key] : ctx.query;
106
+ break;
107
+ case 'body':
108
+ value = param.key ? ctx.body[param.key] : ctx.body;
109
+ break;
110
+ case 'headers':
111
+ value = param.key ? ctx.headers.get(param.key) : ctx.headers;
112
+ break;
113
+ case 'req':
114
+ value = ctx.req;
115
+ break;
116
+ case 'locals':
117
+ value = ctx.locals;
118
+ break;
119
+ default:
120
+ value = undefined;
121
+ }
122
+ if (param.needsValidation && validators[index]) {
123
+ return validators[index](value);
124
+ }
125
+ return value;
126
+ };
127
+ if (hasBodyParam) {
128
+ return async (ctx) => {
129
+ await ctx.getBody();
130
+ const args = paramInfos.map((p, i) => resolveArg(ctx, p, i));
131
+ return handler(...args);
132
+ };
133
+ }
134
+ return (ctx) => {
135
+ const args = paramInfos.map((p, i) => resolveArg(ctx, p, i));
136
+ return handler(...args);
137
+ };
138
+ }
@@ -0,0 +1,17 @@
1
+ import { type ValidatorOptions } from 'class-validator';
2
+ import type { Context } from '../domain/Context';
3
+ import type { ParamResolver, AsyncParamResolver } from './CompiledRoute';
4
+ export interface ParamDecoratorMeta {
5
+ fun: (context: Context, data?: any) => any;
6
+ param?: any;
7
+ }
8
+ export interface ParamInfo {
9
+ type: 'body' | 'query' | 'param' | 'headers' | 'req' | 'locals' | 'di';
10
+ key?: string;
11
+ needsValidation: boolean;
12
+ token?: any;
13
+ }
14
+ export declare function analyzeParamDecorator(decoratorMeta: ParamDecoratorMeta | undefined, token: any): ParamInfo;
15
+ export declare function createParamResolver(decoratorMeta: ParamDecoratorMeta | undefined, token: any, validationConfig?: ValidatorOptions): ParamResolver | AsyncParamResolver | null;
16
+ export declare function createParamResolvers(paramMetas: Record<number, ParamDecoratorMeta> | undefined, argTypes: any[], validationConfig?: ValidatorOptions): (ParamResolver | AsyncParamResolver | null)[];
17
+ export declare function hasAnyDIParam(resolvers: (ParamResolver | AsyncParamResolver | null)[]): boolean;
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.analyzeParamDecorator = analyzeParamDecorator;
4
+ exports.createParamResolver = createParamResolver;
5
+ exports.createParamResolvers = createParamResolvers;
6
+ exports.hasAnyDIParam = hasAnyDIParam;
7
+ const class_transformer_1 = require("class-transformer");
8
+ const class_validator_1 = require("class-validator");
9
+ const HttpException_1 = require("../exceptions/HttpException");
10
+ const ValidationCache_1 = require("../utils/ValidationCache");
11
+ function analyzeParamDecorator(decoratorMeta, token) {
12
+ if (!decoratorMeta) {
13
+ return { type: 'di', needsValidation: false, token };
14
+ }
15
+ const funcStr = decoratorMeta.fun.toString();
16
+ const key = decoratorMeta.param;
17
+ const needsValidation = typeof token === 'function' && (0, ValidationCache_1.isValidatable)(token);
18
+ if (funcStr.includes('context.body')) {
19
+ return { type: 'body', key, needsValidation, token };
20
+ }
21
+ if (funcStr.includes('context.query')) {
22
+ return { type: 'query', key, needsValidation, token };
23
+ }
24
+ if (funcStr.includes('context.param')) {
25
+ return { type: 'param', key, needsValidation, token };
26
+ }
27
+ if (funcStr.includes('context.headers')) {
28
+ return { type: 'headers', key, needsValidation, token };
29
+ }
30
+ if (funcStr.includes('context.req')) {
31
+ return { type: 'req', needsValidation: false, token };
32
+ }
33
+ if (funcStr.includes('context.locals')) {
34
+ return { type: 'locals', needsValidation: false, token };
35
+ }
36
+ return { type: 'di', needsValidation: false, token };
37
+ }
38
+ function createValidationResolver(extractFn, token, validationConfig) {
39
+ return (context) => {
40
+ const value = extractFn(context);
41
+ const obj = (0, class_transformer_1.plainToInstance)(token, value);
42
+ const errors = (0, class_validator_1.validateSync)(obj, validationConfig);
43
+ if (errors.length > 0) {
44
+ throw new HttpException_1.HttpException(errors, 400);
45
+ }
46
+ return obj;
47
+ };
48
+ }
49
+ function createParamResolver(decoratorMeta, token, validationConfig) {
50
+ if (!decoratorMeta) {
51
+ return null;
52
+ }
53
+ const extractFn = (context) => decoratorMeta.fun(context, decoratorMeta.param);
54
+ const needsValidation = typeof token === 'function' && (0, ValidationCache_1.isValidatable)(token);
55
+ if (needsValidation) {
56
+ return createValidationResolver(extractFn, token, validationConfig);
57
+ }
58
+ return extractFn;
59
+ }
60
+ function createParamResolvers(paramMetas, argTypes, validationConfig) {
61
+ const resolvers = [];
62
+ for (let i = 0; i < argTypes.length; i++) {
63
+ const meta = paramMetas?.[i];
64
+ const token = argTypes[i];
65
+ resolvers.push(createParamResolver(meta, token, validationConfig));
66
+ }
67
+ return resolvers;
68
+ }
69
+ function hasAnyDIParam(resolvers) {
70
+ return resolvers.some((r) => r === null);
71
+ }
@@ -0,0 +1,29 @@
1
+ import { type ValidatorOptions } from 'class-validator';
2
+ import type { TokenRouteWithProvider } from '../container/ContainerConfiguration';
3
+ import type { Container } from '../container/container';
4
+ import { ProviderScope } from '../domain/provider-scope';
5
+ import { type CompiledRoute } from './CompiledRoute';
6
+ export interface RouteCompilerOptions {
7
+ container: Container;
8
+ controllerScopes: Map<any, ProviderScope>;
9
+ validationConfig?: ValidatorOptions;
10
+ hasOnRequestHook: boolean;
11
+ hasOnResponseHook: boolean;
12
+ }
13
+ export declare class RouteCompiler {
14
+ private container;
15
+ private controllerScopes;
16
+ private validationConfig?;
17
+ private hasOnRequestHook;
18
+ private hasOnResponseHook;
19
+ constructor(options: RouteCompilerOptions);
20
+ compile(route: TokenRouteWithProvider): CompiledRoute | null;
21
+ private classifyRoute;
22
+ private analyzeMethodParams;
23
+ private createSimpleRoute;
24
+ private createStandardRouteWithInstance;
25
+ private createStandardRoute;
26
+ private createComplexRoute;
27
+ private createFallbackRoute;
28
+ private createValidatedHandler;
29
+ }
@@ -0,0 +1,209 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RouteCompiler = void 0;
4
+ const class_transformer_1 = require("class-transformer");
5
+ const class_validator_1 = require("class-validator");
6
+ const provider_scope_1 = require("../domain/provider-scope");
7
+ const Metadata_1 = require("../domain/Metadata");
8
+ const HttpException_1 = require("../exceptions/HttpException");
9
+ const getMethodArgTypes_1 = require("../utils/getMethodArgTypes");
10
+ const CompiledRoute_1 = require("./CompiledRoute");
11
+ const ParamResolverFactory_1 = require("./ParamResolverFactory");
12
+ const JITCompiler_1 = require("./JITCompiler");
13
+ class RouteCompiler {
14
+ constructor(options) {
15
+ this.container = options.container;
16
+ this.controllerScopes = options.controllerScopes;
17
+ this.validationConfig = options.validationConfig;
18
+ this.hasOnRequestHook = options.hasOnRequestHook;
19
+ this.hasOnResponseHook = options.hasOnResponseHook;
20
+ }
21
+ compile(route) {
22
+ if (!route || !route.provider) {
23
+ return null;
24
+ }
25
+ const provider = this.container.get(route.provider);
26
+ if (!provider) {
27
+ return this.createFallbackRoute(route);
28
+ }
29
+ const scope = this.controllerScopes.get(provider.token);
30
+ const isSingleton = scope === provider_scope_1.ProviderScope.SINGLETON;
31
+ const instance = isSingleton ? provider.instance : null;
32
+ if (!instance) {
33
+ return this.createStandardRoute(route, provider);
34
+ }
35
+ const paramInfos = this.analyzeMethodParams(instance, route.methodName);
36
+ const routeType = this.classifyRoute(route, paramInfos, isSingleton);
37
+ if (routeType === CompiledRoute_1.RouteType.SIMPLE) {
38
+ return this.createSimpleRoute(route, instance, paramInfos);
39
+ }
40
+ if (routeType === CompiledRoute_1.RouteType.STANDARD) {
41
+ return this.createStandardRouteWithInstance(route, instance, paramInfos);
42
+ }
43
+ return this.createComplexRoute(route, paramInfos);
44
+ }
45
+ classifyRoute(route, paramInfos, isSingleton) {
46
+ if (!isSingleton) {
47
+ return CompiledRoute_1.RouteType.COMPLEX;
48
+ }
49
+ const hasMiddlewares = route.middlewares.length > 0;
50
+ const hasDIParams = paramInfos.some((p) => p.type === 'di');
51
+ const hasHooks = this.hasOnRequestHook || this.hasOnResponseHook;
52
+ if (hasMiddlewares || hasDIParams || hasHooks) {
53
+ return CompiledRoute_1.RouteType.STANDARD;
54
+ }
55
+ return CompiledRoute_1.RouteType.SIMPLE;
56
+ }
57
+ analyzeMethodParams(instance, methodName) {
58
+ const argTypes = (0, getMethodArgTypes_1.getMethodArgTypes)(instance, methodName);
59
+ const decoratorMetas = Metadata_1.Metadata.getParamDecoratorFunc(instance, methodName);
60
+ return argTypes.map((token, index) => {
61
+ const meta = decoratorMetas?.[index];
62
+ return (0, ParamResolverFactory_1.analyzeParamDecorator)(meta, token);
63
+ });
64
+ }
65
+ createSimpleRoute(route, instance, paramInfos) {
66
+ const hasValidation = paramInfos.some((p) => p.needsValidation);
67
+ const validationIndices = paramInfos
68
+ .map((p, i) => (p.needsValidation ? i : -1))
69
+ .filter((i) => i >= 0);
70
+ const hasBodyParam = paramInfos.some((p) => p.type === 'body');
71
+ let boundHandler;
72
+ if (hasValidation) {
73
+ boundHandler = this.createValidatedHandler(instance, route.methodName, paramInfos, hasBodyParam);
74
+ }
75
+ else {
76
+ boundHandler = (0, JITCompiler_1.compileRouteHandler)(instance, route.methodName, paramInfos);
77
+ }
78
+ return {
79
+ routeType: CompiledRoute_1.RouteType.SIMPLE,
80
+ controllerInstance: instance,
81
+ boundHandler,
82
+ paramResolvers: [],
83
+ needsLocalsContainer: false,
84
+ hasMiddlewares: false,
85
+ hasValidation,
86
+ validationIndices,
87
+ isAsync: hasBodyParam,
88
+ original: route,
89
+ };
90
+ }
91
+ createStandardRouteWithInstance(route, instance, paramInfos) {
92
+ const hasValidation = paramInfos.some((p) => p.needsValidation);
93
+ const validationIndices = paramInfos
94
+ .map((p, i) => (p.needsValidation ? i : -1))
95
+ .filter((i) => i >= 0);
96
+ const hasDIParams = paramInfos.some((p) => p.type === 'di');
97
+ const hasBodyParam = paramInfos.some((p) => p.type === 'body');
98
+ const hasMiddlewares = route.middlewares.length > 0;
99
+ const needsLocalsContainer = hasDIParams || hasMiddlewares;
100
+ return {
101
+ routeType: CompiledRoute_1.RouteType.STANDARD,
102
+ controllerInstance: instance,
103
+ boundHandler: null,
104
+ paramResolvers: [],
105
+ needsLocalsContainer,
106
+ hasMiddlewares,
107
+ hasValidation,
108
+ validationIndices,
109
+ isAsync: hasBodyParam || hasDIParams,
110
+ original: route,
111
+ };
112
+ }
113
+ createStandardRoute(route, provider) {
114
+ return {
115
+ routeType: CompiledRoute_1.RouteType.STANDARD,
116
+ controllerInstance: null,
117
+ boundHandler: null,
118
+ paramResolvers: [],
119
+ needsLocalsContainer: true,
120
+ hasMiddlewares: route.middlewares.length > 0,
121
+ hasValidation: false,
122
+ validationIndices: [],
123
+ isAsync: true,
124
+ original: route,
125
+ };
126
+ }
127
+ createComplexRoute(route, paramInfos) {
128
+ const hasValidation = paramInfos.some((p) => p.needsValidation);
129
+ const validationIndices = paramInfos
130
+ .map((p, i) => (p.needsValidation ? i : -1))
131
+ .filter((i) => i >= 0);
132
+ return {
133
+ routeType: CompiledRoute_1.RouteType.COMPLEX,
134
+ controllerInstance: null,
135
+ boundHandler: null,
136
+ paramResolvers: [],
137
+ needsLocalsContainer: true,
138
+ hasMiddlewares: route.middlewares.length > 0,
139
+ hasValidation,
140
+ validationIndices,
141
+ isAsync: true,
142
+ original: route,
143
+ };
144
+ }
145
+ createFallbackRoute(route) {
146
+ return {
147
+ routeType: CompiledRoute_1.RouteType.COMPLEX,
148
+ controllerInstance: null,
149
+ boundHandler: null,
150
+ paramResolvers: [],
151
+ needsLocalsContainer: true,
152
+ hasMiddlewares: route.middlewares.length > 0,
153
+ hasValidation: false,
154
+ validationIndices: [],
155
+ isAsync: true,
156
+ original: route,
157
+ };
158
+ }
159
+ createValidatedHandler(instance, methodName, paramInfos, hasBodyParam) {
160
+ const handler = instance[methodName].bind(instance);
161
+ const config = this.validationConfig;
162
+ const resolveArg = (ctx, param) => {
163
+ let value;
164
+ switch (param.type) {
165
+ case 'param':
166
+ value = param.key ? ctx.param[param.key] : ctx.param;
167
+ break;
168
+ case 'query':
169
+ value = param.key ? ctx.query[param.key] : ctx.query;
170
+ break;
171
+ case 'body':
172
+ value = param.key ? ctx.body[param.key] : ctx.body;
173
+ break;
174
+ case 'headers':
175
+ value = param.key ? ctx.headers.get(param.key) : ctx.headers;
176
+ break;
177
+ case 'req':
178
+ value = ctx.req;
179
+ break;
180
+ case 'locals':
181
+ value = ctx.locals;
182
+ break;
183
+ default:
184
+ value = undefined;
185
+ }
186
+ if (param.needsValidation && param.token) {
187
+ const obj = (0, class_transformer_1.plainToInstance)(param.token, value);
188
+ const errors = (0, class_validator_1.validateSync)(obj, config);
189
+ if (errors.length > 0) {
190
+ throw new HttpException_1.HttpException(errors, 400);
191
+ }
192
+ return obj;
193
+ }
194
+ return value;
195
+ };
196
+ if (hasBodyParam) {
197
+ return async (ctx) => {
198
+ await ctx.getBody();
199
+ const args = paramInfos.map((p) => resolveArg(ctx, p));
200
+ return handler(...args);
201
+ };
202
+ }
203
+ return (ctx) => {
204
+ const args = paramInfos.map((p) => resolveArg(ctx, p));
205
+ return handler(...args);
206
+ };
207
+ }
208
+ }
209
+ exports.RouteCompiler = RouteCompiler;
@@ -1,10 +1,11 @@
1
1
  import { InjectorService, TokenRouteWithProvider } from "../container";
2
2
  import { Context, LocalsContainer } from "../domain";
3
+ import type { CompiledRoute } from "./CompiledRoute";
3
4
  declare class Router {
4
5
  private readonly jsonHeaders;
5
6
  private readonly textHeaders;
6
- executeRoute(route: TokenRouteWithProvider, injector: InjectorService, context: Context, locals: LocalsContainer): Promise<Response>;
7
- private mountResponse;
7
+ executeRoute(routeStore: CompiledRoute | TokenRouteWithProvider, injector: InjectorService, context: Context, locals: LocalsContainer): Promise<Response>;
8
+ mountResponse(result: unknown, context: Context): Response;
8
9
  private isNativeResponse;
9
10
  private isBodyInit;
10
11
  private createJsonResponse;
@@ -7,15 +7,23 @@ class Router {
7
7
  this.jsonHeaders = { "Content-Type": "application/json" };
8
8
  this.textHeaders = { "Content-Type": "text/html" };
9
9
  }
10
- async executeRoute(route, injector, context, locals) {
11
- const provider = injector.invoke(route.provider, locals);
12
- route.provider.instance = provider;
13
- // @ts-ignore
14
- if (!provider[route.methodName]) {
10
+ async executeRoute(routeStore, injector, context, locals) {
11
+ const isCompiled = routeStore.routeType !== undefined;
12
+ const tokenRoute = isCompiled
13
+ ? routeStore.original
14
+ : routeStore;
15
+ const controllerInstance = isCompiled
16
+ ? routeStore.controllerInstance
17
+ : null;
18
+ const controller = controllerInstance
19
+ ?? injector.resolveControllerInstance(tokenRoute.provider, locals);
20
+ if (!controller[tokenRoute.methodName]) {
15
21
  throw new Error("Controller not found");
16
22
  }
17
- const result = await injector.invokeRoute(route, context, locals);
18
- await injector.callHook(events_1.EventType.OnResponse, { context, result });
23
+ const result = await injector.invokeRoute(tokenRoute, context, locals, controller);
24
+ if (injector.hasOnResponseHook()) {
25
+ await injector.callHook(events_1.EventType.OnResponse, { context, result });
26
+ }
19
27
  return this.mountResponse(result, context);
20
28
  }
21
29
  mountResponse(result, context) {
@@ -21,5 +21,9 @@ export declare class Memoirist<T> {
21
21
  private static regex;
22
22
  add(method: string, path: string, store: T): FindResult<T>['store'];
23
23
  find(method: string, url: string): FindResult<T> | null;
24
+ updateStore(method: string, path: string, oldStore: T, newStore: T): boolean;
25
+ private updateHistoryStore;
26
+ private normalizePath;
27
+ private findNode;
24
28
  }
25
29
  export default Memoirist;
@@ -167,11 +167,100 @@ class Memoirist {
167
167
  return null;
168
168
  return matchRoute(url, url.length, root, 0);
169
169
  }
170
+ updateStore(method, path, oldStore, newStore) {
171
+ const node = this.findNode(method, path);
172
+ if (!node) {
173
+ return false;
174
+ }
175
+ if (node.store === oldStore) {
176
+ node.store = newStore;
177
+ this.updateHistoryStore(method, path, newStore);
178
+ return true;
179
+ }
180
+ if (node.params?.store === oldStore) {
181
+ node.params.store = newStore;
182
+ this.updateHistoryStore(method, path, newStore);
183
+ return true;
184
+ }
185
+ if (node.wildcardStore === oldStore) {
186
+ node.wildcardStore = newStore;
187
+ this.updateHistoryStore(method, path, newStore);
188
+ return true;
189
+ }
190
+ return false;
191
+ }
192
+ updateHistoryStore(method, path, newStore) {
193
+ const normalizedPath = this.normalizePath(path);
194
+ for (let i = 0; i < this.history.length; i++) {
195
+ const [m, p] = this.history[i];
196
+ if (m === method && this.normalizePath(p) === normalizedPath) {
197
+ this.history[i] = [method, p, newStore];
198
+ break;
199
+ }
200
+ }
201
+ }
202
+ normalizePath(path) {
203
+ if (path === '') {
204
+ return '/';
205
+ }
206
+ return path[0] !== '/' ? `/${path}` : path;
207
+ }
208
+ findNode(method, path) {
209
+ if (path === '') {
210
+ path = '/';
211
+ }
212
+ else if (path[0] !== '/') {
213
+ path = `/${path}`;
214
+ }
215
+ const isWildcard = path[path.length - 1] === '*';
216
+ if (isWildcard) {
217
+ path = path.slice(0, -1);
218
+ }
219
+ const inertParts = path.split(Memoirist.regex.static);
220
+ const paramParts = path.match(Memoirist.regex.params) || [];
221
+ if (inertParts[inertParts.length - 1] === '') {
222
+ inertParts.pop();
223
+ }
224
+ let node = this.root[method];
225
+ if (!node) {
226
+ return null;
227
+ }
228
+ let paramPartsIndex = 0;
229
+ for (let i = 0; i < inertParts.length; ++i) {
230
+ let part = inertParts[i];
231
+ if (i > 0) {
232
+ paramPartsIndex++;
233
+ if (!node.params?.inert) {
234
+ return null;
235
+ }
236
+ node = node.params.inert;
237
+ }
238
+ for (let j = 0;;) {
239
+ if (j === part.length) {
240
+ break;
241
+ }
242
+ if (j === node.part.length) {
243
+ if (!node.inert?.has(part.charCodeAt(j))) {
244
+ return null;
245
+ }
246
+ node = node.inert.get(part.charCodeAt(j));
247
+ part = part.slice(j);
248
+ j = 0;
249
+ continue;
250
+ }
251
+ if (part[j] !== node.part[j]) {
252
+ return null;
253
+ }
254
+ ++j;
255
+ }
256
+ }
257
+ return node;
258
+ }
170
259
  }
171
260
  exports.Memoirist = Memoirist;
172
261
  Memoirist.regex = {
173
- static: /:.+?(?=\/|$)/,
174
- params: /:.+?(?=\/|$)/g
262
+ static: /:[^/]+/,
263
+ params: /:[^/]+/g
175
264
  };
176
265
  const matchRoute = (url, urlLength, node, startIndex) => {
177
266
  const part = node?.part;
@@ -0,0 +1,3 @@
1
+ export declare function isValidatable(token: Function): boolean;
2
+ export declare function preloadValidationForParams(args: any[]): number[];
3
+ export declare function clearValidationCache(): void;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isValidatable = isValidatable;
4
+ exports.preloadValidationForParams = preloadValidationForParams;
5
+ exports.clearValidationCache = clearValidationCache;
6
+ const isClassValidator_1 = require("./isClassValidator");
7
+ const cache = new Map();
8
+ function isValidatable(token) {
9
+ let result = cache.get(token);
10
+ if (result === undefined) {
11
+ result = (0, isClassValidator_1.isClassValidator)(token);
12
+ cache.set(token, result);
13
+ }
14
+ return result;
15
+ }
16
+ function preloadValidationForParams(args) {
17
+ const indices = [];
18
+ for (let i = 0; i < args.length; i++) {
19
+ if (typeof args[i] === 'function' && isValidatable(args[i])) {
20
+ indices.push(i);
21
+ }
22
+ }
23
+ return indices;
24
+ }
25
+ function clearValidationCache() {
26
+ cache.clear();
27
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@carno.js/core",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "Carno.js is a framework for building web applications object oriented with TypeScript and Bun.sh",
5
5
  "keywords": [
6
6
  "bun",
@@ -51,5 +51,5 @@
51
51
  "publishConfig": {
52
52
  "access": "public"
53
53
  },
54
- "gitHead": "91d257751a1386de821cd4dd3252fce3c070cfca"
54
+ "gitHead": "2be3063988696f2cd3eb13363e1f679b9f58c2f1"
55
55
  }