@furystack/rest-service 13.0.0 → 14.1.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 (311) hide show
  1. package/CHANGELOG.md +124 -0
  2. package/README.md +47 -28
  3. package/esm/actions/error-action.d.ts +7 -2
  4. package/esm/actions/error-action.d.ts.map +1 -1
  5. package/esm/actions/error-action.js +7 -2
  6. package/esm/actions/error-action.js.map +1 -1
  7. package/esm/actions/get-current-user.d.ts +1 -4
  8. package/esm/actions/get-current-user.d.ts.map +1 -1
  9. package/esm/actions/get-current-user.js +1 -4
  10. package/esm/actions/get-current-user.js.map +1 -1
  11. package/esm/actions/get-current-user.spec.js +10 -6
  12. package/esm/actions/get-current-user.spec.js.map +1 -1
  13. package/esm/actions/is-authenticated.d.ts +3 -4
  14. package/esm/actions/is-authenticated.d.ts.map +1 -1
  15. package/esm/actions/is-authenticated.js +3 -4
  16. package/esm/actions/is-authenticated.js.map +1 -1
  17. package/esm/actions/is-authenticated.spec.js +10 -6
  18. package/esm/actions/is-authenticated.spec.js.map +1 -1
  19. package/esm/actions/logout-action.spec.js +11 -9
  20. package/esm/actions/logout-action.spec.js.map +1 -1
  21. package/esm/actions/logout.d.ts +2 -6
  22. package/esm/actions/logout.d.ts.map +1 -1
  23. package/esm/actions/logout.js +3 -7
  24. package/esm/actions/logout.js.map +1 -1
  25. package/esm/actions/not-found-action.d.ts +1 -3
  26. package/esm/actions/not-found-action.d.ts.map +1 -1
  27. package/esm/actions/not-found-action.js +1 -3
  28. package/esm/actions/not-found-action.js.map +1 -1
  29. package/esm/actions/password-login-action.d.ts +5 -12
  30. package/esm/actions/password-login-action.d.ts.map +1 -1
  31. package/esm/actions/password-login-action.js +6 -13
  32. package/esm/actions/password-login-action.js.map +1 -1
  33. package/esm/actions/password-login-action.spec.js +23 -28
  34. package/esm/actions/password-login-action.spec.js.map +1 -1
  35. package/esm/authenticate.d.ts +5 -11
  36. package/esm/authenticate.d.ts.map +1 -1
  37. package/esm/authenticate.js +6 -12
  38. package/esm/authenticate.js.map +1 -1
  39. package/esm/authenticate.spec.js +41 -33
  40. package/esm/authenticate.spec.js.map +1 -1
  41. package/esm/authentication-providers/authentication-provider.d.ts +17 -0
  42. package/esm/authentication-providers/authentication-provider.d.ts.map +1 -1
  43. package/esm/authentication-providers/basic-auth-provider.d.ts +7 -0
  44. package/esm/authentication-providers/basic-auth-provider.d.ts.map +1 -1
  45. package/esm/authentication-providers/basic-auth-provider.js +7 -0
  46. package/esm/authentication-providers/basic-auth-provider.js.map +1 -1
  47. package/esm/authentication-providers/cookie-auth-provider.d.ts.map +1 -1
  48. package/esm/authentication-providers/cookie-auth-provider.js +8 -0
  49. package/esm/authentication-providers/cookie-auth-provider.js.map +1 -1
  50. package/esm/authorize.d.ts +5 -17
  51. package/esm/authorize.d.ts.map +1 -1
  52. package/esm/authorize.js +5 -17
  53. package/esm/authorize.js.map +1 -1
  54. package/esm/authorize.spec.js +34 -29
  55. package/esm/authorize.spec.js.map +1 -1
  56. package/esm/endpoint-generators/create-delete-endpoint.d.ts +6 -11
  57. package/esm/endpoint-generators/create-delete-endpoint.d.ts.map +1 -1
  58. package/esm/endpoint-generators/create-delete-endpoint.js +7 -11
  59. package/esm/endpoint-generators/create-delete-endpoint.js.map +1 -1
  60. package/esm/endpoint-generators/create-delete-endpoint.spec.js +14 -21
  61. package/esm/endpoint-generators/create-delete-endpoint.spec.js.map +1 -1
  62. package/esm/endpoint-generators/create-get-collection-endpoint.d.ts +11 -11
  63. package/esm/endpoint-generators/create-get-collection-endpoint.d.ts.map +1 -1
  64. package/esm/endpoint-generators/create-get-collection-endpoint.js +13 -12
  65. package/esm/endpoint-generators/create-get-collection-endpoint.js.map +1 -1
  66. package/esm/endpoint-generators/create-get-collection-endpoint.spec.js +43 -70
  67. package/esm/endpoint-generators/create-get-collection-endpoint.spec.js.map +1 -1
  68. package/esm/endpoint-generators/create-get-entity-endpoint.d.ts +6 -11
  69. package/esm/endpoint-generators/create-get-entity-endpoint.d.ts.map +1 -1
  70. package/esm/endpoint-generators/create-get-entity-endpoint.js +7 -11
  71. package/esm/endpoint-generators/create-get-entity-endpoint.js.map +1 -1
  72. package/esm/endpoint-generators/create-get-entity-endpoint.spec.js +16 -33
  73. package/esm/endpoint-generators/create-get-entity-endpoint.spec.js.map +1 -1
  74. package/esm/endpoint-generators/create-get-openapi-document-action.d.ts +1 -1
  75. package/esm/endpoint-generators/create-get-openapi-document-action.d.ts.map +1 -1
  76. package/esm/endpoint-generators/create-get-openapi-document-action.js +1 -1
  77. package/esm/endpoint-generators/create-get-openapi-document-action.js.map +1 -1
  78. package/esm/endpoint-generators/create-get-schema-action.d.ts +1 -1
  79. package/esm/endpoint-generators/create-get-schema-action.d.ts.map +1 -1
  80. package/esm/endpoint-generators/create-get-schema-action.js.map +1 -1
  81. package/esm/endpoint-generators/create-patch-endpoint.d.ts +7 -13
  82. package/esm/endpoint-generators/create-patch-endpoint.d.ts.map +1 -1
  83. package/esm/endpoint-generators/create-patch-endpoint.js +8 -13
  84. package/esm/endpoint-generators/create-patch-endpoint.js.map +1 -1
  85. package/esm/endpoint-generators/create-patch-endpoint.spec.js +10 -17
  86. package/esm/endpoint-generators/create-patch-endpoint.spec.js.map +1 -1
  87. package/esm/endpoint-generators/create-post-endpoint.d.ts +7 -13
  88. package/esm/endpoint-generators/create-post-endpoint.d.ts.map +1 -1
  89. package/esm/endpoint-generators/create-post-endpoint.js +8 -13
  90. package/esm/endpoint-generators/create-post-endpoint.js.map +1 -1
  91. package/esm/endpoint-generators/create-post-endpoint.spec.js +41 -14
  92. package/esm/endpoint-generators/create-post-endpoint.spec.js.map +1 -1
  93. package/esm/endpoint-generators/utils.d.ts +17 -2
  94. package/esm/endpoint-generators/utils.d.ts.map +1 -1
  95. package/esm/endpoint-generators/utils.js +33 -20
  96. package/esm/endpoint-generators/utils.js.map +1 -1
  97. package/esm/get-schema-from-api.d.ts +1 -1
  98. package/esm/get-schema-from-api.d.ts.map +1 -1
  99. package/esm/get-schema-from-api.js +1 -0
  100. package/esm/get-schema-from-api.js.map +1 -1
  101. package/esm/get-schema-from-api.spec.js.map +1 -1
  102. package/esm/helpers.d.ts +54 -88
  103. package/esm/helpers.d.ts.map +1 -1
  104. package/esm/helpers.js +90 -92
  105. package/esm/helpers.js.map +1 -1
  106. package/esm/helpers.spec.d.ts +2 -1
  107. package/esm/helpers.spec.d.ts.map +1 -1
  108. package/esm/helpers.spec.js +41 -38
  109. package/esm/helpers.spec.js.map +1 -1
  110. package/esm/http-authentication-settings.d.ts +56 -12
  111. package/esm/http-authentication-settings.d.ts.map +1 -1
  112. package/esm/http-authentication-settings.js +27 -29
  113. package/esm/http-authentication-settings.js.map +1 -1
  114. package/esm/http-server-pool.d.ts +64 -0
  115. package/esm/http-server-pool.d.ts.map +1 -0
  116. package/esm/http-server-pool.js +95 -0
  117. package/esm/http-server-pool.js.map +1 -0
  118. package/esm/http-server-pool.spec.d.ts +2 -0
  119. package/esm/http-server-pool.spec.d.ts.map +1 -0
  120. package/esm/http-server-pool.spec.js +193 -0
  121. package/esm/http-server-pool.spec.js.map +1 -0
  122. package/esm/http-user-context.d.ts +34 -44
  123. package/esm/http-user-context.d.ts.map +1 -1
  124. package/esm/http-user-context.js +75 -95
  125. package/esm/http-user-context.js.map +1 -1
  126. package/esm/http-user-context.spec.d.ts +8 -2
  127. package/esm/http-user-context.spec.d.ts.map +1 -1
  128. package/esm/http-user-context.spec.js +226 -332
  129. package/esm/http-user-context.spec.js.map +1 -1
  130. package/esm/identity-cache-keys.d.ts +28 -0
  131. package/esm/identity-cache-keys.d.ts.map +1 -0
  132. package/esm/identity-cache-keys.js +13 -0
  133. package/esm/identity-cache-keys.js.map +1 -0
  134. package/esm/identity-cache-keys.spec.d.ts +2 -0
  135. package/esm/identity-cache-keys.spec.d.ts.map +1 -0
  136. package/esm/identity-cache-keys.spec.js +21 -0
  137. package/esm/identity-cache-keys.spec.js.map +1 -0
  138. package/esm/identity-event-bus.d.ts +102 -0
  139. package/esm/identity-event-bus.d.ts.map +1 -0
  140. package/esm/identity-event-bus.js +151 -0
  141. package/esm/identity-event-bus.js.map +1 -0
  142. package/esm/identity-event-bus.spec.d.ts +2 -0
  143. package/esm/identity-event-bus.spec.d.ts.map +1 -0
  144. package/esm/identity-event-bus.spec.js +392 -0
  145. package/esm/identity-event-bus.spec.js.map +1 -0
  146. package/esm/index.d.ts +9 -4
  147. package/esm/index.d.ts.map +1 -1
  148. package/esm/index.js +9 -4
  149. package/esm/index.js.map +1 -1
  150. package/esm/login-response-strategy.d.ts +11 -10
  151. package/esm/login-response-strategy.d.ts.map +1 -1
  152. package/esm/login-response-strategy.js +7 -6
  153. package/esm/login-response-strategy.js.map +1 -1
  154. package/esm/login-response-strategy.spec.js +25 -29
  155. package/esm/login-response-strategy.spec.js.map +1 -1
  156. package/esm/openapi/generate-openapi-document.spec.js.map +1 -1
  157. package/esm/proxy-manager.spec.js +152 -176
  158. package/esm/proxy-manager.spec.js.map +1 -1
  159. package/esm/proxy-runtime.d.ts +27 -0
  160. package/esm/proxy-runtime.d.ts.map +1 -0
  161. package/esm/proxy-runtime.js +45 -0
  162. package/esm/proxy-runtime.js.map +1 -0
  163. package/esm/proxy-runtime.spec.d.ts +2 -0
  164. package/esm/proxy-runtime.spec.d.ts.map +1 -0
  165. package/esm/proxy-runtime.spec.js +39 -0
  166. package/esm/proxy-runtime.spec.js.map +1 -0
  167. package/esm/request-action-implementation.d.ts +25 -0
  168. package/esm/request-action-implementation.d.ts.map +1 -1
  169. package/esm/request-action-implementation.js +9 -0
  170. package/esm/request-action-implementation.js.map +1 -1
  171. package/esm/rest-api-runtime.d.ts +76 -0
  172. package/esm/rest-api-runtime.d.ts.map +1 -0
  173. package/esm/rest-api-runtime.js +131 -0
  174. package/esm/rest-api-runtime.js.map +1 -0
  175. package/esm/rest-api-runtime.spec.d.ts +2 -0
  176. package/esm/rest-api-runtime.spec.d.ts.map +1 -0
  177. package/esm/rest-api-runtime.spec.js +58 -0
  178. package/esm/rest-api-runtime.spec.js.map +1 -0
  179. package/esm/rest-service.integration.spec.d.ts +1 -1
  180. package/esm/rest-service.integration.spec.d.ts.map +1 -1
  181. package/esm/rest-service.integration.spec.js +57 -80
  182. package/esm/rest-service.integration.spec.js.map +1 -1
  183. package/esm/rest.integration.test.js +26 -84
  184. package/esm/rest.integration.test.js.map +1 -1
  185. package/esm/server-telemetry.d.ts +49 -0
  186. package/esm/server-telemetry.d.ts.map +1 -0
  187. package/esm/server-telemetry.js +25 -0
  188. package/esm/server-telemetry.js.map +1 -0
  189. package/esm/server-telemetry.spec.d.ts +2 -0
  190. package/esm/server-telemetry.spec.d.ts.map +1 -0
  191. package/esm/server-telemetry.spec.js +52 -0
  192. package/esm/server-telemetry.spec.js.map +1 -0
  193. package/esm/static-server-manager.spec.js +60 -125
  194. package/esm/static-server-manager.spec.js.map +1 -1
  195. package/esm/static-site-runtime.d.ts +21 -0
  196. package/esm/static-site-runtime.d.ts.map +1 -0
  197. package/esm/static-site-runtime.js +48 -0
  198. package/esm/static-site-runtime.js.map +1 -0
  199. package/esm/static-site-runtime.spec.d.ts +2 -0
  200. package/esm/static-site-runtime.spec.d.ts.map +1 -0
  201. package/esm/static-site-runtime.spec.js +30 -0
  202. package/esm/static-site-runtime.spec.js.map +1 -0
  203. package/esm/user-resolution-cache.d.ts +65 -0
  204. package/esm/user-resolution-cache.d.ts.map +1 -0
  205. package/esm/user-resolution-cache.js +101 -0
  206. package/esm/user-resolution-cache.js.map +1 -0
  207. package/esm/user-resolution-cache.spec.d.ts +2 -0
  208. package/esm/user-resolution-cache.spec.d.ts.map +1 -0
  209. package/esm/user-resolution-cache.spec.js +220 -0
  210. package/esm/user-resolution-cache.spec.js.map +1 -0
  211. package/esm/user-store.d.ts +39 -0
  212. package/esm/user-store.d.ts.map +1 -0
  213. package/esm/user-store.js +62 -0
  214. package/esm/user-store.js.map +1 -0
  215. package/esm/user-store.spec.d.ts +2 -0
  216. package/esm/user-store.spec.d.ts.map +1 -0
  217. package/esm/user-store.spec.js +72 -0
  218. package/esm/user-store.spec.js.map +1 -0
  219. package/esm/validate.d.ts +13 -12
  220. package/esm/validate.d.ts.map +1 -1
  221. package/esm/validate.integration.spec.js +27 -28
  222. package/esm/validate.integration.spec.js.map +1 -1
  223. package/esm/validate.js +12 -0
  224. package/esm/validate.js.map +1 -1
  225. package/package.json +14 -12
  226. package/src/actions/error-action.ts +7 -3
  227. package/src/actions/get-current-user.spec.ts +11 -10
  228. package/src/actions/get-current-user.ts +1 -4
  229. package/src/actions/is-authenticated.spec.ts +12 -7
  230. package/src/actions/is-authenticated.ts +3 -4
  231. package/src/actions/logout-action.spec.ts +12 -12
  232. package/src/actions/logout.ts +3 -7
  233. package/src/actions/not-found-action.ts +1 -3
  234. package/src/actions/password-login-action.spec.ts +30 -30
  235. package/src/actions/password-login-action.ts +6 -13
  236. package/src/authenticate.spec.ts +45 -51
  237. package/src/authenticate.ts +6 -12
  238. package/src/authentication-providers/authentication-provider.ts +17 -0
  239. package/src/authentication-providers/basic-auth-provider.ts +7 -0
  240. package/src/authentication-providers/cookie-auth-provider.ts +8 -0
  241. package/src/authorize.spec.ts +37 -39
  242. package/src/authorize.ts +6 -17
  243. package/src/endpoint-generators/create-delete-endpoint.spec.ts +16 -24
  244. package/src/endpoint-generators/create-delete-endpoint.ts +11 -17
  245. package/src/endpoint-generators/create-get-collection-endpoint.spec.ts +45 -78
  246. package/src/endpoint-generators/create-get-collection-endpoint.ts +17 -19
  247. package/src/endpoint-generators/create-get-entity-endpoint.spec.ts +17 -33
  248. package/src/endpoint-generators/create-get-entity-endpoint.ts +11 -17
  249. package/src/endpoint-generators/create-get-openapi-document-action.ts +2 -2
  250. package/src/endpoint-generators/create-get-schema-action.ts +1 -1
  251. package/src/endpoint-generators/create-patch-endpoint.spec.ts +11 -18
  252. package/src/endpoint-generators/create-patch-endpoint.ts +12 -23
  253. package/src/endpoint-generators/create-post-endpoint.spec.ts +45 -14
  254. package/src/endpoint-generators/create-post-endpoint.ts +13 -23
  255. package/src/endpoint-generators/utils.ts +36 -28
  256. package/src/get-schema-from-api.spec.ts +1 -1
  257. package/src/get-schema-from-api.ts +2 -1
  258. package/src/helpers.spec.ts +51 -41
  259. package/src/helpers.ts +140 -109
  260. package/src/http-authentication-settings.ts +78 -24
  261. package/src/http-server-pool.spec.ts +220 -0
  262. package/src/http-server-pool.ts +169 -0
  263. package/src/http-user-context.spec.ts +246 -378
  264. package/src/http-user-context.ts +122 -92
  265. package/src/identity-cache-keys.spec.ts +24 -0
  266. package/src/identity-cache-keys.ts +31 -0
  267. package/src/identity-event-bus.spec.ts +285 -0
  268. package/src/identity-event-bus.ts +230 -0
  269. package/src/index.ts +9 -4
  270. package/src/login-response-strategy.spec.ts +32 -31
  271. package/src/login-response-strategy.ts +14 -12
  272. package/src/openapi/generate-openapi-document.spec.ts +2 -2
  273. package/src/proxy-manager.spec.ts +158 -188
  274. package/src/proxy-runtime.spec.ts +50 -0
  275. package/src/proxy-runtime.ts +75 -0
  276. package/src/request-action-implementation.ts +25 -0
  277. package/src/rest-api-runtime.spec.ts +77 -0
  278. package/src/rest-api-runtime.ts +204 -0
  279. package/src/rest-service.integration.spec.ts +65 -85
  280. package/src/rest.integration.test.ts +27 -87
  281. package/src/server-telemetry.spec.ts +56 -0
  282. package/src/server-telemetry.ts +43 -0
  283. package/src/static-server-manager.spec.ts +63 -151
  284. package/src/static-site-runtime.spec.ts +39 -0
  285. package/src/static-site-runtime.ts +75 -0
  286. package/src/user-resolution-cache.spec.ts +241 -0
  287. package/src/user-resolution-cache.ts +164 -0
  288. package/src/user-store.spec.ts +86 -0
  289. package/src/user-store.ts +69 -0
  290. package/src/validate.integration.spec.ts +40 -35
  291. package/src/validate.ts +14 -12
  292. package/esm/api-manager.d.ts +0 -104
  293. package/esm/api-manager.d.ts.map +0 -1
  294. package/esm/api-manager.js +0 -179
  295. package/esm/api-manager.js.map +0 -1
  296. package/esm/proxy-manager.d.ts +0 -53
  297. package/esm/proxy-manager.d.ts.map +0 -1
  298. package/esm/proxy-manager.js +0 -84
  299. package/esm/proxy-manager.js.map +0 -1
  300. package/esm/server-manager.d.ts +0 -48
  301. package/esm/server-manager.d.ts.map +0 -1
  302. package/esm/server-manager.js +0 -100
  303. package/esm/server-manager.js.map +0 -1
  304. package/esm/static-server-manager.d.ts +0 -19
  305. package/esm/static-server-manager.d.ts.map +0 -1
  306. package/esm/static-server-manager.js +0 -65
  307. package/esm/static-server-manager.js.map +0 -1
  308. package/src/api-manager.ts +0 -304
  309. package/src/proxy-manager.ts +0 -102
  310. package/src/server-manager.ts +0 -135
  311. package/src/static-server-manager.ts +0 -90
