@furystack/rest-service 12.3.5 → 14.0.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 (252) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +47 -28
  3. package/esm/actions/get-current-user.spec.js +10 -6
  4. package/esm/actions/get-current-user.spec.js.map +1 -1
  5. package/esm/actions/index.d.ts +0 -1
  6. package/esm/actions/index.d.ts.map +1 -1
  7. package/esm/actions/index.js +0 -1
  8. package/esm/actions/index.js.map +1 -1
  9. package/esm/actions/is-authenticated.spec.js +10 -6
  10. package/esm/actions/is-authenticated.spec.js.map +1 -1
  11. package/esm/actions/logout-action.spec.js +11 -9
  12. package/esm/actions/logout-action.spec.js.map +1 -1
  13. package/esm/actions/logout.js +1 -1
  14. package/esm/actions/logout.js.map +1 -1
  15. package/esm/actions/password-login-action.js +1 -1
  16. package/esm/actions/password-login-action.js.map +1 -1
  17. package/esm/actions/password-login-action.spec.js +23 -28
  18. package/esm/actions/password-login-action.spec.js.map +1 -1
  19. package/esm/authenticate.js +1 -1
  20. package/esm/authenticate.js.map +1 -1
  21. package/esm/authenticate.spec.js +41 -33
  22. package/esm/authenticate.spec.js.map +1 -1
  23. package/esm/authorize.spec.js +34 -29
  24. package/esm/authorize.spec.js.map +1 -1
  25. package/esm/endpoint-generators/create-delete-endpoint.d.ts +6 -11
  26. package/esm/endpoint-generators/create-delete-endpoint.d.ts.map +1 -1
  27. package/esm/endpoint-generators/create-delete-endpoint.js +7 -11
  28. package/esm/endpoint-generators/create-delete-endpoint.js.map +1 -1
  29. package/esm/endpoint-generators/create-delete-endpoint.spec.js +14 -21
  30. package/esm/endpoint-generators/create-delete-endpoint.spec.js.map +1 -1
  31. package/esm/endpoint-generators/create-get-collection-endpoint.d.ts +11 -11
  32. package/esm/endpoint-generators/create-get-collection-endpoint.d.ts.map +1 -1
  33. package/esm/endpoint-generators/create-get-collection-endpoint.js +13 -12
  34. package/esm/endpoint-generators/create-get-collection-endpoint.js.map +1 -1
  35. package/esm/endpoint-generators/create-get-collection-endpoint.spec.js +43 -70
  36. package/esm/endpoint-generators/create-get-collection-endpoint.spec.js.map +1 -1
  37. package/esm/endpoint-generators/create-get-entity-endpoint.d.ts +6 -11
  38. package/esm/endpoint-generators/create-get-entity-endpoint.d.ts.map +1 -1
  39. package/esm/endpoint-generators/create-get-entity-endpoint.js +7 -11
  40. package/esm/endpoint-generators/create-get-entity-endpoint.js.map +1 -1
  41. package/esm/endpoint-generators/create-get-entity-endpoint.spec.js +16 -33
  42. package/esm/endpoint-generators/create-get-entity-endpoint.spec.js.map +1 -1
  43. package/esm/endpoint-generators/create-get-openapi-document-action.d.ts +1 -15
  44. package/esm/endpoint-generators/create-get-openapi-document-action.d.ts.map +1 -1
  45. package/esm/endpoint-generators/create-get-openapi-document-action.js +1 -24
  46. package/esm/endpoint-generators/create-get-openapi-document-action.js.map +1 -1
  47. package/esm/endpoint-generators/create-get-schema-action.d.ts +1 -1
  48. package/esm/endpoint-generators/create-get-schema-action.d.ts.map +1 -1
  49. package/esm/endpoint-generators/create-get-schema-action.js.map +1 -1
  50. package/esm/endpoint-generators/create-patch-endpoint.d.ts +7 -13
  51. package/esm/endpoint-generators/create-patch-endpoint.d.ts.map +1 -1
  52. package/esm/endpoint-generators/create-patch-endpoint.js +8 -13
  53. package/esm/endpoint-generators/create-patch-endpoint.js.map +1 -1
  54. package/esm/endpoint-generators/create-patch-endpoint.spec.js +10 -17
  55. package/esm/endpoint-generators/create-patch-endpoint.spec.js.map +1 -1
  56. package/esm/endpoint-generators/create-post-endpoint.d.ts +7 -13
  57. package/esm/endpoint-generators/create-post-endpoint.d.ts.map +1 -1
  58. package/esm/endpoint-generators/create-post-endpoint.js +8 -13
  59. package/esm/endpoint-generators/create-post-endpoint.js.map +1 -1
  60. package/esm/endpoint-generators/create-post-endpoint.spec.js +41 -14
  61. package/esm/endpoint-generators/create-post-endpoint.spec.js.map +1 -1
  62. package/esm/endpoint-generators/utils.d.ts +17 -2
  63. package/esm/endpoint-generators/utils.d.ts.map +1 -1
  64. package/esm/endpoint-generators/utils.js +33 -20
  65. package/esm/endpoint-generators/utils.js.map +1 -1
  66. package/esm/endpoint-generators/with-schema-and-openapi-action.d.ts +0 -2
  67. package/esm/endpoint-generators/with-schema-and-openapi-action.d.ts.map +1 -1
  68. package/esm/get-schema-from-api.d.ts +1 -1
  69. package/esm/get-schema-from-api.d.ts.map +1 -1
  70. package/esm/get-schema-from-api.spec.js.map +1 -1
  71. package/esm/helpers.d.ts +53 -87
  72. package/esm/helpers.d.ts.map +1 -1
  73. package/esm/helpers.js +91 -92
  74. package/esm/helpers.js.map +1 -1
  75. package/esm/helpers.spec.d.ts +2 -1
  76. package/esm/helpers.spec.d.ts.map +1 -1
  77. package/esm/helpers.spec.js +41 -38
  78. package/esm/helpers.spec.js.map +1 -1
  79. package/esm/http-authentication-settings.d.ts +35 -12
  80. package/esm/http-authentication-settings.d.ts.map +1 -1
  81. package/esm/http-authentication-settings.js +21 -29
  82. package/esm/http-authentication-settings.js.map +1 -1
  83. package/esm/http-server-pool.d.ts +64 -0
  84. package/esm/http-server-pool.d.ts.map +1 -0
  85. package/esm/http-server-pool.js +95 -0
  86. package/esm/http-server-pool.js.map +1 -0
  87. package/esm/http-server-pool.spec.d.ts +2 -0
  88. package/esm/http-server-pool.spec.d.ts.map +1 -0
  89. package/esm/http-server-pool.spec.js +193 -0
  90. package/esm/http-server-pool.spec.js.map +1 -0
  91. package/esm/http-user-context.d.ts +31 -44
  92. package/esm/http-user-context.d.ts.map +1 -1
  93. package/esm/http-user-context.js +76 -95
  94. package/esm/http-user-context.js.map +1 -1
  95. package/esm/http-user-context.spec.d.ts +8 -2
  96. package/esm/http-user-context.spec.d.ts.map +1 -1
  97. package/esm/http-user-context.spec.js +189 -337
  98. package/esm/http-user-context.spec.js.map +1 -1
  99. package/esm/index.d.ts +6 -4
  100. package/esm/index.d.ts.map +1 -1
  101. package/esm/index.js +6 -4
  102. package/esm/index.js.map +1 -1
  103. package/esm/login-response-strategy.d.ts +11 -10
  104. package/esm/login-response-strategy.d.ts.map +1 -1
  105. package/esm/login-response-strategy.js +7 -6
  106. package/esm/login-response-strategy.js.map +1 -1
  107. package/esm/login-response-strategy.spec.js +25 -29
  108. package/esm/login-response-strategy.spec.js.map +1 -1
  109. package/esm/openapi/generate-openapi-document.d.ts +0 -8
  110. package/esm/openapi/generate-openapi-document.d.ts.map +1 -1
  111. package/esm/openapi/generate-openapi-document.js +0 -2
  112. package/esm/openapi/generate-openapi-document.js.map +1 -1
  113. package/esm/proxy-manager.spec.js +152 -176
  114. package/esm/proxy-manager.spec.js.map +1 -1
  115. package/esm/proxy-runtime.d.ts +27 -0
  116. package/esm/proxy-runtime.d.ts.map +1 -0
  117. package/esm/proxy-runtime.js +45 -0
  118. package/esm/proxy-runtime.js.map +1 -0
  119. package/esm/proxy-runtime.spec.d.ts +2 -0
  120. package/esm/proxy-runtime.spec.d.ts.map +1 -0
  121. package/esm/proxy-runtime.spec.js +39 -0
  122. package/esm/proxy-runtime.spec.js.map +1 -0
  123. package/esm/rest-api-runtime.d.ts +76 -0
  124. package/esm/rest-api-runtime.d.ts.map +1 -0
  125. package/esm/rest-api-runtime.js +131 -0
  126. package/esm/rest-api-runtime.js.map +1 -0
  127. package/esm/rest-api-runtime.spec.d.ts +2 -0
  128. package/esm/rest-api-runtime.spec.d.ts.map +1 -0
  129. package/esm/rest-api-runtime.spec.js +58 -0
  130. package/esm/rest-api-runtime.spec.js.map +1 -0
  131. package/esm/rest-service.integration.spec.d.ts +1 -1
  132. package/esm/rest-service.integration.spec.d.ts.map +1 -1
  133. package/esm/rest-service.integration.spec.js +60 -82
  134. package/esm/rest-service.integration.spec.js.map +1 -1
  135. package/esm/rest.integration.test.js +26 -84
  136. package/esm/rest.integration.test.js.map +1 -1
  137. package/esm/server-telemetry.d.ts +57 -0
  138. package/esm/server-telemetry.d.ts.map +1 -0
  139. package/esm/server-telemetry.js +26 -0
  140. package/esm/server-telemetry.js.map +1 -0
  141. package/esm/server-telemetry.spec.d.ts +2 -0
  142. package/esm/server-telemetry.spec.d.ts.map +1 -0
  143. package/esm/server-telemetry.spec.js +52 -0
  144. package/esm/server-telemetry.spec.js.map +1 -0
  145. package/esm/static-server-manager.spec.js +60 -125
  146. package/esm/static-server-manager.spec.js.map +1 -1
  147. package/esm/static-site-runtime.d.ts +21 -0
  148. package/esm/static-site-runtime.d.ts.map +1 -0
  149. package/esm/static-site-runtime.js +48 -0
  150. package/esm/static-site-runtime.js.map +1 -0
  151. package/esm/static-site-runtime.spec.d.ts +2 -0
  152. package/esm/static-site-runtime.spec.d.ts.map +1 -0
  153. package/esm/static-site-runtime.spec.js +30 -0
  154. package/esm/static-site-runtime.spec.js.map +1 -0
  155. package/esm/user-store.d.ts +39 -0
  156. package/esm/user-store.d.ts.map +1 -0
  157. package/esm/user-store.js +62 -0
  158. package/esm/user-store.js.map +1 -0
  159. package/esm/user-store.spec.d.ts +2 -0
  160. package/esm/user-store.spec.d.ts.map +1 -0
  161. package/esm/user-store.spec.js +72 -0
  162. package/esm/user-store.spec.js.map +1 -0
  163. package/esm/validate.integration.spec.js +26 -83
  164. package/esm/validate.integration.spec.js.map +1 -1
  165. package/package.json +8 -8
  166. package/src/actions/get-current-user.spec.ts +11 -10
  167. package/src/actions/index.ts +0 -1
  168. package/src/actions/is-authenticated.spec.ts +12 -7
  169. package/src/actions/logout-action.spec.ts +12 -12
  170. package/src/actions/logout.ts +1 -1
  171. package/src/actions/password-login-action.spec.ts +30 -30
  172. package/src/actions/password-login-action.ts +1 -1
  173. package/src/authenticate.spec.ts +45 -51
  174. package/src/authenticate.ts +1 -1
  175. package/src/authorize.spec.ts +37 -39
  176. package/src/endpoint-generators/create-delete-endpoint.spec.ts +16 -24
  177. package/src/endpoint-generators/create-delete-endpoint.ts +11 -17
  178. package/src/endpoint-generators/create-get-collection-endpoint.spec.ts +45 -78
  179. package/src/endpoint-generators/create-get-collection-endpoint.ts +17 -19
  180. package/src/endpoint-generators/create-get-entity-endpoint.spec.ts +17 -33
  181. package/src/endpoint-generators/create-get-entity-endpoint.ts +11 -17
  182. package/src/endpoint-generators/create-get-openapi-document-action.ts +2 -38
  183. package/src/endpoint-generators/create-get-schema-action.ts +1 -1
  184. package/src/endpoint-generators/create-patch-endpoint.spec.ts +11 -18
  185. package/src/endpoint-generators/create-patch-endpoint.ts +12 -23
  186. package/src/endpoint-generators/create-post-endpoint.spec.ts +45 -14
  187. package/src/endpoint-generators/create-post-endpoint.ts +13 -23
  188. package/src/endpoint-generators/utils.ts +36 -28
  189. package/src/endpoint-generators/with-schema-and-openapi-action.ts +0 -3
  190. package/src/get-schema-from-api.spec.ts +1 -1
  191. package/src/get-schema-from-api.ts +1 -1
  192. package/src/helpers.spec.ts +51 -41
  193. package/src/helpers.ts +140 -109
  194. package/src/http-authentication-settings.ts +51 -25
  195. package/src/http-server-pool.spec.ts +220 -0
  196. package/src/http-server-pool.ts +169 -0
  197. package/src/http-user-context.spec.ts +203 -383
  198. package/src/http-user-context.ts +114 -91
  199. package/src/index.ts +6 -4
  200. package/src/login-response-strategy.spec.ts +32 -31
  201. package/src/login-response-strategy.ts +13 -12
  202. package/src/openapi/generate-openapi-document.ts +0 -3
  203. package/src/proxy-manager.spec.ts +152 -182
  204. package/src/proxy-runtime.spec.ts +50 -0
  205. package/src/proxy-runtime.ts +75 -0
  206. package/src/rest-api-runtime.spec.ts +77 -0
  207. package/src/rest-api-runtime.ts +204 -0
  208. package/src/rest-service.integration.spec.ts +68 -87
  209. package/src/rest.integration.test.ts +27 -87
  210. package/src/server-telemetry.spec.ts +56 -0
  211. package/src/server-telemetry.ts +51 -0
  212. package/src/static-server-manager.spec.ts +63 -151
  213. package/src/static-site-runtime.spec.ts +39 -0
  214. package/src/static-site-runtime.ts +75 -0
  215. package/src/user-store.spec.ts +86 -0
  216. package/src/user-store.ts +68 -0
  217. package/src/validate.integration.spec.ts +39 -97
  218. package/esm/actions/login-action.spec.d.ts +0 -2
  219. package/esm/actions/login-action.spec.d.ts.map +0 -1
  220. package/esm/actions/login-action.spec.js +0 -34
  221. package/esm/actions/login-action.spec.js.map +0 -1
  222. package/esm/actions/login.d.ts +0 -19
  223. package/esm/actions/login.d.ts.map +0 -1
  224. package/esm/actions/login.js +0 -27
  225. package/esm/actions/login.js.map +0 -1
  226. package/esm/api-manager.d.ts +0 -104
  227. package/esm/api-manager.d.ts.map +0 -1
  228. package/esm/api-manager.js +0 -180
  229. package/esm/api-manager.js.map +0 -1
  230. package/esm/proxy-manager.d.ts +0 -53
  231. package/esm/proxy-manager.d.ts.map +0 -1
  232. package/esm/proxy-manager.js +0 -84
  233. package/esm/proxy-manager.js.map +0 -1
  234. package/esm/server-manager.d.ts +0 -48
  235. package/esm/server-manager.d.ts.map +0 -1
  236. package/esm/server-manager.js +0 -100
  237. package/esm/server-manager.js.map +0 -1
  238. package/esm/static-server-manager.d.ts +0 -19
  239. package/esm/static-server-manager.d.ts.map +0 -1
  240. package/esm/static-server-manager.js +0 -65
  241. package/esm/static-server-manager.js.map +0 -1
  242. package/esm/swagger/generate-swagger-json.spec.d.ts +0 -2
  243. package/esm/swagger/generate-swagger-json.spec.d.ts.map +0 -1
  244. package/esm/swagger/generate-swagger-json.spec.js +0 -215
  245. package/esm/swagger/generate-swagger-json.spec.js.map +0 -1
  246. package/src/actions/login-action.spec.ts +0 -42
  247. package/src/actions/login.ts +0 -32
  248. package/src/api-manager.ts +0 -308
  249. package/src/proxy-manager.ts +0 -102
  250. package/src/server-manager.ts +0 -135
  251. package/src/static-server-manager.ts +0 -90
  252. package/src/swagger/generate-swagger-json.spec.ts +0 -250
