@classytic/arc 2.2.5 → 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) 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-BKHSl2nT.mjs → createApp-ByWNRsZj.mjs} +65 -36
  39. package/dist/{defineResource-DO9ONe_D.mjs → defineResource-D9aY5Cy6.mjs} +154 -1165
  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-CyAA2zlB.d.mts → index-BL8CaQih.d.mts} +56 -57
  71. package/dist/index-Diqcm14c.d.mts +369 -0
  72. package/dist/{prisma-xjhMEq_S.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.mjs +3 -7
  99. package/dist/{mongodb-DNKEExbf.mjs → mongodb-BuQ7fNTg.mjs} +1 -4
  100. package/dist/{mongodb-ClykrfGo.d.mts → mongodb-CUpYfxfD.d.mts} +2 -3
  101. package/dist/{mongodb-Dg8O_gvd.d.mts → mongodb-bga9AbkD.d.mts} +2 -2
  102. package/dist/{openapi-9nB_kiuR.mjs → openapi-CBmZ6EQN.mjs} +4 -21
  103. package/dist/org/index.d.mts +12 -14
  104. package/dist/org/index.mjs +92 -119
  105. package/dist/org/types.d.mts +2 -2
  106. package/dist/org/types.mjs +1 -1
  107. package/dist/permissions/index.d.mts +4 -278
  108. package/dist/permissions/index.mjs +4 -579
  109. package/dist/permissions-CA5zg0yK.mjs +751 -0
  110. package/dist/plugins/index.d.mts +104 -107
  111. package/dist/plugins/index.mjs +203 -313
  112. package/dist/plugins/response-cache.mjs +4 -69
  113. package/dist/plugins/tracing-entry.d.mts +1 -1
  114. package/dist/plugins/tracing-entry.mjs +24 -11
  115. package/dist/{pluralize-CM-jZg7p.mjs → pluralize-CcT6qF0a.mjs} +12 -13
  116. package/dist/policies/index.d.mts +2 -2
  117. package/dist/policies/index.mjs +80 -83
  118. package/dist/presets/index.d.mts +26 -19
  119. package/dist/presets/index.mjs +2 -142
  120. package/dist/presets/multiTenant.d.mts +1 -4
  121. package/dist/presets/multiTenant.mjs +4 -6
  122. package/dist/presets-C9QXJV1u.mjs +422 -0
  123. package/dist/{queryCachePlugin-B6R0d4av.mjs → queryCachePlugin-ClosZdNS.mjs} +6 -27
  124. package/dist/{queryCachePlugin-Q6SYuHZ6.d.mts → queryCachePlugin-DcmETvcB.d.mts} +3 -3
  125. package/dist/queryParser-CgCtsjti.mjs +352 -0
  126. package/dist/{redis-UwjEp8Ea.d.mts → redis-CQ5YxMC5.d.mts} +2 -2
  127. package/dist/{redis-stream-CBg0upHI.d.mts → redis-stream-BW9UKLZM.d.mts} +9 -2
  128. package/dist/registry/index.d.mts +1 -4
  129. package/dist/registry/index.mjs +3 -4
  130. package/dist/{introspectionPlugin-B3JkrjwU.mjs → registry-I-ogLgL9.mjs} +1 -8
  131. package/dist/{requestContext-xi6OKBL-.mjs → requestContext-DYtmNpm5.mjs} +1 -3
  132. package/dist/resourceToTools-B6ZN9Ing.mjs +489 -0
  133. package/dist/rpc/index.d.mts +90 -0
  134. package/dist/rpc/index.mjs +248 -0
  135. package/dist/{schemaConverter-Dtg0Kt9T.mjs → schemaConverter-DjzHpFam.mjs} +1 -2
  136. package/dist/schemas/index.d.mts +30 -30
  137. package/dist/schemas/index.mjs +4 -6
  138. package/dist/scope/index.d.mts +13 -2
  139. package/dist/scope/index.mjs +18 -5
  140. package/dist/{sessionManager-D_iEHjQl.d.mts → sessionManager-wbkYj2HL.d.mts} +2 -2
  141. package/dist/{sse-DkqQ1uxb.mjs → sse-BkViJPlT.mjs} +4 -25
  142. package/dist/testing/index.d.mts +551 -567
  143. package/dist/testing/index.mjs +1744 -1799
  144. package/dist/{tracing-8CEbhF0w.d.mts → tracing-bz_U4EM1.d.mts} +6 -1
  145. package/dist/{typeGuards-DwxA1t_L.mjs → typeGuards-Cj5Rgvlg.mjs} +1 -2
  146. package/dist/types/index.d.mts +4 -946
  147. package/dist/types/index.mjs +2 -4
  148. package/dist/types-BJmgxNbF.d.mts +275 -0
  149. package/dist/{types-RLkFVgaw.d.mts → types-BNUccdcf.d.mts} +2 -2
  150. package/dist/{types-Beqn1Un7.mjs → types-C6TQjtdi.mjs} +30 -2
  151. package/dist/{types-DMSBMkaZ.d.mts → types-Dt0-AI6E.d.mts} +85 -27
  152. package/dist/{types-DelU6kln.mjs → types-ZUu_h0jp.mjs} +1 -2
  153. package/dist/utils/index.d.mts +255 -352
  154. package/dist/utils/index.mjs +7 -6
  155. package/dist/utils-Dc0WhlIl.mjs +594 -0
  156. package/dist/versioning-BzfeHmhj.mjs +37 -0
  157. package/package.json +46 -12
  158. package/skills/arc/SKILL.md +506 -0
  159. package/skills/arc/references/auth.md +250 -0
  160. package/skills/arc/references/events.md +272 -0
  161. package/skills/arc/references/integrations.md +385 -0
  162. package/skills/arc/references/mcp.md +386 -0
  163. package/skills/arc/references/production.md +610 -0
  164. package/skills/arc/references/testing.md +183 -0
  165. package/dist/audited-CGdLiSlE.mjs +0 -140
  166. package/dist/chunk-C7Uep-_p.mjs +0 -20
  167. package/dist/circuitBreaker-DYhWBW_D.mjs +0 -1096
  168. package/dist/errorHandler-CW3OOeYq.d.mts +0 -72
  169. package/dist/interface-DZYNK9bb.d.mts +0 -1112
  170. package/dist/presets-BTeYbw7h.d.mts +0 -57
  171. package/dist/presets-CeFtfDR8.mjs +0 -119
  172. /package/dist/{errors-DAWRdiYP.d.mts → errors-CPpvPHT0.d.mts} +0 -0
  173. /package/dist/{externalPaths-SyPF2tgK.d.mts → externalPaths-DpO-s7r8.d.mts} +0 -0
  174. /package/dist/{interface-DTbsvIWe.d.mts → interface-D_BWALyZ.d.mts} +0 -0
@@ -1,7 +1,20 @@
1
1
  import { createRequire } from "node:module";
2
2
  import fp from "fastify-plugin";
3
-
4
3
  //#region src/plugins/tracing.ts
4
+ /**
5
+ * OpenTelemetry Distributed Tracing Plugin
6
+ *
7
+ * Traces HTTP requests, repository operations, and MongoDB queries
8
+ * across the entire application lifecycle.
9
+ *
10
+ * @example
11
+ * import { tracingPlugin } from '@classytic/arc/plugins';
12
+ *
13
+ * await fastify.register(tracingPlugin, {
14
+ * serviceName: 'my-api',
15
+ * exporterUrl: 'http://localhost:4318/v1/traces', // OTLP endpoint
16
+ * });
17
+ */
5
18
  const require = createRequire(import.meta.url);
6
19
  let trace;
7
20
  let context;
@@ -24,17 +37,18 @@ try {
24
37
  require("@opentelemetry/instrumentation-mongodb").MongoDBInstrumentation;
25
38
  getNodeAutoInstrumentations = require("@opentelemetry/auto-instrumentations-node").getNodeAutoInstrumentations;
26
39
  isAvailable = true;
27
- } catch (e) {}
40
+ } catch (_e) {}
28
41
  /**
29
42
  * Create a tracer provider
30
43
  */
