@lakutata/entrypoint 3.0.0-beta.0
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 +23 -0
- package/README.md +183 -0
- package/THIRD_PARTY_NOTICES.md +206 -0
- package/dist/cjs/Entrypoint.d.ts +868 -0
- package/dist/cjs/Entrypoint.js +998 -0
- package/dist/cjs/adapter/commander/CommanderCLIEntrypoint.d.ts +192 -0
- package/dist/cjs/adapter/commander/CommanderCLIEntrypoint.js +439 -0
- package/dist/cjs/adapter/commander/Prompt.d.ts +22 -0
- package/dist/cjs/adapter/commander/Prompt.js +164 -0
- package/dist/cjs/adapter/commander/Width.d.ts +12 -0
- package/dist/cjs/adapter/commander/Width.js +67 -0
- package/dist/cjs/adapter/electron/ElectronIPCServiceEntrypoint.d.ts +203 -0
- package/dist/cjs/adapter/electron/ElectronIPCServiceEntrypoint.js +249 -0
- package/dist/cjs/adapter/express/ExpressHTTPEntrypoint.d.ts +172 -0
- package/dist/cjs/adapter/express/ExpressHTTPEntrypoint.js +301 -0
- package/dist/cjs/adapter/express/Responders.d.ts +89 -0
- package/dist/cjs/adapter/express/Responders.js +71 -0
- package/dist/cjs/adapter/express/StaticUI.d.ts +83 -0
- package/dist/cjs/adapter/express/StaticUI.js +104 -0
- package/dist/cjs/adapter/fastify/FastifyHTTPEntrypoint.d.ts +156 -0
- package/dist/cjs/adapter/fastify/FastifyHTTPEntrypoint.js +339 -0
- package/dist/cjs/adapter/fastify/Responders.d.ts +88 -0
- package/dist/cjs/adapter/fastify/Responders.js +77 -0
- package/dist/cjs/adapter/http/HTTPServiceEntrypoint.d.ts +117 -0
- package/dist/cjs/adapter/http/HTTPServiceEntrypoint.js +257 -0
- package/dist/cjs/adapter/jsonrpc/JSONRPCProtocol.d.ts +194 -0
- package/dist/cjs/adapter/jsonrpc/JSONRPCProtocol.js +187 -0
- package/dist/cjs/adapter/jsonrpc/JSONRPCServiceEntrypoint.d.ts +154 -0
- package/dist/cjs/adapter/jsonrpc/JSONRPCServiceEntrypoint.js +397 -0
- package/dist/cjs/adapter/schedule/ScheduleEntrypoint.d.ts +86 -0
- package/dist/cjs/adapter/schedule/ScheduleEntrypoint.js +275 -0
- package/dist/cjs/adapter/socket.io/SocketIOServiceEntrypoint.d.ts +138 -0
- package/dist/cjs/adapter/socket.io/SocketIOServiceEntrypoint.js +216 -0
- package/dist/cjs/channel/BuiltinChannels.d.ts +18 -0
- package/dist/cjs/channel/BuiltinChannels.js +128 -0
- package/dist/cjs/channel/Channel.d.ts +196 -0
- package/dist/cjs/channel/Channel.js +106 -0
- package/dist/cjs/channel/EntrypointAdapter.d.ts +195 -0
- package/dist/cjs/channel/EntrypointAdapter.js +43 -0
- package/dist/cjs/context/BaseContext.d.ts +75 -0
- package/dist/cjs/context/BaseContext.js +90 -0
- package/dist/cjs/context/CLIContext.d.ts +38 -0
- package/dist/cjs/context/CLIContext.js +53 -0
- package/dist/cjs/context/HTTPContext.d.ts +52 -0
- package/dist/cjs/context/HTTPContext.js +78 -0
- package/dist/cjs/context/ScheduleContext.d.ts +47 -0
- package/dist/cjs/context/ScheduleContext.js +65 -0
- package/dist/cjs/context/ServiceContext.d.ts +47 -0
- package/dist/cjs/context/ServiceContext.js +62 -0
- package/dist/cjs/decorators/CLIAction.d.ts +120 -0
- package/dist/cjs/decorators/CLIAction.js +13 -0
- package/dist/cjs/decorators/HTTPAction.d.ts +178 -0
- package/dist/cjs/decorators/HTTPAction.js +14 -0
- package/dist/cjs/decorators/Prefix.d.ts +44 -0
- package/dist/cjs/decorators/Prefix.js +48 -0
- package/dist/cjs/decorators/Schedule.d.ts +120 -0
- package/dist/cjs/decorators/Schedule.js +64 -0
- package/dist/cjs/decorators/ServiceAction.d.ts +116 -0
- package/dist/cjs/decorators/ServiceAction.js +12 -0
- package/dist/cjs/decorators/UseMiddlewares.d.ts +52 -0
- package/dist/cjs/decorators/UseMiddlewares.js +58 -0
- package/dist/cjs/decorators/http/DELETE.d.ts +87 -0
- package/dist/cjs/decorators/http/DELETE.js +7 -0
- package/dist/cjs/decorators/http/GET.d.ts +89 -0
- package/dist/cjs/decorators/http/GET.js +7 -0
- package/dist/cjs/decorators/http/HEAD.d.ts +84 -0
- package/dist/cjs/decorators/http/HEAD.js +7 -0
- package/dist/cjs/decorators/http/OPTIONS.d.ts +80 -0
- package/dist/cjs/decorators/http/OPTIONS.js +7 -0
- package/dist/cjs/decorators/http/PATCH.d.ts +90 -0
- package/dist/cjs/decorators/http/PATCH.js +7 -0
- package/dist/cjs/decorators/http/POST.d.ts +91 -0
- package/dist/cjs/decorators/http/POST.js +7 -0
- package/dist/cjs/decorators/http/PUT.d.ts +92 -0
- package/dist/cjs/decorators/http/PUT.js +7 -0
- package/dist/cjs/exceptions/AccessDeniedException.d.ts +10 -0
- package/dist/cjs/exceptions/AccessDeniedException.js +17 -0
- package/dist/cjs/exceptions/ControllerActionNotFoundException.d.ts +9 -0
- package/dist/cjs/exceptions/ControllerActionNotFoundException.js +16 -0
- package/dist/cjs/exceptions/DuplicateActionNameException.d.ts +8 -0
- package/dist/cjs/exceptions/DuplicateActionNameException.js +15 -0
- package/dist/cjs/exceptions/EntrypointStoppedException.d.ts +9 -0
- package/dist/cjs/exceptions/EntrypointStoppedException.js +16 -0
- package/dist/cjs/exceptions/InvalidActionGroupException.d.ts +4 -0
- package/dist/cjs/exceptions/InvalidActionGroupException.js +11 -0
- package/dist/cjs/exceptions/InvalidChannelException.d.ts +8 -0
- package/dist/cjs/exceptions/InvalidChannelException.js +15 -0
- package/dist/cjs/exceptions/InvalidEntrypointAdapterException.d.ts +9 -0
- package/dist/cjs/exceptions/InvalidEntrypointAdapterException.js +16 -0
- package/dist/cjs/exceptions/InvalidMiddlewareException.d.ts +8 -0
- package/dist/cjs/exceptions/InvalidMiddlewareException.js +15 -0
- package/dist/cjs/exceptions/InvalidPrefixException.d.ts +8 -0
- package/dist/cjs/exceptions/InvalidPrefixException.js +15 -0
- package/dist/cjs/exceptions/MiddlewareNextCalledException.d.ts +8 -0
- package/dist/cjs/exceptions/MiddlewareNextCalledException.js +15 -0
- package/dist/cjs/exceptions/RouteNotFoundException.d.ts +9 -0
- package/dist/cjs/exceptions/RouteNotFoundException.js +16 -0
- package/dist/cjs/exceptions/ServiceInvokeException.d.ts +11 -0
- package/dist/cjs/exceptions/ServiceInvokeException.js +14 -0
- package/dist/cjs/exports/CTRLDecorators.d.ts +13 -0
- package/dist/cjs/exports/CTRLDecorators.js +38 -0
- package/dist/cjs/exports/Channel.d.ts +14 -0
- package/dist/cjs/exports/Channel.js +33 -0
- package/dist/cjs/exports/CommanderAdapter.d.ts +1 -0
- package/dist/cjs/exports/CommanderAdapter.js +8 -0
- package/dist/cjs/exports/ElectronAdapter.d.ts +1 -0
- package/dist/cjs/exports/ElectronAdapter.js +9 -0
- package/dist/cjs/exports/Entrypoint.d.ts +24 -0
- package/dist/cjs/exports/Entrypoint.js +55 -0
- package/dist/cjs/exports/ExpressAdapter.d.ts +4 -0
- package/dist/cjs/exports/ExpressAdapter.js +15 -0
- package/dist/cjs/exports/FastifyAdapter.d.ts +4 -0
- package/dist/cjs/exports/FastifyAdapter.js +14 -0
- package/dist/cjs/exports/JSONRPCAdapter.d.ts +2 -0
- package/dist/cjs/exports/JSONRPCAdapter.js +11 -0
- package/dist/cjs/exports/SocketIOAdapter.d.ts +1 -0
- package/dist/cjs/exports/SocketIOAdapter.js +9 -0
- package/dist/cjs/lib/AccessControl.d.ts +118 -0
- package/dist/cjs/lib/AccessControl.js +208 -0
- package/dist/cjs/lib/AccessControlRule.d.ts +91 -0
- package/dist/cjs/lib/AccessControlRule.js +76 -0
- package/dist/cjs/lib/ActionOptions.d.ts +67 -0
- package/dist/cjs/lib/ActionOptions.js +67 -0
- package/dist/cjs/lib/ActionResult.d.ts +28 -0
- package/dist/cjs/lib/ActionResult.js +28 -0
- package/dist/cjs/lib/ApplicationOf.d.ts +6 -0
- package/dist/cjs/lib/ApplicationOf.js +14 -0
- package/dist/cjs/lib/Controller.d.ts +59 -0
- package/dist/cjs/lib/Controller.js +64 -0
- package/dist/cjs/lib/ControllerEntrypoint.d.ts +168 -0
- package/dist/cjs/lib/ControllerEntrypoint.js +239 -0
- package/dist/cjs/lib/GetActionDTOAndOptions.d.ts +29 -0
- package/dist/cjs/lib/GetActionDTOAndOptions.js +49 -0
- package/dist/cjs/lib/HTTPErrors.d.ts +55 -0
- package/dist/cjs/lib/HTTPErrors.js +52 -0
- package/dist/cjs/lib/Middleware.d.ts +146 -0
- package/dist/cjs/lib/Middleware.js +144 -0
- package/dist/cjs/lib/Prefix.d.ts +98 -0
- package/dist/cjs/lib/Prefix.js +151 -0
- package/dist/cjs/lib/Result.d.ts +173 -0
- package/dist/cjs/lib/Result.js +153 -0
- package/dist/cjs/lib/ServiceResponseCodec.d.ts +82 -0
- package/dist/cjs/lib/ServiceResponseCodec.js +117 -0
- package/dist/cjs/lib/StringifyPattern.d.ts +7 -0
- package/dist/cjs/lib/StringifyPattern.js +58 -0
- package/dist/cjs/lib/UnsetValue.d.ts +12 -0
- package/dist/cjs/lib/UnsetValue.js +216 -0
- package/dist/cjs/lib/pattern/GlobPattern.d.ts +34 -0
- package/dist/cjs/lib/pattern/GlobPattern.js +68 -0
- package/dist/cjs/lib/pattern/PatternManager.d.ts +103 -0
- package/dist/cjs/lib/pattern/PatternManager.js +233 -0
- package/dist/cjs/lib/schedule/Cron.d.ts +56 -0
- package/dist/cjs/lib/schedule/Cron.js +282 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/types/CLIContextParams.d.ts +14 -0
- package/dist/cjs/types/CLIContextParams.js +2 -0
- package/dist/cjs/types/ErrorDescription.d.ts +14 -0
- package/dist/cjs/types/ErrorDescription.js +2 -0
- package/dist/cjs/types/HTTPContextParams.d.ts +27 -0
- package/dist/cjs/types/HTTPContextParams.js +2 -0
- package/dist/cjs/types/ScheduleContextParams.d.ts +21 -0
- package/dist/cjs/types/ScheduleContextParams.js +2 -0
- package/dist/cjs/types/ServiceContextParams.d.ts +16 -0
- package/dist/cjs/types/ServiceContextParams.js +2 -0
- package/dist/esm/Entrypoint.js +985 -0
- package/dist/esm/adapter/commander/CommanderCLIEntrypoint.js +433 -0
- package/dist/esm/adapter/commander/Prompt.js +127 -0
- package/dist/esm/adapter/commander/Width.js +63 -0
- package/dist/esm/adapter/electron/ElectronIPCServiceEntrypoint.js +212 -0
- package/dist/esm/adapter/express/ExpressHTTPEntrypoint.js +295 -0
- package/dist/esm/adapter/express/Responders.js +66 -0
- package/dist/esm/adapter/express/StaticUI.js +96 -0
- package/dist/esm/adapter/fastify/FastifyHTTPEntrypoint.js +333 -0
- package/dist/esm/adapter/fastify/Responders.js +73 -0
- package/dist/esm/adapter/http/HTTPServiceEntrypoint.js +253 -0
- package/dist/esm/adapter/jsonrpc/JSONRPCProtocol.js +182 -0
- package/dist/esm/adapter/jsonrpc/JSONRPCServiceEntrypoint.js +361 -0
- package/dist/esm/adapter/schedule/ScheduleEntrypoint.js +272 -0
- package/dist/esm/adapter/socket.io/SocketIOServiceEntrypoint.js +212 -0
- package/dist/esm/channel/BuiltinChannels.js +125 -0
- package/dist/esm/channel/Channel.js +100 -0
- package/dist/esm/channel/EntrypointAdapter.js +39 -0
- package/dist/esm/context/BaseContext.js +87 -0
- package/dist/esm/context/CLIContext.js +49 -0
- package/dist/esm/context/HTTPContext.js +74 -0
- package/dist/esm/context/ScheduleContext.js +61 -0
- package/dist/esm/context/ServiceContext.js +58 -0
- package/dist/esm/decorators/CLIAction.js +10 -0
- package/dist/esm/decorators/HTTPAction.js +11 -0
- package/dist/esm/decorators/Prefix.js +45 -0
- package/dist/esm/decorators/Schedule.js +61 -0
- package/dist/esm/decorators/ServiceAction.js +9 -0
- package/dist/esm/decorators/UseMiddlewares.js +55 -0
- package/dist/esm/decorators/http/DELETE.js +4 -0
- package/dist/esm/decorators/http/GET.js +4 -0
- package/dist/esm/decorators/http/HEAD.js +4 -0
- package/dist/esm/decorators/http/OPTIONS.js +4 -0
- package/dist/esm/decorators/http/PATCH.js +4 -0
- package/dist/esm/decorators/http/POST.js +4 -0
- package/dist/esm/decorators/http/PUT.js +4 -0
- package/dist/esm/exceptions/AccessDeniedException.js +13 -0
- package/dist/esm/exceptions/ControllerActionNotFoundException.js +12 -0
- package/dist/esm/exceptions/DuplicateActionNameException.js +11 -0
- package/dist/esm/exceptions/EntrypointStoppedException.js +12 -0
- package/dist/esm/exceptions/InvalidActionGroupException.js +7 -0
- package/dist/esm/exceptions/InvalidChannelException.js +11 -0
- package/dist/esm/exceptions/InvalidEntrypointAdapterException.js +12 -0
- package/dist/esm/exceptions/InvalidMiddlewareException.js +11 -0
- package/dist/esm/exceptions/InvalidPrefixException.js +11 -0
- package/dist/esm/exceptions/MiddlewareNextCalledException.js +11 -0
- package/dist/esm/exceptions/RouteNotFoundException.js +12 -0
- package/dist/esm/exceptions/ServiceInvokeException.js +10 -0
- package/dist/esm/exports/CTRLDecorators.js +19 -0
- package/dist/esm/exports/Channel.js +11 -0
- package/dist/esm/exports/CommanderAdapter.js +1 -0
- package/dist/esm/exports/ElectronAdapter.js +1 -0
- package/dist/esm/exports/Entrypoint.js +22 -0
- package/dist/esm/exports/ExpressAdapter.js +3 -0
- package/dist/esm/exports/FastifyAdapter.js +3 -0
- package/dist/esm/exports/JSONRPCAdapter.js +2 -0
- package/dist/esm/exports/SocketIOAdapter.js +1 -0
- package/dist/esm/lib/AccessControl.js +204 -0
- package/dist/esm/lib/AccessControlRule.js +72 -0
- package/dist/esm/lib/ActionOptions.js +63 -0
- package/dist/esm/lib/ActionResult.js +24 -0
- package/dist/esm/lib/ApplicationOf.js +11 -0
- package/dist/esm/lib/Controller.js +61 -0
- package/dist/esm/lib/ControllerEntrypoint.js +229 -0
- package/dist/esm/lib/GetActionDTOAndOptions.js +46 -0
- package/dist/esm/lib/HTTPErrors.js +48 -0
- package/dist/esm/lib/Middleware.js +134 -0
- package/dist/esm/lib/Prefix.js +143 -0
- package/dist/esm/lib/Result.js +147 -0
- package/dist/esm/lib/ServiceResponseCodec.js +113 -0
- package/dist/esm/lib/StringifyPattern.js +55 -0
- package/dist/esm/lib/UnsetValue.js +213 -0
- package/dist/esm/lib/pattern/GlobPattern.js +64 -0
- package/dist/esm/lib/pattern/PatternManager.js +229 -0
- package/dist/esm/lib/schedule/Cron.js +277 -0
- package/dist/esm/types/CLIContextParams.js +1 -0
- package/dist/esm/types/ErrorDescription.js +1 -0
- package/dist/esm/types/HTTPContextParams.js +1 -0
- package/dist/esm/types/ScheduleContextParams.js +1 -0
- package/dist/esm/types/ServiceContextParams.js +1 -0
- package/dist/types/Entrypoint.d.ts +868 -0
- package/dist/types/adapter/commander/CommanderCLIEntrypoint.d.ts +192 -0
- package/dist/types/adapter/commander/Prompt.d.ts +22 -0
- package/dist/types/adapter/commander/Width.d.ts +12 -0
- package/dist/types/adapter/electron/ElectronIPCServiceEntrypoint.d.ts +203 -0
- package/dist/types/adapter/express/ExpressHTTPEntrypoint.d.ts +172 -0
- package/dist/types/adapter/express/Responders.d.ts +89 -0
- package/dist/types/adapter/express/StaticUI.d.ts +83 -0
- package/dist/types/adapter/fastify/FastifyHTTPEntrypoint.d.ts +156 -0
- package/dist/types/adapter/fastify/Responders.d.ts +88 -0
- package/dist/types/adapter/http/HTTPServiceEntrypoint.d.ts +117 -0
- package/dist/types/adapter/jsonrpc/JSONRPCProtocol.d.ts +194 -0
- package/dist/types/adapter/jsonrpc/JSONRPCServiceEntrypoint.d.ts +154 -0
- package/dist/types/adapter/schedule/ScheduleEntrypoint.d.ts +86 -0
- package/dist/types/adapter/socket.io/SocketIOServiceEntrypoint.d.ts +138 -0
- package/dist/types/channel/BuiltinChannels.d.ts +18 -0
- package/dist/types/channel/Channel.d.ts +196 -0
- package/dist/types/channel/EntrypointAdapter.d.ts +195 -0
- package/dist/types/context/BaseContext.d.ts +75 -0
- package/dist/types/context/CLIContext.d.ts +38 -0
- package/dist/types/context/HTTPContext.d.ts +52 -0
- package/dist/types/context/ScheduleContext.d.ts +47 -0
- package/dist/types/context/ServiceContext.d.ts +47 -0
- package/dist/types/decorators/CLIAction.d.ts +120 -0
- package/dist/types/decorators/HTTPAction.d.ts +178 -0
- package/dist/types/decorators/Prefix.d.ts +44 -0
- package/dist/types/decorators/Schedule.d.ts +120 -0
- package/dist/types/decorators/ServiceAction.d.ts +116 -0
- package/dist/types/decorators/UseMiddlewares.d.ts +52 -0
- package/dist/types/decorators/http/DELETE.d.ts +87 -0
- package/dist/types/decorators/http/GET.d.ts +89 -0
- package/dist/types/decorators/http/HEAD.d.ts +84 -0
- package/dist/types/decorators/http/OPTIONS.d.ts +80 -0
- package/dist/types/decorators/http/PATCH.d.ts +90 -0
- package/dist/types/decorators/http/POST.d.ts +91 -0
- package/dist/types/decorators/http/PUT.d.ts +92 -0
- package/dist/types/exceptions/AccessDeniedException.d.ts +10 -0
- package/dist/types/exceptions/ControllerActionNotFoundException.d.ts +9 -0
- package/dist/types/exceptions/DuplicateActionNameException.d.ts +8 -0
- package/dist/types/exceptions/EntrypointStoppedException.d.ts +9 -0
- package/dist/types/exceptions/InvalidActionGroupException.d.ts +4 -0
- package/dist/types/exceptions/InvalidChannelException.d.ts +8 -0
- package/dist/types/exceptions/InvalidEntrypointAdapterException.d.ts +9 -0
- package/dist/types/exceptions/InvalidMiddlewareException.d.ts +8 -0
- package/dist/types/exceptions/InvalidPrefixException.d.ts +8 -0
- package/dist/types/exceptions/MiddlewareNextCalledException.d.ts +8 -0
- package/dist/types/exceptions/RouteNotFoundException.d.ts +9 -0
- package/dist/types/exceptions/ServiceInvokeException.d.ts +11 -0
- package/dist/types/exports/CTRLDecorators.d.ts +13 -0
- package/dist/types/exports/Channel.d.ts +14 -0
- package/dist/types/exports/CommanderAdapter.d.ts +1 -0
- package/dist/types/exports/ElectronAdapter.d.ts +1 -0
- package/dist/types/exports/Entrypoint.d.ts +24 -0
- package/dist/types/exports/ExpressAdapter.d.ts +4 -0
- package/dist/types/exports/FastifyAdapter.d.ts +4 -0
- package/dist/types/exports/JSONRPCAdapter.d.ts +2 -0
- package/dist/types/exports/SocketIOAdapter.d.ts +1 -0
- package/dist/types/lib/AccessControl.d.ts +118 -0
- package/dist/types/lib/AccessControlRule.d.ts +91 -0
- package/dist/types/lib/ActionOptions.d.ts +67 -0
- package/dist/types/lib/ActionResult.d.ts +28 -0
- package/dist/types/lib/ApplicationOf.d.ts +6 -0
- package/dist/types/lib/Controller.d.ts +59 -0
- package/dist/types/lib/ControllerEntrypoint.d.ts +168 -0
- package/dist/types/lib/GetActionDTOAndOptions.d.ts +29 -0
- package/dist/types/lib/HTTPErrors.d.ts +55 -0
- package/dist/types/lib/Middleware.d.ts +146 -0
- package/dist/types/lib/Prefix.d.ts +98 -0
- package/dist/types/lib/Result.d.ts +173 -0
- package/dist/types/lib/ServiceResponseCodec.d.ts +82 -0
- package/dist/types/lib/StringifyPattern.d.ts +7 -0
- package/dist/types/lib/UnsetValue.d.ts +12 -0
- package/dist/types/lib/pattern/GlobPattern.d.ts +34 -0
- package/dist/types/lib/pattern/PatternManager.d.ts +103 -0
- package/dist/types/lib/schedule/Cron.d.ts +56 -0
- package/dist/types/types/CLIContextParams.d.ts +14 -0
- package/dist/types/types/ErrorDescription.d.ts +14 -0
- package/dist/types/types/HTTPContextParams.d.ts +27 -0
- package/dist/types/types/ScheduleContextParams.d.ts +21 -0
- package/dist/types/types/ServiceContextParams.d.ts +16 -0
- package/package.json +152 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { type FastifyInstance, type FastifyReply, type FastifyRequest, type FastifyServerOptions } from 'fastify';
|
|
2
|
+
import type { Server } from 'node:http';
|
|
3
|
+
import type { Module } from '@lakutata/core';
|
|
4
|
+
import type { EntrypointAdapter } from '../../channel/EntrypointAdapter.js';
|
|
5
|
+
import type { MiddlewareDefinition } from '../../lib/Middleware.js';
|
|
6
|
+
import { HTTPContext } from '../../context/HTTPContext.js';
|
|
7
|
+
import { type FastifyResponder } from './Responders.js';
|
|
8
|
+
/**
|
|
9
|
+
* What the `before` hook of {@link fastifyHTTPEntrypoint} receives besides the fastify instance: the entrypoint's
|
|
10
|
+
* module, the HTTP server (not listening yet) and the routes the actions will occupy.
|
|
11
|
+
*/
|
|
12
|
+
export type FastifyHookContext = {
|
|
13
|
+
/**
|
|
14
|
+
* The module owning the entrypoint, to get the application's objects from (`await module.getObject('db')`).
|
|
15
|
+
*/
|
|
16
|
+
readonly module: Module;
|
|
17
|
+
/**
|
|
18
|
+
* The HTTP server of the fastify instance. It does not listen yet when the hook runs; attach other servers to it
|
|
19
|
+
* there (such as a socket.io server, see the `server` option of `socketIOServiceEntrypoint`).
|
|
20
|
+
*/
|
|
21
|
+
readonly server: Server;
|
|
22
|
+
/**
|
|
23
|
+
* Each action route mapped to its HTTP methods, in upper case (`'/users/:id'` → `{'GET', 'DELETE'}`).
|
|
24
|
+
*/
|
|
25
|
+
readonly routes: Map<string, Set<string>>;
|
|
26
|
+
/**
|
|
27
|
+
* The first segment of each action route (`'/device'`, `'/user'`…). A route starting with a parameter (`/:id`)
|
|
28
|
+
* adds none.
|
|
29
|
+
*/
|
|
30
|
+
readonly actionPrefixes: string[];
|
|
31
|
+
/**
|
|
32
|
+
* Tell whether a request path belongs to the actions: it equals one of the action prefixes or starts with one
|
|
33
|
+
* followed by `/`.
|
|
34
|
+
*/
|
|
35
|
+
readonly isActionPath: (requestPath: string) => boolean;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* The options of {@link fastifyHTTPEntrypoint}.
|
|
39
|
+
*/
|
|
40
|
+
export type FastifyHTTPEntrypointOptions = {
|
|
41
|
+
/**
|
|
42
|
+
* The adapter name, to tell apart several HTTP adapters of one entrypoint.
|
|
43
|
+
*/
|
|
44
|
+
name?: string;
|
|
45
|
+
/**
|
|
46
|
+
* The port the server listens on; 0 picks a random free port. The entrypoint fails to start when the port is in use.
|
|
47
|
+
* @default 80
|
|
48
|
+
*/
|
|
49
|
+
port?: number;
|
|
50
|
+
/**
|
|
51
|
+
* The host or address the server listens on.
|
|
52
|
+
* @default every interface, as the other HTTP adapters (`::`, or `0.0.0.0` without IPv6)
|
|
53
|
+
*/
|
|
54
|
+
host?: string;
|
|
55
|
+
/**
|
|
56
|
+
* An existing fastify instance to add the action routes to, or the options of the instance to create (`bodyLimit`,
|
|
57
|
+
* `trustProxy`, `logger`…). The adapter closes the instance when it stops, a given one included.
|
|
58
|
+
* @default a new fastify instance with its default options (a body limit of 1 MiB)
|
|
59
|
+
*/
|
|
60
|
+
fastify?: FastifyInstance | FastifyServerOptions;
|
|
61
|
+
/**
|
|
62
|
+
* Build an action's input from a request. The route parameters are always merged over its result.
|
|
63
|
+
* @default the query parameters, then the properties of an object body over them (an array body is ignored)
|
|
64
|
+
*/
|
|
65
|
+
input?: (request: FastifyRequest) => Record<string, unknown>;
|
|
66
|
+
/**
|
|
67
|
+
* How the results and the errors of the actions are written.
|
|
68
|
+
* @default FastifyJSONResponder
|
|
69
|
+
*/
|
|
70
|
+
respond?: FastifyResponder;
|
|
71
|
+
/**
|
|
72
|
+
* The middlewares of this adapter's actions, run after the entrypoint's global middlewares.
|
|
73
|
+
*/
|
|
74
|
+
middlewares?: MiddlewareDefinition[];
|
|
75
|
+
/**
|
|
76
|
+
* Run before the action routes are added, to register the application's own plugins, hooks and routes. A failure
|
|
77
|
+
* fails the entrypoint start.
|
|
78
|
+
*/
|
|
79
|
+
before?: (app: FastifyInstance, context: FastifyHookContext) => void | Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* The handler of the requests that match no route and whose path is outside the action prefixes, such as the pages
|
|
82
|
+
* of a web application. The unmatched requests inside the action prefixes, and all of them without a fallback, are
|
|
83
|
+
* answered with a `RouteNotFoundException` (404).
|
|
84
|
+
*/
|
|
85
|
+
fallback?: (request: FastifyRequest, reply: FastifyReply) => unknown;
|
|
86
|
+
/**
|
|
87
|
+
* Run when the adapter stops, after the server is closed.
|
|
88
|
+
*/
|
|
89
|
+
stop?: () => void | Promise<void>;
|
|
90
|
+
/**
|
|
91
|
+
* The interval between the comments keeping the event streams (`EventsResult`) alive through the proxies, in
|
|
92
|
+
* milliseconds.
|
|
93
|
+
* @default 15000
|
|
94
|
+
*/
|
|
95
|
+
heartbeat?: number;
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Create the HTTP adapter of an entrypoint served by a fastify instance: each HTTP action (`@GET`, `@POST`,
|
|
99
|
+
* `@HTTPAction`…) becomes a fastify route. It needs the optional peer dependency `fastify`.
|
|
100
|
+
*
|
|
101
|
+
* An action's input is the request's query and body (see the `input` option) with the route parameters merged over
|
|
102
|
+
* them. Its result is written by the responder (JSON by default, 204 for no result); a `StatusResult` sets the status
|
|
103
|
+
* and the headers, a `FileResult` sends a file, a buffer or a stream (its content type guessed from the extension of
|
|
104
|
+
* its file name, 304 when its ETag matches `If-None-Match`), and an `EventsResult` streams Server-Sent Events. An action
|
|
105
|
+
* that writes the response itself (through `HTTPContext.response`) is left alone. The errors, those of fastify included
|
|
106
|
+
* (a body too large: 413, a malformed JSON body: 400), are written by the responder with their status (500 by default).
|
|
107
|
+
* A GET action also answers HEAD, unless a HEAD action is declared for its route.
|
|
108
|
+
*
|
|
109
|
+
* When the client disconnects before the response, the action's abort signal fires and nothing is written. When the
|
|
110
|
+
* entrypoint stops, fastify stops accepting connections and answers the new requests with 503, the event streams end,
|
|
111
|
+
* and the requests in progress are given up to one second after they settle to send their responses before the
|
|
112
|
+
* remaining connections are closed.
|
|
113
|
+
* @param options The adapter options; by default it listens on port 80 with a new fastify instance.
|
|
114
|
+
* @returns The adapter, to give to the `http` option of `buildEntrypoints`.
|
|
115
|
+
* @example
|
|
116
|
+
* ```typescript
|
|
117
|
+
* import {Application} from 'lakutata'
|
|
118
|
+
* import {buildEntrypoints, Controller} from 'lakutata/com/entrypoint'
|
|
119
|
+
* import {GET, POST} from 'lakutata/decorator/ctrl'
|
|
120
|
+
* import {fastifyHTTPEntrypoint} from 'lakutata/adapter/fastify'
|
|
121
|
+
*
|
|
122
|
+
* class UserController extends Controller {
|
|
123
|
+
* //GET /users/1?verbose=true: the route parameter id merged over the query
|
|
124
|
+
* @GET('/users/:id')
|
|
125
|
+
* public async get(input: {id: string, verbose?: string}): Promise<{id: string}> {
|
|
126
|
+
* return {id: input.id}
|
|
127
|
+
* }
|
|
128
|
+
*
|
|
129
|
+
* //POST /users with the JSON body {"name": "Ada"}
|
|
130
|
+
* @POST('/users')
|
|
131
|
+
* public async create(input: {name: string}): Promise<{name: string}> {
|
|
132
|
+
* return {name: input.name}
|
|
133
|
+
* }
|
|
134
|
+
* }
|
|
135
|
+
*
|
|
136
|
+
* Application.run({
|
|
137
|
+
* id: 'web.app',
|
|
138
|
+
* name: 'Web',
|
|
139
|
+
* components: {
|
|
140
|
+
* entrypoint: buildEntrypoints({
|
|
141
|
+
* controllers: [UserController],
|
|
142
|
+
* http: fastifyHTTPEntrypoint({
|
|
143
|
+
* port: 8080,
|
|
144
|
+
* host: '0.0.0.0',
|
|
145
|
+
* fastify: {bodyLimit: 5 * 1024 * 1024, trustProxy: true},
|
|
146
|
+
* //The application's own routes
|
|
147
|
+
* before: (app): void => {
|
|
148
|
+
* app.get('/health', async (): Promise<string> => 'ok')
|
|
149
|
+
* }
|
|
150
|
+
* })
|
|
151
|
+
* })
|
|
152
|
+
* }
|
|
153
|
+
* })
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
156
|
+
export declare function fastifyHTTPEntrypoint(options?: FastifyHTTPEntrypointOptions): EntrypointAdapter<HTTPContext>;
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.fastifyHTTPEntrypoint = fastifyHTTPEntrypoint;
|
|
7
|
+
const fastify_1 = __importDefault(require("fastify"));
|
|
8
|
+
const node_fs_1 = require("node:fs");
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const HTTPContext_js_1 = require("../../context/HTTPContext.js");
|
|
11
|
+
const BuiltinChannels_js_1 = require("../../channel/BuiltinChannels.js");
|
|
12
|
+
const Result_js_1 = require("../../lib/Result.js");
|
|
13
|
+
const RouteNotFoundException_js_1 = require("../../exceptions/RouteNotFoundException.js");
|
|
14
|
+
const Responders_js_1 = require("./Responders.js");
|
|
15
|
+
/**
|
|
16
|
+
* The content types of the common file extensions
|
|
17
|
+
*/
|
|
18
|
+
const CONTENT_TYPES = {
|
|
19
|
+
'.txt': 'text/plain; charset=utf-8',
|
|
20
|
+
'.html': 'text/html; charset=utf-8',
|
|
21
|
+
'.css': 'text/css; charset=utf-8',
|
|
22
|
+
'.js': 'text/javascript; charset=utf-8',
|
|
23
|
+
'.json': 'application/json; charset=utf-8',
|
|
24
|
+
'.csv': 'text/csv; charset=utf-8',
|
|
25
|
+
'.xml': 'application/xml; charset=utf-8',
|
|
26
|
+
'.png': 'image/png',
|
|
27
|
+
'.jpg': 'image/jpeg',
|
|
28
|
+
'.jpeg': 'image/jpeg',
|
|
29
|
+
'.gif': 'image/gif',
|
|
30
|
+
'.svg': 'image/svg+xml',
|
|
31
|
+
'.webp': 'image/webp',
|
|
32
|
+
'.pdf': 'application/pdf',
|
|
33
|
+
'.zip': 'application/zip'
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* The first segments of the routes
|
|
37
|
+
* @param routes
|
|
38
|
+
*/
|
|
39
|
+
function actionPrefixesOf(routes) {
|
|
40
|
+
const prefixes = new Set();
|
|
41
|
+
for (const route of routes) {
|
|
42
|
+
const segment = route.split('/').find((part) => !!part);
|
|
43
|
+
//A route starting with a parameter matches every path
|
|
44
|
+
if (segment && !/^[:*]/.test(segment))
|
|
45
|
+
prefixes.add(`/${segment}`);
|
|
46
|
+
}
|
|
47
|
+
return [...prefixes];
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The data of an event of a stream
|
|
51
|
+
* @param data
|
|
52
|
+
* @param event
|
|
53
|
+
* @param id
|
|
54
|
+
*/
|
|
55
|
+
function eventFrame(data, event, id) {
|
|
56
|
+
let frame = '';
|
|
57
|
+
if (id !== undefined)
|
|
58
|
+
frame += `id: ${id}\n`;
|
|
59
|
+
if (event !== undefined)
|
|
60
|
+
frame += `event: ${event}\n`;
|
|
61
|
+
const text = typeof data === 'string' ? data : JSON.stringify(data);
|
|
62
|
+
for (const line of text.split(/\r?\n/))
|
|
63
|
+
frame += `data: ${line}\n`;
|
|
64
|
+
return `${frame}\n`;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Whether a value is a fastify instance
|
|
68
|
+
* @param value
|
|
69
|
+
*/
|
|
70
|
+
function isFastifyInstance(value) {
|
|
71
|
+
return !!value && typeof value.route === 'function' && typeof value.listen === 'function';
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Create the HTTP adapter of an entrypoint served by a fastify instance: each HTTP action (`@GET`, `@POST`,
|
|
75
|
+
* `@HTTPAction`…) becomes a fastify route. It needs the optional peer dependency `fastify`.
|
|
76
|
+
*
|
|
77
|
+
* An action's input is the request's query and body (see the `input` option) with the route parameters merged over
|
|
78
|
+
* them. Its result is written by the responder (JSON by default, 204 for no result); a `StatusResult` sets the status
|
|
79
|
+
* and the headers, a `FileResult` sends a file, a buffer or a stream (its content type guessed from the extension of
|
|
80
|
+
* its file name, 304 when its ETag matches `If-None-Match`), and an `EventsResult` streams Server-Sent Events. An action
|
|
81
|
+
* that writes the response itself (through `HTTPContext.response`) is left alone. The errors, those of fastify included
|
|
82
|
+
* (a body too large: 413, a malformed JSON body: 400), are written by the responder with their status (500 by default).
|
|
83
|
+
* A GET action also answers HEAD, unless a HEAD action is declared for its route.
|
|
84
|
+
*
|
|
85
|
+
* When the client disconnects before the response, the action's abort signal fires and nothing is written. When the
|
|
86
|
+
* entrypoint stops, fastify stops accepting connections and answers the new requests with 503, the event streams end,
|
|
87
|
+
* and the requests in progress are given up to one second after they settle to send their responses before the
|
|
88
|
+
* remaining connections are closed.
|
|
89
|
+
* @param options The adapter options; by default it listens on port 80 with a new fastify instance.
|
|
90
|
+
* @returns The adapter, to give to the `http` option of `buildEntrypoints`.
|
|
91
|
+
* @example
|
|
92
|
+
* ```typescript
|
|
93
|
+
* import {Application} from 'lakutata'
|
|
94
|
+
* import {buildEntrypoints, Controller} from 'lakutata/com/entrypoint'
|
|
95
|
+
* import {GET, POST} from 'lakutata/decorator/ctrl'
|
|
96
|
+
* import {fastifyHTTPEntrypoint} from 'lakutata/adapter/fastify'
|
|
97
|
+
*
|
|
98
|
+
* class UserController extends Controller {
|
|
99
|
+
* //GET /users/1?verbose=true: the route parameter id merged over the query
|
|
100
|
+
* @GET('/users/:id')
|
|
101
|
+
* public async get(input: {id: string, verbose?: string}): Promise<{id: string}> {
|
|
102
|
+
* return {id: input.id}
|
|
103
|
+
* }
|
|
104
|
+
*
|
|
105
|
+
* //POST /users with the JSON body {"name": "Ada"}
|
|
106
|
+
* @POST('/users')
|
|
107
|
+
* public async create(input: {name: string}): Promise<{name: string}> {
|
|
108
|
+
* return {name: input.name}
|
|
109
|
+
* }
|
|
110
|
+
* }
|
|
111
|
+
*
|
|
112
|
+
* Application.run({
|
|
113
|
+
* id: 'web.app',
|
|
114
|
+
* name: 'Web',
|
|
115
|
+
* components: {
|
|
116
|
+
* entrypoint: buildEntrypoints({
|
|
117
|
+
* controllers: [UserController],
|
|
118
|
+
* http: fastifyHTTPEntrypoint({
|
|
119
|
+
* port: 8080,
|
|
120
|
+
* host: '0.0.0.0',
|
|
121
|
+
* fastify: {bodyLimit: 5 * 1024 * 1024, trustProxy: true},
|
|
122
|
+
* //The application's own routes
|
|
123
|
+
* before: (app): void => {
|
|
124
|
+
* app.get('/health', async (): Promise<string> => 'ok')
|
|
125
|
+
* }
|
|
126
|
+
* })
|
|
127
|
+
* })
|
|
128
|
+
* }
|
|
129
|
+
* })
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
function fastifyHTTPEntrypoint(options = {}) {
|
|
133
|
+
const respond = options.respond ?? Responders_js_1.FastifyJSONResponder;
|
|
134
|
+
const input = options.input ?? ((request) => ({
|
|
135
|
+
...(request.query && typeof request.query === 'object' ? request.query : {}),
|
|
136
|
+
...(request.body && typeof request.body === 'object' && !Array.isArray(request.body) ? request.body : {})
|
|
137
|
+
}));
|
|
138
|
+
const streams = new Set();
|
|
139
|
+
let app;
|
|
140
|
+
let closed;
|
|
141
|
+
let stopping = false;
|
|
142
|
+
//The responses not sent yet
|
|
143
|
+
const responding = new Set();
|
|
144
|
+
/**
|
|
145
|
+
* Stream the events of a result
|
|
146
|
+
* @param result
|
|
147
|
+
* @param reply
|
|
148
|
+
*/
|
|
149
|
+
const streamEvents = async (result, reply) => {
|
|
150
|
+
const abortController = new AbortController();
|
|
151
|
+
streams.add(abortController);
|
|
152
|
+
reply.hijack();
|
|
153
|
+
const response = reply.raw;
|
|
154
|
+
response.writeHead(200, {
|
|
155
|
+
...reply.getHeaders(),
|
|
156
|
+
'Content-Type': 'text/event-stream; charset=utf-8',
|
|
157
|
+
'Cache-Control': 'no-cache, no-transform',
|
|
158
|
+
'Connection': 'keep-alive',
|
|
159
|
+
'X-Accel-Buffering': 'no'
|
|
160
|
+
});
|
|
161
|
+
response.flushHeaders();
|
|
162
|
+
const heartbeat = setInterval(() => response.write(': ping\n\n'), options.heartbeat ?? 15000);
|
|
163
|
+
const close = () => abortController.abort();
|
|
164
|
+
response.on('close', close);
|
|
165
|
+
try {
|
|
166
|
+
await result.producer((data, event, id) => {
|
|
167
|
+
if (!abortController.signal.aborted)
|
|
168
|
+
response.write(eventFrame(data, event, id));
|
|
169
|
+
}, abortController.signal);
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
//The stream is already started, its error ends it
|
|
173
|
+
}
|
|
174
|
+
finally {
|
|
175
|
+
clearInterval(heartbeat);
|
|
176
|
+
response.off('close', close);
|
|
177
|
+
streams.delete(abortController);
|
|
178
|
+
if (!response.writableEnded)
|
|
179
|
+
response.end();
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
/**
|
|
183
|
+
* Send a file
|
|
184
|
+
* @param result
|
|
185
|
+
* @param request
|
|
186
|
+
* @param reply
|
|
187
|
+
*/
|
|
188
|
+
const sendFile = async (result, request, reply) => {
|
|
189
|
+
const { contentType, filename, etag, headers } = result.options;
|
|
190
|
+
if (headers)
|
|
191
|
+
reply.headers(headers);
|
|
192
|
+
if (etag) {
|
|
193
|
+
reply.header('ETag', `"${etag}"`);
|
|
194
|
+
if (request.headers['if-none-match'] === `"${etag}"`) {
|
|
195
|
+
await reply.code(304).send();
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const source = result.source;
|
|
200
|
+
const name = filename ?? (typeof source === 'string' ? node_path_1.default.basename(source) : undefined);
|
|
201
|
+
if (filename)
|
|
202
|
+
reply.header('Content-Disposition', `attachment; filename="${encodeURIComponent(filename)}"`);
|
|
203
|
+
reply.type(contentType ?? (name && CONTENT_TYPES[node_path_1.default.extname(name).toLowerCase()]) ?? 'application/octet-stream');
|
|
204
|
+
await reply.send(typeof source === 'string' ? (0, node_fs_1.createReadStream)(node_path_1.default.resolve(source)) : source);
|
|
205
|
+
};
|
|
206
|
+
/**
|
|
207
|
+
* Write the result of an action
|
|
208
|
+
* @param result
|
|
209
|
+
* @param request
|
|
210
|
+
* @param reply
|
|
211
|
+
*/
|
|
212
|
+
const output = async (result, request, reply) => {
|
|
213
|
+
//The action wrote the response itself
|
|
214
|
+
if (reply.sent || reply.raw.headersSent)
|
|
215
|
+
return;
|
|
216
|
+
if (result instanceof Result_js_1.EventsResult)
|
|
217
|
+
return await streamEvents(result, reply);
|
|
218
|
+
if (result instanceof Result_js_1.FileResult)
|
|
219
|
+
return await sendFile(result, request, reply);
|
|
220
|
+
if (result instanceof Result_js_1.StatusResult) {
|
|
221
|
+
reply.code(result.status).headers(result.headers);
|
|
222
|
+
return respond.success(reply, result.data, request);
|
|
223
|
+
}
|
|
224
|
+
respond.success(reply, result, request);
|
|
225
|
+
};
|
|
226
|
+
/**
|
|
227
|
+
* The handler of the requests of an action
|
|
228
|
+
* @param runtime
|
|
229
|
+
* @param action
|
|
230
|
+
*/
|
|
231
|
+
const actionHandler = (runtime, action) => async (request, reply) => {
|
|
232
|
+
const abortController = new AbortController();
|
|
233
|
+
//The client left before the response was sent
|
|
234
|
+
reply.raw.once('close', () => {
|
|
235
|
+
if (!reply.raw.writableFinished)
|
|
236
|
+
abortController.abort();
|
|
237
|
+
});
|
|
238
|
+
const context = new HTTPContext_js_1.HTTPContext({
|
|
239
|
+
route: action.address.route,
|
|
240
|
+
method: action.address.method,
|
|
241
|
+
request: request.raw,
|
|
242
|
+
response: reply.raw,
|
|
243
|
+
data: input(request)
|
|
244
|
+
});
|
|
245
|
+
try {
|
|
246
|
+
const result = await runtime.dispatchTo(action, context, { signal: abortController.signal, params: { ...request.params } });
|
|
247
|
+
if (!abortController.signal.aborted)
|
|
248
|
+
await output(result, request, reply);
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
if (!reply.sent && !reply.raw.headersSent && !abortController.signal.aborted)
|
|
252
|
+
respond.error(reply, error, request, 'action');
|
|
253
|
+
}
|
|
254
|
+
//The client left: nothing is sent, the request is done for fastify (it is not waited for when fastify closes)
|
|
255
|
+
if (abortController.signal.aborted && !reply.sent) {
|
|
256
|
+
reply.hijack();
|
|
257
|
+
reply.raw.destroy();
|
|
258
|
+
}
|
|
259
|
+
return reply;
|
|
260
|
+
};
|
|
261
|
+
return {
|
|
262
|
+
channel: 'http',
|
|
263
|
+
definition: BuiltinChannels_js_1.HTTPChannel,
|
|
264
|
+
name: options.name,
|
|
265
|
+
middlewares: options.middlewares,
|
|
266
|
+
start: async (runtime) => {
|
|
267
|
+
const fastify = isFastifyInstance(options.fastify) ? options.fastify : (0, fastify_1.default)(options.fastify ?? {});
|
|
268
|
+
app = fastify;
|
|
269
|
+
const routes = new Map();
|
|
270
|
+
for (const action of runtime.actions)
|
|
271
|
+
routes.set(action.address.route, (routes.get(action.address.route) ?? new Set()).add(String(action.address.method).toUpperCase()));
|
|
272
|
+
const actionPrefixes = actionPrefixesOf(routes.keys());
|
|
273
|
+
const isActionPath = (requestPath) => actionPrefixes.some((prefix) => requestPath === prefix || requestPath.startsWith(`${prefix}/`));
|
|
274
|
+
await options.before?.(fastify, { module: runtime.module, server: fastify.server, routes: routes, actionPrefixes: actionPrefixes, isActionPath: isActionPath });
|
|
275
|
+
for (const action of runtime.actions) {
|
|
276
|
+
const method = String(action.address.method).toUpperCase();
|
|
277
|
+
fastify.route({
|
|
278
|
+
method: method,
|
|
279
|
+
url: action.address.route,
|
|
280
|
+
//A HEAD action of the route replaces the HEAD route fastify adds to a GET route
|
|
281
|
+
exposeHeadRoute: method === 'GET' && !routes.get(action.address.route).has('HEAD'),
|
|
282
|
+
handler: actionHandler(runtime, action)
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
fastify.setNotFoundHandler((request, reply) => {
|
|
286
|
+
const requestPath = request.url.split('?')[0];
|
|
287
|
+
if (options.fallback && !isActionPath(requestPath))
|
|
288
|
+
return options.fallback(request, reply);
|
|
289
|
+
return respond.error(reply, new RouteNotFoundException_js_1.RouteNotFoundException('Route {route} not found', { route: requestPath }), request, 'request');
|
|
290
|
+
});
|
|
291
|
+
//The responses in progress are waited for when the adapter closes
|
|
292
|
+
fastify.addHook('onRequest', async (request, reply) => {
|
|
293
|
+
const responded = new Promise((resolve) => void reply.raw.once('close', resolve));
|
|
294
|
+
responding.add(responded);
|
|
295
|
+
//Only resolved
|
|
296
|
+
void responded.then(() => responding.delete(responded));
|
|
297
|
+
});
|
|
298
|
+
//While the adapter stops, the connections are closed once their response is sent
|
|
299
|
+
fastify.addHook('onResponse', async () => {
|
|
300
|
+
if (stopping)
|
|
301
|
+
setImmediate(() => fastify.server.closeIdleConnections());
|
|
302
|
+
});
|
|
303
|
+
fastify.setErrorHandler((error, request, reply) => respond.error(reply, error, request, 'request'));
|
|
304
|
+
//The adapter starts once the server listens (an occupied port fails the start)
|
|
305
|
+
//Every interface by default, as the other adapters (fastify listens on localhost only)
|
|
306
|
+
if (options.host !== undefined) {
|
|
307
|
+
await fastify.listen({ port: options.port ?? 80, host: options.host });
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
try {
|
|
311
|
+
await fastify.listen({ port: options.port ?? 80, host: '::' });
|
|
312
|
+
}
|
|
313
|
+
catch (error) {
|
|
314
|
+
if (!['EAFNOSUPPORT', 'EADDRNOTAVAIL'].includes(error.code ?? ''))
|
|
315
|
+
throw error;
|
|
316
|
+
await fastify.listen({ port: options.port ?? 80, host: '0.0.0.0' });
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
stop: () => {
|
|
321
|
+
//The event streams end; fastify stops accepting connections, closes the idle ones and answers 503 to the new requests
|
|
322
|
+
stopping = true;
|
|
323
|
+
streams.forEach((stream) => stream.abort());
|
|
324
|
+
closed = app?.close();
|
|
325
|
+
},
|
|
326
|
+
close: async () => {
|
|
327
|
+
//The requests in progress ended or were aborted: their responses are given a second to be sent, then the remaining connections are closed
|
|
328
|
+
//(a connection opened without a request would hold the close of the server)
|
|
329
|
+
let timer;
|
|
330
|
+
await Promise.race([Promise.all(responding), new Promise((resolve) => {
|
|
331
|
+
timer = setTimeout(resolve, 1000);
|
|
332
|
+
})]);
|
|
333
|
+
clearTimeout(timer);
|
|
334
|
+
app?.server.closeAllConnections();
|
|
335
|
+
await closed;
|
|
336
|
+
await options.stop?.();
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { FastifyReply, FastifyRequest } from 'fastify';
|
|
2
|
+
import { type HTTPErrorOrigin } from '../../lib/HTTPErrors.js';
|
|
3
|
+
/**
|
|
4
|
+
* How {@link fastifyHTTPEntrypoint} writes the results and the errors of the actions (its `respond` option). Implement
|
|
5
|
+
* it to give the responses another shape; {@link FastifyJSONResponder} is the default, {@link fastifyJSONEnvelope}
|
|
6
|
+
* wraps them in an envelope. The file results and the event streams are not written by the responder.
|
|
7
|
+
*/
|
|
8
|
+
export interface FastifyResponder {
|
|
9
|
+
/**
|
|
10
|
+
* Write the result of an action. For a `StatusResult`, its status and headers are already set on the reply and
|
|
11
|
+
* `data` is its payload.
|
|
12
|
+
* @param reply The fastify reply to send.
|
|
13
|
+
* @param data The action's result, `undefined` when it returned nothing.
|
|
14
|
+
* @param request The fastify request being answered.
|
|
15
|
+
*/
|
|
16
|
+
success(reply: FastifyReply, data: unknown, request: FastifyRequest): void;
|
|
17
|
+
/**
|
|
18
|
+
* Write an error: a failed action (validation, access control, its own exception), an error of fastify (a hook such
|
|
19
|
+
* as an authenticator, a body too large), or a request matching no route (`RouteNotFoundException`). Not called for
|
|
20
|
+
* an action once the reply is sent, nor after the client left.
|
|
21
|
+
* @param reply The fastify reply to send.
|
|
22
|
+
* @param error The thrown error.
|
|
23
|
+
* @param request The fastify request being answered.
|
|
24
|
+
* @param origin Where the error comes from: `'action'` (the dispatch of an action failed) or `'request'` (a hook,
|
|
25
|
+
* an error of fastify, a route not found); see `HTTPErrorOrigin`.
|
|
26
|
+
*/
|
|
27
|
+
error(reply: FastifyReply, error: unknown, request: FastifyRequest, origin?: HTTPErrorOrigin): void;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The default responder of {@link fastifyHTTPEntrypoint}: a result is sent as JSON, no result as an empty 204 (or the
|
|
31
|
+
* status set by a `StatusResult`); an error is sent as `{errno, errMsg}` (see `describeError`) with its HTTP status
|
|
32
|
+
* (see `statusOfError`: the first 4xx status in its cause chain, its own status, or 500).
|
|
33
|
+
*/
|
|
34
|
+
export declare const FastifyJSONResponder: FastifyResponder;
|
|
35
|
+
/**
|
|
36
|
+
* The options of {@link fastifyJSONEnvelope}.
|
|
37
|
+
*/
|
|
38
|
+
export type FastifyJSONEnvelopeOptions = {
|
|
39
|
+
/**
|
|
40
|
+
* The HTTP status of every error response, such as 200 for the clients that read the errors from the envelope.
|
|
41
|
+
* @default the error's own status (see `statusOfError`)
|
|
42
|
+
*/
|
|
43
|
+
errorStatus?: number;
|
|
44
|
+
/**
|
|
45
|
+
* The HTTP status of the errors of the actions only (their dispatch failed: validation, access control, their own
|
|
46
|
+
* exceptions), over `errorStatus`; the errors of the hooks (an authenticator's 401), of fastify and the routes not
|
|
47
|
+
* found keep theirs. 200 answers as the applications of lakutata 2.x usually did.
|
|
48
|
+
* @default `errorStatus`
|
|
49
|
+
*/
|
|
50
|
+
actionErrorStatus?: number;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Create a responder for {@link fastifyHTTPEntrypoint} that wraps every response in the envelope
|
|
54
|
+
* `{errno, errMsg, data}`: a result as `{errno: null, errMsg: null, data}` (`data` is `null` when the action returned
|
|
55
|
+
* nothing, and its status is not turned into 204), an error as `{errno, errMsg, data: null}` with the error's status,
|
|
56
|
+
* the `errorStatus` option, or for the errors of the actions the `actionErrorStatus` option.
|
|
57
|
+
* @param options The envelope options.
|
|
58
|
+
* @returns The responder, to give to the `respond` option of `fastifyHTTPEntrypoint`.
|
|
59
|
+
* @example
|
|
60
|
+
* ```typescript
|
|
61
|
+
* import {Application} from 'lakutata'
|
|
62
|
+
* import {buildEntrypoints, Controller} from 'lakutata/com/entrypoint'
|
|
63
|
+
* import {GET} from 'lakutata/decorator/ctrl'
|
|
64
|
+
* import {fastifyHTTPEntrypoint, fastifyJSONEnvelope} from 'lakutata/adapter/fastify'
|
|
65
|
+
*
|
|
66
|
+
* class PingController extends Controller {
|
|
67
|
+
* //GET /ping answers {"errno": null, "errMsg": null, "data": "pong"}
|
|
68
|
+
* @GET('/ping')
|
|
69
|
+
* public async ping(): Promise<string> {
|
|
70
|
+
* return 'pong'
|
|
71
|
+
* }
|
|
72
|
+
* }
|
|
73
|
+
*
|
|
74
|
+
* Application.run({
|
|
75
|
+
* id: 'web.app',
|
|
76
|
+
* name: 'Web',
|
|
77
|
+
* components: {
|
|
78
|
+
* entrypoint: buildEntrypoints({
|
|
79
|
+
* controllers: [PingController],
|
|
80
|
+
* //The errors of the actions answered with 200 too (as the applications of lakutata 2.x), the routes not
|
|
81
|
+
* //found and the errors of the hooks with their status (404, 401…)
|
|
82
|
+
* http: fastifyHTTPEntrypoint({port: 8080, respond: fastifyJSONEnvelope({actionErrorStatus: 200})})
|
|
83
|
+
* })
|
|
84
|
+
* }
|
|
85
|
+
* })
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
export declare function fastifyJSONEnvelope(options?: FastifyJSONEnvelopeOptions): FastifyResponder;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FastifyJSONResponder = void 0;
|
|
4
|
+
exports.fastifyJSONEnvelope = fastifyJSONEnvelope;
|
|
5
|
+
const HTTPErrors_js_1 = require("../../lib/HTTPErrors.js");
|
|
6
|
+
/**
|
|
7
|
+
* Send JSON
|
|
8
|
+
* @param reply
|
|
9
|
+
* @param data
|
|
10
|
+
*/
|
|
11
|
+
function sendJSON(reply, data) {
|
|
12
|
+
reply.type('application/json; charset=utf-8').send(JSON.stringify(data));
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The default responder of {@link fastifyHTTPEntrypoint}: a result is sent as JSON, no result as an empty 204 (or the
|
|
16
|
+
* status set by a `StatusResult`); an error is sent as `{errno, errMsg}` (see `describeError`) with its HTTP status
|
|
17
|
+
* (see `statusOfError`: the first 4xx status in its cause chain, its own status, or 500).
|
|
18
|
+
*/
|
|
19
|
+
exports.FastifyJSONResponder = {
|
|
20
|
+
success: (reply, data) => {
|
|
21
|
+
if (data === undefined) {
|
|
22
|
+
reply.code(reply.statusCode === 200 ? 204 : reply.statusCode).send();
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
sendJSON(reply, data);
|
|
26
|
+
},
|
|
27
|
+
error: (reply, error) => {
|
|
28
|
+
reply.code((0, HTTPErrors_js_1.statusOfError)(error));
|
|
29
|
+
sendJSON(reply, (0, HTTPErrors_js_1.describeError)(error));
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Create a responder for {@link fastifyHTTPEntrypoint} that wraps every response in the envelope
|
|
34
|
+
* `{errno, errMsg, data}`: a result as `{errno: null, errMsg: null, data}` (`data` is `null` when the action returned
|
|
35
|
+
* nothing, and its status is not turned into 204), an error as `{errno, errMsg, data: null}` with the error's status,
|
|
36
|
+
* the `errorStatus` option, or for the errors of the actions the `actionErrorStatus` option.
|
|
37
|
+
* @param options The envelope options.
|
|
38
|
+
* @returns The responder, to give to the `respond` option of `fastifyHTTPEntrypoint`.
|
|
39
|
+
* @example
|
|
40
|
+
* ```typescript
|
|
41
|
+
* import {Application} from 'lakutata'
|
|
42
|
+
* import {buildEntrypoints, Controller} from 'lakutata/com/entrypoint'
|
|
43
|
+
* import {GET} from 'lakutata/decorator/ctrl'
|
|
44
|
+
* import {fastifyHTTPEntrypoint, fastifyJSONEnvelope} from 'lakutata/adapter/fastify'
|
|
45
|
+
*
|
|
46
|
+
* class PingController extends Controller {
|
|
47
|
+
* //GET /ping answers {"errno": null, "errMsg": null, "data": "pong"}
|
|
48
|
+
* @GET('/ping')
|
|
49
|
+
* public async ping(): Promise<string> {
|
|
50
|
+
* return 'pong'
|
|
51
|
+
* }
|
|
52
|
+
* }
|
|
53
|
+
*
|
|
54
|
+
* Application.run({
|
|
55
|
+
* id: 'web.app',
|
|
56
|
+
* name: 'Web',
|
|
57
|
+
* components: {
|
|
58
|
+
* entrypoint: buildEntrypoints({
|
|
59
|
+
* controllers: [PingController],
|
|
60
|
+
* //The errors of the actions answered with 200 too (as the applications of lakutata 2.x), the routes not
|
|
61
|
+
* //found and the errors of the hooks with their status (404, 401…)
|
|
62
|
+
* http: fastifyHTTPEntrypoint({port: 8080, respond: fastifyJSONEnvelope({actionErrorStatus: 200})})
|
|
63
|
+
* })
|
|
64
|
+
* }
|
|
65
|
+
* })
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
function fastifyJSONEnvelope(options = {}) {
|
|
69
|
+
return {
|
|
70
|
+
success: (reply, data) => sendJSON(reply, { errno: null, errMsg: null, data: data === undefined ? null : data }),
|
|
71
|
+
error: (reply, error, _request, origin) => {
|
|
72
|
+
const status = origin === 'action' ? options.actionErrorStatus ?? options.errorStatus : options.errorStatus;
|
|
73
|
+
reply.code(status ?? (0, HTTPErrors_js_1.statusOfError)(error));
|
|
74
|
+
sendJSON(reply, { ...(0, HTTPErrors_js_1.describeError)(error), data: null });
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}
|