@classytic/arc 2.16.0 → 2.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -2
- package/dist/{BaseController-DlCCTIxJ.mjs → BaseController-Dmeh4Y_y.mjs} +5 -47
- package/dist/{QueryCache-SvmT_9ti.d.mts → QueryCache-8YmAQ-zW.d.mts} +1 -1
- package/dist/{ResourceRegistry-f48hFk3m.mjs → ResourceRegistry-CzSzVK1m.mjs} +3 -0
- package/dist/audit/index.d.mts +1 -1
- package/dist/audit/index.mjs +2 -1
- package/dist/auth/index.d.mts +4 -4
- package/dist/auth/index.mjs +2 -2
- package/dist/auth/redis-session.d.mts +1 -1
- package/dist/auth/redis-session.mjs +28 -2
- package/dist/{buildHandler-BZX6zzDM.mjs → buildHandler-C_SkBNF3.mjs} +6 -4
- package/dist/cache/index.d.mts +3 -3
- package/dist/cli/commands/describe.d.mts +1 -1
- package/dist/cli/commands/describe.mjs +1 -1
- package/dist/cli/commands/docs.mjs +2 -2
- package/dist/cli/commands/generate.d.mts +1 -1
- package/dist/cli/commands/generate.mjs +1 -1
- package/dist/cli/commands/init.d.mts +1 -1
- package/dist/cli/commands/init.mjs +1 -1
- package/dist/cli/commands/introspect.mjs +1 -1
- package/dist/cli/index.d.mts +2 -2
- package/dist/cli/index.mjs +2 -2
- package/dist/core/index.d.mts +2 -2
- package/dist/core/index.mjs +3 -3
- package/dist/{core-DBJ_j6rX.mjs → core-DYJvu7aI.mjs} +174 -31
- package/dist/{createActionRouter-DUpN3Dd1.mjs → createActionRouter-BHUMb1-y.mjs} +6 -3
- package/dist/{createAggregationRouter-Dq-TUCuY.mjs → createAggregationRouter-BYVvhfc5.mjs} +3 -3
- package/dist/{createApp-DNccuhyI.mjs → createApp-CzCEEsEm.mjs} +9 -9
- package/dist/{defineEvent-DRwY0fYm.mjs → defineEvent-NPKUJRt8.mjs} +1 -1
- package/dist/docs/index.d.mts +2 -2
- package/dist/docs/index.mjs +2 -2
- package/dist/{elevation-Dci0AYLT.mjs → elevation-6mfTLBC-.mjs} +1 -1
- package/dist/{errorHandler-DpoXQHZ9.mjs → errorHandler-wQejMBT3.mjs} +45 -4
- package/dist/{eventPlugin-C2cGqtRO.mjs → eventPlugin-CnJCUyM0.mjs} +1 -1
- package/dist/{eventPlugin-CtHC_av1.d.mts → eventPlugin-DjEx_J3j.d.mts} +1 -1
- package/dist/events/index.d.mts +3 -3
- package/dist/events/index.mjs +5 -5
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis-stream-entry.mjs +34 -1
- package/dist/events/transports/redis.d.mts +1 -1
- package/dist/events/transports/redis.mjs +17 -1
- package/dist/factory/index.d.mts +2 -2
- package/dist/factory/index.mjs +4 -3
- package/dist/fastify-augmentation-7LIR0nDv.d.mts +42 -0
- package/dist/{fields-Anj0xdih.d.mts → fields-DqZLp5d6.d.mts} +1 -1
- package/dist/hooks/index.d.mts +1 -1
- package/dist/idempotency/index.d.mts +2 -2
- package/dist/idempotency/index.mjs +21 -5
- package/dist/idempotency/redis.d.mts +1 -1
- package/dist/{index-3oIimXQn.d.mts → index-3L-adFJO.d.mts} +3 -3
- package/dist/{index-CkW0flkU.d.mts → index-C1JSBan-.d.mts} +256 -8
- package/dist/{index-B-ulKx5P.d.mts → index-CruZir1o.d.mts} +36 -16
- package/dist/index.d.mts +5 -5
- package/dist/index.mjs +5 -5
- package/dist/integrations/event-gateway.d.mts +2 -2
- package/dist/integrations/event-gateway.mjs +1 -1
- package/dist/integrations/index.d.mts +2 -2
- package/dist/integrations/jobs.d.mts +36 -1
- package/dist/integrations/jobs.mjs +82 -9
- package/dist/integrations/mcp/index.d.mts +67 -5
- package/dist/integrations/mcp/index.mjs +93 -2
- package/dist/integrations/mcp/testing.d.mts +1 -1
- package/dist/integrations/mcp/testing.mjs +1 -1
- package/dist/integrations/websocket-redis.d.mts +1 -1
- package/dist/integrations/websocket.d.mts +1 -1
- package/dist/integrations/websocket.mjs +2 -1
- package/dist/{loadResourcesFromEntry-BLMEI2Xa.mjs → loadResourcesFromEntry-DCjYMgfC.mjs} +1 -1
- package/dist/{mcpPlugin-7vGV51ED.mjs → mcpPlugin-hZE00H74.mjs} +114 -11
- package/dist/middleware/index.d.mts +1 -1
- package/dist/middleware/index.mjs +1 -1
- package/dist/{multipartBody-BOvVSVCD.mjs → multipartBody-VjVWmZl8.mjs} +2 -1
- package/dist/{openapi-34T9yNwd.mjs → openapi-DKDs9NfD.mjs} +37 -10
- package/dist/permissions/index.d.mts +2 -2
- package/dist/permissions/index.mjs +2 -2
- package/dist/{permissions-CTxMrreC.mjs → permissions-B1Qy55fg.mjs} +169 -169
- package/dist/pipeline/index.d.mts +1 -1
- package/dist/plugins/index.d.mts +5 -59
- package/dist/plugins/index.mjs +7 -7
- package/dist/plugins/tracing-entry.d.mts +1 -1
- package/dist/plugins/tracing-entry.mjs +1 -1
- package/dist/preloadResources-BOXzPhKU.mjs +45 -0
- package/dist/presets/filesUpload.d.mts +4 -4
- package/dist/presets/filesUpload.mjs +2 -2
- package/dist/presets/index.d.mts +1 -1
- package/dist/presets/index.mjs +1 -1
- package/dist/presets/multiTenant.d.mts +1 -1
- package/dist/presets/search.d.mts +2 -2
- package/dist/presets/search.mjs +1 -1
- package/dist/{presets-C9BE6WaZ.mjs → presets-BYbPobbF.mjs} +5 -3
- package/dist/{queryCachePlugin-Biqzfbi5.d.mts → queryCachePlugin-Do4P_WBC.d.mts} +2 -2
- package/dist/{redis-Cyzrz6SX.d.mts → redis-cpqVb85w.d.mts} +1 -1
- package/dist/{redis-stream-DT-YjzrB.d.mts → redis-stream-BuiAS3aE.d.mts} +1 -1
- package/dist/registry/index.d.mts +30 -8
- package/dist/registry/index.mjs +2 -2
- package/dist/{registry-BBE23CDj.mjs → registry-BrlHkrRS.mjs} +14 -8
- package/dist/{routerShared-CZV5aabX.mjs → routerShared-nCyJGOKX.mjs} +164 -17
- package/dist/scope/index.d.mts +2 -2
- package/dist/scope/index.mjs +2 -2
- package/dist/testing/index.d.mts +2 -25
- package/dist/testing/index.mjs +2 -45
- package/dist/testing/storageContract.d.mts +1 -1
- package/dist/types/index.d.mts +4 -4
- package/dist/types/storage.d.mts +1 -1
- package/dist/{types-D-fYtKjb.d.mts → types-BRmS8TNa.d.mts} +34 -11
- package/dist/{types-BsJMEQ4D.d.mts → types-CoMjNmkF.d.mts} +12 -1
- package/dist/utils/index.d.mts +83 -5
- package/dist/utils/index.mjs +2 -2
- package/dist/{utils-DC5ycPfr.mjs → utils-CvD3glpM.mjs} +168 -3
- package/dist/{validate-By96rH0r.mjs → validate-DFIP2VrM.mjs} +65 -3
- package/dist/{versioning-ZwX9tmbS.d.mts → versioning-5H4930tj.d.mts} +1 -1
- package/package.json +3 -3
- package/skills/arc/SKILL.md +43 -1
- package/skills/arc/references/api-reference.md +2 -0
- package/skills/arc/references/mcp.md +30 -1
- /package/dist/{EventTransport-C-2oAHtw.d.mts → EventTransport-B1GFiYUy.d.mts} +0 -0
- /package/dist/{EventTransport-Hxvv5QQz.mjs → EventTransport-ClEDfiMS.mjs} +0 -0
- /package/dist/{caching-TeHE8G-v.mjs → caching-8MfrrUYU.mjs} +0 -0
- /package/dist/{elevation-0YBpa663.d.mts → elevation-DvwtjDwu.d.mts} +0 -0
- /package/dist/{errorHandler-mHuyWzZE.d.mts → errorHandler-DuqTqeqn.d.mts} +0 -0
- /package/dist/{errors-C1lX_jlm.d.mts → errors-4ZloYFvm.d.mts} +0 -0
- /package/dist/{externalPaths-DFg-2KTp.d.mts → externalPaths-ClykOQqa.d.mts} +0 -0
- /package/dist/{generate-CYac-OLv.mjs → generate-CcM24hoq.mjs} +0 -0
- /package/dist/{generate-BWFwgcCM.d.mts → generate-cRnsafMS.d.mts} +0 -0
- /package/dist/{init-Dv71MsJr.d.mts → init-D-vrTZ1G.d.mts} +0 -0
- /package/dist/{init-HDvoO9L5.mjs → init-DYzfniuX.mjs} +0 -0
- /package/dist/{interface-CH0OQudo.d.mts → interface-BVkZsvEe.d.mts} +0 -0
- /package/dist/{interface-NwJ_qPlY.d.mts → interface-DoE6UoC2.d.mts} +0 -0
- /package/dist/{loadResources-ChQEj8ih.mjs → loadResources-fol6JuvW.mjs} +0 -0
- /package/dist/{metrics-TuOmguhi.mjs → metrics-B-PU4-Yu.mjs} +0 -0
- /package/dist/{replyHelpers-C-gD32oF.mjs → replyHelpers-boS5Rx26.mjs} +0 -0
- /package/dist/{schemaIR-Ctc89DSn.mjs → schemaIR-DAOvBWgT.mjs} +0 -0
- /package/dist/{sessionManager-BqFegc0W.d.mts → sessionManager-DlmaHUAl.d.mts} +0 -0
- /package/dist/{sse-BY6sTy4P.mjs → sse-jh5OqFjR.mjs} +0 -0
- /package/dist/{storage-D2KZJAmn.d.mts → storage-HcAgP5Ni.d.mts} +0 -0
- /package/dist/{store-helpers-B0sunfZZ.mjs → store-helpers-5JgaxieH.mjs} +0 -0
- /package/dist/{tracing-Dm8n7Cnn.d.mts → tracing-bRqqUxFO.d.mts} +0 -0
- /package/dist/{types-DVfpSfx2.d.mts → types-CfapW2t8.d.mts} +0 -0
- /package/dist/{types-D57iXYb8.mjs → types-DV9WDfeg.mjs} +0 -0
- /package/dist/{versioning-B6mimogM.mjs → versioning-CuIwUQZ2.mjs} +0 -0
- /package/dist/{websocket-BkjeGZRn.d.mts → websocket-BJ0w-3Dt.d.mts} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { C as RequestContext, F as FastifyWithDecorators, H as ActionDefinition, K as ArcFieldRule, Kt as AnyRecord, L as RequestWithExtras, T as CrudRouterOptions, Tt as AggregationConfig, V as ResourceDefinition, bt as IRequestContext, ft as RouteMcpConfig, mt as RouteSchemaOptions, q as CrudController, st as ResourceConfig, vt as IController, yt as IControllerResponse } from "./index-
|
|
2
|
-
import { i as RequestScope } from "./types-
|
|
3
|
-
import { c as PermissionCheck } from "./fields-
|
|
1
|
+
import { C as RequestContext, F as FastifyWithDecorators, H as ActionDefinition, K as ArcFieldRule, Kt as AnyRecord, L as RequestWithExtras, T as CrudRouterOptions, Tt as AggregationConfig, V as ResourceDefinition, bt as IRequestContext, ft as RouteMcpConfig, mt as RouteSchemaOptions, q as CrudController, st as ResourceConfig, vt as IController, yt as IControllerResponse } from "./index-C1JSBan-.mjs";
|
|
2
|
+
import { i as RequestScope } from "./types-CfapW2t8.mjs";
|
|
3
|
+
import { c as PermissionCheck } from "./fields-DqZLp5d6.mjs";
|
|
4
4
|
import { FastifyReply, FastifyRequest, RouteHandlerMethod } from "fastify";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import { AggDateBucket, AggMeasure, AggTopN } from "@classytic/repo-core/repository";
|
|
@@ -240,9 +240,13 @@ declare function getEntityQuery(req: RequestWithExtras): Record<string, string>;
|
|
|
240
240
|
//#endregion
|
|
241
241
|
//#region src/core/fastifyAdapter.d.ts
|
|
242
242
|
/**
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
*
|
|
243
|
+
* Project `FastifyRequest` into the curated `IRequestContext` shape that
|
|
244
|
+
* arc's controllers receive. This is a developer-facing facade, not a
|
|
245
|
+
* portability boundary — arc is Fastify-only. The projection lifts the
|
|
246
|
+
* fields tenant-scoped controllers reach for (`organizationId`,
|
|
247
|
+
* `teamId`, `scope`, `context`, server accessor) onto a flat, typed
|
|
248
|
+
* surface so handler code doesn't have to dig through Fastify's
|
|
249
|
+
* decorator surface or `req.metadata._scope` internals.
|
|
246
250
|
*/
|
|
247
251
|
declare function createRequestContext(req: FastifyRequest): IRequestContext;
|
|
248
252
|
/**
|
|
@@ -258,25 +262,41 @@ declare function getControllerContext(req: IRequestContext): RequestContext;
|
|
|
258
262
|
*/
|
|
259
263
|
declare function getControllerScope(req: IRequestContext): RequestScope;
|
|
260
264
|
/**
|
|
261
|
-
* Send IControllerResponse
|
|
265
|
+
* Send a controller's `IControllerResponse` through Fastify's reply.
|
|
266
|
+
*
|
|
267
|
+
* The `IControllerResponse<T>` envelope is arc's internal pipeline
|
|
268
|
+
* contract — it's how custom `status`, `meta`, `headers`, and field
|
|
269
|
+
* permissions thread from a handler through interceptors to the wire
|
|
270
|
+
* without each step having to touch `reply` directly. Not a portability
|
|
271
|
+
* abstraction (arc is Fastify-only); a pipeline-internal one.
|
|
262
272
|
*
|
|
263
|
-
*
|
|
264
|
-
*
|
|
273
|
+
* **Auto-envelope of bare returns.** Custom route handlers are
|
|
274
|
+
* expected to return the envelope shape so this function can apply
|
|
275
|
+
* field permissions, attach `fieldCaps`, and flatten paginated lists.
|
|
276
|
+
* A handler that returns raw `T` (array, plain object, primitive) used
|
|
277
|
+
* to silently produce a broken response — `response.data` was
|
|
278
|
+
* `undefined`, the body sent was `undefined`, and any declared
|
|
279
|
+
* `fields:` permission map was bypassed (the leak shape mentora
|
|
280
|
+
* flagged). 2.17 detects bare returns and wraps them into
|
|
281
|
+
* `{ data: response, status: 200 }` before the field-permission
|
|
282
|
+
* pipeline runs, so `fields:` applies to ALL custom routes whether the
|
|
283
|
+
* handler envelopes its return value or not. The envelope shape is
|
|
284
|
+
* still the canonical contract — auto-wrapping is a safety net, not
|
|
285
|
+
* an invitation to drop the envelope.
|
|
265
286
|
*/
|
|
266
287
|
declare function sendControllerResponse<T>(reply: FastifyReply, response: IControllerResponse<T>, request?: FastifyRequest): void;
|
|
267
288
|
/**
|
|
268
|
-
*
|
|
269
|
-
*
|
|
270
|
-
*
|
|
289
|
+
* Bridge an `IController` method (`(ctx: IRequestContext) => Promise<IControllerResponse<T>>`)
|
|
290
|
+
* into a Fastify route handler. Translates between arc's curated request
|
|
291
|
+
* context surface and Fastify's request/reply, and routes the envelope
|
|
292
|
+
* through `sendControllerResponse` so the pipeline applies field
|
|
293
|
+
* permissions, status, and meta uniformly. Internal plumbing, not a
|
|
294
|
+
* portability layer — arc is Fastify-only.
|
|
271
295
|
*
|
|
272
296
|
* @example
|
|
273
297
|
* ```typescript
|
|
274
298
|
* const controller = new BaseController(repository);
|
|
275
|
-
*
|
|
276
|
-
* // Create Fastify handler
|
|
277
299
|
* const listHandler = createFastifyHandler(controller.list.bind(controller));
|
|
278
|
-
*
|
|
279
|
-
* // Register route
|
|
280
300
|
* fastify.get('/products', listHandler);
|
|
281
301
|
* ```
|
|
282
302
|
*/
|
package/dist/index.d.mts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { $t as BaseController, A as RequestIdOptions, At as AggregationRateLimit, B as defineResource, C as RequestContext, Ct as AggMeasureShorthand, D as HealthCheck, Dt as AggregationIndexHint, E as GracefulShutdownOptions, Et as AggregationDateRangeRequirement, F as FastifyWithDecorators, J as CrudRouteKey, Jt as ArcRequest, Kt as AnyRecord, L as RequestWithExtras, N as FastifyRequestExtras, O as HealthOptions, Ot as AggregationMaterializedContext, P as FastifyWithAuth, Q as MiddlewareConfig, Qt as UserOrganization, S as QueryParserInterface, St as AggMeasureInput, T as CrudRouterOptions, Tt as AggregationConfig, V as ResourceDefinition, Vt as AuthPluginOptions, X as EventDefinition, Y as CrudSchemas, Yt as JWTPayload, Z as FieldRule, _n as ArcListResult, a as InferAdapterDoc, an as SlugMixin, bt as IRequestContext, c as TypedController, cn as BaseControllerOptions, d as PaginationResult, dn as ControllerConstructionOptions, en as SoftDeleteExt, f as IntrospectionData, g as ArcInternalMetadata, gn as ArcGetResult, gt as ControllerLike, h as ResourceMetadata, hn as ArcDeleteResult, i as ValidationResult, in as SlugExt, it as RateLimitConfig, jt as AggregationsMap, k as IntrospectionPluginOptions, kt as AggregationMaterializedResult, l as TypedRepository, ln as BaseCrudController, m as RegistryStats, mn as ArcCreateResult, mt as RouteSchemaOptions, n as ConfigError, nn as TreeExt, o as InferDocType, on as BulkExt, p as RegistryEntry, q as CrudController, qt as ApiResponse, r as ValidateOptions, rn as TreeMixin, rt as PresetResult, s as InferResourceDoc, sn as BulkMixin, st as ResourceConfig, t as RouteHandlerMethod, tn as SoftDeleteMixin, tt as PresetFunction, u as TypedResourceConfig, un as ControllerConfigurableOptions, vn as ArcUpdateResult, vt as IController, w as ServiceContext, wt as AggregationCacheConfig, xt as RouteHandler, y as OwnershipCheck, yn as ListResult, yt as IControllerResponse } from "./index-
|
|
2
|
-
import { a as applyFieldWritePermissions, c as PermissionCheck, d as UserBase, i as applyFieldReadPermissions, l as PermissionContext, n as FieldPermissionMap, o as fields, t as FieldPermission, u as PermissionResult } from "./fields-
|
|
3
|
-
import { A as MAX_FILTER_DEPTH, C as DEFAULT_SORT, D as HOOK_PHASES, E as HOOK_OPERATIONS, F as RESERVED_QUERY_PARAMS, I as SYSTEM_FIELDS, M as MAX_SEARCH_LENGTH, N as MUTATION_OPERATIONS, O as HookOperation, P as MutationOperation, S as DEFAULT_MAX_LIMIT, T as DEFAULT_UPDATE_METHOD, _ as defineAggregation, b as DEFAULT_ID_FIELD, f as defineResourceVariants, j as MAX_REGEX_LENGTH, k as HookPhase, m as defineAction, s as getControllerScope, v as CRUD_OPERATIONS, w as DEFAULT_TENANT_FIELD, x as DEFAULT_LIMIT, y as CrudOperation } from "./index-
|
|
4
|
-
import { A as requireRoles, C as allOf, E as denyAll, M as when, O as requireAuth, S as createOrgPermissions, T as anyOf, a as presets_d_exports, c as readOnly, d as requireOrgRole, f as requireScopeContext, i as ownerWithAdminBypass, k as requireOwnership, l as requireOrgInScope, m as requireTeamMembership, n as authenticated, o as publicRead, p as requireServiceScope, r as fullPublic, s as publicReadAdminWrite, t as adminOnly, u as requireOrgMembership, v as DynamicPermissionMatrix, w as allowPublic, x as createDynamicPermissionMatrix, y as DynamicPermissionMatrixConfig } from "./index-
|
|
5
|
-
import { a as NotFoundError, d as ValidationError, f as createDomainError, i as ForbiddenError, t as ArcError, u as UnauthorizedError } from "./errors-
|
|
1
|
+
import { $t as BaseController, A as RequestIdOptions, At as AggregationRateLimit, B as defineResource, C as RequestContext, Ct as AggMeasureShorthand, D as HealthCheck, Dt as AggregationIndexHint, E as GracefulShutdownOptions, Et as AggregationDateRangeRequirement, F as FastifyWithDecorators, J as CrudRouteKey, Jt as ArcRequest, Kt as AnyRecord, L as RequestWithExtras, N as FastifyRequestExtras, O as HealthOptions, Ot as AggregationMaterializedContext, P as FastifyWithAuth, Q as MiddlewareConfig, Qt as UserOrganization, S as QueryParserInterface, St as AggMeasureInput, T as CrudRouterOptions, Tt as AggregationConfig, V as ResourceDefinition, Vt as AuthPluginOptions, X as EventDefinition, Y as CrudSchemas, Yt as JWTPayload, Z as FieldRule, _n as ArcListResult, a as InferAdapterDoc, an as SlugMixin, bt as IRequestContext, c as TypedController, cn as BaseControllerOptions, d as PaginationResult, dn as ControllerConstructionOptions, en as SoftDeleteExt, f as IntrospectionData, g as ArcInternalMetadata, gn as ArcGetResult, gt as ControllerLike, h as ResourceMetadata, hn as ArcDeleteResult, i as ValidationResult, in as SlugExt, it as RateLimitConfig, jt as AggregationsMap, k as IntrospectionPluginOptions, kt as AggregationMaterializedResult, l as TypedRepository, ln as BaseCrudController, m as RegistryStats, mn as ArcCreateResult, mt as RouteSchemaOptions, n as ConfigError, nn as TreeExt, o as InferDocType, on as BulkExt, p as RegistryEntry, q as CrudController, qt as ApiResponse, r as ValidateOptions, rn as TreeMixin, rt as PresetResult, s as InferResourceDoc, sn as BulkMixin, st as ResourceConfig, t as RouteHandlerMethod, tn as SoftDeleteMixin, tt as PresetFunction, u as TypedResourceConfig, un as ControllerConfigurableOptions, vn as ArcUpdateResult, vt as IController, w as ServiceContext, wt as AggregationCacheConfig, xt as RouteHandler, y as OwnershipCheck, yn as ListResult, yt as IControllerResponse } from "./index-C1JSBan-.mjs";
|
|
2
|
+
import { a as applyFieldWritePermissions, c as PermissionCheck, d as UserBase, i as applyFieldReadPermissions, l as PermissionContext, n as FieldPermissionMap, o as fields, t as FieldPermission, u as PermissionResult } from "./fields-DqZLp5d6.mjs";
|
|
3
|
+
import { A as MAX_FILTER_DEPTH, C as DEFAULT_SORT, D as HOOK_PHASES, E as HOOK_OPERATIONS, F as RESERVED_QUERY_PARAMS, I as SYSTEM_FIELDS, M as MAX_SEARCH_LENGTH, N as MUTATION_OPERATIONS, O as HookOperation, P as MutationOperation, S as DEFAULT_MAX_LIMIT, T as DEFAULT_UPDATE_METHOD, _ as defineAggregation, b as DEFAULT_ID_FIELD, f as defineResourceVariants, j as MAX_REGEX_LENGTH, k as HookPhase, m as defineAction, s as getControllerScope, v as CRUD_OPERATIONS, w as DEFAULT_TENANT_FIELD, x as DEFAULT_LIMIT, y as CrudOperation } from "./index-CruZir1o.mjs";
|
|
4
|
+
import { A as requireRoles, C as allOf, E as denyAll, M as when, O as requireAuth, S as createOrgPermissions, T as anyOf, a as presets_d_exports, c as readOnly, d as requireOrgRole, f as requireScopeContext, i as ownerWithAdminBypass, k as requireOwnership, l as requireOrgInScope, m as requireTeamMembership, n as authenticated, o as publicRead, p as requireServiceScope, r as fullPublic, s as publicReadAdminWrite, t as adminOnly, u as requireOrgMembership, v as DynamicPermissionMatrix, w as allowPublic, x as createDynamicPermissionMatrix, y as DynamicPermissionMatrixConfig } from "./index-3L-adFJO.mjs";
|
|
5
|
+
import { a as NotFoundError, d as ValidationError, f as createDomainError, i as ForbiddenError, t as ArcError, u as UnauthorizedError } from "./errors-4ZloYFvm.mjs";
|
|
6
6
|
|
|
7
7
|
//#region src/index.d.ts
|
|
8
8
|
declare const version: string;
|
package/dist/index.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
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-TrJVIJl0.mjs";
|
|
2
2
|
import { d as createDomainError, i as NotFoundError, l as UnauthorizedError, r as ForbiddenError, t as ArcError, u as ValidationError } from "./errors-j4aJm1Wg.mjs";
|
|
3
|
-
import { a as BulkMixin, i as SlugMixin, n as TreeMixin, o as BaseCrudController, r as SoftDeleteMixin, t as BaseController } from "./BaseController-
|
|
4
|
-
import { C as
|
|
5
|
-
import {
|
|
6
|
-
import { a as defineResource, i as defineResourceVariants, o as ResourceDefinition, s as defineAction, u as defineAggregation } from "./core-
|
|
3
|
+
import { a as BulkMixin, i as SlugMixin, n as TreeMixin, o as BaseCrudController, r as SoftDeleteMixin, t as BaseController } from "./BaseController-Dmeh4Y_y.mjs";
|
|
4
|
+
import { C as requireAuth, D as when, M as applyFieldWritePermissions, N as fields, T as requireRoles, _ as requireTeamMembership, a as presets_exports, b as anyOf, c as readOnly, d as createOrgPermissions, f as requireOrgInScope, g as requireServiceScope, h as requireScopeContext, i as ownerWithAdminBypass, j as applyFieldReadPermissions, m as requireOrgRole, n as authenticated, o as publicRead, p as requireOrgMembership, r as fullPublic, s as publicReadAdminWrite, t as adminOnly, u as createDynamicPermissionMatrix, v as allOf, w as requireOwnership, x as denyAll, y as allowPublic } from "./permissions-B1Qy55fg.mjs";
|
|
5
|
+
import { x as getControllerScope } from "./routerShared-nCyJGOKX.mjs";
|
|
6
|
+
import { a as defineResource, i as defineResourceVariants, o as ResourceDefinition, s as defineAction, u as defineAggregation } from "./core-DYJvu7aI.mjs";
|
|
7
7
|
//#region src/index.ts
|
|
8
|
-
const version = "2.
|
|
8
|
+
const version = "2.17.0";
|
|
9
9
|
//#endregion
|
|
10
10
|
export { ArcError, BaseController, BaseCrudController, BulkMixin, CRUD_OPERATIONS, DEFAULT_ID_FIELD, DEFAULT_LIMIT, DEFAULT_MAX_LIMIT, DEFAULT_SORT, DEFAULT_TENANT_FIELD, DEFAULT_UPDATE_METHOD, ForbiddenError, HOOK_OPERATIONS, HOOK_PHASES, MAX_FILTER_DEPTH, MAX_REGEX_LENGTH, MAX_SEARCH_LENGTH, MUTATION_OPERATIONS, NotFoundError, RESERVED_QUERY_PARAMS, ResourceDefinition, SYSTEM_FIELDS, SlugMixin, SoftDeleteMixin, TreeMixin, UnauthorizedError, ValidationError, adminOnly, allOf, allowPublic, anyOf, applyFieldReadPermissions, applyFieldWritePermissions, authenticated, createDomainError, createDynamicPermissionMatrix, createOrgPermissions, defineAction, defineAggregation, defineResource, defineResourceVariants, denyAll, fields, fullPublic, getControllerScope, ownerWithAdminBypass, presets_exports as permissions, publicRead, publicReadAdminWrite, readOnly, requireAuth, requireOrgInScope, requireOrgMembership, requireOrgRole, requireOwnership, requireRoles, requireScopeContext, requireServiceScope, requireTeamMembership, version, when };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { n as DomainEvent } from "../EventTransport-
|
|
2
|
-
import { a as WebSocketMessage, i as WebSocketClient } from "../websocket-
|
|
1
|
+
import { n as DomainEvent } from "../EventTransport-B1GFiYUy.mjs";
|
|
2
|
+
import { a as WebSocketMessage, i as WebSocketClient } from "../websocket-BJ0w-3Dt.mjs";
|
|
3
3
|
import { FastifyPluginAsync, FastifyRequest } from "fastify";
|
|
4
4
|
|
|
5
5
|
//#region src/integrations/event-gateway.d.ts
|
|
@@ -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-jh5OqFjR.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
|
-
import { a as WebSocketMessage, i as WebSocketClient, o as WebSocketPluginOptions } from "../websocket-
|
|
1
|
+
import { a as WebSocketMessage, i as WebSocketClient, o as WebSocketPluginOptions } from "../websocket-BJ0w-3Dt.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 McpAuthResult, d as PromptDefinition, g as ToolDefinition, h as ToolContext, l as McpPluginOptions, m as ToolAnnotations, n as CallToolResult, o as CrudOperation, r as CreateMcpServerConfig, t as BetterAuthHandler, u as McpResourceConfig } from "../types-
|
|
4
|
+
import { c as McpAuthResult, d as PromptDefinition, g as ToolDefinition, h as ToolContext, l as McpPluginOptions, m as ToolAnnotations, n as CallToolResult, o as CrudOperation, r as CreateMcpServerConfig, t as BetterAuthHandler, u as McpResourceConfig } from "../types-CoMjNmkF.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 };
|
|
@@ -39,6 +39,14 @@ interface JobDefinition<TData = unknown, TResult = unknown> {
|
|
|
39
39
|
deadLetterQueue?: string;
|
|
40
40
|
/** Repeat schedule — cron or interval. Requires explicit timezone for cron. */
|
|
41
41
|
repeat?: JobRepeatOptions;
|
|
42
|
+
/**
|
|
43
|
+
* Arc-level semaphore: max simultaneous handler executions for this job type.
|
|
44
|
+
* Jobs that dequeue while all slots are full are held (state stays `active`)
|
|
45
|
+
* and appear as `throttled: true` in `GET /jobs/:id/status`.
|
|
46
|
+
* Distinct from BullMQ `concurrency` — use this when the constraint is a
|
|
47
|
+
* downstream resource (AI model rate limit, DB connection pool, external API).
|
|
48
|
+
*/
|
|
49
|
+
maxConcurrent?: number;
|
|
42
50
|
}
|
|
43
51
|
interface JobMeta {
|
|
44
52
|
jobId: string;
|
|
@@ -59,6 +67,27 @@ interface JobDispatchOptions {
|
|
|
59
67
|
/** One-shot repeat override at dispatch time. Usually prefer `JobDefinition.repeat`. */
|
|
60
68
|
repeat?: JobRepeatOptions;
|
|
61
69
|
}
|
|
70
|
+
/** Point-in-time snapshot of a single job. */
|
|
71
|
+
interface JobStatus {
|
|
72
|
+
id: string;
|
|
73
|
+
/** Registered job name (= queue name). */
|
|
74
|
+
name: string;
|
|
75
|
+
state: "waiting" | "active" | "completed" | "failed" | "delayed" | "unknown";
|
|
76
|
+
/** Progress reported by the handler via BullMQ `job.updateProgress()`. */
|
|
77
|
+
progress: number | Record<string, unknown>;
|
|
78
|
+
/** True when the job is dequeued by a worker but blocked on a `maxConcurrent` semaphore slot. */
|
|
79
|
+
throttled?: boolean;
|
|
80
|
+
/** Unix ms when the job was enqueued. */
|
|
81
|
+
timestamp?: number;
|
|
82
|
+
/** Unix ms when processing started. */
|
|
83
|
+
processedOn?: number;
|
|
84
|
+
/** Unix ms when the job finished (completed or failed). */
|
|
85
|
+
finishedOn?: number;
|
|
86
|
+
/** Failure message when `state === 'failed'`. */
|
|
87
|
+
failedReason?: string;
|
|
88
|
+
/** Handler return value when `state === 'completed'` (only present if removeOnComplete hasn't cleared the job). */
|
|
89
|
+
returnValue?: unknown;
|
|
90
|
+
}
|
|
62
91
|
interface JobsPluginOptions {
|
|
63
92
|
/** Redis connection options (passed to BullMQ) */
|
|
64
93
|
connection: {
|
|
@@ -91,6 +120,12 @@ interface JobDispatcher {
|
|
|
91
120
|
}>;
|
|
92
121
|
getQueue(name: string): unknown | null;
|
|
93
122
|
getStats(): Promise<Record<string, QueueStats>>;
|
|
123
|
+
/**
|
|
124
|
+
* Fetch a point-in-time status snapshot for a job by its ID.
|
|
125
|
+
* Searches across all registered queues.
|
|
126
|
+
* Returns `null` if the job doesn't exist or has been removed by retention policy.
|
|
127
|
+
*/
|
|
128
|
+
getStatus(jobId: string): Promise<JobStatus | null>;
|
|
94
129
|
close(): Promise<void>;
|
|
95
130
|
}
|
|
96
131
|
interface QueueStats {
|
|
@@ -122,4 +157,4 @@ declare function defineJob<TData = unknown, TResult = unknown>(definition: JobDe
|
|
|
122
157
|
*/
|
|
123
158
|
declare const jobsPlugin: FastifyPluginAsync<JobsPluginOptions>;
|
|
124
159
|
//#endregion
|
|
125
|
-
export { JobDefinition, JobDispatchOptions, JobDispatcher, JobMeta, JobRepeatOptions, JobsPluginOptions, QueueStats, defineJob, jobsPlugin };
|
|
160
|
+
export { JobDefinition, JobDispatchOptions, JobDispatcher, JobMeta, JobRepeatOptions, JobStatus, JobsPluginOptions, QueueStats, defineJob, jobsPlugin };
|
|
@@ -16,6 +16,30 @@ import fp from "fastify-plugin";
|
|
|
16
16
|
function defineJob(definition) {
|
|
17
17
|
return definition;
|
|
18
18
|
}
|
|
19
|
+
var Semaphore = class {
|
|
20
|
+
available;
|
|
21
|
+
waiters = [];
|
|
22
|
+
constructor(permits) {
|
|
23
|
+
this.available = permits;
|
|
24
|
+
}
|
|
25
|
+
get isFull() {
|
|
26
|
+
return this.available === 0;
|
|
27
|
+
}
|
|
28
|
+
acquire() {
|
|
29
|
+
if (this.available > 0) {
|
|
30
|
+
this.available--;
|
|
31
|
+
return Promise.resolve();
|
|
32
|
+
}
|
|
33
|
+
return new Promise((resolve) => {
|
|
34
|
+
this.waiters.push(resolve);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
release() {
|
|
38
|
+
const next = this.waiters.shift();
|
|
39
|
+
if (next) next();
|
|
40
|
+
else this.available++;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
19
43
|
const jobsPluginImpl = async (fastify, options) => {
|
|
20
44
|
const { connection, jobs, prefix = "/jobs", bridgeEvents = true, defaults = {} } = options;
|
|
21
45
|
let Queue;
|
|
@@ -33,6 +57,7 @@ const jobsPluginImpl = async (fastify, options) => {
|
|
|
33
57
|
const queues = /* @__PURE__ */ new Map();
|
|
34
58
|
const dlqQueues = /* @__PURE__ */ new Map();
|
|
35
59
|
const workers = /* @__PURE__ */ new Map();
|
|
60
|
+
const throttledJobs = /* @__PURE__ */ new Map();
|
|
36
61
|
for (const job of jobs) {
|
|
37
62
|
if (!job.repeat) continue;
|
|
38
63
|
const { pattern, every, tz } = job.repeat;
|
|
@@ -66,24 +91,39 @@ const jobsPluginImpl = async (fastify, options) => {
|
|
|
66
91
|
dlqQueues.set(dlqName, dlqQueue);
|
|
67
92
|
}
|
|
68
93
|
const jobTimeout = job.timeout ?? defaults.timeout;
|
|
94
|
+
const semaphore = job.maxConcurrent != null ? new Semaphore(job.maxConcurrent) : null;
|
|
95
|
+
const throttledSet = /* @__PURE__ */ new Set();
|
|
96
|
+
throttledJobs.set(queueName, throttledSet);
|
|
69
97
|
const worker = new Worker(queueName, async (bullJob) => {
|
|
70
98
|
const meta = {
|
|
71
99
|
jobId: bullJob.id ?? "",
|
|
72
100
|
attemptsMade: bullJob.attemptsMade ?? 0,
|
|
73
101
|
timestamp: Date.now()
|
|
74
102
|
};
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
let timer;
|
|
78
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
79
|
-
timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`Job '${queueName}' timed out after ${jobTimeout}ms`)), jobTimeout);
|
|
80
|
-
});
|
|
103
|
+
if (semaphore) {
|
|
104
|
+
if (semaphore.isFull) throttledSet.add(bullJob.id ?? "");
|
|
81
105
|
try {
|
|
82
|
-
|
|
106
|
+
await semaphore.acquire();
|
|
83
107
|
} finally {
|
|
84
|
-
|
|
108
|
+
throttledSet.delete(bullJob.id ?? "");
|
|
85
109
|
}
|
|
86
|
-
}
|
|
110
|
+
}
|
|
111
|
+
let result;
|
|
112
|
+
try {
|
|
113
|
+
if (jobTimeout) {
|
|
114
|
+
let timer;
|
|
115
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
116
|
+
timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`Job '${queueName}' timed out after ${jobTimeout}ms`)), jobTimeout);
|
|
117
|
+
});
|
|
118
|
+
try {
|
|
119
|
+
result = await Promise.race([job.handler(bullJob.data, meta), timeoutPromise]);
|
|
120
|
+
} finally {
|
|
121
|
+
clearTimeout(timer);
|
|
122
|
+
}
|
|
123
|
+
} else result = await job.handler(bullJob.data, meta);
|
|
124
|
+
} finally {
|
|
125
|
+
semaphore?.release();
|
|
126
|
+
}
|
|
87
127
|
if (bridgeEvents && fastify.events?.publish) try {
|
|
88
128
|
await fastify.events.publish(`job.${queueName}.completed`, {
|
|
89
129
|
jobId: bullJob.id,
|
|
@@ -197,6 +237,27 @@ const jobsPluginImpl = async (fastify, options) => {
|
|
|
197
237
|
}
|
|
198
238
|
return stats;
|
|
199
239
|
},
|
|
240
|
+
async getStatus(jobId) {
|
|
241
|
+
for (const [name, queue] of queues) {
|
|
242
|
+
const job = await queue.getJob(jobId);
|
|
243
|
+
if (!job) continue;
|
|
244
|
+
const state = typeof job.getState === "function" ? await job.getState() : "unknown";
|
|
245
|
+
const throttled = throttledJobs.get(name)?.has(jobId) || void 0;
|
|
246
|
+
return {
|
|
247
|
+
id: jobId,
|
|
248
|
+
name,
|
|
249
|
+
state,
|
|
250
|
+
progress: job.progress ?? 0,
|
|
251
|
+
throttled: throttled === true ? true : void 0,
|
|
252
|
+
timestamp: job.timestamp ?? void 0,
|
|
253
|
+
processedOn: job.processedOn ?? void 0,
|
|
254
|
+
finishedOn: job.finishedOn ?? void 0,
|
|
255
|
+
failedReason: job.failedReason ?? void 0,
|
|
256
|
+
returnValue: job.returnvalue ?? void 0
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
return null;
|
|
260
|
+
},
|
|
200
261
|
async close() {
|
|
201
262
|
await Promise.all(Array.from(workers.values()).map((w) => w.pause().catch(() => {})));
|
|
202
263
|
const closePromises = [];
|
|
@@ -213,6 +274,18 @@ const jobsPluginImpl = async (fastify, options) => {
|
|
|
213
274
|
data: await dispatcher.getStats()
|
|
214
275
|
};
|
|
215
276
|
});
|
|
277
|
+
fastify.get(`${prefix}/:id/status`, async (req, reply) => {
|
|
278
|
+
const { id } = req.params;
|
|
279
|
+
const status = await dispatcher.getStatus(id);
|
|
280
|
+
if (!status) return reply.status(404).send({
|
|
281
|
+
success: false,
|
|
282
|
+
error: "Job not found"
|
|
283
|
+
});
|
|
284
|
+
return {
|
|
285
|
+
success: true,
|
|
286
|
+
data: status
|
|
287
|
+
};
|
|
288
|
+
});
|
|
216
289
|
fastify.addHook("onClose", async () => {
|
|
217
290
|
await dispatcher.close();
|
|
218
291
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { V as ResourceDefinition, bt as IRequestContext, yt as IControllerResponse } from "../../index-
|
|
2
|
-
import { i as RequestScope } from "../../types-
|
|
3
|
-
import { c as PermissionCheck } from "../../fields-
|
|
4
|
-
import { a as CrudDescriptionOverride, c as McpAuthResult, d as PromptDefinition, f as PromptResult, g as ToolDefinition, h as ToolContext, i as CrudDescriptionMeta, l as McpPluginOptions, m as ToolAnnotations, n as CallToolResult, o as CrudOperation, p as SessionEntry, r as CreateMcpServerConfig, s as McpAuthResolver, t as BetterAuthHandler, u as McpResourceConfig } from "../../types-
|
|
1
|
+
import { V as ResourceDefinition, bt as IRequestContext, yt as IControllerResponse } from "../../index-C1JSBan-.mjs";
|
|
2
|
+
import { i as RequestScope } from "../../types-CfapW2t8.mjs";
|
|
3
|
+
import { c as PermissionCheck } from "../../fields-DqZLp5d6.mjs";
|
|
4
|
+
import { a as CrudDescriptionOverride, c as McpAuthResult, d as PromptDefinition, f as PromptResult, g as ToolDefinition, h as ToolContext, i as CrudDescriptionMeta, l as McpPluginOptions, m as ToolAnnotations, n as CallToolResult, o as CrudOperation, p as SessionEntry, r as CreateMcpServerConfig, s as McpAuthResolver, t as BetterAuthHandler, u as McpResourceConfig } from "../../types-CoMjNmkF.mjs";
|
|
5
5
|
import { ErrorContract } from "@classytic/repo-core/errors";
|
|
6
6
|
import { FastifyPluginAsync } from "fastify";
|
|
7
7
|
import { z } from "zod";
|
|
@@ -54,6 +54,47 @@ interface BuildMcpToolsFromBridgesOptions {
|
|
|
54
54
|
*/
|
|
55
55
|
declare function buildMcpToolsFromBridges(bridges: readonly McpBridge[], options?: BuildMcpToolsFromBridgesOptions): ToolDefinition[];
|
|
56
56
|
//#endregion
|
|
57
|
+
//#region src/integrations/mcp/betterAuthApiKey.d.ts
|
|
58
|
+
/** Minimal subset of Better Auth's API-key record the helper needs. */
|
|
59
|
+
interface BetterAuthKeyRecord {
|
|
60
|
+
id?: string;
|
|
61
|
+
userId?: string;
|
|
62
|
+
referenceId?: string;
|
|
63
|
+
metadata?: unknown;
|
|
64
|
+
permissions?: unknown;
|
|
65
|
+
scopes?: unknown;
|
|
66
|
+
expiresAt?: string | Date | null;
|
|
67
|
+
disabled?: boolean;
|
|
68
|
+
name?: string | null;
|
|
69
|
+
}
|
|
70
|
+
interface CreateMcpAuthFromBetterAuthApiKeyOptions {
|
|
71
|
+
/**
|
|
72
|
+
* How to resolve the organization id from a verified key.
|
|
73
|
+
*
|
|
74
|
+
* - `true` (default) — read `key.metadata.organizationId`.
|
|
75
|
+
* - `false` — never set `organizationId` (single-tenant deployments).
|
|
76
|
+
* - `(metadata, key) => string | undefined` — custom extractor, e.g.
|
|
77
|
+
* `(m) => m?.tenantId` for hosts that named the column differently.
|
|
78
|
+
*/
|
|
79
|
+
orgFromMetadata?: boolean | ((metadata: unknown, key: BetterAuthKeyRecord) => string | undefined);
|
|
80
|
+
/**
|
|
81
|
+
* Field name on `metadata` that holds the org id. Default
|
|
82
|
+
* `"organizationId"`. Ignored when `orgFromMetadata` is a function.
|
|
83
|
+
*/
|
|
84
|
+
metadataOrgField?: string;
|
|
85
|
+
/**
|
|
86
|
+
* Permission scope name inside `key.permissions` that should be flattened
|
|
87
|
+
* onto `McpAuthResult.scopes`. Default `"mcp"` — matches Better Auth's
|
|
88
|
+
* docs example. Set to `false` to keep scopes empty.
|
|
89
|
+
*/
|
|
90
|
+
permissionScope?: string | false;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Build an {@link McpAuthResolver} that authenticates MCP requests against
|
|
94
|
+
* Better Auth's API-key plugin. See module docstring for the rationale.
|
|
95
|
+
*/
|
|
96
|
+
declare function createMcpAuthFromBetterAuthApiKey(auth: BetterAuthHandler, options?: CreateMcpAuthFromBetterAuthApiKeyOptions): McpAuthResolver;
|
|
97
|
+
//#endregion
|
|
57
98
|
//#region src/integrations/mcp/createMcpServer.d.ts
|
|
58
99
|
/**
|
|
59
100
|
* Mutable auth ref — updated per-request by mcpPlugin.
|
|
@@ -69,6 +110,27 @@ interface AuthRef {
|
|
|
69
110
|
* @returns McpServer instance (not yet connected to a transport)
|
|
70
111
|
*/
|
|
71
112
|
declare function createMcpServer(config: CreateMcpServerConfig, authRef?: AuthRef): Promise<McpServerInstance>;
|
|
113
|
+
/**
|
|
114
|
+
* Detect and resolve duplicate MCP tool names across the assembled tool list.
|
|
115
|
+
*
|
|
116
|
+
* Two outcomes:
|
|
117
|
+
*
|
|
118
|
+
* 1. **Auto-namespace** — when one tool's `source` carries a `preset:<name>`
|
|
119
|
+
* prefix and the other doesn't, the preset tool is renamed to
|
|
120
|
+
* `<presetName>_<originalName>` (e.g. softDelete's `restore_post`
|
|
121
|
+
* becomes `softdelete_restore_post` when the user defines
|
|
122
|
+
* `actions.restore`). The user's tool wins the canonical name because
|
|
123
|
+
* it's the more-specific intent.
|
|
124
|
+
*
|
|
125
|
+
* 2. **Typed throw** — every other collision (two user actions, two
|
|
126
|
+
* custom routes, two presets) raises `ArcError('arc.mcp.tool_name_collision')`
|
|
127
|
+
* naming both sources so the host's stack trace points at the
|
|
128
|
+
* resource definitions, not the MCP SDK internals.
|
|
129
|
+
*
|
|
130
|
+
* Exported for the testing harness so collision behaviour can be exercised
|
|
131
|
+
* without a full server boot.
|
|
132
|
+
*/
|
|
133
|
+
declare function resolveToolCollisions(tools: readonly ToolDefinition[]): ToolDefinition[];
|
|
72
134
|
/** Minimal interface for McpServer — avoids hard SDK type dependency */
|
|
73
135
|
interface McpServerInstance {
|
|
74
136
|
connect: (transport: unknown) => Promise<void>;
|
|
@@ -516,4 +578,4 @@ declare function permissionDeniedResult(args: {
|
|
|
516
578
|
session: McpAuthResult | null;
|
|
517
579
|
}): CallToolResult;
|
|
518
580
|
//#endregion
|
|
519
|
-
export { type AuthRef, type BetterAuthHandler, type BuildMcpToolsFromBridgesOptions, type CallToolResult, type CreateMcpServerConfig, type CrudDescriptionMeta, type CrudDescriptionOverride, type CrudOperation, type DefinePromptConfig, type DefineToolConfig, type FieldRuleEntry, type FieldRulesToZodOptions, type InvokeControllerOptions, type McpAuthResolver, type McpAuthResult, type McpBridge, type McpGuard, type McpOperation, type McpPluginOptions, type McpResourceConfig, type McpServerInstance, type PromptDefinition, type PromptResult, type ResourceToToolsConfig, type ToolAnnotations, type ToolContext, type ToolDefinition, bridgeToMcp, buildMcpToolsFromBridges, buildRequestContext, createMcpServer, customGuard, defaultCrudDescription, definePrompt, defineTool, denied, fieldRulesToZod, filterResourcesForMcp, getOrgId, getUserId, guard, hasOrg, invokeController, isAuthenticated, isOrg, mcpHandlerAdapter, mcpPlugin, permissionDeniedResult, requireAuth, requireOrg, requireOrgId, requireRole, resolveCrudDescription, resourceToTools, toCallToolError, toCallToolResult, toCallToolSuccess };
|
|
581
|
+
export { type AuthRef, type BetterAuthHandler, type BuildMcpToolsFromBridgesOptions, type CallToolResult, type CreateMcpAuthFromBetterAuthApiKeyOptions, type CreateMcpServerConfig, type CrudDescriptionMeta, type CrudDescriptionOverride, type CrudOperation, type DefinePromptConfig, type DefineToolConfig, type FieldRuleEntry, type FieldRulesToZodOptions, type InvokeControllerOptions, type McpAuthResolver, type McpAuthResult, type McpBridge, type McpGuard, type McpOperation, type McpPluginOptions, type McpResourceConfig, type McpServerInstance, type PromptDefinition, type PromptResult, type ResourceToToolsConfig, type ToolAnnotations, type ToolContext, type ToolDefinition, bridgeToMcp, buildMcpToolsFromBridges, buildRequestContext, createMcpAuthFromBetterAuthApiKey, createMcpServer, customGuard, defaultCrudDescription, definePrompt, defineTool, denied, fieldRulesToZod, filterResourcesForMcp, getOrgId, getUserId, guard, hasOrg, invokeController, isAuthenticated, isOrg, mcpHandlerAdapter, mcpPlugin, permissionDeniedResult, requireAuth, requireOrg, requireOrgId, requireRole, resolveCrudDescription, resolveToolCollisions, resourceToTools, toCallToolError, toCallToolResult, toCallToolSuccess };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as defaultCrudDescription, c as mcpHandlerAdapter, d as toCallToolResult, f as toCallToolSuccess, i as fieldRulesToZod, l as permissionDeniedResult, m as createMcpServer, n as mcpPlugin, o as resolveCrudDescription, p as buildRequestContext, r as resourceToTools, s as invokeController, t as filterResourcesForMcp, u as toCallToolError } from "../../mcpPlugin-
|
|
1
|
+
import { a as defaultCrudDescription, c as mcpHandlerAdapter, d as toCallToolResult, f as toCallToolSuccess, h as resolveToolCollisions, i as fieldRulesToZod, l as permissionDeniedResult, m as createMcpServer, n as mcpPlugin, o as resolveCrudDescription, p as buildRequestContext, r as resourceToTools, s as invokeController, t as filterResourcesForMcp, u as toCallToolError } from "../../mcpPlugin-hZE00H74.mjs";
|
|
2
2
|
//#region src/integrations/mcp/defineTool.ts
|
|
3
3
|
/**
|
|
4
4
|
* Define a type-safe MCP tool.
|
|
@@ -167,6 +167,97 @@ function buildMcpToolsFromBridges(bridges, options = {}) {
|
|
|
167
167
|
}).map(bridgeToMcp);
|
|
168
168
|
}
|
|
169
169
|
//#endregion
|
|
170
|
+
//#region src/integrations/mcp/betterAuthApiKey.ts
|
|
171
|
+
/**
|
|
172
|
+
* Build an {@link McpAuthResolver} that authenticates MCP requests against
|
|
173
|
+
* Better Auth's API-key plugin. See module docstring for the rationale.
|
|
174
|
+
*/
|
|
175
|
+
function createMcpAuthFromBetterAuthApiKey(auth, options = {}) {
|
|
176
|
+
const verifyApiKey = auth.api?.verifyApiKey;
|
|
177
|
+
if (typeof verifyApiKey !== "function") throw new Error("createMcpAuthFromBetterAuthApiKey: the provided Better Auth instance does not expose `api.verifyApiKey`. Register the `apiKey()` plugin on your auth instance (see https://www.better-auth.com/docs/plugins/api-key) and pass the same `auth` object you pass to your REST routes.");
|
|
178
|
+
const orgFromMetadata = options.orgFromMetadata ?? true;
|
|
179
|
+
const metadataOrgField = options.metadataOrgField ?? "organizationId";
|
|
180
|
+
const permissionScope = options.permissionScope === void 0 ? "mcp" : options.permissionScope;
|
|
181
|
+
return async (headers) => {
|
|
182
|
+
const apiKey = extractApiKey(headers);
|
|
183
|
+
if (!apiKey) return null;
|
|
184
|
+
let result;
|
|
185
|
+
try {
|
|
186
|
+
result = await verifyApiKey({
|
|
187
|
+
body: { key: apiKey },
|
|
188
|
+
headers
|
|
189
|
+
});
|
|
190
|
+
} catch {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
if (!result?.valid || !result.key) return null;
|
|
194
|
+
const key = result.key;
|
|
195
|
+
if (key.disabled === true) return null;
|
|
196
|
+
if (key.expiresAt) {
|
|
197
|
+
const exp = key.expiresAt instanceof Date ? key.expiresAt : new Date(key.expiresAt);
|
|
198
|
+
if (Number.isFinite(exp.getTime()) && exp.getTime() < Date.now()) return null;
|
|
199
|
+
}
|
|
200
|
+
const userId = key.userId ?? key.referenceId;
|
|
201
|
+
const organizationId = resolveOrg(key, orgFromMetadata, metadataOrgField);
|
|
202
|
+
const scopes = resolveScopes(key, permissionScope);
|
|
203
|
+
const clientId = !userId && key.id ? key.id : void 0;
|
|
204
|
+
if (!userId && !clientId) return null;
|
|
205
|
+
return {
|
|
206
|
+
...userId ? { userId } : {},
|
|
207
|
+
...organizationId ? { organizationId } : {},
|
|
208
|
+
...clientId ? { clientId } : {},
|
|
209
|
+
...scopes && scopes.length ? { scopes } : {}
|
|
210
|
+
};
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
function extractApiKey(headers) {
|
|
214
|
+
const authz = headers.authorization ?? headers.Authorization;
|
|
215
|
+
if (typeof authz === "string" && authz.length > 0) {
|
|
216
|
+
const token = /^Bearer\s+(.+)$/i.exec(authz)?.[1];
|
|
217
|
+
if (token) return token.trim();
|
|
218
|
+
}
|
|
219
|
+
const direct = headers["x-api-key"] ?? headers["X-Api-Key"];
|
|
220
|
+
if (typeof direct === "string" && direct.length > 0) return direct.trim();
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
function resolveOrg(key, setting, field) {
|
|
224
|
+
if (setting === false) return void 0;
|
|
225
|
+
const metadata = parseMetadata(key.metadata);
|
|
226
|
+
if (typeof setting === "function") {
|
|
227
|
+
const value = setting(metadata, key);
|
|
228
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
229
|
+
}
|
|
230
|
+
if (metadata && typeof metadata === "object") {
|
|
231
|
+
const raw = metadata[field];
|
|
232
|
+
if (typeof raw === "string" && raw.length > 0) return raw;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function resolveScopes(key, scopeKey) {
|
|
236
|
+
if (scopeKey === false) return void 0;
|
|
237
|
+
const permissions = parseMetadata(key.permissions);
|
|
238
|
+
if (permissions && typeof permissions === "object") {
|
|
239
|
+
const bucket = permissions[scopeKey];
|
|
240
|
+
if (Array.isArray(bucket) && bucket.every((s) => typeof s === "string")) return bucket;
|
|
241
|
+
}
|
|
242
|
+
if (Array.isArray(key.scopes) && key.scopes.every((s) => typeof s === "string")) return key.scopes;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Better Auth stores `metadata` and `permissions` as JSON-stringified
|
|
246
|
+
* columns in SQL kits. Accept both shapes — already-parsed objects pass
|
|
247
|
+
* through, strings round-trip through `JSON.parse`. Failures yield
|
|
248
|
+
* `undefined` rather than throwing because malformed metadata should
|
|
249
|
+
* degrade gracefully (key still authenticates; org scope is absent).
|
|
250
|
+
*/
|
|
251
|
+
function parseMetadata(value) {
|
|
252
|
+
if (value === null || value === void 0) return void 0;
|
|
253
|
+
if (typeof value === "string") try {
|
|
254
|
+
return JSON.parse(value);
|
|
255
|
+
} catch {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
return value;
|
|
259
|
+
}
|
|
260
|
+
//#endregion
|
|
170
261
|
//#region src/integrations/mcp/definePrompt.ts
|
|
171
262
|
/**
|
|
172
263
|
* Define a type-safe MCP prompt.
|
|
@@ -184,4 +275,4 @@ function definePrompt(name, config) {
|
|
|
184
275
|
};
|
|
185
276
|
}
|
|
186
277
|
//#endregion
|
|
187
|
-
export { bridgeToMcp, buildMcpToolsFromBridges, buildRequestContext, createMcpServer, customGuard, defaultCrudDescription, definePrompt, defineTool, denied, fieldRulesToZod, filterResourcesForMcp, getOrgId, getUserId, guard, hasOrg, invokeController, isAuthenticated, isOrg, mcpHandlerAdapter, mcpPlugin, permissionDeniedResult, requireAuth, requireOrg, requireOrgId, requireRole, resolveCrudDescription, resourceToTools, toCallToolError, toCallToolResult, toCallToolSuccess };
|
|
278
|
+
export { bridgeToMcp, buildMcpToolsFromBridges, buildRequestContext, createMcpAuthFromBetterAuthApiKey, createMcpServer, customGuard, defaultCrudDescription, definePrompt, defineTool, denied, fieldRulesToZod, filterResourcesForMcp, getOrgId, getUserId, guard, hasOrg, invokeController, isAuthenticated, isOrg, mcpHandlerAdapter, mcpPlugin, permissionDeniedResult, requireAuth, requireOrg, requireOrgId, requireRole, resolveCrudDescription, resolveToolCollisions, resourceToTools, toCallToolError, toCallToolResult, toCallToolSuccess };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { m as createMcpServer, r as resourceToTools, t as filterResourcesForMcp } from "../../mcpPlugin-
|
|
1
|
+
import { m as createMcpServer, r as resourceToTools, t as filterResourcesForMcp } from "../../mcpPlugin-hZE00H74.mjs";
|
|
2
2
|
//#region src/integrations/mcp/testing.ts
|
|
3
3
|
/**
|
|
4
4
|
* @classytic/arc/mcp/testing — MCP Test Utilities
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as WebSocketMessage, c as WebSocketAdapter, i as WebSocketClient, n as websocketPlugin, o as WebSocketPluginOptions, r as AuthResult, s as LocalWebSocketAdapter, t as RoomManager } from "../websocket-
|
|
1
|
+
import { a as WebSocketMessage, c as WebSocketAdapter, i as WebSocketClient, n as websocketPlugin, o as WebSocketPluginOptions, r as AuthResult, s as LocalWebSocketAdapter, t as RoomManager } from "../websocket-BJ0w-3Dt.mjs";
|
|
2
2
|
export { AuthResult, LocalWebSocketAdapter, RoomManager, WebSocketAdapter, WebSocketClient, WebSocketMessage, WebSocketPluginOptions, websocketPlugin };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import sjson from "secure-json-parse";
|
|
1
2
|
import fp from "fastify-plugin";
|
|
2
3
|
//#region src/integrations/websocket/adapter.ts
|
|
3
4
|
/**
|
|
@@ -140,7 +141,7 @@ async function handleConnection(ctx, socket, request) {
|
|
|
140
141
|
}
|
|
141
142
|
let msg;
|
|
142
143
|
try {
|
|
143
|
-
msg =
|
|
144
|
+
msg = sjson.parse(typeof raw === "string" ? raw : raw.toString());
|
|
144
145
|
} catch {
|
|
145
146
|
socket.send(JSON.stringify({
|
|
146
147
|
type: "error",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as ResourceRegistry } from "./ResourceRegistry-
|
|
1
|
+
import { t as ResourceRegistry } from "./ResourceRegistry-CzSzVK1m.mjs";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { pathToFileURL } from "node:url";
|
|
4
4
|
//#region src/cli/utils/loadResourcesFromEntry.ts
|