@classytic/arc 2.8.5 → 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 (155) hide show
  1. package/README.md +50 -38
  2. package/dist/{BaseController-DAGGc5Xn.mjs → BaseController-CbKKIflT.mjs} +193 -143
  3. package/dist/EventTransport-CUw5NNWe.d.mts +293 -0
  4. package/dist/{ResourceRegistry-C6uXlWe3.mjs → ResourceRegistry-BPd6NQDm.mjs} +1 -1
  5. package/dist/adapters/index.d.mts +3 -3
  6. package/dist/adapters/index.mjs +2 -2
  7. package/dist/{adapters-BBqAVvPK.mjs → adapters-BXY4i-hw.mjs} +210 -41
  8. package/dist/audit/index.d.mts +135 -11
  9. package/dist/audit/index.mjs +107 -20
  10. package/dist/auth/index.d.mts +17 -9
  11. package/dist/auth/index.mjs +14 -7
  12. package/dist/auth/redis-session.d.mts +1 -1
  13. package/dist/{betterAuthOpenApi-BuUcUEJq.mjs → betterAuthOpenApi-BBRVhjQN.mjs} +1 -1
  14. package/dist/cache/index.d.mts +17 -15
  15. package/dist/cache/index.mjs +15 -14
  16. package/dist/{caching-IMuYVjTL.mjs → caching-CBpK_SCM.mjs} +8 -3
  17. package/dist/cli/commands/describe.mjs +1 -1
  18. package/dist/cli/commands/docs.mjs +2 -2
  19. package/dist/cli/commands/generate.mjs +1 -1
  20. package/dist/cli/commands/init.mjs +1 -1
  21. package/dist/cli/commands/introspect.mjs +1 -1
  22. package/dist/core/index.d.mts +3 -3
  23. package/dist/core/index.mjs +4 -6
  24. package/dist/{defineResource-tcgySDo1.mjs → core-CcR01lup.mjs} +58 -61
  25. package/dist/{createActionRouter-BORM8f17.mjs → createActionRouter-Bp_5c_2b.mjs} +3 -3
  26. package/dist/{createApp-B1EY8zxa.mjs → createApp-BuvPma24.mjs} +15 -14
  27. package/dist/docs/index.d.mts +2 -2
  28. package/dist/docs/index.mjs +2 -2
  29. package/dist/{elevation-DtFxrG0s.mjs → elevation-C7hgL_aI.mjs} +22 -8
  30. package/dist/{errorHandler-f869_8PQ.mjs → errorHandler-Bb49BvPD.mjs} +59 -7
  31. package/dist/{errorHandler-Bah5JhBd.d.mts → errorHandler-DRQ3EqfL.d.mts} +37 -2
  32. package/dist/{eventPlugin-D9DKB2zM.d.mts → eventPlugin-CxWgpd6K.d.mts} +14 -2
  33. package/dist/{eventPlugin-CDjVTM82.mjs → eventPlugin-DCUjuiQT.mjs} +83 -5
  34. package/dist/events/index.d.mts +150 -36
  35. package/dist/events/index.mjs +355 -101
  36. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  37. package/dist/events/transports/redis.d.mts +1 -1
  38. package/dist/factory/index.d.mts +1 -1
  39. package/dist/factory/index.mjs +2 -2
  40. package/dist/{types-DZi1aYhm.d.mts → fields-Lo1VUDpt.d.mts} +121 -1
  41. package/dist/{fields-ipsbIRPK.mjs → fields-bxkeltzz.mjs} +18 -5
  42. package/dist/{filesUpload-C7r7HIeA.mjs → filesUpload-t21LS-py.mjs} +65 -7
  43. package/dist/hooks/index.d.mts +1 -1
  44. package/dist/hooks/index.mjs +1 -1
  45. package/dist/idempotency/index.d.mts +32 -5
  46. package/dist/idempotency/index.mjs +119 -12
  47. package/dist/idempotency/redis.d.mts +1 -1
  48. package/dist/{index-DtDzOBn8.d.mts → index-8qw4y6ff.d.mts} +4 -135
  49. package/dist/{index-BLXBmWud.d.mts → index-ChIw3776.d.mts} +283 -408
  50. package/dist/{interface-CMRutPfe.d.mts → index-Cl0uoKd5.d.mts} +1758 -2506
  51. package/dist/{index-C1meYuDn.d.mts → index-DStwgFUK.d.mts} +81 -7
  52. package/dist/index.d.mts +7 -8
  53. package/dist/index.mjs +11 -12
  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 +26 -8
  58. package/dist/integrations/mcp/index.mjs +96 -17
  59. package/dist/integrations/mcp/testing.d.mts +1 -1
  60. package/dist/integrations/mcp/testing.mjs +1 -1
  61. package/dist/integrations/webhooks.d.mts +5 -0
  62. package/dist/integrations/webhooks.mjs +6 -0
  63. package/dist/interface-D218ikEo.d.mts +77 -0
  64. package/dist/{memory-Cp7_cAko.mjs → memory-B5Amv9A1.mjs} +23 -8
  65. package/dist/{openapi-CbKUJY_m.mjs → openapi-B5F8AddX.mjs} +3 -3
  66. package/dist/org/index.d.mts +2 -2
  67. package/dist/permissions/index.d.mts +3 -4
  68. package/dist/permissions/index.mjs +5 -5
  69. package/dist/{permissions-CH4cNwJi.mjs → permissions-Dk6mshja.mjs} +315 -397
  70. package/dist/plugins/index.d.mts +7 -7
  71. package/dist/plugins/index.mjs +14 -16
  72. package/dist/plugins/response-cache.mjs +2 -2
  73. package/dist/plugins/tracing-entry.d.mts +1 -1
  74. package/dist/plugins/tracing-entry.mjs +1 -1
  75. package/dist/presets/filesUpload.d.mts +27 -5
  76. package/dist/presets/filesUpload.mjs +1 -1
  77. package/dist/presets/index.d.mts +3 -2
  78. package/dist/presets/index.mjs +4 -3
  79. package/dist/presets/multiTenant.d.mts +1 -1
  80. package/dist/presets/multiTenant.mjs +2 -2
  81. package/dist/presets/search.d.mts +178 -0
  82. package/dist/presets/search.mjs +150 -0
  83. package/dist/{presets-C2xgzW6x.mjs → presets-fLJVXdVn.mjs} +1 -1
  84. package/dist/{queryCachePlugin-BJJGBTlu.d.mts → queryCachePlugin-BKbWjgDG.d.mts} +1 -1
  85. package/dist/{queryCachePlugin-BH-fidlv.mjs → queryCachePlugin-DQCEfJis.mjs} +9 -9
  86. package/dist/{queryParser-CgCtsjti.mjs → queryParser-DBqBB6AC.mjs} +1 -1
  87. package/dist/{redis-BM00zaPB.d.mts → redis-DqyeggCa.d.mts} +1 -1
  88. package/dist/{redis-stream-CrsfUmPt.d.mts → redis-stream-CakIQmwR.d.mts} +1 -1
  89. package/dist/registry/index.d.mts +1 -1
  90. package/dist/registry/index.mjs +2 -2
  91. package/dist/{resourceToTools-8s-EsCCe.mjs → resourceToTools-BElv3xPT.mjs} +65 -48
  92. package/dist/{schemaConverter-Y7nCYaLJ.mjs → schemaConverter-BxFDdtXu.mjs} +1 -1
  93. package/dist/scope/index.d.mts +1 -1
  94. package/dist/scope/index.mjs +2 -2
  95. package/dist/{sse-Ad7ypl9e.mjs → sse-yBCgOLGu.mjs} +1 -1
  96. package/dist/store-helpers-ZCSMJJAX.mjs +57 -0
  97. package/dist/testing/index.d.mts +9 -17
  98. package/dist/testing/index.mjs +27 -83
  99. package/dist/testing/storageContract.d.mts +1 -1
  100. package/dist/types/index.d.mts +4 -4
  101. package/dist/types/index.mjs +1 -31
  102. package/dist/types/storage.d.mts +1 -1
  103. package/dist/{types-BsbNMEDR.d.mts → types-Btdda02s.d.mts} +1 -1
  104. package/dist/{types-Ch9pTQbf.d.mts → types-Co8k3NyS.d.mts} +11 -9
  105. package/dist/types-Csi3FLfq.mjs +27 -0
  106. package/dist/utils/index.d.mts +208 -4
  107. package/dist/utils/index.mjs +5 -6
  108. package/dist/{utils-yYT3HDXt.mjs → utils-B2fNOD_i.mjs} +285 -2
  109. package/dist/{versioning-CDugduqI.mjs → versioning-C2U_bLY0.mjs} +3 -5
  110. package/package.json +20 -26
  111. package/skills/arc/SKILL.md +97 -23
  112. package/skills/arc/references/auth.md +94 -0
  113. package/skills/arc/references/events.md +200 -12
  114. package/skills/arc/references/mcp.md +4 -17
  115. package/skills/arc/references/multi-tenancy.md +43 -0
  116. package/skills/arc/references/production.md +34 -60
  117. package/dist/EventTransport-BXja8NOc.d.mts +0 -135
  118. package/dist/audit/mongodb.d.mts +0 -2
  119. package/dist/audit/mongodb.mjs +0 -2
  120. package/dist/circuitBreaker-cmi5XDv5.mjs +0 -284
  121. package/dist/circuitBreaker-dTtG-UyS.d.mts +0 -206
  122. package/dist/core-F0QoWBt2.mjs +0 -34
  123. package/dist/dynamic/index.d.mts +0 -93
  124. package/dist/dynamic/index.mjs +0 -122
  125. package/dist/fields-DpZQa_Q3.d.mts +0 -109
  126. package/dist/idempotency/mongodb.d.mts +0 -2
  127. package/dist/idempotency/mongodb.mjs +0 -123
  128. package/dist/interface-4y979v99.d.mts +0 -54
  129. package/dist/mongodb-BsP-WbhN.d.mts +0 -127
  130. package/dist/mongodb-CTcp0hQZ.d.mts +0 -80
  131. package/dist/mongodb-Utc5k_-0.mjs +0 -90
  132. package/dist/policies/index.d.mts +0 -432
  133. package/dist/policies/index.mjs +0 -318
  134. package/dist/rpc/index.d.mts +0 -90
  135. package/dist/rpc/index.mjs +0 -248
  136. /package/dist/{HookSystem-HprTmvVY.mjs → HookSystem-BNYKnrXF.mjs} +0 -0
  137. /package/dist/{applyPermissionResult-D6GPMsvh.mjs → applyPermissionResult-QhV1Pa-g.mjs} +0 -0
  138. /package/dist/{constants-Cxde4rpC.mjs → constants-BhY1OHoH.mjs} +0 -0
  139. /package/dist/{elevation-B6S5csVA.d.mts → elevation-C5SwtkAn.d.mts} +0 -0
  140. /package/dist/{errors-Ck2h67pm.d.mts → errors-CCSsMpXE.d.mts} +0 -0
  141. /package/dist/{errors-BF2bIOIS.mjs → errors-D5c-5BJL.mjs} +0 -0
  142. /package/dist/{externalPaths-BnkYrNzp.d.mts → externalPaths-BQ8QijNH.d.mts} +0 -0
  143. /package/dist/{interface-DfLGcus7.d.mts → interface-CSbZdv_3.d.mts} +0 -0
  144. /package/dist/{loadResources-PWd0OCpV.mjs → loadResources-BAzJItAJ.mjs} +0 -0
  145. /package/dist/{logger-D1YrIImS.mjs → logger-DLg8-Ueg.mjs} +0 -0
  146. /package/dist/{metrics-B-PU4-Yu.mjs → metrics-DuhiSEZI.mjs} +0 -0
  147. /package/dist/{pluralize-CWP6MB39.mjs → pluralize-A0tWEl1K.mjs} +0 -0
  148. /package/dist/{registry-BiTKT1Dg.mjs → registry-B3lRFBWo.mjs} +0 -0
  149. /package/dist/{replyHelpers-CxkYGT81.mjs → replyHelpers-CXtJDAZ0.mjs} +0 -0
  150. /package/dist/{requestContext-DYvHl113.mjs → requestContext-xHIKedG6.mjs} +0 -0
  151. /package/dist/{sessionManager-DDCmiNIo.d.mts → sessionManager-BkzVU8h2.d.mts} +0 -0
  152. /package/dist/{storage-Dfzt4VTl.d.mts → storage-CVk_SEn2.d.mts} +0 -0
  153. /package/dist/{tracing-DdN2-wHJ.d.mts → tracing-65B51Dw3.d.mts} +0 -0
  154. /package/dist/{typeGuards-CcFZXgU7.mjs → typeGuards-Cj5Rgvlg.mjs} +0 -0
  155. /package/dist/{types-ZUu_h0jp.mjs → types-DV9WDfeg.mjs} +0 -0
