@classytic/arc 2.8.1 → 2.8.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.
Files changed (106) hide show
  1. package/README.md +1 -1
  2. package/dist/{ResourceRegistry-Dtcojmu8.mjs → ResourceRegistry-Dq3_zBQP.mjs} +5 -5
  3. package/dist/adapters/index.d.mts +2 -2
  4. package/dist/audit/index.d.mts +1 -1
  5. package/dist/audit/index.mjs +1 -1
  6. package/dist/audit/mongodb.d.mts +1 -1
  7. package/dist/audit/mongodb.mjs +1 -1
  8. package/dist/auth/index.d.mts +4 -4
  9. package/dist/auth/redis-session.d.mts +1 -1
  10. package/dist/cache/index.d.mts +2 -2
  11. package/dist/cli/commands/describe.mjs +1 -1
  12. package/dist/cli/commands/docs.mjs +2 -2
  13. package/dist/cli/commands/generate.mjs +1 -1
  14. package/dist/cli/commands/init.mjs +10 -10
  15. package/dist/cli/commands/introspect.mjs +3 -3
  16. package/dist/core/index.d.mts +3 -3
  17. package/dist/core/index.mjs +4 -4
  18. package/dist/{core-CrLDuqoT.mjs → core-DKSwNSXf.mjs} +1 -1
  19. package/dist/{createApp-p2OThysU.mjs → createApp-BOYjBgdI.mjs} +16 -7
  20. package/dist/{defineResource-CqeUltrW.mjs → defineResource-Bb_Bdhtw.mjs} +42 -27
  21. package/dist/docs/index.d.mts +2 -2
  22. package/dist/docs/index.mjs +1 -1
  23. package/dist/dynamic/index.d.mts +2 -2
  24. package/dist/dynamic/index.mjs +1 -1
  25. package/dist/{errorHandler-DJ7OAB2V.d.mts → errorHandler-CdZDavNH.d.mts} +2 -2
  26. package/dist/{eventPlugin-Cdjwo0Gv.d.mts → eventPlugin-CVxlE6De.d.mts} +1 -1
  27. package/dist/events/index.d.mts +3 -3
  28. package/dist/events/index.mjs +1 -1
  29. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  30. package/dist/events/transports/redis-stream-entry.mjs +3 -1
  31. package/dist/events/transports/redis.d.mts +1 -1
  32. package/dist/factory/index.d.mts +1 -1
  33. package/dist/factory/index.mjs +2 -152
  34. package/dist/hooks/index.d.mts +1 -1
  35. package/dist/idempotency/index.d.mts +3 -3
  36. package/dist/idempotency/mongodb.d.mts +1 -1
  37. package/dist/idempotency/mongodb.mjs +18 -6
  38. package/dist/idempotency/redis.d.mts +1 -1
  39. package/dist/idempotency/redis.mjs +10 -1
  40. package/dist/{index-0zj73o2U.d.mts → index-BgmMdpm8.d.mts} +1 -1
  41. package/dist/{index-CBru2y5Y.d.mts → index-CSkeivBx.d.mts} +3 -3
  42. package/dist/{index-DadoLP51.d.mts → index-CpTSDqmD.d.mts} +26 -4
  43. package/dist/index.d.mts +7 -7
  44. package/dist/index.mjs +3 -3
  45. package/dist/integrations/event-gateway.d.mts +1 -1
  46. package/dist/integrations/event-gateway.mjs +1 -1
  47. package/dist/integrations/index.d.mts +1 -1
  48. package/dist/integrations/mcp/index.d.mts +2 -2
  49. package/dist/integrations/mcp/index.mjs +1 -1
  50. package/dist/integrations/mcp/testing.d.mts +1 -1
  51. package/dist/integrations/mcp/testing.mjs +1 -1
  52. package/dist/{interface-CS6d7HiB.d.mts → interface-BVuMfeVv.d.mts} +47 -18
  53. package/dist/loadResources-Bksk8ydA.mjs +154 -0
  54. package/dist/{mongodb-B1eVtFhw.d.mts → mongodb-B8U2xaLj.d.mts} +1 -1
  55. package/dist/{mongodb-NShVZDMr.d.mts → mongodb-X7LbEjTN.d.mts} +10 -1
  56. package/dist/{openapi-q6rNKfZy.mjs → openapi-CYCuekCn.mjs} +2 -2
  57. package/dist/org/index.d.mts +2 -2
  58. package/dist/permissions/index.d.mts +3 -3
  59. package/dist/plugins/index.d.mts +5 -5
  60. package/dist/plugins/index.mjs +7 -7
  61. package/dist/plugins/tracing-entry.d.mts +1 -1
  62. package/dist/plugins/tracing-entry.mjs +1 -1
  63. package/dist/policies/index.d.mts +1 -1
  64. package/dist/presets/index.d.mts +1 -1
  65. package/dist/presets/index.mjs +1 -1
  66. package/dist/presets/multiTenant.d.mts +1 -1
  67. package/dist/{presets-BFrGvvjL.mjs → presets-C2xgzW6x.mjs} +10 -18
  68. package/dist/{queryCachePlugin-BCFVXnxK.d.mts → queryCachePlugin-CnTZZTC5.d.mts} +1 -1
  69. package/dist/{redis-stream-BgrYzpeq.d.mts → redis-stream-D54N5oXs.d.mts} +1 -1
  70. package/dist/{redis-Bunu3qWg.d.mts → redis-z3sFr1UP.d.mts} +1 -1
  71. package/dist/registry/index.d.mts +1 -1
  72. package/dist/registry/index.mjs +1 -1
  73. package/dist/{resourceToTools-DNNWnZtx.mjs → resourceToTools-O_HwWXFa.mjs} +1 -1
  74. package/dist/rpc/index.d.mts +1 -1
  75. package/dist/scope/index.d.mts +2 -2
  76. package/dist/testing/index.d.mts +2 -2
  77. package/dist/testing/index.mjs +1 -1
  78. package/dist/types/index.d.mts +4 -4
  79. package/dist/{types-BlOuKTPw.d.mts → types-Bg2X42_m.d.mts} +30 -9
  80. package/dist/{types-BoaZHr-2.d.mts → types-CVC4HOKi.d.mts} +1 -1
  81. package/dist/{types-D3b7hA00.d.mts → types-CcG4avic.d.mts} +1 -1
  82. package/dist/utils/index.d.mts +43 -5
  83. package/dist/utils/index.mjs +3 -3
  84. package/dist/{utils-7sJ8X83I.mjs → utils-yYT3HDXt.mjs} +65 -1
  85. package/package.json +8 -8
  86. package/skills/arc/SKILL.md +79 -6
  87. /package/dist/{EventTransport-CLXJUzyT.d.mts → EventTransport-CinyO7zQ.d.mts} +0 -0
  88. /package/dist/{caching-CHH-iHs3.mjs → caching-CjybdRwx.mjs} +0 -0
  89. /package/dist/{circuitBreaker-BGVoB1hD.d.mts → circuitBreaker-CvXkjfrW.d.mts} +0 -0
  90. /package/dist/{elevation-UJO3-NvX.d.mts → elevation-s5ykdNHr.d.mts} +0 -0
  91. /package/dist/{errorHandler-Cw34h_om.mjs → errorHandler-mzqk4cGl.mjs} +0 -0
  92. /package/dist/{errors-BI8kEKsO.d.mts → errors-Bmn3eZT6.d.mts} +0 -0
  93. /package/dist/{eventPlugin-XijlQmlL.mjs → eventPlugin-D91S2YF4.mjs} +0 -0
  94. /package/dist/{externalPaths-BQ8QijNH.d.mts → externalPaths-Bapitwvd.d.mts} +0 -0
  95. /package/dist/{fields-DoeDgh2b.d.mts → fields-DC4So2M2.d.mts} +0 -0
  96. /package/dist/{interface-CkkWm5uR.d.mts → interface-B-pe8fhj.d.mts} +0 -0
  97. /package/dist/{interface-bpoLKKqx.d.mts → interface-DplgQO2e.d.mts} +0 -0
  98. /package/dist/{metrics-DuhiSEZI.mjs → metrics-TuOmguhi.mjs} +0 -0
  99. /package/dist/{mongodb-5Ff3w8jy.mjs → mongodb-B5O6xaW1.mjs} +0 -0
  100. /package/dist/{pluralize-BneOJkpi.mjs → pluralize-A0tWEl1K.mjs} +0 -0
  101. /package/dist/{replyHelpers-CXtJDAZ0.mjs → replyHelpers-BLojtuvR.mjs} +0 -0
  102. /package/dist/{sessionManager-BkzVU8h2.d.mts → sessionManager-D-oNWHz3.d.mts} +0 -0
  103. /package/dist/{sse-CD5Hghpu.mjs → sse-CJpt7LGI.mjs} +0 -0
  104. /package/dist/{tracing-xqXzWeaf.d.mts → tracing-DxjKk7eW.d.mts} +0 -0
  105. /package/dist/{types-CN6JvmYz.d.mts → types-C72d3NDn.d.mts} +0 -0
  106. /package/dist/{versioning-CPU_5Xfs.mjs → versioning-Cm8qoFDg.mjs} +0 -0
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Database-agnostic resource framework for Fastify. Define resources, get CRUD routes, permissions, presets, caching, events, OpenAPI, and MCP tools — without boilerplate.
4
4
 
