@webiny/event-handler-core 0.0.0-unstable.0d717d18dd → 6.6.0-alpha.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.
Files changed (72) hide show
  1. package/exports/api.d.ts +2 -0
  2. package/exports/api.js +1 -0
  3. package/features/events/ChildContainerFactory.d.ts +11 -0
  4. package/features/events/ChildContainerFactory.js +25 -0
  5. package/features/events/ChildContainerFactory.js.map +1 -0
  6. package/features/events/HandlerApp.d.ts +27 -0
  7. package/features/events/HandlerApp.js +53 -0
  8. package/features/events/HandlerApp.js.map +1 -0
  9. package/features/events/RootContainerFactory.d.ts +12 -0
  10. package/features/events/RootContainerFactory.js +24 -0
  11. package/features/events/RootContainerFactory.js.map +1 -0
  12. package/features/events/abstractions.d.ts +60 -0
  13. package/features/events/abstractions.js +7 -0
  14. package/features/events/abstractions.js.map +1 -0
  15. package/features/events/index.d.ts +4 -4
  16. package/features/events/index.js +4 -4
  17. package/features/http/HttpResponseBuilder.d.ts +36 -0
  18. package/features/http/HttpResponseBuilder.js +133 -0
  19. package/features/http/HttpResponseBuilder.js.map +1 -0
  20. package/features/http/HttpRouter.d.ts +17 -3
  21. package/features/http/HttpRouter.js +24 -17
  22. package/features/http/HttpRouter.js.map +1 -1
  23. package/features/http/HttpStreamBody.d.ts +34 -0
  24. package/features/http/HttpStreamBody.js +44 -0
  25. package/features/http/HttpStreamBody.js.map +1 -0
  26. package/features/http/abstractions.d.ts +154 -3
  27. package/features/http/abstractions.js +3 -2
  28. package/features/http/abstractions.js.map +1 -1
  29. package/features/http/createHttpRouteDefinition.d.ts +17 -0
  30. package/features/http/createHttpRouteDefinition.js +18 -0
  31. package/features/http/createHttpRouteDefinition.js.map +1 -0
  32. package/features/http/decorators/CompressionDecorator.d.ts +28 -0
  33. package/features/http/decorators/CompressionDecorator.js +66 -0
  34. package/features/http/decorators/CompressionDecorator.js.map +1 -0
  35. package/features/http/decorators/SecureHeadersDecorator.js +1 -1
  36. package/features/http/decorators/SecureHeadersDecorator.js.map +1 -1
  37. package/features/http/decorators/index.d.ts +1 -0
  38. package/features/http/decorators/index.js +1 -0
  39. package/features/http/feature.js +2 -2
  40. package/features/http/feature.js.map +1 -1
  41. package/features/http/index.d.ts +5 -0
  42. package/features/http/index.js +5 -0
  43. package/features/http/invokeHttpRoute.d.ts +17 -0
  44. package/features/http/invokeHttpRoute.js +32 -0
  45. package/features/http/invokeHttpRoute.js.map +1 -0
  46. package/features/http/toSseFrame.d.ts +10 -0
  47. package/features/http/toSseFrame.js +6 -0
  48. package/features/http/toSseFrame.js.map +1 -0
  49. package/features/testing/createTestHttpHandler.d.ts +1 -1
  50. package/features/testing/createTestHttpHandler.js +4 -4
  51. package/features/testing/createTestHttpHandler.js.map +1 -1
  52. package/features/testing/index.d.ts +1 -0
  53. package/features/testing/index.js +1 -0
  54. package/features/testing/registerHttpRouteInstance.d.ts +16 -0
  55. package/features/testing/registerHttpRouteInstance.js +27 -0
  56. package/features/testing/registerHttpRouteInstance.js.map +1 -0
  57. package/package.json +6 -6
  58. package/features/events/RequestContextInitializer.d.ts +0 -26
  59. package/features/events/RequestContextInitializer.js +0 -5
  60. package/features/events/RequestContextInitializer.js.map +0 -1
  61. package/features/events/RequestInitializer.d.ts +0 -22
  62. package/features/events/RequestInitializer.js +0 -5
  63. package/features/events/RequestInitializer.js.map +0 -1
  64. package/features/events/createHandler.d.ts +0 -20
  65. package/features/events/createHandler.js +0 -41
  66. package/features/events/createHandler.js.map +0 -1
  67. package/features/events/runRequestContextInitializers.d.ts +0 -20
  68. package/features/events/runRequestContextInitializers.js +0 -16
  69. package/features/events/runRequestContextInitializers.js.map +0 -1
  70. package/features/http/decorators/RequestContextInitializerDecorator.d.ts +0 -20
  71. package/features/http/decorators/RequestContextInitializerDecorator.js +0 -22
  72. package/features/http/decorators/RequestContextInitializerDecorator.js.map +0 -1
