@classytic/arc 2.1.7 → 2.2.5

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-Cb2klgid.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-DZYNK9bb.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-DQBSSHAB.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-xjhMEq_S.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-Cb2klgid.mjs";
2
+ import "../interface-DZYNK9bb.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-Cb2klgid.mjs";
2
+ import "../interface-DZYNK9bb.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-Cb2klgid.mjs";
2
+ import "../interface-DZYNK9bb.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";
@@ -2174,7 +2174,17 @@ ${orgPluginUsage}
2174
2174
  enabled: process.env.NODE_ENV === 'production',
2175
2175
  },
2176
2176
  });
2177
- }
2177
+ ${config.adapter === "mongokit" ? `
2178
+ // Register stub Mongoose models for Better Auth collections.
2179
+ // BA uses the raw MongoDB driver, so no Mongoose models exist by default.
2180
+ // These stubs (strict: false) enable populate() on refs like 'user', 'organization', etc.
2181
+ const baCollections = ['user', 'organization', 'member', 'invitation', 'session', 'account'];
2182
+ for (const name of baCollections) {
2183
+ if (!mongoose.models[name]) {
2184
+ mongoose.model(name, new mongoose.Schema({}, { strict: false, collection: name }));
2185
+ }
2186
+ }
2187
+ ` : ""} }
2178
2188
 
2179
2189
  return _auth;
2180
2190
  }
@@ -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-Cb2klgid.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-DZYNK9bb.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-sGkvUvf5.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-CyAA2zlB.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-DZVbwsFb.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-DO9ONe_D.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 };
@@ -50,7 +50,9 @@ const productionPreset = {
50
50
  allowedHeaders: [
51
51
  "Content-Type",
52
52
  "Authorization",
53
- "Accept"
53
+ "Accept",
54
+ "x-organization-id",
55
+ "x-request-id"
54
56
  ]
55
57
  },
