@classytic/arc 2.6.3 → 2.7.1

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 (135) hide show
  1. package/README.md +84 -1
  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-B_nvKNAQ.mjs} +11 -11
  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-Dm-HTBCt.d.mts +23 -0
  34. package/dist/{errorHandler-Do4vVQ1f.d.mts → errorHandler-COa51ho_.d.mts} +1 -1
  35. package/dist/{errorHandler-r2595m8T.mjs → errorHandler-DXUttWEO.mjs} +1 -1
  36. package/dist/{eventPlugin-DW45v4V5.d.mts → eventPlugin-BgLxJkIB.d.mts} +1 -1
  37. package/dist/{eventPlugin-Ba00swHF.mjs → eventPlugin-DsaNNXzZ.mjs} +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-BYpRGXif.d.mts +640 -0
  50. package/dist/{index-gz6iuzCp.d.mts → index-KXM8_JmQ.d.mts} +47 -4
  51. package/dist/{index-CHeJa4Zd.d.mts → index-StgFaQKD.d.mts} +1 -1
  52. package/dist/index.d.mts +8 -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 +1 -1
  59. package/dist/integrations/mcp/testing.d.mts +1 -1
  60. package/dist/integrations/mcp/testing.mjs +1 -1
  61. package/dist/{interface-DYH8AXGe.d.mts → interface-Dwzqt4mn.d.mts} +150 -14
  62. package/dist/{mongodb-pMvOlR5_.d.mts → mongodb-Bq90j-Uj.d.mts} +1 -1
  63. package/dist/{mongodb-kltrBPa1.d.mts → mongodb-DdyYlIXg.d.mts} +1 -1
  64. package/dist/{openapi-CBmZ6EQN.mjs → openapi-C5UhIeWu.mjs} +1 -1
  65. package/dist/org/index.d.mts +2 -2
  66. package/dist/org/index.mjs +1 -1
  67. package/dist/permissions/index.d.mts +4 -4
  68. package/dist/permissions/index.mjs +3 -2
  69. package/dist/{permissions-C8ImI8gC.mjs → permissions-CH4cNwJi.mjs} +358 -64
  70. package/dist/plugins/index.d.mts +4 -4
  71. package/dist/plugins/index.mjs +10 -10
  72. package/dist/plugins/response-cache.mjs +1 -1
  73. package/dist/plugins/tracing-entry.d.mts +1 -1
  74. package/dist/plugins/tracing-entry.mjs +1 -1
  75. package/dist/policies/index.d.mts +1 -1
  76. package/dist/presets/index.d.mts +3 -3
  77. package/dist/presets/index.mjs +1 -1
  78. package/dist/presets/multiTenant.d.mts +53 -3
  79. package/dist/presets/multiTenant.mjs +89 -47
  80. package/dist/{presets-BMfdy34e.mjs → presets-BFrGvvjL.mjs} +2 -2
  81. package/dist/{queryCachePlugin-DcmETvcB.d.mts → queryCachePlugin-Bw8XyJpX.d.mts} +1 -1
  82. package/dist/{queryCachePlugin-XtFplYO9.mjs → queryCachePlugin-CwTpR04-.mjs} +2 -2
  83. package/dist/{redis-D0Qc-9EW.d.mts → redis-CyCntzTO.d.mts} +1 -1
  84. package/dist/{redis-stream-BW9UKLZM.d.mts → redis-stream-We_Ucl9-.d.mts} +1 -1
  85. package/dist/registry/index.d.mts +1 -1
  86. package/dist/registry/index.mjs +2 -2
  87. package/dist/{resourceToTools-nCJWnG1r.mjs → resourceToTools-CkVSSzKg.mjs} +64 -21
  88. package/dist/rpc/index.d.mts +1 -1
  89. package/dist/rpc/index.mjs +1 -1
  90. package/dist/scope/index.d.mts +3 -2
  91. package/dist/scope/index.mjs +4 -3
  92. package/dist/{sse-BF7GR7IB.mjs → sse-Bp3dabF1.mjs} +2 -2
  93. package/dist/testing/index.d.mts +2 -2
  94. package/dist/testing/index.mjs +1 -1
  95. package/dist/types/index.d.mts +4 -3
  96. package/dist/types/index.mjs +1 -1
  97. package/dist/types-AOD8fxIw.mjs +229 -0
  98. package/dist/types-CNEbix8T.d.mts +286 -0
  99. package/dist/{types-B4_TDdPe.d.mts → types-ClmkMDK1.d.mts} +1 -1
  100. package/dist/{types-By-5mIfn.d.mts → types-D0qf0Mf4.d.mts} +9 -9
  101. package/dist/types-DPsC0taJ.d.mts +178 -0
  102. package/dist/utils/index.d.mts +3 -3
  103. package/dist/utils/index.mjs +5 -5
  104. package/package.json +17 -5
  105. package/skills/arc/SKILL.md +253 -6
  106. package/skills/arc/references/multi-tenancy.md +208 -0
  107. package/dist/elevation-C_taLQrM.d.mts +0 -147
  108. package/dist/index-NGZksqM5.d.mts +0 -398
  109. package/dist/types-BNUccdcf.d.mts +0 -101
  110. package/dist/types-BhtYdxZU.mjs +0 -91
  111. /package/dist/{EventTransport-wc5hSLik.d.mts → EventTransport-CUpRK_Lg.d.mts} +0 -0
  112. /package/dist/{HookSystem-COkyWztM.mjs → HookSystem-D7lfx--K.mjs} +0 -0
  113. /package/dist/{ResourceRegistry-C6ngvOnn.mjs → ResourceRegistry-DsHiG9cL.mjs} +0 -0
  114. /package/dist/{caching-BSXB-Xr7.mjs → caching-5DtLwIqb.mjs} +0 -0
  115. /package/dist/{circuitBreaker-JP2GdJ4b.d.mts → circuitBreaker-DwxrljLB.d.mts} +0 -0
  116. /package/dist/{circuitBreaker-BOBOpN2w.mjs → circuitBreaker-l18oRgL5.mjs} +0 -0
  117. /package/dist/{errors-CcVbl1-T.d.mts → errors-CCSsMpXE.d.mts} +0 -0
  118. /package/dist/{errors-NoQKsbAT.mjs → errors-Cg58SLNi.mjs} +0 -0
  119. /package/dist/{externalPaths-DpO-s7r8.d.mts → externalPaths-Dg7OLsKo.d.mts} +0 -0
  120. /package/dist/{fields-DFwdaWCq.d.mts → fields-CYuLMJPD.d.mts} +0 -0
  121. /package/dist/{interface-gr-7qo9j.d.mts → interface-B9rHWPxD.d.mts} +0 -0
  122. /package/dist/{interface-D_BWALyZ.d.mts → interface-CnluRL4_.d.mts} +0 -0
  123. /package/dist/{logger-Dz3j1ItV.mjs → logger-DLg8-Ueg.mjs} +0 -0
  124. /package/dist/{memory-BFAYkf8H.mjs → memory-Cp7_cAko.mjs} +0 -0
  125. /package/dist/{metrics-Csh4nsvv.mjs → metrics-Qnvwc-LQ.mjs} +0 -0
  126. /package/dist/{mongodb-BuQ7fNTg.mjs → mongodb-mlgxkYI3.mjs} +0 -0
  127. /package/dist/{pluralize-CcT6qF0a.mjs → pluralize-COpOVar8.mjs} +0 -0
  128. /package/dist/{registry-I-ogLgL9.mjs → registry-B3lRFBWo.mjs} +0 -0
  129. /package/dist/{requestContext-DYtmNpm5.mjs → requestContext-xHIKedG6.mjs} +0 -0
  130. /package/dist/{schemaConverter-DjzHpFam.mjs → schemaConverter-0TyONAwM.mjs} +0 -0
  131. /package/dist/{sessionManager-wbkYj2HL.d.mts → sessionManager-IW4sbIea.d.mts} +0 -0
  132. /package/dist/{tracing-bz_U4EM1.d.mts → tracing-65B51Dw3.d.mts} +0 -0
  133. /package/dist/{typeGuards-Cj5Rgvlg.mjs → typeGuards-CcFZXgU7.mjs} +0 -0
  134. /package/dist/{utils-Dc0WhlIl.mjs → utils-B-l6410F.mjs} +0 -0
  135. /package/dist/{versioning-BzfeHmhj.mjs → versioning-aUUVziBY.mjs} +0 -0
