@classytic/arc 2.18.4 → 2.18.5
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-B-uXzf6I.mjs → BaseController-Dy6EQHjM.mjs} +7 -7
- package/dist/{QueryCache-Dsyv2mLS.d.mts → QueryCache-TRfXau3W.d.mts} +1 -1
- package/dist/{ResourceRegistry-CWmOggxS.mjs → ResourceRegistry-5fKZwhK_.mjs} +1 -1
- package/dist/{pipeline-D4sqiokR.mjs → actionPermissions-h7DYDNMg.mjs} +28 -5
- package/dist/audit/index.d.mts +1 -1
- package/dist/auth/index.d.mts +4 -4
- package/dist/auth/index.mjs +252 -5
- package/dist/auth/redis-session.d.mts +1 -1
- package/dist/betterAuthOpenApi-CV1lxpRX.mjs +2 -0
- package/dist/{buildHandler-Cvrc2p7r.mjs → buildHandler-B0qvAguf.mjs} +1 -1
- package/dist/cache/index.d.mts +3 -3
- package/dist/cache/index.mjs +3 -3
- package/dist/{caching-C0DdKiYB.mjs → caching-_NJeDd1_.mjs} +1 -1
- package/dist/cli/commands/describe.d.mts +1 -1
- package/dist/cli/commands/describe.mjs +2 -2
- package/dist/cli/commands/docs.mjs +2 -2
- package/dist/cli/commands/generate.d.mts +2 -2
- package/dist/cli/commands/generate.mjs +1 -1
- package/dist/cli/commands/init.d.mts +2 -2
- package/dist/cli/commands/init.mjs +1 -1
- package/dist/cli/commands/introspect.mjs +1 -1
- package/dist/cli/index.d.mts +2 -2
- package/dist/cli/index.mjs +2 -2
- package/dist/context/index.mjs +1 -1
- package/dist/core/index.d.mts +3 -3
- package/dist/core/index.mjs +4 -4
- package/dist/{core-C88uIwd3.mjs → core-ZnQBa5rh.mjs} +55 -56
- package/dist/{createActionRouter-DGCCp-kP.mjs → createActionRouter-CagItpxI.mjs} +6 -7
- package/dist/{createAggregationRouter-Ca77b8iK.mjs → createAggregationRouter-DVKroXhC.mjs} +4 -4
- package/dist/{createApp-J1iPXY9p.mjs → createApp--703hQE1.mjs} +14 -14
- package/dist/docs/index.d.mts +2 -2
- package/dist/docs/index.mjs +2 -2
- package/dist/{elevation-BzhyVWfn.mjs → elevation-COpYKt2h.mjs} +2 -2
- package/dist/{errorHandler-Bua97SEt.mjs → errorHandler-Dxf6P_kV.mjs} +2 -2
- package/dist/{eventPlugin-Bw4bUDEO.mjs → eventPlugin-DJG2D7sy.mjs} +106 -5
- package/dist/{eventPlugin-C1W1CRbf.d.mts → eventPlugin-DbBFd3x_.d.mts} +1 -1
- package/dist/events/index.d.mts +3 -3
- package/dist/events/index.mjs +189 -5
- 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 +3 -3
- package/dist/{fastify-augmentation-BdCl_eNo.d.mts → fastify-augmentation-Cxb0ROIw.d.mts} +2 -2
- package/dist/{fields-CKLrhyOb.d.mts → fields-C7ahdGYf.d.mts} +1 -1
- package/dist/{generate-BaxDdmJ8.mjs → generate-Brans3fW.mjs} +1 -1
- package/dist/hooks/index.d.mts +1 -1
- package/dist/hooks/index.mjs +1 -1
- package/dist/idempotency/index.d.mts +2 -2
- package/dist/idempotency/index.mjs +1 -1
- package/dist/idempotency/redis.d.mts +1 -1
- package/dist/{index-QYma8mdC.d.mts → index-CHNQIhcb.d.mts} +5 -6
- package/dist/{index-vcWUGkwf.d.mts → index-CRKiiP5o.d.mts} +3 -3
- package/dist/{index-Cvo1u9n6.d.mts → index-Dh0_RSTd.d.mts} +3 -3
- package/dist/index.d.mts +5 -5
- package/dist/index.mjs +7 -7
- package/dist/{init-DSsdRfk9.mjs → init-Bqpt1Hxf.mjs} +274 -130
- package/dist/integrations/event-gateway.d.mts +2 -2
- package/dist/integrations/event-gateway.mjs +1 -1
- package/dist/integrations/index.d.mts +3 -3
- package/dist/integrations/mcp/index.d.mts +4 -4
- 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.mjs +7 -3
- package/dist/integrations/websocket-pushref-redis.d.mts +1 -1
- package/dist/integrations/websocket-redis.d.mts +1 -1
- package/dist/integrations/websocket.d.mts +3 -3
- package/dist/{loadResources-CwXt9MCh.mjs → loadResources-B8UmpITt.mjs} +1 -1
- package/dist/{loadResourcesFromEntry-XHNeUWpu.mjs → loadResourcesFromEntry-BTlFEx05.mjs} +1 -1
- package/dist/{mcpPlugin-CzKdt3B8.mjs → mcpPlugin-D1wuRD2c.mjs} +10 -11
- package/dist/{memory-TdTHCS9-.mjs → memory-CLXA2VeJ.mjs} +1 -1
- package/dist/{metrics-Da2EUPi9.mjs → metrics-fdxI5mn_.mjs} +1 -1
- package/dist/middleware/index.d.mts +1 -1
- package/dist/middleware/index.mjs +2 -2
- package/dist/{openapi-ButnRyOM.mjs → openapi-xVJibapr.mjs} +5 -5
- package/dist/permissions/index.d.mts +3 -3
- package/dist/permissions/index.mjs +2 -2
- package/dist/{permissions-3uzwkbQt.mjs → permissions-BPB1xqGf.mjs} +4 -4
- package/dist/{pipe-9Tl-OAus.mjs → pipe-BOLmGIxa.mjs} +1 -1
- package/dist/pipeline/index.d.mts +1 -1
- package/dist/pipeline/index.mjs +1 -1
- package/dist/plugins/index.d.mts +6 -7
- package/dist/plugins/index.mjs +12 -12
- package/dist/plugins/response-cache.mjs +1 -1
- package/dist/plugins/tracing-entry.d.mts +1 -1
- package/dist/plugins/tracing-entry.mjs +1 -1
- package/dist/presets/filesUpload.d.mts +4 -4
- package/dist/presets/filesUpload.mjs +4 -4
- 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 +1 -1
- package/dist/{presets-CF79RHEo.mjs → presets-BtyFwTO4.mjs} +2 -2
- package/dist/{queryCachePlugin-CnAeFqju.mjs → queryCachePlugin-emHb0CuX.mjs} +4 -4
- package/dist/{queryCachePlugin-Bd2tYgh5.d.mts → queryCachePlugin-xzFyKAjT.d.mts} +2 -2
- package/dist/{redis-3hqH_Qk_.d.mts → redis-C3W0qbz8.d.mts} +1 -1
- package/dist/{redis-stream-BtEUtjxZ.d.mts → redis-stream-DsP1vqZB.d.mts} +1 -1
- package/dist/{rateLimit-yKvxWAky.mjs → registerRoute-CPmdkZGu.mjs} +109 -2
- package/dist/registry/index.d.mts +1 -1
- package/dist/registry/index.mjs +2 -2
- package/dist/{registry-Br15xdGM.mjs → registry-Bm9LjWKs.mjs} +1 -1
- package/dist/{replyHelpers-DXHVxrrj.mjs → replyHelpers-67tRawQZ.mjs} +1 -1
- package/dist/{schemaIR-CE6USIRi.mjs → schemaIR-D1MJwqmp.mjs} +1 -1
- package/dist/schemas/index.d.mts +64 -65
- package/dist/scope/index.d.mts +2 -2
- package/dist/scope/index.mjs +3 -3
- package/dist/{sse-BrY1W6U8.mjs → sse-CfUjwjDk.mjs} +7 -4
- package/dist/streaming-BQcW6_Kq.mjs +162 -0
- package/dist/testing/index.d.mts +7 -7
- package/dist/testing/index.mjs +3 -3
- package/dist/testing/storageContract.d.mts +1 -1
- package/dist/types/index.d.mts +5 -5
- package/dist/types/storage.d.mts +1 -1
- package/dist/{types-WVlAVp8T.d.mts → types-CmkknLzI.d.mts} +10 -10
- package/dist/{types-BWBcb88c.d.mts → types-D60PFwfG.d.mts} +1 -1
- package/dist/{types-Bi0r0vjG.mjs → types-bHblZ9YN.mjs} +1 -1
- package/dist/utils/index.d.mts +42 -4
- package/dist/utils/index.mjs +6 -5
- package/dist/{utils-xAQeDyU_.mjs → utils-C6QQ9-hd.mjs} +1415 -1524
- package/dist/{validate-0__MuUsK.mjs → validate-CRcaWzp2.mjs} +1 -1
- package/dist/{versioning-BSEDGXKC.d.mts → versioning-D_eBCfNr.d.mts} +8 -1
- package/dist/{versioning-C0K8KQLx.mjs → versioning-Dk-71iIz.mjs} +1 -1
- package/dist/{websocket-BFBT5rH-.d.mts → websocket-MlYlrYq0.d.mts} +1 -1
- package/package.json +36 -30
- package/dist/EventTransport-7LPXLEUX.mjs +0 -103
- package/dist/actionPermissions-CX9QIMTd.mjs +0 -24
- package/dist/betterAuthOpenApi-DAAhMZff.mjs +0 -251
- package/dist/defineEvent-DC981g-o.mjs +0 -188
- package/dist/registerRoute-BEYn0ylk.mjs +0 -108
- /package/dist/{EventTransport-CaaXM5Y1.d.mts → EventTransport-IEn7Tn-Q.d.mts} +0 -0
- /package/dist/{HookSystem-BfCpWbwM.mjs → HookSystem-C1vB_Gc9.mjs} +0 -0
- /package/dist/{constants-TrJVIJl0.mjs → constants-gaEeMlz-.mjs} +0 -0
- /package/dist/{elevation-DW90z5bT.d.mts → elevation-xGUBq6p_.d.mts} +0 -0
- /package/dist/{errorHandler-_Lp2k9M3.d.mts → errorHandler-BtUUDPEa.d.mts} +0 -0
- /package/dist/{errors-DZOemeGa.d.mts → errors-DOHkjQlD.d.mts} +0 -0
- /package/dist/{errors-j4aJm1Wg.mjs → errors-DkO8Nk6-.mjs} +0 -0
- /package/dist/{externalPaths-DLle0R07.d.mts → externalPaths-BE6wFpYv.d.mts} +0 -0
- /package/dist/{generate-Dmk8cfvX.d.mts → generate-BnsL9GBm.d.mts} +0 -0
- /package/dist/{init-BS4Daqnv.d.mts → init-BPEu8cs-.d.mts} +0 -0
- /package/dist/{interface-DL7LlWS5.d.mts → interface-C1_WLTMK.d.mts} +0 -0
- /package/dist/{interface-CsrGR8LF.d.mts → interface-Uk8ONJv7.d.mts} +0 -0
- /package/dist/{keys-B3yyBEyH.mjs → keys-Cd316u_y.mjs} +0 -0
- /package/dist/{multipartBody-709YcXi1.mjs → multipartBody-CHEQSZ6G.mjs} +0 -0
- /package/dist/{pluralize-BxdUl2P4.mjs → pluralize-DccFV9R7.mjs} +0 -0
- /package/dist/{preloadResources-B-j-iqo-.mjs → preloadResources-CHw3_oA_.mjs} +0 -0
- /package/dist/{pushref-registry-Dqclzjy7.d.mts → pushref-registry-DnCqL3aa.d.mts} +0 -0
- /package/dist/{requestContext-DDLS3NVU.mjs → requestContext-BZKOr13H.mjs} +0 -0
- /package/dist/{chunk-BpYLSNr0.mjs → rolldown-runtime-8H4AJuhK.mjs} +0 -0
- /package/dist/{schemaConverter-DatAaVNj.mjs → schemaConverter-U0MnO9vd.mjs} +0 -0
- /package/dist/{sessionManager-CyHpYXoB.d.mts → sessionManager-BXG-HGwG.d.mts} +0 -0
- /package/dist/{storage-C5q8fuEt.d.mts → storage-Co_PQpeQ.d.mts} +0 -0
- /package/dist/{store-helpers-DjSYI2uP.mjs → store-helpers-DY6xq4sM.mjs} +0 -0
- /package/dist/{tracing-Bnb1-YVd.d.mts → tracing-D8jlGXP7.d.mts} +0 -0
- /package/dist/{typeGuards-CcFZXgU7.mjs → typeGuards-C9KXUly6.mjs} +0 -0
- /package/dist/{types-BJwJN2Pg.d.mts → types-LNebCoJx.d.mts} +0 -0
- /package/dist/{types-D57iXYb8.mjs → types-XTApa8b6.mjs} +0 -0
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
|
|
2
|
-
import { n as createEvent } from "./EventTransport-7LPXLEUX.mjs";
|
|
3
|
-
//#region src/events/defineEvent.ts
|
|
4
|
-
/**
|
|
5
|
-
* defineEvent — Typed Event Definitions with Optional Schema Validation
|
|
6
|
-
*
|
|
7
|
-
* Provides:
|
|
8
|
-
* 1. defineEvent() — declare an event with name, schema, version, description
|
|
9
|
-
* 2. EventRegistry — catalog of all known events + payload validation
|
|
10
|
-
* 3. .create() helper — build DomainEvent with auto-generated metadata
|
|
11
|
-
*
|
|
12
|
-
* The built-in validator checks: object type, required fields, and top-level
|
|
13
|
-
* property types. It does NOT recurse into nested objects, validate arrays,
|
|
14
|
-
* enums, patterns, formats, or $ref. This is intentional — it's a lightweight
|
|
15
|
-
* guard, not a full JSON Schema engine.
|
|
16
|
-
*
|
|
17
|
-
* For full validation, pass a custom `validate` function to `createEventRegistry()`:
|
|
18
|
-
*
|
|
19
|
-
* @example
|
|
20
|
-
* ```typescript
|
|
21
|
-
* import Ajv from 'ajv';
|
|
22
|
-
* const ajv = new Ajv();
|
|
23
|
-
*
|
|
24
|
-
* const registry = createEventRegistry({
|
|
25
|
-
* validate: (schema, payload) => {
|
|
26
|
-
* const valid = ajv.validate(schema, payload);
|
|
27
|
-
* return valid
|
|
28
|
-
* ? { valid: true }
|
|
29
|
-
* : { valid: false, errors: ajv.errorsText().split(', ') };
|
|
30
|
-
* },
|
|
31
|
-
* });
|
|
32
|
-
* ```
|
|
33
|
-
*
|
|
34
|
-
* @example
|
|
35
|
-
* ```typescript
|
|
36
|
-
* import { defineEvent, createEventRegistry } from '@classytic/arc/events';
|
|
37
|
-
*
|
|
38
|
-
* const OrderCreated = defineEvent({
|
|
39
|
-
* name: 'order.created',
|
|
40
|
-
* version: 1,
|
|
41
|
-
* schema: {
|
|
42
|
-
* type: 'object',
|
|
43
|
-
* properties: {
|
|
44
|
-
* orderId: { type: 'string' },
|
|
45
|
-
* total: { type: 'number' },
|
|
46
|
-
* },
|
|
47
|
-
* required: ['orderId', 'total'],
|
|
48
|
-
* },
|
|
49
|
-
* });
|
|
50
|
-
*
|
|
51
|
-
* // Type-safe event creation
|
|
52
|
-
* const event = OrderCreated.create({ orderId: 'o-1', total: 100 });
|
|
53
|
-
* await fastify.events.publish(event.type, event.payload, event.meta);
|
|
54
|
-
*
|
|
55
|
-
* // Registry for introspection + validation
|
|
56
|
-
* const registry = createEventRegistry();
|
|
57
|
-
* registry.register(OrderCreated);
|
|
58
|
-
* const result = registry.validate('order.created', payload);
|
|
59
|
-
* ```
|
|
60
|
-
*/
|
|
61
|
-
var defineEvent_exports = /* @__PURE__ */ __exportAll({
|
|
62
|
-
createEventRegistry: () => createEventRegistry,
|
|
63
|
-
defineEvent: () => defineEvent
|
|
64
|
-
});
|
|
65
|
-
/**
|
|
66
|
-
* Define a typed event with optional schema validation.
|
|
67
|
-
*
|
|
68
|
-
* @example
|
|
69
|
-
* const OrderCreated = defineEvent({
|
|
70
|
-
* name: 'order.created',
|
|
71
|
-
* schema: { type: 'object', properties: { orderId: { type: 'string' } }, required: ['orderId'] },
|
|
72
|
-
* });
|
|
73
|
-
*
|
|
74
|
-
* const event = OrderCreated.create({ orderId: '123' });
|
|
75
|
-
*/
|
|
76
|
-
function defineEvent(input) {
|
|
77
|
-
const { name, schema, version = 1, description } = input;
|
|
78
|
-
return {
|
|
79
|
-
name,
|
|
80
|
-
schema,
|
|
81
|
-
version,
|
|
82
|
-
description,
|
|
83
|
-
create(payload, meta) {
|
|
84
|
-
return createEvent(name, payload, {
|
|
85
|
-
schemaVersion: version,
|
|
86
|
-
...meta
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Create an event registry for cataloging and validating events.
|
|
93
|
-
*
|
|
94
|
-
* The registry is opt-in — unregistered events pass validation.
|
|
95
|
-
* This allows gradual adoption without breaking existing code.
|
|
96
|
-
*
|
|
97
|
-
* @param options.validate - Custom validator replacing the built-in minimal validator.
|
|
98
|
-
* The built-in validator only checks top-level object structure (type, required, property types).
|
|
99
|
-
* For nested objects, arrays, enums, patterns, or $ref, provide AJV or similar.
|
|
100
|
-
*/
|
|
101
|
-
function createEventRegistry(options) {
|
|
102
|
-
const customValidator = options?.validate;
|
|
103
|
-
const definitions = /* @__PURE__ */ new Map();
|
|
104
|
-
function registryKey(name, version) {
|
|
105
|
-
return `${name}:v${version}`;
|
|
106
|
-
}
|
|
107
|
-
return {
|
|
108
|
-
register(definition) {
|
|
109
|
-
const key = registryKey(definition.name, definition.version);
|
|
110
|
-
if (definitions.has(key)) throw new Error(`Event '${definition.name}' v${definition.version} is already registered. Use a different version number for schema evolution.`);
|
|
111
|
-
definitions.set(key, definition);
|
|
112
|
-
},
|
|
113
|
-
get(name, version) {
|
|
114
|
-
if (version !== void 0) return definitions.get(registryKey(name, version));
|
|
115
|
-
let latest;
|
|
116
|
-
let latestVersion = -1;
|
|
117
|
-
for (const def of definitions.values()) if (def.name === name && def.version > latestVersion) {
|
|
118
|
-
latest = def;
|
|
119
|
-
latestVersion = def.version;
|
|
120
|
-
}
|
|
121
|
-
return latest;
|
|
122
|
-
},
|
|
123
|
-
catalog() {
|
|
124
|
-
return Array.from(definitions.values()).map((def) => ({
|
|
125
|
-
name: def.name,
|
|
126
|
-
version: def.version,
|
|
127
|
-
description: def.description,
|
|
128
|
-
schema: def.schema
|
|
129
|
-
}));
|
|
130
|
-
},
|
|
131
|
-
validate(name, payload, version) {
|
|
132
|
-
let target = version !== void 0 ? definitions.get(registryKey(name, version)) : void 0;
|
|
133
|
-
if (!target && version === void 0) {
|
|
134
|
-
let latestVersion = -1;
|
|
135
|
-
for (const candidate of definitions.values()) if (candidate.name === name && candidate.version > latestVersion) {
|
|
136
|
-
target = candidate;
|
|
137
|
-
latestVersion = candidate.version;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
if (!target) return { valid: true };
|
|
141
|
-
if (!target.schema) return { valid: true };
|
|
142
|
-
return customValidator ? customValidator(target.schema, payload) : validatePayload(payload, target.schema);
|
|
143
|
-
}
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
/**
|
|
147
|
-
* Built-in minimal validator — lightweight guard, NOT a full JSON Schema engine.
|
|
148
|
-
*
|
|
149
|
-
* Checks:
|
|
150
|
-
* - payload is an object (not null, not array)
|
|
151
|
-
* - required fields are present
|
|
152
|
-
* - top-level property types match (string, number, boolean, array, object)
|
|
153
|
-
*
|
|
154
|
-
* Does NOT check:
|
|
155
|
-
* - nested object properties
|
|
156
|
-
* - array item types
|
|
157
|
-
* - enum, pattern, format, minLength, minimum, $ref
|
|
158
|
-
*
|
|
159
|
-
* For full validation, pass a custom `validate` function to `createEventRegistry()`.
|
|
160
|
-
*/
|
|
161
|
-
function validatePayload(payload, schema) {
|
|
162
|
-
const errors = [];
|
|
163
|
-
if (schema.type === "object") {
|
|
164
|
-
if (payload === null || payload === void 0 || typeof payload !== "object" || Array.isArray(payload)) return {
|
|
165
|
-
valid: false,
|
|
166
|
-
errors: ["Payload must be an object"]
|
|
167
|
-
};
|
|
168
|
-
const record = payload;
|
|
169
|
-
if (schema.required) {
|
|
170
|
-
for (const field of schema.required) if (!(field in record) || record[field] === void 0) errors.push(`Missing required field: '${field}'`);
|
|
171
|
-
}
|
|
172
|
-
if (schema.properties) {
|
|
173
|
-
for (const [key, propSchema] of Object.entries(schema.properties)) if (key in record && record[key] !== void 0 && record[key] !== null) {
|
|
174
|
-
const expectedType = propSchema.type;
|
|
175
|
-
if (expectedType) {
|
|
176
|
-
const actualType = Array.isArray(record[key]) ? "array" : typeof record[key];
|
|
177
|
-
if (expectedType !== actualType) errors.push(`Field '${key}': expected ${expectedType}, got ${actualType}`);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
return errors.length === 0 ? { valid: true } : {
|
|
183
|
-
valid: false,
|
|
184
|
-
errors
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
//#endregion
|
|
188
|
-
export { defineEvent as n, defineEvent_exports as r, createEventRegistry as t };
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { f as createError } from "./errors-j4aJm1Wg.mjs";
|
|
2
|
-
import { M as evaluateAndApplyPermission } from "./permissions-3uzwkbQt.mjs";
|
|
3
|
-
//#region src/core/middlewares/permissions.ts
|
|
4
|
-
/**
|
|
5
|
-
* Build a PermissionContext from a Fastify request. Extracted so the CRUD
|
|
6
|
-
* permission middleware and the dynamic action-permission check use the same
|
|
7
|
-
* field layout — divergence here silently broke policy filters for actions.
|
|
8
|
-
*/
|
|
9
|
-
function buildPermissionContext(req, opts) {
|
|
10
|
-
const reqWithExtras = req;
|
|
11
|
-
const params = req.params;
|
|
12
|
-
return {
|
|
13
|
-
user: reqWithExtras.user ?? null,
|
|
14
|
-
request: req,
|
|
15
|
-
resource: opts.resource,
|
|
16
|
-
action: opts.action,
|
|
17
|
-
resourceId: opts.resourceId ?? params?.id,
|
|
18
|
-
params,
|
|
19
|
-
data: opts.data ?? req.body
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Static per-route CRUD permission gate. The permission and action are known
|
|
24
|
-
* at route-registration time, so the gate is a plain preHandler.
|
|
25
|
-
*
|
|
26
|
-
* Actions use the dynamic counterpart `buildActionPermissionMw` — their
|
|
27
|
-
* permission is resolved from `body.action` at request time.
|
|
28
|
-
*/
|
|
29
|
-
function buildCrudPermissionMw(permissionCheck, resourceName, action) {
|
|
30
|
-
if (!permissionCheck) return null;
|
|
31
|
-
return async (req, reply) => {
|
|
32
|
-
await evaluateAndApplyPermission(permissionCheck, buildPermissionContext(req, {
|
|
33
|
-
resource: resourceName,
|
|
34
|
-
action
|
|
35
|
-
}), req, reply);
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Dynamic per-action permission gate for the action router.
|
|
40
|
-
*
|
|
41
|
-
* Resolves the permission from `body.action` at request time and runs
|
|
42
|
-
* `evaluateAndApplyPermission` from the canonical `permissionMw` slot — so
|
|
43
|
-
* `_policyFilters` and `request.scope` are installed BEFORE `pluginMw`
|
|
44
|
-
* (idempotency) and `routeGuards` run. Previously this check lived inside
|
|
45
|
-
* the main action handler, which meant idempotency recorded unauthorized
|
|
46
|
-
* requests and route guards saw unfiltered scope — the very divergence
|
|
47
|
-
* routerShared exists to prevent.
|
|
48
|
-
*
|
|
49
|
-
* Also acts as a defensive fallback for invalid action names — the
|
|
50
|
-
* `oneOf` body schema normally rejects these at AJV validation, but
|
|
51
|
-
* hosts that disable schema validation still get a 400 here.
|
|
52
|
-
*/
|
|
53
|
-
function buildActionPermissionMw(actionEnum, actionPermissions, globalAuth, resourceName) {
|
|
54
|
-
const enumSet = new Set(actionEnum);
|
|
55
|
-
const validActions = [...actionEnum];
|
|
56
|
-
return async (req, reply) => {
|
|
57
|
-
const body = req.body ?? {};
|
|
58
|
-
const action = body.action;
|
|
59
|
-
if (!action || !enumSet.has(action)) throw createError(400, `Invalid action '${action ?? ""}'. Valid actions: ${validActions.join(", ")}`, { validActions });
|
|
60
|
-
const permissionCheck = actionPermissions[action] ?? globalAuth;
|
|
61
|
-
if (!permissionCheck) return;
|
|
62
|
-
const { action: _discard, ...data } = body;
|
|
63
|
-
const params = req.params;
|
|
64
|
-
await evaluateAndApplyPermission(permissionCheck, buildPermissionContext(req, {
|
|
65
|
-
resource: resourceName,
|
|
66
|
-
action,
|
|
67
|
-
resourceId: params?.id,
|
|
68
|
-
data
|
|
69
|
-
}), req, reply, { defaultDenialMessage: (user) => user ? `Permission denied for '${action}'` : "Authentication required" });
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
//#endregion
|
|
73
|
-
//#region src/core/middlewares/registerRoute.ts
|
|
74
|
-
/**
|
|
75
|
-
* Register a route with friendly handling of `FST_ERR_DUPLICATED_ROUTE`.
|
|
76
|
-
*
|
|
77
|
-
* Boot-time `validateRouteCrudCollisions()` catches custom-route → auto-CRUD
|
|
78
|
-
* overlaps before Fastify ever runs, but a few cases still slip through to
|
|
79
|
-
* Fastify itself: two presets contributing the same `routes` entry, hosts
|
|
80
|
-
* mounting two resources at the same prefix, custom routes that share a
|
|
81
|
-
* URL with another plugin. Fastify's default message ("Method 'GET'
|
|
82
|
-
* already declared for route '/'") gives no hint at the resource-shaped
|
|
83
|
-
* fix — `disabledRoutes: ['list']` — so wrap it with one.
|
|
84
|
-
*/
|
|
85
|
-
function tryRegisterRoute(fastify, opts, context) {
|
|
86
|
-
try {
|
|
87
|
-
fastify.route(opts);
|
|
88
|
-
} catch (err) {
|
|
89
|
-
const fastifyErr = err;
|
|
90
|
-
if (fastifyErr?.code !== "FST_ERR_DUPLICATED_ROUTE") throw err;
|
|
91
|
-
const method = opts.method;
|
|
92
|
-
const url = opts.url;
|
|
93
|
-
const opHint = context.op ? ` (op: ${context.op})` : "";
|
|
94
|
-
const fixHint = context.op && [
|
|
95
|
-
"list",
|
|
96
|
-
"get",
|
|
97
|
-
"create",
|
|
98
|
-
"update",
|
|
99
|
-
"delete"
|
|
100
|
-
].includes(context.op) ? ` Suppress the auto-CRUD route with \`disabledRoutes: ['${context.op}']\`.` : " If a custom route in `routes:` collides with auto-CRUD, add `disabledRoutes: ['list' | 'get' | 'create' | 'update' | 'delete']` to `defineResource()` to suppress the matching auto-CRUD route.";
|
|
101
|
-
const enhanced = /* @__PURE__ */ new Error(`[arc] Duplicate route ${method} ${url} on resource "${context.resourceName}"${opHint}.${fixHint} Original: ${fastifyErr.message ?? "FST_ERR_DUPLICATED_ROUTE"}`);
|
|
102
|
-
enhanced.code = "FST_ERR_DUPLICATED_ROUTE";
|
|
103
|
-
enhanced.cause = err;
|
|
104
|
-
throw enhanced;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
//#endregion
|
|
108
|
-
export { buildActionPermissionMw as n, buildCrudPermissionMw as r, tryRegisterRoute as t };
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|