@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 +5 -1
- package/dist/Carno.js +32 -5
- 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 +9 -4
- package/dist/domain/Context.js +45 -31
- 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 +2 -2
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
|
-
|
|
192
|
-
|
|
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
|
-
|
|
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,23 +1,28 @@
|
|
|
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;
|
package/dist/domain/Context.js
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
119
|
+
this._body = this.parseJsonFromBuffer(this.rawBody);
|
|
106
120
|
return;
|
|
107
121
|
}
|
|
108
122
|
if (contentType.includes('application/x-www-form-urlencoded')) {
|
|
109
|
-
this.
|
|
123
|
+
this._body = this.parseUrlEncodedFromBuffer(this.rawBody);
|
|
110
124
|
return;
|
|
111
125
|
}
|
|
112
126
|
// Plain text or unknown content type
|
|
113
|
-
this.
|
|
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(
|
|
7
|
-
|
|
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(
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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(
|
|
18
|
-
|
|
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;
|
package/dist/route/memoirist.js
CHANGED
|
@@ -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:
|
|
262
|
+
static: /:[^/]+/,
|
|
263
|
+
params: /:[^/]+/g
|
|
175
264
|
};
|
|
176
265
|
const matchRoute = (url, urlLength, node, startIndex) => {
|
|
177
266
|
const part = node?.part;
|
|
@@ -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.
|
|
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": "
|
|
54
|
+
"gitHead": "2be3063988696f2cd3eb13363e1f679b9f58c2f1"
|
|
55
55
|
}
|