@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.
Files changed (200) hide show
  1. package/README.md +247 -794
  2. package/bin/arc.js +91 -52
  3. package/dist/EventTransport-BkUDYZEb.d.mts +99 -0
  4. package/dist/HookSystem-BsGV-j2l.mjs +404 -0
  5. package/dist/ResourceRegistry-7Ic20ZMw.mjs +249 -0
  6. package/dist/adapters/index.d.mts +5 -0
  7. package/dist/adapters/index.mjs +3 -0
  8. package/dist/audit/index.d.mts +81 -0
  9. package/dist/audit/index.mjs +275 -0
  10. package/dist/audit/mongodb.d.mts +5 -0
  11. package/dist/audit/mongodb.mjs +3 -0
  12. package/dist/audited-CGdLiSlE.mjs +140 -0
  13. package/dist/auth/index.d.mts +188 -0
  14. package/dist/auth/index.mjs +1096 -0
  15. package/dist/auth/redis-session.d.mts +43 -0
  16. package/dist/auth/redis-session.mjs +75 -0
  17. package/dist/betterAuthOpenApi-DjWDddNc.mjs +249 -0
  18. package/dist/cache/index.d.mts +145 -0
  19. package/dist/cache/index.mjs +91 -0
  20. package/dist/caching-GSDJcA6-.mjs +93 -0
  21. package/dist/chunk-C7Uep-_p.mjs +20 -0
  22. package/dist/circuitBreaker-DYhWBW_D.mjs +1096 -0
  23. package/dist/cli/commands/describe.d.mts +18 -0
  24. package/dist/cli/commands/describe.mjs +238 -0
  25. package/dist/cli/commands/docs.d.mts +13 -0
  26. package/dist/cli/commands/docs.mjs +52 -0
  27. package/dist/cli/commands/{generate.d.ts → generate.d.mts} +3 -2
  28. package/dist/cli/commands/generate.mjs +357 -0
  29. package/dist/cli/commands/{init.d.ts → init.d.mts} +11 -8
  30. package/dist/cli/commands/{init.js → init.mjs} +807 -617
  31. package/dist/cli/commands/introspect.d.mts +10 -0
  32. package/dist/cli/commands/introspect.mjs +75 -0
  33. package/dist/cli/index.d.mts +16 -0
  34. package/dist/cli/index.mjs +156 -0
  35. package/dist/constants-DdXFXQtN.mjs +84 -0
  36. package/dist/core/index.d.mts +5 -0
  37. package/dist/core/index.mjs +4 -0
  38. package/dist/createApp-D2D5XXaV.mjs +559 -0
  39. package/dist/defineResource-PXzSJ15_.mjs +2197 -0
  40. package/dist/discovery/index.d.mts +46 -0
  41. package/dist/discovery/index.mjs +109 -0
  42. package/dist/docs/index.d.mts +162 -0
  43. package/dist/docs/index.mjs +74 -0
  44. package/dist/elevation-DGo5shaX.d.mts +87 -0
  45. package/dist/elevation-DSTbVvYj.mjs +113 -0
  46. package/dist/errorHandler-C3GY3_ow.mjs +108 -0
  47. package/dist/errorHandler-CW3OOeYq.d.mts +72 -0
  48. package/dist/errors-DAWRdiYP.d.mts +124 -0
  49. package/dist/errors-DBANPbGr.mjs +211 -0
  50. package/dist/eventPlugin-BEOvaDqo.mjs +229 -0
  51. package/dist/eventPlugin-H6wDDjGO.d.mts +124 -0
  52. package/dist/events/index.d.mts +53 -0
  53. package/dist/events/index.mjs +51 -0
  54. package/dist/events/transports/redis-stream-entry.d.mts +2 -0
  55. package/dist/events/transports/redis-stream-entry.mjs +177 -0
  56. package/dist/events/transports/redis.d.mts +76 -0
  57. package/dist/events/transports/redis.mjs +124 -0
  58. package/dist/externalPaths-SyPF2tgK.d.mts +50 -0
  59. package/dist/factory/index.d.mts +63 -0
  60. package/dist/factory/index.mjs +3 -0
  61. package/dist/fastifyAdapter-C8DlE0YH.d.mts +216 -0
  62. package/dist/fields-Bi_AVKSo.d.mts +109 -0
  63. package/dist/fields-CTd_CrKr.mjs +114 -0
  64. package/dist/hooks/index.d.mts +4 -0
  65. package/dist/hooks/index.mjs +3 -0
  66. package/dist/idempotency/index.d.mts +96 -0
  67. package/dist/idempotency/index.mjs +319 -0
  68. package/dist/idempotency/mongodb.d.mts +2 -0
  69. package/dist/idempotency/mongodb.mjs +114 -0
  70. package/dist/idempotency/redis.d.mts +2 -0
  71. package/dist/idempotency/redis.mjs +103 -0
  72. package/dist/index.d.mts +260 -0
  73. package/dist/index.mjs +104 -0
  74. package/dist/integrations/event-gateway.d.mts +46 -0
  75. package/dist/integrations/event-gateway.mjs +43 -0
  76. package/dist/integrations/index.d.mts +5 -0
  77. package/dist/integrations/index.mjs +1 -0
  78. package/dist/integrations/jobs.d.mts +103 -0
  79. package/dist/integrations/jobs.mjs +123 -0
  80. package/dist/integrations/streamline.d.mts +60 -0
  81. package/dist/integrations/streamline.mjs +125 -0
  82. package/dist/integrations/websocket.d.mts +82 -0
  83. package/dist/integrations/websocket.mjs +288 -0
  84. package/dist/interface-CSNjltAc.d.mts +77 -0
  85. package/dist/interface-DTbsvIWe.d.mts +54 -0
  86. package/dist/interface-e9XfSsUV.d.mts +1097 -0
  87. package/dist/introspectionPlugin-B3JkrjwU.mjs +53 -0
  88. package/dist/keys-DhqDRxv3.mjs +42 -0
  89. package/dist/logger-ByrvQWZO.mjs +78 -0
  90. package/dist/memory-B2v7KrCB.mjs +143 -0
  91. package/dist/migrations/index.d.mts +156 -0
  92. package/dist/migrations/index.mjs +260 -0
  93. package/dist/mongodb-ClykrfGo.d.mts +118 -0
  94. package/dist/mongodb-DNKEExbf.mjs +93 -0
  95. package/dist/mongodb-Dg8O_gvd.d.mts +71 -0
  96. package/dist/openapi-9nB_kiuR.mjs +525 -0
  97. package/dist/org/index.d.mts +68 -0
  98. package/dist/org/index.mjs +513 -0
  99. package/dist/org/types.d.mts +82 -0
  100. package/dist/org/types.mjs +1 -0
  101. package/dist/permissions/index.d.mts +278 -0
  102. package/dist/permissions/index.mjs +579 -0
  103. package/dist/plugins/index.d.mts +172 -0
  104. package/dist/plugins/index.mjs +522 -0
  105. package/dist/plugins/response-cache.d.mts +87 -0
  106. package/dist/plugins/response-cache.mjs +283 -0
  107. package/dist/plugins/tracing-entry.d.mts +2 -0
  108. package/dist/plugins/tracing-entry.mjs +185 -0
  109. package/dist/pluralize-CM-jZg7p.mjs +86 -0
  110. package/dist/policies/{index.d.ts → index.d.mts} +204 -170
  111. package/dist/policies/index.mjs +321 -0
  112. package/dist/presets/{index.d.ts → index.d.mts} +62 -131
  113. package/dist/presets/index.mjs +143 -0
  114. package/dist/presets/multiTenant.d.mts +24 -0
  115. package/dist/presets/multiTenant.mjs +113 -0
  116. package/dist/presets-BTeYbw7h.d.mts +57 -0
  117. package/dist/presets-CeFtfDR8.mjs +119 -0
  118. package/dist/prisma-C3iornoK.d.mts +274 -0
  119. package/dist/prisma-DJbMt3yf.mjs +627 -0
  120. package/dist/queryCachePlugin-B6R0d4av.mjs +138 -0
  121. package/dist/queryCachePlugin-Q6SYuHZ6.d.mts +71 -0
  122. package/dist/redis-UwjEp8Ea.d.mts +49 -0
  123. package/dist/redis-stream-CBg0upHI.d.mts +103 -0
  124. package/dist/registry/index.d.mts +11 -0
  125. package/dist/registry/index.mjs +4 -0
  126. package/dist/requestContext-xi6OKBL-.mjs +55 -0
  127. package/dist/schemaConverter-Dtg0Kt9T.mjs +98 -0
  128. package/dist/schemas/index.d.mts +63 -0
  129. package/dist/schemas/index.mjs +82 -0
  130. package/dist/scope/index.d.mts +21 -0
  131. package/dist/scope/index.mjs +65 -0
  132. package/dist/sessionManager-D_iEHjQl.d.mts +186 -0
  133. package/dist/sse-DkqQ1uxb.mjs +123 -0
  134. package/dist/testing/index.d.mts +907 -0
  135. package/dist/testing/index.mjs +1976 -0
  136. package/dist/tracing-8CEbhF0w.d.mts +70 -0
  137. package/dist/typeGuards-DwxA1t_L.mjs +9 -0
  138. package/dist/types/index.d.mts +946 -0
  139. package/dist/types/index.mjs +14 -0
  140. package/dist/types-B0dhNrnd.d.mts +445 -0
  141. package/dist/types-Beqn1Un7.mjs +38 -0
  142. package/dist/types-DelU6kln.mjs +25 -0
  143. package/dist/types-RLkFVgaw.d.mts +101 -0
  144. package/dist/utils/index.d.mts +747 -0
  145. package/dist/utils/index.mjs +6 -0
  146. package/package.json +194 -68
  147. package/dist/BaseController-DVAiHxEQ.d.ts +0 -233
  148. package/dist/adapters/index.d.ts +0 -237
  149. package/dist/adapters/index.js +0 -668
  150. package/dist/arcCorePlugin-CsShQdyP.d.ts +0 -273
  151. package/dist/audit/index.d.ts +0 -195
  152. package/dist/audit/index.js +0 -319
  153. package/dist/auth/index.d.ts +0 -47
  154. package/dist/auth/index.js +0 -174
  155. package/dist/cli/commands/docs.d.ts +0 -11
  156. package/dist/cli/commands/docs.js +0 -474
  157. package/dist/cli/commands/generate.js +0 -334
  158. package/dist/cli/commands/introspect.d.ts +0 -8
  159. package/dist/cli/commands/introspect.js +0 -338
  160. package/dist/cli/index.d.ts +0 -4
  161. package/dist/cli/index.js +0 -3269
  162. package/dist/core/index.d.ts +0 -220
  163. package/dist/core/index.js +0 -2786
  164. package/dist/createApp-Ce9wl8W9.d.ts +0 -77
  165. package/dist/docs/index.d.ts +0 -166
  166. package/dist/docs/index.js +0 -658
  167. package/dist/errors-8WIxGS_6.d.ts +0 -122
  168. package/dist/events/index.d.ts +0 -117
  169. package/dist/events/index.js +0 -89
  170. package/dist/factory/index.d.ts +0 -38
  171. package/dist/factory/index.js +0 -1652
  172. package/dist/hooks/index.d.ts +0 -4
  173. package/dist/hooks/index.js +0 -199
  174. package/dist/idempotency/index.d.ts +0 -323
  175. package/dist/idempotency/index.js +0 -500
  176. package/dist/index-B4t03KQ0.d.ts +0 -1366
  177. package/dist/index.d.ts +0 -135
  178. package/dist/index.js +0 -4756
  179. package/dist/migrations/index.d.ts +0 -185
  180. package/dist/migrations/index.js +0 -274
  181. package/dist/org/index.d.ts +0 -129
  182. package/dist/org/index.js +0 -220
  183. package/dist/permissions/index.d.ts +0 -144
  184. package/dist/permissions/index.js +0 -103
  185. package/dist/plugins/index.d.ts +0 -46
  186. package/dist/plugins/index.js +0 -1069
  187. package/dist/policies/index.js +0 -196
  188. package/dist/presets/index.js +0 -384
  189. package/dist/presets/multiTenant.d.ts +0 -39
  190. package/dist/presets/multiTenant.js +0 -112
  191. package/dist/registry/index.d.ts +0 -16
  192. package/dist/registry/index.js +0 -253
  193. package/dist/testing/index.d.ts +0 -618
  194. package/dist/testing/index.js +0 -48020
  195. package/dist/types/index.d.ts +0 -4
  196. package/dist/types/index.js +0 -8
  197. package/dist/types-B99TBmFV.d.ts +0 -76
  198. package/dist/types-BvckRbs2.d.ts +0 -143
  199. package/dist/utils/index.d.ts +0 -679
  200. 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 };