@c9up/ream 0.1.3

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 (451) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +70 -0
  3. package/dist/Application.d.ts +71 -0
  4. package/dist/Application.d.ts.map +1 -0
  5. package/dist/Application.js +151 -0
  6. package/dist/Application.js.map +1 -0
  7. package/dist/Codemods.d.ts +16 -0
  8. package/dist/Codemods.d.ts.map +1 -0
  9. package/dist/Codemods.js +277 -0
  10. package/dist/Codemods.js.map +1 -0
  11. package/dist/ConfigLoader.d.ts +29 -0
  12. package/dist/ConfigLoader.d.ts.map +1 -0
  13. package/dist/ConfigLoader.js +43 -0
  14. package/dist/ConfigLoader.js.map +1 -0
  15. package/dist/ErrorBoundary.d.ts +51 -0
  16. package/dist/ErrorBoundary.d.ts.map +1 -0
  17. package/dist/ErrorBoundary.js +122 -0
  18. package/dist/ErrorBoundary.js.map +1 -0
  19. package/dist/GracefulShutdown.d.ts +28 -0
  20. package/dist/GracefulShutdown.d.ts.map +1 -0
  21. package/dist/GracefulShutdown.js +55 -0
  22. package/dist/GracefulShutdown.js.map +1 -0
  23. package/dist/HealthCheck.d.ts +42 -0
  24. package/dist/HealthCheck.d.ts.map +1 -0
  25. package/dist/HealthCheck.js +65 -0
  26. package/dist/HealthCheck.js.map +1 -0
  27. package/dist/HotReload.d.ts +28 -0
  28. package/dist/HotReload.d.ts.map +1 -0
  29. package/dist/HotReload.js +60 -0
  30. package/dist/HotReload.js.map +1 -0
  31. package/dist/HttpKernel.d.ts +62 -0
  32. package/dist/HttpKernel.d.ts.map +1 -0
  33. package/dist/HttpKernel.js +210 -0
  34. package/dist/HttpKernel.js.map +1 -0
  35. package/dist/Ignitor.d.ts +195 -0
  36. package/dist/Ignitor.d.ts.map +1 -0
  37. package/dist/Ignitor.js +505 -0
  38. package/dist/Ignitor.js.map +1 -0
  39. package/dist/Provider.d.ts +54 -0
  40. package/dist/Provider.d.ts.map +1 -0
  41. package/dist/Provider.js +37 -0
  42. package/dist/Provider.js.map +1 -0
  43. package/dist/bodyparser/BodyParserMiddleware.d.ts +38 -0
  44. package/dist/bodyparser/BodyParserMiddleware.d.ts.map +1 -0
  45. package/dist/bodyparser/BodyParserMiddleware.js +143 -0
  46. package/dist/bodyparser/BodyParserMiddleware.js.map +1 -0
  47. package/dist/bodyparser/MultipartFile.d.ts +77 -0
  48. package/dist/bodyparser/MultipartFile.d.ts.map +1 -0
  49. package/dist/bodyparser/MultipartFile.js +138 -0
  50. package/dist/bodyparser/MultipartFile.js.map +1 -0
  51. package/dist/bodyparser/parseSize.d.ts +6 -0
  52. package/dist/bodyparser/parseSize.d.ts.map +1 -0
  53. package/dist/bodyparser/parseSize.js +21 -0
  54. package/dist/bodyparser/parseSize.js.map +1 -0
  55. package/dist/bootstrap/hyper-server.d.ts +27 -0
  56. package/dist/bootstrap/hyper-server.d.ts.map +1 -0
  57. package/dist/bootstrap/hyper-server.js +63 -0
  58. package/dist/bootstrap/hyper-server.js.map +1 -0
  59. package/dist/console/CommandRunner.d.ts +18 -0
  60. package/dist/console/CommandRunner.d.ts.map +1 -0
  61. package/dist/console/CommandRunner.js +65 -0
  62. package/dist/console/CommandRunner.js.map +1 -0
  63. package/dist/container/Container.d.ts +60 -0
  64. package/dist/container/Container.d.ts.map +1 -0
  65. package/dist/container/Container.js +329 -0
  66. package/dist/container/Container.js.map +1 -0
  67. package/dist/container/types.d.ts +25 -0
  68. package/dist/container/types.d.ts.map +1 -0
  69. package/dist/container/types.js +6 -0
  70. package/dist/container/types.js.map +1 -0
  71. package/dist/decorators/Lazy.d.ts +19 -0
  72. package/dist/decorators/Lazy.d.ts.map +1 -0
  73. package/dist/decorators/Lazy.js +53 -0
  74. package/dist/decorators/Lazy.js.map +1 -0
  75. package/dist/decorators/Service.d.ts +48 -0
  76. package/dist/decorators/Service.d.ts.map +1 -0
  77. package/dist/decorators/Service.js +70 -0
  78. package/dist/decorators/Service.js.map +1 -0
  79. package/dist/errors/FuzzyMatcher.d.ts +41 -0
  80. package/dist/errors/FuzzyMatcher.d.ts.map +1 -0
  81. package/dist/errors/FuzzyMatcher.js +81 -0
  82. package/dist/errors/FuzzyMatcher.js.map +1 -0
  83. package/dist/errors/ModuleErrors.d.ts +41 -0
  84. package/dist/errors/ModuleErrors.d.ts.map +1 -0
  85. package/dist/errors/ModuleErrors.js +65 -0
  86. package/dist/errors/ModuleErrors.js.map +1 -0
  87. package/dist/errors/PipelineStageError.d.ts +31 -0
  88. package/dist/errors/PipelineStageError.d.ts.map +1 -0
  89. package/dist/errors/PipelineStageError.js +77 -0
  90. package/dist/errors/PipelineStageError.js.map +1 -0
  91. package/dist/errors/ReamError.d.ts +50 -0
  92. package/dist/errors/ReamError.d.ts.map +1 -0
  93. package/dist/errors/ReamError.js +108 -0
  94. package/dist/errors/ReamError.js.map +1 -0
  95. package/dist/events/Emitter.d.ts +124 -0
  96. package/dist/events/Emitter.d.ts.map +1 -0
  97. package/dist/events/Emitter.js +271 -0
  98. package/dist/events/Emitter.js.map +1 -0
  99. package/dist/events/EventsProvider.d.ts +28 -0
  100. package/dist/events/EventsProvider.d.ts.map +1 -0
  101. package/dist/events/EventsProvider.js +36 -0
  102. package/dist/events/EventsProvider.js.map +1 -0
  103. package/dist/events/config.d.ts +8 -0
  104. package/dist/events/config.d.ts.map +1 -0
  105. package/dist/events/config.js +4 -0
  106. package/dist/events/config.js.map +1 -0
  107. package/dist/events/configure.d.ts +10 -0
  108. package/dist/events/configure.d.ts.map +1 -0
  109. package/dist/events/configure.js +11 -0
  110. package/dist/events/configure.js.map +1 -0
  111. package/dist/events/helix/helpers.d.ts +62 -0
  112. package/dist/events/helix/helpers.d.ts.map +1 -0
  113. package/dist/events/helix/helpers.js +187 -0
  114. package/dist/events/helix/helpers.js.map +1 -0
  115. package/dist/events/index.d.ts +17 -0
  116. package/dist/events/index.d.ts.map +1 -0
  117. package/dist/events/index.js +15 -0
  118. package/dist/events/index.js.map +1 -0
  119. package/dist/events/native.d.ts +21 -0
  120. package/dist/events/native.d.ts.map +1 -0
  121. package/dist/events/native.js +17 -0
  122. package/dist/events/native.js.map +1 -0
  123. package/dist/events/services/main.d.ts +18 -0
  124. package/dist/events/services/main.d.ts.map +1 -0
  125. package/dist/events/services/main.js +31 -0
  126. package/dist/events/services/main.js.map +1 -0
  127. package/dist/events/testing/FakeBus.d.ts +66 -0
  128. package/dist/events/testing/FakeBus.d.ts.map +1 -0
  129. package/dist/events/testing/FakeBus.js +356 -0
  130. package/dist/events/testing/FakeBus.js.map +1 -0
  131. package/dist/graphql/GraphQLEngine.d.ts +69 -0
  132. package/dist/graphql/GraphQLEngine.d.ts.map +1 -0
  133. package/dist/graphql/GraphQLEngine.js +309 -0
  134. package/dist/graphql/GraphQLEngine.js.map +1 -0
  135. package/dist/graphql/GraphQLProvider.d.ts +32 -0
  136. package/dist/graphql/GraphQLProvider.d.ts.map +1 -0
  137. package/dist/graphql/GraphQLProvider.js +49 -0
  138. package/dist/graphql/GraphQLProvider.js.map +1 -0
  139. package/dist/helpers/base64.d.ts +40 -0
  140. package/dist/helpers/base64.d.ts.map +1 -0
  141. package/dist/helpers/base64.js +70 -0
  142. package/dist/helpers/base64.js.map +1 -0
  143. package/dist/helpers/index.d.ts +7 -0
  144. package/dist/helpers/index.d.ts.map +1 -0
  145. package/dist/helpers/index.js +5 -0
  146. package/dist/helpers/index.js.map +1 -0
  147. package/dist/helpers/is.d.ts +101 -0
  148. package/dist/helpers/is.d.ts.map +1 -0
  149. package/dist/helpers/is.js +151 -0
  150. package/dist/helpers/is.js.map +1 -0
  151. package/dist/helpers/napi-loader.d.ts +46 -0
  152. package/dist/helpers/napi-loader.d.ts.map +1 -0
  153. package/dist/helpers/napi-loader.js +69 -0
  154. package/dist/helpers/napi-loader.js.map +1 -0
  155. package/dist/helpers/safeEqual.d.ts +9 -0
  156. package/dist/helpers/safeEqual.d.ts.map +1 -0
  157. package/dist/helpers/safeEqual.js +16 -0
  158. package/dist/helpers/safeEqual.js.map +1 -0
  159. package/dist/helpers/string.d.ts +161 -0
  160. package/dist/helpers/string.d.ts.map +1 -0
  161. package/dist/helpers/string.js +429 -0
  162. package/dist/helpers/string.js.map +1 -0
  163. package/dist/helpers/types.d.ts +83 -0
  164. package/dist/helpers/types.d.ts.map +1 -0
  165. package/dist/helpers/types.js +6 -0
  166. package/dist/helpers/types.js.map +1 -0
  167. package/dist/http/Exception.d.ts +103 -0
  168. package/dist/http/Exception.d.ts.map +1 -0
  169. package/dist/http/Exception.js +228 -0
  170. package/dist/http/Exception.js.map +1 -0
  171. package/dist/http/HttpContext.d.ts +69 -0
  172. package/dist/http/HttpContext.d.ts.map +1 -0
  173. package/dist/http/HttpContext.js +55 -0
  174. package/dist/http/HttpContext.js.map +1 -0
  175. package/dist/http/RedirectBuilder.d.ts +31 -0
  176. package/dist/http/RedirectBuilder.d.ts.map +1 -0
  177. package/dist/http/RedirectBuilder.js +75 -0
  178. package/dist/http/RedirectBuilder.js.map +1 -0
  179. package/dist/http/Request.d.ts +160 -0
  180. package/dist/http/Request.d.ts.map +1 -0
  181. package/dist/http/Request.js +484 -0
  182. package/dist/http/Request.js.map +1 -0
  183. package/dist/http/Response.d.ts +114 -0
  184. package/dist/http/Response.d.ts.map +1 -0
  185. package/dist/http/Response.js +303 -0
  186. package/dist/http/Response.js.map +1 -0
  187. package/dist/http/SseStream.d.ts +89 -0
  188. package/dist/http/SseStream.d.ts.map +1 -0
  189. package/dist/http/SseStream.js +228 -0
  190. package/dist/http/SseStream.js.map +1 -0
  191. package/dist/http/urlDecode.d.ts +8 -0
  192. package/dist/http/urlDecode.d.ts.map +1 -0
  193. package/dist/http/urlDecode.js +18 -0
  194. package/dist/http/urlDecode.js.map +1 -0
  195. package/dist/index.d.ts +92 -0
  196. package/dist/index.d.ts.map +1 -0
  197. package/dist/index.js +67 -0
  198. package/dist/index.js.map +1 -0
  199. package/dist/middleware/Pipeline.d.ts +36 -0
  200. package/dist/middleware/Pipeline.d.ts.map +1 -0
  201. package/dist/middleware/Pipeline.js +104 -0
  202. package/dist/middleware/Pipeline.js.map +1 -0
  203. package/dist/openapi/OpenApiGenerator.d.ts +58 -0
  204. package/dist/openapi/OpenApiGenerator.d.ts.map +1 -0
  205. package/dist/openapi/OpenApiGenerator.js +192 -0
  206. package/dist/openapi/OpenApiGenerator.js.map +1 -0
  207. package/dist/openapi/OpenApiMiddleware.d.ts +24 -0
  208. package/dist/openapi/OpenApiMiddleware.d.ts.map +1 -0
  209. package/dist/openapi/OpenApiMiddleware.js +78 -0
  210. package/dist/openapi/OpenApiMiddleware.js.map +1 -0
  211. package/dist/openapi/OpenApiProvider.d.ts +36 -0
  212. package/dist/openapi/OpenApiProvider.d.ts.map +1 -0
  213. package/dist/openapi/OpenApiProvider.js +42 -0
  214. package/dist/openapi/OpenApiProvider.js.map +1 -0
  215. package/dist/resources/ApiResource.d.ts +45 -0
  216. package/dist/resources/ApiResource.d.ts.map +1 -0
  217. package/dist/resources/ApiResource.js +58 -0
  218. package/dist/resources/ApiResource.js.map +1 -0
  219. package/dist/router/Router.d.ts +205 -0
  220. package/dist/router/Router.d.ts.map +1 -0
  221. package/dist/router/Router.js +552 -0
  222. package/dist/router/Router.js.map +1 -0
  223. package/dist/rpc/RpcProvider.d.ts +34 -0
  224. package/dist/rpc/RpcProvider.d.ts.map +1 -0
  225. package/dist/rpc/RpcProvider.js +37 -0
  226. package/dist/rpc/RpcProvider.js.map +1 -0
  227. package/dist/rpc/RpcRouter.d.ts +61 -0
  228. package/dist/rpc/RpcRouter.d.ts.map +1 -0
  229. package/dist/rpc/RpcRouter.js +187 -0
  230. package/dist/rpc/RpcRouter.js.map +1 -0
  231. package/dist/scheduler/Schedule.d.ts +53 -0
  232. package/dist/scheduler/Schedule.d.ts.map +1 -0
  233. package/dist/scheduler/Schedule.js +76 -0
  234. package/dist/scheduler/Schedule.js.map +1 -0
  235. package/dist/scheduler/ScheduleProvider.d.ts +56 -0
  236. package/dist/scheduler/ScheduleProvider.d.ts.map +1 -0
  237. package/dist/scheduler/ScheduleProvider.js +145 -0
  238. package/dist/scheduler/ScheduleProvider.js.map +1 -0
  239. package/dist/scheduler/Scheduler.d.ts +209 -0
  240. package/dist/scheduler/Scheduler.d.ts.map +1 -0
  241. package/dist/scheduler/Scheduler.js +432 -0
  242. package/dist/scheduler/Scheduler.js.map +1 -0
  243. package/dist/scheduler/index.d.ts +14 -0
  244. package/dist/scheduler/index.d.ts.map +1 -0
  245. package/dist/scheduler/index.js +6 -0
  246. package/dist/scheduler/index.js.map +1 -0
  247. package/dist/scheduler/locks/LockBackend.d.ts +37 -0
  248. package/dist/scheduler/locks/LockBackend.d.ts.map +1 -0
  249. package/dist/scheduler/locks/LockBackend.js +13 -0
  250. package/dist/scheduler/locks/LockBackend.js.map +1 -0
  251. package/dist/scheduler/locks/MemoryLockBackend.d.ts +39 -0
  252. package/dist/scheduler/locks/MemoryLockBackend.d.ts.map +1 -0
  253. package/dist/scheduler/locks/MemoryLockBackend.js +71 -0
  254. package/dist/scheduler/locks/MemoryLockBackend.js.map +1 -0
  255. package/dist/scheduler/observability/ErrorReporter.d.ts +15 -0
  256. package/dist/scheduler/observability/ErrorReporter.d.ts.map +1 -0
  257. package/dist/scheduler/observability/ErrorReporter.js +13 -0
  258. package/dist/scheduler/observability/ErrorReporter.js.map +1 -0
  259. package/dist/scheduler/observability/ScheduleEvent.d.ts +57 -0
  260. package/dist/scheduler/observability/ScheduleEvent.d.ts.map +1 -0
  261. package/dist/scheduler/observability/ScheduleEvent.js +12 -0
  262. package/dist/scheduler/observability/ScheduleEvent.js.map +1 -0
  263. package/dist/scheduler/observability/ScheduleEventSink.d.ts +20 -0
  264. package/dist/scheduler/observability/ScheduleEventSink.d.ts.map +1 -0
  265. package/dist/scheduler/observability/ScheduleEventSink.js +17 -0
  266. package/dist/scheduler/observability/ScheduleEventSink.js.map +1 -0
  267. package/dist/scheduler/observability/StatsTracker.d.ts +50 -0
  268. package/dist/scheduler/observability/StatsTracker.d.ts.map +1 -0
  269. package/dist/scheduler/observability/StatsTracker.js +96 -0
  270. package/dist/scheduler/observability/StatsTracker.js.map +1 -0
  271. package/dist/security/CookieSigner.d.ts +13 -0
  272. package/dist/security/CookieSigner.d.ts.map +1 -0
  273. package/dist/security/CookieSigner.js +51 -0
  274. package/dist/security/CookieSigner.js.map +1 -0
  275. package/dist/security/SignedUrl.d.ts +17 -0
  276. package/dist/security/SignedUrl.d.ts.map +1 -0
  277. package/dist/security/SignedUrl.js +63 -0
  278. package/dist/security/SignedUrl.js.map +1 -0
  279. package/dist/security/crypto.d.ts +25 -0
  280. package/dist/security/crypto.d.ts.map +1 -0
  281. package/dist/security/crypto.js +82 -0
  282. package/dist/security/crypto.js.map +1 -0
  283. package/dist/server/HyperServer.d.ts +64 -0
  284. package/dist/server/HyperServer.d.ts.map +1 -0
  285. package/dist/server/HyperServer.js +54 -0
  286. package/dist/server/HyperServer.js.map +1 -0
  287. package/dist/server/Server.d.ts +68 -0
  288. package/dist/server/Server.d.ts.map +1 -0
  289. package/dist/server/Server.js +113 -0
  290. package/dist/server/Server.js.map +1 -0
  291. package/dist/services/app.d.ts +14 -0
  292. package/dist/services/app.d.ts.map +1 -0
  293. package/dist/services/app.js +27 -0
  294. package/dist/services/app.js.map +1 -0
  295. package/dist/services/router.d.ts +21 -0
  296. package/dist/services/router.d.ts.map +1 -0
  297. package/dist/services/router.js +36 -0
  298. package/dist/services/router.js.map +1 -0
  299. package/dist/services/server.d.ts +16 -0
  300. package/dist/services/server.d.ts.map +1 -0
  301. package/dist/services/server.js +32 -0
  302. package/dist/services/server.js.map +1 -0
  303. package/dist/session/Session.d.ts +88 -0
  304. package/dist/session/Session.d.ts.map +1 -0
  305. package/dist/session/Session.js +186 -0
  306. package/dist/session/Session.js.map +1 -0
  307. package/dist/session/SessionMiddleware.d.ts +16 -0
  308. package/dist/session/SessionMiddleware.d.ts.map +1 -0
  309. package/dist/session/SessionMiddleware.js +132 -0
  310. package/dist/session/SessionMiddleware.js.map +1 -0
  311. package/dist/session/drivers/CookieDriver.d.ts +17 -0
  312. package/dist/session/drivers/CookieDriver.d.ts.map +1 -0
  313. package/dist/session/drivers/CookieDriver.js +42 -0
  314. package/dist/session/drivers/CookieDriver.js.map +1 -0
  315. package/dist/session/drivers/MemoryDriver.d.ts +9 -0
  316. package/dist/session/drivers/MemoryDriver.d.ts.map +1 -0
  317. package/dist/session/drivers/MemoryDriver.js +24 -0
  318. package/dist/session/drivers/MemoryDriver.js.map +1 -0
  319. package/dist/storage/S3Driver.d.ts +27 -0
  320. package/dist/storage/S3Driver.d.ts.map +1 -0
  321. package/dist/storage/S3Driver.js +135 -0
  322. package/dist/storage/S3Driver.js.map +1 -0
  323. package/dist/storage/StaticMiddleware.d.ts +27 -0
  324. package/dist/storage/StaticMiddleware.d.ts.map +1 -0
  325. package/dist/storage/StaticMiddleware.js +153 -0
  326. package/dist/storage/StaticMiddleware.js.map +1 -0
  327. package/dist/storage/StaticProvider.d.ts +26 -0
  328. package/dist/storage/StaticProvider.d.ts.map +1 -0
  329. package/dist/storage/StaticProvider.js +31 -0
  330. package/dist/storage/StaticProvider.js.map +1 -0
  331. package/dist/storage/StorageManager.d.ts +31 -0
  332. package/dist/storage/StorageManager.d.ts.map +1 -0
  333. package/dist/storage/StorageManager.js +120 -0
  334. package/dist/storage/StorageManager.js.map +1 -0
  335. package/dist/testing/RequestBuilder.d.ts +67 -0
  336. package/dist/testing/RequestBuilder.d.ts.map +1 -0
  337. package/dist/testing/RequestBuilder.js +202 -0
  338. package/dist/testing/RequestBuilder.js.map +1 -0
  339. package/dist/testing/TestClient.d.ts +96 -0
  340. package/dist/testing/TestClient.d.ts.map +1 -0
  341. package/dist/testing/TestClient.js +263 -0
  342. package/dist/testing/TestClient.js.map +1 -0
  343. package/dist/types/helpers.d.ts +47 -0
  344. package/dist/types/helpers.d.ts.map +1 -0
  345. package/dist/types/helpers.js +5 -0
  346. package/dist/types/helpers.js.map +1 -0
  347. package/events.darwin-arm64.node +0 -0
  348. package/events.darwin-x64.node +0 -0
  349. package/events.linux-arm64-gnu.node +0 -0
  350. package/events.linux-x64-gnu.node +0 -0
  351. package/events.win32-x64-msvc.node +0 -0
  352. package/index.darwin-arm64.node +0 -0
  353. package/index.darwin-x64.node +0 -0
  354. package/index.linux-arm64-gnu.node +0 -0
  355. package/index.linux-x64-gnu.node +0 -0
  356. package/index.win32-x64-msvc.node +0 -0
  357. package/package.json +106 -0
  358. package/scheduler.darwin-arm64.node +0 -0
  359. package/scheduler.darwin-x64.node +0 -0
  360. package/scheduler.linux-arm64-gnu.node +0 -0
  361. package/scheduler.linux-x64-gnu.node +0 -0
  362. package/scheduler.win32-x64-msvc.node +0 -0
  363. package/scripts/copy-napi.mjs +113 -0
  364. package/src/Application.ts +177 -0
  365. package/src/Codemods.ts +343 -0
  366. package/src/ConfigLoader.ts +49 -0
  367. package/src/ErrorBoundary.ts +157 -0
  368. package/src/GracefulShutdown.ts +80 -0
  369. package/src/HealthCheck.ts +96 -0
  370. package/src/HotReload.ts +78 -0
  371. package/src/HttpKernel.ts +305 -0
  372. package/src/Ignitor.ts +656 -0
  373. package/src/Provider.ts +74 -0
  374. package/src/bodyparser/BodyParserMiddleware.ts +183 -0
  375. package/src/bodyparser/MultipartFile.ts +181 -0
  376. package/src/bodyparser/parseSize.ts +19 -0
  377. package/src/bootstrap/hyper-server.ts +78 -0
  378. package/src/console/CommandRunner.ts +82 -0
  379. package/src/container/Container.ts +383 -0
  380. package/src/container/types.ts +30 -0
  381. package/src/decorators/Lazy.ts +57 -0
  382. package/src/decorators/Service.ts +85 -0
  383. package/src/errors/FuzzyMatcher.ts +93 -0
  384. package/src/errors/ModuleErrors.ts +73 -0
  385. package/src/errors/PipelineStageError.ts +97 -0
  386. package/src/errors/ReamError.ts +138 -0
  387. package/src/events/Emitter.ts +333 -0
  388. package/src/events/EventsProvider.ts +58 -0
  389. package/src/events/config.ts +10 -0
  390. package/src/events/configure.ts +19 -0
  391. package/src/events/helix/helpers.ts +260 -0
  392. package/src/events/index.ts +22 -0
  393. package/src/events/native.ts +37 -0
  394. package/src/events/services/main.ts +39 -0
  395. package/src/events/testing/FakeBus.ts +420 -0
  396. package/src/graphql/GraphQLEngine.ts +421 -0
  397. package/src/graphql/GraphQLProvider.ts +59 -0
  398. package/src/helpers/base64.ts +72 -0
  399. package/src/helpers/index.ts +6 -0
  400. package/src/helpers/is.ts +162 -0
  401. package/src/helpers/napi-loader.ts +106 -0
  402. package/src/helpers/safeEqual.ts +15 -0
  403. package/src/helpers/string.ts +469 -0
  404. package/src/helpers/types.ts +100 -0
  405. package/src/http/Exception.ts +272 -0
  406. package/src/http/HttpContext.ts +108 -0
  407. package/src/http/RedirectBuilder.ts +89 -0
  408. package/src/http/Request.ts +540 -0
  409. package/src/http/Response.ts +351 -0
  410. package/src/http/SseStream.ts +250 -0
  411. package/src/http/urlDecode.ts +18 -0
  412. package/src/index.ts +263 -0
  413. package/src/middleware/Pipeline.ts +136 -0
  414. package/src/openapi/OpenApiGenerator.ts +254 -0
  415. package/src/openapi/OpenApiMiddleware.ts +101 -0
  416. package/src/openapi/OpenApiProvider.ts +67 -0
  417. package/src/resources/ApiResource.ts +79 -0
  418. package/src/router/Router.ts +733 -0
  419. package/src/rpc/RpcProvider.ts +54 -0
  420. package/src/rpc/RpcRouter.ts +243 -0
  421. package/src/scheduler/Schedule.ts +96 -0
  422. package/src/scheduler/ScheduleProvider.ts +177 -0
  423. package/src/scheduler/Scheduler.ts +601 -0
  424. package/src/scheduler/index.ts +24 -0
  425. package/src/scheduler/locks/LockBackend.ts +38 -0
  426. package/src/scheduler/locks/MemoryLockBackend.ts +85 -0
  427. package/src/scheduler/observability/ErrorReporter.ts +13 -0
  428. package/src/scheduler/observability/ScheduleEvent.ts +61 -0
  429. package/src/scheduler/observability/ScheduleEventSink.ts +21 -0
  430. package/src/scheduler/observability/StatsTracker.ts +134 -0
  431. package/src/security/CookieSigner.ts +54 -0
  432. package/src/security/SignedUrl.ts +71 -0
  433. package/src/security/crypto.ts +98 -0
  434. package/src/server/HyperServer.ts +121 -0
  435. package/src/server/Server.ts +147 -0
  436. package/src/services/app.ts +34 -0
  437. package/src/services/router.ts +44 -0
  438. package/src/services/server.ts +40 -0
  439. package/src/session/Session.ts +231 -0
  440. package/src/session/SessionMiddleware.ts +153 -0
  441. package/src/session/drivers/CookieDriver.ts +48 -0
  442. package/src/session/drivers/MemoryDriver.ts +29 -0
  443. package/src/storage/S3Driver.ts +171 -0
  444. package/src/storage/StaticMiddleware.ts +182 -0
  445. package/src/storage/StaticProvider.ts +40 -0
  446. package/src/storage/StorageManager.ts +142 -0
  447. package/src/testing/RequestBuilder.ts +275 -0
  448. package/src/testing/TestClient.ts +344 -0
  449. package/src/types/helpers.ts +52 -0
  450. package/swcrc.app.json +17 -0
  451. package/tsconfig.app.json +21 -0
