@classytic/arc 2.11.2 → 2.11.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +8 -14
  3. package/bin/arc.js +2 -2
  4. package/dist/{BaseController-JNV08qOT.mjs → BaseController-swXruJ2_.mjs} +2 -2
  5. package/dist/{actionPermissions-C8YYU92K.mjs → actionPermissions-sUUKDhtP.mjs} +4 -2
  6. package/dist/auth/index.mjs +1 -1
  7. package/dist/cli/commands/docs.mjs +1 -1
  8. package/dist/cli/commands/generate.d.mts +0 -2
  9. package/dist/cli/commands/generate.mjs +15 -15
  10. package/dist/cli/commands/init.mjs +24 -22
  11. package/dist/context/index.mjs +1 -1
  12. package/dist/core/index.mjs +3 -3
  13. package/dist/{core-DXdSSFW-.mjs → core-DnUsRpuX.mjs} +20 -8
  14. package/dist/{createActionRouter-BwaSM0No.mjs → createActionRouter-u3ql2EDo.mjs} +73 -13
  15. package/dist/{createApp-P1d6rjPy.mjs → createApp-BFxtdKy6.mjs} +1 -1
  16. package/dist/docs/index.mjs +1 -1
  17. package/dist/{eventPlugin--5HIkdPU.mjs → eventPlugin-KrFIQ097.mjs} +1 -1
  18. package/dist/events/index.mjs +11 -3
  19. package/dist/factory/index.mjs +1 -1
  20. package/dist/index.mjs +6 -6
  21. package/dist/integrations/mcp/index.mjs +1 -1
  22. package/dist/integrations/mcp/testing.mjs +1 -1
  23. package/dist/{openapi-C0L9ar7m.mjs → openapi-BGUn7Ki1.mjs} +2 -2
  24. package/dist/permissions/index.mjs +1 -1
  25. package/dist/{permissions-B4vU9L0Q.mjs → permissions-gd_aUWrR.mjs} +42 -0
  26. package/dist/plugins/index.mjs +1 -1
  27. package/dist/plugins/tracing-entry.mjs +1 -1
  28. package/dist/presets/filesUpload.mjs +1 -1
  29. package/dist/presets/index.mjs +1 -1
  30. package/dist/presets/search.mjs +1 -1
  31. package/dist/{presets-k604Lj99.mjs → presets-Z7P5w4gF.mjs} +1 -1
  32. package/dist/{requestContext-CfRkaxwf.mjs → requestContext-C5XeK3VA.mjs} +15 -0
  33. package/dist/{resourceToTools--okX6QBr.mjs → resourceToTools-ByZpgjeH.mjs} +5 -4
  34. package/dist/{routerShared-DeESFp4a.mjs → routerShared-BqLRb5l7.mjs} +60 -3
  35. package/dist/testing/index.mjs +1 -1
  36. package/dist/utils/index.mjs +1 -1
  37. package/dist/{utils-D3Yxnrwr.mjs → utils-CcYTj09l.mjs} +1 -1
  38. package/package.json +3 -1
  39. package/skills/arc/references/events.md +489 -489
package/dist/index.mjs CHANGED
@@ -1,12 +1,12 @@
1
1
  import { a as createMongooseAdapter, i as MongooseAdapter, r as createPrismaAdapter, t as PrismaAdapter } from "./adapters-D0tT2Tyo.mjs";
2
2
  import { a as DEFAULT_SORT, c as HOOK_OPERATIONS, d as MAX_REGEX_LENGTH, f as MAX_SEARCH_LENGTH, h as SYSTEM_FIELDS, i as DEFAULT_MAX_LIMIT, l as HOOK_PHASES, m as RESERVED_QUERY_PARAMS, n as DEFAULT_ID_FIELD, o as DEFAULT_TENANT_FIELD, p as MUTATION_OPERATIONS, r as DEFAULT_LIMIT, s as DEFAULT_UPDATE_METHOD, t as CRUD_OPERATIONS, u as MAX_FILTER_DEPTH } from "./constants-BhY1OHoH.mjs";