@@ -0,0 +1,95 @@
1
+ import { defineService } from '@furystack/inject';
2
+ import { createServer } from 'http';
3
+ import { ServerTelemetryToken } from './server-telemetry.js';
4
+ export const DEFAULT_HOST = 'localhost';
5
+ const buildHostUrl = (options) => `http://${options.hostName ?? DEFAULT_HOST}:${options.port}`;
6
+ /**
7
+ * DI token for the {@link HttpServerPool}. Scoped so each test / application
8
+ * scope has its own set of HTTP servers and sockets.
9
+ */
10
+ export const HttpServerPoolToken = defineService({
11
+ name: 'furystack/rest-service/HttpServerPool',
12
+ lifetime: 'scoped',
13
+ factory: ({ inject, onDispose }) => {
14
+ const telemetry = inject(ServerTelemetryToken);
15
+ const servers = new Map();
16
+ const pendingCreates = new Map();
17
+ const openedSockets = new Set();
18
+ const trackConnection = (socket) => {
19
+ openedSockets.add(socket);
20
+ socket.once('close', () => openedSockets.delete(socket));
21
+ };
22
+ const createRecord = async (url, options) => {
23
+ const apis = [];
24
+ const server = createServer((req, res) => {
25
+ const apiMatch = apis.find((api) => api.shouldExec({ req, res }));
26
+ if (apiMatch) {
27
+ apiMatch.onRequest({ req, res }).catch((error) => {
28
+ telemetry.emit('onRequestFailed', [error, req, res]);
29
+ });
30
+ return;
31
+ }
32
+ res.destroy();
33
+ });
34
+ server.on('upgrade', (req, socket, head) => {
35
+ const apiMatch = apis.find((api) => api.shouldExec({ req, res: {} }));
36
+ if (apiMatch?.onUpgrade) {
37
+ apiMatch.onUpgrade({ req, socket, head }).catch((error) => {
38
+ telemetry.emit('onRequestFailed', [error, req, {}]);
39
+ socket.destroy();
40
+ });
41
+ return;
42
+ }
43
+ socket.destroy();
44
+ });
45
+ server.on('connection', trackConnection);
46
+ try {
47
+ await new Promise((resolve, reject) => {
48
+ server.on('listening', () => resolve());
49
+ server.on('error', (err) => reject(err));
50
+ server.listen(options.port, options.hostName);
51
+ });
52
+ const record = { server, apis };
53
+ servers.set(url, record);
54
+ telemetry.emit('onServerListening', { url, port: options.port, hostName: options.hostName });
55
+ return record;
56
+ }
57
+ finally {
58
+ pendingCreates.delete(url);
59
+ }
60
+ };
61
+ onDispose(async () => {
62
+ await Promise.allSettled([...pendingCreates.values()]);
63
+ openedSockets.forEach((socket) => socket.destroy());
64
+ await Promise.allSettled([...servers.entries()].map(([url, record]) => new Promise((resolve, reject) => {
65
+ record.server.close((err) => {
66
+ if (err) {
67
+ reject(err);
68
+ return;
69
+ }
70
+ telemetry.emit('onServerClosed', { url });
71
+ resolve();
72
+ });
73
+ record.server.off('connection', trackConnection);
74
+ })));
75
+ servers.clear();
76
+ });
77
+ return {
78
+ acquire: async (options) => {
79
+ const url = buildHostUrl(options);
80
+ const existing = servers.get(url);
81
+ if (existing) {
82
+ return existing;
83
+ }
84
+ const pending = pendingCreates.get(url);
85
+ if (pending) {
86
+ return pending;
87
+ }
88
+ const createPromise = createRecord(url, options);
89
+ pendingCreates.set(url, createPromise);
90
+ return createPromise;
91
+ },
92
+ };
93
+ },
94
+ });
95
+ //# sourceMappingURL=http-server-pool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-server-pool.js","sourceRoot":"","sources":["../src/http-server-pool.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=http-server-pool.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-server-pool.spec.d.ts","sourceRoot":"","sources":["../src/http-server-pool.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,193 @@
1
+ import { getPort } from '@furystack/core/port-generator';
2
+ import { createInjector } from '@furystack/inject';
3
+ import { usingAsync } from '@furystack/utils';
4
+ import { describe, expect, it, vi } from 'vitest';
5
+ import { WebSocket } from 'ws';
6
+ import { HttpServerPoolToken } from './http-server-pool.js';
7
+ import { ServerTelemetryToken } from './server-telemetry.js';
8
+ /** Returns a `ServerApi` that executes `handler` on every matching request. */
9
+ const makeApi = (handler) => ({
10
+ shouldExec: () => true,
11
+ onRequest: handler,
12
+ });
13
+ describe('HttpServerPool', () => {
14
+ it('Should return the cached record on subsequent acquire calls for the same url', async () => {
15
+ await usingAsync(createInjector(), async (injector) => {
16
+ const pool = injector.get(HttpServerPoolToken);
17
+ const port = getPort();
18
+ const first = await pool.acquire({ port });
19
+ const second = await pool.acquire({ port });
20
+ expect(second).toBe(first);
21
+ });
22
+ });
23
+ it('Should de-duplicate concurrent acquire calls into a single server creation', async () => {
24
+ await usingAsync(createInjector(), async (injector) => {
25
+ const pool = injector.get(HttpServerPoolToken);
26
+ const port = getPort();
27
+ const [a, b, c] = await Promise.all([pool.acquire({ port }), pool.acquire({ port }), pool.acquire({ port })]);
28
+ expect(b).toBe(a);
29
+ expect(c).toBe(a);
30
+ });
31
+ });
32
+ it('Should emit onServerListening when a new record is opened', async () => {
33
+ await usingAsync(createInjector(), async (injector) => {
34
+ const telemetry = injector.get(ServerTelemetryToken);
35
+ const listening = vi.fn();
36
+ telemetry.subscribe('onServerListening', listening);
37
+ const port = getPort();
38
+ await injector.get(HttpServerPoolToken).acquire({ port });
39
+ expect(listening).toHaveBeenCalledWith({ url: `http://localhost:${port}`, port, hostName: undefined });
40
+ });
41
+ });
42
+ it('Should destroy the socket and route the rejection through telemetry when onUpgrade rejects', async () => {
43
+ const injector = createInjector();
44
+ try {
45
+ const pool = injector.get(HttpServerPoolToken);
46
+ const telemetry = injector.get(ServerTelemetryToken);
47
+ const onRequestFailed = vi.fn();
48
+ telemetry.subscribe('onRequestFailed', onRequestFailed);
49
+ const port = getPort();
50
+ const record = await pool.acquire({ port });
51
+ const rejection = new Error('upgrade boom');
52
+ const onUpgrade = vi.fn().mockRejectedValue(rejection);
53
+ record.apis.push({
54
+ shouldExec: () => true,
55
+ onRequest: () => Promise.resolve(),
56
+ onUpgrade,
57
+ });
58
+ const socketClosed = new Promise((resolve) => {
59
+ const ws = new WebSocket(`ws://127.0.0.1:${port}`);
60
+ ws.on('close', () => resolve());
61
+ ws.on('error', () => resolve());
62
+ });
63
+ await socketClosed;
64
+ await new Promise((r) => setTimeout(r, 20));
65
+ expect(onUpgrade).toHaveBeenCalledTimes(1);
66
+ expect(onRequestFailed).toHaveBeenCalledTimes(1);
67
+ const [error] = onRequestFailed.mock.calls[0][0];
68
+ expect(error).toBe(rejection);
69
+ }
70
+ finally {
71
+ await injector[Symbol.asyncDispose]();
72
+ }
73
+ });
74
+ it('Should destroy the socket when no API claims the upgrade', async () => {
75
+ const injector = createInjector();
76
+ try {
77
+ const pool = injector.get(HttpServerPoolToken);
78
+ const port = getPort();
79
+ await pool.acquire({ port });
80
+ const ws = new WebSocket(`ws://127.0.0.1:${port}`);
81
+ await new Promise((resolve) => {
82
+ ws.on('close', () => resolve());
83
+ ws.on('error', () => resolve());
84
+ });
85
+ expect(ws.readyState).toBe(WebSocket.CLOSED);
86
+ }
87
+ finally {
88
+ await injector[Symbol.asyncDispose]();
89
+ }
90
+ });
91
+ it('Should destroy the raw socket when no API matches a normal request', async () => {
92
+ const injector = createInjector();
93
+ try {
94
+ const pool = injector.get(HttpServerPoolToken);
95
+ const port = getPort();
96
+ await pool.acquire({ port });
97
+ await expect(fetch(`http://127.0.0.1:${port}/nothing`)).rejects.toBeDefined();
98
+ }
99
+ finally {
100
+ await injector[Symbol.asyncDispose]();
101
+ }
102
+ });
103
+ it('Should surface onRequest rejections through telemetry without crashing the server', async () => {
104
+ const injector = createInjector();
105
+ try {
106
+ const pool = injector.get(HttpServerPoolToken);
107
+ const telemetry = injector.get(ServerTelemetryToken);
108
+ const onRequestFailed = vi.fn();
109
+ telemetry.subscribe('onRequestFailed', onRequestFailed);
110
+ const port = getPort();
111
+ const record = await pool.acquire({ port });
112
+ const rejection = new Error('request boom');
113
+ record.apis.push(makeApi(async ({ res }) => {
114
+ res.end();
115
+ throw rejection;
116
+ }));
117
+ await fetch(`http://127.0.0.1:${port}/test`);
118
+ await new Promise((r) => setTimeout(r, 20));
119
+ expect(onRequestFailed).toHaveBeenCalledTimes(1);
120
+ const [error] = onRequestFailed.mock.calls[0][0];
121
+ expect(error).toBe(rejection);
122
+ }
123
+ finally {
124
+ await injector[Symbol.asyncDispose]();
125
+ }
126
+ });
127
+ it('Should destroy open client sockets during disposal', async () => {
128
+ const injector = createInjector();
129
+ const pool = injector.get(HttpServerPoolToken);
130
+ const port = getPort();
131
+ const record = await pool.acquire({ port });
132
+ record.apis.push({
133
+ shouldExec: () => true,
134
+ onRequest: async ({ res }) => {
135
+ res.writeHead(200);
136
+ res.write('streaming');
137
+ },
138
+ });
139
+ // Keep a client socket open so the dispose path has something to destroy.
140
+ const inflight = fetch(`http://127.0.0.1:${port}/slow`).catch(() => undefined);
141
+ await new Promise((r) => setTimeout(r, 50));
142
+ await injector[Symbol.asyncDispose]();
143
+ await inflight;
144
+ });
145
+ it('Should absorb server.close errors during dispose without crashing', async () => {
146
+ const injector = createInjector();
147
+ const pool = injector.get(HttpServerPoolToken);
148
+ const port = getPort();
149
+ const record = await pool.acquire({ port });
150
+ // Force `server.close` to invoke its callback with an error -- the pool
151
+ // wraps every close in `Promise.allSettled`, so the rejected branch
152
+ // (`if (err) { reject(err); return }`) is exercised but the injector's
153
+ // dispose still resolves cleanly.
154
+ const closeError = new Error('close boom');
155
+ const closeSpy = vi.fn((cb) => {
156
+ cb?.(closeError);
157
+ return record.server;
158
+ });
159
+ record.server.close = closeSpy;
160
+ record.server.off = () => undefined;
161
+ await expect(injector[Symbol.asyncDispose]()).resolves.toBeUndefined();
162
+ expect(closeSpy).toHaveBeenCalled();
163
+ });
164
+ it('Should drain in-flight acquire promises during dispose', async () => {
165
+ const injector = createInjector();
166
+ const port = getPort();
167
+ const pool = injector.get(HttpServerPoolToken);
168
+ const inflight = pool.acquire({ port });
169
+ await injector[Symbol.asyncDispose]();
170
+ // The in-flight promise either resolved (server opened before dispose)
171
+ // or rejected cleanly — both are acceptable; we just assert no process
172
+ // crash / unhandled rejection escaped the pool's `allSettled` guard.
173
+ await inflight.catch(() => undefined);
174
+ });
175
+ it('Should stop tracking sockets that close before dispose', async () => {
176
+ const injector = createInjector();
177
+ try {
178
+ const pool = injector.get(HttpServerPoolToken);
179
+ const port = getPort();
180
+ const record = await pool.acquire({ port });
181
+ record.apis.push(makeApi(async ({ res }) => {
182
+ res.end('ok');
183
+ }));
184
+ await fetch(`http://127.0.0.1:${port}/ok`);
185
+ // Let the TCP socket actually flip to 'close' before we tear down.
186
+ await new Promise((r) => setTimeout(r, 50));
187
+ }
188
+ finally {
189
+ await injector[Symbol.asyncDispose]();
190
+ }
191
+ });
192
+ });
193
+ //# sourceMappingURL=http-server-pool.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-server-pool.spec.js","sourceRoot":"","sources":["../src/http-server-pool.spec.ts"],"names":[],"mappings":"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"}
@@ -1,10 +1,11 @@
1
1
  import type { User } from '@furystack/core';
