@classytic/arc 2.11.4 → 2.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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-D72ia0EH.mjs +1399 -0
- package/dist/{createActionRouter-CIKOcNA7.mjs → createActionRouter-CEvzKcy8.mjs} +7 -20
- package/dist/createAggregationRouter-CyecOxnO.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 +1 -1
- 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-D7G1V7ex.mjs → openapi-CiOMVW1p.mjs} +143 -13
- 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-C5coh64w.mjs} +224 -71
- package/dist/{routerShared-BqLRb5l7.mjs → routerShared-D6_fEGHh.mjs} +40 -36
- package/dist/{schemaIR-Dy2p4MxS.mjs → schemaIR-7Vl611Qs.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/{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,173 +0,0 @@
|
|
|
1
|
-
import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
|
|
2
|
-
import { p as isArcError } from "./errors-D5c-5BJL.mjs";
|
|
3
|
-
import fp from "fastify-plugin";
|
|
4
|
-
//#region src/plugins/errorHandler.ts
|
|
5
|
-
var errorHandler_exports = /* @__PURE__ */ __exportAll({
|
|
6
|
-
defaultIsDuplicateKeyError: () => defaultIsDuplicateKeyError,
|
|
7
|
-
errorHandlerPlugin: () => errorHandlerPlugin
|
|
8
|
-
});
|
|
9
|
-
/**
|
|
10
|
-
* Default duplicate-key detector covering the mainstream drivers arc sees
|
|
11
|
-
* most. Detection is strictly by known driver codes — never by message
|
|
12
|
-
* string matching — because false positives on dup-key silently mask real
|
|
13
|
-
* errors (WriteConflict, NotWritablePrimary, etc.) as 409s. For long-tail
|
|
14
|
-
* drivers (Neo4j, MSSQL, DynamoDB, custom kits), compose rather than
|
|
15
|
-
* replace:
|
|
16
|
-
*
|
|
17
|
-
* ```ts
|
|
18
|
-
* import { defaultIsDuplicateKeyError } from '@classytic/arc/plugins';
|
|
19
|
-
*
|
|
20
|
-
* errorHandler: {
|
|
21
|
-
* isDuplicateKeyError: (err) =>
|
|
22
|
-
* defaultIsDuplicateKeyError(err) || isNeo4jDupKey(err),
|
|
23
|
-
* }
|
|
24
|
-
* ```
|
|
25
|
-
*
|
|
26
|
-
* Drizzle apps get coverage transitively (Drizzle doesn't wrap driver
|
|
27
|
-
* errors — pg/mysql2/better-sqlite3 codes propagate as-is). Neon is
|
|
28
|
-
* Postgres-wire-compatible → `23505` covers `@neondatabase/serverless`.
|
|
29
|
-
*/
|
|
30
|
-
function defaultIsDuplicateKeyError(err) {
|
|
31
|
-
if (!err || typeof err !== "object") return false;
|
|
32
|
-
const e = err;
|
|
33
|
-
if (e.code === 11e3 || e.codeName === "DuplicateKey") return true;
|
|
34
|
-
if (e.code === "P2002") return true;
|
|
35
|
-
if (e.code === "23505") return true;
|
|
36
|
-
if (e.code === "ER_DUP_ENTRY" || e.errno === 1062) return true;
|
|
37
|
-
if (e.code === "SQLITE_CONSTRAINT_UNIQUE" || e.code === "SQLITE_CONSTRAINT_PRIMARYKEY") return true;
|
|
38
|
-
return false;
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Extract the duplicate-field names for the `details.duplicateFields`
|
|
42
|
-
* response. Only called when the caller has opted into detail exposure
|
|
43
|
-
* (`includeStack: true`) — shape differs per driver.
|
|
44
|
-
*/
|
|
45
|
-
function extractDuplicateFields(err) {
|
|
46
|
-
if (!err || typeof err !== "object") return null;
|
|
47
|
-
const e = err;
|
|
48
|
-
if (e.keyValue && typeof e.keyValue === "object") return Object.keys(e.keyValue);
|
|
49
|
-
if (e.meta?.target) {
|
|
50
|
-
if (Array.isArray(e.meta.target)) return e.meta.target.map(String);
|
|
51
|
-
if (typeof e.meta.target === "string") return [e.meta.target];
|
|
52
|
-
}
|
|
53
|
-
if (typeof e.constraint === "string") return [e.constraint];
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
async function errorHandlerPluginFn(fastify, options = {}) {
|
|
57
|
-
const isProduction = process.env.NODE_ENV === "production";
|
|
58
|
-
const { includeStack = !isProduction, onError, errorMap = {}, errorMappers = [], isDuplicateKeyError = defaultIsDuplicateKeyError } = options;
|
|
59
|
-
fastify.setErrorHandler(async (error, request, reply) => {
|
|
60
|
-
if (onError) try {
|
|
61
|
-
await onError(error, request);
|
|
62
|
-
} catch (callbackError) {
|
|
63
|
-
request.log.error({ err: callbackError }, "Error in onError callback");
|
|
64
|
-
}
|
|
65
|
-
const requestId = request.id;
|
|
66
|
-
if (errorMappers.length > 0) {
|
|
67
|
-
for (const mapper of errorMappers) if (error instanceof mapper.type) {
|
|
68
|
-
const mapped = mapper.toResponse(error);
|
|
69
|
-
const response = {
|
|
70
|
-
success: false,
|
|
71
|
-
error: mapped.message ?? error.message,
|
|
72
|
-
code: mapped.code ?? "DOMAIN_ERROR",
|
|
73
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
74
|
-
...requestId && { requestId },
|
|
75
|
-
...mapped.details && { details: mapped.details },
|
|
76
|
-
...includeStack && error.stack ? { stack: error.stack } : {}
|
|
77
|
-
};
|
|
78
|
-
return reply.code(mapped.status).send(response);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
const response = {
|
|
82
|
-
success: false,
|
|
83
|
-
error: error.message || "Internal Server Error",
|
|
84
|
-
code: "INTERNAL_ERROR",
|
|
85
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
86
|
-
...requestId && { requestId }
|
|
87
|
-
};
|
|
88
|
-
let statusCode = 500;
|
|
89
|
-
if (isArcError(error)) {
|
|
90
|
-
statusCode = error.statusCode;
|
|
91
|
-
response.code = error.code;
|
|
92
|
-
if (error.details) response.details = error.details;
|
|
93
|
-
if (error.requestId) response.requestId = error.requestId;
|
|
94
|
-
if (error.cause) request.log.error({ cause: error.cause }, "Error cause chain");
|
|
95
|
-
} else if ("validation" in error && Array.isArray(error.validation)) {
|
|
96
|
-
statusCode = 400;
|
|
97
|
-
response.code = "VALIDATION_ERROR";
|
|
98
|
-
response.error = "Validation failed";
|
|
99
|
-
response.details = { errors: error.validation?.map((v) => ({
|
|
100
|
-
field: v.instancePath?.replace(/^\//, "") || v.params?.missingProperty || "unknown",
|
|
101
|
-
message: v.message || "Invalid value",
|
|
102
|
-
keyword: v.keyword
|
|
103
|
-
})) };
|
|
104
|
-
} else if ("statusCode" in error && typeof error.statusCode === "number") {
|
|
105
|
-
statusCode = error.statusCode;
|
|
106
|
-
response.code = statusCodeToCode(statusCode);
|
|
107
|
-
} else if ("status" in error && typeof error.status === "number") {
|
|
108
|
-
statusCode = error.status;
|
|
109
|
-
response.code = statusCodeToCode(statusCode);
|
|
110
|
-
} else if (error.name && errorMap[error.name]) {
|
|
111
|
-
const mapping = errorMap[error.name];
|
|
112
|
-
statusCode = mapping.statusCode;
|
|
113
|
-
response.code = mapping.code;
|
|
114
|
-
if (mapping.message) response.error = mapping.message;
|
|
115
|
-
} else if (error.name === "ValidationError" && "errors" in error) {
|
|
116
|
-
statusCode = 400;
|
|
117
|
-
response.code = "VALIDATION_ERROR";
|
|
118
|
-
const mongooseErrors = error.errors;
|
|
119
|
-
if (includeStack) response.details = { errors: Object.entries(mongooseErrors).map(([field, err]) => ({
|
|
120
|
-
field: err.path || field,
|
|
121
|
-
message: err.message
|
|
122
|
-
})) };
|
|
123
|
-
else response.details = { errorCount: Object.keys(mongooseErrors).length };
|
|
124
|
-
} else if (error.name === "CastError") {
|
|
125
|
-
statusCode = 400;
|
|
126
|
-
response.code = "INVALID_ID";
|
|
127
|
-
response.error = "Invalid identifier format";
|
|
128
|
-
} else if (isDuplicateKeyError(error)) {
|
|
129
|
-
statusCode = 409;
|
|
130
|
-
response.code = "DUPLICATE_KEY";
|
|
131
|
-
response.error = "Resource already exists";
|
|
132
|
-
if (includeStack) {
|
|
133
|
-
const duplicateFields = extractDuplicateFields(error);
|
|
134
|
-
if (duplicateFields && duplicateFields.length > 0) response.details = { duplicateFields };
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
if (includeStack && error.stack) response.stack = error.stack;
|
|
138
|
-
if (statusCode >= 500) request.log.error({
|
|
139
|
-
err: error,
|
|
140
|
-
statusCode
|
|
141
|
-
}, "Server error");
|
|
142
|
-
else if (statusCode >= 400) request.log.warn({
|
|
143
|
-
err: error,
|
|
144
|
-
statusCode
|
|
145
|
-
}, "Client error");
|
|
146
|
-
return reply.status(statusCode).send(response);
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* Map HTTP status code to error code
|
|
151
|
-
*/
|
|
152
|
-
function statusCodeToCode(statusCode) {
|
|
153
|
-
return {
|
|
154
|
-
400: "BAD_REQUEST",
|
|
155
|
-
401: "UNAUTHORIZED",
|
|
156
|
-
403: "FORBIDDEN",
|
|
157
|
-
404: "NOT_FOUND",
|
|
158
|
-
405: "METHOD_NOT_ALLOWED",
|
|
159
|
-
409: "CONFLICT",
|
|
160
|
-
422: "UNPROCESSABLE_ENTITY",
|
|
161
|
-
429: "RATE_LIMITED",
|
|
162
|
-
500: "INTERNAL_ERROR",
|
|
163
|
-
502: "BAD_GATEWAY",
|
|
164
|
-
503: "SERVICE_UNAVAILABLE",
|
|
165
|
-
504: "GATEWAY_TIMEOUT"
|
|
166
|
-
}[statusCode] ?? "ERROR";
|
|
167
|
-
}
|
|
168
|
-
const errorHandlerPlugin = fp(errorHandlerPluginFn, {
|
|
169
|
-
name: "arc-error-handler",
|
|
170
|
-
fastify: "5.x"
|
|
171
|
-
});
|
|
172
|
-
//#endregion
|
|
173
|
-
export { errorHandlerPlugin as n, errorHandler_exports as r, defaultIsDuplicateKeyError as t };
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import { FastifyInstance, FastifyRequest } from "fastify";
|
|
2
|
-
|
|
3
|
-
//#region src/plugins/errorHandler.d.ts
|
|
4
|
-
/** Class-based error mapper — maps thrown error instances to HTTP responses */
|
|
5
|
-
interface ErrorMapper<T extends Error = Error> {
|
|
6
|
-
/**
|
|
7
|
-
* Error class to match. Checked at runtime via `instanceof` — the constructor
|
|
8
|
-
* arity/signature is not called by the plugin, so the signature is typed
|
|
9
|
-
* permissively to accept real-world error classes:
|
|
10
|
-
*
|
|
11
|
-
* - **Abstract classes** (e.g. base domain errors) — `abstract new` is accepted.
|
|
12
|
-
* - **Specific constructor signatures** (e.g. `new InvalidTransitionError(from, to, id?)`)
|
|
13
|
-
* — `any[]` avoids forcing consumers to widen to `unknown[]` or cast.
|
|
14
|
-
*
|
|
15
|
-
* What matters for dispatch is the `instanceof` check, not the ctor shape.
|
|
16
|
-
*/
|
|
17
|
-
type: abstract new (...args: any[]) => T;
|
|
18
|
-
/** Convert the error to an HTTP response shape */
|
|
19
|
-
toResponse: (error: T) => {
|
|
20
|
-
status: number;
|
|
21
|
-
code?: string;
|
|
22
|
-
message?: string;
|
|
23
|
-
details?: Record<string, unknown>;
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
interface ErrorHandlerOptions {
|
|
27
|
-
/**
|
|
28
|
-
* Include stack trace in error responses (default: false in production)
|
|
29
|
-
*/
|
|
30
|
-
includeStack?: boolean;
|
|
31
|
-
/**
|
|
32
|
-
* Custom error callback for logging to external services
|
|
33
|
-
*/
|
|
34
|
-
onError?: (error: Error, request: FastifyRequest) => void | Promise<void>;
|
|
35
|
-
/**
|
|
36
|
-
* Map specific error types to custom responses (by error.name string)
|
|
37
|
-
*/
|
|
38
|
-
errorMap?: Record<string, {
|
|
39
|
-
statusCode: number;
|
|
40
|
-
code: string;
|
|
41
|
-
message?: string;
|
|
42
|
-
}>;
|
|
43
|
-
/**
|
|
44
|
-
* Class-based error mappers — checked via `instanceof`, highest priority.
|
|
45
|
-
*
|
|
46
|
-
* Register your domain error classes once; Arc auto-catches and maps them
|
|
47
|
-
* in every handler. Handlers just `throw` — no try/catch needed.
|
|
48
|
-
*
|
|
49
|
-
* @example
|
|
50
|
-
* ```typescript
|
|
51
|
-
* class AccountingError extends Error {
|
|
52
|
-
* constructor(message: string, public status: number, public code: string) {
|
|
53
|
-
* super(message);
|
|
54
|
-
* }
|
|
55
|
-
* }
|
|
56
|
-
*
|
|
57
|
-
* const app = await createApp({
|
|
58
|
-
* errorHandler: {
|
|
59
|
-
* errorMappers: [
|
|
60
|
-
* {
|
|
61
|
-
* type: AccountingError,
|
|
62
|
-
* toResponse: (err) => ({ status: err.status, code: err.code, message: err.message }),
|
|
63
|
-
* },
|
|
64
|
-
* ],
|
|
65
|
-
* },
|
|
66
|
-
* });
|
|
67
|
-
*
|
|
68
|
-
* // Now handlers just throw:
|
|
69
|
-
* handler: async (req) => {
|
|
70
|
-
* await ledger.post(id); // throws AccountingError → Arc maps to proper HTTP response
|
|
71
|
-
* }
|
|
72
|
-
* ```
|
|
73
|
-
*/
|
|
74
|
-
errorMappers?: ErrorMapper[];
|
|
75
|
-
/**
|
|
76
|
-
* Classify an error as a duplicate-key / unique-constraint violation →
|
|
77
|
-
* mapped to `409 Conflict` with `code: "DUPLICATE_KEY"`.
|
|
78
|
-
*
|
|
79
|
-
* Mirrors `RepositoryLike.isDuplicateKeyError` for the Fastify layer: errors
|
|
80
|
-
* that escape a controller (custom routes, user hooks, raw driver calls)
|
|
81
|
-
* still land here, so the classifier is duplicated at the edge. Defaults
|
|
82
|
-
* cover MongoDB (`code 11000` / `codeName "DuplicateKey"`), Prisma
|
|
83
|
-
* (`code "P2002"`), and Postgres (`code "23505"`). Override to add other
|
|
84
|
-
* backends (DynamoDB `ConditionalCheckFailedException`, etc.) or to disable
|
|
85
|
-
* the built-in detection.
|
|
86
|
-
*/
|
|
87
|
-
isDuplicateKeyError?: (err: unknown) => boolean;
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Default duplicate-key detector covering the mainstream drivers arc sees
|
|
91
|
-
* most. Detection is strictly by known driver codes — never by message
|
|
92
|
-
* string matching — because false positives on dup-key silently mask real
|
|
93
|
-
* errors (WriteConflict, NotWritablePrimary, etc.) as 409s. For long-tail
|
|
94
|
-
* drivers (Neo4j, MSSQL, DynamoDB, custom kits), compose rather than
|
|
95
|
-
* replace:
|
|
96
|
-
*
|
|
97
|
-
* ```ts
|
|
98
|
-
* import { defaultIsDuplicateKeyError } from '@classytic/arc/plugins';
|
|
99
|
-
*
|
|
100
|
-
* errorHandler: {
|
|
101
|
-
* isDuplicateKeyError: (err) =>
|
|
102
|
-
* defaultIsDuplicateKeyError(err) || isNeo4jDupKey(err),
|
|
103
|
-
* }
|
|
104
|
-
* ```
|
|
105
|
-
*
|
|
106
|
-
* Drizzle apps get coverage transitively (Drizzle doesn't wrap driver
|
|
107
|
-
* errors — pg/mysql2/better-sqlite3 codes propagate as-is). Neon is
|
|
108
|
-
* Postgres-wire-compatible → `23505` covers `@neondatabase/serverless`.
|
|
109
|
-
*/
|
|
110
|
-
declare function defaultIsDuplicateKeyError(err: unknown): boolean;
|
|
111
|
-
declare function errorHandlerPluginFn(fastify: FastifyInstance, options?: ErrorHandlerOptions): Promise<void>;
|
|
112
|
-
declare const errorHandlerPlugin: typeof errorHandlerPluginFn;
|
|
113
|
-
//#endregion
|
|
114
|
-
export { errorHandlerPlugin as i, ErrorMapper as n, defaultIsDuplicateKeyError as r, ErrorHandlerOptions as t };
|
package/dist/errors-D5c-5BJL.mjs
DELETED
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
//#region src/utils/errors.ts
|
|
2
|
-
/**
|
|
3
|
-
* Base Arc Error
|
|
4
|
-
*
|
|
5
|
-
* All Arc errors extend this class and produce a consistent error envelope:
|
|
6
|
-
* {
|
|
7
|
-
* success: false,
|
|
8
|
-
* error: "Human-readable message",
|
|
9
|
-
* code: "MACHINE_CODE",
|
|
10
|
-
* requestId: "uuid", // For tracing
|
|
11
|
-
* timestamp: "ISO date", // When error occurred
|
|
12
|
-
* details: { ... } // Additional context
|
|
13
|
-
* }
|
|
14
|
-
*/
|
|
15
|
-
var ArcError = class ArcError extends Error {
|
|
16
|
-
name;
|
|
17
|
-
code;
|
|
18
|
-
statusCode;
|
|
19
|
-
details;
|
|
20
|
-
cause;
|
|
21
|
-
timestamp;
|
|
22
|
-
requestId;
|
|
23
|
-
constructor(message, options = {}) {
|
|
24
|
-
super(message, options.cause ? { cause: options.cause } : void 0);
|
|
25
|
-
this.name = "ArcError";
|
|
26
|
-
this.code = options.code ?? "ARC_ERROR";
|
|
27
|
-
this.statusCode = options.statusCode ?? 500;
|
|
28
|
-
this.details = options.details;
|
|
29
|
-
this.cause = options.cause;
|
|
30
|
-
this.timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
31
|
-
this.requestId = options.requestId;
|
|
32
|
-
if (Error.captureStackTrace) Error.captureStackTrace(this, this.constructor);
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Set request ID (typically from request context)
|
|
36
|
-
*/
|
|
37
|
-
withRequestId(requestId) {
|
|
38
|
-
this.requestId = requestId;
|
|
39
|
-
return this;
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Convert to JSON response.
|
|
43
|
-
* Includes cause chain when present for debugging visibility.
|
|
44
|
-
*/
|
|
45
|
-
toJSON() {
|
|
46
|
-
return {
|
|
47
|
-
success: false,
|
|
48
|
-
error: this.message,
|
|
49
|
-
code: this.code,
|
|
50
|
-
timestamp: this.timestamp,
|
|
51
|
-
...this.requestId && { requestId: this.requestId },
|
|
52
|
-
...this.details && { details: this.details },
|
|
53
|
-
...this.cause && { cause: this.cause instanceof ArcError ? this.cause.toJSON() : {
|
|
54
|
-
message: this.cause.message,
|
|
55
|
-
name: this.cause.name
|
|
56
|
-
} }
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
/**
|
|
61
|
-
* Not Found Error - 404
|
|
62
|
-
*/
|
|
63
|
-
var NotFoundError = class extends ArcError {
|
|
64
|
-
constructor(resource, identifier) {
|
|
65
|
-
const message = identifier ? `${resource} with identifier '${identifier}' not found` : `${resource} not found`;
|
|
66
|
-
super(message, {
|
|
67
|
-
code: "NOT_FOUND",
|
|
68
|
-
statusCode: 404,
|
|
69
|
-
details: {
|
|
70
|
-
resource,
|
|
71
|
-
identifier
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
this.name = "NotFoundError";
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
/**
|
|
78
|
-
* Validation Error - 400
|
|
79
|
-
*/
|
|
80
|
-
var ValidationError = class extends ArcError {
|
|
81
|
-
errors;
|
|
82
|
-
constructor(message, errors = []) {
|
|
83
|
-
super(message, {
|
|
84
|
-
code: "VALIDATION_ERROR",
|
|
85
|
-
statusCode: 400,
|
|
86
|
-
details: { errors }
|
|
87
|
-
});
|
|
88
|
-
this.name = "ValidationError";
|
|
89
|
-
this.errors = errors;
|
|
90
|
-
}
|
|
91
|
-
};
|
|
92
|
-
/**
|
|
93
|
-
* Unauthorized Error - 401
|
|
94
|
-
*/
|
|
95
|
-
var UnauthorizedError = class extends ArcError {
|
|
96
|
-
constructor(message = "Authentication required") {
|
|
97
|
-
super(message, {
|
|
98
|
-
code: "UNAUTHORIZED",
|
|
99
|
-
statusCode: 401
|
|
100
|
-
});
|
|
101
|
-
this.name = "UnauthorizedError";
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
/**
|
|
105
|
-
* Forbidden Error - 403
|
|
106
|
-
*/
|
|
107
|
-
var ForbiddenError = class extends ArcError {
|
|
108
|
-
constructor(message = "Access denied") {
|
|
109
|
-
super(message, {
|
|
110
|
-
code: "FORBIDDEN",
|
|
111
|
-
statusCode: 403
|
|
112
|
-
});
|
|
113
|
-
this.name = "ForbiddenError";
|
|
114
|
-
}
|
|
115
|
-
};
|
|
116
|
-
/**
|
|
117
|
-
* Conflict Error - 409
|
|
118
|
-
*/
|
|
119
|
-
var ConflictError = class extends ArcError {
|
|
120
|
-
constructor(message, field) {
|
|
121
|
-
super(message, {
|
|
122
|
-
code: "CONFLICT",
|
|
123
|
-
statusCode: 409,
|
|
124
|
-
details: field ? { field } : void 0
|
|
125
|
-
});
|
|
126
|
-
this.name = "ConflictError";
|
|
127
|
-
}
|
|
128
|
-
};
|
|
129
|
-
/**
|
|
130
|
-
* Organization Required Error - 403
|
|
131
|
-
*/
|
|
132
|
-
var OrgRequiredError = class extends ArcError {
|
|
133
|
-
organizations;
|
|
134
|
-
constructor(message, organizations) {
|
|
135
|
-
super(message, {
|
|
136
|
-
code: "ORG_SELECTION_REQUIRED",
|
|
137
|
-
statusCode: 403,
|
|
138
|
-
details: organizations ? { organizations } : void 0
|
|
139
|
-
});
|
|
140
|
-
this.name = "OrgRequiredError";
|
|
141
|
-
this.organizations = organizations;
|
|
142
|
-
}
|
|
143
|
-
};
|
|
144
|
-
/**
|
|
145
|
-
* Organization Access Denied Error - 403
|
|
146
|
-
*/
|
|
147
|
-
var OrgAccessDeniedError = class extends ArcError {
|
|
148
|
-
constructor(orgId) {
|
|
149
|
-
super("Organization access denied", {
|
|
150
|
-
code: "ORG_ACCESS_DENIED",
|
|
151
|
-
statusCode: 403,
|
|
152
|
-
details: orgId ? { organizationId: orgId } : void 0
|
|
153
|
-
});
|
|
154
|
-
this.name = "OrgAccessDeniedError";
|
|
155
|
-
}
|
|
156
|
-
};
|
|
157
|
-
/**
|
|
158
|
-
* Rate Limit Error - 429
|
|
159
|
-
*/
|
|
160
|
-
var RateLimitError = class extends ArcError {
|
|
161
|
-
retryAfter;
|
|
162
|
-
constructor(message = "Too many requests", retryAfter) {
|
|
163
|
-
super(message, {
|
|
164
|
-
code: "RATE_LIMITED",
|
|
165
|
-
statusCode: 429,
|
|
166
|
-
details: retryAfter ? { retryAfter } : void 0
|
|
167
|
-
});
|
|
168
|
-
this.name = "RateLimitError";
|
|
169
|
-
this.retryAfter = retryAfter;
|
|
170
|
-
}
|
|
171
|
-
};
|
|
172
|
-
/**
|
|
173
|
-
* Service Unavailable Error - 503
|
|
174
|
-
*/
|
|
175
|
-
var ServiceUnavailableError = class extends ArcError {
|
|
176
|
-
constructor(message = "Service temporarily unavailable") {
|
|
177
|
-
super(message, {
|
|
178
|
-
code: "SERVICE_UNAVAILABLE",
|
|
179
|
-
statusCode: 503
|
|
180
|
-
});
|
|
181
|
-
this.name = "ServiceUnavailableError";
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
|
-
/**
|
|
185
|
-
* Create error from status code
|
|
186
|
-
*/
|
|
187
|
-
function createError(statusCode, message, details) {
|
|
188
|
-
return new ArcError(message, {
|
|
189
|
-
code: {
|
|
190
|
-
400: "BAD_REQUEST",
|
|
191
|
-
401: "UNAUTHORIZED",
|
|
192
|
-
403: "FORBIDDEN",
|
|
193
|
-
404: "NOT_FOUND",
|
|
194
|
-
409: "CONFLICT",
|
|
195
|
-
429: "RATE_LIMITED",
|
|
196
|
-
500: "INTERNAL_ERROR",
|
|
197
|
-
503: "SERVICE_UNAVAILABLE"
|
|
198
|
-
}[statusCode] ?? "ERROR",
|
|
199
|
-
statusCode,
|
|
200
|
-
details
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
/**
|
|
204
|
-
* Create a domain-specific error with automatic HTTP status mapping.
|
|
205
|
-
*
|
|
206
|
-
* Eliminates manual `if (err.code === 'X') return status` boilerplate.
|
|
207
|
-
* Arc's error handler automatically maps `statusCode` to HTTP response.
|
|
208
|
-
*
|
|
209
|
-
* @example
|
|
210
|
-
* ```typescript
|
|
211
|
-
* import { createDomainError } from '@classytic/arc';
|
|
212
|
-
*
|
|
213
|
-
* throw createDomainError('MEMBER_NOT_FOUND', 'Member does not exist', 404);
|
|
214
|
-
* throw createDomainError('SELF_REFERRAL', 'Cannot refer yourself', 422);
|
|
215
|
-
* throw createDomainError('INSUFFICIENT_BALANCE', 'Not enough credits', 402, { balance: 0 });
|
|
216
|
-
* ```
|
|
217
|
-
*/
|
|
218
|
-
function createDomainError(code, message, statusCode = 400, details) {
|
|
219
|
-
return new ArcError(message, {
|
|
220
|
-
code,
|
|
221
|
-
statusCode,
|
|
222
|
-
details
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
/**
|
|
226
|
-
* Check if error is an Arc error
|
|
227
|
-
*/
|
|
228
|
-
function isArcError(error) {
|
|
229
|
-
return error instanceof ArcError;
|
|
230
|
-
}
|
|
231
|
-
//#endregion
|
|
232
|
-
export { OrgAccessDeniedError as a, ServiceUnavailableError as c, createDomainError as d, createError as f, NotFoundError as i, UnauthorizedError as l, ConflictError as n, OrgRequiredError as o, isArcError as p, ForbiddenError as r, RateLimitError as s, ArcError as t, ValidationError as u };
|