@shipfox/api-server 7.0.2 → 8.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.
package/src/server.ts CHANGED
@@ -1,4 +1,4 @@
1
- import {captureException, closeErrorMonitoring} from '@shipfox/node-error-monitoring';
1
+ import {closeErrorMonitoring, markErrorReported, reportError} from '@shipfox/node-error-monitoring';
2
2
  import {closeApp, createApp, listen} from '@shipfox/node-fastify';
3
3
  import {
4
4
  aggregateLoginMethods,
@@ -17,6 +17,7 @@ import {logger, shutdownServiceMetrics, startServiceMetrics} from '@shipfox/node
17
17
  import {closePostgresClient, createPostgresClient} from '@shipfox/node-postgres';
18
18
  import {config, parseApiTrustProxy} from './config.js';
19
19
  import {createE2eAdminAuthMethod, createE2eRouteGroup} from './e2e.js';
20
+ import {createLoginMethodsRoute} from './routes/login-methods.js';
20
21
 
21
22
  const RUNTIME_FAILURE_HTTP_SHUTDOWN_TIMEOUT_MS = 10_000;
22
23
  const ERROR_MONITORING_SHUTDOWN_TIMEOUT_MS = 2_000;
@@ -46,7 +47,7 @@ export async function createServer(options: CreateServerOptions): Promise<Server
46
47
  hasActiveServer = true;
47
48
 
48
49
  try {
49
- aggregateLoginMethods({modules: options.modules});
50
+ const loginMethods = aggregateLoginMethods({modules: options.modules});
50
51
  startServiceMetrics({serviceName: 'api'});
51
52
  createPostgresClient();
52
53
 
@@ -62,7 +63,7 @@ export async function createServer(options: CreateServerOptions): Promise<Server
62
63
  logger().info('Creating HTTP server');
63
64
  await createApp({
64
65
  auth: [...auth, ...e2eAuth],
65
- routes: [...routes, ...mountedE2eRoutes],
66
+ routes: [createLoginMethodsRoute({loginMethods}), ...routes, ...mountedE2eRoutes],
66
67
  fastifyOptions: {trustProxy: parseApiTrustProxy(config.API_TRUST_PROXY)},
67
68
  });
68
69
 
@@ -116,10 +117,24 @@ export async function createServer(options: CreateServerOptions): Promise<Server
116
117
  () => workersHandle?.stop(),
117
118
  () => shutdownServiceMetrics(),
118
119
  () => closePostgresClient(),
119
- async () => {
120
- await closeErrorMonitoring(ERROR_MONITORING_SHUTDOWN_TIMEOUT_MS);
121
- },
122
120
  ]);
121
+ for (const cleanupError of cleanupErrors) {
122
+ logger().error({err: cleanupError}, 'Failed to clean up API server during shutdown');
123
+ reportError(cleanupError, {boundary: 'api.shutdown', operation: 'cleanup'});
124
+ }
125
+ try {
126
+ const errorMonitoringClosed = await closeErrorMonitoring(
127
+ ERROR_MONITORING_SHUTDOWN_TIMEOUT_MS,
128
+ );
129
+ if (!errorMonitoringClosed) {
130
+ logger().error('Timed out closing error monitoring during API shutdown');
131
+ cleanupErrors.push(
132
+ new Error('Timed out closing error monitoring during API shutdown'),
133
+ );
134
+ }
135
+ } catch (error) {
136
+ cleanupErrors.push(error);
137
+ }
123
138
  throwCleanupErrors(cleanupErrors, 'Failed to stop API server');
124
139
  stopped = true;
125
140
  hasActiveServer = false;
@@ -138,6 +153,7 @@ export async function createServer(options: CreateServerOptions): Promise<Server
138
153
  ]);
139
154
  for (const cleanupError of cleanupErrors) {
140
155
  logger().error({err: cleanupError, bootError: error}, 'Failed to clean up API server boot');
156
+ reportError(cleanupError, {boundary: 'api.startup', operation: 'cleanup'});
141
157
  }