5
- **v2.8.1** | Fastify 5+ | Node.js 22+ | ESM only | 260+ test files, 3600+ tests
5
+ **v2.8.3** | Fastify 5+ | Node.js 22+ | ESM only | 278+ test files, 3844+ tests
6
6
 
7
7
  ## Install
8
8
 
@@ -31,7 +31,7 @@ var ResourceRegistry = class {
31
31
  permissions: resource.permissions,
32
32
  presets: resource._appliedPresets ?? [],
33
33
  routes: [],
34
- additionalRoutes: resource.additionalRoutes.map((r) => ({
34
+ customRoutes: (resource.routes ?? []).map((r) => ({
35
35
  method: r.method,
36
36
  path: r.path,
37
37
  handler: typeof r.handler === "string" ? r.handler : r.handler.name || "anonymous",
@@ -39,7 +39,7 @@ var ResourceRegistry = class {
39
39
  summary: r.summary,
40
40
  description: r.description,
41
41
  permissions: r.permissions,
42
- wrapHandler: r.wrapHandler,
42
+ raw: r.raw,
43
43
  schema: r.schema
44
44
  })),
45
45
  events: Object.keys(resource.events ?? {}),
@@ -111,11 +111,11 @@ var ResourceRegistry = class {
111
111
  presetUsage: presetCounts,
112
112
  totalRoutes: resources.reduce((sum, r) => {
113
113
  const actionsCount = (r.actions?.length ?? 0) > 0 ? 1 : 0;
114
- if (r.disableDefaultRoutes) return sum + (r.additionalRoutes?.length ?? 0) + actionsCount;
114
+ if (r.disableDefaultRoutes) return sum + (r.customRoutes?.length ?? 0) + actionsCount;
115
115
  const disabledSet = new Set(r.disabledRoutes ?? []);
116
116
  let defaultCount = CRUD_OPERATIONS.filter((route) => !disabledSet.has(route)).length;
117
117
  if (!disabledSet.has("update") && r.updateMethod === "both") defaultCount += 1;
118
- return sum + defaultCount + (r.additionalRoutes?.length ?? 0) + actionsCount;
118
+ return sum + defaultCount + (r.customRoutes?.length ?? 0) + actionsCount;
119
119
  }, 0),
120
120
  totalEvents: resources.reduce((sum, r) => sum + (r.events?.length ?? 0), 0)
121
121
  };
@@ -170,7 +170,7 @@ var ResourceRegistry = class {
170
170
  module: r.module,
171
171
  presets: r.presets,
172
172
  permissions: r.permissions,
173
- routes: [...defaultRoutes, ...r.additionalRoutes?.map((ar) => ({
173
+ routes: [...defaultRoutes, ...r.customRoutes?.map((ar) => ({
174
174
  method: ar.method,
175
175
  path: `${r.prefix}${ar.path}`,
176
176
  operation: ar.operation ?? (typeof ar.handler === "string" ? ar.handler : "custom"),
@@ -1,3 +1,3 @@
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-CS6d7HiB.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-0zj73o2U.mjs";
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-BVuMfeVv.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-BgmMdpm8.mjs";
3
3
  export { AdapterFactory, DataAdapter, FieldMetadata, MongooseAdapter, MongooseAdapterOptions, PrismaAdapter, PrismaAdapterOptions, PrismaQueryOptions, PrismaQueryParser, PrismaQueryParserOptions, RelationMetadata, RepositoryLike, SchemaMetadata, ValidationResult, createMongooseAdapter, createPrismaAdapter };
@@ -1,4 +1,4 @@
1
- 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-B1eVtFhw.mjs";
1
+ 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-B8U2xaLj.mjs";
2
2
  import { FastifyPluginAsync } from "fastify";
3
3
 
4
4
  //#region src/audit/auditPlugin.d.ts
@@ -1,4 +1,4 @@
1
- import { t as MongoAuditStore } from "../mongodb-5Ff3w8jy.mjs";
1
+ import { t as MongoAuditStore } from "../mongodb-B5O6xaW1.mjs";
2
2
  import fp from "fastify-plugin";
3
3
  //#region src/audit/stores/interface.ts
4
4
  /**
@@ -1,2 +1,2 @@
1
- import { n as MongoAuditStoreOptions, t as MongoAuditStore } from "../mongodb-B1eVtFhw.mjs";
1
+ import { n as MongoAuditStoreOptions, t as MongoAuditStore } from "../mongodb-B8U2xaLj.mjs";
2
2
  export { MongoAuditStore, type MongoAuditStoreOptions };
@@ -1,2 +1,2 @@
1
- import { t as MongoAuditStore } from "../mongodb-5Ff3w8jy.mjs";
1
+ import { t as MongoAuditStore } from "../mongodb-B5O6xaW1.mjs";
2
2
  export { MongoAuditStore };
@@ -1,7 +1,7 @@
1
- import { b as AuthPluginOptions, y as AuthHelpers } from "../interface-CS6d7HiB.mjs";
2
- import { t as PermissionCheck } from "../types-BoaZHr-2.mjs";
3
- import { t as ExternalOpenApiPaths } from "../externalPaths-BQ8QijNH.mjs";
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-BkzVU8h2.mjs";
1
+ import { b as AuthPluginOptions, y as AuthHelpers } from "../interface-BVuMfeVv.mjs";
2
+ import { t as PermissionCheck } from "../types-CVC4HOKi.mjs";
3
+ import { t as ExternalOpenApiPaths } from "../externalPaths-Bapitwvd.mjs";
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-D-oNWHz3.mjs";
5
5
  import { FastifyPluginAsync, FastifyReply as FastifyReply$1, FastifyRequest as FastifyRequest$1 } from "fastify";
6
6
 
7
7
  //#region src/auth/authPlugin.d.ts
@@ -1,4 +1,4 @@
1
- import { i as SessionData, s as SessionStore } from "../sessionManager-BkzVU8h2.mjs";
1
+ import { i as SessionData, s as SessionStore } from "../sessionManager-D-oNWHz3.mjs";
2
2
 
3
3
  //#region src/auth/redis-session.d.ts
4
4
  /** Minimal Redis client interface — compatible with ioredis */
@@ -1,5 +1,5 @@
1
- import { i as CacheStore, n as CacheSetOptions, r as CacheStats, t as CacheLogger } from "../interface-bpoLKKqx.mjs";
2
- import { a as CacheEnvelope, c as QueryCache, i as queryCachePlugin, l as QueryCacheConfig, n as QueryCacheDefaults, o as CacheResult, r as QueryCachePluginOptions, s as CacheStatus, t as CrossResourceRule } from "../queryCachePlugin-BCFVXnxK.mjs";
1
+ import { i as CacheStore, n as CacheSetOptions, r as CacheStats, t as CacheLogger } from "../interface-DplgQO2e.mjs";
2
+ import { a as CacheEnvelope, c as QueryCache, i as queryCachePlugin, l as QueryCacheConfig, n as QueryCacheDefaults, o as CacheResult, r as QueryCachePluginOptions, s as CacheStatus, t as CrossResourceRule } from "../queryCachePlugin-CnTZZTC5.mjs";
3
3
 
4
4
  //#region src/cache/keys.d.ts
5
5
  /**
@@ -128,7 +128,7 @@ function describeRoutes(resource) {
128
128
  routes.push(route);
129
129
  }
130
130
  }
131
- for (const ar of resource.additionalRoutes) routes.push({
131
+ for (const ar of resource.routes ?? []) routes.push({
132
132
  method: ar.method,
133
133
  path: `${resource.prefix}${ar.path}`,
134
134
  operation: typeof ar.handler === "string" ? ar.handler : "custom",
@@ -1,5 +1,5 @@
1
- import { t as ResourceRegistry } from "../../ResourceRegistry-Dtcojmu8.mjs";
2
- import { t as buildOpenApiSpec } from "../../openapi-q6rNKfZy.mjs";
1
+ import { t as ResourceRegistry } from "../../ResourceRegistry-Dq3_zBQP.mjs";
2
+ import { t as buildOpenApiSpec } from "../../openapi-CYCuekCn.mjs";
3
3
  import { dirname, resolve } from "node:path";
4
4
  import { pathToFileURL } from "node:url";
5
5
  import { mkdirSync, writeFileSync } from "node:fs";
@@ -1,4 +1,4 @@
1
- import { t as pluralize } from "../../pluralize-BneOJkpi.mjs";
1
+ import { t as pluralize } from "../../pluralize-A0tWEl1K.mjs";
2
2
  import { join } from "node:path";
3
3
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
4
  //#region src/cli/commands/generate.ts
@@ -1678,7 +1678,7 @@ const exampleResource = defineResource${ts ? "<ExampleDocument>" : ""}({
1678
1678
  permissions: ${config.tenant === "multi" ? "orgStaffPermissions" : "publicReadPermissions"},
1679
1679
 
1680
1680
  // Add custom routes here:
1681
- // additionalRoutes: [
1681
+ // routes: [
1682
1682
  // {
1683
1683
  // method: 'GET',
1684
1684
  // path: '/custom',
@@ -2134,14 +2134,14 @@ export const authResource = defineResource({
2134
2134
  adapter: createAdapter(User${ts ? " as any" : ""}, userRepository${ts ? " as any" : ""}),
2135
2135
  disableDefaultRoutes: true,
2136
2136
 
2137
- additionalRoutes: [
2137
+ routes: [
2138
2138
  {
2139
2139
  method: 'POST',
2140
2140
  path: '/register',
2141
2141
  summary: 'Register new user',
2142
2142
  permissions: allowPublic(),
2143
2143
  handler: handlers.register,
2144
- wrapHandler: false,
2144
+ raw: true,
2145
2145
  schema: { body: schemas.registerBody, response: { 201: schemas.successResponse } },
2146
2146
  },
2147
2147
  {
@@ -2150,7 +2150,7 @@ export const authResource = defineResource({
2150
2150
  summary: 'User login',
2151
2151
  permissions: allowPublic(),
2152
2152
  handler: handlers.login,
2153
- wrapHandler: false,
2153
+ raw: true,
2154
2154
  schema: { body: schemas.loginBody, response: { 200: schemas.loginResponse } },
2155
2155
  },
2156
2156
  {
@@ -2159,7 +2159,7 @@ export const authResource = defineResource({
2159
2159
  summary: 'Refresh access token',
2160
2160
  permissions: allowPublic(),
2161
2161
  handler: handlers.refreshToken,
2162
- wrapHandler: false,
2162
+ raw: true,
2163
2163
  schema: { body: schemas.refreshBody, response: { 200: schemas.tokenResponse } },
2164
2164
  },
2165
2165
  {
@@ -2168,7 +2168,7 @@ export const authResource = defineResource({
2168
2168
  summary: 'Request password reset',
2169
2169
  permissions: allowPublic(),
2170
2170
  handler: handlers.forgotPassword,
2171
- wrapHandler: false,
2171
+ raw: true,
2172
2172
  schema: { body: schemas.forgotBody, response: { 200: schemas.successResponse } },
2173
2173
  },
2174
2174
  {
@@ -2177,7 +2177,7 @@ export const authResource = defineResource({
2177
2177
  summary: 'Reset password with token',
2178
2178
  permissions: allowPublic(),
2179
2179
  handler: handlers.resetPassword,
2180
- wrapHandler: false,
2180
+ raw: true,
2181
2181
  schema: { body: schemas.resetBody, response: { 200: schemas.successResponse } },
2182
2182
  },
2183
2183
  ],
@@ -2195,14 +2195,14 @@ export const userProfileResource = defineResource({
2195
2195
  adapter: createAdapter(User${ts ? " as any" : ""}, userRepository${ts ? " as any" : ""}),
2196
2196
  disableDefaultRoutes: true,
2197
2197
 
2198
- additionalRoutes: [
2198
+ routes: [
2199
2199
  {
2200
2200
  method: 'GET',
2201
2201
  path: '/me',
2202
2202
  summary: 'Get current user profile',
2203
2203
  permissions: requireAuth(),
2204
2204
  handler: handlers.getUserProfile,
2205
- wrapHandler: false,
2205
+ raw: true,
2206
2206
  schema: { response: { 200: schemas.userProfileResponse } },
2207
2207
  },
2208
2208
  {
@@ -2211,7 +2211,7 @@ export const userProfileResource = defineResource({
2211
2211
  summary: 'Update current user profile',
2212
2212
  permissions: requireAuth(),
2213
2213
  handler: handlers.updateUserProfile,
2214
- wrapHandler: false,
2214
+ raw: true,
2215
2215
  schema: { body: schemas.updateUserBody, response: { 200: schemas.userProfileResponse } },
2216
2216
  },
2217
2217
  ],
@@ -1,4 +1,4 @@
1
- import { t as ResourceRegistry } from "../../ResourceRegistry-Dtcojmu8.mjs";
1
+ import { t as ResourceRegistry } from "../../ResourceRegistry-Dq3_zBQP.mjs";
2
2
  import { resolve } from "node:path";
3
3
  import { pathToFileURL } from "node:url";
4
4
  //#region src/cli/commands/introspect.ts
@@ -56,14 +56,14 @@ async function introspect(args) {
56
56
  });
57
57
  }
58
58
  if (resource.presets && resource.presets.length > 0) console.log(` Presets: ${resource.presets.join(", ")}`);
59
- if (resource.additionalRoutes && resource.additionalRoutes.length > 0) console.log(` Additional Routes: ${resource.additionalRoutes.length}`);
59
+ if (resource.customRoutes && resource.customRoutes.length > 0) console.log(` Additional Routes: ${resource.customRoutes.length}`);
60
60
  console.log("");
61
61
  });
62
62
  const stats = registry.getStats();
63
63
  console.log("Summary:");
64
64
  console.log(` Total Resources: ${stats.totalResources}`);
65
65
  console.log(` With Presets: ${resources.filter((r) => r.presets?.length > 0).length}`);
66
- console.log(` With Custom Routes: ${resources.filter((r) => r.additionalRoutes && r.additionalRoutes.length > 0).length}`);
66
+ console.log(` With Custom Routes: ${resources.filter((r) => r.customRoutes && r.customRoutes.length > 0).length}`);
67
67
  } catch (error) {
68
68
  if (error instanceof Error) throw error;
69
69
  throw new Error(String(error));
@@ -1,3 +1,3 @@
1
- import { At as BaseController, Ft as BodySanitizerConfig, It as AccessControl, Jt as defineResource, Lt as AccessControlConfig, Mt as QueryResolver, Nt as QueryResolverConfig, Pt as BodySanitizer, jt as BaseControllerOptions, qt as ResourceDefinition } from "../interface-CS6d7HiB.mjs";
2
- import { A as MutationOperation, C as HOOK_PHASES, D as MAX_REGEX_LENGTH, E as MAX_FILTER_DEPTH, M as SYSTEM_FIELDS, O as MAX_SEARCH_LENGTH, S as HOOK_OPERATIONS, T as HookPhase, _ as DEFAULT_LIMIT, a as getControllerScope, b as DEFAULT_TENANT_FIELD, c as createCrudRouter, d as ActionRouterConfig, f as IdempotencyService, g as DEFAULT_ID_FIELD, h as CrudOperation, i as getControllerContext, j as RESERVED_QUERY_PARAMS, k as MUTATION_OPERATIONS, l as createPermissionMiddleware, m as CRUD_OPERATIONS, n as createFastifyHandler, o as sendControllerResponse, p as createActionRouter, r as createRequestContext, s as defineResourceVariants, t as createCrudHandlers, u as ActionHandler, v as DEFAULT_MAX_LIMIT, w as HookOperation, x as DEFAULT_UPDATE_METHOD, y as DEFAULT_SORT } from "../index-DadoLP51.mjs";
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, defineResourceVariants, getControllerContext, getControllerScope, sendControllerResponse };
1
+ import { At as BaseController, Ft as BodySanitizerConfig, It as AccessControl, Jt as defineResource, Lt as AccessControlConfig, Mt as QueryResolver, Nt as QueryResolverConfig, Pt as BodySanitizer, jt as BaseControllerOptions, qt as ResourceDefinition } from "../interface-BVuMfeVv.mjs";
2
+ import { A as MUTATION_OPERATIONS, C as HOOK_OPERATIONS, D as MAX_FILTER_DEPTH, E as HookPhase, M as RESERVED_QUERY_PARAMS, N as SYSTEM_FIELDS, O as MAX_REGEX_LENGTH, S as DEFAULT_UPDATE_METHOD, T as HookOperation, _ as DEFAULT_ID_FIELD, a as getControllerScope, b as DEFAULT_SORT, c as createCrudRouter, d as ActionRouterConfig, f as IdempotencyService, g as CrudOperation, h as CRUD_OPERATIONS, i as getControllerContext, j as MutationOperation, k as MAX_SEARCH_LENGTH, l as createPermissionMiddleware, m as createActionRouter, n as createFastifyHandler, o as sendControllerResponse, p as buildActionBodySchema, r as createRequestContext, s as defineResourceVariants, t as createCrudHandlers, u as ActionHandler, v as DEFAULT_LIMIT, w as HOOK_PHASES, x as DEFAULT_TENANT_FIELD, y as DEFAULT_MAX_LIMIT } from "../index-CpTSDqmD.mjs";
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, buildActionBodySchema, createActionRouter, createCrudHandlers, createCrudRouter, createFastifyHandler, createPermissionMiddleware, createRequestContext, defineResource, defineResourceVariants, getControllerContext, getControllerScope, sendControllerResponse };
@@ -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-Cxde4rpC.mjs";
2
2
  import { i as AccessControl, n as QueryResolver, r as BodySanitizer, t as BaseController } from "../BaseController-DAGGc5Xn.mjs";
3
- import { n as createActionRouter } from "../createActionRouter-Df1BuawX.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-CqeUltrW.mjs";
5
- import { t as defineResourceVariants } from "../core-CrLDuqoT.mjs";
6
- 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, defineResourceVariants, getControllerContext, getControllerScope, sendControllerResponse };
3
+ import { n as createActionRouter, t as buildActionBodySchema } from "../createActionRouter-Df1BuawX.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-Bb_Bdhtw.mjs";
5
+ import { t as defineResourceVariants } from "../core-DKSwNSXf.mjs";
6
+ 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, buildActionBodySchema, createActionRouter, createCrudHandlers, createCrudRouter, createFastifyHandler, createPermissionMiddleware, createRequestContext, defineResource, defineResourceVariants, getControllerContext, getControllerScope, sendControllerResponse };
@@ -1,4 +1,4 @@
1
- import { n as defineResource } from "./defineResource-CqeUltrW.mjs";
1
+ import { n as defineResource } from "./defineResource-Bb_Bdhtw.mjs";
2
2
  //#region src/core/defineResourceVariants.ts
3
3
  /**
4
4
  * Define multiple resources from a shared base config and per-variant overrides.
@@ -207,7 +207,7 @@ async function registerArcCore(fastify, config, trackPlugin) {
207
207
  await fastify.register(arcCorePlugin, { emitEvents: config.arcPlugins?.emitEvents !== false });
208
208
  trackPlugin("arc-core");
209
209
  if (config.arcPlugins?.events !== false) {
210
- const { default: eventPlugin } = await import("./eventPlugin-XijlQmlL.mjs").then((n) => n.n);
210
+ const { default: eventPlugin } = await import("./eventPlugin-D91S2YF4.mjs").then((n) => n.n);
211
211
  const eventOpts = typeof config.arcPlugins?.events === "object" ? config.arcPlugins.events : {};
212
212
  await fastify.register(eventPlugin, {
213
213
  ...eventOpts,
@@ -243,7 +243,7 @@ async function registerArcPlugins(fastify, config, trackPlugin, modules) {
243
243
  trackPlugin("arc-graceful-shutdown");
244
244
  }
245
245
  if (config.arcPlugins?.caching) {
246
- const { default: cachingPlugin } = await import("./caching-CHH-iHs3.mjs").then((n) => n.r);
246
+ const { default: cachingPlugin } = await import("./caching-CjybdRwx.mjs").then((n) => n.r);
247
247
  const opts = config.arcPlugins.caching === true ? {} : config.arcPlugins.caching;
248
248
  await fastify.register(cachingPlugin, opts);
249
249
  trackPlugin("arc-caching", opts);
@@ -260,19 +260,19 @@ async function registerArcPlugins(fastify, config, trackPlugin, modules) {
260
260
  }
261
261
  if (config.arcPlugins?.sse) if (config.arcPlugins?.events === false) fastify.log.warn("SSE plugin requires events plugin (arcPlugins.events). SSE disabled.");
262
262
  else {
263
- const { default: ssePlugin } = await import("./sse-CD5Hghpu.mjs").then((n) => n.r);
263
+ const { default: ssePlugin } = await import("./sse-CJpt7LGI.mjs").then((n) => n.r);
264
264
  const opts = config.arcPlugins.sse === true ? {} : config.arcPlugins.sse;
265
265
  await fastify.register(ssePlugin, opts);
266
266
  trackPlugin("arc-sse", opts);
267
267
  }
268
268
  if (config.arcPlugins?.metrics) {
269
- const { default: metricsPlugin } = await import("./metrics-DuhiSEZI.mjs").then((n) => n.r);
269
+ const { default: metricsPlugin } = await import("./metrics-TuOmguhi.mjs").then((n) => n.r);
270
270
  const opts = config.arcPlugins.metrics === true ? {} : config.arcPlugins.metrics;
271
271
  await fastify.register(metricsPlugin, opts);
272
272
  trackPlugin("arc-metrics", opts);
273
273
  }
274
274
  if (config.arcPlugins?.versioning) {
275
- const { default: versioningPlugin } = await import("./versioning-CPU_5Xfs.mjs").then((n) => n.r);
275
+ const { default: versioningPlugin } = await import("./versioning-Cm8qoFDg.mjs").then((n) => n.r);
276
276
  await fastify.register(versioningPlugin, config.arcPlugins.versioning);
277
277
  trackPlugin("arc-versioning", config.arcPlugins.versioning);
278
278
  }
@@ -350,7 +350,7 @@ async function registerElevation(fastify, config, trackPlugin) {
350
350
  */
351
351
  async function registerErrorHandler(fastify, config, trackPlugin) {
352
352
  if (config.errorHandler === false) return;
353
- const { errorHandlerPlugin } = await import("./errorHandler-Cw34h_om.mjs").then((n) => n.n);
353
+ const { errorHandlerPlugin } = await import("./errorHandler-mzqk4cGl.mjs").then((n) => n.n);
354
354
  const errorOpts = typeof config.errorHandler === "object" ? config.errorHandler : { includeStack: config.preset !== "production" };
355
355
  await fastify.register(errorHandlerPlugin, errorOpts);
356
356
  trackPlugin("arc-error-handler", errorOpts);
@@ -416,6 +416,15 @@ async function registerResources(fastify, config) {
416
416
  for (const init of config.bootstrap) await init(fastify);
417
417
  fastify.log.debug(`${config.bootstrap.length} bootstrap function(s) executed`);
418
418
  }
419
+ if (!config.resources?.length && config.resourceDir) {
420
+ const { loadResources } = await import("./loadResources-Bksk8ydA.mjs").then((n) => n.n);
421
+ const { resolve } = await import("node:path");
422
+ const dir = resolve(config.resourceDir);
423
+ config = {
424
+ ...config,
425
+ resources: await loadResources(dir, { logger: fastify.log })
426
+ };
427
+ }
419
428
  if (config.resources?.length) {
420
429
  const seen = /* @__PURE__ */ new Set();
421
430
  for (const resource of config.resources) if (resource.name) {
@@ -720,7 +729,7 @@ async function createApp(options) {
720
729
  await registerErrorHandler(fastify, config, trackPlugin);
721
730
  await registerResources(fastify, config);
722
731
  if (config.replyHelpers) {
723
- const { replyHelpersPlugin } = await import("./replyHelpers-CXtJDAZ0.mjs").then((n) => n.n);
732
+ const { replyHelpersPlugin } = await import("./replyHelpers-BLojtuvR.mjs").then((n) => n.n);
724
733
  await fastify.register(replyHelpersPlugin);
725
734
  }
726
735
  if (config.serializeBigInt) fastify.addHook("preSerialization", async (_request, _reply, payload) => {
@@ -6,10 +6,10 @@ import { t as getUserRoles } from "./types-ZUu_h0jp.mjs";
6
6
  import { n as normalizePermissionResult, t as applyPermissionResult } from "./applyPermissionResult-D6GPMsvh.mjs";
7
7
  import { n as convertRouteSchema, t as convertOpenApiSchemas } from "./schemaConverter-OxfCshus.mjs";
8
8
  import { t as requestContext } from "./requestContext-DYvHl113.mjs";
9
- import { i as getDefaultCrudSchemas } from "./utils-7sJ8X83I.mjs";
9
+ import { i as getDefaultCrudSchemas } from "./utils-yYT3HDXt.mjs";
10
10
  import { r as ForbiddenError } from "./errors-BF2bIOIS.mjs";
11
11
  import { t as hasEvents } from "./typeGuards-CcFZXgU7.mjs";
12
- import { r as getAvailablePresets, t as applyPresets } from "./presets-BFrGvvjL.mjs";
12
+ import { r as getAvailablePresets, t as applyPresets } from "./presets-C2xgzW6x.mjs";
13
13
  //#region src/pipeline/pipe.ts
14
14
  /**
15
15
  * Compose pipeline steps into an ordered array.
@@ -390,7 +390,7 @@ function buildPermissionMiddleware(permissionCheck, resourceName, action) {
390
390
  * Create additional routes from preset/custom definitions
391
391
  */
392
392
  function createAdditionalRoutes(fastify, routes, controller, options) {
393
- const { tag, resourceName, arcDecorator, rateLimitConfig, cacheMw, idempotencyMw, pipeline } = options;
393
+ const { tag, resourceName, arcDecorator, rateLimitConfig, cacheMw, idempotencyMw, pipeline, routeGuards } = options;
394
394
  for (const route of routes) {
395
395
  const opName = route.operation ?? (typeof route.handler === "string" ? route.handler : `${route.method.toLowerCase()}${route.path.replace(/[/:]/g, "_")}`);
396
396
  let handler;
@@ -431,6 +431,7 @@ function createAdditionalRoutes(fastify, routes, controller, options) {
431
431
  authMw,
432
432
  permissionMw,
433
433
  pluginMw,
434
+ ...routeGuards,
434
435
  ...customPreHandlers
435
436
  ].filter(Boolean);
436
437
  const isStream = route.streamResponse === true;
@@ -479,7 +480,7 @@ function createPipelineHandler(controllerMethod, steps, operation, resourceName)
479
480
  * @param options - Router configuration
480
481
  */
481
482
  function createCrudRouter(fastify, controller, options = {}) {
482
- const { tag = "Resource", schemas = {}, permissions = {}, middlewares = {}, additionalRoutes = [], disableDefaultRoutes = false, disabledRoutes = [], resourceName = "unknown", schemaOptions, rateLimit, pipe: pipeline, fields: fieldPermissions, updateMethod = DEFAULT_UPDATE_METHOD } = options;
483
+ const { tag = "Resource", schemas = {}, permissions = {}, middlewares = {}, routeGuards = [], additionalRoutes = [], disableDefaultRoutes = false, disabledRoutes = [], resourceName = "unknown", schemaOptions, rateLimit, pipe: pipeline, fields: fieldPermissions, updateMethod = DEFAULT_UPDATE_METHOD } = options;
483
484
  const rateLimitConfig = buildRateLimitConfig(rateLimit);
484
485
  const cacheMw = !(fastify.hasDecorator("queryCache") && controller && typeof controller._cacheConfig !== "undefined" && controller._cacheConfig !== void 0) && fastify.hasDecorator("responseCache") ? fastify.responseCache.middleware : null;
485
486
  const idempotencyMw = fastify.hasDecorator("idempotency") ? fastify.idempotency.middleware : null;
@@ -542,6 +543,7 @@ function createCrudRouter(fastify, controller, options = {}) {
542
543
  buildAuthMiddleware(fastify, permissions.list),
543
544
  buildPermissionMiddleware(permissions.list, resourceName, "list"),
544
545
  cacheMw,
546
+ ...routeGuards,
545
547
  ...mw.list
546
548
  ].filter(Boolean);
547
549
  fastify.route({
@@ -562,6 +564,7 @@ function createCrudRouter(fastify, controller, options = {}) {
562
564
  buildAuthMiddleware(fastify, permissions.get),
563
565
  buildPermissionMiddleware(permissions.get, resourceName, "get"),
564
566
  cacheMw,
567
+ ...routeGuards,
565
568
  ...mw.get
566
569
  ].filter(Boolean);
567
570
  fastify.route({
@@ -583,6 +586,7 @@ function createCrudRouter(fastify, controller, options = {}) {
583
586
  buildAuthMiddleware(fastify, permissions.create),
584
587
  buildPermissionMiddleware(permissions.create, resourceName, "create"),
585
588
  idempotencyMw,
589
+ ...routeGuards,
586
590
  ...mw.create
587
591
  ].filter(Boolean);
588
592
  fastify.route({
@@ -604,6 +608,7 @@ function createCrudRouter(fastify, controller, options = {}) {
604
608
  buildAuthMiddleware(fastify, permissions.update),
605
609
  buildPermissionMiddleware(permissions.update, resourceName, "update"),
606
610
  idempotencyMw,
611
+ ...routeGuards,
607
612
  ...mw.update
608
613
  ].filter(Boolean);
609
614
  for (const method of updateMethods) fastify.route({
@@ -624,6 +629,7 @@ function createCrudRouter(fastify, controller, options = {}) {
624
629
  arcDecorator,
625
630
  buildAuthMiddleware(fastify, permissions.delete),
626
631
  buildPermissionMiddleware(permissions.delete, resourceName, "delete"),
632
+ ...routeGuards,
627
633
  ...mw.delete
628
634
  ].filter(Boolean);
629
635
  fastify.route({
@@ -647,7 +653,8 @@ function createCrudRouter(fastify, controller, options = {}) {
647
653
  rateLimitConfig,
648
654
  cacheMw,
649
655
  idempotencyMw,
650
- pipeline
656
+ pipeline,
657
+ routeGuards
651
658
  });
652
659
  }
653
660
  /**
@@ -701,10 +708,10 @@ function validateResourceConfig(config, options = {}) {
701
708
  message: "Adapter must provide a repository",
702
709
  suggestion: "Ensure your adapter returns a valid CrudRepository"
703
710
  });
704
- } else if (!config.adapter && !config.additionalRoutes?.length) warnings.push({
711
+ } else if (!config.adapter && !config.routes?.length) warnings.push({
705
712
  field: "config",
706
- message: "Resource has no adapter and no additionalRoutes",
707
- suggestion: "Provide either adapter for CRUD or additionalRoutes for custom logic"
713
+ message: "Resource has no adapter and no routes",
714
+ suggestion: "Provide either adapter for CRUD or routes for custom logic"
708
715
  });
709
716
  if (config.controller && !options.skipControllerCheck && !config.disableDefaultRoutes) {
710
717
  const ctrl = config.controller;
@@ -715,7 +722,7 @@ function validateResourceConfig(config, options = {}) {
715
722
  suggestion: "Extend BaseController which implements IController interface"
716
723
  });
717
724
  }
718
- if (config.controller && config.additionalRoutes) validateAdditionalRouteHandlers(config.controller, config.additionalRoutes, errors);
725
+ if (config.controller && config.routes) validateRouteHandlers(config.controller, config.routes, errors);
719
726
  if (config.permissions) validatePermissionKeys(config, options, errors, warnings);
720
727
  if (config.presets && !options.allowUnknownPresets) validatePresets(config.presets, errors, warnings);
721
728
  if (config.prefix) {
@@ -730,18 +737,18 @@ function validateResourceConfig(config, options = {}) {
730
737
  suggestion: `Change to "${config.prefix.slice(0, -1)}"`
731
738
  });
732
739
  }
733
- if (config.additionalRoutes) validateAdditionalRoutes(config.additionalRoutes, errors);
740
+ if (config.routes) validateRoutes(config.routes, errors);
734
741
  return {
735
742
  valid: errors.length === 0,
736
743
  errors,
737
744
  warnings
738
745
  };
739
746
  }
740
- function validateAdditionalRouteHandlers(controller, routes, errors) {
747
+ function validateRouteHandlers(controller, routes, errors) {
741
748
  const ctrl = controller;
742
749
  for (const route of routes) if (typeof route.handler === "string") {
743
750
  if (typeof ctrl[route.handler] !== "function") errors.push({
744
- field: `additionalRoutes[${route.method} ${route.path}]`,
751
+ field: `routes[${route.method} ${route.path}]`,
745
752
  message: `Handler "${route.handler}" not found on controller`,
746
753
  suggestion: `Add method "${route.handler}" to controller or use a function handler`
747
754
  });
@@ -749,7 +756,7 @@ function validateAdditionalRouteHandlers(controller, routes, errors) {
749
756
  }
750
757
  function validatePermissionKeys(config, options, _errors, warnings) {
751
758
  const validKeys = new Set([...CRUD_OPERATIONS, ...options.additionalPermissionKeys ?? []]);
752
- for (const route of config.additionalRoutes ?? []) if (typeof route.handler === "string") validKeys.add(route.handler);
759
+ for (const route of config.routes ?? []) if (typeof route.handler === "string") validKeys.add(route.handler);
753
760
  for (const preset of config.presets ?? []) {
754
761
  const presetName = typeof preset === "string" ? preset : preset.name;
755
762
  if (presetName === "softDelete") {
@@ -773,7 +780,7 @@ function validatePermissionKeys(config, options, _errors, warnings) {
773
780
  function validatePresets(presets, errors, warnings) {
774
781
  const availablePresets = getAvailablePresets();
775
782
  for (const preset of presets) {
776
- if (typeof preset === "object" && ("middlewares" in preset || "additionalRoutes" in preset)) continue;
783
+ if (typeof preset === "object" && ("middlewares" in preset || "routes" in preset)) continue;
777
784
  const presetName = typeof preset === "string" ? preset : preset.name;
778
785
  if (!availablePresets.includes(presetName)) errors.push({
779
786
  field: "presets",
@@ -798,7 +805,7 @@ function validatePresetOptions(preset, warnings) {
798
805
  suggestion: validOptions.length > 0 ? `Valid options: ${validOptions.join(", ")}` : `Preset "${preset.name}" has no configurable options`
799
806
  });
800
807
  }
801
- function validateAdditionalRoutes(routes, errors) {
808
+ function validateRoutes(routes, errors) {
802
809
  const validMethods = [
803
810
  "GET",
804
811
  "POST",
@@ -811,26 +818,26 @@ function validateAdditionalRoutes(routes, errors) {
811
818
  const seenRoutes = /* @__PURE__ */ new Set();
812
819
  for (const [i, route] of routes.entries()) {
813
820
  if (!validMethods.includes(route.method)) errors.push({
814
- field: `additionalRoutes[${i}].method`,
821
+ field: `routes[${i}].method`,
815
822
  message: `Invalid HTTP method "${route.method}"`,
816
823
  suggestion: `Valid methods: ${validMethods.join(", ")}`
817
824
  });
818
825
  if (!route.path) errors.push({
819
- field: `additionalRoutes[${i}].path`,
826
+ field: `routes[${i}].path`,
820
827
  message: "Route path is required"
821
828
  });
822
829
  else if (!route.path.startsWith("/")) errors.push({
823
- field: `additionalRoutes[${i}].path`,
830
+ field: `routes[${i}].path`,
824
831
  message: `Route path must start with "/" (got "${route.path}")`,
825
832
  suggestion: `Change to "/${route.path}"`
826
833
  });
827
834
  if (!route.handler) errors.push({
828
- field: `additionalRoutes[${i}].handler`,
835
+ field: `routes[${i}].handler`,
829
836
  message: "Route handler is required"
830
837
  });
831
838
  const routeKey = `${route.method} ${route.path}`;
832
839
  if (seenRoutes.has(routeKey)) errors.push({
833
- field: `additionalRoutes[${i}]`,
840
+ field: `routes[${i}]`,
834
841
  message: `Duplicate route "${routeKey}"`
835
842
  });
836
843
  seenRoutes.add(routeKey);
@@ -884,11 +891,6 @@ function defineResource(config) {
884
891
  if (config.permissions) {
885
892
  for (const [key, value] of Object.entries(config.permissions)) if (value !== void 0 && typeof value !== "function") throw new Error(`[Arc] Resource '${config.name}': permissions.${key} must be a PermissionCheck function.\nUse allowPublic(), requireAuth(), or requireRoles(['role']) from @classytic/arc/permissions.`);
886
893
  }
887
- for (const route of config.additionalRoutes ?? []) {
888
- if (typeof route.permissions !== "function") throw new Error(`[Arc] Resource '${config.name}' route ${route.method} ${route.path}: permissions is required and must be a PermissionCheck function.\nUse allowPublic() or requireAuth() from @classytic/arc/permissions.`);
889
- if (typeof route.wrapHandler !== "boolean") throw new Error(`[Arc] Resource '${config.name}' route ${route.method} ${route.path}: wrapHandler is required.\nSet true for ControllerHandler (context object) or false for FastifyHandler (req, reply).`);
890
- }
891
- if (config.routes && config.additionalRoutes) throw new Error(`[Arc] Resource '${config.name}': Cannot use both 'routes' and 'additionalRoutes'.\nUse 'routes' (v2.8) — it replaces 'additionalRoutes'.`);
892
894
  for (const route of config.routes ?? []) if (typeof route.permissions !== "function") throw new Error(`[Arc] Resource '${config.name}' route ${route.method} ${route.path}: permissions is required and must be a PermissionCheck function.`);
893
895
  if (config.actions) {
894
896
  const CRUD_OPS = new Set([
@@ -1098,6 +1100,7 @@ var ResourceDefinition = class {
1098
1100
  */
1099
1101
  routes;
1100
1102
  middlewares;
1103
+ routeGuards;
1101
1104
  disableDefaultRoutes;
1102
1105
  disabledRoutes;
1103
1106
  actions;
@@ -1128,8 +1131,9 @@ var ResourceDefinition = class {
1128
1131
  this.customSchemas = config.customSchemas ?? {};
1129
1132
  this.permissions = config.permissions ?? {};
1130
1133
  this.routes = config.routes;
1131
- this.additionalRoutes = config.routes ? convertRoutesToAdditionalRoutes(config.routes) : config.additionalRoutes ?? [];
1134
+ this.additionalRoutes = config.routes ? convertRoutesToAdditionalRoutes(config.routes) : [];
1132
1135
  this.middlewares = config.middlewares ?? {};
1136
+ this.routeGuards = config.routeGuards;
1133
1137
  this.disableDefaultRoutes = config.disableDefaultRoutes ?? false;
1134
1138
  this.disabledRoutes = config.disabledRoutes ?? [];
1135
1139
  this.actions = config.actions;
@@ -1283,6 +1287,7 @@ var ResourceDefinition = class {
1283
1287
  schemas: schemas ?? void 0,
1284
1288
  permissions: self.permissions,
1285
1289
  middlewares: self.middlewares,
1290
+ routeGuards: self.routeGuards,
1286
1291
  additionalRoutes: resolvedRoutes,
1287
1292
  disableDefaultRoutes: self.disableDefaultRoutes,
1288
1293
  disabledRoutes: self.disabledRoutes,
@@ -1331,7 +1336,17 @@ var ResourceDefinition = class {
1331
1336
  prefix: this.prefix,
1332
1337
  presets: this._appliedPresets,
1333
1338
  permissions: this.permissions,
1334
- additionalRoutes: this.additionalRoutes,
1339
+ customRoutes: (this.routes ?? []).map((r) => ({
1340
+ method: r.method,
1341
+ path: r.path,
1342
+ handler: typeof r.handler === "string" ? r.handler : r.handler.name || "anonymous",
1343
+ operation: r.operation,
1344
+ summary: r.summary,
1345
+ description: r.description,
1346
+ permissions: r.permissions,
1347
+ raw: r.raw,
1348
+ schema: r.schema
1349
+ })),
1335
1350
  routes: [],
1336
1351
  events: Object.keys(this.events)
1337
1352
  };
@@ -1,5 +1,5 @@
1
- import { it as RegistryEntry } from "../interface-CS6d7HiB.mjs";
2
- import { t as ExternalOpenApiPaths } from "../externalPaths-BQ8QijNH.mjs";
1
+ import { it as RegistryEntry } from "../interface-BVuMfeVv.mjs";
2
+ import { t as ExternalOpenApiPaths } from "../externalPaths-Bapitwvd.mjs";
3
3
  import { FastifyPluginAsync } from "fastify";
4
4
 
5
5
  //#region src/docs/openapi.d.ts
@@ -1,5 +1,5 @@
1
1
  import { t as getUserRoles } from "../types-ZUu_h0jp.mjs";
2
- import { n as openApiPlugin, r as openapi_default, t as buildOpenApiSpec } from "../openapi-q6rNKfZy.mjs";
2
+ import { n as openApiPlugin, r as openapi_default, t as buildOpenApiSpec } from "../openapi-CYCuekCn.mjs";
3
3
  import fp from "fastify-plugin";
4
4
  //#region src/docs/scalar.ts
5
5
  const scalarPlugin = async (fastify, opts = {}) => {
@@ -1,5 +1,5 @@
1
- import { qt as ResourceDefinition, r as DataAdapter } from "../interface-CS6d7HiB.mjs";
2
- import { t as PermissionCheck } from "../types-BoaZHr-2.mjs";
1
+ import { qt as ResourceDefinition, r as DataAdapter } from "../interface-BVuMfeVv.mjs";
2
+ import { t as PermissionCheck } from "../types-CVC4HOKi.mjs";
3
3
 
4
4
  //#region src/dynamic/ArcDynamicLoader.d.ts
5
5
  interface ArcArchitectureSchema {