@jskit-ai/kernel 0.1.245 → 0.1.247

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 (102) hide show
  1. package/client/appConfig.js +2 -1
  2. package/client/asyncModuleRecovery.js +2 -4
  3. package/client/index.d.ts +13 -75
  4. package/client/index.js +1 -1
  5. package/client/logging.js +8 -2
  6. package/client/moduleBootstrap.js +41 -33
  7. package/client/moduleBootstrap.test.js +35 -0
  8. package/client/shellBootstrap.js +1 -8
  9. package/client/shellBootstrap.test.js +32 -5
  10. package/client/shellRouting.js +16 -61
  11. package/client/shellRouting.test.js +30 -1
  12. package/client/vite/clientBootstrapPlugin.js +0 -6
  13. package/client/vite/clientBootstrapPlugin.test.js +2 -3
  14. package/internal/node/installedPackages.js +19 -62
  15. package/internal/node/installedPackages.test.js +33 -0
  16. package/package.json +2 -4
  17. package/server/actions/actionCatalogue.js +13 -34
  18. package/{shared → server}/actions/actionContributorHelpers.js +0 -5
  19. package/{shared → server}/actions/actionDefinitions.js +24 -131
  20. package/{shared → server}/actions/actionDefinitions.test.js +75 -21
  21. package/server/actions/actionEvents.js +97 -2
  22. package/server/actions/actionEvents.test.js +79 -0
  23. package/{shared → server}/actions/executionContext.js +3 -13
  24. package/server/actions/idempotency.js +24 -0
  25. package/server/actions/pipeline.js +87 -0
  26. package/{shared → server}/actions/policies.js +20 -33
  27. package/{shared → server}/actions/policies.test.js +6 -6
  28. package/{shared → server}/actions/registry.js +3 -22
  29. package/{shared → server}/actions/registry.test.js +70 -32
  30. package/server/actions/requestMeta.js +17 -0
  31. package/server/http/capabilityHttpRuntime.js +1 -1
  32. package/server/http/capabilityHttpRuntime.test.js +56 -0
  33. package/server/http/errorHandling.js +291 -0
  34. package/server/{runtime/fastifyBootstrap.test.js → http/errorHandling.test.js} +72 -120
  35. package/server/http/lib/routeRegistration.js +36 -150
  36. package/server/http/lib/routeTransport.js +2 -0
  37. package/server/http/lib/routeValidator.js +5 -47
  38. package/server/http/lib/routeValidator.test.js +15 -17
  39. package/server/http/lib/router.js +10 -94
  40. package/server/platform/providerRuntime/capabilityProviderLoader.js +2 -1
  41. package/server/platform/providerRuntime/helpers.js +1 -34
  42. package/server/runtime/integers.js +1 -5
  43. package/server/runtime/serviceAuthorization.js +4 -9
  44. package/server/support/appConfig.js +2 -1
  45. package/server/support/appConfigFiles.js +1 -38
  46. package/server/support/appConfigFiles.test.js +3 -39
  47. package/server/support/index.js +1 -20
  48. package/shared/capabilities/defineProvider.js +1 -1
  49. package/shared/capabilities/index.js +1 -1
  50. package/shared/index.js +1 -1
  51. package/shared/support/index.js +1 -4
  52. package/shared/support/mobileConfig.js +99 -0
  53. package/shared/support/mobileConfig.test.js +113 -0
  54. package/shared/support/normalize.js +0 -108
  55. package/shared/support/normalize.test.js +0 -134
  56. package/shared/{actions/actionContributorHelpers.test.js → support/permissions.test.js} +1 -1
  57. package/shared/support/returnToPath.js +1 -20
  58. package/shared/support/returnToPath.test.js +1 -36
  59. package/shared/support/shellLayoutTargets.js +2 -140
  60. package/shared/support/shellLayoutTargets.test.js +1 -110
  61. package/shared/support/sorting.js +1 -9
  62. package/shared/surface/apiPaths.js +1 -82
  63. package/shared/surface/paths.js +38 -171
  64. package/shared/surface/registry.js +2 -1
  65. package/shared/surface/runtime.js +18 -109
  66. package/shared/surface/runtime.test.js +57 -0
  67. package/shared/validators/index.js +1 -4
  68. package/shared/validators/jsonRestSchemaSupport.js +0 -9
  69. package/shared/validators/recordIdParamsValidator.js +0 -6
  70. package/shared/validators/schemaDefinitions.js +4 -30
  71. package/shared/validators/schemaPayloadValidation.js +2 -6
  72. package/test/barrelExposure.test.js +3 -7
  73. package/test/exportsContract.test.js +1 -3
  74. package/client/mobileLaunchRouting.js +0 -231
  75. package/client/mobileLaunchRouting.test.js +0 -230
  76. package/server/http/lib/controller.js +0 -183
  77. package/server/http/lib/controller.test.js +0 -143
  78. package/server/http/lib/index.js +0 -5
  79. package/server/runtime/canonicalJson.js +0 -74
  80. package/server/runtime/entityChangeEvents.js +0 -307
  81. package/server/runtime/entityChangeEvents.test.js +0 -274
  82. package/server/runtime/fastifyBootstrap.js +0 -553
  83. package/server/runtime/moduleConfig.js +0 -223
  84. package/server/runtime/moduleConfig.test.js +0 -165
  85. package/server/runtime/pagination.js +0 -13
  86. package/server/runtime/realtimeNormalization.js +0 -21
  87. package/server/runtime/routeUtils.js +0 -20
  88. package/server/runtime/securityAudit.js +0 -269
  89. package/server/runtime/securityAudit.test.js +0 -41
  90. package/server/support/pageTargets.js +0 -772
  91. package/server/support/pageTargets.test.js +0 -659
  92. package/server/support/shellOutlets.js +0 -576
  93. package/server/support/shellOutlets.test.js +0 -336
  94. package/server/surface/index.js +0 -18
  95. package/shared/actions/audit.js +0 -7
  96. package/shared/actions/idempotency.js +0 -62
  97. package/shared/actions/observability.js +0 -10
  98. package/shared/actions/pipeline.js +0 -309
  99. package/shared/actions/requestMeta.js +0 -36
  100. package/shared/actions/textNormalization.js +0 -3
  101. package/shared/actions/withActionDefaults.js +0 -34
  102. /package/{shared → server}/actions/executionContext.test.js +0 -0
