@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
@@ -0,0 +1,751 @@
1
+ import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
2
+ import { a as getTeamId, d as isMember, n as PUBLIC_SCOPE, o as getUserId, u as isElevated } from "./types-C6TQjtdi.mjs";
3
+ import { t as getUserRoles } from "./types-ZUu_h0jp.mjs";
4
+ import { t as MemoryCacheStore } from "./memory-Cb_7iy9e.mjs";
5
+ import { randomUUID } from "node:crypto";
6
+ //#region src/permissions/roleHierarchy.ts
7
+ /**
8
+ * Create a role hierarchy from a parent → children map.
9
+ *
10
+ * Each key is a parent role, each value is the array of roles it inherits.
11
+ * Inheritance is transitive: if A → B and B → C, then A expands to [A, B, C].
12
+ * Circular references are handled safely (visited set).
13
+ */
14
+ function createRoleHierarchy(map) {
15
+ const cache = /* @__PURE__ */ new Map();
16
+ function resolveRole(role, visited) {
17
+ if (visited.has(role)) return [];
18
+ visited.add(role);
19
+ const cached = cache.get(role);
20
+ if (cached) return cached;
21
+ const children = map[role];
22
+ if (!children || children.length === 0) {
23
+ cache.set(role, [role]);
24
+ return [role];
25
+ }
26
+ const result = [role];
27
+ for (const child of children) result.push(...resolveRole(child, visited));
28
+ const deduped = [...new Set(result)];
29
+ cache.set(role, deduped);
30
+ return deduped;
31
+ }
32
+ return {
33
+ expand(roles) {
34
+ if (roles.length === 0) return [];
35
+ const all = /* @__PURE__ */ new Set();
36
+ for (const role of roles) for (const expanded of resolveRole(role, /* @__PURE__ */ new Set())) all.add(expanded);
37
+ return [...all];
38
+ },
39
+ includes(userRoles, requiredRole) {
40
+ if (userRoles.length === 0) return false;
41
+ return this.expand(userRoles).includes(requiredRole);
42
+ }
43
+ };
44
+ }
45
+ //#endregion
46
+ //#region src/permissions/presets.ts
47
+ /**
48
+ * Permission Presets — Common permission patterns in one call.
49
+ *
50
+ * Reduces 5 lines of permission declarations to 1.
51
+ * Each preset returns a ResourcePermissions object that can be
52
+ * spread or overridden per-operation.
53
+ *
54
+ * @example
55
+ * ```typescript
56
+ * import { permissions } from '@classytic/arc';
57
+ *
58
+ * // Public read, authenticated write
59
+ * defineResource({ name: 'product', permissions: permissions.publicRead() });
60
+ *
61
+ * // Override specific operations
62
+ * defineResource({
63
+ * name: 'product',
64
+ * permissions: permissions.publicRead({ delete: requireRoles(['superadmin']) }),
65
+ * });
66
+ * ```
67
+ */
68
+ var presets_exports = /* @__PURE__ */ __exportAll({
69
+ adminOnly: () => adminOnly,
70
+ authenticated: () => authenticated,
71
+ fullPublic: () => fullPublic,
72
+ ownerWithAdminBypass: () => ownerWithAdminBypass,
73
+ publicRead: () => publicRead,
74
+ publicReadAdminWrite: () => publicReadAdminWrite,
75
+ readOnly: () => readOnly
76
+ });
77
+ /**
78
+ * Merge a base preset with user overrides.
79
+ * Overrides replace individual operations — undefined values don't clear them.
80
+ */
81
+ function withOverrides(base, overrides) {
82
+ if (!overrides) return base;
83
+ const filtered = Object.fromEntries(Object.entries(overrides).filter(([, v]) => v !== void 0));
84
+ return {
85
+ ...base,
86
+ ...filtered
87
+ };
88
+ }
89
+ /**
90
+ * Public read, authenticated write.
91
+ * list + get = allowPublic(), create + update + delete = requireAuth()
92
+ */
93
+ function publicRead(overrides) {
94
+ return withOverrides({
95
+ list: allowPublic(),
96
+ get: allowPublic(),
97
+ create: requireAuth(),
98
+ update: requireAuth(),
99
+ delete: requireAuth()
100
+ }, overrides);
101
+ }
102
+ /**
103
+ * Public read, admin write.
104
+ * list + get = allowPublic(), create + update + delete = requireRoles(['admin'])
105
+ */
106
+ function publicReadAdminWrite(roles = ["admin"], overrides) {
107
+ return withOverrides({
108
+ list: allowPublic(),
109
+ get: allowPublic(),
110
+ create: requireRoles(roles),
111
+ update: requireRoles(roles),
112
+ delete: requireRoles(roles)
113
+ }, overrides);
114
+ }
115
+ /**
116
+ * All operations require authentication.
117
+ */
118
+ function authenticated(overrides) {
119
+ return withOverrides({
120
+ list: requireAuth(),
121
+ get: requireAuth(),
122
+ create: requireAuth(),
123
+ update: requireAuth(),
124
+ delete: requireAuth()
125
+ }, overrides);
126
+ }
127
+ /**
128
+ * All operations require specific roles.
129
+ * @param roles - Required roles (user needs at least one). Default: ['admin']
130
+ */
131
+ function adminOnly(roles = ["admin"], overrides) {
132
+ return withOverrides({
133
+ list: requireRoles(roles),
134
+ get: requireRoles(roles),
135
+ create: requireRoles(roles),
136
+ update: requireRoles(roles),
137
+ delete: requireRoles(roles)
138
+ }, overrides);
139
+ }
140
+ /**
141
+ * Owner-scoped with admin bypass.
142
+ * list = auth (scoped to owner), get = auth, create = auth,
143
+ * update + delete = ownership check with admin bypass.
144
+ *
145
+ * @param ownerField - Field containing owner ID (default: 'userId')
146
+ * @param bypassRoles - Roles that bypass ownership check (default: ['admin'])
147
+ */
148
+ function ownerWithAdminBypass(ownerField = "userId", bypassRoles = ["admin"], overrides) {
149
+ return withOverrides({
150
+ list: requireAuth(),
151
+ get: requireAuth(),
152
+ create: requireAuth(),
153
+ update: anyOf(requireRoles(bypassRoles), requireOwnership(ownerField)),
154
+ delete: anyOf(requireRoles(bypassRoles), requireOwnership(ownerField))
155
+ }, overrides);
156
+ }
157
+ /**
158
+ * Full public access — no auth required for any operation.
159
+ * Use sparingly (dev/testing, truly public APIs).
160
+ */
161
+ function fullPublic(overrides) {
162
+ return withOverrides({
163
+ list: allowPublic(),
164
+ get: allowPublic(),
165
+ create: allowPublic(),
166
+ update: allowPublic(),
167
+ delete: allowPublic()
168
+ }, overrides);
169
+ }
170
+ /**
171
+ * Read-only: list + get authenticated, write operations denied.
172
+ * Useful for computed/derived resources.
173
+ */
174
+ function readOnly(overrides) {
175
+ return withOverrides({
176
+ list: requireAuth(),
177
+ get: requireAuth()
178
+ }, overrides);
179
+ }
180
+ //#endregion
181
+ //#region src/permissions/index.ts
182
+ /**
183
+ * Allow public access (no authentication required)
184
+ *
185
+ * @example
186
+ * ```typescript
187
+ * permissions: {
188
+ * list: allowPublic(),
189
+ * get: allowPublic(),
190
+ * }
191
+ * ```
192
+ */
193
+ function allowPublic() {
194
+ const check = () => true;
195
+ check._isPublic = true;
196
+ return check;
197
+ }
198
+ /**
199
+ * Require authentication (any authenticated user)
200
+ *
201
+ * @example
202
+ * ```typescript
203
+ * permissions: {
204
+ * create: requireAuth(),
205
+ * update: requireAuth(),
206
+ * }
207
+ * ```
208
+ */
209
+ function requireAuth() {
210
+ const check = (ctx) => {
211
+ if (!ctx.user) return {
212
+ granted: false,
213
+ reason: "Authentication required"
214
+ };
215
+ return true;
216
+ };
217
+ return check;
218
+ }
219
+ /**
220
+ * Require specific roles
221
+ *
222
+ * @param roles - Required roles (user needs at least one)
223
+ * @param options - Optional bypass roles
224
+ *
225
+ * @example
226
+ * ```typescript
227
+ * permissions: {
228
+ * create: requireRoles(['admin', 'editor']),
229
+ * delete: requireRoles(['admin']),
230
+ * }
231
+ *
232
+ * // With bypass roles
233
+ * permissions: {
234
+ * update: requireRoles(['owner'], { bypassRoles: ['admin', 'superadmin'] }),
235
+ * }
236
+ * ```
237
+ */
238
+ function requireRoles(roles, options) {
239
+ const check = (ctx) => {
240
+ if (!ctx.user) return {
241
+ granted: false,
242
+ reason: "Authentication required"
243
+ };
244
+ const userRoles = getUserRoles(ctx.user);
245
+ if (options?.bypassRoles?.some((r) => userRoles.includes(r))) return true;
246
+ if (roles.some((r) => userRoles.includes(r))) return true;
247
+ return {
248
+ granted: false,
249
+ reason: `Required roles: ${roles.join(", ")}`
250
+ };
251
+ };
252
+ check._roles = roles;
253
+ return check;
254
+ }
255
+ /**
256
+ * Require resource ownership
257
+ *
258
+ * Returns filters to scope queries to user's owned resources.
259
+ *
260
+ * @param ownerField - Field containing owner ID (default: 'userId')
261
+ * @param options - Optional bypass roles
262
+ *
263
+ * @example
264
+ * ```typescript
265
+ * permissions: {
266
+ * update: requireOwnership('userId'),
267
+ * delete: requireOwnership('createdBy', { bypassRoles: ['admin'] }),
268
+ * }
269
+ * ```
270
+ */
271
+ function requireOwnership(ownerField = "userId", options) {
272
+ return (ctx) => {
273
+ if (!ctx.user) return {
274
+ granted: false,
275
+ reason: "Authentication required"
276
+ };
277
+ const userRoles = getUserRoles(ctx.user);
278
+ if (options?.bypassRoles?.some((r) => userRoles.includes(r))) return true;
279
+ const userId = getUserId(getScope(ctx.request)) ?? ctx.user.id ?? ctx.user._id;
280
+ if (!userId) return {
281
+ granted: false,
282
+ reason: "User identity missing (no id or _id)"
283
+ };
284
+ return {
285
+ granted: true,
286
+ filters: { [ownerField]: userId }
287
+ };
288
+ };
289
+ }
290
+ /**
291
+ * Combine multiple checks - ALL must pass (AND logic)
292
+ *
293
+ * @example
294
+ * ```typescript
295
+ * permissions: {
296
+ * update: allOf(
297
+ * requireAuth(),
298
+ * requireRoles(['editor']),
299
+ * requireOwnership('createdBy')
300
+ * ),
301
+ * }
302
+ * ```
303
+ */
304
+ function allOf(...checks) {
305
+ return async (ctx) => {
306
+ let mergedFilters = {};
307
+ for (const check of checks) {
308
+ const result = await check(ctx);
309
+ const normalized = typeof result === "boolean" ? { granted: result } : result;
310
+ if (!normalized.granted) return normalized;
311
+ if (normalized.filters) mergedFilters = {
312
+ ...mergedFilters,
313
+ ...normalized.filters
314
+ };
315
+ }
316
+ return {
317
+ granted: true,
318
+ filters: Object.keys(mergedFilters).length > 0 ? mergedFilters : void 0
319
+ };
320
+ };
321
+ }
322
+ /**
323
+ * Combine multiple checks - ANY must pass (OR logic)
324
+ *
325
+ * @example
326
+ * ```typescript
327
+ * permissions: {
328
+ * update: anyOf(
329
+ * requireRoles(['admin']),
330
+ * requireOwnership('createdBy')
331
+ * ),
332
+ * }
333
+ * ```
334
+ */
335
+ function anyOf(...checks) {
336
+ return async (ctx) => {
337
+ const reasons = [];
338
+ for (const check of checks) {
339
+ const result = await check(ctx);
340
+ const normalized = typeof result === "boolean" ? { granted: result } : result;
341
+ if (normalized.granted) return normalized;
342
+ if (normalized.reason) reasons.push(normalized.reason);
343
+ }
344
+ return {
345
+ granted: false,
346
+ reason: reasons.join("; ")
347
+ };
348
+ };
349
+ }
350
+ /**
351
+ * Deny all access
352
+ *
353
+ * @example
354
+ * ```typescript
355
+ * permissions: {
356
+ * delete: denyAll('Deletion not allowed'),
357
+ * }
358
+ * ```
359
+ */
360
+ function denyAll(reason = "Access denied") {
361
+ return () => ({
362
+ granted: false,
363
+ reason
364
+ });
365
+ }
366
+ /**
367
+ * Dynamic permission based on context
368
+ *
369
+ * @example
370
+ * ```typescript
371
+ * permissions: {
372
+ * update: when((ctx) => ctx.data?.status === 'draft'),
373
+ * }
374
+ * ```
375
+ */
376
+ function when(condition) {
377
+ return async (ctx) => {
378
+ const result = await condition(ctx);
379
+ return {
380
+ granted: result,
381
+ reason: result ? void 0 : "Condition not met"
382
+ };
383
+ };
384
+ }
385
+ /** Read request.scope safely */
386
+ function getScope(request) {
387
+ return request.scope ?? PUBLIC_SCOPE;
388
+ }
389
+ /**
390
+ * Require membership in the active organization.
391
+ * User must be authenticated AND have an active org (member or elevated scope).
392
+ *
393
+ * Reads `request.scope` set by auth adapters.
394
+ *
395
+ * @example
396
+ * ```typescript
397
+ * permissions: {
398
+ * list: requireOrgMembership(),
399
+ * get: requireOrgMembership(),
400
+ * }
401
+ * ```
402
+ */
403
+ function requireOrgMembership() {
404
+ const check = (ctx) => {
405
+ if (!ctx.user) return {
406
+ granted: false,
407
+ reason: "Authentication required"
408
+ };
409
+ const scope = getScope(ctx.request);
410
+ if (isElevated(scope)) return true;
411
+ if (isMember(scope)) return true;
412
+ return {
413
+ granted: false,
414
+ reason: "Organization membership required"
415
+ };
416
+ };
417
+ check._orgPermission = "membership";
418
+ return check;
419
+ }
420
+ /**
421
+ * Require specific org-level roles.
422
+ * Reads `request.scope.orgRoles` (set by auth adapters).
423
+ * Elevated scope always passes (platform admin bypass).
424
+ *
425
+ * @param roles - Required org roles (user needs at least one)
426
+ *
427
+ * @example
428
+ * ```typescript
429
+ * permissions: {
430
+ * create: requireOrgRole('admin', 'owner'),
431
+ * delete: requireOrgRole('owner'),
432
+ * }
433
+ * ```
434
+ */
435
+ function requireOrgRole(...args) {
436
+ const roles = Array.isArray(args[0]) ? args[0] : args;
437
+ const check = (ctx) => {
438
+ if (!ctx.user) return {
439
+ granted: false,
440
+ reason: "Authentication required"
441
+ };
442
+ const scope = getScope(ctx.request);
443
+ if (isElevated(scope)) return true;
444
+ if (!isMember(scope)) return {
445
+ granted: false,
446
+ reason: "Organization membership required"
447
+ };
448
+ if (roles.some((r) => scope.orgRoles.includes(r))) return true;
449
+ return {
450
+ granted: false,
451
+ reason: `Required org roles: ${roles.join(", ")}`
452
+ };
453
+ };
454
+ check._orgRoles = roles;
455
+ return check;
456
+ }
457
+ /**
458
+ * Create a scoped permission system for resource-action patterns.
459
+ * Maps org roles to fine-grained permissions without external API calls.
460
+ *
461
+ * @example
462
+ * ```typescript
463
+ * const perms = createOrgPermissions({
464
+ * statements: {
465
+ * product: ['create', 'update', 'delete'],
466
+ * order: ['create', 'approve'],
467
+ * },
468
+ * roles: {
469
+ * owner: { product: ['create', 'update', 'delete'], order: ['create', 'approve'] },
470
+ * admin: { product: ['create', 'update'], order: ['create'] },
471
+ * member: { product: [], order: [] },
472
+ * },
473
+ * });
474
+ *
475
+ * defineResource({
476
+ * permissions: {
477
+ * create: perms.can({ product: ['create'] }),
478
+ * delete: perms.can({ product: ['delete'] }),
479
+ * }
480
+ * });
481
+ * ```
482
+ */
483
+ function createOrgPermissions(config) {
484
+ const { roles: roleMap } = config;
485
+ function hasPermissions(orgRoles, required) {
486
+ for (const [resource, actions] of Object.entries(required)) for (const action of actions) if (!orgRoles.some((role) => {
487
+ return (roleMap[role]?.[resource])?.includes(action);
488
+ })) return false;
489
+ return true;
490
+ }
491
+ return {
492
+ can(permissions) {
493
+ return (ctx) => {
494
+ if (!ctx.user) return {
495
+ granted: false,
496
+ reason: "Authentication required"
497
+ };
498
+ const scope = getScope(ctx.request);
499
+ if (isElevated(scope)) return true;
500
+ if (!isMember(scope)) return {
501
+ granted: false,
502
+ reason: "Organization membership required"
503
+ };
504
+ if (hasPermissions(scope.orgRoles, permissions)) return true;
505
+ return {
506
+ granted: false,
507
+ reason: `Missing permissions: ${Object.entries(permissions).map(([r, a]) => `${r}:[${a.join(",")}]`).join(", ")}`
508
+ };
509
+ };
510
+ },
511
+ requireRole(...roles) {
512
+ return requireOrgRole(roles);
513
+ },
514
+ requireMembership() {
515
+ return requireOrgMembership();
516
+ },
517
+ requireTeamMembership() {
518
+ return requireTeamMembership();
519
+ }
520
+ };
521
+ }
522
+ /**
523
+ * Create a dynamic role-based permission matrix.
524
+ *
525
+ * Use this when role/action mappings are managed outside code
526
+ * (e.g., admin UI matrix, DB-stored ACLs, remote policy service).
527
+ *
528
+ * Supports:
529
+ * - org role union (any assigned org role can grant)
530
+ * - global bypass roles
531
+ * - wildcard resource/action (`*`)
532
+ * - optional in-memory cache
533
+ */
534
+ function createDynamicPermissionMatrix(config) {
535
+ const logger = config.logger ?? console;
536
+ const legacyTtlMs = config.cache?.ttlMs ?? 0;
537
+ const hasExternalStore = !!config.cacheStore;
538
+ const cacheTtlMs = legacyTtlMs > 0 ? legacyTtlMs : hasExternalStore ? 3e5 : 0;
539
+ const internalStore = !config.cacheStore && cacheTtlMs > 0 ? new MemoryCacheStore({
540
+ defaultTtlMs: cacheTtlMs,
541
+ maxEntries: config.cache?.maxEntries ?? 1e3
542
+ }) : void 0;
543
+ const cacheStore = config.cacheStore ?? internalStore;
544
+ const trackedKeys = /* @__PURE__ */ new Set();
545
+ const nodeId = randomUUID().slice(0, 8);
546
+ const DEFAULT_EVENT_TYPE = "arc.permissions.invalidated";
547
+ let eventBridge = null;
548
+ /** Clear local cache for an org without publishing events (avoids infinite loops). */
549
+ async function localInvalidateByOrg(orgId) {
550
+ if (!cacheStore) return;
551
+ const prefix = `${orgId}::`;
552
+ const toDelete = [];
553
+ for (const key of trackedKeys) if (key.startsWith(prefix)) toDelete.push(key);
554
+ for (const key of toDelete) try {
555
+ await cacheStore.delete(key);
556
+ trackedKeys.delete(key);
557
+ } catch (error) {
558
+ logger.warn(`[DynamicPermissionMatrix] invalidateByOrg delete failed for '${key}': ${error instanceof Error ? error.message : String(error)}`);
559
+ }
560
+ }
561
+ function isActionAllowed(actions, action) {
562
+ if (!actions || actions.length === 0) return false;
563
+ return actions.includes("*") || actions.includes(action);
564
+ }
565
+ function roleAllows(matrix, role, resource, action) {
566
+ const rolePermissions = matrix[role];
567
+ if (!rolePermissions) return false;
568
+ const resourceActions = rolePermissions[resource];
569
+ const wildcardResourceActions = rolePermissions["*"];
570
+ return isActionAllowed(resourceActions, action) || isActionAllowed(wildcardResourceActions, action);
571
+ }
572
+ function buildDefaultCacheKey(ctx, orgId, orgRoles) {
573
+ const userId = String(ctx.user?.id ?? ctx.user?._id ?? "anon");
574
+ const roles = (orgRoles ?? []).slice().sort().join(",");
575
+ return `${orgId ?? "no-org"}::${roles}::${userId}`;
576
+ }
577
+ async function resolveMatrix(ctx, orgId, orgRoles) {
578
+ if (!cacheStore) return config.resolveRolePermissions(ctx);
579
+ const cacheKey = config.cache?.key?.(ctx) ?? buildDefaultCacheKey(ctx, orgId, orgRoles);
580
+ if (!cacheKey) return config.resolveRolePermissions(ctx);
581
+ try {
582
+ const hit = await cacheStore.get(cacheKey);
583
+ if (hit) return hit;
584
+ } catch (error) {
585
+ logger.warn(`[DynamicPermissionMatrix] Cache get failed for '${cacheKey}': ${error instanceof Error ? error.message : String(error)}`);
586
+ }
587
+ const value = await config.resolveRolePermissions(ctx);
588
+ try {
589
+ await cacheStore.set(cacheKey, value, { ttlMs: cacheTtlMs });
590
+ trackedKeys.add(cacheKey);
591
+ const maxTracked = config.cache?.maxEntries ?? 1e4;
592
+ if (trackedKeys.size > maxTracked) {
593
+ const overflow = trackedKeys.size - maxTracked;
594
+ const iter = trackedKeys.values();
595
+ for (let i = 0; i < overflow; i++) {
596
+ const oldest = iter.next().value;
597
+ if (oldest) trackedKeys.delete(oldest);
598
+ }
599
+ }
600
+ } catch (error) {
601
+ logger.warn(`[DynamicPermissionMatrix] Cache set failed for '${cacheKey}': ${error instanceof Error ? error.message : String(error)}`);
602
+ }
603
+ return value;
604
+ }
605
+ function can(required) {
606
+ return async (ctx) => {
607
+ if (!ctx.user) return {
608
+ granted: false,
609
+ reason: "Authentication required"
610
+ };
611
+ const scope = getScope(ctx.request);
612
+ if (isElevated(scope)) return true;
613
+ if (!isMember(scope)) return {
614
+ granted: false,
615
+ reason: "Organization membership required"
616
+ };
617
+ const orgRoles = scope.orgRoles;
618
+ if (orgRoles.length === 0) return {
619
+ granted: false,
620
+ reason: "Not a member of this organization"
621
+ };
622
+ let matrix;
623
+ try {
624
+ matrix = await resolveMatrix(ctx, scope.organizationId, orgRoles);
625
+ } catch (error) {
626
+ return {
627
+ granted: false,
628
+ reason: `Permission matrix resolution failed: ${error instanceof Error ? error.message : String(error)}`
629
+ };
630
+ }
631
+ for (const [resource, actions] of Object.entries(required)) for (const action of actions) if (!orgRoles.some((role) => roleAllows(matrix, role, resource, action))) return {
632
+ granted: false,
633
+ reason: `Missing permission: ${resource}:${action}`
634
+ };
635
+ return true;
636
+ };
637
+ }
638
+ return {
639
+ can,
640
+ canAction(resource, action) {
641
+ return can({ [resource]: [action] });
642
+ },
643
+ requireRole(...roles) {
644
+ return requireOrgRole(roles);
645
+ },
646
+ requireMembership() {
647
+ return requireOrgMembership();
648
+ },
649
+ requireTeamMembership() {
650
+ return requireTeamMembership();
651
+ },
652
+ async invalidateByOrg(orgId) {
653
+ await localInvalidateByOrg(orgId);
654
+ if (eventBridge) try {
655
+ await eventBridge.publish(eventBridge.eventType, {
656
+ orgId,
657
+ nodeId
658
+ });
659
+ } catch (error) {
660
+ logger.warn(`[DynamicPermissionMatrix] Failed to publish invalidation event for org '${orgId}': ${error instanceof Error ? error.message : String(error)}`);
661
+ }
662
+ },
663
+ async clearCache() {
664
+ if (!cacheStore) return;
665
+ if (cacheStore.clear) try {
666
+ await cacheStore.clear();
667
+ trackedKeys.clear();
668
+ return;
669
+ } catch (error) {
670
+ logger.warn(`[DynamicPermissionMatrix] cacheStore.clear failed: ${error instanceof Error ? error.message : String(error)}`);
671
+ }
672
+ for (const key of trackedKeys) try {
673
+ await cacheStore.delete(key);
674
+ } catch (error) {
675
+ logger.warn(`[DynamicPermissionMatrix] Cache delete failed for '${key}': ${error instanceof Error ? error.message : String(error)}`);
676
+ }
677
+ trackedKeys.clear();
678
+ },
679
+ async connectEvents(events, options) {
680
+ if (eventBridge) await this.disconnectEvents();
681
+ const eventType = options?.eventType ?? DEFAULT_EVENT_TYPE;
682
+ const unsubscribeFn = await events.subscribe(eventType, async (event) => {
683
+ const payload = event.payload;
684
+ if (!payload?.orgId) return;
685
+ if (payload.nodeId === nodeId) return;
686
+ await localInvalidateByOrg(payload.orgId);
687
+ if (options?.onRemoteInvalidation) try {
688
+ await options.onRemoteInvalidation(payload.orgId);
689
+ } catch (error) {
690
+ logger.warn(`[DynamicPermissionMatrix] onRemoteInvalidation callback failed for org '${payload.orgId}': ${error instanceof Error ? error.message : String(error)}`);
691
+ }
692
+ });
693
+ eventBridge = {
694
+ publish: events.publish,
695
+ unsubscribe: typeof unsubscribeFn === "function" ? unsubscribeFn : null,
696
+ eventType,
697
+ onRemoteInvalidation: options?.onRemoteInvalidation
698
+ };
699
+ },
700
+ async disconnectEvents() {
701
+ if (!eventBridge) return;
702
+ try {
703
+ eventBridge.unsubscribe?.();
704
+ } catch (error) {
705
+ logger.warn(`[DynamicPermissionMatrix] disconnectEvents unsubscribe failed: ${error instanceof Error ? error.message : String(error)}`);
706
+ }
707
+ eventBridge = null;
708
+ },
709
+ get eventsConnected() {
710
+ return eventBridge !== null;
711
+ }
712
+ };
713
+ }
714
+ /**
715
+ * Require membership in the active team.
716
+ * User must be authenticated, a member of the active org, AND have an active team.
717
+ *
718
+ * Better Auth teams are flat member groups (no team-level roles).
719
+ * Reads `request.scope.teamId` set by the Better Auth adapter.
720
+ *
721
+ * @example
722
+ * ```typescript
723
+ * permissions: {
724
+ * list: requireTeamMembership(),
725
+ * create: requireTeamMembership(),
726
+ * }
727
+ * ```
728
+ */
729
+ function requireTeamMembership() {
730
+ const check = (ctx) => {
731
+ if (!ctx.user) return {
732
+ granted: false,
733
+ reason: "Authentication required"
734
+ };
735
+ const scope = getScope(ctx.request);
736
+ if (isElevated(scope)) return true;
737
+ if (!isMember(scope)) return {
738
+ granted: false,
739
+ reason: "Organization membership required"
740
+ };
741
+ if (!getTeamId(scope)) return {
742
+ granted: false,
743
+ reason: "No active team"
744
+ };
745
+ return true;
746
+ };
747
+ check._teamPermission = "membership";
748
+ return check;
749
+ }
750
+ //#endregion
751
+ export { createRoleHierarchy as S, ownerWithAdminBypass as _, createOrgPermissions as a, publicReadAdminWrite as b, requireOrgMembership as c, requireRoles as d, requireTeamMembership as f, fullPublic as g, authenticated as h, createDynamicPermissionMatrix as i, requireOrgRole as l, adminOnly as m, allowPublic as n, denyAll as o, when as p, anyOf as r, requireAuth as s, allOf as t, requireOwnership as u, presets_exports as v, readOnly as x, publicRead as y };