@classytic/arc 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import "../elevation-DGo5shaX.mjs";
2
- import { a as RepositoryLike, i as RelationMetadata, n as DataAdapter, o as SchemaMetadata, r as FieldMetadata, s as ValidationResult, t as AdapterFactory } from "../interface-Dm4-jnia.mjs";
2
+ import { a as RepositoryLike, i as RelationMetadata, n as DataAdapter, o as SchemaMetadata, r as FieldMetadata, s as ValidationResult, t as AdapterFactory } from "../interface-BtdYtQUA.mjs";
3
3
  import "../types-RLkFVgaw.mjs";
4
- 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 "../prisma-Bi9nxirN.mjs";
4
+ 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 "../prisma-Dy5S5F5i.mjs";
5
5
  export { type AdapterFactory, type DataAdapter, type FieldMetadata, MongooseAdapter, type MongooseAdapterOptions, PrismaAdapter, type PrismaAdapterOptions, type PrismaQueryOptions, PrismaQueryParser, type PrismaQueryParserOptions, type RelationMetadata, type RepositoryLike, type SchemaMetadata, type ValidationResult, createMongooseAdapter, createPrismaAdapter };
@@ -1,5 +1,5 @@
1
1
  import "../elevation-DGo5shaX.mjs";
2
- import "../interface-Dm4-jnia.mjs";
2
+ import "../interface-BtdYtQUA.mjs";
3
3
  import "../types-RLkFVgaw.mjs";
4
4
  import { a as AuditContext, c as AuditStore, i as AuditAction, l as AuditStoreOptions, n as MongoAuditStoreOptions, o as AuditEntry, r as MongoConnection, s as AuditQueryOptions, u as createAuditEntry } from "../mongodb-ClykrfGo.mjs";
5
5
  import { FastifyPluginAsync } from "fastify";
@@ -1,5 +1,5 @@
1
1
  import "../elevation-DGo5shaX.mjs";
2
- import "../interface-Dm4-jnia.mjs";
2
+ import "../interface-BtdYtQUA.mjs";
3
3
  import "../types-RLkFVgaw.mjs";
4
4
  import { n as MongoAuditStoreOptions, t as MongoAuditStore } from "../mongodb-ClykrfGo.mjs";
5
5
  export { MongoAuditStore, type MongoAuditStoreOptions };
@@ -1,5 +1,5 @@
1
1
  import "../elevation-DGo5shaX.mjs";
2
- import "../interface-Dm4-jnia.mjs";
2
+ import "../interface-BtdYtQUA.mjs";
3
3
  import { t as PermissionCheck } from "../types-RLkFVgaw.mjs";
4
4
  import { AuthHelpers, AuthPluginOptions } from "../types/index.mjs";
5
5
  import { t as ExternalOpenApiPaths } from "../externalPaths-SyPF2tgK.mjs";
@@ -279,8 +279,7 @@ var ArcQueryParser = class {
279
279
  },
280
280
  _filterOperators: {
281
281
  type: "string",
282
- description: ["Available filter operators (use as field[operator]=value):", ...operatorLines].join("\n"),
283
- "x-internal": true
282
+ description: ["Available filter operators (use as field[operator]=value):", ...operatorLines].join("\n")
284
283
  }
285
284
  }
286
285
  };
