@jskit-ai/kernel 0.1.244 → 0.1.246

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 (101) hide show
  1. package/client/appConfig.js +2 -1
  2. package/client/asyncModuleRecovery.js +2 -4
  3. package/client/index.d.ts +18 -76
  4. package/client/index.js +7 -2
  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 +38 -10
  9. package/client/shellBootstrap.test.js +85 -6
  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 +14 -58
  15. package/package.json +2 -4
  16. package/server/actions/actionCatalogue.js +13 -34
  17. package/{shared → server}/actions/actionContributorHelpers.js +0 -5
  18. package/{shared → server}/actions/actionDefinitions.js +24 -131
  19. package/{shared → server}/actions/actionDefinitions.test.js +75 -21
  20. package/server/actions/actionEvents.js +97 -2
  21. package/server/actions/actionEvents.test.js +79 -0
  22. package/{shared → server}/actions/executionContext.js +3 -13
  23. package/server/actions/idempotency.js +24 -0
  24. package/server/actions/pipeline.js +87 -0
  25. package/{shared → server}/actions/policies.js +20 -33
  26. package/{shared → server}/actions/policies.test.js +6 -6
  27. package/{shared → server}/actions/registry.js +3 -22
  28. package/{shared → server}/actions/registry.test.js +70 -32
  29. package/server/actions/requestMeta.js +17 -0
  30. package/server/http/capabilityHttpRuntime.js +1 -1
  31. package/server/http/capabilityHttpRuntime.test.js +56 -0
  32. package/server/http/errorHandling.js +291 -0
  33. package/server/{runtime/fastifyBootstrap.test.js → http/errorHandling.test.js} +72 -120
  34. package/server/http/lib/routeRegistration.js +36 -150
  35. package/server/http/lib/routeTransport.js +2 -0
  36. package/server/http/lib/routeValidator.js +5 -47
  37. package/server/http/lib/routeValidator.test.js +15 -17
  38. package/server/http/lib/router.js +10 -94
  39. package/server/platform/providerRuntime/capabilityProviderLoader.js +2 -1
  40. package/server/platform/providerRuntime/helpers.js +1 -34
  41. package/server/runtime/integers.js +1 -5
  42. package/server/runtime/serviceAuthorization.js +4 -9
  43. package/server/support/appConfig.js +2 -1
  44. package/server/support/appConfigFiles.js +1 -38
  45. package/server/support/appConfigFiles.test.js +3 -39
  46. package/server/support/index.js +1 -20
  47. package/shared/capabilities/defineProvider.js +1 -1
  48. package/shared/capabilities/index.js +1 -1
  49. package/shared/index.js +1 -1
  50. package/shared/support/index.js +1 -4
  51. package/shared/support/mobileConfig.js +99 -0
  52. package/shared/support/mobileConfig.test.js +113 -0
  53. package/shared/support/normalize.js +0 -108
  54. package/shared/support/normalize.test.js +0 -134
  55. package/shared/{actions/actionContributorHelpers.test.js → support/permissions.test.js} +1 -1
  56. package/shared/support/returnToPath.js +16 -20
  57. package/shared/support/returnToPath.test.js +5 -33
  58. package/shared/support/shellLayoutTargets.js +2 -140
  59. package/shared/support/shellLayoutTargets.test.js +1 -110
  60. package/shared/support/sorting.js +1 -9
  61. package/shared/surface/apiPaths.js +1 -82
  62. package/shared/surface/paths.js +38 -171
  63. package/shared/surface/registry.js +2 -1
  64. package/shared/surface/runtime.js +18 -109
  65. package/shared/surface/runtime.test.js +57 -0
  66. package/shared/validators/index.js +1 -4
  67. package/shared/validators/jsonRestSchemaSupport.js +0 -9
  68. package/shared/validators/recordIdParamsValidator.js +0 -6
  69. package/shared/validators/schemaDefinitions.js +4 -30
  70. package/shared/validators/schemaPayloadValidation.js +2 -6
  71. package/test/barrelExposure.test.js +5 -8
  72. package/test/exportsContract.test.js +1 -3
  73. package/client/mobileLaunchRouting.js +0 -231
  74. package/client/mobileLaunchRouting.test.js +0 -230
  75. package/server/http/lib/controller.js +0 -183
  76. package/server/http/lib/controller.test.js +0 -143
  77. package/server/http/lib/index.js +0 -5
  78. package/server/runtime/canonicalJson.js +0 -74
  79. package/server/runtime/entityChangeEvents.js +0 -307
  80. package/server/runtime/entityChangeEvents.test.js +0 -274
  81. package/server/runtime/fastifyBootstrap.js +0 -553
  82. package/server/runtime/moduleConfig.js +0 -223
  83. package/server/runtime/moduleConfig.test.js +0 -165
  84. package/server/runtime/pagination.js +0 -13
  85. package/server/runtime/realtimeNormalization.js +0 -21
  86. package/server/runtime/routeUtils.js +0 -20
  87. package/server/runtime/securityAudit.js +0 -269
  88. package/server/runtime/securityAudit.test.js +0 -41
  89. package/server/support/pageTargets.js +0 -772
  90. package/server/support/pageTargets.test.js +0 -659
  91. package/server/support/shellOutlets.js +0 -576
  92. package/server/support/shellOutlets.test.js +0 -336
  93. package/server/surface/index.js +0 -18
  94. package/shared/actions/audit.js +0 -7
  95. package/shared/actions/idempotency.js +0 -62
  96. package/shared/actions/observability.js +0 -10
  97. package/shared/actions/pipeline.js +0 -309
  98. package/shared/actions/requestMeta.js +0 -36
  99. package/shared/actions/textNormalization.js +0 -3
  100. package/shared/actions/withActionDefaults.js +0 -34
  101. /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
- };