3
- import { j as getUserId, r as envelope } from "./utils-D3Yxnrwr.mjs";
4
- import { a as BulkMixin, i as SlugMixin, n as TreeMixin, o as BaseCrudController, r as SoftDeleteMixin, t as BaseController } from "./BaseController-JNV08qOT.mjs";
5
- import { C as requireAuth, D as when, M as applyFieldWritePermissions, N as fields, T as requireRoles, _ as requireTeamMembership, a as presets_exports, b as anyOf, c as readOnly, d as createOrgPermissions, f as requireOrgInScope, g as requireServiceScope, h as requireScopeContext, i as ownerWithAdminBypass, j as applyFieldReadPermissions, m as requireOrgRole, n as authenticated, o as publicRead, p as requireOrgMembership, r as fullPublic, s as publicReadAdminWrite, t as adminOnly, u as createDynamicPermissionMatrix, v as allOf, w as requireOwnership, x as denyAll, y as allowPublic } from "./permissions-B4vU9L0Q.mjs";
3
+ import { j as getUserId, r as envelope } from "./utils-CcYTj09l.mjs";
4
+ import { a as BulkMixin, i as SlugMixin, n as TreeMixin, o as BaseCrudController, r as SoftDeleteMixin, t as BaseController } from "./BaseController-swXruJ2_.mjs";
5
+ import { C as requireAuth, D as when, M as applyFieldWritePermissions, N as fields, T as requireRoles, _ as requireTeamMembership, a as presets_exports, b as anyOf, c as readOnly, d as createOrgPermissions, f as requireOrgInScope, g as requireServiceScope, h as requireScopeContext, i as ownerWithAdminBypass, j as applyFieldReadPermissions, m as requireOrgRole, n as authenticated, o as publicRead, p as requireOrgMembership, r as fullPublic, s as publicReadAdminWrite, t as adminOnly, u as createDynamicPermissionMatrix, v as allOf, w as requireOwnership, x as denyAll, y as allowPublic } from "./permissions-gd_aUWrR.mjs";
6
6
  import { d as createDomainError, i as NotFoundError, l as UnauthorizedError, r as ForbiddenError, t as ArcError, u as ValidationError } from "./errors-D5c-5BJL.mjs";
7
- import { _ as getControllerScope } from "./routerShared-DeESFp4a.mjs";
8
- import { n as ResourceDefinition, r as defineResource, t as defineResourceVariants } from "./core-DXdSSFW-.mjs";
7
+ import { v as getControllerScope } from "./routerShared-BqLRb5l7.mjs";
8
+ import { n as ResourceDefinition, r as defineResource, t as defineResourceVariants } from "./core-DnUsRpuX.mjs";
9
9
  //#region src/index.ts
10
- const version = "2.11.2";
10
+ const version = "2.11.3";
11
11
  //#endregion
12
12
  export { ArcError, BaseController, BaseCrudController, BulkMixin, CRUD_OPERATIONS, DEFAULT_ID_FIELD, DEFAULT_LIMIT, DEFAULT_MAX_LIMIT, DEFAULT_SORT, DEFAULT_TENANT_FIELD, DEFAULT_UPDATE_METHOD, ForbiddenError, HOOK_OPERATIONS, HOOK_PHASES, MAX_FILTER_DEPTH, MAX_REGEX_LENGTH, MAX_SEARCH_LENGTH, MUTATION_OPERATIONS, MongooseAdapter, NotFoundError, PrismaAdapter, RESERVED_QUERY_PARAMS, ResourceDefinition, SYSTEM_FIELDS, SlugMixin, SoftDeleteMixin, TreeMixin, UnauthorizedError, ValidationError, adminOnly, allOf, allowPublic, anyOf, applyFieldReadPermissions, applyFieldWritePermissions, authenticated, createDomainError, createDynamicPermissionMatrix, createMongooseAdapter, createOrgPermissions, createPrismaAdapter, defineResource, defineResourceVariants, denyAll, envelope, fields, fullPublic, getControllerScope, getUserId, ownerWithAdminBypass, presets_exports as permissions, publicRead, publicReadAdminWrite, readOnly, requireAuth, requireOrgInScope, requireOrgMembership, requireOrgRole, requireOwnership, requireRoles, requireScopeContext, requireServiceScope, requireTeamMembership, version, when };
