@classytic/arc 1.1.0 → 2.1.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 +247 -794
- package/bin/arc.js +91 -52
- package/dist/EventTransport-BkUDYZEb.d.mts +99 -0
- package/dist/HookSystem-BsGV-j2l.mjs +404 -0
- package/dist/ResourceRegistry-7Ic20ZMw.mjs +249 -0
- package/dist/adapters/index.d.mts +5 -0
- package/dist/adapters/index.mjs +3 -0
- package/dist/audit/index.d.mts +81 -0
- package/dist/audit/index.mjs +275 -0
- package/dist/audit/mongodb.d.mts +5 -0
- package/dist/audit/mongodb.mjs +3 -0
- package/dist/audited-CGdLiSlE.mjs +140 -0
- package/dist/auth/index.d.mts +188 -0
- package/dist/auth/index.mjs +1096 -0
- package/dist/auth/redis-session.d.mts +43 -0
- package/dist/auth/redis-session.mjs +75 -0
- package/dist/betterAuthOpenApi-DjWDddNc.mjs +249 -0
- package/dist/cache/index.d.mts +145 -0
- package/dist/cache/index.mjs +91 -0
- package/dist/caching-GSDJcA6-.mjs +93 -0
- package/dist/chunk-C7Uep-_p.mjs +20 -0
- package/dist/circuitBreaker-DYhWBW_D.mjs +1096 -0
- package/dist/cli/commands/describe.d.mts +18 -0
- package/dist/cli/commands/describe.mjs +238 -0
- package/dist/cli/commands/docs.d.mts +13 -0
- package/dist/cli/commands/docs.mjs +52 -0
- package/dist/cli/commands/{generate.d.ts → generate.d.mts} +3 -2
- package/dist/cli/commands/generate.mjs +357 -0
- package/dist/cli/commands/{init.d.ts → init.d.mts} +11 -8
- package/dist/cli/commands/{init.js → init.mjs} +807 -617
- package/dist/cli/commands/introspect.d.mts +10 -0
- package/dist/cli/commands/introspect.mjs +75 -0
- package/dist/cli/index.d.mts +16 -0
- package/dist/cli/index.mjs +156 -0
- package/dist/constants-DdXFXQtN.mjs +84 -0
- package/dist/core/index.d.mts +5 -0
- package/dist/core/index.mjs +4 -0
- package/dist/createApp-D2D5XXaV.mjs +559 -0
- package/dist/defineResource-PXzSJ15_.mjs +2197 -0
- package/dist/discovery/index.d.mts +46 -0
- package/dist/discovery/index.mjs +109 -0
- package/dist/docs/index.d.mts +162 -0
- package/dist/docs/index.mjs +74 -0
- package/dist/elevation-DGo5shaX.d.mts +87 -0
- package/dist/elevation-DSTbVvYj.mjs +113 -0
- package/dist/errorHandler-C3GY3_ow.mjs +108 -0
- package/dist/errorHandler-CW3OOeYq.d.mts +72 -0
- package/dist/errors-DAWRdiYP.d.mts +124 -0
- package/dist/errors-DBANPbGr.mjs +211 -0
- package/dist/eventPlugin-BEOvaDqo.mjs +229 -0
- package/dist/eventPlugin-H6wDDjGO.d.mts +124 -0
- package/dist/events/index.d.mts +53 -0
- package/dist/events/index.mjs +51 -0
- package/dist/events/transports/redis-stream-entry.d.mts +2 -0
- package/dist/events/transports/redis-stream-entry.mjs +177 -0
- package/dist/events/transports/redis.d.mts +76 -0
- package/dist/events/transports/redis.mjs +124 -0
- package/dist/externalPaths-SyPF2tgK.d.mts +50 -0
- package/dist/factory/index.d.mts +63 -0
- package/dist/factory/index.mjs +3 -0
- package/dist/fastifyAdapter-C8DlE0YH.d.mts +216 -0
- package/dist/fields-Bi_AVKSo.d.mts +109 -0
- package/dist/fields-CTd_CrKr.mjs +114 -0
- package/dist/hooks/index.d.mts +4 -0
- package/dist/hooks/index.mjs +3 -0
- package/dist/idempotency/index.d.mts +96 -0
- package/dist/idempotency/index.mjs +319 -0
- package/dist/idempotency/mongodb.d.mts +2 -0
- package/dist/idempotency/mongodb.mjs +114 -0
- package/dist/idempotency/redis.d.mts +2 -0
- package/dist/idempotency/redis.mjs +103 -0
- package/dist/index.d.mts +260 -0
- package/dist/index.mjs +104 -0
- package/dist/integrations/event-gateway.d.mts +46 -0
- package/dist/integrations/event-gateway.mjs +43 -0
- package/dist/integrations/index.d.mts +5 -0
- package/dist/integrations/index.mjs +1 -0
- package/dist/integrations/jobs.d.mts +103 -0
- package/dist/integrations/jobs.mjs +123 -0
- package/dist/integrations/streamline.d.mts +60 -0
- package/dist/integrations/streamline.mjs +125 -0
- package/dist/integrations/websocket.d.mts +82 -0
- package/dist/integrations/websocket.mjs +288 -0
- package/dist/interface-CSNjltAc.d.mts +77 -0
- package/dist/interface-DTbsvIWe.d.mts +54 -0
- package/dist/interface-e9XfSsUV.d.mts +1097 -0
- package/dist/introspectionPlugin-B3JkrjwU.mjs +53 -0
- package/dist/keys-DhqDRxv3.mjs +42 -0
- package/dist/logger-ByrvQWZO.mjs +78 -0
- package/dist/memory-B2v7KrCB.mjs +143 -0
- package/dist/migrations/index.d.mts +156 -0
- package/dist/migrations/index.mjs +260 -0
- package/dist/mongodb-ClykrfGo.d.mts +118 -0
- package/dist/mongodb-DNKEExbf.mjs +93 -0
- package/dist/mongodb-Dg8O_gvd.d.mts +71 -0
- package/dist/openapi-9nB_kiuR.mjs +525 -0
- package/dist/org/index.d.mts +68 -0
- package/dist/org/index.mjs +513 -0
- package/dist/org/types.d.mts +82 -0
- package/dist/org/types.mjs +1 -0
- package/dist/permissions/index.d.mts +278 -0
- package/dist/permissions/index.mjs +579 -0
- package/dist/plugins/index.d.mts +172 -0
- package/dist/plugins/index.mjs +522 -0
- package/dist/plugins/response-cache.d.mts +87 -0
- package/dist/plugins/response-cache.mjs +283 -0
- package/dist/plugins/tracing-entry.d.mts +2 -0
- package/dist/plugins/tracing-entry.mjs +185 -0
- package/dist/pluralize-CM-jZg7p.mjs +86 -0
- package/dist/policies/{index.d.ts → index.d.mts} +204 -170
- package/dist/policies/index.mjs +321 -0
- package/dist/presets/{index.d.ts → index.d.mts} +62 -131
- package/dist/presets/index.mjs +143 -0
- package/dist/presets/multiTenant.d.mts +24 -0
- package/dist/presets/multiTenant.mjs +113 -0
- package/dist/presets-BTeYbw7h.d.mts +57 -0
- package/dist/presets-CeFtfDR8.mjs +119 -0
- package/dist/prisma-C3iornoK.d.mts +274 -0
- package/dist/prisma-DJbMt3yf.mjs +627 -0
- package/dist/queryCachePlugin-B6R0d4av.mjs +138 -0
- package/dist/queryCachePlugin-Q6SYuHZ6.d.mts +71 -0
- package/dist/redis-UwjEp8Ea.d.mts +49 -0
- package/dist/redis-stream-CBg0upHI.d.mts +103 -0
- package/dist/registry/index.d.mts +11 -0
- package/dist/registry/index.mjs +4 -0
- package/dist/requestContext-xi6OKBL-.mjs +55 -0
- package/dist/schemaConverter-Dtg0Kt9T.mjs +98 -0
- package/dist/schemas/index.d.mts +63 -0
- package/dist/schemas/index.mjs +82 -0
- package/dist/scope/index.d.mts +21 -0
- package/dist/scope/index.mjs +65 -0
- package/dist/sessionManager-D_iEHjQl.d.mts +186 -0
- package/dist/sse-DkqQ1uxb.mjs +123 -0
- package/dist/testing/index.d.mts +907 -0
- package/dist/testing/index.mjs +1976 -0
- package/dist/tracing-8CEbhF0w.d.mts +70 -0
- package/dist/typeGuards-DwxA1t_L.mjs +9 -0
- package/dist/types/index.d.mts +946 -0
- package/dist/types/index.mjs +14 -0
- package/dist/types-B0dhNrnd.d.mts +445 -0
- package/dist/types-Beqn1Un7.mjs +38 -0
- package/dist/types-DelU6kln.mjs +25 -0
- package/dist/types-RLkFVgaw.d.mts +101 -0
- package/dist/utils/index.d.mts +747 -0
- package/dist/utils/index.mjs +6 -0
- package/package.json +194 -68
- package/dist/BaseController-DVAiHxEQ.d.ts +0 -233
- package/dist/adapters/index.d.ts +0 -237
- package/dist/adapters/index.js +0 -668
- package/dist/arcCorePlugin-CsShQdyP.d.ts +0 -273
- package/dist/audit/index.d.ts +0 -195
- package/dist/audit/index.js +0 -319
- package/dist/auth/index.d.ts +0 -47
- package/dist/auth/index.js +0 -174
- package/dist/cli/commands/docs.d.ts +0 -11
- package/dist/cli/commands/docs.js +0 -474
- package/dist/cli/commands/generate.js +0 -334
- package/dist/cli/commands/introspect.d.ts +0 -8
- package/dist/cli/commands/introspect.js +0 -338
- package/dist/cli/index.d.ts +0 -4
- package/dist/cli/index.js +0 -3269
- package/dist/core/index.d.ts +0 -220
- package/dist/core/index.js +0 -2786
- package/dist/createApp-Ce9wl8W9.d.ts +0 -77
- package/dist/docs/index.d.ts +0 -166
- package/dist/docs/index.js +0 -658
- package/dist/errors-8WIxGS_6.d.ts +0 -122
- package/dist/events/index.d.ts +0 -117
- package/dist/events/index.js +0 -89
- package/dist/factory/index.d.ts +0 -38
- package/dist/factory/index.js +0 -1652
- package/dist/hooks/index.d.ts +0 -4
- package/dist/hooks/index.js +0 -199
- package/dist/idempotency/index.d.ts +0 -323
- package/dist/idempotency/index.js +0 -500
- package/dist/index-B4t03KQ0.d.ts +0 -1366
- package/dist/index.d.ts +0 -135
- package/dist/index.js +0 -4756
- package/dist/migrations/index.d.ts +0 -185
- package/dist/migrations/index.js +0 -274
- package/dist/org/index.d.ts +0 -129
- package/dist/org/index.js +0 -220
- package/dist/permissions/index.d.ts +0 -144
- package/dist/permissions/index.js +0 -103
- package/dist/plugins/index.d.ts +0 -46
- package/dist/plugins/index.js +0 -1069
- package/dist/policies/index.js +0 -196
- package/dist/presets/index.js +0 -384
- package/dist/presets/multiTenant.d.ts +0 -39
- package/dist/presets/multiTenant.js +0 -112
- package/dist/registry/index.d.ts +0 -16
- package/dist/registry/index.js +0 -253
- package/dist/testing/index.d.ts +0 -618
- package/dist/testing/index.js +0 -48020
- package/dist/types/index.d.ts +0 -4
- package/dist/types/index.js +0 -8
- package/dist/types-B99TBmFV.d.ts +0 -76
- package/dist/types-BvckRbs2.d.ts +0 -143
- package/dist/utils/index.d.ts +0 -679
- package/dist/utils/index.js +0 -931
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { a as getTeamId, c as isElevated, i as getOrgRoles, l as isMember, n as PUBLIC_SCOPE, o as hasOrgAccess, r as getOrgId, s as isAuthenticated, t as AUTHENTICATED_SCOPE } from "../types-Beqn1Un7.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/types/index.ts
|
|
4
|
+
/**
|
|
5
|
+
* Extract user ID from a user object (supports both id and _id)
|
|
6
|
+
*/
|
|
7
|
+
function getUserId(user) {
|
|
8
|
+
if (!user) return void 0;
|
|
9
|
+
const id = user.id ?? user._id;
|
|
10
|
+
return id ? String(id) : void 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
//#endregion
|
|
14
|
+
export { AUTHENTICATED_SCOPE, PUBLIC_SCOPE, getOrgId, getOrgRoles, getTeamId, getUserId, hasOrgAccess, isAuthenticated, isElevated, isMember };
|
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
import { n as ElevationOptions } from "./elevation-DGo5shaX.mjs";
|
|
2
|
+
import { Authenticator } from "./types/index.mjs";
|
|
3
|
+
import { t as ExternalOpenApiPaths } from "./externalPaths-SyPF2tgK.mjs";
|
|
4
|
+
import { i as CacheStore } from "./interface-DTbsvIWe.mjs";
|
|
5
|
+
import { r as QueryCachePluginOptions } from "./queryCachePlugin-Q6SYuHZ6.mjs";
|
|
6
|
+
import { i as EventTransport } from "./EventTransport-BkUDYZEb.mjs";
|
|
7
|
+
import { t as EventPluginOptions } from "./eventPlugin-H6wDDjGO.mjs";
|
|
8
|
+
import { o as CachingOptions, r as SSEOptions, t as ErrorHandlerOptions } from "./errorHandler-CW3OOeYq.mjs";
|
|
9
|
+
import { r as IdempotencyStore } from "./interface-CSNjltAc.mjs";
|
|
10
|
+
import { FastifyInstance, FastifyPluginAsync, FastifyReply, FastifyRequest, FastifyServerOptions } from "fastify";
|
|
11
|
+
import { FastifyCorsOptions } from "@fastify/cors";
|
|
12
|
+
import { FastifyHelmetOptions } from "@fastify/helmet";
|
|
13
|
+
import { RateLimitOptions } from "@fastify/rate-limit";
|
|
14
|
+
|
|
15
|
+
//#region src/factory/types.d.ts
|
|
16
|
+
/**
|
|
17
|
+
* Arc's built-in JWT auth
|
|
18
|
+
*
|
|
19
|
+
* Registers @fastify/jwt, wires up `fastify.authenticate`, and
|
|
20
|
+
* exposes `fastify.auth` helpers (issueTokens, verifyRefreshToken).
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* const app = await createApp({
|
|
25
|
+
* auth: {
|
|
26
|
+
* type: 'jwt',
|
|
27
|
+
* jwt: { secret: process.env.JWT_SECRET },
|
|
28
|
+
* },
|
|
29
|
+
* });
|
|
30
|
+
*
|
|
31
|
+
* // With custom authenticator
|
|
32
|
+
* const app = await createApp({
|
|
33
|
+
* auth: {
|
|
34
|
+
* type: 'jwt',
|
|
35
|
+
* jwt: { secret: process.env.JWT_SECRET },
|
|
36
|
+
* authenticate: async (request, { jwt }) => {
|
|
37
|
+
* const token = request.headers.authorization?.split(' ')[1];
|
|
38
|
+
* if (!token) return null;
|
|
39
|
+
* const decoded = jwt.verify(token);
|
|
40
|
+
* return userRepo.findById(decoded.id);
|
|
41
|
+
* },
|
|
42
|
+
* },
|
|
43
|
+
* });
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
interface JwtAuthOption {
|
|
47
|
+
type: 'jwt';
|
|
48
|
+
/**
|
|
49
|
+
* JWT configuration (optional but recommended)
|
|
50
|
+
* If provided, jwt utilities are available in authenticator context
|
|
51
|
+
*/
|
|
52
|
+
jwt?: {
|
|
53
|
+
/** JWT secret (required for JWT features) */secret: string; /** Access token expiry (default: '15m') */
|
|
54
|
+
expiresIn?: string; /** Refresh token secret (defaults to main secret) */
|
|
55
|
+
refreshSecret?: string; /** Refresh token expiry (default: '7d') */
|
|
56
|
+
refreshExpiresIn?: string; /** Additional @fastify/jwt sign options */
|
|
57
|
+
sign?: Record<string, unknown>; /** Additional @fastify/jwt verify options */
|
|
58
|
+
verify?: Record<string, unknown>;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Custom authenticator function (recommended)
|
|
62
|
+
*
|
|
63
|
+
* Arc calls this for non-public routes.
|
|
64
|
+
* Return user object to authenticate, null/undefined to reject.
|
|
65
|
+
*
|
|
66
|
+
* If not provided and jwt.secret is set, uses default jwtVerify.
|
|
67
|
+
*/
|
|
68
|
+
authenticate?: Authenticator;
|
|
69
|
+
/**
|
|
70
|
+
* Custom auth failure handler
|
|
71
|
+
* Customize the 401 response when authentication fails
|
|
72
|
+
*/
|
|
73
|
+
onFailure?: (request: FastifyRequest, reply: FastifyReply, error?: Error) => void | Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Expose detailed auth error messages in 401 responses.
|
|
76
|
+
* When false (default), returns generic "Authentication required".
|
|
77
|
+
* When true, includes the actual error message for debugging.
|
|
78
|
+
* Decoupled from log level — set explicitly per environment.
|
|
79
|
+
*/
|
|
80
|
+
exposeAuthErrors?: boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Property name to store user on request (default: 'user')
|
|
83
|
+
*/
|
|
84
|
+
userProperty?: string;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Better Auth adapter integration
|
|
88
|
+
*
|
|
89
|
+
* When provided, Arc registers the Better Auth plugin (which sets up
|
|
90
|
+
* auth routes and decorates fastify.authenticate) and skips Arc's
|
|
91
|
+
* built-in JWT auth setup entirely.
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```typescript
|
|
95
|
+
* import { createBetterAuthAdapter } from '@classytic/arc-better-auth';
|
|
96
|
+
*
|
|
97
|
+
* const app = await createApp({
|
|
98
|
+
* auth: { type: 'betterAuth', betterAuth: createBetterAuthAdapter({ auth: myBetterAuth }) },
|
|
99
|
+
* });
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
interface BetterAuthOption {
|
|
103
|
+
type: 'betterAuth';
|
|
104
|
+
/** Better Auth adapter — pass the result of createBetterAuthAdapter() */
|
|
105
|
+
betterAuth: {
|
|
106
|
+
plugin: FastifyPluginAsync;
|
|
107
|
+
openapi?: ExternalOpenApiPaths;
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Custom auth plugin — full control over authentication setup
|
|
112
|
+
*
|
|
113
|
+
* The plugin is registered directly on the Fastify instance.
|
|
114
|
+
* It must decorate `fastify.authenticate` for protected routes to work.
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```typescript
|
|
118
|
+
* const app = await createApp({
|
|
119
|
+
* auth: {
|
|
120
|
+
* type: 'custom',
|
|
121
|
+
* plugin: async (fastify) => {
|
|
122
|
+
* fastify.decorate('authenticate', async (request, reply) => { ... });
|
|
123
|
+
* },
|
|
124
|
+
* },
|
|
125
|
+
* });
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
interface CustomPluginAuthOption {
|
|
129
|
+
type: 'custom';
|
|
130
|
+
/** Custom Fastify plugin that sets up authentication */
|
|
131
|
+
plugin: FastifyPluginAsync;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Custom authenticator function — lightweight alternative to a full plugin
|
|
135
|
+
*
|
|
136
|
+
* Arc decorates `fastify.authenticate` with this function directly.
|
|
137
|
+
* No JWT setup, no Arc auth plugin — just your function.
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* ```typescript
|
|
141
|
+
* const app = await createApp({
|
|
142
|
+
* auth: {
|
|
143
|
+
* type: 'authenticator',
|
|
144
|
+
* authenticate: async (request, reply) => {
|
|
145
|
+
* const session = await validateSession(request);
|
|
146
|
+
* if (!session) reply.code(401).send({ error: 'Unauthorized' });
|
|
147
|
+
* request.user = session.user;
|
|
148
|
+
* },
|
|
149
|
+
* },
|
|
150
|
+
* });
|
|
151
|
+
* ```
|
|
152
|
+
*/
|
|
153
|
+
interface CustomAuthenticatorOption {
|
|
154
|
+
type: 'authenticator';
|
|
155
|
+
/** Authenticate function — decorates fastify.authenticate directly */
|
|
156
|
+
authenticate: (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* All supported auth configuration shapes
|
|
160
|
+
*
|
|
161
|
+
* - `false` — Disable authentication entirely
|
|
162
|
+
* - `JwtAuthOption` — Arc's built-in JWT auth (`type: 'jwt'`)
|
|
163
|
+
* - `BetterAuthOption` — Better Auth adapter integration (`type: 'betterAuth'`)
|
|
164
|
+
* - `CustomPluginAuthOption` — Your own Fastify auth plugin (`type: 'custom'`)
|
|
165
|
+
* - `CustomAuthenticatorOption` — A bare authenticate function (`type: 'authenticator'`)
|
|
166
|
+
*/
|
|
167
|
+
type AuthOption = false | JwtAuthOption | BetterAuthOption | CustomPluginAuthOption | CustomAuthenticatorOption;
|
|
168
|
+
/**
|
|
169
|
+
* CreateApp Options
|
|
170
|
+
*
|
|
171
|
+
* Configuration for creating an Arc application.
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```typescript
|
|
175
|
+
* // Minimal setup
|
|
176
|
+
* const app = await createApp({
|
|
177
|
+
* preset: 'development',
|
|
178
|
+
* auth: {
|
|
179
|
+
* type: 'jwt',
|
|
180
|
+
* jwt: { secret: process.env.JWT_SECRET },
|
|
181
|
+
* },
|
|
182
|
+
* });
|
|
183
|
+
*
|
|
184
|
+
* // With custom authenticator
|
|
185
|
+
* const app = await createApp({
|
|
186
|
+
* preset: 'production',
|
|
187
|
+
* auth: {
|
|
188
|
+
* type: 'jwt',
|
|
189
|
+
* jwt: { secret: process.env.JWT_SECRET },
|
|
190
|
+
* authenticate: async (request, { jwt }) => {
|
|
191
|
+
* // Check API key first
|
|
192
|
+
* const apiKey = request.headers['x-api-key'];
|
|
193
|
+
* if (apiKey) {
|
|
194
|
+
* const result = await apiKeyService.verify(apiKey);
|
|
195
|
+
* if (result) return { _id: result.userId, isApiKey: true };
|
|
196
|
+
* }
|
|
197
|
+
* // Then check JWT
|
|
198
|
+
* const token = request.headers.authorization?.split(' ')[1];
|
|
199
|
+
* if (token) {
|
|
200
|
+
* const decoded = jwt.verify(token);
|
|
201
|
+
* return userRepo.findById(decoded.id);
|
|
202
|
+
* }
|
|
203
|
+
* return null;
|
|
204
|
+
* },
|
|
205
|
+
* },
|
|
206
|
+
* });
|
|
207
|
+
* ```
|
|
208
|
+
*/
|
|
209
|
+
interface CreateAppOptions {
|
|
210
|
+
/** Environment preset: 'production', 'development', 'testing', or 'edge' */
|
|
211
|
+
preset?: 'production' | 'development' | 'testing' | 'edge';
|
|
212
|
+
/**
|
|
213
|
+
* Runtime profile for store backends.
|
|
214
|
+
* - 'memory' (default): Uses in-memory stores. Suitable for single-instance deployments.
|
|
215
|
+
* - 'distributed': Requires Redis-compatible adapters for cache, events, idempotency.
|
|
216
|
+
* Startup fails fast if any required distributed adapter is missing.
|
|
217
|
+
*/
|
|
218
|
+
runtime?: 'memory' | 'distributed';
|
|
219
|
+
/**
|
|
220
|
+
* Store and transport instances for runtime profile validation.
|
|
221
|
+
* When `runtime` is `'distributed'`, Arc validates that these are
|
|
222
|
+
* not memory-backed. Provide Redis or other durable adapters.
|
|
223
|
+
*/
|
|
224
|
+
stores?: {
|
|
225
|
+
/** Event transport (e.g., RedisEventTransport) */events?: EventTransport; /** Cache store (e.g., RedisCacheStore) */
|
|
226
|
+
cache?: CacheStore; /** Idempotency store (e.g., RedisIdempotencyStore) */
|
|
227
|
+
idempotency?: IdempotencyStore; /** QueryCache store (e.g., RedisCacheStore). Default: MemoryCacheStore. */
|
|
228
|
+
queryCache?: CacheStore;
|
|
229
|
+
};
|
|
230
|
+
/** Fastify logger configuration */
|
|
231
|
+
logger?: FastifyServerOptions['logger'];
|
|
232
|
+
/**
|
|
233
|
+
* Enable Arc debug logging.
|
|
234
|
+
*
|
|
235
|
+
* - `true` — all Arc modules
|
|
236
|
+
* - `string` — comma-separated module names (e.g., `'scope,elevation,sse'`)
|
|
237
|
+
* - `false` or omit — disabled (default)
|
|
238
|
+
*
|
|
239
|
+
* Also configurable via `ARC_DEBUG` environment variable.
|
|
240
|
+
*
|
|
241
|
+
* @example
|
|
242
|
+
* ```typescript
|
|
243
|
+
* // All modules
|
|
244
|
+
* const app = await createApp({ debug: true });
|
|
245
|
+
*
|
|
246
|
+
* // Specific modules
|
|
247
|
+
* const app = await createApp({ debug: 'scope,elevation' });
|
|
248
|
+
* ```
|
|
249
|
+
*/
|
|
250
|
+
debug?: boolean | string;
|
|
251
|
+
/** Trust proxy headers (X-Forwarded-For, etc.) */
|
|
252
|
+
trustProxy?: boolean;
|
|
253
|
+
/**
|
|
254
|
+
* Auth configuration
|
|
255
|
+
*
|
|
256
|
+
* Set to false to disable authentication entirely.
|
|
257
|
+
* Each auth strategy requires a `type` discriminant field.
|
|
258
|
+
*
|
|
259
|
+
* @example
|
|
260
|
+
* ```typescript
|
|
261
|
+
* // Disable auth
|
|
262
|
+
* auth: false,
|
|
263
|
+
*
|
|
264
|
+
* // Arc JWT
|
|
265
|
+
* auth: {
|
|
266
|
+
* type: 'jwt',
|
|
267
|
+
* jwt: { secret: process.env.JWT_SECRET },
|
|
268
|
+
* },
|
|
269
|
+
*
|
|
270
|
+
* // Arc JWT + custom authenticator
|
|
271
|
+
* auth: {
|
|
272
|
+
* type: 'jwt',
|
|
273
|
+
* jwt: { secret: process.env.JWT_SECRET },
|
|
274
|
+
* authenticate: async (request, { jwt }) => {
|
|
275
|
+
* const token = request.headers.authorization?.split(' ')[1];
|
|
276
|
+
* if (!token) return null;
|
|
277
|
+
* const decoded = jwt.verify(token);
|
|
278
|
+
* return userRepo.findById(decoded.id);
|
|
279
|
+
* },
|
|
280
|
+
* },
|
|
281
|
+
*
|
|
282
|
+
* // Better Auth adapter
|
|
283
|
+
* auth: { type: 'betterAuth', betterAuth: createBetterAuthAdapter({ auth: myBetterAuth }) },
|
|
284
|
+
*
|
|
285
|
+
* // Custom auth plugin
|
|
286
|
+
* auth: {
|
|
287
|
+
* type: 'custom',
|
|
288
|
+
* plugin: async (fastify) => {
|
|
289
|
+
* fastify.decorate('authenticate', async (req, reply) => { ... });
|
|
290
|
+
* },
|
|
291
|
+
* },
|
|
292
|
+
*
|
|
293
|
+
* // Custom authenticator function
|
|
294
|
+
* auth: {
|
|
295
|
+
* type: 'authenticator',
|
|
296
|
+
* authenticate: async (request, reply) => {
|
|
297
|
+
* const session = await validateSession(request);
|
|
298
|
+
* if (!session) reply.code(401).send({ error: 'Unauthorized' });
|
|
299
|
+
* request.user = session.user;
|
|
300
|
+
* },
|
|
301
|
+
* },
|
|
302
|
+
* ```
|
|
303
|
+
*/
|
|
304
|
+
auth?: AuthOption;
|
|
305
|
+
/**
|
|
306
|
+
* Platform admin elevation — opt-in for apps with superadmins.
|
|
307
|
+
*
|
|
308
|
+
* When configured, platform admins can explicitly elevate their scope
|
|
309
|
+
* by sending `x-arc-scope: platform` header. Without this header,
|
|
310
|
+
* superadmins are treated as normal users.
|
|
311
|
+
*
|
|
312
|
+
* Set to `false` or omit to disable elevation entirely.
|
|
313
|
+
*
|
|
314
|
+
* @example
|
|
315
|
+
* ```typescript
|
|
316
|
+
* elevation: {
|
|
317
|
+
* platformRoles: ['superadmin'],
|
|
318
|
+
* onElevation: (event) => auditLog.write({
|
|
319
|
+
* action: 'platform_elevation',
|
|
320
|
+
* userId: event.userId,
|
|
321
|
+
* targetOrg: event.organizationId,
|
|
322
|
+
* }),
|
|
323
|
+
* }
|
|
324
|
+
* ```
|
|
325
|
+
*/
|
|
326
|
+
elevation?: ElevationOptions | false;
|
|
327
|
+
/** Helmet security headers. Set to false to disable. */
|
|
328
|
+
helmet?: FastifyHelmetOptions | false;
|
|
329
|
+
/** CORS configuration. Set to false to disable. */
|
|
330
|
+
cors?: FastifyCorsOptions | false;
|
|
331
|
+
/** Rate limiting. Set to false to disable. */
|
|
332
|
+
rateLimit?: RateLimitOptions | false;
|
|
333
|
+
/** Under pressure health monitoring. Set to false to disable. */
|
|
334
|
+
underPressure?: UnderPressureOptions | false;
|
|
335
|
+
/** @fastify/sensible (HTTP helpers). Set to false to disable. */
|
|
336
|
+
sensible?: boolean | false;
|
|
337
|
+
/** @fastify/multipart (file uploads). Set to false to disable. */
|
|
338
|
+
multipart?: MultipartOptions | false;
|
|
339
|
+
/** Raw body parsing (for webhooks). Set to false to disable. */
|
|
340
|
+
rawBody?: RawBodyOptions | false;
|
|
341
|
+
/** Enable Arc plugins (requestId, health, gracefulShutdown, events, caching, sse) */
|
|
342
|
+
arcPlugins?: {
|
|
343
|
+
/** Request ID tracking (default: true) */requestId?: boolean; /** Health endpoints (default: true) */
|
|
344
|
+
health?: boolean; /** Graceful shutdown handling (default: true) */
|
|
345
|
+
gracefulShutdown?: boolean; /** Emit events for CRUD operations (default: true) */
|
|
346
|
+
emitEvents?: boolean;
|
|
347
|
+
/**
|
|
348
|
+
* Event plugin configuration. Default: true (enabled with MemoryEventTransport).
|
|
349
|
+
* Set to false to disable event plugin registration entirely.
|
|
350
|
+
* Set to true for defaults (memory transport), or pass EventPluginOptions for fine control.
|
|
351
|
+
* Transport is sourced from `stores.events` if provided, otherwise defaults to memory.
|
|
352
|
+
*
|
|
353
|
+
* When enabled, registers `eventPlugin` which provides `fastify.events` for
|
|
354
|
+
* pub/sub. Combined with `emitEvents: true`, CRUD operations automatically
|
|
355
|
+
* emit domain events (e.g., `product.created`, `order.updated`).
|
|
356
|
+
*
|
|
357
|
+
* @example
|
|
358
|
+
* ```typescript
|
|
359
|
+
* // Memory transport (default)
|
|
360
|
+
* const app = await createApp({ arcPlugins: { events: true } });
|
|
361
|
+
*
|
|
362
|
+
* // With retry and logging
|
|
363
|
+
* const app = await createApp({
|
|
364
|
+
* stores: { events: new RedisEventTransport({ url: 'redis://...' }) },
|
|
365
|
+
* arcPlugins: {
|
|
366
|
+
* events: {
|
|
367
|
+
* logEvents: true,
|
|
368
|
+
* retry: { maxRetries: 3, backoffMs: 1000 },
|
|
369
|
+
* },
|
|
370
|
+
* },
|
|
371
|
+
* });
|
|
372
|
+
* ```
|
|
373
|
+
*/
|
|
374
|
+
events?: Omit<EventPluginOptions, 'transport'> | boolean;
|
|
375
|
+
/**
|
|
376
|
+
* Caching headers (ETag + Cache-Control). Default: false (opt-in).
|
|
377
|
+
* Set to true for defaults, or pass CachingOptions for fine control.
|
|
378
|
+
*/
|
|
379
|
+
caching?: CachingOptions | boolean;
|
|
380
|
+
/**
|
|
381
|
+
* SSE event streaming. Default: false (opt-in).
|
|
382
|
+
* Set to true for defaults, or pass SSEOptions for fine control.
|
|
383
|
+
* Requires emitEvents to be enabled (or events plugin registered).
|
|
384
|
+
*/
|
|
385
|
+
sse?: SSEOptions | boolean;
|
|
386
|
+
/**
|
|
387
|
+
* QueryCache — TanStack Query-inspired server cache with SWR.
|
|
388
|
+
* Default: false (opt-in). Set to true for memory store defaults.
|
|
389
|
+
* Requires per-resource `cache` config on defineResource().
|
|
390
|
+
*/
|
|
391
|
+
queryCache?: QueryCachePluginOptions | boolean;
|
|
392
|
+
};
|
|
393
|
+
/**
|
|
394
|
+
* Type provider for schema inference.
|
|
395
|
+
*
|
|
396
|
+
* When set to `'typebox'`, enables TypeBox type provider for
|
|
397
|
+
* automatic TypeScript inference from route schemas.
|
|
398
|
+
*
|
|
399
|
+
* Requires `@sinclair/typebox` and `@fastify/type-provider-typebox` installed.
|
|
400
|
+
*
|
|
401
|
+
* @example
|
|
402
|
+
* ```typescript
|
|
403
|
+
* import { Type } from '@classytic/arc/schemas';
|
|
404
|
+
*
|
|
405
|
+
* const app = await createApp({
|
|
406
|
+
* typeProvider: 'typebox',
|
|
407
|
+
* });
|
|
408
|
+
*
|
|
409
|
+
* // Now route schemas built with Type.* give full TS inference
|
|
410
|
+
* ```
|
|
411
|
+
*/
|
|
412
|
+
typeProvider?: 'typebox';
|
|
413
|
+
/**
|
|
414
|
+
* Error handler plugin. Normalizes AJV, Mongoose, and ArcError responses
|
|
415
|
+
* into a consistent JSON envelope. Enabled by default.
|
|
416
|
+
* Set to false to disable, or pass ErrorHandlerOptions for fine control.
|
|
417
|
+
*/
|
|
418
|
+
errorHandler?: ErrorHandlerOptions | false;
|
|
419
|
+
/** Custom plugin registration function */
|
|
420
|
+
plugins?: (fastify: FastifyInstance) => Promise<void>;
|
|
421
|
+
/** Hook called after all plugins are loaded and the app is ready */
|
|
422
|
+
onReady?: (fastify: FastifyInstance) => void | Promise<void>;
|
|
423
|
+
/** Hook called when the app is shutting down */
|
|
424
|
+
onClose?: (fastify: FastifyInstance) => void | Promise<void>;
|
|
425
|
+
}
|
|
426
|
+
interface UnderPressureOptions {
|
|
427
|
+
exposeStatusRoute?: boolean;
|
|
428
|
+
maxEventLoopDelay?: number;
|
|
429
|
+
maxHeapUsedBytes?: number;
|
|
430
|
+
maxRssBytes?: number;
|
|
431
|
+
}
|
|
432
|
+
interface MultipartOptions {
|
|
433
|
+
limits?: {
|
|
434
|
+
fileSize?: number;
|
|
435
|
+
files?: number;
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
interface RawBodyOptions {
|
|
439
|
+
field?: string;
|
|
440
|
+
global?: boolean;
|
|
441
|
+
encoding?: string;
|
|
442
|
+
runFirst?: boolean;
|
|
443
|
+
}
|
|
444
|
+
//#endregion
|
|
445
|
+
export { CustomPluginAuthOption as a, RawBodyOptions as c, CustomAuthenticatorOption as i, UnderPressureOptions as l, BetterAuthOption as n, JwtAuthOption as o, CreateAppOptions as r, MultipartOptions as s, AuthOption as t };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//#region src/scope/types.ts
|
|
2
|
+
/** Check if scope is `member` kind */
|
|
3
|
+
function isMember(scope) {
|
|
4
|
+
return scope.kind === "member";
|
|
5
|
+
}
|
|
6
|
+
/** Check if scope is `elevated` kind */
|
|
7
|
+
function isElevated(scope) {
|
|
8
|
+
return scope.kind === "elevated";
|
|
9
|
+
}
|
|
10
|
+
/** Check if scope has org access (member OR elevated) */
|
|
11
|
+
function hasOrgAccess(scope) {
|
|
12
|
+
return scope.kind === "member" || scope.kind === "elevated";
|
|
13
|
+
}
|
|
14
|
+
/** Check if request is authenticated (any kind except public) */
|
|
15
|
+
function isAuthenticated(scope) {
|
|
16
|
+
return scope.kind !== "public";
|
|
17
|
+
}
|
|
18
|
+
/** Get organizationId from scope (if present) */
|
|
19
|
+
function getOrgId(scope) {
|
|
20
|
+
if (scope.kind === "member") return scope.organizationId;
|
|
21
|
+
if (scope.kind === "elevated") return scope.organizationId;
|
|
22
|
+
}
|
|
23
|
+
/** Get org roles from scope (empty array if not a member) */
|
|
24
|
+
function getOrgRoles(scope) {
|
|
25
|
+
if (scope.kind === "member") return scope.orgRoles;
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
/** Get team ID from scope (only available on member kind) */
|
|
29
|
+
function getTeamId(scope) {
|
|
30
|
+
if (scope.kind === "member") return scope.teamId;
|
|
31
|
+
}
|
|
32
|
+
/** Default public scope — used as initial decoration value */
|
|
33
|
+
const PUBLIC_SCOPE = Object.freeze({ kind: "public" });
|
|
34
|
+
/** Default authenticated scope — used when user is logged in but no org */
|
|
35
|
+
const AUTHENTICATED_SCOPE = Object.freeze({ kind: "authenticated" });
|
|
36
|
+
|
|
37
|
+
//#endregion
|
|
38
|
+
export { getTeamId as a, isElevated as c, getOrgRoles as i, isMember as l, PUBLIC_SCOPE as n, hasOrgAccess as o, getOrgId as r, isAuthenticated as s, AUTHENTICATED_SCOPE as t };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
//#region src/permissions/types.ts
|
|
2
|
+
/**
|
|
3
|
+
* Extract normalized roles from a user object.
|
|
4
|
+
*
|
|
5
|
+
* Reads `user.role` which can be:
|
|
6
|
+
* - A comma-separated string: `"superadmin,user"` (Better Auth admin plugin)
|
|
7
|
+
* - A string array: `["admin", "user"]` (JWT / custom auth)
|
|
8
|
+
* - A single string: `"admin"`
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Normalize a raw role value (string, comma-separated string, or array) into a string[].
|
|
12
|
+
* Shared low-level helper used by both getUserRoles() and the Better Auth adapter.
|
|
13
|
+
*/
|
|
14
|
+
function normalizeRoles(value) {
|
|
15
|
+
if (Array.isArray(value)) return value.map((r) => String(r).trim()).filter(Boolean);
|
|
16
|
+
if (typeof value === "string" && value.length > 0) return value.split(",").map((r) => r.trim()).filter(Boolean);
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
function getUserRoles(user) {
|
|
20
|
+
if (!user) return [];
|
|
21
|
+
return normalizeRoles(user.role);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
//#endregion
|
|
25
|
+
export { normalizeRoles as n, getUserRoles as t };
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { FastifyRequest } from "fastify";
|
|
2
|
+
|
|
3
|
+
//#region src/permissions/types.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* User base interface - minimal shape Arc expects
|
|
6
|
+
* Your actual User can have any additional fields
|
|
7
|
+
*/
|
|
8
|
+
interface UserBase {
|
|
9
|
+
id?: string;
|
|
10
|
+
_id?: string;
|
|
11
|
+
/** User roles — string (comma-separated), string[], or undefined. Matches Better Auth's admin plugin pattern. */
|
|
12
|
+
role?: string | string[];
|
|
13
|
+
[key: string]: unknown;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Extract normalized roles from a user object.
|
|
17
|
+
*
|
|
18
|
+
* Reads `user.role` which can be:
|
|
19
|
+
* - A comma-separated string: `"superadmin,user"` (Better Auth admin plugin)
|
|
20
|
+
* - A string array: `["admin", "user"]` (JWT / custom auth)
|
|
21
|
+
* - A single string: `"admin"`
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Normalize a raw role value (string, comma-separated string, or array) into a string[].
|
|
25
|
+
* Shared low-level helper used by both getUserRoles() and the Better Auth adapter.
|
|
26
|
+
*/
|
|
27
|
+
declare function normalizeRoles(value: unknown): string[];
|
|
28
|
+
declare function getUserRoles(user: UserBase | null | undefined): string[];
|
|
29
|
+
/**
|
|
30
|
+
* Context passed to permission check functions
|
|
31
|
+
*/
|
|
32
|
+
interface PermissionContext<TDoc = any> {
|
|
33
|
+
/** Authenticated user or null if unauthenticated */
|
|
34
|
+
user: UserBase | null;
|
|
35
|
+
/** Fastify request object */
|
|
36
|
+
request: FastifyRequest;
|
|
37
|
+
/** Resource name being accessed */
|
|
38
|
+
resource: string;
|
|
39
|
+
/** Action being performed (list, get, create, update, delete, or custom operation name) */
|
|
40
|
+
action: string;
|
|
41
|
+
/** Resource ID for single-resource operations (shortcut for params.id) */
|
|
42
|
+
resourceId?: string;
|
|
43
|
+
/** All route parameters (slug, parentId, custom params, etc.) */
|
|
44
|
+
params?: Record<string, string>;
|
|
45
|
+
/** Request body data */
|
|
46
|
+
data?: Partial<TDoc> | Record<string, unknown>;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Result from permission check
|
|
50
|
+
*/
|
|
51
|
+
interface PermissionResult {
|
|
52
|
+
/** Whether access is granted */
|
|
53
|
+
granted: boolean;
|
|
54
|
+
/** Reason for denial (for error messages) */
|
|
55
|
+
reason?: string;
|
|
56
|
+
/** Query filters to apply (for ownership patterns) */
|
|
57
|
+
filters?: Record<string, unknown>;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Permission Check Function
|
|
61
|
+
*
|
|
62
|
+
* THE ONLY way to define permissions in Arc.
|
|
63
|
+
* Returns boolean, PermissionResult, or Promise of either.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```typescript
|
|
67
|
+
* // Simple boolean return
|
|
68
|
+
* const isAdmin: PermissionCheck = (ctx) => getUserRoles(ctx.user).includes('admin');
|
|
69
|
+
*
|
|
70
|
+
* // With filters for ownership
|
|
71
|
+
* const ownedByUser: PermissionCheck = (ctx) => ({
|
|
72
|
+
* granted: true,
|
|
73
|
+
* filters: { userId: ctx.user?.id }
|
|
74
|
+
* });
|
|
75
|
+
*
|
|
76
|
+
* // Async check
|
|
77
|
+
* const canAccessOrg: PermissionCheck = async (ctx) => {
|
|
78
|
+
* const isMember = await checkMembership(ctx.user?.id, ctx.organizationId);
|
|
79
|
+
* return { granted: isMember, reason: isMember ? undefined : 'Not a member' };
|
|
80
|
+
* };
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
type PermissionCheck<TDoc = any> = ((context: PermissionContext<TDoc>) => boolean | PermissionResult | Promise<boolean | PermissionResult>) & PermissionCheckMeta;
|
|
84
|
+
/**
|
|
85
|
+
* Optional metadata attached to permission check functions.
|
|
86
|
+
* Used for OpenAPI docs, introspection, and route-level auth decisions.
|
|
87
|
+
*/
|
|
88
|
+
interface PermissionCheckMeta {
|
|
89
|
+
/** Set by allowPublic() — marks the endpoint as publicly accessible */
|
|
90
|
+
_isPublic?: boolean;
|
|
91
|
+
/** Set by requireRoles() — the roles required for access */
|
|
92
|
+
_roles?: readonly string[];
|
|
93
|
+
/** Set by requireOrgMembership() — org-level permission type */
|
|
94
|
+
_orgPermission?: string;
|
|
95
|
+
/** Set by requireOrgRole() — the org roles required for access */
|
|
96
|
+
_orgRoles?: readonly string[];
|
|
97
|
+
/** Set by requireTeamMembership() — team-level permission type */
|
|
98
|
+
_teamPermission?: string;
|
|
99
|
+
}
|
|
100
|
+
//#endregion
|
|
101
|
+
export { getUserRoles as a, UserBase as i, PermissionContext as n, normalizeRoles as o, PermissionResult as r, PermissionCheck as t };
|