@classytic/arc 2.7.1 → 2.7.7

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 (107) hide show
  1. package/README.md +14 -2
  2. package/dist/{HookSystem-D7lfx--K.mjs → HookSystem-BNYKnrXF.mjs} +3 -2
  3. package/dist/adapters/index.d.mts +2 -2
  4. package/dist/audit/index.d.mts +1 -1
  5. package/dist/audit/index.mjs +1 -1
  6. package/dist/audit/mongodb.d.mts +1 -1
  7. package/dist/audit/mongodb.mjs +1 -1
  8. package/dist/auth/index.d.mts +4 -4
  9. package/dist/auth/index.mjs +2 -2
  10. package/dist/auth/redis-session.d.mts +1 -1
  11. package/dist/{betterAuthOpenApi-CCw3YX0g.mjs → betterAuthOpenApi-EkPaMWNM.mjs} +1 -1
  12. package/dist/cache/index.d.mts +2 -2
  13. package/dist/cli/commands/docs.mjs +1 -1
  14. package/dist/cli/commands/generate.mjs +1 -1
  15. package/dist/core/index.d.mts +2 -2
  16. package/dist/core/index.mjs +2 -2
  17. package/dist/{core-BWekSEju.mjs → core-B_zEeA2b.mjs} +1 -1
  18. package/dist/{createApp-B_nvKNAQ.mjs → createApp-D7e77m8C.mjs} +18 -7
  19. package/dist/{defineResource-DZzyl4a4.mjs → defineResource-BW2dMCu9.mjs} +1 -6
  20. package/dist/docs/index.d.mts +2 -2
  21. package/dist/docs/index.mjs +1 -1
  22. package/dist/dynamic/index.d.mts +2 -2
  23. package/dist/dynamic/index.mjs +1 -1
  24. package/dist/{errorHandler-DXUttWEO.mjs → errorHandler-CH8wk1eD.mjs} +16 -1
  25. package/dist/{errorHandler-COa51ho_.d.mts → errorHandler-pCpEtNd7.d.mts} +46 -2
  26. package/dist/{eventPlugin-DsaNNXzZ.mjs → eventPlugin-B6U_nCFU.mjs} +3 -2
  27. package/dist/{eventPlugin-BgLxJkIB.d.mts → eventPlugin-CdvUoUna.d.mts} +1 -1
  28. package/dist/events/index.d.mts +3 -3
  29. package/dist/events/index.mjs +1 -1
  30. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  31. package/dist/events/transports/redis.d.mts +1 -1
  32. package/dist/factory/index.d.mts +1 -1
  33. package/dist/factory/index.mjs +7 -6
  34. package/dist/hooks/index.d.mts +1 -1
  35. package/dist/hooks/index.mjs +1 -1
  36. package/dist/idempotency/index.d.mts +3 -3
  37. package/dist/idempotency/mongodb.d.mts +1 -1
  38. package/dist/idempotency/mongodb.mjs +1 -3
  39. package/dist/idempotency/redis.d.mts +1 -1
  40. package/dist/{index-BYpRGXif.d.mts → index-B0extFr4.d.mts} +3 -3
  41. package/dist/{index-KXM8_JmQ.d.mts → index-BjShrzoj.d.mts} +3 -3
  42. package/dist/{index-StgFaQKD.d.mts → index-C9eYNjGR.d.mts} +1 -1
  43. package/dist/index.d.mts +8 -7
  44. package/dist/index.mjs +3 -3
  45. package/dist/integrations/event-gateway.d.mts +1 -1
  46. package/dist/integrations/event-gateway.mjs +1 -1
  47. package/dist/integrations/index.d.mts +1 -1
  48. package/dist/integrations/mcp/index.d.mts +2 -2
  49. package/dist/integrations/mcp/index.mjs +8 -5
  50. package/dist/integrations/mcp/testing.d.mts +1 -1
  51. package/dist/integrations/mcp/testing.mjs +1 -1
  52. package/dist/integrations/streamline.d.mts +39 -7
  53. package/dist/integrations/streamline.mjs +106 -4
  54. package/dist/integrations/webhooks.d.mts +58 -1
  55. package/dist/integrations/webhooks.mjs +78 -7
  56. package/dist/integrations/websocket.d.mts +7 -1
  57. package/dist/integrations/websocket.mjs +7 -1
  58. package/dist/{interface-Dwzqt4mn.d.mts → interface-B91alUzq.d.mts} +4 -4
  59. package/dist/{mongodb-Bq90j-Uj.d.mts → mongodb-B7zupyck.d.mts} +1 -1
  60. package/dist/{mongodb-DdyYlIXg.d.mts → mongodb-Cgu9F1Nd.d.mts} +1 -1
  61. package/dist/{openapi-C5UhIeWu.mjs → openapi-D7Z7VODz.mjs} +1 -1
  62. package/dist/org/index.d.mts +2 -2
  63. package/dist/permissions/index.d.mts +3 -3
  64. package/dist/plugins/index.d.mts +52 -5
  65. package/dist/plugins/index.mjs +6 -5
  66. package/dist/plugins/tracing-entry.d.mts +1 -1
  67. package/dist/plugins/tracing-entry.mjs +1 -1
  68. package/dist/policies/index.d.mts +1 -1
  69. package/dist/presets/index.d.mts +1 -1
  70. package/dist/presets/multiTenant.d.mts +1 -1
  71. package/dist/{queryCachePlugin-Bw8XyJpX.d.mts → queryCachePlugin-Ckl71mkc.d.mts} +1 -1
  72. package/dist/{redis-CyCntzTO.d.mts → redis-3TQxm2VZ.d.mts} +1 -1
  73. package/dist/{redis-stream-We_Ucl9-.d.mts → redis-stream-Dag5LFa9.d.mts} +1 -1
  74. package/dist/registry/index.d.mts +1 -1
  75. package/dist/replyHelpers-uDUIYh7u.mjs +40 -0
  76. package/dist/{resourceToTools-CkVSSzKg.mjs → resourceToTools-BJkoQoUP.mjs} +11 -5
  77. package/dist/rpc/index.d.mts +1 -1
  78. package/dist/{schemaConverter-0TyONAwM.mjs → schemaConverter-Y5EejTnJ.mjs} +1 -4
  79. package/dist/scope/index.d.mts +2 -2
  80. package/dist/testing/index.d.mts +2 -2
  81. package/dist/testing/index.mjs +1 -1
  82. package/dist/types/index.d.mts +4 -4
  83. package/dist/{types-DPsC0taJ.d.mts → types-B4BNthET.d.mts} +1 -1
  84. package/dist/{types-ClmkMDK1.d.mts → types-C5g2oRC7.d.mts} +18 -2
  85. package/dist/{types-D0qf0Mf4.d.mts → types-CKB47kiu.d.mts} +48 -9
  86. package/dist/utils/index.d.mts +3 -3
  87. package/dist/utils/index.mjs +1 -1
  88. package/package.json +9 -5
  89. package/skills/arc/SKILL.md +62 -1
  90. package/skills/arc/references/integrations.md +32 -7
  91. package/skills/arc/references/mcp.md +31 -7
  92. package/skills/arc/references/production.md +69 -0
  93. /package/dist/{EventTransport-CUpRK_Lg.d.mts → EventTransport-C4VheKeC.d.mts} +0 -0
  94. /package/dist/{circuitBreaker-DwxrljLB.d.mts → circuitBreaker-BBPDt-J_.d.mts} +0 -0
  95. /package/dist/{elevation-Dm-HTBCt.d.mts → elevation-D7WK0RXq.d.mts} +0 -0
  96. /package/dist/{errors-CCSsMpXE.d.mts → errors-BS6lZvWy.d.mts} +0 -0
  97. /package/dist/{externalPaths-Dg7OLsKo.d.mts → externalPaths-iba7jD3d.d.mts} +0 -0
  98. /package/dist/{fields-CYuLMJPD.d.mts → fields-D4nMDqnK.d.mts} +0 -0
  99. /package/dist/{interface-CnluRL4_.d.mts → interface-CG7oRZjX.d.mts} +0 -0
  100. /package/dist/{interface-B9rHWPxD.d.mts → interface-CSbZdv_3.d.mts} +0 -0
  101. /package/dist/{mongodb-mlgxkYI3.mjs → mongodb-B7X7P1P8.mjs} +0 -0
  102. /package/dist/{pluralize-COpOVar8.mjs → pluralize-Dckfq6US.mjs} +0 -0
  103. /package/dist/{sessionManager-IW4sbIea.d.mts → sessionManager-CEo9jwPI.d.mts} +0 -0
  104. /package/dist/{sse-Bp3dabF1.mjs → sse-6W0hjVS_.mjs} +0 -0
  105. /package/dist/{tracing-65B51Dw3.d.mts → tracing-DEqdGkr-.d.mts} +0 -0
  106. /package/dist/{types-CNEbix8T.d.mts → types--D3vvfdt.d.mts} +0 -0
  107. /package/dist/{versioning-aUUVziBY.mjs → versioning-CdBbFefk.mjs} +0 -0