@@ -1,4 +1,4 @@
1
- import { n as fieldRulesToZod, r as createMcpServer, t as resourceToTools } from "../../resourceToTools--okX6QBr.mjs";
1
+ import { n as fieldRulesToZod, r as createMcpServer, t as resourceToTools } from "../../resourceToTools-ByZpgjeH.mjs";
2
2
  import { createHash, randomUUID } from "node:crypto";
3
3
  import fp from "fastify-plugin";
4
4
  //#region src/integrations/mcp/defineTool.ts
@@ -1,4 +1,4 @@
1
- import { r as createMcpServer, t as resourceToTools } from "../../resourceToTools--okX6QBr.mjs";
1
+ import { r as createMcpServer, t as resourceToTools } from "../../resourceToTools-ByZpgjeH.mjs";
2
2
  //#region src/integrations/mcp/testing.ts
3
3
  /**
4
4
  * @classytic/arc/mcp/testing — MCP Test Utilities
@@ -1,7 +1,7 @@
1
1
  import { t as getUserRoles } from "./types-DV9WDfeg.mjs";
2
2
  import { n as convertRouteSchema } from "./schemaConverter-B0oKLuqI.mjs";
3
- import { t as resolveActionPermission } from "./actionPermissions-C8YYU92K.mjs";
4
- import { t as buildActionBodySchema } from "./createActionRouter-BwaSM0No.mjs";
3
+ import { t as resolveActionPermission } from "./actionPermissions-sUUKDhtP.mjs";
4
+ import { t as buildActionBodySchema } from "./createActionRouter-u3ql2EDo.mjs";
5
5
  import fp from "fastify-plugin";
6
6
  //#region src/docs/openapi.ts
7
7
  const openApiPlugin = async (fastify, opts = {}) => {
@@ -1,3 +1,3 @@
1
- import { A as normalizePermissionResult, C as requireAuth, D as when, E as roles, M as applyFieldWritePermissions, N as fields, O as applyPermissionResult, P as resolveEffectiveRoles, S as not, T as requireRoles, _ as requireTeamMembership, a as presets_exports, b as anyOf, c as readOnly, d as createOrgPermissions, f as requireOrgInScope, g as requireServiceScope, h as requireScopeContext, i as ownerWithAdminBypass, j as applyFieldReadPermissions, l as createRoleHierarchy, m as requireOrgRole, n as authenticated, o as publicRead, p as requireOrgMembership, r as fullPublic, s as publicReadAdminWrite, t as adminOnly, u as createDynamicPermissionMatrix, v as allOf, w as requireOwnership, x as denyAll, y as allowPublic } from "../permissions-B4vU9L0Q.mjs";
1
+ import { A as normalizePermissionResult, C as requireAuth, D as when, E as roles, M as applyFieldWritePermissions, N as fields, O as applyPermissionResult, P as resolveEffectiveRoles, S as not, T as requireRoles, _ as requireTeamMembership, a as presets_exports, b as anyOf, c as readOnly, d as createOrgPermissions, f as requireOrgInScope, g as requireServiceScope, h as requireScopeContext, i as ownerWithAdminBypass, j as applyFieldReadPermissions, l as createRoleHierarchy, m as requireOrgRole, n as authenticated, o as publicRead, p as requireOrgMembership, r as fullPublic, s as publicReadAdminWrite, t as adminOnly, u as createDynamicPermissionMatrix, v as allOf, w as requireOwnership, x as denyAll, y as allowPublic } from "../permissions-gd_aUWrR.mjs";
2
2
  import { n as normalizeRoles, t as getUserRoles } from "../types-DV9WDfeg.mjs";
3
3
  export { adminOnly, allOf, allowPublic, anyOf, applyFieldReadPermissions, applyFieldWritePermissions, applyPermissionResult, authenticated, createDynamicPermissionMatrix, createOrgPermissions, createRoleHierarchy, denyAll, fields, fullPublic, getUserRoles, normalizePermissionResult, normalizeRoles, not, ownerWithAdminBypass, presets_exports as permissions, publicRead, publicReadAdminWrite, readOnly, requireAuth, requireOrgInScope, requireOrgMembership, requireOrgRole, requireOwnership, requireRoles, requireScopeContext, requireServiceScope, requireTeamMembership, resolveEffectiveRoles, roles, when };
@@ -31,21 +31,63 @@ function isMongooseDoc(obj) {
31
31
  return !!obj && typeof obj === "object" && "toObject" in obj && typeof obj.toObject === "function";
32
32
  }
33
33
  const fields = {
34
+ /**
35
+ * Field is never included in responses. Not writable via API.
36
+ *
37
+ * @example
38
+ * ```typescript
39
+ * fields: { password: fields.hidden() }
40
+ * ```
41
+ */
34
42
  hidden() {
35
43
  return { _type: "hidden" };
36
44
  },
