@classytic/arc 2.9.1 → 2.10.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 (139) hide show
  1. package/README.md +19 -90
  2. package/dist/{BaseController-Vu2yc56T.mjs → BaseController-CbKKIflT.mjs} +8 -44
  3. package/dist/{ResourceRegistry-Dq3_zBQP.mjs → ResourceRegistry-BPd6NQDm.mjs} +1 -1
  4. package/dist/adapters/index.d.mts +3 -3
  5. package/dist/adapters/index.mjs +2 -2
  6. package/dist/{adapters-BBqAVvPK.mjs → adapters-BXY4i-hw.mjs} +210 -41
  7. package/dist/audit/index.d.mts +38 -3
  8. package/dist/audit/index.mjs +41 -7
  9. package/dist/auth/index.d.mts +4 -4
  10. package/dist/auth/index.mjs +5 -5
  11. package/dist/auth/redis-session.d.mts +1 -1
  12. package/dist/cache/index.d.mts +17 -15
  13. package/dist/cache/index.mjs +15 -14
  14. package/dist/{caching-CjybdRwx.mjs → caching-CBpK_SCM.mjs} +8 -3
  15. package/dist/cli/commands/describe.mjs +1 -1
  16. package/dist/cli/commands/docs.mjs +2 -2
  17. package/dist/cli/commands/generate.mjs +1 -1
  18. package/dist/cli/commands/init.mjs +1 -1
  19. package/dist/cli/commands/introspect.mjs +1 -1
  20. package/dist/core/index.d.mts +2 -2
  21. package/dist/core/index.mjs +3 -4
  22. package/dist/{defineResource-C__jkwvs.mjs → core-CcR01lup.mjs} +44 -12
  23. package/dist/{createActionRouter-DH1YFL9m.mjs → createActionRouter-Bp_5c_2b.mjs} +1 -1
  24. package/dist/{createApp-CBJUJKGP.mjs → createApp-BuvPma24.mjs} +14 -14
  25. package/dist/docs/index.d.mts +2 -2
  26. package/dist/docs/index.mjs +2 -2
  27. package/dist/{elevation-DxQ6ACbt.mjs → elevation-C7hgL_aI.mjs} +2 -2
  28. package/dist/{errorHandler-CZDW4EXS.mjs → errorHandler-Bb49BvPD.mjs} +1 -1
  29. package/dist/{errorHandler-DixGcttC.d.mts → errorHandler-DRQ3EqfL.d.mts} +1 -1
  30. package/dist/{eventPlugin-BxvaCIZF.d.mts → eventPlugin-CxWgpd6K.d.mts} +1 -1
  31. package/dist/{eventPlugin-Dl7MoVWH.mjs → eventPlugin-DCUjuiQT.mjs} +1 -1
  32. package/dist/events/index.d.mts +8 -5
  33. package/dist/events/index.mjs +34 -17
  34. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  35. package/dist/events/transports/redis.d.mts +1 -1
  36. package/dist/factory/index.d.mts +1 -1
  37. package/dist/factory/index.mjs +2 -2
  38. package/dist/{types-DZi1aYhm.d.mts → fields-Lo1VUDpt.d.mts} +121 -1
  39. package/dist/{filesUpload-q8oHt--L.mjs → filesUpload-t21LS-py.mjs} +2 -2
  40. package/dist/hooks/index.d.mts +1 -1
  41. package/dist/hooks/index.mjs +1 -1
  42. package/dist/idempotency/index.d.mts +7 -4
  43. package/dist/idempotency/index.mjs +9 -11
  44. package/dist/idempotency/redis.d.mts +1 -1
  45. package/dist/{index-Cibkchnx.d.mts → index-8qw4y6ff.d.mts} +2 -2
  46. package/dist/{index-C-xjcA6F.d.mts → index-ChIw3776.d.mts} +283 -408
  47. package/dist/{interface-YrWsmKqE.d.mts → index-Cl0uoKd5.d.mts} +1885 -2741
  48. package/dist/{index-CtGKT0lf.d.mts → index-DStwgFUK.d.mts} +81 -7
  49. package/dist/index.d.mts +7 -8
  50. package/dist/index.mjs +11 -12
  51. package/dist/integrations/event-gateway.d.mts +1 -1
  52. package/dist/integrations/event-gateway.mjs +1 -1
  53. package/dist/integrations/index.d.mts +1 -1
  54. package/dist/integrations/mcp/index.d.mts +2 -2
  55. package/dist/integrations/mcp/index.mjs +1 -1
  56. package/dist/integrations/mcp/testing.d.mts +1 -1
  57. package/dist/integrations/mcp/testing.mjs +1 -1
  58. package/dist/interface-D218ikEo.d.mts +77 -0
  59. package/dist/{memory-BFAYkf8H.mjs → memory-B5Amv9A1.mjs} +23 -8
  60. package/dist/{openapi-CXuTG1M9.mjs → openapi-B5F8AddX.mjs} +2 -2
  61. package/dist/org/index.d.mts +2 -2
  62. package/dist/permissions/index.d.mts +3 -4
  63. package/dist/permissions/index.mjs +5 -5
  64. package/dist/{permissions-oNZawnkR.mjs → permissions-Dk6mshja.mjs} +315 -397
  65. package/dist/plugins/index.d.mts +4 -4
  66. package/dist/plugins/index.mjs +12 -14
  67. package/dist/plugins/response-cache.mjs +1 -1
  68. package/dist/plugins/tracing-entry.d.mts +1 -1
  69. package/dist/plugins/tracing-entry.mjs +1 -1
  70. package/dist/presets/filesUpload.d.mts +3 -3
  71. package/dist/presets/filesUpload.mjs +1 -1
  72. package/dist/presets/index.d.mts +1 -1
  73. package/dist/presets/index.mjs +2 -2
  74. package/dist/presets/multiTenant.d.mts +1 -1
  75. package/dist/presets/multiTenant.mjs +1 -1
  76. package/dist/presets/search.d.mts +91 -4
  77. package/dist/presets/search.mjs +1 -1
  78. package/dist/{presets-hM4WhNWY.mjs → presets-fLJVXdVn.mjs} +1 -1
  79. package/dist/{queryCachePlugin-CnTZZTC5.d.mts → queryCachePlugin-BKbWjgDG.d.mts} +1 -1
  80. package/dist/{queryCachePlugin-DbUVroUG.mjs → queryCachePlugin-DQCEfJis.mjs} +8 -8
  81. package/dist/{queryParser-Cs-6SHQK.mjs → queryParser-DBqBB6AC.mjs} +1 -1
  82. package/dist/{redis-MXLp1oOf.d.mts → redis-DqyeggCa.d.mts} +1 -1
  83. package/dist/{redis-stream-Bz-4q96t.d.mts → redis-stream-CakIQmwR.d.mts} +1 -1
  84. package/dist/registry/index.d.mts +1 -1
  85. package/dist/registry/index.mjs +2 -2
  86. package/dist/{resourceToTools-C3cWymnW.mjs → resourceToTools-BElv3xPT.mjs} +3 -3
  87. package/dist/scope/index.d.mts +1 -1
  88. package/dist/scope/index.mjs +2 -2
  89. package/dist/{sse-CJpt7LGI.mjs → sse-yBCgOLGu.mjs} +1 -1
  90. package/dist/testing/index.d.mts +6 -5
  91. package/dist/testing/index.mjs +8 -10
  92. package/dist/testing/storageContract.d.mts +1 -1
  93. package/dist/types/index.d.mts +4 -4
  94. package/dist/types/index.mjs +1 -31
  95. package/dist/types/storage.d.mts +1 -1
  96. package/dist/{types-CoSzA-s-.d.mts → types-Btdda02s.d.mts} +1 -1
  97. package/dist/{types-CunEX4UX.d.mts → types-Co8k3NyS.d.mts} +9 -9
  98. package/dist/types-Csi3FLfq.mjs +27 -0
  99. package/dist/utils/index.d.mts +207 -3
  100. package/dist/utils/index.mjs +3 -4
  101. package/dist/{utils-B7FuRr9w.mjs → utils-B2fNOD_i.mjs} +285 -2
  102. package/dist/{versioning-Cm8qoFDg.mjs → versioning-C2U_bLY0.mjs} +3 -5
  103. package/package.json +15 -18
  104. package/skills/arc/SKILL.md +7 -11
  105. package/skills/arc/references/production.md +0 -41
  106. package/dist/circuitBreaker-CvXkjfrW.d.mts +0 -206
  107. package/dist/circuitBreaker-l18oRgL5.mjs +0 -284
  108. package/dist/core-DNncu0xF.mjs +0 -34
  109. package/dist/dynamic/index.d.mts +0 -93
  110. package/dist/dynamic/index.mjs +0 -122
  111. package/dist/fields-BC7zcmI9.d.mts +0 -121
  112. package/dist/interface-DplgQO2e.d.mts +0 -54
  113. package/dist/policies/index.d.mts +0 -425
  114. package/dist/policies/index.mjs +0 -318
  115. package/dist/rpc/index.d.mts +0 -90
  116. package/dist/rpc/index.mjs +0 -248
  117. /package/dist/{EventTransport-CqZ8FyM_.d.mts → EventTransport-CUw5NNWe.d.mts} +0 -0
  118. /package/dist/{HookSystem-BjFu7zf1.mjs → HookSystem-BNYKnrXF.mjs} +0 -0
  119. /package/dist/{applyPermissionResult-bqGpo9ML.mjs → applyPermissionResult-QhV1Pa-g.mjs} +0 -0
  120. /package/dist/{betterAuthOpenApi--rdY15Ld.mjs → betterAuthOpenApi-BBRVhjQN.mjs} +0 -0
  121. /package/dist/{constants-Cxde4rpC.mjs → constants-BhY1OHoH.mjs} +0 -0
  122. /package/dist/{elevation-B6S5csVA.d.mts → elevation-C5SwtkAn.d.mts} +0 -0
  123. /package/dist/{errors-BI8kEKsO.d.mts → errors-CCSsMpXE.d.mts} +0 -0
  124. /package/dist/{errors-CqWnSqM-.mjs → errors-D5c-5BJL.mjs} +0 -0
  125. /package/dist/{externalPaths-Bapitwvd.d.mts → externalPaths-BQ8QijNH.d.mts} +0 -0
  126. /package/dist/{fields-CU6FlaDV.mjs → fields-bxkeltzz.mjs} +0 -0
  127. /package/dist/{interface-B-pe8fhj.d.mts → interface-CSbZdv_3.d.mts} +0 -0
  128. /package/dist/{loadResources-Bksk8ydA.mjs → loadResources-BAzJItAJ.mjs} +0 -0
  129. /package/dist/{logger-CDjpjySd.mjs → logger-DLg8-Ueg.mjs} +0 -0
  130. /package/dist/{metrics-TuOmguhi.mjs → metrics-DuhiSEZI.mjs} +0 -0
  131. /package/dist/{pluralize-CWP6MB39.mjs → pluralize-A0tWEl1K.mjs} +0 -0
  132. /package/dist/{registry-B0Wl7uVV.mjs → registry-B3lRFBWo.mjs} +0 -0
  133. /package/dist/{replyHelpers-BLojtuvR.mjs → replyHelpers-CXtJDAZ0.mjs} +0 -0
  134. /package/dist/{requestContext-DYtmNpm5.mjs → requestContext-xHIKedG6.mjs} +0 -0
  135. /package/dist/{sessionManager-D-oNWHz3.d.mts → sessionManager-BkzVU8h2.d.mts} +0 -0
  136. /package/dist/{storage-BwGQXUpd.d.mts → storage-CVk_SEn2.d.mts} +0 -0
  137. /package/dist/{store-helpers-DFiZl5TL.mjs → store-helpers-ZCSMJJAX.mjs} +0 -0
  138. /package/dist/{tracing-xqXzWeaf.d.mts → tracing-65B51Dw3.d.mts} +0 -0
  139. /package/dist/{types-ZUu_h0jp.mjs → types-DV9WDfeg.mjs} +0 -0
