@classytic/arc 2.11.3 → 2.13.1

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 (185) hide show
  1. package/README.md +27 -18
  2. package/dist/{BaseController-swXruJ2_.mjs → BaseController-DX_T-bDB.mjs} +388 -423
  3. package/dist/EventTransport-CT_52aWU.d.mts +34 -0
  4. package/dist/EventTransport-DLWoUMHy.mjs +103 -0
  5. package/dist/{QueryCache-DOBNHBE0.d.mts → QueryCache-D41bfdBB.d.mts} +1 -1
  6. package/dist/{ResourceRegistry-DkAeAuTX.mjs → ResourceRegistry-CTERg_2x.mjs} +139 -66
  7. package/dist/audit/index.d.mts +2 -2
  8. package/dist/audit/index.mjs +1 -1
  9. package/dist/auth/audit.d.mts +199 -0
  10. package/dist/auth/audit.mjs +288 -0
  11. package/dist/auth/index.d.mts +5 -5
  12. package/dist/auth/index.mjs +117 -191
  13. package/dist/auth/redis-session.d.mts +1 -1
  14. package/dist/{betterAuthOpenApi-DwxtK3uG.mjs → betterAuthOpenApi--M_i87dQ.mjs} +1 -1
  15. package/dist/buildHandler-olo-gt94.mjs +610 -0
  16. package/dist/cache/index.d.mts +3 -3
  17. package/dist/cache/index.mjs +3 -3
  18. package/dist/cli/commands/describe.d.mts +89 -13
  19. package/dist/cli/commands/describe.mjs +56 -2
  20. package/dist/cli/commands/docs.mjs +2 -2
  21. package/dist/cli/commands/generate.mjs +147 -48
  22. package/dist/cli/commands/init.d.mts +13 -0
  23. package/dist/cli/commands/init.mjs +237 -112
  24. package/dist/cli/commands/introspect.mjs +8 -1
  25. package/dist/context/index.mjs +1 -1
  26. package/dist/core/index.d.mts +3 -3
  27. package/dist/core/index.mjs +5 -5
  28. package/dist/core-D72ia0EH.mjs +1399 -0
  29. package/dist/{createActionRouter-u3ql2EDo.mjs → createActionRouter-CEvzKcy8.mjs} +7 -20
  30. package/dist/createAggregationRouter-CyecOxnO.mjs +114 -0
  31. package/dist/{createApp-BFxtdKy6.mjs → createApp-XX2-N0Yd.mjs} +31 -27
  32. package/dist/defineEvent-D5h7EvAx.mjs +188 -0
  33. package/dist/docs/index.d.mts +2 -2
  34. package/dist/docs/index.mjs +2 -2
  35. package/dist/{elevation-DOFoxoDs.mjs → elevation-DgoeTyfX.mjs} +1 -1
  36. package/dist/errorHandler-Bk-AGhkU.mjs +174 -0
  37. package/dist/errorHandler-DFr45ZG4.d.mts +45 -0
  38. package/dist/errors-j4aJm1Wg.mjs +184 -0
  39. package/dist/{eventPlugin-KrFIQ097.mjs → eventPlugin-CaKTYkYM.mjs} +35 -137
  40. package/dist/{eventPlugin-CUNjYYRY.d.mts → eventPlugin-qXpqTebY.d.mts} +57 -7
  41. package/dist/events/index.d.mts +164 -5
  42. package/dist/events/index.mjs +133 -209
  43. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  44. package/dist/events/transports/redis-stream-entry.mjs +204 -31
  45. package/dist/events/transports/redis.d.mts +1 -1
  46. package/dist/factory/index.d.mts +2 -2
  47. package/dist/factory/index.mjs +2 -2
  48. package/dist/{fields-C8Y0XLAu.d.mts → fields-COhcH3fk.d.mts} +23 -2
  49. package/dist/hooks/index.d.mts +1 -1
  50. package/dist/hooks/index.mjs +1 -1
  51. package/dist/idempotency/index.d.mts +3 -3
  52. package/dist/idempotency/index.mjs +1 -20
  53. package/dist/idempotency/redis.d.mts +1 -1
  54. package/dist/idempotency/redis.mjs +1 -1
  55. package/dist/{index-BYCqHCVu.d.mts → index-BTqLEvhu.d.mts} +164 -4
  56. package/dist/{index-6u4_Gg6G.d.mts → index-BtW7qYwa.d.mts} +661 -281
  57. package/dist/{index-BdXnTPRj.d.mts → index-Ds61mrJE.d.mts} +50 -4
  58. package/dist/{index-DdQ3O9Pg.d.mts → index-Dz5IKsrE.d.mts} +360 -219
  59. package/dist/index.d.mts +6 -7
  60. package/dist/index.mjs +9 -10
  61. package/dist/integrations/event-gateway.d.mts +2 -2
  62. package/dist/integrations/event-gateway.mjs +1 -1
  63. package/dist/integrations/index.d.mts +2 -2
  64. package/dist/integrations/mcp/index.d.mts +2 -2
  65. package/dist/integrations/mcp/index.mjs +1 -1
  66. package/dist/integrations/mcp/testing.d.mts +1 -1
  67. package/dist/integrations/mcp/testing.mjs +1 -1
  68. package/dist/integrations/streamline.d.mts +60 -11
  69. package/dist/integrations/streamline.mjs +75 -85
  70. package/dist/integrations/websocket-redis.d.mts +1 -1
  71. package/dist/integrations/websocket.d.mts +1 -1
  72. package/dist/integrations/websocket.mjs +2 -8
  73. package/dist/middleware/index.d.mts +1 -1
  74. package/dist/middleware/index.mjs +2 -2
  75. package/dist/migrations/index.d.mts +23 -3
  76. package/dist/migrations/index.mjs +0 -7
  77. package/dist/{multipartBody-CvTR1Un6.mjs → multipartBody-BOvVSVCD.mjs} +11 -8
  78. package/dist/{openapi-BGUn7Ki1.mjs → openapi-CiOMVW1p.mjs} +143 -13
  79. package/dist/org/index.d.mts +2 -2
  80. package/dist/org/index.mjs +1 -1
  81. package/dist/permissions/index.d.mts +3 -3
  82. package/dist/permissions/index.mjs +3 -3
  83. package/dist/{permissions-gd_aUWrR.mjs → permissions-ohQyv50e.mjs} +404 -176
  84. package/dist/{pipe-DVoIheVC.mjs → pipe-Zr0KXjQe.mjs} +1 -1
  85. package/dist/pipeline/index.d.mts +1 -1
  86. package/dist/pipeline/index.mjs +1 -1
  87. package/dist/plugins/index.d.mts +18 -33
  88. package/dist/plugins/index.mjs +33 -13
  89. package/dist/plugins/response-cache.mjs +1 -1
  90. package/dist/plugins/tracing-entry.d.mts +1 -1
  91. package/dist/plugins/tracing-entry.mjs +1 -1
  92. package/dist/presets/filesUpload.d.mts +5 -5
  93. package/dist/presets/filesUpload.mjs +6 -9
  94. package/dist/presets/index.d.mts +1 -1
  95. package/dist/presets/index.mjs +1 -1
  96. package/dist/presets/multiTenant.d.mts +1 -1
  97. package/dist/presets/multiTenant.mjs +2 -2
  98. package/dist/presets/search.d.mts +2 -2
  99. package/dist/presets/search.mjs +6 -8
  100. package/dist/{presets-Z7P5w4gF.mjs → presets-BbkjdPeH.mjs} +6 -28
  101. package/dist/{queryCachePlugin-BUXBSm4F.d.mts → queryCachePlugin-CqMdLI2-.d.mts} +2 -2
  102. package/dist/{queryCachePlugin-Bq6bO6vc.mjs → queryCachePlugin-m1XsgAIJ.mjs} +3 -3
  103. package/dist/{redis-Cm1gnRDf.d.mts → redis-DiMkdHEl.d.mts} +1 -1
  104. package/dist/redis-stream-D6HzR1Z_.d.mts +232 -0
  105. package/dist/registry/index.d.mts +1 -1
  106. package/dist/registry/index.mjs +2 -2
  107. package/dist/{replyHelpers-ByllIXXV.mjs → replyHelpers-CK-FNO8E.mjs} +3 -21
  108. package/dist/{resourceToTools-ByZpgjeH.mjs → resourceToTools-C5coh64w.mjs} +224 -71
  109. package/dist/{routerShared-BqLRb5l7.mjs → routerShared-D6_fEGHh.mjs} +40 -36
  110. package/dist/{schemaIR-BlG9bY7v.mjs → schemaIR-7Vl611Qs.mjs} +1 -1
  111. package/dist/schemas/index.d.mts +100 -30
  112. package/dist/schemas/index.mjs +86 -29
  113. package/dist/scim/index.d.mts +264 -0
  114. package/dist/scim/index.mjs +963 -0
  115. package/dist/scope/index.d.mts +3 -3
  116. package/dist/scope/index.mjs +4 -4
  117. package/dist/{sse-V7aXc3bW.mjs → sse-Bz-5ZeTt.mjs} +1 -1
  118. package/dist/{store-helpers-BhrzxvyQ.mjs → store-helpers-BkIN9-vu.mjs} +1 -1
  119. package/dist/testing/index.d.mts +2 -8
  120. package/dist/testing/index.mjs +16 -24
  121. package/dist/testing/storageContract.d.mts +1 -1
  122. package/dist/types/index.d.mts +4 -4
  123. package/dist/types/storage.d.mts +1 -1
  124. package/dist/{types-BH7dEGvU.d.mts → types-BvqwCCSx.d.mts} +77 -29
  125. package/dist/{types-tgR4Pt8F.d.mts → types-CTYvcwHe.d.mts} +195 -1
  126. package/dist/{types-AOD8fxIw.mjs → types-C_s5moIu.mjs} +117 -1
  127. package/dist/{types-9beEMe25.d.mts → types-DQHFc8PM.d.mts} +1 -1
  128. package/dist/utils/index.d.mts +2 -2
  129. package/dist/utils/index.mjs +5 -5
  130. package/dist/{utils-CcYTj09l.mjs → utils-_h9B3c57.mjs} +1269 -1334
  131. package/dist/{versioning-M9lNLhO8.d.mts → versioning-DTTvc80y.d.mts} +1 -1
  132. package/package.json +24 -34
  133. package/skills/arc/SKILL.md +521 -785
  134. package/skills/arc/references/agent-auth.md +238 -0
  135. package/skills/arc/references/api-reference.md +187 -0
  136. package/skills/arc/references/auth.md +354 -7
  137. package/skills/arc/references/enterprise-auth.md +94 -0
  138. package/skills/arc/references/events.md +8 -6
  139. package/skills/arc/references/mcp.md +2 -2
  140. package/skills/arc/references/multi-tenancy.md +11 -2
  141. package/skills/arc/references/production.md +10 -9
  142. package/skills/arc/references/scim.md +247 -0
  143. package/skills/arc/references/testing.md +1 -1
  144. package/skills/arc-code-review/SKILL.md +141 -0
  145. package/skills/arc-code-review/references/anti-patterns.md +911 -0
  146. package/skills/arc-code-review/references/arc-cheatsheet.md +380 -0
  147. package/skills/arc-code-review/references/migration-recipes.md +700 -0
  148. package/skills/arc-code-review/references/mongokit-migration.md +386 -0
  149. package/skills/arc-code-review/references/scaffolding.md +230 -0
  150. package/skills/arc-code-review/references/severity.md +127 -0
  151. package/dist/EventTransport-CfVEGaEl.d.mts +0 -293
  152. package/dist/adapters/index.d.mts +0 -3
  153. package/dist/adapters/index.mjs +0 -2
  154. package/dist/adapters-D0tT2Tyo.mjs +0 -949
  155. package/dist/auth/mongoose.d.mts +0 -191
  156. package/dist/auth/mongoose.mjs +0 -73
  157. package/dist/core-DnUsRpuX.mjs +0 -1049
  158. package/dist/errorHandler-BQm8ZxTK.mjs +0 -173
  159. package/dist/errorHandler-Co3lnVmJ.d.mts +0 -114
  160. package/dist/errors-D5c-5BJL.mjs +0 -232
  161. package/dist/index-BbMrcvGp.d.mts +0 -362
  162. package/dist/redis-stream-CM8TXTix.d.mts +0 -110
  163. /package/dist/{HookSystem-CGsMd6oK.mjs → HookSystem-Iiebom92.mjs} +0 -0
  164. /package/dist/{actionPermissions-sUUKDhtP.mjs → actionPermissions-CyUkQu6O.mjs} +0 -0
  165. /package/dist/{caching-CheW3m-S.mjs → caching-SM8gghN6.mjs} +0 -0
  166. /package/dist/{constants-BhY1OHoH.mjs → constants-Cxde4rpC.mjs} +0 -0
  167. /package/dist/{elevation-s5ykdNHr.d.mts → elevation-BXOWoGCF.d.mts} +0 -0
  168. /package/dist/{externalPaths-Bapitwvd.d.mts → externalPaths-BD5nw6St.d.mts} +0 -0
  169. /package/dist/{interface-CkkWm5uR.d.mts → interface-DfLGcus7.d.mts} +0 -0
  170. /package/dist/{interface-Da0r7Lna.d.mts → interface-beEtJyWM.d.mts} +0 -0
  171. /package/dist/{keys-CARyUjiR.mjs → keys-CGcCbNyu.mjs} +0 -0
  172. /package/dist/{loadResources-CPpkyKfM.mjs → loadResources-DBMQg_Aj.mjs} +0 -0
  173. /package/dist/{memory-DikHSvWa.mjs → memory-UBydS5ku.mjs} +0 -0
  174. /package/dist/{metrics-Csh4nsvv.mjs → metrics-Qnvwc-LQ.mjs} +0 -0
  175. /package/dist/{pluralize-BneOJkpi.mjs → pluralize-DQgqgifU.mjs} +0 -0
  176. /package/dist/{registry-D63ee7fl.mjs → registry-I-ogLgL9.mjs} +0 -0
  177. /package/dist/{requestContext-C5XeK3VA.mjs → requestContext-SSaaTgW8.mjs} +0 -0
  178. /package/dist/{schemaConverter-B0oKLuqI.mjs → schemaConverter-De34B1ZG.mjs} +0 -0
  179. /package/dist/{sessionManager-D-oNWHz3.d.mts → sessionManager-C4Le_UB3.d.mts} +0 -0
  180. /package/dist/{storage-BwGQXUpd.d.mts → storage-Dfzt4VTl.d.mts} +0 -0
  181. /package/dist/{tracing-DokiEsuz.d.mts → tracing-QJVprktp.d.mts} +0 -0
  182. /package/dist/{typeGuards-CcFZXgU7.mjs → typeGuards-BzkXkvVv.mjs} +0 -0
  183. /package/dist/{types-DV9WDfeg.mjs → types-D57iXYb8.mjs} +0 -0
  184. /package/dist/{versioning-CGPjkqAg.mjs → versioning-BUrT5aP4.mjs} +0 -0
  185. /package/dist/{websocket-CyJ1VIFI.d.mts → websocket-ChC2rqe1.d.mts} +0 -0
