@classytic/arc 2.3.0 → 2.4.2

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 (175) hide show
  1. package/README.md +187 -18
  2. package/bin/arc.js +11 -3
  3. package/dist/BaseController-CkM5dUh_.mjs +1031 -0
  4. package/dist/{EventTransport-BkUDYZEb.d.mts → EventTransport-wc5hSLik.d.mts} +1 -1
  5. package/dist/{HookSystem-BsGV-j2l.mjs → HookSystem-COkyWztM.mjs} +2 -3
  6. package/dist/{ResourceRegistry-7Ic20ZMw.mjs → ResourceRegistry-DeCIFlix.mjs} +8 -5
  7. package/dist/adapters/index.d.mts +3 -5
  8. package/dist/adapters/index.mjs +2 -3
  9. package/dist/{prisma-DJbMt3yf.mjs → adapters-DTC4Ug66.mjs} +45 -12
  10. package/dist/audit/index.d.mts +4 -7
  11. package/dist/audit/index.mjs +2 -29
  12. package/dist/audit/mongodb.d.mts +1 -4
  13. package/dist/audit/mongodb.mjs +2 -3
  14. package/dist/auth/index.d.mts +7 -9
  15. package/dist/auth/index.mjs +65 -63
  16. package/dist/auth/redis-session.d.mts +1 -1
  17. package/dist/auth/redis-session.mjs +1 -2
  18. package/dist/{betterAuthOpenApi-DjWDddNc.mjs → betterAuthOpenApi-lz0IRbXJ.mjs} +4 -6
  19. package/dist/cache/index.d.mts +23 -23
  20. package/dist/cache/index.mjs +4 -6
  21. package/dist/{caching-GSDJcA6-.mjs → caching-BSXB-Xr7.mjs} +2 -24
  22. package/dist/chunk-BpYLSNr0.mjs +14 -0
  23. package/dist/circuitBreaker-BOBOpN2w.mjs +284 -0
  24. package/dist/circuitBreaker-JP2GdJ4b.d.mts +206 -0
  25. package/dist/cli/commands/describe.mjs +24 -7
  26. package/dist/cli/commands/docs.mjs +6 -7
  27. package/dist/cli/commands/doctor.d.mts +10 -0
  28. package/dist/cli/commands/doctor.mjs +156 -0
  29. package/dist/cli/commands/generate.mjs +66 -17
  30. package/dist/cli/commands/init.mjs +315 -45
  31. package/dist/cli/commands/introspect.mjs +2 -4
  32. package/dist/cli/index.d.mts +1 -10
  33. package/dist/cli/index.mjs +4 -153
  34. package/dist/{constants-DdXFXQtN.mjs → constants-Cxde4rpC.mjs} +1 -2
  35. package/dist/core/index.d.mts +3 -5
  36. package/dist/core/index.mjs +5 -4
  37. package/dist/core-C1XCMtqM.mjs +185 -0
  38. package/dist/{createApp-CgKOPhA4.mjs → createApp-ByWNRsZj.mjs} +64 -35
  39. package/dist/{defineResource-DWbpJYtm.mjs → defineResource-D9aY5Cy6.mjs} +108 -1157
  40. package/dist/discovery/index.mjs +37 -5
  41. package/dist/docs/index.d.mts +6 -9
  42. package/dist/docs/index.mjs +3 -21
  43. package/dist/dynamic/index.d.mts +93 -0
  44. package/dist/dynamic/index.mjs +122 -0
  45. package/dist/{elevation-DSTbVvYj.mjs → elevation-BEdACOLB.mjs} +5 -36
  46. package/dist/{elevation-DGo5shaX.d.mts → elevation-Ca_yveIO.d.mts} +41 -7
  47. package/dist/{errorHandler-C3GY3_ow.mjs → errorHandler--zp54tGc.mjs} +3 -5
  48. package/dist/errorHandler-Do4vVQ1f.d.mts +139 -0
  49. package/dist/{errors-DBANPbGr.mjs → errors-rxhfP7Hf.mjs} +1 -2
  50. package/dist/{eventPlugin-BEOvaDqo.mjs → eventPlugin-Ba00swHF.mjs} +25 -27
  51. package/dist/{eventPlugin-H6wDDjGO.d.mts → eventPlugin-iGrSEmwJ.d.mts} +105 -5
  52. package/dist/events/index.d.mts +72 -7
  53. package/dist/events/index.mjs +216 -4
  54. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  55. package/dist/events/transports/redis-stream-entry.mjs +19 -7
  56. package/dist/events/transports/redis.d.mts +1 -1
  57. package/dist/events/transports/redis.mjs +3 -4
  58. package/dist/factory/index.d.mts +23 -9
  59. package/dist/factory/index.mjs +48 -3
  60. package/dist/{fields-Bi_AVKSo.d.mts → fields-DFwdaWCq.d.mts} +1 -1
  61. package/dist/{fields-CTd_CrKr.mjs → fields-ipsbIRPK.mjs} +1 -2
  62. package/dist/hooks/index.d.mts +1 -3
  63. package/dist/hooks/index.mjs +2 -3
  64. package/dist/idempotency/index.d.mts +5 -5
  65. package/dist/idempotency/index.mjs +3 -7
  66. package/dist/idempotency/mongodb.d.mts +1 -1
  67. package/dist/idempotency/mongodb.mjs +4 -5
  68. package/dist/idempotency/redis.d.mts +1 -1
  69. package/dist/idempotency/redis.mjs +2 -5
  70. package/dist/{fastifyAdapter-6b_eRDBw.d.mts → index-BL8CaQih.d.mts} +56 -57
  71. package/dist/index-Diqcm14c.d.mts +369 -0
  72. package/dist/{prisma-Dy5S5F5i.d.mts → index-yhxyjqNb.d.mts} +4 -5
  73. package/dist/index.d.mts +100 -105
  74. package/dist/index.mjs +85 -58
  75. package/dist/integrations/event-gateway.d.mts +1 -1
  76. package/dist/integrations/event-gateway.mjs +8 -4
  77. package/dist/integrations/index.d.mts +4 -2
  78. package/dist/integrations/index.mjs +1 -1
  79. package/dist/integrations/jobs.d.mts +2 -2
  80. package/dist/integrations/jobs.mjs +63 -14
  81. package/dist/integrations/mcp/index.d.mts +219 -0
  82. package/dist/integrations/mcp/index.mjs +572 -0
  83. package/dist/integrations/mcp/testing.d.mts +53 -0
  84. package/dist/integrations/mcp/testing.mjs +104 -0
  85. package/dist/integrations/streamline.mjs +39 -19
  86. package/dist/integrations/webhooks.d.mts +56 -0
  87. package/dist/integrations/webhooks.mjs +139 -0
  88. package/dist/integrations/websocket-redis.d.mts +46 -0
  89. package/dist/integrations/websocket-redis.mjs +50 -0
  90. package/dist/integrations/websocket.d.mts +68 -2
  91. package/dist/integrations/websocket.mjs +96 -13
  92. package/dist/{interface-CSNjltAc.d.mts → interface-B4awm1RJ.d.mts} +2 -2
  93. package/dist/interface-DGmPxakH.d.mts +2213 -0
  94. package/dist/{keys-DhqDRxv3.mjs → keys-qcD-TVJl.mjs} +3 -4
  95. package/dist/{logger-ByrvQWZO.mjs → logger-Dz3j1ItV.mjs} +2 -4
  96. package/dist/{memory-B2v7KrCB.mjs → memory-Cb_7iy9e.mjs} +2 -4
  97. package/dist/metrics-Csh4nsvv.mjs +224 -0
  98. package/dist/migrations/index.d.mts +113 -44
  99. package/dist/migrations/index.mjs +84 -102
  100. package/dist/{mongodb-DNKEExbf.mjs → mongodb-BuQ7fNTg.mjs} +1 -4
  101. package/dist/{mongodb-ClykrfGo.d.mts → mongodb-CUpYfxfD.d.mts} +2 -3
  102. package/dist/{mongodb-Dg8O_gvd.d.mts → mongodb-bga9AbkD.d.mts} +2 -2
  103. package/dist/{openapi-9nB_kiuR.mjs → openapi-CBmZ6EQN.mjs} +4 -21
  104. package/dist/org/index.d.mts +12 -14
  105. package/dist/org/index.mjs +92 -119
  106. package/dist/org/types.d.mts +2 -2
  107. package/dist/org/types.mjs +1 -1
  108. package/dist/permissions/index.d.mts +4 -278
  109. package/dist/permissions/index.mjs +4 -579
  110. package/dist/permissions-CA5zg0yK.mjs +751 -0
  111. package/dist/plugins/index.d.mts +104 -107
  112. package/dist/plugins/index.mjs +203 -313
  113. package/dist/plugins/response-cache.mjs +4 -69
  114. package/dist/plugins/tracing-entry.d.mts +1 -1
  115. package/dist/plugins/tracing-entry.mjs +24 -11
  116. package/dist/{pluralize-CM-jZg7p.mjs → pluralize-CcT6qF0a.mjs} +12 -13
  117. package/dist/policies/index.d.mts +2 -2
  118. package/dist/policies/index.mjs +80 -83
  119. package/dist/presets/index.d.mts +26 -19
  120. package/dist/presets/index.mjs +2 -142
  121. package/dist/presets/multiTenant.d.mts +1 -4
  122. package/dist/presets/multiTenant.mjs +4 -6
  123. package/dist/presets-C9QXJV1u.mjs +422 -0
  124. package/dist/{queryCachePlugin-B6R0d4av.mjs → queryCachePlugin-ClosZdNS.mjs} +6 -27
  125. package/dist/{queryCachePlugin-Q6SYuHZ6.d.mts → queryCachePlugin-DcmETvcB.d.mts} +3 -3
  126. package/dist/queryParser-CgCtsjti.mjs +352 -0
  127. package/dist/{redis-UwjEp8Ea.d.mts → redis-CQ5YxMC5.d.mts} +2 -2
  128. package/dist/{redis-stream-CBg0upHI.d.mts → redis-stream-BW9UKLZM.d.mts} +9 -2
  129. package/dist/registry/index.d.mts +1 -4
  130. package/dist/registry/index.mjs +3 -4
  131. package/dist/{introspectionPlugin-B3JkrjwU.mjs → registry-I-ogLgL9.mjs} +1 -8
  132. package/dist/{requestContext-xi6OKBL-.mjs → requestContext-DYtmNpm5.mjs} +1 -3
  133. package/dist/resourceToTools-PMFE8HIv.mjs +533 -0
  134. package/dist/rpc/index.d.mts +90 -0
  135. package/dist/rpc/index.mjs +248 -0
  136. package/dist/{schemaConverter-Dtg0Kt9T.mjs → schemaConverter-DjzHpFam.mjs} +1 -2
  137. package/dist/schemas/index.d.mts +30 -30
  138. package/dist/schemas/index.mjs +2 -4
  139. package/dist/scope/index.d.mts +13 -2
  140. package/dist/scope/index.mjs +18 -5
  141. package/dist/{sessionManager-D_iEHjQl.d.mts → sessionManager-wbkYj2HL.d.mts} +2 -2
  142. package/dist/{sse-DkqQ1uxb.mjs → sse-BkViJPlT.mjs} +4 -25
  143. package/dist/testing/index.d.mts +551 -567
  144. package/dist/testing/index.mjs +1744 -1799
  145. package/dist/{tracing-8CEbhF0w.d.mts → tracing-bz_U4EM1.d.mts} +6 -1
  146. package/dist/{typeGuards-DwxA1t_L.mjs → typeGuards-Cj5Rgvlg.mjs} +1 -2
  147. package/dist/types/index.d.mts +4 -946
  148. package/dist/types/index.mjs +2 -4
  149. package/dist/types-BJmgxNbF.d.mts +275 -0
  150. package/dist/{types-RLkFVgaw.d.mts → types-BNUccdcf.d.mts} +2 -2
  151. package/dist/{types-Beqn1Un7.mjs → types-C6TQjtdi.mjs} +30 -2
  152. package/dist/{types-tKwaViYB.d.mts → types-Dt0-AI6E.d.mts} +68 -27
  153. package/dist/{types-DelU6kln.mjs → types-ZUu_h0jp.mjs} +1 -2
  154. package/dist/utils/index.d.mts +254 -351
  155. package/dist/utils/index.mjs +7 -6
  156. package/dist/utils-Dc0WhlIl.mjs +594 -0
  157. package/dist/versioning-BzfeHmhj.mjs +37 -0
  158. package/package.json +44 -10
  159. package/skills/arc/SKILL.md +518 -0
  160. package/skills/arc/references/auth.md +250 -0
  161. package/skills/arc/references/events.md +272 -0
  162. package/skills/arc/references/integrations.md +385 -0
  163. package/skills/arc/references/mcp.md +431 -0
  164. package/skills/arc/references/production.md +610 -0
  165. package/skills/arc/references/testing.md +183 -0
  166. package/dist/audited-CGdLiSlE.mjs +0 -140
  167. package/dist/chunk-C7Uep-_p.mjs +0 -20
  168. package/dist/circuitBreaker-CSS2VvL6.mjs +0 -1109
  169. package/dist/errorHandler-CW3OOeYq.d.mts +0 -72
  170. package/dist/interface-BtdYtQUA.d.mts +0 -1114
  171. package/dist/presets-BTeYbw7h.d.mts +0 -57
  172. package/dist/presets-CeFtfDR8.mjs +0 -119
  173. /package/dist/{errors-DAWRdiYP.d.mts → errors-CPpvPHT0.d.mts} +0 -0
  174. /package/dist/{externalPaths-SyPF2tgK.d.mts → externalPaths-DpO-s7r8.d.mts} +0 -0
  175. /package/dist/{interface-DTbsvIWe.d.mts → interface-D_BWALyZ.d.mts} +0 -0
