@classytic/arc 2.10.8 → 2.11.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/dist/{BaseController-DVNKvoX4.mjs → BaseController-JNV08qOT.mjs} +480 -442
- package/dist/{queryCachePlugin-Dumka73q.d.mts → QueryCache-DOBNHBE0.d.mts} +2 -32
- 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 +1 -1
- package/dist/auth/index.d.mts +1 -1
- package/dist/auth/index.mjs +5 -5
- package/dist/{betterAuthOpenApi--rdY15Ld.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 +46 -33
- package/dist/cli/commands/introspect.mjs +1 -1
- package/dist/context/index.mjs +1 -1
- 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-BwnEAO2h.mjs → createApp-P1d6rjPy.mjs} +75 -27
- package/dist/docs/index.d.mts +1 -1
- package/dist/docs/index.mjs +2 -2
- package/dist/{elevation-Dci0AYLT.mjs → elevation-DOFoxoDs.mjs} +1 -1
- package/dist/{errorHandler-CSxe7KIM.mjs → errorHandler-BQm8ZxTK.mjs} +1 -1
- package/dist/{eventPlugin-ByU4Cv0e.mjs → eventPlugin--5HIkdPU.mjs} +1 -1
- package/dist/events/index.d.mts +3 -3
- package/dist/events/index.mjs +2 -2
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/factory/index.d.mts +2 -2
- package/dist/factory/index.mjs +2 -2
- 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 +1 -1
- package/dist/idempotency/redis.d.mts +1 -1
- package/dist/{index-C_Noptz-.d.mts → index-BYCqHCVu.d.mts} +2 -2
- package/dist/{index-BGbpGVyM.d.mts → index-C_bgx9o4.d.mts} +712 -500
- package/dist/{index-BziRPS4H.d.mts → index-CvM1e09j.d.mts} +29 -10
- package/dist/{index-EqQN6p0W.d.mts → index-pUczGjO0.d.mts} +11 -8
- package/dist/index-smCAoA5W.d.mts +1179 -0
- package/dist/index.d.mts +6 -38
- package/dist/index.mjs +9 -9
- package/dist/integrations/event-gateway.d.mts +1 -1
- 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-nWQGUTu1.mjs → keys-CARyUjiR.mjs} +2 -0
- package/dist/{loadResources-Bksk8ydA.mjs → loadResources-CPpkyKfM.mjs} +32 -8
- package/dist/middleware/index.d.mts +1 -1
- package/dist/middleware/index.mjs +1 -1
- package/dist/{openapi-DpNpqBmo.mjs → openapi-C0L9ar7m.mjs} +4 -4
- package/dist/org/index.d.mts +1 -1
- package/dist/permissions/index.d.mts +1 -1
- package/dist/permissions/index.mjs +2 -4
- package/dist/{permissions-wkqRwicB.mjs → permissions-B4vU9L0Q.mjs} +221 -3
- package/dist/{pipe-CGJxqDGx.mjs → pipe-DVoIheVC.mjs} +1 -1
- package/dist/pipeline/index.d.mts +1 -1
- package/dist/pipeline/index.mjs +1 -1
- package/dist/plugins/index.d.mts +4 -4
- 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 +1 -1
- package/dist/presets/filesUpload.mjs +3 -3
- 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 +6 -0
- package/dist/presets/search.d.mts +1 -1
- package/dist/presets/search.mjs +1 -1
- package/dist/{presets-CrwOvuXI.mjs → presets-k604Lj99.mjs} +1 -1
- package/dist/queryCachePlugin-BUXBSm4F.d.mts +34 -0
- package/dist/{queryCachePlugin-ChLNZvFT.mjs → queryCachePlugin-Bq6bO6vc.mjs} +3 -3
- package/dist/{redis-MXLp1oOf.d.mts → redis-Cm1gnRDf.d.mts} +1 -1
- package/dist/registry/index.d.mts +1 -1
- package/dist/registry/index.mjs +2 -2
- package/dist/{resourceToTools-BhF3JV5p.mjs → resourceToTools--okX6QBr.mjs} +534 -420
- package/dist/routerShared-DeESFp4a.mjs +515 -0
- package/dist/schemaIR-BlG9bY7v.mjs +137 -0
- package/dist/scope/index.mjs +2 -2
- package/dist/testing/index.d.mts +367 -711
- package/dist/testing/index.mjs +637 -1434
- package/dist/{tracing-xqXzWeaf.d.mts → tracing-DokiEsuz.d.mts} +9 -4
- package/dist/types/index.d.mts +3 -3
- package/dist/types/index.mjs +1 -3
- package/dist/{types-CVdgPXBW.d.mts → types-BdA4uMBV.d.mts} +191 -28
- package/dist/{types-CVKBssX5.d.mts → types-Bh_gEJBi.d.mts} +1 -1
- package/dist/utils/index.d.mts +2 -968
- package/dist/utils/index.mjs +5 -6
- package/dist/utils-D3Yxnrwr.mjs +1639 -0
- package/dist/websocket-CyJ1VIFI.d.mts +186 -0
- package/package.json +7 -5
- 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-3MWJosCH.mjs +0 -1459
- package/dist/createActionRouter-C8UUB3Px.mjs +0 -249
- package/dist/errors-BI8kEKsO.d.mts +0 -140
- package/dist/fields-CTMWOUDt.mjs +0 -126
- package/dist/queryParser-NR__Qiju.mjs +0 -419
- package/dist/types-CDnTEpga.mjs +0 -27
- package/dist/utils-LMwVidKy.mjs +0 -947
- /package/dist/{HookSystem-BjFu7zf1.mjs → HookSystem-CGsMd6oK.mjs} +0 -0
- /package/dist/{ResourceRegistry-CcN2LVrc.mjs → ResourceRegistry-DkAeAuTX.mjs} +0 -0
- /package/dist/{actionPermissions-TUVR3uiZ.mjs → actionPermissions-C8YYU92K.mjs} +0 -0
- /package/dist/{caching-3h93rkJM.mjs → caching-CheW3m-S.mjs} +0 -0
- /package/dist/{errorHandler-2ii4RIYr.d.mts → errorHandler-Co3lnVmJ.d.mts} +0 -0
- /package/dist/{errors-BqdUDja_.mjs → errors-D5c-5BJL.mjs} +0 -0
- /package/dist/{eventPlugin-D1ThQ1Pp.d.mts → eventPlugin-CUNjYYRY.d.mts} +0 -0
- /package/dist/{interface-B-pe8fhj.d.mts → interface-CkkWm5uR.d.mts} +0 -0
- /package/dist/{interface-yhyb_pLY.d.mts → interface-Da0r7Lna.d.mts} +0 -0
- /package/dist/{memory-DqI-449b.mjs → memory-DikHSvWa.mjs} +0 -0
- /package/dist/{metrics-TuOmguhi.mjs → metrics-Csh4nsvv.mjs} +0 -0
- /package/dist/{multipartBody-CUQGVlM_.mjs → multipartBody-CvTR1Un6.mjs} +0 -0
- /package/dist/{pluralize-CWP6MB39.mjs → pluralize-BneOJkpi.mjs} +0 -0
- /package/dist/{redis-stream-bkO88VHx.d.mts → redis-stream-CM8TXTix.d.mts} +0 -0
- /package/dist/{registry-B0Wl7uVV.mjs → registry-D63ee7fl.mjs} +0 -0
- /package/dist/{replyHelpers-BLojtuvR.mjs → replyHelpers-ByllIXXV.mjs} +0 -0
- /package/dist/{requestContext-C38GskNt.mjs → requestContext-CfRkaxwf.mjs} +0 -0
- /package/dist/{schemaConverter-BxFDdtXu.mjs → schemaConverter-B0oKLuqI.mjs} +0 -0
- /package/dist/{sse-D8UeDwis.mjs → sse-V7aXc3bW.mjs} +0 -0
- /package/dist/{store-helpers-DYYUQbQN.mjs → store-helpers-BhrzxvyQ.mjs} +0 -0
- /package/dist/{typeGuards-Cj5Rgvlg.mjs → typeGuards-CcFZXgU7.mjs} +0 -0
- /package/dist/{types-D57iXYb8.mjs → types-DV9WDfeg.mjs} +0 -0
- /package/dist/{versioning-B6mimogM.mjs → versioning-CGPjkqAg.mjs} +0 -0
- /package/dist/{versioning-CeUXHfjw.d.mts → versioning-M9lNLhO8.d.mts} +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,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 };
|
package/dist/fields-CTMWOUDt.mjs
DELETED
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
//#region src/permissions/fields.ts
|
|
2
|
-
/**
|
|
3
|
-
* Field-Level Permissions
|
|
4
|
-
*
|
|
5
|
-
* Control field visibility and writability per role.
|
|
6
|
-
* Integrated into the response path (read) and sanitization path (write).
|
|
7
|
-
*
|
|
8
|
-
* @example
|
|
9
|
-
* ```typescript
|
|
10
|
-
* import { fields, defineResource } from '@classytic/arc';
|
|
11
|
-
*
|
|
12
|
-
* const userResource = defineResource({
|
|
13
|
-
* name: 'user',
|
|
14
|
-
* adapter: userAdapter,
|
|
15
|
-
* fields: {
|
|
16
|
-
* salary: fields.visibleTo(['admin', 'hr']),
|
|
17
|
-
* internalNotes: fields.writableBy(['admin']),
|
|
18
|
-
* email: fields.redactFor(['viewer']),
|
|
19
|
-
* password: fields.hidden(),
|
|
20
|
-
* },
|
|
21
|
-
* });
|
|
22
|
-
* ```
|
|
23
|
-
*/
|
|
24
|
-
/** Type guard for Mongoose-like documents with toObject() */
|
|
25
|
-
function isMongooseDoc(obj) {
|
|
26
|
-
return !!obj && typeof obj === "object" && "toObject" in obj && typeof obj.toObject === "function";
|
|
27
|
-
}
|
|
28
|
-
const fields = {
|
|
29
|
-
hidden() {
|
|
30
|
-
return { _type: "hidden" };
|
|
31
|
-
},
|
|
32
|
-
visibleTo(roles) {
|
|
33
|
-
return {
|
|
34
|
-
_type: "visibleTo",
|
|
35
|
-
roles
|
|
36
|
-
};
|
|
37
|
-
},
|
|
38
|
-
writableBy(roles) {
|
|
39
|
-
return {
|
|
40
|
-
_type: "writableBy",
|
|
41
|
-
roles
|
|
42
|
-
};
|
|
43
|
-
},
|
|
44
|
-
redactFor(roles, redactValue = "***") {
|
|
45
|
-
return {
|
|
46
|
-
_type: "redactFor",
|
|
47
|
-
roles,
|
|
48
|
-
redactValue
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
/**
|
|
53
|
-
* Apply field-level READ permissions to a response object.
|
|
54
|
-
* Strips hidden fields, enforces visibility, and applies redaction.
|
|
55
|
-
*
|
|
56
|
-
* @param data - The response object (mutated in place for performance)
|
|
57
|
-
* @param fieldPermissions - Field permission map from resource config
|
|
58
|
-
* @param userRoles - Current user's roles (empty array for unauthenticated)
|
|
59
|
-
* @returns The filtered object
|
|
60
|
-
*/
|
|
61
|
-
function applyFieldReadPermissions(data, fieldPermissions, userRoles) {
|
|
62
|
-
if (!data || typeof data !== "object") return data;
|
|
63
|
-
const result = { ...isMongooseDoc(data) ? data.toObject() : data };
|
|
64
|
-
for (const [field, perm] of Object.entries(fieldPermissions)) switch (perm._type) {
|
|
65
|
-
case "hidden":
|
|
66
|
-
delete result[field];
|
|
67
|
-
break;
|
|
68
|
-
case "visibleTo":
|
|
69
|
-
if (!perm.roles?.some((r) => userRoles.includes(r))) delete result[field];
|
|
70
|
-
break;
|
|
71
|
-
case "redactFor":
|
|
72
|
-
if (perm.roles?.some((r) => userRoles.includes(r))) result[field] = perm.redactValue ?? "***";
|
|
73
|
-
break;
|
|
74
|
-
case "writableBy": break;
|
|
75
|
-
}
|
|
76
|
-
return result;
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* Apply field-level WRITE permissions to request body.
|
|
80
|
-
*
|
|
81
|
-
* Returns both the filtered body and the list of denied fields. Callers are
|
|
82
|
-
* expected to reject the request when `deniedFields.length > 0` — silently
|
|
83
|
-
* stripping fields hides misconfigurations and real attacks. See
|
|
84
|
-
* `BodySanitizer` for the default policy.
|
|
85
|
-
*
|
|
86
|
-
* @param body - The request body (returns a new filtered copy)
|
|
87
|
-
* @param fieldPermissions - Field permission map from resource config
|
|
88
|
-
* @param userRoles - Current user's roles
|
|
89
|
-
*/
|
|
90
|
-
function applyFieldWritePermissions(body, fieldPermissions, userRoles) {
|
|
91
|
-
const result = { ...body };
|
|
92
|
-
const deniedFields = [];
|
|
93
|
-
for (const [field, perm] of Object.entries(fieldPermissions)) switch (perm._type) {
|
|
94
|
-
case "hidden":
|
|
95
|
-
if (field in result) {
|
|
96
|
-
deniedFields.push(field);
|
|
97
|
-
delete result[field];
|
|
98
|
-
}
|
|
99
|
-
break;
|
|
100
|
-
case "writableBy":
|
|
101
|
-
if (field in result && !perm.roles?.some((r) => userRoles.includes(r))) {
|
|
102
|
-
deniedFields.push(field);
|
|
103
|
-
delete result[field];
|
|
104
|
-
}
|
|
105
|
-
break;
|
|
106
|
-
}
|
|
107
|
-
return {
|
|
108
|
-
body: result,
|
|
109
|
-
deniedFields
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* Resolve effective roles by merging global user roles with org-level roles.
|
|
114
|
-
*
|
|
115
|
-
* Global roles come from `req.user.role` (normalized via getUserRoles()).
|
|
116
|
-
* Org roles come from `req.context.orgRoles` (set by BA adapter's org bridge).
|
|
117
|
-
*
|
|
118
|
-
* When no org context exists, returns global roles only — backward compatible.
|
|
119
|
-
*/
|
|
120
|
-
function resolveEffectiveRoles(userRoles, orgRoles) {
|
|
121
|
-
if (orgRoles.length === 0) return [...userRoles];
|
|
122
|
-
if (userRoles.length === 0) return [...orgRoles];
|
|
123
|
-
return [...new Set([...userRoles, ...orgRoles])];
|
|
124
|
-
}
|
|
125
|
-
//#endregion
|
|
126
|
-
export { resolveEffectiveRoles as i, applyFieldWritePermissions as n, fields as r, applyFieldReadPermissions as t };
|