@@ -1,6 +1,6 @@
1
1
  import { r as RequestScope } from "./types-BD85MlEK.mjs";
2
- import { n as PermissionContext, r as PermissionResult, t as PermissionCheck } from "./types-DZi1aYhm.mjs";
3
- import { i as CacheStore, t as CacheLogger } from "./interface-DplgQO2e.mjs";
2
+ import { c as PermissionCheck, l as PermissionContext, u as PermissionResult } from "./fields-Lo1VUDpt.mjs";
3
+ import { r as CacheStore, t as CacheLogger } from "./interface-D218ikEo.mjs";
4
4
  import { FastifyRequest } from "fastify";
5
5
 
6
6
  //#region src/permissions/applyPermissionResult.d.ts
@@ -36,161 +36,9 @@ type RequestSink = FastifyRequest & {
36
36
  */
37
37
  declare function applyPermissionResult(result: PermissionResult, request: RequestSink): void;
38
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
- }
39
+ //#region src/permissions/core.d.ts
192
40
  /**
193
- * Allow public access (no authentication required)
41
+ * Allow public access (no authentication required).
194
42
  *
195
43
  * @example
196
44
  * ```typescript
@@ -202,7 +50,7 @@ interface DynamicPermissionMatrix {
202
50
  */
203
51
  declare function allowPublic(): PermissionCheck;
