@classytic/arc 2.4.1 → 2.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/auth/index.mjs +1 -1
  2. package/dist/cache/index.mjs +2 -2
  3. package/dist/cli/commands/describe.d.mts +1 -1
  4. package/dist/cli/commands/describe.mjs +1 -1
  5. package/dist/cli/commands/generate.d.mts +1 -1
  6. package/dist/cli/commands/generate.mjs +1 -1
  7. package/dist/cli/commands/init.d.mts +1 -1
  8. package/dist/cli/commands/init.mjs +1 -1
  9. package/dist/cli/commands/introspect.d.mts +1 -1
  10. package/dist/cli/commands/introspect.mjs +1 -1
  11. package/dist/cli/index.d.mts +4 -4
  12. package/dist/cli/index.mjs +4 -4
  13. package/dist/core/index.mjs +1 -1
  14. package/dist/{createApp-ByWNRsZj.mjs → createApp-CBgVaFyh.mjs} +3 -3
  15. package/dist/{defineResource-D9aY5Cy6.mjs → defineResource-B22gcNvn.mjs} +1 -1
  16. package/dist/discovery/index.d.mts +1 -1
  17. package/dist/discovery/index.mjs +1 -1
  18. package/dist/dynamic/index.mjs +2 -2
  19. package/dist/{errorHandler--zp54tGc.mjs → errorHandler-DMbGdzBG.mjs} +4 -4
  20. package/dist/events/transports/redis.d.mts +1 -1
  21. package/dist/events/transports/redis.mjs +1 -1
  22. package/dist/factory/index.mjs +1 -1
  23. package/dist/index.mjs +3 -3
  24. package/dist/integrations/event-gateway.d.mts +1 -1
  25. package/dist/integrations/event-gateway.mjs +1 -1
  26. package/dist/integrations/jobs.d.mts +1 -1
  27. package/dist/integrations/jobs.mjs +1 -1
  28. package/dist/integrations/mcp/index.mjs +1 -1
  29. package/dist/integrations/mcp/testing.mjs +1 -1
  30. package/dist/integrations/streamline.d.mts +1 -1
  31. package/dist/integrations/streamline.mjs +1 -1
  32. package/dist/integrations/websocket-redis.d.mts +1 -1
  33. package/dist/integrations/websocket-redis.mjs +1 -1
  34. package/dist/integrations/websocket.d.mts +1 -1
  35. package/dist/integrations/websocket.mjs +1 -1
  36. package/dist/{memory-Cb_7iy9e.mjs → memory-BFAYkf8H.mjs} +1 -4
  37. package/dist/migrations/index.d.mts +113 -44
  38. package/dist/migrations/index.mjs +85 -99
  39. package/dist/permissions/index.mjs +1 -1
  40. package/dist/{permissions-CA5zg0yK.mjs → permissions-Jk5x3sxz.mjs} +1 -1
  41. package/dist/plugins/index.mjs +1 -1
  42. package/dist/plugins/response-cache.d.mts +1 -1
  43. package/dist/plugins/response-cache.mjs +1 -1
  44. package/dist/plugins/tracing-entry.mjs +1 -1
  45. package/dist/presets/index.d.mts +1 -1
  46. package/dist/presets/index.mjs +2 -2
  47. package/dist/presets/multiTenant.d.mts +1 -1
  48. package/dist/presets/multiTenant.mjs +1 -1
  49. package/dist/{presets-C9QXJV1u.mjs → presets-OMPaHMTY.mjs} +2 -2
  50. package/dist/{queryCachePlugin-ClosZdNS.mjs → queryCachePlugin-XtFplYO9.mjs} +1 -1
  51. package/dist/{resourceToTools-B6ZN9Ing.mjs → resourceToTools-PMFE8HIv.mjs} +50 -6
  52. package/dist/testing/index.mjs +1 -1
  53. package/package.json +4 -3
  54. package/skills/arc/SKILL.md +25 -6
  55. package/skills/arc/references/mcp.md +47 -2
@@ -1,6 +1,6 @@
1
1
  import { n as normalizeRoles, t as getUserRoles } from "../types-ZUu_h0jp.mjs";
2
2
  import { t as ArcError } from "../errors-rxhfP7Hf.mjs";
3
- import { c as requireOrgMembership, f as requireTeamMembership, l as requireOrgRole } from "../permissions-CA5zg0yK.mjs";
3
+ import { c as requireOrgMembership, f as requireTeamMembership, l as requireOrgRole } from "../permissions-Jk5x3sxz.mjs";
4
4
  import { n as extractBetterAuthOpenApi } from "../betterAuthOpenApi-lz0IRbXJ.mjs";
5
5
  import { createHmac, randomUUID, timingSafeEqual } from "node:crypto";
6
6
  import fp from "fastify-plugin";
@@ -1,6 +1,6 @@
1
1
  import { i as versionKey, n as hashParams, r as tagVersionKey, t as buildQueryKey } from "../keys-qcD-TVJl.mjs";
2
- import { t as MemoryCacheStore } from "../memory-Cb_7iy9e.mjs";
3
- import { r as QueryCache, t as queryCachePlugin } from "../queryCachePlugin-ClosZdNS.mjs";
2
+ import { t as MemoryCacheStore } from "../memory-BFAYkf8H.mjs";
3
+ import { r as QueryCache, t as queryCachePlugin } from "../queryCachePlugin-XtFplYO9.mjs";
4
4
  //#region src/cache/redis.ts
5
5
  /**
6
6
  * Redis-backed cache store.
@@ -15,4 +15,4 @@
15
15
  */
16
16
  declare function describe(args: string[]): Promise<void>;
17
17
  //#endregion
18
- export { describe as default, describe };
18
+ export { describe };
@@ -252,4 +252,4 @@ async function describe(args) {
252
252
  }
253
253
  }
254
254
  //#endregion
255
- export { describe as default, describe };
255
+ export { describe };
@@ -19,4 +19,4 @@
19
19
  */
20
20
  declare function generate(type: string | undefined, args: string[]): Promise<void>;
21
21
  //#endregion
22
- export { generate as default, generate };
22
+ export { generate };
@@ -435,4 +435,4 @@ async function generateFile(name, lowerName, resourcePath, fileType, template, e
435
435
  console.log(` + Created: ${filename}`);
436
436
  }
437
437
  //#endregion
