@kurdel/runtime 0.1.0-beta.1
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/LICENSE +21 -0
- package/README.md +44 -0
- package/lib/app/errors/controller-action-missing-result-error.d.ts +9 -0
- package/lib/app/errors/controller-action-missing-result-error.js +14 -0
- package/lib/app/errors/controller-action-missing-result-error.js.map +1 -0
- package/lib/app/errors/module-validation-error.d.ts +25 -0
- package/lib/app/errors/module-validation-error.js +29 -0
- package/lib/app/errors/module-validation-error.js.map +1 -0
- package/lib/app/errors/provider-configuration-error.d.ts +24 -0
- package/lib/app/errors/provider-configuration-error.js +28 -0
- package/lib/app/errors/provider-configuration-error.js.map +1 -0
- package/lib/app/index.d.ts +1 -0
- package/lib/app/index.js +2 -0
- package/lib/app/index.js.map +1 -0
- package/lib/app/lifecycle-manager.d.ts +26 -0
- package/lib/app/lifecycle-manager.js +40 -0
- package/lib/app/lifecycle-manager.js.map +1 -0
- package/lib/app/module-loader.d.ts +29 -0
- package/lib/app/module-loader.js +61 -0
- package/lib/app/module-loader.js.map +1 -0
- package/lib/app/provider-registrar.d.ts +29 -0
- package/lib/app/provider-registrar.js +67 -0
- package/lib/app/provider-registrar.js.map +1 -0
- package/lib/app/runtime-application.d.ts +75 -0
- package/lib/app/runtime-application.js +97 -0
- package/lib/app/runtime-application.js.map +1 -0
- package/lib/app/runtime-composer.d.ts +25 -0
- package/lib/app/runtime-composer.js +100 -0
- package/lib/app/runtime-composer.js.map +1 -0
- package/lib/app/server-runner.d.ts +36 -0
- package/lib/app/server-runner.js +65 -0
- package/lib/app/server-runner.js.map +1 -0
- package/lib/http/index.d.ts +6 -0
- package/lib/http/index.js +7 -0
- package/lib/http/index.js.map +1 -0
- package/lib/http/is-http-error.d.ts +2 -0
- package/lib/http/is-http-error.js +7 -0
- package/lib/http/is-http-error.js.map +1 -0
- package/lib/http/noop-response-renderer.d.ts +7 -0
- package/lib/http/noop-response-renderer.js +10 -0
- package/lib/http/noop-response-renderer.js.map +1 -0
- package/lib/http/render-action-result.d.ts +4 -0
- package/lib/http/render-action-result.js +24 -0
- package/lib/http/render-action-result.js.map +1 -0
- package/lib/http/runtime-controller-pipe.d.ts +16 -0
- package/lib/http/runtime-controller-pipe.js +26 -0
- package/lib/http/runtime-controller-pipe.js.map +1 -0
- package/lib/http/runtime-controller-resolver.d.ts +12 -0
- package/lib/http/runtime-controller-resolver.js +29 -0
- package/lib/http/runtime-controller-resolver.js.map +1 -0
- package/lib/http/runtime-http-context-factory.d.ts +29 -0
- package/lib/http/runtime-http-context-factory.js +91 -0
- package/lib/http/runtime-http-context-factory.js.map +1 -0
- package/lib/http/runtime-middleware-pipe.d.ts +15 -0
- package/lib/http/runtime-middleware-pipe.js +31 -0
- package/lib/http/runtime-middleware-pipe.js.map +1 -0
- package/lib/http/runtime-middleware-registry.d.ts +18 -0
- package/lib/http/runtime-middleware-registry.js +39 -0
- package/lib/http/runtime-middleware-registry.js.map +1 -0
- package/lib/http/runtime-request-orchestrator.d.ts +21 -0
- package/lib/http/runtime-request-orchestrator.js +114 -0
- package/lib/http/runtime-request-orchestrator.js.map +1 -0
- package/lib/http/runtime-response-renderer.d.ts +12 -0
- package/lib/http/runtime-response-renderer.js +37 -0
- package/lib/http/runtime-response-renderer.js.map +1 -0
- package/lib/http/runtime-router.d.ts +37 -0
- package/lib/http/runtime-router.js +135 -0
- package/lib/http/runtime-router.js.map +1 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.js +4 -0
- package/lib/index.js.map +1 -0
- package/lib/middlewares/create-validator.d.ts +7 -0
- package/lib/middlewares/create-validator.js +21 -0
- package/lib/middlewares/create-validator.js.map +1 -0
- package/lib/middlewares/error-handle.d.ts +2 -0
- package/lib/middlewares/error-handle.js +30 -0
- package/lib/middlewares/error-handle.js.map +1 -0
- package/lib/middlewares/index.d.ts +2 -0
- package/lib/middlewares/index.js +3 -0
- package/lib/middlewares/index.js.map +1 -0
- package/lib/middlewares/json-body-parser.d.ts +14 -0
- package/lib/middlewares/json-body-parser.js +23 -0
- package/lib/middlewares/json-body-parser.js.map +1 -0
- package/lib/middlewares/schema-validator.d.ts +13 -0
- package/lib/middlewares/schema-validator.js +44 -0
- package/lib/middlewares/schema-validator.js.map +1 -0
- package/lib/modules/controller-module.d.ts +23 -0
- package/lib/modules/controller-module.js +76 -0
- package/lib/modules/controller-module.js.map +1 -0
- package/lib/modules/database-module.d.ts +25 -0
- package/lib/modules/database-module.js +40 -0
- package/lib/modules/database-module.js.map +1 -0
- package/lib/modules/index.d.ts +1 -0
- package/lib/modules/index.js +2 -0
- package/lib/modules/index.js.map +1 -0
- package/lib/modules/lifecycle-module.d.ts +9 -0
- package/lib/modules/lifecycle-module.js +17 -0
- package/lib/modules/lifecycle-module.js.map +1 -0
- package/lib/modules/middleware-module.d.ts +18 -0
- package/lib/modules/middleware-module.js +45 -0
- package/lib/modules/middleware-module.js.map +1 -0
- package/lib/modules/model-module.d.ts +17 -0
- package/lib/modules/model-module.js +24 -0
- package/lib/modules/model-module.js.map +1 -0
- package/lib/modules/server-module.d.ts +30 -0
- package/lib/modules/server-module.js +65 -0
- package/lib/modules/server-module.js.map +1 -0
- package/lib/template/ensure-template-engine-binding.d.ts +2 -0
- package/lib/template/ensure-template-engine-binding.js +8 -0
- package/lib/template/ensure-template-engine-binding.js.map +1 -0
- package/lib/template/index.d.ts +1 -0
- package/lib/template/index.js +2 -0
- package/lib/template/index.js.map +1 -0
- package/lib/template/noop-template-engine.d.ts +4 -0
- package/lib/template/noop-template-engine.js +6 -0
- package/lib/template/noop-template-engine.js.map +1 -0
- package/package.json +64 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ## RuntimeHttpContextFactory
|
|
3
|
+
*
|
|
4
|
+
* Responsible for constructing per-request {@link HttpContext} objects.
|
|
5
|
+
*
|
|
6
|
+
* Each incoming request receives a fresh, isolated context encapsulating:
|
|
7
|
+
* - HTTP request/response wrappers provided by the adapter
|
|
8
|
+
* - parsed URL, query, params, body
|
|
9
|
+
* - matched route metadata (controller/action/schema/auth)
|
|
10
|
+
* - a request-scoped IoC container
|
|
11
|
+
* - typed constructors for {@link ActionResult}
|
|
12
|
+
*
|
|
13
|
+
* The context object is immutable except for `result`,
|
|
14
|
+
* which is later populated by the orchestrator and renderer.
|
|
15
|
+
*/
|
|
16
|
+
export class RuntimeHttpContextFactory {
|
|
17
|
+
/**
|
|
18
|
+
* Constructs a fully initialized {@link HttpContext} instance.
|
|
19
|
+
*
|
|
20
|
+
* @param req - incoming HTTP request
|
|
21
|
+
* @param res - outgoing HTTP response
|
|
22
|
+
* @param match - router resolution result
|
|
23
|
+
* @param scope - request-scoped IoC container
|
|
24
|
+
*/
|
|
25
|
+
create(req, res, match, scope) {
|
|
26
|
+
const url = new URL(req.url ?? '/', 'http://internal');
|
|
27
|
+
const query = req.query ??
|
|
28
|
+
Object.fromEntries(url.searchParams.entries());
|
|
29
|
+
return {
|
|
30
|
+
/**
|
|
31
|
+
* Request-scoped IoC container.
|
|
32
|
+
*
|
|
33
|
+
* Used by:
|
|
34
|
+
* - authentication strategies
|
|
35
|
+
* - per-request services (logger, db, tracing)
|
|
36
|
+
* - custom middlewares
|
|
37
|
+
* - controllers needing scoped dependencies
|
|
38
|
+
*/
|
|
39
|
+
scope,
|
|
40
|
+
/** Underlying request object */
|
|
41
|
+
req,
|
|
42
|
+
/** Underlying response object */
|
|
43
|
+
res,
|
|
44
|
+
/** Fully parsed URL of the current request */
|
|
45
|
+
url,
|
|
46
|
+
/** Parsed query parameters */
|
|
47
|
+
query,
|
|
48
|
+
/** Path parameters extracted from the matched route */
|
|
49
|
+
params: match.params,
|
|
50
|
+
/** Parsed request body (if available) */
|
|
51
|
+
body: req.body,
|
|
52
|
+
/** Matched route metadata (including schema and controller) */
|
|
53
|
+
route: match,
|
|
54
|
+
/**
|
|
55
|
+
* Authenticated user (if authentication middleware succeeded).
|
|
56
|
+
* Populated later in the pipeline.
|
|
57
|
+
*/
|
|
58
|
+
user: undefined,
|
|
59
|
+
/** The latest computed ActionResult (populated later at runtime) */
|
|
60
|
+
result: undefined,
|
|
61
|
+
// ────────────────────────────────
|
|
62
|
+
// Response helper constructors
|
|
63
|
+
// ────────────────────────────────
|
|
64
|
+
/**
|
|
65
|
+
* Creates a JSON ActionResult with a given status and body.
|
|
66
|
+
*/
|
|
67
|
+
json(status, body) {
|
|
68
|
+
return { kind: 'json', status, body };
|
|
69
|
+
},
|
|
70
|
+
/**
|
|
71
|
+
* Creates a plain text ActionResult.
|
|
72
|
+
*/
|
|
73
|
+
text(status, body) {
|
|
74
|
+
return { kind: 'text', status, body };
|
|
75
|
+
},
|
|
76
|
+
/**
|
|
77
|
+
* Creates a redirect ActionResult to the given location.
|
|
78
|
+
*/
|
|
79
|
+
redirect(status, location) {
|
|
80
|
+
return { kind: 'redirect', status, location };
|
|
81
|
+
},
|
|
82
|
+
/**
|
|
83
|
+
* Creates a standard HTTP 204 “No Content” ActionResult.
|
|
84
|
+
*/
|
|
85
|
+
noContent() {
|
|
86
|
+
return { kind: 'empty', status: 204 };
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=runtime-http-context-factory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-http-context-factory.js","sourceRoot":"","sources":["../../src/http/runtime-http-context-factory.ts"],"names":[],"mappings":"AASA;;;;;;;;;;;;;;GAcG;AACH,MAAM,OAAO,yBAAyB;IACpC;;;;;;;OAOG;IACH,MAAM,CACJ,GAAgB,EAChB,GAAiB,EACjB,KAAiB,EACjB,KAAgB;QAEhB,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,EAAE,iBAAiB,CAAC,CAAC;QAEvD,MAAM,KAAK,GACT,GAAG,CAAC,KAAK;YACT,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,YAAY,CAAC,OAAO,EAAE,CAAC,CAAC;QAEjD,OAAO;YACL;;;;;;;;eAQG;YACH,KAAK;YAEL,gCAAgC;YAChC,GAAG;YAEH,iCAAiC;YACjC,GAAG;YAEH,8CAA8C;YAC9C,GAAG;YAEH,8BAA8B;YAC9B,KAAK;YAEL,uDAAuD;YACvD,MAAM,EAAE,KAAK,CAAC,MAAM;YAEpB,yCAAyC;YACzC,IAAI,EAAE,GAAG,CAAC,IAAI;YAEd,+DAA+D;YAC/D,KAAK,EAAE,KAAK;YAEZ;;;eAGG;YACH,IAAI,EAAE,SAAS;YAEf,oEAAoE;YACpE,MAAM,EAAE,SAAS;YAEjB,mCAAmC;YACnC,+BAA+B;YAC/B,mCAAmC;YAEnC;;eAEG;YACH,IAAI,CAAC,MAAc,EAAE,IAAe;gBAClC,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;YACxC,CAAC;YAED;;eAEG;YACH,IAAI,CAAC,MAAc,EAAE,IAAY;gBAC/B,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;YACxC,CAAC;YAED;;eAEG;YACH,QAAQ,CAAC,MAAc,EAAE,QAAgB;gBACvC,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;YAChD,CAAC;YAED;;eAEG;YACH,SAAS;gBACP,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;YACxC,CAAC;SACF,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { HttpContext, Middleware, ActionResult } from '@kurdel/core/http';
|
|
2
|
+
/**
|
|
3
|
+
* ## RuntimeMiddlewarePipe
|
|
4
|
+
*
|
|
5
|
+
* Executes middleware chain in declared order.
|
|
6
|
+
*
|
|
7
|
+
* - Stops on returned ActionResult
|
|
8
|
+
* - Stops on thrown error
|
|
9
|
+
* - Calls `next()` only when middleware yields `undefined`
|
|
10
|
+
*/
|
|
11
|
+
export declare class RuntimeMiddlewarePipe {
|
|
12
|
+
private readonly middlewares;
|
|
13
|
+
constructor(middlewares?: Middleware[]);
|
|
14
|
+
run(ctx: HttpContext, terminal?: () => Promise<ActionResult | void>): Promise<ActionResult | void>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ## RuntimeMiddlewarePipe
|
|
3
|
+
*
|
|
4
|
+
* Executes middleware chain in declared order.
|
|
5
|
+
*
|
|
6
|
+
* - Stops on returned ActionResult
|
|
7
|
+
* - Stops on thrown error
|
|
8
|
+
* - Calls `next()` only when middleware yields `undefined`
|
|
9
|
+
*/
|
|
10
|
+
export class RuntimeMiddlewarePipe {
|
|
11
|
+
constructor(middlewares = []) {
|
|
12
|
+
this.middlewares = middlewares;
|
|
13
|
+
}
|
|
14
|
+
async run(ctx, terminal) {
|
|
15
|
+
const dispatch = async (index) => {
|
|
16
|
+
if (index >= this.middlewares.length) {
|
|
17
|
+
return terminal ? await terminal() : undefined;
|
|
18
|
+
}
|
|
19
|
+
const mw = this.middlewares[index];
|
|
20
|
+
const result = await mw(ctx, () => dispatch(index + 1));
|
|
21
|
+
// 🚫 STOP: middleware returned an ActionResult
|
|
22
|
+
if (result !== undefined) {
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
// otherwise continue automatically (middleware already called next)
|
|
26
|
+
return undefined;
|
|
27
|
+
};
|
|
28
|
+
return dispatch(0);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=runtime-middleware-pipe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-middleware-pipe.js","sourceRoot":"","sources":["../../src/http/runtime-middleware-pipe.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,MAAM,OAAO,qBAAqB;IAChC,YAA6B,cAA4B,EAAE;QAA9B,gBAAW,GAAX,WAAW,CAAmB;IAAG,CAAC;IAE/D,KAAK,CAAC,GAAG,CACP,GAAgB,EAChB,QAA6C;QAE7C,MAAM,QAAQ,GAAG,KAAK,EAAE,KAAa,EAAgC,EAAE;YACrE,IAAI,KAAK,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC;gBACrC,OAAO,QAAQ,CAAC,CAAC,CAAC,MAAM,QAAQ,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;YACjD,CAAC;YAED,MAAM,EAAE,GAAG,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;YACnC,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC;YAExD,+CAA+C;YAC/C,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBACzB,OAAO,MAAM,CAAC;YAChB,CAAC;YAED,oEAAoE;YACpE,OAAO,SAAS,CAAC;QACnB,CAAC,CAAC;QAEF,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC;IACrB,CAAC;CACF"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Newable } from '@kurdel/common';
|
|
2
|
+
import type { Controller, Middleware, MiddlewareRegistry } from '@kurdel/core/http';
|
|
3
|
+
import type { MiddlewareZone, MiddlewareRegistration } from '@kurdel/core/http';
|
|
4
|
+
export declare class RuntimeMiddlewareRegistry implements MiddlewareRegistry {
|
|
5
|
+
private readonly global;
|
|
6
|
+
private readonly perController;
|
|
7
|
+
use(mw: Middleware, opts?: {
|
|
8
|
+
zone?: MiddlewareZone;
|
|
9
|
+
priority?: number;
|
|
10
|
+
}): void;
|
|
11
|
+
useFor(controller: Newable<Controller>, mw: Middleware, opts?: {
|
|
12
|
+
zone?: MiddlewareZone;
|
|
13
|
+
priority?: number;
|
|
14
|
+
action?: string;
|
|
15
|
+
}): void;
|
|
16
|
+
for(controller: Newable<Controller>, zone?: MiddlewareZone, action?: string): MiddlewareRegistration[];
|
|
17
|
+
all(zone?: MiddlewareZone): MiddlewareRegistration[];
|
|
18
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export class RuntimeMiddlewareRegistry {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.global = new Map();
|
|
4
|
+
this.perController = new Map();
|
|
5
|
+
}
|
|
6
|
+
use(mw, opts = {}) {
|
|
7
|
+
const zone = opts.zone ?? 'pre';
|
|
8
|
+
const priority = opts.priority ?? 0;
|
|
9
|
+
const entry = { use: mw, zone, priority };
|
|
10
|
+
if (!this.global.has(zone))
|
|
11
|
+
this.global.set(zone, []);
|
|
12
|
+
this.global.get(zone).push(entry);
|
|
13
|
+
}
|
|
14
|
+
useFor(controller, mw, opts = {}) {
|
|
15
|
+
const zone = opts.zone ?? 'pre';
|
|
16
|
+
const priority = opts.priority ?? 0;
|
|
17
|
+
const entry = { use: mw, zone, priority, action: opts.action };
|
|
18
|
+
if (!this.perController.has(controller)) {
|
|
19
|
+
this.perController.set(controller, new Map());
|
|
20
|
+
}
|
|
21
|
+
const zones = this.perController.get(controller);
|
|
22
|
+
if (!zones.has(zone))
|
|
23
|
+
zones.set(zone, []);
|
|
24
|
+
zones.get(zone).push(entry);
|
|
25
|
+
}
|
|
26
|
+
for(controller, zone = 'pre', action) {
|
|
27
|
+
const map = this.perController.get(controller);
|
|
28
|
+
if (!map)
|
|
29
|
+
return [];
|
|
30
|
+
const entries = map.get(zone) ?? [];
|
|
31
|
+
return entries
|
|
32
|
+
.filter(e => !e.action || e.action === action)
|
|
33
|
+
.sort((a, b) => b.priority - a.priority);
|
|
34
|
+
}
|
|
35
|
+
all(zone = 'pre') {
|
|
36
|
+
return (this.global.get(zone) ?? []).sort((a, b) => b.priority - a.priority);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=runtime-middleware-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-middleware-registry.js","sourceRoot":"","sources":["../../src/http/runtime-middleware-registry.ts"],"names":[],"mappings":"AAIA,MAAM,OAAO,yBAAyB;IAAtC;QACmB,WAAM,GAAG,IAAI,GAAG,EAA4C,CAAC;QAC7D,kBAAa,GAAG,IAAI,GAAG,EAGrC,CAAC;IA4CN,CAAC;IA1CC,GAAG,CAAC,EAAc,EAAE,OAAqD,EAAE;QACzE,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,KAAK,CAAC;QAChC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC;QACpC,MAAM,KAAK,GAA2B,EAAE,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;QAClE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACtD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACrC,CAAC;IAED,MAAM,CACJ,UAA+B,EAC/B,EAAc,EACd,OAAsE,EAAE;QAExE,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,KAAK,CAAC;QAChC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC;QACpC,MAAM,KAAK,GAA2B,EAAE,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC;QAEvF,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;YACxC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,GAAG,EAAE,CAAC,CAAC;QAChD,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,UAAU,CAAE,CAAC;QAClD,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAC1C,KAAK,CAAC,GAAG,CAAC,IAAI,CAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;IAED,GAAG,CACD,UAA+B,EAC/B,OAAuB,KAAK,EAC5B,MAAe;QAEf,MAAM,GAAG,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAC/C,IAAI,CAAC,GAAG;YAAE,OAAO,EAAE,CAAC;QAEpB,MAAM,OAAO,GAAG,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QACpC,OAAO,OAAO;aACX,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC;aAC7C,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC;IAC7C,CAAC;IAED,GAAG,CAAC,OAAuB,KAAK;QAC9B,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC;IAC/E,CAAC;CACF"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { HttpRequest, HttpResponse } from '@kurdel/common';
|
|
2
|
+
import type { Container } from '@kurdel/ioc';
|
|
3
|
+
import type { ResponseRenderer, Router, MiddlewareRegistry } from '@kurdel/core/http';
|
|
4
|
+
/**
|
|
5
|
+
* ## RuntimeRequestOrchestrator
|
|
6
|
+
*
|
|
7
|
+
* Deterministic pipeline:
|
|
8
|
+
* resolve → validate → auth → pre → controller → render → post → error → final
|
|
9
|
+
*/
|
|
10
|
+
export declare class RuntimeRequestOrchestrator {
|
|
11
|
+
private readonly router;
|
|
12
|
+
private readonly renderer;
|
|
13
|
+
private readonly registry;
|
|
14
|
+
private readonly contextFactory;
|
|
15
|
+
private readonly controllerPipe;
|
|
16
|
+
constructor(router: Router, renderer: ResponseRenderer, registry: MiddlewareRegistry);
|
|
17
|
+
execute(req: HttpRequest, res: HttpResponse, scope: Container): Promise<void>;
|
|
18
|
+
private collect;
|
|
19
|
+
private runZone;
|
|
20
|
+
private runFinal;
|
|
21
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { HttpError } from '@kurdel/core/http';
|
|
2
|
+
import { ControllerActionMissingResultError } from '../app/errors/controller-action-missing-result-error.js';
|
|
3
|
+
import { RuntimeHttpContextFactory } from '../http/runtime-http-context-factory.js';
|
|
4
|
+
import { RuntimeControllerPipe } from '../http/runtime-controller-pipe.js';
|
|
5
|
+
import { RuntimeMiddlewarePipe } from '../http/runtime-middleware-pipe.js';
|
|
6
|
+
/**
|
|
7
|
+
* ## RuntimeRequestOrchestrator
|
|
8
|
+
*
|
|
9
|
+
* Deterministic pipeline:
|
|
10
|
+
* resolve → validate → auth → pre → controller → render → post → error → final
|
|
11
|
+
*/
|
|
12
|
+
export class RuntimeRequestOrchestrator {
|
|
13
|
+
constructor(router, renderer, registry) {
|
|
14
|
+
this.router = router;
|
|
15
|
+
this.renderer = renderer;
|
|
16
|
+
this.registry = registry;
|
|
17
|
+
this.contextFactory = new RuntimeHttpContextFactory();
|
|
18
|
+
this.controllerPipe = new RuntimeControllerPipe();
|
|
19
|
+
}
|
|
20
|
+
async execute(req, res, scope) {
|
|
21
|
+
const method = req.method ?? 'GET';
|
|
22
|
+
const url = req.url ?? '/';
|
|
23
|
+
// --- 1️⃣ Resolve route ---
|
|
24
|
+
const routeMatch = this.router.resolve(method, url, scope);
|
|
25
|
+
if (!routeMatch) {
|
|
26
|
+
this.renderer.handleError(res, new HttpError(404, 'Not Found'));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
// --- 2️⃣ Build HttpContext ---
|
|
30
|
+
const ctx = this.contextFactory.create(req, res, routeMatch, scope);
|
|
31
|
+
// --- 3️⃣ Validation ---
|
|
32
|
+
try {
|
|
33
|
+
const schema = routeMatch.schema;
|
|
34
|
+
if (schema?.body)
|
|
35
|
+
ctx.body = await schema.body.validate(ctx.body);
|
|
36
|
+
if (schema?.params)
|
|
37
|
+
ctx.params = await schema.params.validate(ctx.params);
|
|
38
|
+
if (schema?.query)
|
|
39
|
+
ctx.query = await schema.query.validate(ctx.query);
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
this.renderer.handleError(res, err);
|
|
43
|
+
await this.runFinal(ctx, routeMatch);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
// --- 4️⃣ AUTH zone ---
|
|
48
|
+
const authResult = await this.runZone('auth', ctx, routeMatch);
|
|
49
|
+
if (authResult) {
|
|
50
|
+
ctx.result = authResult;
|
|
51
|
+
this.renderer.render(res, authResult);
|
|
52
|
+
await this.runFinal(ctx, routeMatch);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
// --- 5️⃣ PRE middleware ---
|
|
56
|
+
const preResult = await this.runZone('pre', ctx, routeMatch);
|
|
57
|
+
if (preResult) {
|
|
58
|
+
ctx.result = preResult;
|
|
59
|
+
this.renderer.render(res, preResult);
|
|
60
|
+
await this.runFinal(ctx, routeMatch);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
// --- 6️⃣ Controller execution ---
|
|
64
|
+
const result = await this.controllerPipe.run(routeMatch.controller, ctx, routeMatch.action);
|
|
65
|
+
if (typeof result === 'undefined') {
|
|
66
|
+
throw new ControllerActionMissingResultError(routeMatch.controller, routeMatch.action);
|
|
67
|
+
}
|
|
68
|
+
ctx.result = result;
|
|
69
|
+
this.renderer.render(res, result);
|
|
70
|
+
// --- 7️⃣ POST middleware (only if response was rendered) ---
|
|
71
|
+
if (res.sent) {
|
|
72
|
+
await this.runZone('post', ctx, routeMatch);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
// --- 8️⃣ ERROR middleware zone ---
|
|
77
|
+
const errorResult = await this.runZone('error', ctx, routeMatch, err);
|
|
78
|
+
if (errorResult && !res.sent) {
|
|
79
|
+
this.renderer.render(res, errorResult);
|
|
80
|
+
}
|
|
81
|
+
else if (!res.sent) {
|
|
82
|
+
this.renderer.handleError(res, err);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
// --- 9️⃣ FINAL always executed ---
|
|
87
|
+
await this.runFinal(ctx, routeMatch);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// ---------------------------------------------------------------------
|
|
91
|
+
// Middleware helpers
|
|
92
|
+
// ---------------------------------------------------------------------
|
|
93
|
+
collect(zone, match) {
|
|
94
|
+
const ctor = match.controller?.constructor;
|
|
95
|
+
return [
|
|
96
|
+
...this.registry.all(zone),
|
|
97
|
+
...(ctor ? this.registry.for(ctor, zone, match.action) : []),
|
|
98
|
+
].map(m => m.use);
|
|
99
|
+
}
|
|
100
|
+
async runZone(zone, ctx, match, error) {
|
|
101
|
+
const fns = this.collect(zone, match);
|
|
102
|
+
if (!fns.length)
|
|
103
|
+
return undefined;
|
|
104
|
+
const pipe = new RuntimeMiddlewarePipe(fns);
|
|
105
|
+
if (zone === 'error' && error !== undefined) {
|
|
106
|
+
ctx.error = error;
|
|
107
|
+
}
|
|
108
|
+
return pipe.run(ctx);
|
|
109
|
+
}
|
|
110
|
+
async runFinal(ctx, match) {
|
|
111
|
+
return this.runZone('final', ctx, match);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=runtime-request-orchestrator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-request-orchestrator.js","sourceRoot":"","sources":["../../src/http/runtime-request-orchestrator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ActionResult, ResponseRenderer } from '@kurdel/core/http';
|
|
2
|
+
import type { HttpResponse } from '@kurdel/common';
|
|
3
|
+
/**
|
|
4
|
+
* Handles rendering of ActionResult into HttpResponse
|
|
5
|
+
* and unified error translation.
|
|
6
|
+
*/
|
|
7
|
+
export declare class RuntimeResponseRenderer implements ResponseRenderer {
|
|
8
|
+
private readonly platformRender;
|
|
9
|
+
constructor(platformRender: (res: HttpResponse, result: ActionResult) => void);
|
|
10
|
+
render(res: HttpResponse, result: ActionResult | void): void;
|
|
11
|
+
handleError(res: HttpResponse, err: unknown): void;
|
|
12
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { HttpError } from '@kurdel/core/http';
|
|
2
|
+
/**
|
|
3
|
+
* Handles rendering of ActionResult into HttpResponse
|
|
4
|
+
* and unified error translation.
|
|
5
|
+
*/
|
|
6
|
+
export class RuntimeResponseRenderer {
|
|
7
|
+
constructor(platformRender) {
|
|
8
|
+
this.platformRender = platformRender;
|
|
9
|
+
}
|
|
10
|
+
render(res, result) {
|
|
11
|
+
if (!res.sent && result) {
|
|
12
|
+
this.platformRender(res, result);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
handleError(res, err) {
|
|
16
|
+
if (res.sent)
|
|
17
|
+
return;
|
|
18
|
+
if (err instanceof HttpError) {
|
|
19
|
+
this.platformRender(res, {
|
|
20
|
+
kind: 'json',
|
|
21
|
+
status: err.status,
|
|
22
|
+
body: {
|
|
23
|
+
error: err.message,
|
|
24
|
+
details: err.details,
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
this.platformRender(res, {
|
|
30
|
+
kind: 'json',
|
|
31
|
+
status: 500,
|
|
32
|
+
body: { error: 'Internal Server Error' },
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=runtime-response-renderer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-response-renderer.js","sourceRoot":"","sources":["../../src/http/runtime-response-renderer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAG9C;;;GAGG;AACH,MAAM,OAAO,uBAAuB;IAClC,YAA6B,cAAiE;QAAjE,mBAAc,GAAd,cAAc,CAAmD;IAAG,CAAC;IAElG,MAAM,CAAC,GAAiB,EAAE,MAA2B;QACnD,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,MAAM,EAAE,CAAC;YACxB,IAAI,CAAC,cAAc,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QACnC,CAAC;IACH,CAAC;IAED,WAAW,CAAC,GAAiB,EAAE,GAAY;QACzC,IAAI,GAAG,CAAC,IAAI;YAAE,OAAO;QAErB,IAAI,GAAG,YAAY,SAAS,EAAE,CAAC;YAC7B,IAAI,CAAC,cAAc,CAAC,GAAG,EAAE;gBACvB,IAAI,EAAE,MAAM;gBACZ,MAAM,EAAE,GAAG,CAAC,MAAM;gBAClB,IAAI,EAAE;oBACJ,KAAK,EAAE,GAAG,CAAC,OAAO;oBAClB,OAAO,EAAE,GAAG,CAAC,OAAoB;iBAClC;aACF,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,cAAc,CAAC,GAAG,EAAE;gBACvB,IAAI,EAAE,MAAM;gBACZ,MAAM,EAAE,GAAG;gBACX,IAAI,EAAE,EAAE,KAAK,EAAE,uBAAuB,EAAE;aACzC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Container } from '@kurdel/ioc';
|
|
2
|
+
import type { Method, ControllerConfig, ControllerResolver, Router, RouteMatch, RouteAuth } from '@kurdel/core/http';
|
|
3
|
+
export declare function mergeAuth(base?: RouteAuth, override?: RouteAuth): RouteAuth | undefined;
|
|
4
|
+
/**
|
|
5
|
+
* ## RuntimeRouter
|
|
6
|
+
*
|
|
7
|
+
* Responsible for:
|
|
8
|
+
* - extracting metadata from controller `routes`
|
|
9
|
+
* - compiling patterns into a fast lookup table
|
|
10
|
+
* - performing deterministic route matching at runtime
|
|
11
|
+
*
|
|
12
|
+
* Initialization stages:
|
|
13
|
+
* 1️⃣ iterate controller configs
|
|
14
|
+
* 2️⃣ instantiate controller *only to read routes*
|
|
15
|
+
* 3️⃣ extract `ROUTE_META` from handlers
|
|
16
|
+
* 4️⃣ normalize `prefix + path`
|
|
17
|
+
* 5️⃣ compile regex + param keys
|
|
18
|
+
* 6️⃣ store immutable route entries
|
|
19
|
+
*/
|
|
20
|
+
export declare class RuntimeRouter implements Router {
|
|
21
|
+
/** Immutable table of compiled routes. */
|
|
22
|
+
private readonly entries;
|
|
23
|
+
/** Controller instance resolver (DI-based). */
|
|
24
|
+
private resolver;
|
|
25
|
+
/**
|
|
26
|
+
* Builds the route table by reading controller metadata.
|
|
27
|
+
*/
|
|
28
|
+
init(resolver: ControllerResolver, controllerConfigs: ControllerConfig[]): void;
|
|
29
|
+
/**
|
|
30
|
+
* Matches an incoming HTTP request to a controller/action.
|
|
31
|
+
*/
|
|
32
|
+
resolve(method: Method, url: string, scope: Container): RouteMatch | null;
|
|
33
|
+
/**
|
|
34
|
+
* Adds a compiled route entry into the lookup table.
|
|
35
|
+
*/
|
|
36
|
+
private addEntry;
|
|
37
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { ROUTE_META } from '@kurdel/core/http';
|
|
2
|
+
/**
|
|
3
|
+
* Joins two path segments safely, preventing `//` occurrences.
|
|
4
|
+
*/
|
|
5
|
+
function joinPaths(a, b) {
|
|
6
|
+
return `${a.replace(/\/+$/, '')}/${b.replace(/^\/+/, '')}`;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Extracts path keys and compiles a regex matcher.
|
|
10
|
+
*/
|
|
11
|
+
function compilePath(path) {
|
|
12
|
+
const keys = [];
|
|
13
|
+
const pattern = path
|
|
14
|
+
.split('/')
|
|
15
|
+
.filter(Boolean)
|
|
16
|
+
.map(segment => {
|
|
17
|
+
if (segment.startsWith(':')) {
|
|
18
|
+
keys.push(segment.slice(1));
|
|
19
|
+
return '([^/]+)';
|
|
20
|
+
}
|
|
21
|
+
return segment;
|
|
22
|
+
})
|
|
23
|
+
.join('/');
|
|
24
|
+
const regex = pattern ? new RegExp(`^/${pattern}/?$`) : /^\/$/;
|
|
25
|
+
return { regex, keys };
|
|
26
|
+
}
|
|
27
|
+
export function mergeAuth(base, override) {
|
|
28
|
+
if (!base && !override)
|
|
29
|
+
return undefined;
|
|
30
|
+
return {
|
|
31
|
+
public: override?.public ?? base?.public,
|
|
32
|
+
strategy: override?.strategy ?? base?.strategy,
|
|
33
|
+
roles: override?.roles ?? base?.roles,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* ## RuntimeRouter
|
|
38
|
+
*
|
|
39
|
+
* Responsible for:
|
|
40
|
+
* - extracting metadata from controller `routes`
|
|
41
|
+
* - compiling patterns into a fast lookup table
|
|
42
|
+
* - performing deterministic route matching at runtime
|
|
43
|
+
*
|
|
44
|
+
* Initialization stages:
|
|
45
|
+
* 1️⃣ iterate controller configs
|
|
46
|
+
* 2️⃣ instantiate controller *only to read routes*
|
|
47
|
+
* 3️⃣ extract `ROUTE_META` from handlers
|
|
48
|
+
* 4️⃣ normalize `prefix + path`
|
|
49
|
+
* 5️⃣ compile regex + param keys
|
|
50
|
+
* 6️⃣ store immutable route entries
|
|
51
|
+
*/
|
|
52
|
+
export class RuntimeRouter {
|
|
53
|
+
constructor() {
|
|
54
|
+
/** Immutable table of compiled routes. */
|
|
55
|
+
this.entries = [];
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Builds the route table by reading controller metadata.
|
|
59
|
+
*/
|
|
60
|
+
init(resolver, controllerConfigs) {
|
|
61
|
+
this.resolver = resolver;
|
|
62
|
+
for (const { use: ControllerClass, prefix = '', auth: ctrlAuth = {} } of controllerConfigs) {
|
|
63
|
+
/**
|
|
64
|
+
* We instantiate the controller *only* to read its static `routes` definition.
|
|
65
|
+
*
|
|
66
|
+
* This instance:
|
|
67
|
+
* - is not created by DI
|
|
68
|
+
* - receives no real dependencies (`{}` is enough)
|
|
69
|
+
* - is never used to execute actions
|
|
70
|
+
*
|
|
71
|
+
* During request handling, controllers are instantiated per-scope
|
|
72
|
+
* via ControllerResolver — this initialization step only extracts metadata.
|
|
73
|
+
*/
|
|
74
|
+
const { routes } = new ControllerClass({});
|
|
75
|
+
for (const [action, handler] of Object.entries(routes)) {
|
|
76
|
+
const meta = handler[ROUTE_META];
|
|
77
|
+
if (!meta)
|
|
78
|
+
continue;
|
|
79
|
+
const fullPath = joinPaths(prefix, meta.path);
|
|
80
|
+
const { method, schema } = meta;
|
|
81
|
+
const auth = mergeAuth(ctrlAuth, meta.auth);
|
|
82
|
+
this.addEntry(method, fullPath, ControllerClass, action, auth, schema);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Optional debug output
|
|
86
|
+
if (process.env.DEBUG_ROUTES) {
|
|
87
|
+
console.log('[router] Registered:', this.entries.map(e => `${e.method} ${e.path}`));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Matches an incoming HTTP request to a controller/action.
|
|
92
|
+
*/
|
|
93
|
+
resolve(method, url, scope) {
|
|
94
|
+
const pathname = (url ?? '/').split('?')[0].replace(/\\/g, '/');
|
|
95
|
+
for (const entry of this.entries) {
|
|
96
|
+
if (entry.method !== method)
|
|
97
|
+
continue;
|
|
98
|
+
const match = entry.regex.exec(pathname);
|
|
99
|
+
if (!match)
|
|
100
|
+
continue;
|
|
101
|
+
const params = {};
|
|
102
|
+
entry.keys.forEach((key, i) => {
|
|
103
|
+
params[key] = match[i + 1];
|
|
104
|
+
});
|
|
105
|
+
const controller = this.resolver.resolve(entry.token, scope);
|
|
106
|
+
return {
|
|
107
|
+
controller,
|
|
108
|
+
method,
|
|
109
|
+
action: entry.action,
|
|
110
|
+
path: entry.path,
|
|
111
|
+
params,
|
|
112
|
+
auth: entry.auth,
|
|
113
|
+
schema: entry.schema,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Adds a compiled route entry into the lookup table.
|
|
120
|
+
*/
|
|
121
|
+
addEntry(method, path, token, action, auth, schema) {
|
|
122
|
+
const { regex, keys } = compilePath(path);
|
|
123
|
+
this.entries.push({
|
|
124
|
+
method,
|
|
125
|
+
path,
|
|
126
|
+
regex,
|
|
127
|
+
keys,
|
|
128
|
+
token,
|
|
129
|
+
action,
|
|
130
|
+
auth,
|
|
131
|
+
schema,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=runtime-router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-router.js","sourceRoot":"","sources":["../../src/http/runtime-router.ts"],"names":[],"mappings":"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"}
|
package/lib/index.d.ts
ADDED
package/lib/index.js
ADDED
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,gBAAgB,CAAC;AACtC,OAAO,KAAK,IAAI,MAAM,iBAAiB,CAAC;AACxC,OAAO,KAAK,WAAW,MAAM,wBAAwB,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ValidationError, SchemaValidator } from '@kurdel/core/http';
|
|
2
|
+
/**
|
|
3
|
+
* Generic adapter factory for schema validators (Zod, Joi, Yup, etc.)
|
|
4
|
+
*
|
|
5
|
+
* Wraps any validation library into a unified `SchemaValidator` interface.
|
|
6
|
+
*/
|
|
7
|
+
export declare function createValidator<T>(parse: (data: unknown) => T | Promise<T>, mapError: (err: unknown) => ValidationError | null): SchemaValidator<T>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic adapter factory for schema validators (Zod, Joi, Yup, etc.)
|
|
3
|
+
*
|
|
4
|
+
* Wraps any validation library into a unified `SchemaValidator` interface.
|
|
5
|
+
*/
|
|
6
|
+
export function createValidator(parse, mapError) {
|
|
7
|
+
return {
|
|
8
|
+
async validate(data) {
|
|
9
|
+
try {
|
|
10
|
+
return await parse(data);
|
|
11
|
+
}
|
|
12
|
+
catch (err) {
|
|
13
|
+
const mapped = mapError(err);
|
|
14
|
+
if (mapped)
|
|
15
|
+
throw mapped;
|
|
16
|
+
throw err;
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=create-validator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-validator.js","sourceRoot":"","sources":["../../src/middlewares/create-validator.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAC7B,KAAwC,EACxC,QAAkD;IAElD,OAAO;QACL,KAAK,CAAC,QAAQ,CAAC,IAAa;YAC1B,IAAI,CAAC;gBACH,OAAO,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC;YAC3B,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;gBAC7B,IAAI,MAAM;oBAAE,MAAM,MAAM,CAAC;gBACzB,MAAM,GAAG,CAAC;YACZ,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
|