package/dist/index.d.mts CHANGED
@@ -1,11 +1,12 @@
1
- import { $ as RateLimitConfig, $t as PipelineContext, A as FieldRule, C as CrudRouterOptions, D as FastifyRequestExtras, F as InferDocType, Ft as IController, Gt as PaginatedResult, H as MiddlewareConfig, Ht as defineResource, I as InferResourceDoc, It as IControllerResponse, Jt as Guard, K as OwnershipCheck, L as IntrospectionData, Lt as IRequestContext, M as HealthCheck, N as HealthOptions, Nt as ControllerLike, O as FastifyWithAuth, P as InferAdapterDoc, Qt as PipelineConfig, R as IntrospectionPluginOptions, Rt as RouteHandler, S as CrudRouteKey, St as envelope, T as EventDefinition, Tt as BaseControllerOptions, U as MiddlewareHandler, Ut as CrudRepository, Vt as ResourceDefinition, Xt as NextFunction, Y as PresetFunction, Yt as Interceptor, Z as PresetResult, Zt as OperationFilter, _t as TypedResourceConfig, a as RelationMetadata, bt as ValidateOptions, c as ValidationResult, d as ApiResponse, dt as RouteHandlerMethod, en as PipelineStep, et as RegistryEntry, ft as RouteSchemaOptions, g as AuthPluginOptions, gt as TypedRepository, ht as TypedController, i as FieldMetadata, it as RequestWithExtras, j as GracefulShutdownOptions, k as FastifyWithDecorators, l as AdditionalRoute, lt as ResourceMetadata, m as ArcRequest, nt as RequestContext, o as RepositoryLike, ot as ResourceConfig, p as ArcInternalMetadata, pt as ServiceContext, qt as QueryOptions, r as DataAdapter, rt as RequestIdOptions, s as SchemaMetadata, tn as Transform, tt as RegistryStats, u as AnyRecord, w as CrudSchemas, wt as BaseController, x as CrudController, xt as ValidationResult$1, y as ConfigError, yt as UserOrganization, z as JWTPayload } from "./interface-Dwzqt4mn.mjs";
2
- import { a as applyFieldWritePermissions, i as applyFieldReadPermissions, n as FieldPermissionMap, o as fields, t as FieldPermission } from "./fields-CYuLMJPD.mjs";
3
- import { i as UserBase, n as PermissionContext, r as PermissionResult, t as PermissionCheck } from "./types-DPsC0taJ.mjs";
4
- import { l as createMongooseAdapter, o as createPrismaAdapter, s as MongooseAdapter, t as PrismaAdapter } from "./index-StgFaQKD.mjs";
5
- import { A as MutationOperation, C as HOOK_PHASES, D as MAX_REGEX_LENGTH, E as MAX_FILTER_DEPTH, M as SYSTEM_FIELDS, O as MAX_SEARCH_LENGTH, S as HOOK_OPERATIONS, T as HookPhase, _ as DEFAULT_LIMIT, a as getControllerScope, b as DEFAULT_TENANT_FIELD, g as DEFAULT_ID_FIELD, h as CrudOperation, j as RESERVED_QUERY_PARAMS, k as MUTATION_OPERATIONS, m as CRUD_OPERATIONS, s as defineResourceVariants, v as DEFAULT_MAX_LIMIT, w as HookOperation, x as DEFAULT_UPDATE_METHOD, y as DEFAULT_SORT } from "./index-KXM8_JmQ.mjs";
6
- import { C as authenticated, D as publicRead, E as presets_d_exports, O as publicReadAdminWrite, S as adminOnly, T as ownerWithAdminBypass, _ as requireScopeContext, a as allOf, c as createDynamicPermissionMatrix, d as requireAuth, f as requireOrgInScope, g as requireRoles, h as requireOwnership, k as readOnly, l as createOrgPermissions, m as requireOrgRole, n as DynamicPermissionMatrix, o as allowPublic, p as requireOrgMembership, r as DynamicPermissionMatrixConfig, s as anyOf, u as denyAll, v as requireServiceScope, w as fullPublic, x as when, y as requireTeamMembership } from "./index-BYpRGXif.mjs";
7
- import { a as NotFoundError, d as ValidationError, f as createDomainError, i as ForbiddenError, t as ArcError, u as UnauthorizedError } from "./errors-CCSsMpXE.mjs";
1
+ import { $ as RateLimitConfig, $t as PipelineContext, A as FieldRule, C as CrudRouterOptions, D as FastifyRequestExtras, F as InferDocType, Ft as IController, Gt as PaginatedResult, H as MiddlewareConfig, Ht as defineResource, I as InferResourceDoc, It as IControllerResponse, Jt as Guard, K as OwnershipCheck, L as IntrospectionData, Lt as IRequestContext, M as HealthCheck, N as HealthOptions, Nt as ControllerLike, O as FastifyWithAuth, P as InferAdapterDoc, Qt as PipelineConfig, R as IntrospectionPluginOptions, Rt as RouteHandler, S as CrudRouteKey, St as envelope, T as EventDefinition, Tt as BaseControllerOptions, U as MiddlewareHandler, Ut as CrudRepository, Vt as ResourceDefinition, Xt as NextFunction, Y as PresetFunction, Yt as Interceptor, Z as PresetResult, Zt as OperationFilter, _t as TypedResourceConfig, a as RelationMetadata, bt as ValidateOptions, c as ValidationResult, d as ApiResponse, dt as RouteHandlerMethod, en as PipelineStep, et as RegistryEntry, ft as RouteSchemaOptions, g as AuthPluginOptions, gt as TypedRepository, ht as TypedController, i as FieldMetadata, it as RequestWithExtras, j as GracefulShutdownOptions, k as FastifyWithDecorators, l as AdditionalRoute, lt as ResourceMetadata, m as ArcRequest, nt as RequestContext, o as RepositoryLike, ot as ResourceConfig, p as ArcInternalMetadata, pt as ServiceContext, qt as QueryOptions, r as DataAdapter, rt as RequestIdOptions, s as SchemaMetadata, tn as Transform, tt as RegistryStats, u as AnyRecord, w as CrudSchemas, wt as BaseController, x as CrudController, xt as ValidationResult$1, y as ConfigError, yt as UserOrganization, z as JWTPayload } from "./interface-B91alUzq.mjs";
2
+ import { a as applyFieldWritePermissions, i as applyFieldReadPermissions, n as FieldPermissionMap, o as fields, t as FieldPermission } from "./fields-D4nMDqnK.mjs";
3
+ import { i as UserBase, n as PermissionContext, r as PermissionResult, t as PermissionCheck } from "./types-B4BNthET.mjs";
4
+ import { l as createMongooseAdapter, o as createPrismaAdapter, s as MongooseAdapter, t as PrismaAdapter } from "./index-C9eYNjGR.mjs";
5
+ import { A as MutationOperation, C as HOOK_PHASES, D as MAX_REGEX_LENGTH, E as MAX_FILTER_DEPTH, M as SYSTEM_FIELDS, O as MAX_SEARCH_LENGTH, S as HOOK_OPERATIONS, T as HookPhase, _ as DEFAULT_LIMIT, a as getControllerScope, b as DEFAULT_TENANT_FIELD, g as DEFAULT_ID_FIELD, h as CrudOperation, j as RESERVED_QUERY_PARAMS, k as MUTATION_OPERATIONS, m as CRUD_OPERATIONS, s as defineResourceVariants, v as DEFAULT_MAX_LIMIT, w as HookOperation, x as DEFAULT_UPDATE_METHOD, y as DEFAULT_SORT } from "./index-BjShrzoj.mjs";
6
+ import { C as authenticated, D as publicRead, E as presets_d_exports, O as publicReadAdminWrite, S as adminOnly, T as ownerWithAdminBypass, _ as requireScopeContext, a as allOf, c as createDynamicPermissionMatrix, d as requireAuth, f as requireOrgInScope, g as requireRoles, h as requireOwnership, k as readOnly, l as createOrgPermissions, m as requireOrgRole, n as DynamicPermissionMatrix, o as allowPublic, p as requireOrgMembership, r as DynamicPermissionMatrixConfig, s as anyOf, u as denyAll, v as requireServiceScope, w as fullPublic, x as when, y as requireTeamMembership } from "./index-B0extFr4.mjs";
7
+ import { a as NotFoundError, d as ValidationError, f as createDomainError, i as ForbiddenError, t as ArcError, u as UnauthorizedError } from "./errors-BS6lZvWy.mjs";
8
8
  import { AsyncLocalStorage } from "node:async_hooks";
