@classytic/arc 2.10.3 → 2.11.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 +1 -1
- package/dist/{BaseController-CbKKIflT.mjs → BaseController-JNV08qOT.mjs} +595 -537
- package/dist/{queryCachePlugin-BKbWjgDG.d.mts → QueryCache-DOBNHBE0.d.mts} +2 -32
- package/dist/actionPermissions-C8YYU92K.mjs +22 -0
- package/dist/adapters/index.d.mts +2 -2
- package/dist/adapters/index.mjs +1 -1
- package/dist/{adapters-BXY4i-hw.mjs → adapters-D0tT2Tyo.mjs} +54 -0
- package/dist/audit/index.d.mts +2 -2
- package/dist/audit/index.mjs +15 -17
- package/dist/auth/index.d.mts +4 -4
- package/dist/auth/index.mjs +3 -3
- package/dist/auth/redis-session.d.mts +1 -1
- package/dist/{betterAuthOpenApi-BBRVhjQN.mjs → betterAuthOpenApi-DwxtK3uG.mjs} +1 -1
- package/dist/cache/index.d.mts +3 -2
- package/dist/cache/index.mjs +3 -3
- package/dist/cli/commands/docs.mjs +2 -2
- package/dist/cli/commands/generate.mjs +37 -27
- package/dist/cli/commands/init.mjs +47 -34
- package/dist/cli/commands/introspect.mjs +1 -1
- package/dist/context/index.d.mts +58 -0
- package/dist/context/index.mjs +2 -0
- package/dist/core/index.d.mts +3 -3
- package/dist/core/index.mjs +4 -3
- package/dist/core-DXdSSFW-.mjs +1037 -0
- package/dist/createActionRouter-BwaSM0No.mjs +166 -0
- package/dist/{createApp-BuvPma24.mjs → createApp-DvNYEhpb.mjs} +118 -36
- package/dist/docs/index.d.mts +2 -2
- package/dist/docs/index.mjs +1 -1
- package/dist/{elevation-C7hgL_aI.mjs → elevation-DOFoxoDs.mjs} +1 -1
- package/dist/errorHandler-Co3lnVmJ.d.mts +114 -0
- package/dist/{eventPlugin-DCUjuiQT.mjs → eventPlugin--5HIkdPU.mjs} +1 -1
- package/dist/{eventPlugin-CxWgpd6K.d.mts → eventPlugin-CUNjYYRY.d.mts} +1 -1
- package/dist/events/index.d.mts +4 -4
- package/dist/events/index.mjs +69 -51
- 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 +1 -1
- package/dist/factory/index.mjs +2 -2
- package/dist/{fields-Lo1VUDpt.d.mts → fields-C8Y0XLAu.d.mts} +1 -1
- package/dist/hooks/index.d.mts +1 -1
- package/dist/hooks/index.mjs +1 -1
- package/dist/idempotency/index.d.mts +3 -3
- package/dist/idempotency/index.mjs +38 -27
- package/dist/idempotency/redis.d.mts +1 -1
- package/dist/{index-ChIw3776.d.mts → index-BYCqHCVu.d.mts} +4 -4
- package/dist/{index-Cl0uoKd5.d.mts → index-Cm0vUrr_.d.mts} +2100 -1688
- package/dist/{index-DStwgFUK.d.mts → index-DAushRTt.d.mts} +29 -10
- package/dist/index-DsJ1MNfC.d.mts +1179 -0
- package/dist/{index-8qw4y6ff.d.mts → index-t8pLpPFW.d.mts} +13 -10
- package/dist/index.d.mts +7 -251
- package/dist/index.mjs +8 -128
- package/dist/integrations/event-gateway.d.mts +2 -2
- package/dist/integrations/event-gateway.mjs +1 -1
- package/dist/integrations/index.d.mts +2 -2
- 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 +46 -5
- package/dist/integrations/streamline.mjs +50 -21
- package/dist/integrations/websocket-redis.d.mts +1 -1
- package/dist/integrations/websocket.d.mts +2 -154
- package/dist/integrations/websocket.mjs +292 -224
- package/dist/{keys-qcD-TVJl.mjs → keys-CARyUjiR.mjs} +2 -0
- package/dist/{loadResources-BAzJItAJ.mjs → loadResources-YNwKHvRA.mjs} +3 -1
- package/dist/logger/index.d.mts +81 -0
- package/dist/{logger-DLg8-Ueg.mjs → logger/index.mjs} +1 -6
- package/dist/middleware/index.d.mts +109 -0
- package/dist/middleware/index.mjs +70 -0
- package/dist/multipartBody-CvTR1Un6.mjs +123 -0
- package/dist/{openapi-B5F8AddX.mjs → openapi-C0L9ar7m.mjs} +9 -7
- package/dist/org/index.d.mts +2 -2
- package/dist/permissions/index.d.mts +2 -2
- package/dist/permissions/index.mjs +1 -3
- package/dist/{permissions-Dk6mshja.mjs → permissions-B4vU9L0Q.mjs} +220 -2
- package/dist/pipe-DVoIheVC.mjs +62 -0
- package/dist/pipeline/index.d.mts +62 -0
- package/dist/pipeline/index.mjs +53 -0
- package/dist/plugins/index.d.mts +25 -5
- package/dist/plugins/index.mjs +10 -10
- package/dist/plugins/response-cache.mjs +1 -1
- package/dist/plugins/tracing-entry.d.mts +1 -1
- package/dist/plugins/tracing-entry.mjs +42 -24
- package/dist/presets/filesUpload.d.mts +4 -4
- package/dist/presets/filesUpload.mjs +255 -1
- package/dist/presets/index.d.mts +1 -1
- package/dist/presets/index.mjs +2 -2
- package/dist/presets/multiTenant.d.mts +1 -1
- package/dist/presets/multiTenant.mjs +48 -8
- package/dist/presets/search.d.mts +2 -2
- package/dist/presets/search.mjs +1 -1
- package/dist/{presets-fLJVXdVn.mjs → presets-k604Lj99.mjs} +1 -1
- package/dist/queryCachePlugin-BUXBSm4F.d.mts +34 -0
- package/dist/{queryCachePlugin-DQCEfJis.mjs → queryCachePlugin-Bq6bO6vc.mjs} +3 -3
- package/dist/{redis-DqyeggCa.d.mts → redis-Cm1gnRDf.d.mts} +1 -1
- package/dist/{redis-stream-CakIQmwR.d.mts → redis-stream-CM8TXTix.d.mts} +1 -1
- package/dist/registry/index.d.mts +1 -1
- package/dist/registry/index.mjs +2 -2
- package/dist/{requestContext-xHIKedG6.mjs → requestContext-CfRkaxwf.mjs} +1 -1
- package/dist/{resourceToTools-BElv3xPT.mjs → resourceToTools--okX6QBr.mjs} +534 -415
- package/dist/routerShared-DeESFp4a.mjs +515 -0
- package/dist/schemaIR-BlG9bY7v.mjs +137 -0
- package/dist/scope/index.d.mts +2 -2
- package/dist/scope/index.mjs +1 -1
- package/dist/{sse-yBCgOLGu.mjs → sse-V7aXc3bW.mjs} +1 -1
- package/dist/{store-helpers-ZCSMJJAX.mjs → store-helpers-BhrzxvyQ.mjs} +4 -0
- package/dist/testing/index.d.mts +367 -711
- package/dist/testing/index.mjs +646 -1434
- package/dist/testing/storageContract.d.mts +1 -1
- package/dist/{tracing-65B51Dw3.d.mts → tracing-DokiEsuz.d.mts} +9 -4
- package/dist/types/index.d.mts +5 -5
- package/dist/types/index.mjs +1 -3
- package/dist/types/storage.d.mts +1 -1
- package/dist/{types-Co8k3NyS.d.mts → types-CgikqKAj.d.mts} +133 -21
- package/dist/{types-Btdda02s.d.mts → types-D9NqiYIw.d.mts} +1 -1
- package/dist/utils/index.d.mts +2 -898
- package/dist/utils/index.mjs +4 -5
- package/dist/utils-D3Yxnrwr.mjs +1639 -0
- package/dist/versioning-M9lNLhO8.d.mts +117 -0
- package/dist/websocket-CyJ1VIFI.d.mts +186 -0
- package/package.json +26 -8
- package/skills/arc/SKILL.md +124 -39
- package/skills/arc/references/testing.md +212 -183
- package/dist/applyPermissionResult-QhV1Pa-g.mjs +0 -37
- package/dist/core-CcR01lup.mjs +0 -1411
- package/dist/createActionRouter-Bp_5c_2b.mjs +0 -249
- package/dist/errorHandler-DRQ3EqfL.d.mts +0 -218
- package/dist/errors-CCSsMpXE.d.mts +0 -140
- package/dist/fields-bxkeltzz.mjs +0 -126
- package/dist/filesUpload-t21LS-py.mjs +0 -377
- package/dist/queryParser-DBqBB6AC.mjs +0 -352
- package/dist/types-Csi3FLfq.mjs +0 -27
- package/dist/utils-B2fNOD_i.mjs +0 -929
- /package/dist/{EventTransport-CUw5NNWe.d.mts → EventTransport-CfVEGaEl.d.mts} +0 -0
- /package/dist/{HookSystem-BNYKnrXF.mjs → HookSystem-CGsMd6oK.mjs} +0 -0
- /package/dist/{ResourceRegistry-BPd6NQDm.mjs → ResourceRegistry-DkAeAuTX.mjs} +0 -0
- /package/dist/{caching-CBpK_SCM.mjs → caching-CheW3m-S.mjs} +0 -0
- /package/dist/{elevation-C5SwtkAn.d.mts → elevation-s5ykdNHr.d.mts} +0 -0
- /package/dist/{errorHandler-Bb49BvPD.mjs → errorHandler-BQm8ZxTK.mjs} +0 -0
- /package/dist/{externalPaths-BQ8QijNH.d.mts → externalPaths-Bapitwvd.d.mts} +0 -0
- /package/dist/{interface-CSbZdv_3.d.mts → interface-CkkWm5uR.d.mts} +0 -0
- /package/dist/{interface-D218ikEo.d.mts → interface-Da0r7Lna.d.mts} +0 -0
- /package/dist/{memory-B5Amv9A1.mjs → memory-DikHSvWa.mjs} +0 -0
- /package/dist/{metrics-DuhiSEZI.mjs → metrics-Csh4nsvv.mjs} +0 -0
- /package/dist/{pluralize-A0tWEl1K.mjs → pluralize-BneOJkpi.mjs} +0 -0
- /package/dist/{registry-B3lRFBWo.mjs → registry-D63ee7fl.mjs} +0 -0
- /package/dist/{replyHelpers-CXtJDAZ0.mjs → replyHelpers-ByllIXXV.mjs} +0 -0
- /package/dist/{schemaConverter-BxFDdtXu.mjs → schemaConverter-B0oKLuqI.mjs} +0 -0
- /package/dist/{sessionManager-BkzVU8h2.d.mts → sessionManager-D-oNWHz3.d.mts} +0 -0
- /package/dist/{storage-CVk_SEn2.d.mts → storage-BwGQXUpd.d.mts} +0 -0
- /package/dist/{typeGuards-Cj5Rgvlg.mjs → typeGuards-CcFZXgU7.mjs} +0 -0
- /package/dist/{types-BD85MlEK.d.mts → types-tgR4Pt8F.d.mts} +0 -0
- /package/dist/{versioning-C2U_bLY0.mjs → versioning-CGPjkqAg.mjs} +0 -0
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
|
|
2
|
-
import { n as normalizePermissionResult, t as applyPermissionResult } from "./applyPermissionResult-QhV1Pa-g.mjs";
|
|
3
|
-
import { a as toJsonSchema } from "./schemaConverter-BxFDdtXu.mjs";
|
|
4
|
-
//#region src/core/createActionRouter.ts
|
|
5
|
-
var createActionRouter_exports = /* @__PURE__ */ __exportAll({
|
|
6
|
-
buildActionBodySchema: () => buildActionBodySchema,
|
|
7
|
-
createActionRouter: () => createActionRouter
|
|
8
|
-
});
|
|
9
|
-
/**
|
|
10
|
-
* Create action-based state transition endpoint
|
|
11
|
-
*
|
|
12
|
-
* Registers: POST /:id/action
|
|
13
|
-
* Body: { action: string, ...actionData }
|
|
14
|
-
*
|
|
15
|
-
* @param fastify - Fastify instance
|
|
16
|
-
* @param config - Action router configuration
|
|
17
|
-
*/
|
|
18
|
-
function createActionRouter(fastify, config) {
|
|
19
|
-
const { tag, actions, actionPermissions = {}, actionSchemas = {}, globalAuth, idempotencyService, onError } = config;
|
|
20
|
-
const actionEnum = Object.keys(actions);
|
|
21
|
-
if (actionEnum.length === 0) {
|
|
22
|
-
fastify.log.warn("[createActionRouter] No actions defined, skipping route creation");
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
const bodySchema = buildActionBodySchema(actionEnum, actionSchemas);
|
|
26
|
-
const routeSchema = {
|
|
27
|
-
tags: tag ? [tag] : void 0,
|
|
28
|
-
summary: `Perform action (${actionEnum.join("/")})`,
|
|
29
|
-
description: buildActionDescription(actions, actionPermissions),
|
|
30
|
-
params: {
|
|
31
|
-
type: "object",
|
|
32
|
-
properties: { id: {
|
|
33
|
-
type: "string",
|
|
34
|
-
description: "Resource ID"
|
|
35
|
-
} },
|
|
36
|
-
required: ["id"]
|
|
37
|
-
},
|
|
38
|
-
body: bodySchema
|
|
39
|
-
};
|
|
40
|
-
const preHandler = [];
|
|
41
|
-
const hasPublicActions = Object.entries(actionPermissions).some(([, p]) => p?._isPublic) || globalAuth && globalAuth?._isPublic;
|
|
42
|
-
const hasProtectedActions = Object.entries(actionPermissions).some(([, p]) => !p?._isPublic) || globalAuth && !globalAuth?._isPublic;
|
|
43
|
-
if (hasProtectedActions && !hasPublicActions && fastify.authenticate) preHandler.push(fastify.authenticate);
|
|
44
|
-
fastify.post("/:id/action", {
|
|
45
|
-
schema: routeSchema,
|
|
46
|
-
preHandler: preHandler.length ? preHandler : void 0
|
|
47
|
-
}, async (req, reply) => {
|
|
48
|
-
const { action, ...data } = req.body;
|
|
49
|
-
const { id } = req.params;
|
|
50
|
-
const rawIdempotencyKey = req.headers["idempotency-key"];
|
|
51
|
-
const idempotencyKey = Array.isArray(rawIdempotencyKey) ? rawIdempotencyKey[0] : rawIdempotencyKey;
|
|
52
|
-
const handler = actions[action];
|
|
53
|
-
if (!handler) return reply.code(400).send({
|
|
54
|
-
success: false,
|
|
55
|
-
error: `Invalid action '${action}'. Valid actions: ${actionEnum.join(", ")}`,
|
|
56
|
-
validActions: actionEnum
|
|
57
|
-
});
|
|
58
|
-
const permissionCheck = actionPermissions[action] ?? globalAuth;
|
|
59
|
-
if (hasPublicActions && hasProtectedActions && permissionCheck) {
|
|
60
|
-
if (!permissionCheck?._isPublic && fastify.authenticate) {
|
|
61
|
-
try {
|
|
62
|
-
await fastify.authenticate(req, reply);
|
|
63
|
-
} catch {
|
|
64
|
-
if (!reply.sent) return reply.code(401).send({
|
|
65
|
-
success: false,
|
|
66
|
-
error: "Authentication required"
|
|
67
|
-
});
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
if (reply.sent) return;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
if (permissionCheck) {
|
|
74
|
-
const context = {
|
|
75
|
-
user: req.user ?? null,
|
|
76
|
-
request: req,
|
|
77
|
-
resource: tag ?? "action",
|
|
78
|
-
action,
|
|
79
|
-
resourceId: id,
|
|
80
|
-
params: req.params,
|
|
81
|
-
data
|
|
82
|
-
};
|
|
83
|
-
let result;
|
|
84
|
-
try {
|
|
85
|
-
result = await permissionCheck(context);
|
|
86
|
-
} catch (err) {
|
|
87
|
-
req.log?.warn?.({
|
|
88
|
-
err,
|
|
89
|
-
resource: tag ?? "action",
|
|
90
|
-
action
|
|
91
|
-
}, "Permission check threw");
|
|
92
|
-
return reply.code(403).send({
|
|
93
|
-
success: false,
|
|
94
|
-
error: "Permission denied"
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
const permResult = normalizePermissionResult(result);
|
|
98
|
-
if (!permResult.granted) return reply.code(context.user ? 403 : 401).send({
|
|
99
|
-
success: false,
|
|
100
|
-
error: permResult.reason ?? (context.user ? `Permission denied for '${action}'` : "Authentication required")
|
|
101
|
-
});
|
|
102
|
-
applyPermissionResult(permResult, req);
|
|
103
|
-
}
|
|
104
|
-
try {
|
|
105
|
-
if (idempotencyKey && idempotencyService) {
|
|
106
|
-
const user = req.user;
|
|
107
|
-
const payloadForHash = {
|
|
108
|
-
action,
|
|
109
|
-
id,
|
|
110
|
-
data,
|
|
111
|
-
userId: (user?._id)?.toString?.() || user?.id || null
|
|
112
|
-
};
|
|
113
|
-
const idempotencyResult = await idempotencyService.check(idempotencyKey, payloadForHash);
|
|
114
|
-
if (!idempotencyResult.isNew && "existingResult" in idempotencyResult) return reply.send({
|
|
115
|
-
success: true,
|
|
116
|
-
data: idempotencyResult.existingResult,
|
|
117
|
-
cached: true
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
const result = await handler(id, data, req);
|
|
121
|
-
if (idempotencyService) await idempotencyService.complete(idempotencyKey, result);
|
|
122
|
-
return reply.send({
|
|
123
|
-
success: true,
|
|
124
|
-
data: result
|
|
125
|
-
});
|
|
126
|
-
} catch (error) {
|
|
127
|
-
if (idempotencyService) await idempotencyService.fail(idempotencyKey, error);
|
|
128
|
-
if (onError) {
|
|
129
|
-
const { statusCode, error: errorMsg, code } = onError(error, action, id);
|
|
130
|
-
return reply.code(statusCode).send({
|
|
131
|
-
success: false,
|
|
132
|
-
error: errorMsg,
|
|
133
|
-
code
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
const err = error;
|
|
137
|
-
const statusCode = err.statusCode || err.status || 500;
|
|
138
|
-
const errorCode = err.code || "ACTION_FAILED";
|
|
139
|
-
if (statusCode >= 500) req.log.error({
|
|
140
|
-
err: error,
|
|
141
|
-
action,
|
|
142
|
-
id
|
|
143
|
-
}, "Action handler error");
|
|
144
|
-
return reply.code(statusCode).send({
|
|
145
|
-
success: false,
|
|
146
|
-
error: err.message || `Failed to execute '${action}' action`,
|
|
147
|
-
code: errorCode
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
fastify.log.debug({
|
|
152
|
-
actions: actionEnum,
|
|
153
|
-
tag
|
|
154
|
-
}, "[createActionRouter] Registered action endpoint: POST /:id/action");
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* Build a discriminated body schema for the unified action endpoint.
|
|
158
|
-
*
|
|
159
|
-
* Produces a schema of the form:
|
|
160
|
-
* ```json
|
|
161
|
-
* {
|
|
162
|
-
* "type": "object",
|
|
163
|
-
* "required": ["action"],
|
|
164
|
-
* "oneOf": [
|
|
165
|
-
* { "properties": { "action": { "const": "dispatch" }, "carrier": {...} }, "required": ["action", "carrier"] },
|
|
166
|
-
* { "properties": { "action": { "const": "approve" } }, "required": ["action"] }
|
|
167
|
-
* ]
|
|
168
|
-
* }
|
|
169
|
-
* ```
|
|
170
|
-
*
|
|
171
|
-
* AJV validates this natively, so an action call missing required fields is
|
|
172
|
-
* rejected with HTTP 400 before the handler ever runs.
|
|
173
|
-
*
|
|
174
|
-
* Exported so OpenAPI generation and MCP tool generation can reuse the same
|
|
175
|
-
* schema shape (single source of truth).
|
|
176
|
-
*/
|
|
177
|
-
function buildActionBodySchema(actionEnum, actionSchemas = {}) {
|
|
178
|
-
const branches = [];
|
|
179
|
-
for (const actionName of actionEnum) {
|
|
180
|
-
const raw = actionSchemas[actionName];
|
|
181
|
-
const { properties, required } = normalizeActionSchema(raw);
|
|
182
|
-
const branchProperties = {
|
|
183
|
-
action: {
|
|
184
|
-
type: "string",
|
|
185
|
-
const: actionName
|
|
186
|
-
},
|
|
187
|
-
...properties
|
|
188
|
-
};
|
|
189
|
-
const branchRequired = ["action", ...required.filter((r) => r !== "action")];
|
|
190
|
-
branches.push({
|
|
191
|
-
type: "object",
|
|
192
|
-
properties: branchProperties,
|
|
193
|
-
required: branchRequired
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
return {
|
|
197
|
-
type: "object",
|
|
198
|
-
required: ["action"],
|
|
199
|
-
oneOf: branches
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
/**
|
|
203
|
-
* Normalize the accepted schema shapes into `{ properties, required }`.
|
|
204
|
-
*
|
|
205
|
-
* Handles:
|
|
206
|
-
* 1. Full JSON Schema object (has `type: 'object'` + `properties`)
|
|
207
|
-
* 2. Zod v4 schema (has `_zod` marker) — converted via `toJsonSchema`
|
|
208
|
-
* 3. Legacy field map (`{ fieldName: { type: 'string' } }`) — every field required
|
|
209
|
-
* unless its schema has `nullable: true` or sentinel `required: false`
|
|
210
|
-
*/
|
|
211
|
-
function normalizeActionSchema(raw) {
|
|
212
|
-
if (!raw || typeof raw !== "object") return {
|
|
213
|
-
properties: {},
|
|
214
|
-
required: []
|
|
215
|
-
};
|
|
216
|
-
const converted = toJsonSchema(raw);
|
|
217
|
-
if (converted && typeof converted === "object" && (converted.type === "object" || "properties" in converted)) return {
|
|
218
|
-
properties: converted.properties ?? {},
|
|
219
|
-
required: Array.isArray(converted.required) ? converted.required : []
|
|
220
|
-
};
|
|
221
|
-
const properties = {};
|
|
222
|
-
const required = [];
|
|
223
|
-
for (const [fieldName, fieldSchema] of Object.entries(raw)) {
|
|
224
|
-
if (fieldName === "type" || fieldName === "properties" || fieldName === "required") continue;
|
|
225
|
-
if (!fieldSchema || typeof fieldSchema !== "object") continue;
|
|
226
|
-
const fs = fieldSchema;
|
|
227
|
-
properties[fieldName] = fs;
|
|
228
|
-
if (fs.required !== false) required.push(fieldName);
|
|
229
|
-
}
|
|
230
|
-
return {
|
|
231
|
-
properties,
|
|
232
|
-
required
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
/**
|
|
236
|
-
* Build description with action details
|
|
237
|
-
* Uses _roles metadata from PermissionCheck functions for OpenAPI docs
|
|
238
|
-
*/
|
|
239
|
-
function buildActionDescription(actions, actionPermissions) {
|
|
240
|
-
const lines = ["Unified action endpoint for state transitions.\n\n**Available actions:**"];
|
|
241
|
-
Object.keys(actions).forEach((action) => {
|
|
242
|
-
const roles = actionPermissions[action]?._roles;
|
|
243
|
-
const roleStr = roles?.length ? ` (requires: ${roles.join(" or ")})` : "";
|
|
244
|
-
lines.push(`- \`${action}\`${roleStr}`);
|
|
245
|
-
});
|
|
246
|
-
return lines.join("\n");
|
|
247
|
-
}
|
|
248
|
-
//#endregion
|
|
249
|
-
export { createActionRouter_exports as n, buildActionBodySchema as t };
|
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
import { n as DomainEvent } from "./EventTransport-CUw5NNWe.mjs";
|
|
2
|
-
import { FastifyInstance, FastifyPluginAsync, FastifyRequest } from "fastify";
|
|
3
|
-
|
|
4
|
-
//#region src/plugins/caching.d.ts
|
|
5
|
-
interface CachingRule {
|
|
6
|
-
/** Path prefix to match (e.g., '/api/products') */
|
|
7
|
-
match: string;
|
|
8
|
-
/** Cache-Control max-age in seconds */
|
|
9
|
-
maxAge: number;
|
|
10
|
-
/** Cache-Control: private vs public (default: public) */
|
|
11
|
-
private?: boolean;
|
|
12
|
-
/** stale-while-revalidate directive in seconds */
|
|
13
|
-
staleWhileRevalidate?: number;
|
|
14
|
-
}
|
|
15
|
-
interface CachingOptions {
|
|
16
|
-
/** Default max-age in seconds for Cache-Control (default: 0 = no-cache) */
|
|
17
|
-
maxAge?: number;
|
|
18
|
-
/** Enable ETag generation (default: true) */
|
|
19
|
-
etag?: boolean;
|
|
20
|
-
/** Enable conditional requests — 304 Not Modified (default: true) */
|
|
21
|
-
conditional?: boolean;
|
|
22
|
-
/** HTTP methods to cache (default: ['GET', 'HEAD']) */
|
|
23
|
-
methods?: string[];
|
|
24
|
-
/** Paths to exclude from caching (prefix match) */
|
|
25
|
-
exclude?: string[];
|
|
26
|
-
/** Custom cache rules per path prefix */
|
|
27
|
-
rules?: CachingRule[];
|
|
28
|
-
}
|
|
29
|
-
declare const cachingPlugin: FastifyPluginAsync<CachingOptions>;
|
|
30
|
-
declare const _default$3: FastifyPluginAsync<CachingOptions>;
|
|
31
|
-
//#endregion
|
|
32
|
-
//#region src/plugins/sse.d.ts
|
|
33
|
-
interface SSEOptions {
|
|
34
|
-
/** SSE endpoint path (default: '/events/stream') */
|
|
35
|
-
path?: string;
|
|
36
|
-
/** Require authentication (default: true) */
|
|
37
|
-
requireAuth?: boolean;
|
|
38
|
-
/** Event patterns to stream (default: ['*'] = all) */
|
|
39
|
-
patterns?: string[];
|
|
40
|
-
/** Heartbeat interval in ms (default: 30000) */
|
|
41
|
-
heartbeat?: number;
|
|
42
|
-
/** Filter events by organizationId from request.scope (default: false) */
|
|
43
|
-
orgScoped?: boolean;
|
|
44
|
-
/** Custom event filter function */
|
|
45
|
-
filter?: (event: DomainEvent<unknown>, request: FastifyRequest) => boolean;
|
|
46
|
-
}
|
|
47
|
-
declare const ssePlugin: FastifyPluginAsync<SSEOptions>;
|
|
48
|
-
declare const _default$2: FastifyPluginAsync<SSEOptions>;
|
|
49
|
-
//#endregion
|
|
50
|
-
//#region src/plugins/metrics.d.ts
|
|
51
|
-
interface MetricsOptions {
|
|
52
|
-
/** Endpoint path (default: '/_metrics') */
|
|
53
|
-
path?: string;
|
|
54
|
-
/** Prefix for all metric names (default: 'arc') */
|
|
55
|
-
prefix?: string;
|
|
56
|
-
/** Called after metrics are collected (for OTLP push, etc.) */
|
|
57
|
-
onCollect?: (metrics: MetricEntry[]) => void;
|
|
58
|
-
}
|
|
59
|
-
interface MetricEntry {
|
|
60
|
-
name: string;
|
|
61
|
-
type: "counter" | "histogram" | "gauge";
|
|
62
|
-
help: string;
|
|
63
|
-
values: Array<{
|
|
64
|
-
labels: Record<string, string>;
|
|
65
|
-
value: number;
|
|
66
|
-
}>;
|
|
67
|
-
}
|
|
68
|
-
interface MetricsCollector {
|
|
69
|
-
/** Get all metrics as structured data */
|
|
70
|
-
collect(): MetricEntry[];
|
|
71
|
-
/** Reset all metrics */
|
|
72
|
-
reset(): void;
|
|
73
|
-
/** Record a CRUD operation */
|
|
74
|
-
recordOperation(resource: string, operation: string, status: number, durationMs: number): void;
|
|
75
|
-
/** Record a cache hit */
|
|
76
|
-
recordCacheHit(resource: string): void;
|
|
77
|
-
/** Record a cache miss */
|
|
78
|
-
recordCacheMiss(resource: string): void;
|
|
79
|
-
/** Record an event publish */
|
|
80
|
-
recordEventPublish(eventType: string): void;
|
|
81
|
-
/** Record an event consume */
|
|
82
|
-
recordEventConsume(eventType: string): void;
|
|
83
|
-
/** Record a circuit breaker state change */
|
|
84
|
-
recordCircuitBreakerState(service: string, state: string): void;
|
|
85
|
-
}
|
|
86
|
-
declare module "fastify" {
|
|
87
|
-
interface FastifyInstance {
|
|
88
|
-
metrics: MetricsCollector;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
declare const metricsPlugin: FastifyPluginAsync<MetricsOptions>;
|
|
92
|
-
declare const _default$1: FastifyPluginAsync<MetricsOptions>;
|
|
93
|
-
//#endregion
|
|
94
|
-
//#region src/plugins/versioning.d.ts
|
|
95
|
-
interface VersioningOptions {
|
|
96
|
-
/** Versioning strategy */
|
|
97
|
-
type: "header" | "prefix";
|
|
98
|
-
/** Default version when none specified (default: '1') */
|
|
99
|
-
defaultVersion?: string;
|
|
100
|
-
/** Header name to read (default: 'accept-version') */
|
|
101
|
-
headerName?: string;
|
|
102
|
-
/** Response header name (default: 'x-api-version') */
|
|
103
|
-
responseHeader?: string;
|
|
104
|
-
/** Deprecated versions — adds Deprecation + Sunset headers */
|
|
105
|
-
deprecated?: string[];
|
|
106
|
-
/** Sunset date for deprecated versions (ISO 8601) */
|
|
107
|
-
sunset?: string;
|
|
108
|
-
}
|
|
109
|
-
declare module "fastify" {
|
|
110
|
-
interface FastifyRequest {
|
|
111
|
-
apiVersion: string;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
declare const versioningPlugin: FastifyPluginAsync<VersioningOptions>;
|
|
115
|
-
declare const _default: FastifyPluginAsync<VersioningOptions>;
|
|
116
|
-
//#endregion
|
|
117
|
-
//#region src/plugins/errorHandler.d.ts
|
|
118
|
-
/** Class-based error mapper — maps thrown error instances to HTTP responses */
|
|
119
|
-
interface ErrorMapper<T extends Error = Error> {
|
|
120
|
-
/** Error class to match (uses instanceof) */
|
|
121
|
-
type: new (...args: unknown[]) => T;
|
|
122
|
-
/** Convert the error to an HTTP response shape */
|
|
123
|
-
toResponse: (error: T) => {
|
|
124
|
-
status: number;
|
|
125
|
-
code?: string;
|
|
126
|
-
message?: string;
|
|
127
|
-
details?: Record<string, unknown>;
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
interface ErrorHandlerOptions {
|
|
131
|
-
/**
|
|
132
|
-
* Include stack trace in error responses (default: false in production)
|
|
133
|
-
*/
|
|
134
|
-
includeStack?: boolean;
|
|
135
|
-
/**
|
|
136
|
-
* Custom error callback for logging to external services
|
|
137
|
-
*/
|
|
138
|
-
onError?: (error: Error, request: FastifyRequest) => void | Promise<void>;
|
|
139
|
-
/**
|
|
140
|
-
* Map specific error types to custom responses (by error.name string)
|
|
141
|
-
*/
|
|
142
|
-
errorMap?: Record<string, {
|
|
143
|
-
statusCode: number;
|
|
144
|
-
code: string;
|
|
145
|
-
message?: string;
|
|
146
|
-
}>;
|
|
147
|
-
/**
|
|
148
|
-
* Class-based error mappers — checked via `instanceof`, highest priority.
|
|
149
|
-
*
|
|
150
|
-
* Register your domain error classes once; Arc auto-catches and maps them
|
|
151
|
-
* in every handler. Handlers just `throw` — no try/catch needed.
|
|
152
|
-
*
|
|
153
|
-
* @example
|
|
154
|
-
* ```typescript
|
|
155
|
-
* class AccountingError extends Error {
|
|
156
|
-
* constructor(message: string, public status: number, public code: string) {
|
|
157
|
-
* super(message);
|
|
158
|
-
* }
|
|
159
|
-
* }
|
|
160
|
-
*
|
|
161
|
-
* const app = await createApp({
|
|
162
|
-
* errorHandler: {
|
|
163
|
-
* errorMappers: [
|
|
164
|
-
* {
|
|
165
|
-
* type: AccountingError,
|
|
166
|
-
* toResponse: (err) => ({ status: err.status, code: err.code, message: err.message }),
|
|
167
|
-
* },
|
|
168
|
-
* ],
|
|
169
|
-
* },
|
|
170
|
-
* });
|
|
171
|
-
*
|
|
172
|
-
* // Now handlers just throw:
|
|
173
|
-
* handler: async (req) => {
|
|
174
|
-
* await ledger.post(id); // throws AccountingError → Arc maps to proper HTTP response
|
|
175
|
-
* }
|
|
176
|
-
* ```
|
|
177
|
-
*/
|
|
178
|
-
errorMappers?: ErrorMapper[];
|
|
179
|
-
/**
|
|
180
|
-
* Classify an error as a duplicate-key / unique-constraint violation →
|
|
181
|
-
* mapped to `409 Conflict` with `code: "DUPLICATE_KEY"`.
|
|
182
|
-
*
|
|
183
|
-
* Mirrors `RepositoryLike.isDuplicateKeyError` for the Fastify layer: errors
|
|
184
|
-
* that escape a controller (custom routes, user hooks, raw driver calls)
|
|
185
|
-
* still land here, so the classifier is duplicated at the edge. Defaults
|
|
186
|
-
* cover MongoDB (`code 11000` / `codeName "DuplicateKey"`), Prisma
|
|
187
|
-
* (`code "P2002"`), and Postgres (`code "23505"`). Override to add other
|
|
188
|
-
* backends (DynamoDB `ConditionalCheckFailedException`, etc.) or to disable
|
|
189
|
-
* the built-in detection.
|
|
190
|
-
*/
|
|
191
|
-
isDuplicateKeyError?: (err: unknown) => boolean;
|
|
192
|
-
}
|
|
193
|
-
/**
|
|
194
|
-
* Default duplicate-key detector covering the mainstream drivers arc sees
|
|
195
|
-
* most. Detection is strictly by known driver codes — never by message
|
|
196
|
-
* string matching — because false positives on dup-key silently mask real
|
|
197
|
-
* errors (WriteConflict, NotWritablePrimary, etc.) as 409s. For long-tail
|
|
198
|
-
* drivers (Neo4j, MSSQL, DynamoDB, custom kits), compose rather than
|
|
199
|
-
* replace:
|
|
200
|
-
*
|
|
201
|
-
* ```ts
|
|
202
|
-
* import { defaultIsDuplicateKeyError } from '@classytic/arc/plugins';
|
|
203
|
-
*
|
|
204
|
-
* errorHandler: {
|
|
205
|
-
* isDuplicateKeyError: (err) =>
|
|
206
|
-
* defaultIsDuplicateKeyError(err) || isNeo4jDupKey(err),
|
|
207
|
-
* }
|
|
208
|
-
* ```
|
|
209
|
-
*
|
|
210
|
-
* Drizzle apps get coverage transitively (Drizzle doesn't wrap driver
|
|
211
|
-
* errors — pg/mysql2/better-sqlite3 codes propagate as-is). Neon is
|
|
212
|
-
* Postgres-wire-compatible → `23505` covers `@neondatabase/serverless`.
|
|
213
|
-
*/
|
|
214
|
-
declare function defaultIsDuplicateKeyError(err: unknown): boolean;
|
|
215
|
-
declare function errorHandlerPluginFn(fastify: FastifyInstance, options?: ErrorHandlerOptions): Promise<void>;
|
|
216
|
-
declare const errorHandlerPlugin: typeof errorHandlerPluginFn;
|
|
217
|
-
//#endregion
|
|
218
|
-
export { CachingRule as _, VersioningOptions as a, MetricEntry as c, _default$1 as d, metricsPlugin as f, CachingOptions as g, ssePlugin as h, errorHandlerPlugin as i, MetricsCollector as l, _default$2 as m, ErrorMapper as n, _default as o, SSEOptions as p, defaultIsDuplicateKeyError as r, versioningPlugin as s, ErrorHandlerOptions as t, MetricsOptions as u, _default$3 as v, cachingPlugin as y };
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
//#region src/utils/errors.d.ts
|
|
2
|
-
/**
|
|
3
|
-
* Error Classes
|
|
4
|
-
*
|
|
5
|
-
* Standard error types for the Arc framework.
|
|
6
|
-
*/
|
|
7
|
-
interface ErrorDetails {
|
|
8
|
-
code?: string;
|
|
9
|
-
statusCode?: number;
|
|
10
|
-
details?: Record<string, unknown>;
|
|
11
|
-
cause?: Error;
|
|
12
|
-
requestId?: string;
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Base Arc Error
|
|
16
|
-
*
|
|
17
|
-
* All Arc errors extend this class and produce a consistent error envelope:
|
|
18
|
-
* {
|
|
19
|
-
* success: false,
|
|
20
|
-
* error: "Human-readable message",
|
|
21
|
-
* code: "MACHINE_CODE",
|
|
22
|
-
* requestId: "uuid", // For tracing
|
|
23
|
-
* timestamp: "ISO date", // When error occurred
|
|
24
|
-
* details: { ... } // Additional context
|
|
25
|
-
* }
|
|
26
|
-
*/
|
|
27
|
-
declare class ArcError extends Error {
|
|
28
|
-
name: string;
|
|
29
|
-
readonly code: string;
|
|
30
|
-
readonly statusCode: number;
|
|
31
|
-
readonly details?: Record<string, unknown>;
|
|
32
|
-
readonly cause?: Error;
|
|
33
|
-
readonly timestamp: string;
|
|
34
|
-
requestId?: string;
|
|
35
|
-
constructor(message: string, options?: ErrorDetails);
|
|
36
|
-
/**
|
|
37
|
-
* Set request ID (typically from request context)
|
|
38
|
-
*/
|
|
39
|
-
withRequestId(requestId: string): this;
|
|
40
|
-
/**
|
|
41
|
-
* Convert to JSON response.
|
|
42
|
-
* Includes cause chain when present for debugging visibility.
|
|
43
|
-
*/
|
|
44
|
-
toJSON(): Record<string, unknown>;
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Not Found Error - 404
|
|
48
|
-
*/
|
|
49
|
-
declare class NotFoundError extends ArcError {
|
|
50
|
-
constructor(resource: string, identifier?: string);
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Validation Error - 400
|
|
54
|
-
*/
|
|
55
|
-
declare class ValidationError extends ArcError {
|
|
56
|
-
readonly errors: Array<{
|
|
57
|
-
field: string;
|
|
58
|
-
message: string;
|
|
59
|
-
}>;
|
|
60
|
-
constructor(message: string, errors?: Array<{
|
|
61
|
-
field: string;
|
|
62
|
-
message: string;
|
|
63
|
-
}>);
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Unauthorized Error - 401
|
|
67
|
-
*/
|
|
68
|
-
declare class UnauthorizedError extends ArcError {
|
|
69
|
-
constructor(message?: string);
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Forbidden Error - 403
|
|
73
|
-
*/
|
|
74
|
-
declare class ForbiddenError extends ArcError {
|
|
75
|
-
constructor(message?: string);
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* Conflict Error - 409
|
|
79
|
-
*/
|
|
80
|
-
declare class ConflictError extends ArcError {
|
|
81
|
-
constructor(message: string, field?: string);
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Organization Required Error - 403
|
|
85
|
-
*/
|
|
86
|
-
declare class OrgRequiredError extends ArcError {
|
|
87
|
-
readonly organizations?: Array<{
|
|
88
|
-
id: string;
|
|
89
|
-
roles?: string[];
|
|
90
|
-
}>;
|
|
91
|
-
constructor(message: string, organizations?: Array<{
|
|
92
|
-
id: string;
|
|
93
|
-
roles?: string[];
|
|
94
|
-
}>);
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* Organization Access Denied Error - 403
|
|
98
|
-
*/
|
|
99
|
-
declare class OrgAccessDeniedError extends ArcError {
|
|
100
|
-
constructor(orgId?: string);
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* Rate Limit Error - 429
|
|
104
|
-
*/
|
|
105
|
-
declare class RateLimitError extends ArcError {
|
|
106
|
-
readonly retryAfter?: number;
|
|
107
|
-
constructor(message?: string, retryAfter?: number);
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Service Unavailable Error - 503
|
|
111
|
-
*/
|
|
112
|
-
declare class ServiceUnavailableError extends ArcError {
|
|
113
|
-
constructor(message?: string);
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* Create error from status code
|
|
117
|
-
*/
|
|
118
|
-
declare function createError(statusCode: number, message: string, details?: Record<string, unknown>): ArcError;
|
|
119
|
-
/**
|
|
120
|
-
* Create a domain-specific error with automatic HTTP status mapping.
|
|
121
|
-
*
|
|
122
|
-
* Eliminates manual `if (err.code === 'X') return status` boilerplate.
|
|
123
|
-
* Arc's error handler automatically maps `statusCode` to HTTP response.
|
|
124
|
-
*
|
|
125
|
-
* @example
|
|
126
|
-
* ```typescript
|
|
127
|
-
* import { createDomainError } from '@classytic/arc';
|
|
128
|
-
*
|
|
129
|
-
* throw createDomainError('MEMBER_NOT_FOUND', 'Member does not exist', 404);
|
|
130
|
-
* throw createDomainError('SELF_REFERRAL', 'Cannot refer yourself', 422);
|
|
131
|
-
* throw createDomainError('INSUFFICIENT_BALANCE', 'Not enough credits', 402, { balance: 0 });
|
|
132
|
-
* ```
|
|
133
|
-
*/
|
|
134
|
-
declare function createDomainError(code: string, message: string, statusCode?: number, details?: Record<string, unknown>): ArcError;
|
|
135
|
-
/**
|
|
136
|
-
* Check if error is an Arc error
|
|
137
|
-
*/
|
|
138
|
-
declare function isArcError(error: unknown): error is ArcError;
|
|
139
|
-
//#endregion
|
|
140
|
-
export { NotFoundError as a, RateLimitError as c, ValidationError as d, createDomainError as f, ForbiddenError as i, ServiceUnavailableError as l, isArcError as m, ConflictError as n, OrgAccessDeniedError as o, createError as p, ErrorDetails as r, OrgRequiredError as s, ArcError as t, UnauthorizedError as u };
|