@@ -28,6 +28,10 @@ async function init(options = {}) {
28
28
  console.log(`\nCreating project: ${config.name}`);
29
29
  console.log(` Adapter: ${config.adapter === "mongokit" ? "MongoKit (MongoDB)" : "Custom / Drizzle-ready"}`);
30
30
  console.log(` Auth: ${config.auth === "better-auth" ? "Better Auth (recommended)" : "Arc JWT"}`);
31
+ if (config.auth === "better-auth") {
32
+ console.log(` Session: ${config.session === "cookie" ? "Cookie" : config.session === "bearer" ? "Bearer token" : "Cookie + Bearer"}`);
33
+ if (config.apiKey) console.log(` API keys: enabled (@better-auth/api-key)`);
34
+ }
31
35
  console.log(` Tenant: ${config.tenant === "multi" ? "Multi-tenant" : "Single-tenant"}`);
32
36
  console.log(` Language: ${config.typescript ? "TypeScript" : "JavaScript"}`);
33
37
  console.log(` Target: ${config.edge ? "Edge/Serverless" : "Node.js Server"}\n`);
@@ -87,42 +91,108 @@ function existsSync$1(filePath) {
87
91
  }
88
92
  }
89
93
  /**
90
- * Install dependencies using the detected package manager
94
+ * Single source of truth for scaffolded project dependencies.
95
+ *
96
+ * Versions are pinned to the floor each subsystem requires — peer-dep
97
+ * minimums on Arc, kit minimums (mongokit ≥ 3.11, repo-core ≥ 0.2,
98
+ * mongoose ≥ 9.4.1), and major-version stable for the rest. The carets
99
+ * allow minor + patch upgrades without breaking arc's contract, while
100
+ * preventing the silent breakage of `@latest` on a kit floor bump.
101
+ *
102
+ * Used by both `packageJsonTemplate` (declares the deps in the generated
103
+ * `package.json` so `npm install` works without a pre-pass) and
104
+ * `installDependencies` (runs the package manager's `install` against
105
+ * the declared ranges). One source — no drift.
91
106
  */