204
52
  /**
205
- * Require authentication (any authenticated user)
53
+ * Require authentication (any authenticated user).
206
54
  *
207
55
  * @example
208
56
  * ```typescript
@@ -213,48 +61,23 @@ declare function allowPublic(): PermissionCheck;
213
61
  * ```
214
62
  */
215
63
  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
64
  /**
236
65
  * Require one of the specified roles. Checks BOTH platform roles
237
66
  * (`user.role`) AND organization roles (`scope.orgRoles`) by default —
238
67
  * passing in either layer grants access. Elevated scope always passes.
239
68
  *
240
69
  * Accepts EITHER variadic strings OR a single readonly array — both forms
241
- * produce identical behavior. Use whichever reads better at the call site.
70
+ * produce identical behavior.
242
71
  *
243
72
  * @example
244
73
  * ```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
74
+ * requireRoles('admin')
75
+ * requireRoles('admin', 'editor')
76
+ * requireRoles(['admin', 'editor'])
77
+ * requireRoles(['admin'], { bypassRoles: ['superadmin'] })
78
+ * requireRoles(['admin'], { includeOrgRoles: false }) // platform-only
250
79
  * ```
251
80
  *
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
81
  * For org-only role checks, prefer `requireOrgRole('admin')`.
259
82
  */