45
+ /**
46
+ * Field is only visible to users with specified roles.
47
+ * Other users don't see the field at all.
48
+ *
49
+ * @example
50
+ * ```typescript
51
+ * fields: { salary: fields.visibleTo(['admin', 'hr']) }
52
+ * ```
53
+ */
37
54
  visibleTo(roles) {
38
55
  return {
39
56
  _type: "visibleTo",
40
57
  roles
41
58
  };
42
59
  },
60
+ /**
61
+ * Field is only writable by users with specified roles.
62
+ * All users can still read the field. Users without the role
63
+ * have the field silently stripped from write operations.
64
+ *
65
+ * @example
66
+ * ```typescript
67
+ * fields: { role: fields.writableBy(['admin']) }
68
+ * ```
69
+ */
43
70
  writableBy(roles) {
44
71
  return {
45
72
  _type: "writableBy",
46
73
  roles
47
74
  };
48
75
  },
76
+ /**
77
+ * Field is redacted (replaced with a placeholder) for specified roles.
78
+ * Other users see the real value.
79
+ *
80
+ * @param roles - Roles that see the redacted value
81
+ * @param redactValue - Replacement value (default: '***')
82
+ *
83
+ * @example
84
+ * ```typescript
85
+ * fields: {
86
+ * email: fields.redactFor(['viewer']),
87
+ * ssn: fields.redactFor(['basic'], '***-**-****'),
88
+ * }
89
+ * ```
90
+ */
49
91
  redactFor(roles, redactValue = "***") {
50
92
  return {
51
93
  _type: "redactFor",
@@ -1,6 +1,6 @@
1
1
  import { p as MUTATION_OPERATIONS } from "../constants-BhY1OHoH.mjs";
2
2
  import { o as getOrgId } from "../types-AOD8fxIw.mjs";
3
- import { t as requestContext } from "../requestContext-CfRkaxwf.mjs";
3
+ import { t as requestContext } from "../requestContext-C5XeK3VA.mjs";
4
4
  import { t as hasEvents } from "../typeGuards-CcFZXgU7.mjs";
5
5
  import { t as HookSystem } from "../HookSystem-CGsMd6oK.mjs";
6
6
  import { t as ResourceRegistry } from "../ResourceRegistry-DkAeAuTX.mjs";
@@ -58,7 +58,7 @@ try {
58
58
  function createTracerProvider(options) {
59
59
  if (!isAvailable || !NodeTracerProvider || !BatchSpanProcessor || !OTLPTraceExporter) return null;
60
60
  const { serviceName = "@classytic/arc", serviceVersion, exporterUrl = "http://localhost:4318/v1/traces" } = options;
61
- const resolvedVersion = serviceVersion ?? "2.11.2";
61
+ const resolvedVersion = serviceVersion ?? "2.11.3";
62
62
  const exporter = new OTLPTraceExporter({ url: exporterUrl });
63
63
  const provider = new NodeTracerProvider({ resource: { attributes: {
64
64
  "service.name": serviceName,
@@ -1,5 +1,5 @@
1
1
  import { o as getOrgId, p as getUserId } from "../types-AOD8fxIw.mjs";
2
- import { C as requireAuth, y as allowPublic } from "../permissions-B4vU9L0Q.mjs";
2
+ import { C as requireAuth, y as allowPublic } from "../permissions-gd_aUWrR.mjs";
3
3
  import { i as NotFoundError, u as ValidationError } from "../errors-D5c-5BJL.mjs";
4
4
  import { t as multipartBody } from "../multipartBody-CvTR1Un6.mjs";
5
5
  //#region src/presets/filesUpload.ts
@@ -1,5 +1,5 @@
1
1
  import { multiTenantPreset } from "./multiTenant.mjs";
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-k604Lj99.mjs";
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-Z7P5w4gF.mjs";
3
3
  import { filesUploadPreset } from "./filesUpload.mjs";
4
4
  import { searchPreset } from "./search.mjs";
5
5
  export { applyPresets, auditedPreset, bulkPreset, filesUploadPreset, flexibleMultiTenantPreset, getAvailablePresets, getPreset, multiTenantPreset, ownedByUserPreset, registerPreset, searchPreset, slugLookupPreset, softDeletePreset, treePreset };
@@ -1,4 +1,4 @@
1
- import { C as requireAuth, y as allowPublic } from "../permissions-B4vU9L0Q.mjs";
1
+ import { C as requireAuth, y as allowPublic } from "../permissions-gd_aUWrR.mjs";
2
2
  //#region src/presets/search.ts
3
3
  const BUILTINS = [
4
4
  {
@@ -1,5 +1,5 @@
1
1
  import { _ as isElevated, n as PUBLIC_SCOPE } from "./types-AOD8fxIw.mjs";
2
- import { C as requireAuth, T as requireRoles, y as allowPublic } from "./permissions-B4vU9L0Q.mjs";
2
+ import { C as requireAuth, T as requireRoles, y as allowPublic } from "./permissions-gd_aUWrR.mjs";
3
3
  import { multiTenantPreset } from "./presets/multiTenant.mjs";
4
4
  //#region src/presets/ownedByUser.ts
5
5
  /**
@@ -38,15 +38,30 @@ const storage = new AsyncLocalStorage();
38
38
  * - `getStore()` — alias for get() (matches Node.js API naming)
39
39
  */
40
40
  const requestContext = {
41
+ /**
42
+ * Get the current request context.
43
+ * Returns undefined if called outside a request lifecycle.
44
+ */
41
45
  get() {
42
46
  return storage.getStore();
43
47
  },
48
+ /**
49
+ * Alias for get() — matches Node.js AsyncLocalStorage API naming.
50
+ */
44
51
  getStore() {
45
52
  return storage.getStore();
46
53
  },
54
+ /**
55
+ * Run a function within a specific request context.
56
+ * Used internally by Arc's onRequest hook.
57
+ */
47
58
  run(store, fn) {
48
59
  return storage.run(store, fn);
49
60
  },
61
+ /**
62
+ * The underlying AsyncLocalStorage instance.
63
+ * Exposed for advanced use cases (testing, custom integrations).
64
+ */
50
65
  storage
51
66
  };
52
67
  //#endregion
@@ -1,8 +1,8 @@
1
- import { t as BaseController } from "./BaseController-JNV08qOT.mjs";
2
- import { A as normalizePermissionResult } from "./permissions-B4vU9L0Q.mjs";
3
- import { u as resolvePipelineSteps } from "./routerShared-DeESFp4a.mjs";
1
+ import { t as BaseController } from "./BaseController-swXruJ2_.mjs";
2
+ import { A as normalizePermissionResult } from "./permissions-gd_aUWrR.mjs";
3
+ import { u as resolvePipelineSteps } from "./routerShared-BqLRb5l7.mjs";
4
4
  import { t as executePipeline } from "./pipe-DVoIheVC.mjs";
5
- import { t as resolveActionPermission } from "./actionPermissions-C8YYU92K.mjs";
5
+ import { t as resolveActionPermission } from "./actionPermissions-sUUKDhtP.mjs";
6
6
  import { i as shouldRejectAdditionalProperties, r as schemaIRToZodShape, t as normalizeSchemaIR } from "./schemaIR-BlG9bY7v.mjs";
7
7
  import { t as pluralize } from "./pluralize-BneOJkpi.mjs";
8
8
  import { z } from "zod";
@@ -1145,6 +1145,7 @@ function resourceToTools(resource, config = {}) {
1145
1145
  resourcePermissions: resource.permissions,
1146
1146
  resourceActionPermissions: resource.actionPermissions
1147
1147
  });
1148
+ if (!actionPerms) throw new Error(`[Arc/MCP] Resource '${resource.name}': action '${actionName}' has no permission gate and the resource defines no \`permissions.update\` fallback. Declare one of:\n - \`actions.${actionName}.permissions: <PermissionCheck>\` (per-action)\n - \`actionPermissions: <PermissionCheck>\` (resource-wide)\n - \`permissions.update: <PermissionCheck>\` (inherited by actions)\nUse \`allowPublic()\` if you genuinely want the action unauthenticated.`);
1148
1149
  tools.push({
1149
1150
  name: toolName,
1150
1151
  description: String(description),
@@ -1,7 +1,7 @@
1
1
  import { _ as isElevated, n as PUBLIC_SCOPE, o as getOrgId, p as getUserId, v as isMember } from "./types-AOD8fxIw.mjs";
2
- import { P as resolveEffectiveRoles, j as applyFieldReadPermissions, k as evaluateAndApplyPermission } from "./permissions-B4vU9L0Q.mjs";
2
+ import { P as resolveEffectiveRoles, j as applyFieldReadPermissions, k as evaluateAndApplyPermission } from "./permissions-gd_aUWrR.mjs";
3
3
  import { t as getUserRoles } from "./types-DV9WDfeg.mjs";
4
- import { t as requestContext } from "./requestContext-CfRkaxwf.mjs";
4
+ import { t as requestContext } from "./requestContext-C5XeK3VA.mjs";
5
5
  import { t as executePipeline } from "./pipe-DVoIheVC.mjs";
6
6
  //#region src/scope/projection.ts
7
7
  /**
@@ -511,5 +511,62 @@ function buildPreHandlerChain(parts) {
511
511
  ...parts.customMws ?? []
512
512
  ].filter(Boolean);
513
513
  }
514
+ /**
515
+ * `RouteDefinition.preHandler` accepts two shapes:
516
+ *
517
+ * 1. **Array form** — `RouteHandlerMethod[]`. Used directly.
518
+ * 2. **Factory form** — `(fastify) => RouteHandlerMethod[]`. Called once at
519
+ * route-registration time with the Fastify instance, so handlers can
520
+ * capture decorators (`fastify.authenticate`, `fastify.events`, etc.)
521
+ * that aren't on the request.
522
+ *
523
+ * The two forms are equally idiomatic, but the discrimination is by
524
+ * `typeof preHandler === "function"`. Single-function shapes such as
525
+ * `multipartBody({...})` (a `RouteHandlerMethod`) **structurally satisfy
526
+ * the factory branch** at the call site, then fail with a cryptic
527
+ * `Cannot read properties of undefined (reading 'content-type')` once the
528
+ * handler runs with `fastify` in the request slot.
529
+ *
530
+ * This resolver:
531
+ * 1. Distinguishes the two valid shapes.
532
+ * 2. Validates the factory's RETURN — must be an array of functions.
533
+ * 3. Throws an actionable error pointing at the route + the fix when
534
+ * a single `RouteHandlerMethod` was passed instead of an array, OR
535
+ * when a factory returned the wrong shape.
536
+ *
537
+ * The error message names the route (`{method} {path}`) and the
538
+ * canonical fix (`preHandler: [yourHandler]`) so the failure mode is
539
+ * obvious instead of debug-archaeology.
540
+ *
541
+ * @param preHandler The `route.preHandler` value (any of the valid shapes
542
+ * plus the common bare-handler mistake).
543
+ * @param fastify Passed to factory-form preHandlers.
544
+ * @param routeId `"GET /todos/:id/attach"` (or similar) — used in the
545
+ * error message so a multi-route file points at the
546
+ * actual offender.
547
+ */
548
+ function resolveRoutePreHandlers(preHandler, fastify, routeId) {
549
+ if (preHandler === void 0 || preHandler === null) return [];
550
+ if (Array.isArray(preHandler)) return preHandler.filter((h) => typeof h === "function");
551
+ if (typeof preHandler === "function") {
552
+ let result;
553
+ try {
554
+ result = preHandler(fastify);
555
+ } catch (err) {
556
+ const msg = err instanceof Error ? err.message : String(err);
557
+ throw new TypeError(`Route ${routeId}: preHandler factory threw during route registration: ${msg}.\nIf you intended to pass a single handler (e.g. \`multipartBody({...})\`), wrap it in an array: \`preHandler: [yourHandler]\`. The factory form is \`(fastify) => RouteHandlerMethod[]\` — it must return an array.`, { cause: err instanceof Error ? err : void 0 });
558
+ }
559
+ if (!Array.isArray(result)) throw new TypeError(`Route ${routeId}: preHandler factory must return an array of handlers, got ${describeValue(result)}.\nCommon cause: passing a single \`RouteHandlerMethod\` (e.g. \`multipartBody({...})\`) where an array was expected. Wrap it: \`preHandler: [yourHandler]\`. The factory form \`(fastify) => RouteHandlerMethod[]\` is for cases that need the Fastify instance — e.g. \`(fastify) => [fastify.authenticate, myHandler]\`.`);
560
+ return result.filter((h) => typeof h === "function");
561
+ }
562
+ throw new TypeError(`Route ${routeId}: preHandler must be an array of handlers OR a factory \`(fastify) => RouteHandlerMethod[]\`. Got ${describeValue(preHandler)}.`);
563
+ }
564
+ function describeValue(v) {
565
+ if (v === null) return "null";
566
+ if (v === void 0) return "undefined";
567
+ if (typeof v === "function") return "a function (single handler — wrap in array)";
568
+ if (Array.isArray(v)) return `an array of length ${v.length}`;
569
+ return `${typeof v} (${JSON.stringify(v).slice(0, 80)})`;
570
+ }
514
571
  //#endregion
515
- export { getControllerScope as _, buildAuthMiddlewareForPermissions as a, buildPreHandlerChain as c, resolveRouterPluginMw as d, selectPluginMw as f, getControllerContext as g, createRequestContext as h, buildAuthMiddleware as i, buildRateLimitConfig as l, createFastifyHandler as m, buildActionPipelineHandler as n, buildCrudPermissionMw as o, createCrudHandlers as p, buildArcDecorator as r, buildPipelineHandler as s, buildActionPermissionMw as t, resolvePipelineSteps as u, sendControllerResponse as v, buildRequestScopeProjection as y };
572
+ export { getControllerContext as _, buildAuthMiddlewareForPermissions as a, buildRequestScopeProjection as b, buildPreHandlerChain as c, resolveRoutePreHandlers as d, resolveRouterPluginMw as f, createRequestContext as g, createFastifyHandler as h, buildAuthMiddleware as i, buildRateLimitConfig as l, createCrudHandlers as m, buildActionPipelineHandler as n, buildCrudPermissionMw as o, selectPluginMw as p, buildArcDecorator as r, buildPipelineHandler as s, buildActionPermissionMw as t, resolvePipelineSteps as u, getControllerScope as v, sendControllerResponse as y };
@@ -1081,7 +1081,7 @@ function pickDefaultAuth(authMode, callerAuth) {
1081
1081
  };
1082
1082
  }
1083
1083
  async function createTestApp(options = {}) {
1084
- const { createApp } = await import("../createApp-P1d6rjPy.mjs").then((n) => n.r);
1084
+ const { createApp } = await import("../createApp-BFxtdKy6.mjs").then((n) => n.r);
1085
1085
  const { resources = [], db = "in-memory", connectMongoose = false, authMode = "jwt", defaultOrgId, plugins, auth: callerAuth, ...appOptions } = options;
1086
1086
  let dbHandle;
1087
1087
  let dbUri;
@@ -1,4 +1,4 @@
1
- import { A as createQueryParser, C as mutationResponse, D as successResponseSchema, E as responses, M as simpleEqualityMatcher, O as wrapResponse, S as listResponse, T as queryParams, _ as deleteResponse, a as defineErrorMapper, b as getListQueryParams, c as CircuitBreaker, d as CircuitState, f as createCircuitBreaker, g as validateResourceConfig, h as formatValidationErrors, i as defineGuard, j as getUserId, k as ArcQueryParser, l as CircuitBreakerError, m as assertValidConfig, n as handleRaw, o as defineCompensation, p as createCircuitBreakerRegistry, r as envelope, s as withCompensation, t as createStateMachine, u as CircuitBreakerRegistry, v as errorResponseSchema, w as paginationSchema, x as itemResponse, y as getDefaultCrudSchemas } from "../utils-D3Yxnrwr.mjs";
1
+ import { A as createQueryParser, C as mutationResponse, D as successResponseSchema, E as responses, M as simpleEqualityMatcher, O as wrapResponse, S as listResponse, T as queryParams, _ as deleteResponse, a as defineErrorMapper, b as getListQueryParams, c as CircuitBreaker, d as CircuitState, f as createCircuitBreaker, g as validateResourceConfig, h as formatValidationErrors, i as defineGuard, j as getUserId, k as ArcQueryParser, l as CircuitBreakerError, m as assertValidConfig, n as handleRaw, o as defineCompensation, p as createCircuitBreakerRegistry, r as envelope, s as withCompensation, t as createStateMachine, u as CircuitBreakerRegistry, v as errorResponseSchema, w as paginationSchema, x as itemResponse, y as getDefaultCrudSchemas } from "../utils-CcYTj09l.mjs";
2
2
  import { a as OrgAccessDeniedError, c as ServiceUnavailableError, d as createDomainError, f as createError, i as NotFoundError, l as UnauthorizedError, n as ConflictError, o as OrgRequiredError, p as isArcError, r as ForbiddenError, s as RateLimitError, t as ArcError, u as ValidationError } from "../errors-D5c-5BJL.mjs";
3
3
  import { a as toJsonSchema, i as isZodSchema, n as convertRouteSchema, r as isJsonSchema, t as convertOpenApiSchemas } from "../schemaConverter-B0oKLuqI.mjs";
4
4
  import { t as hasEvents } from "../typeGuards-CcFZXgU7.mjs";
@@ -1,7 +1,7 @@
1
1
  import { m as RESERVED_QUERY_PARAMS, t as CRUD_OPERATIONS } from "./constants-BhY1OHoH.mjs";
2
2
  import { arcLog } from "./logger/index.mjs";
3
3
  import { t as ArcError } from "./errors-D5c-5BJL.mjs";
4
- import { r as getAvailablePresets } from "./presets-k604Lj99.mjs";
4
+ import { r as getAvailablePresets } from "./presets-Z7P5w4gF.mjs";
5
5
  //#region src/utils/simpleEqualityMatcher.ts
6
6
  /**
7
7
  * `simpleEqualityMatcher` — a minimal, dialect-agnostic flat-key equality
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classytic/arc",
3
- "version": "2.11.2",
3
+ "version": "2.11.3",
4
4
  "description": "Resource-oriented backend framework for Fastify — clean, minimal, powerful, tree-shakable",
5
5
  "type": "module",
6
6
  "exports": {
@@ -255,6 +255,7 @@
255
255
  "@fastify/under-pressure": ">=9.0.0",
256
256
  "@fastify/websocket": ">=11.0.0",
257
257
  "@modelcontextprotocol/sdk": ">=1.28.0",
258
+ "@opentelemetry/api": ">=1.9.0",
258
259
  "@opentelemetry/auto-instrumentations-node": ">=0.40.0",
259
260
  "@opentelemetry/exporter-trace-otlp-http": ">=0.50.0",
260
261
  "@opentelemetry/instrumentation-http": ">=0.50.0",
@@ -381,6 +382,7 @@
381
382
  "@fastify/under-pressure": "^9.0.3",
382
383
  "@fastify/websocket": "^11.0.0",
383
384
  "@modelcontextprotocol/sdk": "^1.29.0",
385
+ "@opentelemetry/api": "^1.9.1",
384
386
  "@sinclair/typebox": "^0.34.0",
385
387
  "@types/node": "^22.10.0",
386
388
  "@types/qs": "^6.14.0",