@classytic/arc 1.1.0 → 2.1.2
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/README.md +247 -794
- package/bin/arc.js +91 -52
- package/dist/EventTransport-BD2U0BTc.d.mts +100 -0
- package/dist/EventTransport-BD2U0BTc.d.mts.map +1 -0
- package/dist/HookSystem-BsGV-j2l.mjs +405 -0
- package/dist/HookSystem-BsGV-j2l.mjs.map +1 -0
- package/dist/ResourceRegistry-DsN4KJjV.mjs +250 -0
- package/dist/ResourceRegistry-DsN4KJjV.mjs.map +1 -0
- package/dist/adapters/index.d.mts +5 -0
- package/dist/adapters/index.mjs +3 -0
- package/dist/audit/index.d.mts +82 -0
- package/dist/audit/index.d.mts.map +1 -0
- package/dist/audit/index.mjs +276 -0
- package/dist/audit/index.mjs.map +1 -0
- package/dist/audit/mongodb.d.mts +5 -0
- package/dist/audit/mongodb.mjs +3 -0
- package/dist/audited-C3T5DTUx.mjs +141 -0
- package/dist/audited-C3T5DTUx.mjs.map +1 -0
- package/dist/auth/index.d.mts +189 -0
- package/dist/auth/index.d.mts.map +1 -0
- package/dist/auth/index.mjs +1102 -0
- package/dist/auth/index.mjs.map +1 -0
- package/dist/auth/redis-session.d.mts +44 -0
- package/dist/auth/redis-session.d.mts.map +1 -0
- package/dist/auth/redis-session.mjs +76 -0
- package/dist/auth/redis-session.mjs.map +1 -0
- package/dist/betterAuthOpenApi-BrHKeSAx.mjs +250 -0
- package/dist/betterAuthOpenApi-BrHKeSAx.mjs.map +1 -0
- package/dist/cache/index.d.mts +146 -0
- package/dist/cache/index.d.mts.map +1 -0
- package/dist/cache/index.mjs +92 -0
- package/dist/cache/index.mjs.map +1 -0
- package/dist/caching-Bl28lYsR.mjs +94 -0
- package/dist/caching-Bl28lYsR.mjs.map +1 -0
- package/dist/chunk-C7Uep-_p.mjs +20 -0
- package/dist/circuitBreaker-DeY4FCjs.mjs +1097 -0
- package/dist/circuitBreaker-DeY4FCjs.mjs.map +1 -0
- package/dist/cli/commands/describe.d.mts +19 -0
- package/dist/cli/commands/describe.d.mts.map +1 -0
- package/dist/cli/commands/describe.mjs +239 -0
- package/dist/cli/commands/describe.mjs.map +1 -0
- package/dist/cli/commands/docs.d.mts +14 -0
- package/dist/cli/commands/docs.d.mts.map +1 -0
- package/dist/cli/commands/docs.mjs +53 -0
- package/dist/cli/commands/docs.mjs.map +1 -0
- package/dist/cli/commands/{generate.d.ts → generate.d.mts} +3 -1
- package/dist/cli/commands/generate.d.mts.map +1 -0
- package/dist/cli/commands/generate.mjs +358 -0
- package/dist/cli/commands/generate.mjs.map +1 -0
- package/dist/cli/commands/{init.d.ts → init.d.mts} +12 -8
- package/dist/cli/commands/init.d.mts.map +1 -0
- package/dist/cli/commands/{init.js → init.mjs} +807 -616
- package/dist/cli/commands/init.mjs.map +1 -0
- package/dist/cli/commands/introspect.d.mts +11 -0
- package/dist/cli/commands/introspect.d.mts.map +1 -0
- package/dist/cli/commands/introspect.mjs +76 -0
- package/dist/cli/commands/introspect.mjs.map +1 -0
- package/dist/cli/index.d.mts +17 -0
- package/dist/cli/index.d.mts.map +1 -0
- package/dist/cli/index.mjs +157 -0
- package/dist/cli/index.mjs.map +1 -0
- package/dist/constants-DdXFXQtN.mjs +85 -0
- package/dist/constants-DdXFXQtN.mjs.map +1 -0
- package/dist/core/index.d.mts +5 -0
- package/dist/core/index.mjs +4 -0
- package/dist/createApp-CUgNqegw.mjs +560 -0
- package/dist/createApp-CUgNqegw.mjs.map +1 -0
- package/dist/defineResource-k0_BDn8v.mjs +2197 -0
- package/dist/defineResource-k0_BDn8v.mjs.map +1 -0
- package/dist/discovery/index.d.mts +47 -0
- package/dist/discovery/index.d.mts.map +1 -0
- package/dist/discovery/index.mjs +110 -0
- package/dist/discovery/index.mjs.map +1 -0
- package/dist/docs/index.d.mts +163 -0
- package/dist/docs/index.d.mts.map +1 -0
- package/dist/docs/index.mjs +73 -0
- package/dist/docs/index.mjs.map +1 -0
- package/dist/elevation-BRy3yFWT.mjs +113 -0
- package/dist/elevation-BRy3yFWT.mjs.map +1 -0
- package/dist/elevation-B_2dRLVP.d.mts +88 -0
- package/dist/elevation-B_2dRLVP.d.mts.map +1 -0
- package/dist/errorHandler-BbcgBmIH.d.mts +73 -0
- package/dist/errorHandler-BbcgBmIH.d.mts.map +1 -0
- package/dist/errorHandler-C1okiriz.mjs +109 -0
- package/dist/errorHandler-C1okiriz.mjs.map +1 -0
- package/dist/errors-B9bZok84.mjs +212 -0
- package/dist/errors-B9bZok84.mjs.map +1 -0
- package/dist/errors-ChKiFz62.d.mts +125 -0
- package/dist/errors-ChKiFz62.d.mts.map +1 -0
- package/dist/eventPlugin-CTrLH3mt.d.mts +125 -0
- package/dist/eventPlugin-CTrLH3mt.d.mts.map +1 -0
- package/dist/eventPlugin-DGR_B2on.mjs +230 -0
- package/dist/eventPlugin-DGR_B2on.mjs.map +1 -0
- package/dist/events/index.d.mts +54 -0
- package/dist/events/index.d.mts.map +1 -0
- package/dist/events/index.mjs +52 -0
- package/dist/events/index.mjs.map +1 -0
- package/dist/events/transports/redis-stream-entry.d.mts +2 -0
- package/dist/events/transports/redis-stream-entry.mjs +178 -0
- package/dist/events/transports/redis-stream-entry.mjs.map +1 -0
- package/dist/events/transports/redis.d.mts +77 -0
- package/dist/events/transports/redis.d.mts.map +1 -0
- package/dist/events/transports/redis.mjs +125 -0
- package/dist/events/transports/redis.mjs.map +1 -0
- package/dist/externalPaths-DlINfKbP.d.mts +51 -0
- package/dist/externalPaths-DlINfKbP.d.mts.map +1 -0
- package/dist/factory/index.d.mts +64 -0
- package/dist/factory/index.d.mts.map +1 -0
- package/dist/factory/index.mjs +3 -0
- package/dist/fastifyAdapter-BkrGrlFi.d.mts +217 -0
- package/dist/fastifyAdapter-BkrGrlFi.d.mts.map +1 -0
- package/dist/fields-DyaDVX4J.d.mts +110 -0
- package/dist/fields-DyaDVX4J.d.mts.map +1 -0
- package/dist/fields-iagOozy0.mjs +115 -0
- package/dist/fields-iagOozy0.mjs.map +1 -0
- package/dist/hooks/index.d.mts +4 -0
- package/dist/hooks/index.mjs +3 -0
- package/dist/idempotency/index.d.mts +97 -0
- package/dist/idempotency/index.d.mts.map +1 -0
- package/dist/idempotency/index.mjs +320 -0
- package/dist/idempotency/index.mjs.map +1 -0
- package/dist/idempotency/mongodb.d.mts +2 -0
- package/dist/idempotency/mongodb.mjs +115 -0
- package/dist/idempotency/mongodb.mjs.map +1 -0
- package/dist/idempotency/redis.d.mts +2 -0
- package/dist/idempotency/redis.mjs +104 -0
- package/dist/idempotency/redis.mjs.map +1 -0
- package/dist/index.d.mts +261 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +105 -0
- package/dist/index.mjs.map +1 -0
- package/dist/integrations/event-gateway.d.mts +47 -0
- package/dist/integrations/event-gateway.d.mts.map +1 -0
- package/dist/integrations/event-gateway.mjs +44 -0
- package/dist/integrations/event-gateway.mjs.map +1 -0
- package/dist/integrations/index.d.mts +5 -0
- package/dist/integrations/index.mjs +1 -0
- package/dist/integrations/jobs.d.mts +104 -0
- package/dist/integrations/jobs.d.mts.map +1 -0
- package/dist/integrations/jobs.mjs +124 -0
- package/dist/integrations/jobs.mjs.map +1 -0
- package/dist/integrations/streamline.d.mts +61 -0
- package/dist/integrations/streamline.d.mts.map +1 -0
- package/dist/integrations/streamline.mjs +126 -0
- package/dist/integrations/streamline.mjs.map +1 -0
- package/dist/integrations/websocket.d.mts +83 -0
- package/dist/integrations/websocket.d.mts.map +1 -0
- package/dist/integrations/websocket.mjs +289 -0
- package/dist/integrations/websocket.mjs.map +1 -0
- package/dist/interface-B01JvPVc.d.mts +78 -0
- package/dist/interface-B01JvPVc.d.mts.map +1 -0
- package/dist/interface-CZe8IkMf.d.mts +55 -0
- package/dist/interface-CZe8IkMf.d.mts.map +1 -0
- package/dist/interface-Ch8HU9uM.d.mts +1098 -0
- package/dist/interface-Ch8HU9uM.d.mts.map +1 -0
- package/dist/introspectionPlugin-rFdO8ZUa.mjs +54 -0
- package/dist/introspectionPlugin-rFdO8ZUa.mjs.map +1 -0
- package/dist/keys-BqNejWup.mjs +43 -0
- package/dist/keys-BqNejWup.mjs.map +1 -0
- package/dist/logger-Df2O2WsW.mjs +79 -0
- package/dist/logger-Df2O2WsW.mjs.map +1 -0
- package/dist/memory-cQgelFOj.mjs +144 -0
- package/dist/memory-cQgelFOj.mjs.map +1 -0
- package/dist/migrations/index.d.mts +157 -0
- package/dist/migrations/index.d.mts.map +1 -0
- package/dist/migrations/index.mjs +261 -0
- package/dist/migrations/index.mjs.map +1 -0
- package/dist/mongodb-BfJVlUJH.mjs +94 -0
- package/dist/mongodb-BfJVlUJH.mjs.map +1 -0
- package/dist/mongodb-CGzRbfAK.d.mts +119 -0
- package/dist/mongodb-CGzRbfAK.d.mts.map +1 -0
- package/dist/mongodb-JN-9JA7K.d.mts +72 -0
- package/dist/mongodb-JN-9JA7K.d.mts.map +1 -0
- package/dist/openapi-G3Cw7XuM.mjs +524 -0
- package/dist/openapi-G3Cw7XuM.mjs.map +1 -0
- package/dist/org/index.d.mts +69 -0
- package/dist/org/index.d.mts.map +1 -0
- package/dist/org/index.mjs +514 -0
- package/dist/org/index.mjs.map +1 -0
- package/dist/org/types.d.mts +83 -0
- package/dist/org/types.d.mts.map +1 -0
- package/dist/org/types.mjs +1 -0
- package/dist/permissions/index.d.mts +279 -0
- package/dist/permissions/index.d.mts.map +1 -0
- package/dist/permissions/index.mjs +579 -0
- package/dist/permissions/index.mjs.map +1 -0
- package/dist/plugins/index.d.mts +173 -0
- package/dist/plugins/index.d.mts.map +1 -0
- package/dist/plugins/index.mjs +523 -0
- package/dist/plugins/index.mjs.map +1 -0
- package/dist/plugins/response-cache.d.mts +88 -0
- package/dist/plugins/response-cache.d.mts.map +1 -0
- package/dist/plugins/response-cache.mjs +284 -0
- package/dist/plugins/response-cache.mjs.map +1 -0
- package/dist/plugins/tracing-entry.d.mts +2 -0
- package/dist/plugins/tracing-entry.mjs +186 -0
- package/dist/plugins/tracing-entry.mjs.map +1 -0
- package/dist/pluralize-CEweyOEm.mjs +87 -0
- package/dist/pluralize-CEweyOEm.mjs.map +1 -0
- package/dist/policies/{index.d.ts → index.d.mts} +204 -169
- package/dist/policies/index.d.mts.map +1 -0
- package/dist/policies/index.mjs +322 -0
- package/dist/policies/index.mjs.map +1 -0
- package/dist/presets/{index.d.ts → index.d.mts} +63 -131
- package/dist/presets/index.d.mts.map +1 -0
- package/dist/presets/index.mjs +144 -0
- package/dist/presets/index.mjs.map +1 -0
- package/dist/presets/multiTenant.d.mts +25 -0
- package/dist/presets/multiTenant.d.mts.map +1 -0
- package/dist/presets/multiTenant.mjs +114 -0
- package/dist/presets/multiTenant.mjs.map +1 -0
- package/dist/presets-BITljm96.mjs +120 -0
- package/dist/presets-BITljm96.mjs.map +1 -0
- package/dist/presets-DzSMwlKj.d.mts +58 -0
- package/dist/presets-DzSMwlKj.d.mts.map +1 -0
- package/dist/prisma-DJbMt3yf.mjs +628 -0
- package/dist/prisma-DJbMt3yf.mjs.map +1 -0
- package/dist/prisma-Dg9GoVdj.d.mts +275 -0
- package/dist/prisma-Dg9GoVdj.d.mts.map +1 -0
- package/dist/queryCachePlugin-7THaI5mt.d.mts +72 -0
- package/dist/queryCachePlugin-7THaI5mt.d.mts.map +1 -0
- package/dist/queryCachePlugin-DMBnp2Q0.mjs +139 -0
- package/dist/queryCachePlugin-DMBnp2Q0.mjs.map +1 -0
- package/dist/redis-D-JAeLtm.d.mts +50 -0
- package/dist/redis-D-JAeLtm.d.mts.map +1 -0
- package/dist/redis-stream-Bdh_vUU8.d.mts +104 -0
- package/dist/redis-stream-Bdh_vUU8.d.mts.map +1 -0
- package/dist/registry/index.d.mts +12 -0
- package/dist/registry/index.d.mts.map +1 -0
- package/dist/registry/index.mjs +4 -0
- package/dist/requestContext-QQD6ROJc.mjs +56 -0
- package/dist/requestContext-QQD6ROJc.mjs.map +1 -0
- package/dist/schemaConverter-BwrmWroW.mjs +99 -0
- package/dist/schemaConverter-BwrmWroW.mjs.map +1 -0
- package/dist/schemas/index.d.mts +64 -0
- package/dist/schemas/index.d.mts.map +1 -0
- package/dist/schemas/index.mjs +83 -0
- package/dist/schemas/index.mjs.map +1 -0
- package/dist/scope/index.d.mts +22 -0
- package/dist/scope/index.d.mts.map +1 -0
- package/dist/scope/index.mjs +66 -0
- package/dist/scope/index.mjs.map +1 -0
- package/dist/sessionManager-jPKLbHE0.d.mts +187 -0
- package/dist/sessionManager-jPKLbHE0.d.mts.map +1 -0
- package/dist/sse-B3c3_yZp.mjs +124 -0
- package/dist/sse-B3c3_yZp.mjs.map +1 -0
- package/dist/testing/index.d.mts +908 -0
- package/dist/testing/index.d.mts.map +1 -0
- package/dist/testing/index.mjs +1977 -0
- package/dist/testing/index.mjs.map +1 -0
- package/dist/tracing-Cc7vVQPp.d.mts +71 -0
- package/dist/tracing-Cc7vVQPp.d.mts.map +1 -0
- package/dist/typeGuards-DhMNLuvU.mjs +10 -0
- package/dist/typeGuards-DhMNLuvU.mjs.map +1 -0
- package/dist/types/index.d.mts +947 -0
- package/dist/types/index.d.mts.map +1 -0
- package/dist/types/index.mjs +15 -0
- package/dist/types/index.mjs.map +1 -0
- package/dist/types-Beqn1Un7.mjs +39 -0
- package/dist/types-Beqn1Un7.mjs.map +1 -0
- package/dist/types-CIgB7UUl.d.mts +446 -0
- package/dist/types-CIgB7UUl.d.mts.map +1 -0
- package/dist/types-aYB4V7uN.d.mts +87 -0
- package/dist/types-aYB4V7uN.d.mts.map +1 -0
- package/dist/utils/index.d.mts +748 -0
- package/dist/utils/index.d.mts.map +1 -0
- package/dist/utils/index.mjs +6 -0
- package/package.json +194 -68
- package/dist/BaseController-DVAiHxEQ.d.ts +0 -233
- package/dist/adapters/index.d.ts +0 -237
- package/dist/adapters/index.js +0 -668
- package/dist/arcCorePlugin-CsShQdyP.d.ts +0 -273
- package/dist/audit/index.d.ts +0 -195
- package/dist/audit/index.js +0 -319
- package/dist/auth/index.d.ts +0 -47
- package/dist/auth/index.js +0 -174
- package/dist/cli/commands/docs.d.ts +0 -11
- package/dist/cli/commands/docs.js +0 -474
- package/dist/cli/commands/generate.js +0 -334
- package/dist/cli/commands/introspect.d.ts +0 -8
- package/dist/cli/commands/introspect.js +0 -338
- package/dist/cli/index.d.ts +0 -4
- package/dist/cli/index.js +0 -3269
- package/dist/core/index.d.ts +0 -220
- package/dist/core/index.js +0 -2786
- package/dist/createApp-Ce9wl8W9.d.ts +0 -77
- package/dist/docs/index.d.ts +0 -166
- package/dist/docs/index.js +0 -658
- package/dist/errors-8WIxGS_6.d.ts +0 -122
- package/dist/events/index.d.ts +0 -117
- package/dist/events/index.js +0 -89
- package/dist/factory/index.d.ts +0 -38
- package/dist/factory/index.js +0 -1652
- package/dist/hooks/index.d.ts +0 -4
- package/dist/hooks/index.js +0 -199
- package/dist/idempotency/index.d.ts +0 -323
- package/dist/idempotency/index.js +0 -500
- package/dist/index-B4t03KQ0.d.ts +0 -1366
- package/dist/index.d.ts +0 -135
- package/dist/index.js +0 -4756
- package/dist/migrations/index.d.ts +0 -185
- package/dist/migrations/index.js +0 -274
- package/dist/org/index.d.ts +0 -129
- package/dist/org/index.js +0 -220
- package/dist/permissions/index.d.ts +0 -144
- package/dist/permissions/index.js +0 -103
- package/dist/plugins/index.d.ts +0 -46
- package/dist/plugins/index.js +0 -1069
- package/dist/policies/index.js +0 -196
- package/dist/presets/index.js +0 -384
- package/dist/presets/multiTenant.d.ts +0 -39
- package/dist/presets/multiTenant.js +0 -112
- package/dist/registry/index.d.ts +0 -16
- package/dist/registry/index.js +0 -253
- package/dist/testing/index.d.ts +0 -618
- package/dist/testing/index.js +0 -48020
- package/dist/types/index.d.ts +0 -4
- package/dist/types/index.js +0 -8
- package/dist/types-B99TBmFV.d.ts +0 -76
- package/dist/types-BvckRbs2.d.ts +0 -143
- package/dist/utils/index.d.ts +0 -679
- package/dist/utils/index.js +0 -931
package/dist/factory/index.js
DELETED
|
@@ -1,1652 +0,0 @@
|
|
|
1
|
-
import fp from 'fastify-plugin';
|
|
2
|
-
import { randomUUID } from 'crypto';
|
|
3
|
-
import { createRequire } from 'module';
|
|
4
|
-
import Fastify from 'fastify';
|
|
5
|
-
import qs from 'qs';
|
|
6
|
-
|
|
7
|
-
var __defProp = Object.defineProperty;
|
|
8
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
9
|
-
var __esm = (fn, res) => function __init() {
|
|
10
|
-
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
|
-
};
|
|
12
|
-
var __export = (target, all) => {
|
|
13
|
-
for (var name in all)
|
|
14
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
|
-
};
|
|
16
|
-
var requestIdPlugin, requestId_default;
|
|
17
|
-
var init_requestId = __esm({
|
|
18
|
-
"src/plugins/requestId.ts"() {
|
|
19
|
-
requestIdPlugin = async (fastify, opts = {}) => {
|
|
20
|
-
const {
|
|
21
|
-
header = "x-request-id",
|
|
22
|
-
generator = randomUUID,
|
|
23
|
-
setResponseHeader = true
|
|
24
|
-
} = opts;
|
|
25
|
-
if (!fastify.hasRequestDecorator("requestId")) {
|
|
26
|
-
fastify.decorateRequest("requestId", "");
|
|
27
|
-
}
|
|
28
|
-
fastify.addHook("onRequest", async (request) => {
|
|
29
|
-
const incomingId = request.headers[header];
|
|
30
|
-
const requestId = typeof incomingId === "string" && incomingId.trim() ? incomingId.trim() : generator();
|
|
31
|
-
request.id = requestId;
|
|
32
|
-
request.requestId = requestId;
|
|
33
|
-
});
|
|
34
|
-
if (setResponseHeader) {
|
|
35
|
-
fastify.addHook("onSend", async (request, reply) => {
|
|
36
|
-
reply.header(header, request.requestId);
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
fastify.log?.debug?.("Request ID plugin registered");
|
|
40
|
-
};
|
|
41
|
-
requestId_default = fp(requestIdPlugin, {
|
|
42
|
-
name: "arc-request-id",
|
|
43
|
-
fastify: "5.x"
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
async function runChecks(checks) {
|
|
48
|
-
const results = [];
|
|
49
|
-
for (const check of checks) {
|
|
50
|
-
const start = Date.now();
|
|
51
|
-
const timeout = check.timeout ?? 5e3;
|
|
52
|
-
try {
|
|
53
|
-
const checkPromise = Promise.resolve(check.check());
|
|
54
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
55
|
-
setTimeout(() => reject(new Error("Health check timeout")), timeout);
|
|
56
|
-
});
|
|
57
|
-
const healthy = await Promise.race([checkPromise, timeoutPromise]);
|
|
58
|
-
results.push({
|
|
59
|
-
name: check.name,
|
|
60
|
-
healthy: Boolean(healthy),
|
|
61
|
-
duration: Date.now() - start
|
|
62
|
-
});
|
|
63
|
-
} catch (err) {
|
|
64
|
-
results.push({
|
|
65
|
-
name: check.name,
|
|
66
|
-
healthy: false,
|
|
67
|
-
duration: Date.now() - start,
|
|
68
|
-
error: err.message
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return results;
|
|
73
|
-
}
|
|
74
|
-
var httpMetrics, healthPlugin, health_default;
|
|
75
|
-
var init_health = __esm({
|
|
76
|
-
"src/plugins/health.ts"() {
|
|
77
|
-
httpMetrics = {
|
|
78
|
-
requestsTotal: {},
|
|
79
|
-
requestDurations: [],
|
|
80
|
-
startTime: Date.now()
|
|
81
|
-
};
|
|
82
|
-
healthPlugin = async (fastify, opts = {}) => {
|
|
83
|
-
const {
|
|
84
|
-
prefix = "/_health",
|
|
85
|
-
checks = [],
|
|
86
|
-
metrics = false,
|
|
87
|
-
metricsCollector,
|
|
88
|
-
version,
|
|
89
|
-
collectHttpMetrics = metrics
|
|
90
|
-
} = opts;
|
|
91
|
-
fastify.get(`${prefix}/live`, {
|
|
92
|
-
schema: {
|
|
93
|
-
tags: ["Health"],
|
|
94
|
-
summary: "Liveness probe",
|
|
95
|
-
description: "Returns 200 if the process is alive",
|
|
96
|
-
response: {
|
|
97
|
-
200: {
|
|
98
|
-
type: "object",
|
|
99
|
-
properties: {
|
|
100
|
-
status: { type: "string", enum: ["ok"] },
|
|
101
|
-
timestamp: { type: "string" },
|
|
102
|
-
version: { type: "string" }
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}, async () => {
|
|
108
|
-
return {
|
|
109
|
-
status: "ok",
|
|
110
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
111
|
-
...version ? { version } : {}
|
|
112
|
-
};
|
|
113
|
-
});
|
|
114
|
-
fastify.get(`${prefix}/ready`, {
|
|
115
|
-
schema: {
|
|
116
|
-
tags: ["Health"],
|
|
117
|
-
summary: "Readiness probe",
|
|
118
|
-
description: "Returns 200 if all dependencies are healthy",
|
|
119
|
-
response: {
|
|
120
|
-
200: {
|
|
121
|
-
type: "object",
|
|
122
|
-
properties: {
|
|
123
|
-
status: { type: "string", enum: ["ready", "not_ready"] },
|
|
124
|
-
timestamp: { type: "string" },
|
|
125
|
-
checks: {
|
|
126
|
-
type: "array",
|
|
127
|
-
items: {
|
|
128
|
-
type: "object",
|
|
129
|
-
properties: {
|
|
130
|
-
name: { type: "string" },
|
|
131
|
-
healthy: { type: "boolean" },
|
|
132
|
-
duration: { type: "number" },
|
|
133
|
-
error: { type: "string" }
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
},
|
|
139
|
-
503: {
|
|
140
|
-
type: "object",
|
|
141
|
-
properties: {
|
|
142
|
-
status: { type: "string", enum: ["not_ready"] },
|
|
143
|
-
timestamp: { type: "string" },
|
|
144
|
-
checks: { type: "array" }
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}, async (_, reply) => {
|
|
150
|
-
const results = await runChecks(checks);
|
|
151
|
-
const criticalFailed = results.some(
|
|
152
|
-
(r) => !r.healthy && (checks.find((c) => c.name === r.name)?.critical ?? true)
|
|
153
|
-
);
|
|
154
|
-
const response = {
|
|
155
|
-
status: criticalFailed ? "not_ready" : "ready",
|
|
156
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
157
|
-
checks: results
|
|
158
|
-
};
|
|
159
|
-
if (criticalFailed) {
|
|
160
|
-
reply.code(503);
|
|
161
|
-
}
|
|
162
|
-
return response;
|
|
163
|
-
});
|
|
164
|
-
if (metrics) {
|
|
165
|
-
fastify.get(`${prefix}/metrics`, async (_, reply) => {
|
|
166
|
-
reply.type("text/plain; charset=utf-8");
|
|
167
|
-
if (metricsCollector) {
|
|
168
|
-
return await metricsCollector();
|
|
169
|
-
}
|
|
170
|
-
const uptime = process.uptime();
|
|
171
|
-
const memory = process.memoryUsage();
|
|
172
|
-
const cpu = process.cpuUsage();
|
|
173
|
-
const lines = [
|
|
174
|
-
"# HELP process_uptime_seconds Process uptime in seconds",
|
|
175
|
-
"# TYPE process_uptime_seconds gauge",
|
|
176
|
-
`process_uptime_seconds ${uptime.toFixed(2)}`,
|
|
177
|
-
"",
|
|
178
|
-
"# HELP process_memory_heap_bytes Heap memory usage in bytes",
|
|
179
|
-
"# TYPE process_memory_heap_bytes gauge",
|
|
180
|
-
`process_memory_heap_bytes{type="used"} ${memory.heapUsed}`,
|
|
181
|
-
`process_memory_heap_bytes{type="total"} ${memory.heapTotal}`,
|
|
182
|
-
"",
|
|
183
|
-
"# HELP process_memory_rss_bytes RSS memory in bytes",
|
|
184
|
-
"# TYPE process_memory_rss_bytes gauge",
|
|
185
|
-
`process_memory_rss_bytes ${memory.rss}`,
|
|
186
|
-
"",
|
|
187
|
-
"# HELP process_memory_external_bytes External memory in bytes",
|
|
188
|
-
"# TYPE process_memory_external_bytes gauge",
|
|
189
|
-
`process_memory_external_bytes ${memory.external}`,
|
|
190
|
-
"",
|
|
191
|
-
"# HELP process_cpu_user_microseconds User CPU time in microseconds",
|
|
192
|
-
"# TYPE process_cpu_user_microseconds counter",
|
|
193
|
-
`process_cpu_user_microseconds ${cpu.user}`,
|
|
194
|
-
"",
|
|
195
|
-
"# HELP process_cpu_system_microseconds System CPU time in microseconds",
|
|
196
|
-
"# TYPE process_cpu_system_microseconds counter",
|
|
197
|
-
`process_cpu_system_microseconds ${cpu.system}`,
|
|
198
|
-
""
|
|
199
|
-
];
|
|
200
|
-
if (collectHttpMetrics && Object.keys(httpMetrics.requestsTotal).length > 0) {
|
|
201
|
-
lines.push(
|
|
202
|
-
"# HELP http_requests_total Total HTTP requests by status code",
|
|
203
|
-
"# TYPE http_requests_total counter"
|
|
204
|
-
);
|
|
205
|
-
for (const [status, count] of Object.entries(httpMetrics.requestsTotal)) {
|
|
206
|
-
lines.push(`http_requests_total{status="${status}"} ${count}`);
|
|
207
|
-
}
|
|
208
|
-
lines.push("");
|
|
209
|
-
if (httpMetrics.requestDurations.length > 0) {
|
|
210
|
-
const sorted = [...httpMetrics.requestDurations].sort((a, b) => a - b);
|
|
211
|
-
const p50 = sorted[Math.floor(sorted.length * 0.5)] || 0;
|
|
212
|
-
const p95 = sorted[Math.floor(sorted.length * 0.95)] || 0;
|
|
213
|
-
const p99 = sorted[Math.floor(sorted.length * 0.99)] || 0;
|
|
214
|
-
const sum = sorted.reduce((a, b) => a + b, 0);
|
|
215
|
-
lines.push(
|
|
216
|
-
"# HELP http_request_duration_milliseconds HTTP request duration",
|
|
217
|
-
"# TYPE http_request_duration_milliseconds summary",
|
|
218
|
-
`http_request_duration_milliseconds{quantile="0.5"} ${p50.toFixed(2)}`,
|
|
219
|
-
`http_request_duration_milliseconds{quantile="0.95"} ${p95.toFixed(2)}`,
|
|
220
|
-
`http_request_duration_milliseconds{quantile="0.99"} ${p99.toFixed(2)}`,
|
|
221
|
-
`http_request_duration_milliseconds_sum ${sum.toFixed(2)}`,
|
|
222
|
-
`http_request_duration_milliseconds_count ${sorted.length}`,
|
|
223
|
-
""
|
|
224
|
-
);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
return lines.join("\n");
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
if (collectHttpMetrics) {
|
|
231
|
-
fastify.addHook("onRequest", async (request) => {
|
|
232
|
-
request._startTime = Date.now();
|
|
233
|
-
});
|
|
234
|
-
fastify.addHook("onResponse", async (request, reply) => {
|
|
235
|
-
const duration = Date.now() - (request._startTime || Date.now());
|
|
236
|
-
const statusBucket = `${Math.floor(reply.statusCode / 100)}xx`;
|
|
237
|
-
httpMetrics.requestsTotal[statusBucket] = (httpMetrics.requestsTotal[statusBucket] || 0) + 1;
|
|
238
|
-
httpMetrics.requestDurations.push(duration);
|
|
239
|
-
if (httpMetrics.requestDurations.length > 1e4) {
|
|
240
|
-
httpMetrics.requestDurations.shift();
|
|
241
|
-
}
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
fastify.log?.info?.(`Health plugin registered at ${prefix}`);
|
|
245
|
-
};
|
|
246
|
-
health_default = fp(healthPlugin, {
|
|
247
|
-
name: "arc-health",
|
|
248
|
-
fastify: "5.x"
|
|
249
|
-
});
|
|
250
|
-
}
|
|
251
|
-
});
|
|
252
|
-
function createTracerProvider(options) {
|
|
253
|
-
if (!isAvailable) {
|
|
254
|
-
return null;
|
|
255
|
-
}
|
|
256
|
-
const { serviceName = "@classytic/arc", exporterUrl = "http://localhost:4318/v1/traces" } = options;
|
|
257
|
-
const exporter = new OTLPTraceExporter({
|
|
258
|
-
url: exporterUrl
|
|
259
|
-
});
|
|
260
|
-
const provider = new NodeTracerProvider({
|
|
261
|
-
resource: {
|
|
262
|
-
attributes: {
|
|
263
|
-
"service.name": serviceName,
|
|
264
|
-
"service.version": "1.0.0"
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
});
|
|
268
|
-
provider.addSpanProcessor(new BatchSpanProcessor(exporter));
|
|
269
|
-
provider.register();
|
|
270
|
-
return provider;
|
|
271
|
-
}
|
|
272
|
-
async function tracingPlugin(fastify, options = {}) {
|
|
273
|
-
const {
|
|
274
|
-
serviceName = "@classytic/arc",
|
|
275
|
-
autoInstrumentation = true,
|
|
276
|
-
traceRepository = true,
|
|
277
|
-
traceController = true,
|
|
278
|
-
sampleRate = 1
|
|
279
|
-
} = options;
|
|
280
|
-
if (!isAvailable) {
|
|
281
|
-
fastify.log.warn("OpenTelemetry not installed. Tracing disabled.");
|
|
282
|
-
fastify.log.warn("Install: npm install @opentelemetry/api @opentelemetry/sdk-node");
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
const provider = createTracerProvider(options);
|
|
286
|
-
if (!provider) {
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
if (autoInstrumentation && getNodeAutoInstrumentations) {
|
|
290
|
-
getNodeAutoInstrumentations({
|
|
291
|
-
"@opentelemetry/instrumentation-http": {
|
|
292
|
-
enabled: true
|
|
293
|
-
},
|
|
294
|
-
"@opentelemetry/instrumentation-mongodb": {
|
|
295
|
-
enabled: true
|
|
296
|
-
}
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
|
-
const tracer = trace.getTracer(serviceName);
|
|
300
|
-
fastify.decorateRequest("tracer", void 0);
|
|
301
|
-
fastify.addHook("onRequest", async (request, reply) => {
|
|
302
|
-
if (Math.random() > sampleRate) {
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
const span = tracer.startSpan(`HTTP ${request.method} ${request.url}`, {
|
|
306
|
-
kind: 1,
|
|
307
|
-
// SpanKind.SERVER
|
|
308
|
-
attributes: {
|
|
309
|
-
"http.method": request.method,
|
|
310
|
-
"http.url": request.url,
|
|
311
|
-
"http.target": request.routeOptions?.url ?? request.url,
|
|
312
|
-
"http.host": request.hostname,
|
|
313
|
-
"http.scheme": request.protocol,
|
|
314
|
-
"http.user_agent": request.headers["user-agent"]
|
|
315
|
-
}
|
|
316
|
-
});
|
|
317
|
-
request.tracer = {
|
|
318
|
-
tracer,
|
|
319
|
-
currentSpan: span
|
|
320
|
-
};
|
|
321
|
-
context.with(trace.setSpan(context.active(), span), () => {
|
|
322
|
-
});
|
|
323
|
-
});
|
|
324
|
-
fastify.addHook("onResponse", async (request, reply) => {
|
|
325
|
-
if (!request.tracer?.currentSpan) {
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
const span = request.tracer.currentSpan;
|
|
329
|
-
span.setAttributes({
|
|
330
|
-
"http.status_code": reply.statusCode,
|
|
331
|
-
"http.response_content_length": reply.getHeader("content-length")
|
|
332
|
-
});
|
|
333
|
-
if (reply.statusCode >= 500) {
|
|
334
|
-
span.setStatus({
|
|
335
|
-
code: SpanStatusCode.ERROR,
|
|
336
|
-
message: `HTTP ${reply.statusCode}`
|
|
337
|
-
});
|
|
338
|
-
} else {
|
|
339
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
340
|
-
}
|
|
341
|
-
span.end();
|
|
342
|
-
});
|
|
343
|
-
fastify.addHook("onError", async (request, reply, error) => {
|
|
344
|
-
if (!request.tracer?.currentSpan) {
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
347
|
-
const span = request.tracer.currentSpan;
|
|
348
|
-
span.recordException(error);
|
|
349
|
-
span.setStatus({
|
|
350
|
-
code: SpanStatusCode.ERROR,
|
|
351
|
-
message: error.message
|
|
352
|
-
});
|
|
353
|
-
});
|
|
354
|
-
fastify.log.info({ serviceName }, "OpenTelemetry tracing enabled");
|
|
355
|
-
}
|
|
356
|
-
function createSpan(request, name, fn, attributes) {
|
|
357
|
-
if (!isAvailable || !request.tracer) {
|
|
358
|
-
return fn(null);
|
|
359
|
-
}
|
|
360
|
-
const { tracer, currentSpan } = request.tracer;
|
|
361
|
-
const span = tracer.startSpan(
|
|
362
|
-
name,
|
|
363
|
-
{
|
|
364
|
-
parent: currentSpan,
|
|
365
|
-
attributes: attributes || {}
|
|
366
|
-
},
|
|
367
|
-
trace.setSpan(context.active(), currentSpan)
|
|
368
|
-
);
|
|
369
|
-
return context.with(trace.setSpan(context.active(), span), async () => {
|
|
370
|
-
try {
|
|
371
|
-
const result = await fn(span);
|
|
372
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
373
|
-
return result;
|
|
374
|
-
} catch (error) {
|
|
375
|
-
span.recordException(error);
|
|
376
|
-
span.setStatus({
|
|
377
|
-
code: SpanStatusCode.ERROR,
|
|
378
|
-
message: error.message
|
|
379
|
-
});
|
|
380
|
-
throw error;
|
|
381
|
-
} finally {
|
|
382
|
-
span.end();
|
|
383
|
-
}
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
function traced(spanName) {
|
|
387
|
-
return function(target, propertyKey, descriptor) {
|
|
388
|
-
const originalMethod = descriptor.value;
|
|
389
|
-
descriptor.value = async function(...args) {
|
|
390
|
-
const request = args.find((arg) => arg && arg.tracer);
|
|
391
|
-
if (!request?.tracer) {
|
|
392
|
-
return originalMethod.apply(this, args);
|
|
393
|
-
}
|
|
394
|
-
const name = spanName || `${target.constructor.name}.${propertyKey}`;
|
|
395
|
-
return createSpan(request, name, async (span) => {
|
|
396
|
-
if (span) {
|
|
397
|
-
span.setAttribute("db.operation", propertyKey);
|
|
398
|
-
span.setAttribute("db.system", "mongodb");
|
|
399
|
-
}
|
|
400
|
-
return originalMethod.apply(this, args);
|
|
401
|
-
});
|
|
402
|
-
};
|
|
403
|
-
return descriptor;
|
|
404
|
-
};
|
|
405
|
-
}
|
|
406
|
-
function isTracingAvailable() {
|
|
407
|
-
return isAvailable;
|
|
408
|
-
}
|
|
409
|
-
var require2, trace, context, SpanStatusCode, NodeTracerProvider, BatchSpanProcessor, OTLPTraceExporter, HttpInstrumentation, MongoDBInstrumentation, getNodeAutoInstrumentations, isAvailable, tracing_default;
|
|
410
|
-
var init_tracing = __esm({
|
|
411
|
-
"src/plugins/tracing.ts"() {
|
|
412
|
-
require2 = createRequire(import.meta.url);
|
|
413
|
-
isAvailable = false;
|
|
414
|
-
try {
|
|
415
|
-
const api = require2("@opentelemetry/api");
|
|
416
|
-
trace = api.trace;
|
|
417
|
-
context = api.context;
|
|
418
|
-
SpanStatusCode = api.SpanStatusCode;
|
|
419
|
-
const sdkNode = require2("@opentelemetry/sdk-node");
|
|
420
|
-
NodeTracerProvider = sdkNode.NodeTracerProvider;
|
|
421
|
-
BatchSpanProcessor = sdkNode.BatchSpanProcessor;
|
|
422
|
-
const exporterTraceOtlp = require2("@opentelemetry/exporter-trace-otlp-http");
|
|
423
|
-
OTLPTraceExporter = exporterTraceOtlp.OTLPTraceExporter;
|
|
424
|
-
const instrHttp = require2("@opentelemetry/instrumentation-http");
|
|
425
|
-
HttpInstrumentation = instrHttp.HttpInstrumentation;
|
|
426
|
-
const instrMongo = require2("@opentelemetry/instrumentation-mongodb");
|
|
427
|
-
MongoDBInstrumentation = instrMongo.MongoDBInstrumentation;
|
|
428
|
-
const autoInstr = require2("@opentelemetry/auto-instrumentations-node");
|
|
429
|
-
getNodeAutoInstrumentations = autoInstr.getNodeAutoInstrumentations;
|
|
430
|
-
isAvailable = true;
|
|
431
|
-
} catch (e) {
|
|
432
|
-
}
|
|
433
|
-
tracing_default = fp(tracingPlugin, {
|
|
434
|
-
name: "arc-tracing",
|
|
435
|
-
fastify: "5.x"
|
|
436
|
-
});
|
|
437
|
-
}
|
|
438
|
-
});
|
|
439
|
-
var gracefulShutdownPlugin, gracefulShutdown_default;
|
|
440
|
-
var init_gracefulShutdown = __esm({
|
|
441
|
-
"src/plugins/gracefulShutdown.ts"() {
|
|
442
|
-
gracefulShutdownPlugin = async (fastify, opts = {}) => {
|
|
443
|
-
const {
|
|
444
|
-
timeout = 3e4,
|
|
445
|
-
onShutdown,
|
|
446
|
-
signals = ["SIGTERM", "SIGINT"],
|
|
447
|
-
logEvents = true
|
|
448
|
-
} = opts;
|
|
449
|
-
let isShuttingDown = false;
|
|
450
|
-
const shutdown = async (signal) => {
|
|
451
|
-
if (isShuttingDown) {
|
|
452
|
-
if (logEvents) {
|
|
453
|
-
fastify.log?.warn?.({ signal }, "Shutdown already in progress, ignoring signal");
|
|
454
|
-
}
|
|
455
|
-
return;
|
|
456
|
-
}
|
|
457
|
-
isShuttingDown = true;
|
|
458
|
-
if (logEvents) {
|
|
459
|
-
fastify.log?.info?.({ signal, timeout }, "Shutdown signal received, starting graceful shutdown");
|
|
460
|
-
}
|
|
461
|
-
const forceExitTimer = setTimeout(() => {
|
|
462
|
-
if (logEvents) {
|
|
463
|
-
fastify.log?.error?.("Graceful shutdown timeout exceeded, forcing exit");
|
|
464
|
-
}
|
|
465
|
-
process.exit(1);
|
|
466
|
-
}, timeout);
|
|
467
|
-
forceExitTimer.unref();
|
|
468
|
-
try {
|
|
469
|
-
if (logEvents) {
|
|
470
|
-
fastify.log?.info?.("Closing server to new connections");
|
|
471
|
-
}
|
|
472
|
-
await fastify.close();
|
|
473
|
-
if (onShutdown) {
|
|
474
|
-
if (logEvents) {
|
|
475
|
-
fastify.log?.info?.("Running custom shutdown handler");
|
|
476
|
-
}
|
|
477
|
-
await onShutdown();
|
|
478
|
-
}
|
|
479
|
-
if (logEvents) {
|
|
480
|
-
fastify.log?.info?.("Graceful shutdown complete");
|
|
481
|
-
}
|
|
482
|
-
clearTimeout(forceExitTimer);
|
|
483
|
-
process.exit(0);
|
|
484
|
-
} catch (err) {
|
|
485
|
-
if (logEvents) {
|
|
486
|
-
fastify.log?.error?.({ error: err.message }, "Error during shutdown");
|
|
487
|
-
}
|
|
488
|
-
clearTimeout(forceExitTimer);
|
|
489
|
-
process.exit(1);
|
|
490
|
-
}
|
|
491
|
-
};
|
|
492
|
-
for (const signal of signals) {
|
|
493
|
-
process.on(signal, () => {
|
|
494
|
-
void shutdown(signal);
|
|
495
|
-
});
|
|
496
|
-
}
|
|
497
|
-
fastify.decorate("shutdown", async () => {
|
|
498
|
-
await shutdown("MANUAL");
|
|
499
|
-
});
|
|
500
|
-
if (logEvents) {
|
|
501
|
-
fastify.log?.debug?.({ signals }, "Graceful shutdown plugin registered");
|
|
502
|
-
}
|
|
503
|
-
};
|
|
504
|
-
gracefulShutdown_default = fp(gracefulShutdownPlugin, {
|
|
505
|
-
name: "arc-graceful-shutdown",
|
|
506
|
-
fastify: "5.x"
|
|
507
|
-
});
|
|
508
|
-
}
|
|
509
|
-
});
|
|
510
|
-
|
|
511
|
-
// src/utils/errors.ts
|
|
512
|
-
function isArcError(error) {
|
|
513
|
-
return error instanceof ArcError;
|
|
514
|
-
}
|
|
515
|
-
var ArcError;
|
|
516
|
-
var init_errors = __esm({
|
|
517
|
-
"src/utils/errors.ts"() {
|
|
518
|
-
ArcError = class extends Error {
|
|
519
|
-
name;
|
|
520
|
-
code;
|
|
521
|
-
statusCode;
|
|
522
|
-
details;
|
|
523
|
-
cause;
|
|
524
|
-
timestamp;
|
|
525
|
-
requestId;
|
|
526
|
-
constructor(message, options = {}) {
|
|
527
|
-
super(message);
|
|
528
|
-
this.name = "ArcError";
|
|
529
|
-
this.code = options.code ?? "ARC_ERROR";
|
|
530
|
-
this.statusCode = options.statusCode ?? 500;
|
|
531
|
-
this.details = options.details;
|
|
532
|
-
this.cause = options.cause;
|
|
533
|
-
this.timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
534
|
-
this.requestId = options.requestId;
|
|
535
|
-
if (Error.captureStackTrace) {
|
|
536
|
-
Error.captureStackTrace(this, this.constructor);
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
/**
|
|
540
|
-
* Set request ID (typically from request context)
|
|
541
|
-
*/
|
|
542
|
-
withRequestId(requestId) {
|
|
543
|
-
this.requestId = requestId;
|
|
544
|
-
return this;
|
|
545
|
-
}
|
|
546
|
-
/**
|
|
547
|
-
* Convert to JSON response
|
|
548
|
-
*/
|
|
549
|
-
toJSON() {
|
|
550
|
-
return {
|
|
551
|
-
success: false,
|
|
552
|
-
error: this.message,
|
|
553
|
-
code: this.code,
|
|
554
|
-
timestamp: this.timestamp,
|
|
555
|
-
...this.requestId && { requestId: this.requestId },
|
|
556
|
-
...this.details && { details: this.details }
|
|
557
|
-
};
|
|
558
|
-
}
|
|
559
|
-
};
|
|
560
|
-
}
|
|
561
|
-
});
|
|
562
|
-
async function errorHandlerPluginFn(fastify, options = {}) {
|
|
563
|
-
const {
|
|
564
|
-
includeStack = process.env.NODE_ENV !== "production",
|
|
565
|
-
onError,
|
|
566
|
-
errorMap = {}
|
|
567
|
-
} = options;
|
|
568
|
-
fastify.setErrorHandler(async (error, request, reply) => {
|
|
569
|
-
if (onError) {
|
|
570
|
-
try {
|
|
571
|
-
await onError(error, request);
|
|
572
|
-
} catch (callbackError) {
|
|
573
|
-
request.log.error({ err: callbackError }, "Error in onError callback");
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
const requestId = request.id;
|
|
577
|
-
const response = {
|
|
578
|
-
success: false,
|
|
579
|
-
error: error.message || "Internal Server Error",
|
|
580
|
-
code: "INTERNAL_ERROR",
|
|
581
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
582
|
-
...requestId && { requestId }
|
|
583
|
-
};
|
|
584
|
-
let statusCode = 500;
|
|
585
|
-
if (isArcError(error)) {
|
|
586
|
-
statusCode = error.statusCode;
|
|
587
|
-
response.code = error.code;
|
|
588
|
-
if (error.details) {
|
|
589
|
-
response.details = error.details;
|
|
590
|
-
}
|
|
591
|
-
if (error.requestId) {
|
|
592
|
-
response.requestId = error.requestId;
|
|
593
|
-
}
|
|
594
|
-
} else if ("validation" in error && Array.isArray(error.validation)) {
|
|
595
|
-
statusCode = 400;
|
|
596
|
-
response.code = "VALIDATION_ERROR";
|
|
597
|
-
response.error = "Validation failed";
|
|
598
|
-
response.details = {
|
|
599
|
-
errors: error.validation?.map((v) => ({
|
|
600
|
-
field: v.instancePath?.replace(/^\//, "") || v.params?.missingProperty || "unknown",
|
|
601
|
-
message: v.message || "Invalid value",
|
|
602
|
-
keyword: v.keyword
|
|
603
|
-
}))
|
|
604
|
-
};
|
|
605
|
-
} else if ("statusCode" in error && typeof error.statusCode === "number") {
|
|
606
|
-
statusCode = error.statusCode;
|
|
607
|
-
response.code = statusCodeToCode(statusCode);
|
|
608
|
-
} else if (error.name && errorMap[error.name]) {
|
|
609
|
-
const mapping = errorMap[error.name];
|
|
610
|
-
statusCode = mapping.statusCode;
|
|
611
|
-
response.code = mapping.code;
|
|
612
|
-
if (mapping.message) {
|
|
613
|
-
response.error = mapping.message;
|
|
614
|
-
}
|
|
615
|
-
} else if (error.name === "ValidationError" && "errors" in error) {
|
|
616
|
-
statusCode = 400;
|
|
617
|
-
response.code = "VALIDATION_ERROR";
|
|
618
|
-
const mongooseErrors = error.errors;
|
|
619
|
-
if (process.env.NODE_ENV === "production") {
|
|
620
|
-
response.details = { errorCount: Object.keys(mongooseErrors).length };
|
|
621
|
-
} else {
|
|
622
|
-
response.details = {
|
|
623
|
-
errors: Object.entries(mongooseErrors).map(([field, err]) => ({
|
|
624
|
-
field: err.path || field,
|
|
625
|
-
message: err.message
|
|
626
|
-
}))
|
|
627
|
-
};
|
|
628
|
-
}
|
|
629
|
-
} else if (error.name === "CastError") {
|
|
630
|
-
statusCode = 400;
|
|
631
|
-
response.code = "INVALID_ID";
|
|
632
|
-
response.error = "Invalid identifier format";
|
|
633
|
-
} else if (error.name === "MongoServerError" && error.code === 11e3) {
|
|
634
|
-
statusCode = 409;
|
|
635
|
-
response.code = "DUPLICATE_KEY";
|
|
636
|
-
response.error = "Resource already exists";
|
|
637
|
-
const keyValue = error.keyValue;
|
|
638
|
-
if (keyValue && process.env.NODE_ENV !== "production") {
|
|
639
|
-
response.details = { duplicateFields: Object.keys(keyValue) };
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
if (includeStack && error.stack) {
|
|
643
|
-
response.stack = error.stack;
|
|
644
|
-
}
|
|
645
|
-
if (statusCode >= 500) {
|
|
646
|
-
request.log.error({ err: error, statusCode }, "Server error");
|
|
647
|
-
} else if (statusCode >= 400) {
|
|
648
|
-
request.log.warn({ err: error, statusCode }, "Client error");
|
|
649
|
-
}
|
|
650
|
-
return reply.status(statusCode).send(response);
|
|
651
|
-
});
|
|
652
|
-
}
|
|
653
|
-
function statusCodeToCode(statusCode) {
|
|
654
|
-
const codes = {
|
|
655
|
-
400: "BAD_REQUEST",
|
|
656
|
-
401: "UNAUTHORIZED",
|
|
657
|
-
403: "FORBIDDEN",
|
|
658
|
-
404: "NOT_FOUND",
|
|
659
|
-
405: "METHOD_NOT_ALLOWED",
|
|
660
|
-
409: "CONFLICT",
|
|
661
|
-
422: "UNPROCESSABLE_ENTITY",
|
|
662
|
-
429: "RATE_LIMITED",
|
|
663
|
-
500: "INTERNAL_ERROR",
|
|
664
|
-
502: "BAD_GATEWAY",
|
|
665
|
-
503: "SERVICE_UNAVAILABLE",
|
|
666
|
-
504: "GATEWAY_TIMEOUT"
|
|
667
|
-
};
|
|
668
|
-
return codes[statusCode] ?? "ERROR";
|
|
669
|
-
}
|
|
670
|
-
var errorHandlerPlugin, errorHandler_default;
|
|
671
|
-
var init_errorHandler = __esm({
|
|
672
|
-
"src/plugins/errorHandler.ts"() {
|
|
673
|
-
init_errors();
|
|
674
|
-
errorHandlerPlugin = fp(errorHandlerPluginFn, {
|
|
675
|
-
name: "arc-error-handler",
|
|
676
|
-
fastify: "5.x"
|
|
677
|
-
});
|
|
678
|
-
errorHandler_default = errorHandlerPlugin;
|
|
679
|
-
}
|
|
680
|
-
});
|
|
681
|
-
|
|
682
|
-
// src/hooks/HookSystem.ts
|
|
683
|
-
var HookSystem, hookSystem;
|
|
684
|
-
var init_HookSystem = __esm({
|
|
685
|
-
"src/hooks/HookSystem.ts"() {
|
|
686
|
-
HookSystem = class {
|
|
687
|
-
hooks;
|
|
688
|
-
logger;
|
|
689
|
-
constructor(options) {
|
|
690
|
-
this.hooks = /* @__PURE__ */ new Map();
|
|
691
|
-
this.logger = options?.logger ?? { error: (...args) => console.error(...args) };
|
|
692
|
-
}
|
|
693
|
-
/**
|
|
694
|
-
* Generate hook key
|
|
695
|
-
*/
|
|
696
|
-
getKey(resource, operation, phase) {
|
|
697
|
-
return `${resource}:${operation}:${phase}`;
|
|
698
|
-
}
|
|
699
|
-
/**
|
|
700
|
-
* Register a hook
|
|
701
|
-
* Supports both object parameter and positional arguments
|
|
702
|
-
*/
|
|
703
|
-
register(resourceOrOptions, operation, phase, handler, priority = 10) {
|
|
704
|
-
let resource;
|
|
705
|
-
let finalOperation;
|
|
706
|
-
let finalPhase;
|
|
707
|
-
let finalHandler;
|
|
708
|
-
let finalPriority;
|
|
709
|
-
if (typeof resourceOrOptions === "object") {
|
|
710
|
-
resource = resourceOrOptions.resource;
|
|
711
|
-
finalOperation = resourceOrOptions.operation;
|
|
712
|
-
finalPhase = resourceOrOptions.phase;
|
|
713
|
-
finalHandler = resourceOrOptions.handler;
|
|
714
|
-
finalPriority = resourceOrOptions.priority ?? 10;
|
|
715
|
-
} else {
|
|
716
|
-
resource = resourceOrOptions;
|
|
717
|
-
finalOperation = operation;
|
|
718
|
-
finalPhase = phase;
|
|
719
|
-
finalHandler = handler;
|
|
720
|
-
finalPriority = priority;
|
|
721
|
-
}
|
|
722
|
-
const key = this.getKey(resource, finalOperation, finalPhase);
|
|
723
|
-
if (!this.hooks.has(key)) {
|
|
724
|
-
this.hooks.set(key, []);
|
|
725
|
-
}
|
|
726
|
-
const registration = {
|
|
727
|
-
resource,
|
|
728
|
-
operation: finalOperation,
|
|
729
|
-
phase: finalPhase,
|
|
730
|
-
handler: finalHandler,
|
|
731
|
-
priority: finalPriority
|
|
732
|
-
};
|
|
733
|
-
const hooks = this.hooks.get(key);
|
|
734
|
-
hooks.push(registration);
|
|
735
|
-
hooks.sort((a, b) => a.priority - b.priority);
|
|
736
|
-
return () => {
|
|
737
|
-
const idx = hooks.indexOf(registration);
|
|
738
|
-
if (idx !== -1) {
|
|
739
|
-
hooks.splice(idx, 1);
|
|
740
|
-
}
|
|
741
|
-
};
|
|
742
|
-
}
|
|
743
|
-
/**
|
|
744
|
-
* Register before hook
|
|
745
|
-
*/
|
|
746
|
-
before(resource, operation, handler, priority = 10) {
|
|
747
|
-
return this.register(resource, operation, "before", handler, priority);
|
|
748
|
-
}
|
|
749
|
-
/**
|
|
750
|
-
* Register after hook
|
|
751
|
-
*/
|
|
752
|
-
after(resource, operation, handler, priority = 10) {
|
|
753
|
-
return this.register(resource, operation, "after", handler, priority);
|
|
754
|
-
}
|
|
755
|
-
/**
|
|
756
|
-
* Execute hooks for a given context
|
|
757
|
-
*/
|
|
758
|
-
async execute(ctx) {
|
|
759
|
-
const key = this.getKey(ctx.resource, ctx.operation, ctx.phase);
|
|
760
|
-
const hooks = this.hooks.get(key) ?? [];
|
|
761
|
-
const wildcardKey = this.getKey("*", ctx.operation, ctx.phase);
|
|
762
|
-
const wildcardHooks = this.hooks.get(wildcardKey) ?? [];
|
|
763
|
-
const allHooks = [...wildcardHooks, ...hooks];
|
|
764
|
-
allHooks.sort((a, b) => a.priority - b.priority);
|
|
765
|
-
let result = ctx.data;
|
|
766
|
-
for (const hook of allHooks) {
|
|
767
|
-
const handlerContext = {
|
|
768
|
-
resource: ctx.resource,
|
|
769
|
-
operation: ctx.operation,
|
|
770
|
-
phase: ctx.phase,
|
|
771
|
-
data: result,
|
|
772
|
-
result: ctx.result,
|
|
773
|
-
user: ctx.user,
|
|
774
|
-
context: ctx.context,
|
|
775
|
-
meta: ctx.meta
|
|
776
|
-
};
|
|
777
|
-
const hookResult = await hook.handler(handlerContext);
|
|
778
|
-
if (hookResult !== void 0 && hookResult !== null) {
|
|
779
|
-
result = hookResult;
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
return result;
|
|
783
|
-
}
|
|
784
|
-
/**
|
|
785
|
-
* Execute before hooks
|
|
786
|
-
*/
|
|
787
|
-
async executeBefore(resource, operation, data, options) {
|
|
788
|
-
const result = await this.execute({
|
|
789
|
-
resource,
|
|
790
|
-
operation,
|
|
791
|
-
phase: "before",
|
|
792
|
-
data,
|
|
793
|
-
user: options?.user,
|
|
794
|
-
context: options?.context,
|
|
795
|
-
meta: options?.meta
|
|
796
|
-
});
|
|
797
|
-
return result ?? data;
|
|
798
|
-
}
|
|
799
|
-
/**
|
|
800
|
-
* Execute after hooks
|
|
801
|
-
* Errors in after hooks are logged but don't fail the request
|
|
802
|
-
*/
|
|
803
|
-
async executeAfter(resource, operation, result, options) {
|
|
804
|
-
try {
|
|
805
|
-
await this.execute({
|
|
806
|
-
resource,
|
|
807
|
-
operation,
|
|
808
|
-
phase: "after",
|
|
809
|
-
result,
|
|
810
|
-
user: options?.user,
|
|
811
|
-
context: options?.context,
|
|
812
|
-
meta: options?.meta
|
|
813
|
-
});
|
|
814
|
-
} catch (error) {
|
|
815
|
-
this.logger.error(
|
|
816
|
-
`[HookSystem] Error in after hook for ${resource}:${operation}:`,
|
|
817
|
-
error
|
|
818
|
-
);
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
/**
|
|
822
|
-
* Get all registered hooks
|
|
823
|
-
*/
|
|
824
|
-
getAll() {
|
|
825
|
-
const all = [];
|
|
826
|
-
for (const hooks of this.hooks.values()) {
|
|
827
|
-
all.push(...hooks);
|
|
828
|
-
}
|
|
829
|
-
return all;
|
|
830
|
-
}
|
|
831
|
-
/**
|
|
832
|
-
* Get hooks for a specific resource
|
|
833
|
-
*/
|
|
834
|
-
getForResource(resource) {
|
|
835
|
-
const all = [];
|
|
836
|
-
for (const [key, hooks] of this.hooks.entries()) {
|
|
837
|
-
if (key.startsWith(`${resource}:`)) {
|
|
838
|
-
all.push(...hooks);
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
return all;
|
|
842
|
-
}
|
|
843
|
-
/**
|
|
844
|
-
* Clear all hooks
|
|
845
|
-
*/
|
|
846
|
-
clear() {
|
|
847
|
-
this.hooks.clear();
|
|
848
|
-
}
|
|
849
|
-
/**
|
|
850
|
-
* Clear hooks for a specific resource
|
|
851
|
-
*/
|
|
852
|
-
clearResource(resource) {
|
|
853
|
-
for (const key of this.hooks.keys()) {
|
|
854
|
-
if (key.startsWith(`${resource}:`)) {
|
|
855
|
-
this.hooks.delete(key);
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
};
|
|
860
|
-
hookSystem = new HookSystem();
|
|
861
|
-
}
|
|
862
|
-
});
|
|
863
|
-
|
|
864
|
-
// src/registry/ResourceRegistry.ts
|
|
865
|
-
var ResourceRegistry, registryKey, globalScope, resourceRegistry;
|
|
866
|
-
var init_ResourceRegistry = __esm({
|
|
867
|
-
"src/registry/ResourceRegistry.ts"() {
|
|
868
|
-
ResourceRegistry = class {
|
|
869
|
-
_resources;
|
|
870
|
-
_frozen;
|
|
871
|
-
constructor() {
|
|
872
|
-
this._resources = /* @__PURE__ */ new Map();
|
|
873
|
-
this._frozen = false;
|
|
874
|
-
}
|
|
875
|
-
/**
|
|
876
|
-
* Register a resource
|
|
877
|
-
*/
|
|
878
|
-
register(resource, options = {}) {
|
|
879
|
-
if (this._frozen) {
|
|
880
|
-
throw new Error(
|
|
881
|
-
`Registry frozen. Cannot register '${resource.name}' after startup.`
|
|
882
|
-
);
|
|
883
|
-
}
|
|
884
|
-
if (this._resources.has(resource.name)) {
|
|
885
|
-
throw new Error(`Resource '${resource.name}' already registered.`);
|
|
886
|
-
}
|
|
887
|
-
const entry = {
|
|
888
|
-
name: resource.name,
|
|
889
|
-
displayName: resource.displayName,
|
|
890
|
-
tag: resource.tag,
|
|
891
|
-
prefix: resource.prefix,
|
|
892
|
-
module: options.module ?? void 0,
|
|
893
|
-
adapter: resource.adapter ? {
|
|
894
|
-
type: resource.adapter.type,
|
|
895
|
-
name: resource.adapter.name
|
|
896
|
-
} : null,
|
|
897
|
-
permissions: resource.permissions,
|
|
898
|
-
presets: resource._appliedPresets ?? [],
|
|
899
|
-
routes: [],
|
|
900
|
-
// Populated later by getIntrospection()
|
|
901
|
-
additionalRoutes: resource.additionalRoutes.map((r) => ({
|
|
902
|
-
method: r.method,
|
|
903
|
-
path: r.path,
|
|
904
|
-
handler: typeof r.handler === "string" ? r.handler : r.handler.name || "anonymous",
|
|
905
|
-
summary: r.summary,
|
|
906
|
-
description: r.description,
|
|
907
|
-
permissions: r.permissions,
|
|
908
|
-
wrapHandler: r.wrapHandler,
|
|
909
|
-
schema: r.schema
|
|
910
|
-
// Include schema for OpenAPI docs
|
|
911
|
-
})),
|
|
912
|
-
events: Object.keys(resource.events ?? {}),
|
|
913
|
-
registeredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
914
|
-
disableDefaultRoutes: resource.disableDefaultRoutes,
|
|
915
|
-
openApiSchemas: options.openApiSchemas,
|
|
916
|
-
plugin: resource.toPlugin()
|
|
917
|
-
// Store plugin factory
|
|
918
|
-
};
|
|
919
|
-
this._resources.set(resource.name, entry);
|
|
920
|
-
return this;
|
|
921
|
-
}
|
|
922
|
-
/**
|
|
923
|
-
* Get resource by name
|
|
924
|
-
*/
|
|
925
|
-
get(name) {
|
|
926
|
-
return this._resources.get(name);
|
|
927
|
-
}
|
|
928
|
-
/**
|
|
929
|
-
* Get all resources
|
|
930
|
-
*/
|
|
931
|
-
getAll() {
|
|
932
|
-
return Array.from(this._resources.values());
|
|
933
|
-
}
|
|
934
|
-
/**
|
|
935
|
-
* Get resources by module
|
|
936
|
-
*/
|
|
937
|
-
getByModule(moduleName) {
|
|
938
|
-
return this.getAll().filter((r) => r.module === moduleName);
|
|
939
|
-
}
|
|
940
|
-
/**
|
|
941
|
-
* Get resources by preset
|
|
942
|
-
*/
|
|
943
|
-
getByPreset(presetName) {
|
|
944
|
-
return this.getAll().filter((r) => r.presets.includes(presetName));
|
|
945
|
-
}
|
|
946
|
-
/**
|
|
947
|
-
* Check if resource exists
|
|
948
|
-
*/
|
|
949
|
-
has(name) {
|
|
950
|
-
return this._resources.has(name);
|
|
951
|
-
}
|
|
952
|
-
/**
|
|
953
|
-
* Get registry statistics
|
|
954
|
-
*/
|
|
955
|
-
getStats() {
|
|
956
|
-
const resources = this.getAll();
|
|
957
|
-
const presetCounts = {};
|
|
958
|
-
for (const r of resources) {
|
|
959
|
-
for (const preset of r.presets) {
|
|
960
|
-
presetCounts[preset] = (presetCounts[preset] ?? 0) + 1;
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
return {
|
|
964
|
-
totalResources: resources.length,
|
|
965
|
-
byModule: this._groupBy(resources, "module"),
|
|
966
|
-
presetUsage: presetCounts,
|
|
967
|
-
totalRoutes: resources.reduce((sum, r) => {
|
|
968
|
-
const defaultRouteCount = r.disableDefaultRoutes ? 0 : 5;
|
|
969
|
-
return sum + (r.additionalRoutes?.length ?? 0) + defaultRouteCount;
|
|
970
|
-
}, 0),
|
|
971
|
-
totalEvents: resources.reduce((sum, r) => sum + (r.events?.length ?? 0), 0)
|
|
972
|
-
};
|
|
973
|
-
}
|
|
974
|
-
/**
|
|
975
|
-
* Get full introspection data
|
|
976
|
-
*/
|
|
977
|
-
getIntrospection() {
|
|
978
|
-
return {
|
|
979
|
-
resources: this.getAll().map((r) => {
|
|
980
|
-
const defaultRoutes = r.disableDefaultRoutes ? [] : [
|
|
981
|
-
{ method: "GET", path: r.prefix, operation: "list" },
|
|
982
|
-
{ method: "GET", path: `${r.prefix}/:id`, operation: "get" },
|
|
983
|
-
{ method: "POST", path: r.prefix, operation: "create" },
|
|
984
|
-
{ method: "PATCH", path: `${r.prefix}/:id`, operation: "update" },
|
|
985
|
-
{ method: "DELETE", path: `${r.prefix}/:id`, operation: "delete" }
|
|
986
|
-
];
|
|
987
|
-
return {
|
|
988
|
-
name: r.name,
|
|
989
|
-
displayName: r.displayName,
|
|
990
|
-
prefix: r.prefix,
|
|
991
|
-
module: r.module,
|
|
992
|
-
presets: r.presets,
|
|
993
|
-
permissions: r.permissions,
|
|
994
|
-
routes: [
|
|
995
|
-
...defaultRoutes,
|
|
996
|
-
...r.additionalRoutes?.map((ar) => ({
|
|
997
|
-
method: ar.method,
|
|
998
|
-
path: `${r.prefix}${ar.path}`,
|
|
999
|
-
operation: typeof ar.handler === "string" ? ar.handler : "custom",
|
|
1000
|
-
handler: typeof ar.handler === "string" ? ar.handler : void 0,
|
|
1001
|
-
summary: ar.summary
|
|
1002
|
-
})) ?? []
|
|
1003
|
-
],
|
|
1004
|
-
events: r.events
|
|
1005
|
-
};
|
|
1006
|
-
}),
|
|
1007
|
-
stats: this.getStats(),
|
|
1008
|
-
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1009
|
-
};
|
|
1010
|
-
}
|
|
1011
|
-
/**
|
|
1012
|
-
* Freeze registry (prevent further registrations)
|
|
1013
|
-
*/
|
|
1014
|
-
freeze() {
|
|
1015
|
-
this._frozen = true;
|
|
1016
|
-
}
|
|
1017
|
-
/**
|
|
1018
|
-
* Check if frozen
|
|
1019
|
-
*/
|
|
1020
|
-
isFrozen() {
|
|
1021
|
-
return this._frozen;
|
|
1022
|
-
}
|
|
1023
|
-
/**
|
|
1024
|
-
* Unfreeze registry (for testing)
|
|
1025
|
-
*/
|
|
1026
|
-
_unfreeze() {
|
|
1027
|
-
this._frozen = false;
|
|
1028
|
-
}
|
|
1029
|
-
/**
|
|
1030
|
-
* Clear all resources (for testing)
|
|
1031
|
-
*/
|
|
1032
|
-
_clear() {
|
|
1033
|
-
this._resources.clear();
|
|
1034
|
-
this._frozen = false;
|
|
1035
|
-
}
|
|
1036
|
-
/**
|
|
1037
|
-
* Group by key
|
|
1038
|
-
*/
|
|
1039
|
-
_groupBy(arr, key) {
|
|
1040
|
-
const result = {};
|
|
1041
|
-
for (const item of arr) {
|
|
1042
|
-
const k = String(item[key] ?? "uncategorized");
|
|
1043
|
-
result[k] = (result[k] ?? 0) + 1;
|
|
1044
|
-
}
|
|
1045
|
-
return result;
|
|
1046
|
-
}
|
|
1047
|
-
};
|
|
1048
|
-
registryKey = /* @__PURE__ */ Symbol.for("arc.resourceRegistry");
|
|
1049
|
-
globalScope = globalThis;
|
|
1050
|
-
resourceRegistry = globalScope[registryKey] ?? new ResourceRegistry();
|
|
1051
|
-
if (!globalScope[registryKey]) {
|
|
1052
|
-
globalScope[registryKey] = resourceRegistry;
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
});
|
|
1056
|
-
function hasEvents(instance) {
|
|
1057
|
-
return "events" in instance && instance.events != null && typeof instance.events.publish === "function";
|
|
1058
|
-
}
|
|
1059
|
-
var arcCorePlugin, arcCorePlugin_default;
|
|
1060
|
-
var init_arcCorePlugin = __esm({
|
|
1061
|
-
"src/core/arcCorePlugin.ts"() {
|
|
1062
|
-
init_HookSystem();
|
|
1063
|
-
init_ResourceRegistry();
|
|
1064
|
-
arcCorePlugin = async (fastify, opts = {}) => {
|
|
1065
|
-
const {
|
|
1066
|
-
emitEvents = true,
|
|
1067
|
-
hookSystem: hookSystem2,
|
|
1068
|
-
registry,
|
|
1069
|
-
useGlobalSingletons = false
|
|
1070
|
-
} = opts;
|
|
1071
|
-
const actualHookSystem = useGlobalSingletons ? hookSystem : hookSystem2 ?? new HookSystem();
|
|
1072
|
-
const actualRegistry = useGlobalSingletons ? resourceRegistry : registry ?? new ResourceRegistry();
|
|
1073
|
-
fastify.decorate("arc", {
|
|
1074
|
-
hooks: actualHookSystem,
|
|
1075
|
-
registry: actualRegistry,
|
|
1076
|
-
emitEvents
|
|
1077
|
-
});
|
|
1078
|
-
if (emitEvents) {
|
|
1079
|
-
const eventOperations = ["create", "update", "delete"];
|
|
1080
|
-
for (const operation of eventOperations) {
|
|
1081
|
-
actualHookSystem.after("*", operation, async (ctx) => {
|
|
1082
|
-
if (!hasEvents(fastify)) return;
|
|
1083
|
-
const eventType = `${ctx.resource}.${operation}d`;
|
|
1084
|
-
const payload = {
|
|
1085
|
-
resource: ctx.resource,
|
|
1086
|
-
operation: ctx.operation,
|
|
1087
|
-
data: ctx.result,
|
|
1088
|
-
userId: ctx.user?.id ?? ctx.user?._id,
|
|
1089
|
-
organizationId: ctx.context?.organizationId,
|
|
1090
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1091
|
-
};
|
|
1092
|
-
try {
|
|
1093
|
-
await fastify.events.publish(eventType, payload);
|
|
1094
|
-
} catch (error) {
|
|
1095
|
-
fastify.log?.warn?.(
|
|
1096
|
-
{ eventType, error },
|
|
1097
|
-
"Failed to emit event"
|
|
1098
|
-
);
|
|
1099
|
-
}
|
|
1100
|
-
});
|
|
1101
|
-
}
|
|
1102
|
-
}
|
|
1103
|
-
fastify.addHook("onClose", async () => {
|
|
1104
|
-
actualHookSystem.clear();
|
|
1105
|
-
actualRegistry._clear();
|
|
1106
|
-
});
|
|
1107
|
-
fastify.log?.info?.("✅ Arc core plugin enabled (instance-scoped hooks & registry)");
|
|
1108
|
-
};
|
|
1109
|
-
arcCorePlugin_default = fp(arcCorePlugin, {
|
|
1110
|
-
name: "arc-core",
|
|
1111
|
-
fastify: "5.x"
|
|
1112
|
-
});
|
|
1113
|
-
}
|
|
1114
|
-
});
|
|
1115
|
-
|
|
1116
|
-
// src/plugins/index.ts
|
|
1117
|
-
var plugins_exports = {};
|
|
1118
|
-
__export(plugins_exports, {
|
|
1119
|
-
arcCorePlugin: () => arcCorePlugin_default,
|
|
1120
|
-
arcCorePluginFn: () => arcCorePlugin,
|
|
1121
|
-
createSpan: () => createSpan,
|
|
1122
|
-
errorHandlerPlugin: () => errorHandler_default,
|
|
1123
|
-
errorHandlerPluginFn: () => errorHandlerPlugin,
|
|
1124
|
-
gracefulShutdownPlugin: () => gracefulShutdown_default,
|
|
1125
|
-
gracefulShutdownPluginFn: () => gracefulShutdownPlugin,
|
|
1126
|
-
healthPlugin: () => health_default,
|
|
1127
|
-
healthPluginFn: () => healthPlugin,
|
|
1128
|
-
isTracingAvailable: () => isTracingAvailable,
|
|
1129
|
-
requestIdPlugin: () => requestId_default,
|
|
1130
|
-
requestIdPluginFn: () => requestIdPlugin,
|
|
1131
|
-
traced: () => traced,
|
|
1132
|
-
tracingPlugin: () => tracing_default
|
|
1133
|
-
});
|
|
1134
|
-
var init_plugins = __esm({
|
|
1135
|
-
"src/plugins/index.ts"() {
|
|
1136
|
-
init_requestId();
|
|
1137
|
-
init_health();
|
|
1138
|
-
init_tracing();
|
|
1139
|
-
init_gracefulShutdown();
|
|
1140
|
-
init_errorHandler();
|
|
1141
|
-
init_arcCorePlugin();
|
|
1142
|
-
}
|
|
1143
|
-
});
|
|
1144
|
-
function parseExpiresIn(input, defaultValue) {
|
|
1145
|
-
if (!input) return defaultValue;
|
|
1146
|
-
if (/^\d+$/.test(input)) return parseInt(input, 10);
|
|
1147
|
-
const match = /^(\d+)\s*([smhd])$/i.exec(input);
|
|
1148
|
-
if (!match) return defaultValue;
|
|
1149
|
-
const value = parseInt(match[1], 10);
|
|
1150
|
-
const unit = match[2].toLowerCase();
|
|
1151
|
-
const multipliers = { s: 1, m: 60, h: 3600, d: 86400 };
|
|
1152
|
-
return value * (multipliers[unit] ?? 1);
|
|
1153
|
-
}
|
|
1154
|
-
function extractBearerToken(request) {
|
|
1155
|
-
const auth = request.headers.authorization;
|
|
1156
|
-
if (!auth?.startsWith("Bearer ")) return null;
|
|
1157
|
-
return auth.slice(7);
|
|
1158
|
-
}
|
|
1159
|
-
var authPlugin, authPlugin_default;
|
|
1160
|
-
var init_authPlugin = __esm({
|
|
1161
|
-
"src/auth/authPlugin.ts"() {
|
|
1162
|
-
authPlugin = async (fastify, opts = {}) => {
|
|
1163
|
-
const { jwt: jwtConfig, authenticate: appAuthenticator, onFailure, userProperty = "user" } = opts;
|
|
1164
|
-
let jwtContext = null;
|
|
1165
|
-
if (jwtConfig?.secret) {
|
|
1166
|
-
if (jwtConfig.secret.length < 32) {
|
|
1167
|
-
throw new Error(
|
|
1168
|
-
`JWT secret must be at least 32 characters (current: ${jwtConfig.secret.length}).
|
|
1169
|
-
Use a strong random secret for production.`
|
|
1170
|
-
);
|
|
1171
|
-
}
|
|
1172
|
-
const jwtPlugin = await import('@fastify/jwt');
|
|
1173
|
-
await fastify.register(jwtPlugin.default ?? jwtPlugin, {
|
|
1174
|
-
secret: jwtConfig.secret,
|
|
1175
|
-
sign: {
|
|
1176
|
-
expiresIn: jwtConfig.expiresIn ?? "15m",
|
|
1177
|
-
...jwtConfig.sign ?? {}
|
|
1178
|
-
},
|
|
1179
|
-
verify: { ...jwtConfig.verify ?? {} }
|
|
1180
|
-
});
|
|
1181
|
-
const fastifyWithJwt = fastify;
|
|
1182
|
-
jwtContext = {
|
|
1183
|
-
verify: (token) => {
|
|
1184
|
-
return fastifyWithJwt.jwt.verify(token);
|
|
1185
|
-
},
|
|
1186
|
-
sign: (payload, options) => {
|
|
1187
|
-
return fastifyWithJwt.jwt.sign(payload, options);
|
|
1188
|
-
},
|
|
1189
|
-
decode: (token) => {
|
|
1190
|
-
try {
|
|
1191
|
-
return fastifyWithJwt.jwt.decode(token);
|
|
1192
|
-
} catch {
|
|
1193
|
-
return null;
|
|
1194
|
-
}
|
|
1195
|
-
}
|
|
1196
|
-
};
|
|
1197
|
-
fastify.log.info("Auth: JWT infrastructure enabled");
|
|
1198
|
-
}
|
|
1199
|
-
const authContext = {
|
|
1200
|
-
jwt: jwtContext,
|
|
1201
|
-
fastify
|
|
1202
|
-
};
|
|
1203
|
-
const authenticate = async (request, reply) => {
|
|
1204
|
-
try {
|
|
1205
|
-
let user = null;
|
|
1206
|
-
if (appAuthenticator) {
|
|
1207
|
-
user = await appAuthenticator(request, authContext);
|
|
1208
|
-
} else if (jwtContext) {
|
|
1209
|
-
const token = extractBearerToken(request);
|
|
1210
|
-
if (token) {
|
|
1211
|
-
const decoded = jwtContext.verify(token);
|
|
1212
|
-
user = decoded;
|
|
1213
|
-
}
|
|
1214
|
-
} else {
|
|
1215
|
-
throw new Error(
|
|
1216
|
-
"No authenticator configured. Provide auth.authenticate function or auth.jwt.secret."
|
|
1217
|
-
);
|
|
1218
|
-
}
|
|
1219
|
-
if (!user) {
|
|
1220
|
-
throw new Error("Authentication required");
|
|
1221
|
-
}
|
|
1222
|
-
request[userProperty] = user;
|
|
1223
|
-
} catch (err) {
|
|
1224
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
1225
|
-
if (onFailure) {
|
|
1226
|
-
await onFailure(request, reply, error);
|
|
1227
|
-
return;
|
|
1228
|
-
}
|
|
1229
|
-
const message = process.env.NODE_ENV === "production" ? "Authentication required" : error.message;
|
|
1230
|
-
reply.code(401).send({
|
|
1231
|
-
success: false,
|
|
1232
|
-
error: "Unauthorized",
|
|
1233
|
-
message
|
|
1234
|
-
});
|
|
1235
|
-
}
|
|
1236
|
-
};
|
|
1237
|
-
const refreshSecret = jwtConfig?.refreshSecret ?? jwtConfig?.secret;
|
|
1238
|
-
const accessExpiresIn = jwtConfig?.expiresIn ?? "15m";
|
|
1239
|
-
const refreshExpiresIn = jwtConfig?.refreshExpiresIn ?? "7d";
|
|
1240
|
-
const issueTokens = (payload, options) => {
|
|
1241
|
-
if (!jwtContext) {
|
|
1242
|
-
throw new Error("JWT not configured. Provide auth.jwt.secret to use issueTokens.");
|
|
1243
|
-
}
|
|
1244
|
-
const accessTtl = options?.expiresIn ?? accessExpiresIn;
|
|
1245
|
-
const refreshTtl = options?.refreshExpiresIn ?? refreshExpiresIn;
|
|
1246
|
-
const accessToken = jwtContext.sign(payload, { expiresIn: accessTtl });
|
|
1247
|
-
const refreshPayload = payload.id ? { id: payload.id, type: "refresh" } : payload._id ? { id: payload._id, type: "refresh" } : { ...payload, type: "refresh" };
|
|
1248
|
-
let refreshToken;
|
|
1249
|
-
if (refreshSecret) {
|
|
1250
|
-
const fastifyWithJwt = fastify;
|
|
1251
|
-
refreshToken = fastifyWithJwt.jwt.sign(refreshPayload, {
|
|
1252
|
-
expiresIn: refreshTtl,
|
|
1253
|
-
// Use refresh secret if different from main secret
|
|
1254
|
-
...refreshSecret !== jwtConfig?.secret ? { secret: refreshSecret } : {}
|
|
1255
|
-
});
|
|
1256
|
-
}
|
|
1257
|
-
return {
|
|
1258
|
-
accessToken,
|
|
1259
|
-
refreshToken,
|
|
1260
|
-
expiresIn: parseExpiresIn(accessTtl, 900),
|
|
1261
|
-
refreshExpiresIn: refreshToken ? parseExpiresIn(refreshTtl, 604800) : void 0,
|
|
1262
|
-
tokenType: "Bearer"
|
|
1263
|
-
};
|
|
1264
|
-
};
|
|
1265
|
-
const verifyRefreshToken = (token) => {
|
|
1266
|
-
if (!jwtContext) {
|
|
1267
|
-
throw new Error("JWT not configured. Provide auth.jwt.secret to use verifyRefreshToken.");
|
|
1268
|
-
}
|
|
1269
|
-
const fastifyWithJwt = fastify;
|
|
1270
|
-
return fastifyWithJwt.jwt.verify(token, {
|
|
1271
|
-
...refreshSecret !== jwtConfig?.secret ? { secret: refreshSecret } : {}
|
|
1272
|
-
});
|
|
1273
|
-
};
|
|
1274
|
-
const authorize = (...allowedRoles) => {
|
|
1275
|
-
return async (request, reply) => {
|
|
1276
|
-
const user = request[userProperty];
|
|
1277
|
-
if (!user) {
|
|
1278
|
-
reply.code(401).send({
|
|
1279
|
-
success: false,
|
|
1280
|
-
error: "Unauthorized",
|
|
1281
|
-
message: "No user context"
|
|
1282
|
-
});
|
|
1283
|
-
return;
|
|
1284
|
-
}
|
|
1285
|
-
const userRoles = user.roles ?? [];
|
|
1286
|
-
if (allowedRoles.length === 1 && allowedRoles[0] === "*") {
|
|
1287
|
-
return;
|
|
1288
|
-
}
|
|
1289
|
-
const hasRole = allowedRoles.some((role) => userRoles.includes(role));
|
|
1290
|
-
if (!hasRole) {
|
|
1291
|
-
reply.code(403).send({
|
|
1292
|
-
success: false,
|
|
1293
|
-
error: "Forbidden",
|
|
1294
|
-
message: `Requires one of: ${allowedRoles.join(", ")}`
|
|
1295
|
-
});
|
|
1296
|
-
return;
|
|
1297
|
-
}
|
|
1298
|
-
};
|
|
1299
|
-
};
|
|
1300
|
-
const authHelpers = {
|
|
1301
|
-
jwt: jwtContext,
|
|
1302
|
-
issueTokens,
|
|
1303
|
-
verifyRefreshToken
|
|
1304
|
-
};
|
|
1305
|
-
fastify.decorate("authenticate", authenticate);
|
|
1306
|
-
fastify.decorate("authorize", authorize);
|
|
1307
|
-
fastify.decorate("auth", authHelpers);
|
|
1308
|
-
fastify.log.info(
|
|
1309
|
-
`Auth: Plugin registered (jwt=${!!jwtContext}, customAuth=${!!appAuthenticator})`
|
|
1310
|
-
);
|
|
1311
|
-
};
|
|
1312
|
-
authPlugin_default = fp(authPlugin, {
|
|
1313
|
-
name: "arc-auth",
|
|
1314
|
-
fastify: "5.x"
|
|
1315
|
-
});
|
|
1316
|
-
}
|
|
1317
|
-
});
|
|
1318
|
-
|
|
1319
|
-
// src/auth/index.ts
|
|
1320
|
-
var auth_exports = {};
|
|
1321
|
-
__export(auth_exports, {
|
|
1322
|
-
authPlugin: () => authPlugin_default,
|
|
1323
|
-
authPluginFn: () => authPlugin
|
|
1324
|
-
});
|
|
1325
|
-
var init_auth = __esm({
|
|
1326
|
-
"src/auth/index.ts"() {
|
|
1327
|
-
init_authPlugin();
|
|
1328
|
-
}
|
|
1329
|
-
});
|
|
1330
|
-
|
|
1331
|
-
// src/factory/presets.ts
|
|
1332
|
-
var productionPreset = {
|
|
1333
|
-
// Raw JSON logs for production (log aggregators like Datadog, CloudWatch, etc.)
|
|
1334
|
-
logger: {
|
|
1335
|
-
level: "info"
|
|
1336
|
-
},
|
|
1337
|
-
trustProxy: true,
|
|
1338
|
-
// Security
|
|
1339
|
-
helmet: {
|
|
1340
|
-
contentSecurityPolicy: {
|
|
1341
|
-
directives: {
|
|
1342
|
-
defaultSrc: ["'self'"],
|
|
1343
|
-
styleSrc: ["'self'", "'unsafe-inline'"],
|
|
1344
|
-
scriptSrc: ["'self'"],
|
|
1345
|
-
imgSrc: ["'self'", "data:", "https:"]
|
|
1346
|
-
}
|
|
1347
|
-
}
|
|
1348
|
-
},
|
|
1349
|
-
// CORS - must be explicitly configured
|
|
1350
|
-
cors: {
|
|
1351
|
-
origin: false,
|
|
1352
|
-
// Disabled by default in production
|
|
1353
|
-
credentials: true,
|
|
1354
|
-
methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
|
|
1355
|
-
allowedHeaders: ["Content-Type", "Authorization", "Accept"]
|
|
1356
|
-
},
|
|
1357
|
-
// Rate limiting - strict
|
|
1358
|
-
rateLimit: {
|
|
1359
|
-
max: 100,
|
|
1360
|
-
timeWindow: "1 minute"
|
|
1361
|
-
},
|
|
1362
|
-
// Note: Compression not included (use proxy/CDN instead)
|
|
1363
|
-
// Under pressure - health monitoring
|
|
1364
|
-
underPressure: {
|
|
1365
|
-
exposeStatusRoute: true,
|
|
1366
|
-
maxEventLoopDelay: 1e3,
|
|
1367
|
-
maxHeapUsedBytes: 1024 * 1024 * 1024,
|
|
1368
|
-
// 1GB
|
|
1369
|
-
maxRssBytes: 1024 * 1024 * 1024
|
|
1370
|
-
// 1GB
|
|
1371
|
-
}
|
|
1372
|
-
};
|
|
1373
|
-
var developmentPreset = {
|
|
1374
|
-
logger: {
|
|
1375
|
-
level: "debug",
|
|
1376
|
-
transport: {
|
|
1377
|
-
target: "pino-pretty",
|
|
1378
|
-
options: {
|
|
1379
|
-
colorize: true,
|
|
1380
|
-
translateTime: "SYS:HH:MM:ss",
|
|
1381
|
-
ignore: "pid,hostname"
|
|
1382
|
-
}
|
|
1383
|
-
}
|
|
1384
|
-
},
|
|
1385
|
-
trustProxy: true,
|
|
1386
|
-
// Security - relaxed for development
|
|
1387
|
-
helmet: {
|
|
1388
|
-
contentSecurityPolicy: false
|
|
1389
|
-
// Disable CSP in dev
|
|
1390
|
-
},
|
|
1391
|
-
// CORS - allow all origins in development
|
|
1392
|
-
cors: {
|
|
1393
|
-
origin: true,
|
|
1394
|
-
// Allow all origins
|
|
1395
|
-
credentials: true,
|
|
1396
|
-
methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
|
|
1397
|
-
allowedHeaders: ["Content-Type", "Authorization", "Accept"]
|
|
1398
|
-
},
|
|
1399
|
-
// Rate limiting - very relaxed
|
|
1400
|
-
rateLimit: {
|
|
1401
|
-
max: 1e3,
|
|
1402
|
-
timeWindow: "1 minute"
|
|
1403
|
-
},
|
|
1404
|
-
// Note: Compression not included (use proxy/CDN instead)
|
|
1405
|
-
// Under pressure - relaxed
|
|
1406
|
-
underPressure: {
|
|
1407
|
-
exposeStatusRoute: true,
|
|
1408
|
-
maxEventLoopDelay: 5e3
|
|
1409
|
-
}
|
|
1410
|
-
};
|
|
1411
|
-
var testingPreset = {
|
|
1412
|
-
logger: false,
|
|
1413
|
-
// Disable logging in tests
|
|
1414
|
-
trustProxy: false,
|
|
1415
|
-
// Security - disabled for tests
|
|
1416
|
-
helmet: false,
|
|
1417
|
-
cors: false,
|
|
1418
|
-
rateLimit: false,
|
|
1419
|
-
underPressure: false,
|
|
1420
|
-
// Sensible plugins still enabled
|
|
1421
|
-
sensible: true,
|
|
1422
|
-
multipart: {
|
|
1423
|
-
limits: {
|
|
1424
|
-
fileSize: 1024 * 1024,
|
|
1425
|
-
// 1MB
|
|
1426
|
-
files: 5
|
|
1427
|
-
}
|
|
1428
|
-
}
|
|
1429
|
-
};
|
|
1430
|
-
function getPreset(name) {
|
|
1431
|
-
switch (name) {
|
|
1432
|
-
case "production":
|
|
1433
|
-
return productionPreset;
|
|
1434
|
-
case "development":
|
|
1435
|
-
return developmentPreset;
|
|
1436
|
-
case "testing":
|
|
1437
|
-
return testingPreset;
|
|
1438
|
-
default:
|
|
1439
|
-
throw new Error(`Unknown preset: ${name}`);
|
|
1440
|
-
}
|
|
1441
|
-
}
|
|
1442
|
-
|
|
1443
|
-
// src/factory/createApp.ts
|
|
1444
|
-
var PLUGIN_PACKAGES = {
|
|
1445
|
-
cors: "@fastify/cors",
|
|
1446
|
-
helmet: "@fastify/helmet",
|
|
1447
|
-
rateLimit: "@fastify/rate-limit",
|
|
1448
|
-
underPressure: "@fastify/under-pressure",
|
|
1449
|
-
sensible: "@fastify/sensible",
|
|
1450
|
-
multipart: "@fastify/multipart",
|
|
1451
|
-
rawBody: "fastify-raw-body"
|
|
1452
|
-
};
|
|
1453
|
-
var OPTIONAL_PLUGINS = /* @__PURE__ */ new Set(["multipart", "rawBody"]);
|
|
1454
|
-
async function loadPlugin(name, logger) {
|
|
1455
|
-
const packageName = PLUGIN_PACKAGES[name];
|
|
1456
|
-
if (!packageName) {
|
|
1457
|
-
throw new Error(`Unknown plugin: ${name}`);
|
|
1458
|
-
}
|
|
1459
|
-
try {
|
|
1460
|
-
switch (name) {
|
|
1461
|
-
case "cors":
|
|
1462
|
-
return (await import('@fastify/cors')).default;
|
|
1463
|
-
case "helmet":
|
|
1464
|
-
return (await import('@fastify/helmet')).default;
|
|
1465
|
-
case "rateLimit":
|
|
1466
|
-
return (await import('@fastify/rate-limit')).default;
|
|
1467
|
-
case "underPressure":
|
|
1468
|
-
return (await import('@fastify/under-pressure')).default;
|
|
1469
|
-
case "sensible":
|
|
1470
|
-
return (await import('@fastify/sensible')).default;
|
|
1471
|
-
case "multipart":
|
|
1472
|
-
return (await import('@fastify/multipart')).default;
|
|
1473
|
-
case "rawBody":
|
|
1474
|
-
return (await import('fastify-raw-body')).default;
|
|
1475
|
-
default:
|
|
1476
|
-
throw new Error(`Unknown plugin: ${name}`);
|
|
1477
|
-
}
|
|
1478
|
-
} catch (error) {
|
|
1479
|
-
const err = error;
|
|
1480
|
-
const isModuleNotFound = err.message.includes("Cannot find module") || err.message.includes("Cannot find package") || err.message.includes("MODULE_NOT_FOUND") || err.message.includes("Could not resolve");
|
|
1481
|
-
if (isModuleNotFound && OPTIONAL_PLUGINS.has(name)) {
|
|
1482
|
-
logger?.warn(`ℹ️ Optional plugin '${name}' skipped (${packageName} not installed)`);
|
|
1483
|
-
return null;
|
|
1484
|
-
}
|
|
1485
|
-
if (isModuleNotFound) {
|
|
1486
|
-
throw new Error(
|
|
1487
|
-
`Plugin '${name}' requires package '${packageName}' which is not installed.
|
|
1488
|
-
Install it with: npm install ${packageName}
|
|
1489
|
-
Or disable this plugin by setting ${name}: false in createApp options.`
|
|
1490
|
-
);
|
|
1491
|
-
}
|
|
1492
|
-
throw new Error(`Failed to load plugin '${name}': ${err.message}`);
|
|
1493
|
-
}
|
|
1494
|
-
}
|
|
1495
|
-
async function createApp(options) {
|
|
1496
|
-
const authConfig = options.auth;
|
|
1497
|
-
const isAuthDisabled = authConfig === false;
|
|
1498
|
-
const hasCustomPlugin = typeof authConfig === "object" && "plugin" in authConfig && authConfig.plugin;
|
|
1499
|
-
const hasCustomAuthenticator = typeof authConfig === "object" && "authenticate" in authConfig;
|
|
1500
|
-
const jwtSecret = typeof authConfig === "object" && "jwt" in authConfig ? authConfig.jwt?.secret : void 0;
|
|
1501
|
-
if (!isAuthDisabled && !hasCustomPlugin && !jwtSecret && !hasCustomAuthenticator) {
|
|
1502
|
-
throw new Error(
|
|
1503
|
-
"createApp: JWT secret required when Arc auth is enabled.\nProvide auth.jwt.secret, auth.authenticate, or set auth: false to disable.\nExample: auth: { jwt: { secret: process.env.JWT_SECRET } }"
|
|
1504
|
-
);
|
|
1505
|
-
}
|
|
1506
|
-
const presetConfig = options.preset ? getPreset(options.preset) : {};
|
|
1507
|
-
const config = { ...presetConfig, ...options };
|
|
1508
|
-
const fastify = Fastify({
|
|
1509
|
-
logger: config.logger ?? true,
|
|
1510
|
-
trustProxy: config.trustProxy ?? false,
|
|
1511
|
-
// Use qs parser to support nested bracket notation in query strings
|
|
1512
|
-
// e.g., ?populate[author][select]=name,email → { populate: { author: { select: 'name,email' } } }
|
|
1513
|
-
// This is required for MongoKit's advanced populate options to work
|
|
1514
|
-
querystringParser: (str) => qs.parse(str),
|
|
1515
|
-
ajv: {
|
|
1516
|
-
customOptions: {
|
|
1517
|
-
coerceTypes: true,
|
|
1518
|
-
useDefaults: true,
|
|
1519
|
-
removeAdditional: false
|
|
1520
|
-
}
|
|
1521
|
-
}
|
|
1522
|
-
});
|
|
1523
|
-
if (config.helmet !== false) {
|
|
1524
|
-
const helmet = await loadPlugin("helmet");
|
|
1525
|
-
await fastify.register(helmet, config.helmet ?? {});
|
|
1526
|
-
fastify.log.info("✅ Helmet (security headers) enabled");
|
|
1527
|
-
} else {
|
|
1528
|
-
fastify.log.warn("⚠️ Helmet disabled - security headers not applied");
|
|
1529
|
-
}
|
|
1530
|
-
if (config.cors !== false) {
|
|
1531
|
-
const cors = await loadPlugin("cors");
|
|
1532
|
-
const corsOptions = config.cors ?? {};
|
|
1533
|
-
if (config.preset === "production" && (!corsOptions || !("origin" in corsOptions))) {
|
|
1534
|
-
throw new Error(
|
|
1535
|
-
"CORS origin must be explicitly configured in production.\nSet cors.origin to allowed domains or set cors: false to disable.\nExample: cors: { origin: ['https://yourdomain.com'] }\nDocs: https://github.com/classytic/arc#security"
|
|
1536
|
-
);
|
|
1537
|
-
}
|
|
1538
|
-
await fastify.register(cors, corsOptions);
|
|
1539
|
-
fastify.log.info("✅ CORS enabled");
|
|
1540
|
-
} else {
|
|
1541
|
-
fastify.log.warn("⚠️ CORS disabled");
|
|
1542
|
-
}
|
|
1543
|
-
if (config.rateLimit !== false) {
|
|
1544
|
-
const rateLimit = await loadPlugin("rateLimit");
|
|
1545
|
-
await fastify.register(rateLimit, config.rateLimit ?? { max: 100, timeWindow: "1 minute" });
|
|
1546
|
-
fastify.log.info("✅ Rate limiting enabled");
|
|
1547
|
-
} else {
|
|
1548
|
-
fastify.log.warn("⚠️ Rate limiting disabled");
|
|
1549
|
-
}
|
|
1550
|
-
if (config.underPressure !== false) {
|
|
1551
|
-
const underPressure = await loadPlugin("underPressure");
|
|
1552
|
-
await fastify.register(underPressure, config.underPressure ?? { exposeStatusRoute: true });
|
|
1553
|
-
fastify.log.info("✅ Health monitoring (under-pressure) enabled");
|
|
1554
|
-
} else {
|
|
1555
|
-
fastify.log.info("ℹ️ Health monitoring disabled");
|
|
1556
|
-
}
|
|
1557
|
-
if (config.sensible !== false) {
|
|
1558
|
-
const sensible = await loadPlugin("sensible");
|
|
1559
|
-
await fastify.register(sensible);
|
|
1560
|
-
fastify.log.info("✅ Sensible (HTTP helpers) enabled");
|
|
1561
|
-
}
|
|
1562
|
-
if (config.multipart !== false) {
|
|
1563
|
-
const multipart = await loadPlugin("multipart", fastify.log);
|
|
1564
|
-
if (multipart) {
|
|
1565
|
-
const multipartDefaults = {
|
|
1566
|
-
limits: {
|
|
1567
|
-
fileSize: 10 * 1024 * 1024,
|
|
1568
|
-
// 10MB
|
|
1569
|
-
files: 10
|
|
1570
|
-
}
|
|
1571
|
-
};
|
|
1572
|
-
await fastify.register(multipart, { ...multipartDefaults, ...config.multipart });
|
|
1573
|
-
fastify.log.info("✅ Multipart (file uploads) enabled");
|
|
1574
|
-
}
|
|
1575
|
-
}
|
|
1576
|
-
if (config.rawBody !== false) {
|
|
1577
|
-
const rawBody = await loadPlugin("rawBody", fastify.log);
|
|
1578
|
-
if (rawBody) {
|
|
1579
|
-
const rawBodyDefaults = {
|
|
1580
|
-
field: "rawBody",
|
|
1581
|
-
global: false,
|
|
1582
|
-
encoding: "utf8",
|
|
1583
|
-
runFirst: true
|
|
1584
|
-
};
|
|
1585
|
-
await fastify.register(rawBody, { ...rawBodyDefaults, ...config.rawBody });
|
|
1586
|
-
fastify.log.info("✅ Raw body parsing enabled");
|
|
1587
|
-
}
|
|
1588
|
-
}
|
|
1589
|
-
const { arcCorePlugin: arcCorePlugin2 } = await Promise.resolve().then(() => (init_plugins(), plugins_exports));
|
|
1590
|
-
await fastify.register(arcCorePlugin2, {
|
|
1591
|
-
emitEvents: config.arcPlugins?.emitEvents !== false
|
|
1592
|
-
});
|
|
1593
|
-
if (config.arcPlugins?.requestId !== false) {
|
|
1594
|
-
const { requestIdPlugin: requestIdPlugin2 } = await Promise.resolve().then(() => (init_plugins(), plugins_exports));
|
|
1595
|
-
await fastify.register(requestIdPlugin2);
|
|
1596
|
-
fastify.log.info("✅ Arc requestId plugin enabled");
|
|
1597
|
-
}
|
|
1598
|
-
if (config.arcPlugins?.health !== false) {
|
|
1599
|
-
const { healthPlugin: healthPlugin2 } = await Promise.resolve().then(() => (init_plugins(), plugins_exports));
|
|
1600
|
-
await fastify.register(healthPlugin2);
|
|
1601
|
-
fastify.log.info("✅ Arc health plugin enabled");
|
|
1602
|
-
}
|
|
1603
|
-
if (config.arcPlugins?.gracefulShutdown !== false) {
|
|
1604
|
-
const { gracefulShutdownPlugin: gracefulShutdownPlugin2 } = await Promise.resolve().then(() => (init_plugins(), plugins_exports));
|
|
1605
|
-
await fastify.register(gracefulShutdownPlugin2);
|
|
1606
|
-
fastify.log.info("✅ Arc gracefulShutdown plugin enabled");
|
|
1607
|
-
}
|
|
1608
|
-
if (!isAuthDisabled) {
|
|
1609
|
-
if (hasCustomPlugin) {
|
|
1610
|
-
const pluginFn = authConfig.plugin;
|
|
1611
|
-
await pluginFn(fastify);
|
|
1612
|
-
fastify.log.info("✅ Custom authentication plugin enabled");
|
|
1613
|
-
} else {
|
|
1614
|
-
const { authPlugin: authPlugin2 } = await Promise.resolve().then(() => (init_auth(), auth_exports));
|
|
1615
|
-
const { plugin: _, ...authOpts } = typeof authConfig === "object" ? authConfig : {};
|
|
1616
|
-
await fastify.register(authPlugin2, authOpts);
|
|
1617
|
-
fastify.log.info("✅ Arc authentication plugin enabled");
|
|
1618
|
-
}
|
|
1619
|
-
} else {
|
|
1620
|
-
fastify.log.info("ℹ️ Authentication disabled");
|
|
1621
|
-
}
|
|
1622
|
-
if (config.plugins) {
|
|
1623
|
-
await config.plugins(fastify);
|
|
1624
|
-
fastify.log.info("✅ Custom plugins registered");
|
|
1625
|
-
}
|
|
1626
|
-
fastify.log.info(
|
|
1627
|
-
`🚀 Arc application created successfully (preset: ${config.preset ?? "custom"}, security: helmet=${config.helmet !== false}, cors=${config.cors !== false}, rateLimit=${config.rateLimit !== false})`
|
|
1628
|
-
);
|
|
1629
|
-
return fastify;
|
|
1630
|
-
}
|
|
1631
|
-
var ArcFactory = {
|
|
1632
|
-
/**
|
|
1633
|
-
* Create production app with strict security
|
|
1634
|
-
*/
|
|
1635
|
-
async production(options) {
|
|
1636
|
-
return createApp({ ...options, preset: "production" });
|
|
1637
|
-
},
|
|
1638
|
-
/**
|
|
1639
|
-
* Create development app with relaxed security
|
|
1640
|
-
*/
|
|
1641
|
-
async development(options) {
|
|
1642
|
-
return createApp({ ...options, preset: "development" });
|
|
1643
|
-
},
|
|
1644
|
-
/**
|
|
1645
|
-
* Create testing app with minimal setup
|
|
1646
|
-
*/
|
|
1647
|
-
async testing(options) {
|
|
1648
|
-
return createApp({ ...options, preset: "testing" });
|
|
1649
|
-
}
|
|
1650
|
-
};
|
|
1651
|
-
|
|
1652
|
-
export { ArcFactory, createApp, developmentPreset, getPreset, productionPreset, testingPreset };
|