@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.
- package/dist/auth/index.mjs +1 -1
- package/dist/cache/index.mjs +2 -2
- package/dist/cli/commands/describe.d.mts +1 -1
- package/dist/cli/commands/describe.mjs +1 -1
- 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.d.mts +1 -1
- package/dist/cli/commands/introspect.mjs +1 -1
- package/dist/cli/index.d.mts +4 -4
- package/dist/cli/index.mjs +4 -4
- package/dist/core/index.mjs +1 -1
- package/dist/{createApp-ByWNRsZj.mjs → createApp-CBgVaFyh.mjs} +3 -3
- package/dist/{defineResource-D9aY5Cy6.mjs → defineResource-B22gcNvn.mjs} +1 -1
- package/dist/discovery/index.d.mts +1 -1
- package/dist/discovery/index.mjs +1 -1
- package/dist/dynamic/index.mjs +2 -2
- package/dist/{errorHandler--zp54tGc.mjs → errorHandler-DMbGdzBG.mjs} +4 -4
- package/dist/events/transports/redis.d.mts +1 -1
- package/dist/events/transports/redis.mjs +1 -1
- package/dist/factory/index.mjs +1 -1
- package/dist/index.mjs +3 -3
- package/dist/integrations/event-gateway.d.mts +1 -1
- package/dist/integrations/event-gateway.mjs +1 -1
- package/dist/integrations/jobs.d.mts +1 -1
- package/dist/integrations/jobs.mjs +1 -1
- package/dist/integrations/mcp/index.mjs +1 -1
- package/dist/integrations/mcp/testing.mjs +1 -1
- package/dist/integrations/streamline.d.mts +1 -1
- package/dist/integrations/streamline.mjs +1 -1
- package/dist/integrations/websocket-redis.d.mts +1 -1
- package/dist/integrations/websocket-redis.mjs +1 -1
- package/dist/integrations/websocket.d.mts +1 -1
- package/dist/integrations/websocket.mjs +1 -1
- package/dist/{memory-Cb_7iy9e.mjs → memory-BFAYkf8H.mjs} +1 -4
- package/dist/migrations/index.d.mts +113 -44
- package/dist/migrations/index.mjs +85 -99
- package/dist/permissions/index.mjs +1 -1
- package/dist/{permissions-CA5zg0yK.mjs → permissions-Jk5x3sxz.mjs} +1 -1
- package/dist/plugins/index.mjs +1 -1
- package/dist/plugins/response-cache.d.mts +1 -1
- package/dist/plugins/response-cache.mjs +1 -1
- package/dist/plugins/tracing-entry.mjs +1 -1
- package/dist/presets/index.d.mts +1 -1
- package/dist/presets/index.mjs +2 -2
- package/dist/presets/multiTenant.d.mts +1 -1
- package/dist/presets/multiTenant.mjs +1 -1
- package/dist/{presets-C9QXJV1u.mjs → presets-OMPaHMTY.mjs} +2 -2
- package/dist/{queryCachePlugin-ClosZdNS.mjs → queryCachePlugin-XtFplYO9.mjs} +1 -1
- package/dist/{resourceToTools-B6ZN9Ing.mjs → resourceToTools-PMFE8HIv.mjs} +50 -6
- package/dist/testing/index.mjs +1 -1
- package/package.json +4 -3
- package/skills/arc/SKILL.md +25 -6
- package/skills/arc/references/mcp.md +47 -2
package/dist/auth/index.mjs
CHANGED
|
@@ -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-
|
|
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";
|
package/dist/cache/index.mjs
CHANGED
|
@@ -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-
|
|
3
|
-
import { r as QueryCache, t as queryCachePlugin } from "../queryCachePlugin-
|
|
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.
|
package/dist/cli/index.d.mts
CHANGED
|
@@ -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 };
|
package/dist/cli/index.mjs
CHANGED
|
@@ -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 };
|
package/dist/core/index.mjs
CHANGED
|
@@ -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-
|
|
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-
|
|
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-
|
|
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
|
|
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-
|
|
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,
|
|
46
|
+
export { DiscoverableResource, DiscoveryOptions, DiscoveryPluginOptions, discoverResources, discoveryPlugin };
|
package/dist/discovery/index.mjs
CHANGED
|
@@ -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 {
|
|
141
|
+
export { discoverResources, discoveryPlugin };
|
package/dist/dynamic/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as ArcQueryParser } from "../queryParser-CgCtsjti.mjs";
|
|
2
|
-
import { n as defineResource } from "../defineResource-
|
|
3
|
-
import { _ as ownerWithAdminBypass, b as publicReadAdminWrite, g as fullPublic, h as authenticated, m as adminOnly, x as readOnly, y as publicRead } from "../permissions-
|
|
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,
|
|
76
|
+
export { RedisEventTransport, RedisEventTransportOptions, RedisLike };
|
package/dist/factory/index.mjs
CHANGED
|
@@ -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-
|
|
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-
|
|
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-
|
|
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.
|
|
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
|
|
46
|
+
export { EventGatewayOptions, 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,
|
|
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 {
|
|
172
|
+
export { defineJob, jobsPlugin };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as fieldRulesToZod, r as createMcpServer, t as resourceToTools } from "../../resourceToTools-
|
|
1
|
+
import { n as fieldRulesToZod, r as createMcpServer, t as resourceToTools } from "../../resourceToTools-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-
|
|
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
|
|
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
|
|
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,
|
|
46
|
+
export { RedisLike, RedisWebSocketAdapter, RedisWebSocketAdapterOptions };
|
|
@@ -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
|
|
148
|
+
export { LocalWebSocketAdapter, RoomManager, WebSocketAdapter, WebSocketClient, WebSocketMessage, WebSocketPluginOptions, 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:
|
|
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:
|
|
54
|
+
down: (db: unknown) => Promise<void>;
|
|
19
55
|
/**
|
|
20
56
|
* Optional validation that data is compatible after migration
|
|
21
57
|
*/
|
|
22
|
-
validate?: (db:
|
|
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
|
|
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
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
85
|
+
this.log.info("No pending migrations");
|
|
28
86
|
return;
|
|
29
87
|
}
|
|
30
|
-
|
|
88
|
+
this.log.info(`Running ${pending.length} migration(s)...`);
|
|
31
89
|
for (const migration of pending) await this.runMigration(migration, "up");
|
|
32
|
-
|
|
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.
|
|
96
|
+
const applied = await this.store.getApplied();
|
|
39
97
|
if (applied.length === 0) {
|
|
40
|
-
|
|
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
|
-
|
|
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
|
-
|
|
108
|
+
this.log.info(`Rolling back ${migration.resource} v${migration.version}...`);
|
|
51
109
|
await this.runMigration(migration, "down", true);
|
|
52
|
-
|
|
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.
|
|
116
|
+
const toRollback = (await this.store.getApplied()).filter((m) => m.version > targetVersion).reverse();
|
|
59
117
|
if (toRollback.length === 0) {
|
|
60
|
-
|
|
118
|
+
this.log.info(`Already at or below version ${targetVersion}`);
|
|
61
119
|
return;
|
|
62
120
|
}
|
|
63
|
-
|
|
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
|
-
|
|
127
|
+
this.log.info("Rollback completed");
|
|
70
128
|
}
|
|
71
129
|
/**
|
|
72
130
|
* Get all applied migrations
|
|
73
131
|
*/
|
|
74
132
|
async getAppliedMigrations() {
|
|
75
|
-
return
|
|
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.
|
|
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
|
-
|
|
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.
|
|
164
|
+
await this.store.record(migration, Date.now() - start);
|
|
104
165
|
} else {
|
|
105
166
|
await migration.down(this.db);
|
|
106
|
-
if (isRollback) await this.
|
|
167
|
+
if (isRollback) await this.store.remove(migration);
|
|
107
168
|
}
|
|
108
169
|
const duration = Date.now() - start;
|
|
109
|
-
|
|
170
|
+
this.log.info(`${label} completed (${duration}ms)`);
|
|
110
171
|
} catch (error) {
|
|
111
|
-
|
|
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,
|
|
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-
|
|
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-
|
|
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
|
/**
|
package/dist/plugins/index.mjs
CHANGED
|
@@ -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
|
|
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
|
|
87
|
+
export { ResponseCacheOptions, ResponseCacheRule, ResponseCacheStats, 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.
|
|
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,
|
package/dist/presets/index.d.mts
CHANGED
|
@@ -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
|
|
2
|
+
import { MultiTenantOptions, multiTenantPreset } from "./multiTenant.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/presets/ownedByUser.d.ts
|
|
5
5
|
interface OwnedByUserOptions {
|
package/dist/presets/index.mjs
CHANGED
|
@@ -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-
|
|
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
|
|
21
|
+
export { MultiTenantOptions, 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-
|
|
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-
|
|
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
|
-
|
|
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: [{
|
package/dist/testing/index.mjs
CHANGED
|
@@ -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-
|
|
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.
|
|
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.
|
|
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.
|
|
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",
|
package/skills/arc/SKILL.md
CHANGED
|
@@ -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.
|
|
11
|
+
version: 2.4.2
|
|
12
12
|
license: MIT
|
|
13
13
|
metadata:
|
|
14
14
|
author: Classytic
|
|
15
|
-
version: "2.4.
|
|
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
|
|
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(
|
|
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'],
|
|
375
|
-
allowedLookups: ['categories', 'brands'],
|
|
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` | — |
|
|
40
|
-
| `overrides` | `Record<string, McpResourceConfig>` | — | Per-resource
|
|
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:
|