260
83
  declare function requireRoles(role: string, ...rest: string[]): PermissionCheck;
@@ -262,45 +85,19 @@ declare function requireRoles(roles: readonly string[], options?: {
262
85
  bypassRoles?: readonly string[];
263
86
  /**
264
87
  * 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.
88
+ * Default: `true`. Set to `false` to check only platform roles.
270
89
  */
271
90
  includeOrgRoles?: boolean;
272
91
  }): PermissionCheck;
273
92
  /**
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
- * ```
93
+ * Short-form alias of `requireRoles()`. Identical behavior — checks both
94
+ * platform roles AND org roles. Prefer `requireRoles` for new code; this
95
+ * exists for call sites that want a terser name.
295
96
  */
296
97
  declare function roles(...args: string[] | [readonly string[]]): PermissionCheck;
297
98
  /**
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
99
+ * Require resource ownership. Returns filters to scope queries to the
100
+ * caller's owned resources.
304
101
  *
305
102
  * @example
306
103
  * ```typescript
@@ -314,65 +111,62 @@ declare function requireOwnership<TDoc = Record<string, unknown>>(ownerField?: E
314
111
  bypassRoles?: readonly string[];
315
112
  }): PermissionCheck<TDoc>;
316
113
  /**
317
- * Combine multiple checks - ALL must pass (AND logic).
114
+ * Combine multiple checks ALL must pass (AND logic).
318
115
  *
319
116
  * 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)
117
+ * - `filters` from earlier children merge into the next child's `_policyFilters`
118
+ * - `scope` from earlier children installs on the request before the next child runs
325
119
  *
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.
120
+ * The final result carries both merged `filters` and merged `scope`.
329
121
  *
330
122
  * @example
331
123
  * ```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
124
  * permissions: {
125
+ * update: allOf(requireAuth(), requireRoles(['editor']), requireOwnership('createdBy')),
344
126
  * list: allOf(requireApiKey(), requireOrgMembership()),
345
127
  * }
346
128
  * ```
347
129
  */
348
130
  declare function allOf(...checks: PermissionCheck[]): PermissionCheck;
349
131
  /**
350
- * Combine multiple checks - ANY must pass (OR logic)
132
+ * Combine multiple checks ANY must pass (OR logic).
351
133
  *
352
134
  * @example
353
135
  * ```typescript
354
136
  * permissions: {
355
- * update: anyOf(
356
- * requireRoles(['admin']),
357
- * requireOwnership('createdBy')
358
- * ),
137
+ * update: anyOf(requireRoles(['admin']), requireOwnership('createdBy')),
359
138
  * }
360
139
  * ```
361
140
  */
362
141
  declare function anyOf(...checks: PermissionCheck[]): PermissionCheck;
363
142
  /**
364
- * Deny all access
143
+ * Invert a permission check. Grants when the wrapped check denies, denies
144
+ * when the wrapped check grants. Useful for "block if X" patterns —
145
+ * e.g. `not(requireRoles(['guest']))` to deny guest access.
146
+ *
147
+ * NOTE: filters and scope from the wrapped check are intentionally
148
+ * discarded — an inverted check has no row-level meaning.
365
149
  *
366
150
  * @example
367
151
  * ```typescript
368
152
  * permissions: {
369
- * delete: denyAll('Deletion not allowed'),
153
+ * internalApi: not(requireRoles(['external'])),
154
+ * adminUI: allOf(requireAuth(), not(requireRoles(['readonly']))),
370
155
  * }
371
156
  * ```
372
157
  */
158
+ declare function not(check: PermissionCheck, reason?: string): PermissionCheck;
159
+ /**
160
+ * Deny all access.
161
+ *
162
+ * @example
163
+ * ```typescript
164
+ * permissions: { delete: denyAll('Deletion not allowed') }
165
+ * ```
166
+ */
373
167
  declare function denyAll(reason?: string): PermissionCheck;
374
168
  /**
375
- * Dynamic permission based on context
169
+ * Dynamic permission based on a condition function.
376
170
  *
377
171
  * @example
378
172
  * ```typescript
@@ -382,54 +176,187 @@ declare function denyAll(reason?: string): PermissionCheck;
382
176
  * ```
383
177
  */
384
178
  declare function when<TDoc = Record<string, unknown>>(condition: (ctx: PermissionContext<TDoc>) => boolean | Promise<boolean>): PermissionCheck<TDoc>;
179
+ //#endregion
180
+ //#region src/permissions/dynamic.d.ts
181
+ interface DynamicPermissionMatrixConfig {
182
+ /**
183
+ * Resolve role → resource → actions map dynamically (DB / API / config service).
184
+ * Called at permission-check time (or cache miss when cache enabled).
185
+ */
186
+ resolveRolePermissions: (ctx: PermissionContext) => Record<string, Record<string, readonly string[]>> | Promise<Record<string, Record<string, readonly string[]>>>;
187
+ /**
188
+ * Optional cache store adapter. Use MemoryCacheStore for single-instance
189
+ * apps, RedisCacheStore for distributed setups.
190
+ */
191
+ cacheStore?: CacheStore<Record<string, Record<string, readonly string[]>>>;
192
+ /** Optional logger for cache/runtime failures (default: console). */
193
+ logger?: CacheLogger;
194
+ /**
195
+ * Convenience in-memory cache config. If `cacheStore` is not provided
196
+ * and `ttlSeconds > 0`, Arc creates an internal MemoryCacheStore.
197
+ */
198
+ cache?: {
199
+ /** Cache TTL in seconds */ttlSeconds: number; /** Optional custom cache key builder */
200
+ key?: (ctx: PermissionContext) => string | null | undefined; /** Hard entry cap for internal memory store (default: 1000) */
201
+ maxEntries?: number;
202
+ };
203
+ }
204
+ /** Minimal publish/subscribe interface for cross-node cache invalidation. */
205
+ interface PermissionEventBus {
206
+ publish: <T>(type: string, payload: T) => Promise<void>;
207
+ subscribe: (pattern: string, handler: (event: {
208
+ payload: unknown;
209
+ }) => void | Promise<void>) => Promise<(() => void) | undefined>;
210
+ }
211
+ interface ConnectEventsOptions {
212
+ /** Called on remote invalidation for app-specific cleanup (e.g. resolver cache). */
213
+ onRemoteInvalidation?: (orgId: string) => void | Promise<void>;
214
+ /** Custom event type (default: 'arc.permissions.invalidated'). */
215
+ eventType?: string;
216
+ }
217
+ interface DynamicPermissionMatrix {
218
+ can: (permissions: Record<string, readonly string[]>) => PermissionCheck;
219
+ canAction: (resource: string, action: string) => PermissionCheck;
220
+ requireRole: (...roles: string[]) => PermissionCheck;
221
+ requireMembership: () => PermissionCheck;
222
+ requireTeamMembership: () => PermissionCheck;
223
+ /** Invalidate cached permissions for a specific organization. */
224
+ invalidateByOrg: (orgId: string) => Promise<void>;
225
+ clearCache: () => Promise<void>;
226
+ /**
227
+ * Connect to an event system for cross-node cache invalidation.
228
+ *
229
+ * Late-binding: call after the event plugin is registered (e.g. in an
230
+ * `onReady` hook). Once connected, `invalidateByOrg()` auto-publishes an
231
+ * event, and incoming events from other nodes trigger local cache
232
+ * invalidation. Echo is suppressed via per-process nodeId matching.
233
+ */
234
+ connectEvents(events: PermissionEventBus, options?: ConnectEventsOptions): Promise<void>;
235
+ /** Disconnect from the event system. Safe to call even if never connected. */
236
+ disconnectEvents(): Promise<void>;
237
+ /** Whether events are currently connected. */
238
+ readonly eventsConnected: boolean;
239
+ }
385
240
  /**
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).
241
+ * Create a static role × resource × action permission system. Compile-time
242
+ * matrix use when role mappings are known at build time and don't change
243
+ * per-deployment.
390
244
  *
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.
245
+ * @example
246
+ * ```typescript
247
+ * const perms = createOrgPermissions({
248
+ * statements: {
249
+ * product: ['create', 'update', 'delete'],
250
+ * order: ['create', 'approve'],
251
+ * },
252
+ * roles: {
253
+ * owner: { product: ['create', 'update', 'delete'], order: ['create', 'approve'] },
254
+ * admin: { product: ['create', 'update'], order: ['create'] },
255
+ * member: { product: [], order: [] },
256
+ * },
257
+ * });
394
258
  *
395
- * Reads `request.scope` set by auth adapters or by upstream permission
396
- * checks via `PermissionResult.scope` (e.g. a custom `requireApiKey()`).
259
+ * defineResource({
260
+ * permissions: {
261
+ * create: perms.can({ product: ['create'] }),
262
+ * delete: perms.can({ product: ['delete'] }),
263
+ * }
264
+ * });
265
+ * ```
266
+ */
267
+ declare function createOrgPermissions(config: {
268
+ statements: Record<string, readonly string[]>;
269
+ roles: Record<string, Record<string, readonly string[]>>;
270
+ }): {
271
+ can: (permissions: Record<string, string[]>) => PermissionCheck;
272
+ requireRole: (...roles: string[]) => PermissionCheck;
273
+ requireMembership: () => PermissionCheck;
274
+ requireTeamMembership: () => PermissionCheck;
275
+ };
276
+ /**
277
+ * Create a dynamic role-based permission matrix. Use when role/action
278
+ * mappings are managed outside code (admin UI, DB-stored ACLs, remote
279
+ * policy service).
280
+ *
281
+ * Supports:
282
+ * - Org role union (any assigned org role can grant)
283
+ * - Global bypass roles
284
+ * - Wildcard resource/action (`*`)
285
+ * - Optional in-memory or distributed cache
286
+ * - Cross-node invalidation via the event bus
287
+ */
288
+ declare function createDynamicPermissionMatrix(config: DynamicPermissionMatrixConfig): DynamicPermissionMatrix;
289
+ //#endregion
290
+ //#region src/permissions/roleHierarchy.d.ts
291
+ /**
292
+ * Role Hierarchy — Composable RBAC Inheritance
293
+ *
294
+ * Expands roles based on an inheritance map. Apply at scope-building time
295
+ * so that requireRoles() works with the already-expanded list.
397
296
  *
398
297
  * @example
399
298
  * ```typescript
400
- * permissions: {
401
- * list: requireOrgMembership(),
402
- * get: requireOrgMembership(),
299
+ * import { createRoleHierarchy } from '@classytic/arc/permissions';
403
300
  *
404
- * // Composed with an OAuth-style scope check for API-key callers
405
- * create: allOf(requireOrgMembership(), requireServiceScope('jobs:write')),
406
- * }
301
+ * const hierarchy = createRoleHierarchy({
302
+ * superadmin: ['admin'],
303
+ * admin: ['branch_manager'],
304
+ * branch_manager: ['member'],
305
+ * });
306
+ *
307
+ * // When building scope:
308
+ * const expandedRoles = hierarchy.expand(user.roles);
309
+ * // ['superadmin'] → ['superadmin', 'admin', 'branch_manager', 'member']
310
+ *
311
+ * // Check inclusion:
312
+ * hierarchy.includes(['admin'], 'branch_manager'); // true (admin inherits branch_manager)
313
+ * hierarchy.includes(['member'], 'admin'); // false (child doesn't inherit parent)
407
314
  * ```
408
315
  */