438
- export { generate as default, generate };
438
+ export { generate };
@@ -24,4 +24,4 @@ interface InitOptions {
24
24
  */
25
25
  declare function init(options?: InitOptions): Promise<void>;
26
26
  //#endregion
27
- export { InitOptions, init as default, init };
27
+ export { InitOptions, init };
@@ -2889,4 +2889,4 @@ ${dbConfig}
2889
2889
  `;
2890
2890
  }
2891
2891
  //#endregion
2892
- export { init as default, init };
2892
+ export { init };
@@ -7,4 +7,4 @@
7
7
  */
8
8
  declare function introspect(args: string[]): Promise<void>;
9
9
  //#endregion
10
- export { introspect as default, introspect };
10
+ export { introspect };
@@ -70,4 +70,4 @@ async function introspect(args) {
70
70
  }
71
71
  }
72
72
  //#endregion
73
- export { introspect as default, introspect };
73
+ export { introspect };
@@ -1,7 +1,7 @@
1
- import describe from "./commands/describe.mjs";
1
+ import { describe } from "./commands/describe.mjs";
2
2
  import { exportDocs } from "./commands/docs.mjs";
3
3
  import { doctor } from "./commands/doctor.mjs";
4
- import generate from "./commands/generate.mjs";
5
- import init from "./commands/init.mjs";
6
- import introspect from "./commands/introspect.mjs";
4
+ import { generate } from "./commands/generate.mjs";
5
+ import { init } from "./commands/init.mjs";
6
+ import { introspect } from "./commands/introspect.mjs";
7
7
  export { describe, doctor, exportDocs, generate, init, introspect };
@@ -1,7 +1,7 @@
1
- import describe from "./commands/describe.mjs";
1
+ import { describe } from "./commands/describe.mjs";
2
2
  import { exportDocs } from "./commands/docs.mjs";
3
3
  import { doctor } from "./commands/doctor.mjs";
4
- import generate from "./commands/generate.mjs";
5
- import init from "./commands/init.mjs";
6
- import introspect from "./commands/introspect.mjs";
4
+ import { generate } from "./commands/generate.mjs";
5
+ import { init } from "./commands/init.mjs";
6
+ import { introspect } from "./commands/introspect.mjs";
7
7
  export { describe, doctor, exportDocs, generate, init, introspect };
@@ -1,5 +1,5 @@
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
2
  import { i as AccessControl, n as QueryResolver, r as BodySanitizer, t as BaseController } from "../BaseController-CkM5dUh_.mjs";
3
3
  import { t as createActionRouter } from "../core-C1XCMtqM.mjs";
4
- import { c as createCrudHandlers, d as getControllerContext, f as getControllerScope, l as createFastifyHandler, n as defineResource, o as createCrudRouter, p as sendControllerResponse, s as createPermissionMiddleware, t as ResourceDefinition, u as createRequestContext } from "../defineResource-D9aY5Cy6.mjs";
4
+ import { c as createCrudHandlers, d as getControllerContext, f as getControllerScope, l as createFastifyHandler, n as defineResource, o as createCrudRouter, p as sendControllerResponse, s as createPermissionMiddleware, t as ResourceDefinition, u as createRequestContext } from "../defineResource-B22gcNvn.mjs";
5
5
  export { AccessControl, BaseController, BodySanitizer, CRUD_OPERATIONS, DEFAULT_ID_FIELD, DEFAULT_LIMIT, DEFAULT_MAX_LIMIT, DEFAULT_SORT, DEFAULT_TENANT_FIELD, DEFAULT_UPDATE_METHOD, HOOK_OPERATIONS, HOOK_PHASES, MAX_FILTER_DEPTH, MAX_REGEX_LENGTH, MAX_SEARCH_LENGTH, MUTATION_OPERATIONS, QueryResolver, RESERVED_QUERY_PARAMS, ResourceDefinition, SYSTEM_FIELDS, createActionRouter, createCrudHandlers, createCrudRouter, createFastifyHandler, createPermissionMiddleware, createRequestContext, defineResource, getControllerContext, getControllerScope, sendControllerResponse };
@@ -458,9 +458,9 @@ async function createApp(options) {
458
458
  fastify.log.debug("Arc caching plugin enabled");
459
459
  }
460
460
  if (config.arcPlugins?.queryCache) {
461
- const { queryCachePlugin } = await import("./queryCachePlugin-ClosZdNS.mjs").then((n) => n.n);
461
+ const { queryCachePlugin } = await import("./queryCachePlugin-XtFplYO9.mjs").then((n) => n.n);
462
462
  const qcOpts = config.arcPlugins.queryCache === true ? {} : config.arcPlugins.queryCache;
463
- const store = options.stores?.queryCache ?? new (await (import("./memory-Cb_7iy9e.mjs").then((n) => n.n))).MemoryCacheStore();
463
+ const store = options.stores?.queryCache ?? new (await (import("./memory-BFAYkf8H.mjs").then((n) => n.n))).MemoryCacheStore();
464
464
  await fastify.register(queryCachePlugin, {
465
465
  store,
466
466
  ...qcOpts
@@ -557,7 +557,7 @@ async function createApp(options) {
557
557
  fastify.log.debug("Elevation plugin enabled");
558
558
  }
559
559
  if (config.errorHandler !== false) {
560
- const { errorHandlerPlugin } = await import("./errorHandler--zp54tGc.mjs").then((n) => n.n);
560
+ const { errorHandlerPlugin } = await import("./errorHandler-DMbGdzBG.mjs").then((n) => n.n);
561
561
  const errorOpts = typeof config.errorHandler === "object" ? config.errorHandler : { includeStack: config.preset !== "production" };
562
562
  await fastify.register(errorHandlerPlugin, errorOpts);
563
563
  trackPlugin("arc-error-handler", errorOpts);
@@ -8,7 +8,7 @@ import { i as getDefaultCrudSchemas } from "./utils-Dc0WhlIl.mjs";
8
8
  import { r as ForbiddenError } from "./errors-rxhfP7Hf.mjs";
9
9
  import { n as convertRouteSchema, t as convertOpenApiSchemas } from "./schemaConverter-DjzHpFam.mjs";
10
10
  import { t as hasEvents } from "./typeGuards-Cj5Rgvlg.mjs";
11
- import { r as getAvailablePresets, t as applyPresets } from "./presets-C9QXJV1u.mjs";
11
+ import { r as getAvailablePresets, t as applyPresets } from "./presets-OMPaHMTY.mjs";
12
12
  //#region src/pipeline/pipe.ts
13
13
  /**
14
14
  * Compose pipeline steps into an ordered array.
@@ -43,4 +43,4 @@ declare function discoverResources(options: DiscoveryOptions): Promise<Array<{
43
43
  /** Auto-discovery plugin for Arc resources */
44
44
  declare const discoveryPlugin: FastifyPluginAsync<DiscoveryPluginOptions>;
45
45
  //#endregion
46
- export { DiscoverableResource, DiscoveryOptions, DiscoveryPluginOptions, discoveryPlugin as default, discoveryPlugin, discoverResources };
46
+ export { DiscoverableResource, DiscoveryOptions, DiscoveryPluginOptions, discoverResources, discoveryPlugin };
@@ -138,4 +138,4 @@ const discoveryPluginImpl = async (fastify, options) => {
138
138
  /** Auto-discovery plugin for Arc resources */
139
139
  const discoveryPlugin = discoveryPluginImpl;
140
140
  //#endregion
141
- export { discoveryPlugin as default, discoveryPlugin, discoverResources };
141
+ export { discoverResources, discoveryPlugin };
@@ -1,6 +1,6 @@
1
1
  import { t as ArcQueryParser } from "../queryParser-CgCtsjti.mjs";
2
- import { n as defineResource } from "../defineResource-D9aY5Cy6.mjs";
3
- import { _ as ownerWithAdminBypass, b as publicReadAdminWrite, g as fullPublic, h as authenticated, m as adminOnly, x as readOnly, y as publicRead } from "../permissions-CA5zg0yK.mjs";
2
+ import { n as defineResource } from "../defineResource-B22gcNvn.mjs";
3
+ import { _ as ownerWithAdminBypass, b as publicReadAdminWrite, g as fullPublic, h as authenticated, m as adminOnly, x as readOnly, y as publicRead } from "../permissions-Jk5x3sxz.mjs";
4
4
  //#region src/dynamic/ArcDynamicLoader.ts
5
5
  const VALID_FIELD_TYPES = new Set([
6
6
  "string",
@@ -2,10 +2,7 @@ import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
2
2
  import { f as isArcError } from "./errors-rxhfP7Hf.mjs";
3
3
  import fp from "fastify-plugin";
4
4
  //#region src/plugins/errorHandler.ts
5
- var errorHandler_exports = /* @__PURE__ */ __exportAll({
6
- default: () => errorHandlerPlugin,
7
- errorHandlerPlugin: () => errorHandlerPlugin
8
- });
5
+ var errorHandler_exports = /* @__PURE__ */ __exportAll({ errorHandlerPlugin: () => errorHandlerPlugin });
9
6
  async function errorHandlerPluginFn(fastify, options = {}) {
10
7
  const isProduction = process.env.NODE_ENV === "production";
11
8
  const { includeStack = !isProduction, onError, errorMap = {} } = options;
@@ -42,6 +39,9 @@ async function errorHandlerPluginFn(fastify, options = {}) {
42
39
  } else if ("statusCode" in error && typeof error.statusCode === "number") {
43
40
  statusCode = error.statusCode;
44
41
  response.code = statusCodeToCode(statusCode);
42
+ } else if ("status" in error && typeof error.status === "number") {
43
+ statusCode = error.status;
44
+ response.code = statusCodeToCode(statusCode);
45
45
  } else if (error.name && errorMap[error.name]) {
46
46
  const mapping = errorMap[error.name];
47
47
  statusCode = mapping.statusCode;
@@ -73,4 +73,4 @@ declare class RedisEventTransport implements EventTransport {
73
73
  private isGlob;
74
74
  }
75
75
  //#endregion
76
- export { RedisEventTransport, RedisEventTransport as default, RedisEventTransportOptions, RedisLike };
76
+ export { RedisEventTransport, RedisEventTransportOptions, RedisLike };
@@ -120,4 +120,4 @@ var RedisEventTransport = class {
120
120
  }
121
121
  };
122
122
  //#endregion
123
- export { RedisEventTransport, RedisEventTransport as default };
123
+ export { RedisEventTransport };
@@ -1,4 +1,4 @@
1
- import { a as getPreset, i as developmentPreset, n as createApp, o as productionPreset, s as testingPreset, t as ArcFactory } from "../createApp-ByWNRsZj.mjs";
1
+ import { a as getPreset, i as developmentPreset, n as createApp, o as productionPreset, s as testingPreset, t as ArcFactory } from "../createApp-CBgVaFyh.mjs";
2
2
  //#region src/factory/edge.ts
3
3
  /**
4
4
  * Convert a Fastify app into a Web Standards fetch handler.
package/dist/index.mjs CHANGED
@@ -4,8 +4,8 @@ import { t as BaseController } from "./BaseController-CkM5dUh_.mjs";
4
4
  import { n as applyFieldWritePermissions, r as fields, t as applyFieldReadPermissions } from "./fields-ipsbIRPK.mjs";
5
5
  import { t as requestContext } from "./requestContext-DYtmNpm5.mjs";
6
6
  import { i as NotFoundError, l as UnauthorizedError, r as ForbiddenError, t as ArcError, u as ValidationError } from "./errors-rxhfP7Hf.mjs";
7
- import { a as validateResourceConfig, f as getControllerScope, i as formatValidationErrors, m as pipe, n as defineResource, r as assertValidConfig, t as ResourceDefinition } from "./defineResource-D9aY5Cy6.mjs";
8
- import { _ as ownerWithAdminBypass, a as createOrgPermissions, b as publicReadAdminWrite, c as requireOrgMembership, d as requireRoles, f as requireTeamMembership, g as fullPublic, h as authenticated, i as createDynamicPermissionMatrix, l as requireOrgRole, m as adminOnly, n as allowPublic, o as denyAll, p as when, r as anyOf, s as requireAuth, t as allOf, u as requireOwnership, v as presets_exports, x as readOnly, y as publicRead } from "./permissions-CA5zg0yK.mjs";
7
+ import { a as validateResourceConfig, f as getControllerScope, i as formatValidationErrors, m as pipe, n as defineResource, r as assertValidConfig, t as ResourceDefinition } from "./defineResource-B22gcNvn.mjs";
8
+ import { _ as ownerWithAdminBypass, a as createOrgPermissions, b as publicReadAdminWrite, c as requireOrgMembership, d as requireRoles, f as requireTeamMembership, g as fullPublic, h as authenticated, i as createDynamicPermissionMatrix, l as requireOrgRole, m as adminOnly, n as allowPublic, o as denyAll, p as when, r as anyOf, s as requireAuth, t as allOf, u as requireOwnership, v as presets_exports, x as readOnly, y as publicRead } from "./permissions-Jk5x3sxz.mjs";
9
9
  import { n as configureArcLogger, t as arcLog } from "./logger-Dz3j1ItV.mjs";
10
10
  //#region src/middleware/middleware.ts
11
11
  /**
@@ -126,6 +126,6 @@ function transform(name, handlerOrOptions) {
126
126
  }
127
127
  //#endregion
128
128
  //#region src/index.ts
129
- const version = "2.4.1";
129
+ const version = "2.4.3";
130
130
  //#endregion
131
131
  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, createDynamicPermissionMatrix, createMongooseAdapter, createOrgPermissions, createPrismaAdapter, defineResource, denyAll, 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 };
@@ -43,4 +43,4 @@ interface EventGatewayOptions {
43
43
  }
44
44
  declare const eventGatewayPlugin: FastifyPluginAsync<EventGatewayOptions>;
45
45
  //#endregion
46
- export { EventGatewayOptions, eventGatewayPlugin as default, eventGatewayPlugin };
46
+ export { EventGatewayOptions, eventGatewayPlugin };
@@ -44,4 +44,4 @@ const eventGatewayPlugin = fp(eventGatewayPluginImpl, {
44
44
  fastify: "5.x"
45
45
  });
46
46
  //#endregion
47
- export { eventGatewayPlugin as default, eventGatewayPlugin };
47
+ export { eventGatewayPlugin };
@@ -100,4 +100,4 @@ declare function defineJob<TData = unknown, TResult = unknown>(definition: JobDe
100
100
  /** Pluggable BullMQ job queue integration for Arc */
101
101
  declare const jobsPlugin: FastifyPluginAsync<JobsPluginOptions>;
102
102
  //#endregion
103
- export { JobDefinition, JobDispatchOptions, JobDispatcher, JobMeta, JobsPluginOptions, QueueStats, jobsPlugin as default, jobsPlugin, defineJob };
103
+ export { JobDefinition, JobDispatchOptions, JobDispatcher, JobMeta, JobsPluginOptions, QueueStats, defineJob, jobsPlugin };
@@ -169,4 +169,4 @@ const jobsPluginImpl = async (fastify, options) => {
169
169
  /** Pluggable BullMQ job queue integration for Arc */
170
170
  const jobsPlugin = jobsPluginImpl;
171
171
  //#endregion
172
- export { jobsPlugin as default, jobsPlugin, defineJob };
172
+ export { defineJob, jobsPlugin };
@@ -1,4 +1,4 @@
1
- import { n as fieldRulesToZod, r as createMcpServer, t as resourceToTools } from "../../resourceToTools-B6ZN9Ing.mjs";
1
+ import { n as fieldRulesToZod, r as createMcpServer, t as resourceToTools } from "../../resourceToTools-PMFE8HIv.mjs";
2
2
  import { createHash } from "node:crypto";
3
3
  import fp from "fastify-plugin";
4
4
  //#region src/integrations/mcp/definePrompt.ts
@@ -1,4 +1,4 @@
1
- import { r as createMcpServer, t as resourceToTools } from "../../resourceToTools-B6ZN9Ing.mjs";
1
+ import { r as createMcpServer, t as resourceToTools } from "../../resourceToTools-PMFE8HIv.mjs";
2
2
  //#region src/integrations/mcp/testing.ts
3
3
  /**
4
4
  * @classytic/arc/mcp/testing — MCP Test Utilities
@@ -57,4 +57,4 @@ interface StreamlinePluginOptions {
57
57
  /** Pluggable streamline integration for Arc */
58
58
  declare const streamlinePlugin: FastifyPluginAsync<StreamlinePluginOptions>;
59
59
  //#endregion
60
- export { StreamlinePluginOptions, WorkflowLike, WorkflowRunLike, streamlinePlugin as default, streamlinePlugin };
60
+ export { StreamlinePluginOptions, WorkflowLike, WorkflowRunLike, streamlinePlugin };
@@ -142,4 +142,4 @@ const streamlinePluginImpl = async (fastify, options) => {
142
142
  /** Pluggable streamline integration for Arc */
143
143
  const streamlinePlugin = streamlinePluginImpl;
144
144
  //#endregion
145
- export { streamlinePlugin as default, streamlinePlugin };
145
+ export { streamlinePlugin };
@@ -43,4 +43,4 @@ declare class RedisWebSocketAdapter implements WebSocketAdapter {
43
43
  close(): Promise<void>;
44
44
  }
45
45
  //#endregion
46
- export { RedisLike, RedisWebSocketAdapter, RedisWebSocketAdapter as default, RedisWebSocketAdapterOptions };
46
+ export { RedisLike, RedisWebSocketAdapter, RedisWebSocketAdapterOptions };
@@ -47,4 +47,4 @@ var RedisWebSocketAdapter = class {
47
47
  }
48
48
  };
49
49
  //#endregion
50
- export { RedisWebSocketAdapter, RedisWebSocketAdapter as default };
50
+ export { RedisWebSocketAdapter };
@@ -145,4 +145,4 @@ declare class RoomManager {
145
145
  /** Pluggable WebSocket integration for Arc */
146
146
  declare const websocketPlugin: FastifyPluginAsync<WebSocketPluginOptions>;
147
147
  //#endregion
148
- export { LocalWebSocketAdapter, RoomManager, WebSocketAdapter, WebSocketClient, WebSocketMessage, WebSocketPluginOptions, websocketPlugin as default, websocketPlugin };
148
+ export { LocalWebSocketAdapter, RoomManager, WebSocketAdapter, WebSocketClient, WebSocketMessage, WebSocketPluginOptions, websocketPlugin };
@@ -368,4 +368,4 @@ const websocketPlugin = fp(websocketPluginImpl, {
368
368
  fastify: "5.x"
369
369
  });
370
370
  //#endregion
371
- export { LocalWebSocketAdapter, RoomManager, websocketPlugin as default, websocketPlugin };
371
+ export { LocalWebSocketAdapter, RoomManager, websocketPlugin };
@@ -1,9 +1,6 @@
1
1
  import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
2
2
  //#region src/cache/memory.ts
3
- var memory_exports = /* @__PURE__ */ __exportAll({
4
- MemoryCacheStore: () => MemoryCacheStore,
5
- default: () => MemoryCacheStore
6
- });
3
+ var memory_exports = /* @__PURE__ */ __exportAll({ MemoryCacheStore: () => MemoryCacheStore });
7
4
  /**
8
5
  * In-memory LRU+TTL cache store with hard entry cap and memory budget.
9
6
  * - LRU eviction when `maxEntries` or `maxMemoryBytes` is reached
@@ -1,6 +1,41 @@
1
- import mongoose from "mongoose";
2
-
3
1
  //#region src/migrations/index.d.ts
2
+ /**
3
+ * Schema Versioning and Migrations System
4
+ *
5
+ * Manages database schema changes over time with version tracking.
6
+ * Supports forward migrations, rollbacks, and schema compatibility layers.
7
+ *
8
+ * DB-agnostic: the `db` parameter is typed as `unknown` — the user passes
9
+ * whatever connection object their adapter uses (Mongoose db, Prisma client,
10
+ * Knex instance, etc.) and their `up`/`down` functions cast it internally.
11
+ *
12
+ * @example
13
+ * import { defineMigration, MigrationRunner } from '@classytic/arc/migrations';
14
+ *
15
+ * const productV2 = defineMigration({
16
+ * version: 2,
17
+ * resource: 'product',
18
+ * up: async (db) => {
19
+ * const mongo = db as import('mongoose').mongo.Db;
20
+ * await mongo.collection('products').updateMany(
21
+ * {},
22
+ * { $rename: { 'oldField': 'newField' } }
23
+ * );
24
+ * },
25
+ * down: async (db) => {
26
+ * const mongo = db as import('mongoose').mongo.Db;
27
+ * await mongo.collection('products').updateMany(
28
+ * {},
29
+ * { $rename: { 'newField': 'oldField' } }
30
+ * );
31
+ * },
32
+ * });
33
+ *
34
+ * const runner = new MigrationRunner(mongoose.connection.db, {
35
+ * store: new MongoMigrationStore(mongoose.connection.db),
36
+ * });
37
+ * await runner.up(migrations);
38
+ */
4
39
  interface Migration {
5
40
  /** Migration version (sequential number) */
6
41
  version: number;
@@ -9,17 +44,18 @@ interface Migration {
9
44
  /** Description of the migration */
10
45
  description?: string;
11
46
  /**
12
- * Forward migration (apply schema change)
47
+ * Forward migration (apply schema change).
48
+ * The `db` parameter is whatever connection object you pass to the runner.
13
49
  */
14
- up: (db: mongoose.mongo.Db) => Promise<void>;
50
+ up: (db: unknown) => Promise<void>;
15
51
  /**
16
- * Backward migration (revert schema change)
52
+ * Backward migration (revert schema change).
17
53
  */
18
- down: (db: mongoose.mongo.Db) => Promise<void>;
54
+ down: (db: unknown) => Promise<void>;
19
55
  /**
20
56
  * Optional validation that data is compatible after migration
21
57
  */
22
- validate?: (db: mongoose.mongo.Db) => Promise<boolean>;
58
+ validate?: (db: unknown) => Promise<boolean>;
23
59
  }
24
60
  interface MigrationRecord {
25
61
  version: number;
@@ -28,6 +64,54 @@ interface MigrationRecord {
28
64
  appliedAt: Date;
29
65
  executionTime: number;
30
66
  }
67
+ /**
68
+ * DB-agnostic migration store interface.
69
+ *
70
+ * Users implement this for their database:
71
+ * - MongoMigrationStore (uses a `_migrations` collection)
72
+ * - PrismaMigrationStore (uses a `_migrations` table)
73
+ * - or any custom store
74
+ */
75
+ interface MigrationStore {
76
+ /** Get all applied migration records, sorted by appliedAt ascending */
77
+ getApplied(): Promise<MigrationRecord[]>;
78
+ /** Record a completed migration */
79
+ record(migration: Migration, executionTime: number): Promise<void>;
80
+ /** Remove a migration record (for rollback) */
81
+ remove(migration: Migration): Promise<void>;
82
+ }
83
+ /**
84
+ * Minimal logger interface — matches Fastify's logger, pino, console, etc.
85
+ */
86
+ interface MigrationLogger {
87
+ info(msg: string): void;
88
+ error(msg: string): void;
89
+ }
90
+ /**
91
+ * MongoDB-backed migration store.
92
+ *
93
+ * Uses a `_migrations` collection in the same database.
94
+ * The `db` parameter accepts any object with a `.collection()` method
95
+ * (Mongoose db, native MongoDB Db, etc.)
96
+ */
97
+ declare class MongoMigrationStore implements MigrationStore {
98
+ private readonly collectionName;
99
+ private readonly db;
100
+ constructor(db: {
101
+ collection(name: string): any;
102
+ }, opts?: {
103
+ collectionName?: string;
104
+ });
105
+ getApplied(): Promise<MigrationRecord[]>;
106
+ record(migration: Migration, executionTime: number): Promise<void>;
107
+ remove(migration: Migration): Promise<void>;
108
+ }
109
+ interface MigrationRunnerOptions {
110
+ /** Migration store (required — use MongoMigrationStore or implement your own) */
111
+ store: MigrationStore;
112
+ /** Logger (defaults to process.stdout/stderr) */
113
+ logger?: MigrationLogger;
114
+ }
31
115
  /**
32
116
  * Define a migration
33
117
  */
@@ -35,12 +119,30 @@ declare function defineMigration(migration: Migration): Migration;
35
119
  /**
36
120
  * Migration Runner
37
121
  *
38
- * Manages execution of migrations with tracking and rollback support.
122
+ * DB-agnostic. Manages execution of migrations with tracking and rollback.
123
+ * The `db` parameter is passed through to migration `up`/`down` functions
124
+ * as-is — the runner never touches it directly.
125
+ *
126
+ * @example
127
+ * ```typescript
128
+ * // MongoDB
129
+ * const runner = new MigrationRunner(mongoose.connection.db, {
130
+ * store: new MongoMigrationStore(mongoose.connection.db),
131
+ * });
132
+ *
133
+ * // Prisma
134
+ * const runner = new MigrationRunner(prisma, {
135
+ * store: new PrismaMigrationStore(prisma), // user-implemented
136
+ * });
137
+ *
138
+ * await runner.up(migrations);
139
+ * ```
39
140
  */
40
141
  declare class MigrationRunner {
41
- private readonly collectionName;
42
142
  private readonly db;
43
- constructor(db: mongoose.mongo.Db);
143
+ private readonly store;
144
+ private readonly log;
145
+ constructor(db: unknown, opts: MigrationRunnerOptions);
44
146
  /**
45
147
  * Run all pending migrations
46
148
  */
@@ -69,14 +171,6 @@ declare class MigrationRunner {
69
171
  * Run a single migration
70
172
  */
71
173
  private runMigration;
72
- /**
73
- * Record a completed migration
74
- */
75
- private recordMigration;
76
- /**
77
- * Remove a migration record
78
- */
79
- private removeMigration;
80
174
  }
81
175
  /**
82
176
  * Schema version definition for resources
@@ -127,30 +221,5 @@ declare class MigrationRegistry {
127
221
  */
128
222
  clear(): void;
129
223
  }
130
- /**
131
- * Global migration registry instance
132
- */
133
- declare const migrationRegistry: MigrationRegistry;
134
- /**
135
- * Common migration helpers
136
- */
137
- declare const migrationHelpers: {
138
- /**
139
- * Rename a field across all documents
140
- */
141
- renameField: (collection: string, oldName: string, newName: string) => Migration;
142
- /**
143
- * Add a new field with default value
144
- */
145
- addField: (collection: string, fieldName: string, defaultValue: unknown) => Migration;
146
- /**
147
- * Remove a field
148
- */
149
- removeField: (collection: string, fieldName: string) => Migration;
150
- /**
151
- * Create an index
152
- */
153
- createIndex: (collection: string, fields: Record<string, 1 | -1>, options?: Record<string, unknown>) => Migration;
154
- };
155
224
  //#endregion
156
- export { Migration, MigrationRecord, MigrationRegistry, MigrationRunner, SchemaVersion, defineMigration, migrationHelpers, migrationRegistry, withSchemaVersion };
225
+ export { Migration, MigrationLogger, MigrationRecord, MigrationRegistry, MigrationRunner, MigrationRunnerOptions, MigrationStore, MongoMigrationStore, SchemaVersion, defineMigration, withSchemaVersion };
@@ -1,4 +1,42 @@
1
1
  //#region src/migrations/index.ts
2
+ /** Default logger that writes to stdout/stderr */
3
+ const defaultLogger = {
4
+ info: (msg) => process.stdout.write(`${msg}\n`),
5
+ error: (msg) => process.stderr.write(`${msg}\n`)
6
+ };
7
+ /**
8
+ * MongoDB-backed migration store.
9
+ *
10
+ * Uses a `_migrations` collection in the same database.
11
+ * The `db` parameter accepts any object with a `.collection()` method
12
+ * (Mongoose db, native MongoDB Db, etc.)
13
+ */
14
+ var MongoMigrationStore = class {
15
+ collectionName;
16
+ db;
17
+ constructor(db, opts) {
18
+ this.db = db;
19
+ this.collectionName = opts?.collectionName ?? "_migrations";
20
+ }
21
+ async getApplied() {
22
+ return await this.db.collection(this.collectionName).find({}).sort({ appliedAt: 1 }).toArray();
23
+ }
24
+ async record(migration, executionTime) {
25
+ await this.db.collection(this.collectionName).insertOne({
26
+ version: migration.version,
27
+ resource: migration.resource,
28
+ description: migration.description,
29
+ appliedAt: /* @__PURE__ */ new Date(),
30
+ executionTime
31
+ });
32
+ }
33
+ async remove(migration) {
34
+ await this.db.collection(this.collectionName).deleteOne({
35
+ version: migration.version,
36
+ resource: migration.resource
37
+ });
38
+ }
39
+ };
2
40
  /**
3
41
  * Define a migration
4
42
  */
@@ -8,77 +46,97 @@ function defineMigration(migration) {
8
46
  /**
9
47
  * Migration Runner
10
48
  *
11
- * Manages execution of migrations with tracking and rollback support.
49
+ * DB-agnostic. Manages execution of migrations with tracking and rollback.
50
+ * The `db` parameter is passed through to migration `up`/`down` functions
51
+ * as-is — the runner never touches it directly.
52
+ *
53
+ * @example
54
+ * ```typescript
55
+ * // MongoDB
56
+ * const runner = new MigrationRunner(mongoose.connection.db, {
57
+ * store: new MongoMigrationStore(mongoose.connection.db),
58
+ * });
59
+ *
60
+ * // Prisma
61
+ * const runner = new MigrationRunner(prisma, {
62
+ * store: new PrismaMigrationStore(prisma), // user-implemented
63
+ * });
64
+ *
65
+ * await runner.up(migrations);
66
+ * ```
12
67
  */
13
68
  var MigrationRunner = class {
14
- collectionName = "_migrations";
15
69
  db;
16
- constructor(db) {
70
+ store;
71
+ log;
72
+ constructor(db, opts) {
17
73
  this.db = db;
74
+ this.store = opts.store;
75
+ this.log = opts.logger ?? defaultLogger;
18
76
  }
19
77
  /**
20
78
  * Run all pending migrations
21
79
  */
22
80
  async up(migrations) {
23
- const applied = await this.getAppliedMigrations();
81
+ const applied = await this.store.getApplied();
24
82
  const appliedVersions = new Set(applied.map((m) => `${m.resource}:${m.version}`));
25
83
  const pending = migrations.filter((m) => !appliedVersions.has(`${m.resource}:${m.version}`)).sort((a, b) => a.version - b.version);
26
84
  if (pending.length === 0) {
27
- console.log("No pending migrations");
85
+ this.log.info("No pending migrations");
28
86
  return;
29
87
  }
30
- console.log(`Running ${pending.length} migration(s)...\n`);
88
+ this.log.info(`Running ${pending.length} migration(s)...`);
31
89
  for (const migration of pending) await this.runMigration(migration, "up");
32
- console.log("\nAll migrations completed successfully");
90
+ this.log.info("All migrations completed successfully");
33
91
  }
34
92
  /**
35
93
  * Rollback last migration
36
94
  */
37
95
  async down(migrations) {
38
- const applied = await this.getAppliedMigrations();
96
+ const applied = await this.store.getApplied();
39
97
  if (applied.length === 0) {
40
- console.log("No migrations to rollback");
98
+ this.log.info("No migrations to rollback");
41
99
  return;
42
100
  }
43
101
  const last = applied[applied.length - 1];
44
102
  if (!last) {
45
- console.log("No migrations to rollback");
103
+ this.log.info("No migrations to rollback");
46
104
  return;
47
105
  }
48
106
  const migration = migrations.find((m) => m.resource === last.resource && m.version === last.version);
49
107
  if (!migration) throw new Error(`Migration ${last.resource}:${last.version} not found in migration files`);
50
- console.log(`Rolling back ${migration.resource} v${migration.version}...`);
108
+ this.log.info(`Rolling back ${migration.resource} v${migration.version}...`);
51
109
  await this.runMigration(migration, "down", true);
52
- console.log("Rollback completed");
110
+ this.log.info("Rollback completed");
53
111
  }
54
112
  /**
55
113
  * Rollback to specific version
56
114
  */
57
115
  async downTo(migrations, targetVersion) {
58
- const toRollback = (await this.getAppliedMigrations()).filter((m) => m.version > targetVersion).reverse();
116
+ const toRollback = (await this.store.getApplied()).filter((m) => m.version > targetVersion).reverse();
59
117
  if (toRollback.length === 0) {
60
- console.log(`Already at or below version ${targetVersion}`);
118
+ this.log.info(`Already at or below version ${targetVersion}`);
61
119
  return;
62
120
  }
63
- console.log(`Rolling back ${toRollback.length} migration(s)...\n`);
121
+ this.log.info(`Rolling back ${toRollback.length} migration(s)...`);
64
122
  for (const record of toRollback) {
65
123
  const migration = migrations.find((m) => m.resource === record.resource && m.version === record.version);
66
124
  if (!migration) throw new Error(`Migration ${record.resource}:${record.version} not found`);
67
125
  await this.runMigration(migration, "down", true);
68
126
  }
69
- console.log("\nRollback completed");
127
+ this.log.info("Rollback completed");
70
128
  }
71
129
  /**
72
130
  * Get all applied migrations
73
131
  */
74
132
  async getAppliedMigrations() {
75
- return await this.db.collection(this.collectionName).find({}).sort({ appliedAt: 1 }).toArray();
133
+ return this.store.getApplied();
76
134
  }
77
135
  /**
78
136
  * Get pending migrations
79
137
  */
80
138
  async getPendingMigrations(migrations) {
81
- const applied = await this.getAppliedMigrations();
139
+ const applied = await this.store.getApplied();
82
140
  const appliedVersions = new Set(applied.map((m) => `${m.resource}:${m.version}`));
83
141
  return migrations.filter((m) => !appliedVersions.has(`${m.resource}:${m.version}`));
84
142
  }
@@ -93,46 +151,28 @@ var MigrationRunner = class {
93
151
  */
94
152
  async runMigration(migration, direction, isRollback = false) {
95
153
  const start = Date.now();
96
- console.log(`${direction === "up" ? "Applying" : "Rolling back"} ${migration.resource} v${migration.version}${migration.description ? `: ${migration.description}` : ""}...`);
154
+ const action = direction === "up" ? "Applying" : "Rolling back";
155
+ const label = `${migration.resource} v${migration.version}`;
156
+ const desc = migration.description ? `: ${migration.description}` : "";
157
+ this.log.info(`${action} ${label}${desc}...`);
97
158
  try {
98
159
  if (direction === "up") {
99
160
  await migration.up(this.db);
100
161
  if (migration.validate) {
101
162
  if (!await migration.validate(this.db)) throw new Error("Migration validation failed");
102
163
  }
103
- await this.recordMigration(migration, Date.now() - start);
164
+ await this.store.record(migration, Date.now() - start);
104
165
  } else {
105
166
  await migration.down(this.db);
106
- if (isRollback) await this.removeMigration(migration);
167
+ if (isRollback) await this.store.remove(migration);
107
168
  }
108
169
  const duration = Date.now() - start;
109
- console.log(`✅ ${migration.resource} v${migration.version} (${duration}ms)`);
170
+ this.log.info(`${label} completed (${duration}ms)`);
110
171
  } catch (error) {
111
- console.error(`❌ ${migration.resource} v${migration.version} failed:`, error.message);
172
+ this.log.error(`${label} failed: ${error.message}`);
112
173
  throw error;
113
174
  }
114
175
  }
115
- /**
116
- * Record a completed migration
117
- */
118
- async recordMigration(migration, executionTime) {
119
- await this.db.collection(this.collectionName).insertOne({
120
- version: migration.version,
121
- resource: migration.resource,
122
- description: migration.description,
123
- appliedAt: /* @__PURE__ */ new Date(),
124
- executionTime
125
- });
126
- }
127
- /**
128
- * Remove a migration record
129
- */
130
- async removeMigration(migration) {
131
- await this.db.collection(this.collectionName).deleteOne({
132
- version: migration.version,
133
- resource: migration.resource
134
- });
135
- }
136
176
  };
137
177
  /**
138
178
  * Add versioning to resource definition
@@ -198,59 +238,5 @@ var MigrationRegistry = class {
198
238
  this.migrations.clear();
199
239
  }
200
240
  };
201
- /**
202
- * Global migration registry instance
203
- */
204
- const migrationRegistry = new MigrationRegistry();
205
- /**
206
- * Common migration helpers
207
- */
208
- const migrationHelpers = {
209
- renameField: (collection, oldName, newName) => defineMigration({
210
- version: 0,
211
- resource: collection,
212
- description: `Rename ${oldName} to ${newName}`,
213
- up: async (db) => {
214
- await db.collection(collection).updateMany({}, { $rename: { [oldName]: newName } });
215
- },
216
- down: async (db) => {
217
- await db.collection(collection).updateMany({}, { $rename: { [newName]: oldName } });
218
- }
219
- }),
220
- addField: (collection, fieldName, defaultValue) => defineMigration({
221
- version: 0,
222
- resource: collection,
223
- description: `Add ${fieldName} field`,
224
- up: async (db) => {
225
- await db.collection(collection).updateMany({ [fieldName]: { $exists: false } }, { $set: { [fieldName]: defaultValue } });
226
- },
227
- down: async (db) => {
228
- await db.collection(collection).updateMany({}, { $unset: { [fieldName]: "" } });
229
- }
230
- }),
231
- removeField: (collection, fieldName) => defineMigration({
232
- version: 0,
233
- resource: collection,
234
- description: `Remove ${fieldName} field`,
235
- up: async (db) => {
236
- await db.collection(collection).updateMany({}, { $unset: { [fieldName]: "" } });
237
- },
238
- down: async (_db) => {
239
- console.warn(`Cannot restore ${fieldName} field - data was deleted`);
240
- }
241
- }),
242
- createIndex: (collection, fields, options) => defineMigration({
243
- version: 0,
244
- resource: collection,
245
- description: `Create index on ${Object.keys(fields).join(", ")}`,
246
- up: async (db) => {
247
- await db.collection(collection).createIndex(fields, options);
248
- },
249
- down: async (db) => {
250
- const indexName = typeof options?.name === "string" ? options.name : Object.keys(fields).join("_");
251
- await db.collection(collection).dropIndex(indexName);
252
- }
253
- })
254
- };
255
241
  //#endregion
256
- export { MigrationRegistry, MigrationRunner, defineMigration, migrationHelpers, migrationRegistry, withSchemaVersion };
242
+ export { MigrationRegistry, MigrationRunner, MongoMigrationStore, defineMigration, withSchemaVersion };
@@ -1,4 +1,4 @@
1
1
  import { i as resolveEffectiveRoles, n as applyFieldWritePermissions, r as fields, t as applyFieldReadPermissions } from "../fields-ipsbIRPK.mjs";
2
2
  import { n as normalizeRoles, t as getUserRoles } from "../types-ZUu_h0jp.mjs";
3
- import { S as createRoleHierarchy, _ as ownerWithAdminBypass, a as createOrgPermissions, b as publicReadAdminWrite, c as requireOrgMembership, d as requireRoles, f as requireTeamMembership, g as fullPublic, h as authenticated, i as createDynamicPermissionMatrix, l as requireOrgRole, m as adminOnly, n as allowPublic, o as denyAll, p as when, r as anyOf, s as requireAuth, t as allOf, u as requireOwnership, v as presets_exports, x as readOnly, y as publicRead } from "../permissions-CA5zg0yK.mjs";
3
+ import { S as createRoleHierarchy, _ as ownerWithAdminBypass, a as createOrgPermissions, b as publicReadAdminWrite, c as requireOrgMembership, d as requireRoles, f as requireTeamMembership, g as fullPublic, h as authenticated, i as createDynamicPermissionMatrix, l as requireOrgRole, m as adminOnly, n as allowPublic, o as denyAll, p as when, r as anyOf, s as requireAuth, t as allOf, u as requireOwnership, v as presets_exports, x as readOnly, y as publicRead } from "../permissions-Jk5x3sxz.mjs";
4
4
  export { adminOnly, allOf, allowPublic, anyOf, applyFieldReadPermissions, applyFieldWritePermissions, authenticated, createDynamicPermissionMatrix, createOrgPermissions, createRoleHierarchy, denyAll, fields, fullPublic, getUserRoles, normalizeRoles, ownerWithAdminBypass, presets_exports as permissions, publicRead, publicReadAdminWrite, readOnly, requireAuth, requireOrgMembership, requireOrgRole, requireOwnership, requireRoles, requireTeamMembership, resolveEffectiveRoles, when };
@@ -1,7 +1,7 @@
1
1
  import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
2
2
  import { a as getTeamId, d as isMember, n as PUBLIC_SCOPE, o as getUserId, u as isElevated } from "./types-C6TQjtdi.mjs";
3
3
  import { t as getUserRoles } from "./types-ZUu_h0jp.mjs";
4
- import { t as MemoryCacheStore } from "./memory-Cb_7iy9e.mjs";
4
+ import { t as MemoryCacheStore } from "./memory-BFAYkf8H.mjs";
5
5
  import { randomUUID } from "node:crypto";
6
6
  //#region src/permissions/roleHierarchy.ts
7
7
  /**
@@ -5,7 +5,7 @@ import { t as hasEvents } from "../typeGuards-Cj5Rgvlg.mjs";
5
5
  import { t as HookSystem } from "../HookSystem-COkyWztM.mjs";
6
6
  import { t as ResourceRegistry } from "../ResourceRegistry-DeCIFlix.mjs";
7
7
  import { n as caching_default, t as cachingPlugin } from "../caching-BSXB-Xr7.mjs";
8
- import { t as errorHandlerPlugin } from "../errorHandler--zp54tGc.mjs";
8
+ import { t as errorHandlerPlugin } from "../errorHandler-DMbGdzBG.mjs";
9
9
  import { n as metrics_default, t as metricsPlugin } from "../metrics-Csh4nsvv.mjs";
10
10
  import { n as sse_default, t as ssePlugin } from "../sse-BkViJPlT.mjs";
11
11
  import { n as versioning_default, t as versioningPlugin } from "../versioning-BzfeHmhj.mjs";
@@ -84,4 +84,4 @@ declare module "fastify" {
84
84
  }
85
85
  declare const responseCachePlugin: FastifyPluginAsync<ResponseCacheOptions>;
86
86
  //#endregion
87
- export { ResponseCacheOptions, ResponseCacheRule, ResponseCacheStats, responseCachePlugin as default, responseCachePlugin };
87
+ export { ResponseCacheOptions, ResponseCacheRule, ResponseCacheStats, responseCachePlugin };
@@ -215,4 +215,4 @@ const responseCachePlugin = fp(responseCachePluginImpl, {
215
215
  fastify: "5.x"
216
216
  });
217
217
  //#endregion
218
- export { responseCachePlugin as default, responseCachePlugin };
218
+ export { responseCachePlugin };
@@ -44,7 +44,7 @@ try {
44
44
  function createTracerProvider(options) {
45
45
  if (!isAvailable) return null;
46
46
  const { serviceName = "@classytic/arc", serviceVersion, exporterUrl = "http://localhost:4318/v1/traces" } = options;
47
- const resolvedVersion = serviceVersion ?? "2.4.1";
47
+ const resolvedVersion = serviceVersion ?? "2.4.3";
48
48
  const exporter = new OTLPTraceExporter({ url: exporterUrl });
49
49
  const provider = new NodeTracerProvider({ resource: { attributes: {
50
50
  "service.name": serviceName,
@@ -1,5 +1,5 @@
1
1
  import { Mt as IControllerResponse, Nt as IRequestContext, Vt as PaginatedResult, Y as PresetResult, it as ResourceConfig, l as AnyRecord } from "../interface-DGmPxakH.mjs";
2
- import multiTenantPreset, { MultiTenantOptions } from "./multiTenant.mjs";
2
+ import { MultiTenantOptions, multiTenantPreset } from "./multiTenant.mjs";
3
3
 
4
4
  //#region src/presets/ownedByUser.d.ts
5
5
  interface OwnedByUserOptions {
@@ -1,3 +1,3 @@
1
- import multiTenantPreset from "./multiTenant.mjs";
2
- import { a as registerPreset, c as auditedPreset, d as ownedByUserPreset, i as getPreset, l as softDeletePreset, n as flexibleMultiTenantPreset, o as treePreset, r as getAvailablePresets, s as bulkPreset, t as applyPresets, u as slugLookupPreset } from "../presets-C9QXJV1u.mjs";
1
+ import { multiTenantPreset } from "./multiTenant.mjs";
2
+ import { a as registerPreset, c as auditedPreset, d as ownedByUserPreset, i as getPreset, l as softDeletePreset, n as flexibleMultiTenantPreset, o as treePreset, r as getAvailablePresets, s as bulkPreset, t as applyPresets, u as slugLookupPreset } from "../presets-OMPaHMTY.mjs";
3
3
  export { applyPresets, auditedPreset, bulkPreset, flexibleMultiTenantPreset, getAvailablePresets, getPreset, multiTenantPreset, ownedByUserPreset, registerPreset, slugLookupPreset, softDeletePreset, treePreset };
@@ -18,4 +18,4 @@ interface MultiTenantOptions {
18
18
  }
19
19
  declare function multiTenantPreset(options?: MultiTenantOptions): PresetResult;
20
20
  //#endregion
21
- export { MultiTenantOptions, multiTenantPreset as default, multiTenantPreset };
21
+ export { MultiTenantOptions, multiTenantPreset };
@@ -108,4 +108,4 @@ function multiTenantPreset(options = {}) {
108
108
  };
109
109
  }
110
110
  //#endregion
111
- export { multiTenantPreset as default, multiTenantPreset };
111
+ export { multiTenantPreset };
@@ -1,6 +1,6 @@
1
1
  import { n as PUBLIC_SCOPE, u as isElevated } from "./types-C6TQjtdi.mjs";
2
- import multiTenantPreset from "./presets/multiTenant.mjs";
3
- import { d as requireRoles, n as allowPublic, s as requireAuth } from "./permissions-CA5zg0yK.mjs";
2
+ import { multiTenantPreset } from "./presets/multiTenant.mjs";
3
+ import { d as requireRoles, n as allowPublic, s as requireAuth } from "./permissions-Jk5x3sxz.mjs";
4
4
  //#region src/presets/ownedByUser.ts
5
5
  /**
6
6
  * Create ownership check middleware.
@@ -1,7 +1,7 @@
1
1
  import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
2
2
  import { i as versionKey, r as tagVersionKey } from "./keys-qcD-TVJl.mjs";
3
3
  import { t as hasEvents } from "./typeGuards-Cj5Rgvlg.mjs";
4
- import { t as MemoryCacheStore } from "./memory-Cb_7iy9e.mjs";
4
+ import { t as MemoryCacheStore } from "./memory-BFAYkf8H.mjs";
5
5
  import fp from "fastify-plugin";
6
6
  //#region src/cache/QueryCache.ts
7
7
  var QueryCache = class {
@@ -174,18 +174,19 @@ function buildListShape(fieldRules, options) {
174
174
  * | update | { id } | {} | input minus id |
175
175
  * | delete | { id } | {} | undefined |
176
176
  */
177
- function buildRequestContext(input, auth, operation) {
177
+ function buildRequestContext(input, auth, operation, policyFilters) {
178
178
  const scope = buildScope(auth);
179
179
  const base = {
180
180
  user: auth ? {
181
181
  id: auth.userId,
182
- _id: auth.userId
182
+ _id: auth.userId,
183
+ ...auth
183
184
  } : null,
184
185
  headers: {},
185
186
  context: {},
186
187
  metadata: {
187
188
  _scope: scope,
188
- _policyFilters: {}
189
+ _policyFilters: policyFilters ?? {}
189
190
  }
190
191
  };
191
192
  switch (operation) {
@@ -315,7 +316,7 @@ function resourceToTools(resource, config = {}) {
315
316
  extraHideFields: config.hideFields,
316
317
  filterableFields
317
318
  }),
318
- handler: createHandler(op, controller, resource.name)
319
+ handler: createHandler(op, controller, resource.name, resource.permissions)
319
320
  });
320
321
  }
321
322
  for (const route of resource.additionalRoutes ?? []) {
@@ -381,7 +382,7 @@ function buildInputSchema(op, fieldRules, opts) {
381
382
  case "delete": return { id: z.string().describe("Resource ID") };
382
383
  }
383
384
  }
384
- function createHandler(op, controller, resourceName) {
385
+ function createHandler(op, controller, resourceName, permissions) {
385
386
  const ctrl = controller;
386
387
  return async (input, ctx) => {
387
388
  try {
@@ -393,7 +394,15 @@ function createHandler(op, controller, resourceName) {
393
394
  }],
394
395
  isError: true
395
396
  };
396
- return toCallToolResult(await method(buildRequestContext(input, ctx.session, op)));
397
+ const policyFilters = await evaluatePermission(permissions?.[op], ctx.session, resourceName, op, input);
398
+ if (policyFilters === false) return {
399
+ content: [{
400
+ type: "text",
401
+ text: `Permission denied: ${op} on ${resourceName}`
402
+ }],
403
+ isError: true
404
+ };
405
+ return toCallToolResult(await method(buildRequestContext(input, ctx.session, op, policyFilters || void 0)));
397
406
  } catch (err) {
398
407
  const msg = err instanceof Error ? err.message : String(err);
399
408
  ctx.log("error", `${resourceName}.${op}: ${msg}`).catch(() => {});
@@ -432,6 +441,41 @@ function createAdditionalRouteHandler(route, controller, hasId) {
432
441
  }
433
442
  };
434
443
  }
444
+ /**
445
+ * Evaluate a resource's permission check in MCP context.
446
+ *
447
+ * Returns:
448
+ * - `false` if permission denied
449
+ * - `Record<string, unknown>` if granted with filters (ownership patterns)
450
+ * - `null` if granted without filters (or no permission check defined)
451
+ */
452
+ async function evaluatePermission(check, session, resource, action, input) {
453
+ if (!check) return null;
454
+ const user = session ? {
455
+ id: session.userId,
456
+ _id: session.userId,
457
+ ...session
458
+ } : null;
459
+ const result = await check({
460
+ user,
461
+ request: {
462
+ user,
463
+ headers: {},
464
+ params: {},
465
+ query: {},
466
+ body: input
467
+ },
468
+ resource,
469
+ action,
470
+ resourceId: typeof input.id === "string" ? input.id : void 0,
471
+ params: {},
472
+ data: input
473
+ });
474
+ if (typeof result === "boolean") return result ? null : false;
475
+ const permResult = result;
476
+ if (!permResult.granted) return false;
477
+ return permResult.filters ?? null;
478
+ }
435
479
  function toCallToolResult(result) {
436
480
  if (!result.success) return {
437
481
  content: [{
@@ -1752,7 +1752,7 @@ function runEventTests(resourceName, displayName, events) {
1752
1752
  * ```
1753
1753
  */
1754
1754
  async function createTestApp(options = {}) {
1755
- const { createApp } = await import("../createApp-ByWNRsZj.mjs").then((n) => n.r);
1755
+ const { createApp } = await import("../createApp-CBgVaFyh.mjs").then((n) => n.r);
1756
1756
  const { useInMemoryDb = true, mongoUri: providedMongoUri, ...appOptions } = options;
1757
1757
  const defaultAuth = {
1758
1758
  type: "jwt",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classytic/arc",
3
- "version": "2.4.1",
3
+ "version": "2.4.3",
4
4
  "description": "Resource-oriented backend framework for Fastify — clean, minimal, powerful, tree-shakable",
5
5
  "type": "module",
6
6
  "exports": {
@@ -220,7 +220,7 @@
220
220
  "node": ">=22"
221
221
  },
222
222
  "peerDependencies": {
223
- "@classytic/mongokit": ">=3.4.3",
223
+ "@classytic/mongokit": ">=3.4.5",
224
224
  "@classytic/streamline": ">=2.0.0",
225
225
  "@fastify/cors": "^11.0.0",
226
226
  "@fastify/helmet": "^13.0.0",
@@ -337,7 +337,7 @@
337
337
  },
338
338
  "devDependencies": {
339
339
  "@biomejs/biome": "^2.4.10",
340
- "@classytic/mongokit": "^3.4.3",
340
+ "@classytic/mongokit": "^3.4.5",
341
341
  "@fastify/jwt": "^10.0.0",
342
342
  "@fastify/multipart": "^9.0.0",
343
343
  "@fastify/type-provider-typebox": "^6.0.0",
@@ -349,6 +349,7 @@
349
349
  "@vitest/coverage-v8": "^3.2.4",
350
350
  "fastify-raw-body": "^5.0.0",
351
351
  "jsonwebtoken": "^9.0.0",
352
+ "knip": "^6.3.0",
352
353
  "mongodb": "^7.1.0",
353
354
  "mongodb-memory-server": "^11.0.1",
354
355
  "tsdown": "^0.21.7",
@@ -8,11 +8,11 @@ description: |
8
8
  Triggers: arc, fastify resource, defineResource, createApp, BaseController, arc preset,
9
9
  arc auth, arc events, arc jobs, arc websocket, arc mcp, arc plugin, arc testing, arc cli,
10
10
  arc permissions, arc hooks, arc pipeline, arc factory, arc cache, arc QueryCache.
11
- version: 2.4.0
11
+ version: 2.4.2
12
12
  license: MIT
13
13
  metadata:
14
14
  author: Classytic
15
- version: "2.4.0"
15
+ version: "2.4.1"
16
16
  tags:
17
17
  - fastify
18
18
  - rest-api
@@ -360,7 +360,7 @@ GET /products?lookup[cat][from]=categories&...&lookup[cat][select]=name,slug
360
360
 
361
361
  Operators: `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `nin`, `like`, `regex`, `exists`
362
362
 
363
- **Custom query parser (e.g., MongoKit for $lookup support):**
363
+ **Custom query parser (e.g., MongoKit >=3.4.5 for $lookup, whitelists, MCP auto-derive):**
364
364
 
365
365
  ```typescript
366
366
  import { QueryParser } from '@classytic/mongokit';
@@ -368,11 +368,16 @@ import { QueryParser } from '@classytic/mongokit';
368
368
  defineResource({
369
369
  name: 'product',
370
370
  adapter: createMongooseAdapter({ model: ProductModel, repository: productRepo }),
371
- queryParser: new QueryParser(), // enables lookup, advanced populate, keyset pagination
371
+ queryParser: new QueryParser({
372
+ allowedFilterFields: ['status', 'category', 'orgId'], // whitelist filter fields
373
+ allowedSortFields: ['createdAt', 'price'], // whitelist sort fields
374
+ allowedOperators: ['eq', 'gte', 'lte', 'in'], // whitelist operators
375
+ }),
376
+ // MCP auto-derives filterableFields from queryParser — no duplication needed
372
377
  schemaOptions: {
373
378
  query: {
374
- allowedPopulate: ['category', 'brand'], // whitelist populate paths
375
- allowedLookups: ['categories', 'brands'], // whitelist lookup collections
379
+ allowedPopulate: ['category', 'brand'],
380
+ allowedLookups: ['categories', 'brands'],
376
381
  },
377
382
  },
378
383
  });
@@ -385,6 +390,8 @@ import { ArcError, NotFoundError, ValidationError, UnauthorizedError, ForbiddenE
385
390
  throw new NotFoundError('Product not found'); // 404
386
391
  ```
387
392
 
393
+ Error handler catches: `ArcError` → `.statusCode` (Fastify) → `.status` (MongoKit, http-errors) → `errorMap` → Mongoose/MongoDB → fallback 500. DB-agnostic — any error with `.status` or `.statusCode` gets the correct HTTP response.
394
+
388
395
  ## Compensating Transaction
389
396
 
390
397
  In-process rollback for multi-step operations. Not a distributed saga — use Temporal/Streamline for that.
@@ -452,6 +459,18 @@ auth: async (headers) => {
452
459
 
453
460
  **Multi-tenancy**: `organizationId` from auth flows into BaseController org-scoping automatically.
454
461
 
462
+ **Permission filters**: `PermissionResult.filters` from resource permissions flow into MCP tools — same as REST. Define once, works everywhere:
463
+
464
+ ```typescript
465
+ permissions: {
466
+ list: (ctx) => ({
467
+ granted: !!ctx.user,
468
+ filters: { orgId: ctx.user?.orgId, branchId: ctx.user?.branchId },
469
+ }),
470
+ }
471
+ // MCP tools automatically scope queries by orgId + branchId
472
+ ```
473
+
455
474
  **Project structure** — custom MCP tools co-located with resources:
456
475
 
457
476
  ```
@@ -35,9 +35,11 @@ Tool handlers call `BaseController` — same pipeline as REST (auth, org-scoping
35
35
  | `serverName` | `string` | `'arc-mcp'` | Server identity |
36
36
  | `serverVersion` | `string` | `'1.0.0'` | Server version |
37
37
  | `instructions` | `string` | — | LLM guidance on tool usage |
38
+ | `include` | `string[]` | — | Only these resources get tools (overrides `exclude`) |
38
39
  | `exclude` | `string[]` | — | Resource names to exclude |
39
- | `toolNamePrefix` | `string` | — | Prefix: `'crm'` → `crm_list_products` |
40
- | `overrides` | `Record<string, McpResourceConfig>` | — | Per-resource operation/field overrides |
40
+ | `toolNamePrefix` | `string` | — | Global prefix: `'crm'` → `crm_list_products` |
41
+ | `overrides` | `Record<string, McpResourceConfig>` | — | Per-resource overrides (see below) |
42
+ | `authCacheTtlMs` | `number` | — | Cache auth results for N ms in stateless mode |
41
43
  | `extraTools` | `ToolDefinition[]` | — | Hand-written tools alongside auto-generated |
42
44
  | `extraPrompts` | `PromptDefinition[]` | — | Custom prompts |
43
45
  | `stateful` | `boolean` | `false` | `false` = stateless (default, scalable). `true` = session-cached. |
@@ -52,6 +54,49 @@ Tool handlers call `BaseController` — same pipeline as REST (auth, org-scoping
52
54
  | `create` | `destructiveHint: false` |
53
55
  | `update`, `delete` | `destructiveHint: true, idempotentHint: true` |
54
56
 
57
+ ### Per-Resource Overrides
58
+
59
+ ```typescript
60
+ await app.register(mcpPlugin, {
61
+ resources,
62
+ include: ['job', 'project'], // only expose these
63
+ overrides: {
64
+ job: {
65
+ operations: ['list', 'get'], // restrict ops
66
+ toolNamePrefix: 'db', // db_list_jobs, db_get_job
67
+ names: { get: 'get_job_by_id' }, // custom name for specific op
68
+ hideFields: ['internalScore'], // strip from schema
69
+ descriptions: { list: 'Browse jobs' }, // custom descriptions
70
+ },
71
+ },
72
+ });
73
+ ```
74
+
75
+ ### Permission Filters (v2.4.2)
76
+
77
+ Resource permissions with `filters` are automatically enforced in MCP tools — same as REST:
78
+
79
+ ```typescript
80
+ defineResource({
81
+ name: 'task',
82
+ permissions: {
83
+ list: (ctx) => ({
84
+ granted: !!ctx.user,
85
+ filters: { orgId: ctx.user?.orgId, branchId: ctx.user?.branchId },
86
+ }),
87
+ create: (ctx) => !!ctx.user, // boolean works too
88
+ delete: (ctx) => ({ granted: false, reason: 'Read-only' }), // deny
89
+ },
90
+ });
91
+
92
+ // MCP tools automatically:
93
+ // - list_tasks scopes by orgId + branchId from permission filters
94
+ // - create_task allowed if user is authenticated
95
+ // - delete_task returns "Permission denied: delete on task"
96
+ ```
97
+
98
+ No extra config. `PermissionResult.filters` flow into `_policyFilters` → `BaseController.AccessControl`.
99
+
55
100
  ### Multiple MCP Endpoints
56
101
 
57
102
  Mount separate servers scoped to different resource groups: