@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 +6 -2
- package/dist/Carno.js +38 -39
- package/dist/container/DependencyResolver.d.ts +2 -0
- package/dist/container/DependencyResolver.js +4 -0
- package/dist/container/InjectorService.d.ts +24 -2
- package/dist/container/InjectorService.js +138 -12
- package/dist/domain/Context.d.ts +14 -4
- package/dist/domain/Context.js +87 -33
- package/dist/domain/cors-headers-cache.d.ts +15 -0
- package/dist/domain/cors-headers-cache.js +57 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/route/CompiledRoute.d.ts +23 -0
- package/dist/route/CompiledRoute.js +9 -0
- package/dist/route/FastPathExecutor.d.ts +4 -0
- package/dist/route/FastPathExecutor.js +19 -0
- package/dist/route/JITCompiler.d.ts +4 -0
- package/dist/route/JITCompiler.js +138 -0
- package/dist/route/ParamResolverFactory.d.ts +17 -0
- package/dist/route/ParamResolverFactory.js +71 -0
- package/dist/route/RouteCompiler.d.ts +29 -0
- package/dist/route/RouteCompiler.js +209 -0
- package/dist/route/RouteExecutor.d.ts +3 -2
- package/dist/route/RouteExecutor.js +15 -7
- package/dist/route/memoirist.d.ts +4 -0
- package/dist/route/memoirist.js +91 -2
- package/dist/utils/ValidationCache.d.ts +3 -0
- package/dist/utils/ValidationCache.js +27 -0
- package/package.json +3 -3
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
|
|
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
|
-
|
|
189
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
|
96
|
-
|
|
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);
|
package/dist/domain/Context.d.ts
CHANGED
|
@@ -1,26 +1,36 @@
|
|
|
1
1
|
import { Server } from 'bun';
|
|
2
2
|
export declare class Context {
|
|
3
3
|
query: Record<string, any>;
|
|
4
|
-
|
|
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
|
}
|
package/dist/domain/Context.js
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
119
|
+
this._body = this.parseJsonFromBuffer(this.rawBody);
|
|
94
120
|
return;
|
|
95
121
|
}
|
|
96
|
-
if (contentType.includes('application/x-www-form-urlencoded')
|
|
97
|
-
this.
|
|
122
|
+
if (contentType.includes('application/x-www-form-urlencoded')) {
|
|
123
|
+
this._body = this.parseUrlEncodedFromBuffer(this.rawBody);
|
|
98
124
|
return;
|
|
99
125
|
}
|
|
100
|
-
|
|
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
|
+
}
|