409
- declare function requireOrgMembership<TDoc = Record<string, unknown>>(): PermissionCheck<TDoc>;
316
+ interface RoleHierarchy {
317
+ /** Expand roles to include all inherited (child) roles. Deduplicated. */
318
+ expand(roles: readonly string[]): string[];
319
+ /** Check if any of the user's roles (expanded) include the required role. */
320
+ includes(userRoles: readonly string[], requiredRole: string): boolean;
321
+ }
410
322
  /**
411
- * Require specific org-level roles.
412
- * Reads `request.scope.orgRoles` (set by auth adapters).
413
- * Elevated scope always passes (platform admin bypass).
323
+ * Create a role hierarchy from a parent → children map.
414
324
  *
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:
325
+ * Each key is a parent role, each value is the array of roles it inherits.
326
+ * Inheritance is transitive: if A → B and B → C, then A expands to [A, B, C].
327
+ * Circular references are handled safely (visited set).
328
+ */
329
+ declare function createRoleHierarchy(map: Record<string, readonly string[]>): RoleHierarchy;
330
+ //#endregion
331
+ //#region src/permissions/scope.d.ts
332
+ /**
333
+ * Require an org-bound caller. Grants for `member`, `service`, and
334
+ * `elevated` scopes (anything with org context). Denies `public` and
335
+ * `authenticated` (no org context).
418
336
  *
337
+ * Canonical "is the caller acting inside an org" check. Usual partner for
338
+ * `multiTenantPreset` — if a route filters by tenant, you almost always
339
+ * want this gate too.
340
+ *
341
+ * @example
419
342
  * ```typescript
420
343
  * permissions: {
421
- * create: anyOf(
422
- * requireOrgRole('admin'), // human path
423
- * requireServiceScope('jobs:write'), // machine path
424
- * ),
344
+ * list: requireOrgMembership(),
345
+ * create: allOf(requireOrgMembership(), requireServiceScope('jobs:write')),
425
346
  * }
426
347
  * ```
348
+ */
349
+ declare function requireOrgMembership<TDoc = Record<string, unknown>>(): PermissionCheck<TDoc>;
350
+ /**
351
+ * Require specific org-level roles. Reads `request.scope.orgRoles`.
352
+ * Elevated scope always passes (platform admin bypass).
427
353
  *
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)
354
+ * **Service scopes (API keys) always fail this check** services don't
355
+ * carry user-style org roles, only OAuth-style `scopes` strings. For
356
+ * routes that should accept BOTH human admins AND API keys, compose with
357
+ * `anyOf(requireOrgRole(...), requireServiceScope(...))`. The implicit
358
+ * "API key bypasses role check" path is intentionally NOT supported
359
+ * it's the kind of footgun that ships data breaches.
433
360
  *
434
361
  * @example
435
362
  * ```typescript
@@ -442,89 +369,49 @@ declare function requireOrgMembership<TDoc = Record<string, unknown>>(): Permiss
442
369
  declare function requireOrgRole<TDoc = Record<string, unknown>>(...args: string[] | [readonly string[]]): PermissionCheck<TDoc>;
443
370
  /**
444
371
  * 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:*'`.
372
+ * Reads `request.scope.scopes` (only present when `scope.kind === 'service'`).
450
373
  *
451
374
  * **Pass behavior:**
452
- * - `service` scope where `scopes` contains ANY of the required strings → grant
453
- * - `elevated` scope (platform admin) → grant
375
+ * - `service` scope where `scopes` contains ANY required string → grant
376
+ * - `elevated` scope → grant
454
377
  * - Anything else → deny with a clear reason
455
378
  *
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)
379
+ * Does **not** grant for `member` scopes — humans go through `requireOrgRole`.
380
+ * For routes that should accept both, compose with `anyOf`.
469
381
  *
470
382
  * @example
471
383
  * ```typescript
472
- * // Variadic
473
384
  * requireServiceScope('jobs:write')
474
385
  * requireServiceScope('jobs:read', 'jobs:write')
475
- *
476
- * // Array
477
386
  * requireServiceScope(['jobs:read', 'jobs:write'])
478
387
  *
479
- * // Composed with org membership for org-scoped API keys
480
388
  * permissions: {
481
389
  * list: allOf(requireOrgMembership(), requireServiceScope('jobs:read')),
482
- * create: allOf(requireOrgMembership(), requireServiceScope('jobs:write')),
483
390
  * }
484
391
  * ```
485
392
  */
