@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.
Files changed (325) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +183 -0
  3. package/THIRD_PARTY_NOTICES.md +206 -0
  4. package/dist/cjs/Entrypoint.d.ts +868 -0
  5. package/dist/cjs/Entrypoint.js +998 -0
  6. package/dist/cjs/adapter/commander/CommanderCLIEntrypoint.d.ts +192 -0
  7. package/dist/cjs/adapter/commander/CommanderCLIEntrypoint.js +439 -0
  8. package/dist/cjs/adapter/commander/Prompt.d.ts +22 -0
  9. package/dist/cjs/adapter/commander/Prompt.js +164 -0
  10. package/dist/cjs/adapter/commander/Width.d.ts +12 -0
  11. package/dist/cjs/adapter/commander/Width.js +67 -0
  12. package/dist/cjs/adapter/electron/ElectronIPCServiceEntrypoint.d.ts +203 -0
  13. package/dist/cjs/adapter/electron/ElectronIPCServiceEntrypoint.js +249 -0
  14. package/dist/cjs/adapter/express/ExpressHTTPEntrypoint.d.ts +172 -0
  15. package/dist/cjs/adapter/express/ExpressHTTPEntrypoint.js +301 -0
  16. package/dist/cjs/adapter/express/Responders.d.ts +89 -0
  17. package/dist/cjs/adapter/express/Responders.js +71 -0
  18. package/dist/cjs/adapter/express/StaticUI.d.ts +83 -0
  19. package/dist/cjs/adapter/express/StaticUI.js +104 -0
  20. package/dist/cjs/adapter/fastify/FastifyHTTPEntrypoint.d.ts +156 -0
  21. package/dist/cjs/adapter/fastify/FastifyHTTPEntrypoint.js +339 -0
  22. package/dist/cjs/adapter/fastify/Responders.d.ts +88 -0
  23. package/dist/cjs/adapter/fastify/Responders.js +77 -0
  24. package/dist/cjs/adapter/http/HTTPServiceEntrypoint.d.ts +117 -0
  25. package/dist/cjs/adapter/http/HTTPServiceEntrypoint.js +257 -0
  26. package/dist/cjs/adapter/jsonrpc/JSONRPCProtocol.d.ts +194 -0
  27. package/dist/cjs/adapter/jsonrpc/JSONRPCProtocol.js +187 -0
  28. package/dist/cjs/adapter/jsonrpc/JSONRPCServiceEntrypoint.d.ts +154 -0
  29. package/dist/cjs/adapter/jsonrpc/JSONRPCServiceEntrypoint.js +397 -0
  30. package/dist/cjs/adapter/schedule/ScheduleEntrypoint.d.ts +86 -0
  31. package/dist/cjs/adapter/schedule/ScheduleEntrypoint.js +275 -0
  32. package/dist/cjs/adapter/socket.io/SocketIOServiceEntrypoint.d.ts +138 -0
  33. package/dist/cjs/adapter/socket.io/SocketIOServiceEntrypoint.js +216 -0
  34. package/dist/cjs/channel/BuiltinChannels.d.ts +18 -0
  35. package/dist/cjs/channel/BuiltinChannels.js +128 -0
  36. package/dist/cjs/channel/Channel.d.ts +196 -0
  37. package/dist/cjs/channel/Channel.js +106 -0
  38. package/dist/cjs/channel/EntrypointAdapter.d.ts +195 -0
  39. package/dist/cjs/channel/EntrypointAdapter.js +43 -0
  40. package/dist/cjs/context/BaseContext.d.ts +75 -0
  41. package/dist/cjs/context/BaseContext.js +90 -0
  42. package/dist/cjs/context/CLIContext.d.ts +38 -0
  43. package/dist/cjs/context/CLIContext.js +53 -0
  44. package/dist/cjs/context/HTTPContext.d.ts +52 -0
  45. package/dist/cjs/context/HTTPContext.js +78 -0
  46. package/dist/cjs/context/ScheduleContext.d.ts +47 -0
  47. package/dist/cjs/context/ScheduleContext.js +65 -0
  48. package/dist/cjs/context/ServiceContext.d.ts +47 -0
  49. package/dist/cjs/context/ServiceContext.js +62 -0
  50. package/dist/cjs/decorators/CLIAction.d.ts +120 -0
  51. package/dist/cjs/decorators/CLIAction.js +13 -0
  52. package/dist/cjs/decorators/HTTPAction.d.ts +178 -0
  53. package/dist/cjs/decorators/HTTPAction.js +14 -0
  54. package/dist/cjs/decorators/Prefix.d.ts +44 -0
  55. package/dist/cjs/decorators/Prefix.js +48 -0
  56. package/dist/cjs/decorators/Schedule.d.ts +120 -0
  57. package/dist/cjs/decorators/Schedule.js +64 -0
  58. package/dist/cjs/decorators/ServiceAction.d.ts +116 -0
  59. package/dist/cjs/decorators/ServiceAction.js +12 -0
  60. package/dist/cjs/decorators/UseMiddlewares.d.ts +52 -0
  61. package/dist/cjs/decorators/UseMiddlewares.js +58 -0
  62. package/dist/cjs/decorators/http/DELETE.d.ts +87 -0
  63. package/dist/cjs/decorators/http/DELETE.js +7 -0
  64. package/dist/cjs/decorators/http/GET.d.ts +89 -0
  65. package/dist/cjs/decorators/http/GET.js +7 -0
  66. package/dist/cjs/decorators/http/HEAD.d.ts +84 -0
  67. package/dist/cjs/decorators/http/HEAD.js +7 -0
  68. package/dist/cjs/decorators/http/OPTIONS.d.ts +80 -0
  69. package/dist/cjs/decorators/http/OPTIONS.js +7 -0
  70. package/dist/cjs/decorators/http/PATCH.d.ts +90 -0
  71. package/dist/cjs/decorators/http/PATCH.js +7 -0
  72. package/dist/cjs/decorators/http/POST.d.ts +91 -0
  73. package/dist/cjs/decorators/http/POST.js +7 -0
  74. package/dist/cjs/decorators/http/PUT.d.ts +92 -0
  75. package/dist/cjs/decorators/http/PUT.js +7 -0
  76. package/dist/cjs/exceptions/AccessDeniedException.d.ts +10 -0
  77. package/dist/cjs/exceptions/AccessDeniedException.js +17 -0
  78. package/dist/cjs/exceptions/ControllerActionNotFoundException.d.ts +9 -0
  79. package/dist/cjs/exceptions/ControllerActionNotFoundException.js +16 -0
  80. package/dist/cjs/exceptions/DuplicateActionNameException.d.ts +8 -0
  81. package/dist/cjs/exceptions/DuplicateActionNameException.js +15 -0
  82. package/dist/cjs/exceptions/EntrypointStoppedException.d.ts +9 -0
  83. package/dist/cjs/exceptions/EntrypointStoppedException.js +16 -0
  84. package/dist/cjs/exceptions/InvalidActionGroupException.d.ts +4 -0
  85. package/dist/cjs/exceptions/InvalidActionGroupException.js +11 -0
  86. package/dist/cjs/exceptions/InvalidChannelException.d.ts +8 -0
  87. package/dist/cjs/exceptions/InvalidChannelException.js +15 -0
  88. package/dist/cjs/exceptions/InvalidEntrypointAdapterException.d.ts +9 -0
  89. package/dist/cjs/exceptions/InvalidEntrypointAdapterException.js +16 -0
  90. package/dist/cjs/exceptions/InvalidMiddlewareException.d.ts +8 -0
  91. package/dist/cjs/exceptions/InvalidMiddlewareException.js +15 -0
  92. package/dist/cjs/exceptions/InvalidPrefixException.d.ts +8 -0
  93. package/dist/cjs/exceptions/InvalidPrefixException.js +15 -0
  94. package/dist/cjs/exceptions/MiddlewareNextCalledException.d.ts +8 -0
  95. package/dist/cjs/exceptions/MiddlewareNextCalledException.js +15 -0
  96. package/dist/cjs/exceptions/RouteNotFoundException.d.ts +9 -0
  97. package/dist/cjs/exceptions/RouteNotFoundException.js +16 -0
  98. package/dist/cjs/exceptions/ServiceInvokeException.d.ts +11 -0
  99. package/dist/cjs/exceptions/ServiceInvokeException.js +14 -0
  100. package/dist/cjs/exports/CTRLDecorators.d.ts +13 -0
  101. package/dist/cjs/exports/CTRLDecorators.js +38 -0
  102. package/dist/cjs/exports/Channel.d.ts +14 -0
  103. package/dist/cjs/exports/Channel.js +33 -0
  104. package/dist/cjs/exports/CommanderAdapter.d.ts +1 -0
  105. package/dist/cjs/exports/CommanderAdapter.js +8 -0
  106. package/dist/cjs/exports/ElectronAdapter.d.ts +1 -0
  107. package/dist/cjs/exports/ElectronAdapter.js +9 -0
  108. package/dist/cjs/exports/Entrypoint.d.ts +24 -0
  109. package/dist/cjs/exports/Entrypoint.js +55 -0
  110. package/dist/cjs/exports/ExpressAdapter.d.ts +4 -0
  111. package/dist/cjs/exports/ExpressAdapter.js +15 -0
  112. package/dist/cjs/exports/FastifyAdapter.d.ts +4 -0
  113. package/dist/cjs/exports/FastifyAdapter.js +14 -0
  114. package/dist/cjs/exports/JSONRPCAdapter.d.ts +2 -0
  115. package/dist/cjs/exports/JSONRPCAdapter.js +11 -0
  116. package/dist/cjs/exports/SocketIOAdapter.d.ts +1 -0
  117. package/dist/cjs/exports/SocketIOAdapter.js +9 -0
  118. package/dist/cjs/lib/AccessControl.d.ts +118 -0
  119. package/dist/cjs/lib/AccessControl.js +208 -0
  120. package/dist/cjs/lib/AccessControlRule.d.ts +91 -0
  121. package/dist/cjs/lib/AccessControlRule.js +76 -0
  122. package/dist/cjs/lib/ActionOptions.d.ts +67 -0
  123. package/dist/cjs/lib/ActionOptions.js +67 -0
  124. package/dist/cjs/lib/ActionResult.d.ts +28 -0
  125. package/dist/cjs/lib/ActionResult.js +28 -0
  126. package/dist/cjs/lib/ApplicationOf.d.ts +6 -0
  127. package/dist/cjs/lib/ApplicationOf.js +14 -0
  128. package/dist/cjs/lib/Controller.d.ts +59 -0
  129. package/dist/cjs/lib/Controller.js +64 -0
  130. package/dist/cjs/lib/ControllerEntrypoint.d.ts +168 -0
  131. package/dist/cjs/lib/ControllerEntrypoint.js +239 -0
  132. package/dist/cjs/lib/GetActionDTOAndOptions.d.ts +29 -0
  133. package/dist/cjs/lib/GetActionDTOAndOptions.js +49 -0
  134. package/dist/cjs/lib/HTTPErrors.d.ts +55 -0
  135. package/dist/cjs/lib/HTTPErrors.js +52 -0
  136. package/dist/cjs/lib/Middleware.d.ts +146 -0
  137. package/dist/cjs/lib/Middleware.js +144 -0
  138. package/dist/cjs/lib/Prefix.d.ts +98 -0
  139. package/dist/cjs/lib/Prefix.js +151 -0
  140. package/dist/cjs/lib/Result.d.ts +173 -0
  141. package/dist/cjs/lib/Result.js +153 -0
  142. package/dist/cjs/lib/ServiceResponseCodec.d.ts +82 -0
  143. package/dist/cjs/lib/ServiceResponseCodec.js +117 -0
  144. package/dist/cjs/lib/StringifyPattern.d.ts +7 -0
  145. package/dist/cjs/lib/StringifyPattern.js +58 -0
  146. package/dist/cjs/lib/UnsetValue.d.ts +12 -0
  147. package/dist/cjs/lib/UnsetValue.js +216 -0
  148. package/dist/cjs/lib/pattern/GlobPattern.d.ts +34 -0
  149. package/dist/cjs/lib/pattern/GlobPattern.js +68 -0
  150. package/dist/cjs/lib/pattern/PatternManager.d.ts +103 -0
  151. package/dist/cjs/lib/pattern/PatternManager.js +233 -0
  152. package/dist/cjs/lib/schedule/Cron.d.ts +56 -0
  153. package/dist/cjs/lib/schedule/Cron.js +282 -0
  154. package/dist/cjs/package.json +1 -0
  155. package/dist/cjs/types/CLIContextParams.d.ts +14 -0
  156. package/dist/cjs/types/CLIContextParams.js +2 -0
  157. package/dist/cjs/types/ErrorDescription.d.ts +14 -0
  158. package/dist/cjs/types/ErrorDescription.js +2 -0
  159. package/dist/cjs/types/HTTPContextParams.d.ts +27 -0
  160. package/dist/cjs/types/HTTPContextParams.js +2 -0
  161. package/dist/cjs/types/ScheduleContextParams.d.ts +21 -0
  162. package/dist/cjs/types/ScheduleContextParams.js +2 -0
  163. package/dist/cjs/types/ServiceContextParams.d.ts +16 -0
  164. package/dist/cjs/types/ServiceContextParams.js +2 -0
  165. package/dist/esm/Entrypoint.js +985 -0
  166. package/dist/esm/adapter/commander/CommanderCLIEntrypoint.js +433 -0
  167. package/dist/esm/adapter/commander/Prompt.js +127 -0
  168. package/dist/esm/adapter/commander/Width.js +63 -0
  169. package/dist/esm/adapter/electron/ElectronIPCServiceEntrypoint.js +212 -0
  170. package/dist/esm/adapter/express/ExpressHTTPEntrypoint.js +295 -0
  171. package/dist/esm/adapter/express/Responders.js +66 -0
  172. package/dist/esm/adapter/express/StaticUI.js +96 -0
  173. package/dist/esm/adapter/fastify/FastifyHTTPEntrypoint.js +333 -0
  174. package/dist/esm/adapter/fastify/Responders.js +73 -0
  175. package/dist/esm/adapter/http/HTTPServiceEntrypoint.js +253 -0
  176. package/dist/esm/adapter/jsonrpc/JSONRPCProtocol.js +182 -0
  177. package/dist/esm/adapter/jsonrpc/JSONRPCServiceEntrypoint.js +361 -0
  178. package/dist/esm/adapter/schedule/ScheduleEntrypoint.js +272 -0
  179. package/dist/esm/adapter/socket.io/SocketIOServiceEntrypoint.js +212 -0
  180. package/dist/esm/channel/BuiltinChannels.js +125 -0
  181. package/dist/esm/channel/Channel.js +100 -0
  182. package/dist/esm/channel/EntrypointAdapter.js +39 -0
  183. package/dist/esm/context/BaseContext.js +87 -0
  184. package/dist/esm/context/CLIContext.js +49 -0
  185. package/dist/esm/context/HTTPContext.js +74 -0
  186. package/dist/esm/context/ScheduleContext.js +61 -0
  187. package/dist/esm/context/ServiceContext.js +58 -0
  188. package/dist/esm/decorators/CLIAction.js +10 -0
  189. package/dist/esm/decorators/HTTPAction.js +11 -0
  190. package/dist/esm/decorators/Prefix.js +45 -0
  191. package/dist/esm/decorators/Schedule.js +61 -0
  192. package/dist/esm/decorators/ServiceAction.js +9 -0
  193. package/dist/esm/decorators/UseMiddlewares.js +55 -0
  194. package/dist/esm/decorators/http/DELETE.js +4 -0
  195. package/dist/esm/decorators/http/GET.js +4 -0
  196. package/dist/esm/decorators/http/HEAD.js +4 -0
  197. package/dist/esm/decorators/http/OPTIONS.js +4 -0
  198. package/dist/esm/decorators/http/PATCH.js +4 -0
  199. package/dist/esm/decorators/http/POST.js +4 -0
  200. package/dist/esm/decorators/http/PUT.js +4 -0
  201. package/dist/esm/exceptions/AccessDeniedException.js +13 -0
  202. package/dist/esm/exceptions/ControllerActionNotFoundException.js +12 -0
  203. package/dist/esm/exceptions/DuplicateActionNameException.js +11 -0
  204. package/dist/esm/exceptions/EntrypointStoppedException.js +12 -0
  205. package/dist/esm/exceptions/InvalidActionGroupException.js +7 -0
  206. package/dist/esm/exceptions/InvalidChannelException.js +11 -0
  207. package/dist/esm/exceptions/InvalidEntrypointAdapterException.js +12 -0
  208. package/dist/esm/exceptions/InvalidMiddlewareException.js +11 -0
  209. package/dist/esm/exceptions/InvalidPrefixException.js +11 -0
  210. package/dist/esm/exceptions/MiddlewareNextCalledException.js +11 -0
  211. package/dist/esm/exceptions/RouteNotFoundException.js +12 -0
  212. package/dist/esm/exceptions/ServiceInvokeException.js +10 -0
  213. package/dist/esm/exports/CTRLDecorators.js +19 -0
  214. package/dist/esm/exports/Channel.js +11 -0
  215. package/dist/esm/exports/CommanderAdapter.js +1 -0
  216. package/dist/esm/exports/ElectronAdapter.js +1 -0
  217. package/dist/esm/exports/Entrypoint.js +22 -0
  218. package/dist/esm/exports/ExpressAdapter.js +3 -0
  219. package/dist/esm/exports/FastifyAdapter.js +3 -0
  220. package/dist/esm/exports/JSONRPCAdapter.js +2 -0
  221. package/dist/esm/exports/SocketIOAdapter.js +1 -0
  222. package/dist/esm/lib/AccessControl.js +204 -0
  223. package/dist/esm/lib/AccessControlRule.js +72 -0
  224. package/dist/esm/lib/ActionOptions.js +63 -0
  225. package/dist/esm/lib/ActionResult.js +24 -0
  226. package/dist/esm/lib/ApplicationOf.js +11 -0
  227. package/dist/esm/lib/Controller.js +61 -0
  228. package/dist/esm/lib/ControllerEntrypoint.js +229 -0
  229. package/dist/esm/lib/GetActionDTOAndOptions.js +46 -0
  230. package/dist/esm/lib/HTTPErrors.js +48 -0
  231. package/dist/esm/lib/Middleware.js +134 -0
  232. package/dist/esm/lib/Prefix.js +143 -0
  233. package/dist/esm/lib/Result.js +147 -0
  234. package/dist/esm/lib/ServiceResponseCodec.js +113 -0
  235. package/dist/esm/lib/StringifyPattern.js +55 -0
  236. package/dist/esm/lib/UnsetValue.js +213 -0
  237. package/dist/esm/lib/pattern/GlobPattern.js +64 -0
  238. package/dist/esm/lib/pattern/PatternManager.js +229 -0
  239. package/dist/esm/lib/schedule/Cron.js +277 -0
  240. package/dist/esm/types/CLIContextParams.js +1 -0
  241. package/dist/esm/types/ErrorDescription.js +1 -0
  242. package/dist/esm/types/HTTPContextParams.js +1 -0
  243. package/dist/esm/types/ScheduleContextParams.js +1 -0
  244. package/dist/esm/types/ServiceContextParams.js +1 -0
  245. package/dist/types/Entrypoint.d.ts +868 -0
  246. package/dist/types/adapter/commander/CommanderCLIEntrypoint.d.ts +192 -0
  247. package/dist/types/adapter/commander/Prompt.d.ts +22 -0
  248. package/dist/types/adapter/commander/Width.d.ts +12 -0
  249. package/dist/types/adapter/electron/ElectronIPCServiceEntrypoint.d.ts +203 -0
  250. package/dist/types/adapter/express/ExpressHTTPEntrypoint.d.ts +172 -0
  251. package/dist/types/adapter/express/Responders.d.ts +89 -0
  252. package/dist/types/adapter/express/StaticUI.d.ts +83 -0
  253. package/dist/types/adapter/fastify/FastifyHTTPEntrypoint.d.ts +156 -0
  254. package/dist/types/adapter/fastify/Responders.d.ts +88 -0
  255. package/dist/types/adapter/http/HTTPServiceEntrypoint.d.ts +117 -0
  256. package/dist/types/adapter/jsonrpc/JSONRPCProtocol.d.ts +194 -0
  257. package/dist/types/adapter/jsonrpc/JSONRPCServiceEntrypoint.d.ts +154 -0
  258. package/dist/types/adapter/schedule/ScheduleEntrypoint.d.ts +86 -0
  259. package/dist/types/adapter/socket.io/SocketIOServiceEntrypoint.d.ts +138 -0
  260. package/dist/types/channel/BuiltinChannels.d.ts +18 -0
  261. package/dist/types/channel/Channel.d.ts +196 -0
  262. package/dist/types/channel/EntrypointAdapter.d.ts +195 -0
  263. package/dist/types/context/BaseContext.d.ts +75 -0
  264. package/dist/types/context/CLIContext.d.ts +38 -0
  265. package/dist/types/context/HTTPContext.d.ts +52 -0
  266. package/dist/types/context/ScheduleContext.d.ts +47 -0
  267. package/dist/types/context/ServiceContext.d.ts +47 -0
  268. package/dist/types/decorators/CLIAction.d.ts +120 -0
  269. package/dist/types/decorators/HTTPAction.d.ts +178 -0
  270. package/dist/types/decorators/Prefix.d.ts +44 -0
  271. package/dist/types/decorators/Schedule.d.ts +120 -0
  272. package/dist/types/decorators/ServiceAction.d.ts +116 -0
  273. package/dist/types/decorators/UseMiddlewares.d.ts +52 -0
  274. package/dist/types/decorators/http/DELETE.d.ts +87 -0
  275. package/dist/types/decorators/http/GET.d.ts +89 -0
  276. package/dist/types/decorators/http/HEAD.d.ts +84 -0
  277. package/dist/types/decorators/http/OPTIONS.d.ts +80 -0
  278. package/dist/types/decorators/http/PATCH.d.ts +90 -0
  279. package/dist/types/decorators/http/POST.d.ts +91 -0
  280. package/dist/types/decorators/http/PUT.d.ts +92 -0
  281. package/dist/types/exceptions/AccessDeniedException.d.ts +10 -0
  282. package/dist/types/exceptions/ControllerActionNotFoundException.d.ts +9 -0
  283. package/dist/types/exceptions/DuplicateActionNameException.d.ts +8 -0
  284. package/dist/types/exceptions/EntrypointStoppedException.d.ts +9 -0
  285. package/dist/types/exceptions/InvalidActionGroupException.d.ts +4 -0
  286. package/dist/types/exceptions/InvalidChannelException.d.ts +8 -0
  287. package/dist/types/exceptions/InvalidEntrypointAdapterException.d.ts +9 -0
  288. package/dist/types/exceptions/InvalidMiddlewareException.d.ts +8 -0
  289. package/dist/types/exceptions/InvalidPrefixException.d.ts +8 -0
  290. package/dist/types/exceptions/MiddlewareNextCalledException.d.ts +8 -0
  291. package/dist/types/exceptions/RouteNotFoundException.d.ts +9 -0
  292. package/dist/types/exceptions/ServiceInvokeException.d.ts +11 -0
  293. package/dist/types/exports/CTRLDecorators.d.ts +13 -0
  294. package/dist/types/exports/Channel.d.ts +14 -0
  295. package/dist/types/exports/CommanderAdapter.d.ts +1 -0
  296. package/dist/types/exports/ElectronAdapter.d.ts +1 -0
  297. package/dist/types/exports/Entrypoint.d.ts +24 -0
  298. package/dist/types/exports/ExpressAdapter.d.ts +4 -0
  299. package/dist/types/exports/FastifyAdapter.d.ts +4 -0
  300. package/dist/types/exports/JSONRPCAdapter.d.ts +2 -0
  301. package/dist/types/exports/SocketIOAdapter.d.ts +1 -0
  302. package/dist/types/lib/AccessControl.d.ts +118 -0
  303. package/dist/types/lib/AccessControlRule.d.ts +91 -0
  304. package/dist/types/lib/ActionOptions.d.ts +67 -0
  305. package/dist/types/lib/ActionResult.d.ts +28 -0
  306. package/dist/types/lib/ApplicationOf.d.ts +6 -0
  307. package/dist/types/lib/Controller.d.ts +59 -0
  308. package/dist/types/lib/ControllerEntrypoint.d.ts +168 -0
  309. package/dist/types/lib/GetActionDTOAndOptions.d.ts +29 -0
  310. package/dist/types/lib/HTTPErrors.d.ts +55 -0
  311. package/dist/types/lib/Middleware.d.ts +146 -0
  312. package/dist/types/lib/Prefix.d.ts +98 -0
  313. package/dist/types/lib/Result.d.ts +173 -0
  314. package/dist/types/lib/ServiceResponseCodec.d.ts +82 -0
  315. package/dist/types/lib/StringifyPattern.d.ts +7 -0
  316. package/dist/types/lib/UnsetValue.d.ts +12 -0
  317. package/dist/types/lib/pattern/GlobPattern.d.ts +34 -0
  318. package/dist/types/lib/pattern/PatternManager.d.ts +103 -0
  319. package/dist/types/lib/schedule/Cron.d.ts +56 -0
  320. package/dist/types/types/CLIContextParams.d.ts +14 -0
  321. package/dist/types/types/ErrorDescription.d.ts +14 -0
  322. package/dist/types/types/HTTPContextParams.d.ts +27 -0
  323. package/dist/types/types/ScheduleContextParams.d.ts +21 -0
  324. package/dist/types/types/ServiceContextParams.d.ts +16 -0
  325. package/package.json +152 -0