56
58
  rateLimit: {
@@ -95,7 +97,9 @@ const developmentPreset = {
95
97
  allowedHeaders: [
96
98
  "Content-Type",
97
99
  "Authorization",
98
- "Accept"
100
+ "Accept",
101
+ "x-organization-id",
102
+ "x-request-id"
99
103
  ]
100
104
  },
101
105
  rateLimit: {
@@ -337,8 +341,9 @@ async function createApp(options) {
337
341
  } else fastify.log.warn("Helmet disabled - security headers not applied");
338
342
  if (config.cors !== false) {
339
343
  const cors = await loadPlugin("cors");
340
- const corsOptions = config.cors ?? {};
344
+ const corsOptions = { ...config.cors ?? {} };
341
345
  if (config.preset === "production" && (!corsOptions || !("origin" in corsOptions))) throw new Error("CORS origin must be explicitly configured in production.\nSet cors.origin to allowed domains or set cors: false to disable.\nExample: cors: { origin: ['https://yourdomain.com'] }\nDocs: https://github.com/classytic/arc#security");
346
+ if (corsOptions.credentials && corsOptions.origin === "*") corsOptions.origin = true;
342
347
  await fastify.register(cors, corsOptions);
343
348
  fastify.log.debug("CORS enabled");
344
349
  } else fastify.log.warn("CORS disabled");
@@ -474,10 +479,34 @@ async function createApp(options) {
474
479
  fastify.log.debug("Custom authentication plugin enabled");
475
480
  break;
476
481
  case "authenticator": {
477
- const { authenticate } = authConfig;
482
+ const { authenticate, optionalAuthenticate } = authConfig;
478
483
  fastify.decorate("authenticate", async function(request, reply) {
479
484
  await authenticate(request, reply);
480
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
+ });
481
510
  trackPlugin("auth-authenticator");
482
511
  fastify.log.debug("Custom authenticator enabled");
483
512
  break;
@@ -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;
@@ -397,6 +413,16 @@ var BaseController = class {
397
413
  this.update = this.update.bind(this);
398
414
  this.delete = this.delete.bind(this);
399
415
  }
416
+ /**
417
+ * Get the tenant field name if multi-tenant scoping is enabled.
418
+ * Returns `undefined` when `tenantField` is `false` (platform-universal mode).
419
+ *
420
+ * Use this in subclass overrides instead of accessing `this.tenantField` directly
421
+ * to avoid TypeScript indexing errors with `string | false`.
422
+ */
423
+ getTenantField() {
424
+ return this.tenantField || void 0;
425
+ }
400
426
  /** Extract typed Arc internal metadata from request */
401
427
  meta(req) {
402
428
  return req.metadata;
@@ -772,9 +798,8 @@ var BaseController = class {
772
798
  const slugField = this._presetFields.slugField ?? "slug";
773
799
  const slug = req.params[slugField] ?? req.params.slug;
774
800
  const options = this.queryResolver.resolve(req, this.meta(req));
775
- const arcContext = this.meta(req);
776
801
  const item = await repo.getBySlug(slug, options);
777
- if (!item || !this.accessControl.checkOrgScope(item, arcContext)) return {
802
+ if (!this.accessControl.validateItemAccess(item, req)) return {
778
803
  success: false,
779
804
  error: "Resource not found",
780
805
  status: 404
@@ -826,6 +851,18 @@ var BaseController = class {
826
851
  error: "ID parameter is required",
827
852
  status: 400
828
853
  };
854
+ const existing = await this.accessControl.fetchWithAccessControl(id, req, repo);
855
+ if (!existing) return {
856
+ success: false,
857
+ error: "Resource not found",
858
+ status: 404
859
+ };
860
+ if (!this.accessControl.checkOwnership(existing, req)) return {
861
+ success: false,
862
+ error: "You do not have permission to restore this resource",
863
+ details: { code: "OWNERSHIP_DENIED" },
864
+ status: 403
865
+ };
829
866
  const item = await repo.restore(id);
830
867
  if (!item) return {
831
868
  success: false,
@@ -1,5 +1,5 @@
1
1
  import "../elevation-DGo5shaX.mjs";
2
- import "../interface-Cb2klgid.mjs";
2
+ import "../interface-DZYNK9bb.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-Cb2klgid.mjs";
2
+ import "../interface-DZYNK9bb.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-DMSBMkaZ.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-D2D5XXaV.mjs";
1
+ import { a as getPreset, i as developmentPreset, n as createApp, o as productionPreset, s as testingPreset, t as ArcFactory } from "../createApp-BKHSl2nT.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-Cb2klgid.mjs";
2
+ import { b as IControllerResponse, x as IRequestContext, y as IController } from "./interface-DZYNK9bb.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-Cb2klgid.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-DZYNK9bb.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-Cb2klgid.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-DZYNK9bb.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-DQBSSHAB.mjs";
6
+ import { c as MongooseAdapterOptions, l as createMongooseAdapter, n as PrismaAdapterOptions, o as createPrismaAdapter, s as MongooseAdapter, t as PrismaAdapter } from "./prisma-xjhMEq_S.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-sGkvUvf5.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-CyAA2zlB.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-DZVbwsFb.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-DO9ONe_D.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
  /**
@@ -978,6 +984,14 @@ declare class BaseController<TDoc = AnyRecord, TRepository extends RepositoryLik
978
984
  private _presetFields;
979
985
  private _cacheConfig?;
980
986
  constructor(repository: TRepository, options?: BaseControllerOptions);
987
+ /**
988
+ * Get the tenant field name if multi-tenant scoping is enabled.
989
+ * Returns `undefined` when `tenantField` is `false` (platform-universal mode).
990
+ *
991
+ * Use this in subclass overrides instead of accessing `this.tenantField` directly
992
+ * to avoid TypeScript indexing errors with `string | false`.
993
+ */
994
+ protected getTenantField(): string | undefined;
981
995
  /** Extract typed Arc internal metadata from request */
982
996
  private meta;
983
997
  /** Get hook system from request context (instance-scoped) */
@@ -1,5 +1,5 @@
1
1
  import "../elevation-DGo5shaX.mjs";
2
- import { S as RouteHandler } from "../interface-Cb2klgid.mjs";
2
+ import { S as RouteHandler } from "../interface-DZYNK9bb.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-Cb2klgid.mjs";
2
+ import { K as HookSystem, w as ResourceRegistry } from "../interface-DZYNK9bb.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-Cb2klgid.mjs";
2
+ import { b as IControllerResponse, k as PaginatedResult, x as IRequestContext } from "../interface-DZYNK9bb.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-Cb2klgid.mjs";
2
+ import "../interface-DZYNK9bb.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-Cb2klgid.mjs";
1
+ import { D as CrudRepository, a as RepositoryLike, n as DataAdapter, o as SchemaMetadata, s as ValidationResult } from "./interface-DZYNK9bb.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-Cb2klgid.mjs";
2
+ import { C as RegisterOptions, w as ResourceRegistry } from "../interface-DZYNK9bb.mjs";
3
3
  import "../types-RLkFVgaw.mjs";
4
4
  import { IntrospectionPluginOptions } from "../types/index.mjs";
5
5
  import { FastifyPluginAsync } from "fastify";
@@ -1,11 +1,11 @@
1
1
  import "../elevation-DGo5shaX.mjs";
2
- import { D as CrudRepository, T as ResourceDefinition } from "../interface-Cb2klgid.mjs";
2
+ import { D as CrudRepository, T as ResourceDefinition } from "../interface-DZYNK9bb.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-DMSBMkaZ.mjs";
9
9
  import Fastify, { FastifyInstance } from "fastify";
10
10
  import { Mock } from "vitest";
11
11
  import { Connection } from "mongoose";
@@ -649,7 +649,7 @@ interface HttpTestHarnessOptions<T = unknown> {
649
649
  };
650
650
  /** Auth provider for generating request headers */
651
651
  auth: AuthProvider;
652
- /** API path prefix (default: '/api') */
652
+ /** API path prefix (default: '/api' for eager, '' for deferred) */
653
653
  apiPrefix?: string;
654
654
  }
655
655
  /** Options can be passed directly or as a getter for deferred resolution */
@@ -666,12 +666,21 @@ type OptionsOrGetter<T> = HttpTestHarnessOptions<T> | (() => HttpTestHarnessOpti
666
666
  declare class HttpTestHarness<T = unknown> {
667
667
  private resource;
668
668
  private optionsOrGetter;
669
- private baseUrl;
669
+ private eagerBaseUrl;
670
670
  private enabledRoutes;
671
671
  private updateMethod;
672
672
  constructor(resource: ResourceDefinition<unknown>, optionsOrGetter: OptionsOrGetter<T>);
673
673
  /** Resolve options (supports both direct and deferred) */
674
674
  private getOptions;
675
+ /**
676
+ * Resolve the base URL for requests.
677
+ *
678
+ * - Eager mode: uses pre-computed baseUrl from constructor
679
+ * - Deferred mode: reads apiPrefix from the getter options at runtime
680
+ *
681
+ * Must only be called inside it()/afterAll() callbacks (after beforeAll has run).
682
+ */
683
+ private getBaseUrl;
675
684
  /**
676
685
  * Run all test suites: CRUD + permissions + validation
677
686
  */
@@ -715,6 +724,7 @@ declare class HttpTestHarness<T = unknown> {
715
724
  *
716
725
  * createHttpTestHarness(jobResource, () => ({
717
726
  * app: ctx.app,
727
+ * apiPrefix: '',
718
728
  * fixtures: { valid: { title: 'Test' } },
719
729
  * auth: createBetterAuthProvider({ ... }),
720
730
  * })).runAll();
@@ -1000,7 +1000,7 @@ var DatabaseSnapshot = class {
1000
1000
  * ```
1001
1001
  */
1002
1002
  async function createTestApp(options = {}) {
1003
- const { createApp } = await import("../createApp-D2D5XXaV.mjs").then((n) => n.r);
1003
+ const { createApp } = await import("../createApp-BKHSl2nT.mjs").then((n) => n.r);
1004
1004
  const { useInMemoryDb = true, mongoUri: providedMongoUri, ...appOptions } = options;
1005
1005
  const defaultAuth = {
1006
1006
  type: "jwt",
@@ -1606,6 +1606,7 @@ async function setupBetterAuthOrg(options) {
1606
1606
  *
1607
1607
  * const harness = createHttpTestHarness(jobResource, () => ({
1608
1608
  * app: ctx.app,
1609
+ * apiPrefix: '',
1609
1610
  * fixtures: { valid: { title: 'Test' } },
1610
1611
  * auth: createBetterAuthProvider({ tokens: { admin: ctx.users.admin.token }, orgId: ctx.orgId, adminRole: 'admin' }),
1611
1612
  * }));
@@ -1687,13 +1688,14 @@ function createBetterAuthProvider(options) {
1687
1688
  var HttpTestHarness = class {
1688
1689
  resource;
1689
1690
  optionsOrGetter;
1690
- baseUrl;
1691
+ eagerBaseUrl;
1691
1692
  enabledRoutes;
1692
1693
  updateMethod;
1693
1694
  constructor(resource, optionsOrGetter) {
1694
1695
  this.resource = resource;
1695
1696
  this.optionsOrGetter = optionsOrGetter;
1696
- this.baseUrl = `${typeof optionsOrGetter === "function" ? "/api" : optionsOrGetter.apiPrefix ?? "/api"}${resource.prefix}`;
1697
+ if (typeof optionsOrGetter === "function") this.eagerBaseUrl = null;
1698
+ else this.eagerBaseUrl = `${optionsOrGetter.apiPrefix ?? "/api"}${resource.prefix}`;
1697
1699
  const disabled = new Set(resource.disabledRoutes ?? []);
1698
1700
  this.enabledRoutes = new Set(resource.disableDefaultRoutes ? [] : CRUD_OPERATIONS.filter((op) => !disabled.has(op)));
1699
1701
  this.updateMethod = resource.updateMethod === "PUT" ? "PUT" : "PATCH";
@@ -1703,6 +1705,18 @@ var HttpTestHarness = class {
1703
1705
  return typeof this.optionsOrGetter === "function" ? this.optionsOrGetter() : this.optionsOrGetter;
1704
1706
  }
1705
1707
  /**
1708
+ * Resolve the base URL for requests.
1709
+ *
1710
+ * - Eager mode: uses pre-computed baseUrl from constructor
1711
+ * - Deferred mode: reads apiPrefix from the getter options at runtime
1712
+ *
1713
+ * Must only be called inside it()/afterAll() callbacks (after beforeAll has run).
1714
+ */
1715
+ getBaseUrl() {
1716
+ if (this.eagerBaseUrl !== null) return this.eagerBaseUrl;
1717
+ return `${this.getOptions().apiPrefix ?? ""}${this.resource.prefix}`;
1718
+ }
1719
+ /**
1706
1720
  * Run all test suites: CRUD + permissions + validation
1707
1721
  */
1708
1722
  runAll() {
@@ -1722,12 +1736,13 @@ var HttpTestHarness = class {
1722
1736
  * - GET /:id with non-existent ID → 404
1723
1737
  */
1724
1738
  runCrud() {
1725
- const { resource, baseUrl, enabledRoutes, updateMethod } = this;
1739
+ const { resource, enabledRoutes, updateMethod } = this;
1726
1740
  let createdId = null;
1727
1741
  describe(`${resource.displayName} HTTP CRUD`, () => {
1728
1742
  afterAll(async () => {
1729
1743
  if (createdId && enabledRoutes.has("delete")) {
1730
1744
  const { app, auth } = this.getOptions();
1745
+ const baseUrl = this.getBaseUrl();
1731
1746
  await app.inject({
1732
1747
  method: "DELETE",
1733
1748
  url: `${baseUrl}/${createdId}`,
@@ -1737,6 +1752,7 @@ var HttpTestHarness = class {
1737
1752
  });
1738
1753
  if (enabledRoutes.has("create")) it("POST should create a resource", async () => {
1739
1754
  const { app, auth, fixtures } = this.getOptions();
1755
+ const baseUrl = this.getBaseUrl();
1740
1756
  const adminHeaders = auth.getHeaders(auth.adminRole);
1741
1757
  const res = await app.inject({
1742
1758
  method: "POST",
@@ -1753,6 +1769,7 @@ var HttpTestHarness = class {
1753
1769
  });
1754
1770
  if (enabledRoutes.has("list")) it("GET should list resources", async () => {
1755
1771
  const { app, auth } = this.getOptions();
1772
+ const baseUrl = this.getBaseUrl();
1756
1773
  const res = await app.inject({
1757
1774
  method: "GET",
1758
1775
  url: baseUrl,
@@ -1769,6 +1786,7 @@ var HttpTestHarness = class {
1769
1786
  it("GET /:id should return the resource", async () => {
1770
1787
  if (!createdId) return;
1771
1788
  const { app, auth } = this.getOptions();
1789
+ const baseUrl = this.getBaseUrl();
1772
1790
  const res = await app.inject({
1773
1791
  method: "GET",
1774
1792
  url: `${baseUrl}/${createdId}`,
@@ -1782,6 +1800,7 @@ var HttpTestHarness = class {
1782
1800
  });
1783
1801
  it("GET /:id with non-existent ID should return 404", async () => {
1784
1802
  const { app, auth } = this.getOptions();
1803
+ const baseUrl = this.getBaseUrl();
1785
1804
  const res = await app.inject({
1786
1805
  method: "GET",
1787
1806
  url: `${baseUrl}/000000000000000000000000`,
@@ -1795,6 +1814,7 @@ var HttpTestHarness = class {
1795
1814
  it(`${updateMethod} /:id should update the resource`, async () => {
1796
1815
  if (!createdId) return;
1797
1816
  const { app, auth, fixtures } = this.getOptions();
1817
+ const baseUrl = this.getBaseUrl();
1798
1818
  const updatePayload = fixtures.update || fixtures.valid;
1799
1819
  const res = await app.inject({
1800
1820
  method: updateMethod,
@@ -1809,6 +1829,7 @@ var HttpTestHarness = class {
1809
1829
  });
1810
1830
  it(`${updateMethod} /:id with non-existent ID should return 404`, async () => {
1811
1831
  const { app, auth, fixtures } = this.getOptions();
1832
+ const baseUrl = this.getBaseUrl();
1812
1833
  expect((await app.inject({
1813
1834
  method: updateMethod,
1814
1835
  url: `${baseUrl}/000000000000000000000000`,
@@ -1820,6 +1841,7 @@ var HttpTestHarness = class {
1820
1841
  if (enabledRoutes.has("delete")) {
1821
1842
  it("DELETE /:id should delete the resource", async () => {
1822
1843
  const { app, auth, fixtures } = this.getOptions();
1844
+ const baseUrl = this.getBaseUrl();
1823
1845
  const adminHeaders = auth.getHeaders(auth.adminRole);
1824
1846
  let deleteId;
1825
1847
  if (enabledRoutes.has("create")) {
@@ -1845,6 +1867,7 @@ var HttpTestHarness = class {
1845
1867
  });
1846
1868
  it("DELETE /:id with non-existent ID should return 404", async () => {
1847
1869
  const { app, auth } = this.getOptions();
1870
+ const baseUrl = this.getBaseUrl();
1848
1871
  expect((await app.inject({
1849
1872
  method: "DELETE",
1850
1873
  url: `${baseUrl}/000000000000000000000000`,
@@ -1862,10 +1885,11 @@ var HttpTestHarness = class {
1862
1885
  * - Admin role gets 2xx for all operations
1863
1886
  */
1864
1887
  runPermissions() {
1865
- const { resource, baseUrl, enabledRoutes, updateMethod } = this;
1888
+ const { resource, enabledRoutes, updateMethod } = this;
1866
1889
  describe(`${resource.displayName} HTTP Permissions`, () => {
1867
1890
  if (enabledRoutes.has("list")) it("GET list without auth should return 401", async () => {
1868
1891
  const { app } = this.getOptions();
1892
+ const baseUrl = this.getBaseUrl();
1869
1893
  expect((await app.inject({
1870
1894
  method: "GET",
1871
1895
  url: baseUrl
@@ -1873,6 +1897,7 @@ var HttpTestHarness = class {
1873
1897
  });
1874
1898
  if (enabledRoutes.has("get")) it("GET get without auth should return 401", async () => {
1875
1899
  const { app } = this.getOptions();
1900
+ const baseUrl = this.getBaseUrl();
1876
1901
  expect((await app.inject({
1877
1902
  method: "GET",
1878
1903
  url: `${baseUrl}/000000000000000000000000`
@@ -1880,6 +1905,7 @@ var HttpTestHarness = class {
1880
1905
  });
1881
1906
  if (enabledRoutes.has("create")) it("POST create without auth should return 401", async () => {
1882
1907
  const { app, fixtures } = this.getOptions();
1908
+ const baseUrl = this.getBaseUrl();
1883
1909
  expect((await app.inject({
1884
1910
  method: "POST",
1885
1911
  url: baseUrl,
@@ -1888,6 +1914,7 @@ var HttpTestHarness = class {
1888
1914
  });
1889
1915
  if (enabledRoutes.has("update")) it(`${updateMethod} update without auth should return 401`, async () => {
1890
1916
  const { app, fixtures } = this.getOptions();
1917
+ const baseUrl = this.getBaseUrl();
1891
1918
  expect((await app.inject({
1892
1919
  method: updateMethod,
1893
1920
  url: `${baseUrl}/000000000000000000000000`,
@@ -1896,6 +1923,7 @@ var HttpTestHarness = class {
1896
1923
  });
1897
1924
  if (enabledRoutes.has("delete")) it("DELETE delete without auth should return 401", async () => {
1898
1925
  const { app } = this.getOptions();
1926
+ const baseUrl = this.getBaseUrl();
1899
1927
  expect((await app.inject({
1900
1928
  method: "DELETE",
1901
1929
  url: `${baseUrl}/000000000000000000000000`
@@ -1903,6 +1931,7 @@ var HttpTestHarness = class {
1903
1931
  });
1904
1932
  if (enabledRoutes.has("list")) it("admin should access list endpoint", async () => {
1905
1933
  const { app, auth } = this.getOptions();
1934
+ const baseUrl = this.getBaseUrl();
1906
1935
  expect((await app.inject({
1907
1936
  method: "GET",
1908
1937
  url: baseUrl,
@@ -1911,6 +1940,7 @@ var HttpTestHarness = class {
1911
1940
  });
1912
1941
  if (enabledRoutes.has("create")) it("admin should access create endpoint", async () => {
1913
1942
  const { app, auth, fixtures } = this.getOptions();
1943
+ const baseUrl = this.getBaseUrl();
1914
1944
  const res = await app.inject({
1915
1945
  method: "POST",
1916
1946
  url: baseUrl,
@@ -1933,11 +1963,12 @@ var HttpTestHarness = class {
1933
1963
  * Tests that invalid payloads return 400.
1934
1964
  */
1935
1965
  runValidation() {
1936
- const { resource, baseUrl, enabledRoutes } = this;
1966
+ const { resource, enabledRoutes } = this;
1937
1967
  if (!enabledRoutes.has("create")) return;
1938
1968
  describe(`${resource.displayName} HTTP Validation`, () => {
1939
1969
  it("POST with invalid payload should not return 2xx", async () => {
1940
1970
  const { app, auth, fixtures } = this.getOptions();
1971
+ const baseUrl = this.getBaseUrl();
1941
1972
  if (!fixtures.invalid) return;
1942
1973
  const res = await app.inject({
1943
1974
  method: "POST",
@@ -1963,6 +1994,7 @@ var HttpTestHarness = class {
1963
1994
  *
1964
1995
  * createHttpTestHarness(jobResource, () => ({
1965
1996
  * app: ctx.app,
1997
+ * apiPrefix: '',
1966
1998
  * fixtures: { valid: { title: 'Test' } },
1967
1999
  * auth: createBetterAuthProvider({ ... }),
1968
2000
  * })).runAll();
@@ -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-Cb2klgid.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-DZYNK9bb.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
@@ -1,5 +1,5 @@
1
1
  import "../elevation-DGo5shaX.mjs";
2
- import "../interface-Cb2klgid.mjs";
2
+ import "../interface-DZYNK9bb.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";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classytic/arc",
3
- "version": "2.1.7",
3
+ "version": "2.2.5",
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.3",
194
+ "@classytic/mongokit": "^3.2.4",
195
195
  "@classytic/streamline": ">=1.0.0",
196
196
  "@fastify/cors": "^11.0.0",
197
197
  "@fastify/helmet": "^13.0.0",