486
393
  declare function requireServiceScope<TDoc = Record<string, unknown>>(...args: string[] | [readonly string[]]): PermissionCheck<TDoc>;
487
394
  /**
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.
395
+ * Require app-defined scope context dimensions (branch, project, region,
396
+ * workspace, ) on the request. Arc takes no position on what dimensions
397
+ * you use — your auth function populates `scope.context`, your routes
398
+ * gate on it.
494
399
  *
495
400
  * **Three call shapes:**
496
- *
497
401
  * ```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
402
+ * requireScopeContext('branchId') // presence only
403
+ * requireScopeContext('branchId', 'eng-paris') // value match
404
+ * requireScopeContext({ branchId: 'eng-paris', projectId: 'p-1' }) // multi-key (AND)
405
+ * requireScopeContext({ region: 'eu', branchId: undefined }) // mixed
507
406
  * ```
508
407
  *
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.
408
+ * **Pass behavior:** all required keys present (and matching when
409
+ * specified) grant. `elevated` scope grants unconditionally.
517
410
  *
518
411
  * @example
519
412
  * ```typescript
520
413
  * permissions: {
521
- * // Branch-scoped CRUD — caller must have branchId in their scope context
522
414
  * 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
415
  * euOnly: requireScopeContext('region', 'eu'),
529
416
  * }
530
417
  * ```
@@ -534,34 +421,20 @@ declare function requireScopeContext<TDoc = Record<string, unknown>>(keyOrMap: s
534
421
  * Require that the caller's scope grants access to a target organization
535
422
  * — either the current org or one of its ancestors (`scope.ancestorOrgIds`).
536
423
  *
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.
424
+ * For parent-child organization hierarchies (holding subsidiary branch,
425
+ * MSP → tenant, white-label parent → child). Auth function pre-loads
426
+ * `ancestorOrgIds`; routes opt in explicitly. No automatic inheritance.
543
427
  *
544
428
  * **Two call shapes:**
545
- *
546
429
  * ```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)
430
+ * requireOrgInScope('acme-holding') // static
431
+ * requireOrgInScope((ctx) => ctx.request.params.orgId) // dynamic
553
432
  * ```
554
433
  *
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
434
+ * `elevated` scope grants unconditionally (cross-org bypass).
561
435
  *
562
436
  * @example
563
437
  * ```typescript
564
- * // /orgs/:orgId/jobs — caller can act on any org in their hierarchy chain
565
438
  * permissions: {
566
439
  * list: requireOrgInScope((ctx) => ctx.request.params.orgId),
567
440
  * create: allOf(
@@ -573,59 +446,9 @@ declare function requireScopeContext<TDoc = Record<string, unknown>>(keyOrMap: s
573
446
  */
