@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,275 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.scheduleEntrypoint = scheduleEntrypoint;
|
|
4
|
+
const ScheduleContext_js_1 = require("../../context/ScheduleContext.js");
|
|
5
|
+
const BuiltinChannels_js_1 = require("../../channel/BuiltinChannels.js");
|
|
6
|
+
const Cron_js_1 = require("../../lib/schedule/Cron.js");
|
|
7
|
+
/**
|
|
8
|
+
* The longest time a lock of a run is kept after it (the other instances whose clocks are late skip it)
|
|
9
|
+
*/
|
|
10
|
+
const MAX_HOLD = 30000;
|
|
11
|
+
/**
|
|
12
|
+
* The longest delay of a timer
|
|
13
|
+
*/
|
|
14
|
+
const MAX_DELAY = 2 ** 31 - 1;
|
|
15
|
+
/**
|
|
16
|
+
* Create the schedule adapter of an entrypoint: it runs the controller actions declared with `@Schedule`, which never
|
|
17
|
+
* run without it. It needs no optional dependency.
|
|
18
|
+
*
|
|
19
|
+
* The runs begin once the application has launched (all its objects, its bootstrap and the other adapters are ready).
|
|
20
|
+
* Each run is dispatched like a request: a new controller in its own scope, the middlewares and the access control, a
|
|
21
|
+
* {@link ScheduleContext} giving the schedule, the time the run was due and its number. An interval keeps its pace:
|
|
22
|
+
* the runs missed while the process was suspended are skipped, not caught up. An action whose previous run is still in
|
|
23
|
+
* progress skips its run unless it allows `overlap`.
|
|
24
|
+
*
|
|
25
|
+
* An `exclusive` action runs in one instance of the application at a time: each run first tries to acquire a lock
|
|
26
|
+
* named `schedule:<key>` (the `key` of its `exclusive` option, the action name by default) from the mutex component
|
|
27
|
+
* (the `Mutex` of `lakutata/com/mutex`, named `mutex` by default), and is skipped when another
|
|
28
|
+
* instance holds it; the lock is kept after the run for half of its period (30 seconds at most) so that the instances
|
|
29
|
+
* whose clocks are late skip it too. An exclusive action whose mutex component cannot be resolved never runs (the
|
|
30
|
+
* error is logged).
|
|
31
|
+
*
|
|
32
|
+
* When the entrypoint stops, no new run starts, the kept locks are released and the entrypoint waits for the runs in
|
|
33
|
+
* progress (aborting them after its `shutdownTimeout`).
|
|
34
|
+
* @param options The adapter options.
|
|
35
|
+
* @returns The adapter, to give to the `schedule` option of `buildEntrypoints`.
|
|
36
|
+
* @example
|
|
37
|
+
* ```typescript
|
|
38
|
+
* import {Application} from 'lakutata'
|
|
39
|
+
* import {buildEntrypoints, Controller, ScheduleContext, scheduleEntrypoint} from 'lakutata/com/entrypoint'
|
|
40
|
+
* import {Schedule} from 'lakutata/decorator/ctrl'
|
|
41
|
+
*
|
|
42
|
+
* class MaintenanceController extends Controller {
|
|
43
|
+
* //Every 5 minutes, and once the application launched
|
|
44
|
+
* @Schedule({every: '5m', immediate: true})
|
|
45
|
+
* public async purge(): Promise<void> {
|
|
46
|
+
* const context: ScheduleContext = this.context as ScheduleContext
|
|
47
|
+
* console.log(`Run ${context.run}, due at ${context.scheduledAt.toISOString()}`)
|
|
48
|
+
* }
|
|
49
|
+
*
|
|
50
|
+
* //Every day at 03:00 in Shanghai
|
|
51
|
+
* @Schedule({cron: '0 3 * * *', timezone: 'Asia/Shanghai'})
|
|
52
|
+
* public async report(): Promise<void> {
|
|
53
|
+
* }
|
|
54
|
+
* }
|
|
55
|
+
*
|
|
56
|
+
* Application.run({
|
|
57
|
+
* id: 'worker.app',
|
|
58
|
+
* name: 'Worker',
|
|
59
|
+
* components: {
|
|
60
|
+
* entrypoint: buildEntrypoints({
|
|
61
|
+
* controllers: [MaintenanceController],
|
|
62
|
+
* schedule: scheduleEntrypoint({
|
|
63
|
+
* onError: (error, action): void => console.error(`${action.name} failed`, error),
|
|
64
|
+
* onSkip: (action, scheduledAt, reason): void => console.warn(`${action.name} skipped (${reason})`)
|
|
65
|
+
* })
|
|
66
|
+
* })
|
|
67
|
+
* }
|
|
68
|
+
* })
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
function scheduleEntrypoint(options = {}) {
|
|
72
|
+
const timers = new Set();
|
|
73
|
+
let stopped = false;
|
|
74
|
+
//The locks of the runs ended, kept a while: released at once when the adapter stops
|
|
75
|
+
const keptLocks = new Map();
|
|
76
|
+
const releaseKept = (lock) => {
|
|
77
|
+
clearTimeout(keptLocks.get(lock));
|
|
78
|
+
keptLocks.delete(lock);
|
|
79
|
+
lock.release().catch(() => undefined);
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Run a function at a time
|
|
83
|
+
* @param at
|
|
84
|
+
* @param run
|
|
85
|
+
*/
|
|
86
|
+
const at = (time, run) => {
|
|
87
|
+
if (stopped)
|
|
88
|
+
return;
|
|
89
|
+
const delay = time - Date.now();
|
|
90
|
+
const timer = setTimeout(() => {
|
|
91
|
+
timers.delete(timer);
|
|
92
|
+
//The delays longer than a timer allows are waited in several times
|
|
93
|
+
delay > MAX_DELAY ? at(time, run) : run();
|
|
94
|
+
}, Math.max(0, Math.min(delay, MAX_DELAY)));
|
|
95
|
+
timers.add(timer);
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Write a failed run to the log of the application
|
|
99
|
+
* @param runtime
|
|
100
|
+
* @param error
|
|
101
|
+
* @param action
|
|
102
|
+
* @param context
|
|
103
|
+
*/
|
|
104
|
+
const report = async (runtime, error, action, context) => {
|
|
105
|
+
if (options.onError)
|
|
106
|
+
return await options.onError(error, action, context);
|
|
107
|
+
await log(runtime, error, `The scheduled action ${action.name} (${context.schedule}) failed`);
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* Write an error to the log of the application
|
|
111
|
+
* @param runtime
|
|
112
|
+
* @param error
|
|
113
|
+
* @param message
|
|
114
|
+
*/
|
|
115
|
+
const log = async (runtime, error, message) => {
|
|
116
|
+
try {
|
|
117
|
+
const logger = await runtime.getObject('log');
|
|
118
|
+
logger.error(error, message);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
console.error(`${message}:`, error);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
return {
|
|
125
|
+
channel: 'schedule',
|
|
126
|
+
definition: BuiltinChannels_js_1.ScheduleChannel,
|
|
127
|
+
name: options.name,
|
|
128
|
+
middlewares: options.middlewares,
|
|
129
|
+
start: (runtime) => {
|
|
130
|
+
const states = new Map();
|
|
131
|
+
//The components of the locks of the exclusive actions
|
|
132
|
+
const locks = new Map();
|
|
133
|
+
/**
|
|
134
|
+
* The time between a run of an action and its next one
|
|
135
|
+
* @param action
|
|
136
|
+
* @param scheduledAt
|
|
137
|
+
*/
|
|
138
|
+
const period = (action, scheduledAt) => {
|
|
139
|
+
if (action.address.every !== undefined)
|
|
140
|
+
return action.address.every;
|
|
141
|
+
const next = new Cron_js_1.Cron(action.address.cron, action.address.timezone).next(scheduledAt);
|
|
142
|
+
return next ? next.getTime() - scheduledAt : MAX_HOLD * 2;
|
|
143
|
+
};
|
|
144
|
+
/**
|
|
145
|
+
* Tell a run skipped: an error of the handler is written to the log, the schedule goes on
|
|
146
|
+
* @param action
|
|
147
|
+
* @param scheduledAt
|
|
148
|
+
* @param reason
|
|
149
|
+
*/
|
|
150
|
+
const skip = (action, scheduledAt, reason) => {
|
|
151
|
+
try {
|
|
152
|
+
options.onSkip?.(action, new Date(scheduledAt), reason);
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
void log(runtime, error, `The onSkip handler of the scheduled action ${action.name} failed`);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
* Run an action
|
|
160
|
+
* @param action
|
|
161
|
+
* @param scheduledAt
|
|
162
|
+
*/
|
|
163
|
+
const fire = (action, scheduledAt) => {
|
|
164
|
+
if (stopped)
|
|
165
|
+
return;
|
|
166
|
+
const state = states.get(action);
|
|
167
|
+
if (state.running && !action.address.overlap)
|
|
168
|
+
return skip(action, scheduledAt, 'overlap');
|
|
169
|
+
state.running++;
|
|
170
|
+
const exclusive = locks.get(action);
|
|
171
|
+
void (async () => {
|
|
172
|
+
let lock;
|
|
173
|
+
try {
|
|
174
|
+
if (exclusive) {
|
|
175
|
+
lock = await exclusive.tryAcquire(`schedule:${action.address.exclusiveKey ?? action.name}`);
|
|
176
|
+
//Run by another instance
|
|
177
|
+
if (!lock)
|
|
178
|
+
return skip(action, scheduledAt, 'exclusive');
|
|
179
|
+
}
|
|
180
|
+
state.runs++;
|
|
181
|
+
const context = new ScheduleContext_js_1.ScheduleContext({ schedule: action.address.schedule, scheduledAt: new Date(scheduledAt), run: state.runs, data: {} });
|
|
182
|
+
await runtime.dispatchTo(action, context).catch((error) => report(runtime, error, action, context));
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
await log(runtime, error, `The lock of the scheduled action ${action.name} failed`);
|
|
186
|
+
}
|
|
187
|
+
finally {
|
|
188
|
+
state.running--;
|
|
189
|
+
if (lock) {
|
|
190
|
+
//Kept until the half of its period (30 s at most): the instances whose clocks are late skip
|
|
191
|
+
//the run too
|
|
192
|
+
const held = lock;
|
|
193
|
+
const remaining = scheduledAt + Math.min(period(action, scheduledAt) / 2, MAX_HOLD) - Date.now();
|
|
194
|
+
if (remaining <= 0 || stopped) {
|
|
195
|
+
held.release().catch(() => undefined);
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
const timer = setTimeout(() => releaseKept(held), remaining);
|
|
199
|
+
keptLocks.set(held, timer);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
})().catch(() => undefined);
|
|
204
|
+
};
|
|
205
|
+
/**
|
|
206
|
+
* Plan the runs of an action
|
|
207
|
+
* @param action
|
|
208
|
+
*/
|
|
209
|
+
const plan = (action) => {
|
|
210
|
+
if (action.address.every !== undefined) {
|
|
211
|
+
const interval = action.address.every;
|
|
212
|
+
//The exclusive ones aligned on the multiples of their interval: the same times in every instance
|
|
213
|
+
let next = action.address.exclusive ? Math.ceil((Date.now() + 1) / interval) * interval : Date.now() + interval;
|
|
214
|
+
const tick = () => {
|
|
215
|
+
fire(action, next);
|
|
216
|
+
//The runs keep their pace: the times missed (the process was suspended) are skipped
|
|
217
|
+
next += interval;
|
|
218
|
+
const now = Date.now();
|
|
219
|
+
if (next <= now)
|
|
220
|
+
next += Math.ceil((now - next + 1) / interval) * interval;
|
|
221
|
+
at(next, tick);
|
|
222
|
+
};
|
|
223
|
+
at(next, tick);
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
const cron = new Cron_js_1.Cron(action.address.cron, action.address.timezone);
|
|
227
|
+
const schedule = (after) => {
|
|
228
|
+
const time = cron.next(after);
|
|
229
|
+
if (!time)
|
|
230
|
+
return;
|
|
231
|
+
at(time.getTime(), () => {
|
|
232
|
+
fire(action, time.getTime());
|
|
233
|
+
schedule(Math.max(Date.now(), time.getTime()));
|
|
234
|
+
});
|
|
235
|
+
};
|
|
236
|
+
schedule(Date.now());
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
//The runs begin once the application launched: its objects and the other adapters are ready
|
|
240
|
+
//Only resolved, fire() reports the errors of the runs
|
|
241
|
+
void runtime.ready.then(async () => {
|
|
242
|
+
if (stopped)
|
|
243
|
+
return;
|
|
244
|
+
for (const action of runtime.actions) {
|
|
245
|
+
if (action.address.exclusive) {
|
|
246
|
+
//Its locks: without them, it does not run (rather than in every instance)
|
|
247
|
+
try {
|
|
248
|
+
const component = await runtime.getObject(action.address.exclusive);
|
|
249
|
+
if (typeof component?.tryAcquire !== 'function')
|
|
250
|
+
throw new Error(`The object ${action.address.exclusive} is not a component of the locks (the Mutex of @lakutata/mutex)`);
|
|
251
|
+
locks.set(action, component);
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
await log(runtime, error, `The exclusive scheduled action ${action.name} does not run: its component of the locks ${action.address.exclusive} is not there`);
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (stopped)
|
|
259
|
+
return;
|
|
260
|
+
states.set(action, { running: 0, runs: 0 });
|
|
261
|
+
if (action.address.immediate)
|
|
262
|
+
fire(action, Date.now());
|
|
263
|
+
plan(action);
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
},
|
|
267
|
+
stop: () => {
|
|
268
|
+
//No new run, the runs in progress are waited for by the entrypoint
|
|
269
|
+
stopped = true;
|
|
270
|
+
timers.forEach((timer) => clearTimeout(timer));
|
|
271
|
+
timers.clear();
|
|
272
|
+
[...keptLocks.keys()].forEach(releaseKept);
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { Server as SocketIOServer, type ServerOptions, type Socket } from 'socket.io';
|
|
2
|
+
import { type Server } from 'node:http';
|
|
3
|
+
import type { EntrypointAdapter } from '../../channel/EntrypointAdapter.js';
|
|
4
|
+
import type { MiddlewareDefinition } from '../../lib/Middleware.js';
|
|
5
|
+
import { ServiceContext } from '../../context/ServiceContext.js';
|
|
6
|
+
/**
|
|
7
|
+
* The options of {@link socketIOServiceEntrypoint}.
|
|
8
|
+
*/
|
|
9
|
+
export type SocketIOServiceEntrypointOptions = {
|
|
10
|
+
/**
|
|
11
|
+
* The adapter name, to tell apart several service adapters of one entrypoint.
|
|
12
|
+
*/
|
|
13
|
+
name?: string;
|
|
14
|
+
/**
|
|
15
|
+
* The socket.io event carrying the requests: `socket.emit('message', {cmd: 'echo', …}, (response) => …)`.
|
|
16
|
+
* @default 'message'
|
|
17
|
+
*/
|
|
18
|
+
event?: string;
|
|
19
|
+
/**
|
|
20
|
+
* The socket.io namespace receiving the requests.
|
|
21
|
+
* @default '/'
|
|
22
|
+
*/
|
|
23
|
+
namespace?: string;
|
|
24
|
+
/**
|
|
25
|
+
* An HTTP server to attach the socket.io server to (such as the one of `expressHTTPEntrypoint`, given to its
|
|
26
|
+
* `before` hook), or a function returning it when the adapter starts. The adapter then does not listen itself, and
|
|
27
|
+
* when it stops it disconnects the sockets but leaves the HTTP server to its owner.
|
|
28
|
+
* @default a server of its own, listening on `port`
|
|
29
|
+
*/
|
|
30
|
+
server?: Server | (() => Server);
|
|
31
|
+
/**
|
|
32
|
+
* An existing socket.io server to serve the requests on, used as is: the adapter neither makes it listen nor closes
|
|
33
|
+
* it, and ignores `server`, `port`, `host` and `options`.
|
|
34
|
+
* @default a socket.io server created by the adapter
|
|
35
|
+
*/
|
|
36
|
+
io?: SocketIOServer;
|
|
37
|
+
/**
|
|
38
|
+
* The port of the adapter's own server (without `server` and `io`); 0 picks a random free port. The entrypoint fails
|
|
39
|
+
* to start when the port is in use.
|
|
40
|
+
* @default 3000
|
|
41
|
+
*/
|
|
42
|
+
port?: number;
|
|
43
|
+
/**
|
|
44
|
+
* The host or address of the adapter's own server.
|
|
45
|
+
* @default all the interfaces
|
|
46
|
+
*/
|
|
47
|
+
host?: string;
|
|
48
|
+
/**
|
|
49
|
+
* The options of the socket.io server created by the adapter (`path`, `cors`, `transports`…).
|
|
50
|
+
*/
|
|
51
|
+
options?: Partial<ServerOptions>;
|
|
52
|
+
/**
|
|
53
|
+
* The middlewares of this adapter's actions, run after the entrypoint's global middlewares.
|
|
54
|
+
*/
|
|
55
|
+
middlewares?: MiddlewareDefinition[];
|
|
56
|
+
/**
|
|
57
|
+
* Run once the adapter is ready, with the socket.io server and its HTTP server (`undefined` when `io` is given
|
|
58
|
+
* without `server`).
|
|
59
|
+
* A failure fails the entrypoint start.
|
|
60
|
+
*/
|
|
61
|
+
listening?: (io: SocketIOServer, server: Server | undefined) => void | Promise<void>;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Get the socket.io socket that sent a service action's request, when {@link socketIOServiceEntrypoint} serves it (to
|
|
65
|
+
* read its handshake, join it to rooms, emit to it…).
|
|
66
|
+
* @param context The service action's context (`this.context` in a controller).
|
|
67
|
+
* @returns The socket, or `undefined` when another adapter serves the request.
|
|
68
|
+
* @example
|
|
69
|
+
* ```typescript
|
|
70
|
+
* import {Controller, ServiceContext} from 'lakutata/com/entrypoint'
|
|
71
|
+
* import {ServiceAction} from 'lakutata/decorator/ctrl'
|
|
72
|
+
* import {socketOfContext} from 'lakutata/adapter/socket.io'
|
|
73
|
+
*
|
|
74
|
+
* class RoomController extends Controller {
|
|
75
|
+
* //socket.emit('message', {cmd: 'room.join', room: 'lobby'}, (response) => …)
|
|
76
|
+
* @ServiceAction({cmd: 'room.join'})
|
|
77
|
+
* public async join(input: {room: string}): Promise<void> {
|
|
78
|
+
* await socketOfContext(this.context as ServiceContext)?.join(input.room)
|
|
79
|
+
* }
|
|
80
|
+
* }
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export declare function socketOfContext(context: ServiceContext): Socket | undefined;
|
|
84
|
+
/**
|
|
85
|
+
* Create a service adapter of an entrypoint served by socket.io. It needs the optional peer dependency `socket.io`
|
|
86
|
+
* (and `socket.io-client` on the clients).
|
|
87
|
+
*
|
|
88
|
+
* A request is an emit of the request event (`'message'` by default) whose data holds the action's pattern and input,
|
|
89
|
+
* with an acknowledgement callback receiving the response `{success, error, payload}` (see `ServiceResponseCodec`):
|
|
90
|
+
* `payload` is the result, or `error` the whole error object when the action failed (no matching action, validation…).
|
|
91
|
+
* An emit without an acknowledgement still runs the action.
|
|
92
|
+
*
|
|
93
|
+
* Each socket belongs to a client, identified by the id it gives when it connects (`auth.id`, else the `id` header,
|
|
94
|
+
* else the `id` query parameter; at most 200 characters), else by its socket id; its requests carry it as
|
|
95
|
+
* `ServiceContext.clientId`. The events pushed by `Entrypoint.notify` are emitted to every socket of the client, those
|
|
96
|
+
* of `Entrypoint.broadcast` to every socket of the namespace, with the event's arguments as the emit's arguments.
|
|
97
|
+
*
|
|
98
|
+
* When a socket disconnects, its requests in progress are aborted (their abort signal fires). When the entrypoint
|
|
99
|
+
* stops, the new requests are answered with an `EntrypointStoppedException` error; once the requests in progress end,
|
|
100
|
+
* the socket.io server is closed (the sockets disconnected, with the attached HTTP server left open).
|
|
101
|
+
* @param options The adapter options; by default it listens on port 3000 of all the interfaces with a server of its own.
|
|
102
|
+
* @returns The adapter, to give to the `service` option of `buildEntrypoints`.
|
|
103
|
+
* @example
|
|
104
|
+
* ```typescript
|
|
105
|
+
* import {Application} from 'lakutata'
|
|
106
|
+
* import {buildEntrypoints, Controller} from 'lakutata/com/entrypoint'
|
|
107
|
+
* import {ServiceAction} from 'lakutata/decorator/ctrl'
|
|
108
|
+
* import {socketIOServiceEntrypoint} from 'lakutata/adapter/socket.io'
|
|
109
|
+
* import {io} from 'socket.io-client'
|
|
110
|
+
*
|
|
111
|
+
* class EchoController extends Controller {
|
|
112
|
+
* @ServiceAction({cmd: 'echo'})
|
|
113
|
+
* public async echo(input: {text: string}): Promise<string> {
|
|
114
|
+
* return input.text
|
|
115
|
+
* }
|
|
116
|
+
* }
|
|
117
|
+
*
|
|
118
|
+
* Application.run({
|
|
119
|
+
* id: 'echo.service',
|
|
120
|
+
* name: 'Echo',
|
|
121
|
+
* components: {
|
|
122
|
+
* entrypoint: buildEntrypoints({
|
|
123
|
+
* controllers: [EchoController],
|
|
124
|
+
* service: socketIOServiceEntrypoint({port: 3000, options: {cors: {origin: '*'}}})
|
|
125
|
+
* })
|
|
126
|
+
* }
|
|
127
|
+
* })
|
|
128
|
+
*
|
|
129
|
+
* //A client, identified as client-1 (the events pushed by entrypoint.notify('client-1', 'progress', 50) reach it)
|
|
130
|
+
* const socket = io('http://localhost:3000', {auth: {id: 'client-1'}})
|
|
131
|
+
* socket.on('progress', (percent: number): void => console.log(`${percent}%`))
|
|
132
|
+
* socket.emit('message', {cmd: 'echo', text: 'hi'}, (response: {success: boolean, error: unknown, payload: unknown}): void => {
|
|
133
|
+
* //{success: true, error: null, payload: 'hi'}
|
|
134
|
+
* console.log(response)
|
|
135
|
+
* })
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
export declare function socketIOServiceEntrypoint(options?: SocketIOServiceEntrypointOptions): EntrypointAdapter<ServiceContext>;
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.socketOfContext = socketOfContext;
|
|
4
|
+
exports.socketIOServiceEntrypoint = socketIOServiceEntrypoint;
|
|
5
|
+
const socket_io_1 = require("socket.io");
|
|
6
|
+
const node_http_1 = require("node:http");
|
|
7
|
+
const ServiceContext_js_1 = require("../../context/ServiceContext.js");
|
|
8
|
+
const BuiltinChannels_js_1 = require("../../channel/BuiltinChannels.js");
|
|
9
|
+
const ServiceResponseCodec_js_1 = require("../../lib/ServiceResponseCodec.js");
|
|
10
|
+
const EntrypointStoppedException_js_1 = require("../../exceptions/EntrypointStoppedException.js");
|
|
11
|
+
/**
|
|
12
|
+
* Get the socket.io socket that sent a service action's request, when {@link socketIOServiceEntrypoint} serves it (to
|
|
13
|
+
* read its handshake, join it to rooms, emit to it…).
|
|
14
|
+
* @param context The service action's context (`this.context` in a controller).
|
|
15
|
+
* @returns The socket, or `undefined` when another adapter serves the request.
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* import {Controller, ServiceContext} from 'lakutata/com/entrypoint'
|
|
19
|
+
* import {ServiceAction} from 'lakutata/decorator/ctrl'
|
|
20
|
+
* import {socketOfContext} from 'lakutata/adapter/socket.io'
|
|
21
|
+
*
|
|
22
|
+
* class RoomController extends Controller {
|
|
23
|
+
* //socket.emit('message', {cmd: 'room.join', room: 'lobby'}, (response) => …)
|
|
24
|
+
* @ServiceAction({cmd: 'room.join'})
|
|
25
|
+
* public async join(input: {room: string}): Promise<void> {
|
|
26
|
+
* await socketOfContext(this.context as ServiceContext)?.join(input.room)
|
|
27
|
+
* }
|
|
28
|
+
* }
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
function socketOfContext(context) {
|
|
32
|
+
return context.socket;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The id of the client of a socket: the one it gives when it connects (its auth.id, its id header, its id query), its
|
|
36
|
+
* own id otherwise
|
|
37
|
+
* @param socket
|
|
38
|
+
*/
|
|
39
|
+
function clientIdOf(socket) {
|
|
40
|
+
const given = socket.handshake.auth?.id ?? socket.handshake.headers.id ?? socket.handshake.query.id;
|
|
41
|
+
const id = Array.isArray(given) ? given[0] : given;
|
|
42
|
+
return typeof id === 'string' && id && id.length <= 200 ? id : socket.id;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Create a service adapter of an entrypoint served by socket.io. It needs the optional peer dependency `socket.io`
|
|
46
|
+
* (and `socket.io-client` on the clients).
|
|
47
|
+
*
|
|
48
|
+
* A request is an emit of the request event (`'message'` by default) whose data holds the action's pattern and input,
|
|
49
|
+
* with an acknowledgement callback receiving the response `{success, error, payload}` (see `ServiceResponseCodec`):
|
|
50
|
+
* `payload` is the result, or `error` the whole error object when the action failed (no matching action, validation…).
|
|
51
|
+
* An emit without an acknowledgement still runs the action.
|
|
52
|
+
*
|
|
53
|
+
* Each socket belongs to a client, identified by the id it gives when it connects (`auth.id`, else the `id` header,
|
|
54
|
+
* else the `id` query parameter; at most 200 characters), else by its socket id; its requests carry it as
|
|
55
|
+
* `ServiceContext.clientId`. The events pushed by `Entrypoint.notify` are emitted to every socket of the client, those
|
|
56
|
+
* of `Entrypoint.broadcast` to every socket of the namespace, with the event's arguments as the emit's arguments.
|
|
57
|
+
*
|
|
58
|
+
* When a socket disconnects, its requests in progress are aborted (their abort signal fires). When the entrypoint
|
|
59
|
+
* stops, the new requests are answered with an `EntrypointStoppedException` error; once the requests in progress end,
|
|
60
|
+
* the socket.io server is closed (the sockets disconnected, with the attached HTTP server left open).
|
|
61
|
+
* @param options The adapter options; by default it listens on port 3000 of all the interfaces with a server of its own.
|
|
62
|
+
* @returns The adapter, to give to the `service` option of `buildEntrypoints`.
|
|
63
|
+
* @example
|
|
64
|
+
* ```typescript
|
|
65
|
+
* import {Application} from 'lakutata'
|
|
66
|
+
* import {buildEntrypoints, Controller} from 'lakutata/com/entrypoint'
|
|
67
|
+
* import {ServiceAction} from 'lakutata/decorator/ctrl'
|
|
68
|
+
* import {socketIOServiceEntrypoint} from 'lakutata/adapter/socket.io'
|
|
69
|
+
* import {io} from 'socket.io-client'
|
|
70
|
+
*
|
|
71
|
+
* class EchoController extends Controller {
|
|
72
|
+
* @ServiceAction({cmd: 'echo'})
|
|
73
|
+
* public async echo(input: {text: string}): Promise<string> {
|
|
74
|
+
* return input.text
|
|
75
|
+
* }
|
|
76
|
+
* }
|
|
77
|
+
*
|
|
78
|
+
* Application.run({
|
|
79
|
+
* id: 'echo.service',
|
|
80
|
+
* name: 'Echo',
|
|
81
|
+
* components: {
|
|
82
|
+
* entrypoint: buildEntrypoints({
|
|
83
|
+
* controllers: [EchoController],
|
|
84
|
+
* service: socketIOServiceEntrypoint({port: 3000, options: {cors: {origin: '*'}}})
|
|
85
|
+
* })
|
|
86
|
+
* }
|
|
87
|
+
* })
|
|
88
|
+
*
|
|
89
|
+
* //A client, identified as client-1 (the events pushed by entrypoint.notify('client-1', 'progress', 50) reach it)
|
|
90
|
+
* const socket = io('http://localhost:3000', {auth: {id: 'client-1'}})
|
|
91
|
+
* socket.on('progress', (percent: number): void => console.log(`${percent}%`))
|
|
92
|
+
* socket.emit('message', {cmd: 'echo', text: 'hi'}, (response: {success: boolean, error: unknown, payload: unknown}): void => {
|
|
93
|
+
* //{success: true, error: null, payload: 'hi'}
|
|
94
|
+
* console.log(response)
|
|
95
|
+
* })
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
function socketIOServiceEntrypoint(options = {}) {
|
|
99
|
+
const event = options.event ?? 'message';
|
|
100
|
+
let io;
|
|
101
|
+
let namespace;
|
|
102
|
+
let ownServer;
|
|
103
|
+
let sharedServer = false;
|
|
104
|
+
let stopping = false;
|
|
105
|
+
let onConnection;
|
|
106
|
+
//The sockets of each client (a client may connect several times)
|
|
107
|
+
const clients = new Map();
|
|
108
|
+
//The removal of the listeners the adapter added to each socket (the sockets of a given io outlive the adapter)
|
|
109
|
+
const detachers = new Map();
|
|
110
|
+
return {
|
|
111
|
+
channel: 'service',
|
|
112
|
+
definition: BuiltinChannels_js_1.ServiceChannel,
|
|
113
|
+
name: options.name,
|
|
114
|
+
middlewares: options.middlewares,
|
|
115
|
+
start: async (runtime) => {
|
|
116
|
+
const attachTo = typeof options.server === 'function' ? options.server() : options.server;
|
|
117
|
+
if (options.io) {
|
|
118
|
+
io = options.io;
|
|
119
|
+
}
|
|
120
|
+
else if (attachTo) {
|
|
121
|
+
io = new socket_io_1.Server(attachTo, options.options);
|
|
122
|
+
sharedServer = true;
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
ownServer = (0, node_http_1.createServer)();
|
|
126
|
+
io = new socket_io_1.Server(ownServer, options.options);
|
|
127
|
+
}
|
|
128
|
+
namespace = io.of(options.namespace ?? '/');
|
|
129
|
+
onConnection = (socket) => {
|
|
130
|
+
const clientId = clientIdOf(socket);
|
|
131
|
+
if (!clients.has(clientId))
|
|
132
|
+
clients.set(clientId, new Set());
|
|
133
|
+
clients.get(clientId).add(socket);
|
|
134
|
+
//The requests of a socket in progress, aborted when it disconnects
|
|
135
|
+
const requests = new Set();
|
|
136
|
+
const onDisconnect = () => {
|
|
137
|
+
requests.forEach((request) => request.abort());
|
|
138
|
+
clients.get(clientId)?.delete(socket);
|
|
139
|
+
if (!clients.get(clientId)?.size)
|
|
140
|
+
clients.delete(clientId);
|
|
141
|
+
detachers.delete(socket);
|
|
142
|
+
};
|
|
143
|
+
const onRequest = async (data, acknowledge) => {
|
|
144
|
+
const reply = (response) => {
|
|
145
|
+
if (typeof acknowledge === 'function')
|
|
146
|
+
acknowledge(response);
|
|
147
|
+
};
|
|
148
|
+
if (stopping)
|
|
149
|
+
return reply(ServiceResponseCodec_js_1.ServiceResponseCodec.encodeError(new EntrypointStoppedException_js_1.EntrypointStoppedException('The entrypoint is stopping')));
|
|
150
|
+
const request = new AbortController();
|
|
151
|
+
requests.add(request);
|
|
152
|
+
try {
|
|
153
|
+
const context = new ServiceContext_js_1.ServiceContext({ data: data && typeof data === 'object' ? data : {}, socket: socket, clientId: clientId });
|
|
154
|
+
reply(ServiceResponseCodec_js_1.ServiceResponseCodec.encode(await runtime.dispatch(context, { signal: request.signal }), false));
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
reply(ServiceResponseCodec_js_1.ServiceResponseCodec.encodeError(error));
|
|
158
|
+
}
|
|
159
|
+
finally {
|
|
160
|
+
requests.delete(request);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
socket.once('disconnect', onDisconnect);
|
|
164
|
+
const onEvent = (data, acknowledge) => void onRequest(data, acknowledge);
|
|
165
|
+
socket.on(event, onEvent);
|
|
166
|
+
detachers.set(socket, () => {
|
|
167
|
+
socket.off('disconnect', onDisconnect);
|
|
168
|
+
socket.off(event, onEvent);
|
|
169
|
+
});
|
|
170
|
+
};
|
|
171
|
+
namespace.on('connection', onConnection);
|
|
172
|
+
if (ownServer) {
|
|
173
|
+
//The adapter starts once its server listens (an occupied port fails the start)
|
|
174
|
+
await new Promise((resolve, reject) => {
|
|
175
|
+
ownServer.once('error', reject).listen(options.port ?? 3000, options.host, () => {
|
|
176
|
+
ownServer.off('error', reject);
|
|
177
|
+
resolve();
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
await options.listening?.(io, ownServer ?? attachTo);
|
|
182
|
+
},
|
|
183
|
+
stop: () => {
|
|
184
|
+
//The new requests are refused, the requests in progress end
|
|
185
|
+
stopping = true;
|
|
186
|
+
},
|
|
187
|
+
notify: (clientId, name, args) => {
|
|
188
|
+
const sockets = clients.get(clientId);
|
|
189
|
+
sockets?.forEach((socket) => socket.emit(name, ...args));
|
|
190
|
+
return !!sockets?.size;
|
|
191
|
+
},
|
|
192
|
+
broadcast: (name, args) => {
|
|
193
|
+
if (!namespace)
|
|
194
|
+
return 0;
|
|
195
|
+
namespace.emit(name, ...args);
|
|
196
|
+
return clients.size;
|
|
197
|
+
},
|
|
198
|
+
close: async () => {
|
|
199
|
+
clients.clear();
|
|
200
|
+
if (namespace && onConnection)
|
|
201
|
+
namespace.off('connection', onConnection);
|
|
202
|
+
//The sockets connected keep no listener of the adapter (they stay connected to a given io)
|
|
203
|
+
detachers.forEach((detach) => detach());
|
|
204
|
+
detachers.clear();
|
|
205
|
+
if (!io || options.io)
|
|
206
|
+
return;
|
|
207
|
+
if (sharedServer) {
|
|
208
|
+
//The server is closed by its owner: the clients are disconnected and the transports closed
|
|
209
|
+
io.disconnectSockets(true);
|
|
210
|
+
io.engine.close();
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
await new Promise((resolve) => void io.close(() => resolve()));
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ChannelDefinition } from './Channel.js';
|
|
2
|
+
/**
|
|
3
|
+
* The HTTP channel, addressed by route and method
|
|
4
|
+
*/
|
|
5
|
+
export declare const HTTPChannel: ChannelDefinition;
|
|
6
|
+
/**
|
|
7
|
+
* The CLI channel, addressed by command
|
|
8
|
+
*/
|
|
9
|
+
export declare const CLIChannel: ChannelDefinition;
|
|
10
|
+
/**
|
|
11
|
+
* The service channel, addressed by pattern
|
|
12
|
+
*/
|
|
13
|
+
export declare const ServiceChannel: ChannelDefinition;
|
|
14
|
+
/**
|
|
15
|
+
* The schedule channel: no external caller, each action is run by the timers of the schedule adapter.
|
|
16
|
+
* The address of an action is its schedule, with the key telling apart the actions of a same schedule
|
|
17
|
+
*/
|
|
18
|
+
export declare const ScheduleChannel: ChannelDefinition;
|