@@ -0,0 +1,540 @@
1
+ /**
2
+ * Request — wraps raw HTTP request data from the NAPI layer.
3
+ *
4
+ * Provides a fluent, AdonisJS-compatible API for reading request data.
5
+ * JSON body parsing is lazy — deferred until first access via input()/all()/body().
6
+ *
7
+ * @implements FR21
8
+ */
9
+
10
+ import type { Dict } from '../types/helpers.js'
11
+
12
+ export interface RawRequest {
13
+ method: string
14
+ path: string
15
+ query: string
16
+ headers: Dict
17
+ body: string
18
+ bodyEncoding?: 'utf8' | 'base64'
19
+ /** Socket-level peer address (filled by the HTTP server). */
20
+ remoteAddr?: string
21
+ /**
22
+ * Resolved client IP — pre-computed by the Rust-side HyperServer using the
23
+ * configured trusted-proxy CIDR list. JS reads this field directly; CIDR
24
+ * matching no longer happens in TS. When absent (mock/test fixtures) the
25
+ * `ip()` accessor falls back to legacy header parsing.
26
+ */
27
+ ip?: string
28
+ /**
29
+ * Pre-parsed `multipart/form-data` payload — the HyperServer parses bodies
30
+ * server-side via `multer` and ships fields/files in this typed shape.
31
+ * Files are base64-encoded so the payload stays JSON-safe.
32
+ */
33
+ multipart?: {
34
+ fields: Array<{ name: string; value: string }>
35
+ files: Array<{
36
+ fieldName: string
37
+ clientName: string
38
+ contentType: string
39
+ size: number
40
+ contentB64: string
41
+ }>
42
+ }
43
+ /**
44
+ * Pre-parsed `Cookie:` header — name → value map computed by the Rust
45
+ * cookie crate. JS reads via `request.cookie(name)`. When absent (test
46
+ * fixtures), the accessor falls back to inline header parsing.
47
+ */
48
+ cookies?: Dict
49
+ }
50
+
51
+ export class Request {
52
+ #raw: RawRequest
53
+ #params: Dict
54
+ #parsedBody: Dict<unknown> | undefined
55
+ #parsedQs: Dict<unknown> | undefined
56
+ #merged: Dict<unknown> | undefined
57
+ #files: Map<string, import('../bodyparser/MultipartFile.js').MultipartFile[]> = new Map()
58
+
59
+ /**
60
+ * CSRF token for this request (AdonisJS idiom: `request.csrfToken`). Seeded
61
+ * by the `@c9up/blackhole` middleware when CSRF is enabled; `undefined`
62
+ * otherwise. Also published to `ctx.store` for templating helpers.
63
+ */
64
+ csrfToken?: string
65
+
66
+ constructor(raw: RawRequest, params: Dict = {}) {
67
+ this.#raw = raw
68
+ this.#params = params
69
+ }
70
+
71
+ // ─── HTTP accessors ───────────────────────────────────────
72
+
73
+ /** HTTP method (GET, POST, etc.). */
74
+ method(): string {
75
+ return this.#raw.method
76
+ }
77
+
78
+ /** Request URL (path + query string). */
79
+ url(includeQs = true): string {
80
+ if (includeQs && this.#raw.query) {
81
+ return `${this.#raw.path}?${this.#raw.query}`
82
+ }
83
+ return this.#raw.path
84
+ }
85
+
86
+ /** Request path (without query string). */
87
+ path(): string {
88
+ return this.#raw.path
89
+ }
90
+
91
+ /**
92
+ * Resolved client IP. The HyperServer computes this once per request from
93
+ * the socket peer + `X-Forwarded-For` / `X-Real-IP` headers, gated by the
94
+ * configured trusted-proxy CIDR list, and ships the resolved value on
95
+ * `request.ip`. JS reads that field as-is.
96
+ *
97
+ * When `#raw.ip` is missing — only happens in test fixtures that
98
+ * hand-build `RawRequest` without going through the Rust pipeline — fall
99
+ * back to the socket peer (`remoteAddr`). Do NOT consult `X-Forwarded-For`
100
+ * or `X-Real-IP` here: those headers are attacker-controlled on direct
101
+ * requests and the trusted-proxy gate lives in Rust. Honouring them in
102
+ * the TS fallback would let test fixtures masking real spoofing
103
+ * regressions (two semantics for the same API). Fixtures that need to
104
+ * simulate a proxy-resolved IP must set `#raw.ip` directly to the
105
+ * desired resolved value.
106
+ */
107
+ ip(): string {
108
+ if (this.#raw.ip) return this.#raw.ip
109
+ return this.#raw.remoteAddr ?? '127.0.0.1'
110
+ }
111
+
112
+ /**
113
+ * Return all cookies sent on this request as a name → value map. The
114
+ * HyperServer parses the `Cookie:` header once via the `cookie` crate
115
+ * (RFC 6265) and ships the result on `RawRequest.cookies`. Test fixtures
116
+ * that don't pre-fill the field fall back to inline parsing of the raw
117
+ * header — same tolerant `;`-split with quoted-value passthrough.
118
+ */
119
+ cookies(): Dict {
120
+ if (this.#raw.cookies) return { ...this.#raw.cookies }
121
+ const header = this.#raw.headers.cookie
122
+ if (!header) return {}
123
+ const result: Dict = {}
124
+ for (const pair of header.split(';')) {
125
+ const trimmed = pair.trim()
126
+ if (!trimmed) continue
127
+ const eqIdx = trimmed.indexOf('=')
128
+ if (eqIdx === -1) continue
129
+ const k = trimmed.slice(0, eqIdx).trim()
130
+ const v = trimmed.slice(eqIdx + 1)
131
+ // Empty value (`name=`) is valid per RFC 6265 — used in session/logout
132
+ // flows that explicitly clear cookies. Only the key being empty is
133
+ // grounds to skip. Aligns the fallback with the pre-parsed path
134
+ // (HyperServer's cookie crate also accepts empty values).
135
+ if (k) result[k] = v
136
+ }
137
+ return result
138
+ }
139
+
140
+ /** Pre-parsed cookie value by name, or `null` when absent. */
141
+ cookie(name: string): string | null {
142
+ const cookies = this.#raw.cookies ?? this.cookies()
143
+ return cookies[name] ?? null
144
+ }
145
+
146
+ /** Get the raw body as a string (decoded from base64 if binary). */
147
+ raw(): string {
148
+ if (this.#raw.bodyEncoding === 'base64') {
149
+ return Buffer.from(this.#raw.body, 'base64').toString('utf8')
150
+ }
151
+ return this.#raw.body
152
+ }
153
+
154
+ /** Get the raw body as a Buffer (preserves binary integrity for file uploads). */
155
+ rawBuffer(): Buffer {
156
+ if (this.#raw.bodyEncoding === 'base64') {
157
+ return Buffer.from(this.#raw.body, 'base64')
158
+ }
159
+ return Buffer.from(this.#raw.body, 'utf8')
160
+ }
161
+
162
+ // ─── Headers ──────────────────────────────────────────────
163
+
164
+ /** Get a single request header (case-insensitive). */
165
+ header(key: string): string | undefined {
166
+ return this.#raw.headers[key.toLowerCase()]
167
+ }
168
+
169
+ /** Get all request headers. */
170
+ headers(): Readonly<Dict> {
171
+ return this.#raw.headers
172
+ }
173
+
174
+ // ─── Route params ─────────────────────────────────────────
175
+
176
+ /** Get a single route parameter. */
177
+ param(key: string, defaultValue?: string): string | undefined {
178
+ return this.#params[key] ?? defaultValue
179
+ }
180
+
181
+ /** Get all route parameters. */
182
+ params(): Readonly<Dict> {
183
+ return this.#params
184
+ }
185
+
186
+ // ─── Query string ─────────────────────────────────────────
187
+
188
+ /** Get parsed query string as an object. */
189
+ qs(): Dict<unknown> {
190
+ if (!this.#parsedQs) {
191
+ this.#parsedQs = parseQueryString(this.#raw.query)
192
+ }
193
+ return { ...this.#parsedQs }
194
+ }
195
+
196
+ // ─── Body ─────────────────────────────────────────────────
197
+
198
+ /** Get the parsed request body. */
199
+ body(): unknown {
200
+ this.#ensureParsedBody()
201
+ return this.#parsedBody
202
+ }
203
+
204
+ // ─── Merged input (body + qs) ─────────────────────────────
205
+
206
+ /** Get a single input value from body or query string. */
207
+ input<T = unknown>(key: string, defaultValue?: T): T {
208
+ const merged = this.all()
209
+ if (key in merged) {
210
+ // biome-ignore lint/suspicious/noExplicitAny: generic accessor — caller brands the value type via T
211
+ return merged[key] as any as T
212
+ }
213
+ // biome-ignore lint/suspicious/noExplicitAny: generic default — caller brands the value type via T
214
+ return defaultValue as any as T
215
+ }
216
+
217
+ /** Get all input (query string merged with body). */
218
+ all(): Dict<unknown> {
219
+ if (!this.#merged) {
220
+ this.#ensureParsedBody()
221
+ this.#merged = { ...this.qs(), ...this.#parsedBody }
222
+ }
223
+ return { ...this.#merged }
224
+ }
225
+
226
+ /** Cherry-pick specific keys from input. */
227
+ only<K extends string>(keys: K[]): Partial<Record<K, unknown>> {
228
+ const merged = this.all()
229
+ const result: Partial<Record<K, unknown>> = {}
230
+ for (const key of keys) {
231
+ if (key in merged) {
232
+ result[key] = merged[key]
233
+ }
234
+ }
235
+ return result
236
+ }
237
+
238
+ /** Get all input except specific keys. */
239
+ except(keys: string[]): Dict<unknown> {
240
+ const merged = this.all()
241
+ const keySet = new Set(keys)
242
+ const result: Dict<unknown> = {}
243
+ for (const [k, v] of Object.entries(merged)) {
244
+ if (!keySet.has(k)) result[k] = v
245
+ }
246
+ return result
247
+ }
248
+
249
+ // ─── Content negotiation ──────────────────────────────────
250
+
251
+ /** Check if the request content-type matches any of the given types. */
252
+ is(types: string[]): string | null {
253
+ const ct = this.#raw.headers['content-type'] ?? ''
254
+ for (const type of types) {
255
+ if (type === 'json' && ct.includes('application/json')) return 'json'
256
+ if (type === 'html' && ct.includes('text/html')) return 'html'
257
+ if (type === 'xml' && ct.includes('xml')) return 'xml'
258
+ if (type === 'multipart' && ct.includes('multipart/form-data')) return 'multipart'
259
+ if (ct.includes(type)) return type
260
+ }
261
+ return null
262
+ }
263
+
264
+ /**
265
+ * Content negotiation — which of the offered types does the client accept,
266
+ * honouring RFC 7231 §5.3.2 quality values?
267
+ *
268
+ * Walks the parsed Accept header in descending q-order and returns the
269
+ * first server-offered type that matches. The server's preference order
270
+ * (`types` argument) is the tiebreaker only when multiple client entries
271
+ * share the same q. A bare `accepts(types)` therefore respects the
272
+ * client's stated priorities (`application/json;q=0.9, text/html;q=0.1`
273
+ * beats a server passing `['html', 'json']`).
274
+ *
275
+ * Aliases (`'json'` ↔ `application/json`, `'html'` ↔ `text/html`,
276
+ * `'xml'` ↔ any subtype containing `xml`) are honoured.
277
+ */
278
+ accepts(types: string[]): string | null {
279
+ const header = this.#raw.headers.accept ?? '*/*'
280
+ const entries = parseAcceptHeader(header)
281
+ if (entries.length === 0) return null
282
+ // Group by q so the highest q always wins; within one q-bucket, server
283
+ // offer order is the tiebreaker (devs control that order intentionally).
284
+ const buckets = bucketByQ(entries)
285
+ for (const bucket of buckets) {
286
+ for (const type of types) {
287
+ for (const entry of bucket) {
288
+ if (acceptMatches(entry.value, type)) return type
289
+ }
290
+ }
291
+ }
292
+ return null
293
+ }
294
+
295
+ /**
296
+ * Language negotiation from Accept-Language honouring RFC 4647 / 7231
297
+ * quality values. Returns the first offered language whose tag (or its
298
+ * primary subtag) matches the highest-q client entry, falling back to
299
+ * `langs[0]` when nothing matches and the client header is empty/absent.
300
+ */
301
+ language(langs: string[]): string | null {
302
+ const header = this.#raw.headers['accept-language'] ?? ''
303
+ if (!header.trim()) return langs[0] ?? null
304
+ const entries = parseAcceptHeader(header)
305
+ if (entries.length === 0) return null
306
+ const buckets = bucketByQ(entries)
307
+ for (const bucket of buckets) {
308
+ for (const lang of langs) {
309
+ for (const entry of bucket) {
310
+ if (languageMatches(entry.value, lang)) return lang
311
+ }
312
+ }
313
+ }
314
+ return null
315
+ }
316
+
317
+ // ─── Internals ────────────────────────────────────────────
318
+
319
+ // ─── Files ─────────────────────────────────────────────────
320
+
321
+ /** Get an uploaded file by field name. */
322
+ file(
323
+ fieldName: string,
324
+ options?: import('../bodyparser/MultipartFile.js').FileValidationOptions,
325
+ ): import('../bodyparser/MultipartFile.js').MultipartFile | null {
326
+ const files = this.#files.get(fieldName)
327
+ if (!files || files.length === 0) return null
328
+ const file = files[0]
329
+ if (options) file.validate(options)
330
+ return file
331
+ }
332
+
333
+ /** Get all uploaded files for a field name. */
334
+ files(
335
+ fieldName: string,
336
+ options?: import('../bodyparser/MultipartFile.js').FileValidationOptions,
337
+ ): import('../bodyparser/MultipartFile.js').MultipartFile[] {
338
+ const files = this.#files.get(fieldName) ?? []
339
+ if (options) {
340
+ for (const f of files) f.validate(options)
341
+ }
342
+ return files
343
+ }
344
+
345
+ /** Get all uploaded files. */
346
+ allFiles(): Map<string, import('../bodyparser/MultipartFile.js').MultipartFile[]> {
347
+ return this.#files
348
+ }
349
+
350
+ /**
351
+ * Pre-parsed `multipart/form-data` payload as the HyperServer wrote it.
352
+ * Returns `undefined` when the request wasn't multipart (or the test
353
+ * fixture didn't fill the field). BodyParser hydrates `MultipartFile`
354
+ * instances from this — application code should keep using
355
+ * `request.file()` / `request.files()`.
356
+ */
357
+ multipart(): NonNullable<RawRequest['multipart']> | undefined {
358
+ return this.#raw.multipart
359
+ }
360
+
361
+ /** @internal Set files (called by BodyParser middleware). */
362
+ setFiles(files: import('../bodyparser/MultipartFile.js').MultipartFile[]): void {
363
+ this.#files.clear()
364
+ for (const file of files) {
365
+ const existing = this.#files.get(file.fieldName) ?? []
366
+ existing.push(file)
367
+ this.#files.set(file.fieldName, existing)
368
+ }
369
+ }
370
+
371
+ // ─── Internals ────────────────────────────────────────────
372
+
373
+ /** @internal Set the parsed body (called by BodyParser middleware). */
374
+ setParsedBody(body: Dict<unknown>): void {
375
+ this.#parsedBody = body
376
+ this.#merged = undefined // reset merged cache
377
+ }
378
+
379
+ #ensureParsedBody(): void {
380
+ if (this.#parsedBody !== undefined) return
381
+ const raw = this.#raw.body
382
+ if (!raw || raw.length === 0) {
383
+ this.#parsedBody = {}
384
+ return
385
+ }
386
+ try {
387
+ const parsed: unknown = JSON.parse(raw)
388
+ this.#parsedBody = isPlainObject(parsed) ? parsed : { _body: parsed }
389
+ } catch {
390
+ this.#parsedBody = {}
391
+ }
392
+ }
393
+ }
394
+
395
+ /** Parse a query string (key=value&key2=value2) into an object. */
396
+ /**
397
+ * Test whether an IPv4 address is within a CIDR range. Bare IP entries
398
+ * (`10.0.0.42`) are accepted as `/32`. IPv6 falls back to string equality
399
+ * — a full v6 implementation isn't required by the current security tests.
400
+ */
401
+ /**
402
+ * Type guard for plain JSON-decoded objects. Excludes `null` (which is
403
+ * `typeof === 'object'`) and arrays so consumers can index by string key
404
+ * without a separate runtime check.
405
+ */
406
+ function isPlainObject(value: unknown): value is Dict<unknown> {
407
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
408
+ }
409
+
410
+ /**
411
+ * Decode a percent-encoded segment without throwing on malformed input.
412
+ * Browsers happily send `q=%E0%A4%A` (truncated UTF-8 sequences) — refusing
413
+ * the whole request would let any client knock the route handler offline.
414
+ */
415
+ function safeDecode(value: string): string {
416
+ try {
417
+ return decodeURIComponent(value)
418
+ } catch {
419
+ return value
420
+ }
421
+ }
422
+
423
+ function parseQueryString(qs: string): Dict<unknown> {
424
+ if (!qs) return {}
425
+ const result: Dict<unknown> = {}
426
+ for (const pair of qs.split('&')) {
427
+ const eqIdx = pair.indexOf('=')
428
+ if (eqIdx === -1) {
429
+ result[safeDecode(pair)] = ''
430
+ } else {
431
+ const key = safeDecode(pair.slice(0, eqIdx))
432
+ const value = safeDecode(pair.slice(eqIdx + 1))
433
+ result[key] = value
434
+ }
435
+ }
436
+ return result
437
+ }
438
+
439
+ interface AcceptEntry {
440
+ value: string
441
+ q: number
442
+ index: number
443
+ }
444
+
445
+ /**
446
+ * Parse an Accept-style header (Accept, Accept-Language, Accept-Encoding…)
447
+ * into entries sorted by descending q-value, with the client's original
448
+ * order as a stable tiebreaker. Entries with `q=0` are dropped (RFC 7231:
449
+ * "the value 0 means not acceptable"). A bare wildcard entry is preserved.
450
+ */
451
+ function parseAcceptHeader(header: string): AcceptEntry[] {
452
+ const entries: AcceptEntry[] = []
453
+ let index = 0
454
+ for (const raw of header.split(',')) {
455
+ const part = raw.trim()
456
+ if (!part) continue
457
+ const [valueRaw, ...params] = part.split(';')
458
+ const value = (valueRaw ?? '').trim().toLowerCase()
459
+ if (!value) continue
460
+ let q = 1
461
+ for (const param of params) {
462
+ const [k, v] = param.split('=')
463
+ if ((k ?? '').trim().toLowerCase() === 'q') {
464
+ const parsed = Number.parseFloat((v ?? '').trim())
465
+ if (Number.isFinite(parsed)) q = parsed
466
+ }
467
+ }
468
+ if (q <= 0) continue
469
+ entries.push({ value, q, index })
470
+ index += 1
471
+ }
472
+ entries.sort((a, b) => b.q - a.q || a.index - b.index)
473
+ return entries
474
+ }
475
+
476
+ /**
477
+ * Match an Accept entry value (already lowercased) against an offered
478
+ * type. Honours short aliases (`json` ↔ `application/json`, `html` ↔
479
+ * `text/html`, `xml` ↔ any subtype containing `xml`) and the full
480
+ * wildcard (matches anything). A `type/<wildcard>` entry matches any
481
+ * subtype with the same primary type.
482
+ */
483
+ function acceptMatches(entry: string, offered: string): boolean {
484
+ if (entry === '*/*' || entry === '*') return true
485
+ const o = offered.trim().toLowerCase()
486
+ if (entry === o) return true
487
+ // Expand short aliases to their canonical media-type so the wildcard
488
+ // and exact-match checks below see the full type.
489
+ const expanded = expandAlias(o)
490
+ if (expanded && entry === expanded) return true
491
+ if (o === 'xml' && entry.includes('xml')) return true
492
+ // type/<wildcard> entry (e.g. `text/*` matches `text/html`).
493
+ if (entry.endsWith('/*')) {
494
+ const prefix = entry.slice(0, -1) // keep trailing slash
495
+ if (expanded?.startsWith(prefix)) return true
496
+ return o.startsWith(prefix)
497
+ }
498
+ return false
499
+ }
500
+
501
+ function expandAlias(short: string): string | null {
502
+ if (short === 'json') return 'application/json'
503
+ if (short === 'html') return 'text/html'
504
+ if (short === 'text') return 'text/plain'
505
+ if (short === 'form') return 'application/x-www-form-urlencoded'
506
+ if (short === 'multipart') return 'multipart/form-data'
507
+ return null
508
+ }
509
+
510
+ function bucketByQ<T extends { q: number }>(entries: T[]): T[][] {
511
+ const buckets: T[][] = []
512
+ let current: T[] = []
513
+ let lastQ = Number.POSITIVE_INFINITY
514
+ for (const entry of entries) {
515
+ if (entry.q !== lastQ) {
516
+ if (current.length > 0) buckets.push(current)
517
+ current = []
518
+ lastQ = entry.q
519
+ }
520
+ current.push(entry)
521
+ }
522
+ if (current.length > 0) buckets.push(current)
523
+ return buckets
524
+ }
525
+
526
+ /**
527
+ * Language tag match. Returns true when the Accept-Language entry equals
528
+ * the offered tag (case-insensitive), or when the entry's primary subtag
529
+ * matches the offered tag (RFC 4647 basic filtering: `en` matches `en-US`
530
+ * but not vice versa). Wildcard `*` matches anything.
531
+ */
532
+ function languageMatches(entry: string, offered: string): boolean {
533
+ if (entry === '*') return true
534
+ const o = offered.trim().toLowerCase()
535
+ if (entry === o) return true
536
+ if (entry.startsWith(`${o}-`)) return true
537
+ const primary = entry.split('-')[0]
538
+ if (primary && primary === o.split('-')[0]) return true
539
+ return false
540
+ }