@classytic/arc 2.6.2 → 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 +95 -1
  2. package/dist/{BaseController-AbbRx3e0.mjs → BaseController-CpMfCXdn.mjs} +214 -16
  3. package/dist/adapters/index.d.mts +2 -2
  4. package/dist/adapters/index.mjs +1 -1
  5. package/dist/{adapters-CTn28N4y.mjs → adapters-BxGgSHjj.mjs} +7 -13
  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-Ckxg6HrZ.mjs → defineResource-DZzyl4a4.mjs} +73 -56
  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-B4uZm82R.d.mts → index-KXM8_JmQ.d.mts} +47 -4
  51. package/dist/{index-DrCqa3Jq.d.mts → index-StgFaQKD.d.mts} +3 -3
  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-CrN45qz1.d.mts → interface-Dwzqt4mn.d.mts} +204 -18
  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-DH3c3e-T.mjs → resourceToTools-CkVSSzKg.mjs} +313 -33
  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-DurlBP2N.d.mts → types-ClmkMDK1.d.mts} +1 -1
  100. package/dist/{types-C1Z28coa.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 +34 -22
  105. package/skills/arc/SKILL.md +278 -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
@@ -1,147 +0,0 @@
1
- import { FastifyPluginAsync, FastifyRequest } from "fastify";
2
-
3
- //#region src/scope/types.d.ts
4
- /**
5
- * Request Scope — The One Standard
6
- *
7
- * Discriminated union representing the access context of every request.
8
- * Replaces scattered orgScope/orgRoles/organizationId/bypassRoles.
9
- *
10
- * Set once by auth adapters, read everywhere by permissions/presets/guards.
11
- *
12
- * @example
13
- * ```typescript
14
- * // In a permission check
15
- * const scope = request.scope;
16
- * if (isElevated(scope)) return true;
17
- * if (isMember(scope) && scope.orgRoles.includes('admin')) return true;
18
- *
19
- * // Get user identity from scope
20
- * const userId = getUserId(scope);
21
- * const globalRoles = getUserRoles(scope);
22
- * ```
23
- */
24
- /**
25
- * Request scope — 4 kinds, 4 states, no ambiguity.
26
- *
27
- * | Kind | Meaning |
28
- * |---------------|-----------------------------------|
29
- * | public | No authentication |
30
- * | authenticated | Logged in, no org context |
31
- * | member | In an org with specific roles |
32
- * | elevated | Platform admin, explicit elevation |
33
- *
34
- * `userId` and `userRoles` are available on all authenticated variants.
35
- * `orgRoles` are org-level roles (from membership); `userRoles` are global roles (from user document).
36
- */
37
- type RequestScope = {
38
- kind: "public";
39
- } | {
40
- kind: "authenticated";
41
- userId?: string;
42
- userRoles?: string[];
43
- } | {
44
- kind: "member";
45
- userId?: string;
46
- userRoles: string[];
47
- organizationId: string;
48
- orgRoles: string[];
49
- teamId?: string;
50
- } | {
51
- kind: "elevated";
52
- userId?: string;
53
- organizationId?: string;
54
- elevatedBy: string;
55
- };
56
- /** Check if scope is `member` kind */
57
- declare function isMember(scope: RequestScope): scope is Extract<RequestScope, {
58
- kind: "member";
59
- }>;
60
- /** Check if scope is `elevated` kind */
61
- declare function isElevated(scope: RequestScope): scope is Extract<RequestScope, {
62
- kind: "elevated";
63
- }>;
64
- /** Check if scope has org access (member OR elevated) */
65
- declare function hasOrgAccess(scope: RequestScope): boolean;
66
- /** Check if request is authenticated (any kind except public) */
67
- declare function isAuthenticated(scope: RequestScope): boolean;
68
- /** Get organizationId from scope (if present) */
69
- declare function getOrgId(scope: RequestScope): string | undefined;
70
- /** Get org roles from scope (empty array if not a member) */
71
- declare function getOrgRoles(scope: RequestScope): string[];
72
- /** Get team ID from scope (only available on member kind) */
73
- declare function getTeamId(scope: RequestScope): string | undefined;
74
- /**
75
- * Get userId from scope (available on authenticated, member, elevated).
76
- *
77
- * @example
78
- * ```typescript
79
- * import { getUserId } from '@classytic/arc/scope';
80
- * const userId = getUserId(request.scope);
81
- * ```
82
- */
83
- declare function getUserId(scope: RequestScope): string | undefined;
84
- /**
85
- * Get global user roles from scope (available on authenticated and member).
86
- * These are user-level roles (e.g. superadmin, finance-admin) distinct from
87
- * org-level roles (scope.orgRoles).
88
- *
89
- * @example
90
- * ```typescript
91
- * import { getUserRoles } from '@classytic/arc/scope';
92
- * const globalRoles = getUserRoles(request.scope);
93
- * ```
94
- */
95
- declare function getUserRoles(scope: RequestScope): string[];
96
- /**
97
- * Org context — canonical extraction from a Fastify request.
98
- *
99
- * Works regardless of auth type (JWT, Better Auth, custom) by reading
100
- * `request.scope` and `request.user`. Eliminates the need for each resource
101
- * to re-invent org extraction from headers/user/scope.
102
- *
103
- * @example
104
- * ```typescript
105
- * import { getOrgContext } from '@classytic/arc/scope';
106
- *
107
- * handler: async (request, reply) => {
108
- * const { userId, organizationId, roles, orgRoles } = getOrgContext(request);
109
- * }
110
- * ```
111
- */
112
- declare function getOrgContext(request: {
113
- scope?: RequestScope;
114
- user?: Record<string, unknown> | null;
115
- headers?: Record<string, string | string[] | undefined>;
116
- }): {
117
- userId: string | undefined;
118
- organizationId: string | undefined;
119
- roles: string[];
120
- orgRoles: string[];
121
- };
122
- /** Default public scope — used as initial decoration value */
123
- declare const PUBLIC_SCOPE: Readonly<RequestScope>;
124
- /** Default authenticated scope — used when user is logged in but no org */
125
- declare const AUTHENTICATED_SCOPE: Readonly<RequestScope>;
126
- //#endregion
127
- //#region src/scope/elevation.d.ts
128
- interface ElevationOptions {
129
- /** Roles that can use elevation (default: ['superadmin']) */
130
- platformRoles?: string[];
131
- /** Header name for scope declaration (default: 'x-arc-scope') */
132
- scopeHeader?: string;
133
- /** Header name for target organization (default: 'x-organization-id') */
134
- orgHeader?: string;
135
- /** Called when elevation happens — use for audit logging */
136
- onElevation?: (event: ElevationEvent) => void | Promise<void>;
137
- }
138
- interface ElevationEvent {
139
- userId: string;
140
- organizationId?: string;
141
- request: FastifyRequest;
142
- timestamp: Date;
143
- }
144
- declare const elevationPlugin: FastifyPluginAsync<ElevationOptions>;
145
- declare const _default: FastifyPluginAsync<ElevationOptions>;
146
- //#endregion
147
- export { isMember as _, AUTHENTICATED_SCOPE as a, getOrgContext as c, getTeamId as d, getUserId as f, isElevated as g, isAuthenticated as h, elevationPlugin as i, getOrgId as l, hasOrgAccess as m, ElevationOptions as n, PUBLIC_SCOPE as o, getUserRoles as p, _default as r, RequestScope as s, ElevationEvent as t, getOrgRoles as u };
@@ -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 };