9
+ import { RouteHandlerMethod as RouteHandlerMethod$1 } from "fastify";
9
10
 
10
11
  //#region src/context/requestContext.d.ts
11
12
  /**
package/dist/index.mjs CHANGED
@@ -3,10 +3,10 @@ import { a as createMongooseAdapter, i as MongooseAdapter, r as createPrismaAdap
3
3
  import { t as BaseController } from "./BaseController-CpMfCXdn.mjs";
4
4
  import { envelope } from "./types/index.mjs";
5
5
  import { n as applyFieldWritePermissions, r as fields, t as applyFieldReadPermissions } from "./fields-ipsbIRPK.mjs";
6
- import { t as defineResourceVariants } from "./core-BWekSEju.mjs";
6
+ import { t as defineResourceVariants } from "./core-B_zEeA2b.mjs";
7
7
  import { t as requestContext } from "./requestContext-xHIKedG6.mjs";
8
8
  import { d as createDomainError, i as NotFoundError, l as UnauthorizedError, r as ForbiddenError, t as ArcError, u as ValidationError } from "./errors-Cg58SLNi.mjs";
9
- import { a as validateResourceConfig, f as getControllerScope, i as formatValidationErrors, m as pipe, n as defineResource, r as assertValidConfig, t as ResourceDefinition } from "./defineResource-DZzyl4a4.mjs";
9
+ import { a as validateResourceConfig, f as getControllerScope, i as formatValidationErrors, m as pipe, n as defineResource, r as assertValidConfig, t as ResourceDefinition } from "./defineResource-BW2dMCu9.mjs";
10
10
  import { C as publicRead, S as presets_exports, T as readOnly, _ as when, a as createOrgPermissions, b as fullPublic, c as requireOrgInScope, d as requireOwnership, f as requireRoles, h as requireTeamMembership, i as createDynamicPermissionMatrix, l as requireOrgMembership, m as requireServiceScope, n as allowPublic, o as denyAll, p as requireScopeContext, r as anyOf, s as requireAuth, t as allOf, u as requireOrgRole, v as adminOnly, w as publicReadAdminWrite, x as ownerWithAdminBypass, y as authenticated } from "./permissions-CH4cNwJi.mjs";
11
11
  import { n as configureArcLogger, t as arcLog } from "./logger-DLg8-Ueg.mjs";
12
12
  //#region src/middleware/middleware.ts
@@ -128,6 +128,6 @@ function transform(name, handlerOrOptions) {
128
128
  }
129
129
  //#endregion
130
130
  //#region src/index.ts
131
- const version = "2.7.1";
131
+ const version = "2.7.7";
132
132
  //#endregion
133
133
  export { ArcError, BaseController, 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, UnauthorizedError, ValidationError, adminOnly, allOf, allowPublic, anyOf, applyFieldReadPermissions, applyFieldWritePermissions, arcLog, assertValidConfig, authenticated, configureArcLogger, createDomainError, createDynamicPermissionMatrix, createMongooseAdapter, createOrgPermissions, createPrismaAdapter, defineResource, defineResourceVariants, denyAll, envelope, fields, formatValidationErrors, fullPublic, getControllerScope, guard, intercept, middleware, ownerWithAdminBypass, presets_exports as permissions, pipe, publicRead, publicReadAdminWrite, readOnly, requestContext, requireAuth, requireOrgInScope, requireOrgMembership, requireOrgRole, requireOwnership, requireRoles, requireScopeContext, requireServiceScope, requireTeamMembership, sortMiddlewares, transform, validateResourceConfig, version, when };
@@ -1,4 +1,4 @@
1
- import { t as DomainEvent } from "../EventTransport-CUpRK_Lg.mjs";
1
+ import { t as DomainEvent } from "../EventTransport-C4VheKeC.mjs";
2
2
  import { WebSocketClient, WebSocketMessage } from "./websocket.mjs";
3
3
  import { FastifyPluginAsync, FastifyRequest } from "fastify";
4
4
 
@@ -4,7 +4,7 @@ const eventGatewayPluginImpl = async (fastify, opts = {}) => {
4
4
  const { auth = true, orgScoped = false, roomPolicy, maxMessageBytes, maxSubscriptionsPerClient, authenticate } = opts;
5
5
  if (auth && !authenticate && !fastify.hasDecorator("authenticate")) throw new Error("[arc-event-gateway] auth is true but fastify.authenticate is not registered. Register an auth plugin first, provide a custom authenticate function, or set auth: false.");
6
6
  if (opts.sse !== false) {
7
- const { default: ssePlugin } = await import("../sse-Bp3dabF1.mjs").then((n) => n.r);
7
+ const { default: ssePlugin } = await import("../sse-6W0hjVS_.mjs").then((n) => n.r);
8
8
  await fastify.register(ssePlugin, {
9
9
  path: opts.sse?.path ?? "/events/stream",
10
10
  requireAuth: auth,
@@ -1,7 +1,7 @@
1
1
  import { WebSocketClient, WebSocketMessage, WebSocketPluginOptions } from "./websocket.mjs";
2
2
  import { EventGatewayOptions } from "./event-gateway.mjs";
3
3
  import { JobDefinition, JobDispatchOptions, JobDispatcher, JobMeta, JobsPluginOptions, QueueStats } from "./jobs.mjs";
4
- import { c as McpResourceConfig, f as ToolAnnotations, i as CrudOperation, l as PromptDefinition, m as ToolDefinition, n as CallToolResult, o as McpAuthResult, p as ToolContext, r as CreateMcpServerConfig, s as McpPluginOptions, t as BetterAuthHandler } from "../types-ClmkMDK1.mjs";
4
+ import { c as McpResourceConfig, f as ToolAnnotations, i as CrudOperation, l as PromptDefinition, m as ToolDefinition, n as CallToolResult, o as McpAuthResult, p as ToolContext, r as CreateMcpServerConfig, s as McpPluginOptions, t as BetterAuthHandler } from "../types-C5g2oRC7.mjs";
5
5
  import { StreamlinePluginOptions, WorkflowLike, WorkflowRunLike } from "./streamline.mjs";
6
6
  import { WebhookDeliveryRecord, WebhookManager, WebhookPluginOptions, WebhookStore, WebhookSubscription } from "./webhooks.mjs";
7
7
  export { type BetterAuthHandler, type CallToolResult, type CreateMcpServerConfig, type CrudOperation, type EventGatewayOptions, type JobDefinition, type JobDispatchOptions, type JobDispatcher, type JobMeta, type JobsPluginOptions, type McpAuthResult, type McpPluginOptions, type McpResourceConfig, type PromptDefinition, type QueueStats, type StreamlinePluginOptions, type ToolAnnotations, type ToolContext, type ToolDefinition, type WebSocketClient, type WebSocketMessage, type WebSocketPluginOptions, type WebhookDeliveryRecord, type WebhookManager, type WebhookPluginOptions, type WebhookStore, type WebhookSubscription, type WorkflowLike, type WorkflowRunLike };
@@ -1,5 +1,5 @@
1
- import { Vt as ResourceDefinition } from "../../interface-Dwzqt4mn.mjs";
2
- import { a as McpAuthResolver, c as McpResourceConfig, d as SessionEntry, f as ToolAnnotations, i as CrudOperation, l as PromptDefinition, m as ToolDefinition, n as CallToolResult, o as McpAuthResult, p as ToolContext, r as CreateMcpServerConfig, s as McpPluginOptions, t as BetterAuthHandler, u as PromptResult } from "../../types-ClmkMDK1.mjs";
1
+ import { Vt as ResourceDefinition } from "../../interface-B91alUzq.mjs";
2
+ import { a as McpAuthResolver, c as McpResourceConfig, d as SessionEntry, f as ToolAnnotations, i as CrudOperation, l as PromptDefinition, m as ToolDefinition, n as CallToolResult, o as McpAuthResult, p as ToolContext, r as CreateMcpServerConfig, s as McpPluginOptions, t as BetterAuthHandler, u as PromptResult } from "../../types-C5g2oRC7.mjs";
3
3
  import { FastifyPluginAsync } from "fastify";
4
4
  import { z } from "zod";
5
5
 
@@ -1,4 +1,4 @@
1
- import { n as fieldRulesToZod, r as createMcpServer, t as resourceToTools } from "../../resourceToTools-CkVSSzKg.mjs";
1
+ import { n as fieldRulesToZod, r as createMcpServer, t as resourceToTools } from "../../resourceToTools-BJkoQoUP.mjs";
2
2
  import { createHash } from "node:crypto";
3
3
  import fp from "fastify-plugin";
4
4
  //#region src/integrations/mcp/definePrompt.ts
@@ -150,7 +150,7 @@ function isBetterAuth(auth) {
150
150
  * @param authCache - Optional short-lived cache to avoid redundant auth lookups
151
151
  */
