@classytic/arc 2.8.0 → 2.8.3
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 +10 -1
- package/dist/{BaseController-CpMfCXdn.mjs → BaseController-DAGGc5Xn.mjs} +76 -25
- package/dist/{EventTransport-n1KBxC_N.d.mts → EventTransport-CinyO7zQ.d.mts} +37 -1
- package/dist/{ResourceRegistry-BOtJuRCs.mjs → ResourceRegistry-Dq3_zBQP.mjs} +17 -5
- package/dist/adapters/index.d.mts +2 -2
- package/dist/adapters/index.mjs +1 -1
- package/dist/{adapters-BxGgSHjj.mjs → adapters-BBqAVvPK.mjs} +11 -0
- package/dist/audit/index.d.mts +1 -1
- package/dist/audit/index.mjs +1 -1
- package/dist/audit/mongodb.d.mts +1 -1
- package/dist/audit/mongodb.mjs +1 -1
- 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-CHCIuA-p.mjs → betterAuthOpenApi-C5lDyRH2.mjs} +1 -1
- package/dist/cache/index.d.mts +2 -2
- package/dist/cli/commands/describe.mjs +1 -1
- package/dist/cli/commands/docs.mjs +2 -2
- package/dist/cli/commands/generate.mjs +1 -1
- package/dist/cli/commands/init.mjs +10 -10
- package/dist/cli/commands/introspect.mjs +3 -3
- package/dist/core/index.d.mts +3 -3
- package/dist/core/index.mjs +5 -5
- package/dist/{core-BfrfxNqO.mjs → core-DKSwNSXf.mjs} +1 -1
- package/dist/{createActionRouter-CbkIAaGh.mjs → createActionRouter-Df1BuawX.mjs} +87 -21
- package/dist/{createApp-Cy8eUNKQ.mjs → createApp-BOYjBgdI.mjs} +16 -7
- package/dist/{defineResource-CovBXvTB.mjs → defineResource-Bb_Bdhtw.mjs} +60 -33
- package/dist/docs/index.d.mts +2 -2
- package/dist/docs/index.mjs +1 -1
- package/dist/dynamic/index.d.mts +2 -2
- package/dist/dynamic/index.mjs +1 -1
- package/dist/{errorHandler-BeN-ERN7.d.mts → errorHandler-CdZDavNH.d.mts} +2 -2
- package/dist/{errorHandler-BW08lEiy.mjs → errorHandler-mzqk4cGl.mjs} +1 -1
- package/dist/{eventPlugin-CAOWMQS8.d.mts → eventPlugin-CVxlE6De.d.mts} +1 -1
- package/dist/{eventPlugin-x4jo3sG0.mjs → eventPlugin-D91S2YF4.mjs} +19 -1
- package/dist/events/index.d.mts +399 -28
- package/dist/events/index.mjs +345 -29
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis-stream-entry.mjs +3 -1
- package/dist/events/transports/redis.d.mts +1 -1
- package/dist/factory/index.d.mts +1 -1
- package/dist/factory/index.mjs +2 -152
- package/dist/hooks/index.d.mts +1 -1
- package/dist/idempotency/index.d.mts +3 -3
- package/dist/idempotency/mongodb.d.mts +1 -1
- package/dist/idempotency/mongodb.mjs +18 -6
- package/dist/idempotency/redis.d.mts +1 -1
- package/dist/idempotency/redis.mjs +10 -1
- package/dist/{index-BpMhrFgn.d.mts → index-BgmMdpm8.d.mts} +1 -1
- package/dist/{index-CBru2y5Y.d.mts → index-CSkeivBx.d.mts} +3 -3
- package/dist/{index-qct60lnl.d.mts → index-CpTSDqmD.d.mts} +60 -6
- package/dist/index.d.mts +8 -8
- package/dist/index.mjs +7 -7
- package/dist/integrations/event-gateway.d.mts +1 -1
- package/dist/integrations/event-gateway.mjs +1 -1
- package/dist/integrations/index.d.mts +1 -1
- package/dist/integrations/mcp/index.d.mts +2 -2
- package/dist/integrations/mcp/index.mjs +1 -1
- package/dist/integrations/mcp/testing.d.mts +1 -1
- package/dist/integrations/mcp/testing.mjs +1 -1
- package/dist/{interface-IJqN3pXK.d.mts → interface-BVuMfeVv.d.mts} +596 -125
- package/dist/loadResources-Bksk8ydA.mjs +154 -0
- package/dist/{mongodb-B1eVtFhw.d.mts → mongodb-B8U2xaLj.d.mts} +1 -1
- package/dist/{mongodb-NShVZDMr.d.mts → mongodb-X7LbEjTN.d.mts} +10 -1
- package/dist/{openapi-AYLVjqVe.mjs → openapi-CYCuekCn.mjs} +50 -3
- package/dist/org/index.d.mts +2 -2
- package/dist/permissions/index.d.mts +3 -3
- package/dist/plugins/index.d.mts +5 -5
- package/dist/plugins/index.mjs +8 -8
- package/dist/plugins/tracing-entry.d.mts +1 -1
- package/dist/plugins/tracing-entry.mjs +1 -1
- package/dist/policies/index.d.mts +1 -1
- package/dist/presets/index.d.mts +3 -3
- package/dist/presets/index.mjs +1 -1
- package/dist/presets/multiTenant.d.mts +1 -1
- package/dist/{presets-BFrGvvjL.mjs → presets-C2xgzW6x.mjs} +10 -18
- package/dist/{queryCachePlugin-BCFVXnxK.d.mts → queryCachePlugin-CnTZZTC5.d.mts} +1 -1
- package/dist/{redis-stream-CF1lrKVk.d.mts → redis-stream-D54N5oXs.d.mts} +1 -1
- package/dist/{redis-Bunu3qWg.d.mts → redis-z3sFr1UP.d.mts} +1 -1
- package/dist/registry/index.d.mts +1 -1
- package/dist/registry/index.mjs +1 -1
- package/dist/{resourceToTools-C_1SMiCz.mjs → resourceToTools-O_HwWXFa.mjs} +194 -64
- package/dist/rpc/index.d.mts +1 -1
- package/dist/rpc/index.mjs +1 -1
- package/dist/scope/index.d.mts +2 -2
- package/dist/testing/index.d.mts +2 -2
- package/dist/testing/index.mjs +1 -1
- package/dist/types/index.d.mts +5 -5
- package/dist/{types-gUxAIZHp.d.mts → types-Bg2X42_m.d.mts} +30 -9
- package/dist/{types-BoaZHr-2.d.mts → types-CVC4HOKi.d.mts} +1 -1
- package/dist/{types-Ct0PUUSp.d.mts → types-CcG4avic.d.mts} +1 -1
- package/dist/utils/index.d.mts +43 -17
- package/dist/utils/index.mjs +5 -5
- package/dist/{utils-B-l6410F.mjs → utils-yYT3HDXt.mjs} +65 -13
- package/package.json +10 -9
- package/skills/arc/SKILL.md +79 -6
- /package/dist/{caching-CHH-iHs3.mjs → caching-CjybdRwx.mjs} +0 -0
- /package/dist/{circuitBreaker-BGVoB1hD.d.mts → circuitBreaker-CvXkjfrW.d.mts} +0 -0
- /package/dist/{circuitBreaker-l18oRgL5.mjs → circuitBreaker-cmi5XDv5.mjs} +0 -0
- /package/dist/{elevation-UJO3-NvX.d.mts → elevation-s5ykdNHr.d.mts} +0 -0
- /package/dist/{errors-Cg58SLNi.mjs → errors-BF2bIOIS.mjs} +0 -0
- /package/dist/{errors-BI8kEKsO.d.mts → errors-Bmn3eZT6.d.mts} +0 -0
- /package/dist/{externalPaths-BQ8QijNH.d.mts → externalPaths-Bapitwvd.d.mts} +0 -0
- /package/dist/{fields-DoeDgh2b.d.mts → fields-DC4So2M2.d.mts} +0 -0
- /package/dist/{interface-CkkWm5uR.d.mts → interface-B-pe8fhj.d.mts} +0 -0
- /package/dist/{interface-bpoLKKqx.d.mts → interface-DplgQO2e.d.mts} +0 -0
- /package/dist/{metrics-DuhiSEZI.mjs → metrics-TuOmguhi.mjs} +0 -0
- /package/dist/{mongodb-5Ff3w8jy.mjs → mongodb-B5O6xaW1.mjs} +0 -0
- /package/dist/{pluralize-BneOJkpi.mjs → pluralize-A0tWEl1K.mjs} +0 -0
- /package/dist/{replyHelpers-CXtJDAZ0.mjs → replyHelpers-BLojtuvR.mjs} +0 -0
- /package/dist/{requestContext-xHIKedG6.mjs → requestContext-DYvHl113.mjs} +0 -0
- /package/dist/{schemaConverter-Y5EejTnJ.mjs → schemaConverter-OxfCshus.mjs} +0 -0
- /package/dist/{sessionManager-BkzVU8h2.d.mts → sessionManager-D-oNWHz3.d.mts} +0 -0
- /package/dist/{sse-CD5Hghpu.mjs → sse-CJpt7LGI.mjs} +0 -0
- /package/dist/{tracing-xqXzWeaf.d.mts → tracing-DxjKk7eW.d.mts} +0 -0
- /package/dist/{types-CN6JvmYz.d.mts → types-C72d3NDn.d.mts} +0 -0
- /package/dist/{versioning-CPU_5Xfs.mjs → versioning-Cm8qoFDg.mjs} +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { t as ArcError } from "./errors-BF2bIOIS.mjs";
|
|
1
2
|
//#region src/utils/compensation.ts
|
|
2
3
|
/**
|
|
3
4
|
* Run steps in order with automatic compensation on failure.
|
|
@@ -69,6 +70,69 @@ function defineCompensation(name, steps) {
|
|
|
69
70
|
};
|
|
70
71
|
}
|
|
71
72
|
//#endregion
|
|
73
|
+
//#region src/utils/defineGuard.ts
|
|
74
|
+
/** Hidden property key for guard context storage on the request object. */
|
|
75
|
+
const GUARD_STORE_KEY = "__arcGuardContext";
|
|
76
|
+
/**
|
|
77
|
+
* Create a typed guard. See module JSDoc for usage.
|
|
78
|
+
*/
|
|
79
|
+
function defineGuard(config) {
|
|
80
|
+
const { name, resolve } = config;
|
|
81
|
+
const preHandler = async (req, reply) => {
|
|
82
|
+
const ctx = await resolve(req, reply);
|
|
83
|
+
if (!reply.sent) {
|
|
84
|
+
const store = req[GUARD_STORE_KEY] ?? {};
|
|
85
|
+
store[name] = ctx;
|
|
86
|
+
req[GUARD_STORE_KEY] = store;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
return {
|
|
90
|
+
preHandler,
|
|
91
|
+
name,
|
|
92
|
+
from(req) {
|
|
93
|
+
const store = req[GUARD_STORE_KEY];
|
|
94
|
+
if (!store || !(name in store)) throw new Error(`Guard '${name}' not resolved on this request. Add it to routeGuards or the route's preHandler array.`);
|
|
95
|
+
return store[name];
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
//#endregion
|
|
100
|
+
//#region src/utils/handleRaw.ts
|
|
101
|
+
/**
|
|
102
|
+
* Wrap a raw Fastify handler with Arc's response envelope and error handling.
|
|
103
|
+
*
|
|
104
|
+
* @param handler - Async function that receives `(request, reply)` and returns data.
|
|
105
|
+
* The return value is sent as `{ success: true, data }`. If it returns
|
|
106
|
+
* `undefined` or `null`, `{ success: true }` is sent (no `data` field).
|
|
107
|
+
* @param statusCode - HTTP status code for successful responses (default: 200)
|
|
108
|
+
*/
|
|
109
|
+
function handleRaw(handler, statusCode = 200) {
|
|
110
|
+
return async (request, reply) => {
|
|
111
|
+
try {
|
|
112
|
+
const result = await handler(request, reply);
|
|
113
|
+
if (reply.sent) return;
|
|
114
|
+
if (result === void 0 || result === null) reply.code(statusCode).send({ success: true });
|
|
115
|
+
else reply.code(statusCode).send({
|
|
116
|
+
success: true,
|
|
117
|
+
data: result
|
|
118
|
+
});
|
|
119
|
+
} catch (err) {
|
|
120
|
+
if (reply.sent) return;
|
|
121
|
+
if (err instanceof ArcError) {
|
|
122
|
+
reply.code(err.statusCode).send(err.toJSON());
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const error = err;
|
|
126
|
+
const code = error.statusCode ?? error.status ?? 500;
|
|
127
|
+
reply.code(code).send({
|
|
128
|
+
success: false,
|
|
129
|
+
error: error.message ?? "Internal server error",
|
|
130
|
+
...error.code && { code: error.code }
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
//#endregion
|
|
72
136
|
//#region src/utils/responseSchemas.ts
|
|
73
137
|
/**
|
|
74
138
|
* Base success response schema
|
|
@@ -216,10 +280,6 @@ function listResponse(itemSchema) {
|
|
|
216
280
|
};
|
|
217
281
|
}
|
|
218
282
|
/**
|
|
219
|
-
* Alias for listResponse - matches local responseSchemas.js naming
|
|
220
|
-
*/
|
|
221
|
-
const paginateWrapper = listResponse;
|
|
222
|
-
/**
|
|
223
283
|
* Create a single item response schema
|
|
224
284
|
*
|
|
225
285
|
* Runtime format: { success, data: {...} }
|
|
@@ -228,10 +288,6 @@ function itemResponse(itemSchema) {
|
|
|
228
288
|
return wrapResponse(itemSchema);
|
|
229
289
|
}
|
|
230
290
|
/**
|
|
231
|
-
* Alias for itemResponse - matches local responseSchemas.js naming
|
|
232
|
-
*/
|
|
233
|
-
const itemWrapper = itemResponse;
|
|
234
|
-
/**
|
|
235
291
|
* Create a create/update response schema
|
|
236
292
|
*/
|
|
237
293
|
function mutationResponse(itemSchema) {
|
|
@@ -288,10 +344,6 @@ function deleteResponse() {
|
|
|
288
344
|
additionalProperties: true
|
|
289
345
|
};
|
|
290
346
|
}
|
|
291
|
-
/**
|
|
292
|
-
* Alias for deleteResponse - matches local responseSchemas.js naming
|
|
293
|
-
*/
|
|
294
|
-
const messageWrapper = deleteResponse;
|
|
295
347
|
const responses = {
|
|
296
348
|
200: (schema) => ({
|
|
297
349
|
description: "Successful response",
|
|
@@ -591,4 +643,4 @@ function createStateMachine(name, transitions = {}, options = {}) {
|
|
|
591
643
|
};
|
|
592
644
|
}
|
|
593
645
|
//#endregion
|
|
594
|
-
export {
|
|
646
|
+
export { withCompensation as _, getListQueryParams as a, mutationResponse as c, responses as d, successResponseSchema as f, defineCompensation as g, defineGuard as h, getDefaultCrudSchemas as i, paginationSchema as l, handleRaw as m, deleteResponse as n, itemResponse as o, wrapResponse as p, errorResponseSchema as r, listResponse as s, createStateMachine as t, queryParams as u };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@classytic/arc",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.3",
|
|
4
4
|
"description": "Resource-oriented backend framework for Fastify — clean, minimal, powerful, tree-shakable",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -227,7 +227,7 @@
|
|
|
227
227
|
"node": ">=22"
|
|
228
228
|
},
|
|
229
229
|
"peerDependencies": {
|
|
230
|
-
"@classytic/mongokit": ">=3.
|
|
230
|
+
"@classytic/mongokit": ">=3.6.0",
|
|
231
231
|
"@classytic/streamline": ">=2.1.0",
|
|
232
232
|
"@fastify/cors": ">=11.0.0",
|
|
233
233
|
"@fastify/helmet": ">=13.0.0",
|
|
@@ -340,18 +340,18 @@
|
|
|
340
340
|
},
|
|
341
341
|
"dependencies": {
|
|
342
342
|
"fastify-plugin": "^5.0.1",
|
|
343
|
-
"qs": "^6.
|
|
343
|
+
"qs": "^6.15.1",
|
|
344
344
|
"secure-json-parse": "^4.1.0"
|
|
345
345
|
},
|
|
346
346
|
"devDependencies": {
|
|
347
|
-
"@better-auth/mongo-adapter": "^1.6.
|
|
348
|
-
"@biomejs/biome": "^2.4.
|
|
349
|
-
"@classytic/mongokit": "3.
|
|
347
|
+
"@better-auth/mongo-adapter": "^1.6.2",
|
|
348
|
+
"@biomejs/biome": "^2.4.11",
|
|
349
|
+
"@classytic/mongokit": "file:../../packages/mongokit/classytic-mongokit-3.6.0.tgz",
|
|
350
350
|
"@classytic/streamline": "^2.1.0",
|
|
351
351
|
"@fastify/cors": "^11.2.0",
|
|
352
352
|
"@fastify/helmet": "^13.0.2",
|
|
353
353
|
"@fastify/jwt": "^10.0.0",
|
|
354
|
-
"@fastify/multipart": "^
|
|
354
|
+
"@fastify/multipart": "^10.0.0",
|
|
355
355
|
"@fastify/rate-limit": "^10.3.0",
|
|
356
356
|
"@fastify/sensible": "^6.0.4",
|
|
357
357
|
"@fastify/type-provider-typebox": "^6.0.0",
|
|
@@ -362,10 +362,11 @@
|
|
|
362
362
|
"@types/node": "^22.10.0",
|
|
363
363
|
"@types/qs": "^6.14.0",
|
|
364
364
|
"@vitest/coverage-v8": "^3.2.4",
|
|
365
|
-
"
|
|
365
|
+
"ajv": "^8.18.0",
|
|
366
|
+
"better-auth": "^1.6.2",
|
|
366
367
|
"fastify-raw-body": "^5.0.0",
|
|
367
368
|
"jsonwebtoken": "^9.0.0",
|
|
368
|
-
"knip": "^6.
|
|
369
|
+
"knip": "^6.4.1",
|
|
369
370
|
"mongodb": "^7.1.0",
|
|
370
371
|
"mongodb-memory-server": "^11.0.1",
|
|
371
372
|
"mongoose": "^9.4.1",
|
package/skills/arc/SKILL.md
CHANGED
|
@@ -8,7 +8,7 @@ description: |
|
|
|
8
8
|
Triggers: arc, fastify resource, defineResource, createApp, BaseController, arc preset,
|
|
9
9
|
arc auth, arc events, arc jobs, arc websocket, arc mcp, arc plugin, arc testing, arc cli,
|
|
10
10
|
arc permissions, arc hooks, arc pipeline, arc factory, arc cache, arc QueryCache.
|
|
11
|
-
version: 2.8.
|
|
11
|
+
version: 2.8.1
|
|
12
12
|
license: MIT
|
|
13
13
|
metadata:
|
|
14
14
|
author: Classytic
|
|
@@ -88,17 +88,28 @@ const productResource = defineResource({
|
|
|
88
88
|
delete: requireRoles(['admin']),
|
|
89
89
|
},
|
|
90
90
|
cache: { staleTime: 30, gcTime: 300, tags: ['catalog'] },
|
|
91
|
-
additionalRoutes: [
|
|
92
|
-
{ method: 'GET', path: '/featured', handler: 'getFeatured', permissions: allowPublic(), wrapHandler: true },
|
|
93
|
-
],
|
|
94
91
|
|
|
95
|
-
// v2.8:
|
|
92
|
+
// v2.8.1: routeGuards — auto-apply to ALL routes (CRUD + custom + preset)
|
|
93
|
+
routeGuards: [modeGuard, orgGuard.preHandler],
|
|
94
|
+
|
|
95
|
+
// v2.8.1: fieldRules constraints → auto-map to OpenAPI + AJV validation
|
|
96
|
+
schemaOptions: {
|
|
97
|
+
fieldRules: {
|
|
98
|
+
name: { minLength: 2, maxLength: 200, description: 'Product name' },
|
|
99
|
+
price: { min: 0, max: 100000 },
|
|
100
|
+
sku: { pattern: '^[A-Z]{3}-\\d{3}$' },
|
|
101
|
+
status: { enum: ['draft', 'active', 'archived'] },
|
|
102
|
+
deletedAt: { systemManaged: true },
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
// Custom routes (compose with presets — softDelete adds /deleted, /:id/restore)
|
|
96
107
|
routes: [
|
|
97
108
|
{ method: 'GET', path: '/stats', handler: 'getStats', permissions: auth() },
|
|
98
109
|
{ method: 'POST', path: '/webhook', handler: webhookFn, raw: true, permissions: auth() },
|
|
99
110
|
],
|
|
100
111
|
|
|
101
|
-
//
|
|
112
|
+
// Actions (replaces onRegister + createActionRouter)
|
|
102
113
|
actions: {
|
|
103
114
|
approve: async (id, data, req) => service.approve(id, req.user._id),
|
|
104
115
|
cancel: {
|
|
@@ -112,8 +123,70 @@ const productResource = defineResource({
|
|
|
112
123
|
|
|
113
124
|
await fastify.register(productResource.toPlugin());
|
|
114
125
|
// Auto-generates: GET /, GET /:id, POST /, PATCH /:id, DELETE /:id
|
|
126
|
+
// + softDelete preset adds: GET /deleted, POST /:id/restore
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## routeGuards + defineGuard (v2.8.1)
|
|
130
|
+
|
|
131
|
+
Resource-level guards that apply to **every** route (CRUD + custom + preset):
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
import { defineGuard } from '@classytic/arc/utils';
|
|
135
|
+
import type { RouteHandlerMethod } from '@classytic/arc';
|
|
136
|
+
|
|
137
|
+
// Simple guard — reject if condition fails
|
|
138
|
+
const modeGuard: RouteHandlerMethod = async (req, reply) => {
|
|
139
|
+
if (!req.headers['x-mode']) {
|
|
140
|
+
reply.code(403).send({ error: 'Mode header required' });
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// Typed guard — resolve context once, extract anywhere
|
|
145
|
+
const orgGuard = defineGuard({
|
|
146
|
+
name: 'org',
|
|
147
|
+
resolve: (req) => {
|
|
148
|
+
const orgId = req.headers['x-org-id'] as string;
|
|
149
|
+
if (!orgId) throw new Error('Missing x-org-id');
|
|
150
|
+
return { orgId, actorId: req.user?.id ?? 'system' };
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
defineResource({
|
|
155
|
+
name: 'procurement',
|
|
156
|
+
routeGuards: [modeGuard, orgGuard.preHandler], // all routes protected
|
|
157
|
+
routes: [{
|
|
158
|
+
method: 'GET', path: '/summary', raw: true, permissions: auth(),
|
|
159
|
+
handler: async (req, reply) => {
|
|
160
|
+
const { orgId } = orgGuard.from(req); // typed, no re-computation
|
|
161
|
+
reply.send({ orgId, count: await Model.countDocuments() });
|
|
162
|
+
},
|
|
163
|
+
}],
|
|
164
|
+
// ...
|
|
165
|
+
});
|
|
115
166
|
```
|
|
116
167
|
|
|
168
|
+
**Execution order:** auth → permissions → cache/idempotency → `routeGuards` → per-route `preHandler`
|
|
169
|
+
|
|
170
|
+
## fieldRules → OpenAPI + AJV (v2.8.1)
|
|
171
|
+
|
|
172
|
+
One definition, two outputs — constraints auto-map to OpenAPI schema + Fastify AJV validation:
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
schemaOptions: {
|
|
176
|
+
fieldRules: {
|
|
177
|
+
name: { minLength: 2, maxLength: 200, description: 'Product name' },
|
|
178
|
+
price: { min: 0, max: 100000 },
|
|
179
|
+
sku: { pattern: '^[A-Z]{3}-\\d{3}$' },
|
|
180
|
+
status: { enum: ['draft', 'active', 'archived'] },
|
|
181
|
+
password: { hidden: true }, // blocked from select + OpenAPI
|
|
182
|
+
deletedAt: { systemManaged: true }, // blocked from input schemas
|
|
183
|
+
slug: { immutable: true }, // excluded from update body
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Mongoose model-level constraints (`minlength`, `maxlength`, `min`, `max`, `enum`) take precedence. `fieldRules` supplements what the model doesn't declare.
|
|
189
|
+
|
|
117
190
|
## Authentication
|
|
118
191
|
|
|
119
192
|
Auth uses a **discriminated union** with `type` field:
|
|
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
|