@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,212 @@
1
+ import { ServiceContext } from '../../context/ServiceContext.js';
2
+ import { ServiceChannel } from '../../channel/BuiltinChannels.js';
3
+ import { ServiceResponseCodec } from '../../lib/ServiceResponseCodec.js';
4
+ import { EntrypointStoppedException } from '../../exceptions/EntrypointStoppedException.js';
5
+ /**
6
+ * Get the IPC event of a service action's request, when {@link electronIPCServiceEntrypoint} serves it (to reach the
7
+ * invoking renderer or frame).
8
+ * @param context The service action's context (`this.context` in a controller).
9
+ * @returns The IPC event (Electron's `IpcMainInvokeEvent`, typed as `Event`), or `undefined` when another adapter serves
10
+ * the request.
11
+ * @example
12
+ * ```typescript
13
+ * import {Controller, ServiceContext} from 'lakutata/com/entrypoint'
14
+ * import {ipcEventOfContext} from 'lakutata/adapter/electron'
15
+ * import {ServiceAction} from 'lakutata/decorator/ctrl'
16
+ * import type {IpcMainInvokeEvent} from 'electron'
17
+ *
18
+ * class WindowController extends Controller {
19
+ * //ipcRenderer.invoke('lakutata:service', {cmd: 'window.url'})
20
+ * @ServiceAction({cmd: 'window.url'})
21
+ * public async url(): Promise<string | undefined> {
22
+ * return ipcEventOfContext<IpcMainInvokeEvent>(this.context as ServiceContext)?.sender.getURL()
23
+ * }
24
+ * }
25
+ * ```
26
+ */
27
+ export function ipcEventOfContext(context) {
28
+ return context.event;
29
+ }
30
+ /**
31
+ * The ipcMain of the main process of Electron
32
+ */
33
+ async function loadIPCMain() {
34
+ let ipcMain;
35
+ try {
36
+ //Electron is not a dependency: it is loaded where it runs
37
+ const electronModule = 'electron';
38
+ const electron = await import(electronModule);
39
+ ipcMain = electron.ipcMain ?? electron.default?.ipcMain;
40
+ }
41
+ catch {
42
+ ipcMain = undefined;
43
+ }
44
+ if (!ipcMain)
45
+ throw new Error('electronIPCServiceEntrypoint runs in the main process of Electron (or is given its ipcMain)');
46
+ return ipcMain;
47
+ }
48
+ /**
49
+ * Create a service adapter of an entrypoint served over Electron's IPC, in the main process. It needs the optional
50
+ * peer dependency `electron`: without the `ipcMain` option, the entrypoint fails to start outside Electron's main
51
+ * process.
52
+ *
53
+ * A renderer invokes the channel with the action's pattern and input, and optionally its client id:
54
+ * `ipcRenderer.invoke('lakutata:service', {cmd: 'echo', text: 'hi'}, clientId)`. The invocation resolves to
55
+ * `{success, error, payload}` (see `ServiceResponseCodec`): `payload` is the result, or `error` the whole error object
56
+ * when the action failed (no matching action, validation…).
57
+ *
58
+ * Each page is a client, identified by the id it gives (a non-empty string of at most 200 characters), which its
59
+ * requests carry as `ServiceContext.clientId`. A page registers by sending its id on `<channel>:subscribe` (or with
60
+ * any invocation giving it); a page reloaded in the same frame replaces its previous id. The events pushed by
61
+ * `Entrypoint.notify` and `Entrypoint.broadcast` are sent to the page's frame on `<channel>:event` with the event name
62
+ * and its arguments array. A destroyed renderer drops its clients.
63
+ *
64
+ * When a renderer is destroyed, its requests in progress are aborted (their abort signal fires). When the entrypoint
65
+ * stops, the new invocations resolve to an `EntrypointStoppedException` error; once the requests in progress end, the
66
+ * IPC handlers are removed.
67
+ * @param options The adapter options; by default it serves the channel `'lakutata:service'` on Electron's `ipcMain`.
68
+ * @returns The adapter, to give to the `service` option of `buildEntrypoints`.
69
+ * @example
70
+ * ```typescript
71
+ * import {Application} from 'lakutata'
72
+ * import {buildEntrypoints, Controller} from 'lakutata/com/entrypoint'
73
+ * import {ServiceAction} from 'lakutata/decorator/ctrl'
74
+ * import {electronIPCServiceEntrypoint} from 'lakutata/adapter/electron'
75
+ * import type {IpcRenderer, IpcRendererEvent} from 'electron'
76
+ *
77
+ * class EchoController extends Controller {
78
+ * @ServiceAction({cmd: 'echo'})
79
+ * public async echo(input: {text: string}): Promise<string> {
80
+ * return input.text
81
+ * }
82
+ * }
83
+ *
84
+ * //In the main process
85
+ * Application.run({
86
+ * id: 'desktop.app',
87
+ * name: 'Desktop',
88
+ * components: {
89
+ * entrypoint: buildEntrypoints({
90
+ * controllers: [EchoController],
91
+ * service: electronIPCServiceEntrypoint()
92
+ * })
93
+ * }
94
+ * })
95
+ *
96
+ * //In the preload script of a window
97
+ * declare const ipcRenderer: IpcRenderer
98
+ * const clientId: string = crypto.randomUUID()
99
+ * //The events pushed by entrypoint.notify(clientId, 'progress', 50)
100
+ * ipcRenderer.on('lakutata:service:event', (event: IpcRendererEvent, name: string, args: unknown[]): void => console.log(name, ...args))
101
+ * ipcRenderer.send('lakutata:service:subscribe', clientId)
102
+ * //{success: true, error: null, payload: 'hi'}
103
+ * console.log(await ipcRenderer.invoke('lakutata:service', {cmd: 'echo', text: 'hi'}, clientId))
104
+ * ```
105
+ */
106
+ export function electronIPCServiceEntrypoint(options = {}) {
107
+ const channel = options.channel ?? 'lakutata:service';
108
+ const eventChannel = `${channel}:event`;
109
+ const subscribeChannel = `${channel}:subscribe`;
110
+ let ipcMain;
111
+ let stopping = false;
112
+ //The clients by their ids, the renderers whose destruction removes their clients
113
+ const clients = new Map();
114
+ const watched = new WeakSet();
115
+ /**
116
+ * Register the client of a page, by the id it gives (the one of the page loaded: a page loaded again in the main
117
+ * frame of its window replaces its client); undefined without id
118
+ * @param event
119
+ * @param clientId
120
+ */
121
+ const register = (event, clientId) => {
122
+ if (typeof clientId !== 'string' || !clientId || clientId.length > 200)
123
+ return undefined;
124
+ if (clients.get(clientId)?.sender === event.sender)
125
+ return clientId;
126
+ const frame = event.processId !== undefined && event.frameId !== undefined ? [event.processId, event.frameId] : undefined;
127
+ const main = !event.senderFrame?.parent;
128
+ for (const [id, client] of clients) {
129
+ const sameFrame = !!frame && !!client.frame && client.frame[0] === frame[0] && client.frame[1] === frame[1];
130
+ if (client.sender === event.sender && (sameFrame || (main && client.main)))
131
+ clients.delete(id);
132
+ }
133
+ clients.set(clientId, { sender: event.sender, frame: frame, main: main });
134
+ if (!watched.has(event.sender)) {
135
+ watched.add(event.sender);
136
+ event.sender.once('destroyed', () => {
137
+ for (const [id, client] of clients)
138
+ if (client.sender === event.sender)
139
+ clients.delete(id);
140
+ });
141
+ }
142
+ return clientId;
143
+ };
144
+ /**
145
+ * Send an event to a client, in its frame
146
+ * @param clientId
147
+ * @param client
148
+ * @param event
149
+ * @param args
150
+ */
151
+ const send = (clientId, client, event, args) => {
152
+ if (client.sender.isDestroyed?.()) {
153
+ clients.delete(clientId);
154
+ return false;
155
+ }
156
+ if (client.frame && client.sender.sendToFrame)
157
+ client.sender.sendToFrame(client.frame, eventChannel, event, args);
158
+ else if (client.sender.send)
159
+ client.sender.send(eventChannel, event, args);
160
+ else
161
+ return false;
162
+ return true;
163
+ };
164
+ return {
165
+ channel: 'service',
166
+ definition: ServiceChannel,
167
+ name: options.name,
168
+ middlewares: options.middlewares,
169
+ start: async (runtime) => {
170
+ ipcMain = options.ipcMain ?? await loadIPCMain();
171
+ ipcMain.on?.(subscribeChannel, (event, clientId) => register(event, clientId));
172
+ ipcMain.handle(channel, async (event, data, clientId) => {
173
+ if (stopping)
174
+ return ServiceResponseCodec.encodeError(new EntrypointStoppedException('The entrypoint is stopping'));
175
+ //The request is aborted when its renderer is destroyed
176
+ const request = new AbortController();
177
+ const abort = () => request.abort();
178
+ event.sender.once('destroyed', abort);
179
+ try {
180
+ const context = new ServiceContext({ data: data && typeof data === 'object' ? data : {}, event: event, clientId: register(event, clientId) });
181
+ return ServiceResponseCodec.encode(await runtime.dispatch(context, { signal: request.signal }), false);
182
+ }
183
+ catch (error) {
184
+ return ServiceResponseCodec.encodeError(error);
185
+ }
186
+ finally {
187
+ (event.sender.off ?? event.sender.removeListener)?.call(event.sender, 'destroyed', abort);
188
+ }
189
+ });
190
+ },
191
+ stop: () => {
192
+ //The new requests are refused, the requests in progress end
193
+ stopping = true;
194
+ },
195
+ close: () => {
196
+ ipcMain?.removeHandler(channel);
197
+ ipcMain?.removeAllListeners?.(subscribeChannel);
198
+ clients.clear();
199
+ },
200
+ notify: (clientId, event, args) => {
201
+ const client = clients.get(clientId);
202
+ return !!client && send(clientId, client, event, args);
203
+ },
204
+ broadcast: (event, args) => {
205
+ let sent = 0;
206
+ for (const [clientId, client] of [...clients])
207
+ if (send(clientId, client, event, args))
208
+ sent++;
209
+ return sent;
210
+ }
211
+ };
212
+ }
@@ -0,0 +1,295 @@
1
+ import express from 'express';
2
+ import { createServer } from 'node:http';
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 { JSONResponder } from './Responders.js';
9
+ /**
10
+ * The first segments of the routes
11
+ * @param routes
12
+ */
13
+ function actionPrefixesOf(routes) {
14
+ const prefixes = new Set();
15
+ for (const route of routes) {
16
+ const segment = route.split('/').find((part) => !!part);
17
+ //A route starting with a parameter matches every path
18
+ if (segment && !/^[:*]/.test(segment))
19
+ prefixes.add(`/${segment}`);
20
+ }
21
+ return [...prefixes];
22
+ }
23
+ /**
24
+ * The data of an event of a stream
25
+ * @param data
26
+ * @param event
27
+ * @param id
28
+ */
29
+ function eventFrame(data, event, id) {
30
+ let frame = '';
31
+ if (id !== undefined)
32
+ frame += `id: ${id}\n`;
33
+ if (event !== undefined)
34
+ frame += `event: ${event}\n`;
35
+ const text = typeof data === 'string' ? data : JSON.stringify(data);
36
+ for (const line of text.split(/\r?\n/))
37
+ frame += `data: ${line}\n`;
38
+ return `${frame}\n`;
39
+ }
40
+ /**
41
+ * Create the HTTP adapter of an entrypoint served by an express application: each HTTP action (`@GET`, `@POST`,
42
+ * `@HTTPAction`…) becomes an express route. It needs the optional peer dependency `express`.
43
+ *
44
+ * An action's input is the request's query and body (see the `input` option) with the route parameters merged over
45
+ * them. Its result is written by the responder (JSON by default, 204 for no result); a `StatusResult` sets the status
46
+ * and the headers, a `FileResult` sends a file, a buffer or a stream (304 when its ETag matches `If-None-Match`), and an
47
+ * `EventsResult` streams Server-Sent Events. An action that writes the response itself (through
48
+ * `HTTPContext.response`) is left alone. The errors are written by the responder with their status (400 for the
49
+ * validation failures, 500 by default).
50
+ *
51
+ * When the client disconnects before the response, the action's abort signal fires and nothing is written. When the
52
+ * entrypoint stops, the server stops accepting connections, the event streams end, and the requests in progress are
53
+ * given up to one second after they settle to send their responses before the remaining connections are closed.
54
+ * @param options The adapter options; by default it listens on port 80 of all the interfaces with a new express
55
+ * application.
56
+ * @returns The adapter, to give to the `http` option of `buildEntrypoints`.
57
+ * @example
58
+ * ```typescript
59
+ * import {Application} from 'lakutata'
60
+ * import {buildEntrypoints, Controller} from 'lakutata/com/entrypoint'
61
+ * import {GET, POST} from 'lakutata/decorator/ctrl'
62
+ * import {expressHTTPEntrypoint, staticUI} from 'lakutata/adapter/express'
63
+ *
64
+ * class UserController extends Controller {
65
+ * //GET /users/1?verbose=true: the route parameter id merged over the query
66
+ * @GET('/users/:id')
67
+ * public async get(input: {id: string, verbose?: string}): Promise<{id: string}> {
68
+ * return {id: input.id}
69
+ * }
70
+ *
71
+ * //POST /users with the JSON body {"name": "Ada"}
72
+ * @POST('/users')
73
+ * public async create(input: {name: string}): Promise<{name: string}> {
74
+ * return {name: input.name}
75
+ * }
76
+ * }
77
+ *
78
+ * Application.run({
79
+ * id: 'web.app',
80
+ * name: 'Web',
81
+ * components: {
82
+ * entrypoint: buildEntrypoints({
83
+ * controllers: [UserController],
84
+ * http: expressHTTPEntrypoint({
85
+ * port: 8080,
86
+ * body: {limit: '5mb'},
87
+ * //The application's own routes, before the actions
88
+ * before: (app): void => {
89
+ * app.get('/health', (request, response): void => void response.send('ok'))
90
+ * },
91
+ * //The pages of the UI built in ./ui/dist, for the paths outside /users
92
+ * fallback: staticUI('./ui/dist')
93
+ * })
94
+ * })
95
+ * }
96
+ * })
97
+ * ```
98
+ */
99
+ export function expressHTTPEntrypoint(options = {}) {
100
+ const respond = options.respond ?? JSONResponder;
101
+ const input = options.input ?? ((request) => ({ ...request.query, ...(request.body && typeof request.body === 'object' ? request.body : {}) }));
102
+ const streams = new Set();
103
+ let server;
104
+ let closed;
105
+ let stopping = false;
106
+ //The responses of the actions not sent yet
107
+ const responding = new Set();
108
+ /**
109
+ * Stream the events of a result
110
+ * @param result
111
+ * @param request
112
+ * @param response
113
+ */
114
+ const streamEvents = async (result, request, response) => {
115
+ const abortController = new AbortController();
116
+ streams.add(abortController);
117
+ response.status(200).set({
118
+ 'Content-Type': 'text/event-stream; charset=utf-8',
119
+ 'Cache-Control': 'no-cache, no-transform',
120
+ 'Connection': 'keep-alive',
121
+ 'X-Accel-Buffering': 'no'
122
+ }).flushHeaders();
123
+ const heartbeat = setInterval(() => response.write(': ping\n\n'), options.heartbeat ?? 15000);
124
+ const close = () => abortController.abort();
125
+ response.on('close', close);
126
+ try {
127
+ await result.producer((data, event, id) => {
128
+ if (!abortController.signal.aborted)
129
+ response.write(eventFrame(data, event, id));
130
+ }, abortController.signal);
131
+ }
132
+ catch {
133
+ //The stream is already started, its error ends it
134
+ }
135
+ finally {
136
+ clearInterval(heartbeat);
137
+ response.off('close', close);
138
+ streams.delete(abortController);
139
+ if (!response.writableEnded)
140
+ response.end();
141
+ }
142
+ };
143
+ /**
144
+ * Send a file
145
+ * @param result
146
+ * @param request
147
+ * @param response
148
+ */
149
+ const sendFile = async (result, request, response) => {
150
+ const { contentType, filename, etag, headers } = result.options;
151
+ if (headers)
152
+ response.set(headers);
153
+ if (etag) {
154
+ response.set('ETag', `"${etag}"`);
155
+ if (request.headers['if-none-match'] === `"${etag}"`) {
156
+ response.status(304).end();
157
+ return;
158
+ }
159
+ }
160
+ if (filename)
161
+ response.attachment(filename);
162
+ if (contentType)
163
+ response.type(contentType);
164
+ const source = result.source;
165
+ if (typeof source === 'string') {
166
+ await new Promise((resolve, reject) => response.sendFile(path.resolve(source), (error) => error ? reject(error) : resolve()));
167
+ }
168
+ else if (Buffer.isBuffer(source)) {
169
+ response.send(source);
170
+ }
171
+ else {
172
+ await new Promise((resolve, reject) => {
173
+ source.once('error', reject).pipe(response).once('finish', resolve);
174
+ });
175
+ }
176
+ };
177
+ /**
178
+ * Write the result of an action
179
+ * @param result
180
+ * @param request
181
+ * @param response
182
+ */
183
+ const output = async (result, request, response) => {
184
+ //The action wrote the response itself
185
+ if (response.headersSent)
186
+ return;
187
+ if (result instanceof EventsResult)
188
+ return await streamEvents(result, request, response);
189
+ if (result instanceof FileResult)
190
+ return await sendFile(result, request, response);
191
+ if (result instanceof StatusResult) {
192
+ response.status(result.status).set(result.headers);
193
+ return respond.success(response, result.data, request);
194
+ }
195
+ respond.success(response, result, request);
196
+ };
197
+ /**
198
+ * The handler of the requests of an action
199
+ * @param runtime
200
+ * @param action
201
+ */
202
+ const actionHandler = (runtime, action) => (request, response) => {
203
+ const abortController = new AbortController();
204
+ const responded = new Promise((resolve) => void response.once('close', resolve));
205
+ responding.add(responded);
206
+ //The client left before the response was sent
207
+ response.on('close', () => {
208
+ responding.delete(responded);
209
+ if (!response.writableFinished)
210
+ abortController.abort();
211
+ });
212
+ //While the adapter stops, the connections are closed once their response is sent
213
+ response.on('finish', () => {
214
+ if (stopping)
215
+ setImmediate(() => server?.closeIdleConnections());
216
+ });
217
+ const dispatch = async () => {
218
+ const context = new HTTPContext({
219
+ route: action.address.route,
220
+ method: action.address.method,
221
+ request: request,
222
+ response: response,
223
+ data: input(request)
224
+ });
225
+ const result = await runtime.dispatchTo(action, context, { signal: abortController.signal, params: { ...request.params } });
226
+ if (!abortController.signal.aborted)
227
+ await output(result, request, response);
228
+ };
229
+ dispatch().catch((error) => {
230
+ if (!response.headersSent && !abortController.signal.aborted)
231
+ respond.error(response, error, request, 'action');
232
+ });
233
+ };
234
+ return {
235
+ channel: 'http',
236
+ definition: HTTPChannel,
237
+ name: options.name,
238
+ middlewares: options.middlewares,
239
+ start: async (runtime) => {
240
+ const app = options.app ?? express();
241
+ server = createServer(options.server ?? {}, app);
242
+ const routes = new Map();
243
+ for (const action of runtime.actions)
244
+ routes.set(action.address.route, (routes.get(action.address.route) ?? new Set()).add(action.address.method));
245
+ const actionPrefixes = actionPrefixesOf(routes.keys());
246
+ const isActionPath = (requestPath) => actionPrefixes.some((prefix) => requestPath === prefix || requestPath.startsWith(`${prefix}/`));
247
+ await options.before?.(app, { module: runtime.module, server: server, routes: routes, actionPrefixes: actionPrefixes, isActionPath: isActionPath });
248
+ if (options.body !== false)
249
+ app.use(express.json({ limit: options.body?.limit ?? '1mb' }), express.urlencoded({ extended: true, limit: options.body?.limit ?? '1mb' }));
250
+ for (const action of runtime.actions) {
251
+ const method = String(action.address.method).toLowerCase();
252
+ const register = app[method];
253
+ if (typeof register !== 'function')
254
+ throw new TypeError(`Unsupported HTTP method "${action.address.method}" of the route "${action.address.route}"`);
255
+ register.call(app, action.address.route, actionHandler(runtime, action));
256
+ }
257
+ const fallback = options.fallback;
258
+ if (fallback)
259
+ app.use((request, response, next) => isActionPath(request.path) ? next() : fallback(request, response, next));
260
+ app.use((request, response) => respond.error(response, new RouteNotFoundException('Route {route} not found', { route: request.path }), request, 'request'));
261
+ app.use((error, request, response, next) => {
262
+ if (response.headersSent)
263
+ return next(error);
264
+ respond.error(response, error, request, 'request');
265
+ });
266
+ //The adapter starts once the server listens (an occupied port fails the start)
267
+ await new Promise((resolve, reject) => {
268
+ server.once('error', reject).listen(options.port ?? 80, options.host, () => {
269
+ server.off('error', reject);
270
+ resolve();
271
+ });
272
+ });
273
+ },
274
+ stop: () => {
275
+ //The event streams end, the server stops accepting connections and closes the idle ones
276
+ stopping = true;
277
+ streams.forEach((stream) => stream.abort());
278
+ if (!server?.listening)
279
+ return;
280
+ closed = new Promise((resolve) => void server.close(() => resolve()));
281
+ server.closeIdleConnections();
282
+ },
283
+ close: async () => {
284
+ //The requests in progress ended or were aborted: their responses are written, then the remaining connections are closed
285
+ let timer;
286
+ await Promise.race([Promise.all(responding), new Promise((resolve) => {
287
+ timer = setTimeout(resolve, 1000);
288
+ })]);
289
+ clearTimeout(timer);
290
+ server?.closeAllConnections();
291
+ await closed;
292
+ await options.stop?.();
293
+ }
294
+ };
295
+ }
@@ -0,0 +1,66 @@
1
+ import { describeError, statusOfError } from '../../lib/HTTPErrors.js';
2
+ export { describeError, statusOfError };
3
+ /**
4
+ * The default responder of {@link expressHTTPEntrypoint}: a result is sent as JSON, no result as an empty 204 (or the
5
+ * status set by a `StatusResult`); an error is sent as `{errno, errMsg}` (see `describeError`) with its HTTP status
6
+ * (see `statusOfError`: the first 4xx status in its cause chain, its own status, or 500).
7
+ */
8
+ export const JSONResponder = {
9
+ success: (response, data) => {
10
+ if (data === undefined) {
11
+ response.status(response.statusCode === 200 ? 204 : response.statusCode).end();
12
+ return;
13
+ }
14
+ response.json(data);
15
+ },
16
+ error: (response, error) => {
17
+ response.status(statusOfError(error)).json(describeError(error));
18
+ }
19
+ };
20
+ /**
21
+ * Create a responder for {@link expressHTTPEntrypoint} that wraps every response in the envelope
22
+ * `{errno, errMsg, data}`: a result as `{errno: null, errMsg: null, data}` (`data` is `null` when the action returned
23
+ * nothing, and its status is not turned into 204), an error as `{errno, errMsg, data: null}` with the error's status,
24
+ * the `errorStatus` option, or for the errors of the actions the `actionErrorStatus` option.
25
+ * @param options The envelope options.
26
+ * @returns The responder, to give to the `respond` option of `expressHTTPEntrypoint`.
27
+ * @example
28
+ * ```typescript
29
+ * import {Application} from 'lakutata'
30
+ * import {buildEntrypoints, Controller} from 'lakutata/com/entrypoint'
31
+ * import {GET} from 'lakutata/decorator/ctrl'
32
+ * import {expressHTTPEntrypoint, jsonEnvelope} from 'lakutata/adapter/express'
33
+ *
34
+ * class PingController extends Controller {
35
+ * //GET /ping answers {"errno": null, "errMsg": null, "data": "pong"}
36
+ * @GET('/ping')
37
+ * public async ping(): Promise<string> {
38
+ * return 'pong'
39
+ * }
40
+ * }
41
+ *
42
+ * Application.run({
43
+ * id: 'web.app',
44
+ * name: 'Web',
45
+ * components: {
46
+ * entrypoint: buildEntrypoints({
47
+ * controllers: [PingController],
48
+ * //The errors of the actions answered with 200 too (as the applications of lakutata 2.x), the routes not
49
+ * //found and the errors of the express middlewares with their status (404, 401…)
50
+ * http: expressHTTPEntrypoint({port: 8080, respond: jsonEnvelope({actionErrorStatus: 200})})
51
+ * })
52
+ * }
53
+ * })
54
+ * ```
55
+ */
56
+ export function jsonEnvelope(options = {}) {
57
+ return {
58
+ success: (response, data) => {
59
+ response.json({ errno: null, errMsg: null, data: data === undefined ? null : data });
60
+ },
61
+ error: (response, error, _request, origin) => {
62
+ const status = origin === 'action' ? options.actionErrorStatus ?? options.errorStatus : options.errorStatus;
63
+ response.status(status ?? statusOfError(error)).json({ ...describeError(error), data: null });
64
+ }
65
+ };
66
+ }
@@ -0,0 +1,96 @@
1
+ import express from 'express';
2
+ import path from 'node:path';
3
+ import { parseDuration } from '../../lib/schedule/Cron.js';
4
+ /**
5
+ * Whether a file of a built UI has a content hash in its name: a script, a style, a font or an image whose name ends
6
+ * with a dot or a dash followed by at least 8 letters, digits, `_` or `-`, among them a digit or an uppercase letter
7
+ * @param file
8
+ * @internal
9
+ */
10
+ export function isHashedAsset(file) {
11
+ const matched = /[.-]([0-9A-Za-z_-]{8,})\.(?:js|mjs|css|woff2?|png|jpe?g|svg|gif|webp|avif)$/.exec(file);
12
+ return !!matched && /[0-9A-Z]/.test(matched[1]);
13
+ }
14
+ /**
15
+ * The seconds the hashed assets are cached: a number of seconds, or a duration
16
+ * @param value
17
+ * @internal
18
+ */
19
+ export function assetsMaxAge(value) {
20
+ if (value === undefined)
21
+ return 31536000;
22
+ const seconds = typeof value === 'number' || /^\d+(?:\.\d+)?$/.test(value) ? Number(value) : parseDuration(value) / 1000;
23
+ if (!Number.isFinite(seconds) || seconds < 0)
24
+ throw new RangeError(`Invalid assetsMaxAge "${value}": a number of seconds or a duration ("30d")`);
25
+ return Math.floor(seconds);
26
+ }
27
+ /**
28
+ * Create an express handler serving a built web UI (the `dist` directory of a Vite application): its files, and its
29
+ * index for the paths of its client-side routes. Given as the `fallback` of {@link expressHTTPEntrypoint}, it never
30
+ * receives the paths of the actions. It needs the optional peer dependency `express`.
31
+ *
32
+ * The files with a content hash in their name are cached for a long time (`assetsMaxAge`), the other files and the
33
+ * index are revalidated on each request. A request matching no file that is not a page request (an asset, a non-GET
34
+ * request, a client not accepting HTML) is passed on to the next handler.
35
+ * @param directory The directory of the built UI, resolved from the current working directory.
36
+ * @param options How the index is served and the assets cached.
37
+ * @returns The express request handler.
38
+ * @example
39
+ * ```typescript
40
+ * import {Application} from 'lakutata'
41
+ * import {buildEntrypoints, Controller} from 'lakutata/com/entrypoint'
42
+ * import {GET} from 'lakutata/decorator/ctrl'
43
+ * import {expressHTTPEntrypoint, staticUI} from 'lakutata/adapter/express'
44
+ *
45
+ * class DeviceController extends Controller {
46
+ * //GET /api/devices answers the action; GET /settings answers the index of the UI
47
+ * @GET('/api/devices')
48
+ * public async list(): Promise<string[]> {
49
+ * return ['sensor-1']
50
+ * }
51
+ * }
52
+ *
53
+ * Application.run({
54
+ * id: 'console.app',
55
+ * name: 'Console',
56
+ * components: {
57
+ * entrypoint: buildEntrypoints({
58
+ * controllers: [DeviceController],
59
+ * http: expressHTTPEntrypoint({
60
+ * port: 8080,
61
+ * fallback: staticUI('./ui/dist', {assetsMaxAge: 86400})
62
+ * })
63
+ * })
64
+ * }
65
+ * })
66
+ * ```
67
+ */
68
+ export function staticUI(directory, options = {}) {
69
+ const root = path.resolve(directory);
70
+ const index = options.index ?? 'index.html';
71
+ const maxAge = assetsMaxAge(options.assetsMaxAge);
72
+ const files = express.static(root, {
73
+ index: false,
74
+ setHeaders: (response, file) => {
75
+ if (isHashedAsset(file))
76
+ response.setHeader('Cache-Control', `public, max-age=${maxAge}, immutable`);
77
+ else
78
+ response.setHeader('Cache-Control', 'no-cache');
79
+ }
80
+ });
81
+ return (request, response, next) => {
82
+ files(request, response, (error) => {
83
+ if (error)
84
+ return next(error);
85
+ //The routes of the application are its index; the missing files are not
86
+ const wantsPage = (request.method === 'GET' || request.method === 'HEAD') && !path.extname(request.path) && request.accepts('html') === 'html';
87
+ if (options.spa === false || !wantsPage)
88
+ return next();
89
+ response.setHeader('Cache-Control', 'no-cache');
90
+ response.sendFile(path.join(root, index), (sendError) => {
91
+ if (sendError)
92
+ next(sendError);
93
+ });
94
+ });
95
+ };
96
+ }