152
152
  async function resolveMcpAuth(headers, auth, authCache) {
153
- if (auth === false) return { userId: "anonymous" };
153
+ if (auth === false) return null;
154
154
  const cacheKey = authCache ? extractAuthCacheKey(headers) : null;
155
155
  if (cacheKey && authCache) {
156
156
  const cached = authCache.get(cacheKey);
@@ -167,7 +167,9 @@ async function resolveMcpAuth(headers, auth, authCache) {
167
167
  if (!session?.userId) result = null;
168
168
  else result = {
169
169
  userId: session.userId,
170
- organizationId: session.activeOrganizationId
170
+ organizationId: session.activeOrganizationId,
171
+ ...session.clientId ? { clientId: session.clientId } : {},
172
+ ...session.scopes ? { scopes: session.scopes.split(" ") } : {}
171
173
  };
172
174
  } catch {
173
175
  result = null;
@@ -494,10 +496,11 @@ function registerStatelessRoutes(fastify, prefix, options, createServer, Transpo
494
496
  });
495
497
  }
496
498
  function registerStatefulRoutes(fastify, prefix, options, cache, createServer, Transport) {
497
- /** Check if the requesting user owns the session */
499
+ /** Check if the requesting principal owns the session */
498
500
  function isSessionOwner(entry, authResult) {
499
501
  if (!options.auth || !entry.auth || !authResult) return true;
500
- return entry.auth.userId === authResult.userId && entry.auth.organizationId === authResult.organizationId;
502
+ const prev = entry.auth;
503
+ return prev.userId === authResult.userId && prev.organizationId === authResult.organizationId && prev.clientId === authResult.clientId;
501
504
  }
502
505
  fastify.post(prefix, async (request, reply) => {
503
506
  const authResult = await resolveMcpAuth(request.headers, options.auth ?? false);
@@ -1,4 +1,4 @@
1
- import { o as McpAuthResult, s as McpPluginOptions } from "../../types-ClmkMDK1.mjs";
1
+ import { o as McpAuthResult, s as McpPluginOptions } from "../../types-C5g2oRC7.mjs";
2
2
 
3
3
  //#region src/integrations/mcp/testing.d.ts
4
4
  interface TestMcpClientOptions {
@@ -1,4 +1,4 @@
1
- import { r as createMcpServer, t as resourceToTools } from "../../resourceToTools-CkVSSzKg.mjs";
1
+ import { r as createMcpServer, t as resourceToTools } from "../../resourceToTools-BJkoQoUP.mjs";
2
2
  //#region src/integrations/mcp/testing.ts
3
3
  /**
4
4
  * @classytic/arc/mcp/testing — MCP Test Utilities
@@ -1,28 +1,44 @@
1
1
  import { FastifyPluginAsync } from "fastify";
2
2
 
3
3
  //#region src/integrations/streamline.d.ts
4
+ /** Start options — matches @classytic/streamline v2.1 StartOptions */
5
+ interface WorkflowStartOptions {
6
+ meta?: Record<string, unknown>;
7
+ idempotencyKey?: string;
8
+ priority?: number;
9
+ }
4
10
  /** Minimal workflow interface — matches @classytic/streamline's createWorkflow() return */
5
11
  interface WorkflowLike {
6
12
  definition: {
7
13
  id: string;
8
14
  name?: string;
9
- steps: Record<string, unknown>;
15
+ steps: Record<string, unknown> | unknown[];
10
16
  };
11
17
  engine: {
12
- start(input: unknown, meta?: unknown): Promise<WorkflowRunLike>;
18
+ start(input: unknown, options?: WorkflowStartOptions): Promise<WorkflowRunLike>;
13
19
  execute(runId: string): Promise<WorkflowRunLike>;
14
20
  resume(runId: string, payload?: unknown): Promise<WorkflowRunLike>;
15
21
  cancel(runId: string): Promise<WorkflowRunLike>;
16
22
  pause?(runId: string): Promise<WorkflowRunLike>;
17
23
  rewindTo?(runId: string, stepId: string): Promise<WorkflowRunLike>;
18
24
  get(runId: string): Promise<WorkflowRunLike | null>;
25
+ waitFor?(runId: string, options?: {
26
+ timeout?: number;
27
+ }): Promise<WorkflowRunLike>;
19
28
  shutdown?(): void;
20
29
  };
21
- start(input: unknown, meta?: unknown): Promise<WorkflowRunLike>;
30
+ start(input: unknown, options?: WorkflowStartOptions): Promise<WorkflowRunLike>;
22
31
  resume(runId: string, payload?: unknown): Promise<WorkflowRunLike>;
23
32
  cancel(runId: string): Promise<WorkflowRunLike>;
24
33
  get(runId: string): Promise<WorkflowRunLike | null>;
25
34
  shutdown?(): void;
35
+ /** Streamline container for event bridging (streamline >=2.1) */
36
+ container?: {
37
+ eventBus: {
38
+ on(event: string, listener: (...args: unknown[]) => void): void;
39
+ off(event: string, listener: (...args: unknown[]) => void): void;
40
+ };
41
+ };
26
42
  }
27
43
  interface WorkflowRunLike {
28
44
  _id: string;
@@ -30,11 +46,14 @@ interface WorkflowRunLike {
30
46
  status: string;
31
47
  context?: unknown;
32
48
  input?: unknown;
33
- steps?: Record<string, unknown>;
49
+ steps?: unknown[];
34
50
  error?: unknown;
51
+ idempotencyKey?: string;
52
+ priority?: number;
53
+ concurrencyKey?: string;
54
+ stepLogs?: unknown[];
35
55
  createdAt?: Date;
36
56
  updatedAt?: Date;
37
- [key: string]: unknown;
38
57
  }
39
58
  interface StreamlinePluginOptions {
40
59
  /** Array of workflows created with createWorkflow() */
@@ -43,8 +62,21 @@ interface StreamlinePluginOptions {
43
62
  prefix?: string;
44
63
  /** Require authentication for all workflow endpoints (default: true) */
45
64
  auth?: boolean;
46
- /** Connect workflow events to Arc's event bus (default: true) */
65
+ /** Connect workflow lifecycle events to Arc's event bus (default: true) */
47
66
  bridgeEvents?: boolean;
67
+ /**
68
+ * Bridge step-level events (step:started, step:completed, step:failed) to Arc's event bus.
69
+ * Disabled by default — enable for dashboards or monitoring.
70
+ * Requires the workflow to expose `container.eventBus` (streamline >=2.1).
71
+ * @default false
72
+ */
73
+ bridgeStepEvents?: boolean;
74
+ /**
75
+ * Enable SSE streaming endpoint: GET /:workflowId/runs/:runId/stream
76
+ * Streams step-level events as Server-Sent Events for live UI updates.
77
+ * @default false
78
+ */
79
+ enableStreaming?: boolean;
48
80
  /** Custom permission check for workflow operations */
49
81
  permissions?: {
50
82
  start?: (request: unknown) => boolean | Promise<boolean>;
@@ -57,4 +89,4 @@ interface StreamlinePluginOptions {
57
89
  /** Pluggable streamline integration for Arc */
58
90
  declare const streamlinePlugin: FastifyPluginAsync<StreamlinePluginOptions>;
59
91
  //#endregion
60
- export { StreamlinePluginOptions, WorkflowLike, WorkflowRunLike, streamlinePlugin };
92
+ export { StreamlinePluginOptions, WorkflowLike, WorkflowRunLike, WorkflowStartOptions, streamlinePlugin };
@@ -1,6 +1,6 @@
1
1
  //#region src/integrations/streamline.ts
2
2
  const streamlinePluginImpl = async (fastify, options) => {
3
- const { workflows, prefix = "/workflows", auth = true, bridgeEvents = true, permissions: perms } = options;
3
+ const { workflows, prefix = "/workflows", auth = true, bridgeEvents = true, bridgeStepEvents = false, enableStreaming = false, permissions: perms } = options;
4
4
  const registry = /* @__PURE__ */ new Map();
5
5
  for (const wf of workflows) {
6
6
  const id = wf.definition.id;
@@ -22,8 +22,12 @@ const streamlinePluginImpl = async (fastify, options) => {
22
22
  success: false,
23
23
  error: "Forbidden"
24
24
  });
25
- const { input, meta } = request.body ?? {};
26
- const run = await wf.start(input, meta);
25
+ const { input, meta, idempotencyKey, priority } = request.body ?? {};
26
+ const run = await wf.start(input, {
27
+ meta,
28
+ idempotencyKey,
29
+ priority
30
+ });
27
31
  if (bridgeEvents && fastify.events?.publish) try {
28
32
  await fastify.events.publish(`workflow.${id}.started`, {
29
33
  runId: run._id,
@@ -105,6 +109,26 @@ const streamlinePluginImpl = async (fastify, options) => {
105
109
  data: run
106
110
  };
107
111
  });
112
+ fastify.post(`${routePrefix}/runs/:runId/execute`, { preHandler: authPreHandler }, async (request, _reply) => {
113
+ const { runId } = request.params;
114
+ return {
115
+ success: true,
116
+ data: await wf.engine.execute(runId)
117
+ };
118
+ });
119
+ if (wf.engine.waitFor) fastify.get(`${routePrefix}/runs/:runId/wait`, { preHandler: authPreHandler }, async (request, reply) => {
120
+ if (!await checkPerm("get", request)) return reply.status(403).send({
121
+ success: false,
122
+ error: "Forbidden"
123
+ });
124
+ const { runId } = request.params;
125
+ const { timeout } = request.query ?? {};
126
+ const timeoutMs = timeout ? Number.parseInt(timeout, 10) : 3e4;
127
+ return {
128
+ success: true,
129
+ data: await wf.engine.waitFor(runId, { timeout: Math.min(timeoutMs, 12e4) })
130
+ };
131
+ });
108
132
  if (wf.engine.pause) fastify.post(`${routePrefix}/runs/:runId/pause`, { preHandler: authPreHandler }, async (request, _reply) => {
109
133
  const { runId } = request.params;
110
134
  return {
@@ -124,6 +148,84 @@ const streamlinePluginImpl = async (fastify, options) => {
124
148
  data: await wf.engine.rewindTo?.(runId, stepId)
125
149
  };
126
150
  });
151
+ if (bridgeStepEvents && wf.container?.eventBus && fastify.events?.publish) for (const eventName of [
152
+ "step:started",
153
+ "step:completed",
154
+ "step:failed",
155
+ "step:skipped",
156
+ "step:retry-scheduled"
157
+ ]) wf.container.eventBus.on(eventName, (payload) => {
158
+ const p = payload;
159
+ fastify.events.publish(`workflow.${id}.${eventName}`, {
160
+ runId: p?.runId,
161
+ stepId: p?.stepId,
162
+ workflowId: id,
163
+ ...p
164
+ }).catch((err) => {
165
+ fastify.log.warn({
166
+ err,
167
+ workflowId: id
168
+ }, `Failed to bridge ${eventName}`);
169
+ });
170
+ });
171
+ if (enableStreaming && wf.container?.eventBus) fastify.get(`${routePrefix}/runs/:runId/stream`, { preHandler: authPreHandler }, async (request, reply) => {
172
+ if (!await checkPerm("get", request)) return reply.status(403).send({
173
+ success: false,
174
+ error: "Forbidden"
175
+ });
176
+ const { runId } = request.params;
177
+ if (!await wf.get(runId)) return reply.status(404).send({
178
+ success: false,
179
+ error: "Workflow run not found"
180
+ });
181
+ reply.raw.writeHead(200, {
182
+ "Content-Type": "text/event-stream",
183
+ "Cache-Control": "no-cache",
184
+ Connection: "keep-alive"
185
+ });
186
+ const events = [
187
+ "step:started",
188
+ "step:completed",
189
+ "step:failed",
190
+ "step:skipped",
191
+ "workflow:completed",
192
+ "workflow:failed",
193
+ "workflow:cancelled"
194
+ ];
195
+ const listeners = [];
196
+ let closed = false;
197
+ const send = (event, data) => {
198
+ if (closed) return;
199
+ try {
200
+ reply.raw.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
201
+ } catch {
202
+ cleanup();
203
+ }
204
+ };
205
+ const cleanup = () => {
206
+ if (closed) return;
207
+ closed = true;
208
+ for (const { event, fn } of listeners) wf.container?.eventBus.off(event, fn);
209
+ listeners.length = 0;
210
+ try {
211
+ reply.raw.end();
212
+ } catch {}
213
+ };
214
+ for (const eventName of events) {
215
+ const fn = (payload) => {
216
+ const p = payload;
217
+ if (p?.runId !== runId) return;
218
+ send(eventName, p);
219
+ if (eventName === "workflow:completed" || eventName === "workflow:failed" || eventName === "workflow:cancelled") cleanup();
220
+ };
221
+ wf.container.eventBus.on(eventName, fn);
222
+ listeners.push({
223
+ event: eventName,
224
+ fn
225
+ });
226
+ }
227
+ request.raw.on("close", cleanup);
228
+ });
127
229
  }
128
230
  fastify.get(prefix, { preHandler: authPreHandler }, async () => {
129
231
  return {
@@ -131,7 +233,7 @@ const streamlinePluginImpl = async (fastify, options) => {
131
233
  data: Array.from(registry.entries()).map(([id, wf]) => ({
132
234
  id,
133
235
  name: wf.definition.name ?? id,
134
- steps: Object.keys(wf.definition.steps)
236
+ steps: Array.isArray(wf.definition.steps) ? wf.definition.steps.map((s) => s.id ?? String(s)) : Object.keys(wf.definition.steps)
135
237
  }))
136
238
  };
137
239
  });
@@ -37,6 +37,8 @@ interface WebhookPluginOptions {
37
37
  timeout?: number;
38
38
  /** Max delivery log entries kept in memory (default: 1000) */
39
39
  maxLogEntries?: number;
40
+ /** Max concurrent deliveries per event (default: 5). Set to 1 for sequential. */
41
+ concurrency?: number;
40
42
  }
41
43
  interface WebhookManager {
42
44
  register(sub: WebhookSubscription): Promise<void> | void;
@@ -49,8 +51,63 @@ declare module "fastify" {
49
51
  webhooks: WebhookManager;
50
52
  }
51
53
  }
54
+ /**
55
+ * Sign a payload with HMAC-SHA256 for outbound webhook delivery.
56
+ *
57
+ * @returns `sha256=<hex>` — the format written to `x-webhook-signature`
58
+ */
52
59
  declare function signPayload(payload: string, secret: string): string;
60
+ /** Options for `verifySignature` — customize for non-Arc webhook senders */
61
+ interface VerifySignatureOptions {
62
+ /**
63
+ * Expected prefix before the hex digest (default: `'sha256='`).
64
+ * Set to `''` for bare hex signatures.
65
+ */
66
+ prefix?: string;
67
+ /**
68
+ * HMAC algorithm (default: `'sha256'`).
69
+ * Must match what the sender uses — Arc's `signPayload` always uses sha256.
70
+ */
71
+ algorithm?: string;
72
+ }
73
+ /**
74
+ * Verify an inbound webhook signature using timing-safe comparison.
75
+ *
76
+ * Works with Arc's own `signPayload` format by default (`sha256=<hex>`),
77
+ * but configurable for any HMAC scheme via options.
78
+ *
79
+ * @param body - Raw request body (string or Buffer — must be the exact bytes the sender signed)
80
+ * @param secret - Shared secret between sender and receiver
81
+ * @param signature - The signature header value (e.g. `req.headers['x-webhook-signature']`)
82
+ * @param options - Override prefix/algorithm for non-Arc senders
83
+ * @returns `true` if valid, `false` otherwise — never throws
84
+ *
85
+ * @example
86
+ * ```typescript
87
+ * import { verifySignature } from '@classytic/arc/integrations/webhooks';
88
+ *
89
+ * // Arc-to-Arc (default headers + format)
90
+ * fastify.post('/webhooks/incoming', async (req, reply) => {
91
+ * const sig = req.headers['x-webhook-signature'] as string;
92
+ * if (!verifySignature(req.rawBody, secret, sig)) {
93
+ * return reply.status(401).send({ error: 'Invalid signature' });
94
+ * }
95
+ * // handle event via req.headers['x-webhook-event']
96
+ * });
97
+ *
98
+ * // Third-party sender with custom header + bare hex
99
+ * const valid = verifySignature(body, secret, req.headers['x-hub-signature'], {
100
+ * prefix: 'sha256=', // GitHub format
101
+ * });
102
+ *
103
+ * // Stripe-style (bare hex, different header)
104
+ * const valid = verifySignature(body, stripeSecret, req.headers['stripe-signature'], {
105
+ * prefix: '',
106
+ * });
107
+ * ```
108
+ */
109
+ declare function verifySignature(body: string | Buffer, secret: string, signature: string | undefined, options?: VerifySignatureOptions): boolean;
53
110
  declare const webhookPlugin: FastifyPluginAsync<WebhookPluginOptions>;
54
111
  declare const _default: FastifyPluginAsync<WebhookPluginOptions>;
55
112
  //#endregion
56
- export { WebhookDeliveryRecord, WebhookManager, WebhookPluginOptions, WebhookStore, WebhookSubscription, _default as default, signPayload, webhookPlugin };
113
+ export { VerifySignatureOptions, WebhookDeliveryRecord, WebhookManager, WebhookPluginOptions, WebhookStore, WebhookSubscription, _default as default, signPayload, verifySignature, webhookPlugin };
@@ -1,4 +1,4 @@
1
- import { createHmac } from "node:crypto";
1
+ import { createHmac, timingSafeEqual } from "node:crypto";
2
2
  import fp from "fastify-plugin";
3
3
  //#region src/integrations/webhooks.ts
4
4
  /**
@@ -30,11 +30,69 @@ import fp from "fastify-plugin";
30
30
  * // → POST https://customer.com/webhook with HMAC signature
31
31
  * ```
32
32
  */
33
+ /**
34
+ * Sign a payload with HMAC-SHA256 for outbound webhook delivery.
35
+ *
36
+ * @returns `sha256=<hex>` — the format written to `x-webhook-signature`
37
+ */
33
38
  function signPayload(payload, secret) {
34
39
  const hmac = createHmac("sha256", secret);
35
40
  hmac.update(payload);
36
41
  return `sha256=${hmac.digest("hex")}`;
37
42
  }
43
+ /**
44
+ * Verify an inbound webhook signature using timing-safe comparison.
45
+ *
46
+ * Works with Arc's own `signPayload` format by default (`sha256=<hex>`),
47
+ * but configurable for any HMAC scheme via options.
48
+ *
49
+ * @param body - Raw request body (string or Buffer — must be the exact bytes the sender signed)
50
+ * @param secret - Shared secret between sender and receiver
51
+ * @param signature - The signature header value (e.g. `req.headers['x-webhook-signature']`)
52
+ * @param options - Override prefix/algorithm for non-Arc senders
53
+ * @returns `true` if valid, `false` otherwise — never throws
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * import { verifySignature } from '@classytic/arc/integrations/webhooks';
58
+ *
59
+ * // Arc-to-Arc (default headers + format)
60
+ * fastify.post('/webhooks/incoming', async (req, reply) => {
61
+ * const sig = req.headers['x-webhook-signature'] as string;
62
+ * if (!verifySignature(req.rawBody, secret, sig)) {
63
+ * return reply.status(401).send({ error: 'Invalid signature' });
64
+ * }
65
+ * // handle event via req.headers['x-webhook-event']
66
+ * });
67
+ *
68
+ * // Third-party sender with custom header + bare hex
69
+ * const valid = verifySignature(body, secret, req.headers['x-hub-signature'], {
70
+ * prefix: 'sha256=', // GitHub format
71
+ * });
72
+ *
73
+ * // Stripe-style (bare hex, different header)
74
+ * const valid = verifySignature(body, stripeSecret, req.headers['stripe-signature'], {
75
+ * prefix: '',
76
+ * });
77
+ * ```
78
+ */
79
+ function verifySignature(body, secret, signature, options) {
80
+ if (!signature) return false;
81
+ const prefix = options?.prefix ?? "sha256=";
82
+ const algorithm = options?.algorithm ?? "sha256";
83
+ if (prefix && !signature.startsWith(prefix)) return false;
84
+ const providedHex = signature.slice(prefix.length);
85
+ if (!providedHex) return false;
86
+ const hmac = createHmac(algorithm, secret);
87
+ hmac.update(typeof body === "string" ? body : body);
88
+ const expectedHex = hmac.digest("hex");
89
+ if (providedHex.length !== expectedHex.length) return false;
90
+ try {
91
+ return timingSafeEqual(Buffer.from(providedHex, "hex"), Buffer.from(expectedHex, "hex"));
92
+ } catch {
93
+ return false;
94
+ }
95
+ }
38
96
  function matchesPattern(patterns, eventType) {
39
97
  for (const pattern of patterns) {
40
98
  if (pattern === "*") return true;
@@ -64,6 +122,7 @@ const webhookPlugin = async (fastify, opts = {}) => {
64
122
  const fetchFn = opts.fetch ?? globalThis.fetch;
65
123
  const timeout = opts.timeout ?? 1e4;
66
124
  const maxLogEntries = opts.maxLogEntries ?? 1e3;
125
+ const concurrency = opts.concurrency ?? 5;
67
126
  let subscriptions = [];
68
127
  const log = [];
69
128
  subscriptions = await store.getAll();
@@ -75,7 +134,7 @@ const webhookPlugin = async (fastify, opts = {}) => {
75
134
  payload: event.payload,
76
135
  meta: event.meta
77
136
  });
78
- for (const sub of matching) {
137
+ async function deliverToSubscription(sub) {
79
138
  const record = {
80
139
  subscriptionId: sub.id,
81
140
  eventType: event.type,
@@ -84,8 +143,8 @@ const webhookPlugin = async (fastify, opts = {}) => {
84
143
  };
85
144
  try {
86
145
  const signature = signPayload(body, sub.secret);
87
- const controller = new AbortController();
88
- const timer = setTimeout(() => controller.abort(), timeout);
146
+ const ac = new AbortController();
147
+ const timer = setTimeout(() => ac.abort(), timeout);
89
148
  try {
90
149
  const response = await fetchFn(sub.url, {
91
150
  method: "POST",
@@ -96,7 +155,7 @@ const webhookPlugin = async (fastify, opts = {}) => {
96
155
  "x-webhook-event": event.type
97
156
  },
98
157
  body,
99
- signal: controller.signal
158
+ signal: ac.signal
100
159
  });
101
160
  record.success = response.ok;
102
161
  record.status = response.status;
@@ -109,8 +168,14 @@ const webhookPlugin = async (fastify, opts = {}) => {
109
168
  log.push(record);
110
169
  if (log.length > maxLogEntries) log.splice(0, log.length - maxLogEntries);
111
170
  }
171
+ const pending = [...matching];
172
+ while (pending.length > 0) {
173
+ const batch = pending.splice(0, concurrency);
174
+ await Promise.allSettled(batch.map(deliverToSubscription));
175
+ }
112
176
  }
113
- if (fastify.events) await fastify.events.subscribe("*", dispatchEvent);
177
+ let unsubscribe;
178
+ if (fastify.events) unsubscribe = await fastify.events.subscribe("*", dispatchEvent);
114
179
  fastify.decorate("webhooks", {
115
180
  async register(sub) {
116
181
  await store.save(sub);
@@ -129,6 +194,12 @@ const webhookPlugin = async (fastify, opts = {}) => {
129
194
  return [...log];
130
195
  }
131
196
  });
197
+ fastify.addHook("onClose", async () => {
198
+ if (unsubscribe) {
199
+ unsubscribe();
200
+ unsubscribe = void 0;
201
+ }
202
+ });
132
203
  };
133
204
  var webhooks_default = fp(webhookPlugin, {
134
205
  name: "arc-webhooks",
@@ -136,4 +207,4 @@ var webhooks_default = fp(webhookPlugin, {
136
207
  dependencies: ["arc-events"]
137
208
  });
138
209
  //#endregion
139
- export { webhooks_default as default, signPayload, webhookPlugin };
210
+ export { webhooks_default as default, signPayload, verifySignature, webhookPlugin };
@@ -11,6 +11,10 @@ interface WebSocketClient {
11
11
  subscriptions: Set<string>;
12
12
  userId?: string;
13
13
  organizationId?: string;
14
+ /** OAuth client ID — present for service/machine-to-machine connections */
15
+ clientId?: string;
16
+ /** OAuth scopes — present for service/machine-to-machine connections */
17
+ scopes?: readonly string[];
14
18
  metadata?: Record<string, unknown>;
15
19
  }
16
20
  interface WebSocketMessage {
@@ -31,7 +35,9 @@ interface WebSocketPluginOptions {
31
35
  /** Custom authentication function for WebSocket upgrade */
32
36
  authenticate?: (request: unknown) => Promise<{
33
37
  userId?: string;
34
- organizationId?: string;
38
+ organizationId?: string; /** Set for machine-to-machine / service account auth */
39
+ clientId?: string; /** OAuth scopes for service accounts */
40
+ scopes?: readonly string[];
35
41
  } | null>;
36
42
  /** Max clients per resource subscription (default: 10000) */
37
43
  maxClientsPerRoom?: number;
@@ -165,6 +165,8 @@ const websocketPluginImpl = async (fastify, options) => {
165
165
  const clientId = `ws_${++clientCounter}_${Date.now()}`;
166
166
  let userId;
167
167
  let organizationId;
168
+ let serviceClientId;
169
+ let serviceScopes;
168
170
  if (auth) if (customAuth) {
169
171
  const result = await customAuth(request);
170
172
  if (!result) {
@@ -173,6 +175,8 @@ const websocketPluginImpl = async (fastify, options) => {
173
175
  }
174
176
  userId = result.userId;
175
177
  organizationId = result.organizationId;
178
+ serviceClientId = result.clientId;
179
+ serviceScopes = result.scopes;
176
180
  } else {
177
181
  if (fastify.authenticate) try {
178
182
  let rejected = false;
@@ -208,7 +212,9 @@ const websocketPluginImpl = async (fastify, options) => {
208
212
  socket,
209
213
  subscriptions: /* @__PURE__ */ new Set(),
210
214
  userId,
211
- organizationId
215
+ organizationId,
216
+ ...serviceClientId ? { clientId: serviceClientId } : {},
217
+ ...serviceScopes ? { scopes: serviceScopes } : {}
212
218
  };
213
219
  rooms.addClient(client);
214
220
  await onConnect?.(client);