@@ -0,0 +1,2 @@
1
+ export { HttpRouteDefinition, HttpRouteHandler } from "../features/http/abstractions.js";
2
+ export type { IHttpRequest, IHttpResponse, IHttpResponseBuilder, IMatchedRouteDefinition } from "../features/http/abstractions.js";
package/exports/api.js ADDED
@@ -0,0 +1 @@
1
+ export { HttpRouteDefinition, HttpRouteHandler } from "../features/http/abstractions.js";
@@ -0,0 +1,11 @@
1
+ import { Container } from "@webiny/di";
2
+ import { ChildContainerFactory, HandlerConfig } from "./abstractions.js";
3
+ declare class ChildContainerFactoryImpl implements ChildContainerFactory.Interface {
4
+ private config;
5
+ constructor(config: HandlerConfig.Interface);
6
+ create(root: Container, rawArgs: any[]): Promise<Container>;
7
+ }
8
+ export declare const DefaultChildContainerFactory: typeof ChildContainerFactoryImpl & {
9
+ __abstraction: import("@webiny/di").Abstraction<import("./abstractions.js").IChildContainerFactory>;
10
+ };
11
+ export {};
@@ -0,0 +1,25 @@
1
+ import { ChildContainerFactory, HandlerConfig } from "./abstractions.js";
2
+ import { RequestContainer } from "./RequestContainer.js";
3
+ import { noopTransport } from "./Transport.js";
4
+ class ChildContainerFactoryImpl {
5
+ constructor(config){
6
+ this.config = config;
7
+ }
8
+ async create(root, rawArgs) {
9
+ const child = root.createChildContainer();
10
+ child.registerInstance(RequestContainer, child);
11
+ const transport = this.config.transport ?? noopTransport;
12
+ await transport.bind(child, ...rawArgs);
13
+ if (this.config.child) await this.config.child(child);
14
+ return child;
15
+ }
16
+ }
17
+ const DefaultChildContainerFactory = ChildContainerFactory.createImplementation({
18
+ implementation: ChildContainerFactoryImpl,
19
+ dependencies: [
20
+ HandlerConfig
21
+ ]
22
+ });
23
+ export { DefaultChildContainerFactory };
24
+
25
+ //# sourceMappingURL=ChildContainerFactory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"features/events/ChildContainerFactory.js","sources":["../../../src/features/events/ChildContainerFactory.ts"],"sourcesContent":["import { Container } from \"@webiny/di\";\nimport { ChildContainerFactory, HandlerConfig } from \"./abstractions.js\";\nimport { RequestContainer } from \"./RequestContainer.js\";\nimport { noopTransport } from \"./Transport.js\";\n\nclass ChildContainerFactoryImpl implements ChildContainerFactory.Interface {\n constructor(private config: HandlerConfig.Interface) {}\n\n async create(root: Container, rawArgs: any[]): Promise<Container> {\n const child = root.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 const transport = this.config.transport ?? noopTransport;\n await transport.bind(child, ...rawArgs);\n\n if (this.config.child) {\n await this.config.child(child);\n }\n\n return child;\n }\n}\n\nexport const DefaultChildContainerFactory = ChildContainerFactory.createImplementation({\n implementation: ChildContainerFactoryImpl,\n dependencies: [HandlerConfig]\n});\n"],"names":["ChildContainerFactoryImpl","config","root","rawArgs","child","RequestContainer","transport","noopTransport","DefaultChildContainerFactory","ChildContainerFactory","HandlerConfig"],"mappings":";;;AAKA,MAAMA;IACF,YAAoBC,MAA+B,CAAE;aAAjCA,MAAM,GAANA;IAAkC;IAEtD,MAAM,OAAOC,IAAe,EAAEC,OAAc,EAAsB;QAC9D,MAAMC,QAAQF,KAAK,oBAAoB;QACvCE,MAAM,gBAAgB,CAACC,kBAAkBD;QAIzC,MAAME,YAAY,IAAI,CAAC,MAAM,CAAC,SAAS,IAAIC;QAC3C,MAAMD,UAAU,IAAI,CAACF,UAAUD;QAE/B,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK,EACjB,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,CAACC;QAG5B,OAAOA;IACX;AACJ;AAEO,MAAMI,+BAA+BC,sBAAsB,oBAAoB,CAAC;IACnF,gBAAgBT;IAChB,cAAc;QAACU;KAAc;AACjC"}
@@ -0,0 +1,27 @@
1
+ import { Container } from "@webiny/di";
2
+ import { HandlerConfig } from "./abstractions.js";
3
+ /**
4
+ * The DI-native handler app. `HandlerApp.init(config)` builds a small "app container" (distinct
5
+ * from the per-process root container and the per-request child container it goes on to create),
6
+ * wires the default lifecycle abstractions, and returns an app whose `handle()` is the
7
+ * platform-invocable handler.
8
+ *
9
+ * The lifecycle is delegated to decoratable DI abstractions — {@link RootContainerFactory} (build
10
+ * the root once) and {@link ChildContainerFactory} (create + set up the per-request child) — so
11
+ * transports/composition layers extend it by decoration (`config.app`) instead of this class
12
+ * growing new branches. `HandlerApp` is distinct from {@link EventHandler}, which is a single
13
+ * handler IN the dispatch chain.
14
+ */
15
+ export declare class HandlerApp {
16
+ private rootContainerFactory;
17
+ private childContainerFactory;
18
+ private constructor();
19
+ static init(config: HandlerConfig.Interface): HandlerApp;
20
+ /**
21
+ * Build (once) and return the root container. Lets a transport that needs the root before the
22
+ * first request — e.g. the Node server attaching a WebSockets upgrade handler at startup — get
23
+ * it eagerly. The same memoized root is then reused by every `handle()` call.
24
+ */
25
+ getRootContainer(): Promise<Container>;
26
+ handle(...rawArgs: any[]): Promise<any>;
27
+ }
@@ -0,0 +1,53 @@
1
+ import { Container } from "@webiny/di";
2
+ import { EventType } from "./EventType.js";
3
+ import { ChildContainerFactory, HandlerConfig, RootContainerFactory } from "./abstractions.js";
4
+ import { DefaultRootContainerFactory } from "./RootContainerFactory.js";
5
+ import { DefaultChildContainerFactory } from "./ChildContainerFactory.js";
6
+ import { executeChain } from "./chain.js";
7
+ class HandlerApp {
8
+ constructor(rootContainerFactory, childContainerFactory){
9
+ this.rootContainerFactory = rootContainerFactory;
10
+ this.childContainerFactory = childContainerFactory;
11
+ }
12
+ static init(config) {
13
+ const appContainer = new Container();
14
+ appContainer.registerInstance(HandlerConfig, config);
15
+ appContainer.register(DefaultRootContainerFactory).inSingletonScope();
16
+ appContainer.register(DefaultChildContainerFactory).inSingletonScope();
17
+ config.app?.(appContainer);
18
+ return new HandlerApp(appContainer.resolve(RootContainerFactory), appContainer.resolve(ChildContainerFactory));
19
+ }
20
+ getRootContainer() {
21
+ return this.rootContainerFactory.get();
22
+ }
23
+ async handle(...rawArgs) {
24
+ const root = await this.rootContainerFactory.get();
25
+ const child = await this.childContainerFactory.create(root, rawArgs);
26
+ const event = rawArgs[0];
27
+ const eventTypes = child.resolveAll(EventType);
28
+ const matched = eventTypes.find((et)=>et.canHandle(event));
29
+ if (!matched) {
30
+ const shape = event && "object" == typeof event ? {
31
+ keys: Object.keys(event),
32
+ source: event.source,
33
+ detailType: event["detail-type"]
34
+ } : {
35
+ type: typeof event
36
+ };
37
+ const registered = eventTypes.map((et)=>{
38
+ try {
39
+ return String(et.getHandlerAbstraction());
40
+ } catch {
41
+ return "<unknown>";
42
+ }
43
+ });
44
+ throw new Error(`No event type matched the incoming event. Event shape: ${JSON.stringify(shape)}; registered event types: ${JSON.stringify(registered)}`);
45
+ }
46
+ const abstraction = matched.getHandlerAbstraction();
47
+ const handlers = child.resolveAll(abstraction);
48
+ return executeChain(handlers, event);
49
+ }
50
+ }
51
+ export { HandlerApp };
52
+
53
+ //# sourceMappingURL=HandlerApp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"features/events/HandlerApp.js","sources":["../../../src/features/events/HandlerApp.ts"],"sourcesContent":["import { Container } from \"@webiny/di\";\nimport { EventType } from \"./EventType.js\";\nimport { HandlerConfig, RootContainerFactory, ChildContainerFactory } from \"./abstractions.js\";\nimport { DefaultRootContainerFactory } from \"./RootContainerFactory.js\";\nimport { DefaultChildContainerFactory } from \"./ChildContainerFactory.js\";\nimport { executeChain } from \"./chain.js\";\n\n/**\n * The DI-native handler app. `HandlerApp.init(config)` builds a small \"app container\" (distinct\n * from the per-process root container and the per-request child container it goes on to create),\n * wires the default lifecycle abstractions, and returns an app whose `handle()` is the\n * platform-invocable handler.\n *\n * The lifecycle is delegated to decoratable DI abstractions — {@link RootContainerFactory} (build\n * the root once) and {@link ChildContainerFactory} (create + set up the per-request child) — so\n * transports/composition layers extend it by decoration (`config.app`) instead of this class\n * growing new branches. `HandlerApp` is distinct from {@link EventHandler}, which is a single\n * handler IN the dispatch chain.\n */\nexport class HandlerApp {\n private constructor(\n private rootContainerFactory: RootContainerFactory.Interface,\n private childContainerFactory: ChildContainerFactory.Interface\n ) {}\n\n static init(config: HandlerConfig.Interface): HandlerApp {\n const appContainer = new Container();\n\n // Register the config as-is — the default lifecycle factories resolve HandlerConfig directly.\n appContainer.registerInstance(HandlerConfig, config);\n\n // Register the default lifecycle abstractions. Singleton-scoped so the memoized root\n // container (held by RootContainerFactory) is shared across every warm invocation.\n appContainer.register(DefaultRootContainerFactory).inSingletonScope();\n appContainer.register(DefaultChildContainerFactory).inSingletonScope();\n\n // Seam: let callers decorate the factories before the app is resolved.\n config.app?.(appContainer);\n\n // Resolve the factories once (decorators applied) so their state — notably the memoized\n // root — is reused across every invocation of handle().\n return new HandlerApp(\n appContainer.resolve(RootContainerFactory),\n appContainer.resolve(ChildContainerFactory)\n );\n }\n\n /**\n * Build (once) and return the root container. Lets a transport that needs the root before the\n * first request — e.g. the Node server attaching a WebSockets upgrade handler at startup — get\n * it eagerly. The same memoized root is then reused by every `handle()` call.\n */\n getRootContainer(): Promise<Container> {\n return this.rootContainerFactory.get();\n }\n\n async handle(...rawArgs: any[]): Promise<any> {\n const root = await this.rootContainerFactory.get();\n const child = await this.childContainerFactory.create(root, rawArgs);\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 // Name each registered type by the handler abstraction it dispatches to, NOT by\n // `constructor.name`: the production bundle minifies class names, so every entry came\n // back as \"implementation\" — useless in exactly the place this message matters. An\n // abstraction's name is a string literal, so it survives bundling.\n const registered = eventTypes.map(et => {\n try {\n return String(et.getHandlerAbstraction());\n } catch {\n return \"<unknown>\";\n }\n });\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":["HandlerApp","rootContainerFactory","childContainerFactory","config","appContainer","Container","HandlerConfig","DefaultRootContainerFactory","DefaultChildContainerFactory","RootContainerFactory","ChildContainerFactory","rawArgs","root","child","event","eventTypes","EventType","matched","et","shape","Object","registered","String","Error","JSON","abstraction","handlers","executeChain"],"mappings":";;;;;;AAmBO,MAAMA;IACT,YACYC,oBAAoD,EACpDC,qBAAsD,CAChE;aAFUD,oBAAoB,GAApBA;aACAC,qBAAqB,GAArBA;IACT;IAEH,OAAO,KAAKC,MAA+B,EAAc;QACrD,MAAMC,eAAe,IAAIC;QAGzBD,aAAa,gBAAgB,CAACE,eAAeH;QAI7CC,aAAa,QAAQ,CAACG,6BAA6B,gBAAgB;QACnEH,aAAa,QAAQ,CAACI,8BAA8B,gBAAgB;QAGpEL,OAAO,GAAG,GAAGC;QAIb,OAAO,IAAIJ,WACPI,aAAa,OAAO,CAACK,uBACrBL,aAAa,OAAO,CAACM;IAE7B;IAOA,mBAAuC;QACnC,OAAO,IAAI,CAAC,oBAAoB,CAAC,GAAG;IACxC;IAEA,MAAM,OAAO,GAAGC,OAAc,EAAgB;QAC1C,MAAMC,OAAO,MAAM,IAAI,CAAC,oBAAoB,CAAC,GAAG;QAChD,MAAMC,QAAQ,MAAM,IAAI,CAAC,qBAAqB,CAAC,MAAM,CAACD,MAAMD;QAG5D,MAAMG,QAAQH,OAAO,CAAC,EAAE;QACxB,MAAMI,aAAaF,MAAM,UAAU,CAACG;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;YAK/B,MAAMO,aAAaN,WAAW,GAAG,CAACG,CAAAA;gBAC9B,IAAI;oBACA,OAAOI,OAAOJ,GAAG,qBAAqB;gBAC1C,EAAE,OAAM;oBACJ,OAAO;gBACX;YACJ;YACA,MAAM,IAAIK,MACN,CAAC,uDAAuD,EAAEC,KAAK,SAAS,CACpEL,OACF,0BAA0B,EAAEK,KAAK,SAAS,CAACH,aAAa;QAElE;QAEA,MAAMI,cAAcR,QAAQ,qBAAqB;QACjD,MAAMS,WAAWb,MAAM,UAAU,CAACY;QAElC,OAAOE,aAAaD,UAAUZ;IAClC;AACJ"}
@@ -0,0 +1,12 @@
1
+ import { Container } from "@webiny/di";
2
+ import { HandlerConfig, RootContainerFactory } from "./abstractions.js";
3
+ declare class RootContainerFactoryImpl implements RootContainerFactory.Interface {
4
+ private config;
5
+ private rootContainer;
6
+ constructor(config: HandlerConfig.Interface);
7
+ get(): Promise<Container>;
8
+ }
9
+ export declare const DefaultRootContainerFactory: typeof RootContainerFactoryImpl & {
10
+ __abstraction: import("@webiny/di").Abstraction<import("./abstractions.js").IRootContainerFactory>;
11
+ };
12
+ export {};
@@ -0,0 +1,24 @@
1
+ import { Container } from "@webiny/di";
2
+ import { HandlerConfig, RootContainerFactory } from "./abstractions.js";
3
+ class RootContainerFactoryImpl {
4
+ constructor(config){
5
+ this.config = config;
6
+ this.rootContainer = null;
7
+ }
8
+ async get() {
9
+ if (!this.rootContainer) {
10
+ this.rootContainer = new Container();
11
+ await this.config.root(this.rootContainer);
12
+ }
13
+ return this.rootContainer;
14
+ }
15
+ }
16
+ const DefaultRootContainerFactory = RootContainerFactory.createImplementation({
17
+ implementation: RootContainerFactoryImpl,
18
+ dependencies: [
19
+ HandlerConfig
20
+ ]
21
+ });
22
+ export { DefaultRootContainerFactory };
23
+
24
+ //# sourceMappingURL=RootContainerFactory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"features/events/RootContainerFactory.js","sources":["../../../src/features/events/RootContainerFactory.ts"],"sourcesContent":["import { Container } from \"@webiny/di\";\nimport { HandlerConfig, RootContainerFactory } from \"./abstractions.js\";\n\nclass RootContainerFactoryImpl implements RootContainerFactory.Interface {\n private rootContainer: Container | null = null;\n\n constructor(private config: HandlerConfig.Interface) {}\n\n async get(): Promise<Container> {\n if (!this.rootContainer) {\n this.rootContainer = new Container();\n await this.config.root(this.rootContainer);\n }\n return this.rootContainer;\n }\n}\n\nexport const DefaultRootContainerFactory = RootContainerFactory.createImplementation({\n implementation: RootContainerFactoryImpl,\n dependencies: [HandlerConfig]\n});\n"],"names":["RootContainerFactoryImpl","config","Container","DefaultRootContainerFactory","RootContainerFactory","HandlerConfig"],"mappings":";;AAGA,MAAMA;IAGF,YAAoBC,MAA+B,CAAE;aAAjCA,MAAM,GAANA;aAFZ,aAAa,GAAqB;IAEY;IAEtD,MAAM,MAA0B;QAC5B,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE;YACrB,IAAI,CAAC,aAAa,GAAG,IAAIC;YACzB,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa;QAC7C;QACA,OAAO,IAAI,CAAC,aAAa;IAC7B;AACJ;AAEO,MAAMC,8BAA8BC,qBAAqB,oBAAoB,CAAC;IACjF,gBAAgBJ;IAChB,cAAc;QAACK;KAAc;AACjC"}
@@ -0,0 +1,60 @@
1
+ import { Abstraction, Container } from "@webiny/di";
2
+ import type { Transport } from "./Transport.js";
3
+ import type { HandlerSetup } from "./types.js";
4
+ /**
5
+ * Configuration for `HandlerApp.init` — and the DI value the default lifecycle abstractions
6
+ * ({@link RootContainerFactory} / {@link ChildContainerFactory}) resolve. The object passed to
7
+ * `init` is registered as-is under this abstraction (no remapping), so the config the caller writes
8
+ * is exactly the config the factories read.
9
+ */
10
+ export interface IHandlerConfig {
11
+ /** Set up the ROOT container (once per process). */
12
+ root: HandlerSetup;
13
+ /** Set up the per-request CHILD container. */
14
+ child?: HandlerSetup;
15
+ /**
16
+ * Transport-specific extract step: binds the raw platform arguments (e.g. the AWS Lambda
17
+ * event + context) into the per-request container. Defaults to a no-op, which leaves the
18
+ * event to pass straight through — the plain server/HTTP behavior.
19
+ */
20
+ transport?: Transport;
21
+ /**
22
+ * Decorate the DI-native handler app before its first use. Runs against the APP container (the
23
+ * small container holding the lifecycle abstractions), so callers can `registerDecorator(...)`
24
+ * around any lifecycle step — e.g. wrapping `ChildContainerFactory` to refresh a license before
25
+ * each request.
26
+ */
27
+ app?: (container: Container) => void;
28
+ }
29
+ export declare const HandlerConfig: Abstraction<IHandlerConfig>;
30
+ export declare namespace HandlerConfig {
31
+ type Interface = IHandlerConfig;
32
+ }
33
+ /**
34
+ * Builds the ROOT container once per process and reuses it across warm invocations. Decoratable —
35
+ * wrap it to run process-lifetime setup around the root build. `get()` is idempotent — the root is
36
+ * built (and `config.root` run) on the first call and the same instance is returned thereafter, so a
37
+ * transport can call it eagerly (see `HandlerApp.getRootContainer`) without rebuilding.
38
+ */
39
+ export interface IRootContainerFactory {
40
+ get(): Promise<Container>;
41
+ }
42
+ export declare const RootContainerFactory: Abstraction<IRootContainerFactory>;
43
+ export declare namespace RootContainerFactory {
44
+ type Interface = IRootContainerFactory;
45
+ }
46
+ /**
47
+ * Creates and sets up the per-request (child) container: spawns the child, binds transport
48
+ * primitives, and runs request setup.
49
+ *
50
+ * Decoratable — this is the seam for per-request work that must run BEFORE the register/dispatch
51
+ * flow (e.g. refreshing a project-level license so register-time checks see it). Since such work
52
+ * typically needs only root-scoped state, a decorator can act before delegating to `create()`.
53
+ */
54
+ export interface IChildContainerFactory {
55
+ create(root: Container, rawArgs: any[]): Promise<Container>;
56
+ }
57
+ export declare const ChildContainerFactory: Abstraction<IChildContainerFactory>;
58
+ export declare namespace ChildContainerFactory {
59
+ type Interface = IChildContainerFactory;
60
+ }
@@ -0,0 +1,7 @@
1
+ import { Abstraction } from "@webiny/di";
2
+ const HandlerConfig = new Abstraction("HandlerConfig");
3
+ const RootContainerFactory = new Abstraction("RootContainerFactory");
4
+ const ChildContainerFactory = new Abstraction("ChildContainerFactory");
5
+ export { ChildContainerFactory, HandlerConfig, RootContainerFactory };
6
+
7
+ //# sourceMappingURL=abstractions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"features/events/abstractions.js","sources":["../../../src/features/events/abstractions.ts"],"sourcesContent":["import { Abstraction, Container } from \"@webiny/di\";\nimport type { Transport } from \"./Transport.js\";\nimport type { HandlerSetup } from \"./types.js\";\n\n/**\n * Configuration for `HandlerApp.init` — and the DI value the default lifecycle abstractions\n * ({@link RootContainerFactory} / {@link ChildContainerFactory}) resolve. The object passed to\n * `init` is registered as-is under this abstraction (no remapping), so the config the caller writes\n * is exactly the config the factories read.\n */\nexport interface IHandlerConfig {\n /** Set up the ROOT container (once per process). */\n root: HandlerSetup;\n /** Set up the per-request CHILD container. */\n child?: 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 * Decorate the DI-native handler app before its first use. Runs against the APP container (the\n * small container holding the lifecycle abstractions), so callers can `registerDecorator(...)`\n * around any lifecycle step — e.g. wrapping `ChildContainerFactory` to refresh a license before\n * each request.\n */\n app?: (container: Container) => void;\n}\n\nexport const HandlerConfig = new Abstraction<IHandlerConfig>(\"HandlerConfig\");\n\nexport namespace HandlerConfig {\n export type Interface = IHandlerConfig;\n}\n\n/**\n * Builds the ROOT container once per process and reuses it across warm invocations. Decoratable —\n * wrap it to run process-lifetime setup around the root build. `get()` is idempotent — the root is\n * built (and `config.root` run) on the first call and the same instance is returned thereafter, so a\n * transport can call it eagerly (see `HandlerApp.getRootContainer`) without rebuilding.\n */\nexport interface IRootContainerFactory {\n get(): Promise<Container>;\n}\n\nexport const RootContainerFactory = new Abstraction<IRootContainerFactory>(\"RootContainerFactory\");\n\nexport namespace RootContainerFactory {\n export type Interface = IRootContainerFactory;\n}\n\n/**\n * Creates and sets up the per-request (child) container: spawns the child, binds transport\n * primitives, and runs request setup.\n *\n * Decoratable — this is the seam for per-request work that must run BEFORE the register/dispatch\n * flow (e.g. refreshing a project-level license so register-time checks see it). Since such work\n * typically needs only root-scoped state, a decorator can act before delegating to `create()`.\n */\nexport interface IChildContainerFactory {\n create(root: Container, rawArgs: any[]): Promise<Container>;\n}\n\nexport const ChildContainerFactory = new Abstraction<IChildContainerFactory>(\n \"ChildContainerFactory\"\n);\n\nexport namespace ChildContainerFactory {\n export type Interface = IChildContainerFactory;\n}\n"],"names":["HandlerConfig","Abstraction","RootContainerFactory","ChildContainerFactory"],"mappings":";AA8BO,MAAMA,gBAAgB,IAAIC,YAA4B;AAgBtD,MAAMC,uBAAuB,IAAID,YAAmC;AAkBpE,MAAME,wBAAwB,IAAIF,YACrC"}
@@ -1,10 +1,10 @@
1
1
  export * from "./EventHandler.js";