@@ -1,304 +0,0 @@
1
- import type { User } from '@furystack/core'
2
- import { IdentityContext } from '@furystack/core'
3
- import type { Injector } from '@furystack/inject'
4
- import { Injectable, Injected } from '@furystack/inject'
5
- import type { Method, RestApi } from '@furystack/rest'
6
- import { RequestError, deserializeQueryString } from '@furystack/rest'
7
- import { PathHelper, usingAsync } from '@furystack/utils'
8
- import type { MatchFunction } from 'path-to-regexp'
9
- import { match } from 'path-to-regexp'
10
- import { ErrorAction } from './actions/error-action.js'
11
- import { NotFoundAction } from './actions/not-found-action.js'
12
- import { addCorsHeaders } from './add-cors-header.js'
13
- import { CreateGetSchemaAction } from './endpoint-generators/create-get-schema-action.js'
14
- import { CreateGetOpenApiDocumentAction } from './endpoint-generators/create-get-openapi-document-action.js'
15
- import { HttpUserContext } from './http-user-context.js'
16
- import type { CorsOptions } from './models/cors-options.js'
17
- import { readPostBody } from './read-post-body.js'
18
- import type { RequestAction } from './request-action-implementation.js'
19
- import type { OnRequest, ServerApi } from './server-manager.js'
20
- import { ServerManager } from './server-manager.js'
21
- import './server-response-extensions.js'
22
-
23
- export type RestApiImplementation<T extends RestApi> = {
24
- [TMethod in keyof T]: {
25
- [TUrl in keyof T[TMethod]]: T[TMethod][TUrl] extends { result: unknown } ? RequestAction<T[TMethod][TUrl]> : never
26
- }
27
- }
28
-
29
- export interface ImplementApiOptions<T extends RestApi> {
30
- /**
31
- * The structure of the implemented API.
32
- */
33
- api: RestApiImplementation<T>
34
- /**
35
- * The Injector instance to use for dependency injection in the API actions.
36
- */
37
- injector: Injector
38
- /**
39
- * The host name for the API Server. If not provided, the default host (ServerManager.DEFAULT_HOST) will be used.
40
- */
41
- hostName?: string
42
- /**
43
- * The root path for the API. This will be prepended to all API paths.
44
- */
45
- root: string
46
- /**
47
- * The port on which the API server will listen.
48
- */
49
- port: number
50
- /**
51
- * CORS options to configure Cross-Origin Resource Sharing for the API.
52
- */
53
- cors?: CorsOptions
54
- /**
55
- * An optional function to deserialize query parameters from the URL.
56
- * This function should take a query string (e.g., "?key=value") and return an object with the parsed parameters.
57
- * If not provided, the default deserialization will be used.
58
- */
59
- deserializeQueryParams?: (param: string) => any
60
- /**
61
- * Adds an additional 'GET /schema' endpoint that returns the schema definitions of the API.
62
- * Also adds a 'GET /openapi.json' endpoint that returns the API schema in OpenAPI 3.1 format.
63
- */
64
- enableGetSchema?: boolean
65
-
66
- /**
67
- * Optional name for the API, used in the generated schema.
68
- * This can be useful for documentation or identification purposes.
69
- */
70
- name?: string
71
- /**
72
- * Optional description for the API, used in the generated schema.
73
- * This can provide additional context or information about the API's purpose.
74
- */
75
- description?: string
76
- /**
77
- * Optional version for the API, used in the generated schema.
78
- * This can help in versioning the API and tracking changes over time.
79
- */
80
- version?: string
81
- }
82
-
83
- export type NewCompiledApiEntry = {
84
- method: Method
85
- fullPath: string
86
- matcher: MatchFunction<Partial<Record<string, string | string[]>>>
87
- action: RequestAction<any>
88
- }
89
-
90
- export type NewCompiledApi = {
91
- [K in Method]?: NewCompiledApiEntry[]
92
- }
93
-
94
- export type OnRequestOptions = OnRequest & {
95
- compiledApi: NewCompiledApi
96
- hostName?: string
97
- port: number
98
- rootApiPath: string
99
- injector: Injector
100
- cors?: CorsOptions
101
- supportedMethods: string[]
102
- deserializeQueryParams?: (param: string) => Record<string, unknown>
103
- }
104
-
105
- @Injectable({ lifetime: 'singleton' })
106
- export class ApiManager {
107
- private getSuportedMethods(api: RestApiImplementation<RestApi>): Method[] {
108
- return Object.keys(api) as Method[]
109
- }
110
-
111
- private compileApi<T extends RestApi>(api: RestApiImplementation<T>, root: string) {
112
- const supportedMethods = this.getSuportedMethods(api)
113
-
114
- const compiledApi: NewCompiledApi = {}
115
- supportedMethods.forEach((method: Method) => {
116
- compiledApi[method] = [
117
- ...Object.entries(api[method as keyof typeof api]).map(([path, action]) => {
118
- const fullPath = `/${PathHelper.normalize(PathHelper.joinPaths(root, path))}`
119
- const matcher = match(fullPath, { decode: decodeURIComponent })
120
- return { method, fullPath, matcher, action: action as RequestAction<any> }
121
- }),
122
- ]
123
- })
124
- return compiledApi
125
- }
126
-
127
- public async addApi<T extends RestApi>({
128
- api,
129
- hostName,
130
- port,
131
- root,
132
- cors,
133
- injector,
134
- deserializeQueryParams,
135
- enableGetSchema,
136
- name,
137
- description,
138
- version,
139
- }: ImplementApiOptions<T>) {
140
- const extendedApi: typeof api = enableGetSchema
141
- ? {
142
- ...api,
143
- GET: {
144
- ...api.GET,
145
- '/schema': CreateGetSchemaAction(api, name, description, version),
146
- '/openapi.json': CreateGetOpenApiDocumentAction(api, name, description, version),
147
- },
148
- }
149
- : api
150
-
151
- const supportedMethods = this.getSuportedMethods(extendedApi)
152
- const rootApiPath = PathHelper.normalize(root)
153
- const server = await this.serverManager.getOrCreate({ hostName, port })
154
- const compiledApi = this.compileApi(extendedApi, root)
155
- const serverApi = {
156
- shouldExec: (msg) =>
157
- this.shouldExecRequest({
158
- ...msg,
159
- method: msg.req.method?.toUpperCase() as Method,
160
- rootApiPath,
161
- supportedMethods,
162
- url: PathHelper.normalize(msg.req.url || ''),
163
- }),
164
- onRequest: (msg) =>
165
- this.onMessage({
166
- ...msg,
167
- compiledApi,
168
- rootApiPath,
169
- port,
170
- supportedMethods,
171
- cors,
172
- injector,
173
- hostName,
174
- deserializeQueryParams,
175
- }),
176
- } satisfies ServerApi
177
- server.apis.push(serverApi)
178
- return serverApi
179
- }
180
-
181
- public shouldExecRequest(options: {
182
- method?: Method
183
- url?: string
184
- rootApiPath: string
185
- supportedMethods: Method[]
186
- }): boolean {
187
- return options.method &&
188
- options.url &&
189
- (options.supportedMethods.includes(options.method) || options.method === 'OPTIONS') &&
190
- PathHelper.matchesBaseUrl(options.url, options.rootApiPath)
191
- ? true
192
- : false
193
- }
194
-
195
- private getActionFromEndpoint(compiledEndpoint: NewCompiledApi, fullUrl: URL, method: Method) {
196
- let resolvedParams: unknown
197
- const action = compiledEndpoint[method]?.find((route) => {
198
- const result = route.matcher(fullUrl.pathname)
199
- if (result) {
200
- resolvedParams = result.params
201
- }
202
- return result
203
- })
204
- return (
205
- action && {
206
- ...action,
207
- params: resolvedParams,
208
- }
209
- )
210
- }
211
-
212
- private async executeAction({
213
- injector,
214
- req,
215
- res,
216
- fullUrl,
217
- action,
218
- deserializeQueryParams,
219
- params,
220
- }: OnRequestOptions & {
221
- fullUrl: URL
222
- action: RequestAction<{ body: object; result: object; query: object; url: object; headers: object }>
223
- params: unknown
224
- }) {
225
- await usingAsync(injector.createChild({ owner: action }), async (i) => {
226
- const httpUserContext = i.getInstance(HttpUserContext)
227
- i.setExplicitInstance<IdentityContext>(
228
- {
229
- getCurrentUser: <TUser extends User>() => httpUserContext.getCurrentUser(req) as Promise<TUser>,
230
- isAuthorized: (...roles) => httpUserContext.isAuthorized(req, ...roles),
231
- isAuthenticated: () => httpUserContext.isAuthenticated(req),
232
- },
233
- IdentityContext,
234
- )
235
- try {
236
- const actionResult = await action({
237
- request: req,
238
- response: res,
239
- injector: i,
240
- getBody: () => readPostBody<any>(req),
241
- headers: req.headers,
242
- getQuery: () =>
243
- deserializeQueryParams ? deserializeQueryParams(fullUrl.search) : deserializeQueryString(fullUrl.search),
244
- getUrlParams: () => {
245
- return params as object
246
- },
247
- })
248
- res.sendActionResult(actionResult)
249
- } catch (error) {
250
- const errorActionResult = await ErrorAction({
251
- request: req,
252
- response: res,
253
- injector: i,
254
- getBody: async () => error,
255
- })
256
- res.sendActionResult(errorActionResult)
257
- }
258
- return
259
- })
260
- }
261
-
262
- private async onMessage(options: OnRequestOptions) {
263
- const protocol = 'http://'
264
- const host = `${options.hostName || ServerManager.DEFAULT_HOST}:${options.port}`
265
- const fullUrl = new URL(PathHelper.joinUrl(`${protocol}${host}`, options.req.url as string))
266
-
267
- if (options.cors) {
268
- addCorsHeaders(options.cors, options.req, options.res)
269
- }
270
-
271
- if (options.req.method === 'OPTIONS') {
272
- options.res.writeHead(200)
273
- options.res.end()
274
- return
275
- }
276
-
277
- let action: ReturnType<typeof this.getActionFromEndpoint>
278
- try {
279
- action = this.getActionFromEndpoint(options.compiledApi, fullUrl, options.req.method?.toUpperCase() as Method)
280
- } catch (error) {
281
- const responseError =
282
- error instanceof URIError ? new RequestError('Failed to decode URL path parameter', 400) : (error as Error)
283
- const errorActionResult = await ErrorAction({
284
- request: options.req,
285
- response: options.res,
286
- injector: options.injector,
287
- getBody: async () => responseError,
288
- })
289
- options.res.sendActionResult(errorActionResult)
290
- return
291
- }
292
-
293
- if (action) {
294
- await this.executeAction({ ...options, ...action, fullUrl })
295
- } else {
296
- options.res.sendActionResult(
297
- await NotFoundAction({ injector: options.injector, request: options.req, response: options.res }),
298
- )
299
- }
300
- }
301
-
302
- @Injected(ServerManager)
303
- declare private readonly serverManager: ServerManager
304
- }
@@ -1,102 +0,0 @@
1
- import { Injectable, Injected } from '@furystack/inject'
2
- import { EventHub, PathHelper, type ListenerErrorPayload } from '@furystack/utils'
3
- import type { IncomingMessage, OutgoingHttpHeaders, ServerResponse } from 'http'
4
- import type { Duplex } from 'stream'
5
- import { HttpProxyHandler } from './http-proxy-handler.js'
6
- import { PathProcessor } from './path-processor.js'
7
- import { ServerManager } from './server-manager.js'
8
- import { WebSocketProxyHandler } from './websocket-proxy-handler.js'
9
-
10
- export interface ProxyOptions {
11
- sourceBaseUrl: string
12
- targetBaseUrl: string
13
- pathRewrite?: (sourcePath: string) => string
14
- sourceHostName?: string
15
- sourcePort: number
16
- headers?: (originalHeaders: OutgoingHttpHeaders) => OutgoingHttpHeaders
17
- cookies?: (originalCookies: string[]) => string[]
18
- responseCookies?: (responseCookies: string[]) => string[]
19
- timeout?: number
20
- enableWebsockets?: boolean
21
- }
22
-
23
- /**
24
- * Manages HTTP and WebSocket proxy configurations and routing
25
- */
26
- @Injectable({ lifetime: 'singleton' })
27
- export class ProxyManager extends EventHub<{
28
- onProxyFailed: { from: string; to: string; error: unknown }
29
- onWebSocketProxyFailed: { from: string; to: string; error: unknown }
30
- onListenerError: ListenerErrorPayload
31
- }> {
32
- @Injected(ServerManager)
33
- declare private readonly serverManager: ServerManager
34
-
35
- private readonly pathProcessor = new PathProcessor()
36
-
37
- /**
38
- * Creates a function that determines if a request should be handled by this proxy
39
- */
40
- public shouldExec =
41
- (sourceBaseUrl: string) =>
42
- ({ req }: { req: Pick<IncomingMessage, 'url' | 'method'> }) =>
43
- req.url ? PathHelper.matchesBaseUrl(req.url, sourceBaseUrl) : false
44
-
45
- /**
46
- * Creates an HTTP request handler for the proxy
47
- */
48
- private createRequestHandler(options: ProxyOptions) {
49
- const handler = new HttpProxyHandler({
50
- sourceBaseUrl: options.sourceBaseUrl,
51
- targetBaseUrl: options.targetBaseUrl,
52
- pathRewrite: options.pathRewrite,
53
- headers: options.headers,
54
- cookies: options.cookies,
55
- responseCookies: options.responseCookies,
56
- timeout: options.timeout,
57
- onError: (error) => this.emit('onProxyFailed', error),
58
- })
59
-
60
- return async ({ req, res }: { req: IncomingMessage; res: ServerResponse }) => {
61
- await handler.handle(req, res)
62
- }
63
- }
64
-
65
- /**
66
- * Creates a WebSocket upgrade handler for the proxy
67
- */
68
- private createUpgradeHandler(options: ProxyOptions) {
69
- const handler = new WebSocketProxyHandler({
70
- sourceBaseUrl: options.sourceBaseUrl,
71
- targetBaseUrl: options.targetBaseUrl,
72
- pathRewrite: options.pathRewrite,
73
- headers: options.headers,
74
- timeout: options.timeout,
75
- onError: (error) => this.emit('onWebSocketProxyFailed', error),
76
- })
77
-
78
- return async ({ req, socket, head }: { req: IncomingMessage; socket: Duplex; head: Buffer }) => {
79
- await handler.handle(req, socket, head)
80
- }
81
- }
82
-
83
- /**
84
- * Adds a new proxy configuration
85
- * @throws Error if targetBaseUrl is invalid or uses non-HTTP/HTTPS protocol
86
- */
87
- public async addProxy(options: ProxyOptions): Promise<void> {
88
- // Validate targetBaseUrl format
89
- const url = this.pathProcessor.validateUrl(options.targetBaseUrl, 'targetBaseUrl')
90
- this.pathProcessor.validateHttpProtocol(url)
91
-
92
- const server = await this.serverManager.getOrCreate({ hostName: options.sourceHostName, port: options.sourcePort })
93
-
94
- const api = {
95
- shouldExec: this.shouldExec(options.sourceBaseUrl),
96
- onRequest: this.createRequestHandler(options),
97
- ...(options.enableWebsockets ? { onUpgrade: this.createUpgradeHandler(options) } : {}),
98
- }
99
-
100
- server.apis.push(api)
101
- }
102
- }
@@ -1,135 +0,0 @@
1
- import { Injectable } from '@furystack/inject'
2
- import { EventHub, type ListenerErrorPayload } from '@furystack/utils'
3
- import type { IncomingMessage, Server, ServerResponse } from 'http'
4
- import { createServer } from 'http'
5
- import type { Socket } from 'net'
6
- import type { Duplex } from 'stream'
7
-
8
- export interface ServerOptions {
9
- hostName?: string
10
- port: number
11
- }
12
-
13
- export interface OnRequest {
14
- req: IncomingMessage
15
- res: ServerResponse
16
- }
17
-
18
- export interface OnUpgrade {
19
- req: IncomingMessage
20
- socket: Duplex
21
- head: Buffer
22
- }
23
-
24
- export interface ServerApi {
25
- shouldExec: (options: OnRequest) => boolean
26
- onRequest: (options: OnRequest) => Promise<void>
27
- onUpgrade?: (options: OnUpgrade) => Promise<void>
28
- }
29
-
30
- export interface ServerRecord {
31
- server: Server
32
- apis: ServerApi[]
33
- }
34
-
35
- @Injectable({ lifetime: 'singleton' })
36
- export class ServerManager
37
- extends EventHub<{
38
- onRequestFailed: [unknown, IncomingMessage, ServerResponse<IncomingMessage>]
39
- onServerListening: { url: string; port: number; hostName?: string }
40
- onServerClosed: { url: string }
41
- onListenerError: ListenerErrorPayload
42
- }>
43
- implements AsyncDisposable
44
- {
45
- public static DEFAULT_HOST = 'localhost'
46
- public servers = new Map<string, ServerRecord>()
47
- private openedSockets = new Set<Socket>()
48
- private readonly pendingCreates = new Map<string, Promise<ServerRecord>>()
49
- private getHostUrl = (options: ServerOptions) =>
50
- `http://${options.hostName || ServerManager.DEFAULT_HOST}:${options.port}`
51
-
52
- private onConnection = (socket: Socket) => {
53
- this.openedSockets.add(socket)
54
- socket.once('close', () => this.openedSockets.delete(socket))
55
- }
56
- public async [Symbol.asyncDispose]() {
57
- await Promise.allSettled([...this.pendingCreates.values()])
58
- this.openedSockets.forEach((s) => s.destroy())
59
- await Promise.allSettled(
60
- [...this.servers.entries()].map(
61
- ([url, s]) =>
62
- new Promise<void>((resolve, reject) => {
63
- s.server.close((err) => {
64
- if (err) {
65
- reject(err)
66
- } else {
67
- this.emit('onServerClosed', { url })
68
- resolve()
69
- }
70
- })
71
- s.server.off('connection', this.onConnection)
72
- }),
73
- ),
74
- )
75
- this.servers.clear()
76
- super[Symbol.dispose]?.()
77
- }
78
-
79
- public async getOrCreate(options: ServerOptions): Promise<ServerRecord> {
80
- const url = this.getHostUrl(options)
81
-
82
- const existing = this.servers.get(url)
83
- if (existing) {
84
- return existing
85
- }
86
-
87
- const pending = this.pendingCreates.get(url)
88
- if (pending) {
89
- return pending
90
- }
91
-
92
- const createPromise = this.createServer(url, options)
93
- this.pendingCreates.set(url, createPromise)
94
- return createPromise
95
- }
96
-
97
- private async createServer(url: string, options: ServerOptions): Promise<ServerRecord> {
98
- const apis: ServerRecord['apis'] = []
99
- const server = createServer((req, res) => {
100
- const apiMatch = apis.find((api) => api.shouldExec({ req, res }))
101
- if (apiMatch) {
102
- apiMatch.onRequest({ req, res }).catch((error) => {
103
- this.emit('onRequestFailed', [error, req, res])
104
- })
105
- } else {
106
- res.destroy()
107
- }
108
- })
109
- server.on('upgrade', (req, socket, head) => {
110
- const apiMatch = apis.find((api) => api.shouldExec({ req, res: {} as ServerResponse }))
111
- if (apiMatch?.onUpgrade) {
112
- apiMatch.onUpgrade({ req, socket, head }).catch((error) => {
113
- this.emit('onRequestFailed', [error, req, {} as ServerResponse])
114
- socket.destroy()
115
- })
116
- } else {
117
- socket.destroy()
118
- }
119
- })
120
- server.on('connection', this.onConnection)
121
- try {
122
- await new Promise<void>((resolve, reject) => {
123
- server.on('listening', () => resolve())
124
- server.on('error', (err) => reject(err))
125
- server.listen(options.port, options.hostName)
126
- })
127
- const record: ServerRecord = { server, apis }
128
- this.servers.set(url, record)
129
- this.emit('onServerListening', { url, port: options.port, hostName: options.hostName })
130
- return record
131
- } finally {
132
- this.pendingCreates.delete(url)
133
- }
134
- }
135
- }
@@ -1,90 +0,0 @@
1
- import { Injectable, Injected } from '@furystack/inject'
2
- import { PathHelper } from '@furystack/utils'
3
- import { createReadStream } from 'fs'
4
- import { access, stat } from 'fs/promises'
5
- import type { IncomingMessage, OutgoingHttpHeaders, ServerResponse } from 'http'
6
- import { join, normalize, sep } from 'path'
7
- import { getMimeForFile } from './mime-types.js'
8
- import { ServerManager } from './server-manager.js'
9
-
10
- export interface StaticServerOptions {
11
- baseUrl: string
12
- path: string
13
- hostName?: string
14
- port: number
15
- fallback?: string
16
- headers?: OutgoingHttpHeaders
17
- }
18
-
19
- @Injectable({ lifetime: 'singleton' })
20
- export class StaticServerManager {
21
- @Injected(ServerManager)
22
- declare private readonly serverManager: ServerManager
23
-
24
- private async sendFile({
25
- fullPath,
26
- headers,
27
- res,
28
- }: {
29
- fullPath: string
30
- res: ServerResponse
31
- headers?: OutgoingHttpHeaders
32
- }) {
33
- const { size } = await stat(fullPath)
34
-
35
- await access(fullPath)
36
-
37
- const head = {
38
- ...headers,
39
- 'Content-Length': size,
40
- 'Content-Type': getMimeForFile(fullPath),
41
- }
42
- Object.entries(head).map(([key, value]) => res.setHeader(key, value))
43
- await new Promise<void>((resolve, reject) =>
44
- createReadStream(fullPath, { autoClose: true }).once('finish', resolve).once('error', reject).pipe(res),
45
- )
46
- }
47
-
48
- public shouldExec =
49
- (baseUrl: string) =>
50
- ({ req }: { req: Pick<IncomingMessage, 'url' | 'method'> }) =>
51
- req.url && req.method?.toUpperCase() === 'GET' && PathHelper.matchesBaseUrl(req.url, baseUrl) ? true : false
52
-
53
- private onRequest = ({
54
- path,
55
- baseUrl,
56
- fallback,
57
- headers,
58
- }: {
59
- path: string
60
- baseUrl: string
61
- fallback?: string
62
- headers?: OutgoingHttpHeaders
63
- }) => {
64
- return async ({ req, res }: { req: IncomingMessage; res: ServerResponse }) => {
65
- const extractedPath = PathHelper.extractPath(req.url as string, baseUrl)
66
- const filePath = (extractedPath || '/').replaceAll('/', sep)
67
- const fullPath = normalize(join(path, filePath))
68
-
69
- try {
70
- await this.sendFile({ fullPath, res, headers })
71
- } catch (error) {
72
- if (fallback) {
73
- await this.sendFile({ fullPath: normalize(join(path, fallback)), res, headers })
74
- } else {
75
- res.writeHead(404, { 'Content-Type': 'text/plain' })
76
- res.end('Not found')
77
- }
78
- }
79
- }
80
- }
81
-
82
- public async addStaticSite(options: StaticServerOptions) {
83
- const server = await this.serverManager.getOrCreate({ hostName: options.hostName, port: options.port })
84
-
85
- server.apis.push({
86
- shouldExec: this.shouldExec(options.baseUrl),
87
- onRequest: this.onRequest({ ...options }),
88
- })
89
- }
90
- }