@classytic/arc 2.6.2 → 2.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -0
- package/dist/{BaseController-AbbRx3e0.mjs → BaseController-DzRtluEF.mjs} +88 -8
- package/dist/adapters/index.d.mts +2 -2
- package/dist/adapters/index.mjs +1 -1
- package/dist/{adapters-CTn28N4y.mjs → adapters-gM-WYjNe.mjs} +6 -4
- package/dist/auth/index.d.mts +1 -1
- package/dist/core/index.d.mts +2 -2
- package/dist/core/index.mjs +2 -2
- package/dist/{defineResource-Ckxg6HrZ.mjs → defineResource-wWMBB4GP.mjs} +39 -27
- package/dist/docs/index.d.mts +1 -1
- package/dist/dynamic/index.d.mts +1 -1
- package/dist/dynamic/index.mjs +1 -1
- package/dist/factory/index.d.mts +1 -1
- package/dist/hooks/index.d.mts +1 -1
- package/dist/{index-DrCqa3Jq.d.mts → index-CHeJa4Zd.d.mts} +3 -3
- package/dist/{index-B4uZm82R.d.mts → index-gz6iuzCp.d.mts} +1 -1
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +4 -4
- package/dist/integrations/index.d.mts +1 -1
- package/dist/integrations/mcp/index.d.mts +2 -2
- package/dist/integrations/mcp/index.mjs +1 -1
- package/dist/integrations/mcp/testing.d.mts +1 -1
- package/dist/integrations/mcp/testing.mjs +1 -1
- package/dist/{interface-CrN45qz1.d.mts → interface-DYH8AXGe.d.mts} +54 -4
- package/dist/org/index.d.mts +1 -1
- package/dist/plugins/index.d.mts +1 -1
- package/dist/plugins/tracing-entry.mjs +1 -1
- package/dist/presets/index.d.mts +1 -1
- package/dist/presets/multiTenant.d.mts +1 -1
- package/dist/registry/index.d.mts +1 -1
- package/dist/{resourceToTools-DH3c3e-T.mjs → resourceToTools-nCJWnG1r.mjs} +250 -13
- package/dist/testing/index.d.mts +2 -2
- package/dist/types/index.d.mts +1 -1
- package/dist/{types-DurlBP2N.d.mts → types-B4_TDdPe.d.mts} +1 -1
- package/dist/{types-C1Z28coa.d.mts → types-By-5mIfn.d.mts} +1 -1
- package/dist/utils/index.d.mts +1 -1
- package/package.json +18 -18
- package/skills/arc/SKILL.md +25 -0
package/README.md
CHANGED
|
@@ -108,6 +108,17 @@ const productResource = defineResource({
|
|
|
108
108
|
// Plus preset routes: GET /deleted, POST /:id/restore, GET /slug/:slug
|
|
109
109
|
```
|
|
110
110
|
|
|
111
|
+
**Custom primary key?** Use `idField` for resources keyed by UUIDs, slugs, or business identifiers:
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
defineResource({
|
|
115
|
+
name: 'job',
|
|
116
|
+
adapter: createMongooseAdapter(JobModel, jobRepository),
|
|
117
|
+
idField: 'jobId', // routes + BaseController lookups + OpenAPI + MCP tools all use this
|
|
118
|
+
});
|
|
119
|
+
// GET /jobs/job-5219f346-a4d → 200 (no ObjectId pattern enforcement)
|
|
120
|
+
```
|
|
121
|
+
|
|
111
122
|
## Authentication
|
|
112
123
|
|
|
113
124
|
Auth uses a discriminated union — pick a `type`:
|
|
@@ -96,9 +96,12 @@ var AccessControl = class AccessControl {
|
|
|
96
96
|
*/
|
|
97
97
|
async fetchWithAccessControl(id, req, repository, queryOptions) {
|
|
98
98
|
const compoundFilter = this.buildIdFilter(id, req);
|
|
99
|
-
const
|
|
99
|
+
const needsCompoundLookup = Object.keys(compoundFilter).length > 1 || this.idField !== "_id";
|
|
100
100
|
try {
|
|
101
|
-
if (
|
|
101
|
+
if (needsCompoundLookup && typeof repository.getOne === "function") return await repository.getOne(compoundFilter, queryOptions);
|
|
102
|
+
if (this.idField !== "_id") {
|
|
103
|
+
if (typeof repository.getOne !== "function") throw new Error(`Resource with idField="${this.idField}" requires repository.getOne() to look up by custom field. Arc's BaseController cannot fall back to getById() because it would query by _id.`);
|
|
104
|
+
}
|
|
102
105
|
const item = await repository.getById(id, queryOptions);
|
|
103
106
|
if (!item) return null;
|
|
104
107
|
const arcContext = this._meta(req);
|
|
@@ -719,6 +722,7 @@ var BaseController = class {
|
|
|
719
722
|
details: { code: "OWNERSHIP_DENIED" },
|
|
720
723
|
status: 403
|
|
721
724
|
};
|
|
725
|
+
const repoId = this.idField !== "_id" && existing ? String(existing["_id"] ?? id) : id;
|
|
722
726
|
const hooks = this.getHooks(req);
|
|
723
727
|
let processedData = data;
|
|
724
728
|
if (hooks && this.resourceName) try {
|
|
@@ -741,7 +745,7 @@ var BaseController = class {
|
|
|
741
745
|
status: 400
|
|
742
746
|
};
|
|
743
747
|
}
|
|
744
|
-
const repoUpdate = async () => this.repository.update(
|
|
748
|
+
const repoUpdate = async () => this.repository.update(repoId, processedData, {
|
|
745
749
|
user,
|
|
746
750
|
context: arcContext
|
|
747
751
|
});
|
|
@@ -797,6 +801,7 @@ var BaseController = class {
|
|
|
797
801
|
details: { code: "OWNERSHIP_DENIED" },
|
|
798
802
|
status: 403
|
|
799
803
|
};
|
|
804
|
+
const repoId = this.idField !== "_id" && existing ? String(existing["_id"] ?? id) : id;
|
|
800
805
|
const hooks = this.getHooks(req);
|
|
801
806
|
if (hooks && this.resourceName) try {
|
|
802
807
|
await hooks.executeBefore(this.resourceName, "delete", existing, {
|
|
@@ -815,7 +820,7 @@ var BaseController = class {
|
|
|
815
820
|
status: 400
|
|
816
821
|
};
|
|
817
822
|
}
|
|
818
|
-
const repoDelete = async () => this.repository.delete(
|
|
823
|
+
const repoDelete = async () => this.repository.delete(repoId, {
|
|
819
824
|
user,
|
|
820
825
|
context: arcContext
|
|
821
826
|
});
|
|
@@ -922,7 +927,8 @@ var BaseController = class {
|
|
|
922
927
|
details: { code: "OWNERSHIP_DENIED" },
|
|
923
928
|
status: 403
|
|
924
929
|
};
|
|
925
|
-
const
|
|
930
|
+
const repoId = this.idField !== "_id" && existing ? String(existing["_id"] ?? id) : id;
|
|
931
|
+
const item = await repo.restore(repoId);
|
|
926
932
|
if (!item) return {
|
|
927
933
|
success: false,
|
|
928
934
|
error: "Resource not found",
|
|
@@ -978,7 +984,33 @@ var BaseController = class {
|
|
|
978
984
|
error: "Bulk create requires a non-empty items array",
|
|
979
985
|
status: 400
|
|
980
986
|
};
|
|
981
|
-
|
|
987
|
+
let scopedItems = items;
|
|
988
|
+
if (this.tenantField) {
|
|
989
|
+
const scope = this.meta(req)?._scope;
|
|
990
|
+
if (scope) {
|
|
991
|
+
if (scope.kind === "public") return {
|
|
992
|
+
success: false,
|
|
993
|
+
error: "Organization context required to bulk-create resources",
|
|
994
|
+
details: { code: "ORG_CONTEXT_REQUIRED" },
|
|
995
|
+
status: 403
|
|
996
|
+
};
|
|
997
|
+
if (!isElevated(scope)) {
|
|
998
|
+
const orgId = getOrgId(scope);
|
|
999
|
+
if (!orgId) return {
|
|
1000
|
+
success: false,
|
|
1001
|
+
error: "Organization context required to bulk-create resources",
|
|
1002
|
+
details: { code: "ORG_CONTEXT_REQUIRED" },
|
|
1003
|
+
status: 403
|
|
1004
|
+
};
|
|
1005
|
+
const tenantField = this.tenantField;
|
|
1006
|
+
scopedItems = items.map((item) => ({
|
|
1007
|
+
...item,
|
|
1008
|
+
[tenantField]: orgId
|
|
1009
|
+
}));
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
const created = await repo.createMany(scopedItems);
|
|
982
1014
|
return {
|
|
983
1015
|
success: true,
|
|
984
1016
|
data: created,
|
|
@@ -986,6 +1018,40 @@ var BaseController = class {
|
|
|
986
1018
|
meta: { count: created.length }
|
|
987
1019
|
};
|
|
988
1020
|
}
|
|
1021
|
+
/**
|
|
1022
|
+
* Build a tenant-scoped filter for bulk update/delete.
|
|
1023
|
+
*
|
|
1024
|
+
* Mirrors `AccessControl.buildIdFilter` semantics for single-doc ops:
|
|
1025
|
+
* - Always merge `_policyFilters` (from permission middleware)
|
|
1026
|
+
* - When `tenantField` is set AND a `member` scope is present, add the
|
|
1027
|
+
* org filter so cross-tenant data can't be touched.
|
|
1028
|
+
* - When the scope is `elevated` (platform admin), no org filter is
|
|
1029
|
+
* applied — admins can bulk-update across orgs intentionally.
|
|
1030
|
+
* - When the scope is `public` on a tenant-scoped resource, deny.
|
|
1031
|
+
* - When NO scope is present at all (e.g., direct controller calls in
|
|
1032
|
+
* unit tests, or app routes without auth middleware), the controller
|
|
1033
|
+
* stays lenient — it's the middleware layer's job to fail-close.
|
|
1034
|
+
* Apps that want fail-close on bulk routes should run the multi-tenant
|
|
1035
|
+
* preset middleware (or equivalent) ahead of these handlers.
|
|
1036
|
+
*
|
|
1037
|
+
* Returns the merged filter, or `null` when access must be denied.
|
|
1038
|
+
*/
|
|
1039
|
+
buildBulkFilter(userFilter, req) {
|
|
1040
|
+
const filter = { ...userFilter };
|
|
1041
|
+
const arcContext = this.meta(req);
|
|
1042
|
+
const policyFilters = arcContext?._policyFilters;
|
|
1043
|
+
if (policyFilters) Object.assign(filter, policyFilters);
|
|
1044
|
+
if (this.tenantField) {
|
|
1045
|
+
const scope = arcContext?._scope;
|
|
1046
|
+
if (!scope) return filter;
|
|
1047
|
+
if (scope.kind === "public") return null;
|
|
1048
|
+
if (isElevated(scope)) return filter;
|
|
1049
|
+
const orgId = getOrgId(scope);
|
|
1050
|
+
if (!orgId) return null;
|
|
1051
|
+
filter[this.tenantField] = orgId;
|
|
1052
|
+
}
|
|
1053
|
+
return filter;
|
|
1054
|
+
}
|
|
989
1055
|
async bulkUpdate(req) {
|
|
990
1056
|
const repo = this.repository;
|
|
991
1057
|
if (!repo.updateMany) return {
|
|
@@ -1004,9 +1070,16 @@ var BaseController = class {
|
|
|
1004
1070
|
error: "Bulk update requires non-empty data",
|
|
1005
1071
|
status: 400
|
|
1006
1072
|
};
|
|
1073
|
+
const scopedFilter = this.buildBulkFilter(body.filter, req);
|
|
1074
|
+
if (scopedFilter === null) return {
|
|
1075
|
+
success: false,
|
|
1076
|
+
error: "Organization context required for bulk update",
|
|
1077
|
+
details: { code: "ORG_CONTEXT_REQUIRED" },
|
|
1078
|
+
status: 403
|
|
1079
|
+
};
|
|
1007
1080
|
return {
|
|
1008
1081
|
success: true,
|
|
1009
|
-
data: await repo.updateMany(
|
|
1082
|
+
data: await repo.updateMany(scopedFilter, body.data),
|
|
1010
1083
|
status: 200
|
|
1011
1084
|
};
|
|
1012
1085
|
}
|
|
@@ -1023,9 +1096,16 @@ var BaseController = class {
|
|
|
1023
1096
|
error: "Bulk delete requires a non-empty filter",
|
|
1024
1097
|
status: 400
|
|
1025
1098
|
};
|
|
1099
|
+
const scopedFilter = this.buildBulkFilter(body.filter, req);
|
|
1100
|
+
if (scopedFilter === null) return {
|
|
1101
|
+
success: false,
|
|
1102
|
+
error: "Organization context required for bulk delete",
|
|
1103
|
+
details: { code: "ORG_CONTEXT_REQUIRED" },
|
|
1104
|
+
status: 403
|
|
1105
|
+
};
|
|
1026
1106
|
return {
|
|
1027
1107
|
success: true,
|
|
1028
|
-
data: await repo.deleteMany(
|
|
1108
|
+
data: await repo.deleteMany(scopedFilter),
|
|
1029
1109
|
status: 200
|
|
1030
1110
|
};
|
|
1031
1111
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
import { a as PrismaQueryParserOptions, c as MongooseAdapterOptions, i as PrismaQueryParser, l as createMongooseAdapter, n as PrismaAdapterOptions, o as createPrismaAdapter, r as PrismaQueryOptions, s as MongooseAdapter, t as PrismaAdapter } from "../index-
|
|
1
|
+
import { a as RelationMetadata, c as ValidationResult, i as FieldMetadata, o as RepositoryLike, r as DataAdapter, s as SchemaMetadata, t as AdapterFactory } from "../interface-DYH8AXGe.mjs";
|
|
2
|
+
import { a as PrismaQueryParserOptions, c as MongooseAdapterOptions, i as PrismaQueryParser, l as createMongooseAdapter, n as PrismaAdapterOptions, o as createPrismaAdapter, r as PrismaQueryOptions, s as MongooseAdapter, t as PrismaAdapter } from "../index-CHeJa4Zd.mjs";
|
|
3
3
|
export { AdapterFactory, DataAdapter, FieldMetadata, MongooseAdapter, MongooseAdapterOptions, PrismaAdapter, PrismaAdapterOptions, PrismaQueryOptions, PrismaQueryParser, PrismaQueryParserOptions, RelationMetadata, RepositoryLike, SchemaMetadata, ValidationResult, createMongooseAdapter, createPrismaAdapter };
|
package/dist/adapters/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as createMongooseAdapter, i as MongooseAdapter, n as PrismaQueryParser, r as createPrismaAdapter, t as PrismaAdapter } from "../adapters-
|
|
1
|
+
import { a as createMongooseAdapter, i as MongooseAdapter, n as PrismaQueryParser, r as createPrismaAdapter, t as PrismaAdapter } from "../adapters-gM-WYjNe.mjs";
|
|
2
2
|
export { MongooseAdapter, PrismaAdapter, PrismaQueryParser, createMongooseAdapter, createPrismaAdapter };
|
|
@@ -71,9 +71,9 @@ var MongooseAdapter = class {
|
|
|
71
71
|
* If a `schemaGenerator` plugin was provided (e.g. MongoKit's buildCrudSchemasFromModel),
|
|
72
72
|
* it is used instead of the built-in basic conversion.
|
|
73
73
|
*/
|
|
74
|
-
generateSchemas(schemaOptions) {
|
|
74
|
+
generateSchemas(schemaOptions, context) {
|
|
75
75
|
try {
|
|
76
|
-
if (this.schemaGenerator) return this.schemaGenerator(this.model, schemaOptions);
|
|
76
|
+
if (this.schemaGenerator) return this.schemaGenerator(this.model, schemaOptions, context);
|
|
77
77
|
const paths = this.model.schema.paths;
|
|
78
78
|
const properties = {};
|
|
79
79
|
const required = [];
|
|
@@ -104,11 +104,13 @@ var MongooseAdapter = class {
|
|
|
104
104
|
createBody: {
|
|
105
105
|
type: "object",
|
|
106
106
|
properties: inputProperties,
|
|
107
|
-
required: inputRequired.length > 0 ? inputRequired : void 0
|
|
107
|
+
required: inputRequired.length > 0 ? inputRequired : void 0,
|
|
108
|
+
additionalProperties: true
|
|
108
109
|
},
|
|
109
110
|
updateBody: {
|
|
110
111
|
type: "object",
|
|
111
|
-
properties: updateProperties
|
|
112
|
+
properties: updateProperties,
|
|
113
|
+
additionalProperties: true
|
|
112
114
|
},
|
|
113
115
|
response: {
|
|
114
116
|
type: "object",
|
package/dist/auth/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { g as AuthPluginOptions, h as AuthHelpers } from "../interface-DYH8AXGe.mjs";
|
|
2
2
|
import { t as PermissionCheck } from "../types-BNUccdcf.mjs";
|
|
3
3
|
import { t as ExternalOpenApiPaths } from "../externalPaths-DpO-s7r8.mjs";
|
|
4
4
|
import { a as SessionManagerOptions, c as createSessionManager, i as SessionData, n as MemorySessionStoreOptions, o as SessionManagerResult, r as SessionCookieOptions, s as SessionStore, t as MemorySessionStore } from "../sessionManager-wbkYj2HL.mjs";
|
package/dist/core/index.d.mts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { At as
|
|
2
|
-
import { A as RESERVED_QUERY_PARAMS, C as HookOperation, D as MAX_SEARCH_LENGTH, E as MAX_REGEX_LENGTH, O as MUTATION_OPERATIONS, S as HOOK_PHASES, T as MAX_FILTER_DEPTH, _ as DEFAULT_MAX_LIMIT, a as getControllerScope, b as DEFAULT_UPDATE_METHOD, c as createPermissionMiddleware, d as IdempotencyService, f as createActionRouter, g as DEFAULT_LIMIT, h as DEFAULT_ID_FIELD, i as getControllerContext, j as SYSTEM_FIELDS, k as MutationOperation, l as ActionHandler, m as CrudOperation, n as createFastifyHandler, o as sendControllerResponse, p as CRUD_OPERATIONS, r as createRequestContext, s as createCrudRouter, t as createCrudHandlers, u as ActionRouterConfig, v as DEFAULT_SORT, w as HookPhase, x as HOOK_OPERATIONS, y as DEFAULT_TENANT_FIELD } from "../index-
|
|
1
|
+
import { At as AccessControl, Dt as QueryResolverConfig, Et as QueryResolver, Ht as defineResource, Ot as BodySanitizer, Tt as BaseControllerOptions, Vt as ResourceDefinition, jt as AccessControlConfig, kt as BodySanitizerConfig, wt as BaseController } from "../interface-DYH8AXGe.mjs";
|
|
2
|
+
import { A as RESERVED_QUERY_PARAMS, C as HookOperation, D as MAX_SEARCH_LENGTH, E as MAX_REGEX_LENGTH, O as MUTATION_OPERATIONS, S as HOOK_PHASES, T as MAX_FILTER_DEPTH, _ as DEFAULT_MAX_LIMIT, a as getControllerScope, b as DEFAULT_UPDATE_METHOD, c as createPermissionMiddleware, d as IdempotencyService, f as createActionRouter, g as DEFAULT_LIMIT, h as DEFAULT_ID_FIELD, i as getControllerContext, j as SYSTEM_FIELDS, k as MutationOperation, l as ActionHandler, m as CrudOperation, n as createFastifyHandler, o as sendControllerResponse, p as CRUD_OPERATIONS, r as createRequestContext, s as createCrudRouter, t as createCrudHandlers, u as ActionRouterConfig, v as DEFAULT_SORT, w as HookPhase, x as HOOK_OPERATIONS, y as DEFAULT_TENANT_FIELD } from "../index-gz6iuzCp.mjs";
|
|
3
3
|
export { AccessControl, AccessControlConfig, ActionHandler, ActionRouterConfig, BaseController, BaseControllerOptions, BodySanitizer, BodySanitizerConfig, CRUD_OPERATIONS, CrudOperation, DEFAULT_ID_FIELD, DEFAULT_LIMIT, DEFAULT_MAX_LIMIT, DEFAULT_SORT, DEFAULT_TENANT_FIELD, DEFAULT_UPDATE_METHOD, HOOK_OPERATIONS, HOOK_PHASES, HookOperation, HookPhase, IdempotencyService, MAX_FILTER_DEPTH, MAX_REGEX_LENGTH, MAX_SEARCH_LENGTH, MUTATION_OPERATIONS, MutationOperation, QueryResolver, QueryResolverConfig, RESERVED_QUERY_PARAMS, ResourceDefinition, SYSTEM_FIELDS, createActionRouter, createCrudHandlers, createCrudRouter, createFastifyHandler, createPermissionMiddleware, createRequestContext, defineResource, getControllerContext, getControllerScope, sendControllerResponse };
|
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
|
-
import { i as AccessControl, n as QueryResolver, r as BodySanitizer, t as BaseController } from "../BaseController-
|
|
2
|
+
import { i as AccessControl, n as QueryResolver, r as BodySanitizer, t as BaseController } from "../BaseController-DzRtluEF.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-wWMBB4GP.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 };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { s as DEFAULT_UPDATE_METHOD, t as CRUD_OPERATIONS } from "./constants-Cxde4rpC.mjs";
|
|
2
2
|
import { d as isElevated, f as isMember, n as PUBLIC_SCOPE } from "./types-BhtYdxZU.mjs";
|
|
3
|
-
import { t as BaseController } from "./BaseController-
|
|
3
|
+
import { t as BaseController } from "./BaseController-DzRtluEF.mjs";
|
|
4
4
|
import { i as resolveEffectiveRoles, t as applyFieldReadPermissions } from "./fields-ipsbIRPK.mjs";
|
|
5
5
|
import { t as getUserRoles } from "./types-ZUu_h0jp.mjs";
|
|
6
6
|
import { t as requestContext } from "./requestContext-DYtmNpm5.mjs";
|
|
@@ -1015,9 +1015,38 @@ function defineResource(config) {
|
|
|
1015
1015
|
if (!config.skipRegistry) try {
|
|
1016
1016
|
let openApiSchemas;
|
|
1017
1017
|
if (config.adapter?.generateSchemas) {
|
|
1018
|
-
const
|
|
1018
|
+
const adapterContext = {
|
|
1019
|
+
idField: config.idField,
|
|
1020
|
+
resourceName: config.name
|
|
1021
|
+
};
|
|
1022
|
+
const generated = config.adapter.generateSchemas(config.schemaOptions, adapterContext);
|
|
1019
1023
|
if (generated) openApiSchemas = generated;
|
|
1020
1024
|
}
|
|
1025
|
+
if (config.idField && config.idField !== "_id" && openApiSchemas?.params && typeof openApiSchemas.params === "object") {
|
|
1026
|
+
const params = openApiSchemas.params;
|
|
1027
|
+
const properties = params.properties;
|
|
1028
|
+
const idProp = properties?.id;
|
|
1029
|
+
if (idProp && typeof idProp === "object") {
|
|
1030
|
+
const pattern = idProp.pattern;
|
|
1031
|
+
if (typeof pattern === "string" && (pattern === "^[0-9a-fA-F]{24}$" || pattern === "^[a-f\\d]{24}$" || pattern === "^[a-fA-F0-9]{24}$" || /^\^\[[a-fA-F0-9\\d]+\]\{24\}\$$/.test(pattern))) {
|
|
1032
|
+
const cleanedId = { ...idProp };
|
|
1033
|
+
delete cleanedId.pattern;
|
|
1034
|
+
delete cleanedId.minLength;
|
|
1035
|
+
delete cleanedId.maxLength;
|
|
1036
|
+
if (!cleanedId.description) cleanedId.description = `${config.idField} (custom ID field)`;
|
|
1037
|
+
openApiSchemas = {
|
|
1038
|
+
...openApiSchemas,
|
|
1039
|
+
params: {
|
|
1040
|
+
...params,
|
|
1041
|
+
properties: {
|
|
1042
|
+
...properties,
|
|
1043
|
+
id: cleanedId
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1021
1050
|
const queryParser = config.queryParser;
|
|
1022
1051
|
if (queryParser?.getQuerySchema) {
|
|
1023
1052
|
const querySchema = queryParser.getQuerySchema();
|
|
@@ -1156,7 +1185,7 @@ var ResourceDefinition = class {
|
|
|
1156
1185
|
const openApi = self._registryMeta?.openApiSchemas;
|
|
1157
1186
|
if (openApi && (!self.customSchemas || Object.keys(self.customSchemas).length === 0)) {
|
|
1158
1187
|
const generated = {};
|
|
1159
|
-
const { createBody, updateBody, params
|
|
1188
|
+
const { createBody, updateBody, params } = openApi;
|
|
1160
1189
|
const safeBody = (schema) => {
|
|
1161
1190
|
if (schema && typeof schema === "object" && schema.type === "object") return {
|
|
1162
1191
|
additionalProperties: true,
|
|
@@ -1189,39 +1218,22 @@ var ResourceDefinition = class {
|
|
|
1189
1218
|
}
|
|
1190
1219
|
const listQuerySchema = self._registryMeta?.openApiSchemas?.listQuery;
|
|
1191
1220
|
if (listQuerySchema) {
|
|
1192
|
-
const
|
|
1221
|
+
const KEEP_AS_IS = new Set([
|
|
1193
1222
|
"page",
|
|
1194
1223
|
"limit",
|
|
1195
1224
|
"sort",
|
|
1196
1225
|
"search",
|
|
1197
1226
|
"select",
|
|
1198
|
-
"after"
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
"
|
|
1202
|
-
"minimum",
|
|
1203
|
-
"maximum",
|
|
1204
|
-
"minLength",
|
|
1205
|
-
"maxLength",
|
|
1206
|
-
"pattern",
|
|
1207
|
-
"format",
|
|
1208
|
-
"exclusiveMinimum",
|
|
1209
|
-
"exclusiveMaximum",
|
|
1210
|
-
"multipleOf",
|
|
1211
|
-
"minItems",
|
|
1212
|
-
"maxItems",
|
|
1213
|
-
"uniqueItems"
|
|
1227
|
+
"after",
|
|
1228
|
+
"populate",
|
|
1229
|
+
"lookup",
|
|
1230
|
+
"aggregate"
|
|
1214
1231
|
]);
|
|
1215
1232
|
const props = listQuerySchema.properties;
|
|
1216
1233
|
const normalizedProps = props ? { ...props } : void 0;
|
|
1217
1234
|
if (normalizedProps) for (const key of Object.keys(normalizedProps)) {
|
|
1218
|
-
if (
|
|
1219
|
-
|
|
1220
|
-
if (prop && typeof prop === "object" && "type" in prop) {
|
|
1221
|
-
const cleaned = {};
|
|
1222
|
-
for (const [k, v] of Object.entries(prop)) if (!TYPE_DEPENDENT.has(k)) cleaned[k] = v;
|
|
1223
|
-
normalizedProps[key] = Object.keys(cleaned).length > 0 ? cleaned : {};
|
|
1224
|
-
}
|
|
1235
|
+
if (KEEP_AS_IS.has(key)) continue;
|
|
1236
|
+
normalizedProps[key] = {};
|
|
1225
1237
|
}
|
|
1226
1238
|
const normalizedSchema = {
|
|
1227
1239
|
...listQuerySchema,
|
package/dist/docs/index.d.mts
CHANGED
package/dist/dynamic/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Vt as ResourceDefinition, r as DataAdapter } from "../interface-DYH8AXGe.mjs";
|
|
2
2
|
import { t as PermissionCheck } from "../types-BNUccdcf.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/dynamic/ArcDynamicLoader.d.ts
|
package/dist/dynamic/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as ArcQueryParser } from "../queryParser-CgCtsjti.mjs";
|
|
2
|
-
import { n as defineResource } from "../defineResource-
|
|
2
|
+
import { n as defineResource } from "../defineResource-wWMBB4GP.mjs";
|
|
3
3
|
import { S as readOnly, _ as fullPublic, b as publicRead, g as authenticated, h as adminOnly, v as ownerWithAdminBypass, x as publicReadAdminWrite } from "../permissions-C8ImI8gC.mjs";
|
|
4
4
|
//#region src/dynamic/ArcDynamicLoader.ts
|
|
5
5
|
const VALID_FIELD_TYPES = new Set([
|
package/dist/factory/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as CustomPluginAuthOption, c as RawBodyOptions, d as ResourceLike, f as loadResources, i as CustomAuthenticatorOption, l as UnderPressureOptions, n as BetterAuthOption, o as JwtAuthOption, r as CreateAppOptions, s as MultipartOptions, t as AuthOption, u as LoadResourcesOptions } from "../types-
|
|
1
|
+
import { a as CustomPluginAuthOption, c as RawBodyOptions, d as ResourceLike, f as loadResources, i as CustomAuthenticatorOption, l as UnderPressureOptions, n as BetterAuthOption, o as JwtAuthOption, r as CreateAppOptions, s as MultipartOptions, t as AuthOption, u as LoadResourcesOptions } from "../types-By-5mIfn.mjs";
|
|
2
2
|
import { FastifyInstance } from "fastify";
|
|
3
3
|
|
|
4
4
|
//#region src/factory/createApp.d.ts
|
package/dist/hooks/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { an as
|
|
1
|
+
import { _n as defineHook, an as HookOperation, cn as HookSystem, dn as afterDelete, fn as afterUpdate, gn as createHookSystem, hn as beforeUpdate, in as HookHandler, ln as HookSystemOptions, mn as beforeDelete, nn as DefineHookOptions, on as HookPhase, pn as beforeCreate, rn as HookContext, sn as HookRegistration, un as afterCreate } from "../interface-DYH8AXGe.mjs";
|
|
2
2
|
export { type DefineHookOptions, type HookContext, type HookHandler, type HookOperation, type HookPhase, type HookRegistration, HookSystem, type HookSystemOptions, afterCreate, afterDelete, afterUpdate, beforeCreate, beforeDelete, beforeUpdate, createHookSystem, defineHook };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { G as OpenApiSchemas, Q as QueryParserInterface, Ut as CrudRepository, c as ValidationResult, ft as RouteSchemaOptions, n as AdapterSchemaContext, o as RepositoryLike, q as ParsedQuery, r as DataAdapter, s as SchemaMetadata } from "./interface-DYH8AXGe.mjs";
|
|
2
2
|
import { Model } from "mongoose";
|
|
3
3
|
|
|
4
4
|
//#region src/adapters/mongoose.d.ts
|
|
@@ -29,7 +29,7 @@ interface MongooseAdapterOptions<TDoc = unknown> {
|
|
|
29
29
|
* });
|
|
30
30
|
* ```
|
|
31
31
|
*/
|
|
32
|
-
schemaGenerator?: (model: Model<TDoc>, options?: RouteSchemaOptions) => OpenApiSchemas | Record<string, unknown>;
|
|
32
|
+
schemaGenerator?: (model: Model<TDoc>, options?: RouteSchemaOptions, context?: AdapterSchemaContext) => OpenApiSchemas | Record<string, unknown>;
|
|
33
33
|
}
|
|
34
34
|
/**
|
|
35
35
|
* Mongoose data adapter with proper type safety
|
|
@@ -53,7 +53,7 @@ declare class MongooseAdapter<TDoc = unknown> implements DataAdapter<TDoc> {
|
|
|
53
53
|
* If a `schemaGenerator` plugin was provided (e.g. MongoKit's buildCrudSchemasFromModel),
|
|
54
54
|
* it is used instead of the built-in basic conversion.
|
|
55
55
|
*/
|
|
56
|
-
generateSchemas(schemaOptions?: RouteSchemaOptions): OpenApiSchemas | Record<string, unknown> | null;
|
|
56
|
+
generateSchemas(schemaOptions?: RouteSchemaOptions, context?: AdapterSchemaContext): OpenApiSchemas | Record<string, unknown> | null;
|
|
57
57
|
/**
|
|
58
58
|
* Extract relation metadata
|
|
59
59
|
*/
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { s as RequestScope } from "./elevation-C_taLQrM.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { C as CrudRouterOptions, Ft as IController, It as IControllerResponse, Lt as IRequestContext, it as RequestWithExtras, k as FastifyWithDecorators, nt as RequestContext, x as CrudController } from "./interface-DYH8AXGe.mjs";
|
|
3
3
|
import { t as PermissionCheck } from "./types-BNUccdcf.mjs";
|
|
4
4
|
import { FastifyInstance, FastifyReply, FastifyRequest, RouteHandlerMethod } from "fastify";
|
|
5
5
|
|
package/dist/index.d.mts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as RateLimitConfig, $t as PipelineContext, A as FieldRule, C as CrudRouterOptions, D as FastifyRequestExtras, F as InferDocType, Ft as IController, Gt as PaginatedResult, H as MiddlewareConfig, Ht as defineResource, I as InferResourceDoc, It as IControllerResponse, Jt as Guard, K as OwnershipCheck, L as IntrospectionData, Lt as IRequestContext, M as HealthCheck, N as HealthOptions, Nt as ControllerLike, O as FastifyWithAuth, P as InferAdapterDoc, Qt as PipelineConfig, R as IntrospectionPluginOptions, Rt as RouteHandler, S as CrudRouteKey, St as envelope, T as EventDefinition, Tt as BaseControllerOptions, U as MiddlewareHandler, Ut as CrudRepository, Vt as ResourceDefinition, Xt as NextFunction, Y as PresetFunction, Yt as Interceptor, Z as PresetResult, Zt as OperationFilter, _t as TypedResourceConfig, a as RelationMetadata, bt as ValidateOptions, c as ValidationResult, d as ApiResponse, dt as RouteHandlerMethod, en as PipelineStep, et as RegistryEntry, ft as RouteSchemaOptions, g as AuthPluginOptions, gt as TypedRepository, ht as TypedController, i as FieldMetadata, it as RequestWithExtras, j as GracefulShutdownOptions, k as FastifyWithDecorators, l as AdditionalRoute, lt as ResourceMetadata, m as ArcRequest, nt as RequestContext, o as RepositoryLike, ot as ResourceConfig, p as ArcInternalMetadata, pt as ServiceContext, qt as QueryOptions, r as DataAdapter, rt as RequestIdOptions, s as SchemaMetadata, tn as Transform, tt as RegistryStats, u as AnyRecord, w as CrudSchemas, wt as BaseController, x as CrudController, xt as ValidationResult$1, y as ConfigError, yt as UserOrganization, z as JWTPayload } from "./interface-DYH8AXGe.mjs";
|
|
2
2
|
import { a as applyFieldWritePermissions, i as applyFieldReadPermissions, n as FieldPermissionMap, o as fields, t as FieldPermission } from "./fields-DFwdaWCq.mjs";
|
|
3
3
|
import { i as UserBase, n as PermissionContext, r as PermissionResult, t as PermissionCheck } from "./types-BNUccdcf.mjs";
|
|
4
|
-
import { l as createMongooseAdapter, o as createPrismaAdapter, s as MongooseAdapter, t as PrismaAdapter } from "./index-
|
|
5
|
-
import { A as RESERVED_QUERY_PARAMS, C as HookOperation, D as MAX_SEARCH_LENGTH, E as MAX_REGEX_LENGTH, O as MUTATION_OPERATIONS, S as HOOK_PHASES, T as MAX_FILTER_DEPTH, _ as DEFAULT_MAX_LIMIT, a as getControllerScope, b as DEFAULT_UPDATE_METHOD, g as DEFAULT_LIMIT, h as DEFAULT_ID_FIELD, j as SYSTEM_FIELDS, k as MutationOperation, m as CrudOperation, p as CRUD_OPERATIONS, v as DEFAULT_SORT, w as HookPhase, x as HOOK_OPERATIONS, y as DEFAULT_TENANT_FIELD } from "./index-
|
|
4
|
+
import { l as createMongooseAdapter, o as createPrismaAdapter, s as MongooseAdapter, t as PrismaAdapter } from "./index-CHeJa4Zd.mjs";
|
|
5
|
+
import { A as RESERVED_QUERY_PARAMS, C as HookOperation, D as MAX_SEARCH_LENGTH, E as MAX_REGEX_LENGTH, O as MUTATION_OPERATIONS, S as HOOK_PHASES, T as MAX_FILTER_DEPTH, _ as DEFAULT_MAX_LIMIT, a as getControllerScope, b as DEFAULT_UPDATE_METHOD, g as DEFAULT_LIMIT, h as DEFAULT_ID_FIELD, j as SYSTEM_FIELDS, k as MutationOperation, m as CrudOperation, p as CRUD_OPERATIONS, v as DEFAULT_SORT, w as HookPhase, x as HOOK_OPERATIONS, y as DEFAULT_TENANT_FIELD } from "./index-gz6iuzCp.mjs";
|
|
6
6
|
import { C as presets_d_exports, E as readOnly, S as ownerWithAdminBypass, T as publicReadAdminWrite, a as allOf, b as authenticated, c as createDynamicPermissionMatrix, d as requireAuth, f as requireOrgMembership, g as requireTeamMembership, h as requireRoles, l as createOrgPermissions, m as requireOwnership, n as DynamicPermissionMatrix, o as allowPublic, p as requireOrgRole, r as DynamicPermissionMatrixConfig, s as anyOf, u as denyAll, v as when, w as publicRead, x as fullPublic, y as adminOnly } from "./index-NGZksqM5.mjs";
|
|
7
7
|
import { a as NotFoundError, d as ValidationError, f as createDomainError, i as ForbiddenError, t as ArcError, u as UnauthorizedError } from "./errors-CcVbl1-T.mjs";
|
|
8
8
|
import { AsyncLocalStorage } from "node:async_hooks";
|
package/dist/index.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { a as DEFAULT_SORT, c as HOOK_OPERATIONS, d as MAX_REGEX_LENGTH, f as MAX_SEARCH_LENGTH, h as SYSTEM_FIELDS, i as DEFAULT_MAX_LIMIT, l as HOOK_PHASES, m as RESERVED_QUERY_PARAMS, n as DEFAULT_ID_FIELD, o as DEFAULT_TENANT_FIELD, p as MUTATION_OPERATIONS, r as DEFAULT_LIMIT, s as DEFAULT_UPDATE_METHOD, t as CRUD_OPERATIONS, u as MAX_FILTER_DEPTH } from "./constants-Cxde4rpC.mjs";
|
|
2
|
-
import { a as createMongooseAdapter, i as MongooseAdapter, r as createPrismaAdapter, t as PrismaAdapter } from "./adapters-
|
|
3
|
-
import { t as BaseController } from "./BaseController-
|
|
2
|
+
import { a as createMongooseAdapter, i as MongooseAdapter, r as createPrismaAdapter, t as PrismaAdapter } from "./adapters-gM-WYjNe.mjs";
|
|
3
|
+
import { t as BaseController } from "./BaseController-DzRtluEF.mjs";
|
|
4
4
|
import { envelope } from "./types/index.mjs";
|
|
5
5
|
import { n as applyFieldWritePermissions, r as fields, t as applyFieldReadPermissions } from "./fields-ipsbIRPK.mjs";
|
|
6
6
|
import { t as requestContext } from "./requestContext-DYtmNpm5.mjs";
|
|
7
7
|
import { d as createDomainError, i as NotFoundError, l as UnauthorizedError, r as ForbiddenError, t as ArcError, u as ValidationError } from "./errors-NoQKsbAT.mjs";
|
|
8
|
-
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 { a as validateResourceConfig, f as getControllerScope, i as formatValidationErrors, m as pipe, n as defineResource, r as assertValidConfig, t as ResourceDefinition } from "./defineResource-wWMBB4GP.mjs";
|
|
9
9
|
import { S as readOnly, _ as fullPublic, a as createOrgPermissions, b as publicRead, c as requireOrgMembership, d as requireRoles, f as requireTeamMembership, g as authenticated, h as adminOnly, i as createDynamicPermissionMatrix, l as requireOrgRole, m as when, n as allowPublic, o as denyAll, r as anyOf, s as requireAuth, t as allOf, u as requireOwnership, v as ownerWithAdminBypass, x as publicReadAdminWrite, y as presets_exports } from "./permissions-C8ImI8gC.mjs";
|
|
10
10
|
import { n as configureArcLogger, t as arcLog } from "./logger-Dz3j1ItV.mjs";
|
|
11
11
|
//#region src/middleware/middleware.ts
|
|
@@ -127,6 +127,6 @@ function transform(name, handlerOrOptions) {
|
|
|
127
127
|
}
|
|
128
128
|
//#endregion
|
|
129
129
|
//#region src/index.ts
|
|
130
|
-
const version = "2.6.
|
|
130
|
+
const version = "2.6.3";
|
|
131
131
|
//#endregion
|
|
132
132
|
export { ArcError, BaseController, CRUD_OPERATIONS, DEFAULT_ID_FIELD, DEFAULT_LIMIT, DEFAULT_MAX_LIMIT, DEFAULT_SORT, DEFAULT_TENANT_FIELD, DEFAULT_UPDATE_METHOD, ForbiddenError, HOOK_OPERATIONS, HOOK_PHASES, MAX_FILTER_DEPTH, MAX_REGEX_LENGTH, MAX_SEARCH_LENGTH, MUTATION_OPERATIONS, MongooseAdapter, NotFoundError, PrismaAdapter, RESERVED_QUERY_PARAMS, ResourceDefinition, SYSTEM_FIELDS, UnauthorizedError, ValidationError, adminOnly, allOf, allowPublic, anyOf, applyFieldReadPermissions, applyFieldWritePermissions, arcLog, assertValidConfig, authenticated, configureArcLogger, createDomainError, createDynamicPermissionMatrix, createMongooseAdapter, createOrgPermissions, createPrismaAdapter, defineResource, denyAll, envelope, fields, formatValidationErrors, fullPublic, getControllerScope, guard, intercept, middleware, ownerWithAdminBypass, presets_exports as permissions, pipe, publicRead, publicReadAdminWrite, readOnly, requestContext, requireAuth, requireOrgMembership, requireOrgRole, requireOwnership, requireRoles, requireTeamMembership, sortMiddlewares, transform, validateResourceConfig, version, when };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { WebSocketClient, WebSocketMessage, WebSocketPluginOptions } from "./websocket.mjs";
|
|
2
2
|
import { EventGatewayOptions } from "./event-gateway.mjs";
|
|
3
3
|
import { JobDefinition, JobDispatchOptions, JobDispatcher, JobMeta, JobsPluginOptions, QueueStats } from "./jobs.mjs";
|
|
4
|
-
import { c as McpResourceConfig, f as ToolAnnotations, i as CrudOperation, l as PromptDefinition, m as ToolDefinition, n as CallToolResult, o as McpAuthResult, p as ToolContext, r as CreateMcpServerConfig, s as McpPluginOptions, t as BetterAuthHandler } from "../types-
|
|
4
|
+
import { c as McpResourceConfig, f as ToolAnnotations, i as CrudOperation, l as PromptDefinition, m as ToolDefinition, n as CallToolResult, o as McpAuthResult, p as ToolContext, r as CreateMcpServerConfig, s as McpPluginOptions, t as BetterAuthHandler } from "../types-B4_TDdPe.mjs";
|
|
5
5
|
import { StreamlinePluginOptions, WorkflowLike, WorkflowRunLike } from "./streamline.mjs";
|
|
6
6
|
import { WebhookDeliveryRecord, WebhookManager, WebhookPluginOptions, WebhookStore, WebhookSubscription } from "./webhooks.mjs";
|
|
7
7
|
export { type BetterAuthHandler, type CallToolResult, type CreateMcpServerConfig, type CrudOperation, type EventGatewayOptions, type JobDefinition, type JobDispatchOptions, type JobDispatcher, type JobMeta, type JobsPluginOptions, type McpAuthResult, type McpPluginOptions, type McpResourceConfig, type PromptDefinition, type QueueStats, type StreamlinePluginOptions, type ToolAnnotations, type ToolContext, type ToolDefinition, type WebSocketClient, type WebSocketMessage, type WebSocketPluginOptions, type WebhookDeliveryRecord, type WebhookManager, type WebhookPluginOptions, type WebhookStore, type WebhookSubscription, type WorkflowLike, type WorkflowRunLike };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as McpAuthResolver, c as McpResourceConfig, d as SessionEntry, f as ToolAnnotations, i as CrudOperation, l as PromptDefinition, m as ToolDefinition, n as CallToolResult, o as McpAuthResult, p as ToolContext, r as CreateMcpServerConfig, s as McpPluginOptions, t as BetterAuthHandler, u as PromptResult } from "../../types-
|
|
1
|
+
import { Vt as ResourceDefinition } from "../../interface-DYH8AXGe.mjs";
|
|
2
|
+
import { a as McpAuthResolver, c as McpResourceConfig, d as SessionEntry, f as ToolAnnotations, i as CrudOperation, l as PromptDefinition, m as ToolDefinition, n as CallToolResult, o as McpAuthResult, p as ToolContext, r as CreateMcpServerConfig, s as McpPluginOptions, t as BetterAuthHandler, u as PromptResult } from "../../types-B4_TDdPe.mjs";
|
|
3
3
|
import { FastifyPluginAsync } from "fastify";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as fieldRulesToZod, r as createMcpServer, t as resourceToTools } from "../../resourceToTools-
|
|
1
|
+
import { n as fieldRulesToZod, r as createMcpServer, t as resourceToTools } from "../../resourceToTools-nCJWnG1r.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-nCJWnG1r.mjs";
|
|
2
2
|
//#region src/integrations/mcp/testing.ts
|
|
3
3
|
/**
|
|
4
4
|
* @classytic/arc/mcp/testing — MCP Test Utilities
|
|
@@ -1037,6 +1037,25 @@ declare class BaseController<TDoc = AnyRecord, TRepository extends RepositoryLik
|
|
|
1037
1037
|
getTree(req: IRequestContext): Promise<IControllerResponse<TDoc[]>>;
|
|
1038
1038
|
getChildren(req: IRequestContext): Promise<IControllerResponse<TDoc[]>>;
|
|
1039
1039
|
bulkCreate(req: IRequestContext): Promise<IControllerResponse<TDoc[]>>;
|
|
1040
|
+
/**
|
|
1041
|
+
* Build a tenant-scoped filter for bulk update/delete.
|
|
1042
|
+
*
|
|
1043
|
+
* Mirrors `AccessControl.buildIdFilter` semantics for single-doc ops:
|
|
1044
|
+
* - Always merge `_policyFilters` (from permission middleware)
|
|
1045
|
+
* - When `tenantField` is set AND a `member` scope is present, add the
|
|
1046
|
+
* org filter so cross-tenant data can't be touched.
|
|
1047
|
+
* - When the scope is `elevated` (platform admin), no org filter is
|
|
1048
|
+
* applied — admins can bulk-update across orgs intentionally.
|
|
1049
|
+
* - When the scope is `public` on a tenant-scoped resource, deny.
|
|
1050
|
+
* - When NO scope is present at all (e.g., direct controller calls in
|
|
1051
|
+
* unit tests, or app routes without auth middleware), the controller
|
|
1052
|
+
* stays lenient — it's the middleware layer's job to fail-close.
|
|
1053
|
+
* Apps that want fail-close on bulk routes should run the multi-tenant
|
|
1054
|
+
* preset middleware (or equivalent) ahead of these handlers.
|
|
1055
|
+
*
|
|
1056
|
+
* Returns the merged filter, or `null` when access must be denied.
|
|
1057
|
+
*/
|
|
1058
|
+
private buildBulkFilter;
|
|
1040
1059
|
bulkUpdate(req: IRequestContext): Promise<IControllerResponse<{
|
|
1041
1060
|
matchedCount: number;
|
|
1042
1061
|
modifiedCount: number;
|
|
@@ -1523,9 +1542,22 @@ interface ResourceConfig<TDoc = AnyRecord> {
|
|
|
1523
1542
|
tenantField?: string | false;
|
|
1524
1543
|
/**
|
|
1525
1544
|
* Primary key field name (default: '_id').
|
|
1526
|
-
*
|
|
1545
|
+
*
|
|
1546
|
+
* Type-narrowed to `keyof TDoc` when `defineResource<TDoc>` is called with
|
|
1547
|
+
* a typed document interface — gives autocomplete for valid field names —
|
|
1548
|
+
* while still accepting any string when TDoc is `unknown` / `AnyRecord` so
|
|
1549
|
+
* adapters with dynamic shapes still work.
|
|
1550
|
+
*
|
|
1551
|
+
* @example
|
|
1552
|
+
* ```ts
|
|
1553
|
+
* defineResource<IJob>({ idField: 'jobId' }) // ← autocompletes from IJob fields
|
|
1554
|
+
* defineResource({ idField: 'sku' }) // ← any string allowed
|
|
1555
|
+
* ```
|
|
1556
|
+
*
|
|
1557
|
+
* Override for non-MongoDB adapters (e.g., 'id' for SQL databases) or
|
|
1558
|
+
* resources keyed by a business identifier (slug, sku, orderNumber).
|
|
1527
1559
|
*/
|
|
1528
|
-
idField?: string;
|
|
1560
|
+
idField?: (keyof TDoc & string) | (string & {});
|
|
1529
1561
|
module?: string;
|
|
1530
1562
|
events?: Record<string, EventDefinition>;
|
|
1531
1563
|
skipValidation?: boolean;
|
|
@@ -2367,9 +2399,15 @@ interface DataAdapter<TDoc = unknown> {
|
|
|
2367
2399
|
* For example, Mongoose adapter can use mongokit to generate schemas from Mongoose models.
|
|
2368
2400
|
*
|
|
2369
2401
|
* @param options - Schema generation options (field rules, populate settings, etc.)
|
|
2402
|
+
* @param context - Resource-level context: idField (for params schema), resourceName.
|
|
2403
|
+
* Adapters should honor `context.idField` when producing the params
|
|
2404
|
+
* schema — e.g. skip the ObjectId pattern when idField is a custom
|
|
2405
|
+
* string field. Backwards compatible: legacy adapters ignoring the
|
|
2406
|
+
* context still work because Arc strips the mismatched pattern as
|
|
2407
|
+
* a safety net.
|
|
2370
2408
|
* @returns OpenAPI schemas for CRUD operations or null if not supported
|
|
2371
2409
|
*/
|
|
2372
|
-
generateSchemas?(options?: RouteSchemaOptions): OpenApiSchemas | Record<string, unknown> | null;
|
|
2410
|
+
generateSchemas?(options?: RouteSchemaOptions, context?: AdapterSchemaContext): OpenApiSchemas | Record<string, unknown> | null;
|
|
2373
2411
|
/** Extract schema metadata for OpenAPI/introspection */
|
|
2374
2412
|
getSchemaMetadata?(): SchemaMetadata | null;
|
|
2375
2413
|
/** Validate data against schema before persistence */
|
|
@@ -2385,6 +2423,18 @@ interface DataAdapter<TDoc = unknown> {
|
|
|
2385
2423
|
/** Close/cleanup resources */
|
|
2386
2424
|
close?(): Promise<void>;
|
|
2387
2425
|
}
|
|
2426
|
+
/**
|
|
2427
|
+
* Context passed to `adapter.generateSchemas()` so adapters can shape the
|
|
2428
|
+
* output to match resource-level configuration (idField overrides, etc).
|
|
2429
|
+
* All fields are optional — adapters are free to ignore this argument, in
|
|
2430
|
+
* which case Arc applies safety-net normalization to the generated schemas.
|
|
2431
|
+
*/
|
|
2432
|
+
interface AdapterSchemaContext {
|
|
2433
|
+
/** The idField configured on the resource. Defaults to "_id". */
|
|
2434
|
+
idField?: string;
|
|
2435
|
+
/** Resource name (for error messages / logging). */
|
|
2436
|
+
resourceName?: string;
|
|
2437
|
+
}
|
|
2388
2438
|
interface SchemaMetadata {
|
|
2389
2439
|
name: string;
|
|
2390
2440
|
fields: Record<string, FieldMetadata>;
|
|
@@ -2426,4 +2476,4 @@ interface ValidationResult {
|
|
|
2426
2476
|
}
|
|
2427
2477
|
type AdapterFactory<TDoc> = (config: unknown) => DataAdapter<TDoc>;
|
|
2428
2478
|
//#endregion
|
|
2429
|
-
export {
|
|
2479
|
+
export { RateLimitConfig as $, PipelineContext as $t, FieldRule as A, AccessControl as At, JwtContext as B, ResourceRegistry as Bt, CrudRouterOptions as C, getUserId as Ct, FastifyRequestExtras as D, QueryResolverConfig as Dt, EventsDecorator as E, QueryResolver as Et, InferDocType as F, IController as Ft, OpenApiSchemas as G, PaginatedResult as Gt, MiddlewareConfig as H, defineResource as Ht, InferResourceDoc as I, IControllerResponse as It, PopulateOption as J, Guard as Jt, OwnershipCheck as K, PaginationParams as Kt, IntrospectionData as L, IRequestContext as Lt, HealthCheck as M, ControllerHandler as Mt, HealthOptions as N, ControllerLike as Nt, FastifyWithAuth as O, BodySanitizer as Ot, InferAdapterDoc as P, FastifyHandler as Pt, QueryParserInterface as Q, PipelineConfig as Qt, IntrospectionPluginOptions as R, RouteHandler as Rt, CrudRouteKey as S, envelope as St, EventDefinition as T, BaseControllerOptions as Tt, MiddlewareHandler as U, CrudRepository as Ut, LookupOption as V, ResourceDefinition as Vt, ObjectId as W, InferDoc as Wt, PresetHook as X, NextFunction as Xt, PresetFunction as Y, Interceptor as Yt, PresetResult as Z, OperationFilter as Zt, Authenticator as _, defineHook as _n, TypedResourceConfig as _t, RelationMetadata as a, HookOperation as an, ResourceCacheConfig as at, ControllerQueryOptions as b, ValidateOptions as bt, ValidationResult as c, HookSystem as cn, ResourceHooks as ct, ApiResponse as d, afterDelete as dn, RouteHandlerMethod$1 as dt, PipelineStep as en, RegistryEntry as et, ArcDecorator as f, afterUpdate as fn, RouteSchemaOptions as ft, AuthPluginOptions as g, createHookSystem as gn, TypedRepository as gt, AuthHelpers as h, beforeUpdate as hn, TypedController as ht, FieldMetadata as i, HookHandler as in, RequestWithExtras as it, GracefulShutdownOptions as j, AccessControlConfig as jt, FastifyWithDecorators as k, BodySanitizerConfig as kt, AdditionalRoute as l, HookSystemOptions as ln, ResourceMetadata as lt, ArcRequest as m, beforeDelete as mn, TokenPair as mt, AdapterSchemaContext as n, DefineHookOptions as nn, RequestContext as nt, RepositoryLike as o, HookPhase as on, ResourceConfig as ot, ArcInternalMetadata as p, beforeCreate as pn, ServiceContext as pt, ParsedQuery as q, QueryOptions as qt, DataAdapter as r, HookContext as rn, RequestIdOptions as rt, SchemaMetadata as s, HookRegistration as sn, ResourceHookContext as st, AdapterFactory as t, Transform as tn, RegistryStats as tt, AnyRecord as u, afterCreate as un, ResourcePermissions as ut, AuthenticatorContext as v, UserLike as vt, CrudSchemas as w, BaseController as wt, CrudController as x, ValidationResult$1 as xt, ConfigError as y, UserOrganization as yt, JWTPayload as z, RegisterOptions as zt };
|
package/dist/org/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Rt as RouteHandler } from "../interface-DYH8AXGe.mjs";
|
|
2
2
|
import { i as UserBase } from "../types-BNUccdcf.mjs";
|
|
3
3
|
import { InvitationAdapter, InvitationDoc, MemberDoc, OrgAdapter, OrgDoc, OrgPermissionStatement, OrgRole, OrganizationPluginOptions } from "./types.mjs";
|
|
4
4
|
import { FastifyPluginAsync, RouteHandlerMethod } from "fastify";
|
package/dist/plugins/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Bt as ResourceRegistry, H as MiddlewareConfig, X as PresetHook, cn as HookSystem, ft as RouteSchemaOptions, l as AdditionalRoute, u as AnyRecord } from "../interface-DYH8AXGe.mjs";
|
|
2
2
|
import { t as ExternalOpenApiPaths } from "../externalPaths-DpO-s7r8.mjs";
|
|
3
3
|
import { _ as cachingPlugin, a as versioningPlugin, c as MetricsOptions, d as SSEOptions, f as _default$6, g as _default$1, h as CachingRule, i as _default$7, l as _default$4, m as CachingOptions, n as errorHandlerPlugin, o as MetricEntry, p as ssePlugin, r as VersioningOptions, s as MetricsCollector, t as ErrorHandlerOptions, u as metricsPlugin } from "../errorHandler-Do4vVQ1f.mjs";
|
|
4
4
|
import { t as TracingOptions } from "../tracing-bz_U4EM1.mjs";
|
|
@@ -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.6.
|
|
47
|
+
const resolvedVersion = serviceVersion ?? "2.6.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,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Gt as PaginatedResult, It as IControllerResponse, Lt as IRequestContext, Z as PresetResult, ot as ResourceConfig, u as AnyRecord } from "../interface-DYH8AXGe.mjs";
|
|
2
2
|
import { MultiTenantOptions, multiTenantPreset } from "./multiTenant.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/presets/ownedByUser.d.ts
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Bt as ResourceRegistry, R as IntrospectionPluginOptions, zt as RegisterOptions } from "../interface-DYH8AXGe.mjs";
|
|
2
2
|
import { FastifyPluginAsync } from "fastify";
|
|
3
3
|
|
|
4
4
|
//#region src/registry/introspectionPlugin.d.ts
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as BaseController } from "./BaseController-
|
|
1
|
+
import { t as BaseController } from "./BaseController-DzRtluEF.mjs";
|
|
2
2
|
import { t as pluralize } from "./pluralize-CcT6qF0a.mjs";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
//#region src/integrations/mcp/createMcpServer.ts
|
|
@@ -314,6 +314,146 @@ function buildScope(auth) {
|
|
|
314
314
|
};
|
|
315
315
|
}
|
|
316
316
|
//#endregion
|
|
317
|
+
//#region src/integrations/mcp/jsonSchemaToZod.ts
|
|
318
|
+
/**
|
|
319
|
+
* @classytic/arc — JSON Schema → Zod shape converter
|
|
320
|
+
*
|
|
321
|
+
* Converts an adapter-emitted JSON Schema body shape (`createBody` / `updateBody`)
|
|
322
|
+
* to a flat Zod shape compatible with the MCP SDK's `registerTool({ inputSchema })`
|
|
323
|
+
* contract.
|
|
324
|
+
*
|
|
325
|
+
* Why this exists:
|
|
326
|
+
* - The MCP SDK expects a flat `Record<string, ZodType>` shape (it wraps it in
|
|
327
|
+
* z.object() internally).
|
|
328
|
+
* - When users don't supply explicit `schemaOptions.fieldRules`, MCP would
|
|
329
|
+
* otherwise see an empty schema and silently strip every body field — that's
|
|
330
|
+
* a real DX footgun.
|
|
331
|
+
* - Adapters (Mongoose, MongoKit's buildCrudSchemasFromModel, custom) already
|
|
332
|
+
* emit JSON Schema describing the body. We translate it to Zod so MCP tools
|
|
333
|
+
* can validate input the same way REST routes do.
|
|
334
|
+
*
|
|
335
|
+
* Supported JSON Schema features:
|
|
336
|
+
* - Primitives: string, number, integer, boolean, null (skipped)
|
|
337
|
+
* - Constraints: minLength, maxLength, minimum, maximum, pattern, enum, format
|
|
338
|
+
* - Arrays: typed items + nested object items
|
|
339
|
+
* - Nested objects with `properties` (recursive)
|
|
340
|
+
* - Type unions: ["string", "null"] → string (null skipped)
|
|
341
|
+
* - Composition: oneOf / anyOf / allOf → first viable branch
|
|
342
|
+
* - $ref → permissive (z.unknown()) — refs are not resolved
|
|
343
|
+
* - Unknown types → z.unknown() (lenient — let the controller validate)
|
|
344
|
+
*
|
|
345
|
+
* NOT supported (intentionally — keeps the surface small + deterministic):
|
|
346
|
+
* - Conditional schemas (if/then/else)
|
|
347
|
+
* - dependencies / dependentRequired
|
|
348
|
+
* - Custom keywords beyond standard JSON Schema
|
|
349
|
+
*/
|
|
350
|
+
/**
|
|
351
|
+
* Convert a JSON Schema **object** body to a flat Zod shape.
|
|
352
|
+
* Returns `undefined` if the input has no usable properties.
|
|
353
|
+
*
|
|
354
|
+
* @param schema Top-level JSON Schema (must be `type: 'object'` with `properties`)
|
|
355
|
+
* @param mode 'create' enforces required fields, 'update' makes everything optional
|
|
356
|
+
*/
|
|
357
|
+
function jsonSchemaToZodShape(schema, mode = "create") {
|
|
358
|
+
if (!schema || typeof schema !== "object") return void 0;
|
|
359
|
+
if (!schema.properties || typeof schema.properties !== "object") return void 0;
|
|
360
|
+
const requiredSet = new Set(schema.required ?? []);
|
|
361
|
+
const shape = {};
|
|
362
|
+
for (const [name, propSchema] of Object.entries(schema.properties)) {
|
|
363
|
+
if (!propSchema || typeof propSchema !== "object") continue;
|
|
364
|
+
const fieldZod = jsonSchemaPropertyToZod(propSchema);
|
|
365
|
+
if (!fieldZod) continue;
|
|
366
|
+
shape[name] = mode === "create" && requiredSet.has(name) ? fieldZod : fieldZod.optional();
|
|
367
|
+
}
|
|
368
|
+
return Object.keys(shape).length > 0 ? shape : void 0;
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Convert one JSON Schema node to a Zod type.
|
|
372
|
+
* Handles primitives, arrays, nested objects, type unions, and composition.
|
|
373
|
+
*/
|
|
374
|
+
function jsonSchemaPropertyToZod(prop) {
|
|
375
|
+
if (prop.$ref) return applyDescription(z.unknown(), prop);
|
|
376
|
+
if (Array.isArray(prop.oneOf) && prop.oneOf.length > 0) for (const branch of prop.oneOf) {
|
|
377
|
+
const z1 = jsonSchemaPropertyToZod(branch);
|
|
378
|
+
if (z1) return applyDescription(z1, prop);
|
|
379
|
+
}
|
|
380
|
+
if (Array.isArray(prop.anyOf) && prop.anyOf.length > 0) for (const branch of prop.anyOf) {
|
|
381
|
+
const z1 = jsonSchemaPropertyToZod(branch);
|
|
382
|
+
if (z1) return applyDescription(z1, prop);
|
|
383
|
+
}
|
|
384
|
+
if (Array.isArray(prop.allOf) && prop.allOf.length > 0) for (let i = prop.allOf.length - 1; i >= 0; i--) {
|
|
385
|
+
const branch = prop.allOf[i];
|
|
386
|
+
const z1 = jsonSchemaPropertyToZod(branch);
|
|
387
|
+
if (z1) return applyDescription(z1, prop);
|
|
388
|
+
}
|
|
389
|
+
if (Array.isArray(prop.enum) && prop.enum.length > 0) {
|
|
390
|
+
const stringValues = prop.enum.filter((v) => typeof v === "string");
|
|
391
|
+
if (stringValues.length > 0) return applyDescription(z.enum(stringValues), prop);
|
|
392
|
+
return applyDescription(z.number(), prop);
|
|
393
|
+
}
|
|
394
|
+
const typeCandidates = pickEffectiveType(prop.type);
|
|
395
|
+
for (const t of typeCandidates) switch (t) {
|
|
396
|
+
case "string": return applyStringConstraints(z.string(), prop);
|
|
397
|
+
case "number":
|
|
398
|
+
case "integer": return applyNumberConstraints(z.number(), prop);
|
|
399
|
+
case "boolean": return applyDescription(z.boolean(), prop);
|
|
400
|
+
case "array": return applyDescription(arrayToZod(prop), prop);
|
|
401
|
+
case "object": return applyDescription(objectToZod(prop), prop);
|
|
402
|
+
case "null": continue;
|
|
403
|
+
default: break;
|
|
404
|
+
}
|
|
405
|
+
return applyDescription(z.unknown(), prop);
|
|
406
|
+
}
|
|
407
|
+
function pickEffectiveType(rawType) {
|
|
408
|
+
if (!rawType) return [];
|
|
409
|
+
if (Array.isArray(rawType)) return rawType.filter((t) => t !== "null");
|
|
410
|
+
return rawType === "null" ? [] : [rawType];
|
|
411
|
+
}
|
|
412
|
+
function arrayToZod(prop) {
|
|
413
|
+
const items = prop.items;
|
|
414
|
+
if (items && typeof items === "object") {
|
|
415
|
+
const itemZod = jsonSchemaPropertyToZod(items);
|
|
416
|
+
if (itemZod) return z.array(itemZod);
|
|
417
|
+
}
|
|
418
|
+
return z.array(z.unknown());
|
|
419
|
+
}
|
|
420
|
+
function objectToZod(prop) {
|
|
421
|
+
if (prop.properties && typeof prop.properties === "object") {
|
|
422
|
+
const requiredSet = new Set(prop.required ?? []);
|
|
423
|
+
const innerShape = {};
|
|
424
|
+
for (const [k, v] of Object.entries(prop.properties)) {
|
|
425
|
+
if (!v || typeof v !== "object") continue;
|
|
426
|
+
const inner = jsonSchemaPropertyToZod(v);
|
|
427
|
+
if (!inner) continue;
|
|
428
|
+
innerShape[k] = requiredSet.has(k) ? inner : inner.optional();
|
|
429
|
+
}
|
|
430
|
+
if (Object.keys(innerShape).length > 0) return z.object(innerShape);
|
|
431
|
+
}
|
|
432
|
+
return z.record(z.string(), z.unknown());
|
|
433
|
+
}
|
|
434
|
+
function applyStringConstraints(base, prop) {
|
|
435
|
+
let s = base;
|
|
436
|
+
if (typeof prop.minLength === "number") s = s.min(prop.minLength);
|
|
437
|
+
if (typeof prop.maxLength === "number") s = s.max(prop.maxLength);
|
|
438
|
+
if (typeof prop.pattern === "string") try {
|
|
439
|
+
s = s.regex(new RegExp(prop.pattern));
|
|
440
|
+
} catch {}
|
|
441
|
+
if (prop.format === "email") s = s.email();
|
|
442
|
+
if (prop.format === "uuid") s = s.uuid();
|
|
443
|
+
if (prop.format === "uri" || prop.format === "url") s = s.url();
|
|
444
|
+
return applyDescription(s, prop);
|
|
445
|
+
}
|
|
446
|
+
function applyNumberConstraints(base, prop) {
|
|
447
|
+
let n = base;
|
|
448
|
+
if (typeof prop.minimum === "number") n = n.min(prop.minimum);
|
|
449
|
+
if (typeof prop.maximum === "number") n = n.max(prop.maximum);
|
|
450
|
+
return applyDescription(n, prop);
|
|
451
|
+
}
|
|
452
|
+
function applyDescription(zodType, prop) {
|
|
453
|
+
if (typeof prop.description === "string" && prop.description.length > 0) return zodType.describe(prop.description);
|
|
454
|
+
return zodType;
|
|
455
|
+
}
|
|
456
|
+
//#endregion
|
|
317
457
|
//#region src/integrations/mcp/resourceToTools.ts
|
|
318
458
|
/**
|
|
319
459
|
* @classytic/arc — Resource → MCP Tools Generator
|
|
@@ -359,9 +499,11 @@ const ANNOTATIONS = {
|
|
|
359
499
|
function resourceToTools(resource, config = {}) {
|
|
360
500
|
const controller = resource.controller ?? (resource.adapter ? createMcpController(resource) : void 0);
|
|
361
501
|
if (!controller) return [];
|
|
362
|
-
const
|
|
502
|
+
const explicitFieldRules = resource.schemaOptions?.fieldRules;
|
|
363
503
|
const hiddenFields = resource.schemaOptions?.hiddenFields;
|
|
364
504
|
const readonlyFields = resource.schemaOptions?.readonlyFields;
|
|
505
|
+
const adapterBodies = explicitFieldRules ? void 0 : getAdapterBodies(resource);
|
|
506
|
+
const fieldRules = explicitFieldRules ?? deriveFieldRulesFromAdapter(resource);
|
|
365
507
|
const filterableFields = resource.schemaOptions?.filterableFields ?? resource.queryParser?.allowedFilterFields;
|
|
366
508
|
const sortableFields = resource.queryParser?.allowedSortFields;
|
|
367
509
|
const allowedOperators = resource.queryParser?.allowedOperators;
|
|
@@ -388,7 +530,8 @@ function resourceToTools(resource, config = {}) {
|
|
|
388
530
|
readonlyFields,
|
|
389
531
|
extraHideFields: config.hideFields,
|
|
390
532
|
filterableFields,
|
|
391
|
-
allowedOperators
|
|
533
|
+
allowedOperators,
|
|
534
|
+
adapterBodies
|
|
392
535
|
}),
|
|
393
536
|
handler: createHandler(op, controller, resource.name, resource.permissions)
|
|
394
537
|
});
|
|
@@ -442,20 +585,57 @@ function buildInputSchema(op, fieldRules, opts) {
|
|
|
442
585
|
...opts
|
|
443
586
|
});
|
|
444
587
|
case "get": return { id: z.string().describe("Resource ID") };
|
|
445
|
-
case "create":
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
mode: "update",
|
|
588
|
+
case "create":
|
|
589
|
+
if (!fieldRules && opts.adapterBodies?.createBody) {
|
|
590
|
+
const shape = jsonSchemaToZodShape(opts.adapterBodies.createBody, "create");
|
|
591
|
+
if (shape) return shape;
|
|
592
|
+
}
|
|
593
|
+
return fieldRulesToZod(fieldRules, {
|
|
594
|
+
mode: "create",
|
|
453
595
|
...opts
|
|
454
|
-
})
|
|
455
|
-
|
|
596
|
+
});
|
|
597
|
+
case "update": {
|
|
598
|
+
const idShape = { id: z.string().describe("Resource ID") };
|
|
599
|
+
if (!fieldRules && opts.adapterBodies?.updateBody) {
|
|
600
|
+
const shape = jsonSchemaToZodShape(opts.adapterBodies.updateBody, "update");
|
|
601
|
+
if (shape) return {
|
|
602
|
+
...idShape,
|
|
603
|
+
...shape
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
return {
|
|
607
|
+
...idShape,
|
|
608
|
+
...fieldRulesToZod(fieldRules, {
|
|
609
|
+
mode: "update",
|
|
610
|
+
...opts
|
|
611
|
+
})
|
|
612
|
+
};
|
|
613
|
+
}
|
|
456
614
|
case "delete": return { id: z.string().describe("Resource ID") };
|
|
457
615
|
}
|
|
458
616
|
}
|
|
617
|
+
/**
|
|
618
|
+
* Pull the adapter's `createBody` / `updateBody` schemas, if any.
|
|
619
|
+
* Returns `undefined` when the adapter doesn't generate schemas or throws.
|
|
620
|
+
*/
|
|
621
|
+
function getAdapterBodies(resource) {
|
|
622
|
+
const adapter = resource.adapter;
|
|
623
|
+
if (!adapter || typeof adapter.generateSchemas !== "function") return void 0;
|
|
624
|
+
try {
|
|
625
|
+
const generated = adapter.generateSchemas(resource.schemaOptions, {
|
|
626
|
+
idField: resource.idField,
|
|
627
|
+
resourceName: resource.name
|
|
628
|
+
});
|
|
629
|
+
if (!generated || typeof generated !== "object") return void 0;
|
|
630
|
+
const schemas = generated;
|
|
631
|
+
return {
|
|
632
|
+
createBody: schemas.createBody,
|
|
633
|
+
updateBody: schemas.updateBody
|
|
634
|
+
};
|
|
635
|
+
} catch {
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
459
639
|
function createHandler(op, controller, resourceName, permissions) {
|
|
460
640
|
const ctrl = controller;
|
|
461
641
|
return async (input, ctx) => {
|
|
@@ -550,6 +730,63 @@ async function evaluatePermission(check, session, resource, action, input) {
|
|
|
550
730
|
if (!permResult.granted) return false;
|
|
551
731
|
return permResult.filters ?? null;
|
|
552
732
|
}
|
|
733
|
+
/**
|
|
734
|
+
* Derive a fieldRules-shaped object from the adapter's auto-generated body
|
|
735
|
+
* schemas. Used as a fallback when the resource doesn't supply explicit
|
|
736
|
+
* fieldRules — this lets MCP create/update tools accept the same body fields
|
|
737
|
+
* that the REST routes already accept.
|
|
738
|
+
*
|
|
739
|
+
* Returns `undefined` if no usable schema can be extracted, in which case
|
|
740
|
+
* `fieldRulesToZod` falls back to its own behavior (empty shape).
|
|
741
|
+
*/
|
|
742
|
+
function deriveFieldRulesFromAdapter(resource) {
|
|
743
|
+
const adapter = resource.adapter;
|
|
744
|
+
if (!adapter || typeof adapter.generateSchemas !== "function") return void 0;
|
|
745
|
+
let generated;
|
|
746
|
+
try {
|
|
747
|
+
generated = adapter.generateSchemas(resource.schemaOptions, {
|
|
748
|
+
idField: resource.idField,
|
|
749
|
+
resourceName: resource.name
|
|
750
|
+
});
|
|
751
|
+
} catch {
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
if (!generated || typeof generated !== "object") return void 0;
|
|
755
|
+
const schemas = generated;
|
|
756
|
+
const createBody = schemas.createBody;
|
|
757
|
+
const updateBody = schemas.updateBody;
|
|
758
|
+
const properties = createBody?.properties ?? updateBody?.properties;
|
|
759
|
+
if (!properties || typeof properties !== "object") return void 0;
|
|
760
|
+
const requiredSet = new Set(createBody?.required ?? []);
|
|
761
|
+
const rules = {};
|
|
762
|
+
for (const [name, propSchema] of Object.entries(properties)) {
|
|
763
|
+
if (!propSchema || typeof propSchema !== "object") continue;
|
|
764
|
+
const prop = propSchema;
|
|
765
|
+
const rawType = prop.type;
|
|
766
|
+
const rule = { type: mapJsonSchemaTypeToArcType((Array.isArray(rawType) ? rawType.filter((t) => typeof t === "string") : typeof rawType === "string" ? [rawType] : [])[0]) };
|
|
767
|
+
if (requiredSet.has(name)) rule.required = true;
|
|
768
|
+
if (typeof prop.description === "string") rule.description = prop.description;
|
|
769
|
+
if (Array.isArray(prop.enum)) rule.enum = prop.enum.filter((v) => typeof v === "string");
|
|
770
|
+
if (typeof prop.minLength === "number") rule.minLength = prop.minLength;
|
|
771
|
+
if (typeof prop.maxLength === "number") rule.maxLength = prop.maxLength;
|
|
772
|
+
if (typeof prop.minimum === "number") rule.min = prop.minimum;
|
|
773
|
+
if (typeof prop.maximum === "number") rule.max = prop.maximum;
|
|
774
|
+
if (typeof prop.pattern === "string") rule.pattern = prop.pattern;
|
|
775
|
+
rules[name] = rule;
|
|
776
|
+
}
|
|
777
|
+
return Object.keys(rules).length > 0 ? rules : void 0;
|
|
778
|
+
}
|
|
779
|
+
function mapJsonSchemaTypeToArcType(jsonType) {
|
|
780
|
+
switch (jsonType) {
|
|
781
|
+
case "string": return "string";
|
|
782
|
+
case "number":
|
|
783
|
+
case "integer": return "number";
|
|
784
|
+
case "boolean": return "boolean";
|
|
785
|
+
case "array": return "array";
|
|
786
|
+
case "object": return "object";
|
|
787
|
+
default: return "string";
|
|
788
|
+
}
|
|
789
|
+
}
|
|
553
790
|
function toCallToolResult(result) {
|
|
554
791
|
if (!result.success) return {
|
|
555
792
|
content: [{
|
package/dist/testing/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { d as ResourceLike, r as CreateAppOptions } from "../types-
|
|
1
|
+
import { Ut as CrudRepository, Vt as ResourceDefinition, u as AnyRecord } from "../interface-DYH8AXGe.mjs";
|
|
2
|
+
import { d as ResourceLike, r as CreateAppOptions } from "../types-By-5mIfn.mjs";
|
|
3
3
|
import Fastify, { FastifyInstance, FastifyServerOptions } from "fastify";
|
|
4
4
|
import { Connection } from "mongoose";
|
|
5
5
|
import { Mock } from "vitest";
|
package/dist/types/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { _ as isMember, a as AUTHENTICATED_SCOPE, d as getTeamId, g as isElevated, h as isAuthenticated, l as getOrgId, m as hasOrgAccess, n as ElevationOptions, o as PUBLIC_SCOPE, s as RequestScope, t as ElevationEvent, u as getOrgRoles } from "../elevation-C_taLQrM.mjs";
|
|
2
|
-
import { $ as
|
|
2
|
+
import { $ as RateLimitConfig, A as FieldRule, B as JwtContext, C as CrudRouterOptions, Ct as getUserId, D as FastifyRequestExtras, E as EventsDecorator, F as InferDocType, Ft as IController, G as OpenApiSchemas, Gt as PaginatedResult, H as MiddlewareConfig, I as InferResourceDoc, It as IControllerResponse, J as PopulateOption, K as OwnershipCheck, Kt as PaginationParams, L as IntrospectionData, Lt as IRequestContext, M as HealthCheck, Mt as ControllerHandler, N as HealthOptions, Nt as ControllerLike, O as FastifyWithAuth, P as InferAdapterDoc, Pt as FastifyHandler, Q as QueryParserInterface, R as IntrospectionPluginOptions, Rt as RouteHandler, S as CrudRouteKey, St as envelope, T as EventDefinition, Tt as BaseControllerOptions, U as MiddlewareHandler, Ut as CrudRepository, V as LookupOption, W as ObjectId, Wt as InferDoc, X as PresetHook, Y as PresetFunction, Z as PresetResult, _ as Authenticator, _t as TypedResourceConfig, at as ResourceCacheConfig, b as ControllerQueryOptions, bt as ValidateOptions, ct as ResourceHooks, d as ApiResponse, dt as RouteHandlerMethod, et as RegistryEntry, f as ArcDecorator, ft as RouteSchemaOptions, g as AuthPluginOptions, gt as TypedRepository, h as AuthHelpers, ht as TypedController, it as RequestWithExtras, j as GracefulShutdownOptions, k as FastifyWithDecorators, l as AdditionalRoute, lt as ResourceMetadata, m as ArcRequest, mt as TokenPair, nt as RequestContext, ot as ResourceConfig, p as ArcInternalMetadata, pt as ServiceContext, q as ParsedQuery, qt as QueryOptions, rt as RequestIdOptions, st as ResourceHookContext, tt as RegistryStats, u as AnyRecord, ut as ResourcePermissions, v as AuthenticatorContext, vt as UserLike, w as CrudSchemas, x as CrudController, xt as ValidationResult, y as ConfigError, yt as UserOrganization, z as JWTPayload } from "../interface-DYH8AXGe.mjs";
|
|
3
3
|
import { i as UserBase, n as PermissionContext, r as PermissionResult, t as PermissionCheck } from "../types-BNUccdcf.mjs";
|
|
4
4
|
export { AUTHENTICATED_SCOPE, AdditionalRoute, AnyRecord, ApiResponse, ArcDecorator, ArcInternalMetadata, ArcRequest, AuthHelpers, AuthPluginOptions, Authenticator, AuthenticatorContext, BaseControllerOptions, ConfigError, ControllerHandler, ControllerLike, ControllerQueryOptions, CrudController, CrudRepository, CrudRouteKey, CrudRouterOptions, CrudSchemas, ElevationEvent, ElevationOptions, EventDefinition, EventsDecorator, FastifyHandler, FastifyRequestExtras, FastifyWithAuth, FastifyWithDecorators, FieldRule, GracefulShutdownOptions, HealthCheck, HealthOptions, IController, IControllerResponse, IRequestContext, InferAdapterDoc, InferDoc, InferDocType, InferResourceDoc, IntrospectionData, IntrospectionPluginOptions, JWTPayload, JwtContext, LookupOption, MiddlewareConfig, MiddlewareHandler, ObjectId, OpenApiSchemas, OwnershipCheck, PUBLIC_SCOPE, PaginatedResult, PaginationParams, ParsedQuery, PermissionCheck, PermissionContext, PermissionResult, PopulateOption, PresetFunction, PresetHook, PresetResult, QueryOptions, QueryParserInterface, RateLimitConfig, RegistryEntry, RegistryStats, RequestContext, RequestIdOptions, RequestScope, RequestWithExtras, ResourceCacheConfig, ResourceConfig, ResourceHookContext, ResourceHooks, ResourceMetadata, ResourcePermissions, RouteHandler, RouteHandlerMethod, RouteSchemaOptions, ServiceContext, TokenPair, TypedController, TypedRepository, TypedResourceConfig, UserBase, UserLike, UserOrganization, ValidateOptions, ValidationResult, envelope, getOrgId, getOrgRoles, getTeamId, getUserId, hasOrgAccess, isAuthenticated, isElevated, isMember };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as ElevationOptions } from "./elevation-C_taLQrM.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { _ as Authenticator } from "./interface-DYH8AXGe.mjs";
|
|
3
3
|
import { t as ExternalOpenApiPaths } from "./externalPaths-DpO-s7r8.mjs";
|
|
4
4
|
import { i as CacheStore } from "./interface-D_BWALyZ.mjs";
|
|
5
5
|
import { r as QueryCachePluginOptions } from "./queryCachePlugin-DcmETvcB.mjs";
|
package/dist/utils/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { G as OpenApiSchemas, Q as QueryParserInterface, q as ParsedQuery, u as AnyRecord } from "../interface-DYH8AXGe.mjs";
|
|
2
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-CcVbl1-T.mjs";
|
|
3
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-JP2GdJ4b.mjs";
|
|
4
4
|
import { FastifyInstance } from "fastify";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@classytic/arc",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.3",
|
|
4
4
|
"description": "Resource-oriented backend framework for Fastify — clean, minimal, powerful, tree-shakable",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -222,31 +222,31 @@
|
|
|
222
222
|
"peerDependencies": {
|
|
223
223
|
"@classytic/mongokit": ">=3.5.0",
|
|
224
224
|
"@classytic/streamline": ">=2.0.0",
|
|
225
|
-
"@fastify/cors": "
|
|
226
|
-
"@fastify/helmet": "
|
|
227
|
-
"@fastify/jwt": "
|
|
228
|
-
"@fastify/multipart": "
|
|
229
|
-
"@fastify/rate-limit": "
|
|
230
|
-
"@fastify/sensible": "
|
|
231
|
-
"@fastify/type-provider-typebox": "
|
|
232
|
-
"@fastify/under-pressure": "
|
|
233
|
-
"@fastify/websocket": "
|
|
225
|
+
"@fastify/cors": ">=11.0.0",
|
|
226
|
+
"@fastify/helmet": ">=13.0.0",
|
|
227
|
+
"@fastify/jwt": ">=10.0.0",
|
|
228
|
+
"@fastify/multipart": ">=9.0.0",
|
|
229
|
+
"@fastify/rate-limit": ">=10.0.0",
|
|
230
|
+
"@fastify/sensible": ">=6.0.0",
|
|
231
|
+
"@fastify/type-provider-typebox": ">=6.0.0",
|
|
232
|
+
"@fastify/under-pressure": ">=9.0.0",
|
|
233
|
+
"@fastify/websocket": ">=11.0.0",
|
|
234
234
|
"@modelcontextprotocol/sdk": ">=1.28.0",
|
|
235
235
|
"@opentelemetry/auto-instrumentations-node": ">=0.40.0",
|
|
236
236
|
"@opentelemetry/exporter-trace-otlp-http": ">=0.50.0",
|
|
237
237
|
"@opentelemetry/instrumentation-http": ">=0.50.0",
|
|
238
238
|
"@opentelemetry/instrumentation-mongodb": ">=0.40.0",
|
|
239
239
|
"@opentelemetry/sdk-node": ">=0.50.0",
|
|
240
|
-
"@sinclair/typebox": "
|
|
240
|
+
"@sinclair/typebox": ">=0.34.0",
|
|
241
241
|
"better-auth": ">=1.5.5",
|
|
242
|
-
"bullmq": "
|
|
243
|
-
"fastify": "
|
|
244
|
-
"fastify-raw-body": "
|
|
245
|
-
"ioredis": "
|
|
246
|
-
"mongodb": "
|
|
242
|
+
"bullmq": ">=5.0.0",
|
|
243
|
+
"fastify": ">=5.0.0",
|
|
244
|
+
"fastify-raw-body": ">=5.0.0",
|
|
245
|
+
"ioredis": ">=5.0.0",
|
|
246
|
+
"mongodb": ">=6.0.0",
|
|
247
247
|
"mongoose": ">=9.0.0",
|
|
248
|
-
"pino-pretty": "
|
|
249
|
-
"zod": "
|
|
248
|
+
"pino-pretty": ">=13.0.0",
|
|
249
|
+
"zod": ">=4.0.0"
|
|
250
250
|
},
|
|
251
251
|
"peerDependenciesMeta": {
|
|
252
252
|
"@classytic/mongokit": {
|
package/skills/arc/SKILL.md
CHANGED
|
@@ -220,6 +220,31 @@ When to use `tenantField: false`:
|
|
|
220
220
|
- Cross-org reports or analytics
|
|
221
221
|
- Single-tenant apps where org scoping isn't needed
|
|
222
222
|
|
|
223
|
+
### idField — Custom Primary Key
|
|
224
|
+
|
|
225
|
+
Default is `'_id'`. Override for resources keyed by a business identifier (UUID, slug, `ORD-2026-0001`, `job-5219f346-a4d`, etc.).
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
defineResource({
|
|
229
|
+
name: 'job',
|
|
230
|
+
adapter: createMongooseAdapter(JobModel, jobRepository),
|
|
231
|
+
idField: 'jobId', // ← one line
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// GET /jobs/job-5219f346-a4d → controller runs { jobId: 'job-5219f346-a4d' }
|
|
235
|
+
// GET /jobs/<uuid> → accepted (no ObjectId pattern enforcement)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Changes all three layers:
|
|
239
|
+
- **Fastify AJV** — strips any ObjectId pattern from `params.id` so custom formats aren't pre-rejected
|
|
240
|
+
- **BaseController** — `get`/`update`/`delete` query by `{ [idField]: id }` (merged with tenant + policy filters)
|
|
241
|
+
- **OpenAPI docs** — `spec.paths['/jobs/{id}']` emits a plain string `id` with description
|
|
242
|
+
- **MCP tools** — auto-generated CRUD tools use `idField` transparently
|
|
243
|
+
|
|
244
|
+
URL path segment stays `:id` (not `:jobId`) — clients send the ID value, Arc maps it server-side. User-provided `openApiSchemas.params` still overrides everything.
|
|
245
|
+
|
|
246
|
+
For custom adapters, honor the new `AdapterSchemaContext` passed to `generateSchemas(options, context?)` to emit the right `params.id` pattern from the start. Legacy adapters still work — Arc's safety net strips mismatched ObjectId patterns automatically.
|
|
247
|
+
|
|
223
248
|
## QueryCache
|
|
224
249
|
|
|
225
250
|
TanStack Query-inspired server cache with stale-while-revalidate and auto-invalidation on mutations.
|