2
2
  export * from "./EventType.js";
3
3
  export * from "./RequestContainer.js";
4
- export * from "./RequestContextInitializer.js";
5
- export * from "./RequestInitializer.js";
6
4
  export * from "./chain.js";
7
- export * from "./createHandler.js";
5
+ export * from "./abstractions.js";
6
+ export * from "./HandlerApp.js";
7
+ export * from "./RootContainerFactory.js";
8
+ export * from "./ChildContainerFactory.js";
8
9
  export * from "./Transport.js";
9
10
  export * from "./types.js";
10
- export * from "./runRequestContextInitializers.js";
@@ -1,10 +1,10 @@
1
1
  export * from "./EventHandler.js";
2
2
  export * from "./EventType.js";
3
3
  export * from "./RequestContainer.js";
4
- export * from "./RequestContextInitializer.js";
5
- export * from "./RequestInitializer.js";
6
4
  export * from "./chain.js";
7
- export * from "./createHandler.js";
5
+ export * from "./abstractions.js";
6
+ export * from "./HandlerApp.js";
7
+ export * from "./RootContainerFactory.js";
8
+ export * from "./ChildContainerFactory.js";
8
9
  export * from "./Transport.js";
9
10
  export * from "./types.js";
10
- export * from "./runRequestContextInitializers.js";
@@ -0,0 +1,36 @@
1
+ import type { CookieOptions, IHttpResponse, IHttpResponseBuilder } from "./abstractions.js";
2
+ import type { HttpStreamSource } from "./HttpStreamBody.js";
3
+ /**
4
+ * Serialize a single `Set-Cookie` value. Kept dependency-free (no `cookie` package) — the attribute
5
+ * set below is everything the framework needs.
6
+ */
7
+ export declare function serializeCookie(name: string, value: string, options?: CookieOptions): string;
8
+ export declare class HttpResponseBuilder implements IHttpResponseBuilder {
9
+ private statusCodeValue;
10
+ private headerValues;
11
+ private cookieValues;
12
+ private bodyValue;
13
+ private modified;
14
+ status(statusCode: number): this;
15
+ header(name: string, value: string): this;
16
+ setHeaders(headers: Record<string, string>): this;
17
+ getHeader(name: string): string | undefined;
18
+ removeHeader(name: string): this;
19
+ type(contentType: string): this;
20
+ cookie(name: string, value: string, options?: CookieOptions): this;
21
+ clearCookie(name: string, options?: Omit<CookieOptions, "expires" | "maxAge">): this;
22
+ json(body?: any): this;
23
+ send(body?: any): this;
24
+ text(body: string): this;
25
+ html(body: string): this;
26
+ redirect(url: string, statusCode?: number): this;
27
+ end(body?: any): this;
28
+ sse(source: HttpStreamSource): this;
29
+ /**
30
+ * Whether anything was set on this builder. The router uses it to decide if builder state needs
31
+ * merging into a plain IHttpResponse a route returned instead.
32
+ */
33
+ isModified(): boolean;
34
+ toResponse(): IHttpResponse;
35
+ }
36
+ export declare function createHttpResponseBuilder(): HttpResponseBuilder;
@@ -0,0 +1,133 @@
1
+ import { HttpStreamBody } from "./HttpStreamBody.js";
2
+ const CONTENT_TYPE = "content-type";
3
+ const TYPE_SHORTHANDS = {
4
+ json: "application/json",
5
+ text: "text/plain; charset=utf-8",
6
+ html: "text/html; charset=utf-8",
7
+ xml: "application/xml",
8
+ bin: "application/octet-stream"
9
+ };
10
+ function serializeCookie(name, value, options = {}) {
11
+ const parts = [
12
+ `${name}=${encodeURIComponent(value)}`
13
+ ];
14
+ if (void 0 !== options.maxAge) parts.push(`Max-Age=${Math.floor(options.maxAge)}`);
15
+ if (options.domain) parts.push(`Domain=${options.domain}`);
16
+ parts.push(`Path=${options.path ?? "/"}`);
17
+ if (options.expires) parts.push(`Expires=${options.expires.toUTCString()}`);
18
+ if (options.httpOnly) parts.push("HttpOnly");
19
+ if (options.secure) parts.push("Secure");
20
+ if (options.partitioned) parts.push("Partitioned");
21
+ if (options.sameSite) {
22
+ const sameSite = options.sameSite;
23
+ parts.push(`SameSite=${sameSite.charAt(0).toUpperCase()}${sameSite.slice(1)}`);
24
+ }
25
+ return parts.join("; ");
26
+ }
27
+ class HttpResponseBuilder {
28
+ status(statusCode) {
29
+ this.statusCodeValue = statusCode;
30
+ this.modified = true;
31
+ return this;
32
+ }
33
+ header(name, value) {
34
+ this.headerValues[name.toLowerCase()] = value;
35
+ this.modified = true;
36
+ return this;
37
+ }
38
+ setHeaders(headers) {
39
+ for (const [name, value] of Object.entries(headers))this.header(name, value);
40
+ return this;
41
+ }
42
+ getHeader(name) {
43
+ return this.headerValues[name.toLowerCase()];
44
+ }
45
+ removeHeader(name) {
46
+ delete this.headerValues[name.toLowerCase()];
47
+ this.modified = true;
48
+ return this;
49
+ }
50
+ type(contentType) {
51
+ return this.header(CONTENT_TYPE, TYPE_SHORTHANDS[contentType] ?? contentType);
52
+ }
53
+ cookie(name, value, options) {
54
+ this.cookieValues.push(serializeCookie(name, value, options));
55
+ this.modified = true;
56
+ return this;
57
+ }
58
+ clearCookie(name, options) {
59
+ return this.cookie(name, "", {
60
+ ...options,
61
+ expires: new Date(0),
62
+ maxAge: 0
63
+ });
64
+ }
65
+ json(body) {
66
+ if (void 0 === this.getHeader(CONTENT_TYPE)) this.type("json");
67
+ this.bodyValue = void 0 === body ? void 0 : JSON.stringify(body);
68
+ this.modified = true;
69
+ return this;
70
+ }
71
+ send(body) {
72
+ if (null == body) return this.end(body);
73
+ if ("string" == typeof body) {
74
+ if (void 0 === this.getHeader(CONTENT_TYPE)) this.type("html");
75
+ return this.end(body);
76
+ }
77
+ if (Buffer.isBuffer(body) || body instanceof Uint8Array) {
78
+ if (void 0 === this.getHeader(CONTENT_TYPE)) this.type("bin");
79
+ return this.end(body);
80
+ }
81
+ return this.json(body);
82
+ }
83
+ text(body) {
84
+ return this.type("text").end(body);
85
+ }
86
+ html(body) {
87
+ return this.type("html").end(body);
88
+ }
89
+ redirect(url, statusCode = 302) {
90
+ return this.status(statusCode).header("location", url).end();
91
+ }
92
+ end(body) {
93
+ this.bodyValue = body;
94
+ this.modified = true;
95
+ return this;
96
+ }
97
+ sse(source) {
98
+ return this.setHeaders({
99
+ "content-type": "text/event-stream",
100
+ "cache-control": "no-cache, no-transform",
101
+ connection: "keep-alive",
102
+ "x-accel-buffering": "no"
103
+ }).end(new HttpStreamBody(source));
104
+ }
105
+ isModified() {
106
+ return this.modified;
107
+ }
108
+ toResponse() {
109
+ const response = {
110
+ statusCode: this.statusCodeValue
111
+ };
112
+ if (Object.keys(this.headerValues).length > 0) response.headers = {
113
+ ...this.headerValues
114
+ };
115
+ if (this.cookieValues.length > 0) response.cookies = [
116
+ ...this.cookieValues
117
+ ];
118
+ if (void 0 !== this.bodyValue) response.body = this.bodyValue;
119
+ return response;
120
+ }
121
+ constructor(){
122
+ this.statusCodeValue = 200;
123
+ this.headerValues = {};
124
+ this.cookieValues = [];
125
+ this.modified = false;
126
+ }
127
+ }
128
+ function createHttpResponseBuilder() {
129
+ return new HttpResponseBuilder();
130
+ }
131
+ export { HttpResponseBuilder, createHttpResponseBuilder, serializeCookie };
132
+
133
+ //# sourceMappingURL=HttpResponseBuilder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"features/http/HttpResponseBuilder.js","sources":["../../../src/features/http/HttpResponseBuilder.ts"],"sourcesContent":["import type { CookieOptions, IHttpResponse, IHttpResponseBuilder } from \"./abstractions.js\";\nimport { HttpStreamBody } from \"./HttpStreamBody.js\";\nimport type { HttpStreamSource } from \"./HttpStreamBody.js\";\n\nconst CONTENT_TYPE = \"content-type\";\n\nconst TYPE_SHORTHANDS: Record<string, string> = {\n json: \"application/json\",\n text: \"text/plain; charset=utf-8\",\n html: \"text/html; charset=utf-8\",\n xml: \"application/xml\",\n bin: \"application/octet-stream\"\n};\n\n/**\n * Serialize a single `Set-Cookie` value. Kept dependency-free (no `cookie` package) — the attribute\n * set below is everything the framework needs.\n */\nexport function serializeCookie(name: string, value: string, options: CookieOptions = {}): string {\n const parts = [`${name}=${encodeURIComponent(value)}`];\n\n if (options.maxAge !== undefined) {\n parts.push(`Max-Age=${Math.floor(options.maxAge)}`);\n }\n if (options.domain) {\n parts.push(`Domain=${options.domain}`);\n }\n // Default to \"/\" so a cookie set on e.g. POST /graphql isn't scoped to that one path.\n parts.push(`Path=${options.path ?? \"/\"}`);\n if (options.expires) {\n parts.push(`Expires=${options.expires.toUTCString()}`);\n }\n if (options.httpOnly) {\n parts.push(\"HttpOnly\");\n }\n if (options.secure) {\n parts.push(\"Secure\");\n }\n if (options.partitioned) {\n parts.push(\"Partitioned\");\n }\n if (options.sameSite) {\n const sameSite = options.sameSite;\n parts.push(`SameSite=${sameSite.charAt(0).toUpperCase()}${sameSite.slice(1)}`);\n }\n\n return parts.join(\"; \");\n}\n\nexport class HttpResponseBuilder implements IHttpResponseBuilder {\n private statusCodeValue = 200;\n private headerValues: Record<string, string> = {};\n private cookieValues: string[] = [];\n private bodyValue: any;\n private modified = false;\n\n status(statusCode: number): this {\n this.statusCodeValue = statusCode;\n this.modified = true;\n return this;\n }\n\n header(name: string, value: string): this {\n this.headerValues[name.toLowerCase()] = value;\n this.modified = true;\n return this;\n }\n\n setHeaders(headers: Record<string, string>): this {\n for (const [name, value] of Object.entries(headers)) {\n this.header(name, value);\n }\n return this;\n }\n\n getHeader(name: string): string | undefined {\n return this.headerValues[name.toLowerCase()];\n }\n\n removeHeader(name: string): this {\n delete this.headerValues[name.toLowerCase()];\n this.modified = true;\n return this;\n }\n\n type(contentType: string): this {\n return this.header(CONTENT_TYPE, TYPE_SHORTHANDS[contentType] ?? contentType);\n }\n\n cookie(name: string, value: string, options?: CookieOptions): this {\n this.cookieValues.push(serializeCookie(name, value, options));\n this.modified = true;\n return this;\n }\n\n clearCookie(name: string, options?: Omit<CookieOptions, \"expires\" | \"maxAge\">): this {\n return this.cookie(name, \"\", {\n ...options,\n expires: new Date(0),\n maxAge: 0\n });\n }\n\n json(body?: any): this {\n if (this.getHeader(CONTENT_TYPE) === undefined) {\n this.type(\"json\");\n }\n // Serialize here rather than leaving an object for the transport, so the body a route\n // produces is the exact bytes that go out regardless of transport.\n this.bodyValue = body === undefined ? undefined : JSON.stringify(body);\n this.modified = true;\n return this;\n }\n\n send(body?: any): this {\n if (body === undefined || body === null) {\n return this.end(body);\n }\n if (typeof body === \"string\") {\n if (this.getHeader(CONTENT_TYPE) === undefined) {\n this.type(\"html\");\n }\n return this.end(body);\n }\n if (Buffer.isBuffer(body) || body instanceof Uint8Array) {\n if (this.getHeader(CONTENT_TYPE) === undefined) {\n this.type(\"bin\");\n }\n return this.end(body);\n }\n return this.json(body);\n }\n\n text(body: string): this {\n return this.type(\"text\").end(body);\n }\n\n html(body: string): this {\n return this.type(\"html\").end(body);\n }\n\n redirect(url: string, statusCode = 302): this {\n return this.status(statusCode).header(\"location\", url).end();\n }\n\n end(body?: any): this {\n this.bodyValue = body;\n this.modified = true;\n return this;\n }\n\n sse(source: HttpStreamSource): this {\n // `no-transform` and `x-accel-buffering: no` are the two that silently matter: without them a\n // proxy compresses or buffers the body and the response stops being incremental while still\n // looking correct. See the interface docs.\n return this.setHeaders({\n \"content-type\": \"text/event-stream\",\n \"cache-control\": \"no-cache, no-transform\",\n connection: \"keep-alive\",\n \"x-accel-buffering\": \"no\"\n }).end(new HttpStreamBody(source));\n }\n\n /**\n * Whether anything was set on this builder. The router uses it to decide if builder state needs\n * merging into a plain IHttpResponse a route returned instead.\n */\n isModified(): boolean {\n return this.modified;\n }\n\n toResponse(): IHttpResponse {\n const response: IHttpResponse = { statusCode: this.statusCodeValue };\n if (Object.keys(this.headerValues).length > 0) {\n response.headers = { ...this.headerValues };\n }\n if (this.cookieValues.length > 0) {\n response.cookies = [...this.cookieValues];\n }\n if (this.bodyValue !== undefined) {\n response.body = this.bodyValue;\n }\n return response;\n }\n}\n\nexport function createHttpResponseBuilder(): HttpResponseBuilder {\n return new HttpResponseBuilder();\n}\n"],"names":["CONTENT_TYPE","TYPE_SHORTHANDS","serializeCookie","name","value","options","parts","encodeURIComponent","undefined","Math","sameSite","HttpResponseBuilder","statusCode","headers","Object","contentType","Date","body","JSON","Buffer","Uint8Array","url","source","HttpStreamBody","response","createHttpResponseBuilder"],"mappings":"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"}
@@ -1,8 +1,22 @@
1
+ import type { Container } from "@webiny/di";
1
2
  import { HttpRouter } from "../../features/http/abstractions.js";
