@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,182 @@
|
|
|
1
|
+
import { InvalidValueException } from '@lakutata/core';
|
|
2
|
+
import { ServiceContext } from '../../context/ServiceContext.js';
|
|
3
|
+
import { ServiceResponseCodec } from '../../lib/ServiceResponseCodec.js';
|
|
4
|
+
import { ControllerActionNotFoundException } from '../../exceptions/ControllerActionNotFoundException.js';
|
|
5
|
+
import { EntrypointStoppedException } from '../../exceptions/EntrypointStoppedException.js';
|
|
6
|
+
/**
|
|
7
|
+
* The error codes of the JSON-RPC 2.0 responses sent by {@link jsonRPCServiceEntrypoint}.
|
|
8
|
+
*/
|
|
9
|
+
export const JSONRPCErrorCode = {
|
|
10
|
+
/**
|
|
11
|
+
* The message is not valid JSON.
|
|
12
|
+
*/
|
|
13
|
+
PARSE_ERROR: -32700,
|
|
14
|
+
/**
|
|
15
|
+
* The message is not a JSON-RPC 2.0 request (or is an empty batch, or the HTTP request is not a `POST` to the path).
|
|
16
|
+
*/
|
|
17
|
+
INVALID_REQUEST: -32600,
|
|
18
|
+
/**
|
|
19
|
+
* No service action matches the request (`ControllerActionNotFoundException`).
|
|
20
|
+
*/
|
|
21
|
+
METHOD_NOT_FOUND: -32601,
|
|
22
|
+
/**
|
|
23
|
+
* The params are given by position without the `positionalParams` option, or fail the action's input validation
|
|
24
|
+
* (`InvalidValueException`).
|
|
25
|
+
*/
|
|
26
|
+
INVALID_PARAMS: -32602,
|
|
27
|
+
/**
|
|
28
|
+
* Reserved by the specification for the internal errors; the adapter does not send it.
|
|
29
|
+
*/
|
|
30
|
+
INTERNAL_ERROR: -32603,
|
|
31
|
+
/**
|
|
32
|
+
* Any other error thrown by an action (in the server error range, -32000 to -32099), with the whole error object as
|
|
33
|
+
* the error's `data`.
|
|
34
|
+
*/
|
|
35
|
+
SERVER_ERROR: -32000,
|
|
36
|
+
/**
|
|
37
|
+
* The entrypoint is stopping and refuses the new requests (`EntrypointStoppedException`).
|
|
38
|
+
*/
|
|
39
|
+
STOPPING: -32001
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Get the JSON-RPC request behind a service action's request, when {@link jsonRPCServiceEntrypoint} serves it (to
|
|
43
|
+
* tell a notification from a request, or read the transport).
|
|
44
|
+
* @param context The service action's context (`this.context` in a controller).
|
|
45
|
+
* @returns The request's method, id and transport, or `undefined` when another adapter serves the request.
|
|
46
|
+
* @example
|
|
47
|
+
* ```typescript
|
|
48
|
+
* import {Controller, ServiceContext} from 'lakutata/com/entrypoint'
|
|
49
|
+
* import {ServiceAction} from 'lakutata/decorator/ctrl'
|
|
50
|
+
* import {jsonRPCRequestOfContext, type JSONRPCRequestInfo} from 'lakutata/adapter/jsonrpc'
|
|
51
|
+
*
|
|
52
|
+
* class LogController extends Controller {
|
|
53
|
+
* //{"jsonrpc": "2.0", "method": "log.write", "params": {"line": "started"}}: a notification, no id
|
|
54
|
+
* @ServiceAction({method: 'log.write'})
|
|
55
|
+
* public async write(input: {line: string}): Promise<void> {
|
|
56
|
+
* const request: JSONRPCRequestInfo | undefined = jsonRPCRequestOfContext(this.context as ServiceContext)
|
|
57
|
+
* console.log(`${request?.transport}: ${input.line}`)
|
|
58
|
+
* }
|
|
59
|
+
* }
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export function jsonRPCRequestOfContext(context) {
|
|
63
|
+
return context.jsonrpc;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* An error of JSON-RPC
|
|
67
|
+
* @param id
|
|
68
|
+
* @param code
|
|
69
|
+
* @param message
|
|
70
|
+
* @param data
|
|
71
|
+
*/
|
|
72
|
+
function errorResponse(id, code, message, data) {
|
|
73
|
+
return { jsonrpc: '2.0', id: id, error: { code: code, message: message, ...data === undefined ? {} : { data: data } } };
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The code of an error of an action
|
|
77
|
+
* @param error
|
|
78
|
+
*/
|
|
79
|
+
function errorCode(error) {
|
|
80
|
+
if (error instanceof ControllerActionNotFoundException)
|
|
81
|
+
return JSONRPCErrorCode.METHOD_NOT_FOUND;
|
|
82
|
+
if (error instanceof InvalidValueException)
|
|
83
|
+
return JSONRPCErrorCode.INVALID_PARAMS;
|
|
84
|
+
if (error instanceof EntrypointStoppedException)
|
|
85
|
+
return JSONRPCErrorCode.STOPPING;
|
|
86
|
+
return JSONRPCErrorCode.SERVER_ERROR;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* The requests of JSON-RPC 2.0 dispatched to the service actions: the single ones, the batches, the notifications (no
|
|
90
|
+
* response); their methods given to the patterns of the actions
|
|
91
|
+
*/
|
|
92
|
+
export class JSONRPCProtocol {
|
|
93
|
+
constructor(options = {}) {
|
|
94
|
+
this.options = options;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* The pattern and the input of the action of a request
|
|
98
|
+
* @param request
|
|
99
|
+
* @protected
|
|
100
|
+
*/
|
|
101
|
+
pattern(request) {
|
|
102
|
+
if (this.options.toPattern)
|
|
103
|
+
return this.options.toPattern(request.method, request.params);
|
|
104
|
+
const field = this.options.methodField ?? 'method';
|
|
105
|
+
if (Array.isArray(request.params)) {
|
|
106
|
+
if (!this.options.positionalParams)
|
|
107
|
+
throw Object.assign(new Error('The params by position are not accepted: give them by name (an object)'), { jsonrpcCode: JSONRPCErrorCode.INVALID_PARAMS });
|
|
108
|
+
return { [this.options.positionalParams]: request.params, [field]: request.method };
|
|
109
|
+
}
|
|
110
|
+
return { ...request.params ?? {}, [field]: request.method };
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* The response of a request (none for a notification)
|
|
114
|
+
* @param request
|
|
115
|
+
* @param dispatch
|
|
116
|
+
* @protected
|
|
117
|
+
*/
|
|
118
|
+
async one(request, dispatch) {
|
|
119
|
+
const candidate = request;
|
|
120
|
+
const id = typeof candidate?.id === 'string' || typeof candidate?.id === 'number' ? candidate.id : null;
|
|
121
|
+
if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate) || candidate.jsonrpc !== '2.0' || typeof candidate.method !== 'string'
|
|
122
|
+
|| (candidate.params !== undefined && (candidate.params === null || typeof candidate.params !== 'object'))
|
|
123
|
+
|| (candidate.id !== undefined && candidate.id !== null && typeof candidate.id !== 'string' && typeof candidate.id !== 'number')) {
|
|
124
|
+
return errorResponse(id, JSONRPCErrorCode.INVALID_REQUEST, 'The request is not a request of JSON-RPC 2.0');
|
|
125
|
+
}
|
|
126
|
+
const notification = !('id' in candidate);
|
|
127
|
+
const respond = (response) => notification ? undefined : response;
|
|
128
|
+
if (dispatch.stopping())
|
|
129
|
+
return respond(errorResponse(id, JSONRPCErrorCode.STOPPING, 'The entrypoint is stopping'));
|
|
130
|
+
let data;
|
|
131
|
+
try {
|
|
132
|
+
data = this.pattern(candidate);
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
return respond(errorResponse(id, error.jsonrpcCode ?? JSONRPCErrorCode.INVALID_PARAMS, error.message));
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
const context = new ServiceContext({
|
|
139
|
+
data: data,
|
|
140
|
+
clientId: dispatch.clientId,
|
|
141
|
+
jsonrpc: { method: candidate.method, id: candidate.id, transport: dispatch.transport },
|
|
142
|
+
...dispatch.extra ?? {}
|
|
143
|
+
});
|
|
144
|
+
//A StatusResult sent as its data (a file or an event stream cannot be sent: an error)
|
|
145
|
+
const result = ServiceResponseCodec.payload(await dispatch.runtime.dispatch(context, { signal: dispatch.signal }));
|
|
146
|
+
return respond({ jsonrpc: '2.0', id: id, result: result === undefined ? null : result });
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
//The error of the action: its message, the whole error as its data (as the other service adapters give it)
|
|
150
|
+
return respond(errorResponse(id, errorCode(error), error?.message || String(error), ServiceResponseCodec.encodeError(error).error));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* The response of a message (a request, a batch): undefined when there is none (notifications)
|
|
155
|
+
* @param message its text
|
|
156
|
+
* @param dispatch
|
|
157
|
+
*/
|
|
158
|
+
async handle(message, dispatch) {
|
|
159
|
+
let parsed;
|
|
160
|
+
try {
|
|
161
|
+
parsed = JSON.parse(message);
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return errorResponse(null, JSONRPCErrorCode.PARSE_ERROR, 'The message is not JSON');
|
|
165
|
+
}
|
|
166
|
+
if (!Array.isArray(parsed))
|
|
167
|
+
return await this.one(parsed, dispatch);
|
|
168
|
+
if (!parsed.length)
|
|
169
|
+
return errorResponse(null, JSONRPCErrorCode.INVALID_REQUEST, 'The batch is empty');
|
|
170
|
+
const responses = await Promise.all(parsed.map((request) => this.one(request, dispatch)));
|
|
171
|
+
const answered = responses.filter((response) => !!response);
|
|
172
|
+
return answered.length ? answered : undefined;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* A notification sent to a client (an event pushed)
|
|
176
|
+
* @param event
|
|
177
|
+
* @param args
|
|
178
|
+
*/
|
|
179
|
+
static notification(event, args) {
|
|
180
|
+
return JSON.stringify({ jsonrpc: '2.0', method: event, params: args });
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { createServer } from 'node:http';
|
|
3
|
+
import { ServiceChannel } from '../../channel/BuiltinChannels.js';
|
|
4
|
+
import { JSONRPCErrorCode, JSONRPCProtocol } from './JSONRPCProtocol.js';
|
|
5
|
+
import { applicationOf } from '../../lib/ApplicationOf.js';
|
|
6
|
+
/**
|
|
7
|
+
* The headers of the responses of HTTP: the tools of the browsers may call the adapter
|
|
8
|
+
*/
|
|
9
|
+
const CORS_HEADERS = {
|
|
10
|
+
'Access-Control-Allow-Origin': '*',
|
|
11
|
+
'Access-Control-Allow-Methods': 'POST, OPTIONS',
|
|
12
|
+
'Access-Control-Allow-Headers': 'Content-Type, id'
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* The id of the client of a request of HTTP (its header id, its query id)
|
|
16
|
+
* @param request
|
|
17
|
+
*/
|
|
18
|
+
function clientIdOf(request) {
|
|
19
|
+
const url = new URL(request.url ?? '/', 'http://localhost');
|
|
20
|
+
const given = request.headers.id ?? url.searchParams.get('id');
|
|
21
|
+
const id = Array.isArray(given) ? given[0] : given ?? undefined;
|
|
22
|
+
return id && id.length <= 200 ? id : undefined;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The body of a request, up to a limit
|
|
26
|
+
* @param request
|
|
27
|
+
* @param limit
|
|
28
|
+
*/
|
|
29
|
+
async function readBody(request, limit) {
|
|
30
|
+
const chunks = [];
|
|
31
|
+
let size = 0;
|
|
32
|
+
for await (const chunk of request) {
|
|
33
|
+
size += chunk.length;
|
|
34
|
+
if (size > limit)
|
|
35
|
+
throw Object.assign(new Error(`The body of the request is larger than ${limit} bytes`), { status: 413 });
|
|
36
|
+
chunks.push(chunk);
|
|
37
|
+
}
|
|
38
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The messages of a stream framed by lines, or by their Content-Length headers. A message larger than the limit is
|
|
42
|
+
* skipped (the rest of its line, or the length its header declares, keeping the framing) and reported
|
|
43
|
+
* @param input
|
|
44
|
+
* @param framing
|
|
45
|
+
* @param limit
|
|
46
|
+
* @param receive
|
|
47
|
+
* @param ended
|
|
48
|
+
* @param oversized
|
|
49
|
+
*/
|
|
50
|
+
function readMessages(input, framing, limit, receive, ended, oversized) {
|
|
51
|
+
let buffer = Buffer.alloc(0);
|
|
52
|
+
//The bytes of an oversized message still to skip (content-length), or its line being skipped (newline)
|
|
53
|
+
let skipping = 0;
|
|
54
|
+
let skippingLine = false;
|
|
55
|
+
const onData = (chunk) => {
|
|
56
|
+
buffer = Buffer.concat([buffer, typeof chunk === 'string' ? Buffer.from(chunk) : chunk]);
|
|
57
|
+
for (;;) {
|
|
58
|
+
if (skipping) {
|
|
59
|
+
const skipped = Math.min(skipping, buffer.length);
|
|
60
|
+
buffer = buffer.subarray(skipped);
|
|
61
|
+
skipping -= skipped;
|
|
62
|
+
if (skipping)
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
if (framing === 'newline') {
|
|
66
|
+
const end = buffer.indexOf(0x0a);
|
|
67
|
+
if (end < 0) {
|
|
68
|
+
//A line growing over the limit: skipped up to its end
|
|
69
|
+
if (buffer.length > limit) {
|
|
70
|
+
buffer = Buffer.alloc(0);
|
|
71
|
+
if (!skippingLine)
|
|
72
|
+
oversized();
|
|
73
|
+
skippingLine = true;
|
|
74
|
+
}
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
const tooLarge = skippingLine || end > limit;
|
|
78
|
+
const line = tooLarge ? '' : buffer.subarray(0, end).toString('utf8').trim();
|
|
79
|
+
buffer = buffer.subarray(end + 1);
|
|
80
|
+
if (tooLarge && !skippingLine)
|
|
81
|
+
oversized();
|
|
82
|
+
skippingLine = false;
|
|
83
|
+
if (line)
|
|
84
|
+
receive(line);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const headerEnd = buffer.indexOf('\r\n\r\n');
|
|
88
|
+
if (headerEnd < 0) {
|
|
89
|
+
//No header of a message: what is not one is dropped, its end kept (a part of the next header)
|
|
90
|
+
if (buffer.length > limit)
|
|
91
|
+
buffer = buffer.subarray(buffer.length - 3);
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
const length = Number(buffer.subarray(0, headerEnd).toString('ascii').match(/content-length:\s*(\d+)/i)?.[1] ?? NaN);
|
|
95
|
+
if (!Number.isFinite(length)) {
|
|
96
|
+
//Not a header of a message: skipped
|
|
97
|
+
buffer = buffer.subarray(headerEnd + 4);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (length > limit) {
|
|
101
|
+
//Its content skipped as it arrives
|
|
102
|
+
buffer = buffer.subarray(headerEnd + 4);
|
|
103
|
+
skipping = length;
|
|
104
|
+
oversized();
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (buffer.length < headerEnd + 4 + length)
|
|
108
|
+
break;
|
|
109
|
+
receive(buffer.subarray(headerEnd + 4, headerEnd + 4 + length).toString('utf8'));
|
|
110
|
+
buffer = buffer.subarray(headerEnd + 4 + length);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
input.on('data', onData);
|
|
114
|
+
input.once('end', ended);
|
|
115
|
+
return () => {
|
|
116
|
+
input.off('data', onData);
|
|
117
|
+
input.off('end', ended);
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Create a service adapter of an entrypoint served over JSON-RPC 2.0, on HTTP, WebSocket or stdio. Only the WebSocket
|
|
122
|
+
* transport needs an optional peer dependency, `ws`; without it, the entrypoint fails to start.
|
|
123
|
+
*
|
|
124
|
+
* A request's method and named params become the service action's pattern and input: by default
|
|
125
|
+
* `{"method": "user.get", "params": {"id": 1}}` is dispatched as `{method: 'user.get', id: 1}` (see
|
|
126
|
+
* {@link JSONRPCMappingOptions} for the other mappings). The batches are answered with an array of responses, the
|
|
127
|
+
* notifications (no `id`) run without a response. The action's result is the response's `result` (`null` for none);
|
|
128
|
+
* its error becomes a JSON-RPC error (see {@link JSONRPCErrorCode}): `METHOD_NOT_FOUND`, `INVALID_PARAMS` for a failed
|
|
129
|
+
* validation, otherwise `SERVER_ERROR` with the whole error object as its `data`.
|
|
130
|
+
*
|
|
131
|
+
* Over HTTP, a message is the body of a `POST` to the path, answered with 200 and the JSON response (204 when it held
|
|
132
|
+
* only notifications); any other method or path is answered with 404, and the CORS headers allow any origin. Over
|
|
133
|
+
* WebSocket and stdio, the events pushed by `Entrypoint.notify` and `Entrypoint.broadcast` are sent as notifications,
|
|
134
|
+
* `{"jsonrpc": "2.0", "method": event, "params": args}`. The client id carried by `ServiceContext.clientId` is the `id`
|
|
135
|
+
* header or `id` query parameter of the HTTP request or of the WebSocket connection (at most 200 characters; a random
|
|
136
|
+
* one for a WebSocket connection without it), and `'stdio'` on stdio.
|
|
137
|
+
*
|
|
138
|
+
* When an HTTP client disconnects before its response, the action's abort signal fires. When the entrypoint stops,
|
|
139
|
+
* stdio stops reading, the new requests are answered with `STOPPING`, and once the requests in progress are answered
|
|
140
|
+
* the WebSocket connections are closed (code 1001) and the server shuts down.
|
|
141
|
+
* @param options The adapter options; by default it serves HTTP on port 3000 of all the interfaces, on the path `/`.
|
|
142
|
+
* @returns The adapter, to give to the `service` option of `buildEntrypoints`.
|
|
143
|
+
* @example
|
|
144
|
+
* ```typescript
|
|
145
|
+
* import {Application} from 'lakutata'
|
|
146
|
+
* import {buildEntrypoints, Controller} from 'lakutata/com/entrypoint'
|
|
147
|
+
* import {ServiceAction} from 'lakutata/decorator/ctrl'
|
|
148
|
+
* import {jsonRPCServiceEntrypoint} from 'lakutata/adapter/jsonrpc'
|
|
149
|
+
* import WebSocket from 'ws'
|
|
150
|
+
*
|
|
151
|
+
* class UserController extends Controller {
|
|
152
|
+
* @ServiceAction({method: 'user.get'})
|
|
153
|
+
* public async get(input: {id: number}): Promise<{id: number, name: string}> {
|
|
154
|
+
* return {id: input.id, name: 'Ada'}
|
|
155
|
+
* }
|
|
156
|
+
* }
|
|
157
|
+
*
|
|
158
|
+
* Application.run({
|
|
159
|
+
* id: 'user.service',
|
|
160
|
+
* name: 'Users',
|
|
161
|
+
* components: {
|
|
162
|
+
* entrypoint: buildEntrypoints({
|
|
163
|
+
* controllers: [UserController],
|
|
164
|
+
* service: [
|
|
165
|
+
* //POST http://localhost:3000/rpc
|
|
166
|
+
* jsonRPCServiceEntrypoint({name: 'rpc-http', port: 3000, path: '/rpc'}),
|
|
167
|
+
* //ws://localhost:3001/rpc, with the events pushed
|
|
168
|
+
* jsonRPCServiceEntrypoint({name: 'rpc-ws', transport: 'websocket', port: 3001, path: '/rpc'})
|
|
169
|
+
* ]
|
|
170
|
+
* })
|
|
171
|
+
* }
|
|
172
|
+
* })
|
|
173
|
+
*
|
|
174
|
+
* //An HTTP client: {"jsonrpc": "2.0", "id": 1, "result": {"id": 1, "name": "Ada"}}
|
|
175
|
+
* const response: Response = await fetch('http://localhost:3000/rpc', {
|
|
176
|
+
* method: 'POST',
|
|
177
|
+
* headers: {'Content-Type': 'application/json'},
|
|
178
|
+
* body: JSON.stringify({jsonrpc: '2.0', id: 1, method: 'user.get', params: {id: 1}})
|
|
179
|
+
* })
|
|
180
|
+
* console.log(await response.json())
|
|
181
|
+
*
|
|
182
|
+
* //A WebSocket client, identified as client-1: the responses and the notifications arrive as messages
|
|
183
|
+
* const socket: WebSocket = new WebSocket('ws://localhost:3001/rpc?id=client-1')
|
|
184
|
+
* socket.on('open', (): void => socket.send(JSON.stringify({jsonrpc: '2.0', id: 2, method: 'user.get', params: {id: 2}})))
|
|
185
|
+
* socket.on('message', (data: WebSocket.RawData): void => console.log(JSON.parse(data.toString())))
|
|
186
|
+
* ```
|
|
187
|
+
*/
|
|
188
|
+
export function jsonRPCServiceEntrypoint(options = {}) {
|
|
189
|
+
const transport = options.transport ?? 'http';
|
|
190
|
+
const protocol = new JSONRPCProtocol(options);
|
|
191
|
+
const limit = options.limit ?? 10 * 1024 * 1024;
|
|
192
|
+
const path = (options.path ?? '/').replace(/(.)\/+$/, '$1');
|
|
193
|
+
let stopping = false;
|
|
194
|
+
let server;
|
|
195
|
+
let webSocketServer;
|
|
196
|
+
let stopReading;
|
|
197
|
+
let heartbeat;
|
|
198
|
+
//The connections of each client (WebSocket), the output of stdio
|
|
199
|
+
const clients = new Map();
|
|
200
|
+
const inProgress = new Set();
|
|
201
|
+
const addClient = (clientId, send) => {
|
|
202
|
+
if (!clients.has(clientId))
|
|
203
|
+
clients.set(clientId, new Set());
|
|
204
|
+
clients.get(clientId).add(send);
|
|
205
|
+
return () => {
|
|
206
|
+
clients.get(clientId)?.delete(send);
|
|
207
|
+
if (!clients.get(clientId)?.size)
|
|
208
|
+
clients.delete(clientId);
|
|
209
|
+
};
|
|
210
|
+
};
|
|
211
|
+
/**
|
|
212
|
+
* Answer a message
|
|
213
|
+
* @param runtime
|
|
214
|
+
* @param message
|
|
215
|
+
* @param dispatch
|
|
216
|
+
* @param send
|
|
217
|
+
*/
|
|
218
|
+
const answer = (runtime, message, dispatch, send) => {
|
|
219
|
+
const handled = protocol.handle(message, { ...dispatch, runtime: runtime, transport: transport, stopping: () => stopping })
|
|
220
|
+
.then((response) => {
|
|
221
|
+
if (response)
|
|
222
|
+
send(JSON.stringify(response));
|
|
223
|
+
})
|
|
224
|
+
.catch(() => undefined);
|
|
225
|
+
inProgress.add(handled);
|
|
226
|
+
void handled.finally(() => inProgress.delete(handled));
|
|
227
|
+
return handled;
|
|
228
|
+
};
|
|
229
|
+
const listen = async (target) => {
|
|
230
|
+
await new Promise((resolve, reject) => {
|
|
231
|
+
target.once('error', reject).listen(options.port ?? 3000, options.host, () => {
|
|
232
|
+
target.off('error', reject);
|
|
233
|
+
resolve();
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
await options.listening?.(target);
|
|
237
|
+
};
|
|
238
|
+
return {
|
|
239
|
+
channel: 'service',
|
|
240
|
+
definition: ServiceChannel,
|
|
241
|
+
name: options.name,
|
|
242
|
+
middlewares: options.middlewares,
|
|
243
|
+
start: async (runtime) => {
|
|
244
|
+
if (transport === 'stdio') {
|
|
245
|
+
const output = options.output ?? process.stdout;
|
|
246
|
+
const write = (message) => output.write(options.framing === 'content-length' ? `Content-Length: ${Buffer.byteLength(message)}\r\n\r\n${message}` : `${message}\n`);
|
|
247
|
+
addClient('stdio', write);
|
|
248
|
+
const ended = () => {
|
|
249
|
+
const mode = options.end ?? 'application';
|
|
250
|
+
//The requests in progress answered first
|
|
251
|
+
void Promise.allSettled([...inProgress]).then(async () => {
|
|
252
|
+
if (typeof mode === 'function')
|
|
253
|
+
return await mode();
|
|
254
|
+
if (mode === 'application')
|
|
255
|
+
applicationOf(runtime.module)?.exit(0);
|
|
256
|
+
});
|
|
257
|
+
};
|
|
258
|
+
//A message over the limit: an Invalid Request error, without id (it is not read)
|
|
259
|
+
const oversized = () => write(JSON.stringify({ jsonrpc: '2.0', id: null, error: { code: JSONRPCErrorCode.INVALID_REQUEST, message: `The message is larger than ${limit} bytes` } }));
|
|
260
|
+
stopReading = readMessages(options.input ?? process.stdin, options.framing ?? 'newline', limit, (message) => void answer(runtime, message, { clientId: 'stdio' }, write), ended, oversized);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (transport === 'http') {
|
|
264
|
+
server = createServer((request, response) => {
|
|
265
|
+
const url = new URL(request.url ?? '/', 'http://localhost');
|
|
266
|
+
if (request.method === 'OPTIONS')
|
|
267
|
+
return void response.writeHead(204, CORS_HEADERS).end();
|
|
268
|
+
if (request.method !== 'POST' || url.pathname !== path) {
|
|
269
|
+
return void response.writeHead(404, { ...CORS_HEADERS, 'Content-Type': 'application/json' }).end(JSON.stringify({ jsonrpc: '2.0', id: null, error: { code: -32600, message: `POST ${path} receives the requests of JSON-RPC 2.0` } }));
|
|
270
|
+
}
|
|
271
|
+
const aborted = new AbortController();
|
|
272
|
+
response.once('close', () => {
|
|
273
|
+
if (!response.writableFinished)
|
|
274
|
+
aborted.abort();
|
|
275
|
+
});
|
|
276
|
+
readBody(request, limit).then((body) => {
|
|
277
|
+
void answer(runtime, body, { clientId: clientIdOf(request), extra: { request: request }, signal: aborted.signal }, (message) => {
|
|
278
|
+
response.writeHead(200, { ...CORS_HEADERS, 'Content-Type': 'application/json; charset=utf-8' }).end(message);
|
|
279
|
+
}).then(() => {
|
|
280
|
+
//Only notifications: no content
|
|
281
|
+
if (!response.headersSent)
|
|
282
|
+
response.writeHead(204, CORS_HEADERS).end();
|
|
283
|
+
});
|
|
284
|
+
}, (error) => {
|
|
285
|
+
response.writeHead(error.status ?? 400, { ...CORS_HEADERS, 'Content-Type': 'application/json' }).end(JSON.stringify({ jsonrpc: '2.0', id: null, error: { code: -32600, message: error.message } }));
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
return await listen(server);
|
|
289
|
+
}
|
|
290
|
+
//WebSocket: the server of ws (an optional dependency)
|
|
291
|
+
let ws;
|
|
292
|
+
try {
|
|
293
|
+
ws = await import('ws');
|
|
294
|
+
}
|
|
295
|
+
catch {
|
|
296
|
+
throw new Error('The WebSocket transport of jsonRPCServiceEntrypoint needs the package ws (npm install ws)');
|
|
297
|
+
}
|
|
298
|
+
server = createServer((_request, response) => void response.writeHead(426, { 'Content-Type': 'text/plain' }).end('A WebSocket connection is expected'));
|
|
299
|
+
webSocketServer = new ws.WebSocketServer({ server: server, path: path, maxPayload: limit });
|
|
300
|
+
//Whether each connection answered the last ping
|
|
301
|
+
const alive = new WeakMap();
|
|
302
|
+
webSocketServer.on('connection', (socket, request) => {
|
|
303
|
+
const clientId = clientIdOf(request) ?? randomUUID();
|
|
304
|
+
const send = (message) => {
|
|
305
|
+
if (socket.readyState === 1)
|
|
306
|
+
socket.send(message);
|
|
307
|
+
};
|
|
308
|
+
const remove = addClient(clientId, send);
|
|
309
|
+
socket.on('message', (data, isBinary) => {
|
|
310
|
+
if (isBinary)
|
|
311
|
+
return;
|
|
312
|
+
void answer(runtime, Buffer.isBuffer(data) ? data.toString('utf8') : Array.isArray(data) ? Buffer.concat(data).toString('utf8') : Buffer.from(data).toString('utf8'), { clientId: clientId, extra: { socket: socket, request: request } }, send);
|
|
313
|
+
});
|
|
314
|
+
socket.on('close', remove);
|
|
315
|
+
socket.on('error', () => undefined);
|
|
316
|
+
socket.on('pong', () => alive.set(socket, true));
|
|
317
|
+
alive.set(socket, true);
|
|
318
|
+
});
|
|
319
|
+
//The connections which do not answer the pings closed (the ones a proxy dropped)
|
|
320
|
+
const interval = options.heartbeat ?? 30000;
|
|
321
|
+
if (interval > 0) {
|
|
322
|
+
heartbeat = setInterval(() => webSocketServer?.clients.forEach((socket) => {
|
|
323
|
+
if (!alive.get(socket))
|
|
324
|
+
return socket.terminate();
|
|
325
|
+
alive.set(socket, false);
|
|
326
|
+
socket.ping();
|
|
327
|
+
}), interval);
|
|
328
|
+
heartbeat.unref();
|
|
329
|
+
}
|
|
330
|
+
await listen(server);
|
|
331
|
+
},
|
|
332
|
+
stop: () => {
|
|
333
|
+
//The new requests are refused, the requests in progress end
|
|
334
|
+
stopping = true;
|
|
335
|
+
stopReading?.();
|
|
336
|
+
},
|
|
337
|
+
close: async () => {
|
|
338
|
+
clearInterval(heartbeat);
|
|
339
|
+
await Promise.allSettled([...inProgress]);
|
|
340
|
+
clients.clear();
|
|
341
|
+
if (webSocketServer) {
|
|
342
|
+
webSocketServer.clients.forEach((socket) => socket.close(1001, 'The service stops'));
|
|
343
|
+
await new Promise((resolve) => webSocketServer.close(() => resolve()));
|
|
344
|
+
}
|
|
345
|
+
if (!server)
|
|
346
|
+
return;
|
|
347
|
+
const closing = new Promise((resolve) => void server.close(() => resolve()));
|
|
348
|
+
server.closeAllConnections();
|
|
349
|
+
await closing;
|
|
350
|
+
},
|
|
351
|
+
notify: (clientId, event, args) => {
|
|
352
|
+
const connections = clients.get(clientId);
|
|
353
|
+
connections?.forEach((send) => send(JSONRPCProtocol.notification(event, args)));
|
|
354
|
+
return !!connections?.size;
|
|
355
|
+
},
|
|
356
|
+
broadcast: (event, args) => {
|
|
357
|
+
clients.forEach((connections) => connections.forEach((send) => send(JSONRPCProtocol.notification(event, args))));
|
|
358
|
+
return clients.size;
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
}
|