@webiny/event-handler-core 0.0.0-unstable.0d717d18dd
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 +11 -0
- package/features/events/EventHandler.d.ts +13 -0
- package/features/events/EventHandler.js +5 -0
- package/features/events/EventHandler.js.map +1 -0
- package/features/events/EventType.d.ts +10 -0
- package/features/events/EventType.js +5 -0
- package/features/events/EventType.js.map +1 -0
- package/features/events/RequestContainer.d.ts +8 -0
- package/features/events/RequestContainer.js +5 -0
- package/features/events/RequestContainer.js.map +1 -0
- package/features/events/RequestContextInitializer.d.ts +26 -0
- package/features/events/RequestContextInitializer.js +5 -0
- package/features/events/RequestContextInitializer.js.map +1 -0
- package/features/events/RequestInitializer.d.ts +22 -0
- package/features/events/RequestInitializer.js +5 -0
- package/features/events/RequestInitializer.js.map +1 -0
- package/features/events/Transport.d.ts +19 -0
- package/features/events/Transport.js +6 -0
- package/features/events/Transport.js.map +1 -0
- package/features/events/chain.d.ts +2 -0
- package/features/events/chain.js +22 -0
- package/features/events/chain.js.map +1 -0
- package/features/events/createHandler.d.ts +20 -0
- package/features/events/createHandler.js +41 -0
- package/features/events/createHandler.js.map +1 -0
- package/features/events/index.d.ts +10 -0
- package/features/events/index.js +10 -0
- package/features/events/runRequestContextInitializers.d.ts +20 -0
- package/features/events/runRequestContextInitializers.js +16 -0
- package/features/events/runRequestContextInitializers.js.map +1 -0
- package/features/events/types.d.ts +4 -0
- package/features/events/types.js +0 -0
- package/features/http/HttpRouter.d.ts +12 -0
- package/features/http/HttpRouter.js +51 -0
- package/features/http/HttpRouter.js.map +1 -0
- package/features/http/abstractions.d.ts +34 -0
- package/features/http/abstractions.js +11 -0
- package/features/http/abstractions.js.map +1 -0
- package/features/http/decorators/RequestContextInitializerDecorator.d.ts +20 -0
- package/features/http/decorators/RequestContextInitializerDecorator.js +22 -0
- package/features/http/decorators/RequestContextInitializerDecorator.js.map +1 -0
- package/features/http/decorators/SecureHeadersDecorator.d.ts +11 -0
- package/features/http/decorators/SecureHeadersDecorator.js +40 -0
- package/features/http/decorators/SecureHeadersDecorator.js.map +1 -0
- package/features/http/decorators/index.d.ts +1 -0
- package/features/http/decorators/index.js +1 -0
- package/features/http/feature.d.ts +4 -0
- package/features/http/feature.js +15 -0
- package/features/http/feature.js.map +1 -0
- package/features/http/index.d.ts +4 -0
- package/features/http/index.js +4 -0
- package/features/testing/HttpRouterHandler.d.ts +13 -0
- package/features/testing/HttpRouterHandler.js +42 -0
- package/features/testing/HttpRouterHandler.js.map +1 -0
- package/features/testing/TestHttpEventHandler.d.ts +6 -0
- package/features/testing/TestHttpEventHandler.js +5 -0
- package/features/testing/TestHttpEventHandler.js.map +1 -0
- package/features/testing/TestHttpEventType.d.ts +15 -0
- package/features/testing/TestHttpEventType.js +20 -0
- package/features/testing/TestHttpEventType.js.map +1 -0
- package/features/testing/createTestHttpHandler.d.ts +14 -0
- package/features/testing/createTestHttpHandler.js +26 -0
- package/features/testing/createTestHttpHandler.js.map +1 -0
- package/features/testing/index.d.ts +4 -0
- package/features/testing/index.js +4 -0
- package/index.d.ts +3 -0
- package/index.js +3 -0
- package/package.json +39 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Webiny
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# @webiny/event-handler-core
|
|
2
|
+
|
|
3
|
+
> [!NOTE]
|
|
4
|
+
> This package is part of the [Webiny](https://www.webiny.com) monorepo.
|
|
5
|
+
> It’s **included in every Webiny project by default** and is not meant to be used as a standalone package.
|
|
6
|
+
|
|
7
|
+
📘 **Documentation:** [https://www.webiny.com/docs](https://www.webiny.com/docs)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
_This README file is automatically generated during the publish process._
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Abstraction } from "@webiny/di";
|
|
2
|
+
import type { NextFunction } from "./types.js";
|
|
3
|
+
export interface EventContext<TEvent = any> {
|
|
4
|
+
event: TEvent;
|
|
5
|
+
metadata: Record<string, any>;
|
|
6
|
+
}
|
|
7
|
+
export interface IEventHandler<TEvent = any, TResult = any> {
|
|
8
|
+
execute(ctx: EventContext<TEvent>, next: NextFunction): Promise<TResult>;
|
|
9
|
+
}
|
|
10
|
+
export declare const EventHandler: Abstraction<IEventHandler<any, any>>;
|
|
11
|
+
export declare namespace EventHandler {
|
|
12
|
+
type Interface<TEvent = any, TResult = any> = IEventHandler<TEvent, TResult>;
|
|
13
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features/events/EventHandler.js","sources":["../../../src/features/events/EventHandler.ts"],"sourcesContent":["import { Abstraction } from \"@webiny/di\";\nimport type { NextFunction } from \"./types.js\";\n\nexport interface EventContext<TEvent = any> {\n event: TEvent;\n metadata: Record<string, any>;\n}\n\nexport interface IEventHandler<TEvent = any, TResult = any> {\n execute(ctx: EventContext<TEvent>, next: NextFunction): Promise<TResult>;\n}\n\nexport const EventHandler = new Abstraction<IEventHandler>(\"EventHandler\");\n\nexport namespace EventHandler {\n export type Interface<TEvent = any, TResult = any> = IEventHandler<TEvent, TResult>;\n}\n"],"names":["EventHandler","Abstraction"],"mappings":";AAYO,MAAMA,eAAe,IAAIC,YAA2B"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Abstraction } from "@webiny/di";
|
|
2
|
+
import type { IEventHandler } from "./EventHandler.js";
|
|
3
|
+
export interface IEventType<TEvent = any> {
|
|
4
|
+
canHandle(event: any): event is TEvent;
|
|
5
|
+
getHandlerAbstraction(): Abstraction<IEventHandler>;
|
|
6
|
+
}
|
|
7
|
+
export declare const EventType: Abstraction<IEventType<any>>;
|
|
8
|
+
export declare namespace EventType {
|
|
9
|
+
type Interface<TEvent = any> = IEventType<TEvent>;
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features/events/EventType.js","sources":["../../../src/features/events/EventType.ts"],"sourcesContent":["import { Abstraction } from \"@webiny/di\";\nimport type { IEventHandler } from \"./EventHandler.js\";\n\nexport interface IEventType<TEvent = any> {\n canHandle(event: any): event is TEvent;\n getHandlerAbstraction(): Abstraction<IEventHandler>;\n}\n\nexport const EventType = new Abstraction<IEventType>(\"EventType\");\n\nexport namespace EventType {\n export type Interface<TEvent = any> = IEventType<TEvent>;\n}\n"],"names":["EventType","Abstraction"],"mappings":";AAQO,MAAMA,YAAY,IAAIC,YAAwB"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Abstraction, Container } from "@webiny/di";
|
|
2
|
+
/**
|
|
3
|
+
* Per-request abstraction holding the child DI container.
|
|
4
|
+
* Registered automatically on each request — available for injection into
|
|
5
|
+
* per-request services that need access to the request-scoped container
|
|
6
|
+
* (e.g. GraphQLEngine passing it as resolver contextValue).
|
|
7
|
+
*/
|
|
8
|
+
export declare const RequestContainer: Abstraction<Container>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features/events/RequestContainer.js","sources":["../../../src/features/events/RequestContainer.ts"],"sourcesContent":["import { Abstraction, Container } from \"@webiny/di\";\n\n/**\n * Per-request abstraction holding the child DI container.\n * Registered automatically on each request — available for injection into\n * per-request services that need access to the request-scoped container\n * (e.g. GraphQLEngine passing it as resolver contextValue).\n */\nexport const RequestContainer = new Abstraction<Container>(\"RequestContainer\");\n"],"names":["RequestContainer","Abstraction"],"mappings":";AAQO,MAAMA,mBAAmB,IAAIC,YAAuB"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Abstraction } from "@webiny/di";
|
|
2
|
+
/**
|
|
3
|
+
* Per-request, POST-auth initialization hook. Runs after the request's tenant/identity have been
|
|
4
|
+
* established (in the route, before the schema is built and before resolvers), so unlike
|
|
5
|
+
* {@link RequestInitializer} (which runs pre-auth) it can do tenant/identity-dependent setup.
|
|
6
|
+
*
|
|
7
|
+
* Use it for per-request work that must happen before resolvers and that genuinely can't be a lazy
|
|
8
|
+
* DI factory — e.g. resolving a per-tenant content model (async), registering decorators that must
|
|
9
|
+
* precede the resolvers they wrap, or building a dynamic, model-derived GraphQL schema.
|
|
10
|
+
*
|
|
11
|
+
* - For pre-auth, tenant-agnostic per-request setup, use {@link RequestInitializer}.
|
|
12
|
+
* - For tenant/identity-dependent capabilities that can be built on demand, prefer a lazy DI factory.
|
|
13
|
+
*
|
|
14
|
+
* It contributes no schema content; its only product is the side effect of init(ctx). Multiple
|
|
15
|
+
* initializers may be registered; they run in registration order.
|
|
16
|
+
*
|
|
17
|
+
* NOTE: despite living in event-handler-core, the current runners are the GraphQL routes
|
|
18
|
+
* (GraphQLEngine + the CMS route). Making it run for non-GraphQL transports is a separate step.
|
|
19
|
+
*/
|
|
20
|
+
export interface IRequestContextInitializer {
|
|
21
|
+
init(ctx: Record<string, any>): void | Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
export declare const RequestContextInitializer: Abstraction<IRequestContextInitializer>;
|
|
24
|
+
export declare namespace RequestContextInitializer {
|
|
25
|
+
type Interface = IRequestContextInitializer;
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features/events/RequestContextInitializer.js","sources":["../../../src/features/events/RequestContextInitializer.ts"],"sourcesContent":["import { Abstraction } from \"@webiny/di\";\n\n/**\n * Per-request, POST-auth initialization hook. Runs after the request's tenant/identity have been\n * established (in the route, before the schema is built and before resolvers), so unlike\n * {@link RequestInitializer} (which runs pre-auth) it can do tenant/identity-dependent setup.\n *\n * Use it for per-request work that must happen before resolvers and that genuinely can't be a lazy\n * DI factory — e.g. resolving a per-tenant content model (async), registering decorators that must\n * precede the resolvers they wrap, or building a dynamic, model-derived GraphQL schema.\n *\n * - For pre-auth, tenant-agnostic per-request setup, use {@link RequestInitializer}.\n * - For tenant/identity-dependent capabilities that can be built on demand, prefer a lazy DI factory.\n *\n * It contributes no schema content; its only product is the side effect of init(ctx). Multiple\n * initializers may be registered; they run in registration order.\n *\n * NOTE: despite living in event-handler-core, the current runners are the GraphQL routes\n * (GraphQLEngine + the CMS route). Making it run for non-GraphQL transports is a separate step.\n */\nexport interface IRequestContextInitializer {\n init(ctx: Record<string, any>): void | Promise<void>;\n}\n\nexport const RequestContextInitializer = new Abstraction<IRequestContextInitializer>(\n \"RequestContextInitializer\"\n);\n\nexport namespace RequestContextInitializer {\n export type Interface = IRequestContextInitializer;\n}\n"],"names":["RequestContextInitializer","Abstraction"],"mappings":";AAwBO,MAAMA,4BAA4B,IAAIC,YACzC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Abstraction } from "@webiny/di";
|
|
2
|
+
/**
|
|
3
|
+
* Per-request async initialization hook. Runs once per request, after the request container is set
|
|
4
|
+
* up (options.request) and BEFORE the event is dispatched to its handler chain — i.e. before
|
|
5
|
+
* auth/tenant are established.
|
|
6
|
+
*
|
|
7
|
+
* Use it for async, tenant-agnostic, per-request setup that must be ready before synchronous
|
|
8
|
+
* consumers run, and that can change between requests (e.g. loading the WCP project license).
|
|
9
|
+
*
|
|
10
|
+
* - For tenant/identity-dependent state, use a lazy DI factory instead (resolved during request
|
|
11
|
+
* handling, after auth has run).
|
|
12
|
+
* - For build-once async setup (a process-lifetime singleton), use a RootInitializer.
|
|
13
|
+
*
|
|
14
|
+
* Multiple initializers may be registered; they run in registration order.
|
|
15
|
+
*/
|
|
16
|
+
export interface IRequestInitializer {
|
|
17
|
+
init(): void | Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
export declare const RequestInitializer: Abstraction<IRequestInitializer>;
|
|
20
|
+
export declare namespace RequestInitializer {
|
|
21
|
+
type Interface = IRequestInitializer;
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features/events/RequestInitializer.js","sources":["../../../src/features/events/RequestInitializer.ts"],"sourcesContent":["import { Abstraction } from \"@webiny/di\";\n\n/**\n * Per-request async initialization hook. Runs once per request, after the request container is set\n * up (options.request) and BEFORE the event is dispatched to its handler chain — i.e. before\n * auth/tenant are established.\n *\n * Use it for async, tenant-agnostic, per-request setup that must be ready before synchronous\n * consumers run, and that can change between requests (e.g. loading the WCP project license).\n *\n * - For tenant/identity-dependent state, use a lazy DI factory instead (resolved during request\n * handling, after auth has run).\n * - For build-once async setup (a process-lifetime singleton), use a RootInitializer.\n *\n * Multiple initializers may be registered; they run in registration order.\n */\nexport interface IRequestInitializer {\n init(): void | Promise<void>;\n}\n\nexport const RequestInitializer = new Abstraction<IRequestInitializer>(\"RequestInitializer\");\n\nexport namespace RequestInitializer {\n export type Interface = IRequestInitializer;\n}\n"],"names":["RequestInitializer","Abstraction"],"mappings":";AAoBO,MAAMA,qBAAqB,IAAIC,YAAiC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Container } from "@webiny/di";
|
|
2
|
+
/**
|
|
3
|
+
* A Transport owns the single transport-specific step of the handler loop: the "bind".
|
|
4
|
+
*
|
|
5
|
+
* Given the raw arguments the platform invoked the handler with, it binds any transport
|
|
6
|
+
* primitives into the per-request container (side-effect only) so downstream translators and
|
|
7
|
+
* handlers can resolve them. Everything else in the loop — request setup, initializers, event-type
|
|
8
|
+
* matching, dispatch — is transport-agnostic and lives in {@link createHandler}.
|
|
9
|
+
*
|
|
10
|
+
* The event to match on is always `rawArgs[0]`; a Transport never changes the match target.
|
|
11
|
+
*/
|
|
12
|
+
export interface Transport {
|
|
13
|
+
bind(container: Container, ...rawArgs: any[]): void | Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Default transport: binds nothing. Reproduces the plain `createHandler` behavior for callers
|
|
17
|
+
* (e.g. the Node HTTP server) that pass the event straight through with no transport primitives.
|
|
18
|
+
*/
|
|
19
|
+
export declare const noopTransport: Transport;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features/events/Transport.js","sources":["../../../src/features/events/Transport.ts"],"sourcesContent":["import type { Container } from \"@webiny/di\";\n\n/**\n * A Transport owns the single transport-specific step of the handler loop: the \"bind\".\n *\n * Given the raw arguments the platform invoked the handler with, it binds any transport\n * primitives into the per-request container (side-effect only) so downstream translators and\n * handlers can resolve them. Everything else in the loop — request setup, initializers, event-type\n * matching, dispatch — is transport-agnostic and lives in {@link createHandler}.\n *\n * The event to match on is always `rawArgs[0]`; a Transport never changes the match target.\n */\nexport interface Transport {\n bind(container: Container, ...rawArgs: any[]): void | Promise<void>;\n}\n\n/**\n * Default transport: binds nothing. Reproduces the plain `createHandler` behavior for callers\n * (e.g. the Node HTTP server) that pass the event straight through with no transport primitives.\n */\nexport const noopTransport: Transport = {\n bind() {}\n};\n"],"names":["noopTransport"],"mappings":"AAoBO,MAAMA,gBAA2B;IACpC,SAAQ;AACZ"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
function executeChain(handlers, event) {
|
|
2
|
+
if (0 === handlers.length) throw new Error("No handlers registered in container");
|
|
3
|
+
const initialCtx = {
|
|
4
|
+
event,
|
|
5
|
+
metadata: {}
|
|
6
|
+
};
|
|
7
|
+
let chain = (_ctx)=>{
|
|
8
|
+
throw new Error("No registered handler claimed this event");
|
|
9
|
+
};
|
|
10
|
+
for(let i = handlers.length - 1; i >= 0; i--){
|
|
11
|
+
const handler = handlers[i];
|
|
12
|
+
const nextChain = chain;
|
|
13
|
+
chain = (ctx)=>{
|
|
14
|
+
const next = (newCtx)=>nextChain(newCtx ?? ctx);
|
|
15
|
+
return handler.execute(ctx, next);
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
return chain(initialCtx);
|
|
19
|
+
}
|
|
20
|
+
export { executeChain };
|
|
21
|
+
|
|
22
|
+
//# sourceMappingURL=chain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features/events/chain.js","sources":["../../../src/features/events/chain.ts"],"sourcesContent":["import type { IEventHandler, EventContext } from \"~/features/events/EventHandler.js\";\n\nexport function executeChain(handlers: IEventHandler[], event: any): Promise<any> {\n if (handlers.length === 0) {\n throw new Error(\"No handlers registered in container\");\n }\n\n const initialCtx: EventContext = { event, metadata: {} };\n\n let chain = (_ctx: EventContext): Promise<any> => {\n throw new Error(\"No registered handler claimed this event\");\n };\n\n for (let i = handlers.length - 1; i >= 0; i--) {\n const handler = handlers[i];\n const nextChain = chain;\n chain = (ctx: EventContext) => {\n const next = (newCtx?: EventContext) => nextChain(newCtx ?? ctx);\n return handler.execute(ctx, next);\n };\n }\n\n return chain(initialCtx);\n}\n"],"names":["executeChain","handlers","event","Error","initialCtx","chain","_ctx","i","handler","nextChain","ctx","next","newCtx"],"mappings":"AAEO,SAASA,aAAaC,QAAyB,EAAEC,KAAU;IAC9D,IAAID,AAAoB,MAApBA,SAAS,MAAM,EACf,MAAM,IAAIE,MAAM;IAGpB,MAAMC,aAA2B;QAAEF;QAAO,UAAU,CAAC;IAAE;IAEvD,IAAIG,QAAQ,CAACC;QACT,MAAM,IAAIH,MAAM;IACpB;IAEA,IAAK,IAAII,IAAIN,SAAS,MAAM,GAAG,GAAGM,KAAK,GAAGA,IAAK;QAC3C,MAAMC,UAAUP,QAAQ,CAACM,EAAE;QAC3B,MAAME,YAAYJ;QAClBA,QAAQ,CAACK;YACL,MAAMC,OAAO,CAACC,SAA0BH,UAAUG,UAAUF;YAC5D,OAAOF,QAAQ,OAAO,CAACE,KAAKC;QAChC;IACJ;IAEA,OAAON,MAAMD;AACjB"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Container } from "@webiny/di";
|
|
2
|
+
import type { Transport } from "../../features/events/Transport.js";
|
|
3
|
+
import type { HandlerSetup } from "../../features/events/types.js";
|
|
4
|
+
export interface CreateHandlerOptions {
|
|
5
|
+
root: HandlerSetup;
|
|
6
|
+
request?: HandlerSetup;
|
|
7
|
+
/**
|
|
8
|
+
* Transport-specific extract step: binds the raw platform arguments (e.g. the AWS Lambda
|
|
9
|
+
* event + context) into the per-request container. Defaults to a no-op, which leaves the
|
|
10
|
+
* event to pass straight through — the plain server/HTTP behavior.
|
|
11
|
+
*/
|
|
12
|
+
transport?: Transport;
|
|
13
|
+
/**
|
|
14
|
+
* A pre-built, already root-initialized container. When provided, `root` is NOT called again —
|
|
15
|
+
* used by transports that must build the root eagerly at startup (e.g. the Node server, which
|
|
16
|
+
* needs the root container ready to attach a WebSockets upgrade handler before the first request).
|
|
17
|
+
*/
|
|
18
|
+
rootContainer?: Container;
|
|
19
|
+
}
|
|
20
|
+
export declare function createHandler(options: CreateHandlerOptions): (...rawArgs: any[]) => Promise<any>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Container } from "@webiny/di";
|
|
2
|
+
import { EventType } from "./EventType.js";
|
|
3
|
+
import { RequestContainer } from "./RequestContainer.js";
|
|
4
|
+
import { RequestInitializer } from "./RequestInitializer.js";
|
|
5
|
+
import { executeChain } from "./chain.js";
|
|
6
|
+
import { noopTransport } from "./Transport.js";
|
|
7
|
+
function createHandler(options) {
|
|
8
|
+
let rootContainer = options.rootContainer ?? null;
|
|
9
|
+
const transport = options.transport ?? noopTransport;
|
|
10
|
+
return async (...rawArgs)=>{
|
|
11
|
+
if (!rootContainer) {
|
|
12
|
+
rootContainer = new Container();
|
|
13
|
+
await options.root(rootContainer);
|
|
14
|
+
}
|
|
15
|
+
const child = rootContainer.createChildContainer();
|
|
16
|
+
child.registerInstance(RequestContainer, child);
|
|
17
|
+
await transport.bind(child, ...rawArgs);
|
|
18
|
+
if (options.request) await options.request(child);
|
|
19
|
+
for (const initializer of child.resolveAll(RequestInitializer))await initializer.init();
|
|
20
|
+
const event = rawArgs[0];
|
|
21
|
+
const eventTypes = child.resolveAll(EventType);
|
|
22
|
+
const matched = eventTypes.find((et)=>et.canHandle(event));
|
|
23
|
+
if (!matched) {
|
|
24
|
+
const shape = event && "object" == typeof event ? {
|
|
25
|
+
keys: Object.keys(event),
|
|
26
|
+
source: event.source,
|
|
27
|
+
detailType: event["detail-type"]
|
|
28
|
+
} : {
|
|
29
|
+
type: typeof event
|
|
30
|
+
};
|
|
31
|
+
const registered = eventTypes.map((et)=>et?.constructor?.name);
|
|
32
|
+
throw new Error(`No event type matched the incoming event. Event shape: ${JSON.stringify(shape)}; registered event types: ${JSON.stringify(registered)}`);
|
|
33
|
+
}
|
|
34
|
+
const abstraction = matched.getHandlerAbstraction();
|
|
35
|
+
const handlers = child.resolveAll(abstraction);
|
|
36
|
+
return executeChain(handlers, event);
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export { createHandler };
|
|
40
|
+
|
|
41
|
+
//# sourceMappingURL=createHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features/events/createHandler.js","sources":["../../../src/features/events/createHandler.ts"],"sourcesContent":["import { Container } from \"@webiny/di\";\nimport { EventType } from \"~/features/events/EventType.js\";\nimport { RequestContainer } from \"~/features/events/RequestContainer.js\";\nimport { RequestInitializer } from \"~/features/events/RequestInitializer.js\";\nimport { executeChain } from \"~/features/events/chain.js\";\nimport { noopTransport } from \"~/features/events/Transport.js\";\nimport type { Transport } from \"~/features/events/Transport.js\";\nimport type { HandlerSetup } from \"~/features/events/types.js\";\n\nexport interface CreateHandlerOptions {\n root: HandlerSetup;\n request?: HandlerSetup;\n /**\n * Transport-specific extract step: binds the raw platform arguments (e.g. the AWS Lambda\n * event + context) into the per-request container. Defaults to a no-op, which leaves the\n * event to pass straight through — the plain server/HTTP behavior.\n */\n transport?: Transport;\n /**\n * A pre-built, already root-initialized container. When provided, `root` is NOT called again —\n * used by transports that must build the root eagerly at startup (e.g. the Node server, which\n * needs the root container ready to attach a WebSockets upgrade handler before the first request).\n */\n rootContainer?: Container;\n}\n\nexport function createHandler(options: CreateHandlerOptions) {\n let rootContainer: Container | null = options.rootContainer ?? null;\n const transport = options.transport ?? noopTransport;\n\n return async (...rawArgs: any[]): Promise<any> => {\n if (!rootContainer) {\n rootContainer = new Container();\n await options.root(rootContainer);\n }\n\n const child = rootContainer.createChildContainer();\n child.registerInstance(RequestContainer, child);\n\n // Transport-specific bind: register the raw platform arguments into the request container\n // before request setup runs. The default transport binds nothing.\n await transport.bind(child, ...rawArgs);\n\n if (options.request) {\n await options.request(child);\n }\n\n // Per-request async initialization (tenant-agnostic), before the event is dispatched and\n // before auth/tenant are established. For tenant-dependent setup use lazy DI factories.\n for (const initializer of child.resolveAll(RequestInitializer)) {\n await initializer.init();\n }\n\n // The event to match on is always the first raw argument (transports never change this).\n const event = rawArgs[0];\n const eventTypes = child.resolveAll(EventType);\n const matched = eventTypes.find(et => et.canHandle(event));\n\n if (!matched) {\n // Include a non-sensitive shape summary so this is debuggable: which event types were\n // registered vs. what the event actually looks like (keys + EventBridge discriminators).\n const shape =\n event && typeof event === \"object\"\n ? {\n keys: Object.keys(event),\n source: (event as any).source,\n detailType: (event as any)[\"detail-type\"]\n }\n : { type: typeof event };\n const registered = eventTypes.map(et => (et as any)?.constructor?.name);\n throw new Error(\n `No event type matched the incoming event. Event shape: ${JSON.stringify(\n shape\n )}; registered event types: ${JSON.stringify(registered)}`\n );\n }\n\n const abstraction = matched.getHandlerAbstraction();\n const handlers = child.resolveAll(abstraction);\n\n return executeChain(handlers, event);\n };\n}\n"],"names":["createHandler","options","rootContainer","transport","noopTransport","rawArgs","Container","child","RequestContainer","initializer","RequestInitializer","event","eventTypes","EventType","matched","et","shape","Object","registered","Error","JSON","abstraction","handlers","executeChain"],"mappings":";;;;;;AA0BO,SAASA,cAAcC,OAA6B;IACvD,IAAIC,gBAAkCD,QAAQ,aAAa,IAAI;IAC/D,MAAME,YAAYF,QAAQ,SAAS,IAAIG;IAEvC,OAAO,OAAO,GAAGC;QACb,IAAI,CAACH,eAAe;YAChBA,gBAAgB,IAAII;YACpB,MAAML,QAAQ,IAAI,CAACC;QACvB;QAEA,MAAMK,QAAQL,cAAc,oBAAoB;QAChDK,MAAM,gBAAgB,CAACC,kBAAkBD;QAIzC,MAAMJ,UAAU,IAAI,CAACI,UAAUF;QAE/B,IAAIJ,QAAQ,OAAO,EACf,MAAMA,QAAQ,OAAO,CAACM;QAK1B,KAAK,MAAME,eAAeF,MAAM,UAAU,CAACG,oBACvC,MAAMD,YAAY,IAAI;QAI1B,MAAME,QAAQN,OAAO,CAAC,EAAE;QACxB,MAAMO,aAAaL,MAAM,UAAU,CAACM;QACpC,MAAMC,UAAUF,WAAW,IAAI,CAACG,CAAAA,KAAMA,GAAG,SAAS,CAACJ;QAEnD,IAAI,CAACG,SAAS;YAGV,MAAME,QACFL,SAAS,AAAiB,YAAjB,OAAOA,QACV;gBACI,MAAMM,OAAO,IAAI,CAACN;gBAClB,QAASA,MAAc,MAAM;gBAC7B,YAAaA,KAAa,CAAC,cAAc;YAC7C,IACA;gBAAE,MAAM,OAAOA;YAAM;YAC/B,MAAMO,aAAaN,WAAW,GAAG,CAACG,CAAAA,KAAOA,IAAY,aAAa;YAClE,MAAM,IAAII,MACN,CAAC,uDAAuD,EAAEC,KAAK,SAAS,CACpEJ,OACF,0BAA0B,EAAEI,KAAK,SAAS,CAACF,aAAa;QAElE;QAEA,MAAMG,cAAcP,QAAQ,qBAAqB;QACjD,MAAMQ,WAAWf,MAAM,UAAU,CAACc;QAElC,OAAOE,aAAaD,UAAUX;IAClC;AACJ"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from "./EventHandler.js";
|
|
2
|
+
export * from "./EventType.js";
|
|
3
|
+
export * from "./RequestContainer.js";
|
|
4
|
+
export * from "./RequestContextInitializer.js";
|
|
5
|
+
export * from "./RequestInitializer.js";
|
|
6
|
+
export * from "./chain.js";
|
|
7
|
+
export * from "./createHandler.js";
|
|
8
|
+
export * from "./Transport.js";
|
|
9
|
+
export * from "./types.js";
|
|
10
|
+
export * from "./runRequestContextInitializers.js";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from "./EventHandler.js";
|
|
2
|
+
export * from "./EventType.js";
|
|
3
|
+
export * from "./RequestContainer.js";
|
|
4
|
+
export * from "./RequestContextInitializer.js";
|
|
5
|
+
export * from "./RequestInitializer.js";
|
|
6
|
+
export * from "./chain.js";
|
|
7
|
+
export * from "./createHandler.js";
|
|
8
|
+
export * from "./Transport.js";
|
|
9
|
+
export * from "./types.js";
|
|
10
|
+
export * from "./runRequestContextInitializers.js";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Container } from "@webiny/di";
|
|
2
|
+
export interface RunRequestContextInitializersOptions {
|
|
3
|
+
/**
|
|
4
|
+
* When true, a failing initializer is logged and skipped instead of aborting the whole run.
|
|
5
|
+
* Use for event categories (e.g. background tasks) that register the full HTTP initializer set
|
|
6
|
+
* but don't need every one — a side-effect initializer that doesn't apply shouldn't fail the task.
|
|
7
|
+
* HTTP keeps the default (fail-fast) so a broken critical initializer surfaces.
|
|
8
|
+
*/
|
|
9
|
+
continueOnError?: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Runs all registered {@link RequestContextInitializer}s (post-auth per-request setup) against the
|
|
13
|
+
* given container. Call this once per request, AFTER the request's identity/tenant have been
|
|
14
|
+
* established, and BEFORE dispatching to the handler that needs them.
|
|
15
|
+
*
|
|
16
|
+
* Context establishment differs per event category (HTTP establishers, a background task's payload,
|
|
17
|
+
* etc.), but the initializer run itself is identical — so each category invokes this at its own
|
|
18
|
+
* "context established" point (the HTTP layer for all HTTP routes; the bg-task handler for tasks).
|
|
19
|
+
*/
|
|
20
|
+
export declare const runRequestContextInitializers: (container: Container, options?: RunRequestContextInitializersOptions) => Promise<void>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { RequestContextInitializer } from "./RequestContextInitializer.js";
|
|
2
|
+
const runRequestContextInitializers = async (container, options = {})=>{
|
|
3
|
+
const ctx = {
|
|
4
|
+
container
|
|
5
|
+
};
|
|
6
|
+
for (const initializer of container.resolveAll(RequestContextInitializer))try {
|
|
7
|
+
await initializer.init(ctx);
|
|
8
|
+
} catch (err) {
|
|
9
|
+
if (!options.continueOnError) throw err;
|
|
10
|
+
const name = initializer?.constructor?.name ?? "RequestContextInitializer";
|
|
11
|
+
console.error(`[runRequestContextInitializers] "${name}" failed (continuing):`, err);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
export { runRequestContextInitializers };
|
|
15
|
+
|
|
16
|
+
//# sourceMappingURL=runRequestContextInitializers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features/events/runRequestContextInitializers.js","sources":["../../../src/features/events/runRequestContextInitializers.ts"],"sourcesContent":["import type { Container } from \"@webiny/di\";\nimport { RequestContextInitializer } from \"./RequestContextInitializer.js\";\n\nexport interface RunRequestContextInitializersOptions {\n /**\n * When true, a failing initializer is logged and skipped instead of aborting the whole run.\n * Use for event categories (e.g. background tasks) that register the full HTTP initializer set\n * but don't need every one — a side-effect initializer that doesn't apply shouldn't fail the task.\n * HTTP keeps the default (fail-fast) so a broken critical initializer surfaces.\n */\n continueOnError?: boolean;\n}\n\n/**\n * Runs all registered {@link RequestContextInitializer}s (post-auth per-request setup) against the\n * given container. Call this once per request, AFTER the request's identity/tenant have been\n * established, and BEFORE dispatching to the handler that needs them.\n *\n * Context establishment differs per event category (HTTP establishers, a background task's payload,\n * etc.), but the initializer run itself is identical — so each category invokes this at its own\n * \"context established\" point (the HTTP layer for all HTTP routes; the bg-task handler for tasks).\n */\nexport const runRequestContextInitializers = async (\n container: Container,\n options: RunRequestContextInitializersOptions = {}\n): Promise<void> => {\n const ctx: Record<string, any> = { container };\n for (const initializer of container.resolveAll(RequestContextInitializer)) {\n try {\n await initializer.init(ctx);\n } catch (err) {\n if (!options.continueOnError) {\n throw err;\n }\n const name = (initializer as any)?.constructor?.name ?? \"RequestContextInitializer\";\n console.error(`[runRequestContextInitializers] \"${name}\" failed (continuing):`, err);\n }\n }\n};\n"],"names":["runRequestContextInitializers","container","options","ctx","initializer","RequestContextInitializer","err","name","console"],"mappings":";AAsBO,MAAMA,gCAAgC,OACzCC,WACAC,UAAgD,CAAC,CAAC;IAElD,MAAMC,MAA2B;QAAEF;IAAU;IAC7C,KAAK,MAAMG,eAAeH,UAAU,UAAU,CAACI,2BAC3C,IAAI;QACA,MAAMD,YAAY,IAAI,CAACD;IAC3B,EAAE,OAAOG,KAAK;QACV,IAAI,CAACJ,QAAQ,eAAe,EACxB,MAAMI;QAEV,MAAMC,OAAQH,aAAqB,aAAa,QAAQ;QACxDI,QAAQ,KAAK,CAAC,CAAC,iCAAiC,EAAED,KAAK,sBAAsB,CAAC,EAAED;IACpF;AAER"}
|
|
File without changes
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { HttpRouter } from "../../features/http/abstractions.js";
|
|
2
|
+
import type { IHttpRoute, IHttpRequest, IHttpResponse } from "../../features/http/abstractions.js";
|
|
3
|
+
declare class HttpRouterImplClass implements HttpRouter.Interface {
|
|
4
|
+
private routes;
|
|
5
|
+
constructor(routes: IHttpRoute[]);
|
|
6
|
+
route(request: IHttpRequest): Promise<IHttpResponse>;
|
|
7
|
+
private match;
|
|
8
|
+
}
|
|
9
|
+
export declare const HttpRouterImpl: typeof HttpRouterImplClass & {
|
|
10
|
+
__abstraction: import("@webiny/di").Abstraction<import("~/features/http/abstractions.js").IHttpRouter>;
|
|
11
|
+
};
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { HttpRoute, HttpRouter, RouteNotFoundError } from "./abstractions.js";
|
|
2
|
+
function matchPath(pattern, path) {
|
|
3
|
+
if (pattern.endsWith("/*")) {
|
|
4
|
+
const prefix = pattern.slice(0, -2);
|
|
5
|
+
return path.startsWith(prefix) ? {} : null;
|
|
6
|
+
}
|
|
7
|
+
const patternParts = pattern.split("/").filter(Boolean);
|
|
8
|
+
const pathParts = path.split("/").filter(Boolean);
|
|
9
|
+
if (patternParts.length !== pathParts.length) return null;
|
|
10
|
+
const params = {};
|
|
11
|
+
for(let i = 0; i < patternParts.length; i++){
|
|
12
|
+
const pp = patternParts[i];
|
|
13
|
+
const pathPart = pathParts[i];
|
|
14
|
+
if (pp.startsWith(":")) params[pp.slice(1)] = decodeURIComponent(pathPart);
|
|
15
|
+
else if (pp !== pathPart) return null;
|
|
16
|
+
}
|
|
17
|
+
return params;
|
|
18
|
+
}
|
|
19
|
+
class HttpRouterImplClass {
|
|
20
|
+
constructor(routes){
|
|
21
|
+
this.routes = routes;
|
|
22
|
+
}
|
|
23
|
+
async route(request) {
|
|
24
|
+
for (const route of this.routes){
|
|
25
|
+
const params = this.match(route, request);
|
|
26
|
+
if (null !== params) return route.handle({
|
|
27
|
+
...request,
|
|
28
|
+
pathParameters: params
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
throw new RouteNotFoundError(request.method, request.path);
|
|
32
|
+
}
|
|
33
|
+
match(route, request) {
|
|
34
|
+
if (route.method !== request.method) return null;
|
|
35
|
+
return matchPath(route.path, request.path);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const HttpRouterImpl = HttpRouter.createImplementation({
|
|
39
|
+
implementation: HttpRouterImplClass,
|
|
40
|
+
dependencies: [
|
|
41
|
+
[
|
|
42
|
+
HttpRoute,
|
|
43
|
+
{
|
|
44
|
+
multiple: true
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
]
|
|
48
|
+
});
|
|
49
|
+
export { HttpRouterImpl };
|
|
50
|
+
|
|
51
|
+
//# sourceMappingURL=HttpRouter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features/http/HttpRouter.js","sources":["../../../src/features/http/HttpRouter.ts"],"sourcesContent":["import { HttpRouter, HttpRoute, RouteNotFoundError } from \"~/features/http/abstractions.js\";\nimport type { IHttpRoute, IHttpRequest, IHttpResponse } from \"~/features/http/abstractions.js\";\n\nfunction matchPath(pattern: string, path: string): Record<string, string> | null {\n if (pattern.endsWith(\"/*\")) {\n const prefix = pattern.slice(0, -2);\n return path.startsWith(prefix) ? {} : null;\n }\n\n const patternParts = pattern.split(\"/\").filter(Boolean);\n const pathParts = path.split(\"/\").filter(Boolean);\n\n if (patternParts.length !== pathParts.length) {\n return null;\n }\n\n const params: Record<string, string> = {};\n\n for (let i = 0; i < patternParts.length; i++) {\n const pp = patternParts[i];\n const pathPart = pathParts[i];\n\n if (pp.startsWith(\":\")) {\n params[pp.slice(1)] = decodeURIComponent(pathPart);\n } else if (pp !== pathPart) {\n return null;\n }\n }\n\n return params;\n}\n\nclass HttpRouterImplClass implements HttpRouter.Interface {\n // TODO: revisit eager route construction.\n // Injecting [HttpRoute, { multiple: true }] constructs EVERY registered route on each request\n // just to path-match, so a route's constructor runs even when its path doesn't match. Any route\n // whose constructor pulls a request-time-registered token (e.g. CMS use-cases needing\n // EntryFromStorageTransform / a per-request CmsModel) then throws \"No registration found\" before\n // any handler runs. Current routes work around this by resolving such deps lazily inside handle()\n // (AssetDeliveryRoute, WebsiteBuilderRedirectsRoute) or by pre-registering them before routing.\n // The systemic fix is to construct only the matched route lazily (inject route factories/thunks,\n // or resolve HttpRoute by matched path on demand) so this workaround isn't required per route.\n constructor(private routes: IHttpRoute[]) {}\n\n async route(request: IHttpRequest): Promise<IHttpResponse> {\n for (const route of this.routes) {\n const params = this.match(route, request);\n if (params !== null) {\n return route.handle({ ...request, pathParameters: params });\n }\n }\n throw new RouteNotFoundError(request.method, request.path);\n }\n\n private match(route: IHttpRoute, request: IHttpRequest): Record<string, string> | null {\n if (route.method !== request.method) {\n return null;\n }\n return matchPath(route.path, request.path);\n }\n}\n\nexport const HttpRouterImpl = HttpRouter.createImplementation({\n implementation: HttpRouterImplClass,\n dependencies: [[HttpRoute, { multiple: true }]]\n});\n"],"names":["matchPath","pattern","path","prefix","patternParts","Boolean","pathParts","params","i","pp","pathPart","decodeURIComponent","HttpRouterImplClass","routes","request","route","RouteNotFoundError","HttpRouterImpl","HttpRouter","HttpRoute"],"mappings":";AAGA,SAASA,UAAUC,OAAe,EAAEC,IAAY;IAC5C,IAAID,QAAQ,QAAQ,CAAC,OAAO;QACxB,MAAME,SAASF,QAAQ,KAAK,CAAC,GAAG;QAChC,OAAOC,KAAK,UAAU,CAACC,UAAU,CAAC,IAAI;IAC1C;IAEA,MAAMC,eAAeH,QAAQ,KAAK,CAAC,KAAK,MAAM,CAACI;IAC/C,MAAMC,YAAYJ,KAAK,KAAK,CAAC,KAAK,MAAM,CAACG;IAEzC,IAAID,aAAa,MAAM,KAAKE,UAAU,MAAM,EACxC,OAAO;IAGX,MAAMC,SAAiC,CAAC;IAExC,IAAK,IAAIC,IAAI,GAAGA,IAAIJ,aAAa,MAAM,EAAEI,IAAK;QAC1C,MAAMC,KAAKL,YAAY,CAACI,EAAE;QAC1B,MAAME,WAAWJ,SAAS,CAACE,EAAE;QAE7B,IAAIC,GAAG,UAAU,CAAC,MACdF,MAAM,CAACE,GAAG,KAAK,CAAC,GAAG,GAAGE,mBAAmBD;aACtC,IAAID,OAAOC,UACd,OAAO;IAEf;IAEA,OAAOH;AACX;AAEA,MAAMK;IAUF,YAAoBC,MAAoB,CAAE;aAAtBA,MAAM,GAANA;IAAuB;IAE3C,MAAM,MAAMC,OAAqB,EAA0B;QACvD,KAAK,MAAMC,SAAS,IAAI,CAAC,MAAM,CAAE;YAC7B,MAAMR,SAAS,IAAI,CAAC,KAAK,CAACQ,OAAOD;YACjC,IAAIP,AAAW,SAAXA,QACA,OAAOQ,MAAM,MAAM,CAAC;gBAAE,GAAGD,OAAO;gBAAE,gBAAgBP;YAAO;QAEjE;QACA,MAAM,IAAIS,mBAAmBF,QAAQ,MAAM,EAAEA,QAAQ,IAAI;IAC7D;IAEQ,MAAMC,KAAiB,EAAED,OAAqB,EAAiC;QACnF,IAAIC,MAAM,MAAM,KAAKD,QAAQ,MAAM,EAC/B,OAAO;QAEX,OAAOd,UAAUe,MAAM,IAAI,EAAED,QAAQ,IAAI;IAC7C;AACJ;AAEO,MAAMG,iBAAiBC,WAAW,oBAAoB,CAAC;IAC1D,gBAAgBN;IAChB,cAAc;QAAC;YAACO;YAAW;gBAAE,UAAU;YAAK;SAAE;KAAC;AACnD"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Abstraction } from "@webiny/di";
|
|
2
|
+
export interface IHttpRequest {
|
|
3
|
+
method: string;
|
|
4
|
+
path: string;
|
|
5
|
+
headers: Record<string, string>;
|
|
6
|
+
query: Record<string, string>;
|
|
7
|
+
pathParameters: Record<string, string>;
|
|
8
|
+
body: any;
|
|
9
|
+
}
|
|
10
|
+
export interface IHttpResponse {
|
|
11
|
+
statusCode: number;
|
|
12
|
+
headers?: Record<string, string>;
|
|
13
|
+
body?: any;
|
|
14
|
+
}
|
|
15
|
+
export interface IHttpRoute {
|
|
16
|
+
readonly method: string;
|
|
17
|
+
readonly path: string;
|
|
18
|
+
handle(request: IHttpRequest): Promise<IHttpResponse>;
|
|
19
|
+
}
|
|
20
|
+
export interface IHttpRouter {
|
|
21
|
+
route(request: IHttpRequest): Promise<IHttpResponse>;
|
|
22
|
+
}
|
|
23
|
+
export declare const HttpRoute: Abstraction<IHttpRoute>;
|
|
24
|
+
export declare const HttpRouter: Abstraction<IHttpRouter>;
|
|
25
|
+
export declare namespace HttpRoute {
|
|
26
|
+
type Interface = IHttpRoute;
|
|
27
|
+
}
|
|
28
|
+
export declare namespace HttpRouter {
|
|
29
|
+
type Interface = IHttpRouter;
|
|
30
|
+
}
|
|
31
|
+
export declare class RouteNotFoundError extends Error {
|
|
32
|
+
readonly code: "ROUTE_NOT_FOUND";
|
|
33
|
+
constructor(method: string, path: string);
|
|
34
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Abstraction } from "@webiny/di";
|
|
2
|
+
const HttpRoute = new Abstraction("HttpRoute");
|
|
3
|
+
const HttpRouter = new Abstraction("HttpRouter");
|
|
4
|
+
class RouteNotFoundError extends Error {
|
|
5
|
+
constructor(method, path){
|
|
6
|
+
super(`Route not found: ${method} ${path}`), this.code = "ROUTE_NOT_FOUND";
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export { HttpRoute, HttpRouter, RouteNotFoundError };
|
|
10
|
+
|
|
11
|
+
//# sourceMappingURL=abstractions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features/http/abstractions.js","sources":["../../../src/features/http/abstractions.ts"],"sourcesContent":["import { Abstraction } from \"@webiny/di\";\n\nexport interface IHttpRequest {\n method: string;\n path: string;\n headers: Record<string, string>;\n query: Record<string, string>;\n pathParameters: Record<string, string>;\n body: any;\n}\n\nexport interface IHttpResponse {\n statusCode: number;\n headers?: Record<string, string>;\n body?: any;\n}\n\nexport interface IHttpRoute {\n readonly method: string;\n readonly path: string;\n handle(request: IHttpRequest): Promise<IHttpResponse>;\n}\n\nexport interface IHttpRouter {\n route(request: IHttpRequest): Promise<IHttpResponse>;\n}\n\nexport const HttpRoute = new Abstraction<IHttpRoute>(\"HttpRoute\");\nexport const HttpRouter = new Abstraction<IHttpRouter>(\"HttpRouter\");\n\nexport namespace HttpRoute {\n export type Interface = IHttpRoute;\n}\n\nexport namespace HttpRouter {\n export type Interface = IHttpRouter;\n}\n\nexport class RouteNotFoundError extends Error {\n readonly code = \"ROUTE_NOT_FOUND\" as const;\n\n constructor(method: string, path: string) {\n super(`Route not found: ${method} ${path}`);\n }\n}\n"],"names":["HttpRoute","Abstraction","HttpRouter","RouteNotFoundError","Error","method","path"],"mappings":";AA2BO,MAAMA,YAAY,IAAIC,YAAwB;AAC9C,MAAMC,aAAa,IAAID,YAAyB;AAUhD,MAAME,2BAA2BC;IAGpC,YAAYC,MAAc,EAAEC,IAAY,CAAE;QACtC,KAAK,CAAC,CAAC,iBAAiB,EAAED,OAAO,CAAC,EAAEC,MAAM,QAHrC,IAAI,GAAG;IAIhB;AACJ"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Container } from "@webiny/di";
|
|
2
|
+
import type { IHttpRouter, IHttpRequest, IHttpResponse } from "../../../features/http/abstractions.js";
|
|
3
|
+
/**
|
|
4
|
+
* Runs the post-auth {@link RequestContextInitializer}s once per request, for EVERY HTTP route
|
|
5
|
+
* (graphql, cms, /files/* asset delivery, ...) — not just the GraphQL engine. It's transport-
|
|
6
|
+
* agnostic: it lives in event-handler-core and depends only on DI abstractions. The transport's
|
|
7
|
+
* auth decorators (e.g. ApiGatewayIdentityLoaderDecorator / ApiGatewayTenantLoaderDecorator)
|
|
8
|
+
* establish identity/tenant BEFORE control reaches HttpRouter, so by the time this runs, the request
|
|
9
|
+
* context is established.
|
|
10
|
+
*/
|
|
11
|
+
declare class RequestContextInitializerDecoratorImpl implements IHttpRouter {
|
|
12
|
+
private container;
|
|
13
|
+
private decoratee;
|
|
14
|
+
constructor(container: Container, decoratee: IHttpRouter);
|
|
15
|
+
route(request: IHttpRequest): Promise<IHttpResponse>;
|
|
16
|
+
}
|
|
17
|
+
export declare const RequestContextInitializerDecorator: typeof RequestContextInitializerDecoratorImpl & {
|
|
18
|
+
__abstraction: import("@webiny/di").Abstraction<IHttpRouter>;
|
|
19
|
+
};
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { HttpRouter } from "../abstractions.js";
|
|
2
|
+
import { RequestContainer } from "../../events/RequestContainer.js";
|
|
3
|
+
import { runRequestContextInitializers } from "../../events/runRequestContextInitializers.js";
|
|
4
|
+
class RequestContextInitializerDecoratorImpl {
|
|
5
|
+
constructor(container, decoratee){
|
|
6
|
+
this.container = container;
|
|
7
|
+
this.decoratee = decoratee;
|
|
8
|
+
}
|
|
9
|
+
async route(request) {
|
|
10
|
+
await runRequestContextInitializers(this.container);
|
|
11
|
+
return this.decoratee.route(request);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const RequestContextInitializerDecorator = HttpRouter.createDecorator({
|
|
15
|
+
decorator: RequestContextInitializerDecoratorImpl,
|
|
16
|
+
dependencies: [
|
|
17
|
+
RequestContainer
|
|
18
|
+
]
|
|
19
|
+
});
|
|
20
|
+
export { RequestContextInitializerDecorator };
|
|
21
|
+
|
|
22
|
+
//# sourceMappingURL=RequestContextInitializerDecorator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features/http/decorators/RequestContextInitializerDecorator.js","sources":["../../../../src/features/http/decorators/RequestContextInitializerDecorator.ts"],"sourcesContent":["import type { Container } from \"@webiny/di\";\nimport { HttpRouter } from \"~/features/http/abstractions.js\";\nimport type { IHttpRouter, IHttpRequest, IHttpResponse } from \"~/features/http/abstractions.js\";\nimport { RequestContainer } from \"~/features/events/RequestContainer.js\";\nimport { runRequestContextInitializers } from \"~/features/events/runRequestContextInitializers.js\";\n\n/**\n * Runs the post-auth {@link RequestContextInitializer}s once per request, for EVERY HTTP route\n * (graphql, cms, /files/* asset delivery, ...) — not just the GraphQL engine. It's transport-\n * agnostic: it lives in event-handler-core and depends only on DI abstractions. The transport's\n * auth decorators (e.g. ApiGatewayIdentityLoaderDecorator / ApiGatewayTenantLoaderDecorator)\n * establish identity/tenant BEFORE control reaches HttpRouter, so by the time this runs, the request\n * context is established.\n */\nclass RequestContextInitializerDecoratorImpl implements IHttpRouter {\n constructor(\n private container: Container,\n private decoratee: IHttpRouter\n ) {}\n\n async route(request: IHttpRequest): Promise<IHttpResponse> {\n await runRequestContextInitializers(this.container);\n return this.decoratee.route(request);\n }\n}\n\nexport const RequestContextInitializerDecorator = HttpRouter.createDecorator({\n decorator: RequestContextInitializerDecoratorImpl,\n dependencies: [RequestContainer]\n});\n"],"names":["RequestContextInitializerDecoratorImpl","container","decoratee","request","runRequestContextInitializers","RequestContextInitializerDecorator","HttpRouter","RequestContainer"],"mappings":";;;AAcA,MAAMA;IACF,YACYC,SAAoB,EACpBC,SAAsB,CAChC;aAFUD,SAAS,GAATA;aACAC,SAAS,GAATA;IACT;IAEH,MAAM,MAAMC,OAAqB,EAA0B;QACvD,MAAMC,8BAA8B,IAAI,CAAC,SAAS;QAClD,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAACD;IAChC;AACJ;AAEO,MAAME,qCAAqCC,WAAW,eAAe,CAAC;IACzE,WAAWN;IACX,cAAc;QAACO;KAAiB;AACpC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { IHttpRouter, IHttpRequest, IHttpResponse } from "../../../features/http/abstractions.js";
|
|
2
|
+
declare class SecureHeadersDecoratorImpl implements IHttpRouter {
|
|
3
|
+
private decoratee;
|
|
4
|
+
constructor(decoratee: IHttpRouter);
|
|
5
|
+
route(request: IHttpRequest): Promise<IHttpResponse>;
|
|
6
|
+
private optionsResponse;
|
|
7
|
+
}
|
|
8
|
+
export declare const SecureHeadersDecorator: typeof SecureHeadersDecoratorImpl & {
|
|
9
|
+
__abstraction: import("@webiny/di").Abstraction<IHttpRouter>;
|
|
10
|
+
};
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { HttpRouter } from "../abstractions.js";
|
|
2
|
+
const ALLOWED_HEADERS = "accept, authorization, cache-control, content-type, x-i18n-locale, x-tenant";
|
|
3
|
+
class SecureHeadersDecoratorImpl {
|
|
4
|
+
constructor(decoratee){
|
|
5
|
+
this.decoratee = decoratee;
|
|
6
|
+
}
|
|
7
|
+
async route(request) {
|
|
8
|
+
if ("OPTIONS" === request.method) return this.optionsResponse(request);
|
|
9
|
+
const response = await this.decoratee.route(request);
|
|
10
|
+
return {
|
|
11
|
+
...response,
|
|
12
|
+
headers: {
|
|
13
|
+
...response.headers,
|
|
14
|
+
"access-control-allow-origin": request.headers["origin"] || "*",
|
|
15
|
+
"access-control-allow-credentials": "true",
|
|
16
|
+
vary: "origin"
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
optionsResponse(request) {
|
|
21
|
+
return {
|
|
22
|
+
statusCode: 204,
|
|
23
|
+
headers: {
|
|
24
|
+
"access-control-allow-origin": request.headers["origin"] || "*",
|
|
25
|
+
"access-control-allow-credentials": "true",
|
|
26
|
+
"access-control-allow-methods": "OPTIONS,POST,GET,DELETE,PUT,PATCH",
|
|
27
|
+
"access-control-allow-headers": ALLOWED_HEADERS,
|
|
28
|
+
"access-control-max-age": "86400",
|
|
29
|
+
"cache-control": "public, max-age=86400"
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const SecureHeadersDecorator = HttpRouter.createDecorator({
|
|
35
|
+
decorator: SecureHeadersDecoratorImpl,
|
|
36
|
+
dependencies: []
|
|
37
|
+
});
|
|
38
|
+
export { SecureHeadersDecorator };
|
|
39
|
+
|
|
40
|
+
//# sourceMappingURL=SecureHeadersDecorator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features/http/decorators/SecureHeadersDecorator.js","sources":["../../../../src/features/http/decorators/SecureHeadersDecorator.ts"],"sourcesContent":["import { HttpRouter } from \"~/features/http/abstractions.js\";\nimport type { IHttpRouter, IHttpRequest, IHttpResponse } from \"~/features/http/abstractions.js\";\n\nconst ALLOWED_HEADERS = [\n \"accept\",\n \"authorization\",\n \"cache-control\",\n \"content-type\",\n \"x-i18n-locale\",\n \"x-tenant\"\n].join(\", \");\n\nclass SecureHeadersDecoratorImpl implements IHttpRouter {\n constructor(private decoratee: IHttpRouter) {}\n\n async route(request: IHttpRequest): Promise<IHttpResponse> {\n if (request.method === \"OPTIONS\") {\n return this.optionsResponse(request);\n }\n\n const response = await this.decoratee.route(request);\n\n return {\n ...response,\n headers: {\n ...response.headers,\n \"access-control-allow-origin\": request.headers[\"origin\"] || \"*\",\n \"access-control-allow-credentials\": \"true\",\n vary: \"origin\"\n }\n };\n }\n\n private optionsResponse(request: IHttpRequest): IHttpResponse {\n return {\n statusCode: 204,\n headers: {\n \"access-control-allow-origin\": request.headers[\"origin\"] || \"*\",\n \"access-control-allow-credentials\": \"true\",\n \"access-control-allow-methods\": \"OPTIONS,POST,GET,DELETE,PUT,PATCH\",\n \"access-control-allow-headers\": ALLOWED_HEADERS,\n \"access-control-max-age\": \"86400\",\n \"cache-control\": \"public, max-age=86400\"\n }\n };\n }\n}\n\nexport const SecureHeadersDecorator = HttpRouter.createDecorator({\n decorator: SecureHeadersDecoratorImpl,\n dependencies: []\n});\n"],"names":["ALLOWED_HEADERS","SecureHeadersDecoratorImpl","decoratee","request","response","SecureHeadersDecorator","HttpRouter"],"mappings":";AAGA,MAAMA,kBAAkB;AASxB,MAAMC;IACF,YAAoBC,SAAsB,CAAE;aAAxBA,SAAS,GAATA;IAAyB;IAE7C,MAAM,MAAMC,OAAqB,EAA0B;QACvD,IAAIA,AAAmB,cAAnBA,QAAQ,MAAM,EACd,OAAO,IAAI,CAAC,eAAe,CAACA;QAGhC,MAAMC,WAAW,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,CAACD;QAE5C,OAAO;YACH,GAAGC,QAAQ;YACX,SAAS;gBACL,GAAGA,SAAS,OAAO;gBACnB,+BAA+BD,QAAQ,OAAO,CAAC,SAAS,IAAI;gBAC5D,oCAAoC;gBACpC,MAAM;YACV;QACJ;IACJ;IAEQ,gBAAgBA,OAAqB,EAAiB;QAC1D,OAAO;YACH,YAAY;YACZ,SAAS;gBACL,+BAA+BA,QAAQ,OAAO,CAAC,SAAS,IAAI;gBAC5D,oCAAoC;gBACpC,gCAAgC;gBAChC,gCAAgCH;gBAChC,0BAA0B;gBAC1B,iBAAiB;YACrB;QACJ;IACJ;AACJ;AAEO,MAAMK,yBAAyBC,WAAW,eAAe,CAAC;IAC7D,WAAWL;IACX,cAAc,EAAE;AACpB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./SecureHeadersDecorator.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./SecureHeadersDecorator.js";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createFeature } from "@webiny/feature/api";
|
|
2
|
+
import { HttpRouterImpl } from "./HttpRouter.js";
|
|
3
|
+
import { SecureHeadersDecorator } from "./decorators/SecureHeadersDecorator.js";
|
|
4
|
+
import { RequestContextInitializerDecorator } from "./decorators/RequestContextInitializerDecorator.js";
|
|
5
|
+
const HttpFeature = createFeature({
|
|
6
|
+
name: "Http",
|
|
7
|
+
register (container) {
|
|
8
|
+
container.register(HttpRouterImpl);
|
|
9
|
+
container.registerDecorator(RequestContextInitializerDecorator);
|
|
10
|
+
container.registerDecorator(SecureHeadersDecorator);
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
export { HttpFeature };
|
|
14
|
+
|
|
15
|
+
//# sourceMappingURL=feature.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features/http/feature.js","sources":["../../../src/features/http/feature.ts"],"sourcesContent":["import { createFeature } from \"@webiny/feature/api\";\nimport { HttpRouterImpl } from \"~/features/http/HttpRouter.js\";\nimport { SecureHeadersDecorator } from \"~/features/http/decorators/SecureHeadersDecorator.js\";\nimport { RequestContextInitializerDecorator } from \"~/features/http/decorators/RequestContextInitializerDecorator.js\";\n\nexport const HttpFeature = createFeature({\n name: \"Http\",\n register(container) {\n // Transient so per-request routes (e.g. GraphQLRoute) resolve from child container\n container.register(HttpRouterImpl);\n // Registered before SecureHeaders so SecureHeaders stays the OUTER decorator (it short-\n // circuits OPTIONS before we do the per-request initializer work). This runs the post-auth\n // RequestContextInitializers for every HTTP route, not just the GraphQL engine.\n container.registerDecorator(RequestContextInitializerDecorator);\n container.registerDecorator(SecureHeadersDecorator);\n }\n});\n"],"names":["HttpFeature","createFeature","container","HttpRouterImpl","RequestContextInitializerDecorator","SecureHeadersDecorator"],"mappings":";;;;AAKO,MAAMA,cAAcC,cAAc;IACrC,MAAM;IACN,UAASC,SAAS;QAEdA,UAAU,QAAQ,CAACC;QAInBD,UAAU,iBAAiB,CAACE;QAC5BF,UAAU,iBAAiB,CAACG;IAChC;AACJ"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { TestHttpEventHandler } from "./TestHttpEventHandler.js";
|
|
2
|
+
import type { EventContext } from "../../features/events/EventHandler.js";
|
|
3
|
+
import type { NextFunction } from "../../features/events/types.js";
|
|
4
|
+
import type { IHttpRouter } from "../../features/http/abstractions.js";
|
|
5
|
+
declare class HttpRouterHandlerImpl implements TestHttpEventHandler.Interface {
|
|
6
|
+
private router;
|
|
7
|
+
constructor(router: IHttpRouter);
|
|
8
|
+
execute(ctx: EventContext, _next: NextFunction): Promise<any>;
|
|
9
|
+
}
|
|
10
|
+
export declare const HttpRouterHandler: typeof HttpRouterHandlerImpl & {
|
|
11
|
+
__abstraction: import("@webiny/di").Abstraction<import("~/features/events/EventHandler.js").IEventHandler<any, any>>;
|
|
12
|
+
};
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { TestHttpEventHandler } from "./TestHttpEventHandler.js";
|
|
2
|
+
import { HttpRouter, RouteNotFoundError } from "../http/abstractions.js";
|
|
3
|
+
class HttpRouterHandlerImpl {
|
|
4
|
+
constructor(router){
|
|
5
|
+
this.router = router;
|
|
6
|
+
}
|
|
7
|
+
async execute(ctx, _next) {
|
|
8
|
+
try {
|
|
9
|
+
return await this.router.route(ctx.event);
|
|
10
|
+
} catch (e) {
|
|
11
|
+
if (e instanceof RouteNotFoundError) return {
|
|
12
|
+
statusCode: 404,
|
|
13
|
+
body: {
|
|
14
|
+
message: e.message
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
if (e && "object" == typeof e && e.code) return {
|
|
18
|
+
statusCode: 500,
|
|
19
|
+
body: {
|
|
20
|
+
message: e.message,
|
|
21
|
+
code: e.code,
|
|
22
|
+
data: e.data ?? null
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
return {
|
|
26
|
+
statusCode: 500,
|
|
27
|
+
body: {
|
|
28
|
+
message: "Internal server error"
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const HttpRouterHandler = TestHttpEventHandler.createImplementation({
|
|
35
|
+
implementation: HttpRouterHandlerImpl,
|
|
36
|
+
dependencies: [
|
|
37
|
+
HttpRouter
|
|
38
|
+
]
|
|
39
|
+
});
|
|
40
|
+
export { HttpRouterHandler };
|
|
41
|
+
|
|
42
|
+
//# sourceMappingURL=HttpRouterHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features/testing/HttpRouterHandler.js","sources":["../../../src/features/testing/HttpRouterHandler.ts"],"sourcesContent":["import { TestHttpEventHandler } from \"./TestHttpEventHandler.js\";\nimport { HttpRouter, RouteNotFoundError } from \"~/features/http/abstractions.js\";\nimport type { EventContext } from \"~/features/events/EventHandler.js\";\nimport type { NextFunction } from \"~/features/events/types.js\";\nimport type { IHttpRouter } from \"~/features/http/abstractions.js\";\n\nclass HttpRouterHandlerImpl implements TestHttpEventHandler.Interface {\n constructor(private router: IHttpRouter) {}\n\n async execute(ctx: EventContext, _next: NextFunction): Promise<any> {\n try {\n return await this.router.route(ctx.event);\n } catch (e) {\n if (e instanceof RouteNotFoundError) {\n return { statusCode: 404, body: { message: e.message } };\n }\n // Propagate structured errors (e.g. WebinyError with code/data) for test visibility\n if (e && typeof e === \"object\" && (e as any).code) {\n return {\n statusCode: 500,\n body: {\n message: (e as any).message,\n code: (e as any).code,\n data: (e as any).data ?? null\n }\n };\n }\n return { statusCode: 500, body: { message: \"Internal server error\" } };\n }\n }\n}\n\nexport const HttpRouterHandler = TestHttpEventHandler.createImplementation({\n implementation: HttpRouterHandlerImpl,\n dependencies: [HttpRouter]\n});\n"],"names":["HttpRouterHandlerImpl","router","ctx","_next","e","RouteNotFoundError","HttpRouterHandler","TestHttpEventHandler","HttpRouter"],"mappings":";;AAMA,MAAMA;IACF,YAAoBC,MAAmB,CAAE;aAArBA,MAAM,GAANA;IAAsB;IAE1C,MAAM,QAAQC,GAAiB,EAAEC,KAAmB,EAAgB;QAChE,IAAI;YACA,OAAO,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,CAACD,IAAI,KAAK;QAC5C,EAAE,OAAOE,GAAG;YACR,IAAIA,aAAaC,oBACb,OAAO;gBAAE,YAAY;gBAAK,MAAM;oBAAE,SAASD,EAAE,OAAO;gBAAC;YAAE;YAG3D,IAAIA,KAAK,AAAa,YAAb,OAAOA,KAAmBA,EAAU,IAAI,EAC7C,OAAO;gBACH,YAAY;gBACZ,MAAM;oBACF,SAAUA,EAAU,OAAO;oBAC3B,MAAOA,EAAU,IAAI;oBACrB,MAAOA,EAAU,IAAI,IAAI;gBAC7B;YACJ;YAEJ,OAAO;gBAAE,YAAY;gBAAK,MAAM;oBAAE,SAAS;gBAAwB;YAAE;QACzE;IACJ;AACJ;AAEO,MAAME,oBAAoBC,qBAAqB,oBAAoB,CAAC;IACvE,gBAAgBP;IAChB,cAAc;QAACQ;KAAW;AAC9B"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Abstraction } from "@webiny/di";
|
|
2
|
+
import type { IEventHandler } from "../../features/events/EventHandler.js";
|
|
3
|
+
export declare const TestHttpEventHandler: Abstraction<IEventHandler<any, any>>;
|
|
4
|
+
export declare namespace TestHttpEventHandler {
|
|
5
|
+
type Interface = IEventHandler;
|
|
6
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features/testing/TestHttpEventHandler.js","sources":["../../../src/features/testing/TestHttpEventHandler.ts"],"sourcesContent":["import { Abstraction } from \"@webiny/di\";\nimport type { IEventHandler } from \"~/features/events/EventHandler.js\";\n\nexport const TestHttpEventHandler = new Abstraction<IEventHandler>(\"TestHttpEventHandler\");\n\nexport namespace TestHttpEventHandler {\n export type Interface = IEventHandler;\n}\n"],"names":["TestHttpEventHandler","Abstraction"],"mappings":";AAGO,MAAMA,uBAAuB,IAAIC,YAA2B"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { IEventType } from "../../features/events/EventType.js";
|
|
2
|
+
import type { IHttpRequest } from "../../features/http/index.js";
|
|
3
|
+
export declare function isHttpRequest(event: any): event is IHttpRequest;
|
|
4
|
+
/**
|
|
5
|
+
* EventType that recognizes IHttpRequest directly — no transport translation needed.
|
|
6
|
+
* Use in tests to feed IHttpRequest straight into the chain, bypassing AWS/Node adapters.
|
|
7
|
+
*/
|
|
8
|
+
declare class TestHttpEventTypeImplementation implements IEventType<IHttpRequest> {
|
|
9
|
+
canHandle(event: any): event is IHttpRequest;
|
|
10
|
+
getHandlerAbstraction(): import("@webiny/di").Abstraction<import("../events/EventHandler.js").IEventHandler<any, any>>;
|
|
11
|
+
}
|
|
12
|
+
export declare const TestHttpEventType: typeof TestHttpEventTypeImplementation & {
|
|
13
|
+
__abstraction: import("@webiny/di").Abstraction<IEventType<any>>;
|
|
14
|
+
};
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { EventType } from "../events/EventType.js";
|
|
2
|
+
import { TestHttpEventHandler } from "./TestHttpEventHandler.js";
|
|
3
|
+
function isHttpRequest(event) {
|
|
4
|
+
return "object" == typeof event && null !== event && "string" == typeof event.method && "string" == typeof event.path && "object" == typeof event.headers && "object" == typeof event.query;
|
|
5
|
+
}
|
|
6
|
+
class TestHttpEventTypeImplementation {
|
|
7
|
+
canHandle(event) {
|
|
8
|
+
return isHttpRequest(event);
|
|
9
|
+
}
|
|
10
|
+
getHandlerAbstraction() {
|
|
11
|
+
return TestHttpEventHandler;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const TestHttpEventType = EventType.createImplementation({
|
|
15
|
+
implementation: TestHttpEventTypeImplementation,
|
|
16
|
+
dependencies: []
|
|
17
|
+
});
|
|
18
|
+
export { TestHttpEventType, isHttpRequest };
|
|
19
|
+
|
|
20
|
+
//# sourceMappingURL=TestHttpEventType.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features/testing/TestHttpEventType.js","sources":["../../../src/features/testing/TestHttpEventType.ts"],"sourcesContent":["import { EventType } from \"~/features/events/EventType.js\";\nimport type { IEventType } from \"~/features/events/EventType.js\";\nimport { TestHttpEventHandler } from \"./TestHttpEventHandler.js\";\nimport type { IHttpRequest } from \"~/features/http/index.js\";\n\nexport function isHttpRequest(event: any): event is IHttpRequest {\n return (\n typeof event === \"object\" &&\n event !== null &&\n typeof event.method === \"string\" &&\n typeof event.path === \"string\" &&\n typeof event.headers === \"object\" &&\n typeof event.query === \"object\"\n );\n}\n\n/**\n * EventType that recognizes IHttpRequest directly — no transport translation needed.\n * Use in tests to feed IHttpRequest straight into the chain, bypassing AWS/Node adapters.\n */\nclass TestHttpEventTypeImplementation implements IEventType<IHttpRequest> {\n canHandle(event: any): event is IHttpRequest {\n return isHttpRequest(event);\n }\n\n getHandlerAbstraction() {\n return TestHttpEventHandler;\n }\n}\nexport const TestHttpEventType = EventType.createImplementation({\n implementation: TestHttpEventTypeImplementation,\n dependencies: []\n});\n"],"names":["isHttpRequest","event","TestHttpEventTypeImplementation","TestHttpEventHandler","TestHttpEventType","EventType"],"mappings":";;AAKO,SAASA,cAAcC,KAAU;IACpC,OACI,AAAiB,YAAjB,OAAOA,SACPA,AAAU,SAAVA,SACA,AAAwB,YAAxB,OAAOA,MAAM,MAAM,IACnB,AAAsB,YAAtB,OAAOA,MAAM,IAAI,IACjB,AAAyB,YAAzB,OAAOA,MAAM,OAAO,IACpB,AAAuB,YAAvB,OAAOA,MAAM,KAAK;AAE1B;AAMA,MAAMC;IACF,UAAUD,KAAU,EAAyB;QACzC,OAAOD,cAAcC;IACzB;IAEA,wBAAwB;QACpB,OAAOE;IACX;AACJ;AACO,MAAMC,oBAAoBC,UAAU,oBAAoB,CAAC;IAC5D,gBAAgBH;IAChB,cAAc,EAAE;AACpB"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { HandlerSetup, IHttpRequest, IHttpResponse } from "../../index.js";
|
|
2
|
+
export interface createTestHttpHandlerOptions {
|
|
3
|
+
root: HandlerSetup;
|
|
4
|
+
request?: HandlerSetup;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Test handler that accepts IHttpRequest directly and returns IHttpResponse.
|
|
8
|
+
* Registers HttpRouterHandler as the base TestHttpEventHandler implementation.
|
|
9
|
+
* Callers layer middleware on top via container.registerDecorator() in options.root.
|
|
10
|
+
*/
|
|
11
|
+
export declare function createTestHttpHandler(options: createTestHttpHandlerOptions): (request: Partial<IHttpRequest> & {
|
|
12
|
+
method: string;
|
|
13
|
+
path: string;
|
|
14
|
+
}) => Promise<IHttpResponse>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { HttpFeature } from "../http/feature.js";
|
|
2
|
+
import { HttpRouterHandler } from "./HttpRouterHandler.js";
|
|
3
|
+
import { createHandler } from "../events/createHandler.js";
|
|
4
|
+
import { TestHttpEventType } from "./TestHttpEventType.js";
|
|
5
|
+
function createTestHttpHandler(options) {
|
|
6
|
+
const invoke = createHandler({
|
|
7
|
+
root: async (container)=>{
|
|
8
|
+
container.register(TestHttpEventType);
|
|
9
|
+
container.register(HttpRouterHandler);
|
|
10
|
+
HttpFeature.register(container);
|
|
11
|
+
await options.root(container);
|
|
12
|
+
},
|
|
13
|
+
request: options.request
|
|
14
|
+
});
|
|
15
|
+
return async (request)=>invoke({
|
|
16
|
+
method: request.method,
|
|
17
|
+
path: request.path,
|
|
18
|
+
headers: request.headers ?? {},
|
|
19
|
+
query: request.query ?? {},
|
|
20
|
+
pathParameters: request.pathParameters ?? {},
|
|
21
|
+
body: request.body
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
export { createTestHttpHandler };
|
|
25
|
+
|
|
26
|
+
//# sourceMappingURL=createTestHttpHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features/testing/createTestHttpHandler.js","sources":["../../../src/features/testing/createTestHttpHandler.ts"],"sourcesContent":["import { HttpFeature } from \"~/features/http/feature.js\";\nimport { HttpRouterHandler } from \"./HttpRouterHandler.js\";\nimport { createHandler } from \"~/features/events/createHandler.js\";\nimport { TestHttpEventType } from \"./TestHttpEventType.js\";\nimport type { HandlerSetup, IHttpRequest, IHttpResponse } from \"~/index.js\";\n\nexport interface createTestHttpHandlerOptions {\n root: HandlerSetup;\n request?: HandlerSetup;\n}\n\n/**\n * Test handler that accepts IHttpRequest directly and returns IHttpResponse.\n * Registers HttpRouterHandler as the base TestHttpEventHandler implementation.\n * Callers layer middleware on top via container.registerDecorator() in options.root.\n */\nexport function createTestHttpHandler(options: createTestHttpHandlerOptions) {\n const invoke = createHandler({\n root: async container => {\n container.register(TestHttpEventType);\n container.register(HttpRouterHandler);\n HttpFeature.register(container);\n await options.root(container);\n },\n request: options.request\n });\n\n return async (\n request: Partial<IHttpRequest> & { method: string; path: string }\n ): Promise<IHttpResponse> => {\n return invoke({\n method: request.method,\n path: request.path,\n headers: request.headers ?? {},\n query: request.query ?? {},\n pathParameters: request.pathParameters ?? {},\n body: request.body\n });\n };\n}\n"],"names":["createTestHttpHandler","options","invoke","createHandler","container","TestHttpEventType","HttpRouterHandler","HttpFeature","request"],"mappings":";;;;AAgBO,SAASA,sBAAsBC,OAAqC;IACvE,MAAMC,SAASC,cAAc;QACzB,MAAM,OAAMC;YACRA,UAAU,QAAQ,CAACC;YACnBD,UAAU,QAAQ,CAACE;YACnBC,YAAY,QAAQ,CAACH;YACrB,MAAMH,QAAQ,IAAI,CAACG;QACvB;QACA,SAASH,QAAQ,OAAO;IAC5B;IAEA,OAAO,OACHO,UAEON,OAAO;YACV,QAAQM,QAAQ,MAAM;YACtB,MAAMA,QAAQ,IAAI;YAClB,SAASA,QAAQ,OAAO,IAAI,CAAC;YAC7B,OAAOA,QAAQ,KAAK,IAAI,CAAC;YACzB,gBAAgBA,QAAQ,cAAc,IAAI,CAAC;YAC3C,MAAMA,QAAQ,IAAI;QACtB;AAER"}
|
package/index.d.ts
ADDED
package/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@webiny/event-handler-core",
|
|
3
|
+
"version": "0.0.0-unstable.0d717d18dd",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./index.js",
|
|
8
|
+
"./*": "./*"
|
|
9
|
+
},
|
|
10
|
+
"types": "index.d.ts",
|
|
11
|
+
"description": "Cloud-agnostic handler chain for building cloud functions with Dependency Injection.",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://github.com/webiny/webiny-js.git"
|
|
15
|
+
},
|
|
16
|
+
"contributors": [
|
|
17
|
+
"Webiny Team"
|
|
18
|
+
],
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@webiny/feature": "0.0.0-unstable.0d717d18dd"
|
|
22
|
+
},
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"@webiny/di": "1.0.2"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@webiny/build-tools": "0.0.0-unstable.0d717d18dd",
|
|
28
|
+
"@webiny/di": "1.0.2",
|
|
29
|
+
"rimraf": "6.1.3",
|
|
30
|
+
"typescript": "7.0.2",
|
|
31
|
+
"vitest": "4.1.10"
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"webiny": {
|
|
37
|
+
"publishFrom": "dist"
|
|
38
|
+
}
|
|
39
|
+
}
|