@@ -1,553 +0,0 @@
1
- import { ActionRuntimeError } from "../../shared/actions/actionDefinitions.js";
2
- import { normalizeOpaqueId, normalizeTransactionOutcome } from "../../shared/support/normalize.js";
3
- import { resolveDefaultSurfaceId } from "../support/appConfig.js";
4
-
5
- const JSON_API_CONTENT_TYPE = "application/vnd.api+json";
6
- const JSON_API_CONTENT_TYPE_PARSER_MARKER = Symbol.for("jskit.fastify.jsonApiContentTypeParserRegistered");
7
- const BODYLESS_CONTENT_TYPE_NORMALIZER_MARKER = Symbol.for("jskit.fastify.bodylessContentTypeNormalizerRegistered");
8
-
9
- function resolveLoggerLevel({ configuredLevel = "", nodeEnv = "development", allowedLevels = [] } = {}) {
10
- const normalizedConfiguredLevel = String(configuredLevel || "")
11
- .trim()
12
- .toLowerCase();
13
- const allowedLevelSet =
14
- allowedLevels instanceof Set
15
- ? new Set(allowedLevels)
16
- : new Set(Array.isArray(allowedLevels) ? allowedLevels : []);
17
- if (allowedLevelSet.has(normalizedConfiguredLevel)) {
18
- return normalizedConfiguredLevel;
19
- }
20
-
21
- return String(nodeEnv || "").trim().toLowerCase() === "production" ? "info" : "debug";
22
- }
23
-
24
- function createFastifyLoggerOptions({
25
- configuredLevel = "",
26
- nodeEnv = "development",
27
- allowedLevels = [],
28
- redactPaths = [],
29
- redactCensor = "[REDACTED]"
30
- } = {}) {
31
- return {
32
- level: resolveLoggerLevel({
33
- configuredLevel,
34
- nodeEnv,
35
- allowedLevels
36
- }),
37
- redact: {
38
- paths: Array.isArray(redactPaths) ? redactPaths : [],
39
- censor: redactCensor
40
- }
41
- };
42
- }
43
-
44
- function createFallbackJsonBodyParser() {
45
- return function parseJsonBody(_request, body, done) {
46
- const source = typeof body === "string" ? body.trim() : "";
47
- if (!source) {
48
- done(null, {});
49
- return;
50
- }
51
-
52
- try {
53
- done(null, JSON.parse(source));
54
- } catch (error) {
55
- if (error && typeof error === "object" && !Array.isArray(error)) {
56
- error.statusCode = 400;
57
- }
58
- done(error);
59
- }
60
- };
61
- }
62
-
63
- function registerJsonApiContentTypeParser(fastify) {
64
- if (!fastify || typeof fastify.addContentTypeParser !== "function") {
65
- throw new TypeError("registerJsonApiContentTypeParser requires a Fastify instance.");
66
- }
67
-
68
- if (fastify[JSON_API_CONTENT_TYPE_PARSER_MARKER]) {
69
- return false;
70
- }
71
-
72
- if (typeof fastify.hasContentTypeParser === "function" && fastify.hasContentTypeParser(JSON_API_CONTENT_TYPE)) {
73
- fastify[JSON_API_CONTENT_TYPE_PARSER_MARKER] = true;
74
- return false;
75
- }
76
-
77
- const parser = typeof fastify.getDefaultJsonParser === "function"
78
- ? fastify.getDefaultJsonParser("ignore", "ignore")
79
- : createFallbackJsonBodyParser();
80
-
81
- fastify.addContentTypeParser(JSON_API_CONTENT_TYPE, { parseAs: "string" }, parser);
82
- fastify[JSON_API_CONTENT_TYPE_PARSER_MARKER] = true;
83
- return true;
84
- }
85
-
86
- function hasHeaderValue(headers = {}, key = "") {
87
- return String(headers?.[key] ?? "").trim().length > 0;
88
- }
89
-
90
- function hasRequestBody(headers = {}) {
91
- const transferEncoding = String(headers?.["transfer-encoding"] ?? "").trim();
92
- if (transferEncoding) {
93
- return true;
94
- }
95
-
96
- const contentLengthHeader = String(headers?.["content-length"] ?? "").trim();
97
- if (!contentLengthHeader) {
98
- return false;
99
- }
100
-
101
- const contentLength = Number(contentLengthHeader);
102
- if (!Number.isFinite(contentLength)) {
103
- return true;
104
- }
105
-
106
- return contentLength > 0;
107
- }
108
-
109
- function normalizeBodylessContentTypeHeader(request = null) {
110
- const headers = request?.headers;
111
- if (!headers || typeof headers !== "object" || Array.isArray(headers)) {
112
- return false;
113
- }
114
-
115
- if (!hasHeaderValue(headers, "content-type") && !hasHeaderValue(headers, "Content-Type")) {
116
- return false;
117
- }
118
-
119
- if (hasRequestBody(headers)) {
120
- return false;
121
- }
122
-
123
- delete headers["content-type"];
124
- delete headers["Content-Type"];
125
- return true;
126
- }
127
-
128
- function registerBodylessContentTypeNormalizer(fastify) {
129
- if (!fastify || typeof fastify.addHook !== "function") {
130
- throw new TypeError("registerBodylessContentTypeNormalizer requires a Fastify instance.");
131
- }
132
-
133
- if (fastify[BODYLESS_CONTENT_TYPE_NORMALIZER_MARKER]) {
134
- return false;
135
- }
136
-
137
- fastify.addHook("onRequest", async (request) => {
138
- normalizeBodylessContentTypeHeader(request);
139
- });
140
- fastify[BODYLESS_CONTENT_TYPE_NORMALIZER_MARKER] = true;
141
- return true;
142
- }
143
-
144
- function registerRequestLoggingHooks(
145
- app,
146
- {
147
- requestStartedAtSymbol,
148
- getPathname,
149
- getSurface,
150
- observeRequest,
151
- enableRequestLogs = true,
152
- defaultSurfaceId = ""
153
- } = {}
154
- ) {
155
- if (!app || typeof app.addHook !== "function") {
156
- throw new TypeError("registerRequestLoggingHooks requires a Fastify instance.");
157
- }
158
-
159
- const startedAtSymbol = requestStartedAtSymbol || Symbol("request_started_at_ns");
160
- const resolvePathname = typeof getPathname === "function" ? getPathname : () => "/";
161
- const resolveSurface =
162
- typeof getSurface === "function"
163
- ? getSurface
164
- : () =>
165
- resolveDefaultSurfaceId(null, {
166
- defaultSurfaceId
167
- });
168
-
169
- app.addHook("onRequest", async (request) => {
170
- request[startedAtSymbol] = process.hrtime.bigint();
171
- });
172
-
173
- app.addHook("onResponse", async (request, reply) => {
174
- const startedAt = request[startedAtSymbol];
175
- const durationMs =
176
- typeof startedAt === "bigint" ? Number(process.hrtime.bigint() - startedAt) / 1_000_000 : Number.NaN;
177
- const pathnameValue = resolvePathname(request);
178
- const routeUrl = String(request?.routeOptions?.url || "").trim();
179
- const surface = resolveSurface(pathnameValue, request);
180
- const actorId = normalizeOpaqueId(request?.user?.id);
181
- const logPayload = {
182
- requestId: String(request?.id || ""),
183
- method: String(request?.method || ""),
184
- path: pathnameValue,
185
- routeUrl,
186
- surface,
187
- statusCode: Number(reply?.statusCode || 0),
188
- durationMs: Number.isFinite(durationMs) ? Number(durationMs.toFixed(3)) : null
189
- };
190
-
191
- if (actorId != null) {
192
- logPayload.actorId = actorId;
193
- }
194
-
195
- if (typeof observeRequest === "function") {
196
- observeRequest({
197
- method: logPayload.method,
198
- route: routeUrl || pathnameValue,
199
- surface,
200
- statusCode: logPayload.statusCode,
201
- durationMs
202
- });
203
- }
204
-
205
- if (enableRequestLogs) {
206
- request.log.info(logPayload, "request.completed");
207
- }
208
- });
209
- }
210
-
211
- function resolveValidationFieldErrors(error) {
212
- const issues = Array.isArray(error?.validation) ? error.validation : [];
213
- const fieldErrors = {};
214
-
215
- for (const issue of issues) {
216
- const fieldFromPath = String(issue.instancePath || "")
217
- .replace(/^\//, "")
218
- .replace(/\//g, ".");
219
- const field =
220
- fieldFromPath || String(issue.params?.missingProperty || issue.params?.additionalProperty || "request").trim();
221
-
222
- if (!fieldErrors[field]) {
223
- fieldErrors[field] = issue.message || "Invalid value.";
224
- }
225
- }
226
-
227
- return fieldErrors;
228
- }
229
-
230
- function resolveRequestRouteTransport(request) {
231
- const directTransport =
232
- request?.routeTransport && typeof request.routeTransport === "object" && !Array.isArray(request.routeTransport)
233
- ? request.routeTransport
234
- : null;
235
- if (directTransport) {
236
- return directTransport;
237
- }
238
-
239
- const configTransport =
240
- request?.routeOptions?.config?.transport &&
241
- typeof request.routeOptions.config.transport === "object" &&
242
- !Array.isArray(request.routeOptions.config.transport)
243
- ? request.routeOptions.config.transport
244
- : null;
245
- const runtimeTransport =
246
- configTransport?.runtime && typeof configTransport.runtime === "object" && !Array.isArray(configTransport.runtime)
247
- ? configTransport.runtime
248
- : null;
249
- if (runtimeTransport) {
250
- return runtimeTransport;
251
- }
252
-
253
- return configTransport;
254
- }
255
-
256
- function applyRouteTransportErrorResponse(reply, request, error, {
257
- statusCode = 500,
258
- normalizedErrorCode = "",
259
- message = error?.message,
260
- exposeDetails = true
261
- } = {}) {
262
- const transport = resolveRequestRouteTransport(request);
263
- const errorSerializer = transport && typeof transport.error === "function" ? transport.error : null;
264
- if (!errorSerializer) {
265
- return false;
266
- }
267
-
268
- const payload = errorSerializer(error, {
269
- request,
270
- reply,
271
- statusCode,
272
- code: normalizedErrorCode,
273
- message,
274
- exposeDetails
275
- });
276
-
277
- if (payload && typeof payload.then === "function") {
278
- throw new TypeError("Route transport error serializer must return synchronously.");
279
- }
280
-
281
- if (transport.contentType) {
282
- reply.header("Content-Type", transport.contentType);
283
- }
284
-
285
- reply.code(statusCode).send(payload);
286
- return true;
287
- }
288
-
289
- function resolveApiErrorStatus(error, code) {
290
- // JSON REST's public error codes need HTTP classification even without status hints.
291
- switch (code) {
292
- case "REST_API_VALIDATION":
293
- return 422;
294
- case "REST_API_RESOURCE":
295
- switch (error?.subtype) {
296
- case "not_found": return 404;
297
- case "forbidden": return 403;
298
- case "conflict": return 409;
299
- default: return 400;
300
- }
301
- case "REST_API_VERSION_CONFLICT":
302
- return 409;
303
- case "REST_API_PRECONDITION_FAILED":
304
- return 412;
305
- case "REST_API_FIELDSET_INVALID":
306
- case "REST_API_INCLUDE_INVALID":
307
- return 400;
308
- case "REST_API_PAYLOAD":
309
- return error?.statusCode === 413 ? 413 : 400;
310
- case "REST_API_TEMPORAL_DATA_INVALID":
311
- return 500;
312
- default: {
313
- const status = Number(error?.statusCode || error?.status);
314
- return Number.isInteger(status) && status >= 400 && status <= 599 ? status : 500;
315
- }
316
- }
317
- }
318
-
319
- function registerApiErrorHandler(
320
- app,
321
- {
322
- isAppError,
323
- onRecordDbError,
324
- onCaptureServerError,
325
- appErrorLogMessage = "AppError 5xx",
326
- unhandledErrorLogMessage = "Unhandled error"
327
- } = {}
328
- ) {
329
- if (!app || typeof app.setErrorHandler !== "function") {
330
- throw new TypeError("registerApiErrorHandler requires a Fastify instance.");
331
- }
332
- if (typeof isAppError !== "function") {
333
- throw new TypeError("registerApiErrorHandler requires isAppError.");
334
- }
335
-
336
- const recordDbError = typeof onRecordDbError === "function" ? onRecordDbError : () => {};
337
- const captureServerError = typeof onCaptureServerError === "function" ? onCaptureServerError : () => {};
338
-
339
- function shouldExposeAppErrorDetails(errorCode = "") {
340
- return String(errorCode || "").trim() !== "ACTION_PERMISSION_DENIED";
341
- }
342
-
343
- app.setErrorHandler((error, request, reply) => {
344
- const normalizedErrorCode = String(error?.code || "").trim();
345
- const transactionOutcome = normalizeTransactionOutcome(error?.transactionOutcome);
346
- const outcomeFields = transactionOutcome ? { transactionOutcome } : {};
347
- const isCsrfErrorCode = normalizedErrorCode.startsWith("FST_CSRF_");
348
- const statusCode = resolveApiErrorStatus(error, normalizedErrorCode);
349
-
350
- if (Array.isArray(error?.validation)) {
351
- const fieldErrors = resolveValidationFieldErrors(error);
352
- const validationErrorCode = normalizedErrorCode || "validation_failed";
353
- if (applyRouteTransportErrorResponse(reply, request, error, {
354
- statusCode: 400,
355
- normalizedErrorCode: validationErrorCode
356
- })) {
357
- return;
358
- }
359
- reply.code(400).send({
360
- error: "Validation failed.",
361
- code: validationErrorCode,
362
- ...outcomeFields,
363
- fieldErrors,
364
- details: {
365
- fieldErrors
366
- }
367
- });
368
- return;
369
- }
370
-
371
- if (isAppError(error) || error instanceof ActionRuntimeError) {
372
- if (applyRouteTransportErrorResponse(reply, request, error, {
373
- statusCode: error.status,
374
- normalizedErrorCode: normalizedErrorCode || "app_error",
375
- exposeDetails: shouldExposeAppErrorDetails(normalizedErrorCode)
376
- })) {
377
- return;
378
- }
379
- if (error.status >= 500) {
380
- recordDbError(error);
381
- captureServerError(request, error, error.status);
382
- app.log.error({ err: error }, appErrorLogMessage);
383
- }
384
-
385
- const appErrorCode = normalizedErrorCode || "app_error";
386
- const payload = {
387
- error: error.message,
388
- code: appErrorCode,
389
- ...outcomeFields
390
- };
391
- if (error.details && shouldExposeAppErrorDetails(appErrorCode)) {
392
- payload.details = error.details;
393
- if (error.details.fieldErrors) {
394
- payload.fieldErrors = error.details.fieldErrors;
395
- }
396
- }
397
-
398
- if (error.headers && typeof error.headers === "object") {
399
- Object.entries(error.headers).forEach(([name, value]) => {
400
- reply.header(name, value);
401
- });
402
- }
403
-
404
- reply.code(error.status).send(payload);
405
- return;
406
- }
407
-
408
- if (error.headers && typeof error.headers === "object") {
409
- Object.entries(error.headers).forEach(([name, value]) => {
410
- reply.header(name, value);
411
- });
412
- }
413
-
414
- if (statusCode >= 500) {
415
- recordDbError(error);
416
- }
417
- captureServerError(request, error, statusCode);
418
- app.log.error({ err: error }, unhandledErrorLogMessage);
419
-
420
- const message = statusCode >= 500 ? "Internal server error." : String(error?.message || "Request failed.");
421
- const fallbackErrorCode =
422
- normalizedErrorCode || (statusCode >= 500 ? "internal_server_error" : "request_failed");
423
- const payload = {
424
- error: message,
425
- code: fallbackErrorCode,
426
- ...outcomeFields
427
- };
428
- if (isCsrfErrorCode) {
429
- payload.details = {
430
- code: normalizedErrorCode
431
- };
432
- }
433
- if (applyRouteTransportErrorResponse(reply, request, error, {
434
- statusCode,
435
- normalizedErrorCode: fallbackErrorCode,
436
- message,
437
- exposeDetails: statusCode < 500
438
- })) {
439
- return;
440
- }
441
- reply.code(statusCode).send(payload);
442
- });
443
- }
444
-
445
- function resolveDatabaseErrorCode(error) {
446
- const errorCode = String(error?.code || "")
447
- .trim()
448
- .toUpperCase();
449
- if (errorCode && (errorCode.startsWith("ER_") || errorCode.startsWith("SQLITE_") || errorCode.startsWith("PG"))) {
450
- return errorCode;
451
- }
452
-
453
- const sqlState = String(error?.sqlState || error?.sqlstate || "")
454
- .trim()
455
- .toUpperCase();
456
- if (sqlState) {
457
- return `SQLSTATE_${sqlState}`;
458
- }
459
-
460
- const errno = Number(error?.errno);
461
- if (Number.isInteger(errno)) {
462
- return `ERRNO_${errno}`;
463
- }
464
-
465
- const message = String(error?.message || "").toLowerCase();
466
- const name = String(error?.name || "").toLowerCase();
467
- if (message.includes("mysql") || message.includes("sql") || message.includes("knex") || name.includes("mysql")) {
468
- return "DB_UNKNOWN";
469
- }
470
-
471
- return "";
472
- }
473
-
474
- function recordDbErrorBestEffort(observabilityService, error) {
475
- if (!observabilityService || typeof observabilityService.recordDbError !== "function") {
476
- return;
477
- }
478
-
479
- const code = resolveDatabaseErrorCode(error);
480
- if (!code) {
481
- return;
482
- }
483
-
484
- observabilityService.recordDbError({ code });
485
- }
486
-
487
- async function runGracefulShutdown({
488
- signal = "",
489
- exitProcess = false,
490
- exitCode = 0,
491
- timeoutMs = 10_000,
492
- appInstance = null,
493
- stopBackgroundRuntimes = () => {},
494
- closeDatabase = async () => {},
495
- logger = console
496
- } = {}) {
497
- let forcedExitTimer = null;
498
-
499
- if (signal) {
500
- logger.log(`Received ${signal}. Shutting down.`);
501
- }
502
-
503
- stopBackgroundRuntimes();
504
-
505
- if (exitProcess) {
506
- forcedExitTimer = setTimeout(() => {
507
- try {
508
- appInstance?.server?.closeIdleConnections?.();
509
- appInstance?.server?.closeAllConnections?.();
510
- } catch {
511
- // Ignore best-effort force-close failures.
512
- }
513
-
514
- logger.error(`Graceful shutdown timed out after ${timeoutMs}ms. Forcing process exit.`);
515
- process.exit(1);
516
- }, timeoutMs);
517
- forcedExitTimer.unref?.();
518
- }
519
-
520
- try {
521
- if (appInstance) {
522
- await appInstance.close();
523
- }
524
- await closeDatabase();
525
- } catch (error) {
526
- logger.error("Failed to close server cleanly:", error);
527
- if (exitProcess) {
528
- process.exit(1);
529
- }
530
- throw error;
531
- } finally {
532
- if (forcedExitTimer) {
533
- clearTimeout(forcedExitTimer);
534
- }
535
- }
536
-
537
- if (exitProcess) {
538
- process.exit(exitCode);
539
- }
540
- }
541
-
542
- export {
543
- resolveLoggerLevel,
544
- createFastifyLoggerOptions,
545
- registerJsonApiContentTypeParser,
546
- normalizeBodylessContentTypeHeader,
547
- registerBodylessContentTypeNormalizer,
548
- registerRequestLoggingHooks,
549
- registerApiErrorHandler,
550
- resolveDatabaseErrorCode,
551
- recordDbErrorBestEffort,
552
- runGracefulShutdown
553
- };