@classytic/arc 2.6.3 → 2.7.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.
- package/README.md +98 -3
- package/dist/{BaseController-DzRtluEF.mjs → BaseController-CpMfCXdn.mjs} +134 -16
- package/dist/adapters/index.d.mts +2 -2
- package/dist/adapters/index.mjs +1 -1
- package/dist/{adapters-gM-WYjNe.mjs → adapters-BxGgSHjj.mjs} +1 -9
- package/dist/applyPermissionResult-D6GPMsvh.mjs +37 -0
- package/dist/audit/index.d.mts +1 -1
- package/dist/audit/index.mjs +1 -1
- package/dist/audit/mongodb.d.mts +1 -1
- package/dist/audit/mongodb.mjs +1 -1
- package/dist/auth/index.d.mts +4 -4
- package/dist/auth/index.mjs +7 -6
- package/dist/auth/mongoose.d.mts +191 -0
- package/dist/auth/mongoose.mjs +73 -0
- package/dist/auth/redis-session.d.mts +1 -1
- package/dist/{betterAuthOpenApi-lz0IRbXJ.mjs → betterAuthOpenApi-CCw3YX0g.mjs} +1 -1
- package/dist/cache/index.d.mts +2 -2
- package/dist/cache/index.mjs +2 -2
- package/dist/cli/commands/docs.mjs +2 -2
- package/dist/cli/commands/generate.mjs +1 -1
- package/dist/cli/commands/init.mjs +7 -5
- package/dist/cli/commands/introspect.mjs +1 -1
- package/dist/core/index.d.mts +3 -3
- package/dist/core/index.mjs +4 -4
- package/dist/{core-C1XCMtqM.mjs → core-BWekSEju.mjs} +41 -13
- package/dist/{createApp-D2w0LdYJ.mjs → createApp-D7e77m8C.mjs} +25 -14
- package/dist/{defineResource-wWMBB4GP.mjs → defineResource-DZzyl4a4.mjs} +42 -37
- package/dist/docs/index.d.mts +2 -2
- package/dist/docs/index.mjs +1 -1
- package/dist/dynamic/index.d.mts +2 -2
- package/dist/dynamic/index.mjs +2 -2
- package/dist/{elevation-BEdACOLB.mjs → elevation-By_p2lnn.mjs} +1 -1
- package/dist/elevation-D7WK0RXq.d.mts +23 -0
- package/dist/{errorHandler-r2595m8T.mjs → errorHandler-CH8wk1eD.mjs} +17 -2
- package/dist/{errorHandler-Do4vVQ1f.d.mts → errorHandler-pCpEtNd7.d.mts} +46 -2
- package/dist/{eventPlugin-Ba00swHF.mjs → eventPlugin-B6U_nCFU.mjs} +4 -3
- package/dist/{eventPlugin-DW45v4V5.d.mts → eventPlugin-CdvUoUna.d.mts} +1 -1
- package/dist/events/index.d.mts +3 -3
- package/dist/events/index.mjs +1 -1
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis.d.mts +1 -1
- package/dist/factory/index.d.mts +1 -1
- package/dist/factory/index.mjs +1 -1
- package/dist/hooks/index.d.mts +1 -1
- package/dist/hooks/index.mjs +1 -1
- package/dist/idempotency/index.d.mts +3 -3
- package/dist/idempotency/mongodb.d.mts +1 -1
- package/dist/idempotency/redis.d.mts +1 -1
- package/dist/index-B0extFr4.d.mts +640 -0
- package/dist/{index-gz6iuzCp.d.mts → index-BjShrzoj.d.mts} +47 -4
- package/dist/{index-CHeJa4Zd.d.mts → index-C9eYNjGR.d.mts} +1 -1
- package/dist/index.d.mts +9 -8
- package/dist/index.mjs +10 -9
- package/dist/integrations/event-gateway.d.mts +1 -1
- package/dist/integrations/event-gateway.mjs +1 -1
- package/dist/integrations/index.d.mts +1 -1
- package/dist/integrations/mcp/index.d.mts +2 -2
- package/dist/integrations/mcp/index.mjs +8 -5
- package/dist/integrations/mcp/testing.d.mts +1 -1
- package/dist/integrations/mcp/testing.mjs +1 -1
- package/dist/integrations/webhooks.d.mts +58 -1
- package/dist/integrations/webhooks.mjs +78 -7
- package/dist/integrations/websocket.d.mts +7 -1
- package/dist/integrations/websocket.mjs +7 -1
- package/dist/{interface-DYH8AXGe.d.mts → interface-B91alUzq.d.mts} +151 -15
- package/dist/{mongodb-pMvOlR5_.d.mts → mongodb-B7zupyck.d.mts} +1 -1
- package/dist/{mongodb-kltrBPa1.d.mts → mongodb-Cgu9F1Nd.d.mts} +1 -1
- package/dist/{openapi-CBmZ6EQN.mjs → openapi-BBSTVcMm.mjs} +1 -1
- package/dist/org/index.d.mts +2 -2
- package/dist/org/index.mjs +1 -1
- package/dist/permissions/index.d.mts +4 -4
- package/dist/permissions/index.mjs +3 -2
- package/dist/{permissions-C8ImI8gC.mjs → permissions-CH4cNwJi.mjs} +358 -64
- package/dist/plugins/index.d.mts +52 -5
- package/dist/plugins/index.mjs +12 -11
- package/dist/plugins/response-cache.mjs +1 -1
- package/dist/plugins/tracing-entry.d.mts +1 -1
- package/dist/plugins/tracing-entry.mjs +1 -1
- package/dist/policies/index.d.mts +1 -1
- package/dist/presets/index.d.mts +3 -3
- package/dist/presets/index.mjs +1 -1
- package/dist/presets/multiTenant.d.mts +53 -3
- package/dist/presets/multiTenant.mjs +89 -47
- package/dist/{presets-BMfdy34e.mjs → presets-BFrGvvjL.mjs} +2 -2
- package/dist/{queryCachePlugin-DcmETvcB.d.mts → queryCachePlugin-Ckl71mkc.d.mts} +1 -1
- package/dist/{queryCachePlugin-XtFplYO9.mjs → queryCachePlugin-CwTpR04-.mjs} +2 -2
- package/dist/{redis-D0Qc-9EW.d.mts → redis-3TQxm2VZ.d.mts} +1 -1
- package/dist/{redis-stream-BW9UKLZM.d.mts → redis-stream-Dag5LFa9.d.mts} +1 -1
- package/dist/registry/index.d.mts +1 -1
- package/dist/registry/index.mjs +2 -2
- package/dist/replyHelpers-uDUIYh7u.mjs +40 -0
- package/dist/{resourceToTools-nCJWnG1r.mjs → resourceToTools-BJkoQoUP.mjs} +74 -25
- package/dist/rpc/index.d.mts +1 -1
- package/dist/rpc/index.mjs +1 -1
- package/dist/scope/index.d.mts +3 -2
- package/dist/scope/index.mjs +4 -3
- package/dist/{sse-BF7GR7IB.mjs → sse-6W0hjVS_.mjs} +2 -2
- package/dist/testing/index.d.mts +2 -2
- package/dist/testing/index.mjs +1 -1
- package/dist/types/index.d.mts +4 -3
- package/dist/types/index.mjs +1 -1
- package/dist/types--D3vvfdt.d.mts +286 -0
- package/dist/{types-By-5mIfn.d.mts → types-2FlNl0mL.d.mts} +44 -9
- package/dist/types-AOD8fxIw.mjs +229 -0
- package/dist/types-B4BNthET.d.mts +178 -0
- package/dist/{types-B4_TDdPe.d.mts → types-C5g2oRC7.d.mts} +18 -2
- package/dist/utils/index.d.mts +3 -3
- package/dist/utils/index.mjs +5 -5
- package/package.json +21 -6
- package/skills/arc/SKILL.md +314 -6
- package/skills/arc/references/integrations.md +32 -7
- package/skills/arc/references/mcp.md +31 -7
- package/skills/arc/references/multi-tenancy.md +208 -0
- package/skills/arc/references/production.md +69 -0
- package/dist/elevation-C_taLQrM.d.mts +0 -147
- package/dist/index-NGZksqM5.d.mts +0 -398
- package/dist/types-BNUccdcf.d.mts +0 -101
- package/dist/types-BhtYdxZU.mjs +0 -91
- /package/dist/{EventTransport-wc5hSLik.d.mts → EventTransport-C4VheKeC.d.mts} +0 -0
- /package/dist/{HookSystem-COkyWztM.mjs → HookSystem-D7lfx--K.mjs} +0 -0
- /package/dist/{ResourceRegistry-C6ngvOnn.mjs → ResourceRegistry-DsHiG9cL.mjs} +0 -0
- /package/dist/{caching-BSXB-Xr7.mjs → caching-5DtLwIqb.mjs} +0 -0
- /package/dist/{circuitBreaker-JP2GdJ4b.d.mts → circuitBreaker-BBPDt-J_.d.mts} +0 -0
- /package/dist/{circuitBreaker-BOBOpN2w.mjs → circuitBreaker-l18oRgL5.mjs} +0 -0
- /package/dist/{errors-CcVbl1-T.d.mts → errors-BS6lZvWy.d.mts} +0 -0
- /package/dist/{errors-NoQKsbAT.mjs → errors-Cg58SLNi.mjs} +0 -0
- /package/dist/{externalPaths-DpO-s7r8.d.mts → externalPaths-iba7jD3d.d.mts} +0 -0
- /package/dist/{fields-DFwdaWCq.d.mts → fields-D4nMDqnK.d.mts} +0 -0
- /package/dist/{interface-D_BWALyZ.d.mts → interface-CG7oRZjX.d.mts} +0 -0
- /package/dist/{interface-gr-7qo9j.d.mts → interface-CSbZdv_3.d.mts} +0 -0
- /package/dist/{logger-Dz3j1ItV.mjs → logger-DLg8-Ueg.mjs} +0 -0
- /package/dist/{memory-BFAYkf8H.mjs → memory-Cp7_cAko.mjs} +0 -0
- /package/dist/{metrics-Csh4nsvv.mjs → metrics-Qnvwc-LQ.mjs} +0 -0
- /package/dist/{mongodb-BuQ7fNTg.mjs → mongodb-B7X7P1P8.mjs} +0 -0
- /package/dist/{pluralize-CcT6qF0a.mjs → pluralize-Dckfq6US.mjs} +0 -0
- /package/dist/{registry-I-ogLgL9.mjs → registry-B3lRFBWo.mjs} +0 -0
- /package/dist/{requestContext-DYtmNpm5.mjs → requestContext-xHIKedG6.mjs} +0 -0
- /package/dist/{schemaConverter-DjzHpFam.mjs → schemaConverter-0TyONAwM.mjs} +0 -0
- /package/dist/{sessionManager-wbkYj2HL.d.mts → sessionManager-CEo9jwPI.d.mts} +0 -0
- /package/dist/{tracing-bz_U4EM1.d.mts → tracing-DEqdGkr-.d.mts} +0 -0
- /package/dist/{typeGuards-Cj5Rgvlg.mjs → typeGuards-CcFZXgU7.mjs} +0 -0
- /package/dist/{utils-Dc0WhlIl.mjs → utils-B-l6410F.mjs} +0 -0
- /package/dist/{versioning-BzfeHmhj.mjs → versioning-CdBbFefk.mjs} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { C as CrudRouterOptions, Ft as IController, It as IControllerResponse, Lt as IRequestContext, it as RequestWithExtras, k as FastifyWithDecorators, nt as RequestContext, x as CrudController } from "./interface-
|
|
3
|
-
import { t as PermissionCheck } from "./types-
|
|
1
|
+
import { r as RequestScope } from "./types--D3vvfdt.mjs";
|
|
2
|
+
import { C as CrudRouterOptions, Ft as IController, It as IControllerResponse, Lt as IRequestContext, Vt as ResourceDefinition, it as RequestWithExtras, k as FastifyWithDecorators, nt as RequestContext, ot as ResourceConfig, u as AnyRecord, x as CrudController } from "./interface-B91alUzq.mjs";
|
|
3
|
+
import { t as PermissionCheck } from "./types-B4BNthET.mjs";
|
|
4
4
|
import { FastifyInstance, FastifyReply, FastifyRequest, RouteHandlerMethod } from "fastify";
|
|
5
5
|
|
|
6
6
|
//#region src/constants.d.ts
|
|
@@ -144,6 +144,49 @@ declare function createCrudRouter<TDoc = unknown>(fastify: FastifyWithDecorators
|
|
|
144
144
|
*/
|
|
145
145
|
declare function createPermissionMiddleware(permission: PermissionCheck, resourceName: string, action: string): RouteHandlerMethod | null;
|
|
146
146
|
//#endregion
|
|
147
|
+
//#region src/core/defineResourceVariants.d.ts
|
|
148
|
+
/**
|
|
149
|
+
* Required identity fields for each variant. The user MUST provide a unique
|
|
150
|
+
* `name` (registry collision prevention) and a unique `prefix` (route
|
|
151
|
+
* collision prevention) for every variant.
|
|
152
|
+
*/
|
|
153
|
+
type VariantIdentity = Required<Pick<ResourceConfig, "name" | "prefix">>;
|
|
154
|
+
/**
|
|
155
|
+
* A variant override = identity (name + prefix) + any other ResourceConfig
|
|
156
|
+
* field that should differ from the base.
|
|
157
|
+
*/
|
|
158
|
+
type VariantOverride<TDoc = AnyRecord> = VariantIdentity & Partial<ResourceConfig<TDoc>>;
|
|
159
|
+
/**
|
|
160
|
+
* Map of variant key → override config. The key becomes the property name in
|
|
161
|
+
* the returned object (e.g. `{ articlePublic: ... }` → `result.articlePublic`).
|
|
162
|
+
*/
|
|
163
|
+
type VariantsMap<TDoc = AnyRecord> = Record<string, VariantOverride<TDoc>>;
|
|
164
|
+
/**
|
|
165
|
+
* Result type — preserves the variant keys so destructuring is type-safe:
|
|
166
|
+
* `const { articlePublic, articleAdmin } = defineResourceVariants(...)`.
|
|
167
|
+
*/
|
|
168
|
+
type VariantsResult<TDoc, V extends VariantsMap<TDoc>> = { [K in keyof V]: ResourceDefinition<TDoc> };
|
|
169
|
+
/**
|
|
170
|
+
* Define multiple resources from a shared base config and per-variant overrides.
|
|
171
|
+
*
|
|
172
|
+
* Each variant is independently passed through `defineResource()` — the
|
|
173
|
+
* returned `ResourceDefinition`s are real, fully-registered resources.
|
|
174
|
+
* Register each one's plugin in your app:
|
|
175
|
+
*
|
|
176
|
+
* ```typescript
|
|
177
|
+
* await app.register(articlePublic.toPlugin());
|
|
178
|
+
* await app.register(articleAdmin.toPlugin());
|
|
179
|
+
* ```
|
|
180
|
+
*
|
|
181
|
+
* @param base Shared config — adapter, queryParser, schemaOptions, hooks, etc.
|
|
182
|
+
* Must NOT include `name` or `prefix` (those are per-variant).
|
|
183
|
+
* @param variants Map of variant key → override. Each variant must declare
|
|
184
|
+
* its own `name` and `prefix`. Other fields override the base.
|
|
185
|
+
* @returns A record where each key from `variants` maps to a real
|
|
186
|
+
* `ResourceDefinition` ready for `.toPlugin()` registration.
|
|
187
|
+
*/
|
|
188
|
+
declare function defineResourceVariants<TDoc = AnyRecord, V extends VariantsMap<TDoc> = VariantsMap<TDoc>>(base: Omit<ResourceConfig<TDoc>, "name" | "prefix">, variants: V): VariantsResult<TDoc, V>;
|
|
189
|
+
//#endregion
|
|
147
190
|
//#region src/core/fastifyAdapter.d.ts
|
|
148
191
|
/**
|
|
149
192
|
* Create IRequestContext from Fastify request
|
|
@@ -212,4 +255,4 @@ declare function createCrudHandlers<TDoc>(controller: IController<TDoc>): {
|
|
|
212
255
|
delete: (req: FastifyRequest, reply: FastifyReply) => Promise<void>;
|
|
213
256
|
};
|
|
214
257
|
//#endregion
|
|
215
|
-
export {
|
|
258
|
+
export { MutationOperation as A, HOOK_PHASES as C, MAX_REGEX_LENGTH as D, MAX_FILTER_DEPTH as E, SYSTEM_FIELDS as M, MAX_SEARCH_LENGTH as O, HOOK_OPERATIONS as S, HookPhase as T, DEFAULT_LIMIT as _, getControllerScope as a, DEFAULT_TENANT_FIELD as b, createCrudRouter as c, ActionRouterConfig as d, IdempotencyService as f, DEFAULT_ID_FIELD as g, CrudOperation as h, getControllerContext as i, RESERVED_QUERY_PARAMS as j, MUTATION_OPERATIONS as k, createPermissionMiddleware as l, CRUD_OPERATIONS as m, createFastifyHandler as n, sendControllerResponse as o, createActionRouter as p, createRequestContext as r, defineResourceVariants as s, createCrudHandlers as t, ActionHandler as u, DEFAULT_MAX_LIMIT as v, HookOperation as w, DEFAULT_UPDATE_METHOD as x, DEFAULT_SORT as y };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { G as OpenApiSchemas, Q as QueryParserInterface, Ut as CrudRepository, c as ValidationResult, ft as RouteSchemaOptions, n as AdapterSchemaContext, o as RepositoryLike, q as ParsedQuery, r as DataAdapter, s as SchemaMetadata } from "./interface-
|
|
1
|
+
import { G as OpenApiSchemas, Q as QueryParserInterface, Ut as CrudRepository, c as ValidationResult, ft as RouteSchemaOptions, n as AdapterSchemaContext, o as RepositoryLike, q as ParsedQuery, r as DataAdapter, s as SchemaMetadata } from "./interface-B91alUzq.mjs";
|
|
2
2
|
import { Model } from "mongoose";
|
|
3
3
|
|
|
4
4
|
//#region src/adapters/mongoose.d.ts
|
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-
|
|
2
|
-
import { a as applyFieldWritePermissions, i as applyFieldReadPermissions, n as FieldPermissionMap, o as fields, t as FieldPermission } from "./fields-
|
|
3
|
-
import { i as UserBase, n as PermissionContext, r as PermissionResult, t as PermissionCheck } from "./types-
|
|
4
|
-
import { l as createMongooseAdapter, o as createPrismaAdapter, s as MongooseAdapter, t as PrismaAdapter } from "./index-
|
|
5
|
-
import { A as
|
|
6
|
-
import { C as
|
|
7
|
-
import { a as NotFoundError, d as ValidationError, f as createDomainError, i as ForbiddenError, t as ArcError, u as UnauthorizedError } from "./errors-
|
|
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
|
/**
|
|
@@ -252,4 +253,4 @@ declare function arcLog(module: string): ArcLogger;
|
|
|
252
253
|
//#region src/index.d.ts
|
|
253
254
|
declare const version: string;
|
|
254
255
|
//#endregion
|
|
255
|
-
export { type ValidationResult as AdapterValidationResult, type AdditionalRoute, type AnyRecord, type ApiResponse, ArcError, type ArcInternalMetadata, type ArcLogWriter, type ArcLogger, type ArcLoggerOptions, type ArcRequest, type AuthPluginOptions, BaseController, type BaseControllerOptions, CRUD_OPERATIONS, type ConfigError, type ControllerLike, type CrudController, CrudOperation, type CrudRepository, type CrudRouteKey, type CrudRouterOptions, type CrudSchemas, DEFAULT_ID_FIELD, DEFAULT_LIMIT, DEFAULT_MAX_LIMIT, DEFAULT_SORT, DEFAULT_TENANT_FIELD, DEFAULT_UPDATE_METHOD, type DataAdapter, type DynamicPermissionMatrix, type DynamicPermissionMatrixConfig, type EventDefinition, type FastifyRequestExtras, type FastifyWithAuth, type FastifyWithDecorators, type FieldMetadata, type FieldPermission, type FieldPermissionMap, type FieldRule, ForbiddenError, type GracefulShutdownOptions, type Guard, HOOK_OPERATIONS, HOOK_PHASES, type HealthCheck, type HealthOptions, HookOperation, HookPhase, type IController, type IControllerResponse, type IRequestContext, type InferAdapterDoc, type InferDocType, type InferResourceDoc, type Interceptor, type IntrospectionData, type IntrospectionPluginOptions, type JWTPayload, MAX_FILTER_DEPTH, MAX_REGEX_LENGTH, MAX_SEARCH_LENGTH, MUTATION_OPERATIONS, type MiddlewareConfig, MongooseAdapter, MutationOperation, type NamedMiddleware, NotFoundError, type OwnershipCheck, type PaginatedResult, type PermissionCheck, type PermissionContext, type PermissionResult, type PipelineConfig, type PipelineContext, type PipelineStep, type PresetFunction, type PresetResult, PrismaAdapter, type QueryOptions, RESERVED_QUERY_PARAMS, type RateLimitConfig, type RegistryEntry, type RegistryStats, type RelationMetadata, type RepositoryLike, type RequestContext, type RequestIdOptions, type RequestStore, type RequestWithExtras, type ResourceConfig, ResourceDefinition, type ResourceMetadata, type RouteHandler, type RouteHandlerMethod, type RouteSchemaOptions, SYSTEM_FIELDS, type SchemaMetadata, type ServiceContext, type Transform, type TypedController, type TypedRepository, type TypedResourceConfig, UnauthorizedError, type UserBase, type UserOrganization, type ValidateOptions, ValidationError, type ValidationResult$1 as ValidationResult, adminOnly, allOf, allowPublic, anyOf, applyFieldReadPermissions, applyFieldWritePermissions, arcLog, assertValidConfig, authenticated, configureArcLogger, createDomainError, createDynamicPermissionMatrix, createMongooseAdapter, createOrgPermissions, createPrismaAdapter, defineResource, denyAll, envelope, fields, formatValidationErrors, fullPublic, getControllerScope, guard, intercept, middleware, ownerWithAdminBypass, presets_d_exports as permissions, pipe, publicRead, publicReadAdminWrite, readOnly, requestContext, requireAuth, requireOrgMembership, requireOrgRole, requireOwnership, requireRoles, requireTeamMembership, sortMiddlewares, transform, validateResourceConfig, version, when };
|
|
256
|
+
export { type ValidationResult as AdapterValidationResult, type AdditionalRoute, type AnyRecord, type ApiResponse, ArcError, type ArcInternalMetadata, type ArcLogWriter, type ArcLogger, type ArcLoggerOptions, type ArcRequest, type AuthPluginOptions, BaseController, type BaseControllerOptions, CRUD_OPERATIONS, type ConfigError, type ControllerLike, type CrudController, CrudOperation, type CrudRepository, type CrudRouteKey, type CrudRouterOptions, type CrudSchemas, DEFAULT_ID_FIELD, DEFAULT_LIMIT, DEFAULT_MAX_LIMIT, DEFAULT_SORT, DEFAULT_TENANT_FIELD, DEFAULT_UPDATE_METHOD, type DataAdapter, type DynamicPermissionMatrix, type DynamicPermissionMatrixConfig, type EventDefinition, type FastifyRequestExtras, type FastifyWithAuth, type FastifyWithDecorators, type FieldMetadata, type FieldPermission, type FieldPermissionMap, type FieldRule, ForbiddenError, type GracefulShutdownOptions, type Guard, HOOK_OPERATIONS, HOOK_PHASES, type HealthCheck, type HealthOptions, HookOperation, HookPhase, type IController, type IControllerResponse, type IRequestContext, type InferAdapterDoc, type InferDocType, type InferResourceDoc, type Interceptor, type IntrospectionData, type IntrospectionPluginOptions, type JWTPayload, MAX_FILTER_DEPTH, MAX_REGEX_LENGTH, MAX_SEARCH_LENGTH, MUTATION_OPERATIONS, type MiddlewareConfig, MongooseAdapter, MutationOperation, type NamedMiddleware, NotFoundError, type OwnershipCheck, type PaginatedResult, type PermissionCheck, type PermissionContext, type PermissionResult, type PipelineConfig, type PipelineContext, type PipelineStep, type PresetFunction, type PresetResult, PrismaAdapter, type QueryOptions, RESERVED_QUERY_PARAMS, type RateLimitConfig, type RegistryEntry, type RegistryStats, type RelationMetadata, type RepositoryLike, type RequestContext, type RequestIdOptions, type RequestStore, type RequestWithExtras, type ResourceConfig, ResourceDefinition, type ResourceMetadata, type RouteHandler, type RouteHandlerMethod, type RouteSchemaOptions, SYSTEM_FIELDS, type SchemaMetadata, type ServiceContext, type Transform, type TypedController, type TypedRepository, type TypedResourceConfig, UnauthorizedError, type UserBase, type UserOrganization, type ValidateOptions, ValidationError, type ValidationResult$1 as ValidationResult, 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_d_exports as permissions, pipe, publicRead, publicReadAdminWrite, readOnly, requestContext, requireAuth, requireOrgInScope, requireOrgMembership, requireOrgRole, requireOwnership, requireRoles, requireScopeContext, requireServiceScope, requireTeamMembership, sortMiddlewares, transform, validateResourceConfig, version, when };
|
package/dist/index.mjs
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
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-Cxde4rpC.mjs";
|
|
2
|
-
import { a as createMongooseAdapter, i as MongooseAdapter, r as createPrismaAdapter, t as PrismaAdapter } from "./adapters-
|
|
3
|
-
import { t as BaseController } from "./BaseController-
|
|
2
|
+
import { a as createMongooseAdapter, i as MongooseAdapter, r as createPrismaAdapter, t as PrismaAdapter } from "./adapters-BxGgSHjj.mjs";
|
|
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
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { n as
|
|
6
|
+
import { t as defineResourceVariants } from "./core-BWekSEju.mjs";
|
|
7
|
+
import { t as requestContext } from "./requestContext-xHIKedG6.mjs";
|
|
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";
|
|
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
|
+
import { n as configureArcLogger, t as arcLog } from "./logger-DLg8-Ueg.mjs";
|
|
11
12
|
//#region src/middleware/middleware.ts
|
|
12
13
|
/**
|
|
13
14
|
* Named Middleware — Priority-based, conditional middleware execution.
|
|
@@ -127,6 +128,6 @@ function transform(name, handlerOrOptions) {
|
|
|
127
128
|
}
|
|
128
129
|
//#endregion
|
|
129
130
|
//#region src/index.ts
|
|
130
|
-
const version = "2.
|
|
131
|
+
const version = "2.7.3";
|
|
131
132
|
//#endregion
|
|
132
|
-
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, denyAll, envelope, fields, formatValidationErrors, fullPublic, getControllerScope, guard, intercept, middleware, ownerWithAdminBypass, presets_exports as permissions, pipe, publicRead, publicReadAdminWrite, readOnly, requestContext, requireAuth, requireOrgMembership, requireOrgRole, requireOwnership, requireRoles, requireTeamMembership, sortMiddlewares, transform, validateResourceConfig, version, when };
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
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
|
|
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
|
-
|
|
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 { r as createMcpServer, t as resourceToTools } from "../../resourceToTools-
|
|
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
|
|
@@ -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
|
-
|
|
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
|
|
88
|
-
const timer = setTimeout(() =>
|
|
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:
|
|
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
|
-
|
|
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);
|