@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.
Files changed (166) hide show
  1. package/README.md +16 -12
  2. package/dist/{BaseController-swXruJ2_.mjs → BaseController-DX_T-bDB.mjs} +388 -423
  3. package/dist/EventTransport-CT_52aWU.d.mts +34 -0
  4. package/dist/EventTransport-DLWoUMHy.mjs +103 -0
  5. package/dist/{ResourceRegistry-DkAeAuTX.mjs → ResourceRegistry-CTERg_2x.mjs} +139 -66
  6. package/dist/audit/index.d.mts +2 -2
  7. package/dist/audit/index.mjs +1 -1
  8. package/dist/auth/audit.d.mts +199 -0
  9. package/dist/auth/audit.mjs +288 -0
  10. package/dist/auth/index.d.mts +3 -3
  11. package/dist/auth/index.mjs +117 -191
  12. package/dist/{betterAuthOpenApi-DwxtK3uG.mjs → betterAuthOpenApi--M_i87dQ.mjs} +1 -1
  13. package/dist/buildHandler-olo-gt94.mjs +610 -0
  14. package/dist/cache/index.mjs +3 -3
  15. package/dist/cli/commands/describe.d.mts +89 -13
  16. package/dist/cli/commands/describe.mjs +56 -2
  17. package/dist/cli/commands/docs.mjs +2 -2
  18. package/dist/cli/commands/generate.mjs +147 -48
  19. package/dist/cli/commands/init.d.mts +13 -0
  20. package/dist/cli/commands/init.mjs +130 -87
  21. package/dist/cli/commands/introspect.mjs +8 -1
  22. package/dist/context/index.mjs +1 -1
  23. package/dist/core/index.d.mts +3 -3
  24. package/dist/core/index.mjs +5 -5
  25. package/dist/core-D72ia0EH.mjs +1399 -0
  26. package/dist/{createActionRouter-CIKOcNA7.mjs → createActionRouter-CEvzKcy8.mjs} +7 -20
  27. package/dist/createAggregationRouter-CyecOxnO.mjs +114 -0
  28. package/dist/{createApp-C9bRrqlX.mjs → createApp-XX2-N0Yd.mjs} +28 -22
  29. package/dist/{defineEvent-D1Ky9M1D.mjs → defineEvent-D5h7EvAx.mjs} +1 -1
  30. package/dist/docs/index.d.mts +1 -1
  31. package/dist/docs/index.mjs +2 -2
  32. package/dist/{elevation-DOFoxoDs.mjs → elevation-DgoeTyfX.mjs} +1 -1
  33. package/dist/errorHandler-Bk-AGhkU.mjs +174 -0
  34. package/dist/errorHandler-DFr45ZG4.d.mts +45 -0
  35. package/dist/errors-j4aJm1Wg.mjs +184 -0
  36. package/dist/{eventPlugin-Cts2-Tfj.mjs → eventPlugin-CaKTYkYM.mjs} +28 -4
  37. package/dist/{eventPlugin-DDJoNEPL.d.mts → eventPlugin-qXpqTebY.d.mts} +24 -1
  38. package/dist/events/index.d.mts +6 -6
  39. package/dist/events/index.mjs +11 -35
  40. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  41. package/dist/events/transports/redis.d.mts +1 -1
  42. package/dist/factory/index.d.mts +2 -2
  43. package/dist/factory/index.mjs +2 -2
  44. package/dist/{fields-BRjxOAFp.d.mts → fields-COhcH3fk.d.mts} +23 -2
  45. package/dist/hooks/index.d.mts +1 -1
  46. package/dist/hooks/index.mjs +1 -1
  47. package/dist/idempotency/index.d.mts +1 -1
  48. package/dist/idempotency/index.mjs +1 -20
  49. package/dist/idempotency/redis.mjs +1 -1
  50. package/dist/{index-rHjXmJar.d.mts → index-BTqLEvhu.d.mts} +163 -3
  51. package/dist/{index-CXXRbnf8.d.mts → index-BtW7qYwa.d.mts} +660 -326
  52. package/dist/{index-m8mOOlFW.d.mts → index-Ds61mrJE.d.mts} +50 -4
  53. package/dist/{index-D9t1KNaB.d.mts → index-Dz5IKsrE.d.mts} +360 -219
  54. package/dist/index.d.mts +6 -7
  55. package/dist/index.mjs +9 -10
  56. package/dist/integrations/event-gateway.d.mts +1 -1
  57. package/dist/integrations/event-gateway.mjs +1 -1
  58. package/dist/integrations/index.d.mts +1 -1
  59. package/dist/integrations/mcp/index.d.mts +2 -2
  60. package/dist/integrations/mcp/index.mjs +1 -1
  61. package/dist/integrations/mcp/testing.d.mts +1 -1
  62. package/dist/integrations/mcp/testing.mjs +1 -1
  63. package/dist/integrations/streamline.d.mts +60 -11
  64. package/dist/integrations/streamline.mjs +75 -85
  65. package/dist/integrations/websocket.mjs +2 -8
  66. package/dist/middleware/index.d.mts +1 -1
  67. package/dist/middleware/index.mjs +2 -2
  68. package/dist/migrations/index.d.mts +23 -3
  69. package/dist/migrations/index.mjs +0 -7
  70. package/dist/{multipartBody-CvTR1Un6.mjs → multipartBody-BOvVSVCD.mjs} +11 -8
  71. package/dist/{openapi-D7G1V7ex.mjs → openapi-CiOMVW1p.mjs} +143 -13
  72. package/dist/org/index.d.mts +2 -2
  73. package/dist/org/index.mjs +1 -1
  74. package/dist/permissions/index.d.mts +3 -3
  75. package/dist/permissions/index.mjs +3 -3
  76. package/dist/{permissions-gd_aUWrR.mjs → permissions-ohQyv50e.mjs} +404 -176
  77. package/dist/{pipe-DVoIheVC.mjs → pipe-Zr0KXjQe.mjs} +1 -1
  78. package/dist/pipeline/index.d.mts +1 -1
  79. package/dist/pipeline/index.mjs +1 -1
  80. package/dist/plugins/index.d.mts +16 -31
  81. package/dist/plugins/index.mjs +33 -13
  82. package/dist/plugins/response-cache.mjs +1 -1
  83. package/dist/plugins/tracing-entry.mjs +1 -1
  84. package/dist/presets/filesUpload.d.mts +4 -4
  85. package/dist/presets/filesUpload.mjs +6 -9
  86. package/dist/presets/index.d.mts +1 -1
  87. package/dist/presets/index.mjs +1 -1
  88. package/dist/presets/multiTenant.d.mts +1 -1
  89. package/dist/presets/multiTenant.mjs +2 -2
  90. package/dist/presets/search.d.mts +2 -2
  91. package/dist/presets/search.mjs +6 -8
  92. package/dist/{presets-Z7P5w4gF.mjs → presets-BbkjdPeH.mjs} +6 -28
  93. package/dist/{queryCachePlugin-Bq6bO6vc.mjs → queryCachePlugin-m1XsgAIJ.mjs} +3 -3
  94. package/dist/{redis-stream-xTGxB2bm.d.mts → redis-stream-D6HzR1Z_.d.mts} +1 -1
  95. package/dist/registry/index.d.mts +1 -1
  96. package/dist/registry/index.mjs +2 -2
  97. package/dist/{replyHelpers-ByllIXXV.mjs → replyHelpers-CK-FNO8E.mjs} +3 -21
  98. package/dist/{resourceToTools-CxNmI6xF.mjs → resourceToTools-C5coh64w.mjs} +224 -71
  99. package/dist/{routerShared-BqLRb5l7.mjs → routerShared-D6_fEGHh.mjs} +40 -36
  100. package/dist/{schemaIR-Dy2p4MxS.mjs → schemaIR-7Vl611Qs.mjs} +1 -1
  101. package/dist/schemas/index.d.mts +100 -30
  102. package/dist/schemas/index.mjs +86 -29
  103. package/dist/scim/index.d.mts +264 -0
  104. package/dist/scim/index.mjs +963 -0
  105. package/dist/scope/index.d.mts +3 -3
  106. package/dist/scope/index.mjs +4 -4
  107. package/dist/{sse-V7aXc3bW.mjs → sse-Bz-5ZeTt.mjs} +1 -1
  108. package/dist/{store-helpers-Cp4uKC1U.mjs → store-helpers-BkIN9-vu.mjs} +1 -1
  109. package/dist/testing/index.d.mts +2 -8
  110. package/dist/testing/index.mjs +16 -24
  111. package/dist/types/index.d.mts +4 -4
  112. package/dist/{types-D7KpfiL1.d.mts → types-BvqwCCSx.d.mts} +73 -25
  113. package/dist/{types-DDyTPc6y.d.mts → types-CTYvcwHe.d.mts} +195 -1
  114. package/dist/{types-AOD8fxIw.mjs → types-C_s5moIu.mjs} +117 -1
  115. package/dist/{types-BQ9TJQNy.d.mts → types-DQHFc8PM.d.mts} +1 -1
  116. package/dist/utils/index.d.mts +2 -2
  117. package/dist/utils/index.mjs +5 -5
  118. package/dist/{utils-CcYTj09l.mjs → utils-_h9B3c57.mjs} +1269 -1334
  119. package/dist/{versioning-DsglKfM_.d.mts → versioning-DTTvc80y.d.mts} +1 -1
  120. package/package.json +24 -34
  121. package/skills/arc/SKILL.md +147 -51
  122. package/skills/arc/references/agent-auth.md +238 -0
  123. package/skills/arc/references/api-reference.md +187 -0
  124. package/skills/arc/references/auth.md +354 -7
  125. package/skills/arc/references/enterprise-auth.md +94 -0
  126. package/skills/arc/references/events.md +8 -6
  127. package/skills/arc/references/mcp.md +2 -2
  128. package/skills/arc/references/multi-tenancy.md +11 -2
  129. package/skills/arc/references/production.md +10 -9
  130. package/skills/arc/references/scim.md +247 -0
  131. package/skills/arc/references/testing.md +1 -1
  132. package/skills/arc-code-review/SKILL.md +141 -0
  133. package/skills/arc-code-review/references/anti-patterns.md +911 -0
  134. package/skills/arc-code-review/references/arc-cheatsheet.md +380 -0
  135. package/skills/arc-code-review/references/migration-recipes.md +700 -0
  136. package/skills/arc-code-review/references/mongokit-migration.md +386 -0
  137. package/skills/arc-code-review/references/scaffolding.md +230 -0
  138. package/skills/arc-code-review/references/severity.md +127 -0
  139. package/dist/EventTransport-BFQjw9pB.mjs +0 -133
  140. package/dist/EventTransport-CYNUXdCJ.d.mts +0 -293
  141. package/dist/adapters/index.d.mts +0 -3
  142. package/dist/adapters/index.mjs +0 -2
  143. package/dist/adapters-DUUiiimH.mjs +0 -964
  144. package/dist/auth/mongoose.d.mts +0 -191
  145. package/dist/auth/mongoose.mjs +0 -73
  146. package/dist/core-CbcQRIch.mjs +0 -1054
  147. package/dist/errorHandler-BQm8ZxTK.mjs +0 -173
  148. package/dist/errorHandler-DEWmGWPz.d.mts +0 -114
  149. package/dist/errors-D5c-5BJL.mjs +0 -232
  150. package/dist/index-Rg8axYPz.d.mts +0 -370
  151. /package/dist/{HookSystem-CGsMd6oK.mjs → HookSystem-Iiebom92.mjs} +0 -0
  152. /package/dist/{actionPermissions-sUUKDhtP.mjs → actionPermissions-CyUkQu6O.mjs} +0 -0
  153. /package/dist/{caching-CheW3m-S.mjs → caching-SM8gghN6.mjs} +0 -0
  154. /package/dist/{constants-BhY1OHoH.mjs → constants-Cxde4rpC.mjs} +0 -0
  155. /package/dist/{elevation-BQQXZ_VR.d.mts → elevation-BXOWoGCF.d.mts} +0 -0
  156. /package/dist/{keys-CARyUjiR.mjs → keys-CGcCbNyu.mjs} +0 -0
  157. /package/dist/{loadResources-CPpkyKfM.mjs → loadResources-DBMQg_Aj.mjs} +0 -0
  158. /package/dist/{memory-DikHSvWa.mjs → memory-UBydS5ku.mjs} +0 -0
  159. /package/dist/{metrics-Csh4nsvv.mjs → metrics-Qnvwc-LQ.mjs} +0 -0
  160. /package/dist/{pluralize-CWP6MB39.mjs → pluralize-DQgqgifU.mjs} +0 -0
  161. /package/dist/{registry-D63ee7fl.mjs → registry-I-ogLgL9.mjs} +0 -0
  162. /package/dist/{requestContext-C5XeK3VA.mjs → requestContext-SSaaTgW8.mjs} +0 -0
  163. /package/dist/{schemaConverter-B0oKLuqI.mjs → schemaConverter-De34B1ZG.mjs} +0 -0
  164. /package/dist/{typeGuards-CcFZXgU7.mjs → typeGuards-BzkXkvVv.mjs} +0 -0
  165. /package/dist/{types-DV9WDfeg.mjs → types-D57iXYb8.mjs} +0 -0
  166. /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 };
@@ -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 };