@@ -0,0 +1,333 @@
1
+ import Fastify from 'fastify';
2
+ import { createReadStream } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { HTTPContext } from '../../context/HTTPContext.js';
5
+ import { HTTPChannel } from '../../channel/BuiltinChannels.js';
6
+ import { EventsResult, FileResult, StatusResult } from '../../lib/Result.js';
7
+ import { RouteNotFoundException } from '../../exceptions/RouteNotFoundException.js';
8
+ import { FastifyJSONResponder } from './Responders.js';
9
+ /**
10
+ * The content types of the common file extensions
11
+ */
12
+ const CONTENT_TYPES = {
13
+ '.txt': 'text/plain; charset=utf-8',
14
+ '.html': 'text/html; charset=utf-8',
15
+ '.css': 'text/css; charset=utf-8',
16
+ '.js': 'text/javascript; charset=utf-8',
17
+ '.json': 'application/json; charset=utf-8',
18
+ '.csv': 'text/csv; charset=utf-8',
19
+ '.xml': 'application/xml; charset=utf-8',
20
+ '.png': 'image/png',
21
+ '.jpg': 'image/jpeg',
22
+ '.jpeg': 'image/jpeg',
23
+ '.gif': 'image/gif',
24
+ '.svg': 'image/svg+xml',
25
+ '.webp': 'image/webp',
26
+ '.pdf': 'application/pdf',
27
+ '.zip': 'application/zip'
28
+ };
29
+ /**
30
+ * The first segments of the routes
31
+ * @param routes
32
+ */
33
+ function actionPrefixesOf(routes) {
34
+ const prefixes = new Set();
35
+ for (const route of routes) {
36
+ const segment = route.split('/').find((part) => !!part);
37
+ //A route starting with a parameter matches every path
38
+ if (segment && !/^[:*]/.test(segment))
39
+ prefixes.add(`/${segment}`);
40
+ }
41
+ return [...prefixes];
42
+ }
43
+ /**
44
+ * The data of an event of a stream
45
+ * @param data
46
+ * @param event
47
+ * @param id
48
+ */
49
+ function eventFrame(data, event, id) {
50
+ let frame = '';
51
+ if (id !== undefined)
52
+ frame += `id: ${id}\n`;
53
+ if (event !== undefined)
54
+ frame += `event: ${event}\n`;
55
+ const text = typeof data === 'string' ? data : JSON.stringify(data);
56
+ for (const line of text.split(/\r?\n/))
57
+ frame += `data: ${line}\n`;
58
+ return `${frame}\n`;
59
+ }
60
+ /**
61
+ * Whether a value is a fastify instance
62
+ * @param value
63
+ */
64
+ function isFastifyInstance(value) {
65
+ return !!value && typeof value.route === 'function' && typeof value.listen === 'function';
66
+ }
67
+ /**
68
+ * Create the HTTP adapter of an entrypoint served by a fastify instance: each HTTP action (`@GET`, `@POST`,
69
+ * `@HTTPAction`…) becomes a fastify route. It needs the optional peer dependency `fastify`.
70
+ *
71
+ * An action's input is the request's query and body (see the `input` option) with the route parameters merged over
72
+ * them. Its result is written by the responder (JSON by default, 204 for no result); a `StatusResult` sets the status
73
+ * and the headers, a `FileResult` sends a file, a buffer or a stream (its content type guessed from the extension of
74
+ * its file name, 304 when its ETag matches `If-None-Match`), and an `EventsResult` streams Server-Sent Events. An action
75
+ * that writes the response itself (through `HTTPContext.response`) is left alone. The errors, those of fastify included
76
+ * (a body too large: 413, a malformed JSON body: 400), are written by the responder with their status (500 by default).
77
+ * A GET action also answers HEAD, unless a HEAD action is declared for its route.
78
+ *
79
+ * When the client disconnects before the response, the action's abort signal fires and nothing is written. When the
80
+ * entrypoint stops, fastify stops accepting connections and answers the new requests with 503, the event streams end,
81
+ * and the requests in progress are given up to one second after they settle to send their responses before the
82
+ * remaining connections are closed.
83
+ * @param options The adapter options; by default it listens on port 80 with a new fastify instance.
84
+ * @returns The adapter, to give to the `http` option of `buildEntrypoints`.
85
+ * @example
86
+ * ```typescript
87
+ * import {Application} from 'lakutata'
88
+ * import {buildEntrypoints, Controller} from 'lakutata/com/entrypoint'
89
+ * import {GET, POST} from 'lakutata/decorator/ctrl'
90
+ * import {fastifyHTTPEntrypoint} from 'lakutata/adapter/fastify'
91
+ *
92
+ * class UserController extends Controller {
93
+ * //GET /users/1?verbose=true: the route parameter id merged over the query
94
+ * @GET('/users/:id')
95
+ * public async get(input: {id: string, verbose?: string}): Promise<{id: string}> {
96
+ * return {id: input.id}
97
+ * }
98
+ *
99
+ * //POST /users with the JSON body {"name": "Ada"}
100
+ * @POST('/users')
101
+ * public async create(input: {name: string}): Promise<{name: string}> {
102
+ * return {name: input.name}
103
+ * }
104
+ * }
105
+ *
106
+ * Application.run({
107
+ * id: 'web.app',
108
+ * name: 'Web',
109
+ * components: {
110
+ * entrypoint: buildEntrypoints({
111
+ * controllers: [UserController],
112
+ * http: fastifyHTTPEntrypoint({
113
+ * port: 8080,
114
+ * host: '0.0.0.0',
115
+ * fastify: {bodyLimit: 5 * 1024 * 1024, trustProxy: true},
116
+ * //The application's own routes
117
+ * before: (app): void => {
118
+ * app.get('/health', async (): Promise<string> => 'ok')
119
+ * }
120
+ * })
121
+ * })
122
+ * }
123
+ * })
124
+ * ```
125
+ */
126
+ export function fastifyHTTPEntrypoint(options = {}) {
127
+ const respond = options.respond ?? FastifyJSONResponder;
128
+ const input = options.input ?? ((request) => ({
129
+ ...(request.query && typeof request.query === 'object' ? request.query : {}),
130
+ ...(request.body && typeof request.body === 'object' && !Array.isArray(request.body) ? request.body : {})
131
+ }));
132
+ const streams = new Set();
133
+ let app;
134
+ let closed;
135
+ let stopping = false;
136
+ //The responses not sent yet
137
+ const responding = new Set();
138
+ /**
139
+ * Stream the events of a result
140
+ * @param result
141
+ * @param reply
142
+ */
143
+ const streamEvents = async (result, reply) => {
144
+ const abortController = new AbortController();
145
+ streams.add(abortController);
146
+ reply.hijack();
147
+ const response = reply.raw;
148
+ response.writeHead(200, {
149
+ ...reply.getHeaders(),
150
+ 'Content-Type': 'text/event-stream; charset=utf-8',
151
+ 'Cache-Control': 'no-cache, no-transform',
152
+ 'Connection': 'keep-alive',
153
+ 'X-Accel-Buffering': 'no'
154
+ });
155
+ response.flushHeaders();
156
+ const heartbeat = setInterval(() => response.write(': ping\n\n'), options.heartbeat ?? 15000);
157
+ const close = () => abortController.abort();
158
+ response.on('close', close);
159
+ try {
160
+ await result.producer((data, event, id) => {
161
+ if (!abortController.signal.aborted)
162
+ response.write(eventFrame(data, event, id));
163
+ }, abortController.signal);
164
+ }
165
+ catch {
166
+ //The stream is already started, its error ends it
167
+ }
168
+ finally {
169
+ clearInterval(heartbeat);
170
+ response.off('close', close);
171
+ streams.delete(abortController);
172
+ if (!response.writableEnded)
173
+ response.end();
174
+ }
175
+ };
176
+ /**
177
+ * Send a file
178
+ * @param result
179
+ * @param request
180
+ * @param reply
181
+ */
182
+ const sendFile = async (result, request, reply) => {
183
+ const { contentType, filename, etag, headers } = result.options;
184
+ if (headers)
185
+ reply.headers(headers);
186
+ if (etag) {
187
+ reply.header('ETag', `"${etag}"`);
188
+ if (request.headers['if-none-match'] === `"${etag}"`) {
189
+ await reply.code(304).send();
190
+ return;
191
+ }
192
+ }
193
+ const source = result.source;
194
+ const name = filename ?? (typeof source === 'string' ? path.basename(source) : undefined);
195
+ if (filename)
196
+ reply.header('Content-Disposition', `attachment; filename="${encodeURIComponent(filename)}"`);
197
+ reply.type(contentType ?? (name && CONTENT_TYPES[path.extname(name).toLowerCase()]) ?? 'application/octet-stream');
198
+ await reply.send(typeof source === 'string' ? createReadStream(path.resolve(source)) : source);
199
+ };
200
+ /**
201
+ * Write the result of an action
202
+ * @param result
203
+ * @param request
204
+ * @param reply
205
+ */
206
+ const output = async (result, request, reply) => {
207
+ //The action wrote the response itself
208
+ if (reply.sent || reply.raw.headersSent)
209
+ return;
210
+ if (result instanceof EventsResult)
211
+ return await streamEvents(result, reply);
212
+ if (result instanceof FileResult)
213
+ return await sendFile(result, request, reply);
214
+ if (result instanceof StatusResult) {
215
+ reply.code(result.status).headers(result.headers);
216
+ return respond.success(reply, result.data, request);
217
+ }
218
+ respond.success(reply, result, request);
219
+ };
220
+ /**
221
+ * The handler of the requests of an action
222
+ * @param runtime
223
+ * @param action
224
+ */
225
+ const actionHandler = (runtime, action) => async (request, reply) => {
226
+ const abortController = new AbortController();
227
+ //The client left before the response was sent
228
+ reply.raw.once('close', () => {
229
+ if (!reply.raw.writableFinished)
230
+ abortController.abort();
231
+ });
232
+ const context = new HTTPContext({
233
+ route: action.address.route,
234
+ method: action.address.method,
235
+ request: request.raw,
236
+ response: reply.raw,
237
+ data: input(request)
238
+ });
239
+ try {
240
+ const result = await runtime.dispatchTo(action, context, { signal: abortController.signal, params: { ...request.params } });
241
+ if (!abortController.signal.aborted)
242
+ await output(result, request, reply);
243
+ }
244
+ catch (error) {
245
+ if (!reply.sent && !reply.raw.headersSent && !abortController.signal.aborted)
246
+ respond.error(reply, error, request, 'action');
247
+ }
248
+ //The client left: nothing is sent, the request is done for fastify (it is not waited for when fastify closes)
249
+ if (abortController.signal.aborted && !reply.sent) {
250
+ reply.hijack();
251
+ reply.raw.destroy();
252
+ }
253
+ return reply;
254
+ };
255
+ return {
256
+ channel: 'http',
257
+ definition: HTTPChannel,
258
+ name: options.name,
259
+ middlewares: options.middlewares,
260
+ start: async (runtime) => {
261
+ const fastify = isFastifyInstance(options.fastify) ? options.fastify : Fastify(options.fastify ?? {});
262
+ app = fastify;
263
+ const routes = new Map();
264
+ for (const action of runtime.actions)
265
+ routes.set(action.address.route, (routes.get(action.address.route) ?? new Set()).add(String(action.address.method).toUpperCase()));
266
+ const actionPrefixes = actionPrefixesOf(routes.keys());
267
+ const isActionPath = (requestPath) => actionPrefixes.some((prefix) => requestPath === prefix || requestPath.startsWith(`${prefix}/`));
268
+ await options.before?.(fastify, { module: runtime.module, server: fastify.server, routes: routes, actionPrefixes: actionPrefixes, isActionPath: isActionPath });
269
+ for (const action of runtime.actions) {
270
+ const method = String(action.address.method).toUpperCase();
271
+ fastify.route({
272
+ method: method,
273
+ url: action.address.route,
274
+ //A HEAD action of the route replaces the HEAD route fastify adds to a GET route
275
+ exposeHeadRoute: method === 'GET' && !routes.get(action.address.route).has('HEAD'),
276
+ handler: actionHandler(runtime, action)
277
+ });
278
+ }
279
+ fastify.setNotFoundHandler((request, reply) => {
280
+ const requestPath = request.url.split('?')[0];
281
+ if (options.fallback && !isActionPath(requestPath))
282
+ return options.fallback(request, reply);
283
+ return respond.error(reply, new RouteNotFoundException('Route {route} not found', { route: requestPath }), request, 'request');
284
+ });
285
+ //The responses in progress are waited for when the adapter closes
286
+ fastify.addHook('onRequest', async (request, reply) => {
287
+ const responded = new Promise((resolve) => void reply.raw.once('close', resolve));
288
+ responding.add(responded);
289
+ //Only resolved
290
+ void responded.then(() => responding.delete(responded));
291
+ });
292
+ //While the adapter stops, the connections are closed once their response is sent
293
+ fastify.addHook('onResponse', async () => {
294
+ if (stopping)
295
+ setImmediate(() => fastify.server.closeIdleConnections());
296
+ });
297
+ fastify.setErrorHandler((error, request, reply) => respond.error(reply, error, request, 'request'));
298
+ //The adapter starts once the server listens (an occupied port fails the start)
299
+ //Every interface by default, as the other adapters (fastify listens on localhost only)
300
+ if (options.host !== undefined) {
301
+ await fastify.listen({ port: options.port ?? 80, host: options.host });
302
+ }
303
+ else {
304
+ try {
305
+ await fastify.listen({ port: options.port ?? 80, host: '::' });
306
+ }
307
+ catch (error) {
308
+ if (!['EAFNOSUPPORT', 'EADDRNOTAVAIL'].includes(error.code ?? ''))
309
+ throw error;
310
+ await fastify.listen({ port: options.port ?? 80, host: '0.0.0.0' });
311
+ }
312
+ }
313
+ },
314
+ stop: () => {
315
+ //The event streams end; fastify stops accepting connections, closes the idle ones and answers 503 to the new requests
316
+ stopping = true;
317
+ streams.forEach((stream) => stream.abort());
318
+ closed = app?.close();
319
+ },
320
+ close: async () => {
321
+ //The requests in progress ended or were aborted: their responses are given a second to be sent, then the remaining connections are closed
322
+ //(a connection opened without a request would hold the close of the server)
323
+ let timer;
324
+ await Promise.race([Promise.all(responding), new Promise((resolve) => {
325
+ timer = setTimeout(resolve, 1000);
326
+ })]);
327
+ clearTimeout(timer);
328
+ app?.server.closeAllConnections();
329
+ await closed;
330
+ await options.stop?.();
331
+ }
332
+ };
333
+ }
@@ -0,0 +1,73 @@
1
+ import { describeError, statusOfError } from '../../lib/HTTPErrors.js';
2
+ /**
3
+ * Send JSON
4
+ * @param reply
5
+ * @param data
6
+ */
7
+ function sendJSON(reply, data) {
8
+ reply.type('application/json; charset=utf-8').send(JSON.stringify(data));
9
+ }
10
+ /**
11
+ * The default responder of {@link fastifyHTTPEntrypoint}: a result is sent as JSON, no result as an empty 204 (or the
12
+ * status set by a `StatusResult`); an error is sent as `{errno, errMsg}` (see `describeError`) with its HTTP status
13
+ * (see `statusOfError`: the first 4xx status in its cause chain, its own status, or 500).
14
+ */
15
+ export const FastifyJSONResponder = {
16
+ success: (reply, data) => {
17
+ if (data === undefined) {
18
+ reply.code(reply.statusCode === 200 ? 204 : reply.statusCode).send();
19
+ return;
20
+ }
21
+ sendJSON(reply, data);
22
+ },
23
+ error: (reply, error) => {
24
+ reply.code(statusOfError(error));
25
+ sendJSON(reply, describeError(error));
26
+ }
27
+ };
28
+ /**
29
+ * Create a responder for {@link fastifyHTTPEntrypoint} that wraps every response in the envelope
30
+ * `{errno, errMsg, data}`: a result as `{errno: null, errMsg: null, data}` (`data` is `null` when the action returned
31
+ * nothing, and its status is not turned into 204), an error as `{errno, errMsg, data: null}` with the error's status,
32
+ * the `errorStatus` option, or for the errors of the actions the `actionErrorStatus` option.
33
+ * @param options The envelope options.
34
+ * @returns The responder, to give to the `respond` option of `fastifyHTTPEntrypoint`.
35
+ * @example
36
+ * ```typescript
37
+ * import {Application} from 'lakutata'
38
+ * import {buildEntrypoints, Controller} from 'lakutata/com/entrypoint'
39
+ * import {GET} from 'lakutata/decorator/ctrl'
40
+ * import {fastifyHTTPEntrypoint, fastifyJSONEnvelope} from 'lakutata/adapter/fastify'
41
+ *
42
+ * class PingController extends Controller {
43
+ * //GET /ping answers {"errno": null, "errMsg": null, "data": "pong"}
44
+ * @GET('/ping')
45
+ * public async ping(): Promise<string> {
46
+ * return 'pong'
47
+ * }
48
+ * }
49
+ *
50
+ * Application.run({
51
+ * id: 'web.app',
52
+ * name: 'Web',
53
+ * components: {
54
+ * entrypoint: buildEntrypoints({
55
+ * controllers: [PingController],
56
+ * //The errors of the actions answered with 200 too (as the applications of lakutata 2.x), the routes not
57
+ * //found and the errors of the hooks with their status (404, 401…)
58
+ * http: fastifyHTTPEntrypoint({port: 8080, respond: fastifyJSONEnvelope({actionErrorStatus: 200})})
59
+ * })
60
+ * }
61
+ * })
62
+ * ```
63
+ */
64
+ export function fastifyJSONEnvelope(options = {}) {
65
+ return {
66
+ success: (reply, data) => sendJSON(reply, { errno: null, errMsg: null, data: data === undefined ? null : data }),
67
+ error: (reply, error, _request, origin) => {
68
+ const status = origin === 'action' ? options.actionErrorStatus ?? options.errorStatus : options.errorStatus;
69
+ reply.code(status ?? statusOfError(error));
70
+ sendJSON(reply, { ...describeError(error), data: null });
71
+ }
72
+ };
73
+ }
@@ -0,0 +1,253 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { createServer } from 'node:http';
3
+ import { ServiceContext } from '../../context/ServiceContext.js';
4
+ import { ServiceChannel } from '../../channel/BuiltinChannels.js';
5
+ import { ServiceResponseCodec } from '../../lib/ServiceResponseCodec.js';
6
+ import { EntrypointStoppedException } from '../../exceptions/EntrypointStoppedException.js';
7
+ /**
8
+ * Get the HTTP request carrying a service action's request, when {@link httpServiceEntrypoint} serves it (to read its
9
+ * headers, its remote address…).
10
+ * @param context The service action's context (`this.context` in a controller).
11
+ * @returns The Node.js HTTP request, or `undefined` when another adapter serves the request.
12
+ * @example
13
+ * ```typescript
14
+ * import {Controller, httpRequestOfContext, ServiceContext} from 'lakutata/com/entrypoint'
15
+ * import {ServiceAction} from 'lakutata/decorator/ctrl'
16
+ *
17
+ * class WhoAmIController extends Controller {
18
+ * @ServiceAction({cmd: 'whoami'})
19
+ * public async whoami(): Promise<string | undefined> {
20
+ * return httpRequestOfContext(this.context as ServiceContext)?.socket.remoteAddress
21
+ * }
22
+ * }
23
+ * ```
24
+ */
25
+ export function httpRequestOfContext(context) {
26
+ return context.request;
27
+ }
28
+ /**
29
+ * The headers of the responses: the tools of the browsers may call the adapter (the development of the applications)
30
+ */
31
+ const CORS_HEADERS = {
32
+ 'Access-Control-Allow-Origin': '*',
33
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
34
+ 'Access-Control-Allow-Headers': 'Content-Type, id'
35
+ };
36
+ /**
37
+ * The id of the client of a request: its header id, its query id
38
+ * @param request
39
+ * @param url
40
+ */
41
+ function clientIdOf(request, url) {
42
+ const given = request.headers.id ?? url.searchParams.get('id');
43
+ const id = Array.isArray(given) ? given[0] : given ?? undefined;
44
+ return id && id.length <= 200 ? id : undefined;
45
+ }
46
+ /**
47
+ * Write a response of JSON
48
+ * @param response
49
+ * @param status
50
+ * @param body
51
+ */
52
+ function reply(response, status, body) {
53
+ if (response.headersSent || response.writableEnded)
54
+ return;
55
+ response.writeHead(status, { ...CORS_HEADERS, 'Content-Type': 'application/json; charset=utf-8' }).end(JSON.stringify(body));
56
+ }
57
+ /**
58
+ * The body of a request, up to a limit
59
+ * @param request
60
+ * @param limit
61
+ */
62
+ async function readBody(request, limit) {
63
+ const chunks = [];
64
+ let size = 0;
65
+ for await (const chunk of request) {
66
+ size += chunk.length;
67
+ if (size > limit)
68
+ throw Object.assign(new Error(`The body of the request is larger than ${limit} bytes`), { statusCode: 413 });
69
+ chunks.push(chunk);
70
+ }
71
+ return Buffer.concat(chunks).toString('utf8');
72
+ }
73
+ /**
74
+ * Create a service adapter of an entrypoint served over plain HTTP, handy to call the service actions from tools such
75
+ * as curl or Postman during development. It needs no optional dependency.
76
+ *
77
+ * A request is a `POST` to the path whose JSON body holds the action's pattern and input (`{"cmd": "echo", "text":
78
+ * "hi"}`). The response is always 200 with the body `{success, error, payload}` (see `ServiceResponseCodec`): `payload`
79
+ * is the result, or `error` the whole error object when the action failed (no matching action, validation…). A body that
80
+ * is not JSON or exceeds `limit` is answered with 400 and an error body; any other method or path with 404. The CORS
81
+ * headers allow any origin.
82
+ *
83
+ * The events pushed to the clients (`Entrypoint.notify`, `Entrypoint.broadcast`) are streamed as Server-Sent Events by
84
+ * `GET <path>/events`: each event's name is the SSE event, its arguments the JSON array of its data. A client gives its
85
+ * id in the `id` header or the `id` query parameter (at most 200 characters), the same on its requests so that they
86
+ * carry it as `ServiceContext.clientId`; without one, the stream assigns a random id, sent first as the `client` event.
87
+ * The streams receive a keep-alive comment every 15 seconds.
88
+ *
89
+ * When a client disconnects before its response, the action's abort signal fires. When the entrypoint stops, the new
90
+ * requests are answered with an `EntrypointStoppedException` error; once the requests in progress end, the event
91
+ * streams are closed and the server shuts down.
92
+ * @param options The adapter options; by default it listens on port 3000 of all the interfaces, on the path `/`.
93
+ * @returns The adapter, to give to the `service` option of `buildEntrypoints`.
94
+ * @example
95
+ * ```typescript
96
+ * import {Application} from 'lakutata'
97
+ * import {buildEntrypoints, Controller, httpServiceEntrypoint} from 'lakutata/com/entrypoint'
98
+ * import {ServiceAction} from 'lakutata/decorator/ctrl'
99
+ *
100
+ * class EchoController extends Controller {
101
+ * @ServiceAction({cmd: 'echo'})
102
+ * public async echo(input: {text: string}): Promise<string> {
103
+ * return input.text
104
+ * }
105
+ * }
106
+ *
107
+ * Application.run({
108
+ * id: 'echo.service',
109
+ * name: 'Echo',
110
+ * components: {
111
+ * entrypoint: buildEntrypoints({
112
+ * controllers: [EchoController],
113
+ * service: httpServiceEntrypoint({port: 3000, path: '/rpc'})
114
+ * })
115
+ * }
116
+ * })
117
+ *
118
+ * //A client: the response is {"success": true, "error": null, "payload": "hi"}
119
+ * const response: Response = await fetch('http://localhost:3000/rpc', {
120
+ * method: 'POST',
121
+ * headers: {'Content-Type': 'application/json', 'id': 'client-1'},
122
+ * body: JSON.stringify({cmd: 'echo', text: 'hi'})
123
+ * })
124
+ * console.log(await response.json())
125
+ * //The events pushed to client-1: GET http://localhost:3000/rpc/events?id=client-1 (text/event-stream)
126
+ * ```
127
+ */
128
+ export function httpServiceEntrypoint(options = {}) {
129
+ const base = (options.path ?? '/').replace(/\/+$/, '');
130
+ const requestPath = base || '/';
131
+ const eventsPath = `${base}/events`;
132
+ const limit = options.limit ?? 10 * 1024 * 1024;
133
+ let server;
134
+ let stopping = false;
135
+ let keepAlive;
136
+ //The event streams of each client (a client may listen several times)
137
+ const clients = new Map();
138
+ /**
139
+ * Write an event to a stream
140
+ * @param stream
141
+ * @param event
142
+ * @param args
143
+ */
144
+ const write = (stream, event, args) => {
145
+ stream.write(`event: ${event.replace(/[\r\n]+/g, ' ')}\ndata: ${JSON.stringify(args)}\n\n`);
146
+ };
147
+ /**
148
+ * Stream the events of a client
149
+ * @param request
150
+ * @param response
151
+ * @param url
152
+ */
153
+ const listen = (request, response, url) => {
154
+ const clientId = clientIdOf(request, url) ?? randomUUID();
155
+ response.writeHead(200, { ...CORS_HEADERS, 'Content-Type': 'text/event-stream; charset=utf-8', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' });
156
+ //Its id, given to the requests of the client (their header id)
157
+ write(response, 'client', [clientId]);
158
+ if (!clients.has(clientId))
159
+ clients.set(clientId, new Set());
160
+ clients.get(clientId).add(response);
161
+ request.once('close', () => {
162
+ clients.get(clientId)?.delete(response);
163
+ if (!clients.get(clientId)?.size)
164
+ clients.delete(clientId);
165
+ });
166
+ };
167
+ /**
168
+ * Answer a request of an action
169
+ * @param runtime
170
+ * @param request
171
+ * @param response
172
+ * @param url
173
+ */
174
+ const invoke = async (runtime, request, response, url) => {
175
+ if (stopping)
176
+ return reply(response, 200, ServiceResponseCodec.encodeError(new EntrypointStoppedException('The entrypoint is stopping')));
177
+ //Aborted when its client leaves before its response
178
+ const aborted = new AbortController();
179
+ response.once('close', () => {
180
+ if (!response.writableFinished)
181
+ aborted.abort();
182
+ });
183
+ let data;
184
+ try {
185
+ const body = await readBody(request, limit);
186
+ data = body ? JSON.parse(body) : {};
187
+ }
188
+ catch (error) {
189
+ //413 for a body larger than the limit, 400 for a body which is not JSON
190
+ return reply(response, error.statusCode ?? 400, ServiceResponseCodec.encodeError(error instanceof SyntaxError ? new Error('The body of the request is not JSON') : error));
191
+ }
192
+ let result;
193
+ try {
194
+ const context = new ServiceContext({ data: data && typeof data === 'object' ? data : {}, request: request, clientId: clientIdOf(request, url) });
195
+ result = ServiceResponseCodec.encode(await runtime.dispatch(context, { signal: aborted.signal }), false);
196
+ }
197
+ catch (error) {
198
+ result = ServiceResponseCodec.encodeError(error);
199
+ }
200
+ reply(response, 200, result);
201
+ };
202
+ return {
203
+ channel: 'service',
204
+ definition: ServiceChannel,
205
+ name: options.name,
206
+ middlewares: options.middlewares,
207
+ start: async (runtime) => {
208
+ server = createServer((request, response) => {
209
+ const url = new URL(request.url ?? '/', 'http://localhost');
210
+ if (request.method === 'OPTIONS')
211
+ return void response.writeHead(204, CORS_HEADERS).end();
212
+ if (request.method === 'GET' && url.pathname === eventsPath)
213
+ return listen(request, response, url);
214
+ if (request.method === 'POST' && url.pathname === requestPath)
215
+ return void invoke(runtime, request, response, url);
216
+ reply(response, 404, { success: false, error: { message: `POST ${requestPath} invokes the actions, GET ${eventsPath} streams their events` }, payload: null });
217
+ });
218
+ //The event streams kept open by the proxies
219
+ keepAlive = setInterval(() => clients.forEach((streams) => streams.forEach((stream) => stream.write(': keep-alive\n\n'))), 15000);
220
+ keepAlive.unref();
221
+ await new Promise((resolve, reject) => {
222
+ server.once('error', reject).listen(options.port ?? 3000, options.host, () => {
223
+ server.off('error', reject);
224
+ resolve();
225
+ });
226
+ });
227
+ await options.listening?.(server);
228
+ },
229
+ stop: () => {
230
+ //The new requests are refused, the requests in progress end
231
+ stopping = true;
232
+ },
233
+ close: async () => {
234
+ clearInterval(keepAlive);
235
+ clients.forEach((streams) => streams.forEach((stream) => stream.end()));
236
+ clients.clear();
237
+ if (!server)
238
+ return;
239
+ const closing = new Promise((resolve) => void server.close(() => resolve()));
240
+ server.closeAllConnections();
241
+ await closing;
242
+ },
243
+ notify: (clientId, event, args) => {
244
+ const streams = clients.get(clientId);
245
+ streams?.forEach((stream) => write(stream, event, args));
246
+ return !!streams?.size;
247
+ },
248
+ broadcast: (event, args) => {
249
+ clients.forEach((streams) => streams.forEach((stream) => write(stream, event, args)));
250
+ return clients.size;
251
+ }
252
+ };
253
+ }