@classytic/arc 2.8.1 → 2.8.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -1
- package/dist/{ResourceRegistry-Dtcojmu8.mjs → ResourceRegistry-Dq3_zBQP.mjs} +5 -5
- package/dist/adapters/index.d.mts +2 -2
- package/dist/audit/index.d.mts +1 -1
- package/dist/audit/index.mjs +1 -1
- package/dist/audit/mongodb.d.mts +1 -1
- package/dist/audit/mongodb.mjs +1 -1
- package/dist/auth/index.d.mts +4 -4
- package/dist/auth/redis-session.d.mts +1 -1
- package/dist/cache/index.d.mts +2 -2
- package/dist/cli/commands/describe.mjs +1 -1
- package/dist/cli/commands/docs.mjs +2 -2
- package/dist/cli/commands/generate.mjs +1 -1
- package/dist/cli/commands/init.mjs +10 -10
- package/dist/cli/commands/introspect.mjs +3 -3
- package/dist/core/index.d.mts +3 -3
- package/dist/core/index.mjs +4 -4
- package/dist/{core-CrLDuqoT.mjs → core-DKSwNSXf.mjs} +1 -1
- package/dist/{createApp-p2OThysU.mjs → createApp-BOYjBgdI.mjs} +16 -7
- package/dist/{defineResource-CqeUltrW.mjs → defineResource-Bb_Bdhtw.mjs} +42 -27
- package/dist/docs/index.d.mts +2 -2
- package/dist/docs/index.mjs +1 -1
- package/dist/dynamic/index.d.mts +2 -2
- package/dist/dynamic/index.mjs +1 -1
- package/dist/{errorHandler-DJ7OAB2V.d.mts → errorHandler-CdZDavNH.d.mts} +2 -2
- package/dist/{eventPlugin-Cdjwo0Gv.d.mts → eventPlugin-CVxlE6De.d.mts} +1 -1
- package/dist/events/index.d.mts +3 -3
- package/dist/events/index.mjs +1 -1
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis-stream-entry.mjs +3 -1
- package/dist/events/transports/redis.d.mts +1 -1
- package/dist/factory/index.d.mts +1 -1
- package/dist/factory/index.mjs +2 -152
- package/dist/hooks/index.d.mts +1 -1
- package/dist/idempotency/index.d.mts +3 -3
- package/dist/idempotency/mongodb.d.mts +1 -1
- package/dist/idempotency/mongodb.mjs +18 -6
- package/dist/idempotency/redis.d.mts +1 -1
- package/dist/idempotency/redis.mjs +10 -1
- package/dist/{index-0zj73o2U.d.mts → index-BgmMdpm8.d.mts} +1 -1
- package/dist/{index-CBru2y5Y.d.mts → index-CSkeivBx.d.mts} +3 -3
- package/dist/{index-DadoLP51.d.mts → index-CpTSDqmD.d.mts} +26 -4
- package/dist/index.d.mts +7 -7
- 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/index.d.mts +1 -1
- package/dist/integrations/mcp/index.d.mts +51 -3
- package/dist/integrations/mcp/index.mjs +78 -19
- package/dist/integrations/mcp/testing.d.mts +1 -1
- package/dist/integrations/mcp/testing.mjs +1 -1
- package/dist/{interface-CS6d7HiB.d.mts → interface-BVuMfeVv.d.mts} +47 -18
- package/dist/loadResources-Bksk8ydA.mjs +154 -0
- package/dist/{mongodb-B1eVtFhw.d.mts → mongodb-B8U2xaLj.d.mts} +1 -1
- package/dist/{mongodb-NShVZDMr.d.mts → mongodb-X7LbEjTN.d.mts} +10 -1
- package/dist/{openapi-q6rNKfZy.mjs → openapi-CYCuekCn.mjs} +2 -2
- package/dist/org/index.d.mts +2 -2
- package/dist/permissions/index.d.mts +3 -3
- package/dist/plugins/index.d.mts +5 -5
- package/dist/plugins/index.mjs +7 -7
- package/dist/plugins/tracing-entry.d.mts +1 -1
- package/dist/plugins/tracing-entry.mjs +1 -1
- package/dist/policies/index.d.mts +1 -1
- package/dist/presets/index.d.mts +1 -1
- package/dist/presets/index.mjs +1 -1
- package/dist/presets/multiTenant.d.mts +1 -1
- package/dist/{presets-BFrGvvjL.mjs → presets-C2xgzW6x.mjs} +10 -18
- package/dist/{queryCachePlugin-BCFVXnxK.d.mts → queryCachePlugin-CnTZZTC5.d.mts} +1 -1
- package/dist/{redis-stream-BgrYzpeq.d.mts → redis-stream-D54N5oXs.d.mts} +1 -1
- package/dist/{redis-Bunu3qWg.d.mts → redis-z3sFr1UP.d.mts} +1 -1
- package/dist/registry/index.d.mts +1 -1
- package/dist/registry/index.mjs +1 -1
- package/dist/{resourceToTools-DNNWnZtx.mjs → resourceToTools-O_HwWXFa.mjs} +1 -1
- package/dist/rpc/index.d.mts +1 -1
- package/dist/scope/index.d.mts +2 -2
- package/dist/testing/index.d.mts +2 -2
- package/dist/testing/index.mjs +1 -1
- package/dist/types/index.d.mts +4 -4
- package/dist/{types-BlOuKTPw.d.mts → types-Bg2X42_m.d.mts} +30 -9
- package/dist/{types-BoaZHr-2.d.mts → types-CVC4HOKi.d.mts} +1 -1
- package/dist/{types-D3b7hA00.d.mts → types-CcG4avic.d.mts} +1 -1
- package/dist/utils/index.d.mts +43 -5
- package/dist/utils/index.mjs +3 -3
- package/dist/{utils-7sJ8X83I.mjs → utils-yYT3HDXt.mjs} +65 -1
- package/package.json +8 -8
- package/skills/arc/SKILL.md +101 -6
- package/skills/arc/references/mcp.md +37 -0
- /package/dist/{EventTransport-CLXJUzyT.d.mts → EventTransport-CinyO7zQ.d.mts} +0 -0
- /package/dist/{caching-CHH-iHs3.mjs → caching-CjybdRwx.mjs} +0 -0
- /package/dist/{circuitBreaker-BGVoB1hD.d.mts → circuitBreaker-CvXkjfrW.d.mts} +0 -0
- /package/dist/{elevation-UJO3-NvX.d.mts → elevation-s5ykdNHr.d.mts} +0 -0
- /package/dist/{errorHandler-Cw34h_om.mjs → errorHandler-mzqk4cGl.mjs} +0 -0
- /package/dist/{errors-BI8kEKsO.d.mts → errors-Bmn3eZT6.d.mts} +0 -0
- /package/dist/{eventPlugin-XijlQmlL.mjs → eventPlugin-D91S2YF4.mjs} +0 -0
- /package/dist/{externalPaths-BQ8QijNH.d.mts → externalPaths-Bapitwvd.d.mts} +0 -0
- /package/dist/{fields-DoeDgh2b.d.mts → fields-DC4So2M2.d.mts} +0 -0
- /package/dist/{interface-CkkWm5uR.d.mts → interface-B-pe8fhj.d.mts} +0 -0
- /package/dist/{interface-bpoLKKqx.d.mts → interface-DplgQO2e.d.mts} +0 -0
- /package/dist/{metrics-DuhiSEZI.mjs → metrics-TuOmguhi.mjs} +0 -0
- /package/dist/{mongodb-5Ff3w8jy.mjs → mongodb-B5O6xaW1.mjs} +0 -0
- /package/dist/{pluralize-BneOJkpi.mjs → pluralize-A0tWEl1K.mjs} +0 -0
- /package/dist/{replyHelpers-CXtJDAZ0.mjs → replyHelpers-BLojtuvR.mjs} +0 -0
- /package/dist/{sessionManager-BkzVU8h2.d.mts → sessionManager-D-oNWHz3.d.mts} +0 -0
- /package/dist/{sse-CD5Hghpu.mjs → sse-CJpt7LGI.mjs} +0 -0
- /package/dist/{tracing-xqXzWeaf.d.mts → tracing-DxjKk7eW.d.mts} +0 -0
- /package/dist/{types-CN6JvmYz.d.mts → types-C72d3NDn.d.mts} +0 -0
- /package/dist/{versioning-CPU_5Xfs.mjs → versioning-Cm8qoFDg.mjs} +0 -0
|
@@ -41,13 +41,12 @@ function slugLookupPreset(options = {}) {
|
|
|
41
41
|
const { slugField = "slug" } = options;
|
|
42
42
|
return {
|
|
43
43
|
name: "slugLookup",
|
|
44
|
-
|
|
44
|
+
routes: (permissions) => [{
|
|
45
45
|
method: "GET",
|
|
46
46
|
path: `/slug/:${slugField}`,
|
|
47
47
|
handler: "getBySlug",
|
|
48
48
|
summary: "Get by slug",
|
|
49
49
|
permissions: permissions.get ?? allowPublic(),
|
|
50
|
-
wrapHandler: true,
|
|
51
50
|
operation: "getBySlug"
|
|
52
51
|
}],
|
|
53
52
|
controllerOptions: { slugField }
|
|
@@ -65,13 +64,12 @@ function slugLookupPreset(options = {}) {
|
|
|
65
64
|
function softDeletePreset() {
|
|
66
65
|
return {
|
|
67
66
|
name: "softDelete",
|
|
68
|
-
|
|
67
|
+
routes: (permissions) => [{
|
|
69
68
|
method: "GET",
|
|
70
69
|
path: "/deleted",
|
|
71
70
|
handler: "getDeleted",
|
|
72
71
|
summary: "Get soft-deleted items",
|
|
73
72
|
permissions: permissions.list ?? requireRoles(["admin"]),
|
|
74
|
-
wrapHandler: true,
|
|
75
73
|
operation: "listDeleted"
|
|
76
74
|
}, {
|
|
77
75
|
method: "POST",
|
|
@@ -79,7 +77,6 @@ function softDeletePreset() {
|
|
|
79
77
|
handler: "restore",
|
|
80
78
|
summary: "Restore soft-deleted item",
|
|
81
79
|
permissions: permissions.update ?? requireRoles(["admin"]),
|
|
82
|
-
wrapHandler: true,
|
|
83
80
|
operation: "restore"
|
|
84
81
|
}]
|
|
85
82
|
};
|
|
@@ -153,13 +150,12 @@ function bulkPreset(opts) {
|
|
|
153
150
|
const maxCreateItems = opts?.maxCreateItems ?? 1e3;
|
|
154
151
|
return {
|
|
155
152
|
name: "bulk",
|
|
156
|
-
|
|
153
|
+
routes: (permissions) => {
|
|
157
154
|
const routes = [];
|
|
158
155
|
if (operations.includes("createMany")) routes.push({
|
|
159
156
|
method: "POST",
|
|
160
157
|
path: "/bulk",
|
|
161
158
|
handler: "bulkCreate",
|
|
162
|
-
wrapHandler: true,
|
|
163
159
|
operation: "bulkCreate",
|
|
164
160
|
summary: "Create multiple items",
|
|
165
161
|
permissions: permissions.create ?? requireAuth(),
|
|
@@ -190,7 +186,6 @@ function bulkPreset(opts) {
|
|
|
190
186
|
method: "PATCH",
|
|
191
187
|
path: "/bulk",
|
|
192
188
|
handler: "bulkUpdate",
|
|
193
|
-
wrapHandler: true,
|
|
194
189
|
operation: "bulkUpdate",
|
|
195
190
|
summary: "Update multiple items matching filter",
|
|
196
191
|
permissions: permissions.update ?? requireAuth(),
|
|
@@ -222,7 +217,6 @@ function bulkPreset(opts) {
|
|
|
222
217
|
method: "DELETE",
|
|
223
218
|
path: "/bulk",
|
|
224
219
|
handler: "bulkDelete",
|
|
225
|
-
wrapHandler: true,
|
|
226
220
|
operation: "bulkDelete",
|
|
227
221
|
summary: "Delete multiple items matching filter",
|
|
228
222
|
permissions: permissions.delete ?? requireAuth(),
|
|
@@ -259,13 +253,12 @@ function treePreset(options = {}) {
|
|
|
259
253
|
const { parentField = "parent" } = options;
|
|
260
254
|
return {
|
|
261
255
|
name: "tree",
|
|
262
|
-
|
|
256
|
+
routes: (permissions) => [{
|
|
263
257
|
method: "GET",
|
|
264
258
|
path: "/tree",
|
|
265
259
|
handler: "getTree",
|
|
266
260
|
summary: "Get hierarchical tree",
|
|
267
261
|
permissions: permissions.list ?? allowPublic(),
|
|
268
|
-
wrapHandler: true,
|
|
269
262
|
operation: "getTree"
|
|
270
263
|
}, {
|
|
271
264
|
method: "GET",
|
|
@@ -273,7 +266,6 @@ function treePreset(options = {}) {
|
|
|
273
266
|
handler: "getChildren",
|
|
274
267
|
summary: "Get children of parent",
|
|
275
268
|
permissions: permissions.list ?? allowPublic(),
|
|
276
|
-
wrapHandler: true,
|
|
277
269
|
operation: "getChildren"
|
|
278
270
|
}],
|
|
279
271
|
controllerOptions: { parentField }
|
|
@@ -341,8 +333,8 @@ function validatePresetCombination(presets) {
|
|
|
341
333
|
const routeMap = /* @__PURE__ */ new Map();
|
|
342
334
|
for (const preset of presets) {
|
|
343
335
|
const name = preset.name ?? "unknown";
|
|
344
|
-
const
|
|
345
|
-
for (const route of
|
|
336
|
+
const presetRoutes = preset.routes ? typeof preset.routes === "function" ? preset.routes({}) : preset.routes : [];
|
|
337
|
+
for (const route of presetRoutes) {
|
|
346
338
|
const key = `${route.method} ${route.path}`;
|
|
347
339
|
const existing = routeMap.get(key);
|
|
348
340
|
if (existing) conflicts.push({
|
|
@@ -371,7 +363,7 @@ function applyPresets(config, presets = []) {
|
|
|
371
363
|
* Resolve preset input to PresetResult
|
|
372
364
|
*/
|
|
373
365
|
function resolvePresetInput(preset) {
|
|
374
|
-
if (typeof preset === "object" && ("middlewares" in preset || "
|
|
366
|
+
if (typeof preset === "object" && ("middlewares" in preset || "routes" in preset)) return preset;
|
|
375
367
|
if (typeof preset === "object" && "name" in preset) {
|
|
376
368
|
const { name, ...options } = preset;
|
|
377
369
|
return resolvePreset(name, options);
|
|
@@ -383,9 +375,9 @@ function resolvePresetInput(preset) {
|
|
|
383
375
|
*/
|
|
384
376
|
function mergePreset(config, preset) {
|
|
385
377
|
const result = { ...config };
|
|
386
|
-
if (preset.
|
|
387
|
-
const
|
|
388
|
-
result.
|
|
378
|
+
if (preset.routes) {
|
|
379
|
+
const resolved = typeof preset.routes === "function" ? preset.routes(config.permissions ?? {}) : preset.routes;
|
|
380
|
+
result.routes = [...result.routes ?? [], ...resolved];
|
|
389
381
|
}
|
|
390
382
|
if (preset.middlewares) {
|
|
391
383
|
result.middlewares = result.middlewares ?? {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as EventTransport, n as EventHandler, r as EventLogger, t as DomainEvent } from "./EventTransport-
|
|
1
|
+
import { i as EventTransport, n as EventHandler, r as EventLogger, t as DomainEvent } from "./EventTransport-CinyO7zQ.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/events/transports/redis-stream.d.ts
|
|
4
4
|
interface RedisStreamLike {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Gt as RegisterOptions, H as IntrospectionPluginOptions, Kt as ResourceRegistry } from "../interface-
|
|
1
|
+
import { Gt as RegisterOptions, H as IntrospectionPluginOptions, Kt as ResourceRegistry } from "../interface-BVuMfeVv.mjs";
|
|
2
2
|
import { FastifyPluginAsync } from "fastify";
|
|
3
3
|
|
|
4
4
|
//#region src/registry/introspectionPlugin.d.ts
|
package/dist/registry/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { n as introspectionPlugin_default, t as introspectionPlugin } from "../registry-B0Wl7uVV.mjs";
|
|
2
|
-
import { t as ResourceRegistry } from "../ResourceRegistry-
|
|
2
|
+
import { t as ResourceRegistry } from "../ResourceRegistry-Dq3_zBQP.mjs";
|
|
3
3
|
export { ResourceRegistry, introspectionPlugin_default as introspectionPlugin, introspectionPlugin as introspectionPluginFn };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as BaseController } from "./BaseController-DAGGc5Xn.mjs";
|
|
2
2
|
import { n as normalizePermissionResult } from "./applyPermissionResult-D6GPMsvh.mjs";
|
|
3
|
-
import { t as pluralize } from "./pluralize-
|
|
3
|
+
import { t as pluralize } from "./pluralize-A0tWEl1K.mjs";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
//#region src/integrations/mcp/createMcpServer.ts
|
|
6
6
|
/**
|
package/dist/rpc/index.d.mts
CHANGED
package/dist/scope/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { _ as isAuthenticated, a as getClientId, b as isOrgInScope, c as getOrgRoles, d as getScopeContextMap, f as getServiceScopes, g as hasOrgAccess, h as getUserRoles, i as getAncestorOrgIds, l as getRequestScope, m as getUserId, n as PUBLIC_SCOPE, o as getOrgContext, p as getTeamId, r as RequestScope, s as getOrgId, t as AUTHENTICATED_SCOPE, u as getScopeContext, v as isElevated, x as isService, y as isMember } from "../types-
|
|
2
|
-
import { i as elevationPlugin, n as ElevationOptions, r as _default, t as ElevationEvent } from "../elevation-
|
|
1
|
+
import { _ as isAuthenticated, a as getClientId, b as isOrgInScope, c as getOrgRoles, d as getScopeContextMap, f as getServiceScopes, g as hasOrgAccess, h as getUserRoles, i as getAncestorOrgIds, l as getRequestScope, m as getUserId, n as PUBLIC_SCOPE, o as getOrgContext, p as getTeamId, r as RequestScope, s as getOrgId, t as AUTHENTICATED_SCOPE, u as getScopeContext, v as isElevated, x as isService, y as isMember } from "../types-C72d3NDn.mjs";
|
|
2
|
+
import { i as elevationPlugin, n as ElevationOptions, r as _default, t as ElevationEvent } from "../elevation-s5ykdNHr.mjs";
|
|
3
3
|
import { FastifyReply, FastifyRequest } from "fastify";
|
|
4
4
|
|
|
5
5
|
//#region src/scope/rateLimitKey.d.ts
|
package/dist/testing/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Zt as CrudRepository, m as AnyRecord, qt as ResourceDefinition } from "../interface-
|
|
2
|
-
import { d as ResourceLike, r as CreateAppOptions } from "../types-
|
|
1
|
+
import { Zt as CrudRepository, m as AnyRecord, qt as ResourceDefinition } from "../interface-BVuMfeVv.mjs";
|
|
2
|
+
import { d as ResourceLike, r as CreateAppOptions } from "../types-Bg2X42_m.mjs";
|
|
3
3
|
import Fastify, { FastifyInstance, FastifyServerOptions } from "fastify";
|
|
4
4
|
import { Connection } from "mongoose";
|
|
5
5
|
import { Mock } from "vitest";
|
package/dist/testing/index.mjs
CHANGED
|
@@ -1796,7 +1796,7 @@ function runEventTests(resourceName, displayName, events) {
|
|
|
1796
1796
|
* ```
|
|
1797
1797
|
*/
|
|
1798
1798
|
async function createTestApp(options = {}) {
|
|
1799
|
-
const { createApp } = await import("../createApp-
|
|
1799
|
+
const { createApp } = await import("../createApp-BOYjBgdI.mjs").then((n) => n.r);
|
|
1800
1800
|
const { useInMemoryDb = true, mongoUri: providedMongoUri, ...appOptions } = options;
|
|
1801
1801
|
const defaultAuth = {
|
|
1802
1802
|
type: "jwt",
|
package/dist/types/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { _ as isAuthenticated, c as getOrgRoles, g as hasOrgAccess, n as PUBLIC_SCOPE, p as getTeamId, r as RequestScope, s as getOrgId, t as AUTHENTICATED_SCOPE, v as isElevated, y as isMember } from "../types-
|
|
2
|
-
import { $ as PresetFunction, $t as DeleteOptions, A as EventsDecorator, B as InferResourceDoc, Bt as FastifyHandler, C as ConfigError, Ct as TypedResourceConfig, D as CrudRouterOptions, Dt as ValidationResult, E as CrudRouteKey, Et as ValidateOptions, F as GracefulShutdownOptions, G as LookupOption, H as IntrospectionPluginOptions, Ht as IControllerResponse, I as HealthCheck, J as ObjectId, K as MiddlewareConfig, L as HealthOptions, M as FastifyWithAuth, N as FastifyWithDecorators, O as CrudSchemas, Ot as envelope, P as FieldRule, Q as PopulateOption, Qt as DeleteManyResult, R as InferAdapterDoc, Rt as ControllerHandler, S as AuthenticatorContext, St as TypedRepository, T as CrudController, Tt as UserOrganization, U as JWTPayload, Ut as IRequestContext, V as IntrospectionData, Vt as IController, W as JwtContext, Wt as RouteHandler, X as OwnershipCheck, Xt as BulkWriteResult, Y as OpenApiSchemas, Yt as BulkWriteOperation, Z as ParsedQuery, Zt as CrudRepository, _ as ArcInternalMetadata, _t as RouteMcpConfig, an as PaginationParams, at as RegistryStats, b as AuthPluginOptions, bt as TokenPair, cn as RepositorySession, ct as RequestWithExtras, d as ActionHandlerFn, dt as ResourceHookContext, en as DeleteResult, et as PresetHook, f as ActionsMap, ft as ResourceHooks, g as ArcDecorator, gt as RouteHandlerMethod, h as ApiResponse, ht as RouteDefinition, in as PaginatedResult, it as RegistryEntry, j as FastifyRequestExtras, jt as BaseControllerOptions, k as EventDefinition, kt as getUserId, l as ActionDefinition, ln as UpdateManyResult, lt as ResourceCacheConfig, m as AnyRecord, mt as ResourcePermissions, nn as KeysetPaginatedResult, nt as QueryParserInterface, on as PaginationResult, ot as RequestContext, p as AdditionalRoute, pt as ResourceMetadata, q as MiddlewareHandler, rn as OffsetPaginatedResult, rt as RateLimitConfig, sn as QueryOptions, st as RequestIdOptions, tn as InferDoc, tt as PresetResult, u as ActionEntry, un as WriteOptions, ut as ResourceConfig, v as ArcRequest, vt as RouteSchemaOptions, w as ControllerQueryOptions, wt as UserLike, x as Authenticator, xt as TypedController, y as AuthHelpers, yt as ServiceContext, z as InferDocType, zt as ControllerLike } from "../interface-
|
|
3
|
-
import { i as UserBase, n as PermissionContext, r as PermissionResult, t as PermissionCheck } from "../types-
|
|
4
|
-
import { n as ElevationOptions, t as ElevationEvent } from "../elevation-
|
|
1
|
+
import { _ as isAuthenticated, c as getOrgRoles, g as hasOrgAccess, n as PUBLIC_SCOPE, p as getTeamId, r as RequestScope, s as getOrgId, t as AUTHENTICATED_SCOPE, v as isElevated, y as isMember } from "../types-C72d3NDn.mjs";
|
|
2
|
+
import { $ as PresetFunction, $t as DeleteOptions, A as EventsDecorator, B as InferResourceDoc, Bt as FastifyHandler, C as ConfigError, Ct as TypedResourceConfig, D as CrudRouterOptions, Dt as ValidationResult, E as CrudRouteKey, Et as ValidateOptions, F as GracefulShutdownOptions, G as LookupOption, H as IntrospectionPluginOptions, Ht as IControllerResponse, I as HealthCheck, J as ObjectId, K as MiddlewareConfig, L as HealthOptions, M as FastifyWithAuth, N as FastifyWithDecorators, O as CrudSchemas, Ot as envelope, P as FieldRule, Q as PopulateOption, Qt as DeleteManyResult, R as InferAdapterDoc, Rt as ControllerHandler, S as AuthenticatorContext, St as TypedRepository, T as CrudController, Tt as UserOrganization, U as JWTPayload, Ut as IRequestContext, V as IntrospectionData, Vt as IController, W as JwtContext, Wt as RouteHandler, X as OwnershipCheck, Xt as BulkWriteResult, Y as OpenApiSchemas, Yt as BulkWriteOperation, Z as ParsedQuery, Zt as CrudRepository, _ as ArcInternalMetadata, _t as RouteMcpConfig, an as PaginationParams, at as RegistryStats, b as AuthPluginOptions, bt as TokenPair, cn as RepositorySession, ct as RequestWithExtras, d as ActionHandlerFn, dt as ResourceHookContext, en as DeleteResult, et as PresetHook, f as ActionsMap, ft as ResourceHooks, g as ArcDecorator, gt as RouteHandlerMethod, h as ApiResponse, ht as RouteDefinition, in as PaginatedResult, it as RegistryEntry, j as FastifyRequestExtras, jt as BaseControllerOptions, k as EventDefinition, kt as getUserId, l as ActionDefinition, ln as UpdateManyResult, lt as ResourceCacheConfig, m as AnyRecord, mt as ResourcePermissions, nn as KeysetPaginatedResult, nt as QueryParserInterface, on as PaginationResult, ot as RequestContext, p as AdditionalRoute, pt as ResourceMetadata, q as MiddlewareHandler, rn as OffsetPaginatedResult, rt as RateLimitConfig, sn as QueryOptions, st as RequestIdOptions, tn as InferDoc, tt as PresetResult, u as ActionEntry, un as WriteOptions, ut as ResourceConfig, v as ArcRequest, vt as RouteSchemaOptions, w as ControllerQueryOptions, wt as UserLike, x as Authenticator, xt as TypedController, y as AuthHelpers, yt as ServiceContext, z as InferDocType, zt as ControllerLike } from "../interface-BVuMfeVv.mjs";
|
|
3
|
+
import { i as UserBase, n as PermissionContext, r as PermissionResult, t as PermissionCheck } from "../types-CVC4HOKi.mjs";
|
|
4
|
+
import { n as ElevationOptions, t as ElevationEvent } from "../elevation-s5ykdNHr.mjs";
|
|
5
5
|
export { AUTHENTICATED_SCOPE, ActionDefinition, ActionEntry, ActionHandlerFn, ActionsMap, AdditionalRoute, AnyRecord, ApiResponse, ArcDecorator, ArcInternalMetadata, ArcRequest, AuthHelpers, AuthPluginOptions, Authenticator, AuthenticatorContext, BaseControllerOptions, BulkWriteOperation, BulkWriteResult, ConfigError, ControllerHandler, ControllerLike, ControllerQueryOptions, CrudController, CrudRepository, CrudRouteKey, CrudRouterOptions, CrudSchemas, DeleteManyResult, DeleteOptions, DeleteResult, ElevationEvent, ElevationOptions, EventDefinition, EventsDecorator, FastifyHandler, FastifyRequestExtras, FastifyWithAuth, FastifyWithDecorators, FieldRule, GracefulShutdownOptions, HealthCheck, HealthOptions, IController, IControllerResponse, IRequestContext, InferAdapterDoc, InferDoc, InferDocType, InferResourceDoc, IntrospectionData, IntrospectionPluginOptions, JWTPayload, JwtContext, KeysetPaginatedResult, LookupOption, MiddlewareConfig, MiddlewareHandler, ObjectId, OffsetPaginatedResult, OpenApiSchemas, OwnershipCheck, PUBLIC_SCOPE, PaginatedResult, PaginationParams, PaginationResult, ParsedQuery, PermissionCheck, PermissionContext, PermissionResult, PopulateOption, PresetFunction, PresetHook, PresetResult, QueryOptions, QueryParserInterface, RateLimitConfig, RegistryEntry, RegistryStats, RepositorySession, RequestContext, RequestIdOptions, RequestScope, RequestWithExtras, ResourceCacheConfig, ResourceConfig, ResourceHookContext, ResourceHooks, ResourceMetadata, ResourcePermissions, RouteDefinition, RouteHandler, RouteHandlerMethod, RouteMcpConfig, RouteSchemaOptions, ServiceContext, TokenPair, TypedController, TypedRepository, TypedResourceConfig, UpdateManyResult, UserBase, UserLike, UserOrganization, ValidateOptions, ValidationResult, WriteOptions, envelope, getOrgId, getOrgRoles, getTeamId, getUserId, hasOrgAccess, isAuthenticated, isElevated, isMember };
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { x as Authenticator } from "./interface-
|
|
2
|
-
import { n as ElevationOptions } from "./elevation-
|
|
3
|
-
import { t as ExternalOpenApiPaths } from "./externalPaths-
|
|
4
|
-
import { i as CacheStore } from "./interface-
|
|
5
|
-
import { r as QueryCachePluginOptions } from "./queryCachePlugin-
|
|
6
|
-
import { i as EventTransport } from "./EventTransport-
|
|
7
|
-
import { t as EventPluginOptions } from "./eventPlugin-
|
|
8
|
-
import {
|
|
9
|
-
import { r as IdempotencyStore } from "./interface-
|
|
1
|
+
import { x as Authenticator } from "./interface-BVuMfeVv.mjs";
|
|
2
|
+
import { n as ElevationOptions } from "./elevation-s5ykdNHr.mjs";
|
|
3
|
+
import { t as ExternalOpenApiPaths } from "./externalPaths-Bapitwvd.mjs";
|
|
4
|
+
import { i as CacheStore } from "./interface-DplgQO2e.mjs";
|
|
5
|
+
import { r as QueryCachePluginOptions } from "./queryCachePlugin-CnTZZTC5.mjs";
|
|
6
|
+
import { i as EventTransport } from "./EventTransport-CinyO7zQ.mjs";
|
|
7
|
+
import { t as EventPluginOptions } from "./eventPlugin-CVxlE6De.mjs";
|
|
8
|
+
import { f as SSEOptions, h as CachingOptions, i as VersioningOptions, l as MetricsOptions, t as ErrorHandlerOptions } from "./errorHandler-CdZDavNH.mjs";
|
|
9
|
+
import { r as IdempotencyStore } from "./interface-B-pe8fhj.mjs";
|
|
10
10
|
import { FastifyInstance, FastifyPluginAsync, FastifyReply, FastifyRequest, FastifyServerOptions } from "fastify";
|
|
11
11
|
|
|
12
12
|
//#region src/factory/loadResources.d.ts
|
|
@@ -664,6 +664,27 @@ interface CreateAppOptions {
|
|
|
664
664
|
* ```
|
|
665
665
|
*/
|
|
666
666
|
resourcePrefix?: string;
|
|
667
|
+
/**
|
|
668
|
+
* Auto-discover resources from a directory instead of passing an explicit
|
|
669
|
+
* `resources` array. Resolves relative to `process.cwd()`.
|
|
670
|
+
*
|
|
671
|
+
* This replaces the common pattern:
|
|
672
|
+
* ```ts
|
|
673
|
+
* resources: await loadResources(import.meta.url)
|
|
674
|
+
* ```
|
|
675
|
+
*
|
|
676
|
+
* When both `resourceDir` and `resources` are provided, `resources` wins
|
|
677
|
+
* (explicit always beats convention).
|
|
678
|
+
*
|
|
679
|
+
* @example
|
|
680
|
+
* ```ts
|
|
681
|
+
* const app = await createApp({
|
|
682
|
+
* resourceDir: 'src/resources',
|
|
683
|
+
* resourcePrefix: '/api/v1',
|
|
684
|
+
* });
|
|
685
|
+
* ```
|
|
686
|
+
*/
|
|
687
|
+
resourceDir?: string;
|
|
667
688
|
/**
|
|
668
689
|
* Custom plugin registration — runs after Arc core (security, auth, events)
|
|
669
690
|
* but before `bootstrap` and `resources`.
|
package/dist/utils/index.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Y as OpenApiSchemas, Z as ParsedQuery, m as AnyRecord, nt as QueryParserInterface } from "../interface-
|
|
2
|
-
import { a as NotFoundError, c as RateLimitError, d as ValidationError, i as ForbiddenError, l as ServiceUnavailableError, m as isArcError, n as ConflictError, o as OrgAccessDeniedError, p as createError, r as ErrorDetails, s as OrgRequiredError, t as ArcError, u as UnauthorizedError } from "../errors-
|
|
3
|
-
import { a as CircuitBreakerStats, c as createCircuitBreakerRegistry, i as CircuitBreakerRegistry, n as CircuitBreakerError, o as CircuitState, r as CircuitBreakerOptions, s as createCircuitBreaker, t as CircuitBreaker } from "../circuitBreaker-
|
|
4
|
-
import { FastifyInstance } from "fastify";
|
|
1
|
+
import { Y as OpenApiSchemas, Z as ParsedQuery, m as AnyRecord, nt as QueryParserInterface } from "../interface-BVuMfeVv.mjs";
|
|
2
|
+
import { a as NotFoundError, c as RateLimitError, d as ValidationError, f as createDomainError, i as ForbiddenError, l as ServiceUnavailableError, m as isArcError, n as ConflictError, o as OrgAccessDeniedError, p as createError, r as ErrorDetails, s as OrgRequiredError, t as ArcError, u as UnauthorizedError } from "../errors-Bmn3eZT6.mjs";
|
|
3
|
+
import { a as CircuitBreakerStats, c as createCircuitBreakerRegistry, i as CircuitBreakerRegistry, n as CircuitBreakerError, o as CircuitState, r as CircuitBreakerOptions, s as createCircuitBreaker, t as CircuitBreaker } from "../circuitBreaker-CvXkjfrW.mjs";
|
|
4
|
+
import { FastifyInstance, FastifyReply, FastifyRequest, RouteHandlerMethod } from "fastify";
|
|
5
5
|
|
|
6
6
|
//#region src/utils/compensation.d.ts
|
|
7
7
|
/**
|
|
@@ -92,6 +92,44 @@ interface CompensationDefinition<TCtx extends Record<string, unknown> = Record<s
|
|
|
92
92
|
}
|
|
93
93
|
declare function defineCompensation<TCtx extends Record<string, unknown> = Record<string, unknown>>(name: string, steps: readonly CompensationStep<TCtx>[]): CompensationDefinition<TCtx>;
|
|
94
94
|
//#endregion
|
|
95
|
+
//#region src/utils/defineGuard.d.ts
|
|
96
|
+
interface GuardConfig<T> {
|
|
97
|
+
/** Unique name — used as the storage key on the request. */
|
|
98
|
+
readonly name: string;
|
|
99
|
+
/**
|
|
100
|
+
* Resolve the guard context from the request. Throw to abort the request
|
|
101
|
+
* (Fastify's error handler will produce the appropriate HTTP response).
|
|
102
|
+
* Return a value to stash it for `from()` extraction.
|
|
103
|
+
*/
|
|
104
|
+
readonly resolve: (req: FastifyRequest, reply: FastifyReply) => T | Promise<T>;
|
|
105
|
+
}
|
|
106
|
+
interface Guard<T> {
|
|
107
|
+
/** Use in `routeGuards` or per-route `preHandler` arrays. */
|
|
108
|
+
readonly preHandler: RouteHandlerMethod;
|
|
109
|
+
/**
|
|
110
|
+
* Extract the resolved context from a request. Throws if the guard
|
|
111
|
+
* hasn't run yet (i.e. not in the preHandler chain).
|
|
112
|
+
*/
|
|
113
|
+
from(req: FastifyRequest): T;
|
|
114
|
+
/** The guard name (for debugging). */
|
|
115
|
+
readonly name: string;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Create a typed guard. See module JSDoc for usage.
|
|
119
|
+
*/
|
|
120
|
+
declare function defineGuard<T>(config: GuardConfig<T>): Guard<T>;
|
|
121
|
+
//#endregion
|
|
122
|
+
//#region src/utils/handleRaw.d.ts
|
|
123
|
+
/**
|
|
124
|
+
* Wrap a raw Fastify handler with Arc's response envelope and error handling.
|
|
125
|
+
*
|
|
126
|
+
* @param handler - Async function that receives `(request, reply)` and returns data.
|
|
127
|
+
* The return value is sent as `{ success: true, data }`. If it returns
|
|
128
|
+
* `undefined` or `null`, `{ success: true }` is sent (no `data` field).
|
|
129
|
+
* @param statusCode - HTTP status code for successful responses (default: 200)
|
|
130
|
+
*/
|
|
131
|
+
declare function handleRaw<T>(handler: (request: FastifyRequest, reply: FastifyReply) => Promise<T>, statusCode?: number): (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
|
|
132
|
+
//#endregion
|
|
95
133
|
//#region src/utils/queryParser.d.ts
|
|
96
134
|
interface ArcQueryParserOptions {
|
|
97
135
|
/** Maximum allowed limit value (default: 1000) */
|
|
@@ -635,4 +673,4 @@ declare function hasEvents(instance: FastifyInstance): instance is FastifyInstan
|
|
|
635
673
|
events: EventsDecorator;
|
|
636
674
|
};
|
|
637
675
|
//#endregion
|
|
638
|
-
export { ArcError, ArcQueryParser, type ArcQueryParserOptions, CircuitBreaker, CircuitBreakerError, type CircuitBreakerOptions, CircuitBreakerRegistry, type CircuitBreakerStats, CircuitState, type CompensationDefinition, type CompensationError, type CompensationHooks, type CompensationResult, type CompensationStep, ConflictError, type ErrorDetails, type EventsDecorator, ForbiddenError, type JsonSchema, NotFoundError, OrgAccessDeniedError, OrgRequiredError, RateLimitError, ServiceUnavailableError, type StateMachine, type TransitionConfig, UnauthorizedError, ValidationError, convertOpenApiSchemas, convertRouteSchema, createCircuitBreaker, createCircuitBreakerRegistry, createError, createQueryParser, createStateMachine, defineCompensation, deleteResponse, errorResponseSchema, getDefaultCrudSchemas, getListQueryParams, hasEvents, isArcError, isJsonSchema, isZodSchema, itemResponse, listResponse, mutationResponse, paginationSchema, queryParams, responses, successResponseSchema, toJsonSchema, withCompensation, wrapResponse };
|
|
676
|
+
export { ArcError, ArcQueryParser, type ArcQueryParserOptions, CircuitBreaker, CircuitBreakerError, type CircuitBreakerOptions, CircuitBreakerRegistry, type CircuitBreakerStats, CircuitState, type CompensationDefinition, type CompensationError, type CompensationHooks, type CompensationResult, type CompensationStep, ConflictError, type ErrorDetails, type EventsDecorator, ForbiddenError, type Guard, type GuardConfig, type JsonSchema, NotFoundError, OrgAccessDeniedError, OrgRequiredError, RateLimitError, ServiceUnavailableError, type StateMachine, type TransitionConfig, UnauthorizedError, ValidationError, convertOpenApiSchemas, convertRouteSchema, createCircuitBreaker, createCircuitBreakerRegistry, createDomainError, createError, createQueryParser, createStateMachine, defineCompensation, defineGuard, deleteResponse, errorResponseSchema, getDefaultCrudSchemas, getListQueryParams, handleRaw, hasEvents, isArcError, isJsonSchema, isZodSchema, itemResponse, listResponse, mutationResponse, paginationSchema, queryParams, responses, successResponseSchema, toJsonSchema, withCompensation, wrapResponse };
|
package/dist/utils/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { n as createQueryParser, t as ArcQueryParser } from "../queryParser-CgCtsjti.mjs";
|
|
2
2
|
import { a as toJsonSchema, i as isZodSchema, n as convertRouteSchema, r as isJsonSchema, t as convertOpenApiSchemas } from "../schemaConverter-OxfCshus.mjs";
|
|
3
3
|
import { a as createCircuitBreaker, i as CircuitState, n as CircuitBreakerError, o as createCircuitBreakerRegistry, r as CircuitBreakerRegistry, t as CircuitBreaker } from "../circuitBreaker-cmi5XDv5.mjs";
|
|
4
|
-
import { a as getListQueryParams, c as mutationResponse, d as responses, f as successResponseSchema, h as
|
|
5
|
-
import { a as OrgAccessDeniedError, c as ServiceUnavailableError, f as createError, i as NotFoundError, l as UnauthorizedError, n as ConflictError, o as OrgRequiredError, p as isArcError, r as ForbiddenError, s as RateLimitError, t as ArcError, u as ValidationError } from "../errors-BF2bIOIS.mjs";
|
|
4
|
+
import { _ as withCompensation, a as getListQueryParams, c as mutationResponse, d as responses, f as successResponseSchema, g as defineCompensation, h as defineGuard, i as getDefaultCrudSchemas, l as paginationSchema, m as handleRaw, n as deleteResponse, o as itemResponse, p as wrapResponse, r as errorResponseSchema, s as listResponse, t as createStateMachine, u as queryParams } from "../utils-yYT3HDXt.mjs";
|
|
5
|
+
import { a as OrgAccessDeniedError, c as ServiceUnavailableError, d as createDomainError, f as createError, i as NotFoundError, l as UnauthorizedError, n as ConflictError, o as OrgRequiredError, p as isArcError, r as ForbiddenError, s as RateLimitError, t as ArcError, u as ValidationError } from "../errors-BF2bIOIS.mjs";
|
|
6
6
|
import { t as hasEvents } from "../typeGuards-CcFZXgU7.mjs";
|
|
7
|
-
export { ArcError, ArcQueryParser, CircuitBreaker, CircuitBreakerError, CircuitBreakerRegistry, CircuitState, ConflictError, ForbiddenError, NotFoundError, OrgAccessDeniedError, OrgRequiredError, RateLimitError, ServiceUnavailableError, UnauthorizedError, ValidationError, convertOpenApiSchemas, convertRouteSchema, createCircuitBreaker, createCircuitBreakerRegistry, createError, createQueryParser, createStateMachine, defineCompensation, deleteResponse, errorResponseSchema, getDefaultCrudSchemas, getListQueryParams, hasEvents, isArcError, isJsonSchema, isZodSchema, itemResponse, listResponse, mutationResponse, paginationSchema, queryParams, responses, successResponseSchema, toJsonSchema, withCompensation, wrapResponse };
|
|
7
|
+
export { ArcError, ArcQueryParser, CircuitBreaker, CircuitBreakerError, CircuitBreakerRegistry, CircuitState, ConflictError, ForbiddenError, NotFoundError, OrgAccessDeniedError, OrgRequiredError, RateLimitError, ServiceUnavailableError, UnauthorizedError, ValidationError, convertOpenApiSchemas, convertRouteSchema, createCircuitBreaker, createCircuitBreakerRegistry, createDomainError, createError, createQueryParser, createStateMachine, defineCompensation, defineGuard, deleteResponse, errorResponseSchema, getDefaultCrudSchemas, getListQueryParams, handleRaw, hasEvents, isArcError, isJsonSchema, isZodSchema, itemResponse, listResponse, mutationResponse, paginationSchema, queryParams, responses, successResponseSchema, toJsonSchema, withCompensation, wrapResponse };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { t as ArcError } from "./errors-BF2bIOIS.mjs";
|
|
1
2
|
//#region src/utils/compensation.ts
|
|
2
3
|
/**
|
|
3
4
|
* Run steps in order with automatic compensation on failure.
|
|
@@ -69,6 +70,69 @@ function defineCompensation(name, steps) {
|
|
|
69
70
|
};
|
|
70
71
|
}
|
|
71
72
|
//#endregion
|
|
73
|
+
//#region src/utils/defineGuard.ts
|
|
74
|
+
/** Hidden property key for guard context storage on the request object. */
|
|
75
|
+
const GUARD_STORE_KEY = "__arcGuardContext";
|
|
76
|
+
/**
|
|
77
|
+
* Create a typed guard. See module JSDoc for usage.
|
|
78
|
+
*/
|
|
79
|
+
function defineGuard(config) {
|
|
80
|
+
const { name, resolve } = config;
|
|
81
|
+
const preHandler = async (req, reply) => {
|
|
82
|
+
const ctx = await resolve(req, reply);
|
|
83
|
+
if (!reply.sent) {
|
|
84
|
+
const store = req[GUARD_STORE_KEY] ?? {};
|
|
85
|
+
store[name] = ctx;
|
|
86
|
+
req[GUARD_STORE_KEY] = store;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
return {
|
|
90
|
+
preHandler,
|
|
91
|
+
name,
|
|
92
|
+
from(req) {
|
|
93
|
+
const store = req[GUARD_STORE_KEY];
|
|
94
|
+
if (!store || !(name in store)) throw new Error(`Guard '${name}' not resolved on this request. Add it to routeGuards or the route's preHandler array.`);
|
|
95
|
+
return store[name];
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
//#endregion
|
|
100
|
+
//#region src/utils/handleRaw.ts
|
|
101
|
+
/**
|
|
102
|
+
* Wrap a raw Fastify handler with Arc's response envelope and error handling.
|
|
103
|
+
*
|
|
104
|
+
* @param handler - Async function that receives `(request, reply)` and returns data.
|
|
105
|
+
* The return value is sent as `{ success: true, data }`. If it returns
|
|
106
|
+
* `undefined` or `null`, `{ success: true }` is sent (no `data` field).
|
|
107
|
+
* @param statusCode - HTTP status code for successful responses (default: 200)
|
|
108
|
+
*/
|
|
109
|
+
function handleRaw(handler, statusCode = 200) {
|
|
110
|
+
return async (request, reply) => {
|
|
111
|
+
try {
|
|
112
|
+
const result = await handler(request, reply);
|
|
113
|
+
if (reply.sent) return;
|
|
114
|
+
if (result === void 0 || result === null) reply.code(statusCode).send({ success: true });
|
|
115
|
+
else reply.code(statusCode).send({
|
|
116
|
+
success: true,
|
|
117
|
+
data: result
|
|
118
|
+
});
|
|
119
|
+
} catch (err) {
|
|
120
|
+
if (reply.sent) return;
|
|
121
|
+
if (err instanceof ArcError) {
|
|
122
|
+
reply.code(err.statusCode).send(err.toJSON());
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const error = err;
|
|
126
|
+
const code = error.statusCode ?? error.status ?? 500;
|
|
127
|
+
reply.code(code).send({
|
|
128
|
+
success: false,
|
|
129
|
+
error: error.message ?? "Internal server error",
|
|
130
|
+
...error.code && { code: error.code }
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
//#endregion
|
|
72
136
|
//#region src/utils/responseSchemas.ts
|
|
73
137
|
/**
|
|
74
138
|
* Base success response schema
|
|
@@ -579,4 +643,4 @@ function createStateMachine(name, transitions = {}, options = {}) {
|
|
|
579
643
|
};
|
|
580
644
|
}
|
|
581
645
|
//#endregion
|
|
582
|
-
export { getListQueryParams as a, mutationResponse as c, responses as d, successResponseSchema as f,
|
|
646
|
+
export { withCompensation as _, getListQueryParams as a, mutationResponse as c, responses as d, successResponseSchema as f, defineCompensation as g, defineGuard as h, getDefaultCrudSchemas as i, paginationSchema as l, handleRaw as m, deleteResponse as n, itemResponse as o, wrapResponse as p, errorResponseSchema as r, listResponse as s, createStateMachine as t, queryParams as u };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@classytic/arc",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.4",
|
|
4
4
|
"description": "Resource-oriented backend framework for Fastify — clean, minimal, powerful, tree-shakable",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -340,19 +340,18 @@
|
|
|
340
340
|
},
|
|
341
341
|
"dependencies": {
|
|
342
342
|
"fastify-plugin": "^5.0.1",
|
|
343
|
-
"qs": "^6.
|
|
343
|
+
"qs": "^6.15.1",
|
|
344
344
|
"secure-json-parse": "^4.1.0"
|
|
345
345
|
},
|
|
346
346
|
"devDependencies": {
|
|
347
|
-
"@better-auth/mongo-adapter": "^1.6.
|
|
348
|
-
"@biomejs/biome": "^2.4.
|
|
349
|
-
"ajv": "^8.18.0",
|
|
347
|
+
"@better-auth/mongo-adapter": "^1.6.2",
|
|
348
|
+
"@biomejs/biome": "^2.4.11",
|
|
350
349
|
"@classytic/mongokit": "file:../../packages/mongokit/classytic-mongokit-3.6.0.tgz",
|
|
351
350
|
"@classytic/streamline": "^2.1.0",
|
|
352
351
|
"@fastify/cors": "^11.2.0",
|
|
353
352
|
"@fastify/helmet": "^13.0.2",
|
|
354
353
|
"@fastify/jwt": "^10.0.0",
|
|
355
|
-
"@fastify/multipart": "^
|
|
354
|
+
"@fastify/multipart": "^10.0.0",
|
|
356
355
|
"@fastify/rate-limit": "^10.3.0",
|
|
357
356
|
"@fastify/sensible": "^6.0.4",
|
|
358
357
|
"@fastify/type-provider-typebox": "^6.0.0",
|
|
@@ -363,10 +362,11 @@
|
|
|
363
362
|
"@types/node": "^22.10.0",
|
|
364
363
|
"@types/qs": "^6.14.0",
|
|
365
364
|
"@vitest/coverage-v8": "^3.2.4",
|
|
366
|
-
"
|
|
365
|
+
"ajv": "^8.18.0",
|
|
366
|
+
"better-auth": "^1.6.2",
|
|
367
367
|
"fastify-raw-body": "^5.0.0",
|
|
368
368
|
"jsonwebtoken": "^9.0.0",
|
|
369
|
-
"knip": "^6.
|
|
369
|
+
"knip": "^6.4.1",
|
|
370
370
|
"mongodb": "^7.1.0",
|
|
371
371
|
"mongodb-memory-server": "^11.0.1",
|
|
372
372
|
"mongoose": "^9.4.1",
|
package/skills/arc/SKILL.md
CHANGED
|
@@ -8,7 +8,7 @@ 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.8.
|
|
11
|
+
version: 2.8.1
|
|
12
12
|
license: MIT
|
|
13
13
|
metadata:
|
|
14
14
|
author: Classytic
|
|
@@ -88,17 +88,28 @@ const productResource = defineResource({
|
|
|
88
88
|
delete: requireRoles(['admin']),
|
|
89
89
|
},
|
|
90
90
|
cache: { staleTime: 30, gcTime: 300, tags: ['catalog'] },
|
|
91
|
-
additionalRoutes: [
|
|
92
|
-
{ method: 'GET', path: '/featured', handler: 'getFeatured', permissions: allowPublic(), wrapHandler: true },
|
|
93
|
-
],
|
|
94
91
|
|
|
95
|
-
// v2.8:
|
|
92
|
+
// v2.8.1: routeGuards — auto-apply to ALL routes (CRUD + custom + preset)
|
|
93
|
+
routeGuards: [modeGuard, orgGuard.preHandler],
|
|
94
|
+
|
|
95
|
+
// v2.8.1: fieldRules constraints → auto-map to OpenAPI + AJV validation
|
|
96
|
+
schemaOptions: {
|
|
97
|
+
fieldRules: {
|
|
98
|
+
name: { minLength: 2, maxLength: 200, description: 'Product name' },
|
|
99
|
+
price: { min: 0, max: 100000 },
|
|
100
|
+
sku: { pattern: '^[A-Z]{3}-\\d{3}$' },
|
|
101
|
+
status: { enum: ['draft', 'active', 'archived'] },
|
|
102
|
+
deletedAt: { systemManaged: true },
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
// Custom routes (compose with presets — softDelete adds /deleted, /:id/restore)
|
|
96
107
|
routes: [
|
|
97
108
|
{ method: 'GET', path: '/stats', handler: 'getStats', permissions: auth() },
|
|
98
109
|
{ method: 'POST', path: '/webhook', handler: webhookFn, raw: true, permissions: auth() },
|
|
99
110
|
],
|
|
100
111
|
|
|
101
|
-
//
|
|
112
|
+
// Actions (replaces onRegister + createActionRouter)
|
|
102
113
|
actions: {
|
|
103
114
|
approve: async (id, data, req) => service.approve(id, req.user._id),
|
|
104
115
|
cancel: {
|
|
@@ -112,8 +123,70 @@ const productResource = defineResource({
|
|
|
112
123
|
|
|
113
124
|
await fastify.register(productResource.toPlugin());
|
|
114
125
|
// Auto-generates: GET /, GET /:id, POST /, PATCH /:id, DELETE /:id
|
|
126
|
+
// + softDelete preset adds: GET /deleted, POST /:id/restore
|
|
115
127
|
```
|
|
116
128
|
|
|
129
|
+
## routeGuards + defineGuard (v2.8.1)
|
|
130
|
+
|
|
131
|
+
Resource-level guards that apply to **every** route (CRUD + custom + preset):
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
import { defineGuard } from '@classytic/arc/utils';
|
|
135
|
+
import type { RouteHandlerMethod } from '@classytic/arc';
|
|
136
|
+
|
|
137
|
+
// Simple guard — reject if condition fails
|
|
138
|
+
const modeGuard: RouteHandlerMethod = async (req, reply) => {
|
|
139
|
+
if (!req.headers['x-mode']) {
|
|
140
|
+
reply.code(403).send({ error: 'Mode header required' });
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// Typed guard — resolve context once, extract anywhere
|
|
145
|
+
const orgGuard = defineGuard({
|
|
146
|
+
name: 'org',
|
|
147
|
+
resolve: (req) => {
|
|
148
|
+
const orgId = req.headers['x-org-id'] as string;
|
|
149
|
+
if (!orgId) throw new Error('Missing x-org-id');
|
|
150
|
+
return { orgId, actorId: req.user?.id ?? 'system' };
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
defineResource({
|
|
155
|
+
name: 'procurement',
|
|
156
|
+
routeGuards: [modeGuard, orgGuard.preHandler], // all routes protected
|
|
157
|
+
routes: [{
|
|
158
|
+
method: 'GET', path: '/summary', raw: true, permissions: auth(),
|
|
159
|
+
handler: async (req, reply) => {
|
|
160
|
+
const { orgId } = orgGuard.from(req); // typed, no re-computation
|
|
161
|
+
reply.send({ orgId, count: await Model.countDocuments() });
|
|
162
|
+
},
|
|
163
|
+
}],
|
|
164
|
+
// ...
|
|
165
|
+
});
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**Execution order:** auth → permissions → cache/idempotency → `routeGuards` → per-route `preHandler`
|
|
169
|
+
|
|
170
|
+
## fieldRules → OpenAPI + AJV (v2.8.1)
|
|
171
|
+
|
|
172
|
+
One definition, two outputs — constraints auto-map to OpenAPI schema + Fastify AJV validation:
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
schemaOptions: {
|
|
176
|
+
fieldRules: {
|
|
177
|
+
name: { minLength: 2, maxLength: 200, description: 'Product name' },
|
|
178
|
+
price: { min: 0, max: 100000 },
|
|
179
|
+
sku: { pattern: '^[A-Z]{3}-\\d{3}$' },
|
|
180
|
+
status: { enum: ['draft', 'active', 'archived'] },
|
|
181
|
+
password: { hidden: true }, // blocked from select + OpenAPI
|
|
182
|
+
deletedAt: { systemManaged: true }, // blocked from input schemas
|
|
183
|
+
slug: { immutable: true }, // excluded from update body
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Mongoose model-level constraints (`minlength`, `maxlength`, `min`, `max`, `enum`) take precedence. `fieldRules` supplements what the model doesn't declare.
|
|
189
|
+
|
|
117
190
|
## Authentication
|
|
118
191
|
|
|
119
192
|
Auth uses a **discriminated union** with `type` field:
|
|
@@ -783,6 +856,28 @@ auth: async (headers) => ({
|
|
|
783
856
|
|
|
784
857
|
**Guards** for custom tools: `guard(requireAuth, requireOrg, requireRole('admin'), handler)`
|
|
785
858
|
|
|
859
|
+
**AI SDK bridge** (v2.8.4+) — expose AI SDK `tool()` definitions over MCP without duplicating glue. Handles auth, guards, `{ error } → isError` translation, and thrown-error mapping:
|
|
860
|
+
|
|
861
|
+
```typescript
|
|
862
|
+
import { bridgeToMcp, buildMcpToolsFromBridges, getUserId, hasOrg, type McpBridge } from '@classytic/arc/mcp';
|
|
863
|
+
|
|
864
|
+
export const triggerJobBridge: McpBridge = {
|
|
865
|
+
name: 'trigger_job',
|
|
866
|
+
description: 'Start a job.',
|
|
867
|
+
inputSchema: { phase: z.enum(['investigate', 'fix']) },
|
|
868
|
+
annotations: { destructiveHint: true },
|
|
869
|
+
buildTool: (ctx) => buildTriggerJobTool(getUserId(ctx) ?? ''),
|
|
870
|
+
guard: (ctx) => (hasOrg(ctx) ? null : 'Organization scope required'),
|
|
871
|
+
};
|
|
872
|
+
|
|
873
|
+
await app.register(mcpPlugin, {
|
|
874
|
+
resources,
|
|
875
|
+
extraTools: buildMcpToolsFromBridges([triggerJobBridge], {
|
|
876
|
+
exclude: process.env.DEPLOYMENT === 'readonly' ? ['trigger_job'] : [],
|
|
877
|
+
}),
|
|
878
|
+
});
|
|
879
|
+
```
|
|
880
|
+
|
|
786
881
|
**Service scope**: When `clientId` is set in auth result, MCP produces `kind: "service"` RequestScope — works with `requireServiceScope()`, `getClientId()`, `getServiceScopes()`. No synthetic userId needed for machine principals.
|
|
787
882
|
|
|
788
883
|
**Multi-tenancy**: `organizationId` from auth flows into BaseController org-scoping automatically.
|