142
158
  hasActiveServer = false;
143
159
  throw error;
@@ -168,11 +184,23 @@ export async function runServer(options: RunServerOptions): Promise<ServerHandle
168
184
  {err: cleanupError, startError: error},
169
185
  'Failed to clean up API server startup',
170
186
  );
187
+ reportError(cleanupError, {
188
+ boundary: 'api.startup',
189
+ operation: 'cleanup-after-failed-start',
190
+ });
171
191
  }
172
192
  throw error;
173
193
  }
174
194
 
175
- const stopAndExit = () => void handle.stop().finally(() => process.exit(0));
195
+ const stopAndExit = () =>
196
+ void handle.stop().then(
197
+ () => process.exit(0),
198
+ (error) => {
199
+ logger().error({err: error}, 'Failed to stop API server after shutdown signal');
200
+ reportError(error, {boundary: 'api.shutdown', operation: 'signal-stop'});
201
+ process.exit(1);
202
+ },
203
+ );
176
204
  process.once('SIGTERM', stopAndExit);
177
205
  process.once('SIGINT', stopAndExit);
178
206
 
@@ -210,7 +238,9 @@ async function runCleanupSteps(steps: CleanupStep[]): Promise<unknown[]> {
210
238
  function throwCleanupErrors(cleanupErrors: unknown[], message: string): void {
211
239
  if (cleanupErrors.length === 0) return;
212
240
  if (cleanupErrors.length === 1) throw cleanupErrors[0];
213
- throw new AggregateError(cleanupErrors, message);
241
+ const aggregate = new AggregateError(cleanupErrors, message);
242
+ markErrorReported(aggregate);
243
+ throw aggregate;
214
244
  }
215
245
 
216
246
  async function handleModuleWorkerFailure(
@@ -246,11 +276,19 @@ async function handleModuleRuntimeFailure(options: {
246
276
  onFailure(): void | Promise<void>;
247
277
  }): Promise<void> {
248
278
  logger().error({err: options.error, ...options.fields}, options.message);
249
- captureException(options.error);
279
+ reportError(options.error, {
280
+ boundary: 'api.runtime',
281
+ tags: options.fields,
282
+ });
250
283
 
251
284
  try {
252
285
  await closeHttpServerAfterRuntimeFailure();
253
- await closeErrorMonitoring(ERROR_MONITORING_SHUTDOWN_TIMEOUT_MS);
286
+ const errorMonitoringClosed = await closeErrorMonitoring(ERROR_MONITORING_SHUTDOWN_TIMEOUT_MS);
287
+ if (!errorMonitoringClosed) {
288
+ logger().error('Timed out closing error monitoring after module runtime failure');
289
+ }
290
+ } catch (error) {
291
+ logger().error({err: error}, 'Failed to close error monitoring after module runtime failure');
254
292
  } finally {
255
293
  await options.onFailure();
256
294
  }
@@ -270,13 +308,13 @@ async function closeHttpServerAfterRuntimeFailure(): Promise<void> {
270
308
  },
271
309
  );
272
310
  if (result === 'timeout') {
273
- logger().error(
274
- {timeoutMs: RUNTIME_FAILURE_HTTP_SHUTDOWN_TIMEOUT_MS},
275
- 'Timed out closing HTTP server after module runtime failure',
276
- );
311
+ const error = new Error('Timed out closing HTTP server after module runtime failure');
312
+ logger().error({timeoutMs: RUNTIME_FAILURE_HTTP_SHUTDOWN_TIMEOUT_MS}, error.message);
313
+ reportError(error, {boundary: 'api.runtime', operation: 'close-http-timeout'});
277
314
  }
278
315
  } catch (error) {
279
316
  logger().error({err: error}, 'Failed to close HTTP server after module runtime failure');
317
+ reportError(error, {boundary: 'api.runtime', operation: 'close-http'});
280
318
  } finally {
281
319
  if (timeoutId) {
282
320
  clearTimeout(timeoutId);