@classytic/arc 2.6.3 → 2.7.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 (143) hide show
  1. package/README.md +98 -3
  2. package/dist/{BaseController-DzRtluEF.mjs → BaseController-CpMfCXdn.mjs} +134 -16
  3. package/dist/adapters/index.d.mts +2 -2
  4. package/dist/adapters/index.mjs +1 -1
  5. package/dist/{adapters-gM-WYjNe.mjs → adapters-BxGgSHjj.mjs} +1 -9
  6. package/dist/applyPermissionResult-D6GPMsvh.mjs +37 -0
  7. package/dist/audit/index.d.mts +1 -1
  8. package/dist/audit/index.mjs +1 -1
  9. package/dist/audit/mongodb.d.mts +1 -1
  10. package/dist/audit/mongodb.mjs +1 -1
  11. package/dist/auth/index.d.mts +4 -4
  12. package/dist/auth/index.mjs +7 -6
  13. package/dist/auth/mongoose.d.mts +191 -0
  14. package/dist/auth/mongoose.mjs +73 -0
  15. package/dist/auth/redis-session.d.mts +1 -1
  16. package/dist/{betterAuthOpenApi-lz0IRbXJ.mjs → betterAuthOpenApi-CCw3YX0g.mjs} +1 -1
  17. package/dist/cache/index.d.mts +2 -2
  18. package/dist/cache/index.mjs +2 -2
  19. package/dist/cli/commands/docs.mjs +2 -2
  20. package/dist/cli/commands/generate.mjs +1 -1
  21. package/dist/cli/commands/init.mjs +7 -5
  22. package/dist/cli/commands/introspect.mjs +1 -1
  23. package/dist/core/index.d.mts +3 -3
  24. package/dist/core/index.mjs +4 -4
  25. package/dist/{core-C1XCMtqM.mjs → core-BWekSEju.mjs} +41 -13
  26. package/dist/{createApp-D2w0LdYJ.mjs → createApp-D7e77m8C.mjs} +25 -14
  27. package/dist/{defineResource-wWMBB4GP.mjs → defineResource-DZzyl4a4.mjs} +42 -37
  28. package/dist/docs/index.d.mts +2 -2
  29. package/dist/docs/index.mjs +1 -1
  30. package/dist/dynamic/index.d.mts +2 -2
  31. package/dist/dynamic/index.mjs +2 -2
  32. package/dist/{elevation-BEdACOLB.mjs → elevation-By_p2lnn.mjs} +1 -1
  33. package/dist/elevation-D7WK0RXq.d.mts +23 -0
  34. package/dist/{errorHandler-r2595m8T.mjs → errorHandler-CH8wk1eD.mjs} +17 -2
  35. package/dist/{errorHandler-Do4vVQ1f.d.mts → errorHandler-pCpEtNd7.d.mts} +46 -2
  36. package/dist/{eventPlugin-Ba00swHF.mjs → eventPlugin-B6U_nCFU.mjs} +4 -3
  37. package/dist/{eventPlugin-DW45v4V5.d.mts → eventPlugin-CdvUoUna.d.mts} +1 -1
  38. package/dist/events/index.d.mts +3 -3
  39. package/dist/events/index.mjs +1 -1
  40. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  41. package/dist/events/transports/redis.d.mts +1 -1
  42. package/dist/factory/index.d.mts +1 -1
  43. package/dist/factory/index.mjs +1 -1
  44. package/dist/hooks/index.d.mts +1 -1
  45. package/dist/hooks/index.mjs +1 -1
  46. package/dist/idempotency/index.d.mts +3 -3
  47. package/dist/idempotency/mongodb.d.mts +1 -1
  48. package/dist/idempotency/redis.d.mts +1 -1
  49. package/dist/index-B0extFr4.d.mts +640 -0
  50. package/dist/{index-gz6iuzCp.d.mts → index-BjShrzoj.d.mts} +47 -4
  51. package/dist/{index-CHeJa4Zd.d.mts → index-C9eYNjGR.d.mts} +1 -1
  52. package/dist/index.d.mts +9 -8
  53. package/dist/index.mjs +10 -9
  54. package/dist/integrations/event-gateway.d.mts +1 -1
  55. package/dist/integrations/event-gateway.mjs +1 -1
  56. package/dist/integrations/index.d.mts +1 -1
  57. package/dist/integrations/mcp/index.d.mts +2 -2
  58. package/dist/integrations/mcp/index.mjs +8 -5
  59. package/dist/integrations/mcp/testing.d.mts +1 -1
  60. package/dist/integrations/mcp/testing.mjs +1 -1
  61. package/dist/integrations/webhooks.d.mts +58 -1
  62. package/dist/integrations/webhooks.mjs +78 -7
  63. package/dist/integrations/websocket.d.mts +7 -1
  64. package/dist/integrations/websocket.mjs +7 -1
  65. package/dist/{interface-DYH8AXGe.d.mts → interface-B91alUzq.d.mts} +151 -15
  66. package/dist/{mongodb-pMvOlR5_.d.mts → mongodb-B7zupyck.d.mts} +1 -1
  67. package/dist/{mongodb-kltrBPa1.d.mts → mongodb-Cgu9F1Nd.d.mts} +1 -1
  68. package/dist/{openapi-CBmZ6EQN.mjs → openapi-BBSTVcMm.mjs} +1 -1
  69. package/dist/org/index.d.mts +2 -2
  70. package/dist/org/index.mjs +1 -1
  71. package/dist/permissions/index.d.mts +4 -4
  72. package/dist/permissions/index.mjs +3 -2
  73. package/dist/{permissions-C8ImI8gC.mjs → permissions-CH4cNwJi.mjs} +358 -64
  74. package/dist/plugins/index.d.mts +52 -5
  75. package/dist/plugins/index.mjs +12 -11
  76. package/dist/plugins/response-cache.mjs +1 -1
  77. package/dist/plugins/tracing-entry.d.mts +1 -1
  78. package/dist/plugins/tracing-entry.mjs +1 -1
  79. package/dist/policies/index.d.mts +1 -1
  80. package/dist/presets/index.d.mts +3 -3
  81. package/dist/presets/index.mjs +1 -1
  82. package/dist/presets/multiTenant.d.mts +53 -3
  83. package/dist/presets/multiTenant.mjs +89 -47
  84. package/dist/{presets-BMfdy34e.mjs → presets-BFrGvvjL.mjs} +2 -2
  85. package/dist/{queryCachePlugin-DcmETvcB.d.mts → queryCachePlugin-Ckl71mkc.d.mts} +1 -1
  86. package/dist/{queryCachePlugin-XtFplYO9.mjs → queryCachePlugin-CwTpR04-.mjs} +2 -2
  87. package/dist/{redis-D0Qc-9EW.d.mts → redis-3TQxm2VZ.d.mts} +1 -1
  88. package/dist/{redis-stream-BW9UKLZM.d.mts → redis-stream-Dag5LFa9.d.mts} +1 -1
  89. package/dist/registry/index.d.mts +1 -1
  90. package/dist/registry/index.mjs +2 -2
  91. package/dist/replyHelpers-uDUIYh7u.mjs +40 -0
  92. package/dist/{resourceToTools-nCJWnG1r.mjs → resourceToTools-BJkoQoUP.mjs} +74 -25
  93. package/dist/rpc/index.d.mts +1 -1
  94. package/dist/rpc/index.mjs +1 -1
  95. package/dist/scope/index.d.mts +3 -2
  96. package/dist/scope/index.mjs +4 -3
  97. package/dist/{sse-BF7GR7IB.mjs → sse-6W0hjVS_.mjs} +2 -2
  98. package/dist/testing/index.d.mts +2 -2
  99. package/dist/testing/index.mjs +1 -1
  100. package/dist/types/index.d.mts +4 -3
  101. package/dist/types/index.mjs +1 -1
  102. package/dist/types--D3vvfdt.d.mts +286 -0
  103. package/dist/{types-By-5mIfn.d.mts → types-2FlNl0mL.d.mts} +44 -9
  104. package/dist/types-AOD8fxIw.mjs +229 -0
  105. package/dist/types-B4BNthET.d.mts +178 -0
  106. package/dist/{types-B4_TDdPe.d.mts → types-C5g2oRC7.d.mts} +18 -2
  107. package/dist/utils/index.d.mts +3 -3
  108. package/dist/utils/index.mjs +5 -5
  109. package/package.json +21 -6
  110. package/skills/arc/SKILL.md +314 -6
  111. package/skills/arc/references/integrations.md +32 -7
  112. package/skills/arc/references/mcp.md +31 -7
  113. package/skills/arc/references/multi-tenancy.md +208 -0
  114. package/skills/arc/references/production.md +69 -0
  115. package/dist/elevation-C_taLQrM.d.mts +0 -147
  116. package/dist/index-NGZksqM5.d.mts +0 -398
  117. package/dist/types-BNUccdcf.d.mts +0 -101
  118. package/dist/types-BhtYdxZU.mjs +0 -91
  119. /package/dist/{EventTransport-wc5hSLik.d.mts → EventTransport-C4VheKeC.d.mts} +0 -0
  120. /package/dist/{HookSystem-COkyWztM.mjs → HookSystem-D7lfx--K.mjs} +0 -0
  121. /package/dist/{ResourceRegistry-C6ngvOnn.mjs → ResourceRegistry-DsHiG9cL.mjs} +0 -0
  122. /package/dist/{caching-BSXB-Xr7.mjs → caching-5DtLwIqb.mjs} +0 -0
  123. /package/dist/{circuitBreaker-JP2GdJ4b.d.mts → circuitBreaker-BBPDt-J_.d.mts} +0 -0
  124. /package/dist/{circuitBreaker-BOBOpN2w.mjs → circuitBreaker-l18oRgL5.mjs} +0 -0
  125. /package/dist/{errors-CcVbl1-T.d.mts → errors-BS6lZvWy.d.mts} +0 -0
  126. /package/dist/{errors-NoQKsbAT.mjs → errors-Cg58SLNi.mjs} +0 -0
  127. /package/dist/{externalPaths-DpO-s7r8.d.mts → externalPaths-iba7jD3d.d.mts} +0 -0
  128. /package/dist/{fields-DFwdaWCq.d.mts → fields-D4nMDqnK.d.mts} +0 -0
  129. /package/dist/{interface-D_BWALyZ.d.mts → interface-CG7oRZjX.d.mts} +0 -0
  130. /package/dist/{interface-gr-7qo9j.d.mts → interface-CSbZdv_3.d.mts} +0 -0
  131. /package/dist/{logger-Dz3j1ItV.mjs → logger-DLg8-Ueg.mjs} +0 -0
  132. /package/dist/{memory-BFAYkf8H.mjs → memory-Cp7_cAko.mjs} +0 -0
  133. /package/dist/{metrics-Csh4nsvv.mjs → metrics-Qnvwc-LQ.mjs} +0 -0
  134. /package/dist/{mongodb-BuQ7fNTg.mjs → mongodb-B7X7P1P8.mjs} +0 -0
  135. /package/dist/{pluralize-CcT6qF0a.mjs → pluralize-Dckfq6US.mjs} +0 -0
  136. /package/dist/{registry-I-ogLgL9.mjs → registry-B3lRFBWo.mjs} +0 -0
  137. /package/dist/{requestContext-DYtmNpm5.mjs → requestContext-xHIKedG6.mjs} +0 -0
  138. /package/dist/{schemaConverter-DjzHpFam.mjs → schemaConverter-0TyONAwM.mjs} +0 -0
  139. /package/dist/{sessionManager-wbkYj2HL.d.mts → sessionManager-CEo9jwPI.d.mts} +0 -0
  140. /package/dist/{tracing-bz_U4EM1.d.mts → tracing-DEqdGkr-.d.mts} +0 -0
  141. /package/dist/{typeGuards-Cj5Rgvlg.mjs → typeGuards-CcFZXgU7.mjs} +0 -0
  142. /package/dist/{utils-Dc0WhlIl.mjs → utils-B-l6410F.mjs} +0 -0
  143. /package/dist/{versioning-BzfeHmhj.mjs → versioning-CdBbFefk.mjs} +0 -0