2
+ import { type Token } from '@furystack/inject';
2
3
  import { EventHub, type ListenerErrorPayload } from '@furystack/utils';
3
4
  import type { IncomingMessage } from 'http';
4
5
  import { HttpAuthenticationSettings } from './http-authentication-settings.js';
5
- import type { DefaultSession } from './models/default-session.js';
6
6
  /**
7
- * Events emitted by the {@link HttpUserContext}
7
+ * Events emitted by a per-request {@link HttpUserContext} instance. Since
8
+ * the context is scoped, each request sees its own event stream.
8
9
  */
9
10
  export type HttpUserContextEvents = {
10
11
  onLogin: {
@@ -15,56 +16,42 @@ export type HttpUserContextEvents = {
15
16
  onListenerError: ListenerErrorPayload;
16
17
  };
17
18
  /**
18
- * Injectable UserContext for FuryStack HTTP Api
19
+ * Request-scoped authentication/authorization facade.
20
+ *
21
+ * `HttpUserContext` consolidates identity lookup, authentication-provider
22
+ * walk-through and cookie session helpers behind a single object. Resolved
23
+ * users are memoized per `request.headers` via a {@link WeakMap} so repeated
24
+ * calls within the same request skip re-authentication — and so ancestor
25
+ * scopes caching the same `HttpUserContext` instance cannot leak an
26
+ * authenticated user into sibling requests.
19
27
  */
20
- export declare class HttpUserContext extends EventHub<HttpUserContextEvents> {
21
- getUserDataSet: () => import("@furystack/repository").DataSet<User, "username", import("@furystack/core").WithOptionalId<User, "username">>;
22
- getSessionDataSet: () => import("@furystack/repository").DataSet<DefaultSession, "sessionId", import("@furystack/core").WithOptionalId<DefaultSession, "sessionId">>;
23
- private getUserByName;
24
- private user?;
25
- /**
26
- * @param request The request to be authenticated
27
- * @returns whether the current user is authenticated
28
- */
29
- isAuthenticated(request: IncomingMessage): Promise<boolean>;
30
- /**
31
- * Returns whether the current user can be authorized with ALL of the specified roles
32
- * @param request The request to be authenticated
33
- * @param roles The list of roles to authorize
34
- * @returns a boolean value that indicates whether the user is authorized
35
- */
36
- isAuthorized(request: IncomingMessage, ...roles: string[]): Promise<boolean>;
37
- /**
38
- * Checks if the system contains a user with the provided name and password, throws an error otherwise
39
- * @param userName The username
40
- * @param password The password
41
- * @returns the authenticated User
42
- */
43
- authenticateUser(userName: string, password: string): Promise<import("@furystack/core").PartialResult<User, (keyof User)[]>>;
28
+ export interface HttpUserContext extends EventHub<HttpUserContextEvents> {
29
+ /** The active {@link HttpAuthenticationSettings} captured at resolve time. */
30
+ readonly authentication: HttpAuthenticationSettings;
31
+ /** Whether the current user is authenticated according to the bound providers. */
32
+ isAuthenticated(request: Pick<IncomingMessage, 'headers'>): Promise<boolean>;
33
+ /** Returns `true` when the current user has **all** of the supplied roles. */
34
+ isAuthorized(request: Pick<IncomingMessage, 'headers'>, ...roles: string[]): Promise<boolean>;
35
+ /** Verifies a username/password pair against the system `PasswordAuthenticator`. */
36
+ authenticateUser(userName: string, password: string): Promise<User>;
37
+ /** Returns the cached user for this request or resolves it via the provider chain. */
44
38
  getCurrentUser(request: Pick<IncomingMessage, 'headers'>): Promise<User>;
39
+ /** Extracts the session id for `request` using the configured cookie name. */
45
40
  getSessionIdFromRequest(request: Pick<IncomingMessage, 'headers'>): string | null;
46
- /**
47
- * Iterates registered authentication providers in order.
48
- * - A provider returning `User` means authentication succeeded.
49
- * - A provider returning `null` means it does not apply; try the next one.
50
- * - A provider throwing means it owns the request but auth failed; propagate the error.
51
- */
41
+ /** Walks the configured authentication providers and returns the first match. */
52
42
  authenticateRequest(request: Pick<IncomingMessage, 'headers'>): Promise<User>;
53
- /**
54
- * Creates and sets up a cookie-based session for the provided user
55
- * @param user The user to create a session for
56
- * @param serverResponse A serverResponse to set the cookie
57
- * @returns the current User
58
- */
59
- cookieLogin(user: User, serverResponse: {
43
+ /** Creates and persists a session for `user`, setting the cookie on `response`. */
44
+ cookieLogin(user: User, response: {
60
45
  setHeader: (header: string, value: string) => void;
61
46
  }): Promise<User>;
47
+ /** Clears the session identified by the request's cookie (if any). */
62
48
  cookieLogout(request: Pick<IncomingMessage, 'headers'>, response: {
63
49
  setHeader: (header: string, value: string) => void;
64
50
  }): Promise<void>;
65
- readonly authentication: HttpAuthenticationSettings<User, DefaultSession>;
66
- private readonly systemInjector;
67
- private readonly authenticator;
68
- init(): void;
69
51
  }
52
+ /**
53
+ * DI token for the per-request {@link HttpUserContext}. Scoped — each HTTP
54
+ * request resolves its own instance with a fresh `user` cache.
55
+ */
56
+ export declare const HttpUserContext: Token<HttpUserContext, 'scoped'>;
70
57
  //# sourceMappingURL=http-user-context.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"http-user-context.d.ts","sourceRoot":"","sources":["../src/http-user-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AAK3C,OAAO,EAAE,QAAQ,EAAE,KAAK,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AAEtE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,MAAM,CAAA;AAE3C,OAAO,EAAE,0BAA0B,EAAE,MAAM,mCAAmC,CAAA;AAC9E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAEjE;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,EAAE;QAAE,IAAI,EAAE,IAAI,CAAA;KAAE,CAAA;IACvB,QAAQ,EAAE,SAAS,CAAA;IACnB,oBAAoB,EAAE,SAAS,CAAA;IAC/B,eAAe,EAAE,oBAAoB,CAAA;CACtC,CAAA;AAED;;GAEG;AACH,qBACa,eAAgB,SAAQ,QAAQ,CAAC,qBAAqB,CAAC;IAC3D,cAAc,8HAAgE;IAE9E,iBAAiB,oJAAmE;IAE3F,OAAO,CAAC,aAAa,CAOpB;IAED,OAAO,CAAC,IAAI,CAAC,CAAM;IAEnB;;;OAGG;IACU,eAAe,CAAC,OAAO,EAAE,eAAe;IASrD;;;;;OAKG;IACU,YAAY,CAAC,OAAO,EAAE,eAAe,EAAE,GAAG,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAUzF;;;;;OAKG;IACU,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;IAanD,cAAc,CAAC,OAAO,EAAE,IAAI,CAAC,eAAe,EAAE,SAAS,CAAC;IAQ9D,uBAAuB,CAAC,OAAO,EAAE,IAAI,CAAC,eAAe,EAAE,SAAS,CAAC,GAAG,MAAM,GAAG,IAAI;IAIxF;;;;;OAKG;IACU,mBAAmB,CAAC,OAAO,EAAE,IAAI,CAAC,eAAe,EAAE,SAAS,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ1F;;;;;OAKG;IACU,WAAW,CACtB,IAAI,EAAE,IAAI,EACV,cAAc,EAAE;QAAE,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,GACrE,OAAO,CAAC,IAAI,CAAC;IASH,YAAY,CACvB,OAAO,EAAE,IAAI,CAAC,eAAe,EAAE,SAAS,CAAC,EACzC,QAAQ,EAAE;QAAE,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE;IAclE,SACwB,cAAc,EAAE,0BAA0B,CAAC,IAAI,EAAE,cAAc,CAAC,CAAA;IAExF,iBACyB,cAAc,CAAU;IAEjD,iBACyB,aAAa,CAAuB;IAEtD,IAAI;CAmBZ"}
1
+ {"version":3,"file":"http-user-context.d.ts","sourceRoot":"","sources":["../src/http-user-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AAG3C,OAAO,EAAiB,KAAK,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAE7D,OAAO,EAAE,QAAQ,EAAE,KAAK,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AAEtE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,MAAM,CAAA;AAE3C,OAAO,EAAE,0BAA0B,EAAE,MAAM,mCAAmC,CAAA;AAE9E;;;GAGG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,EAAE;QAAE,IAAI,EAAE,IAAI,CAAA;KAAE,CAAA;IACvB,QAAQ,EAAE,SAAS,CAAA;IACnB,oBAAoB,EAAE,SAAS,CAAA;IAC/B,eAAe,EAAE,oBAAoB,CAAA;CACtC,CAAA;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,eAAgB,SAAQ,QAAQ,CAAC,qBAAqB,CAAC;IACtE,8EAA8E;IAC9E,QAAQ,CAAC,cAAc,EAAE,0BAA0B,CAAA;IACnD,kFAAkF;IAClF,eAAe,CAAC,OAAO,EAAE,IAAI,CAAC,eAAe,EAAE,SAAS,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAC5E,8EAA8E;IAC9E,YAAY,CAAC,OAAO,EAAE,IAAI,CAAC,eAAe,EAAE,SAAS,CAAC,EAAE,GAAG,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAC7F,oFAAoF;IACpF,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACnE,sFAAsF;IACtF,cAAc,CAAC,OAAO,EAAE,IAAI,CAAC,eAAe,EAAE,SAAS,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACxE,8EAA8E;IAC9E,uBAAuB,CAAC,OAAO,EAAE,IAAI,CAAC,eAAe,EAAE,SAAS,CAAC,GAAG,MAAM,GAAG,IAAI,CAAA;IACjF,iFAAiF;IACjF,mBAAmB,CAAC,OAAO,EAAE,IAAI,CAAC,eAAe,EAAE,SAAS,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC7E,mFAAmF;IACnF,WAAW,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE;QAAE,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACxG,sEAAsE;IACtE,YAAY,CACV,OAAO,EAAE,IAAI,CAAC,eAAe,EAAE,SAAS,CAAC,EACzC,QAAQ,EAAE;QAAE,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,GAC/D,OAAO,CAAC,IAAI,CAAC,CAAA;CACjB;AAoID;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,eAAe,EAAE,QAAQ,CAW3D,CAAA"}