@@ -485,7 +484,7 @@ function mutationResponse(itemSchema) {
485
484
  /**
486
485
  * Create a delete response schema
487
486
  *
488
- * Runtime format: { success, message }
487
+ * Runtime format: { success, data: { message, id?, soft? } }
489
488
  */
490
489
  function deleteResponse() {
491
490
  return {
@@ -495,9 +494,23 @@ function deleteResponse() {
495
494
  type: "boolean",
496
495
  example: true
497
496
  },
498
- message: {
499
- type: "string",
500
- example: "Deleted successfully"
497
+ data: {
498
+ type: "object",
499
+ properties: {
500
+ message: {
501
+ type: "string",
502
+ example: "Deleted successfully"
503
+ },
504
+ id: {
505
+ type: "string",
506
+ example: "507f1f77bcf86cd799439011"
507
+ },
508
+ soft: {
509
+ type: "boolean",
510
+ example: false
511
+ }
512
+ },
513
+ required: ["message"]
501
514
  }
502
515
  },
503
516
  required: ["success"],
@@ -1,5 +1,5 @@
1
1
  import "../elevation-DGo5shaX.mjs";
2
- import { E as defineResource, T as ResourceDefinition, c as BaseController, d as QueryResolverConfig, f as BodySanitizer, h as AccessControlConfig, l as BaseControllerOptions, m as AccessControl, p as BodySanitizerConfig, u as QueryResolver } from "../interface-Dm4-jnia.mjs";
2
+ import { E as defineResource, T as ResourceDefinition, c as BaseController, d as QueryResolverConfig, f as BodySanitizer, h as AccessControlConfig, l as BaseControllerOptions, m as AccessControl, p as BodySanitizerConfig, u as QueryResolver } from "../interface-BtdYtQUA.mjs";
3
3
  import "../types-RLkFVgaw.mjs";
4
- import { A as createCrudRouter, C as MutationOperation, D as ActionRouterConfig, E as ActionHandler, O as IdempotencyService, S as MUTATION_OPERATIONS, T as SYSTEM_FIELDS, _ as HookOperation, a as getControllerScope, b as MAX_REGEX_LENGTH, c as CrudOperation, d as DEFAULT_MAX_LIMIT, f as DEFAULT_SORT, g as HOOK_PHASES, h as HOOK_OPERATIONS, i as getControllerContext, j as createPermissionMiddleware, k as createActionRouter, l as DEFAULT_ID_FIELD, m as DEFAULT_UPDATE_METHOD, n as createFastifyHandler, o as sendControllerResponse, p as DEFAULT_TENANT_FIELD, r as createRequestContext, s as CRUD_OPERATIONS, t as createCrudHandlers, u as DEFAULT_LIMIT, v as HookPhase, w as RESERVED_QUERY_PARAMS, x as MAX_SEARCH_LENGTH, y as MAX_FILTER_DEPTH } from "../fastifyAdapter-DTOLNtjw.mjs";
4
+ import { A as createCrudRouter, C as MutationOperation, D as ActionRouterConfig, E as ActionHandler, O as IdempotencyService, S as MUTATION_OPERATIONS, T as SYSTEM_FIELDS, _ as HookOperation, a as getControllerScope, b as MAX_REGEX_LENGTH, c as CrudOperation, d as DEFAULT_MAX_LIMIT, f as DEFAULT_SORT, g as HOOK_PHASES, h as HOOK_OPERATIONS, i as getControllerContext, j as createPermissionMiddleware, k as createActionRouter, l as DEFAULT_ID_FIELD, m as DEFAULT_UPDATE_METHOD, n as createFastifyHandler, o as sendControllerResponse, p as DEFAULT_TENANT_FIELD, r as createRequestContext, s as CRUD_OPERATIONS, t as createCrudHandlers, u as DEFAULT_LIMIT, v as HookPhase, w as RESERVED_QUERY_PARAMS, x as MAX_SEARCH_LENGTH, y as MAX_FILTER_DEPTH } from "../fastifyAdapter-6b_eRDBw.mjs";
5
5
  export { AccessControl, type AccessControlConfig, type ActionHandler, type ActionRouterConfig, BaseController, type BaseControllerOptions, BodySanitizer, type 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, type IdempotencyService, MAX_FILTER_DEPTH, MAX_REGEX_LENGTH, MAX_SEARCH_LENGTH, MUTATION_OPERATIONS, MutationOperation, QueryResolver, type QueryResolverConfig, RESERVED_QUERY_PARAMS, ResourceDefinition, SYSTEM_FIELDS, createActionRouter, createCrudHandlers, createCrudRouter, createFastifyHandler, createPermissionMiddleware, createRequestContext, defineResource, getControllerContext, getControllerScope, sendControllerResponse };
@@ -1,4 +1,4 @@
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-DdXFXQtN.mjs";
2
- import { _ as QueryResolver, c as createPermissionMiddleware, d as createFastifyHandler, f as createRequestContext, g as BaseController, h as sendControllerResponse, m as getControllerScope, n as defineResource, o as createActionRouter, p as getControllerContext, s as createCrudRouter, t as ResourceDefinition, u as createCrudHandlers, v as BodySanitizer, y as AccessControl } from "../defineResource-Bq_fXZtm.mjs";
2
+ import { _ as QueryResolver, c as createPermissionMiddleware, d as createFastifyHandler, f as createRequestContext, g as BaseController, h as sendControllerResponse, m as getControllerScope, n as defineResource, o as createActionRouter, p as getControllerContext, s as createCrudRouter, t as ResourceDefinition, u as createCrudHandlers, v as BodySanitizer, y as AccessControl } from "../defineResource-DWbpJYtm.mjs";
3
3
 
4
4
  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 };
@@ -315,7 +315,7 @@ async function createApp(options) {
315
315
  coerceTypes: true,
316
316
  useDefaults: true,
317
317
  removeAdditional: false,
318
- keywords: ["example"]
318
+ keywords: ["example", ...config.ajv?.keywords ?? []]
319
319
  } }
320
320
  });
