@classytic/arc 2.4.2 → 2.5.0

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 (86) hide show
  1. package/dist/{BaseController-CkM5dUh_.mjs → BaseController-CNwMYpDW.mjs} +1 -1
  2. package/dist/adapters/index.d.mts +2 -2
  3. package/dist/auth/index.d.mts +1 -1
  4. package/dist/auth/index.mjs +2 -2
  5. package/dist/cache/index.mjs +2 -2
  6. package/dist/cli/commands/describe.d.mts +1 -1
  7. package/dist/cli/commands/describe.mjs +1 -1
  8. package/dist/cli/commands/generate.d.mts +1 -1
  9. package/dist/cli/commands/generate.mjs +1 -1
  10. package/dist/cli/commands/init.d.mts +1 -1
  11. package/dist/cli/commands/init.mjs +1 -1
  12. package/dist/cli/commands/introspect.d.mts +1 -1
  13. package/dist/cli/commands/introspect.mjs +1 -1
  14. package/dist/cli/index.d.mts +4 -4
  15. package/dist/cli/index.mjs +4 -4
  16. package/dist/core/index.d.mts +2 -2
  17. package/dist/core/index.mjs +2 -2
  18. package/dist/{createApp-ByWNRsZj.mjs → createApp-oic3-ieX.mjs} +5 -5
  19. package/dist/{defineResource-D9aY5Cy6.mjs → defineResource-BYm3CIoe.mjs} +85 -10
  20. package/dist/discovery/index.d.mts +1 -1
  21. package/dist/discovery/index.mjs +1 -1
  22. package/dist/docs/index.d.mts +1 -1
  23. package/dist/dynamic/index.d.mts +1 -1
  24. package/dist/dynamic/index.mjs +2 -2
  25. package/dist/{elevation-Ca_yveIO.d.mts → elevation-C_taLQrM.d.mts} +27 -1
  26. package/dist/{errorHandler--zp54tGc.mjs → errorHandler-r2595m8T.mjs} +5 -5
  27. package/dist/{errors-CPpvPHT0.d.mts → errors-CcVbl1-T.d.mts} +17 -1
  28. package/dist/{errors-rxhfP7Hf.mjs → errors-NoQKsbAT.mjs} +23 -1
  29. package/dist/events/transports/redis.d.mts +1 -1
  30. package/dist/events/transports/redis.mjs +1 -1
  31. package/dist/factory/index.d.mts +1 -1
  32. package/dist/factory/index.mjs +1 -1
  33. package/dist/hooks/index.d.mts +1 -1
  34. package/dist/{index-BL8CaQih.d.mts → index-TG7-pXDC.d.mts} +2 -2
  35. package/dist/{index-yhxyjqNb.d.mts → index-bX8T5bmn.d.mts} +4 -8
  36. package/dist/index.d.mts +5 -5
  37. package/dist/index.mjs +7 -6
  38. package/dist/integrations/event-gateway.d.mts +1 -1
  39. package/dist/integrations/event-gateway.mjs +2 -2
  40. package/dist/integrations/index.d.mts +1 -1
  41. package/dist/integrations/jobs.d.mts +1 -1
  42. package/dist/integrations/jobs.mjs +1 -1
  43. package/dist/integrations/mcp/index.d.mts +2 -2
  44. package/dist/integrations/mcp/index.mjs +1 -1
  45. package/dist/integrations/mcp/testing.d.mts +1 -1
  46. package/dist/integrations/mcp/testing.mjs +1 -1
  47. package/dist/integrations/streamline.d.mts +1 -1
  48. package/dist/integrations/streamline.mjs +1 -1
  49. package/dist/integrations/websocket-redis.d.mts +1 -1
  50. package/dist/integrations/websocket-redis.mjs +1 -1
  51. package/dist/integrations/websocket.d.mts +1 -1
  52. package/dist/integrations/websocket.mjs +1 -1
  53. package/dist/{interface-DGmPxakH.d.mts → interface-BnNjdl33.d.mts} +170 -8
  54. package/dist/{memory-Cb_7iy9e.mjs → memory-BFAYkf8H.mjs} +1 -4
  55. package/dist/org/index.d.mts +1 -1
  56. package/dist/org/index.mjs +1 -1
  57. package/dist/permissions/index.mjs +1 -1
  58. package/dist/{permissions-CA5zg0yK.mjs → permissions-D9_AAtvz.mjs} +2 -2
  59. package/dist/plugins/index.d.mts +1 -1
  60. package/dist/plugins/index.mjs +3 -3
  61. package/dist/plugins/response-cache.d.mts +1 -1
  62. package/dist/plugins/response-cache.mjs +1 -1
  63. package/dist/plugins/tracing-entry.mjs +1 -1
  64. package/dist/presets/index.d.mts +2 -2
  65. package/dist/presets/index.mjs +2 -2
  66. package/dist/presets/multiTenant.d.mts +2 -2
  67. package/dist/presets/multiTenant.mjs +2 -2
  68. package/dist/{presets-C9QXJV1u.mjs → presets-CD3e6M7c.mjs} +3 -3
  69. package/dist/{queryCachePlugin-ClosZdNS.mjs → queryCachePlugin-XtFplYO9.mjs} +1 -1
  70. package/dist/registry/index.d.mts +1 -1
  71. package/dist/{resourceToTools-PMFE8HIv.mjs → resourceToTools-CN0lwJrL.mjs} +1 -1
  72. package/dist/scope/index.d.mts +2 -2
  73. package/dist/scope/index.mjs +2 -2
  74. package/dist/{sse-BkViJPlT.mjs → sse-BF7GR7IB.mjs} +1 -1
  75. package/dist/testing/index.d.mts +2 -2
  76. package/dist/testing/index.mjs +1 -1
  77. package/dist/types/index.d.mts +3 -3
  78. package/dist/types/index.mjs +23 -2
  79. package/dist/{types-C6TQjtdi.mjs → types-BhtYdxZU.mjs} +26 -1
  80. package/dist/{types-BJmgxNbF.d.mts → types-ByCPlfZ1.d.mts} +1 -1
  81. package/dist/{types-Dt0-AI6E.d.mts → types-Guk83PDz.d.mts} +2 -2
  82. package/dist/utils/index.d.mts +2 -2
  83. package/dist/utils/index.mjs +1 -1
  84. package/package.json +5 -4
  85. package/skills/arc/SKILL.md +64 -6
  86. package/skills/arc/references/mcp.md +135 -0
@@ -1,5 +1,5 @@
1
- import { Lt as ResourceDefinition, l as AnyRecord, zt as CrudRepository } from "../interface-DGmPxakH.mjs";
2
- import { r as CreateAppOptions } from "../types-Dt0-AI6E.mjs";
1
+ import { Bt as ResourceDefinition, Ht as CrudRepository, l as AnyRecord } from "../interface-BnNjdl33.mjs";
2
+ import { r as CreateAppOptions } from "../types-Guk83PDz.mjs";
3
3
  import Fastify, { FastifyInstance, FastifyServerOptions } from "fastify";
4
4
  import { Connection } from "mongoose";
5
5
  import { Mock } from "vitest";
@@ -1752,7 +1752,7 @@ function runEventTests(resourceName, displayName, events) {
1752
1752
  * ```
1753
1753
  */
1754
1754
  async function createTestApp(options = {}) {
1755
- const { createApp } = await import("../createApp-ByWNRsZj.mjs").then((n) => n.r);
1755
+ const { createApp } = await import("../createApp-oic3-ieX.mjs").then((n) => n.r);
1756
1756
  const { useInMemoryDb = true, mongoUri: providedMongoUri, ...appOptions } = options;
1757
1757
  const defaultAuth = {
1758
1758
  type: "jwt",
@@ -1,4 +1,4 @@
1
- import { a as AUTHENTICATED_SCOPE, c as getOrgId, g as isMember, h as isElevated, l as getOrgRoles, m as isAuthenticated, n as ElevationOptions, o as PUBLIC_SCOPE, p as hasOrgAccess, s as RequestScope, t as ElevationEvent, u as getTeamId } from "../elevation-Ca_yveIO.mjs";
2
- import { $ as RegistryStats, A as HealthCheck, At as FastifyHandler, B as MiddlewareConfig, Bt as InferDoc, C as EventDefinition, D as FastifyWithDecorators, E as FastifyWithAuth, F as IntrospectionData, G as ParsedQuery, H as ObjectId, Ht as PaginationParams, I as IntrospectionPluginOptions, J as PresetHook, K as PopulateOption, L as JWTPayload, M as InferAdapterDoc, Mt as IControllerResponse, N as InferDocType, Nt as IRequestContext, O as FieldRule, Ot as ControllerHandler, P as InferResourceDoc, Pt as RouteHandler, Q as RegistryEntry, R as JwtContext, S as CrudSchemas, T as FastifyRequestExtras, U as OpenApiSchemas, Ut as QueryOptions, V as MiddlewareHandler, Vt as PaginatedResult, W as OwnershipCheck, X as QueryParserInterface, Y as PresetResult, Z as RateLimitConfig, _ as ConfigError, _t as ValidateOptions, at as ResourceHooks, b as CrudRouteKey, c as AdditionalRoute, ct as RouteHandlerMethod, d as ArcDecorator, dt as TokenPair, et as RequestContext, f as ArcInternalMetadata, ft as TypedController, g as AuthenticatorContext, gt as UserOrganization, h as Authenticator, ht as UserLike, it as ResourceConfig, j as HealthOptions, jt as IController, k as GracefulShutdownOptions, kt as ControllerLike, l as AnyRecord, lt as RouteSchemaOptions, m as AuthPluginOptions, mt as TypedResourceConfig, nt as RequestWithExtras, ot as ResourceMetadata, p as AuthHelpers, pt as TypedRepository, q as PresetFunction, rt as ResourceCacheConfig, st as ResourcePermissions, tt as RequestIdOptions, u as ApiResponse, ut as ServiceContext, v as ControllerQueryOptions, vt as ValidationResult, w as EventsDecorator, x as CrudRouterOptions, xt as BaseControllerOptions, y as CrudController, yt as getUserId, z as LookupOption, zt as CrudRepository } from "../interface-DGmPxakH.mjs";
1
+ import { _ as isMember, a as AUTHENTICATED_SCOPE, d as getTeamId, g as isElevated, h as isAuthenticated, l as getOrgId, m as hasOrgAccess, n as ElevationOptions, o as PUBLIC_SCOPE, s as RequestScope, t as ElevationEvent, u as getOrgRoles } from "../elevation-C_taLQrM.mjs";
2
+ import { $ as RegistryEntry, A as GracefulShutdownOptions, B as LookupOption, C as CrudSchemas, D as FastifyWithAuth, E as FastifyRequestExtras, F as InferResourceDoc, Ft as IControllerResponse, G as OwnershipCheck, Gt as PaginationParams, H as MiddlewareHandler, Ht as CrudRepository, I as IntrospectionData, It as IRequestContext, J as PresetFunction, K as ParsedQuery, Kt as QueryOptions, L as IntrospectionPluginOptions, Lt as RouteHandler, M as HealthOptions, Mt as ControllerLike, N as InferAdapterDoc, Nt as FastifyHandler, O as FastifyWithDecorators, P as InferDocType, Pt as IController, Q as RateLimitConfig, R as JWTPayload, S as CrudRouterOptions, St as getUserId, T as EventsDecorator, U as ObjectId, Ut as InferDoc, V as MiddlewareConfig, W as OpenApiSchemas, Wt as PaginatedResult, X as PresetResult, Y as PresetHook, Z as QueryParserInterface, _ as AuthenticatorContext, _t as UserLike, at as ResourceConfig, b as CrudController, bt as ValidationResult, c as AdditionalRoute, ct as ResourceMetadata, d as ArcDecorator, dt as RouteSchemaOptions, et as RegistryStats, f as ArcInternalMetadata, ft as ServiceContext, g as Authenticator, gt as TypedResourceConfig, h as AuthPluginOptions, ht as TypedRepository, it as ResourceCacheConfig, j as HealthCheck, jt as ControllerHandler, k as FieldRule, l as AnyRecord, lt as ResourcePermissions, m as AuthHelpers, mt as TypedController, nt as RequestIdOptions, ot as ResourceHookContext, p as ArcRequest, pt as TokenPair, q as PopulateOption, rt as RequestWithExtras, st as ResourceHooks, tt as RequestContext, u as ApiResponse, ut as RouteHandlerMethod, v as ConfigError, vt as UserOrganization, w as EventDefinition, wt as BaseControllerOptions, x as CrudRouteKey, xt as envelope, y as ControllerQueryOptions, yt as ValidateOptions, z as JwtContext } from "../interface-BnNjdl33.mjs";
3
3
  import { i as UserBase, n as PermissionContext, r as PermissionResult, t as PermissionCheck } from "../types-BNUccdcf.mjs";
4
- export { AUTHENTICATED_SCOPE, AdditionalRoute, AnyRecord, ApiResponse, ArcDecorator, ArcInternalMetadata, AuthHelpers, AuthPluginOptions, Authenticator, AuthenticatorContext, BaseControllerOptions, ConfigError, ControllerHandler, ControllerLike, ControllerQueryOptions, CrudController, CrudRepository, CrudRouteKey, CrudRouterOptions, CrudSchemas, ElevationEvent, ElevationOptions, EventDefinition, EventsDecorator, FastifyHandler, FastifyRequestExtras, FastifyWithAuth, FastifyWithDecorators, FieldRule, GracefulShutdownOptions, HealthCheck, HealthOptions, IController, IControllerResponse, IRequestContext, InferAdapterDoc, InferDoc, InferDocType, InferResourceDoc, IntrospectionData, IntrospectionPluginOptions, JWTPayload, JwtContext, LookupOption, MiddlewareConfig, MiddlewareHandler, ObjectId, OpenApiSchemas, OwnershipCheck, PUBLIC_SCOPE, PaginatedResult, PaginationParams, ParsedQuery, PermissionCheck, PermissionContext, PermissionResult, PopulateOption, PresetFunction, PresetHook, PresetResult, QueryOptions, QueryParserInterface, RateLimitConfig, RegistryEntry, RegistryStats, RequestContext, RequestIdOptions, RequestScope, RequestWithExtras, ResourceCacheConfig, ResourceConfig, ResourceHooks, ResourceMetadata, ResourcePermissions, RouteHandler, RouteHandlerMethod, RouteSchemaOptions, ServiceContext, TokenPair, TypedController, TypedRepository, TypedResourceConfig, UserBase, UserLike, UserOrganization, ValidateOptions, ValidationResult, getOrgId, getOrgRoles, getTeamId, getUserId, hasOrgAccess, isAuthenticated, isElevated, isMember };
4
+ export { AUTHENTICATED_SCOPE, AdditionalRoute, AnyRecord, ApiResponse, ArcDecorator, ArcInternalMetadata, ArcRequest, AuthHelpers, AuthPluginOptions, Authenticator, AuthenticatorContext, BaseControllerOptions, ConfigError, ControllerHandler, ControllerLike, ControllerQueryOptions, CrudController, CrudRepository, CrudRouteKey, CrudRouterOptions, CrudSchemas, ElevationEvent, ElevationOptions, EventDefinition, EventsDecorator, FastifyHandler, FastifyRequestExtras, FastifyWithAuth, FastifyWithDecorators, FieldRule, GracefulShutdownOptions, HealthCheck, HealthOptions, IController, IControllerResponse, IRequestContext, InferAdapterDoc, InferDoc, InferDocType, InferResourceDoc, IntrospectionData, IntrospectionPluginOptions, JWTPayload, JwtContext, LookupOption, MiddlewareConfig, MiddlewareHandler, ObjectId, OpenApiSchemas, OwnershipCheck, PUBLIC_SCOPE, PaginatedResult, PaginationParams, ParsedQuery, PermissionCheck, PermissionContext, PermissionResult, PopulateOption, PresetFunction, PresetHook, PresetResult, QueryOptions, QueryParserInterface, RateLimitConfig, RegistryEntry, RegistryStats, RequestContext, RequestIdOptions, RequestScope, RequestWithExtras, ResourceCacheConfig, ResourceConfig, ResourceHookContext, ResourceHooks, ResourceMetadata, ResourcePermissions, RouteHandler, RouteHandlerMethod, RouteSchemaOptions, ServiceContext, TokenPair, TypedController, TypedRepository, TypedResourceConfig, UserBase, UserLike, UserOrganization, ValidateOptions, ValidationResult, envelope, getOrgId, getOrgRoles, getTeamId, getUserId, hasOrgAccess, isAuthenticated, isElevated, isMember };
@@ -1,6 +1,27 @@
1
- import { a as getTeamId, c as hasOrgAccess, d as isMember, i as getOrgRoles, l as isAuthenticated, n as PUBLIC_SCOPE, r as getOrgId, t as AUTHENTICATED_SCOPE, u as isElevated } from "../types-C6TQjtdi.mjs";
1
+ import { a as getOrgRoles, d as isElevated, f as isMember, i as getOrgId, l as hasOrgAccess, n as PUBLIC_SCOPE, o as getTeamId, t as AUTHENTICATED_SCOPE, u as isAuthenticated } from "../types-BhtYdxZU.mjs";
2
2
  //#region src/types/index.ts
3
3
  /**
4
+ * Response envelope helper — wraps data in Arc's standard `{ success, data }` format.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * import { envelope } from '@classytic/arc';
9
+ *
10
+ * handler: async (req, reply) => {
11
+ * const data = await getResults();
12
+ * return envelope(data);
13
+ * // → { success: true, data }
14
+ * }
15
+ * ```
16
+ */
17
+ function envelope(data, meta) {
18
+ return {
19
+ success: true,
20
+ data,
21
+ ...meta
22
+ };
23
+ }
24
+ /**
4
25
  * Extract user ID from a user object (supports both id and _id)
5
26
  */
6
27
  function getUserId(user) {
@@ -9,4 +30,4 @@ function getUserId(user) {
9
30
  return id ? String(id) : void 0;
10
31
  }
11
32
  //#endregion
12
- export { AUTHENTICATED_SCOPE, PUBLIC_SCOPE, getOrgId, getOrgRoles, getTeamId, getUserId, hasOrgAccess, isAuthenticated, isElevated, isMember };
33
+ export { AUTHENTICATED_SCOPE, PUBLIC_SCOPE, envelope, getOrgId, getOrgRoles, getTeamId, getUserId, hasOrgAccess, isAuthenticated, isElevated, isMember };
@@ -58,9 +58,34 @@ function getUserRoles(scope) {
58
58
  if (scope.kind === "member") return scope.userRoles;
59
59
  return [];
60
60
  }
61
+ /**
62
+ * Org context — canonical extraction from a Fastify request.
63
+ *
64
+ * Works regardless of auth type (JWT, Better Auth, custom) by reading
65
+ * `request.scope` and `request.user`. Eliminates the need for each resource
66
+ * to re-invent org extraction from headers/user/scope.
67
+ *
68
+ * @example
69
+ * ```typescript
70
+ * import { getOrgContext } from '@classytic/arc/scope';
71
+ *
72
+ * handler: async (request, reply) => {
73
+ * const { userId, organizationId, roles, orgRoles } = getOrgContext(request);
74
+ * }
75
+ * ```
76
+ */
77
+ function getOrgContext(request) {
78
+ const scope = request.scope ?? { kind: "public" };
79
+ return {
80
+ userId: getUserId(scope) ?? request.user?.id ?? request.user?._id,
81
+ organizationId: getOrgId(scope) ?? request.user?.organizationId ?? request.headers?.["x-organization-id"],
82
+ roles: getUserRoles(scope),
83
+ orgRoles: getOrgRoles(scope)
84
+ };
85
+ }
61
86
  /** Default public scope — used as initial decoration value */
62
87
  const PUBLIC_SCOPE = Object.freeze({ kind: "public" });
63
88
  /** Default authenticated scope — used when user is logged in but no org */
64
89
  const AUTHENTICATED_SCOPE = Object.freeze({ kind: "authenticated" });
65
90
  //#endregion
66
- export { getTeamId as a, hasOrgAccess as c, isMember as d, getOrgRoles as i, isAuthenticated as l, PUBLIC_SCOPE as n, getUserId as o, getOrgId as r, getUserRoles as s, AUTHENTICATED_SCOPE as t, isElevated as u };
91
+ export { getOrgRoles as a, getUserRoles as c, isElevated as d, isMember as f, getOrgId as i, hasOrgAccess as l, PUBLIC_SCOPE as n, getTeamId as o, getOrgContext as r, getUserId as s, AUTHENTICATED_SCOPE as t, isAuthenticated as u };
@@ -1,4 +1,4 @@
1
- import { Lt as ResourceDefinition } from "./interface-DGmPxakH.mjs";
1
+ import { Bt as ResourceDefinition } from "./interface-BnNjdl33.mjs";
2
2
  import { z } from "zod";
3
3
 
4
4
  //#region src/integrations/mcp/types.d.ts
@@ -1,5 +1,5 @@
1
- import { n as ElevationOptions } from "./elevation-Ca_yveIO.mjs";
2
- import { h as Authenticator } from "./interface-DGmPxakH.mjs";
1
+ import { n as ElevationOptions } from "./elevation-C_taLQrM.mjs";
2
+ import { g as Authenticator } from "./interface-BnNjdl33.mjs";
3
3
  import { t as ExternalOpenApiPaths } from "./externalPaths-DpO-s7r8.mjs";
4
4
  import { i as CacheStore } from "./interface-D_BWALyZ.mjs";
5
5
  import { r as QueryCachePluginOptions } from "./queryCachePlugin-DcmETvcB.mjs";
@@ -1,5 +1,5 @@
1
- import { G as ParsedQuery, U as OpenApiSchemas, X as QueryParserInterface, l as AnyRecord } from "../interface-DGmPxakH.mjs";
2
- import { a as NotFoundError, c as RateLimitError, d as ValidationError, f as createError, i as ForbiddenError, l as ServiceUnavailableError, n as ConflictError, o as OrgAccessDeniedError, p as isArcError, r as ErrorDetails, s as OrgRequiredError, t as ArcError, u as UnauthorizedError } from "../errors-CPpvPHT0.mjs";
1
+ import { K as ParsedQuery, W as OpenApiSchemas, Z as QueryParserInterface, l as AnyRecord } from "../interface-BnNjdl33.mjs";
2
+ import { a as NotFoundError, c as RateLimitError, d as ValidationError, i as ForbiddenError, l as ServiceUnavailableError, m as isArcError, n as ConflictError, o as OrgAccessDeniedError, p as createError, r as ErrorDetails, s as OrgRequiredError, t as ArcError, u as UnauthorizedError } from "../errors-CcVbl1-T.mjs";
3
3
  import { a as CircuitBreakerStats, c as createCircuitBreakerRegistry, i as CircuitBreakerRegistry, n as CircuitBreakerError, o as CircuitState, r as CircuitBreakerOptions, s as createCircuitBreaker, t as CircuitBreaker } from "../circuitBreaker-JP2GdJ4b.mjs";
4
4
  import { FastifyInstance } from "fastify";
5
5
 
@@ -1,7 +1,7 @@
1
1
  import { n as createQueryParser, t as ArcQueryParser } from "../queryParser-CgCtsjti.mjs";
2
2
  import { a as createCircuitBreaker, i as CircuitState, n as CircuitBreakerError, o as createCircuitBreakerRegistry, r as CircuitBreakerRegistry, t as CircuitBreaker } from "../circuitBreaker-BOBOpN2w.mjs";
3
3
  import { _ as defineCompensation, a as getListQueryParams, c as listResponse, d as paginateWrapper, f as paginationSchema, g as wrapResponse, h as successResponseSchema, i as getDefaultCrudSchemas, l as messageWrapper, m as responses, n as deleteResponse, o as itemResponse, p as queryParams, r as errorResponseSchema, s as itemWrapper, t as createStateMachine, u as mutationResponse, v as withCompensation } from "../utils-Dc0WhlIl.mjs";
4
- import { a as OrgAccessDeniedError, c as ServiceUnavailableError, d as createError, f as isArcError, i as NotFoundError, l as UnauthorizedError, n as ConflictError, o as OrgRequiredError, r as ForbiddenError, s as RateLimitError, t as ArcError, u as ValidationError } from "../errors-rxhfP7Hf.mjs";
4
+ import { a as OrgAccessDeniedError, c as ServiceUnavailableError, 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-NoQKsbAT.mjs";
5
5
  import { a as toJsonSchema, i as isZodSchema, n as convertRouteSchema, r as isJsonSchema, t as convertOpenApiSchemas } from "../schemaConverter-DjzHpFam.mjs";
6
6
  import { t as hasEvents } from "../typeGuards-Cj5Rgvlg.mjs";
7
7
  export { ArcError, ArcQueryParser, CircuitBreaker, CircuitBreakerError, CircuitBreakerRegistry, CircuitState, ConflictError, ForbiddenError, NotFoundError, OrgAccessDeniedError, OrgRequiredError, RateLimitError, ServiceUnavailableError, UnauthorizedError, ValidationError, convertOpenApiSchemas, convertRouteSchema, createCircuitBreaker, createCircuitBreakerRegistry, createError, createQueryParser, createStateMachine, defineCompensation, deleteResponse, errorResponseSchema, getDefaultCrudSchemas, getListQueryParams, hasEvents, isArcError, isJsonSchema, isZodSchema, itemResponse, itemWrapper, listResponse, messageWrapper, mutationResponse, paginateWrapper, paginationSchema, queryParams, responses, successResponseSchema, toJsonSchema, withCompensation, wrapResponse };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classytic/arc",
3
- "version": "2.4.2",
3
+ "version": "2.5.0",
4
4
  "description": "Resource-oriented backend framework for Fastify — clean, minimal, powerful, tree-shakable",
5
5
  "type": "module",
6
6
  "exports": {
@@ -220,7 +220,7 @@
220
220
  "node": ">=22"
221
221
  },
222
222
  "peerDependencies": {
223
- "@classytic/mongokit": ">=3.4.3",
223
+ "@classytic/mongokit": ">=3.5.0",
224
224
  "@classytic/streamline": ">=2.0.0",
225
225
  "@fastify/cors": "^11.0.0",
226
226
  "@fastify/helmet": "^13.0.0",
@@ -244,7 +244,7 @@
244
244
  "fastify-raw-body": "^5.0.0",
245
245
  "ioredis": "^5.0.0",
246
246
  "mongodb": "^6.0.0 || ^7.0.0",
247
- "mongoose": "^8.0.0 || ^9.0.0",
247
+ "mongoose": ">=9.0.0",
248
248
  "pino-pretty": "^13.0.0",
249
249
  "zod": "^4.0.0"
250
250
  },
@@ -337,7 +337,7 @@
337
337
  },
338
338
  "devDependencies": {
339
339
  "@biomejs/biome": "^2.4.10",
340
- "@classytic/mongokit": "^3.4.3",
340
+ "@classytic/mongokit": "^3.5.2",
341
341
  "@fastify/jwt": "^10.0.0",
342
342
  "@fastify/multipart": "^9.0.0",
343
343
  "@fastify/type-provider-typebox": "^6.0.0",
@@ -349,6 +349,7 @@
349
349
  "@vitest/coverage-v8": "^3.2.4",
350
350
  "fastify-raw-body": "^5.0.0",
351
351
  "jsonwebtoken": "^9.0.0",
352
+ "knip": "^6.3.0",
352
353
  "mongodb": "^7.1.0",
353
354
  "mongodb-memory-server": "^11.0.1",
354
355
  "tsdown": "^0.21.7",
@@ -314,6 +314,26 @@ const app = await createApp({
314
314
 
315
315
  ## Hooks
316
316
 
317
+ **Inline on resource (recommended):**
318
+
319
+ ```typescript
320
+ defineResource({
321
+ name: 'chat',
322
+ hooks: {
323
+ beforeCreate: async (ctx) => { ctx.data.slug = slugify(ctx.data.name); },
324
+ afterCreate: async (ctx) => { analytics.track('created', { id: ctx.data._id, user: ctx.user?.id }); },
325
+ beforeUpdate: async (ctx) => { console.log('Updating', ctx.meta?.id, 'existing:', ctx.meta?.existing); },
326
+ afterUpdate: async (ctx) => { await invalidateCache(ctx.data._id); },
327
+ beforeDelete: async (ctx) => { if (ctx.data.isProtected) throw new Error('Cannot delete'); },
328
+ afterDelete: async (ctx) => { await cleanupFiles(ctx.meta?.id); },
329
+ },
330
+ });
331
+ ```
332
+
333
+ `ResourceHookContext`: `{ data, user?, meta? }` — `data` is the document, `meta` has `id` and `existing` (for update/delete).
334
+
335
+ **App-level (cross-resource):**
336
+
317
337
  ```typescript
318
338
  import { createHookSystem, beforeCreate, afterUpdate } from '@classytic/arc/hooks';
319
339
 
@@ -360,7 +380,7 @@ GET /products?lookup[cat][from]=categories&...&lookup[cat][select]=name,slug
360
380
 
361
381
  Operators: `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `nin`, `like`, `regex`, `exists`
362
382
 
363
- **Custom query parser (e.g., MongoKit for $lookup support):**
383
+ **Custom query parser (e.g., MongoKit >=3.4.5 for $lookup, whitelists, MCP auto-derive):**
364
384
 
365
385
  ```typescript
366
386
  import { QueryParser } from '@classytic/mongokit';
@@ -368,11 +388,16 @@ import { QueryParser } from '@classytic/mongokit';
368
388
  defineResource({
369
389
  name: 'product',
370
390
  adapter: createMongooseAdapter({ model: ProductModel, repository: productRepo }),
371
- queryParser: new QueryParser(), // enables lookup, advanced populate, keyset pagination
391
+ queryParser: new QueryParser({
392
+ allowedFilterFields: ['status', 'category', 'orgId'], // whitelist filter fields
393
+ allowedSortFields: ['createdAt', 'price'], // whitelist sort fields
394
+ allowedOperators: ['eq', 'gte', 'lte', 'in'], // whitelist operators
395
+ }),
396
+ // MCP auto-derives filterableFields from queryParser — no duplication needed
372
397
  schemaOptions: {
373
398
  query: {
374
- allowedPopulate: ['category', 'brand'], // whitelist populate paths
375
- allowedLookups: ['categories', 'brands'], // whitelist lookup collections
399
+ allowedPopulate: ['category', 'brand'],
400
+ allowedLookups: ['categories', 'brands'],
376
401
  },
377
402
  },
378
403
  });
@@ -381,10 +406,14 @@ defineResource({
381
406
  ## Error Classes
382
407
 
383
408
  ```typescript
384
- import { ArcError, NotFoundError, ValidationError, UnauthorizedError, ForbiddenError } from '@classytic/arc';
385
- throw new NotFoundError('Product not found'); // 404
409
+ import { ArcError, NotFoundError, ValidationError, createDomainError } from '@classytic/arc';
410
+ throw new NotFoundError('Product not found'); // 404
411
+ throw createDomainError('MEMBER_NOT_FOUND', 'Not found', 404); // domain error with code
412
+ throw createDomainError('SELF_REFERRAL', 'Cannot self-refer', 422, { field: 'referralCode' });
386
413
  ```
387
414
 
415
+ Error handler catches: `ArcError` → `.statusCode` (Fastify) → `.status` (MongoKit, http-errors) → `errorMap` → Mongoose/MongoDB → fallback 500. DB-agnostic — any error with `.status` or `.statusCode` gets the correct HTTP response.
416
+
388
417
  ## Compensating Transaction
389
418
 
390
419
  In-process rollback for multi-step operations. Not a distributed saga — use Temporal/Streamline for that.
@@ -474,6 +503,35 @@ src/resources/order/
474
503
 
475
504
  Generate: `arc generate resource order --mcp` | Wire: `extraTools: [fulfillOrderTool]`
476
505
 
506
+ **DX helpers** (v2.4.4):
507
+
508
+ ```typescript
509
+ // Typed request for wrapHandler: false routes — no more (req as any).user
510
+ import type { ArcRequest } from '@classytic/arc';
511
+ handler: async (req: ArcRequest, reply) => { req.user?.id; req.scope; req.signal; }
512
+
513
+ // Response envelope — no manual { success, data } wrapping
514
+ import { envelope } from '@classytic/arc';
515
+ reply.send(envelope(data, { total: 100 }));
516
+
517
+ // Canonical org extraction — replaces 19 duplicated patterns
518
+ import { getOrgContext } from '@classytic/arc/scope';
519
+ const { userId, organizationId, roles, orgRoles } = getOrgContext(request);
520
+
521
+ // Domain errors with auto HTTP status mapping
522
+ import { createDomainError } from '@classytic/arc';
523
+ throw createDomainError('SELF_REFERRAL', 'Cannot refer yourself', 422);
524
+
525
+ // Resource lifecycle hook — wire singletons during registration
526
+ defineResource({ name: 'notification', onRegister: (f) => setSseManager(f.sseManager) });
527
+
528
+ // SSE auth — preAuth runs BEFORE auth middleware (EventSource can't set headers)
529
+ additionalRoutes: [{ preAuth: [(req) => { req.headers.authorization = `Bearer ${req.query.token}`; }] }]
530
+
531
+ // SSE streaming — auto headers + bypasses response wrapper
532
+ additionalRoutes: [{ streamResponse: true, handler: async (req, reply) => reply.send(stream) }]
533
+ ```
534
+
477
535
  ## Subpath Imports
478
536
 
479
537
  ```typescript
@@ -429,3 +429,138 @@ await app.register(mcpPlugin, {
429
429
  - `DELETE /mcp` — terminates session
430
430
 
431
431
  Sessions: lazily created, TTL-cached, LRU-evicted at max capacity, auto-cleaned on shutdown.
432
+
433
+ ## Health Endpoint
434
+
435
+ `GET /mcp/health` — no MCP protocol needed, plain JSON:
436
+
437
+ ```json
438
+ {
439
+ "status": "ok",
440
+ "mode": "stateless",
441
+ "tools": 11,
442
+ "resources": 2,
443
+ "toolNames": ["list_products", "get_product", ...],
444
+ "sessions": null
445
+ }
446
+ ```
447
+
448
+ Use to verify the MCP server is alive before configuring Claude CLI.
449
+
450
+ ## DX Helpers (v2.4.4)
451
+
452
+ ### ArcRequest — Typed Fastify Request
453
+
454
+ For `wrapHandler: false` routes, use `ArcRequest` instead of `(req as any).user`:
455
+
456
+ ```typescript
457
+ import type { ArcRequest } from '@classytic/arc';
458
+
459
+ handler: async (req: ArcRequest, reply) => {
460
+ req.user?.id; // typed
461
+ req.scope.organizationId; // typed (when member)
462
+ req.signal; // AbortSignal (Fastify 5 built-in)
463
+ }
464
+ ```
465
+
466
+ ### envelope() — Response Helper
467
+
468
+ ```typescript
469
+ import { envelope } from '@classytic/arc';
470
+
471
+ handler: async (req, reply) => {
472
+ const data = await service.getResults();
473
+ return reply.send(envelope(data));
474
+ // → { success: true, data }
475
+ return reply.send(envelope(data, { total: 100, page: 1 }));
476
+ // → { success: true, data, total: 100, page: 1 }
477
+ }
478
+ ```
479
+
480
+ ### getOrgContext() — Canonical Org Extraction
481
+
482
+ Eliminates duplicated `req.user.organizationId || req.headers['x-organization-id']` patterns:
483
+
484
+ ```typescript
485
+ import { getOrgContext } from '@classytic/arc/scope';
486
+
487
+ handler: async (req, reply) => {
488
+ const { userId, organizationId, roles, orgRoles } = getOrgContext(req);
489
+ // Works regardless of auth type (JWT, Better Auth, custom)
490
+ }
491
+ ```
492
+
493
+ ### createDomainError() — Error Factory
494
+
495
+ Eliminates manual `if (err.code) return status` mapping:
496
+
497
+ ```typescript
498
+ import { createDomainError } from '@classytic/arc';
499
+
500
+ throw createDomainError('MEMBER_NOT_FOUND', 'Member does not exist', 404);
501
+ throw createDomainError('SELF_REFERRAL', 'Cannot refer yourself', 422);
502
+ throw createDomainError('INSUFFICIENT_BALANCE', 'Not enough credits', 402, { balance: 0 });
503
+ // Arc's error handler auto-maps statusCode to HTTP response
504
+ ```
505
+
506
+ ### onRegister — Resource Lifecycle Hook
507
+
508
+ Called during plugin registration with the scoped Fastify instance:
509
+
510
+ ```typescript
511
+ defineResource({
512
+ name: 'notification',
513
+ onRegister: (fastify) => {
514
+ setSseManager(fastify.sseManager);
515
+ },
516
+ })
517
+ ```
518
+
519
+ ### preAuth — Pre-Auth Handlers for SSE/WebSocket
520
+
521
+ Run before auth middleware. Use for promoting `?token=` to `Authorization` header (EventSource can't set headers):
522
+
523
+ ```typescript
524
+ additionalRoutes: [{
525
+ method: 'GET',
526
+ path: '/stream',
527
+ wrapHandler: false,
528
+ permissions: requireAuth(),
529
+ preAuth: [(req) => {
530
+ const token = req.query?.token;
531
+ if (token) req.headers.authorization = `Bearer ${token}`;
532
+ }],
533
+ handler: sseHandler,
534
+ }]
535
+ ```
536
+
537
+ ### streamResponse — SSE Route Flag
538
+
539
+ Auto-sets SSE headers and bypasses Arc's response wrapper:
540
+
541
+ ```typescript
542
+ additionalRoutes: [{
543
+ method: 'POST',
544
+ path: '/stream',
545
+ streamResponse: true, // SSE headers + no { success, data } wrapper
546
+ permissions: requireAuth(),
547
+ handler: async (request, reply) => {
548
+ const { stream } = await generateStream({ abortSignal: request.signal });
549
+ return reply.send(stream);
550
+ },
551
+ }]
552
+ ```
553
+
554
+ ## Test Coverage
555
+
556
+ 165 test files, 2439 tests. MCP-specific:
557
+
558
+ | Test File | Tests | Covers |
559
+ |-----------|-------|--------|
560
+ | `mcp-auth-e2e.test.ts` | 16 | All auth modes, multi-tenancy, permission filters, async permissions |
561
+ | `mcp-dx-features.test.ts` | 14 | include, names, prefix, disableDefaultRoutes, mcpHandler, guards, CRUD lifecycle |
562
+ | `resourceToTools.test.ts` | 12 | Tool generation, annotations, field hiding, soft delete |
563
+ | `createMcpServer.test.ts` | 10 | Server creation, tool registration, InMemoryTransport |
564
+ | `guards.test.ts` | 8 | requireAuth, requireOrg, requireRole, customGuard, composition |
565
+ | `dx-features.test.ts` | 17 | envelope, getOrgContext, createDomainError, onRegister, preAuth, streamResponse |
566
+ | Others | 32 | fieldRulesToZod, defineTool, definePrompt, buildRequestContext, sessionCache, authCache |