@@ -1,579 +1,4 @@
1
- import { a as getTeamId, c as isElevated, l as isMember, n as PUBLIC_SCOPE } from "../types-Beqn1Un7.mjs";
2
- import { i as resolveEffectiveRoles, n as applyFieldWritePermissions, r as fields, t as applyFieldReadPermissions } from "../fields-CTd_CrKr.mjs";
3
- import { n as normalizeRoles, t as getUserRoles } from "../types-DelU6kln.mjs";
4
- import { t as MemoryCacheStore } from "../memory-B2v7KrCB.mjs";
5
- import { a as presets_exports, c as readOnly, i as ownerWithAdminBypass, n as authenticated, o as publicRead, r as fullPublic, s as publicReadAdminWrite, t as adminOnly } from "../presets-CeFtfDR8.mjs";
6
- import { randomUUID } from "node:crypto";
7
-
8
- //#region src/permissions/index.ts
9
- /**
10
- * Allow public access (no authentication required)
11
- *
12
- * @example
13
- * ```typescript
14
- * permissions: {
15
- * list: allowPublic(),
16
- * get: allowPublic(),
17
- * }
18
- * ```
19
- */
20
- function allowPublic() {
21
- const check = () => true;
22
- check._isPublic = true;
23
- return check;
24
- }
25
- /**
26
- * Require authentication (any authenticated user)
27
- *
28
- * @example
29
- * ```typescript
30
- * permissions: {
31
- * create: requireAuth(),
32
- * update: requireAuth(),
33
- * }
34
- * ```
35
- */
36
- function requireAuth() {
37
- const check = (ctx) => {
38
- if (!ctx.user) return {
39
- granted: false,
40
- reason: "Authentication required"
41
- };
42
- return true;
43
- };
44
- return check;
45
- }
46
- /**
47
- * Require specific roles
48
- *
49
- * @param roles - Required roles (user needs at least one)
50
- * @param options - Optional bypass roles
51
- *
52
- * @example
53
- * ```typescript
54
- * permissions: {
55
- * create: requireRoles(['admin', 'editor']),
56
- * delete: requireRoles(['admin']),
57
- * }
58
- *
59
- * // With bypass roles
60
- * permissions: {
61
- * update: requireRoles(['owner'], { bypassRoles: ['admin', 'superadmin'] }),
62
- * }
63
- * ```
64
- */
65
- function requireRoles(roles, options) {
66
- const check = (ctx) => {
67
- if (!ctx.user) return {
68
- granted: false,
69
- reason: "Authentication required"
70
- };
71
- const userRoles = getUserRoles(ctx.user);
72
- if (options?.bypassRoles?.some((r) => userRoles.includes(r))) return true;
73
- if (roles.some((r) => userRoles.includes(r))) return true;
74
- return {
75
- granted: false,
76
- reason: `Required roles: ${roles.join(", ")}`
77
- };
78
- };
79
- check._roles = roles;
80
- return check;
81
- }
82
- /**
83
- * Require resource ownership
84
- *
85
- * Returns filters to scope queries to user's owned resources.
86
- *
87
- * @param ownerField - Field containing owner ID (default: 'userId')
88
- * @param options - Optional bypass roles
89
- *
90
- * @example
91
- * ```typescript
92
- * permissions: {
93
- * update: requireOwnership('userId'),
94
- * delete: requireOwnership('createdBy', { bypassRoles: ['admin'] }),
95
- * }
96
- * ```
97
- */
98
- function requireOwnership(ownerField = "userId", options) {
99
- return (ctx) => {
100
- if (!ctx.user) return {
101
- granted: false,
102
- reason: "Authentication required"
103
- };
104
- const userRoles = getUserRoles(ctx.user);
105
- if (options?.bypassRoles?.some((r) => userRoles.includes(r))) return true;
106
- const userId = ctx.user.id ?? ctx.user._id;
107
- if (!userId) return {
108
- granted: false,
109
- reason: "User identity missing (no id or _id)"
110
- };
111
- return {
112
- granted: true,
113
- filters: { [ownerField]: userId }
114
- };
115
- };
116
- }
117
- /**
118
- * Combine multiple checks - ALL must pass (AND logic)
119
- *
120
- * @example
121
- * ```typescript
122
- * permissions: {
123
- * update: allOf(
124
- * requireAuth(),
125
- * requireRoles(['editor']),
126
- * requireOwnership('createdBy')
127
- * ),
128
- * }
129
- * ```
130
- */
131
- function allOf(...checks) {
132
- return async (ctx) => {
133
- let mergedFilters = {};
134
- for (const check of checks) {
135
- const result = await check(ctx);
136
- const normalized = typeof result === "boolean" ? { granted: result } : result;
137
- if (!normalized.granted) return normalized;
138
- if (normalized.filters) mergedFilters = {
139
- ...mergedFilters,
140
- ...normalized.filters
141
- };
142
- }
143
- return {
144
- granted: true,
145
- filters: Object.keys(mergedFilters).length > 0 ? mergedFilters : void 0
146
- };
147
- };
148
- }
149
- /**
150
- * Combine multiple checks - ANY must pass (OR logic)
151
- *
152
- * @example
153
- * ```typescript
154
- * permissions: {
155
- * update: anyOf(
156
- * requireRoles(['admin']),
157
- * requireOwnership('createdBy')
158
- * ),
159
- * }
160
- * ```
161
- */
162
- function anyOf(...checks) {
163
- return async (ctx) => {
164
- const reasons = [];
165
- for (const check of checks) {
166
- const result = await check(ctx);
167
- const normalized = typeof result === "boolean" ? { granted: result } : result;
168
- if (normalized.granted) return normalized;
169
- if (normalized.reason) reasons.push(normalized.reason);
170
- }
171
- return {
172
- granted: false,
173
- reason: reasons.join("; ")
174
- };
175
- };
176
- }
177
- /**
178
- * Deny all access
179
- *
180
- * @example
181
- * ```typescript
182
- * permissions: {
183
- * delete: denyAll('Deletion not allowed'),
184
- * }
185
- * ```
186
- */
187
- function denyAll(reason = "Access denied") {
188
- return () => ({
189
- granted: false,
190
- reason
191
- });
192
- }
193
- /**
194
- * Dynamic permission based on context
195
- *
196
- * @example
197
- * ```typescript
198
- * permissions: {
199
- * update: when((ctx) => ctx.data?.status === 'draft'),
200
- * }
201
- * ```
202
- */
203
- function when(condition) {
204
- return async (ctx) => {
205
- const result = await condition(ctx);
206
- return {
207
- granted: result,
208
- reason: result ? void 0 : "Condition not met"
209
- };
210
- };
211
- }
212
- /** Read request.scope safely */
213
- function getScope(request) {
214
- return request.scope ?? PUBLIC_SCOPE;
215
- }
216
- /**
217
- * Require membership in the active organization.
218
- * User must be authenticated AND have an active org (member or elevated scope).
219
- *
220
- * Reads `request.scope` set by auth adapters.
221
- *
222
- * @example
223
- * ```typescript
224
- * permissions: {
225
- * list: requireOrgMembership(),
226
- * get: requireOrgMembership(),
227
- * }
228
- * ```
229
- */
230
- function requireOrgMembership() {
231
- const check = (ctx) => {
232
- if (!ctx.user) return {
233
- granted: false,
234
- reason: "Authentication required"
235
- };
236
- const scope = getScope(ctx.request);
237
- if (isElevated(scope)) return true;
238
- if (isMember(scope)) return true;
239
- return {
240
- granted: false,
241
- reason: "Organization membership required"
242
- };
243
- };
244
- check._orgPermission = "membership";
245
- return check;
246
- }
247
- /**
248
- * Require specific org-level roles.
249
- * Reads `request.scope.orgRoles` (set by auth adapters).
250
- * Elevated scope always passes (platform admin bypass).
251
- *
252
- * @param roles - Required org roles (user needs at least one)
253
- *
254
- * @example
255
- * ```typescript
256
- * permissions: {
257
- * create: requireOrgRole('admin', 'owner'),
258
- * delete: requireOrgRole('owner'),
259
- * }
260
- * ```
261
- */
262
- function requireOrgRole(...args) {
263
- const roles = Array.isArray(args[0]) ? args[0] : args;
264
- const check = (ctx) => {
265
- if (!ctx.user) return {
266
- granted: false,
267
- reason: "Authentication required"
268
- };
269
- const scope = getScope(ctx.request);
270
- if (isElevated(scope)) return true;
271
- if (!isMember(scope)) return {
272
- granted: false,
273
- reason: "Organization membership required"
274
- };
275
- if (roles.some((r) => scope.orgRoles.includes(r))) return true;
276
- return {
277
- granted: false,
278
- reason: `Required org roles: ${roles.join(", ")}`
279
- };
280
- };
281
- check._orgRoles = roles;
282
- return check;
283
- }
284
- /**
285
- * Create a scoped permission system for resource-action patterns.
286
- * Maps org roles to fine-grained permissions without external API calls.
287
- *
288
- * @example
289
- * ```typescript
290
- * const perms = createOrgPermissions({
291
- * statements: {
292
- * product: ['create', 'update', 'delete'],
293
- * order: ['create', 'approve'],
294
- * },
295
- * roles: {
296
- * owner: { product: ['create', 'update', 'delete'], order: ['create', 'approve'] },
297
- * admin: { product: ['create', 'update'], order: ['create'] },
298
- * member: { product: [], order: [] },
299
- * },
300
- * });
301
- *
302
- * defineResource({
303
- * permissions: {
304
- * create: perms.can({ product: ['create'] }),
305
- * delete: perms.can({ product: ['delete'] }),
306
- * }
307
- * });
308
- * ```
309
- */
310
- function createOrgPermissions(config) {
311
- const { roles: roleMap } = config;
312
- function hasPermissions(orgRoles, required) {
313
- for (const [resource, actions] of Object.entries(required)) for (const action of actions) if (!orgRoles.some((role) => {
314
- return (roleMap[role]?.[resource])?.includes(action);
315
- })) return false;
316
- return true;
317
- }
318
- return {
319
- can(permissions) {
320
- return (ctx) => {
321
- if (!ctx.user) return {
322
- granted: false,
323
- reason: "Authentication required"
324
- };
325
- const scope = getScope(ctx.request);
326
- if (isElevated(scope)) return true;
327
- if (!isMember(scope)) return {
328
- granted: false,
329
- reason: "Organization membership required"
330
- };
331
- if (hasPermissions(scope.orgRoles, permissions)) return true;
332
- return {
333
- granted: false,
334
- reason: `Missing permissions: ${Object.entries(permissions).map(([r, a]) => `${r}:[${a.join(",")}]`).join(", ")}`
335
- };
336
- };
337
- },
338
- requireRole(...roles) {
339
- return requireOrgRole(roles);
340
- },
341
- requireMembership() {
342
- return requireOrgMembership();
343
- },
344
- requireTeamMembership() {
345
- return requireTeamMembership();
346
- }
347
- };
348
- }
349
- /**
350
- * Create a dynamic role-based permission matrix.
351
- *
352
- * Use this when role/action mappings are managed outside code
353
- * (e.g., admin UI matrix, DB-stored ACLs, remote policy service).
354
- *
355
- * Supports:
356
- * - org role union (any assigned org role can grant)
357
- * - global bypass roles
358
- * - wildcard resource/action (`*`)
359
- * - optional in-memory cache
360
- */
361
- function createDynamicPermissionMatrix(config) {
362
- const logger = config.logger ?? console;
363
- const legacyTtlMs = config.cache?.ttlMs ?? 0;
364
- const hasExternalStore = !!config.cacheStore;
365
- const cacheTtlMs = legacyTtlMs > 0 ? legacyTtlMs : hasExternalStore ? 3e5 : 0;
366
- const internalStore = !config.cacheStore && cacheTtlMs > 0 ? new MemoryCacheStore({
367
- defaultTtlMs: cacheTtlMs,
368
- maxEntries: config.cache?.maxEntries ?? 1e3
369
- }) : void 0;
370
- const cacheStore = config.cacheStore ?? internalStore;
371
- const trackedKeys = /* @__PURE__ */ new Set();
372
- const nodeId = randomUUID().slice(0, 8);
373
- const DEFAULT_EVENT_TYPE = "arc.permissions.invalidated";
374
- let eventBridge = null;
375
- /** Clear local cache for an org without publishing events (avoids infinite loops). */
376
- async function localInvalidateByOrg(orgId) {
377
- if (!cacheStore) return;
378
- const prefix = `${orgId}::`;
379
- const toDelete = [];
380
- for (const key of trackedKeys) if (key.startsWith(prefix)) toDelete.push(key);
381
- for (const key of toDelete) try {
382
- await cacheStore.delete(key);
383
- trackedKeys.delete(key);
384
- } catch (error) {
385
- logger.warn(`[DynamicPermissionMatrix] invalidateByOrg delete failed for '${key}': ${error instanceof Error ? error.message : String(error)}`);
386
- }
387
- }
388
- function isActionAllowed(actions, action) {
389
- if (!actions || actions.length === 0) return false;
390
- return actions.includes("*") || actions.includes(action);
391
- }
392
- function roleAllows(matrix, role, resource, action) {
393
- const rolePermissions = matrix[role];
394
- if (!rolePermissions) return false;
395
- const resourceActions = rolePermissions[resource];
396
- const wildcardResourceActions = rolePermissions["*"];
397
- return isActionAllowed(resourceActions, action) || isActionAllowed(wildcardResourceActions, action);
398
- }
399
- function buildDefaultCacheKey(ctx, orgId, orgRoles) {
400
- const userId = String(ctx.user?.id ?? ctx.user?._id ?? "anon");
401
- const roles = (orgRoles ?? []).slice().sort().join(",");
402
- return `${orgId ?? "no-org"}::${roles}::${userId}`;
403
- }
404
- async function resolveMatrix(ctx, orgId, orgRoles) {
405
- if (!cacheStore) return config.resolveRolePermissions(ctx);
406
- const cacheKey = config.cache?.key?.(ctx) ?? buildDefaultCacheKey(ctx, orgId, orgRoles);
407
- if (!cacheKey) return config.resolveRolePermissions(ctx);
408
- try {
409
- const hit = await cacheStore.get(cacheKey);
410
- if (hit) return hit;
411
- } catch (error) {
412
- logger.warn(`[DynamicPermissionMatrix] Cache get failed for '${cacheKey}': ${error instanceof Error ? error.message : String(error)}`);
413
- }
414
- const value = await config.resolveRolePermissions(ctx);
415
- try {
416
- await cacheStore.set(cacheKey, value, { ttlMs: cacheTtlMs });
417
- trackedKeys.add(cacheKey);
418
- const maxTracked = config.cache?.maxEntries ?? 1e4;
419
- if (trackedKeys.size > maxTracked) {
420
- const overflow = trackedKeys.size - maxTracked;
421
- const iter = trackedKeys.values();
422
- for (let i = 0; i < overflow; i++) {
423
- const oldest = iter.next().value;
424
- if (oldest) trackedKeys.delete(oldest);
425
- }
426
- }
427
- } catch (error) {
428
- logger.warn(`[DynamicPermissionMatrix] Cache set failed for '${cacheKey}': ${error instanceof Error ? error.message : String(error)}`);
429
- }
430
- return value;
431
- }
432
- function can(required) {
433
- return async (ctx) => {
434
- if (!ctx.user) return {
435
- granted: false,
436
- reason: "Authentication required"
437
- };
438
- const scope = getScope(ctx.request);
439
- if (isElevated(scope)) return true;
440
- if (!isMember(scope)) return {
441
- granted: false,
442
- reason: "Organization membership required"
443
- };
444
- const orgRoles = scope.orgRoles;
445
- if (orgRoles.length === 0) return {
446
- granted: false,
447
- reason: "Not a member of this organization"
448
- };
449
- let matrix;
450
- try {
451
- matrix = await resolveMatrix(ctx, scope.organizationId, orgRoles);
452
- } catch (error) {
453
- return {
454
- granted: false,
455
- reason: `Permission matrix resolution failed: ${error instanceof Error ? error.message : String(error)}`
456
- };
457
- }
458
- for (const [resource, actions] of Object.entries(required)) for (const action of actions) if (!orgRoles.some((role) => roleAllows(matrix, role, resource, action))) return {
459
- granted: false,
460
- reason: `Missing permission: ${resource}:${action}`
461
- };
462
- return true;
463
- };
464
- }
465
- return {
466
- can,
467
- canAction(resource, action) {
468
- return can({ [resource]: [action] });
469
- },
470
- requireRole(...roles) {
471
- return requireOrgRole(roles);
472
- },
473
- requireMembership() {
474
- return requireOrgMembership();
475
- },
476
- requireTeamMembership() {
477
- return requireTeamMembership();
478
- },
479
- async invalidateByOrg(orgId) {
480
- await localInvalidateByOrg(orgId);
481
- if (eventBridge) try {
482
- await eventBridge.publish(eventBridge.eventType, {
483
- orgId,
484
- nodeId
485
- });
486
- } catch (error) {
487
- logger.warn(`[DynamicPermissionMatrix] Failed to publish invalidation event for org '${orgId}': ${error instanceof Error ? error.message : String(error)}`);
488
- }
489
- },
490
- async clearCache() {
491
- if (!cacheStore) return;
492
- if (cacheStore.clear) try {
493
- await cacheStore.clear();
494
- trackedKeys.clear();
495
- return;
496
- } catch (error) {
497
- logger.warn(`[DynamicPermissionMatrix] cacheStore.clear failed: ${error instanceof Error ? error.message : String(error)}`);
498
- }
499
- for (const key of trackedKeys) try {
500
- await cacheStore.delete(key);
501
- } catch (error) {
502
- logger.warn(`[DynamicPermissionMatrix] Cache delete failed for '${key}': ${error instanceof Error ? error.message : String(error)}`);
503
- }
504
- trackedKeys.clear();
505
- },
506
- async connectEvents(events, options) {
507
- if (eventBridge) await this.disconnectEvents();
508
- const eventType = options?.eventType ?? DEFAULT_EVENT_TYPE;
509
- const unsubscribeFn = await events.subscribe(eventType, async (event) => {
510
- const payload = event.payload;
511
- if (!payload?.orgId) return;
512
- if (payload.nodeId === nodeId) return;
513
- await localInvalidateByOrg(payload.orgId);
514
- if (options?.onRemoteInvalidation) try {
515
- await options.onRemoteInvalidation(payload.orgId);
516
- } catch (error) {
517
- logger.warn(`[DynamicPermissionMatrix] onRemoteInvalidation callback failed for org '${payload.orgId}': ${error instanceof Error ? error.message : String(error)}`);
518
- }
519
- });
520
- eventBridge = {
521
- publish: events.publish,
522
- unsubscribe: typeof unsubscribeFn === "function" ? unsubscribeFn : null,
523
- eventType,
524
- onRemoteInvalidation: options?.onRemoteInvalidation
525
- };
526
- },
527
- async disconnectEvents() {
528
- if (!eventBridge) return;
529
- try {
530
- eventBridge.unsubscribe?.();
531
- } catch (error) {
532
- logger.warn(`[DynamicPermissionMatrix] disconnectEvents unsubscribe failed: ${error instanceof Error ? error.message : String(error)}`);
533
- }
534
- eventBridge = null;
535
- },
536
- get eventsConnected() {
537
- return eventBridge !== null;
538
- }
539
- };
540
- }
541
- /**
542
- * Require membership in the active team.
543
- * User must be authenticated, a member of the active org, AND have an active team.
544
- *
545
- * Better Auth teams are flat member groups (no team-level roles).
546
- * Reads `request.scope.teamId` set by the Better Auth adapter.
547
- *
548
- * @example
549
- * ```typescript
550
- * permissions: {
551
- * list: requireTeamMembership(),
552
- * create: requireTeamMembership(),
553
- * }
554
- * ```
555
- */
556
- function requireTeamMembership() {
557
- const check = (ctx) => {
558
- if (!ctx.user) return {
559
- granted: false,
560
- reason: "Authentication required"
561
- };
562
- const scope = getScope(ctx.request);
563
- if (isElevated(scope)) return true;
564
- if (!isMember(scope)) return {
565
- granted: false,
566
- reason: "Organization membership required"
567
- };
568
- if (!getTeamId(scope)) return {
569
- granted: false,
570
- reason: "No active team"
571
- };
572
- return true;
573
- };
574
- check._teamPermission = "membership";
575
- return check;
576
- }
577
-
578
- //#endregion
579
- export { adminOnly, allOf, allowPublic, anyOf, applyFieldReadPermissions, applyFieldWritePermissions, authenticated, createDynamicPermissionMatrix, createOrgPermissions, denyAll, fields, fullPublic, getUserRoles, normalizeRoles, ownerWithAdminBypass, presets_exports as permissions, publicRead, publicReadAdminWrite, readOnly, requireAuth, requireOrgMembership, requireOrgRole, requireOwnership, requireRoles, requireTeamMembership, resolveEffectiveRoles, when };
1
+ import { i as resolveEffectiveRoles, n as applyFieldWritePermissions, r as fields, t as applyFieldReadPermissions } from "../fields-ipsbIRPK.mjs";
2
+ import { n as normalizeRoles, t as getUserRoles } from "../types-ZUu_h0jp.mjs";
3
+ import { S as createRoleHierarchy, _ as ownerWithAdminBypass, a as createOrgPermissions, b as publicReadAdminWrite, c as requireOrgMembership, d as requireRoles, f as requireTeamMembership, g as fullPublic, h as authenticated, i as createDynamicPermissionMatrix, l as requireOrgRole, m as adminOnly, n as allowPublic, o as denyAll, p as when, r as anyOf, s as requireAuth, t as allOf, u as requireOwnership, v as presets_exports, x as readOnly, y as publicRead } from "../permissions-CA5zg0yK.mjs";
4
+ export { adminOnly, allOf, allowPublic, anyOf, applyFieldReadPermissions, applyFieldWritePermissions, authenticated, createDynamicPermissionMatrix, createOrgPermissions, createRoleHierarchy, denyAll, fields, fullPublic, getUserRoles, normalizeRoles, ownerWithAdminBypass, presets_exports as permissions, publicRead, publicReadAdminWrite, readOnly, requireAuth, requireOrgMembership, requireOrgRole, requireOwnership, requireRoles, requireTeamMembership, resolveEffectiveRoles, when };