574
447
  declare function requireOrgInScope<TDoc = Record<string, unknown>>(target: string | ((ctx: PermissionContext<TDoc>) => string | undefined)): PermissionCheck<TDoc>;
575
448
  /**
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.
449
+ * Require membership in the active team. User must be authenticated, a
450
+ * member of the active org, AND have an active team. Better Auth teams
451
+ * are flat member groups (no team-level roles). Reads `request.scope.teamId`.
629
452
  *
630
453
  * @example
631
454
  * ```typescript
@@ -636,5 +459,57 @@ declare function createDynamicPermissionMatrix(config: DynamicPermissionMatrixCo
636
459
  * ```
637
460
  */
638
461
  declare function requireTeamMembership<TDoc = Record<string, unknown>>(): PermissionCheck<TDoc>;
462
+ declare namespace presets_d_exports {
463
+ export { adminOnly, authenticated, fullPublic, ownerWithAdminBypass, publicRead, publicReadAdminWrite, readOnly };
464
+ }
465
+ /**
466
+ * ResourcePermissions shape — matches the type in types/index.ts
467
+ */
468
+ interface ResourcePermissions<TDoc = any> {
469
+ list?: PermissionCheck<TDoc>;
470
+ get?: PermissionCheck<TDoc>;
471
+ create?: PermissionCheck<TDoc>;
472
+ update?: PermissionCheck<TDoc>;
473
+ delete?: PermissionCheck<TDoc>;
474
+ }
475
+ type PermissionOverrides<TDoc = any> = Partial<ResourcePermissions<TDoc>>;
476
+ /**
477
+ * Public read, authenticated write.
478
+ * list + get = allowPublic(), create + update + delete = requireAuth()
479
+ */
480
+ declare function publicRead<TDoc = any>(overrides?: PermissionOverrides<TDoc>): ResourcePermissions<TDoc>;
481
+ /**
482
+ * Public read, admin write.
483
+ * list + get = allowPublic(), create + update + delete = requireRoles(['admin'])
484
+ */
485
+ declare function publicReadAdminWrite<TDoc = any>(roles?: readonly string[], overrides?: PermissionOverrides<TDoc>): ResourcePermissions<TDoc>;
486
+ /**
487
+ * All operations require authentication.
488
+ */
489
+ declare function authenticated<TDoc = any>(overrides?: PermissionOverrides<TDoc>): ResourcePermissions<TDoc>;
490
+ /**
491
+ * All operations require specific roles.
492
+ * @param roles - Required roles (user needs at least one). Default: ['admin']
493
+ */
494
+ declare function adminOnly<TDoc = any>(roles?: readonly string[], overrides?: PermissionOverrides<TDoc>): ResourcePermissions<TDoc>;
495
+ /**
496
+ * Owner-scoped with admin bypass.
497
+ * list = auth (scoped to owner), get = auth, create = auth,
498
+ * update + delete = ownership check with admin bypass.
499
+ *
500
+ * @param ownerField - Field containing owner ID (default: 'userId')
501
+ * @param bypassRoles - Roles that bypass ownership check (default: ['admin'])
502
+ */
503
+ declare function ownerWithAdminBypass<TDoc = any>(ownerField?: Extract<keyof TDoc, string> | string, bypassRoles?: readonly string[], overrides?: PermissionOverrides<TDoc>): ResourcePermissions<TDoc>;
504
+ /**
505
+ * Full public access — no auth required for any operation.
506
+ * Use sparingly (dev/testing, truly public APIs).
507
+ */
508
+ declare function fullPublic<TDoc = any>(overrides?: PermissionOverrides<TDoc>): ResourcePermissions<TDoc>;
509
+ /**
510
+ * Read-only: list + get authenticated, write operations denied.
511
+ * Useful for computed/derived resources.
512
+ */
513
+ declare function readOnly<TDoc = any>(overrides?: PermissionOverrides<TDoc>): ResourcePermissions<TDoc>;
639
514
  //#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 };
515
+ export { requireRoles as A, allOf as C, not as D, denyAll as E, when as M, applyPermissionResult as N, requireAuth as O, normalizePermissionResult as P, createOrgPermissions as S, anyOf as T, ConnectEventsOptions as _, presets_d_exports as a, PermissionEventBus as b, readOnly as c, requireOrgRole as d, requireScopeContext as f, createRoleHierarchy as g, RoleHierarchy as h, ownerWithAdminBypass as i, roles as j, requireOwnership as k, requireOrgInScope as l, requireTeamMembership as m, authenticated as n, publicRead as o, requireServiceScope as p, fullPublic as r, publicReadAdminWrite as s, adminOnly as t, requireOrgMembership as u, DynamicPermissionMatrix as v, allowPublic as w, createDynamicPermissionMatrix as x, DynamicPermissionMatrixConfig as y };