@classytic/arc 2.11.4 → 2.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -12
- package/dist/{BaseController-swXruJ2_.mjs → BaseController-DX_T-bDB.mjs} +388 -423
- package/dist/EventTransport-CT_52aWU.d.mts +34 -0
- package/dist/EventTransport-DLWoUMHy.mjs +103 -0
- package/dist/{ResourceRegistry-DkAeAuTX.mjs → ResourceRegistry-CTERg_2x.mjs} +139 -66
- package/dist/audit/index.d.mts +2 -2
- package/dist/audit/index.mjs +1 -1
- package/dist/auth/audit.d.mts +199 -0
- package/dist/auth/audit.mjs +288 -0
- package/dist/auth/index.d.mts +3 -3
- package/dist/auth/index.mjs +117 -191
- package/dist/{betterAuthOpenApi-DwxtK3uG.mjs → betterAuthOpenApi--M_i87dQ.mjs} +1 -1
- package/dist/buildHandler-olo-gt94.mjs +610 -0
- package/dist/cache/index.mjs +3 -3
- package/dist/cli/commands/describe.d.mts +89 -13
- package/dist/cli/commands/describe.mjs +56 -2
- package/dist/cli/commands/docs.mjs +2 -2
- package/dist/cli/commands/generate.mjs +147 -48
- package/dist/cli/commands/init.d.mts +13 -0
- package/dist/cli/commands/init.mjs +130 -87
- package/dist/cli/commands/introspect.mjs +8 -1
- package/dist/context/index.mjs +1 -1
- package/dist/core/index.d.mts +3 -3
- package/dist/core/index.mjs +5 -5
- package/dist/core-DECn6zaU.mjs +1399 -0
- package/dist/{createActionRouter-CIKOcNA7.mjs → createActionRouter-CBxLLbn3.mjs} +7 -20
- package/dist/createAggregationRouter-CRIBv4sC.mjs +114 -0
- package/dist/{createApp-C9bRrqlX.mjs → createApp-XX2-N0Yd.mjs} +28 -22
- package/dist/{defineEvent-D1Ky9M1D.mjs → defineEvent-D5h7EvAx.mjs} +1 -1
- package/dist/docs/index.d.mts +24 -11
- package/dist/docs/index.mjs +2 -2
- package/dist/{elevation-DOFoxoDs.mjs → elevation-DgoeTyfX.mjs} +1 -1
- package/dist/errorHandler-Bk-AGhkU.mjs +174 -0
- package/dist/errorHandler-DFr45ZG4.d.mts +45 -0
- package/dist/errors-j4aJm1Wg.mjs +184 -0
- package/dist/{eventPlugin-Cts2-Tfj.mjs → eventPlugin-CaKTYkYM.mjs} +28 -4
- package/dist/{eventPlugin-DDJoNEPL.d.mts → eventPlugin-qXpqTebY.d.mts} +24 -1
- package/dist/events/index.d.mts +6 -6
- package/dist/events/index.mjs +11 -35
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis.d.mts +1 -1
- package/dist/factory/index.d.mts +2 -2
- package/dist/factory/index.mjs +2 -2
- package/dist/{fields-BRjxOAFp.d.mts → fields-COhcH3fk.d.mts} +23 -2
- package/dist/hooks/index.d.mts +1 -1
- package/dist/hooks/index.mjs +1 -1
- package/dist/idempotency/index.d.mts +1 -1
- package/dist/idempotency/index.mjs +1 -20
- package/dist/idempotency/redis.mjs +1 -1
- package/dist/{index-rHjXmJar.d.mts → index-BTqLEvhu.d.mts} +163 -3
- package/dist/{index-CXXRbnf8.d.mts → index-BtW7qYwa.d.mts} +660 -326
- package/dist/{index-m8mOOlFW.d.mts → index-Ds61mrJE.d.mts} +50 -4
- package/dist/{index-D9t1KNaB.d.mts → index-Dz5IKsrE.d.mts} +360 -219
- package/dist/index.d.mts +6 -7
- package/dist/index.mjs +9 -10
- package/dist/integrations/event-gateway.d.mts +1 -1
- package/dist/integrations/event-gateway.mjs +1 -1
- package/dist/integrations/index.d.mts +1 -1
- package/dist/integrations/mcp/index.d.mts +2 -2
- package/dist/integrations/mcp/index.mjs +1 -1
- package/dist/integrations/mcp/testing.d.mts +1 -1
- package/dist/integrations/mcp/testing.mjs +1 -1
- package/dist/integrations/streamline.d.mts +60 -11
- package/dist/integrations/streamline.mjs +75 -85
- package/dist/integrations/websocket.mjs +2 -8
- package/dist/middleware/index.d.mts +1 -1
- package/dist/middleware/index.mjs +2 -2
- package/dist/migrations/index.d.mts +23 -3
- package/dist/migrations/index.mjs +0 -7
- package/dist/{multipartBody-CvTR1Un6.mjs → multipartBody-BOvVSVCD.mjs} +11 -8
- package/dist/openapi-noXno2CV.mjs +968 -0
- package/dist/org/index.d.mts +2 -2
- package/dist/org/index.mjs +1 -1
- package/dist/permissions/index.d.mts +3 -3
- package/dist/permissions/index.mjs +3 -3
- package/dist/{permissions-gd_aUWrR.mjs → permissions-ohQyv50e.mjs} +404 -176
- package/dist/{pipe-DVoIheVC.mjs → pipe-Zr0KXjQe.mjs} +1 -1
- package/dist/pipeline/index.d.mts +1 -1
- package/dist/pipeline/index.mjs +1 -1
- package/dist/plugins/index.d.mts +16 -31
- package/dist/plugins/index.mjs +33 -13
- package/dist/plugins/response-cache.mjs +1 -1
- package/dist/plugins/tracing-entry.mjs +1 -1
- package/dist/presets/filesUpload.d.mts +4 -4
- package/dist/presets/filesUpload.mjs +6 -9
- package/dist/presets/index.d.mts +1 -1
- package/dist/presets/index.mjs +1 -1
- package/dist/presets/multiTenant.d.mts +1 -1
- package/dist/presets/multiTenant.mjs +2 -2
- package/dist/presets/search.d.mts +2 -2
- package/dist/presets/search.mjs +6 -8
- package/dist/{presets-Z7P5w4gF.mjs → presets-BbkjdPeH.mjs} +6 -28
- package/dist/{queryCachePlugin-Bq6bO6vc.mjs → queryCachePlugin-m1XsgAIJ.mjs} +3 -3
- package/dist/{redis-stream-xTGxB2bm.d.mts → redis-stream-D6HzR1Z_.d.mts} +1 -1
- package/dist/registry/index.d.mts +1 -1
- package/dist/registry/index.mjs +2 -2
- package/dist/{replyHelpers-ByllIXXV.mjs → replyHelpers-CK-FNO8E.mjs} +3 -21
- package/dist/{resourceToTools-CxNmI6xF.mjs → resourceToTools-DLL32us3.mjs} +224 -71
- package/dist/{routerShared-BqLRb5l7.mjs → routerShared-DrOa-26E.mjs} +41 -36
- package/dist/{schemaIR-Dy2p4MxS.mjs → schemaIR-lYhC2gE5.mjs} +1 -1
- package/dist/schemas/index.d.mts +100 -30
- package/dist/schemas/index.mjs +86 -29
- package/dist/scim/index.d.mts +264 -0
- package/dist/scim/index.mjs +963 -0
- package/dist/scope/index.d.mts +3 -3
- package/dist/scope/index.mjs +4 -4
- package/dist/{sse-V7aXc3bW.mjs → sse-Bz-5ZeTt.mjs} +1 -1
- package/dist/{store-helpers-Cp4uKC1U.mjs → store-helpers-BkIN9-vu.mjs} +1 -1
- package/dist/testing/index.d.mts +2 -8
- package/dist/testing/index.mjs +16 -24
- package/dist/types/index.d.mts +4 -4
- package/dist/{types-D7KpfiL1.d.mts → types-BvqwCCSx.d.mts} +73 -25
- package/dist/{types-DDyTPc6y.d.mts → types-CTYvcwHe.d.mts} +195 -1
- package/dist/{types-AOD8fxIw.mjs → types-C_s5moIu.mjs} +117 -1
- package/dist/{types-BQ9TJQNy.d.mts → types-DQHFc8PM.d.mts} +1 -1
- package/dist/utils/index.d.mts +2 -2
- package/dist/utils/index.mjs +5 -5
- package/dist/{utils-CcYTj09l.mjs → utils-_h9B3c57.mjs} +1269 -1334
- package/dist/{versioning-DsglKfM_.d.mts → versioning-DTTvc80y.d.mts} +1 -1
- package/package.json +24 -34
- package/skills/arc/SKILL.md +147 -51
- package/skills/arc/references/agent-auth.md +238 -0
- package/skills/arc/references/api-reference.md +187 -0
- package/skills/arc/references/auth.md +354 -7
- package/skills/arc/references/enterprise-auth.md +94 -0
- package/skills/arc/references/events.md +8 -6
- package/skills/arc/references/mcp.md +2 -2
- package/skills/arc/references/multi-tenancy.md +11 -2
- package/skills/arc/references/production.md +10 -9
- package/skills/arc/references/scim.md +247 -0
- package/skills/arc/references/testing.md +1 -1
- package/skills/arc-code-review/SKILL.md +141 -0
- package/skills/arc-code-review/references/anti-patterns.md +911 -0
- package/skills/arc-code-review/references/arc-cheatsheet.md +380 -0
- package/skills/arc-code-review/references/migration-recipes.md +700 -0
- package/skills/arc-code-review/references/mongokit-migration.md +386 -0
- package/skills/arc-code-review/references/scaffolding.md +230 -0
- package/skills/arc-code-review/references/severity.md +127 -0
- package/dist/EventTransport-BFQjw9pB.mjs +0 -133
- package/dist/EventTransport-CYNUXdCJ.d.mts +0 -293
- package/dist/adapters/index.d.mts +0 -3
- package/dist/adapters/index.mjs +0 -2
- package/dist/adapters-DUUiiimH.mjs +0 -964
- package/dist/auth/mongoose.d.mts +0 -191
- package/dist/auth/mongoose.mjs +0 -73
- package/dist/core-CbcQRIch.mjs +0 -1054
- package/dist/errorHandler-BQm8ZxTK.mjs +0 -173
- package/dist/errorHandler-DEWmGWPz.d.mts +0 -114
- package/dist/errors-D5c-5BJL.mjs +0 -232
- package/dist/index-Rg8axYPz.d.mts +0 -370
- package/dist/openapi-D7G1V7ex.mjs +0 -557
- /package/dist/{HookSystem-CGsMd6oK.mjs → HookSystem-Iiebom92.mjs} +0 -0
- /package/dist/{actionPermissions-sUUKDhtP.mjs → actionPermissions-CyUkQu6O.mjs} +0 -0
- /package/dist/{caching-CheW3m-S.mjs → caching-SM8gghN6.mjs} +0 -0
- /package/dist/{constants-BhY1OHoH.mjs → constants-Cxde4rpC.mjs} +0 -0
- /package/dist/{elevation-BQQXZ_VR.d.mts → elevation-BXOWoGCF.d.mts} +0 -0
- /package/dist/{keys-CARyUjiR.mjs → keys-CGcCbNyu.mjs} +0 -0
- /package/dist/{loadResources-CPpkyKfM.mjs → loadResources-DBMQg_Aj.mjs} +0 -0
- /package/dist/{memory-DikHSvWa.mjs → memory-UBydS5ku.mjs} +0 -0
- /package/dist/{metrics-Csh4nsvv.mjs → metrics-Qnvwc-LQ.mjs} +0 -0
- /package/dist/{pluralize-CWP6MB39.mjs → pluralize-DQgqgifU.mjs} +0 -0
- /package/dist/{registry-D63ee7fl.mjs → registry-I-ogLgL9.mjs} +0 -0
- /package/dist/{requestContext-C5XeK3VA.mjs → requestContext-SSaaTgW8.mjs} +0 -0
- /package/dist/{schemaConverter-B0oKLuqI.mjs → schemaConverter-De34B1ZG.mjs} +0 -0
- /package/dist/{typeGuards-CcFZXgU7.mjs → typeGuards-BzkXkvVv.mjs} +0 -0
- /package/dist/{types-DV9WDfeg.mjs → types-D57iXYb8.mjs} +0 -0
- /package/dist/{versioning-CGPjkqAg.mjs → versioning-BUrT5aP4.mjs} +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { n as
|
|
2
|
+
import { f as createError } from "./errors-j4aJm1Wg.mjs";
|
|
3
|
+
import { a as buildAuthMiddlewareForPermissions, c as buildPreHandlerChain, f as resolveRouterPluginMw, l as buildRateLimitConfig, n as buildActionPipelineHandler, p as selectPluginMw, r as buildArcDecorator, t as buildActionPermissionMw, u as resolvePipelineSteps, y as sendControllerResponse } from "./routerShared-DrOa-26E.mjs";
|
|
4
|
+
import { n as schemaIRToJsonSchemaBranch, t as normalizeSchemaIR } from "./schemaIR-lYhC2gE5.mjs";
|
|
4
5
|
//#region src/core/createActionRouter.ts
|
|
5
6
|
var createActionRouter_exports = /* @__PURE__ */ __exportAll({
|
|
6
7
|
buildActionBodySchema: () => buildActionBodySchema,
|
|
@@ -69,23 +70,13 @@ function createActionRouter(fastify, config) {
|
|
|
69
70
|
const { action, ...data } = req.body;
|
|
70
71
|
const { id } = req.params;
|
|
71
72
|
const handler = wrappedHandlers.get(action);
|
|
72
|
-
if (!handler)
|
|
73
|
-
success: false,
|
|
74
|
-
status: 400,
|
|
75
|
-
error: `Invalid action '${action}'. Valid actions: ${actionEnum.join(", ")}`,
|
|
76
|
-
meta: { validActions: actionEnum }
|
|
77
|
-
}, req);
|
|
73
|
+
if (!handler) throw createError(400, `Invalid action '${action}'. Valid actions: ${actionEnum.join(", ")}`, { validActions: actionEnum });
|
|
78
74
|
try {
|
|
79
75
|
return sendControllerResponse(reply, await handler(id, data, req), req);
|
|
80
76
|
} catch (error) {
|
|
81
77
|
if (onError) {
|
|
82
78
|
const { statusCode, error: errorMsg, code } = onError(error, action, id);
|
|
83
|
-
|
|
84
|
-
success: false,
|
|
85
|
-
status: statusCode,
|
|
86
|
-
error: errorMsg,
|
|
87
|
-
...code ? { meta: { code } } : {}
|
|
88
|
-
}, req);
|
|
79
|
+
throw createError(statusCode, errorMsg, code ? { code } : void 0);
|
|
89
80
|
}
|
|
90
81
|
const err = error;
|
|
91
82
|
const statusCode = err.statusCode || err.status || 500;
|
|
@@ -95,12 +86,8 @@ function createActionRouter(fastify, config) {
|
|
|
95
86
|
action,
|
|
96
87
|
id
|
|
97
88
|
}, "Action handler error");
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
status: statusCode,
|
|
101
|
-
error: err.message || `Failed to execute '${action}' action`,
|
|
102
|
-
meta: { code: errorCode }
|
|
103
|
-
}, req);
|
|
89
|
+
if (error?.name === "ArcError" || error instanceof Error === false) throw error;
|
|
90
|
+
throw createError(statusCode, err.message || `Failed to execute '${action}' action`, { code: errorCode });
|
|
104
91
|
}
|
|
105
92
|
}
|
|
106
93
|
});
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { f as createError, l as UnauthorizedError, r as ForbiddenError } from "./errors-j4aJm1Wg.mjs";
|
|
2
|
+
import { c as buildPreHandlerChain, f as resolveRouterPluginMw, i as buildAuthMiddleware, l as buildRateLimitConfig, p as selectPluginMw, r as buildArcDecorator } from "./routerShared-DrOa-26E.mjs";
|
|
3
|
+
import { r as validateAggregations, t as buildAggregationHandler } from "./buildHandler-olo-gt94.mjs";
|
|
4
|
+
//#region src/core/aggregation/createAggregationRouter.ts
|
|
5
|
+
/**
|
|
6
|
+
* Register one Fastify route per aggregation. No-op when the map is
|
|
7
|
+
* empty — same convention `createActionRouter` follows.
|
|
8
|
+
*/
|
|
9
|
+
function createAggregationRouter(fastify, config) {
|
|
10
|
+
const { tag, resourceName, aggregations, fields: fieldPermissions, schemaOptions, permissions: resourcePermissions, routeGuards = [], repository, buildOptions } = config;
|
|
11
|
+
if (!aggregations || Object.keys(aggregations).length === 0) return;
|
|
12
|
+
const normalized = validateAggregations(resourceName, aggregations, schemaOptions);
|
|
13
|
+
const arcDecorator = buildArcDecorator({
|
|
14
|
+
resourceName,
|
|
15
|
+
schemaOptions,
|
|
16
|
+
permissions: resourcePermissions,
|
|
17
|
+
hooks: fastify.arc?.hooks,
|
|
18
|
+
events: fastify.events,
|
|
19
|
+
fields: fieldPermissions
|
|
20
|
+
});
|
|
21
|
+
for (const aggregation of normalized) registerOne(fastify, aggregation, {
|
|
22
|
+
tag,
|
|
23
|
+
arcDecorator,
|
|
24
|
+
routeGuards,
|
|
25
|
+
repository,
|
|
26
|
+
buildOptions
|
|
27
|
+
});
|
|
28
|
+
fastify.log?.debug?.({
|
|
29
|
+
aggregations: normalized.map((a) => a.name),
|
|
30
|
+
resourceName
|
|
31
|
+
}, `[createAggregationRouter] registered ${normalized.length} aggregation route(s)`);
|
|
32
|
+
}
|
|
33
|
+
function registerOne(fastify, normalized, ctx) {
|
|
34
|
+
const { tag, arcDecorator, routeGuards, repository, buildOptions } = ctx;
|
|
35
|
+
const { name } = normalized;
|
|
36
|
+
const config = normalized.base;
|
|
37
|
+
const authMw = buildAuthMiddleware(fastify, config.permissions);
|
|
38
|
+
const permissionFn = config.permissions;
|
|
39
|
+
const permissionMw = async (req, _reply) => {
|
|
40
|
+
const raw = await permissionFn(buildPermissionContextLite(req, normalized.name));
|
|
41
|
+
if (!normalizePermissionGranted(raw)) {
|
|
42
|
+
const status = req.user ? 403 : 401;
|
|
43
|
+
const reason = normalizePermissionReason(raw);
|
|
44
|
+
if (status === 401) throw new UnauthorizedError(reason ?? "Authentication required to access this aggregation.");
|
|
45
|
+
throw new ForbiddenError(reason ?? "You do not have permission to access this aggregation.");
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
const preHandler = buildPreHandlerChain({
|
|
49
|
+
arcDecorator,
|
|
50
|
+
authMw,
|
|
51
|
+
permissionMw,
|
|
52
|
+
pluginMw: selectPluginMw("GET", resolveRouterPluginMw(fastify, false)),
|
|
53
|
+
routeGuards
|
|
54
|
+
});
|
|
55
|
+
const rateLimitConfig = buildRateLimitConfig(config.rateLimit ? {
|
|
56
|
+
max: config.rateLimit.max,
|
|
57
|
+
timeWindow: `${config.rateLimit.windowMs}ms`
|
|
58
|
+
} : void 0);
|
|
59
|
+
const handler = buildAggregationHandler(normalized, {
|
|
60
|
+
repo: repository,
|
|
61
|
+
buildOptions
|
|
62
|
+
});
|
|
63
|
+
const routeSchema = {
|
|
64
|
+
tags: tag ? [tag] : void 0,
|
|
65
|
+
summary: config.summary ?? `Aggregation: ${name}`,
|
|
66
|
+
description: config.description ?? "Portable aggregation generated by arc. Filters from query string compose with the declaration's base filter + tenant scope."
|
|
67
|
+
};
|
|
68
|
+
fastify.route({
|
|
69
|
+
method: "GET",
|
|
70
|
+
url: `/aggregations/${name}`,
|
|
71
|
+
schema: routeSchema,
|
|
72
|
+
preHandler: preHandler.length > 0 ? preHandler : void 0,
|
|
73
|
+
...rateLimitConfig ? { config: rateLimitConfig } : {},
|
|
74
|
+
handler: async (req, reply) => {
|
|
75
|
+
try {
|
|
76
|
+
return await handler(req, reply);
|
|
77
|
+
} catch (err) {
|
|
78
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
79
|
+
req.log.error({
|
|
80
|
+
err,
|
|
81
|
+
aggregation: name
|
|
82
|
+
}, "Aggregation handler error");
|
|
83
|
+
throw createError(500, `Aggregation "${name}" failed: ${message}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Minimal `PermissionContext` for aggregation routes. Aggregations are
|
|
90
|
+
* read-shape so the action is `'list'` and `data` / `resourceId` stay
|
|
91
|
+
* undefined unless the URL includes them (none do today — `:name` is
|
|
92
|
+
* the only path param).
|
|
93
|
+
*/
|
|
94
|
+
function buildPermissionContextLite(req, aggregationName) {
|
|
95
|
+
const reqWithExtras = req;
|
|
96
|
+
return {
|
|
97
|
+
user: reqWithExtras.user ?? null,
|
|
98
|
+
request: req,
|
|
99
|
+
resource: reqWithExtras.arc?.resource ?? "aggregation",
|
|
100
|
+
action: `aggregation:${aggregationName}`
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/** PermissionCheck returns `boolean | PermissionResult`. Pull `granted`. */
|
|
104
|
+
function normalizePermissionGranted(raw) {
|
|
105
|
+
if (typeof raw === "boolean") return raw;
|
|
106
|
+
return raw.granted;
|
|
107
|
+
}
|
|
108
|
+
/** Pull `reason` when the check returned a structured `PermissionResult`. */
|
|
109
|
+
function normalizePermissionReason(raw) {
|
|
110
|
+
if (typeof raw === "boolean") return void 0;
|
|
111
|
+
return raw.reason;
|
|
112
|
+
}
|
|
113
|
+
//#endregion
|
|
114
|
+
export { createAggregationRouter };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { arcLog } from "./logger/index.mjs";
|
|
3
|
+
import { n as PUBLIC_SCOPE } from "./types-C_s5moIu.mjs";
|
|
3
4
|
import Fastify from "fastify";
|
|
4
5
|
import qs from "qs";
|
|
5
6
|
//#region src/factory/presets.ts
|
|
@@ -201,7 +202,7 @@ async function registerArcCore(fastify, config, trackPlugin) {
|
|
|
201
202
|
await fastify.register(arcCorePlugin, { emitEvents: config.arcPlugins?.emitEvents !== false });
|
|
202
203
|
trackPlugin("arc-core");
|
|
203
204
|
if (config.arcPlugins?.events !== false) {
|
|
204
|
-
const { default: eventPlugin } = await import("./eventPlugin-
|
|
205
|
+
const { default: eventPlugin } = await import("./eventPlugin-CaKTYkYM.mjs").then((n) => n.n);
|
|
205
206
|
const eventOpts = typeof config.arcPlugins?.events === "object" ? config.arcPlugins.events : {};
|
|
206
207
|
await fastify.register(eventPlugin, {
|
|
207
208
|
...eventOpts,
|
|
@@ -237,15 +238,15 @@ async function registerArcPlugins(fastify, config, trackPlugin, modules) {
|
|
|
237
238
|
trackPlugin("arc-graceful-shutdown");
|
|
238
239
|
}
|
|
239
240
|
if (config.arcPlugins?.caching) {
|
|
240
|
-
const { default: cachingPlugin } = await import("./caching-
|
|
241
|
+
const { default: cachingPlugin } = await import("./caching-SM8gghN6.mjs").then((n) => n.r);
|
|
241
242
|
const opts = config.arcPlugins.caching === true ? {} : config.arcPlugins.caching;
|
|
242
243
|
await fastify.register(cachingPlugin, opts);
|
|
243
244
|
trackPlugin("arc-caching", opts);
|
|
244
245
|
}
|
|
245
246
|
if (config.arcPlugins?.queryCache) {
|
|
246
|
-
const { queryCachePlugin } = await import("./queryCachePlugin-
|
|
247
|
+
const { queryCachePlugin } = await import("./queryCachePlugin-m1XsgAIJ.mjs").then((n) => n.n);
|
|
247
248
|
const opts = config.arcPlugins.queryCache === true ? {} : config.arcPlugins.queryCache;
|
|
248
|
-
const store = config.stores?.queryCache ?? new (await (import("./memory-
|
|
249
|
+
const store = config.stores?.queryCache ?? new (await (import("./memory-UBydS5ku.mjs").then((n) => n.n))).MemoryCacheStore();
|
|
249
250
|
await fastify.register(queryCachePlugin, {
|
|
250
251
|
store,
|
|
251
252
|
...opts
|
|
@@ -254,19 +255,19 @@ async function registerArcPlugins(fastify, config, trackPlugin, modules) {
|
|
|
254
255
|
}
|
|
255
256
|
if (config.arcPlugins?.sse) if (config.arcPlugins?.events === false) fastify.log.warn("SSE plugin requires events plugin (arcPlugins.events). SSE disabled.");
|
|
256
257
|
else {
|
|
257
|
-
const { default: ssePlugin } = await import("./sse-
|
|
258
|
+
const { default: ssePlugin } = await import("./sse-Bz-5ZeTt.mjs").then((n) => n.r);
|
|
258
259
|
const opts = config.arcPlugins.sse === true ? {} : config.arcPlugins.sse;
|
|
259
260
|
await fastify.register(ssePlugin, opts);
|
|
260
261
|
trackPlugin("arc-sse", opts);
|
|
261
262
|
}
|
|
262
263
|
if (config.arcPlugins?.metrics) {
|
|
263
|
-
const { default: metricsPlugin } = await import("./metrics-
|
|
264
|
+
const { default: metricsPlugin } = await import("./metrics-Qnvwc-LQ.mjs").then((n) => n.r);
|
|
264
265
|
const opts = config.arcPlugins.metrics === true ? {} : config.arcPlugins.metrics;
|
|
265
266
|
await fastify.register(metricsPlugin, opts);
|
|
266
267
|
trackPlugin("arc-metrics", opts);
|
|
267
268
|
}
|
|
268
269
|
if (config.arcPlugins?.versioning) {
|
|
269
|
-
const { default: versioningPlugin } = await import("./versioning-
|
|
270
|
+
const { default: versioningPlugin } = await import("./versioning-BUrT5aP4.mjs").then((n) => n.r);
|
|
270
271
|
await fastify.register(versioningPlugin, config.arcPlugins.versioning);
|
|
271
272
|
trackPlugin("arc-versioning", config.arcPlugins.versioning);
|
|
272
273
|
}
|
|
@@ -335,7 +336,7 @@ async function registerAuth(fastify, config, trackPlugin) {
|
|
|
335
336
|
*/
|
|
336
337
|
async function registerElevation(fastify, config, trackPlugin) {
|
|
337
338
|
if (!config.elevation) return;
|
|
338
|
-
const { elevationPlugin } = await import("./elevation-
|
|
339
|
+
const { elevationPlugin } = await import("./elevation-DgoeTyfX.mjs").then((n) => n.r);
|
|
339
340
|
await fastify.register(elevationPlugin, config.elevation);
|
|
340
341
|
trackPlugin("arc-elevation", config.elevation);
|
|
341
342
|
fastify.log.debug("Elevation plugin enabled");
|
|
@@ -345,7 +346,7 @@ async function registerElevation(fastify, config, trackPlugin) {
|
|
|
345
346
|
*/
|
|
346
347
|
async function registerErrorHandler(fastify, config, trackPlugin) {
|
|
347
348
|
if (config.errorHandler === false) return;
|
|
348
|
-
const { errorHandlerPlugin } = await import("./errorHandler-
|
|
349
|
+
const { errorHandlerPlugin } = await import("./errorHandler-Bk-AGhkU.mjs").then((n) => n.r);
|
|
349
350
|
const errorOpts = typeof config.errorHandler === "object" ? config.errorHandler : { includeStack: config.preset !== "production" };
|
|
350
351
|
await fastify.register(errorHandlerPlugin, errorOpts);
|
|
351
352
|
trackPlugin("arc-error-handler", errorOpts);
|
|
@@ -399,7 +400,7 @@ async function registerOne(parent, resource) {
|
|
|
399
400
|
}
|
|
400
401
|
/**
|
|
401
402
|
* Execute the full resource lifecycle:
|
|
402
|
-
* 1. plugins() — infra (DB,
|
|
403
|
+
* 1. plugins() — infra (DB, data, webhooks)
|
|
403
404
|
* 2. bootstrap[] — domain init (singletons, event handlers)
|
|
404
405
|
* 3. resources factory (if any) — resolved AFTER bootstrap, so engine-backed
|
|
405
406
|
* adapters can `await ensureEngine()` and pass
|
|
@@ -445,7 +446,7 @@ async function registerResources(fastify, config) {
|
|
|
445
446
|
let discoveryPath;
|
|
446
447
|
let discoveryYieldedZero = false;
|
|
447
448
|
if (resolvedResources === void 0 && config.resourceDir) {
|
|
448
|
-
const { loadResources } = await import("./loadResources-
|
|
449
|
+
const { loadResources } = await import("./loadResources-DBMQg_Aj.mjs").then((n) => n.n);
|
|
449
450
|
const { resolve, dirname } = await import("node:path");
|
|
450
451
|
const { fileURLToPath } = await import("node:url");
|
|
451
452
|
const rawDir = config.resourceDir;
|
|
@@ -745,7 +746,7 @@ function validateDistributedRuntime(options) {
|
|
|
745
746
|
* 4. Arc core (fastify.arc, events)
|
|
746
747
|
* 5. Arc plugins (requestId, health, caching, SSE, metrics, versioning)
|
|
747
748
|
* 6. Auth (scope decoration, auth strategy, elevation, error handler)
|
|
748
|
-
* 7. plugins() — user infra (DB,
|
|
749
|
+
* 7. plugins() — user infra (DB, data, webhooks)
|
|
749
750
|
* 8. bootstrap[] — domain init (singletons, event handlers)
|
|
750
751
|
* 9. resources[] — auto-discovered routes (prefix + skipGlobalPrefix)
|
|
751
752
|
* 10. afterResources() — post-registration wiring
|
|
@@ -810,17 +811,22 @@ async function createApp(options) {
|
|
|
810
811
|
await registerErrorHandler(fastify, config, trackPlugin);
|
|
811
812
|
await registerResources(fastify, config);
|
|
812
813
|
if (config.replyHelpers) {
|
|
813
|
-
const { replyHelpersPlugin } = await import("./replyHelpers-
|
|
814
|
+
const { replyHelpersPlugin } = await import("./replyHelpers-CK-FNO8E.mjs").then((n) => n.n);
|
|
814
815
|
await fastify.register(replyHelpersPlugin);
|
|
815
816
|
}
|
|
816
|
-
if (config.serializeBigInt)
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
return payload;
|
|
822
|
-
|
|
823
|
-
|
|
817
|
+
if (config.serializeBigInt) {
|
|
818
|
+
const mode = config.serializeBigInt === "string" ? "string" : "number";
|
|
819
|
+
if (config.serializeBigInt === true) arcLog("createApp").warn("serializeBigInt: true is a back-compat alias for 'number' (lossy above Number.MAX_SAFE_INTEGER = 9007199254740991). For IDs / money / counters / ledger values use serializeBigInt: 'string' (lossless). The boolean form will be removed in a future major.");
|
|
820
|
+
else if (mode === "number") arcLog("createApp").warn("serializeBigInt: 'number' loses precision above Number.MAX_SAFE_INTEGER (9007199254740991). Use 'string' instead unless you've audited that no bigint payload field can exceed the safe range.");
|
|
821
|
+
fastify.addHook("preSerialization", async (_request, _reply, payload) => {
|
|
822
|
+
if (payload === null || payload === void 0) return payload;
|
|
823
|
+
try {
|
|
824
|
+
return JSON.parse(JSON.stringify(payload, (_key, value) => typeof value === "bigint" ? mode === "string" ? value.toString() : Number(value) : value));
|
|
825
|
+
} catch {
|
|
826
|
+
return payload;
|
|
827
|
+
}
|
|
828
|
+
});
|
|
829
|
+
}
|
|
824
830
|
const authMode = config.auth === false ? "none" : config.auth ? config.auth.type : "none";
|
|
825
831
|
fastify.log.info({
|
|
826
832
|
preset: config.preset ?? "custom",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { n as createEvent } from "./EventTransport-DLWoUMHy.mjs";
|
|
3
3
|
//#region src/events/defineEvent.ts
|
|
4
4
|
/**
|
|
5
5
|
* defineEvent — Typed Event Definitions with Optional Schema Validation
|
package/dist/docs/index.d.mts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
import { p as RegistryEntry } from "../index-
|
|
1
|
+
import { p as RegistryEntry } from "../index-BtW7qYwa.mjs";
|
|
2
2
|
import { t as ExternalOpenApiPaths } from "../externalPaths-BD5nw6St.mjs";
|
|
3
3
|
import { FastifyPluginAsync } from "fastify";
|
|
4
4
|
|
|
5
|
-
//#region src/docs/openapi.d.ts
|
|
5
|
+
//#region src/docs/openapi/types.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* OpenAPI 3.0 type primitives used by arc's spec emitter.
|
|
8
|
+
*
|
|
9
|
+
* Internal to `src/docs/openapi/*` — public exports are surfaced via
|
|
10
|
+
* `src/docs/index.ts` (which re-exports `OpenApiSpec` only).
|
|
11
|
+
*/
|
|
6
12
|
interface OpenApiOptions {
|
|
7
13
|
/** API title */
|
|
8
14
|
title?: string;
|
|
@@ -23,6 +29,13 @@ interface OpenApiOptions {
|
|
|
23
29
|
/** Custom OpenAPI extensions */
|
|
24
30
|
extensions?: Record<string, unknown>;
|
|
25
31
|
}
|
|
32
|
+
interface OpenApiBuildOptions {
|
|
33
|
+
title?: string;
|
|
34
|
+
version?: string;
|
|
35
|
+
description?: string;
|
|
36
|
+
serverUrl?: string;
|
|
37
|
+
apiPrefix?: string;
|
|
38
|
+
}
|
|
26
39
|
interface OpenApiSpec {
|
|
27
40
|
openapi: string;
|
|
28
41
|
info: {
|
|
@@ -45,13 +58,6 @@ interface OpenApiSpec {
|
|
|
45
58
|
}>;
|
|
46
59
|
security?: Array<Record<string, string[]>>;
|
|
47
60
|
}
|
|
48
|
-
interface OpenApiBuildOptions {
|
|
49
|
-
title?: string;
|
|
50
|
-
version?: string;
|
|
51
|
-
description?: string;
|
|
52
|
-
serverUrl?: string;
|
|
53
|
-
apiPrefix?: string;
|
|
54
|
-
}
|
|
55
61
|
interface PathItem {
|
|
56
62
|
get?: Operation;
|
|
57
63
|
post?: Operation;
|
|
@@ -101,7 +107,7 @@ interface Response {
|
|
|
101
107
|
}>;
|
|
102
108
|
}
|
|
103
109
|
interface SchemaObject {
|
|
104
|
-
type?: string;
|
|
110
|
+
type?: string | string[];
|
|
105
111
|
format?: string;
|
|
106
112
|
properties?: Record<string, SchemaObject>;
|
|
107
113
|
items?: SchemaObject;
|
|
@@ -110,12 +116,17 @@ interface SchemaObject {
|
|
|
110
116
|
description?: string;
|
|
111
117
|
example?: unknown;
|
|
112
118
|
additionalProperties?: boolean | SchemaObject;
|
|
113
|
-
enum?: string[];
|
|
119
|
+
enum?: (string | number | boolean | null)[];
|
|
114
120
|
minimum?: number;
|
|
115
121
|
maximum?: number;
|
|
116
122
|
minLength?: number;
|
|
117
123
|
maxLength?: number;
|
|
118
124
|
pattern?: string;
|
|
125
|
+
oneOf?: SchemaObject[];
|
|
126
|
+
anyOf?: SchemaObject[];
|
|
127
|
+
allOf?: SchemaObject[];
|
|
128
|
+
default?: unknown;
|
|
129
|
+
nullable?: boolean;
|
|
119
130
|
}
|
|
120
131
|
interface SecurityScheme {
|
|
121
132
|
type: string;
|
|
@@ -124,6 +135,8 @@ interface SecurityScheme {
|
|
|
124
135
|
in?: string;
|
|
125
136
|
name?: string;
|
|
126
137
|
}
|
|
138
|
+
//#endregion
|
|
139
|
+
//#region src/docs/openapi/index.d.ts
|
|
127
140
|
declare const openApiPlugin: FastifyPluginAsync<OpenApiOptions>;
|
|
128
141
|
/**
|
|
129
142
|
* Build OpenAPI spec from registry resources.
|
package/dist/docs/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as getUserRoles } from "../types-
|
|
2
|
-
import { n as openApiPlugin, r as openapi_default, t as buildOpenApiSpec } from "../openapi-
|
|
1
|
+
import { t as getUserRoles } from "../types-D57iXYb8.mjs";
|
|
2
|
+
import { n as openApiPlugin, r as openapi_default, t as buildOpenApiSpec } from "../openapi-noXno2CV.mjs";
|
|
3
3
|
import fp from "fastify-plugin";
|
|
4
4
|
//#region src/docs/scalar.ts
|
|
5
5
|
const scalarPlugin = async (fastify, opts = {}) => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
|
|
2
2
|
import { arcLog } from "./logger/index.mjs";
|
|
3
|
-
import { t as getUserRoles } from "./types-
|
|
3
|
+
import { t as getUserRoles } from "./types-D57iXYb8.mjs";
|
|
4
4
|
import fp from "fastify-plugin";
|
|
5
5
|
//#region src/scope/elevation.ts
|
|
6
6
|
var elevation_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
|
|
2
|
+
import { m as statusToArcCode, p as isArcError } from "./errors-j4aJm1Wg.mjs";
|
|
3
|
+
import { isHttpError, toErrorContract } from "@classytic/repo-core/errors";
|
|
4
|
+
import fp from "fastify-plugin";
|
|
5
|
+
//#region src/plugins/errorHandler.ts
|
|
6
|
+
var errorHandler_exports = /* @__PURE__ */ __exportAll({
|
|
7
|
+
defaultIsDuplicateKeyError: () => defaultIsDuplicateKeyError,
|
|
8
|
+
errorHandlerPlugin: () => errorHandlerPlugin
|
|
9
|
+
});
|
|
10
|
+
/**
|
|
11
|
+
* Default duplicate-key detector. Strict driver-code matching only — never
|
|
12
|
+
* message strings (false positives mask real WriteConflict / NotWritable
|
|
13
|
+
* errors as 409s). Long-tail drivers compose: see jsdoc on
|
|
14
|
+
* {@link ErrorHandlerOptions.isDuplicateKeyError}.
|
|
15
|
+
*/
|
|
16
|
+
function defaultIsDuplicateKeyError(err) {
|
|
17
|
+
if (!err || typeof err !== "object") return false;
|
|
18
|
+
const e = err;
|
|
19
|
+
if (e.code === 11e3 || e.codeName === "DuplicateKey") return true;
|
|
20
|
+
if (e.code === "P2002") return true;
|
|
21
|
+
if (e.code === "23505") return true;
|
|
22
|
+
if (e.code === "ER_DUP_ENTRY" || e.errno === 1062) return true;
|
|
23
|
+
if (e.code === "SQLITE_CONSTRAINT_UNIQUE" || e.code === "SQLITE_CONSTRAINT_PRIMARYKEY") return true;
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
function extractDuplicateFields(err) {
|
|
27
|
+
if (!err || typeof err !== "object") return null;
|
|
28
|
+
const e = err;
|
|
29
|
+
if (e.keyValue && typeof e.keyValue === "object") return Object.keys(e.keyValue);
|
|
30
|
+
if (e.meta?.target) {
|
|
31
|
+
if (Array.isArray(e.meta.target)) return e.meta.target.map(String);
|
|
32
|
+
if (typeof e.meta.target === "string") return [e.meta.target];
|
|
33
|
+
}
|
|
34
|
+
if (typeof e.constraint === "string") return [e.constraint];
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
/** Map Fastify schema-validation errors → canonical `ErrorDetail[]`. */
|
|
38
|
+
function fastifyValidationDetails(error) {
|
|
39
|
+
return (error.validation ?? []).map((v) => {
|
|
40
|
+
const missingProperty = v.params?.missingProperty;
|
|
41
|
+
const path = v.instancePath?.replace(/^\//, "") || (typeof missingProperty === "string" ? missingProperty : void 0);
|
|
42
|
+
return {
|
|
43
|
+
...path ? { path } : {},
|
|
44
|
+
code: v.keyword ?? "invalid",
|
|
45
|
+
message: v.message || "Invalid value"
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
/** Map Mongoose `ValidationError.errors` → canonical `ErrorDetail[]`. */
|
|
50
|
+
function mongooseValidationDetails(errors) {
|
|
51
|
+
return Object.entries(errors).map(([field, e]) => ({
|
|
52
|
+
path: e.path || field,
|
|
53
|
+
code: "validation_error",
|
|
54
|
+
message: e.message
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
async function errorHandlerPluginFn(fastify, options = {}) {
|
|
58
|
+
const isProduction = process.env.NODE_ENV === "production";
|
|
59
|
+
const { includeStack = !isProduction, onError, errorMap = {}, errorMappers = [], isDuplicateKeyError = defaultIsDuplicateKeyError } = options;
|
|
60
|
+
fastify.setErrorHandler(async (error, request, reply) => {
|
|
61
|
+
if (onError) try {
|
|
62
|
+
await onError(error, request);
|
|
63
|
+
} catch (callbackError) {
|
|
64
|
+
request.log.error({ err: callbackError }, "Error in onError callback");
|
|
65
|
+
}
|
|
66
|
+
const correlationId = request.id;
|
|
67
|
+
const contract = classify(error, {
|
|
68
|
+
errorMappers,
|
|
69
|
+
errorMap,
|
|
70
|
+
isDuplicateKeyError
|
|
71
|
+
});
|
|
72
|
+
const meta = { ...contract.meta ?? {} };
|
|
73
|
+
if (includeStack && error.stack) meta.stack = error.stack;
|
|
74
|
+
const wire = {
|
|
75
|
+
...contract,
|
|
76
|
+
...correlationId ? { correlationId } : {},
|
|
77
|
+
...Object.keys(meta).length > 0 ? { meta } : {}
|
|
78
|
+
};
|
|
79
|
+
const status = wire.status ?? 500;
|
|
80
|
+
if (status >= 500) request.log.error({
|
|
81
|
+
err: error,
|
|
82
|
+
status
|
|
83
|
+
}, "Server error");
|
|
84
|
+
else if (status >= 400) request.log.warn({
|
|
85
|
+
err: error,
|
|
86
|
+
status
|
|
87
|
+
}, "Client error");
|
|
88
|
+
if (isArcError(error) && error.cause) request.log.error({ cause: error.cause }, "Error cause chain");
|
|
89
|
+
return reply.status(status).send(wire);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Single-pass error → `ErrorContract` classifier. Dispatch order is fixed:
|
|
94
|
+
*
|
|
95
|
+
* 1. Class-based mappers (`instanceof`) — user-registered domain errors
|
|
96
|
+
* 2. `ArcError` / any `HttpError`-shaped throw — flows through `toErrorContract`
|
|
97
|
+
* 3. Fastify schema-validation errors — `error.validation` array
|
|
98
|
+
* 4. Fastify-style errors with a numeric `statusCode`
|
|
99
|
+
* 5. Name-keyed `errorMap` entries
|
|
100
|
+
* 6. Mongoose `ValidationError` / `CastError`
|
|
101
|
+
* 7. Driver-specific duplicate-key classifier
|
|
102
|
+
* 8. Fallback: `arc.internal_error` 500
|
|
103
|
+
*/
|
|
104
|
+
function classify(error, ctx) {
|
|
105
|
+
for (const mapper of ctx.errorMappers) if (error instanceof mapper.type) {
|
|
106
|
+
const mapped = mapper.toResponse(error);
|
|
107
|
+
return {
|
|
108
|
+
code: mapped.code ?? statusToArcCode(mapped.status),
|
|
109
|
+
message: mapped.message ?? error.message,
|
|
110
|
+
status: mapped.status,
|
|
111
|
+
...mapped.details ? { details: mapped.details } : {},
|
|
112
|
+
...mapped.meta ? { meta: mapped.meta } : {}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
if (isArcError(error) || isHttpError(error)) return toErrorContract(error);
|
|
116
|
+
const fastifyErr = error;
|
|
117
|
+
if (Array.isArray(fastifyErr.validation)) return {
|
|
118
|
+
code: "arc.validation_error",
|
|
119
|
+
message: "Validation failed",
|
|
120
|
+
status: 400,
|
|
121
|
+
details: fastifyValidationDetails(fastifyErr)
|
|
122
|
+
};
|
|
123
|
+
if (typeof fastifyErr.statusCode === "number") return {
|
|
124
|
+
code: statusToArcCode(fastifyErr.statusCode),
|
|
125
|
+
message: error.message || "Error",
|
|
126
|
+
status: fastifyErr.statusCode
|
|
127
|
+
};
|
|
128
|
+
if (error.name && ctx.errorMap[error.name]) {
|
|
129
|
+
const m = ctx.errorMap[error.name];
|
|
130
|
+
return {
|
|
131
|
+
code: m.code,
|
|
132
|
+
message: m.message ?? error.message,
|
|
133
|
+
status: m.statusCode
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
if (error.name === "ValidationError" && "errors" in error) {
|
|
137
|
+
const errs = error.errors;
|
|
138
|
+
return {
|
|
139
|
+
code: "arc.validation_error",
|
|
140
|
+
message: error.message || "Validation failed",
|
|
141
|
+
status: 400,
|
|
142
|
+
details: mongooseValidationDetails(errs)
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
if (error.name === "CastError") return {
|
|
146
|
+
code: "arc.invalid_id",
|
|
147
|
+
message: "Invalid identifier format",
|
|
148
|
+
status: 400
|
|
149
|
+
};
|
|
150
|
+
if (ctx.isDuplicateKeyError(error)) {
|
|
151
|
+
const fields = extractDuplicateFields(error);
|
|
152
|
+
return {
|
|
153
|
+
code: "arc.conflict",
|
|
154
|
+
message: "Resource already exists",
|
|
155
|
+
status: 409,
|
|
156
|
+
...fields && fields.length > 0 ? { details: fields.map((f) => ({
|
|
157
|
+
path: f,
|
|
158
|
+
code: "duplicate_key",
|
|
159
|
+
message: `Duplicate value for "${f}"`
|
|
160
|
+
})) } : {}
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
code: "arc.internal_error",
|
|
165
|
+
message: error.message || "Internal Server Error",
|
|
166
|
+
status: 500
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
const errorHandlerPlugin = fp(errorHandlerPluginFn, {
|
|
170
|
+
name: "arc-error-handler",
|
|
171
|
+
fastify: "5.x"
|
|
172
|
+
});
|
|
173
|
+
//#endregion
|
|
174
|
+
export { errorHandlerPlugin as n, errorHandler_exports as r, defaultIsDuplicateKeyError as t };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { ErrorDetail } from "@classytic/repo-core/errors";
|
|
2
|
+
import { FastifyInstance, FastifyRequest } from "fastify";
|
|
3
|
+
|
|
4
|
+
//#region src/plugins/errorHandler.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Class-based error mapper — `instanceof` check converts a thrown class
|
|
7
|
+
* to a partial `ErrorContract`. Highest-priority dispatch in the handler.
|
|
8
|
+
*/
|
|
9
|
+
interface ErrorMapper<T extends Error = Error> {
|
|
10
|
+
type: abstract new (...args: any[]) => T;
|
|
11
|
+
toResponse: (error: T) => {
|
|
12
|
+
status: number;
|
|
13
|
+
code?: string;
|
|
14
|
+
message?: string;
|
|
15
|
+
details?: ReadonlyArray<ErrorDetail>;
|
|
16
|
+
meta?: Record<string, unknown>;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
interface ErrorHandlerOptions {
|
|
20
|
+
/** Include `meta.stack` on the wire (defaults to `NODE_ENV !== 'production'`). */
|
|
21
|
+
includeStack?: boolean;
|
|
22
|
+
/** Custom callback fired for every error — log to Sentry / Datadog / etc. */
|
|
23
|
+
onError?: (error: Error, request: FastifyRequest) => void | Promise<void>;
|
|
24
|
+
/** Map by `error.name` string. Lower priority than `errorMappers`. */
|
|
25
|
+
errorMap?: Record<string, {
|
|
26
|
+
statusCode: number;
|
|
27
|
+
code: string;
|
|
28
|
+
message?: string;
|
|
29
|
+
}>;
|
|
30
|
+
/** Map by `instanceof`. Highest priority — checked first. */
|
|
31
|
+
errorMappers?: ErrorMapper[];
|
|
32
|
+
/** Driver-aware duplicate-key classifier. Override to add long-tail drivers. */
|
|
33
|
+
isDuplicateKeyError?: (err: unknown) => boolean;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Default duplicate-key detector. Strict driver-code matching only — never
|
|
37
|
+
* message strings (false positives mask real WriteConflict / NotWritable
|
|
38
|
+
* errors as 409s). Long-tail drivers compose: see jsdoc on
|
|
39
|
+
* {@link ErrorHandlerOptions.isDuplicateKeyError}.
|
|
40
|
+
*/
|
|
41
|
+
declare function defaultIsDuplicateKeyError(err: unknown): boolean;
|
|
42
|
+
declare function errorHandlerPluginFn(fastify: FastifyInstance, options?: ErrorHandlerOptions): Promise<void>;
|
|
43
|
+
declare const errorHandlerPlugin: typeof errorHandlerPluginFn;
|
|
44
|
+
//#endregion
|
|
45
|
+
export { errorHandlerPlugin as i, ErrorMapper as n, defaultIsDuplicateKeyError as r, ErrorHandlerOptions as t };
|