2
- import type { IHttpRoute, IHttpRequest, IHttpResponse } from "../../features/http/abstractions.js";
3
+ import type { IHttpRequest, IHttpResponse } from "../../features/http/abstractions.js";
3
4
  declare class HttpRouterImplClass implements HttpRouter.Interface {
4
- private routes;
5
- constructor(routes: IHttpRoute[]);
5
+ private container;
6
+ /**
7
+ * Takes the container so it can build the matched route — and ONLY the matched route.
8
+ *
9
+ * A definition declares no dependencies, so resolving all of them to match a path is a handful
10
+ * of field assignments. Routes used to be resolved as instances just to read their `path`,
11
+ * which built every one of their dependency graphs on every request: a static-asset request
12
+ * constructed the whole GraphQL engine, every contextual schema and the AI provider before
13
+ * discovering it wanted none of them.
14
+ *
15
+ * `resolveImplementation` asks for exactly the matched route's class: it reads that route's
16
+ * dependencies from its own metadata, resolves them here, and applies decorators registered
17
+ * for `HttpRouteHandler`, so a route stays decoratable.
18
+ */
19
+ constructor(container: Container);
6
20
  route(request: IHttpRequest): Promise<IHttpResponse>;
7
21
  private match;
8
22
  }
@@ -1,4 +1,7 @@
1
- import { HttpRoute, HttpRouter, RouteNotFoundError } from "./abstractions.js";
1
+ import { HttpRouteDefinition, HttpRouter, RouteNotFoundError } from "./abstractions.js";
2
+ import { HttpResponseBuilder } from "./HttpResponseBuilder.js";
3
+ import { toHttpResponse } from "./invokeHttpRoute.js";
4
+ import { RequestContainer } from "../events/RequestContainer.js";
2
5
  function matchPath(pattern, path) {
3
6
  if (pattern.endsWith("/*")) {
4
7
  const prefix = pattern.slice(0, -2);
@@ -17,33 +20,37 @@ function matchPath(pattern, path) {
17
20
  return params;
18
21
  }
19
22
  class HttpRouterImplClass {
20
- constructor(routes){
21
- this.routes = routes;
23
+ constructor(container){
24
+ this.container = container;
22
25
  }
23
26
  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
+ for (const definition of this.container.resolveAll(HttpRouteDefinition)){
28
+ const params = this.match(definition, request);
29
+ if (null === params) continue;
30
+ const route = this.container.resolveImplementation(definition.handler);
31
+ const response = new HttpResponseBuilder();
32
+ const result = await route.handle({
27
33
  ...request,
28
- pathParameters: params
29
- });
34
+ pathParameters: params,
35
+ route: {
36
+ name: definition.name,
37
+ method: definition.method,
38
+ path: definition.path
39
+ }
40
+ }, response);
41
+ return toHttpResponse(result, response);
30
42
  }
31
43
  throw new RouteNotFoundError(request.method, request.path);
32
44
  }
33
- match(route, request) {
34
- if (route.method !== request.method) return null;
35
- return matchPath(route.path, request.path);
45
+ match(definition, request) {
46
+ if (definition.method !== request.method) return null;
47
+ return matchPath(definition.path, request.path);
36
48
  }
37
49
  }
38
50
  const HttpRouterImpl = HttpRouter.createImplementation({
39
51
  implementation: HttpRouterImplClass,
40
52
  dependencies: [
41
- [
42
- HttpRoute,
43
- {
44
- multiple: true
45
- }
46
- ]
53
+ RequestContainer
47
54
  ]
48
55
  });
49
56
  export { HttpRouterImpl };
@@ -1 +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"}
1
+ {"version":3,"file":"features/http/HttpRouter.js","sources":["../../../src/features/http/HttpRouter.ts"],"sourcesContent":["import type { Container } from \"@webiny/di\";\nimport {\n HttpRouter,\n HttpRouteDefinition,\n RouteNotFoundError\n} from \"~/features/http/abstractions.js\";\nimport { HttpResponseBuilder } from \"~/features/http/HttpResponseBuilder.js\";\nimport { toHttpResponse } from \"~/features/http/invokeHttpRoute.js\";\nimport { RequestContainer } from \"~/features/events/RequestContainer.js\";\nimport type {\n IHttpRouteDefinition,\n IHttpRequest,\n IHttpResponse\n} 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 /**\n * Takes the container so it can build the matched route — and ONLY the matched route.\n *\n * A definition declares no dependencies, so resolving all of them to match a path is a handful\n * of field assignments. Routes used to be resolved as instances just to read their `path`,\n * which built every one of their dependency graphs on every request: a static-asset request\n * constructed the whole GraphQL engine, every contextual schema and the AI provider before\n * discovering it wanted none of them.\n *\n * `resolveImplementation` asks for exactly the matched route's class: it reads that route's\n * dependencies from its own metadata, resolves them here, and applies decorators registered\n * for `HttpRouteHandler`, so a route stays decoratable.\n */\n constructor(private container: Container) {}\n\n async route(request: IHttpRequest): Promise<IHttpResponse> {\n for (const definition of this.container.resolveAll(HttpRouteDefinition)) {\n const params = this.match(definition, request);\n if (params === null) {\n continue;\n }\n\n const route = this.container.resolveImplementation(definition.handler);\n const response = new HttpResponseBuilder();\n const result = await route.handle(\n {\n ...request,\n pathParameters: params,\n // Lets a route — or anything wrapping one — know which route is running.\n route: {\n name: definition.name,\n method: definition.method,\n path: definition.path\n }\n },\n response\n );\n return toHttpResponse(result, response);\n }\n throw new RouteNotFoundError(request.method, request.path);\n }\n\n private match(\n definition: IHttpRouteDefinition,\n request: IHttpRequest\n ): Record<string, string> | null {\n if (definition.method !== request.method) {\n return null;\n }\n return matchPath(definition.path, request.path);\n }\n}\n\nexport const HttpRouterImpl = HttpRouter.createImplementation({\n implementation: HttpRouterImplClass,\n dependencies: [RequestContainer]\n});\n"],"names":["matchPath","pattern","path","prefix","patternParts","Boolean","pathParts","params","i","pp","pathPart","decodeURIComponent","HttpRouterImplClass","container","request","definition","HttpRouteDefinition","route","response","HttpResponseBuilder","result","toHttpResponse","RouteNotFoundError","HttpRouterImpl","HttpRouter","RequestContainer"],"mappings":";;;;AAeA,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;IAcF,YAAoBC,SAAoB,CAAE;aAAtBA,SAAS,GAATA;IAAuB;IAE3C,MAAM,MAAMC,OAAqB,EAA0B;QACvD,KAAK,MAAMC,cAAc,IAAI,CAAC,SAAS,CAAC,UAAU,CAACC,qBAAsB;YACrE,MAAMT,SAAS,IAAI,CAAC,KAAK,CAACQ,YAAYD;YACtC,IAAIP,AAAW,SAAXA,QACA;YAGJ,MAAMU,QAAQ,IAAI,CAAC,SAAS,CAAC,qBAAqB,CAACF,WAAW,OAAO;YACrE,MAAMG,WAAW,IAAIC;YACrB,MAAMC,SAAS,MAAMH,MAAM,MAAM,CAC7B;gBACI,GAAGH,OAAO;gBACV,gBAAgBP;gBAEhB,OAAO;oBACH,MAAMQ,WAAW,IAAI;oBACrB,QAAQA,WAAW,MAAM;oBACzB,MAAMA,WAAW,IAAI;gBACzB;YACJ,GACAG;YAEJ,OAAOG,eAAeD,QAAQF;QAClC;QACA,MAAM,IAAII,mBAAmBR,QAAQ,MAAM,EAAEA,QAAQ,IAAI;IAC7D;IAEQ,MACJC,UAAgC,EAChCD,OAAqB,EACQ;QAC7B,IAAIC,WAAW,MAAM,KAAKD,QAAQ,MAAM,EACpC,OAAO;QAEX,OAAOd,UAAUe,WAAW,IAAI,EAAED,QAAQ,IAAI;IAClD;AACJ;AAEO,MAAMS,iBAAiBC,WAAW,oBAAoB,CAAC;IAC1D,gBAAgBZ;IAChB,cAAc;QAACa;KAAiB;AACpC"}