321
321
  if (config.typeProvider === "typebox") try {
@@ -479,10 +479,34 @@ async function createApp(options) {
479
479
  fastify.log.debug("Custom authentication plugin enabled");
480
480
  break;
481
481
  case "authenticator": {
482
- const { authenticate } = authConfig;
482
+ const { authenticate, optionalAuthenticate } = authConfig;
483
483
  fastify.decorate("authenticate", async function(request, reply) {
484
484
  await authenticate(request, reply);
485
485
  });
486
+ if (!fastify.hasDecorator("optionalAuthenticate")) if (optionalAuthenticate) fastify.decorate("optionalAuthenticate", async function(request, reply) {
487
+ await optionalAuthenticate(request, reply);
488
+ });
489
+ else fastify.decorate("optionalAuthenticate", async function(request, reply) {
490
+ let intercepted = false;
491
+ const proxyReply = new Proxy(reply, { get(target, prop) {
492
+ if (prop === "code") return (statusCode) => {
493
+ if (statusCode === 401 || statusCode === 403) {
494
+ intercepted = true;
495
+ return new Proxy(target, { get(_t, p) {
496
+ if (p === "send" || p === "type" || p === "header" || p === "headers") return () => proxyReply;
497
+ return Reflect.get(target, p, target);
498
+ } });
499
+ }
500
+ return target.code(statusCode);
501
+ };
502
+ if (prop === "send" && intercepted) return () => proxyReply;
503
+ if (prop === "sent") return intercepted ? false : target.sent;
504
+ return Reflect.get(target, prop, target);
505
+ } });
506
+ try {
507
+ await authenticate(request, proxyReply);
508
+ } catch {}
509
+ });
486
510
  trackPlugin("auth-authenticator");
487
511
  fastify.log.debug("Custom authenticator enabled");
488
512
  break;
@@ -3,7 +3,7 @@ import { c as isElevated, l as isMember, n as PUBLIC_SCOPE, r as getOrgId } from
3
3
  import { getUserId } from "./types/index.mjs";
4
4
  import { i as resolveEffectiveRoles, n as applyFieldWritePermissions, t as applyFieldReadPermissions } from "./fields-CTd_CrKr.mjs";
5
5
  import { t as getUserRoles } from "./types-DelU6kln.mjs";
6
- import { C as ArcQueryParser, u as getDefaultCrudSchemas } from "./circuitBreaker-DYhWBW_D.mjs";
6
+ import { C as ArcQueryParser, u as getDefaultCrudSchemas } from "./circuitBreaker-CSS2VvL6.mjs";
7
7
  import { t as buildQueryKey } from "./keys-DhqDRxv3.mjs";
8
8
  import { r as ForbiddenError } from "./errors-DBANPbGr.mjs";
9
9
  import { t as hasEvents } from "./typeGuards-DwxA1t_L.mjs";
@@ -107,6 +107,18 @@ var AccessControl = class AccessControl {
107
107
  throw error;
108
108
  }
109
109
  }
110
+ /**
111
+ * Post-fetch access control validation for items fetched by non-ID queries
112
+ * (e.g., getBySlug, restore). Applies org scope, policy filters, and
113
+ * ownership checks — the same guarantees as fetchWithAccessControl.
114
+ */
115
+ validateItemAccess(item, req) {
116
+ if (!item) return false;
117
+ const arcContext = this._meta(req);
118
+ if (!this.checkOrgScope(item, arcContext)) return false;
119
+ if (!this.checkPolicyFilters(item, req)) return false;
120
+ return true;
121
+ }
110
122
  /** Extract typed Arc internal metadata from request */
111
123
  _meta(req) {
112
124
  return req.metadata;
@@ -161,16 +173,20 @@ var AccessControl = class AccessControl {
161
173
  * Supports: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $exists, $regex, $and, $or
162
174
  */
163
175
  defaultMatchesPolicyFilters(item, policyFilters) {
164
- if (policyFilters.$and && Array.isArray(policyFilters.$and)) return policyFilters.$and.every((condition) => {
165
- return Object.entries(condition).every(([key, value]) => {
166
- return this.matchesFilter(item, key, value);
167
- });
168
- });
169
- if (policyFilters.$or && Array.isArray(policyFilters.$or)) return policyFilters.$or.some((condition) => {
170
- return Object.entries(condition).every(([key, value]) => {
171
- return this.matchesFilter(item, key, value);
172
- });
173
- });
176
+ if (policyFilters.$and && Array.isArray(policyFilters.$and)) {
177
+ if (!policyFilters.$and.every((condition) => {
178
+ return Object.entries(condition).every(([key, value]) => {
179
+ return this.matchesFilter(item, key, value);
180
+ });
181
+ })) return false;
182
+ }
183
+ if (policyFilters.$or && Array.isArray(policyFilters.$or)) {
184
+ if (!policyFilters.$or.some((condition) => {
185
+ return Object.entries(condition).every(([key, value]) => {
186
+ return this.matchesFilter(item, key, value);
187
+ });
188
+ })) return false;
189
+ }
174
190
  for (const [key, value] of Object.entries(policyFilters)) {
175
191
  if (key.startsWith("$")) continue;
176
192
  if (!this.matchesFilter(item, key, value)) return false;
@@ -766,9 +782,14 @@ var BaseController = class {
766
782
  context: arcContext,
767
783
  meta: { id }
768
784
  });
785
+ const deleteResult = typeof result === "object" && result !== null ? result : {};
769
786
  return {
770
787
  success: true,
771
- data: { message: "Deleted successfully" },
788
+ data: {
789
+ message: deleteResult.message || "Deleted successfully",
790
+ ...id ? { id } : {},
791
+ ...deleteResult.soft ? { soft: true } : {}
792
+ },
772
793
  status: 200
773
794
  };
774
795
  }
@@ -782,9 +803,8 @@ var BaseController = class {
782
803
  const slugField = this._presetFields.slugField ?? "slug";
783
804
  const slug = req.params[slugField] ?? req.params.slug;
784
805
  const options = this.queryResolver.resolve(req, this.meta(req));
785
- const arcContext = this.meta(req);
786
806
  const item = await repo.getBySlug(slug, options);
787
- if (!item || !this.accessControl.checkOrgScope(item, arcContext)) return {
807
+ if (!this.accessControl.validateItemAccess(item, req)) return {
788
808
  success: false,
789
809
  error: "Resource not found",
790
810
  status: 404
@@ -836,6 +856,18 @@ var BaseController = class {
836
856
  error: "ID parameter is required",
837
857
  status: 400
838
858
  };
859
+ const existing = await this.accessControl.fetchWithAccessControl(id, req, repo);
860
+ if (!existing) return {
861
+ success: false,
862
+ error: "Resource not found",
863
+ status: 404
864
+ };
865
+ if (!this.accessControl.checkOwnership(existing, req)) return {
866
+ success: false,
867
+ error: "You do not have permission to restore this resource",
868
+ details: { code: "OWNERSHIP_DENIED" },
869
+ status: 403
870
+ };
839
871
  const item = await repo.restore(id);
840
872
  if (!item) return {
841
873
  success: false,
@@ -1954,19 +1986,28 @@ function defineResource(config) {
1954
1986
  const resolvedConfig = config.presets?.length ? applyPresets(config, config.presets) : config;
1955
1987
  resolvedConfig._appliedPresets = originalPresets;
1956
1988
  let controller = resolvedConfig.controller;
1957
- if (!controller && hasCrudRoutes && repository) controller = new BaseController(repository, {
1958
- resourceName: resolvedConfig.name,
1959
- schemaOptions: resolvedConfig.schemaOptions,
1960
- queryParser: resolvedConfig.queryParser,
1961
- tenantField: resolvedConfig.tenantField,
1962
- idField: resolvedConfig.idField,
1963
- matchesFilter: config.adapter?.matchesFilter,
1964
- cache: resolvedConfig.cache,
1965
- presetFields: resolvedConfig._controllerOptions ? {
1966
- slugField: resolvedConfig._controllerOptions.slugField,
1967
- parentField: resolvedConfig._controllerOptions.parentField
1968
- } : void 0
1969
- });
1989
+ if (!controller && hasCrudRoutes && repository) {
1990
+ const qp = resolvedConfig.queryParser;
1991
+ let maxLimitFromParser;
1992
+ if (qp?.getQuerySchema) {
1993
+ const limitProp = qp.getQuerySchema()?.properties?.limit;
1994
+ if (limitProp?.maximum) maxLimitFromParser = limitProp.maximum;
1995
+ }
1996
+ controller = new BaseController(repository, {
1997
+ resourceName: resolvedConfig.name,
1998
+ schemaOptions: resolvedConfig.schemaOptions,
1999
+ queryParser: resolvedConfig.queryParser,
2000
+ maxLimit: maxLimitFromParser,
2001
+ tenantField: resolvedConfig.tenantField,
2002
+ idField: resolvedConfig.idField,
2003
+ matchesFilter: config.adapter?.matchesFilter,
2004
+ cache: resolvedConfig.cache,
2005
+ presetFields: resolvedConfig._controllerOptions ? {
2006
+ slugField: resolvedConfig._controllerOptions.slugField,
2007
+ parentField: resolvedConfig._controllerOptions.parentField
2008
+ } : void 0
2009
+ });
2010
+ }
1970
2011
  const resource = new ResourceDefinition({
1971
2012
  ...resolvedConfig,
1972
2013
  adapter: config.adapter,
@@ -2111,6 +2152,30 @@ var ResourceDefinition = class {
2111
2152
  schemas[key] = schemas[key] ? deepMergeSchemas(schemas[key], converted) : converted;
2112
2153
  }
2113
2154
  }
2155
+ const listQuerySchema = self._registryMeta?.openApiSchemas?.listQuery;
2156
+ if (listQuerySchema) {
2157
+ const FLEXIBLE_PARAMS = [
2158
+ "populate",
2159
+ "select",
2160
+ "lookup",
2161
+ "aggregate"
2162
+ ];
2163
+ const props = listQuerySchema.properties;
2164
+ const normalizedProps = props ? { ...props } : void 0;
2165
+ if (normalizedProps) {
2166
+ for (const key of FLEXIBLE_PARAMS) if (normalizedProps[key] && typeof normalizedProps[key] === "object") {
2167
+ const { type: _type, ...rest } = normalizedProps[key];
2168
+ normalizedProps[key] = rest;
2169
+ }
2170
+ }
2171
+ const normalizedSchema = {
2172
+ ...listQuerySchema,
2173
+ ...normalizedProps ? { properties: normalizedProps } : {},
2174
+ additionalProperties: listQuerySchema.additionalProperties ?? true
2175
+ };
2176
+ schemas = schemas ?? {};
2177
+ schemas.list = schemas.list ? deepMergeSchemas({ querystring: normalizedSchema }, schemas.list) : { querystring: normalizedSchema };
2178
+ }
2114
2179
  const resolvedRoutes = self.additionalRoutes;
2115
2180
  createCrudRouter(typedInstance, self.controller, {
2116
2181
  tag: self.tag,
@@ -1,5 +1,5 @@
1
1
  import "../elevation-DGo5shaX.mjs";
2
- import "../interface-Dm4-jnia.mjs";
2
+ import "../interface-BtdYtQUA.mjs";
3
3
  import "../types-RLkFVgaw.mjs";
4
4
  import { RegistryEntry } from "../types/index.mjs";
5
5
  import { t as ExternalOpenApiPaths } from "../externalPaths-SyPF2tgK.mjs";
@@ -1,10 +1,10 @@
1
1
  import "../elevation-DGo5shaX.mjs";
2
- import "../interface-Dm4-jnia.mjs";
2
+ import "../interface-BtdYtQUA.mjs";
3
3
  import "../types-RLkFVgaw.mjs";
4
4
  import "../queryCachePlugin-Q6SYuHZ6.mjs";
5
5
  import "../eventPlugin-H6wDDjGO.mjs";
6
6
  import "../errorHandler-CW3OOeYq.mjs";
7
- import { a as CustomPluginAuthOption, c as RawBodyOptions, i as CustomAuthenticatorOption, l as UnderPressureOptions, n as BetterAuthOption, o as JwtAuthOption, r as CreateAppOptions, s as MultipartOptions, t as AuthOption } from "../types-B0dhNrnd.mjs";
7
+ import { a as CustomPluginAuthOption, c as RawBodyOptions, i as CustomAuthenticatorOption, l as UnderPressureOptions, n as BetterAuthOption, o as JwtAuthOption, r as CreateAppOptions, s as MultipartOptions, t as AuthOption } from "../types-tKwaViYB.mjs";
8
8
  import { FastifyInstance } from "fastify";
9
9
 
10
10
  //#region src/factory/createApp.d.ts
@@ -1,3 +1,3 @@
1
- import { a as getPreset, i as developmentPreset, n as createApp, o as productionPreset, s as testingPreset, t as ArcFactory } from "../createApp-BTHYuHNU.mjs";
1
+ import { a as getPreset, i as developmentPreset, n as createApp, o as productionPreset, s as testingPreset, t as ArcFactory } from "../createApp-CgKOPhA4.mjs";
2
2
 
3
3
  export { ArcFactory, createApp, developmentPreset, getPreset, productionPreset, testingPreset };
@@ -1,5 +1,5 @@
1
1
  import { s as RequestScope } from "./elevation-DGo5shaX.mjs";
2
- import { b as IControllerResponse, x as IRequestContext, y as IController } from "./interface-Dm4-jnia.mjs";
2
+ import { b as IControllerResponse, x as IRequestContext, y as IController } from "./interface-BtdYtQUA.mjs";
3
3
  import { t as PermissionCheck } from "./types-RLkFVgaw.mjs";
4
4
  import { CrudController, CrudRouterOptions, FastifyWithDecorators, RequestContext, RequestWithExtras } from "./types/index.mjs";
5
5
  import { FastifyInstance, FastifyReply, FastifyRequest, RouteHandlerMethod } from "fastify";
@@ -1,4 +1,4 @@
1
1
  import "../elevation-DGo5shaX.mjs";
2
- import { $ as beforeUpdate, B as DefineHookOptions, G as HookRegistration, H as HookHandler, J as afterCreate, K as HookSystem, Q as beforeDelete, U as HookOperation, V as HookContext, W as HookPhase, X as afterUpdate, Y as afterDelete, Z as beforeCreate, et as createHookSystem, q as HookSystemOptions, tt as defineHook } from "../interface-Dm4-jnia.mjs";
2
+ import { $ as beforeUpdate, B as DefineHookOptions, G as HookRegistration, H as HookHandler, J as afterCreate, K as HookSystem, Q as beforeDelete, U as HookOperation, V as HookContext, W as HookPhase, X as afterUpdate, Y as afterDelete, Z as beforeCreate, et as createHookSystem, q as HookSystemOptions, tt as defineHook } from "../interface-BtdYtQUA.mjs";
3
3
  import "../types-RLkFVgaw.mjs";
4
4
  export { type DefineHookOptions, type HookContext, type HookHandler, type HookOperation, type HookPhase, type HookRegistration, HookSystem, type HookSystemOptions, afterCreate, afterDelete, afterUpdate, beforeCreate, beforeDelete, beforeUpdate, createHookSystem, defineHook };
package/dist/index.d.mts CHANGED
@@ -1,11 +1,11 @@
1
1
  import "./elevation-DGo5shaX.mjs";
2
- import { D as CrudRepository, E as defineResource, F as OperationFilter, I as PipelineConfig, L as PipelineContext, M as Guard, N as Interceptor, P as NextFunction, R as PipelineStep, S as RouteHandler, T as ResourceDefinition, _ as ControllerLike, a as RepositoryLike, b as IControllerResponse, c as BaseController, i as RelationMetadata, j as QueryOptions, k as PaginatedResult, l as BaseControllerOptions, n as DataAdapter, o as SchemaMetadata, r as FieldMetadata, s as ValidationResult, x as IRequestContext, y as IController, z as Transform } from "./interface-Dm4-jnia.mjs";
2
+ import { D as CrudRepository, E as defineResource, F as OperationFilter, I as PipelineConfig, L as PipelineContext, M as Guard, N as Interceptor, P as NextFunction, R as PipelineStep, S as RouteHandler, T as ResourceDefinition, _ as ControllerLike, a as RepositoryLike, b as IControllerResponse, c as BaseController, i as RelationMetadata, j as QueryOptions, k as PaginatedResult, l as BaseControllerOptions, n as DataAdapter, o as SchemaMetadata, r as FieldMetadata, s as ValidationResult, x as IRequestContext, y as IController, z as Transform } from "./interface-BtdYtQUA.mjs";
3
3
  import { a as applyFieldWritePermissions, i as applyFieldReadPermissions, n as FieldPermissionMap, o as fields, t as FieldPermission } from "./fields-Bi_AVKSo.mjs";
4
4
  import { i as UserBase, n as PermissionContext, r as PermissionResult, t as PermissionCheck } from "./types-RLkFVgaw.mjs";
5
5
  import { AdditionalRoute, AnyRecord, ApiResponse, ArcInternalMetadata, AuthPluginOptions, ConfigError, CrudController, CrudRouteKey, CrudRouterOptions, CrudSchemas, EventDefinition, FastifyRequestExtras, FastifyWithAuth, FastifyWithDecorators, FieldRule, GracefulShutdownOptions, HealthCheck, HealthOptions, InferAdapterDoc, InferDocType, InferResourceDoc, IntrospectionData, IntrospectionPluginOptions, JWTPayload, MiddlewareConfig, MiddlewareHandler, OwnershipCheck, PresetFunction, PresetResult, RateLimitConfig, RegistryEntry, RegistryStats, RequestContext, RequestIdOptions, RequestWithExtras, ResourceConfig, ResourceMetadata, RouteHandlerMethod, RouteSchemaOptions, ServiceContext, TypedController, TypedRepository, TypedResourceConfig, UserOrganization, ValidateOptions, ValidationResult as ValidationResult$1 } from "./types/index.mjs";
6
- import { c as MongooseAdapterOptions, l as createMongooseAdapter, n as PrismaAdapterOptions, o as createPrismaAdapter, s as MongooseAdapter, t as PrismaAdapter } from "./prisma-Bi9nxirN.mjs";
6
+ import { c as MongooseAdapterOptions, l as createMongooseAdapter, n as PrismaAdapterOptions, o as createPrismaAdapter, s as MongooseAdapter, t as PrismaAdapter } from "./prisma-Dy5S5F5i.mjs";
7
7
  import "./adapters/index.mjs";
8
- import { C as MutationOperation, S as MUTATION_OPERATIONS, T as SYSTEM_FIELDS, _ as HookOperation, a as getControllerScope, b as MAX_REGEX_LENGTH, c as CrudOperation, d as DEFAULT_MAX_LIMIT, f as DEFAULT_SORT, g as HOOK_PHASES, h as HOOK_OPERATIONS, l as DEFAULT_ID_FIELD, m as DEFAULT_UPDATE_METHOD, p as DEFAULT_TENANT_FIELD, s as CRUD_OPERATIONS, u as DEFAULT_LIMIT, v as HookPhase, w as RESERVED_QUERY_PARAMS, x as MAX_SEARCH_LENGTH, y as MAX_FILTER_DEPTH } from "./fastifyAdapter-DTOLNtjw.mjs";
8
+ import { C as MutationOperation, S as MUTATION_OPERATIONS, T as SYSTEM_FIELDS, _ as HookOperation, a as getControllerScope, b as MAX_REGEX_LENGTH, c as CrudOperation, d as DEFAULT_MAX_LIMIT, f as DEFAULT_SORT, g as HOOK_PHASES, h as HOOK_OPERATIONS, l as DEFAULT_ID_FIELD, m as DEFAULT_UPDATE_METHOD, p as DEFAULT_TENANT_FIELD, s as CRUD_OPERATIONS, u as DEFAULT_LIMIT, v as HookPhase, w as RESERVED_QUERY_PARAMS, x as MAX_SEARCH_LENGTH, y as MAX_FILTER_DEPTH } from "./fastifyAdapter-6b_eRDBw.mjs";
9
9
  import "./core/index.mjs";
10
10
  import { a as NotFoundError, d as ValidationError, i as ForbiddenError, t as ArcError, u as UnauthorizedError } from "./errors-DAWRdiYP.mjs";
11
11
  import { a as presets_d_exports, c as readOnly, i as ownerWithAdminBypass, n as authenticated, o as publicRead, r as fullPublic, s as publicReadAdminWrite, t as adminOnly } from "./presets-BTeYbw7h.mjs";
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
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-DdXFXQtN.mjs";
2
2
  import { a as createMongooseAdapter, i as MongooseAdapter, r as createPrismaAdapter, t as PrismaAdapter } from "./prisma-DJbMt3yf.mjs";
3
- import { a as validateResourceConfig, g as BaseController, i as formatValidationErrors, l as pipe, m as getControllerScope, n as defineResource, r as assertValidConfig, t as ResourceDefinition } from "./defineResource-Bq_fXZtm.mjs";
3
+ import { a as validateResourceConfig, g as BaseController, i as formatValidationErrors, l as pipe, m as getControllerScope, n as defineResource, r as assertValidConfig, t as ResourceDefinition } from "./defineResource-DWbpJYtm.mjs";
4
4
  import { n as applyFieldWritePermissions, r as fields, t as applyFieldReadPermissions } from "./fields-CTd_CrKr.mjs";
5
5
  import { i as NotFoundError, l as UnauthorizedError, r as ForbiddenError, t as ArcError, u as ValidationError } from "./errors-DBANPbGr.mjs";
6
6
  import { t as requestContext } from "./requestContext-xi6OKBL-.mjs";
@@ -818,6 +818,12 @@ declare class AccessControl {
818
818
  * buildIdFilter -> getOne (or getById + checkOrgScope + checkPolicyFilters)
819
819
  */
820
820
  fetchWithAccessControl<TDoc>(id: string, req: IRequestContext, repository: AccessControlRepository, queryOptions?: unknown): Promise<TDoc | null>;
821
+ /**
822
+ * Post-fetch access control validation for items fetched by non-ID queries
823
+ * (e.g., getBySlug, restore). Applies org scope, policy filters, and
824
+ * ownership checks — the same guarantees as fetchWithAccessControl.
825
+ */
826
+ validateItemAccess(item: AnyRecord | null, req: IRequestContext): boolean;
821
827
  /** Extract typed Arc internal metadata from request */
822
828
  private _meta;
823
829
  /**
@@ -1004,6 +1010,8 @@ declare class BaseController<TDoc = AnyRecord, TRepository extends RepositoryLik
1004
1010
  update(req: IRequestContext): Promise<IControllerResponse<TDoc>>;
1005
1011
  delete(req: IRequestContext): Promise<IControllerResponse<{
1006
1012
  message: string;
1013
+ id?: string;
1014
+ soft?: boolean;
1007
1015
  }>>;
1008
1016
  getBySlug(req: IRequestContext): Promise<IControllerResponse<TDoc>>;
1009
1017
  getDeleted(req: IRequestContext): Promise<IControllerResponse<PaginatedResult<TDoc>>>;
@@ -1,5 +1,5 @@
1
1
  import "../elevation-DGo5shaX.mjs";
2
- import { S as RouteHandler } from "../interface-Dm4-jnia.mjs";
2
+ import { S as RouteHandler } from "../interface-BtdYtQUA.mjs";
3
3
  import { i as UserBase } from "../types-RLkFVgaw.mjs";
4
4
  import "../types/index.mjs";
5
5
  import { InvitationAdapter, InvitationDoc, MemberDoc, OrgAdapter, OrgDoc, OrgPermissionStatement, OrgRole, OrganizationPluginOptions } from "./types.mjs";
@@ -1,5 +1,5 @@
1
1
  import "../elevation-DGo5shaX.mjs";
2
- import { K as HookSystem, w as ResourceRegistry } from "../interface-Dm4-jnia.mjs";
2
+ import { K as HookSystem, w as ResourceRegistry } from "../interface-BtdYtQUA.mjs";
3
3
  import "../types-RLkFVgaw.mjs";
4
4
  import { AdditionalRoute, AnyRecord, MiddlewareConfig, PresetHook, RouteSchemaOptions } from "../types/index.mjs";
5
5
  import { t as ExternalOpenApiPaths } from "../externalPaths-SyPF2tgK.mjs";
@@ -1,5 +1,5 @@
1
1
  import "../elevation-DGo5shaX.mjs";
2
- import { b as IControllerResponse, k as PaginatedResult, x as IRequestContext } from "../interface-Dm4-jnia.mjs";
2
+ import { b as IControllerResponse, k as PaginatedResult, x as IRequestContext } from "../interface-BtdYtQUA.mjs";
3
3
  import "../types-RLkFVgaw.mjs";
4
4
  import { AnyRecord, PresetResult, ResourceConfig } from "../types/index.mjs";
5
5
  import multiTenantPreset, { MultiTenantOptions } from "./multiTenant.mjs";
@@ -1,5 +1,5 @@
1
1
  import "../elevation-DGo5shaX.mjs";
2
- import "../interface-Dm4-jnia.mjs";
2
+ import "../interface-BtdYtQUA.mjs";
3
3
  import "../types-RLkFVgaw.mjs";
4
4
  import { CrudRouteKey, PresetResult } from "../types/index.mjs";
5
5
 
@@ -1,4 +1,4 @@
1
- import { D as CrudRepository, a as RepositoryLike, n as DataAdapter, o as SchemaMetadata, s as ValidationResult } from "./interface-Dm4-jnia.mjs";
1
+ import { D as CrudRepository, a as RepositoryLike, n as DataAdapter, o as SchemaMetadata, s as ValidationResult } from "./interface-BtdYtQUA.mjs";
2
2
  import { OpenApiSchemas, ParsedQuery, QueryParserInterface, RouteSchemaOptions } from "./types/index.mjs";
3
3
  import { Model } from "mongoose";
4
4
 
@@ -1,5 +1,5 @@
1
1
  import "../elevation-DGo5shaX.mjs";
2
- import { C as RegisterOptions, w as ResourceRegistry } from "../interface-Dm4-jnia.mjs";
2
+ import { C as RegisterOptions, w as ResourceRegistry } from "../interface-BtdYtQUA.mjs";
3
3
  import "../types-RLkFVgaw.mjs";
4
4
  import { IntrospectionPluginOptions } from "../types/index.mjs";
5
5
  import { FastifyPluginAsync } from "fastify";
@@ -57,7 +57,7 @@ declare function ArcPaginationQuery(): _sinclair_typebox0.TObject<{
57
57
  limit: _sinclair_typebox0.TOptional<_sinclair_typebox0.TInteger>;
58
58
  sort: _sinclair_typebox0.TOptional<_sinclair_typebox0.TString>;
59
59
  select: _sinclair_typebox0.TOptional<_sinclair_typebox0.TString>;
60
- populate: _sinclair_typebox0.TOptional<_sinclair_typebox0.TString>;
60
+ populate: _sinclair_typebox0.TOptional<_sinclair_typebox0.TAny>;
61
61
  }>;
62
62
  //#endregion
63
63
  export { ArcDeleteResponse, ArcErrorResponse, ArcItemResponse, ArcListResponse, ArcMutationResponse, ArcPaginationQuery, type FastifyPluginAsyncTypebox, type FastifyPluginCallbackTypebox, type Static, type TObject, type TSchema, Type, type TypeBoxTypeProvider, TypeBoxValidatorCompiler };
@@ -74,8 +74,8 @@ function ArcPaginationQuery() {
74
74
  })),
75
75
  sort: Type$1.Optional(Type$1.String()),
76
76
  select: Type$1.Optional(Type$1.String()),
77
- populate: Type$1.Optional(Type$1.String())
78
- });
77
+ populate: Type$1.Optional(Type$1.Any())
78
+ }, { additionalProperties: true });
79
79
  }
80
80
 
81
81
  //#endregion
@@ -1,11 +1,11 @@
1
1
  import "../elevation-DGo5shaX.mjs";
2
- import { D as CrudRepository, T as ResourceDefinition } from "../interface-Dm4-jnia.mjs";
2
+ import { D as CrudRepository, T as ResourceDefinition } from "../interface-BtdYtQUA.mjs";
3
3
  import "../types-RLkFVgaw.mjs";
4
4
  import { AnyRecord } from "../types/index.mjs";
5
5
  import "../queryCachePlugin-Q6SYuHZ6.mjs";
6
6
  import "../eventPlugin-H6wDDjGO.mjs";
7
7
  import "../errorHandler-CW3OOeYq.mjs";
8
- import { r as CreateAppOptions } from "../types-B0dhNrnd.mjs";
8
+ import { r as CreateAppOptions } from "../types-tKwaViYB.mjs";
9
9
  import Fastify, { FastifyInstance } from "fastify";
10
10
  import { Mock } from "vitest";
11
11
  import { Connection } from "mongoose";
@@ -1000,7 +1000,7 @@ var DatabaseSnapshot = class {
1000
1000
  * ```
1001
1001
  */
1002
1002
  async function createTestApp(options = {}) {
1003
- const { createApp } = await import("../createApp-BTHYuHNU.mjs").then((n) => n.r);
1003
+ const { createApp } = await import("../createApp-CgKOPhA4.mjs").then((n) => n.r);
1004
1004
  const { useInMemoryDb = true, mongoUri: providedMongoUri, ...appOptions } = options;
1005
1005
  const defaultAuth = {
1006
1006
  type: "jwt",
@@ -1,5 +1,5 @@
1
1
  import { a as AUTHENTICATED_SCOPE, c as getOrgId, d as hasOrgAccess, f as isAuthenticated, l as getOrgRoles, m as isMember, n as ElevationOptions, o as PUBLIC_SCOPE, p as isElevated, s as RequestScope, t as ElevationEvent, u as getTeamId } from "../elevation-DGo5shaX.mjs";
2
- import { A as PaginationParams, D as CrudRepository, I as PipelineConfig, K as HookSystem, O as InferDoc, S as RouteHandler, _ as ControllerLike, b as IControllerResponse, g as ControllerHandler, j as QueryOptions, k as PaginatedResult, l as BaseControllerOptions, n as DataAdapter, v as FastifyHandler, w as ResourceRegistry, x as IRequestContext, y as IController } from "../interface-Dm4-jnia.mjs";
2
+ import { A as PaginationParams, D as CrudRepository, I as PipelineConfig, K as HookSystem, O as InferDoc, S as RouteHandler, _ as ControllerLike, b as IControllerResponse, g as ControllerHandler, j as QueryOptions, k as PaginatedResult, l as BaseControllerOptions, n as DataAdapter, v as FastifyHandler, w as ResourceRegistry, x as IRequestContext, y as IController } from "../interface-BtdYtQUA.mjs";
3
3
  import { n as FieldPermissionMap } from "../fields-Bi_AVKSo.mjs";
4
4
  import { i as UserBase, n as PermissionContext, r as PermissionResult, t as PermissionCheck } from "../types-RLkFVgaw.mjs";
5
5
  import { FastifyInstance, FastifyReply, FastifyRequest, RouteHandlerMethod, RouteHandlerMethod as RouteHandlerMethod$1 } from "fastify";
@@ -154,6 +154,13 @@ interface CustomAuthenticatorOption {
154
154
  type: 'authenticator';
155
155
  /** Authenticate function — decorates fastify.authenticate directly */
156
156
  authenticate: (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
157
+ /**
158
+ * Optional authenticate function for public routes.
159
+ * If not provided, Arc auto-generates one by wrapping `authenticate` and
160
+ * intercepting 401/403 responses so unauthenticated requests proceed as public.
161
+ * Provide this if your authenticator has side effects that shouldn't run on public routes.
162
+ */
163
+ optionalAuthenticate?: (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
157
164
  }
158
165
  /**
159
166
  * All supported auth configuration shapes
@@ -416,6 +423,23 @@ interface CreateAppOptions {
416
423
  * Set to false to disable, or pass ErrorHandlerOptions for fine control.
417
424
  */
418
425
  errorHandler?: ErrorHandlerOptions | false;
426
+ /**
427
+ * Custom AJV keywords to allow in route schemas.
428
+ *
429
+ * Arc already allows `"example"` by default. Use this to add
430
+ * additional non-standard keywords your query parsers or schema
431
+ * generators may use (e.g., `x-internal` from MongoKit).
432
+ *
433
+ * @example
434
+ * ```typescript
435
+ * const app = await createApp({
436
+ * ajv: { keywords: ['x-internal'] },
437
+ * });
438
+ * ```
439
+ */
440
+ ajv?: {
441
+ keywords?: string[];
442
+ };
419
443
  /** Custom plugin registration function */
420
444
  plugins?: (fastify: FastifyInstance) => Promise<void>;
421
445
  /** Hook called after all plugins are loaded and the app is ready */
@@ -1,5 +1,5 @@
1
1
  import "../elevation-DGo5shaX.mjs";
2
- import "../interface-Dm4-jnia.mjs";
2
+ import "../interface-BtdYtQUA.mjs";
3
3
  import "../types-RLkFVgaw.mjs";
4
4
  import { AnyRecord, OpenApiSchemas, ParsedQuery, QueryParserInterface } from "../types/index.mjs";
5
5
  import { a as NotFoundError, c as RateLimitError, d as ValidationError, f as createError, i as ForbiddenError, l as ServiceUnavailableError, n as ConflictError, o as OrgAccessDeniedError, p as isArcError, r as ErrorDetails, s as OrgRequiredError, t as ArcError, u as UnauthorizedError } from "../errors-DAWRdiYP.mjs";
@@ -65,7 +65,7 @@ declare function mutationResponse(itemSchema: JsonSchema): JsonSchema;
65
65
  /**
66
66
  * Create a delete response schema
67
67
  *
68
- * Runtime format: { success, message }
68
+ * Runtime format: { success, data: { message, id?, soft? } }
69
69
  */
70
70
  declare function deleteResponse(): JsonSchema;
71
71
  /**
@@ -1,4 +1,4 @@
1
- import { C as ArcQueryParser, S as wrapResponse, _ as paginateWrapper, a as createCircuitBreaker, b as responses, c as deleteResponse, d as getListQueryParams, f as itemResponse, g as mutationResponse, h as messageWrapper, i as CircuitState, l as errorResponseSchema, m as listResponse, n as CircuitBreakerError, o as createCircuitBreakerRegistry, p as itemWrapper, r as CircuitBreakerRegistry, s as createStateMachine, t as CircuitBreaker, u as getDefaultCrudSchemas, v as paginationSchema, w as createQueryParser, x as successResponseSchema, y as queryParams } from "../circuitBreaker-DYhWBW_D.mjs";
1
+ import { C as ArcQueryParser, S as wrapResponse, _ as paginateWrapper, a as createCircuitBreaker, b as responses, c as deleteResponse, d as getListQueryParams, f as itemResponse, g as mutationResponse, h as messageWrapper, i as CircuitState, l as errorResponseSchema, m as listResponse, n as CircuitBreakerError, o as createCircuitBreakerRegistry, p as itemWrapper, r as CircuitBreakerRegistry, s as createStateMachine, t as CircuitBreaker, u as getDefaultCrudSchemas, v as paginationSchema, w as createQueryParser, x as successResponseSchema, y as queryParams } from "../circuitBreaker-CSS2VvL6.mjs";
2
2
  import { a as OrgAccessDeniedError, c as ServiceUnavailableError, d as createError, f as isArcError, i as NotFoundError, l as UnauthorizedError, n as ConflictError, o as OrgRequiredError, r as ForbiddenError, s as RateLimitError, t as ArcError, u as ValidationError } from "../errors-DBANPbGr.mjs";
3
3
  import { t as hasEvents } from "../typeGuards-DwxA1t_L.mjs";
4
4
  import { a as toJsonSchema, i as isZodSchema, n as convertRouteSchema, r as isJsonSchema, t as convertOpenApiSchemas } from "../schemaConverter-Dtg0Kt9T.mjs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classytic/arc",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "description": "Resource-oriented backend framework for Fastify — clean, minimal, powerful, tree-shakable",
5
5
  "type": "module",
6
6
  "exports": {
@@ -191,7 +191,7 @@
191
191
  "node": ">=22"
192
192
  },
193
193
  "peerDependencies": {
194
- "@classytic/mongokit": "^3.2.4",
194
+ "@classytic/mongokit": ">=3.3.2",
195
195
  "@classytic/streamline": ">=1.0.0",
196
196
  "@fastify/cors": "^11.0.0",
197
197
  "@fastify/helmet": "^13.0.0",
@@ -304,11 +304,11 @@
304
304
  "qs": "^6.14.1"
305
305
  },
306
306
  "devDependencies": {
307
- "@classytic/mongokit": "^3.2.3",
307
+ "@classytic/mongokit": "^3.3.2",
308
308
  "@fastify/jwt": "^10.0.0",
309
309
  "@fastify/multipart": "^9.0.0",
310
- "@fastify/websocket": "^11.0.0",
311
310
  "@fastify/type-provider-typebox": "^6.0.0",
311
+ "@fastify/websocket": "^11.0.0",
312
312
  "@sinclair/typebox": "^0.34.0",
313
313
  "@types/node": "^22.10.0",
314
314
  "@types/qs": "^6.14.0",
@@ -346,4 +346,4 @@
346
346
  "type": "git",
347
347
  "url": "https://github.com/classytic/arc.git"
348
348
  }
349
- }
349
+ }