92
- async function installDependencies(projectPath, config, pm) {
93
- const deps = [
94
- "@classytic/arc@latest",
95
- "fastify@latest",
96
- "@fastify/cors@latest",
97
- "@fastify/helmet@latest",
98
- "@fastify/rate-limit@latest",
99
- "@fastify/sensible@latest",
100
- "@fastify/under-pressure@latest",
101
- "dotenv@latest"
102
- ];
103
- if (config.auth === "better-auth") deps.push("better-auth@^1.6.0", "mongodb@latest");
104
- else deps.push("@fastify/jwt@latest", "bcryptjs@latest");
105
- if (config.adapter === "mongokit") deps.push("@classytic/mongokit@^3.11.0", "@classytic/repo-core@^0.2.0", "mongoose@^9.4.1");
106
- const devDeps = ["vitest@latest", "pino-pretty@latest"];
107
- if (config.typescript) devDeps.push("typescript@latest", "@types/node@latest", "tsx@latest");
108
- const installCmd = getInstallCommand(pm, deps, false);
109
- const installDevCmd = getInstallCommand(pm, devDeps, true);
107
+ const SCAFFOLD_DEP_VERSIONS = {
108
+ core: {
109
+ "@classytic/arc": "^2.13.0",
110
+ "@classytic/primitives": "^0.3.0",
111
+ "@classytic/repo-core": "^0.4.0",
112
+ "@fastify/cors": "^11.2.0",
113
+ "@fastify/helmet": "^13.0.2",
114
+ "@fastify/rate-limit": "^10.3.0",
115
+ "@fastify/sensible": "^6.0.4",
116
+ "@fastify/under-pressure": "^9.0.3",
117
+ dotenv: "^17.4.2",
118
+ fastify: "^5.8.5"
119
+ },
120
+ authJwt: {
121
+ "@fastify/jwt": "^10.0.0",
122
+ bcryptjs: "^3.0.0"
123
+ },
124
+ authBetterAuth: {
125
+ "better-auth": "^1.6.9",
126
+ mongodb: "^7.1.0"
127
+ },
128
+ authBetterAuthApiKey: { "@better-auth/api-key": "^1.6.9" },
129
+ adapterMongokit: {
130
+ "@classytic/mongokit": "^3.13.0",
131
+ mongoose: "^9.6.1"
132
+ },
133
+ devCommon: {
134
+ "mongodb-memory-server": "^11.1.0",
135
+ "pino-pretty": "^13.0.0",
136
+ vitest: "^4.1.5"
137
+ },
138
+ devTypescript: {
139
+ "@types/node": "^22.10.0",
140
+ tsx: "^4.21.0",
141
+ typescript: "^5.7.2"
142
+ },
143
+ typesJwt: { "@types/bcryptjs": "^3.0.0" }
144
+ };
145
+ /**
146
+ * Resolve the dependency manifest for a scaffold configuration.
147
+ *
148
+ * Returns sorted records (alphabetical by package name) so the generated
149
+ * `package.json` is deterministic — diffs across re-runs stay clean.
150
+ */
151
+ function resolveScaffoldDependencies(config) {
152
+ const dependencies = { ...SCAFFOLD_DEP_VERSIONS.core };
153
+ const devDependencies = { ...SCAFFOLD_DEP_VERSIONS.devCommon };
154
+ if (config.auth === "better-auth") {
155
+ Object.assign(dependencies, SCAFFOLD_DEP_VERSIONS.authBetterAuth);
156
+ if (config.apiKey) Object.assign(dependencies, SCAFFOLD_DEP_VERSIONS.authBetterAuthApiKey);
157
+ } else {
158
+ Object.assign(dependencies, SCAFFOLD_DEP_VERSIONS.authJwt);
159
+ if (config.typescript) Object.assign(devDependencies, SCAFFOLD_DEP_VERSIONS.typesJwt);
160
+ }
161
+ if (config.adapter === "mongokit") Object.assign(dependencies, SCAFFOLD_DEP_VERSIONS.adapterMongokit);
162
+ if (config.typescript) Object.assign(devDependencies, SCAFFOLD_DEP_VERSIONS.devTypescript);
163
+ return {
164
+ dependencies: sortByKey(dependencies),
165
+ devDependencies: sortByKey(devDependencies)
166
+ };
167
+ }
168
+ /**
169
+ * Sort a record alphabetically by key — package.json convention.
170
+ */
171
+ function sortByKey(record) {
172
+ return Object.fromEntries(Object.entries(record).sort(([a], [b]) => a.localeCompare(b)));
173
+ }
174
+ /**
175
+ * Install dependencies using the detected package manager.
176
+ *
177
+ * Dependencies are already declared in the generated `package.json` (see
178
+ * `packageJsonTemplate`), so a single plain `install` resolves the full
179
+ * tree. No two-pass `npm add` flow — the manifest is the source of truth.
180
+ */
181
+ async function installDependencies(projectPath, _config, pm) {
110
182
  console.log(` Installing dependencies...`);
111
- await runCommand(installCmd, projectPath);
112
- console.log(` Installing dev dependencies...`);
113
- await runCommand(installDevCmd, projectPath);
183
+ await runCommand(getInstallCommand(pm), projectPath);
114
184
  console.log(`\nDependencies installed successfully.`);
115
185
  }
116
186
  /**
117
- * Get the install command for a package manager
187
+ * Get the plain `install` command for a package manager. Reads the declared
188
+ * dependencies from the project's `package.json`.
118
189
  */
119
- function getInstallCommand(pm, packages, isDev) {
120
- const pkgList = packages.join(" ");
190
+ function getInstallCommand(pm) {
121
191
  switch (pm) {
122
- case "pnpm": return `pnpm add ${isDev ? "-D" : ""} ${pkgList}`;
123
- case "yarn": return `yarn add ${isDev ? "-D" : ""} ${pkgList}`;
124
- case "bun": return `bun add ${isDev ? "-d" : ""} ${pkgList}`;
125
- default: return `npm install ${isDev ? "--save-dev" : ""} ${pkgList}`;
192
+ case "pnpm": return "pnpm install";
193
+ case "yarn": return "yarn install";
194
+ case "bun": return "bun install";
195
+ default: return "npm install";
126
196
  }
127
197
  }
128
198
  /**
@@ -159,6 +229,15 @@ async function gatherConfig(options) {
159
229
  if (!options.adapter && !nonInteractive) adapter = await question("Database adapter [1=MongoKit (recommended), 2=Custom / Drizzle-ready]: ") === "2" ? "custom" : "mongokit";
160
230
  let auth = options.auth || "better-auth";
161
231
  if (!options.auth && !nonInteractive) auth = await question("Auth strategy [1=Better Auth (recommended), 2=Arc JWT]: ") === "2" ? "jwt" : "better-auth";
232
+ let session = options.session ?? "cookie";
233
+ let apiKey = options.apiKey ?? false;
234
+ if (auth === "better-auth" && !nonInteractive) {
235
+ if (options.session === void 0) {
236
+ const sessionChoice = await question("Session strategy [1=Cookie (web app, default), 2=Bearer token (mobile/SPA), 3=Both]: ");
237
+ session = sessionChoice === "2" ? "bearer" : sessionChoice === "3" ? "both" : "cookie";
238
+ }
239
+ if (options.apiKey === void 0) apiKey = (await question("Enable API key plugin (machine-to-machine auth via @better-auth/api-key)? [y/N]: ")).toLowerCase() === "y";
240
+ }
162
241
  let tenant = options.tenant || "single";
163
242
  if (!options.tenant && !nonInteractive) tenant = await question("Tenant mode [1=Single-tenant, 2=Multi-tenant]: ") === "2" ? "multi" : "single";
164
243
  let typescript = options.typescript ?? true;
@@ -176,7 +255,7 @@ async function gatherConfig(options) {
176
255
  console.log("");
177
256
  if ((await question("Continue with MongoKit? [y/N]: ")).toLowerCase() !== "y") {
178
257
  adapter = "custom";
179
- console.log(" Switched to custom adapter. Wire sqlitekit/Drizzle here; Prisma remains experimental.");
258
+ console.log(" Switched to custom adapter. Wire sqlitekit/Drizzle, prismakit, or any RepositoryLike-conforming repo.");
180
259
  }
181
260
  }
182
261
  return {
@@ -184,6 +263,8 @@ async function gatherConfig(options) {
184
263
  adapter,
185
264
  auth,
186
265
  tenant,
266
+ apiKey,
267
+ session,
187
268
  typescript,
188
269
  edge
189
270
  };
@@ -267,6 +348,7 @@ async function createProjectStructure(projectPath, config) {
267
348
  }
268
349
  }
269
350
  function packageJsonTemplate(config) {
351
+ const { dependencies, devDependencies } = resolveScaffoldDependencies(config);
270
352
  const scripts = config.typescript ? config.edge ? {
271
353
  dev: "tsx watch src/index.ts",
272
354
  build: "tsc",
@@ -309,6 +391,8 @@ function packageJsonTemplate(config) {
309
391
  "#utils/*": "./src/utils/*"
310
392
  },
311
393
  scripts,
394
+ dependencies,
395
+ devDependencies,
312
396
  engines: { node: ">=22" }
313
397
  }, null, 2);
314
398
  }
@@ -557,7 +641,7 @@ Short forms also supported: \`.env.prod\`, \`.env.dev\`, \`.env.test\`
557
641
 
558
642
  API documentation is available via Scalar UI:
559
643
 
560
- - **Interactive UI**: [http://localhost:8040/docs](http://localhost:8040/docs)
644
+ - **Interactive UI**: [http://localhost:8040/docs](http://localhost:8040/data)
561
645
  - **OpenAPI Spec**: [http://localhost:8040/_docs/openapi.json](http://localhost:8040/_docs/openapi.json)
562
646
 
563
647
  ## API Endpoints
@@ -678,7 +762,10 @@ function appTemplate(config) {
678
762
  const betterAuthImport = config.auth === "better-auth" ? `import { createBetterAuthAdapter } from '@classytic/arc/auth';
679
763
  import { getAuth } from './auth.js';
680
764
  ` : "";
681
- const authConfig = config.auth === "better-auth" ? config.tenant === "multi" ? `auth: { type: 'betterAuth', betterAuth: createBetterAuthAdapter({ auth: getAuth(), orgContext: true }) },` : `auth: { type: 'betterAuth', betterAuth: createBetterAuthAdapter({ auth: getAuth() }) },` : `auth: {
765
+ const authConfig = config.auth === "better-auth" ? `auth: {
766
+ type: 'betterAuth',
767
+ betterAuth: createBetterAuthAdapter({ auth: getAuth(), orgContext: true }),
768
+ },` : `auth: {
682
769
  type: 'jwt',
683
770
  jwt: { secret: config.jwt.secret },
684
771
  },`;
@@ -708,10 +795,13 @@ import { resources, registerResources } from '#resources/index.js';
708
795
  * @returns Configured Fastify instance ready to use
709
796
  */
710
797
  export async function createAppInstance()${ts ? ": Promise<FastifyInstance>" : ""} {
711
- // Create Arc app with resources and base configuration
798
+ // Create Arc app with resources and base configuration. \`resourcePrefix\`
799
+ // mounts every resource under \`/api\`, matching the \`apiPrefix\` the
800
+ // OpenAPI plugin advertises — keeps docs and routes in agreement.
712
801
  const app = await createApp({
713
802
  preset: config.env === 'production' ? (${config.edge ? "'edge'" : "'production'"}) : 'development',
714
803
  resources,
804
+ resourcePrefix: '/api',
715
805
  ${authConfig}
716
806
  cors: {
717
807
  origin: config.cors.origins,
@@ -952,7 +1042,8 @@ function sharedIndexTemplate(_config) {
952
1042
  export { createAdapter } from './adapter.js';
953
1043
 
954
1044
  // Core Arc exports
955
- export { createMongooseAdapter, defineResource } from '@classytic/arc';
1045
+ export { defineResource } from '@classytic/arc';
1046
+ export { createMongooseAdapter } from '@classytic/mongokit/adapter';
956
1047
 
957
1048
  // Permission helpers (core + application-level)
958
1049
  export * from './permissions.js';
@@ -970,14 +1061,17 @@ function createAdapterTemplate(config) {
970
1061
  * The repository handles query parsing via MongoKit's built-in QueryParser.
971
1062
  */
972
1063
 
973
- import { createMongooseAdapter } from '@classytic/arc';
1064
+ import { createMongooseAdapter } from '@classytic/mongokit/adapter';
1065
+ import { buildCrudSchemasFromModel } from '@classytic/mongokit';
974
1066
  ${ts ? "import type { Model } from 'mongoose';\nimport type { Repository } from '@classytic/mongokit';" : ""}
975
1067
 
976
1068
  /**
977
- * Create a MongoKit-powered adapter for a resource
1069
+ * Create a MongoKit-powered adapter for a resource.
978
1070
  *
979
1071
  * Note: Query parsing is handled by MongoKit's Repository class.
980
- * Just pass the model and repository - Arc handles the rest.
1072
+ * \`buildCrudSchemasFromModel\` is the canonical OpenAPI schema generator
1073
+ * for arc + Mongoose (arc 2.12+ no longer ships a built-in fallback —
1074
+ * passing it explicitly is required for OpenAPI auto-generation).
981
1075
  */
982
1076
  export function createAdapter${ts ? "<TDoc = any>" : ""}(
983
1077
  model${ts ? ": Model<TDoc>" : ""},
@@ -986,6 +1080,7 @@ export function createAdapter${ts ? "<TDoc = any>" : ""}(
986
1080
  return createMongooseAdapter({
987
1081
  model,
988
1082
  repository,
1083
+ schemaGenerator: buildCrudSchemasFromModel,
989
1084
  });
990
1085
  }
991
1086
  `;
@@ -995,21 +1090,21 @@ function customAdapterTemplate(config) {
995
1090
  return `/**
996
1091
  * Custom Adapter Factory
997
1092
  *
998
- * Use this for sqlitekit/Drizzle, Prisma experiments, or any repository
999
- * that satisfies Arc's RepositoryLike contract.
1093
+ * Use this for the bring-your-own-repository path any object that
1094
+ * satisfies the \`RepositoryLike\` contract from
1095
+ * \`@classytic/repo-core/adapter\` plugs in here. Each classytic kit
1096
+ * also ships its own \`/adapter\` subpath; if one of those fits, import
1097
+ * its factory directly instead.
1000
1098
  */
1001
1099
 
1002
- ${ts ? "import type { DataAdapter, RepositoryLike } from '@classytic/arc/adapters';" : ""}
1100
+ ${ts ? "import type { DataAdapter, RepositoryLike } from '@classytic/repo-core/adapter';" : ""}
1003
1101
 
1004
1102
  /**
1005
1103
  * Create a custom adapter for a resource.
1006
1104
  *
1007
- * Recommended SQL path:
1008
- * - sqlitekit repository + Arc's createDrizzleAdapter for Drizzle tables
1009
- *
1010
- * Experimental path:
1011
- * - Prisma can be wired with createPrismaAdapter, but keep it opt-in until
1012
- * your app has integration coverage.
1105
+ * Pass any object satisfying \`RepositoryLike<TDoc>\` (a 5-method floor:
1106
+ * \`getAll\` / \`getById\` / \`create\` / \`update\` / \`delete\`, plus any
1107
+ * optional \`StandardRepo\` methods you implement).
1013
1108
  */
1014
1109
  export function createAdapter${ts ? "<TDoc = unknown>" : ""}(
1015
1110
  _source${ts ? ": unknown" : ""},
@@ -1222,9 +1317,8 @@ function createTenantInjection(tenantField${ts ? ": string" : ""}) {
1222
1317
  // Fail-closed: Require orgId for create operations
1223
1318
  if (!orgId) {
1224
1319
  return reply.code(403).send({
1225
- success: false,
1226
- error: 'Forbidden',
1227
- message: 'Organization context required to create resources',
1320
+ error: 'Organization context required to create resources',
1321
+ code: 'arc.org_required',
1228
1322
  });
1229
1323
  }
1230
1324
 
@@ -1808,8 +1902,10 @@ describe('Example Resource', () => {
1808
1902
  authMode: 'jwt',
1809
1903
  ${config.adapter === "mongokit" ? " connectMongoose: true,\n" : ""} });
1810
1904
 
1905
+ // Arc's permission engine reads singular user.role — string,
1906
+ // comma-separated string, or array all normalise via getUserRoles().
1811
1907
  ctx.auth${ts ? "!" : ""}.register('admin', {
1812
- user: { id: '1', roles: ['admin'] },
1908
+ user: { id: '1', role: 'admin' },
1813
1909
  orgId: 'org-1',
1814
1910
  });
1815
1911
  });
@@ -1913,13 +2009,19 @@ userSchema.methods.removeOrganization = function(organizationId${ts ? ": Types.O
1913
2009
  userSchema.index({ 'organizations.organizationId': 1 });
1914
2010
  ` : "";
1915
2011
  const userType = ts ? `
1916
- type PlatformRole = 'user' | 'admin' | 'superadmin';
2012
+ const PLATFORM_ROLES = ['user', 'admin', 'superadmin'] as const;
2013
+ type PlatformRole = typeof PLATFORM_ROLES[number];
1917
2014
 
2015
+ /**
2016
+ * Comma-separated list of platform roles (Better Auth admin-plugin convention).
2017
+ * Single role: 'admin'. Multiple: 'admin,trainer'. Arc's permission engine
2018
+ * normalises both forms via getUserRoles() — see @classytic/arc/scope.
2019
+ */
1918
2020
  type User = {
1919
2021
  name: string;
1920
2022
  email: string;
1921
2023
  password: string;
1922
- roles: PlatformRole[];${config.tenant === "multi" ? `
2024
+ role: string;${config.tenant === "multi" ? `
1923
2025
  organizations: UserOrganization[];` : ""}
1924
2026
  resetPasswordToken?: string;
1925
2027
  resetPasswordExpires?: Date;
@@ -1958,11 +2060,21 @@ const userSchema = new Schema${ts ? "<User, UserModel, UserMethods>" : ""}(
1958
2060
  },
1959
2061
  password: { type: String, required: true },
1960
2062
 
1961
- // Platform roles
1962
- roles: {
1963
- type: [String],
1964
- enum: ['user', 'admin', 'superadmin'],
1965
- default: ['user'],
2063
+ // Platform role — singular field, matches Arc's permission engine
2064
+ // (req.user.role) and Better Auth's admin-plugin convention.
2065
+ // Comma-separated for multi-role users (e.g. 'admin,trainer');
2066
+ // getUserRoles() in @classytic/arc/scope normalises both forms.
2067
+ role: {
2068
+ type: String,
2069
+ required: true,
2070
+ default: 'user',
2071
+ index: true,
2072
+ validate: {
2073
+ validator: (v${ts ? ": string" : ""}) =>
2074
+ /^(user|admin|superadmin)(,(user|admin|superadmin))*$/.test(v),
2075
+ message: (props${ts ? ": { value: string }" : ""}) =>
2076
+ \`Invalid role "\${props.value}" — expected one or more of user|admin|superadmin\`,
2077
+ },
1966
2078
  },
1967
2079
  ${orgSchema}
1968
2080
  // Password reset
@@ -2237,39 +2349,58 @@ export default authResource;
2237
2349
  }
2238
2350
  function betterAuthSetupTemplate(config) {
2239
2351
  const ts = config.typescript;
2240
- const mongoImport = config.adapter === "mongokit" ? `import mongoose from 'mongoose';
2241
- import { mongodbAdapter } from 'better-auth/adapters/mongodb';` : "";
2242
- const dbAdapter = config.adapter === "mongokit" ? config.typescript ? `database: mongodbAdapter(mongoose.connection.getClient().db() as any),` : `database: mongodbAdapter(mongoose.connection.getClient().db()),` : `// Configure your database adapter here
2352
+ const useMongo = config.adapter === "mongokit";
2353
+ const useStubs = useMongo;
2354
+ const useOrg = config.tenant === "multi";
2355
+ const useBearer = config.session === "bearer" || config.session === "both";
2356
+ const useApiKey = config.apiKey;
2357
+ const mongoImport = useMongo ? `import mongoose from 'mongoose';
2358
+ import { mongodbAdapter } from '@better-auth/mongo-adapter';` : "";
2359
+ const stubsImport = useStubs ? `import { registerBetterAuthStubs } from '@classytic/mongokit/better-auth';` : "";
2360
+ const pluginImports = [
2361
+ useOrg ? `import { organization } from 'better-auth/plugins';` : "",
2362
+ useBearer ? `import { bearer } from 'better-auth/plugins';` : "",
2363
+ useApiKey ? `import { apiKey } from '@better-auth/api-key';` : ""
2364
+ ].filter(Boolean).join("\n");
2365
+ const dbAdapter = useMongo ? config.typescript ? `database: mongodbAdapter(mongoose.connection.getClient().db() as any),` : `database: mongodbAdapter(mongoose.connection.getClient().db()),` : `// Configure your database adapter here
2243
2366
  // See: https://www.better-auth.com/docs/concepts/database`;
2244
- const orgPlugin = config.tenant === "multi" ? `
2245
- import { organization } from 'better-auth/plugins/organization';
2246
- import { bearer } from 'better-auth/plugins/bearer';` : "";
2247
- const orgPluginUsage = config.tenant === "multi" ? `
2248
- plugins: [
2249
- bearer(),
2250
- organization({
2367
+ const pluginEntries = [
2368
+ useBearer ? ` bearer(),` : "",
2369
+ useApiKey ? ` apiKey({
2370
+ enableMetadata: true,
2371
+ rateLimit: { enabled: true, timeWindow: 1000 * 60 * 60 * 24, maxRequests: 10 },
2372
+ }),` : "",
2373
+ useOrg ? ` organization({
2251
2374
  allowUserToCreateOrganization: true,
2252
2375
  creatorRole: 'owner',
2253
- teams: {
2254
- enabled: true,
2255
- },
2256
- }),
2376
+ teams: { enabled: true },
2377
+ }),` : ""
2378
+ ].filter(Boolean);
2379
+ const orgPluginUsage = pluginEntries.length > 0 ? `
2380
+ plugins: [
2381
+ ${pluginEntries.join("\n")}
2257
2382
  ],` : "";
2383
+ const stubPluginsList = [useOrg ? `'organization'` : ""].filter(Boolean).join(", ");
2384
+ const stubExtras = useApiKey ? `, extraCollections: ['apikey']` : "";
2258
2385
  return `/**
2259
2386
  * Better Auth Configuration
2260
2387
  * Generated by Arc CLI
2261
2388
  *
2262
- * Authentication is handled entirely by Better Auth.
2263
- * Routes are registered automatically at /api/auth/*
2389
+ * Better Auth owns auth flows + writes its own tables. Arc reads them as
2390
+ * resources via \`@classytic/mongokit/better-auth\`'s overlay (full pagination,
2391
+ * queryparser, OpenAPI, audit) without re-implementing CRUD.
2264
2392
  *
2265
- * Better Auth manages these collections:
2266
- * - user, session, account${config.tenant === "multi" ? ", organization, member, invitation, team, teamMember" : ""}
2393
+ * BA-managed tables: user, session, account, verification${useOrg ? ", organization, member, invitation, team, teamMember" : ""}${useApiKey ? ", apikey" : ""}
2267
2394
  *
2268
2395
  * @see https://www.better-auth.com/docs
2269
2396
  */
2270
2397
 
2271
2398
  import { betterAuth } from 'better-auth';
2272
- ${mongoImport}${orgPlugin}
2399
+ ${[
2400
+ mongoImport,
2401
+ pluginImports,
2402
+ stubsImport
2403
+ ].filter(Boolean).join("\n")}
2273
2404
  import config from '#config/index.js';
2274
2405
 
2275
2406
  let _auth${ts ? ": ReturnType<typeof betterAuth> | null" : ""} = null;
@@ -2333,16 +2464,11 @@ ${orgPluginUsage}
2333
2464
  enabled: process.env.NODE_ENV === 'production',
2334
2465
  },
2335
2466
  });
2336
- ${config.adapter === "mongokit" ? `
2337
- // Register stub Mongoose models for Better Auth collections.
2338
- // BA uses the raw MongoDB driver, so no Mongoose models exist by default.
2339
- // These stubs (strict: false) enable populate() on refs like 'user', 'organization', etc.
2340
- const baCollections = ['user', 'organization', 'member', 'invitation', 'session', 'account'];
2341
- for (const name of baCollections) {
2342
- if (!mongoose.models[name]) {
2343
- mongoose.model(name, new mongoose.Schema({}, { strict: false, collection: name }));
2344
- }
2345
- }
2467
+ ${useStubs ? `
2468
+ // Register stub Mongoose models for Better Auth's collections so
2469
+ // \`.populate('user')\` / \`ref: 'organization'\` resolve against BA-owned
2470
+ // documents app-wide. Idempotent, strict:false, plugin-aware.
2471
+ registerBetterAuthStubs(mongoose, {${stubPluginsList ? ` plugins: [${stubPluginsList}]` : ""}${stubExtras} });
2346
2472
  ` : ""} }
2347
2473
 
2348
2474
  return _auth;
@@ -2377,16 +2503,16 @@ export async function register(request${ts ? ": FastifyRequest" : ""}, reply${ts
2377
2503
 
2378
2504
  // Check if email exists
2379
2505
  if (await userRepository.emailExists(email)) {
2380
- return reply.code(400).send({ success: false, message: 'Email already registered' });
2506
+ return reply.code(400).send({ error: 'Email already registered', code: 'arc.email_exists' });
2381
2507
  }
2382
2508
 
2383
2509
  // Create user
2384
- await userRepository.create({ name, email, password, roles: ['user'] });
2510
+ await userRepository.create({ name, email, password, role: 'user' });
2385
2511
 
2386
- return reply.code(201).send({ success: true, message: 'User registered successfully' });
2512
+ return reply.code(201).send({ message: 'User registered successfully' });
2387
2513
  } catch (error) {
2388
2514
  request.log.error({ err: error }, 'Register error');
2389
- return reply.code(500).send({ success: false, message: 'Registration failed' });
2515
+ return reply.code(500).send({ error: 'Registration failed', code: 'arc.internal' });
2390
2516
  }
2391
2517
  }
2392
2518
 
@@ -2399,19 +2525,18 @@ export async function login(request${ts ? ": FastifyRequest" : ""}, reply${ts ?
2399
2525
 
2400
2526
  const user = await userRepository.findByEmail(email);
2401
2527
  if (!user || !(await user.matchPassword(password))) {
2402
- return reply.code(401).send({ success: false, message: 'Invalid credentials' });
2528
+ return reply.code(401).send({ error: 'Invalid credentials', code: 'arc.unauthorized' });
2403
2529
  }
2404
2530
 
2405
2531
  const tokens = request.server.auth.issueTokens({ id: user._id.toString(), role: user.role });
2406
2532
 
2407
2533
  return reply.send({
2408
- success: true,
2409
2534
  user: { id: user._id, name: user.name, email: user.email, role: user.role },
2410
2535
  ...tokens,
2411
2536
  });
2412
2537
  } catch (error) {
2413
2538
  request.log.error({ err: error }, 'Login error');
2414
- return reply.code(500).send({ success: false, message: 'Login failed' });
2539
+ return reply.code(500).send({ error: 'Login failed', code: 'arc.internal' });
2415
2540
  }
2416
2541
  }
2417
2542
 
@@ -2422,15 +2547,15 @@ export async function refreshToken(request${ts ? ": FastifyRequest" : ""}, reply
2422
2547
  try {
2423
2548
  const { token } = request.body${ts ? " as any" : ""};
2424
2549
  if (!token) {
2425
- return reply.code(401).send({ success: false, message: 'Refresh token required' });
2550
+ return reply.code(401).send({ error: 'Refresh token required', code: 'arc.unauthorized' });
2426
2551
  }
2427
2552
 
2428
2553
  const decoded = request.server.auth.verifyRefreshToken(token)${ts ? " as { id: string }" : ""};
2429
2554
  const tokens = request.server.auth.issueTokens({ id: decoded.id });
2430
2555
 
2431
- return reply.send({ success: true, ...tokens });
2556
+ return reply.send(tokens);
2432
2557
  } catch {
2433
- return reply.code(401).send({ success: false, message: 'Invalid refresh token' });
2558
+ return reply.code(401).send({ error: 'Invalid refresh token', code: 'arc.unauthorized' });
2434
2559
  }
2435
2560
  }
2436
2561
 
@@ -2451,11 +2576,11 @@ export async function forgotPassword(request${ts ? ": FastifyRequest" : ""}, rep
2451
2576
  request.log.info(\`Password reset requested for \${email}\`);
2452
2577
  }
2453
2578
 
2454
- // Always return success to prevent email enumeration
2455
- return reply.send({ success: true, message: 'If email exists, reset link sent' });
2579
+ // Always 200 to prevent email enumeration
2580
+ return reply.send({ message: 'If email exists, reset link sent' });
2456
2581
  } catch (error) {
2457
2582
  request.log.error({ err: error }, 'Forgot password error');
2458
- return reply.code(500).send({ success: false, message: 'Failed to process request' });
2583
+ return reply.code(500).send({ error: 'Failed to process request', code: 'arc.internal' });
2459
2584
  }
2460
2585
  }
2461
2586
 
@@ -2468,14 +2593,14 @@ export async function resetPassword(request${ts ? ": FastifyRequest" : ""}, repl
2468
2593
  const user = await userRepository.findByResetToken(token);
2469
2594
 
2470
2595
  if (!user) {
2471
- return reply.code(400).send({ success: false, message: 'Invalid or expired token' });
2596
+ return reply.code(400).send({ error: 'Invalid or expired token', code: 'arc.bad_request' });
2472
2597
  }
2473
2598
 
2474
2599
  await userRepository.updatePassword(user._id, newPassword);
2475
- return reply.send({ success: true, message: 'Password has been reset' });
2600
+ return reply.send({ message: 'Password has been reset' });
2476
2601
  } catch (error) {
2477
2602
  request.log.error({ err: error }, 'Reset password error');
2478
- return reply.code(500).send({ success: false, message: 'Failed to reset password' });
2603
+ return reply.code(500).send({ error: 'Failed to reset password', code: 'arc.internal' });
2479
2604
  }
2480
2605
  }
2481
2606
 
@@ -2488,13 +2613,13 @@ export async function getUserProfile(request${ts ? ": FastifyRequest" : ""}, rep
2488
2613
  const user = await userRepository.getById(userId);
2489
2614
 
2490
2615
  if (!user) {
2491
- return reply.code(404).send({ success: false, message: 'User not found' });
2616
+ return reply.code(404).send({ error: 'User not found', code: 'arc.not_found' });
2492
2617
  }
2493
2618
 
2494
- return reply.send({ success: true, data: user });
2619
+ return reply.send(user);
2495
2620
  } catch (error) {
2496
2621
  request.log.error({ err: error }, 'Get profile error');
2497
- return reply.code(500).send({ success: false, message: 'Failed to get profile' });
2622
+ return reply.code(500).send({ error: 'Failed to get profile', code: 'arc.internal' });
2498
2623
  }
2499
2624
  }
2500
2625
 
@@ -2506,16 +2631,16 @@ export async function updateUserProfile(request${ts ? ": FastifyRequest" : ""},
2506
2631
  const userId = (request${ts ? " as any" : ""}).user?._id || (request${ts ? " as any" : ""}).user?.id;
2507
2632
  const updates = { ...request.body${ts ? " as any" : ""} };
2508
2633
 
2509
- // Prevent updating protected fields
2510
- if ('password' in updates) delete updates.password;
2511
- if ('roles' in updates) delete updates.roles;
2512
- if ('organizations' in updates) delete updates.organizations;
2634
+ // Prevent updating protected fields — auth-managed only
2635
+ delete updates.password;
2636
+ delete updates.role;
2637
+ delete updates.organizations;
2513
2638
 
2514
2639
  const user = await userRepository.Model.findByIdAndUpdate(userId, updates, { new: true });
2515
- return reply.send({ success: true, data: user });
2640
+ return reply.send(user);
2516
2641
  } catch (error) {
2517
2642
  request.log.error({ err: error }, 'Update profile error');
2518
- return reply.code(500).send({ success: false, message: 'Failed to update profile' });
2643
+ return reply.code(500).send({ error: 'Failed to update profile', code: 'arc.internal' });
2519
2644
  }
2520
2645
  }
2521
2646
  `;
@@ -2598,7 +2723,7 @@ export const loginResponse = {
2598
2723
  id: { type: 'string' },
2599
2724
  name: { type: 'string' },
2600
2725
  email: { type: 'string' },
2601
- roles: { type: 'array', items: { type: 'string' } },
2726
+ role: { type: 'string' },
2602
2727
  },
2603
2728
  },
2604
2729
  accessToken: { type: 'string' },
@@ -1,4 +1,4 @@
1
- import { t as ResourceRegistry } from "../../ResourceRegistry-DkAeAuTX.mjs";
1
+ import { t as ResourceRegistry } from "../../ResourceRegistry-CTERg_2x.mjs";
2
2
  import { resolve } from "node:path";
3
3
  import { pathToFileURL } from "node:url";
4
4
  //#region src/cli/commands/introspect.ts
@@ -57,6 +57,11 @@ async function introspect(args) {
57
57
  }
58
58
  if (resource.presets && resource.presets.length > 0) console.log(` Presets: ${resource.presets.join(", ")}`);
59
59
  if (resource.customRoutes && resource.customRoutes.length > 0) console.log(` Additional Routes: ${resource.customRoutes.length}`);
60
+ if (resource.actions && resource.actions.length > 0) {
61
+ const fallback = resource.actionPermissions ? ` (fallback: ${describePermission(resource.actionPermissions)})` : "";
62
+ console.log(` Actions: ${resource.actions.map((a) => a.name).join(", ")}${fallback}`);
63
+ }
64
+ if (resource.aggregations && resource.aggregations.length > 0) console.log(` Aggregations: ${resource.aggregations.map((a) => a.name).join(", ")}`);
60
65
  console.log("");
61
66
  });
62
67
  const stats = registry.getStats();
@@ -64,6 +69,8 @@ async function introspect(args) {
64
69
  console.log(` Total Resources: ${stats.totalResources}`);
65
70
  console.log(` With Presets: ${resources.filter((r) => r.presets?.length > 0).length}`);
66
71
  console.log(` With Custom Routes: ${resources.filter((r) => r.customRoutes && r.customRoutes.length > 0).length}`);
72
+ console.log(` With Actions: ${resources.filter((r) => r.actions && r.actions.length > 0).length}`);
73
+ console.log(` With Aggregations: ${resources.filter((r) => r.aggregations && r.aggregations.length > 0).length}`);
67
74
  } catch (error) {
68
75
  if (error instanceof Error) throw error;
69
76
  throw new Error(String(error));
@@ -1,2 +1,2 @@
1
- import { t as requestContext } from "../requestContext-C5XeK3VA.mjs";
1
+ import { t as requestContext } from "../requestContext-SSaaTgW8.mjs";
2
2
  export { requestContext };