@@ -1,90 +0,0 @@
1
- //#region src/audit/stores/mongodb.ts
2
- var MongoAuditStore = class {
3
- name = "mongodb";
4
- collection;
5
- initialized = false;
6
- ttlDays;
7
- constructor(options) {
8
- const collectionName = options.collection ?? "audit_logs";
9
- this.collection = options.connection.collection(collectionName);
10
- this.ttlDays = options.ttlDays ?? 90;
11
- }
12
- async ensureIndexes() {
13
- if (this.initialized) return;
14
- try {
15
- await this.collection.createIndex({
16
- resource: 1,
17
- documentId: 1,
18
- timestamp: -1
19
- });
20
- await this.collection.createIndex({
21
- userId: 1,
22
- timestamp: -1
23
- });
24
- await this.collection.createIndex({
25
- organizationId: 1,
26
- timestamp: -1
27
- });
28
- if (this.ttlDays > 0) await this.collection.createIndex({ timestamp: 1 }, { expireAfterSeconds: this.ttlDays * 24 * 60 * 60 });
29
- this.initialized = true;
30
- } catch {
31
- this.initialized = true;
32
- }
33
- }
34
- async log(entry) {
35
- await this.ensureIndexes();
36
- await this.collection.insertOne({
37
- _id: entry.id,
38
- resource: entry.resource,
39
- documentId: entry.documentId,
40
- action: entry.action,
41
- userId: entry.userId,
42
- organizationId: entry.organizationId,
43
- before: entry.before,
44
- after: entry.after,
45
- changes: entry.changes,
46
- requestId: entry.requestId,
47
- ipAddress: entry.ipAddress,
48
- userAgent: entry.userAgent,
49
- metadata: entry.metadata,
50
- timestamp: entry.timestamp
51
- });
52
- }
53
- async query(options = {}) {
54
- await this.ensureIndexes();
55
- const query = {};
56
- if (options.resource) query.resource = options.resource;
57
- if (options.documentId) query.documentId = options.documentId;
58
- if (options.userId) query.userId = options.userId;
59
- if (options.organizationId) query.organizationId = options.organizationId;
60
- if (options.action) {
61
- const actions = Array.isArray(options.action) ? options.action : [options.action];
62
- query.action = actions.length === 1 ? actions[0] : { $in: actions };
63
- }
64
- if (options.from || options.to) {
65
- query.timestamp = {};
66
- if (options.from) query.timestamp.$gte = options.from;
67
- if (options.to) query.timestamp.$lte = options.to;
68
- }
69
- const offset = options.offset ?? 0;
70
- const limit = options.limit ?? 100;
71
- return (await this.collection.find(query).sort({ timestamp: -1 }).skip(offset).limit(limit).toArray()).map((doc) => ({
72
- id: String(doc._id),
73
- resource: doc.resource,
74
- documentId: doc.documentId,
75
- action: doc.action,
76
- userId: doc.userId,
77
- organizationId: doc.organizationId,
78
- before: doc.before,
79
- after: doc.after,
80
- changes: doc.changes,
81
- requestId: doc.requestId,
82
- ipAddress: doc.ipAddress,
83
- userAgent: doc.userAgent,
84
- metadata: doc.metadata,
85
- timestamp: doc.timestamp
86
- }));
87
- }
88
- };
89
- //#endregion
90
- export { MongoAuditStore as t };
@@ -1,432 +0,0 @@
1
- import { t as PermissionCheck } from "../types-DZi1aYhm.mjs";
2
- import { FastifyReply, FastifyRequest } from "fastify";
3
-
4
- //#region src/policies/PolicyInterface.d.ts
5
- /**
6
- * Policy result returned by can() method
7
- */
8
- interface PolicyResult {
9
- /**
10
- * Whether the operation is allowed
11
- */
12
- allowed: boolean;
13
- /**
14
- * Human-readable reason if denied
15
- * Returned in 403 error responses
16
- */
17
- reason?: string;
18
- /**
19
- * Query filters to apply (for list operations)
20
- *
21
- * @example
22
- * ```typescript
23
- * // Multi-tenant filter
24
- * { organizationId: user.organizationId }
25
- *
26
- * // Ownership filter
27
- * { userId: user.id }
28
- *
29
- * // Complex filter
30
- * { $or: [{ public: true }, { createdBy: user.id }] }
31
- * ```
32
- */
33
- filters?: Record<string, any>;
34
- /**
35
- * Fields to include/exclude in response
36
- *
37
- * @example
38
- * ```typescript
39
- * // Hide sensitive fields from non-admins
40
- * { exclude: ['password', 'ssn', 'salary'] }
41
- *
42
- * // Only show specific fields
43
- * { include: ['name', 'email', 'role'] }
44
- * ```
45
- */
46
- fieldMask?: {
47
- include?: string[];
48
- exclude?: string[];
49
- };
50
- /**
51
- * Additional context for downstream middleware
52
- *
53
- * @example
54
- * ```typescript
55
- * {
56
- * auditLog: { action: 'read', resource: 'patient', userId: user.id },
57
- * rateLimit: { tier: user.subscriptionTier },
58
- * }
59
- * ```
60
- */
61
- metadata?: Record<string, any>;
62
- }
63
- /**
64
- * Policy context provided to can() method
65
- */
66
- interface PolicyContext {
67
- /**
68
- * The document being accessed (for update/delete/get)
69
- * Populated by fetchDocument middleware
70
- */
71
- document?: any;
72
- /**
73
- * Request body (for create/update)
74
- */
75
- body?: any;
76
- /**
77
- * Request params (e.g., :id from route)
78
- */
79
- params?: any;
80
- /**
81
- * Request query parameters
82
- */
83
- query?: any;
84
- /**
85
- * Additional app-specific context
86
- * Can include anything your policy needs to make decisions
87
- */
88
- [key: string]: any;
89
- }
90
- /**
91
- * Policy Engine Interface
92
- *
93
- * Implement this interface to create your own authorization strategy.
94
- *
95
- * Arc provides the interface, apps provide the implementation.
96
- * This follows the same pattern as:
97
- * - Database drivers (interface: query(), implementation: PostgreSQL, MySQL)
98
- * - Storage providers (interface: upload(), implementation: S3, Azure)
99
- * - Authentication strategies (interface: verify(), implementation: JWT, OAuth)
100
- *
101
- * @example E-commerce RBAC + Ownership
102
- * ```typescript
103
- * class EcommercePolicyEngine implements PolicyEngine {
104
- * constructor(private config: { roles: Record<string, string[]> }) {}
105
- *
106
- * can(user, operation, context) {
107
- * // Check RBAC
108
- * const allowedRoles = this.config.roles[operation] || [];
109
- * if (!getUserRoles(user).some(r => allowedRoles.includes(r))) {
110
- * return { allowed: false, reason: 'Insufficient permissions' };
111
- * }
112
- *
113
- * // Check ownership for update/delete
114
- * if (['update', 'delete'].includes(operation)) {
115
- * if (context.document.userId !== user.id) {
116
- * return { allowed: false, reason: 'Not the owner' };
117
- * }
118
- * }
119
- *
120
- * // Multi-tenant filter for list
121
- * if (operation === 'list') {
122
- * return {
123
- * allowed: true,
124
- * filters: { organizationId: user.organizationId },
125
- * };
126
- * }
127
- *
128
- * return { allowed: true };
129
- * }
130
- *
131
- * toMiddleware(operation) {
132
- * return async (request, reply) => {
133
- * const result = await this.can(request.user, operation, {
134
- * document: request.document,
135
- * body: request.body,
136
- * params: request.params,
137
- * query: request.query,
138
- * });
139
- *
140
- * if (!result.allowed) {
141
- * reply.code(403).send({ error: result.reason });
142
- * }
143
- *
144
- * // Attach filters/fieldMask to request
145
- * request.policyResult = result;
146
- * };
147
- * }
148
- * }
149
- * ```
150
- *
151
- * @example HIPAA Compliance
152
- * ```typescript
153
- * class HIPAAPolicyEngine implements PolicyEngine {
154
- * can(user, operation, context) {
155
- * // Check patient consent
156
- * // Verify user certifications
157
- * // Check data sensitivity level
158
- * // Create audit log entry
159
- *
160
- * return {
161
- * allowed: this.checkHIPAACompliance(user, operation, context),
162
- * reason: 'HIPAA compliance check failed',
163
- * metadata: {
164
- * auditLog: this.createAuditEntry(user, operation),
165
- * },
166
- * };
167
- * }
168
- *
169
- * toMiddleware(operation) {
170
- * // HIPAA-specific middleware with audit logging
171
- * }
172
- * }
173
- * ```
174
- */
175
- interface PolicyEngine {
176
- /**
177
- * Check if user can perform operation
178
- *
179
- * @param user - User object from request (request.user)
180
- * @param operation - Operation name (list, get, create, update, delete, custom)
181
- * @param context - Additional context (document, body, params, query, etc.)
182
- * @returns Policy result with allowed/denied and optional filters/fieldMask
183
- *
184
- * @example
185
- * ```typescript
186
- * const result = await policy.can(request.user, 'update', {
187
- * document: existingDocument,
188
- * body: request.body,
189
- * });
190
- *
191
- * if (!result.allowed) {
192
- * throw new Error(result.reason);
193
- * }
194
- * ```
195
- */
196
- can(user: any, operation: string, context?: PolicyContext): PolicyResult | Promise<PolicyResult>;
197
- /**
198
- * Generate Fastify middleware for this policy
199
- *
200
- * Called during route registration to create preHandler middleware.
201
- * Middleware should:
202
- * 1. Call can() with request context
203
- * 2. Return 403 if denied
204
- * 3. Attach result to request for downstream use
205
- *
206
- * @param operation - Operation name (list, get, create, update, delete)
207
- * @returns Fastify preHandler middleware
208
- *
209
- * @example
210
- * ```typescript
211
- * const middleware = policy.toMiddleware('update');
212
- * fastify.put('/products/:id', {
213
- * preHandler: [authenticate, middleware],
214
- * }, handler);
215
- * ```
216
- */
217
- toMiddleware(operation: string): (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
218
- }
219
- /**
220
- * Policy factory function signature
221
- *
222
- * Policies are typically created via factory functions that accept configuration.
223
- *
224
- * @example
225
- * ```typescript
226
- * export function definePolicy(config: PolicyConfig): PolicyEngine {
227
- * return new MyPolicyEngine(config);
228
- * }
229
- *
230
- * // Usage
231
- * const productPolicy = definePolicy({
232
- * resource: 'product',
233
- * roles: { list: ['user'], create: ['admin'] },
234
- * ownership: { field: 'userId', operations: ['update', 'delete'] },
235
- * });
236
- * ```
237
- */
238
- type PolicyFactory<TConfig = any> = (config: TConfig) => PolicyEngine;
239
- /**
240
- * Extended Fastify request with policy result
241
- */
242
- /**
243
- * Access control statement
244
- *
245
- * Maps to Better Auth's organization permission model
246
- * where permissions are defined as resource + action pairs.
247
- */
248
- interface AccessControlStatement {
249
- /** Resource name (e.g., 'product', 'order') */
250
- resource: string;
251
- /** Allowed actions on this resource */
252
- action: string[];
253
- }
254
- /**
255
- * Options for createAccessControlPolicy
256
- */
257
- interface AccessControlPolicyOptions {
258
- /** Permission statements defining resource-action pairs */
259
- statements: AccessControlStatement[];
260
- /**
261
- * Optional async permission check against external source (e.g., org role permissions).
262
- * Called when the static statements allow the action — use this for dynamic checks
263
- * like verifying the user's org role actually grants the permission.
264
- *
265
- * @param userId - ID of the user
266
- * @param resource - Resource being accessed
267
- * @param action - Action being performed
268
- * @returns Whether the user has the permission
269
- */
270
- checkPermission?: (userId: string, resource: string, action: string) => Promise<boolean>;
271
- }
272
- /**
273
- * Create a PermissionCheck from access control statements.
274
- *
275
- * Maps Better Auth's statement-based access control model to Arc's
276
- * PermissionCheck function, which can be used directly in resource permissions.
277
- *
278
- * The returned PermissionCheck:
279
- * 1. Looks up the resource + action in the statements list
280
- * 2. If no matching statement exists, denies access
281
- * 3. If a matching statement exists and `checkPermission` is provided,
282
- * calls it for dynamic verification (e.g., check org role)
283
- * 4. If `checkPermission` is not provided, allows access based on static statements
284
- *
285
- * @example Static statements only
286
- * ```typescript
287
- * import { createAccessControlPolicy } from '@classytic/arc/policies';
288
- *
289
- * const editorPermissions = createAccessControlPolicy({
290
- * statements: [
291
- * { resource: 'product', action: ['create', 'update'] },
292
- * { resource: 'order', action: ['read'] },
293
- * ],
294
- * });
295
- *
296
- * // Use in resource config
297
- * defineResource({
298
- * name: 'product',
299
- * permissions: {
300
- * create: editorPermissions,
301
- * update: editorPermissions,
302
- * },
303
- * });
304
- * ```
305
- *
306
- * @example With dynamic permission check (Better Auth org roles)
307
- * ```typescript
308
- * const policy = createAccessControlPolicy({
309
- * statements: [
310
- * { resource: 'product', action: ['create', 'update'] },
311
- * { resource: 'order', action: ['read'] },
312
- * ],
313
- * checkPermission: async (userId, resource, action) => {
314
- * return hasOrgPermission(userId, resource, action);
315
- * },
316
- * });
317
- * ```
318
- */
319
- declare function createAccessControlPolicy(options: AccessControlPolicyOptions): PermissionCheck;
320
- declare module "fastify" {
321
- interface FastifyRequest {
322
- policyResult?: PolicyResult;
323
- }
324
- }
325
- //#endregion
326
- //#region src/policies/helpers.d.ts
327
- /**
328
- * Helper to create Fastify middleware from any PolicyEngine implementation
329
- *
330
- * This is a convenience function that provides a standard middleware pattern.
331
- * Most policies can use this instead of implementing toMiddleware() manually.
332
- *
333
- * @param policy - Policy engine instance
334
- * @param operation - Operation name (list, get, create, update, delete)
335
- * @returns Fastify preHandler middleware
336
- *
337
- * @example
338
- * ```typescript
339
- * class SimplePolicy implements PolicyEngine {
340
- * can(user, operation) {
341
- * return { allowed: user.isActive };
342
- * }
343
- *
344
- * toMiddleware(operation) {
345
- * return createPolicyMiddleware(this, operation);
346
- * }
347
- * }
348
- * ```
349
- */
350
- declare function createPolicyMiddleware(policy: PolicyEngine, operation: string): (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
351
- /**
352
- * Combine multiple policies with AND logic
353
- *
354
- * All policies must allow the operation for it to succeed.
355
- * First denial stops evaluation and returns the denial reason.
356
- *
357
- * @param policies - Array of policy engines to combine
358
- * @returns Combined policy engine
359
- *
360
- * @example
361
- * ```typescript
362
- * const combinedPolicy = combinePolicies(
363
- * rbacPolicy, // Must have correct role
364
- * ownershipPolicy, // Must own the resource
365
- * auditPolicy, // Logs access for compliance
366
- * );
367
- *
368
- * // All three policies must pass for the operation to succeed
369
- * const result = await combinedPolicy.can(user, 'update', context);
370
- * ```
371
- *
372
- * @example Multi-tenant + RBAC
373
- * ```typescript
374
- * const policy = combinePolicies(
375
- * definePolicy({ tenant: { field: 'organizationId' } }),
376
- * definePolicy({ roles: { update: ['admin', 'editor'] } }),
377
- * );
378
- * ```
379
- */
380
- declare function combinePolicies(...policies: PolicyEngine[]): PolicyEngine;
381
- /**
382
- * Combine multiple policies with OR logic
383
- *
384
- * At least one policy must allow the operation for it to succeed.
385
- * If all policies deny, returns the first denial reason.
386
- *
387
- * @param policies - Array of policy engines to combine
388
- * @returns Combined policy engine
389
- *
390
- * @example
391
- * ```typescript
392
- * const policy = anyPolicy(
393
- * ownerPolicy, // User owns the resource
394
- * adminPolicy, // OR user is admin
395
- * publicPolicy, // OR resource is public
396
- * );
397
- *
398
- * // Any one of these policies passing allows the operation
399
- * ```
400
- */
401
- declare function anyPolicy(...policies: PolicyEngine[]): PolicyEngine;
402
- /**
403
- * Create a pass-through policy that always allows
404
- *
405
- * Useful for testing or for routes that don't need authorization.
406
- *
407
- * @example
408
- * ```typescript
409
- * const policy = allowAll();
410
- * const result = await policy.can(user, 'any-operation');
411
- * // result.allowed === true
412
- * ```
413
- */
414
- declare function allowAll(): PolicyEngine;
415
- /**
416
- * Create a policy that always denies
417
- *
418
- * Useful for explicitly blocking operations or for testing.
419
- *
420
- * @param reason - Denial reason
421
- *
422
- * @example
423
- * ```typescript
424
- * const policy = denyAll('This resource is deprecated');
425
- * const result = await policy.can(user, 'any-operation');
426
- * // result.allowed === false
427
- * // result.reason === 'This resource is deprecated'
428
- * ```
429
- */
430
- declare function denyAll(reason?: string): PolicyEngine;
431
- //#endregion
432
- export { type AccessControlPolicyOptions, type AccessControlStatement, type PolicyContext, type PolicyEngine, type PolicyFactory, type PolicyResult, allowAll, anyPolicy, combinePolicies, createAccessControlPolicy, createPolicyMiddleware, denyAll };