@classytic/arc 2.1.2 → 2.1.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 (261) hide show
  1. package/dist/{EventTransport-BD2U0BTc.d.mts → EventTransport-BkUDYZEb.d.mts} +1 -2
  2. package/dist/HookSystem-BsGV-j2l.mjs +1 -2
  3. package/dist/{ResourceRegistry-DsN4KJjV.mjs → ResourceRegistry-7Ic20ZMw.mjs} +1 -2
  4. package/dist/adapters/index.d.mts +4 -4
  5. package/dist/audit/index.d.mts +5 -6
  6. package/dist/audit/index.mjs +2 -3
  7. package/dist/audit/mongodb.d.mts +4 -4
  8. package/dist/audit/mongodb.mjs +1 -1
  9. package/dist/{audited-C3T5DTUx.mjs → audited-CGdLiSlE.mjs} +1 -2
  10. package/dist/auth/index.d.mts +6 -7
  11. package/dist/auth/index.mjs +10 -16
  12. package/dist/auth/redis-session.d.mts +2 -3
  13. package/dist/auth/redis-session.mjs +1 -2
  14. package/dist/{betterAuthOpenApi-BrHKeSAx.mjs → betterAuthOpenApi-DjWDddNc.mjs} +2 -3
  15. package/dist/cache/index.d.mts +3 -4
  16. package/dist/cache/index.mjs +4 -5
  17. package/dist/{caching-Bl28lYsR.mjs → caching-GSDJcA6-.mjs} +1 -2
  18. package/dist/{circuitBreaker-DeY4FCjs.mjs → circuitBreaker-DYhWBW_D.mjs} +1 -2
  19. package/dist/cli/commands/describe.d.mts +1 -2
  20. package/dist/cli/commands/describe.mjs +1 -2
  21. package/dist/cli/commands/docs.d.mts +1 -2
  22. package/dist/cli/commands/docs.mjs +3 -4
  23. package/dist/cli/commands/generate.d.mts +1 -2
  24. package/dist/cli/commands/generate.mjs +2 -3
  25. package/dist/cli/commands/init.d.mts +1 -2
  26. package/dist/cli/commands/init.mjs +6 -7
  27. package/dist/cli/commands/introspect.d.mts +1 -2
  28. package/dist/cli/commands/introspect.mjs +2 -3
  29. package/dist/cli/index.d.mts +1 -2
  30. package/dist/cli/index.mjs +1 -2
  31. package/dist/constants-DdXFXQtN.mjs +1 -2
  32. package/dist/core/index.d.mts +4 -4
  33. package/dist/core/index.mjs +1 -1
  34. package/dist/{createApp-CUgNqegw.mjs → createApp-D2D5XXaV.mjs} +9 -10
  35. package/dist/{defineResource-k0_BDn8v.mjs → defineResource-PXzSJ15_.mjs} +11 -11
  36. package/dist/discovery/index.d.mts +1 -2
  37. package/dist/discovery/index.mjs +1 -2
  38. package/dist/docs/index.d.mts +5 -6
  39. package/dist/docs/index.mjs +5 -4
  40. package/dist/{elevation-B_2dRLVP.d.mts → elevation-DGo5shaX.d.mts} +1 -2
  41. package/dist/{elevation-BRy3yFWT.mjs → elevation-DSTbVvYj.mjs} +4 -4
  42. package/dist/{errorHandler-C1okiriz.mjs → errorHandler-C3GY3_ow.mjs} +2 -3
  43. package/dist/{errorHandler-BbcgBmIH.d.mts → errorHandler-CW3OOeYq.d.mts} +2 -3
  44. package/dist/{errors-ChKiFz62.d.mts → errors-DAWRdiYP.d.mts} +1 -2
  45. package/dist/{errors-B9bZok84.mjs → errors-DBANPbGr.mjs} +1 -2
  46. package/dist/{eventPlugin-DGR_B2on.mjs → eventPlugin-BEOvaDqo.mjs} +2 -3
  47. package/dist/{eventPlugin-CTrLH3mt.d.mts → eventPlugin-H6wDDjGO.d.mts} +2 -3
  48. package/dist/events/index.d.mts +4 -5
  49. package/dist/events/index.mjs +2 -3
  50. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  51. package/dist/events/transports/redis-stream-entry.mjs +1 -2
  52. package/dist/events/transports/redis.d.mts +2 -3
  53. package/dist/events/transports/redis.mjs +1 -2
  54. package/dist/{externalPaths-DlINfKbP.d.mts → externalPaths-SyPF2tgK.d.mts} +1 -2
  55. package/dist/factory/index.d.mts +8 -9
  56. package/dist/factory/index.mjs +1 -1
  57. package/dist/{fastifyAdapter-BkrGrlFi.d.mts → fastifyAdapter-C8DlE0YH.d.mts} +4 -5
  58. package/dist/{fields-DyaDVX4J.d.mts → fields-Bi_AVKSo.d.mts} +2 -3
  59. package/dist/{fields-iagOozy0.mjs → fields-CTd_CrKr.mjs} +2 -3
  60. package/dist/hooks/index.d.mts +3 -3
  61. package/dist/idempotency/index.d.mts +4 -5
  62. package/dist/idempotency/index.mjs +1 -2
  63. package/dist/idempotency/mongodb.d.mts +1 -1
  64. package/dist/idempotency/mongodb.mjs +1 -2
  65. package/dist/idempotency/redis.d.mts +1 -1
  66. package/dist/idempotency/redis.mjs +1 -2
  67. package/dist/index.d.mts +9 -10
  68. package/dist/index.mjs +7 -8
  69. package/dist/integrations/event-gateway.d.mts +2 -3
  70. package/dist/integrations/event-gateway.mjs +2 -3
  71. package/dist/integrations/jobs.d.mts +1 -2
  72. package/dist/integrations/jobs.mjs +1 -2
  73. package/dist/integrations/streamline.d.mts +1 -2
  74. package/dist/integrations/streamline.mjs +1 -2
  75. package/dist/integrations/websocket.d.mts +1 -2
  76. package/dist/integrations/websocket.mjs +1 -2
  77. package/dist/{interface-B01JvPVc.d.mts → interface-CSNjltAc.d.mts} +1 -2
  78. package/dist/{interface-CZe8IkMf.d.mts → interface-DTbsvIWe.d.mts} +1 -2
  79. package/dist/{interface-Ch8HU9uM.d.mts → interface-e9XfSsUV.d.mts} +3 -4
  80. package/dist/{introspectionPlugin-rFdO8ZUa.mjs → introspectionPlugin-B3JkrjwU.mjs} +1 -2
  81. package/dist/{keys-BqNejWup.mjs → keys-DhqDRxv3.mjs} +1 -2
  82. package/dist/{logger-Df2O2WsW.mjs → logger-ByrvQWZO.mjs} +1 -2
  83. package/dist/{memory-cQgelFOj.mjs → memory-B2v7KrCB.mjs} +1 -2
  84. package/dist/migrations/index.d.mts +1 -2
  85. package/dist/migrations/index.mjs +1 -2
  86. package/dist/{mongodb-CGzRbfAK.d.mts → mongodb-ClykrfGo.d.mts} +2 -3
  87. package/dist/{mongodb-BfJVlUJH.mjs → mongodb-DNKEExbf.mjs} +1 -2
  88. package/dist/{mongodb-JN-9JA7K.d.mts → mongodb-Dg8O_gvd.d.mts} +2 -3
  89. package/dist/{openapi-G3Cw7XuM.mjs → openapi-9nB_kiuR.mjs} +5 -4
  90. package/dist/org/index.d.mts +4 -5
  91. package/dist/org/index.mjs +1 -2
  92. package/dist/org/types.d.mts +1 -2
  93. package/dist/permissions/index.d.mts +5 -6
  94. package/dist/permissions/index.mjs +7 -7
  95. package/dist/plugins/index.d.mts +7 -8
  96. package/dist/plugins/index.mjs +7 -8
  97. package/dist/plugins/response-cache.d.mts +1 -2
  98. package/dist/plugins/response-cache.mjs +2 -3
  99. package/dist/plugins/tracing-entry.d.mts +1 -1
  100. package/dist/plugins/tracing-entry.mjs +1 -2
  101. package/dist/{pluralize-CEweyOEm.mjs → pluralize-CM-jZg7p.mjs} +1 -2
  102. package/dist/policies/index.d.mts +4 -5
  103. package/dist/policies/index.mjs +1 -2
  104. package/dist/presets/index.d.mts +4 -5
  105. package/dist/presets/index.mjs +2 -3
  106. package/dist/presets/multiTenant.d.mts +4 -5
  107. package/dist/presets/multiTenant.mjs +1 -2
  108. package/dist/{presets-DzSMwlKj.d.mts → presets-BTeYbw7h.d.mts} +2 -3
  109. package/dist/{presets-BITljm96.mjs → presets-CeFtfDR8.mjs} +1 -2
  110. package/dist/{prisma-Dg9GoVdj.d.mts → prisma-C3iornoK.d.mts} +2 -3
  111. package/dist/prisma-DJbMt3yf.mjs +1 -2
  112. package/dist/{queryCachePlugin-DMBnp2Q0.mjs → queryCachePlugin-B6R0d4av.mjs} +4 -5
  113. package/dist/{queryCachePlugin-7THaI5mt.d.mts → queryCachePlugin-Q6SYuHZ6.d.mts} +2 -3
  114. package/dist/{redis-D-JAeLtm.d.mts → redis-UwjEp8Ea.d.mts} +2 -3
  115. package/dist/{redis-stream-Bdh_vUU8.d.mts → redis-stream-CBg0upHI.d.mts} +2 -3
  116. package/dist/registry/index.d.mts +4 -5
  117. package/dist/registry/index.mjs +2 -2
  118. package/dist/{requestContext-QQD6ROJc.mjs → requestContext-xi6OKBL-.mjs} +1 -2
  119. package/dist/{schemaConverter-BwrmWroW.mjs → schemaConverter-Dtg0Kt9T.mjs} +1 -2
  120. package/dist/schemas/index.d.mts +1 -2
  121. package/dist/schemas/index.mjs +1 -2
  122. package/dist/scope/index.d.mts +2 -3
  123. package/dist/scope/index.mjs +2 -3
  124. package/dist/{sessionManager-jPKLbHE0.d.mts → sessionManager-D_iEHjQl.d.mts} +1 -2
  125. package/dist/{sse-B3c3_yZp.mjs → sse-DkqQ1uxb.mjs} +2 -3
  126. package/dist/testing/index.d.mts +8 -9
  127. package/dist/testing/index.mjs +3 -4
  128. package/dist/{tracing-Cc7vVQPp.d.mts → tracing-8CEbhF0w.d.mts} +1 -2
  129. package/dist/{typeGuards-DhMNLuvU.mjs → typeGuards-DwxA1t_L.mjs} +1 -2
  130. package/dist/types/index.d.mts +6 -7
  131. package/dist/types/index.mjs +1 -2
  132. package/dist/{types-CIgB7UUl.d.mts → types-B0dhNrnd.d.mts} +9 -10
  133. package/dist/types-Beqn1Un7.mjs +1 -2
  134. package/dist/types-DelU6kln.mjs +25 -0
  135. package/dist/{types-aYB4V7uN.d.mts → types-RLkFVgaw.d.mts} +18 -4
  136. package/dist/utils/index.d.mts +5 -6
  137. package/dist/utils/index.mjs +4 -4
  138. package/package.json +1 -1
  139. package/dist/EventTransport-BD2U0BTc.d.mts.map +0 -1
  140. package/dist/HookSystem-BsGV-j2l.mjs.map +0 -1
  141. package/dist/ResourceRegistry-DsN4KJjV.mjs.map +0 -1
  142. package/dist/audit/index.d.mts.map +0 -1
  143. package/dist/audit/index.mjs.map +0 -1
  144. package/dist/audited-C3T5DTUx.mjs.map +0 -1
  145. package/dist/auth/index.d.mts.map +0 -1
  146. package/dist/auth/index.mjs.map +0 -1
  147. package/dist/auth/redis-session.d.mts.map +0 -1
  148. package/dist/auth/redis-session.mjs.map +0 -1
  149. package/dist/betterAuthOpenApi-BrHKeSAx.mjs.map +0 -1
  150. package/dist/cache/index.d.mts.map +0 -1
  151. package/dist/cache/index.mjs.map +0 -1
  152. package/dist/caching-Bl28lYsR.mjs.map +0 -1
  153. package/dist/circuitBreaker-DeY4FCjs.mjs.map +0 -1
  154. package/dist/cli/commands/describe.d.mts.map +0 -1
  155. package/dist/cli/commands/describe.mjs.map +0 -1
  156. package/dist/cli/commands/docs.d.mts.map +0 -1
  157. package/dist/cli/commands/docs.mjs.map +0 -1
  158. package/dist/cli/commands/generate.d.mts.map +0 -1
  159. package/dist/cli/commands/generate.mjs.map +0 -1
  160. package/dist/cli/commands/init.d.mts.map +0 -1
  161. package/dist/cli/commands/init.mjs.map +0 -1
  162. package/dist/cli/commands/introspect.d.mts.map +0 -1
  163. package/dist/cli/commands/introspect.mjs.map +0 -1
  164. package/dist/cli/index.d.mts.map +0 -1
  165. package/dist/cli/index.mjs.map +0 -1
  166. package/dist/constants-DdXFXQtN.mjs.map +0 -1
  167. package/dist/createApp-CUgNqegw.mjs.map +0 -1
  168. package/dist/defineResource-k0_BDn8v.mjs.map +0 -1
  169. package/dist/discovery/index.d.mts.map +0 -1
  170. package/dist/discovery/index.mjs.map +0 -1
  171. package/dist/docs/index.d.mts.map +0 -1
  172. package/dist/docs/index.mjs.map +0 -1
  173. package/dist/elevation-BRy3yFWT.mjs.map +0 -1
  174. package/dist/elevation-B_2dRLVP.d.mts.map +0 -1
  175. package/dist/errorHandler-BbcgBmIH.d.mts.map +0 -1
  176. package/dist/errorHandler-C1okiriz.mjs.map +0 -1
  177. package/dist/errors-B9bZok84.mjs.map +0 -1
  178. package/dist/errors-ChKiFz62.d.mts.map +0 -1
  179. package/dist/eventPlugin-CTrLH3mt.d.mts.map +0 -1
  180. package/dist/eventPlugin-DGR_B2on.mjs.map +0 -1
  181. package/dist/events/index.d.mts.map +0 -1
  182. package/dist/events/index.mjs.map +0 -1
  183. package/dist/events/transports/redis-stream-entry.mjs.map +0 -1
  184. package/dist/events/transports/redis.d.mts.map +0 -1
  185. package/dist/events/transports/redis.mjs.map +0 -1
  186. package/dist/externalPaths-DlINfKbP.d.mts.map +0 -1
  187. package/dist/factory/index.d.mts.map +0 -1
  188. package/dist/fastifyAdapter-BkrGrlFi.d.mts.map +0 -1
  189. package/dist/fields-DyaDVX4J.d.mts.map +0 -1
  190. package/dist/fields-iagOozy0.mjs.map +0 -1
  191. package/dist/idempotency/index.d.mts.map +0 -1
  192. package/dist/idempotency/index.mjs.map +0 -1
  193. package/dist/idempotency/mongodb.mjs.map +0 -1
  194. package/dist/idempotency/redis.mjs.map +0 -1
  195. package/dist/index.d.mts.map +0 -1
  196. package/dist/index.mjs.map +0 -1
  197. package/dist/integrations/event-gateway.d.mts.map +0 -1
  198. package/dist/integrations/event-gateway.mjs.map +0 -1
  199. package/dist/integrations/jobs.d.mts.map +0 -1
  200. package/dist/integrations/jobs.mjs.map +0 -1
  201. package/dist/integrations/streamline.d.mts.map +0 -1
  202. package/dist/integrations/streamline.mjs.map +0 -1
  203. package/dist/integrations/websocket.d.mts.map +0 -1
  204. package/dist/integrations/websocket.mjs.map +0 -1
  205. package/dist/interface-B01JvPVc.d.mts.map +0 -1
  206. package/dist/interface-CZe8IkMf.d.mts.map +0 -1
  207. package/dist/interface-Ch8HU9uM.d.mts.map +0 -1
  208. package/dist/introspectionPlugin-rFdO8ZUa.mjs.map +0 -1
  209. package/dist/keys-BqNejWup.mjs.map +0 -1
  210. package/dist/logger-Df2O2WsW.mjs.map +0 -1
  211. package/dist/memory-cQgelFOj.mjs.map +0 -1
  212. package/dist/migrations/index.d.mts.map +0 -1
  213. package/dist/migrations/index.mjs.map +0 -1
  214. package/dist/mongodb-BfJVlUJH.mjs.map +0 -1
  215. package/dist/mongodb-CGzRbfAK.d.mts.map +0 -1
  216. package/dist/mongodb-JN-9JA7K.d.mts.map +0 -1
  217. package/dist/openapi-G3Cw7XuM.mjs.map +0 -1
  218. package/dist/org/index.d.mts.map +0 -1
  219. package/dist/org/index.mjs.map +0 -1
  220. package/dist/org/types.d.mts.map +0 -1
  221. package/dist/permissions/index.d.mts.map +0 -1
  222. package/dist/permissions/index.mjs.map +0 -1
  223. package/dist/plugins/index.d.mts.map +0 -1
  224. package/dist/plugins/index.mjs.map +0 -1
  225. package/dist/plugins/response-cache.d.mts.map +0 -1
  226. package/dist/plugins/response-cache.mjs.map +0 -1
  227. package/dist/plugins/tracing-entry.mjs.map +0 -1
  228. package/dist/pluralize-CEweyOEm.mjs.map +0 -1
  229. package/dist/policies/index.d.mts.map +0 -1
  230. package/dist/policies/index.mjs.map +0 -1
  231. package/dist/presets/index.d.mts.map +0 -1
  232. package/dist/presets/index.mjs.map +0 -1
  233. package/dist/presets/multiTenant.d.mts.map +0 -1
  234. package/dist/presets/multiTenant.mjs.map +0 -1
  235. package/dist/presets-BITljm96.mjs.map +0 -1
  236. package/dist/presets-DzSMwlKj.d.mts.map +0 -1
  237. package/dist/prisma-DJbMt3yf.mjs.map +0 -1
  238. package/dist/prisma-Dg9GoVdj.d.mts.map +0 -1
  239. package/dist/queryCachePlugin-7THaI5mt.d.mts.map +0 -1
  240. package/dist/queryCachePlugin-DMBnp2Q0.mjs.map +0 -1
  241. package/dist/redis-D-JAeLtm.d.mts.map +0 -1
  242. package/dist/redis-stream-Bdh_vUU8.d.mts.map +0 -1
  243. package/dist/registry/index.d.mts.map +0 -1
  244. package/dist/requestContext-QQD6ROJc.mjs.map +0 -1
  245. package/dist/schemaConverter-BwrmWroW.mjs.map +0 -1
  246. package/dist/schemas/index.d.mts.map +0 -1
  247. package/dist/schemas/index.mjs.map +0 -1
  248. package/dist/scope/index.d.mts.map +0 -1
  249. package/dist/scope/index.mjs.map +0 -1
  250. package/dist/sessionManager-jPKLbHE0.d.mts.map +0 -1
  251. package/dist/sse-B3c3_yZp.mjs.map +0 -1
  252. package/dist/testing/index.d.mts.map +0 -1
  253. package/dist/testing/index.mjs.map +0 -1
  254. package/dist/tracing-Cc7vVQPp.d.mts.map +0 -1
  255. package/dist/typeGuards-DhMNLuvU.mjs.map +0 -1
  256. package/dist/types/index.d.mts.map +0 -1
  257. package/dist/types/index.mjs.map +0 -1
  258. package/dist/types-Beqn1Un7.mjs.map +0 -1
  259. package/dist/types-CIgB7UUl.d.mts.map +0 -1
  260. package/dist/types-aYB4V7uN.d.mts.map +0 -1
  261. package/dist/utils/index.d.mts.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"generate.mjs","names":[],"sources":["../../../src/cli/commands/generate.ts"],"sourcesContent":["/**\n * Arc CLI - Generate Command\n *\n * Scaffolds resources with consistent naming:\n * - src/resources/product/product.model.ts\n * - src/resources/product/product.repository.ts\n * - src/resources/product/product.resource.ts\n * - src/resources/product/product.controller.ts\n * - src/resources/product/product.schemas.ts\n */\n\nimport { writeFileSync, mkdirSync, existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { pluralize } from \"../utils/pluralize.js\";\n\ninterface ArcProjectConfig {\n adapter?: \"mongokit\" | \"custom\";\n auth?: \"jwt\" | \"better-auth\";\n tenant?: \"multi\" | \"single\";\n typescript?: boolean;\n}\n\n// Read .arcrc project config (written by `arc init`)\nfunction readProjectConfig(): ArcProjectConfig {\n try {\n const rcPath = join(process.cwd(), \".arcrc\");\n return JSON.parse(readFileSync(rcPath, \"utf-8\"));\n } catch {\n return {};\n }\n}\n\n// Check if TypeScript project\nfunction isTypeScriptProject(): boolean {\n return existsSync(join(process.cwd(), \"tsconfig.json\"));\n}\n\n// Templates\nfunction getTemplates(ts: boolean, config: ArcProjectConfig = {}) {\n const isMultiTenant = config.tenant === \"multi\";\n\n return {\n model: (name: string) => `/**\n * ${name} Model\n * Generated by Arc CLI\n */\n\nimport mongoose${ts ? \", { type HydratedDocument }\" : \"\"} from 'mongoose';\n\nconst { Schema } = mongoose;\n${\n ts\n ? `\nexport interface I${name} {\n name: string;\n description?: string;\n isActive: boolean;\n}\n\nexport type ${name}Document = HydratedDocument<I${name}>;\n`\n : \"\"\n}\nconst ${name.toLowerCase()}Schema = new Schema${ts ? `<I${name}>` : \"\"}(\n {\n name: { type: String, required: true, trim: true },\n description: { type: String, trim: true },\n isActive: { type: Boolean, default: true },\n },\n { timestamps: true }\n);\n\n// Indexes\n${name.toLowerCase()}Schema.index({ name: 1 });\n${name.toLowerCase()}Schema.index({ isActive: 1 });\n\nconst ${name} = mongoose.models.${name} || mongoose.model('${name}', ${name.toLowerCase()}Schema);\nexport default ${name};\n`,\n\n repository: (name: string) => `/**\n * ${name} Repository\n * Generated by Arc CLI\n */\n\nimport {\n Repository,\n methodRegistryPlugin,\n softDeletePlugin,\n mongoOperationsPlugin,\n} from '@classytic/mongokit';\nimport ${name} from './${name.toLowerCase()}.model.js';\n\nclass ${name}Repository extends Repository {\n constructor() {\n super(${name}, [\n methodRegistryPlugin(),\n softDeletePlugin(),\n mongoOperationsPlugin(),\n ]);\n }\n\n /**\n * Find all active records\n */\n async findActive() {\n return this.Model.find({ isActive: true, deletedAt: null }).lean();\n }\n}\n\nconst ${name.toLowerCase()}Repository = new ${name}Repository();\nexport default ${name.toLowerCase()}Repository;\nexport { ${name}Repository };\n`,\n\n controller: (name: string) => `/**\n * ${name} Controller\n * Generated by Arc CLI\n *\n * Note: defineResource() auto-creates a controller from the adapter.\n * Only create a custom controller when you need custom methods.\n */\n\nimport { BaseController } from '@classytic/arc';\nimport ${name.toLowerCase()}Repository from './${name.toLowerCase()}.repository.js';\n\nclass ${name}Controller extends BaseController {\n constructor() {\n super(${name.toLowerCase()}Repository, {\n resourceName: '${name.toLowerCase()}',\n });\n }\n\n // Add custom controller methods here\n}\n\nconst ${name.toLowerCase()}Controller = new ${name}Controller();\nexport default ${name.toLowerCase()}Controller;\n`,\n\n schemas: (name: string) => `/**\n * ${name} Schemas\n * Generated by Arc CLI\n */\n\nimport ${name} from './${name.toLowerCase()}.model.js';\nimport { buildCrudSchemasFromModel } from '@classytic/mongokit/utils';\n\n/**\n * CRUD Schemas with Field Rules\n */\nconst crudSchemas = buildCrudSchemasFromModel(${name}, {\n strictAdditionalProperties: true,\n fieldRules: {\n // Mark fields as system-managed (excluded from create/update)\n // deletedAt: { systemManaged: true },\n },\n query: {\n filterableFields: {\n isActive: 'boolean',\n createdAt: 'date',\n },\n },\n});\n\nexport default crudSchemas;\n`,\n\n resource: (name: string) => {\n const useMongoKit = config.adapter === \"mongokit\" || !config.adapter;\n const queryParserImport = useMongoKit\n ? `\\nimport { QueryParser } from '@classytic/mongokit';\\n\\nconst queryParser = new QueryParser();\\n`\n : \"\";\n const queryParserConfig = useMongoKit ? `\\n queryParser,` : \"\";\n\n return isMultiTenant\n ? `/**\n * ${name} Resource\n * Generated by Arc CLI\n */\n\nimport { defineResource, createMongooseAdapter } from '@classytic/arc';\nimport { requireOrgMembership, requireOrgRole } from '@classytic/arc/permissions';\nimport ${name}${ts ? `, { type I${name} }` : \"\"} from './${name.toLowerCase()}.model.js';\nimport ${name.toLowerCase()}Repository from './${name.toLowerCase()}.repository.js';${queryParserImport}\n\nconst ${name.toLowerCase()}Resource = defineResource${ts ? `<I${name}>` : \"\"}({\n name: '${name.toLowerCase()}',\n adapter: createMongooseAdapter(${name}, ${name.toLowerCase()}Repository),${queryParserConfig}\n presets: ['softDelete'],\n permissions: {\n list: requireOrgMembership(),\n get: requireOrgMembership(),\n create: requireOrgRole('admin'),\n update: requireOrgRole('admin'),\n delete: requireOrgRole('admin'),\n },\n});\n\nexport default ${name.toLowerCase()}Resource;\n`\n : `/**\n * ${name} Resource\n * Generated by Arc CLI\n */\n\nimport { defineResource, createMongooseAdapter } from '@classytic/arc';\nimport { requireAuth, requireRoles } from '@classytic/arc/permissions';\nimport ${name}${ts ? `, { type I${name} }` : \"\"} from './${name.toLowerCase()}.model.js';\nimport ${name.toLowerCase()}Repository from './${name.toLowerCase()}.repository.js';${queryParserImport}\n\nconst ${name.toLowerCase()}Resource = defineResource${ts ? `<I${name}>` : \"\"}({\n name: '${name.toLowerCase()}',\n adapter: createMongooseAdapter(${name}, ${name.toLowerCase()}Repository),${queryParserConfig}\n presets: ['softDelete'],\n permissions: {\n list: requireAuth(),\n get: requireAuth(),\n create: requireRoles(['admin']),\n update: requireRoles(['admin']),\n delete: requireRoles(['admin']),\n },\n});\n\nexport default ${name.toLowerCase()}Resource;\n`;\n },\n\n test: (name: string) => `/**\n * ${name} Tests\n * Generated by Arc CLI\n */\n\nimport { describe, it, expect, beforeAll, afterAll } from 'vitest';\nimport mongoose from 'mongoose';\nimport { createMinimalTestApp } from '@classytic/arc/testing';\n${ts ? \"import type { FastifyInstance } from 'fastify';\\n\" : \"\"}import ${name.toLowerCase()}Resource from '../src/resources/${name.toLowerCase()}/${name.toLowerCase()}.resource.js';\n\ndescribe('${name} Resource', () => {\n let app${ts ? \": FastifyInstance\" : \"\"};\n\n beforeAll(async () => {\n const testDbUri = process.env.MONGODB_URI || 'mongodb://localhost:27017/arc-app-test';\n await mongoose.connect(testDbUri);\n\n app = createMinimalTestApp();\n await app.register(${name.toLowerCase()}Resource.toPlugin());\n await app.ready();\n });\n\n afterAll(async () => {\n await app.close();\n await mongoose.connection.close();\n });\n\n describe('GET /${pluralize(name.toLowerCase())}', () => {\n it('should return a list', async () => {\n const response = await app.inject({\n method: 'GET',\n url: '/${pluralize(name.toLowerCase())}',\n });\n\n expect(response.statusCode).toBe(200);\n const body = JSON.parse(response.body);\n expect(body).toHaveProperty('docs');\n });\n });\n});\n`,\n };\n}\n\n/**\n * Generate command handler\n */\nexport async function generate(\n type: string | undefined,\n args: string[],\n): Promise<void> {\n if (!type) {\n throw new Error(\n \"Missing type argument\\nUsage: arc generate <resource|controller|model|repository|schemas> <name>\",\n );\n }\n\n const [name] = args;\n if (!name) {\n throw new Error(\n \"Missing name argument\\nUsage: arc generate <type> <name>\\nExample: arc generate resource product\",\n );\n }\n\n // Capitalize first letter\n const capitalizedName = name.charAt(0).toUpperCase() + name.slice(1);\n const lowerName = name.toLowerCase();\n\n // Detect project config\n const projectConfig = readProjectConfig();\n const ts = projectConfig.typescript ?? isTypeScriptProject();\n const ext = ts ? \"ts\" : \"js\";\n const templates = getTemplates(ts, projectConfig);\n\n // Resource path: src/resources/<name>/\n const resourcePath = join(process.cwd(), \"src\", \"resources\", lowerName);\n\n switch (type) {\n case \"resource\":\n case \"r\":\n await generateResource(\n capitalizedName,\n lowerName,\n resourcePath,\n templates,\n ext,\n );\n break;\n\n case \"controller\":\n case \"c\":\n await generateFile(\n capitalizedName,\n lowerName,\n resourcePath,\n \"controller\",\n templates.controller,\n ext,\n );\n break;\n\n case \"model\":\n case \"m\":\n await generateFile(\n capitalizedName,\n lowerName,\n resourcePath,\n \"model\",\n templates.model,\n ext,\n );\n break;\n\n case \"repository\":\n case \"repo\":\n await generateFile(\n capitalizedName,\n lowerName,\n resourcePath,\n \"repository\",\n templates.repository,\n ext,\n );\n break;\n\n case \"schemas\":\n case \"s\":\n await generateFile(\n capitalizedName,\n lowerName,\n resourcePath,\n \"schemas\",\n templates.schemas,\n ext,\n );\n break;\n\n default:\n throw new Error(\n `Unknown type: ${type}\\nAvailable types: resource, controller, model, repository, schemas`,\n );\n }\n}\n\n/**\n * Generate a full resource\n */\nasync function generateResource(\n name: string,\n lowerName: string,\n resourcePath: string,\n templates: ReturnType<typeof getTemplates>,\n ext: string,\n): Promise<void> {\n console.log(`\\nGenerating resource: ${name}...\\n`);\n\n // Create directory\n if (!existsSync(resourcePath)) {\n mkdirSync(resourcePath, { recursive: true });\n console.log(` + Created: src/resources/${lowerName}/`);\n }\n\n // Generate files (defineResource() auto-creates controller, no schemas needed)\n const files: Record<string, string> = {\n [`${lowerName}.model.${ext}`]: templates.model(name),\n [`${lowerName}.repository.${ext}`]: templates.repository(name),\n [`${lowerName}.resource.${ext}`]: templates.resource(name),\n };\n\n for (const [filename, content] of Object.entries(files)) {\n const filepath = join(resourcePath, filename);\n if (existsSync(filepath)) {\n console.warn(` ! Skipped: ${filename} (already exists)`);\n } else {\n writeFileSync(filepath, content);\n console.log(` + Created: ${filename}`);\n }\n }\n\n // Generate test file\n const testsDir = join(process.cwd(), \"tests\");\n if (!existsSync(testsDir)) {\n mkdirSync(testsDir, { recursive: true });\n }\n const testPath = join(testsDir, `${lowerName}.test.${ext}`);\n if (!existsSync(testPath)) {\n writeFileSync(testPath, templates.test(name));\n console.log(` + Created: tests/${lowerName}.test.${ext}`);\n }\n\n const isMultiTenant = readProjectConfig().tenant === \"multi\";\n\n console.log(`\n╔═══════════════════════════════════════════════════════════════╗\n║ Resource Generated ║\n╚═══════════════════════════════════════════════════════════════╝\n\nNext steps:\n\n1. Register in src/resources/index.${ext}:\n import ${lowerName}Resource from './${lowerName}/${lowerName}.resource.js';\n\n export const resources = [\n // ... existing resources\n ${lowerName}Resource,\n ];\n\n2. Customize the model schema in:\n src/resources/${lowerName}/${lowerName}.model.${ext}\n\n3. Adjust permissions in ${lowerName}.resource.${ext}:\n${\n isMultiTenant\n ? ` - requireOrgMembership() → any org member\n - requireOrgRole('admin') → specific org roles`\n : ` - requireAuth() → any authenticated user\n - requireRoles(['admin']) → specific platform roles`\n}\n\n4. Run tests:\n npm test\n`);\n}\n\n/**\n * Generate a single file\n */\nasync function generateFile(\n name: string,\n lowerName: string,\n resourcePath: string,\n fileType: string,\n template: (name: string) => string,\n ext: string,\n): Promise<void> {\n console.log(`\\nGenerating ${fileType}: ${name}...\\n`);\n\n if (!existsSync(resourcePath)) {\n mkdirSync(resourcePath, { recursive: true });\n console.log(` + Created: src/resources/${lowerName}/`);\n }\n\n const filename = `${lowerName}.${fileType}.${ext}`;\n const filepath = join(resourcePath, filename);\n\n if (existsSync(filepath)) {\n throw new Error(\n `${filename} already exists. Remove it first or use a different name.`,\n );\n }\n\n writeFileSync(filepath, template(name));\n console.log(` + Created: ${filename}`);\n}\n\nexport default generate;\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"init.d.mts","names":[],"sources":["../../../src/cli/commands/init.ts"],"mappings":";;AAuBA;;;;;;;;;UAAiB,WAAA;EACf,IAAA;EACA,OAAA;EACA,IAAA;EACA,MAAA;EACA,UAAA;EACA,IAAA;EACA,WAAA;EACA,KAAA;AAAA;;;;iBAmBoB,IAAA,CAAK,OAAA,GAAS,WAAA,GAAmB,OAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"init.mjs","names":["existsSync"],"sources":["../../../src/cli/commands/init.ts"],"sourcesContent":["/**\r\n * Arc CLI - Init Command\r\n *\r\n * Scaffolds a new Arc project with clean architecture:\r\n * - MongoKit or Custom adapter\r\n * - Multi-tenant or Single-tenant\r\n * - TypeScript or JavaScript\r\n *\r\n * Automatically installs dependencies using detected package manager.\r\n */\r\n\r\nimport * as fs from \"node:fs/promises\";\r\nimport { accessSync } from \"node:fs\";\r\nimport * as path from \"node:path\";\r\nimport * as readline from \"node:readline\";\r\nimport { execSync, spawn } from \"node:child_process\";\r\n\r\ntype PackageManager = \"npm\" | \"pnpm\" | \"yarn\" | \"bun\";\r\n\r\n// ============================================================================\r\n// Types\r\n// ============================================================================\r\n\r\nexport interface InitOptions {\r\n name?: string;\r\n adapter?: \"mongokit\" | \"custom\";\r\n auth?: \"jwt\" | \"better-auth\";\r\n tenant?: \"multi\" | \"single\";\r\n typescript?: boolean;\r\n edge?: boolean;\r\n skipInstall?: boolean;\r\n force?: boolean;\r\n}\r\n\r\ninterface ProjectConfig {\r\n name: string;\r\n adapter: \"mongokit\" | \"custom\";\r\n auth: \"jwt\" | \"better-auth\";\r\n tenant: \"multi\" | \"single\";\r\n typescript: boolean;\r\n edge: boolean;\r\n}\r\n\r\n// ============================================================================\r\n// Main Init Function\r\n// ============================================================================\r\n\r\n/**\r\n * Initialize a new Arc project\r\n */\r\nexport async function init(options: InitOptions = {}): Promise<void> {\r\n console.log(`\r\n╔═══════════════════════════════════════════════════════════════╗\r\n║ Arc Project Setup ║\r\n║ Resource-Oriented Backend Framework ║\r\n╚═══════════════════════════════════════════════════════════════╝\r\n`);\r\n\r\n // Gather configuration (from options or prompts)\r\n const config = await gatherConfig(options);\r\n\r\n console.log(`\\nCreating project: ${config.name}`);\r\n console.log(\r\n ` Adapter: ${config.adapter === \"mongokit\" ? \"MongoKit (MongoDB)\" : \"Custom\"}`,\r\n );\r\n console.log(\r\n ` Auth: ${config.auth === \"better-auth\" ? \"Better Auth (recommended)\" : \"Arc JWT\"}`,\r\n );\r\n console.log(\r\n ` Tenant: ${config.tenant === \"multi\" ? \"Multi-tenant\" : \"Single-tenant\"}`,\r\n );\r\n console.log(\r\n ` Language: ${config.typescript ? \"TypeScript\" : \"JavaScript\"}`,\r\n );\r\n console.log(\r\n ` Target: ${config.edge ? \"Edge/Serverless\" : \"Node.js Server\"}\\n`,\r\n );\r\n\r\n const projectPath = path.join(process.cwd(), config.name);\r\n\r\n // Check if directory exists\r\n try {\r\n await fs.access(projectPath);\r\n // If we reach here, the directory EXISTS\r\n if (!options.force) {\r\n throw new Error(\r\n `Directory \"${config.name}\" already exists. Use --force to overwrite.`,\r\n );\r\n }\r\n } catch (err) {\r\n // ENOENT = directory doesn't exist = good, fall through to scaffolding\r\n const isNotFound =\r\n err && typeof err === \"object\" && \"code\" in err && err.code === \"ENOENT\";\r\n if (!isNotFound) throw err;\r\n // else: directory doesn't exist, continue normally\r\n }\r\n\r\n // Detect package manager\r\n const packageManager = detectPackageManager();\r\n console.log(`Using package manager: ${packageManager}\\n`);\r\n\r\n // Create project structure (without dependencies in package.json)\r\n await createProjectStructure(projectPath, config);\r\n\r\n // Install dependencies unless --skip-install\r\n if (!options.skipInstall) {\r\n console.log(\"\\n📥 Installing dependencies...\\n\");\r\n await installDependencies(projectPath, config, packageManager);\r\n }\r\n\r\n // Print success message\r\n printSuccessMessage(config, options.skipInstall);\r\n}\r\n\r\n// ============================================================================\r\n// Package Manager Detection & Installation\r\n// ============================================================================\r\n\r\n/**\r\n * Detect which package manager to use\r\n * Priority: pnpm > yarn > bun > npm (based on lockfile or global availability)\r\n */\r\nfunction detectPackageManager(): PackageManager {\r\n // Check for lockfiles in current directory (user preference)\r\n try {\r\n const cwd = process.cwd();\r\n if (existsSync(path.join(cwd, \"pnpm-lock.yaml\"))) return \"pnpm\";\r\n if (existsSync(path.join(cwd, \"yarn.lock\"))) return \"yarn\";\r\n if (existsSync(path.join(cwd, \"bun.lockb\"))) return \"bun\";\r\n if (existsSync(path.join(cwd, \"package-lock.json\"))) return \"npm\";\r\n } catch {\r\n // Ignore errors\r\n }\r\n\r\n // Check which package managers are available\r\n if (isCommandAvailable(\"pnpm\")) return \"pnpm\";\r\n if (isCommandAvailable(\"yarn\")) return \"yarn\";\r\n if (isCommandAvailable(\"bun\")) return \"bun\";\r\n\r\n // Default to npm\r\n return \"npm\";\r\n}\r\n\r\n/**\r\n * Check if a command is available in PATH\r\n */\r\nfunction isCommandAvailable(command: string): boolean {\r\n try {\r\n execSync(`${command} --version`, { stdio: \"ignore\" });\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * Sync check if file exists (ESM-compatible — no require())\r\n */\r\nfunction existsSync(filePath: string): boolean {\r\n try {\r\n accessSync(filePath);\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * Install dependencies using the detected package manager\r\n */\r\nasync function installDependencies(\r\n projectPath: string,\r\n config: ProjectConfig,\r\n pm: PackageManager,\r\n): Promise<void> {\r\n // Build dependency lists\r\n const deps = [\r\n \"@classytic/arc@latest\",\r\n \"fastify@latest\",\r\n \"@fastify/cors@latest\",\r\n \"@fastify/helmet@latest\",\r\n \"@fastify/rate-limit@latest\",\r\n \"@fastify/sensible@latest\",\r\n \"@fastify/under-pressure@latest\",\r\n \"dotenv@latest\",\r\n ];\r\n\r\n if (config.auth === \"better-auth\") {\r\n deps.push(\"better-auth@latest\", \"mongodb@latest\");\r\n } else {\r\n deps.push(\"@fastify/jwt@latest\", \"bcryptjs@latest\");\r\n }\r\n\r\n if (config.adapter === \"mongokit\") {\r\n deps.push(\"@classytic/mongokit@latest\", \"mongoose@latest\");\r\n }\r\n\r\n const devDeps = [\"vitest@latest\", \"pino-pretty@latest\"];\r\n\r\n if (config.typescript) {\r\n devDeps.push(\"typescript@latest\", \"@types/node@latest\", \"tsx@latest\");\r\n }\r\n\r\n // Build install commands based on package manager\r\n const installCmd = getInstallCommand(pm, deps, false);\r\n const installDevCmd = getInstallCommand(pm, devDeps, true);\r\n\r\n // Run installation\r\n console.log(` Installing dependencies...`);\r\n await runCommand(installCmd, projectPath);\r\n\r\n console.log(` Installing dev dependencies...`);\r\n await runCommand(installDevCmd, projectPath);\r\n\r\n console.log(`\\nDependencies installed successfully.`);\r\n}\r\n\r\n/**\r\n * Get the install command for a package manager\r\n */\r\nfunction getInstallCommand(\r\n pm: PackageManager,\r\n packages: string[],\r\n isDev: boolean,\r\n): string {\r\n const pkgList = packages.join(\" \");\r\n\r\n switch (pm) {\r\n case \"pnpm\":\r\n return `pnpm add ${isDev ? \"-D\" : \"\"} ${pkgList}`;\r\n case \"yarn\":\r\n return `yarn add ${isDev ? \"-D\" : \"\"} ${pkgList}`;\r\n case \"bun\":\r\n return `bun add ${isDev ? \"-d\" : \"\"} ${pkgList}`;\r\n case \"npm\":\r\n default:\r\n return `npm install ${isDev ? \"--save-dev\" : \"\"} ${pkgList}`;\r\n }\r\n}\r\n\r\n/**\r\n * Run a shell command in a directory\r\n */\r\nfunction runCommand(command: string, cwd: string): Promise<void> {\r\n return new Promise((resolve, reject) => {\r\n const isWindows = process.platform === \"win32\";\r\n const shell = isWindows ? \"cmd\" : \"/bin/sh\";\r\n const shellFlag = isWindows ? \"/c\" : \"-c\";\r\n\r\n const child = spawn(shell, [shellFlag, command], {\r\n cwd,\r\n stdio: \"inherit\",\r\n env: { ...process.env, FORCE_COLOR: \"1\" },\r\n });\r\n\r\n child.on(\"close\", (code) => {\r\n if (code === 0) {\r\n resolve();\r\n } else {\r\n reject(new Error(`Command failed with exit code ${code}`));\r\n }\r\n });\r\n\r\n child.on(\"error\", reject);\r\n });\r\n}\r\n\r\n// ============================================================================\r\n// Configuration Gathering\r\n// ============================================================================\r\n\r\nasync function gatherConfig(options: InitOptions): Promise<ProjectConfig> {\r\n const rl = readline.createInterface({\r\n input: process.stdin,\r\n output: process.stdout,\r\n });\r\n\r\n const question = (prompt: string): Promise<string> =>\r\n new Promise((resolve) => rl.question(prompt, resolve));\r\n\r\n // Non-interactive mode: if a project name was provided via args, skip prompts\r\n // and use defaults for any unspecified options\r\n const nonInteractive = !!options.name;\r\n\r\n try {\r\n // Project name\r\n const name =\r\n options.name || (await question(\"Project name: \")) || \"my-arc-app\";\r\n\r\n // Adapter choice\r\n let adapter: \"mongokit\" | \"custom\" = options.adapter || \"mongokit\";\r\n if (!options.adapter && !nonInteractive) {\r\n const adapterChoice = await question(\r\n \"Database adapter [1=MongoKit (recommended), 2=Custom]: \",\r\n );\r\n adapter = adapterChoice === \"2\" ? \"custom\" : \"mongokit\";\r\n }\r\n\r\n // Auth strategy\r\n let auth: \"jwt\" | \"better-auth\" = options.auth || \"better-auth\";\r\n if (!options.auth && !nonInteractive) {\r\n const authChoice = await question(\r\n \"Auth strategy [1=Better Auth (recommended), 2=Arc JWT]: \",\r\n );\r\n auth = authChoice === \"2\" ? \"jwt\" : \"better-auth\";\r\n }\r\n\r\n // Tenant mode\r\n let tenant: \"multi\" | \"single\" = options.tenant || \"single\";\r\n if (!options.tenant && !nonInteractive) {\r\n const tenantChoice = await question(\r\n \"Tenant mode [1=Single-tenant, 2=Multi-tenant]: \",\r\n );\r\n tenant = tenantChoice === \"2\" ? \"multi\" : \"single\";\r\n }\r\n\r\n // TypeScript or JavaScript\r\n let typescript = options.typescript ?? true;\r\n if (options.typescript === undefined && !nonInteractive) {\r\n const tsChoice = await question(\r\n \"Language [1=TypeScript (recommended), 2=JavaScript]: \",\r\n );\r\n typescript = tsChoice !== \"2\";\r\n }\r\n\r\n // Environment/Target choice\r\n let edge = options.edge ?? false;\r\n if (options.edge === undefined && !nonInteractive) {\r\n const edgeChoice = await question(\r\n \"Deployment target [1=Node.js Server (default), 2=Edge/Serverless]: \",\r\n );\r\n edge = edgeChoice === \"2\";\r\n }\r\n\r\n return { name, adapter, auth, tenant, typescript, edge };\r\n } finally {\r\n rl.close();\r\n }\r\n}\r\n\r\n// ============================================================================\r\n// Project Structure Creation\r\n// ============================================================================\r\n\r\nasync function createProjectStructure(\r\n projectPath: string,\r\n config: ProjectConfig,\r\n): Promise<void> {\r\n const ext = config.typescript ? \"ts\" : \"js\";\r\n\r\n // Create directories - Clean architecture (organized by resource, no barrels)\r\n const dirs = [\r\n \"\",\r\n \"src\",\r\n \"src/config\", // Config & env loading (import first!)\r\n \"src/shared\", // Shared utilities (adapters, presets, permissions)\r\n \"src/shared/presets\", // Preset definitions\r\n \"src/plugins\", // App-specific plugins\r\n \"src/resources\", // Resource definitions\r\n ...(config.auth === \"jwt\"\r\n ? [\r\n \"src/resources/user\", // User resource (user.model, user.repository, etc.)\r\n \"src/resources/auth\", // Auth resource (auth.resource, auth.handlers, etc.)\r\n ]\r\n : []),\r\n \"src/resources/example\", // Example resource\r\n \"tests\",\r\n ];\r\n\r\n for (const dir of dirs) {\r\n await fs.mkdir(path.join(projectPath, dir), { recursive: true });\r\n console.log(` + Created: ${dir || \"/\"}`);\r\n }\r\n\r\n // Generate and write files\r\n const files: Record<string, string> = {\r\n \"package.json\": packageJsonTemplate(config),\r\n \".gitignore\": gitignoreTemplate(),\r\n \".env.example\": envExampleTemplate(config),\r\n \".env.dev\": envDevTemplate(config),\r\n \"README.md\": readmeTemplate(config),\r\n };\r\n\r\n // TypeScript config\r\n if (config.typescript) {\r\n files[\"tsconfig.json\"] = tsconfigTemplate();\r\n }\r\n\r\n // Vitest config (always needed for path alias resolution)\r\n files[\"vitest.config.ts\"] = vitestConfigTemplate(config);\r\n\r\n // Config files (env loader FIRST - imported before everything)\r\n files[`src/config/env.${ext}`] = envLoaderTemplate(config);\r\n files[`src/config/index.${ext}`] = configTemplate(config);\r\n\r\n // App factory + Entry point (separation for workers/tests)\r\n files[`src/app.${ext}`] = appTemplate(config);\r\n files[`src/index.${ext}`] = indexTemplate(config);\r\n\r\n // Shared utilities\r\n files[`src/shared/index.${ext}`] = sharedIndexTemplate(config);\r\n files[`src/shared/adapter.${ext}`] =\r\n config.adapter === \"mongokit\"\r\n ? createAdapterTemplate(config)\r\n : customAdapterTemplate(config);\r\n files[`src/shared/permissions.${ext}`] = permissionsTemplate(config);\r\n\r\n // Presets\r\n if (config.tenant === \"multi\") {\r\n files[`src/shared/presets/index.${ext}`] =\r\n presetsMultiTenantTemplate(config);\r\n files[`src/shared/presets/flexible-multi-tenant.${ext}`] =\r\n flexibleMultiTenantPresetTemplate(config);\r\n } else {\r\n files[`src/shared/presets/index.${ext}`] =\r\n presetsSingleTenantTemplate(config);\r\n }\r\n\r\n // Plugins (app-specific, easy to extend)\r\n files[`src/plugins/index.${ext}`] = pluginsIndexTemplate(config);\r\n\r\n // Resources (organized by folder, no barrels - prefixed filenames)\r\n files[`src/resources/index.${ext}`] = resourcesIndexTemplate(config);\r\n\r\n // Auth setup — depends on strategy\r\n if (config.auth === \"better-auth\") {\r\n // Better Auth: single config file, no manual auth handlers\r\n files[`src/auth.${ext}`] = betterAuthSetupTemplate(config);\r\n } else {\r\n // JWT: manual user model + auth handlers\r\n files[`src/resources/user/user.model.${ext}`] = userModelTemplate(config);\r\n files[`src/resources/user/user.repository.${ext}`] =\r\n userRepositoryTemplate(config);\r\n files[`src/resources/user/user.controller.${ext}`] =\r\n userControllerTemplate(config);\r\n files[`src/resources/auth/auth.resource.${ext}`] =\r\n authResourceTemplate(config);\r\n files[`src/resources/auth/auth.handlers.${ext}`] =\r\n authHandlersTemplate(config);\r\n files[`src/resources/auth/auth.schemas.${ext}`] =\r\n authSchemasTemplate(config);\r\n }\r\n\r\n // Example resource (src/resources/example/)\r\n files[`src/resources/example/example.model.${ext}`] =\r\n exampleModelTemplate(config);\r\n files[`src/resources/example/example.repository.${ext}`] =\r\n exampleRepositoryTemplate(config);\r\n files[`src/resources/example/example.resource.${ext}`] =\r\n exampleResourceTemplate(config);\r\n files[`src/resources/example/example.controller.${ext}`] =\r\n exampleControllerTemplate(config);\r\n files[`src/resources/example/example.schemas.${ext}`] =\r\n exampleSchemasTemplate(config);\r\n\r\n // Tests\r\n files[`tests/example.test.${ext}`] = exampleTestTemplate(config);\r\n if (config.auth === \"jwt\") {\r\n files[`tests/auth.test.${ext}`] = authTestTemplate(config);\r\n }\r\n\r\n // Save project config for CLI tools (generate, etc.)\r\n files[\".arcrc\"] =\r\n JSON.stringify(\r\n {\r\n adapter: config.adapter,\r\n auth: config.auth,\r\n tenant: config.tenant,\r\n typescript: config.typescript,\r\n },\r\n null,\r\n 2,\r\n ) + \"\\n\";\r\n\r\n // Write all files\r\n for (const [filePath, content] of Object.entries(files)) {\r\n const fullPath = path.join(projectPath, filePath);\r\n await fs.mkdir(path.dirname(fullPath), { recursive: true });\r\n await fs.writeFile(fullPath, content);\r\n console.log(` + Created: ${filePath}`);\r\n }\r\n}\r\n\r\n// ============================================================================\r\n// Templates\r\n// ============================================================================\r\n\r\nfunction packageJsonTemplate(config: ProjectConfig): string {\r\n // Minimal package.json - dependencies are installed via package manager\r\n const scripts: Record<string, string> = config.typescript\r\n ? {\r\n dev: \"tsx watch src/index.ts\",\r\n build: \"tsc\",\r\n start: \"node dist/index.js\",\r\n test: \"vitest run\",\r\n \"test:watch\": \"vitest\",\r\n }\r\n : {\r\n dev: \"node --watch src/index.js\",\r\n start: \"node src/index.js\",\r\n test: \"vitest run\",\r\n \"test:watch\": \"vitest\",\r\n };\r\n\r\n // Subpath imports for clean DX\r\n const imports: Record<string, string> = config.typescript\r\n ? {\r\n \"#config/*\": \"./dist/config/*\",\r\n \"#shared/*\": \"./dist/shared/*\",\r\n \"#resources/*\": \"./dist/resources/*\",\r\n \"#plugins/*\": \"./dist/plugins/*\",\r\n }\r\n : {\r\n \"#config/*\": \"./src/config/*\",\r\n \"#shared/*\": \"./src/shared/*\",\r\n \"#resources/*\": \"./src/resources/*\",\r\n \"#plugins/*\": \"./src/plugins/*\",\r\n };\r\n\r\n return JSON.stringify(\r\n {\r\n name: config.name,\r\n version: \"1.0.0\",\r\n type: \"module\",\r\n main: config.typescript ? \"dist/index.js\" : \"src/index.js\",\r\n imports,\r\n scripts,\r\n engines: {\r\n node: \">=20\",\r\n },\r\n },\r\n null,\r\n 2,\r\n );\r\n}\r\n\r\nfunction tsconfigTemplate(): string {\r\n return JSON.stringify(\r\n {\r\n compilerOptions: {\r\n target: \"ES2022\",\r\n module: \"NodeNext\",\r\n moduleResolution: \"NodeNext\",\r\n lib: [\"ES2022\"],\r\n outDir: \"./dist\",\r\n rootDir: \"./src\",\r\n strict: true,\r\n esModuleInterop: true,\r\n skipLibCheck: true,\r\n forceConsistentCasingInFileNames: true,\r\n declaration: true,\r\n declarationMap: true,\r\n sourceMap: true,\r\n resolveJsonModule: true,\r\n paths: {\r\n \"#shared/*\": [\"./src/shared/*\"],\r\n \"#resources/*\": [\"./src/resources/*\"],\r\n \"#config/*\": [\"./src/config/*\"],\r\n \"#plugins/*\": [\"./src/plugins/*\"],\r\n },\r\n },\r\n include: [\"src/**/*\"],\r\n exclude: [\"node_modules\", \"dist\"],\r\n },\r\n null,\r\n 2,\r\n );\r\n}\r\n\r\nfunction vitestConfigTemplate(config: ProjectConfig): string {\r\n const srcDir = config.typescript ? \"./src\" : \"./src\";\r\n\r\n return `import { defineConfig } from 'vitest/config';\r\nimport { resolve } from 'path';\r\n\r\nexport default defineConfig({\r\n test: {\r\n globals: true,\r\n environment: 'node',\r\n },\r\n resolve: {\r\n alias: {\r\n '#config': resolve(__dirname, '${srcDir}/config'),\r\n '#shared': resolve(__dirname, '${srcDir}/shared'),\r\n '#resources': resolve(__dirname, '${srcDir}/resources'),\r\n '#plugins': resolve(__dirname, '${srcDir}/plugins'),\r\n },\r\n },\r\n});\r\n`;\r\n}\r\n\r\nfunction gitignoreTemplate(): string {\r\n return `# Dependencies\r\nnode_modules/\r\n\r\n# Build\r\ndist/\r\n*.js.map\r\n\r\n# Environment\r\n.env\r\n.env.local\r\n.env.*.local\r\n\r\n# IDE\r\n.vscode/\r\n.idea/\r\n*.swp\r\n*.swo\r\n\r\n# OS\r\n.DS_Store\r\nThumbs.db\r\n\r\n# Logs\r\n*.log\r\nnpm-debug.log*\r\n\r\n# Test coverage\r\ncoverage/\r\n`;\r\n}\r\n\r\nfunction envExampleTemplate(config: ProjectConfig): string {\r\n let content = `# Server\r\nPORT=8040\r\nHOST=0.0.0.0\r\nNODE_ENV=development\r\n`;\r\n\r\n if (config.auth === \"better-auth\") {\r\n content += `\r\n# Better Auth\r\nBETTER_AUTH_SECRET=your-32-character-minimum-secret-here\r\nFRONTEND_URL=http://localhost:3000\r\n\r\n# Google OAuth (optional)\r\n# GOOGLE_CLIENT_ID=\r\n# GOOGLE_CLIENT_SECRET=\r\n`;\r\n } else {\r\n content += `\r\n# JWT\r\nJWT_SECRET=your-32-character-minimum-secret-here\r\nJWT_EXPIRES_IN=7d\r\n`;\r\n }\r\n\r\n content += `\r\n# CORS - Allowed origins\r\n# Options:\r\n# * = allow all origins (not recommended for production)\r\n# Comma-separated list = specific origins only\r\nCORS_ORIGINS=http://localhost:3000,http://localhost:5173\r\n`;\r\n\r\n if (config.adapter === \"mongokit\") {\r\n content += `\r\n# MongoDB\r\nMONGODB_URI=mongodb://localhost:27017/${config.name}\r\n`;\r\n }\r\n\r\n if (config.tenant === \"multi\") {\r\n content += `\r\n# Multi-tenant\r\nORG_HEADER=x-organization-id\r\n`;\r\n }\r\n\r\n return content;\r\n}\r\n\r\nfunction readmeTemplate(config: ProjectConfig): string {\r\n const ext = config.typescript ? \"ts\" : \"js\";\r\n\r\n return `# ${config.name}\r\n\r\nBuilt with [Arc](https://github.com/classytic/arc) - Resource-Oriented Backend Framework\r\n\r\n## Quick Start\r\n\r\n\\`\\`\\`bash\r\n# Install dependencies\r\nnpm install\r\n\r\n# Start development server (uses .env.dev)\r\nnpm run dev\r\n\r\n# Run tests\r\nnpm test\r\n\\`\\`\\`\r\n\r\n## Project Structure\r\n\r\n\\`\\`\\`\r\nsrc/\r\n├── config/ # Configuration (loaded first)\r\n│ ├── env.${ext} # Env loader (import first!)\r\n│ └── index.${ext} # App config\r\n├── shared/ # Shared utilities\r\n│ ├── adapter.${ext} # ${config.adapter === \"mongokit\" ? \"MongoKit adapter factory\" : \"Custom adapter\"}\r\n│ ├── permissions.${ext} # Permission helpers\r\n│ └── presets/ # ${config.tenant === \"multi\" ? \"Multi-tenant presets\" : \"Standard presets\"}\r\n├── plugins/ # App-specific plugins\r\n│ └── index.${ext} # Plugin registry\r\n├── resources/ # API Resources\r\n│ ├── index.${ext} # Resource registry\r\n│ └── example/ # Example resource\r\n│ ├── index.${ext} # Resource definition\r\n│ ├── model.${ext} # Mongoose schema\r\n│ └── repository.${ext} # MongoKit repository\r\n├── app.${ext} # App factory (reusable)\r\n└── index.${ext} # Server entry point\r\ntests/\r\n└── example.test.${ext} # Example tests\r\n\\`\\`\\`\r\n\r\n## Architecture\r\n\r\n### Entry Points\r\n\r\n- **\\`src/index.${ext}\\`** - HTTP server entry point\r\n- **\\`src/app.${ext}\\`** - App factory (import for workers/tests)\r\n\r\n\\`\\`\\`${config.typescript ? \"typescript\" : \"javascript\"}\r\n// For workers or custom entry points:\r\nimport { createAppInstance } from './app.js';\r\n\r\nconst app = await createAppInstance();\r\n// Use app for your worker logic\r\n\\`\\`\\`\r\n\r\n### Adding Resources\r\n\r\n1. Create a new folder in \\`src/resources/\\`:\r\n\r\n\\`\\`\\`\r\nsrc/resources/product/\r\n├── index.${ext} # Resource definition\r\n├── model.${ext} # Mongoose schema\r\n└── repository.${ext} # MongoKit repository\r\n\\`\\`\\`\r\n\r\n2. Register in \\`src/resources/index.${ext}\\`:\r\n\r\n\\`\\`\\`${config.typescript ? \"typescript\" : \"javascript\"}\r\nimport productResource from './product/index.js';\r\n\r\nexport const resources = [\r\n exampleResource,\r\n productResource, // Add here\r\n];\r\n\\`\\`\\`\r\n\r\n### Adding Plugins\r\n\r\nAdd custom plugins in \\`src/plugins/index.${ext}\\`:\r\n\r\n\\`\\`\\`${config.typescript ? \"typescript\" : \"javascript\"}\r\nexport async function registerPlugins(app, deps) {\r\n const { config } = deps; // Explicit dependency injection\r\n\r\n await app.register(myCustomPlugin, { ...options });\r\n}\r\n\\`\\`\\`\r\n\r\n## CLI Commands\r\n\r\n\\`\\`\\`bash\r\n# Generate a new resource\r\narc generate resource product\r\n\r\n# Introspect existing schema\r\narc introspect\r\n\r\n# Generate API docs\r\narc docs\r\n\\`\\`\\`\r\n\r\n## Environment Files\r\n\r\n- \\`.env.development\\` / \\`.env.dev\\` - Development (default)\r\n- \\`.env.test\\` / \\`.env.qa\\` - Testing / QA\r\n- \\`.env.production\\` / \\`.env.prod\\` - Production\r\n- \\`.env\\` - Fallback\r\n\r\n## API Documentation\r\n\r\nAPI documentation is available via Scalar UI:\r\n\r\n- **Interactive UI**: [http://localhost:8040/docs](http://localhost:8040/docs)\r\n- **OpenAPI Spec**: [http://localhost:8040/_docs/openapi.json](http://localhost:8040/_docs/openapi.json)\r\n\r\n## API Endpoints\r\n\r\n| Method | Endpoint | Description |\r\n|--------|----------|-------------|\r\n| GET | /docs | API documentation (Scalar UI) |\r\n| GET | /_docs/openapi.json | OpenAPI 3.0 spec |\r\n| GET | /examples | List all |\r\n| GET | /examples/:id | Get by ID |\r\n| POST | /examples | Create |\r\n| PATCH | /examples/:id | Update |\r\n| DELETE | /examples/:id | Delete |\r\n`;\r\n}\r\n\r\nfunction indexTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n\r\n return `/**\r\n * ${config.name} - Server Entry Point\r\n * Generated by Arc CLI\r\n *\r\n * This file starts the HTTP server.\r\n * For workers or other entry points, import createAppInstance from './app.js'\r\n */\r\n\r\n// Load environment FIRST (before any other imports)\r\nimport '#config/env.js';\r\n\r\nimport config from '#config/index.js';\r\n${config.adapter === \"mongokit\" ? \"import mongoose from 'mongoose';\" : \"\"}\r\nimport { createAppInstance } from './app.js';\r\n\r\nasync function main()${ts ? \": Promise<void>\" : \"\"} {\r\n console.log(\\`Environment: \\${config.env}\\`);\r\n${\r\n config.adapter === \"mongokit\"\r\n ? `\r\n // Connect to MongoDB\r\n await mongoose.connect(config.database.uri);\r\n console.log('Connected to MongoDB');\r\n`\r\n : \"\"\r\n}\r\n // Create and configure app\r\n const app = await createAppInstance();\r\n\r\n // Start server\r\n await app.listen({ port: config.server.port, host: config.server.host });\r\n console.log(\\`Server running at http://\\${config.server.host}:\\${config.server.port}\\`);\r\n}\r\n\r\nmain().catch((err) => {\r\n console.error('Failed to start server:', err);\r\n process.exit(1);\r\n});\r\n`;\r\n}\r\n\r\nfunction appTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n const typeImport = ts\r\n ? \"import type { FastifyInstance } from 'fastify';\\n\"\r\n : \"\";\r\n\r\n const betterAuthImport =\r\n config.auth === \"better-auth\"\r\n ? `import { createBetterAuthAdapter } from '@classytic/arc/auth';\r\nimport { getAuth } from './auth.js';\r\n`\r\n : \"\";\r\n\r\n const authConfig =\r\n config.auth === \"better-auth\"\r\n ? config.tenant === \"multi\"\r\n ? `auth: { type: 'betterAuth', betterAuth: createBetterAuthAdapter({ auth: getAuth(), orgContext: true }) },`\r\n : `auth: { type: 'betterAuth', betterAuth: createBetterAuthAdapter({ auth: getAuth() }) },`\r\n : `auth: {\r\n type: 'jwt',\r\n jwt: { secret: config.jwt.secret },\r\n },`;\r\n\r\n return `/**\r\n * ${config.name} - App Factory\r\n * Generated by Arc CLI\r\n *\r\n * Creates and configures the Fastify app instance.\r\n * Can be imported by:\r\n * - index.ts (HTTP server)\r\n * - worker.ts (background workers)\r\n * - tests (integration tests)\r\n */\r\n\r\n${typeImport}import config from '#config/index.js';\r\nimport { createApp } from '@classytic/arc/factory';\r\n${betterAuthImport}\r\n// App-specific plugins\r\nimport { registerPlugins } from '#plugins/index.js';\r\n\r\n// Resource registry\r\nimport { registerResources } from '#resources/index.js';\r\n\r\n/**\r\n * Create a fully configured app instance\r\n *\r\n * @returns Configured Fastify instance ready to use\r\n */\r\nexport async function createAppInstance()${ts ? \": Promise<FastifyInstance>\" : \"\"} {\r\n // Create Arc app with base configuration\r\n const app = await createApp({\r\n preset: config.env === 'production' ? (${config.edge ? \"'edge'\" : \"'production'\"}) : 'development',\r\n ${authConfig}\r\n cors: {\r\n origin: config.cors.origins,\r\n methods: config.cors.methods,\r\n allowedHeaders: config.cors.allowedHeaders,\r\n credentials: config.cors.credentials,\r\n },\r\n trustProxy: true,\r\n });\r\n\r\n // Register app-specific plugins (explicit dependency injection)\r\n await registerPlugins(app, { config });\r\n\r\n // Register all resources\r\n await registerResources(app);\r\n\r\n return app;\r\n}\r\n\r\nexport default createAppInstance;\r\n`;\r\n}\r\n\r\nfunction envLoaderTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n\r\n return `/**\r\n * Environment Loader\r\n *\r\n * MUST be imported FIRST before any other imports.\r\n * Loads .env files based on NODE_ENV.\r\n *\r\n * Usage:\r\n * import './config/env.js'; // First line of entry point\r\n */\r\n\r\nimport dotenv from 'dotenv';\r\nimport { existsSync } from 'node:fs';\r\nimport { resolve } from 'node:path';\r\n\r\n/**\r\n * Normalize environment string to short form\r\n */\r\nfunction normalizeEnv(env${ts ? \": string | undefined\" : \"\"})${ts ? \": string\" : \"\"} {\r\n const normalized = (env || '').toLowerCase();\r\n if (normalized === 'production' || normalized === 'prod') return 'prod';\r\n if (normalized === 'test' || normalized === 'qa') return 'test';\r\n return 'dev';\r\n}\r\n\r\n// Determine environment\r\nconst env = normalizeEnv(process.env.NODE_ENV);\r\n\r\n// Load environment-specific .env file\r\nconst envFile = resolve(process.cwd(), \\`.env.\\${env}\\`);\r\nconst defaultEnvFile = resolve(process.cwd(), '.env');\r\n\r\nif (existsSync(envFile)) {\r\n dotenv.config({ path: envFile });\r\n console.log(\\`Loaded: .env.\\${env}\\`);\r\n} else if (existsSync(defaultEnvFile)) {\r\n dotenv.config({ path: defaultEnvFile });\r\n console.log('Loaded: .env');\r\n} else {\r\n console.warn('Warning: No .env file found');\r\n}\r\n\r\n// Export for reference\r\nexport const ENV = env;\r\n`;\r\n}\r\n\r\nfunction envDevTemplate(config: ProjectConfig): string {\r\n let content = `# Development Environment\r\nNODE_ENV=development\r\n\r\n# Server\r\nPORT=8040\r\nHOST=0.0.0.0\r\n`;\r\n\r\n if (config.auth === \"better-auth\") {\r\n content += `\r\n# Better Auth\r\nBETTER_AUTH_SECRET=dev-secret-change-in-production-min-32-chars\r\nFRONTEND_URL=http://localhost:3000\r\n\r\n# Google OAuth (optional — leave empty to disable)\r\nGOOGLE_CLIENT_ID=\r\nGOOGLE_CLIENT_SECRET=\r\n`;\r\n } else {\r\n content += `\r\n# JWT\r\nJWT_SECRET=dev-secret-change-in-production-min-32-chars\r\nJWT_EXPIRES_IN=7d\r\n`;\r\n }\r\n\r\n content += `\r\n# CORS - Allowed origins\r\n# Options:\r\n# * = allow all origins (not recommended for production)\r\n# Comma-separated list = specific origins only\r\nCORS_ORIGINS=http://localhost:3000,http://localhost:5173\r\n`;\r\n\r\n if (config.adapter === \"mongokit\") {\r\n content += `\r\n# MongoDB\r\nMONGODB_URI=mongodb://localhost:27017/${config.name}\r\n`;\r\n }\r\n\r\n if (config.tenant === \"multi\") {\r\n content += `\r\n# Multi-tenant\r\nORG_HEADER=x-organization-id\r\n`;\r\n }\r\n\r\n return content;\r\n}\r\n\r\nfunction pluginsIndexTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n const typeImport = ts\r\n ? \"import type { FastifyInstance } from 'fastify';\\n\"\r\n : \"\";\r\n const configType = ts ? \": { config: AppConfig }\" : \"\";\r\n const appType = ts ? \": FastifyInstance\" : \"\";\r\n\r\n let content = `/**\r\n * App Plugins Registry\r\n *\r\n * Register your app-specific plugins here.\r\n * Dependencies are passed explicitly (no shims, no magic).\r\n */\r\n\r\n${typeImport}${ts ? \"import type { AppConfig } from '../config/index.js';\\n\" : \"\"}import { openApiPlugin, scalarPlugin } from '@classytic/arc/docs';\r\nimport { errorHandlerPlugin } from '@classytic/arc/plugins';\r\n`;\r\n\r\n content += `\r\n/**\r\n * Register all app-specific plugins\r\n *\r\n * @param app - Fastify instance\r\n * @param deps - Explicit dependencies (config, services, etc.)\r\n */\r\nexport async function registerPlugins(\r\n app${appType},\r\n deps${configType}\r\n)${ts ? \": Promise<void>\" : \"\"} {\r\n const { config } = deps;\r\n\r\n // Error handling (CastError → 400, validation → 422, duplicate → 409)\r\n await app.register(errorHandlerPlugin, {\r\n includeStack: config.isDev,\r\n });\r\n\r\n // API Documentation (Scalar UI)\r\n // OpenAPI spec: /_docs/openapi.json\r\n // Scalar UI: /docs\r\n await app.register(openApiPlugin, {\r\n title: '${config.name} API',\r\n version: '1.0.0',\r\n description: 'API documentation for ${config.name}',\r\n apiPrefix: '/api',\r\n });\r\n await app.register(scalarPlugin, {\r\n routePrefix: '/docs',\r\n theme: 'default',\r\n });\r\n\r\n // Add your custom plugins here:\r\n // await app.register(myCustomPlugin, { ...options });\r\n}\r\n`;\r\n\r\n return content;\r\n}\r\n\r\nfunction resourcesIndexTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n const typeImport = ts\r\n ? \"import type { FastifyInstance } from 'fastify';\\n\"\r\n : \"\";\r\n const appType = ts ? \": FastifyInstance\" : \"\";\r\n\r\n const authImports =\r\n config.auth === \"jwt\"\r\n ? `\r\n// Auth resources (register, login, /users/me)\r\nimport { authResource, userProfileResource } from './auth/auth.resource.js';\r\n`\r\n : `\r\n// Auth is handled by Better Auth — routes at /api/auth/*\r\n// No manual auth resource needed.\r\n`;\r\n\r\n const authResources =\r\n config.auth === \"jwt\"\r\n ? ` authResource,\r\n userProfileResource,\r\n `\r\n : ` `;\r\n\r\n return `/**\r\n * Resources Registry\r\n *\r\n * Central registry for all API resources.\r\n * All resources are mounted under /api prefix via Fastify scoping.\r\n */\r\n\r\n${typeImport}${authImports}\r\n// App resources\r\nimport exampleResource from './example/example.resource.js';\r\n\r\n// Add more resources here:\r\n// import productResource from './product/product.resource.js';\r\n\r\n/**\r\n * All registered resources\r\n */\r\nexport const resources = [\r\n${authResources}exampleResource,\r\n]${ts ? \" as const\" : \"\"};\r\n\r\n/**\r\n * Register all resources with the app under a common prefix.\r\n * Fastify scoping ensures all routes are mounted at /api/*.\r\n * The apiPrefix option in openApiPlugin keeps OpenAPI docs in sync.\r\n */\r\nexport async function registerResources(app${appType}, prefix = '/api')${ts ? \": Promise<void>\" : \"\"} {\r\n await app.register(async (scope) => {\r\n for (const resource of resources) {\r\n await scope.register(resource.toPlugin());\r\n }\r\n }, { prefix });\r\n}\r\n`;\r\n}\r\n\r\nfunction sharedIndexTemplate(_config: ProjectConfig): string {\r\n return `/**\r\n * Shared Utilities\r\n *\r\n * Central exports for resource definitions.\r\n * Import from here for clean, consistent code.\r\n */\r\n\r\n// Adapter factory\r\nexport { createAdapter } from './adapter.js';\r\n\r\n// Core Arc exports\r\nexport { createMongooseAdapter, defineResource } from '@classytic/arc';\r\n\r\n// Permission helpers (core + application-level)\r\nexport * from './permissions.js';\r\n\r\n// Presets\r\nexport * from './presets/index.js';\r\n`;\r\n}\r\n\r\nfunction createAdapterTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n\r\n return `/**\r\n * MongoKit Adapter Factory\r\n *\r\n * Creates Arc adapters using MongoKit repositories.\r\n * The repository handles query parsing via MongoKit's built-in QueryParser.\r\n */\r\n\r\nimport { createMongooseAdapter } from '@classytic/arc';\r\n${ts ? \"import type { Model } from 'mongoose';\\nimport type { Repository } from '@classytic/mongokit';\" : \"\"}\r\n\r\n/**\r\n * Create a MongoKit-powered adapter for a resource\r\n *\r\n * Note: Query parsing is handled by MongoKit's Repository class.\r\n * Just pass the model and repository - Arc handles the rest.\r\n */\r\nexport function createAdapter${ts ? \"<TDoc = any>\" : \"\"}(\r\n model${ts ? \": Model<TDoc>\" : \"\"},\r\n repository${ts ? \": Repository<TDoc>\" : \"\"}\r\n) {\r\n return createMongooseAdapter({\r\n model,\r\n repository,\r\n });\r\n}\r\n`;\r\n}\r\n\r\nfunction customAdapterTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n\r\n return `/**\r\n * Custom Adapter Factory\r\n *\r\n * Implement your own database adapter here.\r\n */\r\n\r\nimport { createMongooseAdapter } from '@classytic/arc';\r\n${ts ? \"import type { Model } from 'mongoose';\" : \"\"}\r\n\r\n/**\r\n * Create a custom adapter for a resource\r\n *\r\n * Implement this based on your database choice:\r\n * - Prisma: Use @classytic/prismakit (coming soon)\r\n * - Drizzle: Create custom adapter\r\n * - Raw SQL: Create custom adapter\r\n */\r\nexport function createAdapter${ts ? \"<TDoc>\" : \"\"}(\r\n model${ts ? \": Model<TDoc>\" : \"\"},\r\n repository${ts ? \": any\" : \"\"}\r\n)${ts ? \": ReturnType<typeof createMongooseAdapter>\" : \"\"} {\r\n // SCAFFOLD: Replace with your custom adapter implementation\r\n return createMongooseAdapter({\r\n model,\r\n repository,\r\n });\r\n}\r\n`;\r\n}\r\n\r\nfunction presetsMultiTenantTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n\r\n return `/**\r\n * Arc Presets - Multi-Tenant Configuration\r\n *\r\n * Pre-configured presets for multi-tenant applications.\r\n * Includes both strict and flexible tenant isolation options.\r\n */\r\n\r\nimport {\r\n multiTenantPreset,\r\n ownedByUserPreset,\r\n softDeletePreset,\r\n slugLookupPreset,\r\n} from '@classytic/arc/presets';\r\n\r\n// Flexible preset for mixed public/private routes\r\nexport { flexibleMultiTenantPreset } from './flexible-multi-tenant.js';\r\n\r\n/**\r\n * Organization-scoped preset (STRICT)\r\n * Always requires auth, always filters by organizationId.\r\n * Use for admin-only resources.\r\n */\r\nexport const orgScoped = multiTenantPreset({\r\n tenantField: 'organizationId',\r\n});\r\n\r\n/**\r\n * Owned by creator preset\r\n * Filters queries by createdBy field.\r\n */\r\nexport const ownedByCreator = ownedByUserPreset({\r\n ownerField: 'createdBy',\r\n});\r\n\r\n/**\r\n * Owned by user preset\r\n * For resources where userId references the owner.\r\n */\r\nexport const ownedByUser = ownedByUserPreset({\r\n ownerField: 'userId',\r\n});\r\n\r\n/**\r\n * Soft delete preset\r\n * Adds deletedAt filtering and restore endpoint.\r\n */\r\nexport const softDelete = softDeletePreset();\r\n\r\n/**\r\n * Slug lookup preset\r\n * Enables GET by slug in addition to ID.\r\n */\r\nexport const slugLookup = slugLookupPreset();\r\n\r\n// Export all presets\r\nexport const presets = {\r\n orgScoped,\r\n ownedByCreator,\r\n ownedByUser,\r\n softDelete,\r\n slugLookup,\r\n}${ts ? \" as const\" : \"\"};\r\n\r\nexport default presets;\r\n`;\r\n}\r\n\r\nfunction presetsSingleTenantTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n\r\n return `/**\r\n * Arc Presets - Single-Tenant Configuration\r\n *\r\n * Pre-configured presets for single-tenant applications.\r\n */\r\n\r\nimport {\r\n ownedByUserPreset,\r\n softDeletePreset,\r\n slugLookupPreset,\r\n} from '@classytic/arc/presets';\r\n\r\n/**\r\n * Owned by creator preset\r\n * Filters queries by createdBy field.\r\n */\r\nexport const ownedByCreator = ownedByUserPreset({\r\n ownerField: 'createdBy',\r\n});\r\n\r\n/**\r\n * Owned by user preset\r\n * For resources where userId references the owner.\r\n */\r\nexport const ownedByUser = ownedByUserPreset({\r\n ownerField: 'userId',\r\n});\r\n\r\n/**\r\n * Soft delete preset\r\n * Adds deletedAt filtering and restore endpoint.\r\n */\r\nexport const softDelete = softDeletePreset();\r\n\r\n/**\r\n * Slug lookup preset\r\n * Enables GET by slug in addition to ID.\r\n */\r\nexport const slugLookup = slugLookupPreset();\r\n\r\n// Export all presets\r\nexport const presets = {\r\n ownedByCreator,\r\n ownedByUser,\r\n softDelete,\r\n slugLookup,\r\n}${ts ? \" as const\" : \"\"};\r\n\r\nexport default presets;\r\n`;\r\n}\r\n\r\nfunction flexibleMultiTenantPresetTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n const typeAnnotations = ts\r\n ? `\r\nimport { getOrgId, isElevated, isMember } from '@classytic/arc/scope';\r\nimport type { RequestScope } from '@classytic/arc/scope';\r\n\r\ninterface FlexibleMultiTenantOptions {\r\n tenantField?: string;\r\n}\r\n\r\ninterface PresetMiddlewares {\r\n list: ((request: any, reply: any) => Promise<void>)[];\r\n get: ((request: any, reply: any) => Promise<void>)[];\r\n create: ((request: any, reply: any) => Promise<void>)[];\r\n update: ((request: any, reply: any) => Promise<void>)[];\r\n delete: ((request: any, reply: any) => Promise<void>)[];\r\n}\r\n\r\ninterface Preset {\r\n [key: string]: unknown;\r\n name: string;\r\n middlewares: PresetMiddlewares;\r\n}\r\n`\r\n : `\r\nconst { getOrgId, isElevated, isMember } = require('@classytic/arc/scope');\r\n`;\r\n\r\n return `/**\r\n * Flexible Multi-Tenant Preset\r\n *\r\n * Smarter tenant filtering that works with public + authenticated routes.\r\n *\r\n * Philosophy:\r\n * - No org scope → No filtering (public data, all orgs)\r\n * - Org scope present → Filter by org\r\n * - Elevated scope → No filter (platform admin sees all)\r\n *\r\n * Uses request.scope (RequestScope) from Arc's scope system.\r\n */\r\n${typeAnnotations}\r\n/**\r\n * Create flexible tenant filter middleware.\r\n * Only filters when org context is present.\r\n */\r\nfunction createFlexibleTenantFilter(tenantField${ts ? \": string\" : \"\"}) {\r\n return async (request${ts ? \": any\" : \"\"}, reply${ts ? \": any\" : \"\"}) => {\r\n const scope${ts ? \": RequestScope\" : \"\"} = request.scope ?? { kind: 'public' };\r\n\r\n // Elevated scope — platform admin sees all, no filter\r\n if (isElevated(scope)) {\r\n request.log?.debug?.({ msg: 'Elevated scope — no tenant filter' });\r\n return;\r\n }\r\n\r\n // Member scope — filter by org\r\n if (isMember(scope)) {\r\n request.query = request.query ?? {};\r\n request.query._policyFilters = {\r\n ...(request.query._policyFilters ?? {}),\r\n [tenantField]: scope.organizationId,\r\n };\r\n request.log?.debug?.({ msg: 'Tenant filter applied', orgId: scope.organizationId, tenantField });\r\n return;\r\n }\r\n\r\n // Public / authenticated — no org context, show all data (public routes)\r\n request.log?.debug?.({ msg: 'No org context — showing all data' });\r\n };\r\n}\r\n\r\n/**\r\n * Create tenant injection middleware.\r\n * Injects tenant ID into request body on create.\r\n */\r\nfunction createTenantInjection(tenantField${ts ? \": string\" : \"\"}) {\r\n return async (request${ts ? \": any\" : \"\"}, reply${ts ? \": any\" : \"\"}) => {\r\n const scope${ts ? \": RequestScope\" : \"\"} = request.scope ?? { kind: 'public' };\r\n const orgId = getOrgId(scope);\r\n\r\n // Fail-closed: Require orgId for create operations\r\n if (!orgId) {\r\n return reply.code(403).send({\r\n success: false,\r\n error: 'Forbidden',\r\n message: 'Organization context required to create resources',\r\n });\r\n }\r\n\r\n if (request.body) {\r\n request.body[tenantField] = orgId;\r\n }\r\n };\r\n}\r\n\r\n/**\r\n * Flexible Multi-Tenant Preset\r\n *\r\n * @param options.tenantField - Field name in database (default: 'organizationId')\r\n */\r\nexport function flexibleMultiTenantPreset(options${ts ? \": FlexibleMultiTenantOptions = {}\" : \" = {}\"})${ts ? \": Preset\" : \"\"} {\r\n const { tenantField = 'organizationId' } = options;\r\n\r\n const tenantFilter = createFlexibleTenantFilter(tenantField);\r\n const tenantInjection = createTenantInjection(tenantField);\r\n\r\n return {\r\n name: 'flexibleMultiTenant',\r\n middlewares: {\r\n list: [tenantFilter],\r\n get: [tenantFilter],\r\n create: [tenantInjection],\r\n update: [tenantFilter],\r\n delete: [tenantFilter],\r\n },\r\n };\r\n}\r\n\r\nexport default flexibleMultiTenantPreset;\r\n`;\r\n}\r\n\r\nfunction permissionsTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n const typeImport = ts ? \",\\n type PermissionCheck,\" : \"\";\r\n const returnType = ts ? \": PermissionCheck\" : \"\";\r\n\r\n let content = `/**\r\n * Permission Helpers\r\n *\r\n * Clean, type-safe permission definitions for resources.\r\n */\r\n\r\nimport {\r\n requireAuth,\r\n requireRoles,\r\n requireOwnership,\r\n allowPublic,\r\n anyOf,\r\n allOf,\r\n denyAll,\r\n when${typeImport}\r\n} from '@classytic/arc/permissions';\r\n\r\n// Re-export core helpers\r\nexport {\r\n allowPublic,\r\n requireAuth,\r\n requireRoles,\r\n requireOwnership,\r\n allOf,\r\n anyOf,\r\n denyAll,\r\n when,\r\n};\r\n\r\n// ============================================================================\r\n// Permission Helpers\r\n// ============================================================================\r\n\r\n/**\r\n * Require any authenticated user\r\n */\r\nexport const requireAuthenticated = ()${returnType} =>\r\n requireRoles(['user', 'admin', 'superadmin']);\r\n\r\n/**\r\n * Require admin or superadmin\r\n */\r\nexport const requireAdmin = ()${returnType} =>\r\n requireRoles(['admin', 'superadmin']);\r\n\r\n/**\r\n * Require superadmin only\r\n */\r\nexport const requireSuperadmin = ()${returnType} =>\r\n requireRoles(['superadmin']);\r\n`;\r\n\r\n if (config.tenant === \"multi\") {\r\n if (config.auth === \"better-auth\") {\r\n // Better Auth: use requireOrgRole() which checks per-org member.role\r\n content += `\r\n// ============================================================================\r\n// Better Auth Organization & Team Permission Helpers\r\n// ============================================================================\r\n\r\n/**\r\n * Organization-level guards (per-org member.role):\r\n *\r\n * - requireOrgRole(['admin','owner']) — checks member.role in active org\r\n * - requireOrgMembership() — just checks if user is in the org (any role)\r\n * - requireTeamMembership() — checks if user is in the active team\r\n *\r\n * These are DIFFERENT from platform-level helpers above (requireRoles checks user.roles).\r\n * Platform superadmin automatically bypasses all org role checks.\r\n *\r\n * IMPORTANT: When using Better Auth's Access Control (ac) with custom roles,\r\n * you MUST define ALL roles (owner, admin, member, + any custom) using the\r\n * same AC instance. BA's built-in defaults won't cover custom statements.\r\n * Omitting any role causes BA's hasPermission to fail silently for that role.\r\n *\r\n * @see multi-org-betterauth boilerplate (src/shared/access-control.ts) for the recommended pattern.\r\n */\r\nimport {\r\n requireOrgMembership,\r\n requireOrgRole,\r\n requireTeamMembership,\r\n} from '@classytic/arc/permissions';\r\nexport { requireOrgMembership, requireOrgRole, requireTeamMembership };\r\n\r\n/**\r\n * Require organization owner (checks member.role, not user.roles)\r\n */\r\nexport const requireOrgOwner = ()${returnType} =>\r\n requireOrgRole(['owner']);\r\n\r\n/**\r\n * Require organization manager or higher (checks member.role, not user.roles)\r\n */\r\nexport const requireOrgManager = ()${returnType} =>\r\n requireOrgRole(['manager', 'admin', 'owner']);\r\n\r\n/**\r\n * Require any organization member (any role)\r\n */\r\nexport const requireOrgStaff = ()${returnType} =>\r\n requireOrgMembership();\r\n`;\r\n } else {\r\n // JWT: no BA org plugin — use requireRoles() with user.roles\r\n content += `\r\n/**\r\n * Require organization owner (elevated scope auto-bypasses)\r\n */\r\nexport const requireOrgOwner = ()${returnType} =>\r\n requireRoles(['owner', 'admin', 'superadmin']);\r\n\r\n/**\r\n * Require organization manager or higher\r\n */\r\nexport const requireOrgManager = ()${returnType} =>\r\n requireRoles(['owner', 'manager', 'admin', 'superadmin']);\r\n\r\n/**\r\n * Require organization staff (any org member)\r\n */\r\nexport const requireOrgStaff = ()${returnType} =>\r\n requireRoles(['owner', 'manager', 'staff', 'admin', 'superadmin']);\r\n`;\r\n }\r\n }\r\n\r\n content += `\r\n// ============================================================================\r\n// Standard Permission Sets\r\n// ============================================================================\r\n\r\n/**\r\n * Public read, authenticated write (default for most resources)\r\n */\r\nexport const publicReadPermissions = {\r\n list: allowPublic(),\r\n get: allowPublic(),\r\n create: requireAuthenticated(),\r\n update: requireAuthenticated(),\r\n delete: requireAuthenticated(),\r\n};\r\n\r\n/**\r\n * All operations require authentication\r\n */\r\nexport const authenticatedPermissions = {\r\n list: requireAuth(),\r\n get: requireAuth(),\r\n create: requireAuth(),\r\n update: requireAuth(),\r\n delete: requireAuth(),\r\n};\r\n\r\n/**\r\n * Admin only permissions\r\n */\r\nexport const adminPermissions = {\r\n list: requireAdmin(),\r\n get: requireAdmin(),\r\n create: requireSuperadmin(),\r\n update: requireSuperadmin(),\r\n delete: requireSuperadmin(),\r\n};\r\n`;\r\n\r\n if (config.tenant === \"multi\") {\r\n content += `\r\n/**\r\n * Organization staff permissions\r\n */\r\nexport const orgStaffPermissions = {\r\n list: requireOrgStaff(),\r\n get: requireOrgStaff(),\r\n create: requireOrgManager(),\r\n update: requireOrgManager(),\r\n delete: requireOrgOwner(),\r\n};\r\n`;\r\n\r\n if (config.auth === \"better-auth\") {\r\n content += `\r\n/**\r\n * Team-scoped permissions (requires active team)\r\n * Uses Better Auth's team membership — flat groups, no team-level roles.\r\n */\r\nexport const teamScopedPermissions = {\r\n list: requireTeamMembership(),\r\n get: requireTeamMembership(),\r\n create: requireTeamMembership(),\r\n update: requireTeamMembership(),\r\n delete: requireOrgOwner(),\r\n};\r\n`;\r\n }\r\n }\r\n\r\n return content;\r\n}\r\n\r\nfunction configTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n\r\n const authTypeBlock =\r\n config.auth === \"better-auth\"\r\n ? `\r\n betterAuth: {\r\n secret: string;\r\n };\r\n frontend: {\r\n url: string;\r\n };`\r\n : `\r\n jwt: {\r\n secret: string;\r\n expiresIn: string;\r\n };`;\r\n\r\n let typeDefinition = \"\";\r\n if (ts) {\r\n typeDefinition = `\r\nexport interface AppConfig {\r\n env: string;\r\n isDev: boolean;\r\n isProd: boolean;\r\n server: {\r\n port: number;\r\n host: string;\r\n };${authTypeBlock}\r\n cors: {\r\n origins: string[] | boolean; // true = allow all ('*')\r\n methods: string[];\r\n allowedHeaders: string[];\r\n credentials: boolean;\r\n };${\r\n config.adapter === \"mongokit\"\r\n ? `\r\n database: {\r\n uri: string;\r\n };`\r\n : \"\"\r\n }${\r\n config.tenant === \"multi\"\r\n ? `\r\n org: {\r\n header: string;\r\n };`\r\n : \"\"\r\n }\r\n}\r\n`;\r\n }\r\n\r\n const authConfigBlock =\r\n config.auth === \"better-auth\"\r\n ? `\r\n betterAuth: {\r\n secret: process.env.BETTER_AUTH_SECRET || 'dev-secret-change-in-production-min-32-chars',\r\n },\r\n\r\n frontend: {\r\n url: process.env.FRONTEND_URL || 'http://localhost:3000',\r\n },`\r\n : `\r\n jwt: {\r\n secret: process.env.JWT_SECRET || 'dev-secret-change-in-production-min-32',\r\n expiresIn: process.env.JWT_EXPIRES_IN || '7d',\r\n },`;\r\n\r\n return `/**\r\n * Application Configuration\r\n *\r\n * All config is loaded from environment variables.\r\n * ENV file is loaded by config/env.ts (imported first in entry points).\r\n */\r\n${typeDefinition}\r\nconst config${ts ? \": AppConfig\" : \"\"} = {\r\n env: process.env.NODE_ENV || 'development',\r\n isDev: (process.env.NODE_ENV || 'development') !== 'production',\r\n isProd: process.env.NODE_ENV === 'production',\r\n\r\n server: {\r\n port: parseInt(process.env.PORT || '8040', 10),\r\n host: process.env.HOST || '0.0.0.0',\r\n },\r\n${authConfigBlock}\r\n\r\n cors: {\r\n // '*' = allow all origins (true), otherwise comma-separated list\r\n origins:\r\n process.env.CORS_ORIGINS === '*'\r\n ? true\r\n : (process.env.CORS_ORIGINS || 'http://localhost:3000').split(','),\r\n methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],\r\n allowedHeaders: ['Content-Type', 'Authorization', 'x-organization-id', 'x-request-id'],\r\n credentials: true,\r\n },\r\n${\r\n config.adapter === \"mongokit\"\r\n ? `\r\n database: {\r\n uri: process.env.MONGODB_URI || 'mongodb://localhost:27017/${config.name}',\r\n },\r\n`\r\n : \"\"\r\n}${\r\n config.tenant === \"multi\"\r\n ? `\r\n org: {\r\n header: process.env.ORG_HEADER || 'x-organization-id',\r\n },\r\n`\r\n : \"\"\r\n }};\r\n\r\nexport default config;\r\n`;\r\n}\r\n\r\nfunction exampleModelTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n const typeExport = ts\r\n ? `\r\nexport type ExampleDocument = mongoose.InferSchemaType<typeof exampleSchema>;\r\nexport type ExampleModel = mongoose.Model<ExampleDocument>;\r\n`\r\n : \"\";\r\n\r\n return `/**\r\n * Example Model\r\n * Generated by Arc CLI\r\n */\r\n\r\nimport mongoose from 'mongoose';\r\n\r\nconst exampleSchema = new mongoose.Schema(\r\n {\r\n name: { type: String, required: true, trim: true },\r\n description: { type: String, trim: true },\r\n isActive: { type: Boolean, default: true, index: true },\r\n${config.tenant === \"multi\" ? \" organizationId: { type: mongoose.Schema.Types.ObjectId, ref: 'Organization', required: true, index: true },\\n\" : \"\"} createdBy: { type: mongoose.Schema.Types.ObjectId, ref: 'User', index: true },\r\n deletedAt: { type: Date, default: null, index: true },\r\n },\r\n {\r\n timestamps: true,\r\n toJSON: { virtuals: true },\r\n toObject: { virtuals: true },\r\n }\r\n);\r\n\r\n// Indexes for common queries\r\nexampleSchema.index({ name: 1 });\r\nexampleSchema.index({ deletedAt: 1, isActive: 1 });\r\n${config.tenant === \"multi\" ? \"exampleSchema.index({ organizationId: 1, deletedAt: 1 });\\n\" : \"\"}${typeExport}\r\nconst Example = mongoose.model${ts ? \"<ExampleDocument>\" : \"\"}('Example', exampleSchema);\r\n\r\nexport default Example;\r\n`;\r\n}\r\n\r\nfunction exampleRepositoryTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n const typeImport = ts\r\n ? \"import type { ExampleDocument } from './example.model.js';\\n\"\r\n : \"\";\r\n const generic = ts ? \"<ExampleDocument>\" : \"\";\r\n\r\n return `/**\r\n * Example Repository\r\n * Generated by Arc CLI\r\n *\r\n * MongoKit repository with plugins for:\r\n * - Soft delete (deletedAt filtering)\r\n * - Custom business logic methods\r\n */\r\n\r\nimport {\r\n Repository,\r\n softDeletePlugin,\r\n methodRegistryPlugin,\r\n} from '@classytic/mongokit';\r\n${typeImport}import Example from './example.model.js';\r\n\r\nclass ExampleRepository extends Repository${generic} {\r\n constructor() {\r\n super(Example, [\r\n methodRegistryPlugin(), // Required for plugin method registration\r\n softDeletePlugin(), // Soft delete support\r\n ]);\r\n }\r\n\r\n /**\r\n * Find all active (non-deleted) records\r\n */\r\n async findActive() {\r\n return this.Model.find({ isActive: true, deletedAt: null }).lean();\r\n }\r\n${\r\n config.tenant === \"multi\"\r\n ? `\r\n /**\r\n * Find active records for an organization\r\n */\r\n async findActiveByOrg(organizationId${ts ? \": string\" : \"\"}) {\r\n return this.Model.find({\r\n organizationId,\r\n isActive: true,\r\n deletedAt: null,\r\n }).lean();\r\n }\r\n`\r\n : \"\"\r\n}\r\n // Note: softDeletePlugin provides restore() and getDeleted() methods automatically\r\n}\r\n\r\nconst exampleRepository = new ExampleRepository();\r\n\r\nexport default exampleRepository;\r\nexport { ExampleRepository };\r\n`;\r\n}\r\n\r\nfunction exampleResourceTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n\r\n return `/**\r\n * Example Resource\r\n * Generated by Arc CLI\r\n *\r\n * A complete resource with:\r\n * - Model (Mongoose schema)\r\n * - Repository (MongoKit with plugins)\r\n * - Permissions (role-based access)\r\n * - Presets (soft delete${config.tenant === \"multi\" ? \", multi-tenant\" : \"\"})\r\n */\r\n\r\nimport { defineResource } from '@classytic/arc';\r\nimport { createAdapter } from '#shared/adapter.js';\r\nimport { ${config.tenant === \"multi\" ? \"orgStaffPermissions\" : \"publicReadPermissions\"} } from '#shared/permissions.js';\r\n${config.tenant === \"multi\" ? \"import { flexibleMultiTenantPreset } from '#shared/presets/flexible-multi-tenant.js';\\n\" : \"\"}import Example${ts ? \", { type ExampleDocument }\" : \"\"} from './example.model.js';\r\nimport exampleRepository from './example.repository.js';\r\nimport exampleController from './example.controller.js';\r\n\r\nconst exampleResource = defineResource${ts ? \"<ExampleDocument>\" : \"\"}({\r\n name: 'example',\r\n displayName: 'Examples',\r\n prefix: '/examples',\r\n\r\n adapter: createAdapter(Example, exampleRepository),\r\n controller: exampleController,\r\n\r\n presets: [\r\n 'softDelete',${\r\n config.tenant === \"multi\"\r\n ? `\r\n flexibleMultiTenantPreset({ tenantField: 'organizationId' }),`\r\n : \"\"\r\n }\r\n ],\r\n\r\n permissions: ${config.tenant === \"multi\" ? \"orgStaffPermissions\" : \"publicReadPermissions\"},\r\n\r\n // Add custom routes here:\r\n // additionalRoutes: [\r\n // {\r\n // method: 'GET',\r\n // path: '/custom',\r\n // summary: 'Custom endpoint',\r\n // handler: async (request, reply) => { ... },\r\n // },\r\n // ],\r\n});\r\n\r\nexport default exampleResource;\r\n`;\r\n}\r\n\r\nfunction exampleControllerTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n\r\n return `/**\r\n * Example Controller\r\n * Generated by Arc CLI\r\n *\r\n * BaseController provides CRUD operations with:\r\n * - Automatic pagination\r\n * - Query parsing\r\n * - Validation\r\n */\r\n\r\nimport { BaseController } from '@classytic/arc';\r\nimport exampleRepository from './example.repository.js';\r\nimport { exampleSchemaOptions } from './example.schemas.js';\r\n\r\nclass ExampleController extends BaseController {\r\n constructor() {\r\n super(exampleRepository${ts ? \" as any\" : \"\"}, {\r\n schemaOptions: exampleSchemaOptions,${\r\n config.tenant === \"multi\"\r\n ? `\r\n tenantField: 'organizationId', // Configurable tenant field for multi-tenant`\r\n : `\r\n // tenantField: 'organizationId', // For multi-tenant apps`\r\n }\r\n });\r\n }\r\n\r\n // Add custom controller methods here:\r\n // async customAction(request, reply) {\r\n // // Custom logic\r\n // }\r\n}\r\n\r\nconst exampleController = new ExampleController();\r\nexport default exampleController;\r\n`;\r\n}\r\n\r\nfunction exampleSchemasTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n const multiTenantFields = config.tenant === \"multi\";\r\n\r\n return `/**\r\n * Example Schemas\r\n * Generated by Arc CLI\r\n *\r\n * Schema options for controller validation and query parsing\r\n */\r\n\r\nimport Example from './example.model.js';\r\nimport { buildCrudSchemasFromModel } from '@classytic/mongokit/utils';\r\n\r\n/**\r\n * CRUD Schemas with Field Rules\r\n * Auto-generated from Mongoose model\r\n */\r\nconst crudSchemas = buildCrudSchemasFromModel(Example, {\r\n strictAdditionalProperties: true,\r\n fieldRules: {\r\n // Mark fields as system-managed (excluded from create/update)\r\n // deletedAt: { systemManaged: true },\r\n },\r\n query: {\r\n filterableFields: {\r\n isActive: 'boolean',${\r\n multiTenantFields\r\n ? `\r\n organizationId: 'ObjectId',`\r\n : \"\"\r\n }\r\n createdAt: 'date',\r\n },\r\n },\r\n});\r\n\r\n// Schema options for controller\r\nexport const exampleSchemaOptions${ts ? \": any\" : \"\"} = {\r\n query: {${\r\n multiTenantFields\r\n ? `\r\n allowedPopulate: ['organizationId'],`\r\n : \"\"\r\n }\r\n filterableFields: {\r\n isActive: 'boolean',${\r\n multiTenantFields\r\n ? `\r\n organizationId: 'ObjectId',`\r\n : \"\"\r\n }\r\n createdAt: 'date',\r\n },\r\n },\r\n};\r\n\r\nexport default crudSchemas;\r\n`;\r\n}\r\n\r\nfunction exampleTestTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n\r\n return `/**\r\n * Example Resource Tests\r\n * Generated by Arc CLI\r\n *\r\n * Run tests: npm test\r\n * Watch mode: npm run test:watch\r\n */\r\n\r\nimport { describe, it, expect, beforeAll, afterAll } from 'vitest';\r\n${config.adapter === \"mongokit\" ? \"import mongoose from 'mongoose';\\n\" : \"\"}import { createAppInstance } from '../src/app.js';\r\n${ts ? \"import type { FastifyInstance } from 'fastify';\\n\" : \"\"}\r\ndescribe('Example Resource', () => {\r\n let app${ts ? \": FastifyInstance\" : \"\"};\r\n\r\n beforeAll(async () => {\r\n${\r\n config.adapter === \"mongokit\"\r\n ? ` // Connect to test database\r\n const testDbUri = process.env.MONGODB_URI || 'mongodb://localhost:27017/${config.name}-test';\r\n await mongoose.connect(testDbUri);\r\n`\r\n : \"\"\r\n}\r\n // Create app instance\r\n app = await createAppInstance();\r\n await app.ready();\r\n });\r\n\r\n afterAll(async () => {\r\n await app.close();\r\n${config.adapter === \"mongokit\" ? \" await mongoose.connection.close();\" : \"\"}\r\n });\r\n\r\n describe('GET /examples', () => {\r\n it('should return a list of examples', async () => {\r\n const response = await app.inject({\r\n method: 'GET',\r\n url: '/examples',\r\n });\r\n\r\n expect(response.statusCode).toBe(200);\r\n const body = JSON.parse(response.body);\r\n expect(body).toHaveProperty('docs');\r\n expect(Array.isArray(body.docs)).toBe(true);\r\n });\r\n });\r\n\r\n describe('POST /examples', () => {\r\n it('should require authentication', async () => {\r\n const response = await app.inject({\r\n method: 'POST',\r\n url: '/examples',\r\n payload: { name: 'Test Example' },\r\n });\r\n\r\n // Should fail without auth token\r\n expect(response.statusCode).toBe(401);\r\n });\r\n });\r\n\r\n // Add more tests as needed:\r\n // - GET /examples/:id\r\n // - PATCH /examples/:id\r\n // - DELETE /examples/:id\r\n // - Custom endpoints\r\n});\r\n`;\r\n}\r\n\r\n// ============================================================================\r\n// User & Auth Templates\r\n// ============================================================================\r\n\r\nfunction userModelTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n\r\n const orgRoles =\r\n config.tenant === \"multi\"\r\n ? `\r\n// Organization roles (for multi-tenant)\r\nconst ORG_ROLES = ['owner', 'manager', 'hr', 'staff', 'contractor'] as const;\r\ntype OrgRole = typeof ORG_ROLES[number];\r\n`\r\n : \"\";\r\n\r\n const orgInterface =\r\n config.tenant === \"multi\"\r\n ? `\r\ntype UserOrganization = {\r\n organizationId: Types.ObjectId;\r\n organizationName: string;\r\n roles: OrgRole[];\r\n joinedAt: Date;\r\n};\r\n`\r\n : \"\";\r\n\r\n const orgSchema =\r\n config.tenant === \"multi\"\r\n ? `\r\n // Multi-org support\r\n organizations: [{\r\n organizationId: { type: Schema.Types.ObjectId, ref: 'Organization', required: true },\r\n organizationName: { type: String, required: true },\r\n roles: { type: [String], enum: ORG_ROLES, default: [] },\r\n joinedAt: { type: Date, default: () => new Date() },\r\n }],\r\n`\r\n : \"\";\r\n\r\n const orgMethods =\r\n config.tenant === \"multi\"\r\n ? `\r\n// Organization methods\r\nuserSchema.methods.getOrgRoles = function(orgId${ts ? \": Types.ObjectId | string\" : \"\"}) {\r\n const org = this.organizations.find(o => o.organizationId.toString() === orgId.toString());\r\n return org?.roles || [];\r\n};\r\n\r\nuserSchema.methods.hasOrgAccess = function(orgId${ts ? \": Types.ObjectId | string\" : \"\"}) {\r\n return this.organizations.some(o => o.organizationId.toString() === orgId.toString());\r\n};\r\n\r\nuserSchema.methods.addOrganization = function(\r\n organizationId${ts ? \": Types.ObjectId\" : \"\"},\r\n organizationName${ts ? \": string\" : \"\"},\r\n roles${ts ? \": OrgRole[]\" : \"\"} = []\r\n) {\r\n const existing = this.organizations.find(o => o.organizationId.toString() === organizationId.toString());\r\n if (existing) {\r\n existing.organizationName = organizationName;\r\n existing.roles = [...new Set([...existing.roles, ...roles])];\r\n } else {\r\n this.organizations.push({ organizationId, organizationName, roles, joinedAt: new Date() });\r\n }\r\n return this;\r\n};\r\n\r\nuserSchema.methods.removeOrganization = function(organizationId${ts ? \": Types.ObjectId\" : \"\"}) {\r\n this.organizations = this.organizations.filter(o => o.organizationId.toString() !== organizationId.toString());\r\n return this;\r\n};\r\n\r\n// Index for org queries\r\nuserSchema.index({ 'organizations.organizationId': 1 });\r\n`\r\n : \"\";\r\n\r\n const userType = ts\r\n ? `\r\ntype PlatformRole = 'user' | 'admin' | 'superadmin';\r\n\r\ntype User = {\r\n name: string;\r\n email: string;\r\n password: string;\r\n roles: PlatformRole[];${\r\n config.tenant === \"multi\"\r\n ? `\r\n organizations: UserOrganization[];`\r\n : \"\"\r\n }\r\n resetPasswordToken?: string;\r\n resetPasswordExpires?: Date;\r\n};\r\n\r\ntype UserMethods = {\r\n matchPassword: (enteredPassword: string) => Promise<boolean>;${\r\n config.tenant === \"multi\"\r\n ? `\r\n getOrgRoles: (orgId: Types.ObjectId | string) => OrgRole[];\r\n hasOrgAccess: (orgId: Types.ObjectId | string) => boolean;\r\n addOrganization: (orgId: Types.ObjectId, name: string, roles?: OrgRole[]) => UserDocument;\r\n removeOrganization: (orgId: Types.ObjectId) => UserDocument;`\r\n : \"\"\r\n }\r\n};\r\n\r\nexport type UserDocument = HydratedDocument<User, UserMethods>;\r\nexport type UserModel = Model<User, {}, UserMethods>;\r\n`\r\n : \"\";\r\n\r\n return `/**\r\n * User Model\r\n * Generated by Arc CLI\r\n */\r\n\r\nimport bcrypt from 'bcryptjs';\r\nimport mongoose${ts ? \", { type HydratedDocument, type Model, type Types }\" : \"\"} from 'mongoose';\r\n${orgRoles}\r\nconst { Schema } = mongoose;\r\n${orgInterface}${userType}\r\nconst userSchema = new Schema${ts ? \"<User, UserModel, UserMethods>\" : \"\"}(\r\n {\r\n name: { type: String, required: true, trim: true },\r\n email: {\r\n type: String,\r\n required: true,\r\n unique: true,\r\n lowercase: true,\r\n trim: true,\r\n },\r\n password: { type: String, required: true },\r\n\r\n // Platform roles\r\n roles: {\r\n type: [String],\r\n enum: ['user', 'admin', 'superadmin'],\r\n default: ['user'],\r\n },\r\n${orgSchema}\r\n // Password reset\r\n resetPasswordToken: String,\r\n resetPasswordExpires: Date,\r\n },\r\n { timestamps: true }\r\n);\r\n\r\n// Password hashing\r\nuserSchema.pre('save', async function() {\r\n if (!this.isModified('password')) return;\r\n const salt = await bcrypt.genSalt(10);\r\n this.password = await bcrypt.hash(this.password, salt);\r\n});\r\n\r\n// Password comparison\r\nuserSchema.methods.matchPassword = async function(enteredPassword${ts ? \": string\" : \"\"}) {\r\n return bcrypt.compare(enteredPassword, this.password);\r\n};\r\n${orgMethods}\r\n// Exclude password in JSON\r\nuserSchema.set('toJSON', {\r\n transform: (_doc, ret${ts ? \": any\" : \"\"}) => {\r\n delete ret.password;\r\n delete ret.resetPasswordToken;\r\n delete ret.resetPasswordExpires;\r\n return ret;\r\n },\r\n});\r\n\r\nconst User = mongoose.models.User${ts ? \" as UserModel\" : \"\"} || mongoose.model${ts ? \"<User, UserModel>\" : \"\"}('User', userSchema);\r\nexport default User;\r\n`;\r\n}\r\n\r\nfunction userRepositoryTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n const typeImport = ts\r\n ? \"import type { UserDocument } from './user.model.js';\\nimport type { ClientSession, Types } from 'mongoose';\\n\"\r\n : \"\";\r\n\r\n return `/**\r\n * User Repository\r\n * Generated by Arc CLI\r\n *\r\n * MongoKit repository with plugins for common operations\r\n */\r\n\r\nimport {\r\n Repository,\r\n methodRegistryPlugin,\r\n mongoOperationsPlugin,\r\n} from '@classytic/mongokit';\r\n${typeImport}import User from './user.model.js';\r\n\r\n${ts ? \"type ID = string | Types.ObjectId;\\n\" : \"\"}\r\nclass UserRepository extends Repository${ts ? \"<UserDocument>\" : \"\"} {\r\n constructor() {\r\n super(User${ts ? \" as any\" : \"\"}, [\r\n methodRegistryPlugin(),\r\n mongoOperationsPlugin(),\r\n ]);\r\n }\r\n\r\n /**\r\n * Find user by email\r\n */\r\n async findByEmail(email${ts ? \": string\" : \"\"}) {\r\n return this.Model.findOne({ email: email.toLowerCase().trim() });\r\n }\r\n\r\n /**\r\n * Find user by reset token\r\n */\r\n async findByResetToken(token${ts ? \": string\" : \"\"}) {\r\n return this.Model.findOne({\r\n resetPasswordToken: token,\r\n resetPasswordExpires: { $gt: Date.now() },\r\n });\r\n }\r\n\r\n /**\r\n * Check if email exists\r\n */\r\n async emailExists(email${ts ? \": string\" : \"\"})${ts ? \": Promise<boolean>\" : \"\"} {\r\n const result = await this.Model.exists({ email: email.toLowerCase().trim() });\r\n return !!result;\r\n }\r\n\r\n /**\r\n * Update user password (triggers hash middleware)\r\n */\r\n async updatePassword(userId${ts ? \": ID\" : \"\"}, newPassword${ts ? \": string\" : \"\"}, options${ts ? \": { session?: ClientSession }\" : \"\"} = {}) {\r\n const user = await this.Model.findById(userId).session(options.session ?? null);\r\n if (!user) throw new Error('User not found');\r\n\r\n user.password = newPassword;\r\n user.resetPasswordToken = undefined;\r\n user.resetPasswordExpires = undefined;\r\n await user.save({ session: options.session ?? undefined });\r\n return user;\r\n }\r\n\r\n /**\r\n * Set reset token\r\n */\r\n async setResetToken(userId${ts ? \": ID\" : \"\"}, token${ts ? \": string\" : \"\"}, expiresAt${ts ? \": Date\" : \"\"}) {\r\n return this.Model.findByIdAndUpdate(\r\n userId,\r\n { resetPasswordToken: token, resetPasswordExpires: expiresAt },\r\n { new: true }\r\n );\r\n }\r\n${\r\n config.tenant === \"multi\"\r\n ? `\r\n /**\r\n * Find users by organization\r\n */\r\n async findByOrganization(organizationId${ts ? \": ID\" : \"\"}) {\r\n return this.Model.find({ 'organizations.organizationId': organizationId })\r\n .select('-password -resetPasswordToken -resetPasswordExpires')\r\n .lean();\r\n }\r\n`\r\n : \"\"\r\n}\r\n}\r\n\r\nconst userRepository = new UserRepository();\r\nexport default userRepository;\r\nexport { UserRepository };\r\n`;\r\n}\r\n\r\nfunction userControllerTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n\r\n return `/**\r\n * User Controller\r\n * Generated by Arc CLI\r\n *\r\n * BaseController for user management operations.\r\n * Used by auth resource for /users/me endpoints.\r\n */\r\n\r\nimport { BaseController } from '@classytic/arc';\r\nimport userRepository from './user.repository.js';\r\n\r\nclass UserController extends BaseController {\r\n constructor() {\r\n super(userRepository${ts ? \" as any\" : \"\"});\r\n }\r\n\r\n // Custom user operations can be added here\r\n}\r\n\r\nconst userController = new UserController();\r\nexport default userController;\r\n`;\r\n}\r\n\r\nfunction authResourceTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n\r\n return `/**\r\n * Auth Resource\r\n * Generated by Arc CLI\r\n *\r\n * Combined auth + user profile endpoints:\r\n * - POST /auth/register\r\n * - POST /auth/login\r\n * - POST /auth/refresh\r\n * - POST /auth/forgot-password\r\n * - POST /auth/reset-password\r\n * - GET /users/me\r\n * - PATCH /users/me\r\n */\r\n\r\nimport { defineResource } from '@classytic/arc';\r\nimport { allowPublic, requireAuth } from '@classytic/arc/permissions';\r\nimport { createAdapter } from '#shared/adapter.js';\r\nimport User from '../user/user.model.js';\r\nimport userRepository from '../user/user.repository.js';\r\nimport * as handlers from './auth.handlers.js';\r\nimport * as schemas from './auth.schemas.js';\r\n\r\n/**\r\n * Auth Resource - handles authentication\r\n */\r\nexport const authResource = defineResource({\r\n name: 'auth',\r\n displayName: 'Authentication',\r\n tag: 'Authentication',\r\n prefix: '/auth',\r\n\r\n adapter: createAdapter(User${ts ? \" as any\" : \"\"}, userRepository${ts ? \" as any\" : \"\"}),\r\n disableDefaultRoutes: true,\r\n\r\n additionalRoutes: [\r\n {\r\n method: 'POST',\r\n path: '/register',\r\n summary: 'Register new user',\r\n permissions: allowPublic(),\r\n handler: handlers.register,\r\n wrapHandler: false,\r\n schema: { body: schemas.registerBody, response: { 201: schemas.successResponse } },\r\n },\r\n {\r\n method: 'POST',\r\n path: '/login',\r\n summary: 'User login',\r\n permissions: allowPublic(),\r\n handler: handlers.login,\r\n wrapHandler: false,\r\n schema: { body: schemas.loginBody, response: { 200: schemas.loginResponse } },\r\n },\r\n {\r\n method: 'POST',\r\n path: '/refresh',\r\n summary: 'Refresh access token',\r\n permissions: allowPublic(),\r\n handler: handlers.refreshToken,\r\n wrapHandler: false,\r\n schema: { body: schemas.refreshBody, response: { 200: schemas.tokenResponse } },\r\n },\r\n {\r\n method: 'POST',\r\n path: '/forgot-password',\r\n summary: 'Request password reset',\r\n permissions: allowPublic(),\r\n handler: handlers.forgotPassword,\r\n wrapHandler: false,\r\n schema: { body: schemas.forgotBody, response: { 200: schemas.successResponse } },\r\n },\r\n {\r\n method: 'POST',\r\n path: '/reset-password',\r\n summary: 'Reset password with token',\r\n permissions: allowPublic(),\r\n handler: handlers.resetPassword,\r\n wrapHandler: false,\r\n schema: { body: schemas.resetBody, response: { 200: schemas.successResponse } },\r\n },\r\n ],\r\n});\r\n\r\n/**\r\n * User Profile Resource - handles /users/me\r\n */\r\nexport const userProfileResource = defineResource({\r\n name: 'user-profile',\r\n displayName: 'User Profile',\r\n tag: 'User Profile',\r\n prefix: '/users',\r\n\r\n adapter: createAdapter(User${ts ? \" as any\" : \"\"}, userRepository${ts ? \" as any\" : \"\"}),\r\n disableDefaultRoutes: true,\r\n\r\n additionalRoutes: [\r\n {\r\n method: 'GET',\r\n path: '/me',\r\n summary: 'Get current user profile',\r\n permissions: requireAuth(),\r\n handler: handlers.getUserProfile,\r\n wrapHandler: false,\r\n schema: { response: { 200: schemas.userProfileResponse } },\r\n },\r\n {\r\n method: 'PATCH',\r\n path: '/me',\r\n summary: 'Update current user profile',\r\n permissions: requireAuth(),\r\n handler: handlers.updateUserProfile,\r\n wrapHandler: false,\r\n schema: { body: schemas.updateUserBody, response: { 200: schemas.userProfileResponse } },\r\n },\r\n ],\r\n});\r\n\r\nexport default authResource;\r\n`;\r\n}\r\n\r\nfunction betterAuthSetupTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n const mongoImport =\r\n config.adapter === \"mongokit\"\r\n ? `import mongoose from 'mongoose';\r\nimport { mongodbAdapter } from 'better-auth/adapters/mongodb';`\r\n : \"\";\r\n\r\n const dbAdapter =\r\n config.adapter === \"mongokit\"\r\n ? config.typescript\r\n ? `database: mongodbAdapter(mongoose.connection.getClient().db() as any),`\r\n : `database: mongodbAdapter(mongoose.connection.getClient().db()),`\r\n : `// Configure your database adapter here\r\n // See: https://www.better-auth.com/docs/concepts/database`;\r\n\r\n const orgPlugin =\r\n config.tenant === \"multi\"\r\n ? `\r\nimport { organization } from 'better-auth/plugins/organization';\r\nimport { bearer } from 'better-auth/plugins/bearer';`\r\n : \"\";\r\n\r\n const orgPluginUsage =\r\n config.tenant === \"multi\"\r\n ? `\r\n plugins: [\r\n bearer(),\r\n organization({\r\n allowUserToCreateOrganization: true,\r\n creatorRole: 'owner',\r\n teams: {\r\n enabled: true,\r\n },\r\n }),\r\n ],`\r\n : \"\";\r\n\r\n const googleProvider = `\r\n // Google OAuth (enabled when env vars are set)\r\n ...(process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET\r\n ? {\r\n socialProviders: {\r\n google: {\r\n clientId: process.env.GOOGLE_CLIENT_ID,\r\n clientSecret: process.env.GOOGLE_CLIENT_SECRET,\r\n },\r\n },\r\n }\r\n : {}),`;\r\n\r\n return `/**\r\n * Better Auth Configuration\r\n * Generated by Arc CLI\r\n *\r\n * Authentication is handled entirely by Better Auth.\r\n * Routes are registered automatically at /api/auth/*\r\n *\r\n * Better Auth manages these collections:\r\n * - user, session, account${config.tenant === \"multi\" ? \", organization, member, invitation, team, teamMember\" : \"\"}\r\n *\r\n * @see https://www.better-auth.com/docs\r\n */\r\n\r\nimport { betterAuth } from 'better-auth';\r\n${mongoImport}${orgPlugin}\r\nimport config from '#config/index.js';\r\n\r\nlet _auth${ts ? \": ReturnType<typeof betterAuth> | null\" : \"\"} = null;\r\n\r\n/**\r\n * Get the Better Auth instance (lazy singleton)\r\n *\r\n * Must be called AFTER database connection is established.\r\n */\r\nexport function getAuth()${ts ? \": ReturnType<typeof betterAuth>\" : \"\"} {\r\n if (process.env.NODE_ENV === 'production' && !process.env.BETTER_AUTH_SECRET) {\r\n throw new Error('BETTER_AUTH_SECRET is required in production (min 32 chars)');\r\n }\r\n\r\n if (!_auth) {\r\n _auth = betterAuth({\r\n secret: config.betterAuth.secret,\r\n baseURL: process.env.BETTER_AUTH_URL || \\`http://localhost:\\${config.server.port}\\`,\r\n basePath: '/api/auth',\r\n\r\n ${dbAdapter}\r\n${\r\n config.tenant === \"multi\"\r\n ? `\r\n user: {\r\n additionalFields: {\r\n roles: {\r\n type: 'string[]',\r\n defaultValue: ['user'],\r\n required: false,\r\n input: false, // Cannot be set during signup\r\n },\r\n },\r\n },\r\n`\r\n : \"\"\r\n}\r\n emailAndPassword: {\r\n enabled: true,\r\n minPasswordLength: 6,\r\n },\r\n${googleProvider}\r\n${orgPluginUsage}\r\n session: {\r\n cookieCache: {\r\n enabled: true,\r\n maxAge: 5 * 60, // 5 minutes\r\n },\r\n },\r\n\r\n trustedOrigins: [config.frontend.url],\r\n\r\n rateLimit: {\r\n enabled: process.env.NODE_ENV === 'production',\r\n },\r\n });\r\n }\r\n\r\n return _auth;\r\n}\r\n\r\nexport default getAuth;\r\n`;\r\n}\r\n\r\nfunction authHandlersTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n const typeAnnotations = ts\r\n ? `\r\nimport type { FastifyRequest, FastifyReply } from 'fastify';\r\n// Load Arc auth type augmentations (adds request.server.auth typings)\r\nimport '@classytic/arc/auth';\r\n`\r\n : \"\";\r\n\r\n return `/**\r\n * Auth Handlers\r\n * Generated by Arc CLI\r\n *\r\n * Uses Arc's built-in JWT utilities via fastify.auth (provided by @fastify/jwt v10).\r\n * No standalone jsonwebtoken dependency needed.\r\n */\r\n\r\nimport userRepository from '../user/user.repository.js';\r\n${typeAnnotations}\r\n\r\n/**\r\n * Register new user\r\n */\r\nexport async function register(request${ts ? \": FastifyRequest\" : \"\"}, reply${ts ? \": FastifyReply\" : \"\"}) {\r\n try {\r\n const { name, email, password } = request.body${ts ? \" as any\" : \"\"};\r\n\r\n // Check if email exists\r\n if (await userRepository.emailExists(email)) {\r\n return reply.code(400).send({ success: false, message: 'Email already registered' });\r\n }\r\n\r\n // Create user\r\n await userRepository.create({ name, email, password, roles: ['user'] });\r\n\r\n return reply.code(201).send({ success: true, message: 'User registered successfully' });\r\n } catch (error) {\r\n request.log.error({ err: error }, 'Register error');\r\n return reply.code(500).send({ success: false, message: 'Registration failed' });\r\n }\r\n}\r\n\r\n/**\r\n * Login user\r\n */\r\nexport async function login(request${ts ? \": FastifyRequest\" : \"\"}, reply${ts ? \": FastifyReply\" : \"\"}) {\r\n try {\r\n const { email, password } = request.body${ts ? \" as any\" : \"\"};\r\n\r\n const user = await userRepository.findByEmail(email);\r\n if (!user || !(await user.matchPassword(password))) {\r\n return reply.code(401).send({ success: false, message: 'Invalid credentials' });\r\n }\r\n\r\n const tokens = request.server.auth.issueTokens({ id: user._id.toString(), roles: user.roles });\r\n\r\n return reply.send({\r\n success: true,\r\n user: { id: user._id, name: user.name, email: user.email, roles: user.roles },\r\n ...tokens,\r\n });\r\n } catch (error) {\r\n request.log.error({ err: error }, 'Login error');\r\n return reply.code(500).send({ success: false, message: 'Login failed' });\r\n }\r\n}\r\n\r\n/**\r\n * Refresh access token\r\n */\r\nexport async function refreshToken(request${ts ? \": FastifyRequest\" : \"\"}, reply${ts ? \": FastifyReply\" : \"\"}) {\r\n try {\r\n const { token } = request.body${ts ? \" as any\" : \"\"};\r\n if (!token) {\r\n return reply.code(401).send({ success: false, message: 'Refresh token required' });\r\n }\r\n\r\n const decoded = request.server.auth.verifyRefreshToken(token)${ts ? \" as { id: string }\" : \"\"};\r\n const tokens = request.server.auth.issueTokens({ id: decoded.id });\r\n\r\n return reply.send({ success: true, ...tokens });\r\n } catch {\r\n return reply.code(401).send({ success: false, message: 'Invalid refresh token' });\r\n }\r\n}\r\n\r\n/**\r\n * Forgot password\r\n */\r\nexport async function forgotPassword(request${ts ? \": FastifyRequest\" : \"\"}, reply${ts ? \": FastifyReply\" : \"\"}) {\r\n try {\r\n const { email } = request.body${ts ? \" as any\" : \"\"};\r\n const user = await userRepository.findByEmail(email);\r\n\r\n if (user) {\r\n const { randomBytes } = await import('node:crypto');\r\n const token = randomBytes(32).toString('hex');\r\n const expires = new Date(Date.now() + 3600000); // 1 hour\r\n await userRepository.setResetToken(user._id, token, expires);\r\n // SCAFFOLD: Integrate your email provider to send the reset link\r\n request.log.info(\\`Password reset requested for \\${email}\\`);\r\n }\r\n\r\n // Always return success to prevent email enumeration\r\n return reply.send({ success: true, message: 'If email exists, reset link sent' });\r\n } catch (error) {\r\n request.log.error({ err: error }, 'Forgot password error');\r\n return reply.code(500).send({ success: false, message: 'Failed to process request' });\r\n }\r\n}\r\n\r\n/**\r\n * Reset password\r\n */\r\nexport async function resetPassword(request${ts ? \": FastifyRequest\" : \"\"}, reply${ts ? \": FastifyReply\" : \"\"}) {\r\n try {\r\n const { token, newPassword } = request.body${ts ? \" as any\" : \"\"};\r\n const user = await userRepository.findByResetToken(token);\r\n\r\n if (!user) {\r\n return reply.code(400).send({ success: false, message: 'Invalid or expired token' });\r\n }\r\n\r\n await userRepository.updatePassword(user._id, newPassword);\r\n return reply.send({ success: true, message: 'Password has been reset' });\r\n } catch (error) {\r\n request.log.error({ err: error }, 'Reset password error');\r\n return reply.code(500).send({ success: false, message: 'Failed to reset password' });\r\n }\r\n}\r\n\r\n/**\r\n * Get current user profile\r\n */\r\nexport async function getUserProfile(request${ts ? \": FastifyRequest\" : \"\"}, reply${ts ? \": FastifyReply\" : \"\"}) {\r\n try {\r\n const userId = (request${ts ? \" as any\" : \"\"}).user?._id || (request${ts ? \" as any\" : \"\"}).user?.id;\r\n const user = await userRepository.getById(userId);\r\n\r\n if (!user) {\r\n return reply.code(404).send({ success: false, message: 'User not found' });\r\n }\r\n\r\n return reply.send({ success: true, data: user });\r\n } catch (error) {\r\n request.log.error({ err: error }, 'Get profile error');\r\n return reply.code(500).send({ success: false, message: 'Failed to get profile' });\r\n }\r\n}\r\n\r\n/**\r\n * Update current user profile\r\n */\r\nexport async function updateUserProfile(request${ts ? \": FastifyRequest\" : \"\"}, reply${ts ? \": FastifyReply\" : \"\"}) {\r\n try {\r\n const userId = (request${ts ? \" as any\" : \"\"}).user?._id || (request${ts ? \" as any\" : \"\"}).user?.id;\r\n const updates = { ...request.body${ts ? \" as any\" : \"\"} };\r\n\r\n // Prevent updating protected fields\r\n if ('password' in updates) delete updates.password;\r\n if ('roles' in updates) delete updates.roles;\r\n if ('organizations' in updates) delete updates.organizations;\r\n\r\n const user = await userRepository.Model.findByIdAndUpdate(userId, updates, { new: true });\r\n return reply.send({ success: true, data: user });\r\n } catch (error) {\r\n request.log.error({ err: error }, 'Update profile error');\r\n return reply.code(500).send({ success: false, message: 'Failed to update profile' });\r\n }\r\n}\r\n`;\r\n}\r\n\r\nfunction authSchemasTemplate(_config: ProjectConfig): string {\r\n return `/**\r\n * Auth Schemas\r\n * Generated by Arc CLI\r\n */\r\n\r\nexport const registerBody = {\r\n type: 'object',\r\n required: ['name', 'email', 'password'],\r\n properties: {\r\n name: { type: 'string', minLength: 2 },\r\n email: { type: 'string', format: 'email' },\r\n password: { type: 'string', minLength: 6 },\r\n },\r\n};\r\n\r\nexport const loginBody = {\r\n type: 'object',\r\n required: ['email', 'password'],\r\n properties: {\r\n email: { type: 'string', format: 'email' },\r\n password: { type: 'string' },\r\n },\r\n};\r\n\r\nexport const refreshBody = {\r\n type: 'object',\r\n required: ['token'],\r\n properties: {\r\n token: { type: 'string' },\r\n },\r\n};\r\n\r\nexport const forgotBody = {\r\n type: 'object',\r\n required: ['email'],\r\n properties: {\r\n email: { type: 'string', format: 'email' },\r\n },\r\n};\r\n\r\nexport const resetBody = {\r\n type: 'object',\r\n required: ['token', 'newPassword'],\r\n properties: {\r\n token: { type: 'string' },\r\n newPassword: { type: 'string', minLength: 6 },\r\n },\r\n};\r\n\r\nexport const updateUserBody = {\r\n type: 'object',\r\n properties: {\r\n name: { type: 'string', minLength: 2 },\r\n email: { type: 'string', format: 'email' },\r\n },\r\n};\r\n\r\n// Response schemas (enables fast-json-stringify serialization)\r\n\r\nexport const successResponse = {\r\n type: 'object',\r\n properties: {\r\n success: { type: 'boolean' },\r\n message: { type: 'string' },\r\n },\r\n};\r\n\r\nexport const loginResponse = {\r\n type: 'object',\r\n properties: {\r\n success: { type: 'boolean' },\r\n user: {\r\n type: 'object',\r\n properties: {\r\n id: { type: 'string' },\r\n name: { type: 'string' },\r\n email: { type: 'string' },\r\n roles: { type: 'array', items: { type: 'string' } },\r\n },\r\n },\r\n accessToken: { type: 'string' },\r\n refreshToken: { type: 'string' },\r\n },\r\n};\r\n\r\nexport const tokenResponse = {\r\n type: 'object',\r\n properties: {\r\n success: { type: 'boolean' },\r\n accessToken: { type: 'string' },\r\n refreshToken: { type: 'string' },\r\n },\r\n};\r\n\r\nexport const userProfileResponse = {\r\n type: 'object',\r\n properties: {\r\n success: { type: 'boolean' },\r\n data: { type: 'object', additionalProperties: true },\r\n },\r\n};\r\n`;\r\n}\r\n\r\nfunction authTestTemplate(config: ProjectConfig): string {\r\n const ts = config.typescript;\r\n\r\n return `/**\r\n * Auth Tests\r\n * Generated by Arc CLI\r\n */\r\n\r\nimport { describe, it, expect, beforeAll, afterAll } from 'vitest';\r\n${config.adapter === \"mongokit\" ? \"import mongoose from 'mongoose';\\n\" : \"\"}import { createAppInstance } from '../src/app.js';\r\n${ts ? \"import type { FastifyInstance } from 'fastify';\\n\" : \"\"}\r\ndescribe('Auth', () => {\r\n let app${ts ? \": FastifyInstance\" : \"\"};\r\n const testUser = {\r\n name: 'Test User',\r\n email: 'test@example.com',\r\n password: 'password123',\r\n };\r\n\r\n beforeAll(async () => {\r\n${\r\n config.adapter === \"mongokit\"\r\n ? ` const testDbUri = process.env.MONGODB_URI || 'mongodb://localhost:27017/${config.name}-test';\r\n await mongoose.connect(testDbUri);\r\n // Clean up test data\r\n await mongoose.connection.collection('users').deleteMany({ email: testUser.email });\r\n`\r\n : \"\"\r\n}\r\n app = await createAppInstance();\r\n await app.ready();\r\n });\r\n\r\n afterAll(async () => {\r\n${\r\n config.adapter === \"mongokit\"\r\n ? ` await mongoose.connection.collection('users').deleteMany({ email: testUser.email });\r\n await mongoose.connection.close();\r\n`\r\n : \"\"\r\n} await app.close();\r\n });\r\n\r\n describe('POST /auth/register', () => {\r\n it('should register a new user', async () => {\r\n const response = await app.inject({\r\n method: 'POST',\r\n url: '/auth/register',\r\n payload: testUser,\r\n });\r\n\r\n expect(response.statusCode).toBe(201);\r\n const body = JSON.parse(response.body);\r\n expect(body.success).toBe(true);\r\n });\r\n\r\n it('should reject duplicate email', async () => {\r\n const response = await app.inject({\r\n method: 'POST',\r\n url: '/auth/register',\r\n payload: testUser,\r\n });\r\n\r\n expect(response.statusCode).toBe(400);\r\n });\r\n });\r\n\r\n describe('POST /auth/login', () => {\r\n it('should login with valid credentials', async () => {\r\n const response = await app.inject({\r\n method: 'POST',\r\n url: '/auth/login',\r\n payload: { email: testUser.email, password: testUser.password },\r\n });\r\n\r\n expect(response.statusCode).toBe(200);\r\n const body = JSON.parse(response.body);\r\n expect(body.success).toBe(true);\r\n expect(body.accessToken).toBeDefined();\r\n expect(body.refreshToken).toBeDefined();\r\n });\r\n\r\n it('should reject invalid credentials', async () => {\r\n const response = await app.inject({\r\n method: 'POST',\r\n url: '/auth/login',\r\n payload: { email: testUser.email, password: 'wrongpassword' },\r\n });\r\n\r\n expect(response.statusCode).toBe(401);\r\n });\r\n });\r\n\r\n describe('GET /users/me', () => {\r\n it('should require authentication', async () => {\r\n const response = await app.inject({\r\n method: 'GET',\r\n url: '/users/me',\r\n });\r\n\r\n expect(response.statusCode).toBe(401);\r\n });\r\n });\r\n});\r\n`;\r\n}\r\n\r\n// ============================================================================\r\n// Success Message\r\n// ============================================================================\r\n\r\nfunction printSuccessMessage(\r\n config: ProjectConfig,\r\n skipInstall?: boolean,\r\n): void {\r\n const installStep = skipInstall ? ` npm install\\n` : \"\";\r\n const ext = config.typescript ? \"ts\" : \"js\";\r\n\r\n const authInfo =\r\n config.auth === \"better-auth\"\r\n ? `\r\nAuth (Better Auth):\r\n\r\n Auth routes: http://localhost:8040/api/auth/*\r\n Better Auth handles: registration, login, sessions, OAuth\r\n Config file: src/auth.${ext}\r\n`\r\n : `\r\nAuth (JWT):\r\n\r\n POST /auth/register # Register\r\n POST /auth/login # Login (returns JWT)\r\n POST /auth/refresh # Refresh token\r\n GET /users/me # Current user profile\r\n`;\r\n\r\n console.log(`\r\n╔═══════════════════════════════════════════════════════════════╗\r\n║ Project Created ║\r\n╚═══════════════════════════════════════════════════════════════╝\r\n\r\nNext steps:\r\n\r\n cd ${config.name}\r\n${installStep} npm run dev # Uses .env.dev automatically\r\n${authInfo}\r\nAPI Documentation:\r\n\r\n http://localhost:8040/docs # Scalar UI\r\n http://localhost:8040/_docs/openapi.json # OpenAPI spec\r\n\r\nRun tests:\r\n\r\n npm test # Run once\r\n npm run test:watch # Watch mode\r\n\r\nAdd resources:\r\n\r\n arc generate resource product\r\n\r\nProject structure:\r\n\r\n src/\r\n ├── app.${ext} # App factory (for workers/tests)\r\n ├── index.${ext} # Server entry${config.auth === \"better-auth\" ? `\\n ├── auth.${ext} # Better Auth config` : \"\"}\r\n ├── config/ # Configuration\r\n ├── shared/ # Adapters, presets, permissions\r\n ├── plugins/ # App plugins (DI pattern)\r\n └── resources/ # API resources\r\n\r\nDocumentation:\r\n https://github.com/classytic/arc\r\n`);\r\n}\r\n\r\nexport default init;\r\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"introspect.d.mts","names":[],"sources":["../../../src/cli/commands/introspect.ts"],"mappings":";;AAsBA;;;;;iBAAsB,UAAA,CAAW,IAAA,aAAiB,OAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"introspect.mjs","names":[],"sources":["../../../src/cli/commands/introspect.ts"],"sourcesContent":["/**\n * Arc CLI - Introspect Command\n *\n * Shows all registered resources and their configuration.\n * Requires an entry file that exports defineResource() results.\n */\n\nimport { resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { ResourceRegistry } from '../../registry/index.js';\nimport type { RegistryEntry } from '../../types/index.js';\n\nfunction describePermission(value: unknown): string {\n if (!value) return 'none';\n if (typeof value === 'function') {\n return value.name ? `${value.name}()` : '[anonymous permission function]';\n }\n if (Array.isArray(value)) return `[${value.join(', ')}]`;\n if (typeof value === 'object') return '[permission object]';\n return String(value);\n}\n\nexport async function introspect(args: string[]): Promise<void> {\n console.log('Introspecting Arc resources...\\n');\n\n try {\n const entryPath = args[0];\n if (!entryPath) {\n console.log('Usage: arc introspect <entry-file>\\n');\n console.log('Where entry-file exports your defineResource() results.');\n console.log('Example: arc introspect ./src/resources.js');\n return;\n }\n\n // Dynamically import user's entry file (pathToFileURL needed for Windows)\n const entryFileUrl = pathToFileURL(resolve(process.cwd(), entryPath)).href;\n const entryModule = await import(entryFileUrl);\n\n // Collect ResourceDefinition objects from exports (they have _registryMeta + toPlugin)\n // Also handles arrays of resources (e.g. `export const resources = [r1, r2]`)\n const registry = new ResourceRegistry();\n let registered = 0;\n\n function tryRegister(value: unknown): void {\n if (\n value &&\n typeof value === 'object' &&\n 'name' in value &&\n '_registryMeta' in value &&\n 'toPlugin' in value\n ) {\n registry.register(value as any, (value as any)._registryMeta ?? {});\n registered++;\n }\n }\n\n for (const exported of Object.values(entryModule)) {\n if (Array.isArray(exported)) {\n exported.forEach(tryRegister);\n } else {\n tryRegister(exported);\n }\n }\n\n if (registered === 0) {\n console.log('No resource definitions found in entry file.');\n console.log('\\nMake sure your file exports defineResource() results:');\n console.log(' export const productResource = defineResource({ ... });');\n return;\n }\n\n const resources: RegistryEntry[] = registry.getAll();\n\n console.log(`Found ${resources.length} resource(s):\\n`);\n\n resources.forEach((resource, index) => {\n console.log(`${index + 1}. ${resource.name}`);\n console.log(` Display Name: ${resource.displayName}`);\n console.log(` Prefix: ${resource.prefix}`);\n console.log(` Module: ${resource.module || 'none'}`);\n\n if (resource.permissions) {\n console.log(` Permissions:`);\n Object.entries(resource.permissions).forEach(([op, permission]) => {\n console.log(` ${op}: ${describePermission(permission)}`);\n });\n }\n\n if (resource.presets && resource.presets.length > 0) {\n console.log(` Presets: ${resource.presets.join(', ')}`);\n }\n\n if (resource.additionalRoutes && resource.additionalRoutes.length > 0) {\n console.log(` Additional Routes: ${resource.additionalRoutes.length}`);\n }\n\n console.log('');\n });\n\n // Summary\n const stats = registry.getStats();\n console.log('Summary:');\n console.log(` Total Resources: ${stats.totalResources}`);\n console.log(` With Presets: ${resources.filter((r) => r.presets?.length > 0).length}`);\n console.log(\n ` With Custom Routes: ${resources.filter((r) => r.additionalRoutes && r.additionalRoutes.length > 0).length}`\n );\n } catch (error: unknown) {\n if (error instanceof Error) throw error;\n throw new Error(String(error));\n }\n}\n\nexport default introspect;\n"],"mappings":";;;;;;;;;;;AAYA,SAAS,mBAAmB,OAAwB;AAClD,KAAI,CAAC,MAAO,QAAO;AACnB,KAAI,OAAO,UAAU,WACnB,QAAO,MAAM,OAAO,GAAG,MAAM,KAAK,MAAM;AAE1C,KAAI,MAAM,QAAQ,MAAM,CAAE,QAAO,IAAI,MAAM,KAAK,KAAK,CAAC;AACtD,KAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAO,OAAO,MAAM;;AAGtB,eAAsB,WAAW,MAA+B;AAC9D,SAAQ,IAAI,mCAAmC;AAE/C,KAAI;EACF,MAAM,YAAY,KAAK;AACvB,MAAI,CAAC,WAAW;AACd,WAAQ,IAAI,uCAAuC;AACnD,WAAQ,IAAI,0DAA0D;AACtE,WAAQ,IAAI,6CAA6C;AACzD;;EAKF,MAAM,cAAc,MAAM,OADL,cAAc,QAAQ,QAAQ,KAAK,EAAE,UAAU,CAAC,CAAC;EAKtE,MAAM,WAAW,IAAI,kBAAkB;EACvC,IAAI,aAAa;EAEjB,SAAS,YAAY,OAAsB;AACzC,OACE,SACA,OAAO,UAAU,YACjB,UAAU,SACV,mBAAmB,SACnB,cAAc,OACd;AACA,aAAS,SAAS,OAAe,MAAc,iBAAiB,EAAE,CAAC;AACnE;;;AAIJ,OAAK,MAAM,YAAY,OAAO,OAAO,YAAY,CAC/C,KAAI,MAAM,QAAQ,SAAS,CACzB,UAAS,QAAQ,YAAY;MAE7B,aAAY,SAAS;AAIzB,MAAI,eAAe,GAAG;AACpB,WAAQ,IAAI,+CAA+C;AAC3D,WAAQ,IAAI,0DAA0D;AACtE,WAAQ,IAAI,4DAA4D;AACxE;;EAGF,MAAM,YAA6B,SAAS,QAAQ;AAEpD,UAAQ,IAAI,SAAS,UAAU,OAAO,iBAAiB;AAEvD,YAAU,SAAS,UAAU,UAAU;AACrC,WAAQ,IAAI,GAAG,QAAQ,EAAE,IAAI,SAAS,OAAO;AAC7C,WAAQ,IAAI,oBAAoB,SAAS,cAAc;AACvD,WAAQ,IAAI,cAAc,SAAS,SAAS;AAC5C,WAAQ,IAAI,cAAc,SAAS,UAAU,SAAS;AAEtD,OAAI,SAAS,aAAa;AACxB,YAAQ,IAAI,kBAAkB;AAC9B,WAAO,QAAQ,SAAS,YAAY,CAAC,SAAS,CAAC,IAAI,gBAAgB;AACjE,aAAQ,IAAI,QAAQ,GAAG,IAAI,mBAAmB,WAAW,GAAG;MAC5D;;AAGJ,OAAI,SAAS,WAAW,SAAS,QAAQ,SAAS,EAChD,SAAQ,IAAI,eAAe,SAAS,QAAQ,KAAK,KAAK,GAAG;AAG3D,OAAI,SAAS,oBAAoB,SAAS,iBAAiB,SAAS,EAClE,SAAQ,IAAI,yBAAyB,SAAS,iBAAiB,SAAS;AAG1E,WAAQ,IAAI,GAAG;IACf;EAGF,MAAM,QAAQ,SAAS,UAAU;AACjC,UAAQ,IAAI,WAAW;AACvB,UAAQ,IAAI,sBAAsB,MAAM,iBAAiB;AACzD,UAAQ,IAAI,mBAAmB,UAAU,QAAQ,MAAM,EAAE,SAAS,SAAS,EAAE,CAAC,SAAS;AACvF,UAAQ,IACN,yBAAyB,UAAU,QAAQ,MAAM,EAAE,oBAAoB,EAAE,iBAAiB,SAAS,EAAE,CAAC,SACvG;UACM,OAAgB;AACvB,MAAI,iBAAiB,MAAO,OAAM;AAClC,QAAM,IAAI,MAAM,OAAO,MAAM,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/cli/commands/doctor.ts"],"mappings":";;;;;;;;;;;;;iBAgBsB,MAAA,CAAO,KAAA,cAAuB,OAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/cli/commands/doctor.ts"],"sourcesContent":["/**\n * Arc CLI - Doctor Command\n *\n * Health check utility that validates the development environment.\n * Checks Node.js version, dependencies, configuration, and env variables.\n */\n\nimport { readFileSync, existsSync } from 'node:fs';\nimport { resolve, join } from 'node:path';\n\ninterface CheckResult {\n status: 'pass' | 'warn' | 'fail';\n label: string;\n detail?: string;\n}\n\nexport async function doctor(_args: string[] = []): Promise<void> {\n console.log('\\nArc Doctor\\n');\n\n const results: CheckResult[] = [];\n const cwd = process.cwd();\n\n // 1. Node.js version\n const nodeVersion = process.versions.node;\n const nodeMajor = parseInt(nodeVersion.split('.')[0] ?? '0', 10);\n if (nodeMajor >= 22) {\n results.push({ status: 'pass', label: `Node.js ${nodeVersion}`, detail: 'required: >=22' });\n } else {\n results.push({ status: 'fail', label: `Node.js ${nodeVersion}`, detail: 'required: >=22' });\n }\n\n // 2. Find nearest package.json\n const pkg = findPackageJson(cwd);\n const allDeps = {\n ...(pkg?.dependencies ?? {}),\n ...(pkg?.devDependencies ?? {}),\n };\n\n // 3. Arc version\n const arcVersion = allDeps['@classytic/arc'];\n if (arcVersion) {\n results.push({ status: 'pass', label: `@classytic/arc ${arcVersion}` });\n } else {\n results.push({ status: 'warn', label: '@classytic/arc not found in dependencies' });\n }\n\n // 4. Fastify version\n const fastifyVersion = allDeps['fastify'];\n if (fastifyVersion) {\n const clean = fastifyVersion.replace(/^[\\^~>=<]+/, '');\n const major = parseInt(clean.split('.')[0] ?? '0', 10);\n if (major >= 5) {\n results.push({ status: 'pass', label: `fastify ${fastifyVersion}`, detail: 'required: ^5.0.0' });\n } else {\n results.push({ status: 'fail', label: `fastify ${fastifyVersion}`, detail: 'required: ^5.0.0 — Arc requires Fastify 5' });\n }\n } else {\n results.push({ status: 'fail', label: 'fastify not found in dependencies', detail: 'required: ^5.0.0' });\n }\n\n // 5. tsconfig.json\n const tsconfigPath = resolve(cwd, 'tsconfig.json');\n if (existsSync(tsconfigPath)) {\n try {\n const raw = readFileSync(tsconfigPath, 'utf-8');\n // Strip single-line comments for JSON parsing\n const stripped = raw.replace(/\\/\\/.*$/gm, '');\n const tsconfig = JSON.parse(stripped);\n const moduleRes = tsconfig?.compilerOptions?.moduleResolution;\n if (moduleRes && !['nodenext', 'node16', 'bundler'].includes(moduleRes.toLowerCase())) {\n results.push({ status: 'warn', label: 'tsconfig.json found', detail: `moduleResolution \"${moduleRes}\" — recommend \"NodeNext\" or \"Bundler\"` });\n } else {\n results.push({ status: 'pass', label: 'tsconfig.json found' });\n }\n } catch {\n results.push({ status: 'pass', label: 'tsconfig.json found' });\n }\n } else {\n results.push({ status: 'warn', label: 'tsconfig.json not found' });\n }\n\n // 6. Peer / optional dependencies\n const optionalDeps: Array<{ name: string; purpose: string }> = [\n { name: '@fastify/rate-limit', purpose: 'rate limiting' },\n { name: '@fastify/helmet', purpose: 'security headers' },\n { name: '@fastify/cors', purpose: 'CORS support' },\n ];\n\n for (const dep of optionalDeps) {\n if (allDeps[dep.name]) {\n results.push({ status: 'pass', label: `${dep.name} installed` });\n } else {\n results.push({ status: 'warn', label: `${dep.name} not installed`, detail: `${dep.purpose} disabled` });\n }\n }\n\n // 7. Better Auth detection\n if (allDeps['better-auth']) {\n results.push({ status: 'pass', label: `better-auth ${allDeps['better-auth']}` });\n }\n\n // 8. Environment variables\n const envChecks: Array<{ name: string; severity: 'warn' | 'fail'; detail: string }> = [\n { name: 'MONGO_URI', severity: 'warn', detail: 'required at runtime for MongoDB' },\n { name: 'BETTER_AUTH_SECRET', severity: 'warn', detail: 'required for Better Auth session encryption' },\n ];\n\n for (const env of envChecks) {\n if (process.env[env.name]) {\n results.push({ status: 'pass', label: `${env.name} set` });\n } else {\n results.push({ status: env.severity, label: `${env.name} not set`, detail: env.detail });\n }\n }\n\n // Print results\n let passCount = 0;\n let warnCount = 0;\n let failCount = 0;\n\n for (const r of results) {\n const icon = r.status === 'pass' ? '[pass]' : r.status === 'warn' ? '[warn]' : '[FAIL]';\n const detail = r.detail ? ` (${r.detail})` : '';\n console.log(` ${icon} ${r.label}${detail}`);\n\n if (r.status === 'pass') passCount++;\n else if (r.status === 'warn') warnCount++;\n else failCount++;\n }\n\n console.log(`\\n${passCount} passed, ${warnCount} warnings, ${failCount} failures\\n`);\n\n if (failCount > 0) {\n process.exitCode = 1;\n }\n}\n\nfunction findPackageJson(dir: string): Record<string, Record<string, string>> | null {\n const paths = [\n join(dir, 'package.json'),\n join(dir, '..', 'package.json'),\n ];\n\n for (const p of paths) {\n try {\n if (existsSync(p)) {\n return JSON.parse(readFileSync(p, 'utf-8'));\n }\n } catch {\n // Skip\n }\n }\n return null;\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"constants-DdXFXQtN.mjs","names":[],"sources":["../src/constants.ts"],"sourcesContent":["/**\n * Arc Framework Constants — Single Source of Truth\n *\n * Every default value, magic string, and framework constant lives here.\n * Import from this module instead of hard-coding values inline.\n *\n * All exported values are deeply frozen (Object.freeze) to prevent\n * accidental mutation at runtime — inspired by Go's const blocks\n * and Rust's immutable-by-default philosophy.\n */\n\n// ============================================================================\n// CRUD Operations\n// ============================================================================\n\n/** Standard CRUD operation names */\nexport const CRUD_OPERATIONS = Object.freeze(\n ['list', 'get', 'create', 'update', 'delete'] as const,\n);\nexport type CrudOperation = (typeof CRUD_OPERATIONS)[number];\n\n/** Mutation operations that emit events */\nexport const MUTATION_OPERATIONS = Object.freeze(\n ['create', 'update', 'delete'] as const,\n);\nexport type MutationOperation = (typeof MUTATION_OPERATIONS)[number];\n\n// ============================================================================\n// Hook Phases\n// ============================================================================\n\n/** Lifecycle hook phases */\nexport const HOOK_PHASES = Object.freeze(\n ['before', 'around', 'after'] as const,\n);\nexport type HookPhase = (typeof HOOK_PHASES)[number];\n\n/** Hook operations (superset of CRUD — includes 'read' alias for 'get') */\nexport const HOOK_OPERATIONS = Object.freeze(\n ['create', 'update', 'delete', 'read', 'list'] as const,\n);\nexport type HookOperation = (typeof HOOK_OPERATIONS)[number];\n\n// ============================================================================\n// Pagination & Query Defaults\n// ============================================================================\n\n/** Default items per page */\nexport const DEFAULT_LIMIT = 20 as const;\n\n/** Maximum items per page (framework-wide ceiling) */\nexport const DEFAULT_MAX_LIMIT = 1000 as const;\n\n/** Default sort field (descending creation date) */\nexport const DEFAULT_SORT = '-createdAt' as const;\n\n// ============================================================================\n// Field & Schema Defaults\n// ============================================================================\n\n/** Default primary key field name */\nexport const DEFAULT_ID_FIELD = '_id' as const;\n\n/** Default multi-tenant scoping field */\nexport const DEFAULT_TENANT_FIELD = 'organizationId' as const;\n\n/** Default HTTP method for update routes */\nexport const DEFAULT_UPDATE_METHOD = 'PATCH' as const;\n\n/** System-managed fields that cannot be set via request body */\nexport const SYSTEM_FIELDS = Object.freeze(\n ['_id', '__v', 'createdAt', 'updatedAt', 'deletedAt'] as const,\n);\n\n// ============================================================================\n// Security Limits\n// ============================================================================\n\n/** Maximum regex pattern length (ReDoS mitigation) */\nexport const MAX_REGEX_LENGTH = 200 as const;\n\n/** Maximum search query length */\nexport const MAX_SEARCH_LENGTH = 200 as const;\n\n/** Maximum filter nesting depth (prevents filter bombs) */\nexport const MAX_FILTER_DEPTH = 10 as const;\n\n// ============================================================================\n// Reserved Query Parameters\n// ============================================================================\n\n/**\n * Query parameters consumed by the framework — never treated as filters.\n * Shared by all query parsers (Arc built-in, Prisma, custom).\n */\nexport const RESERVED_QUERY_PARAMS = Object.freeze(\n new Set([\n 'page',\n 'limit',\n 'sort',\n 'populate',\n 'search',\n 'select',\n 'after',\n 'cursor',\n 'lean',\n '_policyFilters',\n ]),\n);\n"],"mappings":";;;;;;;;;;;;AAgBA,MAAa,kBAAkB,OAAO,OACpC;CAAC;CAAQ;CAAO;CAAU;CAAU;CAAS,CAC9C;;AAID,MAAa,sBAAsB,OAAO,OACxC;CAAC;CAAU;CAAU;CAAS,CAC/B;;AAQD,MAAa,cAAc,OAAO,OAChC;CAAC;CAAU;CAAU;CAAQ,CAC9B;;AAID,MAAa,kBAAkB,OAAO,OACpC;CAAC;CAAU;CAAU;CAAU;CAAQ;CAAO,CAC/C;;AAQD,MAAa,gBAAgB;;AAG7B,MAAa,oBAAoB;;AAGjC,MAAa,eAAe;;AAO5B,MAAa,mBAAmB;;AAGhC,MAAa,uBAAuB;;AAGpC,MAAa,wBAAwB;;AAGrC,MAAa,gBAAgB,OAAO,OAClC;CAAC;CAAO;CAAO;CAAa;CAAa;CAAY,CACtD;;AAOD,MAAa,mBAAmB;;AAGhC,MAAa,oBAAoB;;AAGjC,MAAa,mBAAmB;;;;;AAUhC,MAAa,wBAAwB,OAAO,OAC1C,IAAI,IAAI;CACN;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC,CACH"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"createApp-CUgNqegw.mjs","names":[],"sources":["../src/factory/presets.ts","../src/factory/createApp.ts"],"sourcesContent":["/**\n * Environment Presets for createApp\n *\n * Provides sensible defaults for different environments:\n * - production: Strict security, performance optimized\n * - development: Relaxed CORS, verbose logging\n * - testing: In-memory DB, no rate limiting\n */\n\nimport type { CreateAppOptions } from './types.js';\n\n/**\n * Production preset - strict security, performance optimized\n */\nexport const productionPreset: Partial<CreateAppOptions> = {\n // Raw JSON logs for production (log aggregators like Datadog, CloudWatch, etc.)\n logger: {\n level: 'info',\n // Redact sensitive data from logs to prevent credential leaks\n redact: {\n paths: [\n 'req.headers.authorization',\n 'req.headers.cookie',\n 'req.headers[\"set-cookie\"]',\n '*.password',\n '*.secret',\n '*.token',\n '*.accessToken',\n '*.refreshToken',\n '*.creditCard',\n ],\n censor: '[REDACTED]',\n },\n },\n trustProxy: true,\n\n // Security\n helmet: {\n contentSecurityPolicy: {\n directives: {\n defaultSrc: [\"'self'\"],\n styleSrc: [\"'self'\", \"'unsafe-inline'\"],\n scriptSrc: [\"'self'\"],\n imgSrc: [\"'self'\", \"data:\", \"https:\"],\n },\n },\n },\n\n // CORS - must be explicitly configured\n cors: {\n origin: false, // Disabled by default in production\n credentials: true,\n methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],\n allowedHeaders: ['Content-Type', 'Authorization', 'Accept'],\n },\n\n // Rate limiting - strict\n rateLimit: {\n max: 100,\n timeWindow: '1 minute',\n },\n\n // Note: Compression not included (use proxy/CDN instead)\n\n // Under pressure - health monitoring\n underPressure: {\n exposeStatusRoute: true,\n maxEventLoopDelay: 1000,\n maxHeapUsedBytes: 1024 * 1024 * 1024, // 1GB\n maxRssBytes: 1024 * 1024 * 1024, // 1GB\n },\n};\n\n/**\n * Development preset - relaxed security, verbose logging\n */\nexport const developmentPreset: Partial<CreateAppOptions> = {\n logger: {\n level: 'debug',\n transport: {\n target: 'pino-pretty',\n options: {\n colorize: true,\n translateTime: 'SYS:HH:MM:ss',\n ignore: 'pid,hostname',\n },\n },\n },\n trustProxy: true,\n\n // Security - relaxed for development\n helmet: {\n contentSecurityPolicy: false, // Disable CSP in dev\n },\n\n // CORS - allow all origins in development\n cors: {\n origin: true, // Allow all origins\n credentials: true,\n methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],\n allowedHeaders: ['Content-Type', 'Authorization', 'Accept'],\n },\n\n // Rate limiting - very relaxed\n rateLimit: {\n max: 1000,\n timeWindow: '1 minute',\n },\n\n // Note: Compression not included (use proxy/CDN instead)\n\n // Under pressure - relaxed\n underPressure: {\n exposeStatusRoute: true,\n maxEventLoopDelay: 5000,\n },\n};\n\n/**\n * Testing preset - minimal setup, fast startup\n */\nexport const testingPreset: Partial<CreateAppOptions> = {\n logger: false, // Disable logging in tests\n trustProxy: false,\n\n // Security - disabled for tests\n helmet: false,\n cors: false,\n rateLimit: false,\n underPressure: false,\n\n // Sensible plugins still enabled\n sensible: true,\n multipart: {\n limits: {\n fileSize: 1024 * 1024, // 1MB\n files: 5,\n },\n },\n};\n\n/**\n * Edge/Serverless preset - minimal cold-start overhead\n *\n * Optimized for AWS Lambda, Vercel, Cloudflare Workers, and similar environments.\n * Disables all heavy plugins that add cold-start latency:\n * - Security headers (handled by API Gateway / CDN)\n * - Rate limiting (handled by API Gateway / CDN)\n * - Health monitoring (Lambda has its own health checks)\n * - File uploads (use pre-signed URLs instead)\n * - Raw body parsing (register per-route if needed)\n *\n * Arc core plugins (requestId, health, gracefulShutdown) are also disabled\n * since the serverless runtime manages request lifecycle.\n */\nexport const edgePreset: Partial<CreateAppOptions> = {\n logger: {\n level: 'warn', // Minimal logging to reduce I/O overhead\n },\n trustProxy: true, // Always behind API Gateway / CDN\n\n // Security — handled by API Gateway / CDN layer\n helmet: false,\n cors: false,\n rateLimit: false,\n\n // Performance — not needed in serverless\n underPressure: false,\n\n // Utilities — minimal footprint\n sensible: true,\n multipart: false, // Use pre-signed URLs for file uploads\n rawBody: false, // Register per-route if needed for webhooks\n\n // Arc plugins — serverless runtime handles lifecycle\n arcPlugins: {\n requestId: false, // API Gateway provides request IDs\n health: false, // Lambda has its own health checks\n gracefulShutdown: false, // Runtime manages shutdown\n emitEvents: true, // Keep events for business logic\n },\n};\n\n/**\n * Get preset by name\n */\nexport function getPreset(name: 'production' | 'development' | 'testing' | 'edge'): Partial<CreateAppOptions> {\n switch (name) {\n case 'production':\n return productionPreset;\n case 'development':\n return developmentPreset;\n case 'testing':\n return testingPreset;\n case 'edge':\n return edgePreset;\n default:\n throw new Error(`Unknown preset: ${name}`);\n }\n}\n","/**\n * ArcFactory - Production-ready Fastify application factory\n *\n * Enforces security best practices by making plugins opt-out instead of opt-in.\n * A developer must explicitly disable security features rather than forget to enable them.\n *\n * Note: Arc is database-agnostic. Connect your database separately and provide\n * adapters when defining resources. This allows multiple databases, custom\n * connection pooling, and full control over your data layer.\n *\n * @example\n * // 1. Connect your database(s) separately\n * import mongoose from 'mongoose';\n * await mongoose.connect(process.env.MONGO_URI);\n *\n * // 2. Create Arc app (no database config needed)\n * const app = await createApp({\n * preset: 'production',\n * auth: { type: 'jwt', jwt: { secret: process.env.JWT_SECRET } },\n * cors: { origin: ['https://example.com'] },\n * });\n *\n * // 3. Register resources with your adapters\n * await app.register(productResource.toPlugin());\n *\n * @example\n * // Multiple databases example\n * const primaryDb = await mongoose.connect(process.env.PRIMARY_DB);\n * const analyticsDb = mongoose.createConnection(process.env.ANALYTICS_DB);\n *\n * const orderResource = defineResource({\n * adapter: createMongooseAdapter({ model: OrderModel, repository: orderRepo }),\n * });\n *\n * const analyticsResource = defineResource({\n * adapter: createMongooseAdapter({ model: AnalyticsModel, repository: analyticsRepo }),\n * });\n */\n\nimport Fastify, { type FastifyInstance, type FastifyReply, type FastifyRequest } from 'fastify';\nimport qs from 'qs';\nimport type { CreateAppOptions } from './types.js';\nimport { getPreset } from './presets.js';\nimport { PUBLIC_SCOPE } from '../scope/types.js';\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Fastify plugin types vary per package\ntype FastifyPlugin = (...args: any[]) => any;\n\n// Plugin registry: name → { package, loader, optional }\nconst PLUGIN_REGISTRY: Record<string, {\n package: string;\n loader: () => Promise<FastifyPlugin>;\n optional?: boolean;\n}> = {\n cors: { package: '@fastify/cors', loader: () => import('@fastify/cors').then(m => m.default) },\n helmet: { package: '@fastify/helmet', loader: () => import('@fastify/helmet').then(m => m.default) },\n rateLimit: { package: '@fastify/rate-limit', loader: () => import('@fastify/rate-limit').then(m => m.default) },\n underPressure: { package: '@fastify/under-pressure', loader: () => import('@fastify/under-pressure').then(m => m.default) },\n sensible: { package: '@fastify/sensible', loader: () => import('@fastify/sensible').then(m => m.default) },\n multipart: { package: '@fastify/multipart', loader: () => import('@fastify/multipart').then(m => m.default), optional: true },\n rawBody: { package: 'fastify-raw-body', loader: () => import('fastify-raw-body').then(m => m.default), optional: true },\n};\n\n// Import plugins (with lazy loading for optional dependencies)\nasync function loadPlugin(name: string, logger?: { warn: (msg: string) => void }): Promise<FastifyPlugin | null> {\n const entry = PLUGIN_REGISTRY[name];\n if (!entry) {\n throw new Error(`Unknown plugin: ${name}`);\n }\n\n try {\n return await entry.loader();\n } catch (error) {\n const err = error as Error;\n const isModuleNotFound = err.message.includes('Cannot find module') ||\n err.message.includes('Cannot find package') ||\n err.message.includes('MODULE_NOT_FOUND') ||\n err.message.includes('Could not resolve');\n\n // For optional plugins, return null instead of throwing\n if (isModuleNotFound && entry.optional) {\n logger?.warn(`Optional plugin '${name}' skipped (${entry.package} not installed)`);\n return null;\n }\n\n // For required plugins, throw helpful error\n if (isModuleNotFound) {\n throw new Error(\n `Plugin '${name}' requires package '${entry.package}' which is not installed.\\n` +\n `Install it with: npm install ${entry.package}\\n` +\n `Or disable this plugin by setting ${name}: false in createApp options.`\n );\n }\n\n // Re-throw other errors\n throw new Error(`Failed to load plugin '${name}': ${err.message}`);\n }\n}\n\n/**\n * Create a production-ready Fastify application with Arc framework\n *\n * Security plugins are enabled by default (opt-out):\n * - helmet (security headers)\n * - cors (cross-origin requests)\n * - rateLimit (DDoS protection)\n * - underPressure (health monitoring)\n *\n * Note: Compression is not included due to known Fastify 5 issues.\n * Use a reverse proxy (Nginx, Caddy) or CDN for compression.\n *\n * @param options - Application configuration\n * @returns Configured Fastify instance\n */\nexport async function createApp(options: CreateAppOptions): Promise<FastifyInstance> {\n // ============================================\n // 0. CONFIGURE ARC LOGGER\n // ============================================\n if (options.debug !== undefined && options.debug !== false) {\n const { configureArcLogger } = await import('../logger/index.js');\n configureArcLogger({ debug: options.debug });\n }\n\n // ============================================\n // 1. VALIDATE AUTH OPTIONS\n // ============================================\n const authConfig = options.auth;\n const isAuthDisabled = authConfig === false;\n\n // Validate JWT auth requires a secret (unless a custom authenticator is provided)\n if (!isAuthDisabled && authConfig && authConfig.type === 'jwt') {\n if (!authConfig.jwt?.secret && !authConfig.authenticate) {\n throw new Error(\n 'createApp: JWT secret required when Arc auth is enabled.\\n' +\n 'Provide auth.jwt.secret, auth.authenticate, or set auth: false to disable.\\n' +\n 'Example: auth: { type: \\'jwt\\', jwt: { secret: process.env.JWT_SECRET } }'\n );\n }\n }\n\n // ============================================\n // 1b. VALIDATE RUNTIME PROFILE\n // ============================================\n if (options.runtime === 'distributed') {\n const MEMORY_NAMES = new Set(['memory', 'memory-cache']);\n const missing: string[] = [];\n\n const eventsTransport = options.stores?.events;\n if (!eventsTransport || MEMORY_NAMES.has(eventsTransport.name)) {\n missing.push('events transport');\n }\n\n const cacheStore = options.stores?.cache;\n if (!cacheStore || MEMORY_NAMES.has(cacheStore.name)) {\n missing.push('cache store');\n }\n\n const idempotencyStore = options.stores?.idempotency;\n if (!idempotencyStore || MEMORY_NAMES.has(idempotencyStore.name)) {\n missing.push('idempotency store');\n }\n\n // QueryCache store validation (only when queryCache plugin is enabled)\n if (options.arcPlugins?.queryCache) {\n const qcStore = options.stores?.queryCache;\n if (!qcStore || MEMORY_NAMES.has(qcStore.name)) {\n missing.push('queryCache store');\n }\n }\n\n if (missing.length > 0) {\n throw new Error(\n `[Arc] runtime: 'distributed' requires Redis/durable adapters.\\n` +\n `Missing: ${missing.join(', ')}.\\n` +\n `Provide Redis-backed stores or use runtime: 'memory' for development.`\n );\n }\n }\n\n // ============================================\n // 2. MERGE WITH PRESET\n // ============================================\n const presetConfig = options.preset ? getPreset(options.preset) : {};\n const config = { ...presetConfig, ...options }; // User options override preset\n\n // ============================================\n // 3. CREATE FASTIFY INSTANCE\n // ============================================\n let fastify: FastifyInstance = Fastify({\n logger: config.logger ?? true,\n trustProxy: config.trustProxy ?? false,\n // Use qs parser to support nested bracket notation in query strings\n // e.g., ?populate[author][select]=name,email → { populate: { author: { select: 'name,email' } } }\n // This is required for MongoKit's advanced populate options to work\n // Placed under routerOptions to avoid FSTDEP022 deprecation warning in Fastify 5\n routerOptions: {\n querystringParser: (str: string) => qs.parse(str),\n },\n ajv: {\n customOptions: {\n coerceTypes: true,\n useDefaults: true,\n removeAdditional: false,\n // Allow OpenAPI keywords (example, description, etc.) in schemas\n // These are used by response schemas for documentation but aren't standard JSON Schema\n keywords: ['example'],\n },\n },\n });\n\n // Apply TypeBox type provider if requested\n // This enables TypeScript type inference from TypeBox route schemas\n if (config.typeProvider === 'typebox') {\n try {\n const { TypeBoxValidatorCompiler } = await import('@fastify/type-provider-typebox');\n fastify.setValidatorCompiler(TypeBoxValidatorCompiler);\n fastify.log.debug('TypeBox type provider enabled');\n } catch {\n fastify.log.warn(\n 'typeProvider: \"typebox\" requested but @fastify/type-provider-typebox is not installed. ' +\n 'Install it with: npm install @sinclair/typebox @fastify/type-provider-typebox'\n );\n }\n }\n\n // ============================================\n // 3b. FIX EMPTY JSON BODY ON DELETE/GET REQUESTS\n // ============================================\n // Some clients (browsers, fetch wrappers) send Content-Type: application/json\n // on DELETE/GET requests with no body. Fastify's default JSON parser rejects\n // empty bodies with FST_ERR_CTP_EMPTY_JSON_BODY. Override to treat them as undefined.\n fastify.removeContentTypeParser('application/json');\n fastify.addContentTypeParser(\n 'application/json',\n { parseAs: 'string' },\n (_req: FastifyRequest, body: string, done: (err: Error | null, body?: unknown) => void) => {\n if (!body || body.length === 0) {\n return done(null, undefined);\n }\n try {\n done(null, JSON.parse(body));\n } catch (err) {\n done(err as Error);\n }\n }\n );\n\n // ============================================\n // 4. REGISTER SECURITY PLUGINS (opt-out)\n // ============================================\n\n // Helmet - Security headers\n if (config.helmet !== false) {\n const helmet = (await loadPlugin('helmet'))!;\n // Use type assertion to handle complex helmet options\n await fastify.register(helmet, (config.helmet ?? {}) as Record<string, unknown>);\n fastify.log.debug('Helmet (security headers) enabled');\n } else {\n fastify.log.warn('Helmet disabled - security headers not applied');\n }\n\n // CORS - Cross-origin requests\n if (config.cors !== false) {\n const cors = (await loadPlugin('cors'))!;\n const corsOptions = config.cors ?? {};\n\n // Require explicit origin in production\n if (config.preset === 'production' && (!corsOptions || !('origin' in corsOptions))) {\n throw new Error(\n 'CORS origin must be explicitly configured in production.\\n' +\n 'Set cors.origin to allowed domains or set cors: false to disable.\\n' +\n 'Example: cors: { origin: [\\'https://yourdomain.com\\'] }\\n' +\n 'Docs: https://github.com/classytic/arc#security'\n );\n }\n\n await fastify.register(cors, corsOptions);\n fastify.log.debug('CORS enabled');\n } else {\n fastify.log.warn('CORS disabled');\n }\n\n // Rate limiting - DDoS protection\n if (config.rateLimit !== false) {\n const rateLimit = (await loadPlugin('rateLimit'))!;\n const rateLimitOpts = config.rateLimit ?? { max: 100, timeWindow: '1 minute' };\n await fastify.register(rateLimit, rateLimitOpts);\n\n // Warn if production without Redis store (in-memory = per-instance counters)\n if (config.preset === 'production') {\n const hasStore = typeof rateLimitOpts === 'object' && 'store' in rateLimitOpts;\n if (!hasStore) {\n fastify.log.warn(\n 'Rate limiting is using in-memory store. In multi-instance deployments, ' +\n 'each instance tracks limits independently. Configure a Redis store for distributed rate limiting: ' +\n 'rateLimit: { store: new RedisStore({ ... }) }'\n );\n }\n }\n\n fastify.log.debug('Rate limiting enabled');\n } else {\n fastify.log.warn('Rate limiting disabled');\n }\n\n // ============================================\n // 5. REGISTER PERFORMANCE PLUGINS\n // ============================================\n\n // Note: Compression is NOT included due to known Fastify 5 stream issues.\n // Use a reverse proxy (Nginx, Caddy) or CDN for response compression.\n // See: https://github.com/fastify/fastify/issues/6017\n if (config.preset === 'production') {\n fastify.log.warn(\n 'Response compression is not enabled (Fastify 5 stream issues). ' +\n 'Use a reverse proxy (Nginx, Caddy, Cloudflare) for gzip/brotli in production.'\n );\n }\n\n // Under Pressure - Health monitoring\n if (config.underPressure !== false) {\n const underPressure = (await loadPlugin('underPressure'))!;\n await fastify.register(underPressure, config.underPressure ?? { exposeStatusRoute: true });\n fastify.log.debug('Health monitoring (under-pressure) enabled');\n } else {\n fastify.log.debug('Health monitoring disabled');\n }\n\n // ============================================\n // 6. REGISTER UTILITY PLUGINS (opt-out)\n // ============================================\n\n // Sensible - HTTP helpers\n if (config.sensible !== false) {\n const sensible = (await loadPlugin('sensible'))!;\n await fastify.register(sensible);\n fastify.log.debug('Sensible (HTTP helpers) enabled');\n }\n\n // Multipart - File uploads (optional)\n if (config.multipart !== false) {\n const multipart = await loadPlugin('multipart', fastify.log);\n if (multipart) {\n const multipartDefaults = {\n limits: {\n fileSize: 10 * 1024 * 1024, // 10MB\n files: 10,\n },\n // CRITICAL: Throw on file size exceeded instead of silently truncating\n throwFileSizeLimit: true,\n };\n await fastify.register(multipart, { ...multipartDefaults, ...config.multipart });\n fastify.log.debug('Multipart (file uploads) enabled');\n }\n }\n\n // Raw body - For webhooks (optional)\n if (config.rawBody !== false) {\n const rawBody = await loadPlugin('rawBody', fastify.log);\n if (rawBody) {\n const rawBodyDefaults = {\n field: 'rawBody',\n global: false,\n encoding: 'utf8',\n runFirst: true,\n };\n await fastify.register(rawBody, { ...rawBodyDefaults, ...config.rawBody });\n fastify.log.debug('Raw body parsing enabled');\n }\n }\n\n // ============================================\n // 7. REGISTER ARC CORE & PLUGINS\n // ============================================\n\n // Single dynamic import for all Arc plugins\n const {\n arcCorePlugin,\n requestIdPlugin,\n healthPlugin,\n gracefulShutdownPlugin,\n } = await import('../plugins/index.js');\n\n // Always register arc core first - provides fastify.arc with hooks & registry\n // This prevents global singleton leaks between app instances (e.g., in tests)\n await fastify.register(arcCorePlugin, {\n emitEvents: config.arcPlugins?.emitEvents !== false,\n });\n\n /** Track a plugin in the Arc plugin registry */\n const trackPlugin = (name: string, opts?: Record<string, unknown>) => {\n fastify.arc.plugins.set(name, {\n name,\n options: opts,\n registeredAt: new Date().toISOString(),\n });\n };\n trackPlugin('arc-core');\n\n // Register event plugin — provides fastify.events for pub/sub.\n // Without this, arcCorePlugin's CRUD event hooks are no-ops (hasEvents check).\n // Transport is sourced from stores.events (defaults to MemoryEventTransport).\n if (config.arcPlugins?.events !== false) {\n const { default: eventPlugin } = await import('../events/eventPlugin.js');\n const eventOpts = typeof config.arcPlugins?.events === 'object' ? config.arcPlugins.events : {};\n await fastify.register(eventPlugin, {\n ...eventOpts,\n transport: options.stores?.events, // undefined → eventPlugin defaults to MemoryEventTransport\n });\n trackPlugin('arc-events', eventOpts as Record<string, unknown>);\n fastify.log.debug(`Arc events plugin enabled (transport: ${fastify.events.transportName})`);\n }\n\n // ============================================\n // 8. REGISTER ARC PLUGINS (opt-in)\n // ============================================\n\n if (config.arcPlugins?.requestId !== false) {\n await fastify.register(requestIdPlugin);\n trackPlugin('arc-request-id');\n fastify.log.debug('Arc requestId plugin enabled');\n }\n\n if (config.arcPlugins?.health !== false) {\n await fastify.register(healthPlugin);\n trackPlugin('arc-health');\n fastify.log.debug('Arc health plugin enabled');\n }\n\n if (config.arcPlugins?.gracefulShutdown !== false) {\n await fastify.register(gracefulShutdownPlugin);\n trackPlugin('arc-graceful-shutdown');\n fastify.log.debug('Arc gracefulShutdown plugin enabled');\n }\n\n // Caching plugin (opt-in)\n if (config.arcPlugins?.caching) {\n const { default: cachingPlugin } = await import('../plugins/caching.js');\n const cachingOpts = config.arcPlugins.caching === true ? {} : config.arcPlugins.caching;\n await fastify.register(cachingPlugin, cachingOpts);\n trackPlugin('arc-caching', cachingOpts as Record<string, unknown>);\n fastify.log.debug('Arc caching plugin enabled');\n }\n\n // QueryCache plugin (opt-in)\n if (config.arcPlugins?.queryCache) {\n const { queryCachePlugin } = await import('../cache/queryCachePlugin.js');\n const qcOpts = config.arcPlugins.queryCache === true ? {} : config.arcPlugins.queryCache;\n const store = options.stores?.queryCache ?? new (await import('../cache/memory.js')).MemoryCacheStore();\n await fastify.register(queryCachePlugin, { store, ...qcOpts });\n trackPlugin('arc-query-cache', qcOpts as Record<string, unknown>);\n fastify.log.debug('Arc queryCache plugin enabled');\n }\n\n // SSE plugin (opt-in, requires events)\n if (config.arcPlugins?.sse) {\n if (config.arcPlugins?.events === false) {\n fastify.log.warn('SSE plugin requires events plugin (arcPlugins.events). SSE disabled.');\n } else {\n const { default: ssePlugin } = await import('../plugins/sse.js');\n const sseOpts = config.arcPlugins.sse === true ? {} : config.arcPlugins.sse;\n await fastify.register(ssePlugin, sseOpts);\n trackPlugin('arc-sse', sseOpts as Record<string, unknown>);\n fastify.log.debug('Arc SSE plugin enabled');\n }\n }\n\n // ============================================\n // 9a. DECORATE request.scope (default: public)\n // ============================================\n // Every request starts as 'public'. Auth hooks upgrade to 'authenticated' or 'member'.\n // Elevation plugin (if registered) may further upgrade to 'elevated'.\n // Initial value is null — the onRequest hook below sets the real default per-request.\n // Using null avoids Fastify 5's reference-type sharing bug (objects are shared across requests).\n fastify.decorateRequest('scope', null!);\n fastify.addHook('onRequest', async (request) => {\n if (!request.scope) {\n request.scope = PUBLIC_SCOPE;\n }\n });\n\n // ============================================\n // 9b. REGISTER AUTHENTICATION (Arc, Better Auth, or custom)\n // ============================================\n\n if (isAuthDisabled) {\n fastify.log.debug('Authentication disabled');\n } else if (authConfig) {\n switch (authConfig.type) {\n case 'betterAuth': {\n // Better Auth adapter — registers auth routes + fastify.authenticate\n const { plugin, openapi } = authConfig.betterAuth;\n await fastify.register(plugin);\n trackPlugin('auth-better-auth');\n // Push OpenAPI paths if the adapter extracted them (and plugin didn't already)\n if (openapi && !fastify.arc.externalOpenApiPaths.includes(openapi)) {\n fastify.arc.externalOpenApiPaths.push(openapi);\n }\n fastify.log.debug('Better Auth authentication enabled');\n break;\n }\n case 'custom': {\n // Custom auth plugin — user has full control\n await fastify.register(authConfig.plugin);\n trackPlugin('auth-custom');\n fastify.log.debug('Custom authentication plugin enabled');\n break;\n }\n case 'authenticator': {\n // Custom authenticator function — decorate directly\n const { authenticate } = authConfig;\n fastify.decorate('authenticate', async function (request: FastifyRequest, reply: FastifyReply) {\n await authenticate(request, reply);\n });\n trackPlugin('auth-authenticator');\n fastify.log.debug('Custom authenticator enabled');\n break;\n }\n case 'jwt': {\n // Arc's built-in JWT auth plugin\n const { authPlugin } = await import('../auth/index.js');\n // Pass all fields except `type` to the auth plugin (matches AuthPluginOptions shape)\n const { type: _, ...arcAuthOpts } = authConfig;\n await fastify.register(authPlugin, arcAuthOpts);\n trackPlugin('auth-jwt');\n fastify.log.debug('Arc authentication plugin enabled');\n break;\n }\n }\n }\n\n // ============================================\n // 9c. REGISTER ELEVATION PLUGIN (opt-in, after auth)\n // ============================================\n if (config.elevation) {\n const { elevationPlugin } = await import('../scope/elevation.js');\n await fastify.register(elevationPlugin, config.elevation);\n trackPlugin('arc-elevation', config.elevation as Record<string, unknown>);\n fastify.log.debug('Elevation plugin enabled');\n }\n\n // ============================================\n // 9d. REGISTER ERROR HANDLER (opt-out)\n // ============================================\n if (config.errorHandler !== false) {\n const { errorHandlerPlugin } = await import('../plugins/errorHandler.js');\n const errorOpts = typeof config.errorHandler === 'object' ? config.errorHandler : {\n includeStack: config.preset !== 'production',\n };\n await fastify.register(errorHandlerPlugin, errorOpts);\n trackPlugin('arc-error-handler', errorOpts as Record<string, unknown>);\n fastify.log.debug('Arc error handler enabled');\n }\n\n // ============================================\n // 10. REGISTER CUSTOM PLUGINS\n // ============================================\n\n if (config.plugins) {\n await config.plugins(fastify);\n fastify.log.debug('Custom plugins registered');\n }\n\n // ============================================\n // 10b. LIFECYCLE HOOKS\n // ============================================\n if (config.onReady) {\n const onReady = config.onReady;\n fastify.addHook('onReady', async () => {\n await onReady(fastify);\n });\n }\n if (config.onClose) {\n const onClose = config.onClose;\n fastify.addHook('onClose', async () => {\n await onClose(fastify);\n });\n }\n\n // ============================================\n // 11. LOG SUMMARY\n // ============================================\n\n const authMode = isAuthDisabled ? 'none' : authConfig ? authConfig.type : 'none';\n fastify.log.info(\n { preset: config.preset ?? 'custom', runtime: config.runtime ?? 'memory', auth: authMode, helmet: config.helmet !== false, cors: config.cors !== false, rateLimit: config.rateLimit !== false },\n 'Arc application created'\n );\n\n return fastify;\n}\n\n/**\n * Quick factory for common scenarios\n */\nexport const ArcFactory = {\n /**\n * Create production app with strict security\n */\n async production(options: Omit<CreateAppOptions, 'preset'>): Promise<FastifyInstance> {\n return createApp({ ...options, preset: 'production' });\n },\n\n /**\n * Create development app with relaxed security\n */\n async development(options: Omit<CreateAppOptions, 'preset'>): Promise<FastifyInstance> {\n return createApp({ ...options, preset: 'development' });\n },\n\n /**\n * Create testing app with minimal setup\n */\n async testing(options: Omit<CreateAppOptions, 'preset'>): Promise<FastifyInstance> {\n return createApp({ ...options, preset: 'testing' });\n },\n};\n"],"mappings":"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"}