@shipfox/api-server 5.0.0 → 7.0.1
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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +125 -0
- package/README.md +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modules.d.ts +5 -1
- package/dist/modules.d.ts.map +1 -1
- package/dist/modules.js +119 -62
- package/dist/modules.js.map +1 -1
- package/dist/server.d.ts +2 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +61 -23
- package/dist/server.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +31 -16
- package/src/index.ts +1 -1
- package/src/modules.test.ts +267 -58
- package/src/modules.ts +140 -87
- package/src/server.test.ts +126 -34
- package/src/server.ts +61 -20
- package/tsconfig.build.tsbuildinfo +1 -1
package/src/server.ts
CHANGED
|
@@ -3,13 +3,14 @@ import {closeApp, createApp, listen} from '@shipfox/node-fastify';
|
|
|
3
3
|
import {
|
|
4
4
|
aggregateLoginMethods,
|
|
5
5
|
initializeModules,
|
|
6
|
+
type ModuleService,
|
|
7
|
+
type ModuleServicesHandle,
|
|
6
8
|
type ModuleWorker,
|
|
7
9
|
type ModuleWorkersHandle,
|
|
8
10
|
registerModuleMetrics,
|
|
9
|
-
resetPublishers,
|
|
10
|
-
resetSubscribers,
|
|
11
11
|
runModuleStartupTasks,
|
|
12
12
|
type ShipfoxModule,
|
|
13
|
+
startModuleServices,
|
|
13
14
|
startModuleWorkers,
|
|
14
15
|
} from '@shipfox/node-module';
|
|
15
16
|
import {logger, shutdownServiceMetrics, startServiceMetrics} from '@shipfox/node-opentelemetry';
|
|
@@ -17,7 +18,7 @@ import {closePostgresClient, createPostgresClient} from '@shipfox/node-postgres'
|
|
|
17
18
|
import {config, parseApiTrustProxy} from './config.js';
|
|
18
19
|
import {createE2eAdminAuthMethod, createE2eRouteGroup} from './e2e.js';
|
|
19
20
|
|
|
20
|
-
const
|
|
21
|
+
const RUNTIME_FAILURE_HTTP_SHUTDOWN_TIMEOUT_MS = 10_000;
|
|
21
22
|
const ERROR_MONITORING_SHUTDOWN_TIMEOUT_MS = 2_000;
|
|
22
23
|
|
|
23
24
|
let hasActiveServer = false;
|
|
@@ -25,6 +26,7 @@ let hasActiveServer = false;
|
|
|
25
26
|
export interface CreateServerOptions {
|
|
26
27
|
modules: ShipfoxModule[];
|
|
27
28
|
onWorkerFailure?: (error: unknown, worker: ModuleWorker) => void | Promise<void>;
|
|
29
|
+
onServiceFailure?: (error: unknown, service: ModuleService) => void | Promise<void>;
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
export interface ServerHandle {
|
|
@@ -48,9 +50,11 @@ export async function createServer(options: CreateServerOptions): Promise<Server
|
|
|
48
50
|
startServiceMetrics({serviceName: 'api'});
|
|
49
51
|
createPostgresClient();
|
|
50
52
|
|
|
51
|
-
const {auth, routes, e2eRoutes, workers} = await initializeModules({
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
const {auth, routes, e2eRoutes, workers, services, outboxRegistry} = await initializeModules({
|
|
54
|
+
modules: options.modules,
|
|
55
|
+
});
|
|
56
|
+
registerModuleMetrics({modules: options.modules, context: {outboxRegistry}});
|
|
57
|
+
await runModuleStartupTasks({modules: options.modules, context: {outboxRegistry}});
|
|
54
58
|
|
|
55
59
|
const e2eAuth = config.E2E_ENABLED ? [createE2eAdminAuthMethod(config)] : [];
|
|
56
60
|
const mountedE2eRoutes = createE2eRouteGroup(e2eRoutes, config);
|
|
@@ -63,6 +67,7 @@ export async function createServer(options: CreateServerOptions): Promise<Server
|
|
|
63
67
|
});
|
|
64
68
|
|
|
65
69
|
let workersHandle: ModuleWorkersHandle | undefined;
|
|
70
|
+
let servicesHandle: ModuleServicesHandle | undefined;
|
|
66
71
|
let startPromise: Promise<string> | undefined;
|
|
67
72
|
let stopPromise: Promise<void> | undefined;
|
|
68
73
|
let stopped = false;
|
|
@@ -75,10 +80,19 @@ export async function createServer(options: CreateServerOptions): Promise<Server
|
|
|
75
80
|
logger().info('Starting module workers');
|
|
76
81
|
workersHandle = await startModuleWorkers({
|
|
77
82
|
workers,
|
|
83
|
+
context: {outboxRegistry},
|
|
78
84
|
onWorkerFailure: (error, worker) =>
|
|
79
85
|
handleModuleWorkerFailure(error, worker, options.onWorkerFailure),
|
|
80
86
|
});
|
|
81
87
|
|
|
88
|
+
logger().info('Starting module services');
|
|
89
|
+
servicesHandle = await startModuleServices({
|
|
90
|
+
services,
|
|
91
|
+
context: {outboxRegistry},
|
|
92
|
+
onServiceFailure: (error, service) =>
|
|
93
|
+
handleModuleServiceFailure(error, service, options.onServiceFailure),
|
|
94
|
+
});
|
|
95
|
+
|
|
82
96
|
if (stopPromise) throw new Error('API server stopped during startup');
|
|
83
97
|
|
|
84
98
|
logger().info('Starting HTTP server');
|
|
@@ -98,12 +112,13 @@ export async function createServer(options: CreateServerOptions): Promise<Server
|
|
|
98
112
|
await startPromise?.catch(() => undefined);
|
|
99
113
|
const cleanupErrors = await runCleanupSteps([
|
|
100
114
|
() => closeApp(),
|
|
115
|
+
() => servicesHandle?.stop(),
|
|
101
116
|
() => workersHandle?.stop(),
|
|
102
117
|
() => shutdownServiceMetrics(),
|
|
103
118
|
() => closePostgresClient(),
|
|
104
|
-
() =>
|
|
105
|
-
|
|
106
|
-
|
|
119
|
+
async () => {
|
|
120
|
+
await closeErrorMonitoring(ERROR_MONITORING_SHUTDOWN_TIMEOUT_MS);
|
|
121
|
+
},
|
|
107
122
|
]);
|
|
108
123
|
throwCleanupErrors(cleanupErrors, 'Failed to stop API server');
|
|
109
124
|
stopped = true;
|
|
@@ -120,8 +135,6 @@ export async function createServer(options: CreateServerOptions): Promise<Server
|
|
|
120
135
|
const cleanupErrors = await runCleanupSteps([
|
|
121
136
|
() => shutdownServiceMetrics(),
|
|
122
137
|
() => closePostgresClient(),
|
|
123
|
-
() => resetPublishers(),
|
|
124
|
-
() => resetSubscribers(),
|
|
125
138
|
]);
|
|
126
139
|
for (const cleanupError of cleanupErrors) {
|
|
127
140
|
logger().error({err: cleanupError, bootError: error}, 'Failed to clean up API server boot');
|
|
@@ -137,6 +150,7 @@ export async function runServer(options: RunServerOptions): Promise<ServerHandle
|
|
|
137
150
|
handle = await createServer({
|
|
138
151
|
modules: options.modules,
|
|
139
152
|
onWorkerFailure: () => process.exit(1),
|
|
153
|
+
onServiceFailure: () => process.exit(1),
|
|
140
154
|
});
|
|
141
155
|
} catch (error) {
|
|
142
156
|
await reportStartupFailure(error, options.onStartupFailure);
|
|
@@ -204,21 +218,48 @@ async function handleModuleWorkerFailure(
|
|
|
204
218
|
worker: ModuleWorker,
|
|
205
219
|
onWorkerFailure: CreateServerOptions['onWorkerFailure'],
|
|
206
220
|
): Promise<void> {
|
|
207
|
-
|
|
208
|
-
|
|
221
|
+
await handleModuleRuntimeFailure({
|
|
222
|
+
error,
|
|
223
|
+
fields: {taskQueue: worker.taskQueue},
|
|
224
|
+
message: 'Module worker stopped unexpectedly',
|
|
225
|
+
onFailure: () => onWorkerFailure?.(error, worker),
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async function handleModuleServiceFailure(
|
|
230
|
+
error: unknown,
|
|
231
|
+
service: ModuleService,
|
|
232
|
+
onServiceFailure: CreateServerOptions['onServiceFailure'],
|
|
233
|
+
): Promise<void> {
|
|
234
|
+
await handleModuleRuntimeFailure({
|
|
235
|
+
error,
|
|
236
|
+
fields: {service: service.name},
|
|
237
|
+
message: 'Module service stopped unexpectedly',
|
|
238
|
+
onFailure: () => onServiceFailure?.(error, service),
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async function handleModuleRuntimeFailure(options: {
|
|
243
|
+
error: unknown;
|
|
244
|
+
fields: Record<string, string>;
|
|
245
|
+
message: string;
|
|
246
|
+
onFailure(): void | Promise<void>;
|
|
247
|
+
}): Promise<void> {
|
|
248
|
+
logger().error({err: options.error, ...options.fields}, options.message);
|
|
249
|
+
captureException(options.error);
|
|
209
250
|
|
|
210
251
|
try {
|
|
211
|
-
await
|
|
252
|
+
await closeHttpServerAfterRuntimeFailure();
|
|
212
253
|
await closeErrorMonitoring(ERROR_MONITORING_SHUTDOWN_TIMEOUT_MS);
|
|
213
254
|
} finally {
|
|
214
|
-
await
|
|
255
|
+
await options.onFailure();
|
|
215
256
|
}
|
|
216
257
|
}
|
|
217
258
|
|
|
218
|
-
async function
|
|
259
|
+
async function closeHttpServerAfterRuntimeFailure(): Promise<void> {
|
|
219
260
|
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
|
220
261
|
const timeout = new Promise<'timeout'>((resolve) => {
|
|
221
|
-
timeoutId = setTimeout(() => resolve('timeout'),
|
|
262
|
+
timeoutId = setTimeout(() => resolve('timeout'), RUNTIME_FAILURE_HTTP_SHUTDOWN_TIMEOUT_MS);
|
|
222
263
|
});
|
|
223
264
|
try {
|
|
224
265
|
const result = await Promise.race([closeApp().then(() => 'closed' as const), timeout]).finally(
|
|
@@ -230,12 +271,12 @@ async function closeHttpServerAfterWorkerFailure(): Promise<void> {
|
|
|
230
271
|
);
|
|
231
272
|
if (result === 'timeout') {
|
|
232
273
|
logger().error(
|
|
233
|
-
{timeoutMs:
|
|
234
|
-
'Timed out closing HTTP server after
|
|
274
|
+
{timeoutMs: RUNTIME_FAILURE_HTTP_SHUTDOWN_TIMEOUT_MS},
|
|
275
|
+
'Timed out closing HTTP server after module runtime failure',
|
|
235
276
|
);
|
|
236
277
|
}
|
|
237
278
|
} catch (error) {
|
|
238
|
-
logger().error({err: error}, 'Failed to close HTTP server after
|
|
279
|
+
logger().error({err: error}, 'Failed to close HTTP server after module runtime failure');
|
|
239
280
|
} finally {
|
|
240
281
|
if (timeoutId) {
|
|
241
282
|
clearTimeout(timeoutId);
|