@@ -0,0 +1,640 @@
1
+ import { r as RequestScope } from "./types-CNEbix8T.mjs";
2
+ import { n as PermissionContext, r as PermissionResult, t as PermissionCheck } from "./types-DPsC0taJ.mjs";
3
+ import { i as CacheStore, t as CacheLogger } from "./interface-CnluRL4_.mjs";
4
+ import { FastifyRequest } from "fastify";
5
+
6
+ //#region src/permissions/applyPermissionResult.d.ts
7
+ /**
8
+ * Normalize a permission check return value (`boolean | PermissionResult`)
9
+ * into a concrete `PermissionResult`. This is the only place in Arc that
10
+ * promotes booleans to results — keeps the type narrowing honest everywhere.
11
+ */
12
+ declare function normalizePermissionResult(result: boolean | PermissionResult): PermissionResult;
13
+ /**
14
+ * Minimal shape of a Fastify request that can receive permission side-effects.
15
+ * We avoid depending on the full augmented `FastifyRequest` type here because
16
+ * `_policyFilters` / `scope` are declared via ambient module augmentation in
17
+ * multiple places and the unaugmented interface is what the core routers see.
18
+ */
19
+ type RequestSink = FastifyRequest & {
20
+ _policyFilters?: Record<string, unknown>;
21
+ scope?: RequestScope;
22
+ };
23
+ /**
24
+ * Apply a granted `PermissionResult` to a Fastify request — merges row-level
25
+ * filters into `_policyFilters` and conditionally installs the scope.
26
+ *
27
+ * **Scope install rule:** only writes `scope` when the current request scope
28
+ * is absent or `public`. This prevents downgrading an already-authenticated
29
+ * request (e.g. Better Auth set `member`, then a permission check returns a
30
+ * narrower `service` scope — the original `member` wins because it came from
31
+ * a more authoritative source).
32
+ *
33
+ * Safe to call with a non-granted result — it simply no-ops. Callers should
34
+ * still check `result.granted` and send an error response before reaching here,
35
+ * but this function tolerates the misuse defensively.
36
+ */
37
+ declare function applyPermissionResult(result: PermissionResult, request: RequestSink): void;
38
+ //#endregion
39
+ //#region src/permissions/roleHierarchy.d.ts
40
+ /**
41
+ * Role Hierarchy — Composable RBAC Inheritance
42
+ *
43
+ * Expands roles based on an inheritance map. Apply at scope-building time
44
+ * so that requireRoles() works with the already-expanded list.
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * import { createRoleHierarchy } from '@classytic/arc/permissions';
49
+ *
50
+ * const hierarchy = createRoleHierarchy({
51
+ * superadmin: ['admin'],
52
+ * admin: ['branch_manager'],
53
+ * branch_manager: ['member'],
54
+ * });
55
+ *
56
+ * // When building scope:
57
+ * const expandedRoles = hierarchy.expand(user.roles);
58
+ * // ['superadmin'] → ['superadmin', 'admin', 'branch_manager', 'member']
59
+ *
60
+ * // Check inclusion:
61
+ * hierarchy.includes(['admin'], 'branch_manager'); // true (admin inherits branch_manager)
62
+ * hierarchy.includes(['member'], 'admin'); // false (child doesn't inherit parent)
63
+ * ```
64
+ */
65
+ interface RoleHierarchy {
66
+ /** Expand roles to include all inherited (child) roles. Deduplicated. */
67
+ expand(roles: readonly string[]): string[];
68
+ /** Check if any of the user's roles (expanded) include the required role. */
69
+ includes(userRoles: readonly string[], requiredRole: string): boolean;
70
+ }
71
+ /**
72
+ * Create a role hierarchy from a parent → children map.
73
+ *
74
+ * Each key is a parent role, each value is the array of roles it inherits.
75
+ * Inheritance is transitive: if A → B and B → C, then A expands to [A, B, C].
76
+ * Circular references are handled safely (visited set).
77
+ */
78
+ declare function createRoleHierarchy(map: Record<string, readonly string[]>): RoleHierarchy;
79
+ declare namespace presets_d_exports {
80
+ export { adminOnly, authenticated, fullPublic, ownerWithAdminBypass, publicRead, publicReadAdminWrite, readOnly };
81
+ }
82
+ /**
83
+ * ResourcePermissions shape — matches the type in types/index.ts
84
+ */
85
+ interface ResourcePermissions<TDoc = any> {
86
+ list?: PermissionCheck<TDoc>;
87
+ get?: PermissionCheck<TDoc>;
88
+ create?: PermissionCheck<TDoc>;
89
+ update?: PermissionCheck<TDoc>;
90
+ delete?: PermissionCheck<TDoc>;
91
+ }
92
+ type PermissionOverrides<TDoc = any> = Partial<ResourcePermissions<TDoc>>;
93
+ /**
94
+ * Public read, authenticated write.
95
+ * list + get = allowPublic(), create + update + delete = requireAuth()
96
+ */
97
+ declare function publicRead<TDoc = any>(overrides?: PermissionOverrides<TDoc>): ResourcePermissions<TDoc>;
98
+ /**
99
+ * Public read, admin write.
100
+ * list + get = allowPublic(), create + update + delete = requireRoles(['admin'])
101
+ */
102
+ declare function publicReadAdminWrite<TDoc = any>(roles?: readonly string[], overrides?: PermissionOverrides<TDoc>): ResourcePermissions<TDoc>;
103
+ /**
104
+ * All operations require authentication.
105
+ */
106
+ declare function authenticated<TDoc = any>(overrides?: PermissionOverrides<TDoc>): ResourcePermissions<TDoc>;
107
+ /**
108
+ * All operations require specific roles.
109
+ * @param roles - Required roles (user needs at least one). Default: ['admin']
110
+ */
111
+ declare function adminOnly<TDoc = any>(roles?: readonly string[], overrides?: PermissionOverrides<TDoc>): ResourcePermissions<TDoc>;
112
+ /**
113
+ * Owner-scoped with admin bypass.
114
+ * list = auth (scoped to owner), get = auth, create = auth,
115
+ * update + delete = ownership check with admin bypass.
116
+ *
117
+ * @param ownerField - Field containing owner ID (default: 'userId')
118
+ * @param bypassRoles - Roles that bypass ownership check (default: ['admin'])
119
+ */
120
+ declare function ownerWithAdminBypass<TDoc = any>(ownerField?: Extract<keyof TDoc, string> | string, bypassRoles?: readonly string[], overrides?: PermissionOverrides<TDoc>): ResourcePermissions<TDoc>;
121
+ /**
122
+ * Full public access — no auth required for any operation.
123
+ * Use sparingly (dev/testing, truly public APIs).
124
+ */
125
+ declare function fullPublic<TDoc = any>(overrides?: PermissionOverrides<TDoc>): ResourcePermissions<TDoc>;
126
+ /**
127
+ * Read-only: list + get authenticated, write operations denied.
128
+ * Useful for computed/derived resources.
129
+ */
130
+ declare function readOnly<TDoc = any>(overrides?: PermissionOverrides<TDoc>): ResourcePermissions<TDoc>;
131
+ //#endregion
132
+ //#region src/permissions/index.d.ts
133
+ interface DynamicPermissionMatrixConfig {
134
+ /**
135
+ * Resolve role → resource → actions map dynamically (DB/API/config service).
136
+ * Called at permission-check time (or cache miss if cache enabled).
137
+ */
138
+ resolveRolePermissions: (ctx: PermissionContext) => Record<string, Record<string, readonly string[]>> | Promise<Record<string, Record<string, readonly string[]>>>;
139
+ /**
140
+ * Optional cache store adapter.
141
+ * Use MemoryCacheStore for single-instance apps or RedisCacheStore for distributed setups.
142
+ */
143
+ cacheStore?: CacheStore<Record<string, Record<string, readonly string[]>>>;
144
+ /** Optional logger for cache/runtime failures (default: console) */
145
+ logger?: CacheLogger;
146
+ /**
147
+ * Legacy convenience in-memory cache config.
148
+ * If `cacheStore` is not provided and ttlMs > 0, Arc creates an internal MemoryCacheStore.
149
+ */
150
+ cache?: {
151
+ /** Cache TTL in milliseconds */ttlMs: number; /** Optional custom cache key builder */
152
+ key?: (ctx: PermissionContext) => string | null | undefined; /** Hard entry cap for internal memory store (default: 1000) */
153
+ maxEntries?: number;
154
+ };
155
+ }
156
+ /** Minimal publish/subscribe interface for cross-node cache invalidation. */
157
+ interface PermissionEventBus {
158
+ publish: <T>(type: string, payload: T) => Promise<void>;
159
+ subscribe: (pattern: string, handler: (event: {
160
+ payload: unknown;
161
+ }) => void | Promise<void>) => Promise<(() => void) | undefined>;
162
+ }
163
+ interface ConnectEventsOptions {
164
+ /** Called on remote invalidation for app-specific cleanup (e.g., resolver cache) */
165
+ onRemoteInvalidation?: (orgId: string) => void | Promise<void>;
166
+ /** Custom event type (default: 'arc.permissions.invalidated') */
167
+ eventType?: string;
168
+ }
169
+ interface DynamicPermissionMatrix {
170
+ can: (permissions: Record<string, readonly string[]>) => PermissionCheck;
171
+ canAction: (resource: string, action: string) => PermissionCheck;
172
+ requireRole: (...roles: string[]) => PermissionCheck;
173
+ requireMembership: () => PermissionCheck;
174
+ requireTeamMembership: () => PermissionCheck;
175
+ /** Invalidate cached permissions for a specific organization */
176
+ invalidateByOrg: (orgId: string) => Promise<void>;
177
+ clearCache: () => Promise<void>;
178
+ /**
179
+ * Connect to an event system for cross-node cache invalidation.
180
+ *
181
+ * Late-binding: call after the event plugin is registered (e.g., in onReady hook).
182
+ * Once connected, `invalidateByOrg()` auto-publishes an event, and incoming
183
+ * events from other nodes trigger local cache invalidation.
184
+ * Echo is suppressed via per-process nodeId matching.
185
+ */
186
+ connectEvents(events: PermissionEventBus, options?: ConnectEventsOptions): Promise<void>;
187
+ /** Disconnect from the event system. Safe to call even if never connected. */
188
+ disconnectEvents(): Promise<void>;
189
+ /** Whether events are currently connected. */
190
+ readonly eventsConnected: boolean;
191
+ }
192
+ /**
193
+ * Allow public access (no authentication required)
194
+ *
195
+ * @example
196
+ * ```typescript
197
+ * permissions: {
198
+ * list: allowPublic(),
199
+ * get: allowPublic(),
200
+ * }
201
+ * ```
202
+ */
203
+ declare function allowPublic(): PermissionCheck;
204
+ /**
205
+ * Require authentication (any authenticated user)
206
+ *
207
+ * @example
208
+ * ```typescript
209
+ * permissions: {
210
+ * create: requireAuth(),
211
+ * update: requireAuth(),
212
+ * }
213
+ * ```
214
+ */
215
+ declare function requireAuth(): PermissionCheck;
216
+ /**
217
+ * Require specific roles
218
+ *
219
+ * @param roles - Required roles (user needs at least one)
220
+ * @param options - Optional bypass roles
221
+ *
222
+ * @example
223
+ * ```typescript
224
+ * permissions: {
225
+ * create: requireRoles(['admin', 'editor']),
226
+ * delete: requireRoles(['admin']),
227
+ * }
228
+ *
229
+ * // With bypass roles
230
+ * permissions: {
231
+ * update: requireRoles(['owner'], { bypassRoles: ['admin', 'superadmin'] }),
232
+ * }
233
+ * ```
234
+ */
235
+ /**
236
+ * Require one of the specified roles. Checks BOTH platform roles
237
+ * (`user.role`) AND organization roles (`scope.orgRoles`) by default —
238
+ * passing in either layer grants access. Elevated scope always passes.
239
+ *
240
+ * Accepts EITHER variadic strings OR a single readonly array — both forms
241
+ * produce identical behavior. Use whichever reads better at the call site.
242
+ *
243
+ * @example
244
+ * ```typescript
245
+ * requireRoles('admin') // single role, variadic
246
+ * requireRoles('admin', 'editor') // multiple roles, variadic
247
+ * requireRoles(['admin', 'editor']) // array form
248
+ * requireRoles(['admin'], { bypassRoles: ['superadmin'] }) // with options
249
+ * requireRoles(['admin'], { includeOrgRoles: false }) // platform-only
250
+ * ```
251
+ *
252
+ * **2.7.1 BREAKING CHANGE:** `includeOrgRoles` now defaults to `true`. The
253
+ * old default (`false`, platform-only) was a footgun for the common case of
254
+ * Better Auth's organization plugin where roles like 'admin' are assigned at
255
+ * the org level. To restore the old behavior explicitly, pass
256
+ * `{ includeOrgRoles: false }`.
257
+ *
258
+ * For org-only role checks, prefer `requireOrgRole('admin')`.
259
+ */
260
+ declare function requireRoles(role: string, ...rest: string[]): PermissionCheck;
261
+ declare function requireRoles(roles: readonly string[], options?: {
262
+ bypassRoles?: readonly string[];
263
+ /**
264
+ * Also check org membership roles (`scope.orgRoles`) when in org context.
265
+ * Default: `true` (changed in 2.7.1).
266
+ *
267
+ * Set to `false` to restore the pre-2.7.1 behavior of checking only
268
+ * platform roles (`user.role`). For org-only role checks, prefer
269
+ * `requireOrgRole('admin')` instead.
270
+ */
271
+ includeOrgRoles?: boolean;
272
+ }): PermissionCheck;
273
+ /**
274
+ * **Alias of `requireRoles()`** — checks both platform roles AND org roles.
275
+ *
276
+ * Since 2.7.1, `requireRoles()` defaults to `includeOrgRoles: true`, which
277
+ * means `roles('admin')` and `requireRoles('admin')` are now functionally
278
+ * identical. This helper is preserved for backwards compatibility and for
279
+ * call sites that prefer the shorter `roles()` name.
280
+ *
281
+ * **For new code, prefer `requireRoles()`** — it's the canonical name and
282
+ * matches the rest of the `requireXxx()` family (`requireAuth`, `requireOwnership`,
283
+ * `requireOrgRole`, etc.).
284
+ *
285
+ * For platform-only checks: `requireRoles(['admin'], { includeOrgRoles: false })`
286
+ * For org-only checks: `requireOrgRole('admin')`
287
+ *
288
+ * @example
289
+ * ```typescript
290
+ * // These are identical:
291
+ * roles('admin', 'editor')
292
+ * requireRoles('admin', 'editor')
293
+ * requireRoles(['admin', 'editor'])
294
+ * ```
295
+ */
296
+ declare function roles(...args: string[] | [readonly string[]]): PermissionCheck;
297
+ /**
298
+ * Require resource ownership
299
+ *
300
+ * Returns filters to scope queries to user's owned resources.
301
+ *
302
+ * @param ownerField - Field containing owner ID (default: 'userId')
303
+ * @param options - Optional bypass roles
304
+ *
305
+ * @example
306
+ * ```typescript
307
+ * permissions: {
308
+ * update: requireOwnership('userId'),
309
+ * delete: requireOwnership('createdBy', { bypassRoles: ['admin'] }),
310
+ * }
311
+ * ```
312
+ */
313
+ declare function requireOwnership<TDoc = Record<string, unknown>>(ownerField?: Extract<keyof TDoc, string> | string, options?: {
314
+ bypassRoles?: readonly string[];
315
+ }): PermissionCheck<TDoc>;
316
+ /**
317
+ * Combine multiple checks - ALL must pass (AND logic).
318
+ *
319
+ * Each child runs against the **accumulated** state of previous children:
320
+ * - `filters` from earlier children are merged into the next child's
321
+ * `_policyFilters` (so e.g. `requireOwnership` sees row-level scoping)
322
+ * - `scope` from earlier children is installed on the request before the
323
+ * next child runs (so e.g. `requireOrgMembership` after `requireApiKey`
324
+ * sees the service scope from the API key check)
325
+ *
326
+ * The final returned `PermissionResult` carries both the merged `filters` AND
327
+ * the merged `scope`, so the outer middleware's `applyPermissionResult` call
328
+ * sees the same end-state.
329
+ *
330
+ * @example
331
+ * ```typescript
332
+ * // CRUD permissions composed across roles + ownership
333
+ * permissions: {
334
+ * update: allOf(
335
+ * requireAuth(),
336
+ * requireRoles(['editor']),
337
+ * requireOwnership('createdBy')
338
+ * ),
339
+ * }
340
+ *
341
+ * // Custom auth + org membership — first check installs the scope,
342
+ * // second check reads it.
343
+ * permissions: {
344
+ * list: allOf(requireApiKey(), requireOrgMembership()),
345
+ * }
346
+ * ```
347
+ */
348
+ declare function allOf(...checks: PermissionCheck[]): PermissionCheck;
349
+ /**
350
+ * Combine multiple checks - ANY must pass (OR logic)
351
+ *
352
+ * @example
353
+ * ```typescript
354
+ * permissions: {
355
+ * update: anyOf(
356
+ * requireRoles(['admin']),
357
+ * requireOwnership('createdBy')
358
+ * ),
359
+ * }
360
+ * ```
361
+ */
362
+ declare function anyOf(...checks: PermissionCheck[]): PermissionCheck;
363
+ /**
364
+ * Deny all access
365
+ *
366
+ * @example
367
+ * ```typescript
368
+ * permissions: {
369
+ * delete: denyAll('Deletion not allowed'),
370
+ * }
371
+ * ```
372
+ */
373
+ declare function denyAll(reason?: string): PermissionCheck;
374
+ /**
375
+ * Dynamic permission based on context
376
+ *
377
+ * @example
378
+ * ```typescript
379
+ * permissions: {
380
+ * update: when((ctx) => ctx.data?.status === 'draft'),
381
+ * }
382
+ * ```
383
+ */
384
+ declare function when<TDoc = Record<string, unknown>>(condition: (ctx: PermissionContext<TDoc>) => boolean | Promise<boolean>): PermissionCheck<TDoc>;
385
+ /**
386
+ * Require an org-bound caller. Grants access for any scope kind that
387
+ * carries org context: `member` (human user with org membership), `service`
388
+ * (API key bound to an org), or `elevated` (platform admin). Denies for
389
+ * `public` and `authenticated` scopes (no org context).
390
+ *
391
+ * This is the canonical "is the caller acting inside an org" check, and the
392
+ * usual partner for `multiTenantPreset` — if a route is multi-tenant
393
+ * filtered, you almost always want this gate too.
394
+ *
395
+ * Reads `request.scope` set by auth adapters or by upstream permission
396
+ * checks via `PermissionResult.scope` (e.g. a custom `requireApiKey()`).
397
+ *
398
+ * @example
399
+ * ```typescript
400
+ * permissions: {
401
+ * list: requireOrgMembership(),
402
+ * get: requireOrgMembership(),
403
+ *
404
+ * // Composed with an OAuth-style scope check for API-key callers
405
+ * create: allOf(requireOrgMembership(), requireServiceScope('jobs:write')),
406
+ * }
407
+ * ```
408
+ */
409
+ declare function requireOrgMembership<TDoc = Record<string, unknown>>(): PermissionCheck<TDoc>;
410
+ /**
411
+ * Require specific org-level roles.
412
+ * Reads `request.scope.orgRoles` (set by auth adapters).
413
+ * Elevated scope always passes (platform admin bypass).
414
+ *
415
+ * **Service scopes (API keys) always fail this check** — services don't
416
+ * carry user-style org roles, only OAuth-style `scopes` strings. For routes
417
+ * that should accept BOTH human admins AND API keys, compose explicitly:
418
+ *
419
+ * ```typescript
420
+ * permissions: {
421
+ * create: anyOf(
422
+ * requireOrgRole('admin'), // human path
423
+ * requireServiceScope('jobs:write'), // machine path
424
+ * ),
425
+ * }
426
+ * ```
427
+ *
428
+ * This separation is intentional — implicit "API key bypasses role checks"
429
+ * is the kind of footgun that ships data breaches. Services must opt into
430
+ * specific scopes the same way OAuth clients do.
431
+ *
432
+ * @param roles - Required org roles (user needs at least one)
433
+ *
434
+ * @example
435
+ * ```typescript
436
+ * permissions: {
437
+ * create: requireOrgRole('admin', 'owner'),
438
+ * delete: requireOrgRole('owner'),
439
+ * }
440
+ * ```
441
+ */
442
+ declare function requireOrgRole<TDoc = Record<string, unknown>>(...args: string[] | [readonly string[]]): PermissionCheck<TDoc>;
443
+ /**
444
+ * Require specific OAuth-style scope strings on a service (API key) identity.
445
+ *
446
+ * Reads `request.scope.scopes` — only populated when the scope kind is
447
+ * `service`. Mirrors how OAuth 2.0 / Better Auth's apiKey plugin / API
448
+ * gateways express machine permissions: a comma- or array-encoded list of
449
+ * scope strings like `'jobs:read'`, `'jobs:write'`, `'memories:*'`.
450
+ *
451
+ * **Pass behavior:**
452
+ * - `service` scope where `scopes` contains ANY of the required strings → grant
453
+ * - `elevated` scope (platform admin) → grant
454
+ * - Anything else → deny with a clear reason
455
+ *
456
+ * Notably this does **not** grant for `member` scopes — humans go through
457
+ * `requireOrgRole`. For routes that should accept both, compose with `anyOf`:
458
+ *
459
+ * ```typescript
460
+ * permissions: {
461
+ * create: anyOf(
462
+ * requireOrgRole('admin'),
463
+ * requireServiceScope('jobs:write'),
464
+ * ),
465
+ * }
466
+ * ```
467
+ *
468
+ * @param scopes - Required scope strings (caller needs at least one)
469
+ *
470
+ * @example
471
+ * ```typescript
472
+ * // Variadic
473
+ * requireServiceScope('jobs:write')
474
+ * requireServiceScope('jobs:read', 'jobs:write')
475
+ *
476
+ * // Array
477
+ * requireServiceScope(['jobs:read', 'jobs:write'])
478
+ *
479
+ * // Composed with org membership for org-scoped API keys
480
+ * permissions: {
481
+ * list: allOf(requireOrgMembership(), requireServiceScope('jobs:read')),
482
+ * create: allOf(requireOrgMembership(), requireServiceScope('jobs:write')),
483
+ * }
484
+ * ```
485
+ */
486
+ declare function requireServiceScope<TDoc = Record<string, unknown>>(...args: string[] | [readonly string[]]): PermissionCheck<TDoc>;
487
+ /**
488
+ * Require app-defined scope context dimensions (branch, project, department,
489
+ * region, workspace, etc.) on the current request.
490
+ *
491
+ * Reads `request.scope.context` (a `Readonly<Record<string, string>>` slot
492
+ * available on `member`, `service`, and `elevated` scope kinds). Arc takes
493
+ * no position on what dimensions you use — you set them, you check them.
494
+ *
495
+ * **Three call shapes:**
496
+ *
497
+ * ```typescript
498
+ * // 1. Presence check — key must exist on scope.context
499
+ * requireScopeContext('branchId')
500
+ *
501
+ * // 2. Value match — key must equal a specific string
502
+ * requireScopeContext('branchId', 'eng-paris')
503
+ *
504
+ * // 3. Multi-key (object form, AND semantics) — every key must match
505
+ * requireScopeContext({ branchId: 'eng-paris', projectId: 'p-123' })
506
+ * requireScopeContext({ region: 'eu', branchId: undefined }) // 'undefined' = presence-only for that key
507
+ * ```
508
+ *
509
+ * **Pass behavior:**
510
+ * - All required keys present (and matching values when specified) → grant
511
+ * - `elevated` scope (platform admin) → grant unconditionally (cross-context bypass)
512
+ * - Any required key missing or mismatched → deny with a clear reason
513
+ * - Scope kind without context support (`public`, `authenticated`) → deny
514
+ *
515
+ * Pairs with `multiTenantPreset({ tenantFields: [...] })` for row-level
516
+ * filtering on the same dimensions.
517
+ *
518
+ * @example
519
+ * ```typescript
520
+ * permissions: {
521
+ * // Branch-scoped CRUD — caller must have branchId in their scope context
522
+ * list: allOf(requireOrgMembership(), requireScopeContext('branchId')),
523
+ *
524
+ * // Project admin — caller must have BOTH project context AND admin role
525
+ * delete: allOf(requireOrgRole('admin'), requireScopeContext('projectId')),
526
+ *
527
+ * // Region-locked endpoint
528
+ * euOnly: requireScopeContext('region', 'eu'),
529
+ * }
530
+ * ```
531
+ */
532
+ declare function requireScopeContext<TDoc = Record<string, unknown>>(keyOrMap: string | Record<string, string | undefined>, value?: string): PermissionCheck<TDoc>;
533
+ /**
534
+ * Require that the caller's scope grants access to a target organization
535
+ * — either the current org or one of its ancestors (`scope.ancestorOrgIds`).
536
+ *
537
+ * Designed for parent-child organization hierarchies (holding company →
538
+ * subsidiary → branch, MSP → managed tenants, white-label parent → child
539
+ * accounts) where some routes need to accept "this org OR any org I have
540
+ * access to via the chain". Arc takes no position on the source of the
541
+ * chain — your auth function loads `ancestorOrgIds` from your own data
542
+ * model. There's no automatic inheritance: every route opts in explicitly.
543
+ *
544
+ * **Two call shapes:**
545
+ *
546
+ * ```typescript
547
+ * // Static target — rare, used when one route only ever acts on one org
548
+ * requireOrgInScope('acme-holding')
549
+ *
550
+ * // Dynamic target — extracted from request params/body/headers per call
551
+ * requireOrgInScope((ctx) => ctx.request.params.orgId)
552
+ * requireOrgInScope((ctx) => ctx.request.body?.organizationId)
553
+ * ```
554
+ *
555
+ * **Pass behavior:**
556
+ * - Target equals `scope.organizationId` → grant
557
+ * - Target appears in `scope.ancestorOrgIds` → grant
558
+ * - `elevated` scope → grant unconditionally (cross-org admin bypass)
559
+ * - Target is undefined (extractor returned nothing) → deny with reason
560
+ * - Anything else → deny with target name in reason
561
+ *
562
+ * @example
563
+ * ```typescript
564
+ * // /orgs/:orgId/jobs — caller can act on any org in their hierarchy chain
565
+ * permissions: {
566
+ * list: requireOrgInScope((ctx) => ctx.request.params.orgId),
567
+ * create: allOf(
568
+ * requireOrgInScope((ctx) => ctx.request.body?.organizationId),
569
+ * requireOrgRole('admin'),
570
+ * ),
571
+ * }
572
+ * ```
573
+ */
574
+ declare function requireOrgInScope<TDoc = Record<string, unknown>>(target: string | ((ctx: PermissionContext<TDoc>) => string | undefined)): PermissionCheck<TDoc>;
575
+ /**
576
+ * Create a scoped permission system for resource-action patterns.
577
+ * Maps org roles to fine-grained permissions without external API calls.
578
+ *
579
+ * @example
580
+ * ```typescript
581
+ * const perms = createOrgPermissions({
582
+ * statements: {
583
+ * product: ['create', 'update', 'delete'],
584
+ * order: ['create', 'approve'],
585
+ * },
586
+ * roles: {
587
+ * owner: { product: ['create', 'update', 'delete'], order: ['create', 'approve'] },
588
+ * admin: { product: ['create', 'update'], order: ['create'] },
589
+ * member: { product: [], order: [] },
590
+ * },
591
+ * });
592
+ *
593
+ * defineResource({
594
+ * permissions: {
595
+ * create: perms.can({ product: ['create'] }),
596
+ * delete: perms.can({ product: ['delete'] }),
597
+ * }
598
+ * });
599
+ * ```
600
+ */
601
+ declare function createOrgPermissions(config: {
602
+ statements: Record<string, readonly string[]>;
603
+ roles: Record<string, Record<string, readonly string[]>>;
604
+ }): {
605
+ can: (permissions: Record<string, string[]>) => PermissionCheck;
606
+ requireRole: (...roles: string[]) => PermissionCheck;
607
+ requireMembership: () => PermissionCheck;
608
+ requireTeamMembership: () => PermissionCheck;
609
+ };
610
+ /**
611
+ * Create a dynamic role-based permission matrix.
612
+ *
613
+ * Use this when role/action mappings are managed outside code
614
+ * (e.g., admin UI matrix, DB-stored ACLs, remote policy service).
615
+ *
616
+ * Supports:
617
+ * - org role union (any assigned org role can grant)
618
+ * - global bypass roles
619
+ * - wildcard resource/action (`*`)
620
+ * - optional in-memory cache
621
+ */
622
+ declare function createDynamicPermissionMatrix(config: DynamicPermissionMatrixConfig): DynamicPermissionMatrix;
623
+ /**
624
+ * Require membership in the active team.
625
+ * User must be authenticated, a member of the active org, AND have an active team.
626
+ *
627
+ * Better Auth teams are flat member groups (no team-level roles).
628
+ * Reads `request.scope.teamId` set by the Better Auth adapter.
629
+ *
630
+ * @example
631
+ * ```typescript
632
+ * permissions: {
633
+ * list: requireTeamMembership(),
634
+ * create: requireTeamMembership(),
635
+ * }
636
+ * ```
637
+ */
638
+ declare function requireTeamMembership<TDoc = Record<string, unknown>>(): PermissionCheck<TDoc>;
639
+ //#endregion
640
+ export { RoleHierarchy as A, authenticated as C, publicRead as D, presets_d_exports as E, applyPermissionResult as M, normalizePermissionResult as N, publicReadAdminWrite as O, adminOnly as S, ownerWithAdminBypass as T, requireScopeContext as _, allOf as a, roles as b, createDynamicPermissionMatrix as c, requireAuth as d, requireOrgInScope as f, requireRoles as g, requireOwnership as h, PermissionEventBus as i, createRoleHierarchy as j, readOnly as k, createOrgPermissions as l, requireOrgRole as m, DynamicPermissionMatrix as n, allowPublic as o, requireOrgMembership as p, DynamicPermissionMatrixConfig as r, anyOf as s, ConnectEventsOptions as t, denyAll as u, requireServiceScope as v, fullPublic as w, when as x, requireTeamMembership as y };