@carno.js/core 0.2.10 → 0.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/Carno.d.ts CHANGED
@@ -12,6 +12,7 @@ export interface ApplicationConfig<TAdapter extends ValidatorAdapterConstructor
12
12
  providers?: any[];
13
13
  cors?: CorsConfig;
14
14
  globalMiddlewares?: any[];
15
+ disableStartupLog?: boolean;
15
16
  }
16
17
  export declare class Carno<TAdapter extends ValidatorAdapterConstructor = ValidatorAdapterConstructor> {
17
18
  config: ApplicationConfig<TAdapter>;
@@ -20,6 +21,8 @@ export declare class Carno<TAdapter extends ValidatorAdapterConstructor = Valida
20
21
  private corsCache?;
21
22
  private readonly emptyLocals;
22
23
  private validatorAdapter;
24
+ private corsEnabled;
25
+ private hasOnRequestHook;
23
26
  private fetch;
24
27
  private server;
25
28
  constructor(config?: ApplicationConfig<TAdapter>);
@@ -53,7 +56,7 @@ export declare class Carno<TAdapter extends ValidatorAdapterConstructor = Valida
53
56
  getHttpServer(): Server<any>;
54
57
  getInjector(): import("./container").InjectorService;
55
58
  private createHttpServer;
56
- private fetcher;
59
+ private fetcherAsync;
57
60
  private catcher;
58
61
  private bootstrapApplication;
59
62
  private handleShutdownHook;
@@ -62,6 +65,7 @@ export declare class Carno<TAdapter extends ValidatorAdapterConstructor = Valida
62
65
  private reportHookFailure;
63
66
  private resolveLogger;
64
67
  private isCorsEnabled;
68
+ private errorResponse;
65
69
  private handlePreflightRequest;
66
70
  private applyCorsHeaders;
67
71
  close(closeActiveConnections?: boolean): void;
package/dist/Carno.js CHANGED
@@ -21,36 +21,52 @@ const RouteExecutor_1 = require("./route/RouteExecutor");
21
21
  const memoirist_1 = __importDefault(require("./route/memoirist"));
22
22
  const CompiledRoute_1 = require("./route/CompiledRoute");
23
23
  const logger_service_1 = require("./services/logger.service");
24
- const parseUrl = require("parseurl-fast");
24
+ const METHOD_MAP = {
25
+ GET: 'get',
26
+ POST: 'post',
27
+ PUT: 'put',
28
+ DELETE: 'delete',
29
+ PATCH: 'patch',
30
+ HEAD: 'head',
31
+ OPTIONS: 'options'
32
+ };
25
33
  class Carno {
26
34
  constructor(config = {}) {
27
35
  this.config = config;
28
36
  this.router = new memoirist_1.default();
29
37
  this.injector = (0, createInjector_1.createInjector)();
30
38
  this.emptyLocals = new LocalsContainer_1.LocalsContainer();
31
- this.fetch = async (request, server) => {
32
- try {
33
- return await this.fetcher(request, server);
34
- }
35
- catch (error) {
36
- if (error instanceof HttpException_1.HttpException) {
37
- let response = new Response(JSON.stringify({
38
- message: error.getResponse(),
39
- statusCode: error.getStatus(),
40
- }), {
41
- status: error.statusCode,
42
- headers: { "Content-Type": "application/json" },
43
- });
44
- if (this.isCorsEnabled()) {
45
- const origin = request.headers.get("origin");
46
- if (origin && this.corsCache.isOriginAllowed(origin)) {
47
- response = this.applyCorsHeaders(response, origin);
48
- }
49
- }
50
- return response;
39
+ this.corsEnabled = false;
40
+ this.hasOnRequestHook = false;
41
+ this.fetch = (request, server) => {
42
+ const method = request.method;
43
+ if (this.corsEnabled) {
44
+ const origin = request.headers.get("origin");
45
+ if (method === "OPTIONS" && origin) {
46
+ return this.handlePreflightRequest(request);
51
47
  }
52
- throw error;
53
48
  }
49
+ const url = request.url;
50
+ const startIndex = url.indexOf('/', 12);
51
+ const queryIndex = url.indexOf('?', startIndex);
52
+ const pathname = queryIndex === -1
53
+ ? (startIndex === -1 ? '/' : url.slice(startIndex))
54
+ : url.slice(startIndex, queryIndex);
55
+ const methodLower = METHOD_MAP[method] || method.toLowerCase();
56
+ const route = this.router.find(methodLower, pathname);
57
+ if (!route) {
58
+ return this.errorResponse(request, "Method not allowed", 404);
59
+ }
60
+ const compiled = route.store;
61
+ const isCompiledRoute = compiled.routeType !== undefined;
62
+ const isSimpleRoute = isCompiledRoute && compiled.routeType === CompiledRoute_1.RouteType.SIMPLE;
63
+ const isGetOrHead = method === 'GET' || method === 'HEAD';
64
+ const hasQuery = queryIndex !== -1;
65
+ if (isSimpleRoute && isGetOrHead && !this.corsEnabled && !hasQuery && !compiled.isAsync) {
66
+ const context = Context_1.Context.createFastContext(request, route.params);
67
+ return compiled.boundHandler(context);
68
+ }
69
+ return this.fetcherAsync(request, server, route, compiled, isSimpleRoute, hasQuery, queryIndex, url);
54
70
  };
55
71
  this.catcher = (error) => {
56
72
  this.resolveLogger().error("Unhandled error", error);
@@ -161,6 +177,8 @@ class Carno {
161
177
  (0, ValidationCache_1.setValidatorAdapter)(this.validatorAdapter);
162
178
  this.loadProvidersAndControllers();
163
179
  await this.injector.loadModule((0, createContainer_1.createContainer)(), this.config, this.router, this.validatorAdapter);
180
+ this.corsEnabled = !!this.config.cors;
181
+ this.hasOnRequestHook = this.injector.hasOnRequestHook();
164
182
  }
165
183
  async listen(port = 3000) {
166
184
  this.registerShutdownHandlers();
@@ -183,48 +201,48 @@ class Carno {
183
201
  }
184
202
  createHttpServer(port) {
185
203
  this.server = Bun.serve({ port, fetch: this.fetch, error: this.catcher });
186
- this.resolveLogger().info(`Server running on port ${port}`);
204
+ if (!this.config.disableStartupLog) {
205
+ this.resolveLogger().info(`Server running on port ${port}`);
206
+ }
187
207
  }
188
- async fetcher(request, server) {
189
- if (this.isCorsEnabled()) {
190
- const origin = request.headers.get("origin");
191
- if (request.method === "OPTIONS" && origin) {
192
- return this.handlePreflightRequest(request);
208
+ async fetcherAsync(request, server, route, compiled, isSimpleRoute, hasQuery, queryIndex, url) {
209
+ try {
210
+ let response;
211
+ const query = hasQuery ? url.slice(queryIndex + 1) : undefined;
212
+ const context = Context_1.Context.createFromRequestSync({ query }, request, server);
213
+ context.param = route.params;
214
+ if (isSimpleRoute) {
215
+ response = compiled.isAsync
216
+ ? await compiled.boundHandler(context)
217
+ : compiled.boundHandler(context);
193
218
  }
194
- }
195
- const urlParsed = parseUrl(request);
196
- const routePath = this.discoverRoutePath(urlParsed);
197
- const route = this.router.find(request.method.toLowerCase(), routePath);
198
- if (!route) {
199
- throw new HttpException_1.HttpException("Method not allowed", 404);
200
- }
201
- const compiled = route.store;
202
- const context = Context_1.Context.createFromRequestSync(urlParsed, request, server);
203
- context.param = route.params;
204
- let response;
205
- const isCompiledRoute = compiled.routeType !== undefined;
206
- if (isCompiledRoute && compiled.routeType === CompiledRoute_1.RouteType.SIMPLE) {
207
- response = compiled.isAsync
208
- ? await compiled.boundHandler(context)
209
- : compiled.boundHandler(context);
210
- }
211
- else {
212
- const needsLocalsContainer = isCompiledRoute
213
- ? compiled.needsLocalsContainer
214
- : true;
215
- const locals = this.resolveLocalsContainer(needsLocalsContainer, context);
216
- if (this.injector.hasOnRequestHook()) {
217
- await this.injector.callHook(on_event_1.EventType.OnRequest, { context });
219
+ else {
220
+ const needsLocalsContainer = compiled.routeType !== undefined
221
+ ? compiled.needsLocalsContainer
222
+ : true;
223
+ const locals = this.resolveLocalsContainer(needsLocalsContainer, context);
224
+ if (this.hasOnRequestHook) {
225
+ await this.injector.callHook(on_event_1.EventType.OnRequest, { context });
226
+ }
227
+ response = await RouteExecutor_1.RouteExecutor.executeRoute(compiled, this.injector, context, locals);
228
+ }
229
+ if (this.corsEnabled) {
230
+ const origin = request.headers.get("origin");
231
+ if (origin && this.corsCache.isOriginAllowed(origin)) {
232
+ if (!(response instanceof Response)) {
233
+ response = RouteExecutor_1.RouteExecutor.mountResponse(response, context);
234
+ }
235
+ return this.applyCorsHeaders(response, origin);
236
+ }
218
237
  }
219
- response = await RouteExecutor_1.RouteExecutor.executeRoute(compiled, this.injector, context, locals);
238
+ return response;
220
239
  }
221
- if (this.isCorsEnabled()) {
222
- const origin = request.headers.get("origin");
223
- if (origin && this.corsCache.isOriginAllowed(origin)) {
224
- response = this.applyCorsHeaders(response, origin);
240
+ catch (error) {
241
+ if (error instanceof HttpException_1.HttpException) {
242
+ return this.errorResponse(request, error.getResponse(), error.getStatus());
225
243
  }
244
+ throw error;
226
245
  }
227
- return response;
228
246
  }
229
247
  async bootstrapApplication() {
230
248
  try {
@@ -266,6 +284,22 @@ class Carno {
266
284
  isCorsEnabled() {
267
285
  return !!this.config.cors;
268
286
  }
287
+ errorResponse(request, message, statusCode) {
288
+ let response = new Response(JSON.stringify({
289
+ message,
290
+ statusCode,
291
+ }), {
292
+ status: statusCode,
293
+ headers: { "Content-Type": "application/json" },
294
+ });
295
+ if (this.corsEnabled) {
296
+ const origin = request.headers.get("origin");
297
+ if (origin && this.corsCache.isOriginAllowed(origin)) {
298
+ response = this.applyCorsHeaders(response, origin);
299
+ }
300
+ }
301
+ return response;
302
+ }
269
303
  handlePreflightRequest(request) {
270
304
  const origin = request.headers.get("origin");
271
305
  if (!origin || !this.corsCache.isOriginAllowed(origin)) {
@@ -295,10 +329,10 @@ class Carno {
295
329
  return locals;
296
330
  }
297
331
  discoverRoutePath(url) {
298
- if (url?.pathname) {
299
- return url.pathname;
332
+ if (typeof url === 'string') {
333
+ return url;
300
334
  }
301
- return url?.path || "/";
335
+ return url?.pathname || url?.path || '/';
302
336
  }
303
337
  }
304
338
  exports.Carno = Carno;
@@ -99,10 +99,12 @@ let InjectorService = InjectorService_1 = class InjectorService {
99
99
  }
100
100
  return this.invoke(token, locals);
101
101
  }
102
- async invokeRoute(route, context, locals, instance) {
103
- await middleware_resolver_1.MiddlewareRes.resolveMiddlewares(route, this, locals);
104
- const result = await this.methodInvoker.invoke(instance, route.methodName, locals, context, (t, l) => this.invoke(t, l));
105
- return result;
102
+ invokeRoute(route, context, locals, instance) {
103
+ const middlewarePromise = middleware_resolver_1.MiddlewareRes.resolveMiddlewares(route, this, locals);
104
+ if (middlewarePromise instanceof Promise) {
105
+ return middlewarePromise.then(() => this.methodInvoker.invoke(instance, route.methodName, locals, context, (t, l) => this.invoke(t, l)));
106
+ }
107
+ return this.methodInvoker.invoke(instance, route.methodName, locals, context, (t, l) => this.invoke(t, l));
106
108
  }
107
109
  scopeOf(provider) {
108
110
  return provider.scope || provider_scope_1.ProviderScope.SINGLETON;
@@ -45,6 +45,7 @@ export declare class Context {
45
45
  static createFromRequestSync(url: {
46
46
  query?: string;
47
47
  }, request: Request, server: Server<any>): Context;
48
+ static createFastContext(request: Request, params: Record<string, any>): Context;
48
49
  static createFromRequest(url: {
49
50
  query?: string;
50
51
  }, request: Request, server: Server<any>): Promise<Context>;
@@ -41,7 +41,6 @@ let Context = Context_1 = class Context {
41
41
  this._rawBody = null;
42
42
  this._bodyParsed = false;
43
43
  this.req = undefined;
44
- this.param = {};
45
44
  this.status = 200;
46
45
  }
47
46
  get headers() {
@@ -113,6 +112,13 @@ let Context = Context_1 = class Context {
113
112
  }
114
113
  return ctx;
115
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;
121
+ }
116
122
  static async createFromRequest(url, request, server) {
117
123
  const ctx = Context_1.createFromRequestSync(url, request, server);
118
124
  if (!ctx._bodyParsed) {
@@ -1,16 +1,11 @@
1
1
  import { TokenRouteWithProvider, TokenRouteWithProviderMap } from "../container";
2
2
  import { Context } from "../domain";
3
+ interface ParsedUrl {
4
+ pathname: string;
5
+ }
3
6
  declare class Matcher {
4
7
  match(request: Request, routes: TokenRouteWithProviderMap, context: Context): TokenRouteWithProvider;
5
- /**
6
- * Identify route by url path.
7
- * The route can have params (:param) and wildcards (*).
8
- *
9
- * @param route
10
- * @param url
11
- * @param context
12
- */
13
- identifyRoute(route: TokenRouteWithProvider, url: URL, context: Context): boolean;
8
+ identifyRoute(route: TokenRouteWithProvider, url: ParsedUrl, context: Context): boolean;
14
9
  }
15
10
  export declare const RouteResolver: Matcher;
16
11
  export {};
@@ -1,7 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.RouteResolver = void 0;
4
- const parseUrl = require('parseurl-fast');
4
+ function parseUrl(request) {
5
+ const url = request.url;
6
+ const startIndex = url.indexOf('/', 12);
7
+ const queryIndex = url.indexOf('?', startIndex);
8
+ if (queryIndex === -1) {
9
+ return { pathname: startIndex === -1 ? '/' : url.slice(startIndex) };
10
+ }
11
+ return { pathname: url.slice(startIndex, queryIndex) };
12
+ }
5
13
  class Matcher {
6
14
  match(request, routes, context) {
7
15
  const method = request.method.toLowerCase();
@@ -10,20 +18,12 @@ class Matcher {
10
18
  }
11
19
  const routeMethod = routes.get(method);
12
20
  const url = parseUrl(request);
13
- const route = routeMethod?.find(route => this.identifyRoute(route, url, context));
21
+ const route = routeMethod?.find((route) => this.identifyRoute(route, url, context));
14
22
  if (!route) {
15
23
  throw new Error('Method not allowed');
16
24
  }
17
25
  return route;
18
26
  }
19
- /**
20
- * Identify route by url path.
21
- * The route can have params (:param) and wildcards (*).
22
- *
23
- * @param route
24
- * @param url
25
- * @param context
26
- */
27
27
  identifyRoute(route, url, context) {
28
28
  const urlPath = url.pathname.split('/');
29
29
  const routePathSegments = route.path.split('/');
@@ -2,13 +2,11 @@ import { InjectorService, TokenRouteWithProvider } from "../container";
2
2
  import { Context, LocalsContainer } from "../domain";
3
3
  import type { CompiledRoute } from "./CompiledRoute";
4
4
  declare class Router {
5
- private readonly jsonHeaders;
6
5
  private readonly textHeaders;
7
6
  executeRoute(routeStore: CompiledRoute | TokenRouteWithProvider, injector: InjectorService, context: Context, locals: LocalsContainer): Promise<Response>;
8
7
  mountResponse(result: unknown, context: Context): Response;
9
8
  private isNativeResponse;
10
9
  private isBodyInit;
11
- private createJsonResponse;
12
10
  }
13
11
  export declare const RouteExecutor: Router;
14
12
  export {};
@@ -4,7 +4,6 @@ exports.RouteExecutor = void 0;
4
4
  const events_1 = require("../events");
5
5
  class Router {
6
6
  constructor() {
7
- this.jsonHeaders = { "Content-Type": "application/json" };
8
7
  this.textHeaders = { "Content-Type": "text/html" };
9
8
  }
10
9
  async executeRoute(routeStore, injector, context, locals) {
@@ -41,7 +40,7 @@ class Router {
41
40
  if (this.isBodyInit(result)) {
42
41
  return new Response(result, { status });
43
42
  }
44
- return this.createJsonResponse(result, status);
43
+ return Response.json(result, { status });
45
44
  }
46
45
  mountResponse(result, context) {
47
46
  const status = context.getResponseStatus() || 200;
@@ -61,7 +60,7 @@ class Router {
61
60
  if (this.isBodyInit(result)) {
62
61
  return new Response(result, { status });
63
62
  }
64
- return this.createJsonResponse(result, status);
63
+ return Response.json(result, { status });
65
64
  }
66
65
  isNativeResponse(result) {
67
66
  return result instanceof Response;
@@ -81,15 +80,5 @@ class Router {
81
80
  }
82
81
  return result instanceof FormData || result instanceof URLSearchParams;
83
82
  }
84
- createJsonResponse(body, status) {
85
- try {
86
- const json = JSON.stringify(body);
87
- return new Response(json, { status, headers: this.jsonHeaders });
88
- }
89
- catch (error) {
90
- const fallback = JSON.stringify({ error: "Serialization failed" });
91
- return new Response(fallback, { status: 500, headers: this.jsonHeaders });
92
- }
93
- }
94
83
  }
95
84
  exports.RouteExecutor = new Router();
@@ -23,7 +23,6 @@ export declare class Memoirist<T> {
23
23
  add(method: string, path: string, store: T): FindResult<T>['store'];
24
24
  find(method: string, url: string): FindResult<T> | null;
25
25
  updateStore(method: string, path: string, oldStore: T, newStore: T): boolean;
26
- private buildCacheKey;
27
26
  private invalidateCache;
28
27
  private updateHistoryStore;
29
28
  private normalizePath;
@@ -170,15 +170,19 @@ class Memoirist {
170
170
  return node.store;
171
171
  }
172
172
  find(method, url) {
173
- const cacheKey = this.buildCacheKey(method, url);
174
- if (this.routeCache.has(cacheKey)) {
175
- return this.routeCache.get(cacheKey);
173
+ let methodCache = this.routeCache.get(url);
174
+ if (methodCache && methodCache[method] !== undefined) {
175
+ return methodCache[method];
176
176
  }
177
177
  const root = this.root[method];
178
178
  if (!root)
179
179
  return null;
180
180
  const result = matchRoute(url, url.length, root, 0);
181
- this.routeCache.set(cacheKey, result);
181
+ if (!methodCache) {
182
+ methodCache = {};
183
+ this.routeCache.set(url, methodCache);
184
+ }
185
+ methodCache[method] = result;
182
186
  return result;
183
187
  }
184
188
  updateStore(method, path, oldStore, newStore) {
@@ -210,10 +214,6 @@ class Memoirist {
210
214
  }
211
215
  return false;
212
216
  }
213
- buildCacheKey(method, url) {
214
- const normalizedMethod = method.toLowerCase();
215
- return `${normalizedMethod}:${url}`;
216
- }
217
217
  invalidateCache() {
218
218
  this.routeCache.clear();
219
219
  }
@@ -9,13 +9,6 @@ class LoggerService {
9
9
  constructor(injector) {
10
10
  this.injector = injector;
11
11
  const pinoConfig = this.injector.applicationConfig.logger || {};
12
- pinoConfig['transport'] = pinoConfig.transport || {
13
- target: 'pino-pretty',
14
- options: {
15
- colorize: true,
16
- ignore: 'pid,hostname',
17
- },
18
- };
19
12
  this.logger = (0, pino_1.default)(pinoConfig);
20
13
  }
21
14
  child(bindings) {
@@ -4,7 +4,11 @@ exports.createCoreTestHarness = createCoreTestHarness;
4
4
  exports.withCoreApplication = withCoreApplication;
5
5
  const Carno_1 = require("../Carno");
6
6
  async function createCoreTestHarness(options = {}) {
7
- const app = new Carno_1.Carno(options.config);
7
+ const config = { ...options.config };
8
+ if (config.disableStartupLog === undefined) {
9
+ config.disableStartupLog = true;
10
+ }
11
+ const app = new Carno_1.Carno(config);
8
12
  applyPlugins(app, options.plugins);
9
13
  const boot = await bootApplication(app, options);
10
14
  const injector = app.getInjector();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@carno.js/core",
3
- "version": "0.2.10",
3
+ "version": "0.2.11",
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",
@@ -62,5 +62,5 @@
62
62
  "publishConfig": {
63
63
  "access": "public"
64
64
  },
65
- "gitHead": "5c765c2c9172bec7f509ca2513f6c10472ec89eb"
65
+ "gitHead": "495ba33561d058107b1bb76d737a0d38a361f5b2"
66
66
  }