@@ -1,398 +0,0 @@
1
- import { n as PermissionContext, t as PermissionCheck } from "./types-BNUccdcf.mjs";
2
- import { i as CacheStore, t as CacheLogger } from "./interface-D_BWALyZ.mjs";
3
-
4
- //#region src/permissions/roleHierarchy.d.ts
5
- /**
6
- * Role Hierarchy — Composable RBAC Inheritance
7
- *
8
- * Expands roles based on an inheritance map. Apply at scope-building time
9
- * so that requireRoles() works with the already-expanded list.
10
- *
11
- * @example
12
- * ```typescript
13
- * import { createRoleHierarchy } from '@classytic/arc/permissions';
14
- *
15
- * const hierarchy = createRoleHierarchy({
16
- * superadmin: ['admin'],
17
- * admin: ['branch_manager'],
18
- * branch_manager: ['member'],
19
- * });
20
- *
21
- * // When building scope:
22
- * const expandedRoles = hierarchy.expand(user.roles);
23
- * // ['superadmin'] → ['superadmin', 'admin', 'branch_manager', 'member']
24
- *
25
- * // Check inclusion:
26
- * hierarchy.includes(['admin'], 'branch_manager'); // true (admin inherits branch_manager)
27
- * hierarchy.includes(['member'], 'admin'); // false (child doesn't inherit parent)
28
- * ```
29
- */
30
- interface RoleHierarchy {
31
- /** Expand roles to include all inherited (child) roles. Deduplicated. */
32
- expand(roles: readonly string[]): string[];
33
- /** Check if any of the user's roles (expanded) include the required role. */
34
- includes(userRoles: readonly string[], requiredRole: string): boolean;
35
- }
36
- /**
37
- * Create a role hierarchy from a parent → children map.
38
- *
39
- * Each key is a parent role, each value is the array of roles it inherits.
40
- * Inheritance is transitive: if A → B and B → C, then A expands to [A, B, C].
41
- * Circular references are handled safely (visited set).
42
- */
43
- declare function createRoleHierarchy(map: Record<string, readonly string[]>): RoleHierarchy;
44
- declare namespace presets_d_exports {
45
- export { adminOnly, authenticated, fullPublic, ownerWithAdminBypass, publicRead, publicReadAdminWrite, readOnly };
46
- }
47
- /**
48
- * ResourcePermissions shape — matches the type in types/index.ts
49
- */
50
- interface ResourcePermissions<TDoc = any> {
51
- list?: PermissionCheck<TDoc>;
52
- get?: PermissionCheck<TDoc>;
53
- create?: PermissionCheck<TDoc>;
54
- update?: PermissionCheck<TDoc>;
55
- delete?: PermissionCheck<TDoc>;
56
- }
57
- type PermissionOverrides<TDoc = any> = Partial<ResourcePermissions<TDoc>>;
58
- /**
59
- * Public read, authenticated write.
60
- * list + get = allowPublic(), create + update + delete = requireAuth()
61
- */
62
- declare function publicRead<TDoc = any>(overrides?: PermissionOverrides<TDoc>): ResourcePermissions<TDoc>;
63
- /**
64
- * Public read, admin write.
65
- * list + get = allowPublic(), create + update + delete = requireRoles(['admin'])
66
- */
67
- declare function publicReadAdminWrite<TDoc = any>(roles?: readonly string[], overrides?: PermissionOverrides<TDoc>): ResourcePermissions<TDoc>;
68
- /**
69
- * All operations require authentication.
70
- */
71
- declare function authenticated<TDoc = any>(overrides?: PermissionOverrides<TDoc>): ResourcePermissions<TDoc>;
72
- /**
73
- * All operations require specific roles.
74
- * @param roles - Required roles (user needs at least one). Default: ['admin']
75
- */
76
- declare function adminOnly<TDoc = any>(roles?: readonly string[], overrides?: PermissionOverrides<TDoc>): ResourcePermissions<TDoc>;
77
- /**
78
- * Owner-scoped with admin bypass.
79
- * list = auth (scoped to owner), get = auth, create = auth,
80
- * update + delete = ownership check with admin bypass.
81
- *
82
- * @param ownerField - Field containing owner ID (default: 'userId')
83
- * @param bypassRoles - Roles that bypass ownership check (default: ['admin'])
84
- */
85
- declare function ownerWithAdminBypass<TDoc = any>(ownerField?: Extract<keyof TDoc, string> | string, bypassRoles?: readonly string[], overrides?: PermissionOverrides<TDoc>): ResourcePermissions<TDoc>;
86
- /**
87
- * Full public access — no auth required for any operation.
88
- * Use sparingly (dev/testing, truly public APIs).
89
- */
90
- declare function fullPublic<TDoc = any>(overrides?: PermissionOverrides<TDoc>): ResourcePermissions<TDoc>;
91
- /**
92
- * Read-only: list + get authenticated, write operations denied.
93
- * Useful for computed/derived resources.
94
- */
95
- declare function readOnly<TDoc = any>(overrides?: PermissionOverrides<TDoc>): ResourcePermissions<TDoc>;
96
- //#endregion
97
- //#region src/permissions/index.d.ts
98
- interface DynamicPermissionMatrixConfig {
99
- /**
100
- * Resolve role → resource → actions map dynamically (DB/API/config service).
101
- * Called at permission-check time (or cache miss if cache enabled).
102
- */
103
- resolveRolePermissions: (ctx: PermissionContext) => Record<string, Record<string, readonly string[]>> | Promise<Record<string, Record<string, readonly string[]>>>;
104
- /**
105
- * Optional cache store adapter.
106
- * Use MemoryCacheStore for single-instance apps or RedisCacheStore for distributed setups.
107
- */
108
- cacheStore?: CacheStore<Record<string, Record<string, readonly string[]>>>;
109
- /** Optional logger for cache/runtime failures (default: console) */
110
- logger?: CacheLogger;
111
- /**
112
- * Legacy convenience in-memory cache config.
113
- * If `cacheStore` is not provided and ttlMs > 0, Arc creates an internal MemoryCacheStore.
114
- */
115
- cache?: {
116
- /** Cache TTL in milliseconds */ttlMs: number; /** Optional custom cache key builder */
117
- key?: (ctx: PermissionContext) => string | null | undefined; /** Hard entry cap for internal memory store (default: 1000) */
118
- maxEntries?: number;
119
- };
120
- }
121
- /** Minimal publish/subscribe interface for cross-node cache invalidation. */
122
- interface PermissionEventBus {
123
- publish: <T>(type: string, payload: T) => Promise<void>;
124
- subscribe: (pattern: string, handler: (event: {
125
- payload: unknown;
126
- }) => void | Promise<void>) => Promise<(() => void) | undefined>;
127
- }
128
- interface ConnectEventsOptions {
129
- /** Called on remote invalidation for app-specific cleanup (e.g., resolver cache) */
130
- onRemoteInvalidation?: (orgId: string) => void | Promise<void>;
131
- /** Custom event type (default: 'arc.permissions.invalidated') */
132
- eventType?: string;
133
- }
134
- interface DynamicPermissionMatrix {
135
- can: (permissions: Record<string, readonly string[]>) => PermissionCheck;
136
- canAction: (resource: string, action: string) => PermissionCheck;
137
- requireRole: (...roles: string[]) => PermissionCheck;
138
- requireMembership: () => PermissionCheck;
139
- requireTeamMembership: () => PermissionCheck;
140
- /** Invalidate cached permissions for a specific organization */
141
- invalidateByOrg: (orgId: string) => Promise<void>;
142
- clearCache: () => Promise<void>;
143
- /**
144
- * Connect to an event system for cross-node cache invalidation.
145
- *
146
- * Late-binding: call after the event plugin is registered (e.g., in onReady hook).
147
- * Once connected, `invalidateByOrg()` auto-publishes an event, and incoming
148
- * events from other nodes trigger local cache invalidation.
149
- * Echo is suppressed via per-process nodeId matching.
150
- */
151
- connectEvents(events: PermissionEventBus, options?: ConnectEventsOptions): Promise<void>;
152
- /** Disconnect from the event system. Safe to call even if never connected. */
153
- disconnectEvents(): Promise<void>;
154
- /** Whether events are currently connected. */
155
- readonly eventsConnected: boolean;
156
- }
157
- /**
158
- * Allow public access (no authentication required)
159
- *
160
- * @example
161
- * ```typescript
162
- * permissions: {
163
- * list: allowPublic(),
164
- * get: allowPublic(),
165
- * }
166
- * ```
167
- */
168
- declare function allowPublic(): PermissionCheck;
169
- /**
170
- * Require authentication (any authenticated user)
171
- *
172
- * @example
173
- * ```typescript
174
- * permissions: {
175
- * create: requireAuth(),
176
- * update: requireAuth(),
177
- * }
178
- * ```
179
- */
180
- declare function requireAuth(): PermissionCheck;
181
- /**
182
- * Require specific roles
183
- *
184
- * @param roles - Required roles (user needs at least one)
185
- * @param options - Optional bypass roles
186
- *
187
- * @example
188
- * ```typescript
189
- * permissions: {
190
- * create: requireRoles(['admin', 'editor']),
191
- * delete: requireRoles(['admin']),
192
- * }
193
- *
194
- * // With bypass roles
195
- * permissions: {
196
- * update: requireRoles(['owner'], { bypassRoles: ['admin', 'superadmin'] }),
197
- * }
198
- * ```
199
- */
200
- declare function requireRoles(roles: readonly string[], options?: {
201
- bypassRoles?: readonly string[];
202
- /**
203
- * Also check org membership roles (`scope.orgRoles`) when in org context.
204
- * Default: `false` — only checks platform roles (`user.role`).
205
- *
206
- * Set to `true` when using Better Auth organization plugin where roles like
207
- * 'admin' are assigned at the org level, not the user level.
208
- *
209
- * For org-only role checks, prefer `requireOrgRole('admin')` instead.
210
- */
211
- includeOrgRoles?: boolean;
212
- }): PermissionCheck;
213
- /**
214
- * Unified role check — checks both platform roles AND org roles.
215
- *
216
- * This is the recommended helper for Better Auth organization plugin users.
217
- * It checks `user.role` (platform) first, then `scope.orgRoles` (org membership).
218
- * Elevated scope always passes.
219
- *
220
- * For platform-only checks: use `requireRoles(['admin'])`
221
- * For org-only checks: use `requireOrgRole('admin')`
222
- *
223
- * @example
224
- * ```typescript
225
- * permissions: {
226
- * create: roles('admin', 'editor'), // passes if user has role at either level
227
- * delete: roles('admin'),
228
- * }
229
- * ```
230
- */
231
- declare function roles(...args: string[] | [readonly string[]]): PermissionCheck;
232
- /**
233
- * Require resource ownership
234
- *
235
- * Returns filters to scope queries to user's owned resources.
236
- *
237
- * @param ownerField - Field containing owner ID (default: 'userId')
238
- * @param options - Optional bypass roles
239
- *
240
- * @example
241
- * ```typescript
242
- * permissions: {
243
- * update: requireOwnership('userId'),
244
- * delete: requireOwnership('createdBy', { bypassRoles: ['admin'] }),
245
- * }
246
- * ```
247
- */
248
- declare function requireOwnership<TDoc = Record<string, unknown>>(ownerField?: Extract<keyof TDoc, string> | string, options?: {
249
- bypassRoles?: readonly string[];
250
- }): PermissionCheck<TDoc>;
251
- /**
252
- * Combine multiple checks - ALL must pass (AND logic)
253
- *
254
- * @example
255
- * ```typescript
256
- * permissions: {
257
- * update: allOf(
258
- * requireAuth(),
259
- * requireRoles(['editor']),
260
- * requireOwnership('createdBy')
261
- * ),
262
- * }
263
- * ```
264
- */
265
- declare function allOf(...checks: PermissionCheck[]): PermissionCheck;
266
- /**
267
- * Combine multiple checks - ANY must pass (OR logic)
268
- *
269
- * @example
270
- * ```typescript
271
- * permissions: {
272
- * update: anyOf(
273
- * requireRoles(['admin']),
274
- * requireOwnership('createdBy')
275
- * ),
276
- * }
277
- * ```
278
- */
279
- declare function anyOf(...checks: PermissionCheck[]): PermissionCheck;
280
- /**
281
- * Deny all access
282
- *
283
- * @example
284
- * ```typescript
285
- * permissions: {
286
- * delete: denyAll('Deletion not allowed'),
287
- * }
288
- * ```
289
- */
290
- declare function denyAll(reason?: string): PermissionCheck;
291
- /**
292
- * Dynamic permission based on context
293
- *
294
- * @example
295
- * ```typescript
296
- * permissions: {
297
- * update: when((ctx) => ctx.data?.status === 'draft'),
298
- * }
299
- * ```
300
- */
301
- declare function when<TDoc = Record<string, unknown>>(condition: (ctx: PermissionContext<TDoc>) => boolean | Promise<boolean>): PermissionCheck<TDoc>;
302
- /**
303
- * Require membership in the active organization.
304
- * User must be authenticated AND have an active org (member or elevated scope).
305
- *
306
- * Reads `request.scope` set by auth adapters.
307
- *
308
- * @example
309
- * ```typescript
310
- * permissions: {
311
- * list: requireOrgMembership(),
312
- * get: requireOrgMembership(),
313
- * }
314
- * ```
315
- */
316
- declare function requireOrgMembership<TDoc = Record<string, unknown>>(): PermissionCheck<TDoc>;
317
- /**
318
- * Require specific org-level roles.
319
- * Reads `request.scope.orgRoles` (set by auth adapters).
320
- * Elevated scope always passes (platform admin bypass).
321
- *
322
- * @param roles - Required org roles (user needs at least one)
323
- *
324
- * @example
325
- * ```typescript
326
- * permissions: {
327
- * create: requireOrgRole('admin', 'owner'),
328
- * delete: requireOrgRole('owner'),
329
- * }
330
- * ```
331
- */
332
- declare function requireOrgRole<TDoc = Record<string, unknown>>(...args: string[] | [readonly string[]]): PermissionCheck<TDoc>;
333
- /**
334
- * Create a scoped permission system for resource-action patterns.
335
- * Maps org roles to fine-grained permissions without external API calls.
336
- *
337
- * @example
338
- * ```typescript
339
- * const perms = createOrgPermissions({
340
- * statements: {
341
- * product: ['create', 'update', 'delete'],
342
- * order: ['create', 'approve'],
343
- * },
344
- * roles: {
345
- * owner: { product: ['create', 'update', 'delete'], order: ['create', 'approve'] },
346
- * admin: { product: ['create', 'update'], order: ['create'] },
347
- * member: { product: [], order: [] },
348
- * },
349
- * });
350
- *
351
- * defineResource({
352
- * permissions: {
353
- * create: perms.can({ product: ['create'] }),
354
- * delete: perms.can({ product: ['delete'] }),
355
- * }
356
- * });
357
- * ```
358
- */
359
- declare function createOrgPermissions(config: {
360
- statements: Record<string, readonly string[]>;
361
- roles: Record<string, Record<string, readonly string[]>>;
362
- }): {
363
- can: (permissions: Record<string, string[]>) => PermissionCheck;
364
- requireRole: (...roles: string[]) => PermissionCheck;
365
- requireMembership: () => PermissionCheck;
366
- requireTeamMembership: () => PermissionCheck;
367
- };
368
- /**
369
- * Create a dynamic role-based permission matrix.
370
- *
371
- * Use this when role/action mappings are managed outside code
372
- * (e.g., admin UI matrix, DB-stored ACLs, remote policy service).
373
- *
374
- * Supports:
375
- * - org role union (any assigned org role can grant)
376
- * - global bypass roles
377
- * - wildcard resource/action (`*`)
378
- * - optional in-memory cache
379
- */
380
- declare function createDynamicPermissionMatrix(config: DynamicPermissionMatrixConfig): DynamicPermissionMatrix;
381
- /**
382
- * Require membership in the active team.
383
- * User must be authenticated, a member of the active org, AND have an active team.
384
- *
385
- * Better Auth teams are flat member groups (no team-level roles).
386
- * Reads `request.scope.teamId` set by the Better Auth adapter.
387
- *
388
- * @example
389
- * ```typescript
390
- * permissions: {
391
- * list: requireTeamMembership(),
392
- * create: requireTeamMembership(),
393
- * }
394
- * ```
395
- */
396
- declare function requireTeamMembership<TDoc = Record<string, unknown>>(): PermissionCheck<TDoc>;
397
- //#endregion
398
- export { presets_d_exports as C, RoleHierarchy as D, readOnly as E, createRoleHierarchy as O, ownerWithAdminBypass as S, publicReadAdminWrite as T, roles as _, allOf as a, authenticated as b, createDynamicPermissionMatrix as c, requireAuth as d, requireOrgMembership as f, requireTeamMembership as g, requireRoles as h, PermissionEventBus as i, createOrgPermissions as l, requireOwnership as m, DynamicPermissionMatrix as n, allowPublic as o, requireOrgRole as p, DynamicPermissionMatrixConfig as r, anyOf as s, ConnectEventsOptions as t, denyAll as u, when as v, publicRead as w, fullPublic as x, adminOnly as y };
@@ -1,101 +0,0 @@
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 = Record<string, unknown>> {
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 = Record<string, unknown>> = ((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 };
@@ -1,91 +0,0 @@
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
- /**
33
- * Get userId from scope (available on authenticated, member, elevated).
34
- *
35
- * @example
36
- * ```typescript
37
- * import { getUserId } from '@classytic/arc/scope';
38
- * const userId = getUserId(request.scope);
39
- * ```
40
- */
41
- function getUserId(scope) {
42
- if (scope.kind === "public") return void 0;
43
- return scope.userId;
44
- }
45
- /**
46
- * Get global user roles from scope (available on authenticated and member).
47
- * These are user-level roles (e.g. superadmin, finance-admin) distinct from
48
- * org-level roles (scope.orgRoles).
49
- *
50
- * @example
51
- * ```typescript
52
- * import { getUserRoles } from '@classytic/arc/scope';
53
- * const globalRoles = getUserRoles(request.scope);
54
- * ```
55
- */
56
- function getUserRoles(scope) {
57
- if (scope.kind === "authenticated") return scope.userRoles ?? [];
58
- if (scope.kind === "member") return scope.userRoles;
59
- return [];
60
- }
61
- /**
62
- * Org context — canonical extraction from a Fastify request.
63
- *
64
- * Works regardless of auth type (JWT, Better Auth, custom) by reading
65
- * `request.scope` and `request.user`. Eliminates the need for each resource
66
- * to re-invent org extraction from headers/user/scope.
67
- *
68
- * @example
69
- * ```typescript
70
- * import { getOrgContext } from '@classytic/arc/scope';
71
- *
72
- * handler: async (request, reply) => {
73
- * const { userId, organizationId, roles, orgRoles } = getOrgContext(request);
74
- * }
75
- * ```
76
- */
77
- function getOrgContext(request) {
78
- const scope = request.scope ?? { kind: "public" };
79
- return {
80
- userId: getUserId(scope) ?? request.user?.id ?? request.user?._id,
81
- organizationId: getOrgId(scope) ?? request.user?.organizationId ?? request.headers?.["x-organization-id"],
82
- roles: getUserRoles(scope),
83
- orgRoles: getOrgRoles(scope)
84
- };
85
- }
86
- /** Default public scope — used as initial decoration value */
87
- const PUBLIC_SCOPE = Object.freeze({ kind: "public" });
88
- /** Default authenticated scope — used when user is logged in but no org */
89
- const AUTHENTICATED_SCOPE = Object.freeze({ kind: "authenticated" });
90
- //#endregion
91
- export { getOrgRoles as a, getUserRoles as c, isElevated as d, isMember as f, getOrgId as i, hasOrgAccess as l, PUBLIC_SCOPE as n, getTeamId as o, getOrgContext as r, getUserId as s, AUTHENTICATED_SCOPE as t, isAuthenticated as u };
File without changes