31
44
  function createTracerProvider(options) {
32
45
  if (!isAvailable) return null;
33
- const { serviceName = "@classytic/arc", exporterUrl = "http://localhost:4318/v1/traces" } = options;
46
+ const { serviceName = "@classytic/arc", serviceVersion, exporterUrl = "http://localhost:4318/v1/traces" } = options;
47
+ const resolvedVersion = serviceVersion ?? "2.4.1";
34
48
  const exporter = new OTLPTraceExporter({ url: exporterUrl });
35
49
  const provider = new NodeTracerProvider({ resource: { attributes: {
36
50
  "service.name": serviceName,
37
- "service.version": "1.0.0"
51
+ "service.version": resolvedVersion
38
52
  } } });
39
53
  provider.addSpanProcessor(new BatchSpanProcessor(exporter));
40
54
  provider.register();
@@ -61,7 +75,7 @@ async function tracingPlugin(fastify, options = {}) {
61
75
  }
62
76
  const tracer = trace.getTracer(serviceName);
63
77
  fastify.decorateRequest("tracer", void 0);
64
- fastify.addHook("onRequest", async (request, reply) => {
78
+ fastify.addHook("onRequest", async (request, _reply) => {
65
79
  if (Math.random() > sampleRate) return;
66
80
  const span = tracer.startSpan(`HTTP ${request.method} ${request.url}`, {
67
81
  kind: 1,
@@ -94,7 +108,7 @@ async function tracingPlugin(fastify, options = {}) {
94
108
  else span.setStatus({ code: SpanStatusCode.OK });
95
109
  span.end();
96
110
  });
97
- fastify.addHook("onError", async (request, reply, error) => {
111
+ fastify.addHook("onError", async (request, _reply, error) => {
98
112
  if (!request.tracer?.currentSpan) return;
99
113
  const span = request.tracer.currentSpan;
100
114
  span.recordException(error);
@@ -119,7 +133,7 @@ async function tracingPlugin(fastify, options = {}) {
119
133
  * }
120
134
  */
121
135
  function createSpan(request, name, fn, attributes) {
122
- if (!isAvailable || !request.tracer) return fn(null);
136
+ if (!request.tracer) return fn(null);
123
137
  const { tracer, currentSpan } = request.tracer;
124
138
  const span = tracer.startSpan(name, {
125
139
  parent: currentSpan,
@@ -154,10 +168,10 @@ function createSpan(request, name, fn, attributes) {
154
168
  * }
155
169
  */
156
170
  function traced(spanName) {
157
- return function(target, propertyKey, descriptor) {
171
+ return (target, propertyKey, descriptor) => {
158
172
  const originalMethod = descriptor.value;
159
173
  descriptor.value = async function(...args) {
160
- const request = args.find((arg) => arg && arg.tracer);
174
+ const request = args.find((arg) => arg?.tracer);
161
175
  if (!request?.tracer) return originalMethod.apply(this, args);
162
176
  return createSpan(request, spanName || `${target.constructor.name}.${propertyKey}`, async (span) => {
163
177
  if (span) {
@@ -180,6 +194,5 @@ var tracing_default = fp(tracingPlugin, {
180
194
  name: "arc-tracing",
181
195
  fastify: "5.x"
182
196
  });
183
-
184
197
  //#endregion
185
- export { createSpan, isTracingAvailable, traced, tracing_default as tracingPlugin };
198
+ export { createSpan, isTracingAvailable, traced, tracing_default as tracingPlugin };
@@ -1,4 +1,4 @@
1
- //#region src/cli/utils/pluralize.ts
1
+ //#region src/utils/pluralize.ts
2
2
  /**
3
3
  * Lightweight English pluralization for the Arc CLI.
4
4
  *
@@ -58,12 +58,12 @@ function pluralize(word) {
58
58
  if (UNCOUNTABLES.has(lower)) return word;
59
59
  if (IRREGULARS[lower]) {
60
60
  const plural = IRREGULARS[lower];
61
- return word[0] === word[0].toUpperCase() ? plural.charAt(0).toUpperCase() + plural.slice(1) : plural;
61
+ return word[0] === word[0]?.toUpperCase() ? plural.charAt(0).toUpperCase() + plural.slice(1) : plural;
62
62
  }
63
- if (lower.endsWith("fe")) return word.slice(0, -2) + "ves";
64
- if (lower.endsWith("f") && !lower.endsWith("ff") && !lower.endsWith("roof") && !lower.endsWith("chief") && !lower.endsWith("belief")) return word.slice(0, -1) + "ves";
65
- if (lower.endsWith("y") && !/[aeiou]y$/i.test(lower)) return word.slice(0, -1) + "ies";
66
- if (lower.endsWith("is")) return word.slice(0, -2) + "es";
63
+ if (lower.endsWith("fe")) return `${word.slice(0, -2)}ves`;
64
+ if (lower.endsWith("f") && !lower.endsWith("ff") && !lower.endsWith("roof") && !lower.endsWith("chief") && !lower.endsWith("belief")) return `${word.slice(0, -1)}ves`;
65
+ if (lower.endsWith("y") && !/[aeiou]y$/i.test(lower)) return `${word.slice(0, -1)}ies`;
66
+ if (lower.endsWith("is")) return `${word.slice(0, -2)}es`;
67
67
  if (new Set([
68
68
  "cactus",
69
69
  "stimulus",
@@ -75,12 +75,11 @@ function pluralize(word) {
75
75
  "alumnus",
76
76
  "terminus",
77
77
  "bacillus"
78
- ]).has(lower)) return word.slice(0, -2) + "i";
79
- if (lower.endsWith("z") && !lower.endsWith("zz")) return word + "zes";
80
- if (/(?:s|sh|ch|x|zz)$/i.test(lower)) return word + "es";
81
- if (lower.endsWith("o") && !/[aeiou]o$/i.test(lower)) return word + "es";
82
- return word + "s";
78
+ ]).has(lower)) return `${word.slice(0, -2)}i`;
79
+ if (lower.endsWith("z") && !lower.endsWith("zz")) return `${word}zes`;
80
+ if (/(?:s|sh|ch|x|zz)$/i.test(lower)) return `${word}es`;
81
+ if (lower.endsWith("o") && !/[aeiou]o$/i.test(lower)) return `${word}es`;
82
+ return `${word}s`;
83
83
  }
84
-
85
84
  //#endregion
86
- export { pluralize as t };
85
+ export { pluralize as t };
@@ -1,4 +1,4 @@
1
- import { t as PermissionCheck } from "../types-RLkFVgaw.mjs";
1
+ import { t as PermissionCheck } from "../types-BNUccdcf.mjs";
2
2
  import { FastifyReply, FastifyRequest } from "fastify";
3
3
 
4
4
  //#region src/policies/PolicyInterface.d.ts
@@ -317,7 +317,7 @@ interface AccessControlPolicyOptions {
317
317
  * ```
318
318
  */
319
319
  declare function createAccessControlPolicy(options: AccessControlPolicyOptions): PermissionCheck;
320
- declare module 'fastify' {
320
+ declare module "fastify" {
321
321
  interface FastifyRequest {
322
322
  policyResult?: PolicyResult;
323
323
  }
@@ -1,82 +1,3 @@
1
- //#region src/policies/PolicyInterface.ts
2
- /**
3
- * Create a PermissionCheck from access control statements.
4
- *
5
- * Maps Better Auth's statement-based access control model to Arc's
6
- * PermissionCheck function, which can be used directly in resource permissions.
7
- *
8
- * The returned PermissionCheck:
9
- * 1. Looks up the resource + action in the statements list
10
- * 2. If no matching statement exists, denies access
11
- * 3. If a matching statement exists and `checkPermission` is provided,
12
- * calls it for dynamic verification (e.g., check org role)
13
- * 4. If `checkPermission` is not provided, allows access based on static statements
14
- *
15
- * @example Static statements only
16
- * ```typescript
17
- * import { createAccessControlPolicy } from '@classytic/arc/policies';
18
- *
19
- * const editorPermissions = createAccessControlPolicy({
20
- * statements: [
21
- * { resource: 'product', action: ['create', 'update'] },
22
- * { resource: 'order', action: ['read'] },
23
- * ],
24
- * });
25
- *
26
- * // Use in resource config
27
- * defineResource({
28
- * name: 'product',
29
- * permissions: {
30
- * create: editorPermissions,
31
- * update: editorPermissions,
32
- * },
33
- * });
34
- * ```
35
- *
36
- * @example With dynamic permission check (Better Auth org roles)
37
- * ```typescript
38
- * const policy = createAccessControlPolicy({
39
- * statements: [
40
- * { resource: 'product', action: ['create', 'update'] },
41
- * { resource: 'order', action: ['read'] },
42
- * ],
43
- * checkPermission: async (userId, resource, action) => {
44
- * return hasOrgPermission(userId, resource, action);
45
- * },
46
- * });
47
- * ```
48
- */
49
- function createAccessControlPolicy(options) {
50
- const statementMap = /* @__PURE__ */ new Map();
51
- for (const statement of options.statements) {
52
- const existing = statementMap.get(statement.resource);
53
- if (existing) for (const action of statement.action) existing.add(action);
54
- else statementMap.set(statement.resource, new Set(statement.action));
55
- }
56
- const permissionCheck = async (context) => {
57
- const { user, resource, action } = context;
58
- const allowedActions = statementMap.get(resource);
59
- if (!allowedActions || !allowedActions.has(action)) return {
60
- granted: false,
61
- reason: `Action '${action}' is not permitted on resource '${resource}'`
62
- };
63
- if (options.checkPermission) {
64
- const userId = user?.id ?? user?._id;
65
- if (!userId) return {
66
- granted: false,
67
- reason: "Authentication required"
68
- };
69
- if (!await options.checkPermission(String(userId), resource, action)) return {
70
- granted: false,
71
- reason: `User does not have '${action}' permission on '${resource}'`
72
- };
73
- }
74
- return { granted: true };
75
- };
76
- return permissionCheck;
77
- }
78
-
79
- //#endregion
80
1
  //#region src/policies/helpers.ts
81
2
  /**
82
3
  * Helper to create Fastify middleware from any PolicyEngine implementation
@@ -170,7 +91,7 @@ function combinePolicies(...policies) {
170
91
  const allExcludes = /* @__PURE__ */ new Set();
171
92
  const allIncludes = [];
172
93
  for (const result of results) {
173
- if (result.fieldMask?.exclude) result.fieldMask.exclude.forEach((field) => allExcludes.add(field));
94
+ if (result.fieldMask?.exclude) for (const field of result.fieldMask.exclude) allExcludes.add(field);
174
95
  if (result.fieldMask?.include) allIncludes.push(new Set(result.fieldMask.include));
175
96
  }
176
97
  if (allExcludes.size > 0 || allIncludes.length > 0) {
@@ -306,7 +227,7 @@ function denyAll(reason = "Operation not allowed") {
306
227
  };
307
228
  },
308
229
  toMiddleware() {
309
- return async (request, reply) => {
230
+ return async (_request, reply) => {
310
231
  return reply.code(403).send({
311
232
  success: false,
312
233
  error: "Access denied",
@@ -316,6 +237,82 @@ function denyAll(reason = "Operation not allowed") {
316
237
  }
317
238
  };
318
239
  }
319
-
320
240
  //#endregion
321
- export { allowAll, anyPolicy, combinePolicies, createAccessControlPolicy, createPolicyMiddleware, denyAll };
241
+ //#region src/policies/PolicyInterface.ts
242
+ /**
243
+ * Create a PermissionCheck from access control statements.
244
+ *
245
+ * Maps Better Auth's statement-based access control model to Arc's
246
+ * PermissionCheck function, which can be used directly in resource permissions.
247
+ *
248
+ * The returned PermissionCheck:
249
+ * 1. Looks up the resource + action in the statements list
250
+ * 2. If no matching statement exists, denies access
251
+ * 3. If a matching statement exists and `checkPermission` is provided,
252
+ * calls it for dynamic verification (e.g., check org role)
253
+ * 4. If `checkPermission` is not provided, allows access based on static statements
254
+ *
255
+ * @example Static statements only
256
+ * ```typescript
257
+ * import { createAccessControlPolicy } from '@classytic/arc/policies';
258
+ *
259
+ * const editorPermissions = createAccessControlPolicy({
260
+ * statements: [
261
+ * { resource: 'product', action: ['create', 'update'] },
262
+ * { resource: 'order', action: ['read'] },
263
+ * ],
264
+ * });
265
+ *
266
+ * // Use in resource config
267
+ * defineResource({
268
+ * name: 'product',
269
+ * permissions: {
270
+ * create: editorPermissions,
271
+ * update: editorPermissions,
272
+ * },
273
+ * });
274
+ * ```
275
+ *
276
+ * @example With dynamic permission check (Better Auth org roles)
277
+ * ```typescript
278
+ * const policy = createAccessControlPolicy({
279
+ * statements: [
280
+ * { resource: 'product', action: ['create', 'update'] },
281
+ * { resource: 'order', action: ['read'] },
282
+ * ],
283
+ * checkPermission: async (userId, resource, action) => {
284
+ * return hasOrgPermission(userId, resource, action);
285
+ * },
286
+ * });
287
+ * ```
288
+ */
289
+ function createAccessControlPolicy(options) {
290
+ const statementMap = /* @__PURE__ */ new Map();
291
+ for (const statement of options.statements) {
292
+ const existing = statementMap.get(statement.resource);
293
+ if (existing) for (const action of statement.action) existing.add(action);
294
+ else statementMap.set(statement.resource, new Set(statement.action));
295
+ }
296
+ const permissionCheck = async (context) => {
297
+ const { user, resource, action } = context;
298
+ if (!statementMap.get(resource)?.has(action)) return {
299
+ granted: false,
300
+ reason: `Action '${action}' is not permitted on resource '${resource}'`
301
+ };
302
+ if (options.checkPermission) {
303
+ const userId = user?.id ?? user?._id;
304
+ if (!userId) return {
305
+ granted: false,
306
+ reason: "Authentication required"
307
+ };
308
+ if (!await options.checkPermission(String(userId), resource, action)) return {
309
+ granted: false,
310
+ reason: `User does not have '${action}' permission on '${resource}'`
311
+ };
312
+ }
313
+ return { granted: true };
314
+ };
315
+ return permissionCheck;
316
+ }
317
+ //#endregion
318
+ export { allowAll, anyPolicy, combinePolicies, createAccessControlPolicy, createPolicyMiddleware, denyAll };
@@ -1,29 +1,20 @@
1
- import "../elevation-DGo5shaX.mjs";
2
- import { b as IControllerResponse, k as PaginatedResult, x as IRequestContext } from "../interface-DZYNK9bb.mjs";
3
- import "../types-RLkFVgaw.mjs";
4
- import { AnyRecord, PresetResult, ResourceConfig } from "../types/index.mjs";
1
+ import { Mt as IControllerResponse, Nt as IRequestContext, Vt as PaginatedResult, Y as PresetResult, it as ResourceConfig, l as AnyRecord } from "../interface-DGmPxakH.mjs";
5
2
  import multiTenantPreset, { MultiTenantOptions } from "./multiTenant.mjs";
6
3
 
7
- //#region src/presets/softDelete.d.ts
8
- declare function softDeletePreset(): PresetResult;
9
- //#endregion
10
- //#region src/presets/slugLookup.d.ts
11
- interface SlugLookupOptions {
12
- slugField?: string;
13
- }
14
- declare function slugLookupPreset(options?: SlugLookupOptions): PresetResult;
15
- //#endregion
16
4
  //#region src/presets/ownedByUser.d.ts
17
5
  interface OwnedByUserOptions {
18
6
  ownerField?: string;
19
7
  }
20
8
  declare function ownedByUserPreset(options?: OwnedByUserOptions): PresetResult;
21
9
  //#endregion
22
- //#region src/presets/tree.d.ts
23
- interface TreeOptions {
24
- parentField?: string;
10
+ //#region src/presets/slugLookup.d.ts
11
+ interface SlugLookupOptions {
12
+ slugField?: string;
25
13
  }
26
- declare function treePreset(options?: TreeOptions): PresetResult;
14
+ declare function slugLookupPreset(options?: SlugLookupOptions): PresetResult;
15
+ //#endregion
16
+ //#region src/presets/softDelete.d.ts
17
+ declare function softDeletePreset(): PresetResult;
27
18
  //#endregion
28
19
  //#region src/presets/audited.d.ts
29
20
  interface AuditedPresetOptions {
@@ -37,6 +28,22 @@ interface AuditedPresetOptions {
37
28
  */
38
29
  declare function auditedPreset(options?: AuditedPresetOptions): PresetResult;
39
30
  //#endregion
31
+ //#region src/presets/bulk.d.ts
32
+ type BulkOperation = "createMany" | "updateMany" | "deleteMany";
33
+ interface BulkPresetOptions {
34
+ /** Which bulk operations to enable (default: all three) */
35
+ operations?: BulkOperation[];
36
+ /** Max items per bulk create (default: 1000) */
37
+ maxCreateItems?: number;
38
+ }
39
+ declare function bulkPreset(opts?: BulkPresetOptions): PresetResult;
40
+ //#endregion
41
+ //#region src/presets/tree.d.ts
42
+ interface TreeOptions {
43
+ parentField?: string;
44
+ }
45
+ declare function treePreset(options?: TreeOptions): PresetResult;
46
+ //#endregion
40
47
  //#region src/presets/types.d.ts
41
48
  /**
42
49
  * Soft Delete Preset Interface
@@ -228,7 +235,7 @@ type IAuditedPreset = never;
228
235
  * }
229
236
  * ```
230
237
  */
231
- type IPresetController<TDoc = unknown, TPresets extends 'softDelete' | 'slugLookup' | 'tree' | never = never> = TPresets extends 'softDelete' ? ISoftDeleteController<TDoc> : TPresets extends 'slugLookup' ? ISlugLookupController<TDoc> : TPresets extends 'tree' ? ITreeController<TDoc> : unknown;
238
+ type IPresetController<TDoc = unknown, TPresets extends "softDelete" | "slugLookup" | "tree" | never = never> = TPresets extends "softDelete" ? ISoftDeleteController<TDoc> : TPresets extends "slugLookup" ? ISlugLookupController<TDoc> : TPresets extends "tree" ? ITreeController<TDoc> : unknown;
232
239
  //#endregion
233
240
  //#region src/presets/index.d.ts
234
241
  /**
@@ -264,4 +271,4 @@ type PresetInput = string | PresetResult | {
264
271
  */
265
272
  declare function applyPresets<TDoc = AnyRecord>(config: ResourceConfig<TDoc>, presets?: PresetInput[]): ResourceConfig<TDoc>;
266
273
  //#endregion
267
- export { type AuditedPresetOptions, type IAuditedPreset, type IMultiTenantPreset, type IOwnedByUserPreset, type IPresetController, type ISlugLookupController, type ISoftDeleteController, type ITreeController, type MultiTenantOptions, type OwnedByUserOptions, type SlugLookupOptions, type TreeOptions, applyPresets, auditedPreset, flexibleMultiTenantPreset, getAvailablePresets, getPreset, multiTenantPreset, ownedByUserPreset, registerPreset, slugLookupPreset, softDeletePreset, treePreset };
274
+ export { type AuditedPresetOptions, type BulkOperation, type BulkPresetOptions, type IAuditedPreset, type IMultiTenantPreset, type IOwnedByUserPreset, type IPresetController, type ISlugLookupController, type ISoftDeleteController, type ITreeController, type MultiTenantOptions, type OwnedByUserOptions, type SlugLookupOptions, type TreeOptions, applyPresets, auditedPreset, bulkPreset, flexibleMultiTenantPreset, getAvailablePresets, getPreset, multiTenantPreset, ownedByUserPreset, registerPreset, slugLookupPreset, softDeletePreset, treePreset };
@@ -1,143 +1,3 @@
1
- import { a as softDeletePreset, i as slugLookupPreset, n as treePreset, r as ownedByUserPreset, t as auditedPreset } from "../audited-CGdLiSlE.mjs";
2
1
  import multiTenantPreset from "./multiTenant.mjs";
3
-
4
- //#region src/presets/index.ts
5
- /**
6
- * Convenience alias for multiTenantPreset with public list/get routes
7
- * Equivalent to: multiTenantPreset({ allowPublic: ['list', 'get'] })
8
- */
9
- const flexibleMultiTenantPreset = (options = {}) => multiTenantPreset({
10
- ...options,
11
- allowPublic: ["list", "get"]
12
- });
13
- const presetRegistry = {
14
- softDelete: softDeletePreset,
15
- slugLookup: slugLookupPreset,
16
- ownedByUser: ownedByUserPreset,
17
- multiTenant: multiTenantPreset,
18
- tree: treePreset,
19
- audited: auditedPreset
20
- };
21
- /**
22
- * Get preset by name with options
23
- */
24
- function getPreset(nameOrConfig) {
25
- if (typeof nameOrConfig === "object" && nameOrConfig.name) {
26
- const { name, ...options } = nameOrConfig;
27
- return resolvePreset(name, options);
28
- }
29
- return resolvePreset(nameOrConfig);
30
- }
31
- /**
32
- * Resolve preset by name
33
- */
34
- function resolvePreset(name, options = {}) {
35
- const factory = presetRegistry[name];
36
- if (!factory) {
37
- const available = Object.keys(presetRegistry).join(", ");
38
- throw new Error(`Unknown preset: '${name}'\nAvailable presets: ${available}\nDocs: https://github.com/classytic/arc#presets`);
39
- }
40
- return factory(options);
41
- }
42
- /**
43
- * Register a custom preset
44
- */
45
- function registerPreset(name, factory, options) {
46
- if (presetRegistry[name] && !options?.override) throw new Error(`Preset '${name}' already exists. Pass { override: true } to replace.`);
47
- presetRegistry[name] = factory;
48
- }
49
- /**
50
- * Get all available preset names
51
- */
52
- function getAvailablePresets() {
53
- return Object.keys(presetRegistry);
54
- }
55
- /**
56
- * Validate that preset combinations don't conflict.
57
- * Detects route collisions (same method + path from different presets).
58
- */
59
- function validatePresetCombination(presets) {
60
- const conflicts = [];
61
- const routeMap = /* @__PURE__ */ new Map();
62
- for (const preset of presets) {
63
- const name = preset.name ?? "unknown";
64
- const routes = typeof preset.additionalRoutes === "function" ? preset.additionalRoutes({}) : preset.additionalRoutes ?? [];
65
- for (const route of routes) {
66
- const key = `${route.method} ${route.path}`;
67
- const existing = routeMap.get(key);
68
- if (existing) conflicts.push({
69
- presets: [existing, name],
70
- message: `Both '${existing}' and '${name}' define route ${key}`,
71
- severity: "error"
72
- });
73
- routeMap.set(key, name);
74
- }
75
- }
76
- return conflicts;
77
- }
78
- /**
79
- * Apply presets to resource config.
80
- * Validates preset combinations for conflicts before merging.
81
- */
82
- function applyPresets(config, presets = []) {
83
- let result = { ...config };
84
- const resolved = presets.map(resolvePresetInput);
85
- const errors = validatePresetCombination(resolved).filter((c) => c.severity === "error");
86
- if (errors.length > 0) throw new Error(`[Arc] Resource '${config.name}' preset conflicts:\n` + errors.map((c) => ` - ${c.message}`).join("\n"));
87
- for (const preset of resolved) result = mergePreset(result, preset);
88
- return result;
89
- }
90
- /**
91
- * Resolve preset input to PresetResult
92
- */
93
- function resolvePresetInput(preset) {
94
- if (typeof preset === "object" && ("middlewares" in preset || "additionalRoutes" in preset)) return preset;
95
- if (typeof preset === "object" && "name" in preset) {
96
- const { name, ...options } = preset;
97
- return resolvePreset(name, options);
98
- }
99
- return resolvePreset(preset);
100
- }
101
- /**
102
- * Merge preset into config
103
- */
104
- function mergePreset(config, preset) {
105
- const result = { ...config };
106
- if (preset.additionalRoutes) {
107
- const routes = typeof preset.additionalRoutes === "function" ? preset.additionalRoutes(config.permissions ?? {}) : preset.additionalRoutes;
108
- result.additionalRoutes = [...result.additionalRoutes ?? [], ...routes];
109
- }
110
- if (preset.middlewares) {
111
- result.middlewares = result.middlewares ?? {};
112
- for (const [op, mws] of Object.entries(preset.middlewares)) {
113
- const key = op;
114
- result.middlewares[key] = [...result.middlewares[key] ?? [], ...mws ?? []];
115
- }
116
- }
117
- if (preset.schemaOptions) result.schemaOptions = {
118
- ...result.schemaOptions,
119
- ...preset.schemaOptions,
120
- fieldRules: {
121
- ...result.schemaOptions?.fieldRules,
122
- ...preset.schemaOptions?.fieldRules
123
- }
124
- };
125
- if (preset.controllerOptions) result._controllerOptions = {
126
- ...result._controllerOptions,
127
- ...preset.controllerOptions
128
- };
129
- if (preset.hooks && preset.hooks.length > 0) {
130
- result._hooks = result._hooks ?? [];
131
- for (const hook of preset.hooks) result._hooks.push({
132
- presetName: preset.name,
133
- operation: hook.operation,
134
- phase: hook.phase,
135
- handler: hook.handler,
136
- priority: hook.priority
137
- });
138
- }
139
- return result;
140
- }
141
-
142
- //#endregion
143
- export { applyPresets, auditedPreset, flexibleMultiTenantPreset, getAvailablePresets, getPreset, multiTenantPreset, ownedByUserPreset, registerPreset, slugLookupPreset, softDeletePreset, treePreset };
2
+ import { a as registerPreset, c as auditedPreset, d as ownedByUserPreset, i as getPreset, l as softDeletePreset, n as flexibleMultiTenantPreset, o as treePreset, r as getAvailablePresets, s as bulkPreset, t as applyPresets, u as slugLookupPreset } from "../presets-C9QXJV1u.mjs";
3
+ export { applyPresets, auditedPreset, bulkPreset, flexibleMultiTenantPreset, getAvailablePresets, getPreset, multiTenantPreset, ownedByUserPreset, registerPreset, slugLookupPreset, softDeletePreset, treePreset };
@@ -1,7 +1,4 @@
1
- import "../elevation-DGo5shaX.mjs";
2
- import "../interface-DZYNK9bb.mjs";
3
- import "../types-RLkFVgaw.mjs";
4
- import { CrudRouteKey, PresetResult } from "../types/index.mjs";
1
+ import { Y as PresetResult, b as CrudRouteKey } from "../interface-DGmPxakH.mjs";
5
2
 
6
3
  //#region src/presets/multiTenant.d.ts
7
4
  interface MultiTenantOptions {
@@ -1,6 +1,5 @@
1
- import { o as DEFAULT_TENANT_FIELD } from "../constants-DdXFXQtN.mjs";
2
- import { c as isElevated, l as isMember, n as PUBLIC_SCOPE, r as getOrgId } from "../types-Beqn1Un7.mjs";
3
-
1
+ import { o as DEFAULT_TENANT_FIELD } from "../constants-Cxde4rpC.mjs";
2
+ import { d as isMember, n as PUBLIC_SCOPE, r as getOrgId, u as isElevated } from "../types-C6TQjtdi.mjs";
4
3
  //#region src/presets/multiTenant.ts
5
4
  /** Read request.scope safely */
6
5
  function getScope(request) {
@@ -51,7 +50,7 @@ function createTenantFilter(tenantField) {
51
50
  * Org context present = require auth and apply filter
52
51
  */
53
52
  function createFlexibleTenantFilter(tenantField) {
54
- return async (request, reply) => {
53
+ return async (request, _reply) => {
55
54
  const scope = getScope(request);
56
55
  if (isElevated(scope)) {
57
56
  const orgId = getOrgId(scope);
@@ -108,6 +107,5 @@ function multiTenantPreset(options = {}) {
108
107
  }
109
108
  };
110
109
  }
111
-
112
110
  //#endregion
113
- export { multiTenantPreset as default, multiTenantPreset };
111
+ export { multiTenantPreset as default, multiTenantPreset };