@classytic/arc 2.3.0 → 2.4.2

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 (175) hide show
  1. package/README.md +187 -18
  2. package/bin/arc.js +11 -3
  3. package/dist/BaseController-CkM5dUh_.mjs +1031 -0
  4. package/dist/{EventTransport-BkUDYZEb.d.mts → EventTransport-wc5hSLik.d.mts} +1 -1
  5. package/dist/{HookSystem-BsGV-j2l.mjs → HookSystem-COkyWztM.mjs} +2 -3
  6. package/dist/{ResourceRegistry-7Ic20ZMw.mjs → ResourceRegistry-DeCIFlix.mjs} +8 -5
  7. package/dist/adapters/index.d.mts +3 -5
  8. package/dist/adapters/index.mjs +2 -3
  9. package/dist/{prisma-DJbMt3yf.mjs → adapters-DTC4Ug66.mjs} +45 -12
  10. package/dist/audit/index.d.mts +4 -7
  11. package/dist/audit/index.mjs +2 -29
  12. package/dist/audit/mongodb.d.mts +1 -4
  13. package/dist/audit/mongodb.mjs +2 -3
  14. package/dist/auth/index.d.mts +7 -9
  15. package/dist/auth/index.mjs +65 -63
  16. package/dist/auth/redis-session.d.mts +1 -1
  17. package/dist/auth/redis-session.mjs +1 -2
  18. package/dist/{betterAuthOpenApi-DjWDddNc.mjs → betterAuthOpenApi-lz0IRbXJ.mjs} +4 -6
  19. package/dist/cache/index.d.mts +23 -23
  20. package/dist/cache/index.mjs +4 -6
  21. package/dist/{caching-GSDJcA6-.mjs → caching-BSXB-Xr7.mjs} +2 -24
  22. package/dist/chunk-BpYLSNr0.mjs +14 -0
  23. package/dist/circuitBreaker-BOBOpN2w.mjs +284 -0
  24. package/dist/circuitBreaker-JP2GdJ4b.d.mts +206 -0
  25. package/dist/cli/commands/describe.mjs +24 -7
  26. package/dist/cli/commands/docs.mjs +6 -7
  27. package/dist/cli/commands/doctor.d.mts +10 -0
  28. package/dist/cli/commands/doctor.mjs +156 -0
  29. package/dist/cli/commands/generate.mjs +66 -17
  30. package/dist/cli/commands/init.mjs +315 -45
  31. package/dist/cli/commands/introspect.mjs +2 -4
  32. package/dist/cli/index.d.mts +1 -10
  33. package/dist/cli/index.mjs +4 -153
  34. package/dist/{constants-DdXFXQtN.mjs → constants-Cxde4rpC.mjs} +1 -2
  35. package/dist/core/index.d.mts +3 -5
  36. package/dist/core/index.mjs +5 -4
  37. package/dist/core-C1XCMtqM.mjs +185 -0
  38. package/dist/{createApp-CgKOPhA4.mjs → createApp-ByWNRsZj.mjs} +64 -35
  39. package/dist/{defineResource-DWbpJYtm.mjs → defineResource-D9aY5Cy6.mjs} +108 -1157
  40. package/dist/discovery/index.mjs +37 -5
  41. package/dist/docs/index.d.mts +6 -9
  42. package/dist/docs/index.mjs +3 -21
  43. package/dist/dynamic/index.d.mts +93 -0
  44. package/dist/dynamic/index.mjs +122 -0
  45. package/dist/{elevation-DSTbVvYj.mjs → elevation-BEdACOLB.mjs} +5 -36
  46. package/dist/{elevation-DGo5shaX.d.mts → elevation-Ca_yveIO.d.mts} +41 -7
  47. package/dist/{errorHandler-C3GY3_ow.mjs → errorHandler--zp54tGc.mjs} +3 -5
  48. package/dist/errorHandler-Do4vVQ1f.d.mts +139 -0
  49. package/dist/{errors-DBANPbGr.mjs → errors-rxhfP7Hf.mjs} +1 -2
  50. package/dist/{eventPlugin-BEOvaDqo.mjs → eventPlugin-Ba00swHF.mjs} +25 -27
  51. package/dist/{eventPlugin-H6wDDjGO.d.mts → eventPlugin-iGrSEmwJ.d.mts} +105 -5
  52. package/dist/events/index.d.mts +72 -7
  53. package/dist/events/index.mjs +216 -4
  54. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  55. package/dist/events/transports/redis-stream-entry.mjs +19 -7
  56. package/dist/events/transports/redis.d.mts +1 -1
  57. package/dist/events/transports/redis.mjs +3 -4
  58. package/dist/factory/index.d.mts +23 -9
  59. package/dist/factory/index.mjs +48 -3
  60. package/dist/{fields-Bi_AVKSo.d.mts → fields-DFwdaWCq.d.mts} +1 -1
  61. package/dist/{fields-CTd_CrKr.mjs → fields-ipsbIRPK.mjs} +1 -2
  62. package/dist/hooks/index.d.mts +1 -3
  63. package/dist/hooks/index.mjs +2 -3
  64. package/dist/idempotency/index.d.mts +5 -5
  65. package/dist/idempotency/index.mjs +3 -7
  66. package/dist/idempotency/mongodb.d.mts +1 -1
  67. package/dist/idempotency/mongodb.mjs +4 -5
  68. package/dist/idempotency/redis.d.mts +1 -1
  69. package/dist/idempotency/redis.mjs +2 -5
  70. package/dist/{fastifyAdapter-6b_eRDBw.d.mts → index-BL8CaQih.d.mts} +56 -57
  71. package/dist/index-Diqcm14c.d.mts +369 -0
  72. package/dist/{prisma-Dy5S5F5i.d.mts → index-yhxyjqNb.d.mts} +4 -5
  73. package/dist/index.d.mts +100 -105
  74. package/dist/index.mjs +85 -58
  75. package/dist/integrations/event-gateway.d.mts +1 -1
  76. package/dist/integrations/event-gateway.mjs +8 -4
  77. package/dist/integrations/index.d.mts +4 -2
  78. package/dist/integrations/index.mjs +1 -1
  79. package/dist/integrations/jobs.d.mts +2 -2
  80. package/dist/integrations/jobs.mjs +63 -14
  81. package/dist/integrations/mcp/index.d.mts +219 -0
  82. package/dist/integrations/mcp/index.mjs +572 -0
  83. package/dist/integrations/mcp/testing.d.mts +53 -0
  84. package/dist/integrations/mcp/testing.mjs +104 -0
  85. package/dist/integrations/streamline.mjs +39 -19
  86. package/dist/integrations/webhooks.d.mts +56 -0
  87. package/dist/integrations/webhooks.mjs +139 -0
  88. package/dist/integrations/websocket-redis.d.mts +46 -0
  89. package/dist/integrations/websocket-redis.mjs +50 -0
  90. package/dist/integrations/websocket.d.mts +68 -2
  91. package/dist/integrations/websocket.mjs +96 -13
  92. package/dist/{interface-CSNjltAc.d.mts → interface-B4awm1RJ.d.mts} +2 -2
  93. package/dist/interface-DGmPxakH.d.mts +2213 -0
  94. package/dist/{keys-DhqDRxv3.mjs → keys-qcD-TVJl.mjs} +3 -4
  95. package/dist/{logger-ByrvQWZO.mjs → logger-Dz3j1ItV.mjs} +2 -4
  96. package/dist/{memory-B2v7KrCB.mjs → memory-Cb_7iy9e.mjs} +2 -4
  97. package/dist/metrics-Csh4nsvv.mjs +224 -0
  98. package/dist/migrations/index.d.mts +113 -44
  99. package/dist/migrations/index.mjs +84 -102
  100. package/dist/{mongodb-DNKEExbf.mjs → mongodb-BuQ7fNTg.mjs} +1 -4
  101. package/dist/{mongodb-ClykrfGo.d.mts → mongodb-CUpYfxfD.d.mts} +2 -3
  102. package/dist/{mongodb-Dg8O_gvd.d.mts → mongodb-bga9AbkD.d.mts} +2 -2
  103. package/dist/{openapi-9nB_kiuR.mjs → openapi-CBmZ6EQN.mjs} +4 -21
  104. package/dist/org/index.d.mts +12 -14
  105. package/dist/org/index.mjs +92 -119
  106. package/dist/org/types.d.mts +2 -2
  107. package/dist/org/types.mjs +1 -1
  108. package/dist/permissions/index.d.mts +4 -278
  109. package/dist/permissions/index.mjs +4 -579
  110. package/dist/permissions-CA5zg0yK.mjs +751 -0
  111. package/dist/plugins/index.d.mts +104 -107
  112. package/dist/plugins/index.mjs +203 -313
  113. package/dist/plugins/response-cache.mjs +4 -69
  114. package/dist/plugins/tracing-entry.d.mts +1 -1
  115. package/dist/plugins/tracing-entry.mjs +24 -11
  116. package/dist/{pluralize-CM-jZg7p.mjs → pluralize-CcT6qF0a.mjs} +12 -13
  117. package/dist/policies/index.d.mts +2 -2
  118. package/dist/policies/index.mjs +80 -83
  119. package/dist/presets/index.d.mts +26 -19
  120. package/dist/presets/index.mjs +2 -142
  121. package/dist/presets/multiTenant.d.mts +1 -4
  122. package/dist/presets/multiTenant.mjs +4 -6
  123. package/dist/presets-C9QXJV1u.mjs +422 -0
  124. package/dist/{queryCachePlugin-B6R0d4av.mjs → queryCachePlugin-ClosZdNS.mjs} +6 -27
  125. package/dist/{queryCachePlugin-Q6SYuHZ6.d.mts → queryCachePlugin-DcmETvcB.d.mts} +3 -3
  126. package/dist/queryParser-CgCtsjti.mjs +352 -0
  127. package/dist/{redis-UwjEp8Ea.d.mts → redis-CQ5YxMC5.d.mts} +2 -2
  128. package/dist/{redis-stream-CBg0upHI.d.mts → redis-stream-BW9UKLZM.d.mts} +9 -2
  129. package/dist/registry/index.d.mts +1 -4
  130. package/dist/registry/index.mjs +3 -4
  131. package/dist/{introspectionPlugin-B3JkrjwU.mjs → registry-I-ogLgL9.mjs} +1 -8
  132. package/dist/{requestContext-xi6OKBL-.mjs → requestContext-DYtmNpm5.mjs} +1 -3
  133. package/dist/resourceToTools-PMFE8HIv.mjs +533 -0
  134. package/dist/rpc/index.d.mts +90 -0
  135. package/dist/rpc/index.mjs +248 -0
  136. package/dist/{schemaConverter-Dtg0Kt9T.mjs → schemaConverter-DjzHpFam.mjs} +1 -2
  137. package/dist/schemas/index.d.mts +30 -30
  138. package/dist/schemas/index.mjs +2 -4
  139. package/dist/scope/index.d.mts +13 -2
  140. package/dist/scope/index.mjs +18 -5
  141. package/dist/{sessionManager-D_iEHjQl.d.mts → sessionManager-wbkYj2HL.d.mts} +2 -2
  142. package/dist/{sse-DkqQ1uxb.mjs → sse-BkViJPlT.mjs} +4 -25
  143. package/dist/testing/index.d.mts +551 -567
  144. package/dist/testing/index.mjs +1744 -1799
  145. package/dist/{tracing-8CEbhF0w.d.mts → tracing-bz_U4EM1.d.mts} +6 -1
  146. package/dist/{typeGuards-DwxA1t_L.mjs → typeGuards-Cj5Rgvlg.mjs} +1 -2
  147. package/dist/types/index.d.mts +4 -946
  148. package/dist/types/index.mjs +2 -4
  149. package/dist/types-BJmgxNbF.d.mts +275 -0
  150. package/dist/{types-RLkFVgaw.d.mts → types-BNUccdcf.d.mts} +2 -2
  151. package/dist/{types-Beqn1Un7.mjs → types-C6TQjtdi.mjs} +30 -2
  152. package/dist/{types-tKwaViYB.d.mts → types-Dt0-AI6E.d.mts} +68 -27
  153. package/dist/{types-DelU6kln.mjs → types-ZUu_h0jp.mjs} +1 -2
  154. package/dist/utils/index.d.mts +254 -351
  155. package/dist/utils/index.mjs +7 -6
  156. package/dist/utils-Dc0WhlIl.mjs +594 -0
  157. package/dist/versioning-BzfeHmhj.mjs +37 -0
  158. package/package.json +44 -10
  159. package/skills/arc/SKILL.md +518 -0
  160. package/skills/arc/references/auth.md +250 -0
  161. package/skills/arc/references/events.md +272 -0
  162. package/skills/arc/references/integrations.md +385 -0
  163. package/skills/arc/references/mcp.md +431 -0
  164. package/skills/arc/references/production.md +610 -0
  165. package/skills/arc/references/testing.md +183 -0
  166. package/dist/audited-CGdLiSlE.mjs +0 -140
  167. package/dist/chunk-C7Uep-_p.mjs +0 -20
  168. package/dist/circuitBreaker-CSS2VvL6.mjs +0 -1109
  169. package/dist/errorHandler-CW3OOeYq.d.mts +0 -72
  170. package/dist/interface-BtdYtQUA.d.mts +0 -1114
  171. package/dist/presets-BTeYbw7h.d.mts +0 -57
  172. package/dist/presets-CeFtfDR8.mjs +0 -119
  173. /package/dist/{errors-DAWRdiYP.d.mts → errors-CPpvPHT0.d.mts} +0 -0
  174. /package/dist/{externalPaths-SyPF2tgK.d.mts → externalPaths-DpO-s7r8.d.mts} +0 -0
  175. /package/dist/{interface-DTbsvIWe.d.mts → interface-D_BWALyZ.d.mts} +0 -0
@@ -1,9 +1,8 @@
1
- import { accessSync } from "node:fs";
2
1
  import * as path from "node:path";
2
+ import { accessSync } from "node:fs";
3
+ import { execSync, spawn } from "node:child_process";
3
4
  import * as fs from "node:fs/promises";
4
5
  import * as readline from "node:readline";
5
- import { execSync, spawn } from "node:child_process";
6
-
7
6
  //#region src/cli/commands/init.ts
8
7
  /**
9
8
  * Arc CLI - Init Command
@@ -166,6 +165,20 @@ async function gatherConfig(options) {
166
165
  if (options.typescript === void 0 && !nonInteractive) typescript = await question("Language [1=TypeScript (recommended), 2=JavaScript]: ") !== "2";
167
166
  let edge = options.edge ?? false;
168
167
  if (options.edge === void 0 && !nonInteractive) edge = await question("Deployment target [1=Node.js Server (default), 2=Edge/Serverless]: ") === "2";
168
+ if (edge && adapter === "mongokit" && !nonInteractive) {
169
+ console.log("");
170
+ console.log(" ⚠ Edge + MongoKit: Mongoose does NOT work on Cloudflare Workers.");
171
+ console.log(" MongoDB Atlas works with the raw driver (mongodb 6.15+ with nodejs_compat_v2),");
172
+ console.log(" but MongoKit depends on Mongoose. Options:");
173
+ console.log(" 1. Use AWS Lambda / Vercel Serverless (Node.js) — Mongoose works normally");
174
+ console.log(" 2. Use Cloudflare Hyperdrive + PostgreSQL (switch to Prisma/Drizzle adapter)");
175
+ console.log(" 3. Continue with MongoKit — works on Lambda/Vercel, NOT on Cloudflare Workers");
176
+ console.log("");
177
+ if ((await question("Continue with MongoKit? [y/N]: ")).toLowerCase() !== "y") {
178
+ adapter = "custom";
179
+ console.log(" Switched to custom adapter. You can wire Drizzle, Prisma, or the raw MongoDB driver.");
180
+ }
181
+ }
169
182
  return {
170
183
  name,
171
184
  adapter,
@@ -234,12 +247,18 @@ async function createProjectStructure(projectPath, config) {
234
247
  files[`src/resources/example/example.schemas.${ext}`] = exampleSchemasTemplate(config);
235
248
  files[`tests/example.test.${ext}`] = exampleTestTemplate(config);
236
249
  if (config.auth === "jwt") files[`tests/auth.test.${ext}`] = authTestTemplate(config);
237
- files[".arcrc"] = JSON.stringify({
250
+ if (!config.edge) {
251
+ files.Dockerfile = dockerfileTemplate(config);
252
+ files[".dockerignore"] = dockerignoreTemplate();
253
+ files["docker-compose.yml"] = dockerComposeTemplate(config);
254
+ }
255
+ if (config.edge) files["wrangler.toml"] = wranglerTemplate(config);
256
+ files[".arcrc"] = `${JSON.stringify({
238
257
  adapter: config.adapter,
239
258
  auth: config.auth,
240
259
  tenant: config.tenant,
241
260
  typescript: config.typescript
242
- }, null, 2) + "\n";
261
+ }, null, 2)}\n`;
243
262
  for (const [filePath, content] of Object.entries(files)) {
244
263
  const fullPath = path.join(projectPath, filePath);
245
264
  await fs.mkdir(path.dirname(fullPath), { recursive: true });
@@ -248,12 +267,27 @@ async function createProjectStructure(projectPath, config) {
248
267
  }
249
268
  }
250
269
  function packageJsonTemplate(config) {
251
- const scripts = config.typescript ? {
270
+ const scripts = config.typescript ? config.edge ? {
271
+ dev: "tsx watch src/index.ts",
272
+ build: "tsc",
273
+ start: "node dist/index.js",
274
+ deploy: "wrangler deploy",
275
+ "deploy:dev": "wrangler dev",
276
+ test: "vitest run",
277
+ "test:watch": "vitest"
278
+ } : {
252
279
  dev: "tsx watch src/index.ts",
253
280
  build: "tsc",
254
281
  start: "node dist/index.js",
255
282
  test: "vitest run",
256
283
  "test:watch": "vitest"
284
+ } : config.edge ? {
285
+ dev: "node --watch src/index.js",
286
+ start: "node src/index.js",
287
+ deploy: "wrangler deploy",
288
+ "deploy:dev": "wrangler dev",
289
+ test: "vitest run",
290
+ "test:watch": "vitest"
257
291
  } : {
258
292
  dev: "node --watch src/index.js",
259
293
  start: "node src/index.js",
@@ -335,10 +369,11 @@ node_modules/
335
369
  dist/
336
370
  *.js.map
337
371
 
338
- # Environment
339
- .env
372
+ # Environment (local overrides — never commit secrets)
340
373
  .env.local
341
374
  .env.*.local
375
+ # Uncomment if your .env contains secrets:
376
+ # .env
342
377
 
343
378
  # IDE
344
379
  .vscode/
@@ -359,7 +394,15 @@ coverage/
359
394
  `;
360
395
  }
361
396
  function envExampleTemplate(config) {
362
- let content = `# Server
397
+ let content = `# Environment Files (Next.js-style priority):
398
+ # .env.local → machine-specific overrides (gitignored)
399
+ # .env.production → production defaults
400
+ # .env.development → development defaults (or .env.dev)
401
+ # .env → shared defaults (fallback)
402
+ #
403
+ # Tip: Copy this file to .env.local for local development
404
+
405
+ # Server
363
406
  PORT=8040
364
407
  HOST=0.0.0.0
365
408
  NODE_ENV=development
@@ -443,7 +486,7 @@ tests/
443
486
 
444
487
  ### Entry Points
445
488
 
446
- - **\`src/index.${ext}\`** - HTTP server entry point
489
+ - **\`src/index.${ext}\`** - ${config.edge ? "Edge/serverless fetch handler (Cloudflare Workers, Lambda, Vercel)" : "HTTP server entry point"}
447
490
  - **\`src/app.${ext}\`** - App factory (import for workers/tests)
448
491
 
449
492
  \`\`\`${config.typescript ? "typescript" : "javascript"}
@@ -501,12 +544,14 @@ arc introspect
501
544
  arc docs
502
545
  \`\`\`
503
546
 
504
- ## Environment Files
547
+ ## Environment Files (Next.js-style)
548
+
549
+ Priority (first loaded wins):
550
+ 1. \`.env.local\` — Machine-specific overrides (gitignored)
551
+ 2. \`.env.{environment}\` — e.g., \`.env.production\`, \`.env.development\`, \`.env.test\`
552
+ 3. \`.env\` — Shared defaults (fallback)
505
553
 
506
- - \`.env.development\` / \`.env.dev\` - Development (default)
507
- - \`.env.test\` / \`.env.qa\` - Testing / QA
508
- - \`.env.production\` / \`.env.prod\` - Production
509
- - \`.env\` - Fallback
554
+ Short forms also supported: \`.env.prod\`, \`.env.dev\`, \`.env.test\`
510
555
 
511
556
  ## API Documentation
512
557
 
@@ -526,10 +571,29 @@ API documentation is available via Scalar UI:
526
571
  | POST | /examples | Create |
527
572
  | PATCH | /examples/:id | Update |
528
573
  | DELETE | /examples/:id | Delete |
574
+
575
+ ## Docker Deployment
576
+
577
+ This project comes ready for containerization:
578
+
579
+ \`\`\`bash
580
+ # Build the production image
581
+ docker build -t ${config.name} .
582
+
583
+ # Run the container
584
+ docker run -p 8040:8040 --env-file .env ${config.name}
585
+ \`\`\`
586
+
587
+ If you're using a database (like MongoDB), you can use Docker Compose to spin up the full stack locally:
588
+
589
+ \`\`\`bash
590
+ docker-compose up -d
591
+ \`\`\`
529
592
  `;
530
593
  }
531
594
  function indexTemplate(config) {
532
595
  const ts = config.typescript;
596
+ if (config.edge) return edgeIndexTemplate(config);
533
597
  return `/**
534
598
  * ${config.name} - Server Entry Point
535
599
  * Generated by Arc CLI
@@ -566,6 +630,48 @@ main().catch((err) => {
566
630
  });
567
631
  `;
568
632
  }
633
+ /**
634
+ * Edge/serverless entry point — exports a Web Standards fetch handler.
635
+ * Works on Cloudflare Workers, AWS Lambda, Vercel Serverless, etc.
636
+ */
637
+ function edgeIndexTemplate(config) {
638
+ const ts = config.typescript;
639
+ const dbNote = config.adapter === "mongokit" ? ` *\n * NOTE: Mongoose does NOT work on Cloudflare Workers. This entry point\n * works on AWS Lambda and Vercel Serverless (Node.js runtime) where\n * Mongoose/MongoKit works normally. For Cloudflare Workers, switch to\n * Drizzle + Hyperdrive (PostgreSQL) or the raw mongodb driver.` : "";
640
+ return `/**
641
+ * ${config.name} - Edge/Serverless Entry Point
642
+ * Generated by Arc CLI
643
+ *
644
+ * Exports a Web Standards fetch handler that works on:
645
+ * - Cloudflare Workers (enable nodejs_compat in wrangler.toml)
646
+ * - AWS Lambda (via fetch-based adapter)
647
+ * - Vercel Serverless Functions
648
+ * - Any runtime supporting the Web Standards Request/Response API
649
+ *
650
+ * No app.listen() — routes through Fastify's .inject() internally.
651
+ ${dbNote}
652
+ */
653
+
654
+ import { toFetchHandler } from '@classytic/arc/factory';
655
+ import { createAppInstance } from './app.js';
656
+
657
+ const app = await createAppInstance();
658
+ const handler = toFetchHandler(app);
659
+
660
+ /**
661
+ * Cloudflare Workers / generic fetch handler
662
+ */
663
+ export default {
664
+ async fetch(request${ts ? ": Request" : ""})${ts ? ": Promise<Response>" : ""} {
665
+ return handler(request);
666
+ },
667
+ };
668
+
669
+ /**
670
+ * Named export for platforms that expect it (Vercel, AWS Lambda adapters)
671
+ */
672
+ export { handler };
673
+ `;
674
+ }
569
675
  function appTemplate(config) {
570
676
  const ts = config.typescript;
571
677
  const typeImport = ts ? "import type { FastifyInstance } from 'fastify';\n" : "";
@@ -582,9 +688,9 @@ import { getAuth } from './auth.js';
582
688
  *
583
689
  * Creates and configures the Fastify app instance.
584
690
  * Can be imported by:
585
- * - index.ts (HTTP server)
691
+ * - index.ts (HTTP server via app.listen, or edge handler via toFetchHandler)
586
692
  * - worker.ts (background workers)
587
- * - tests (integration tests)
693
+ * - tests (integration tests via app.inject)
588
694
  */
589
695
 
590
696
  ${typeImport}import config from '#config/index.js';
@@ -613,6 +719,9 @@ export async function createAppInstance()${ts ? ": Promise<FastifyInstance>" : "
613
719
  credentials: config.cors.credentials,
614
720
  },
615
721
  trustProxy: true,
722
+ arcPlugins: {
723
+ metrics: config.env === 'production', // Prometheus /_metrics endpoint
724
+ },
616
725
  });
617
726
 
618
727
  // Register app-specific plugins (explicit dependency injection)
@@ -633,44 +742,64 @@ function envLoaderTemplate(config) {
633
742
  * Environment Loader
634
743
  *
635
744
  * MUST be imported FIRST before any other imports.
636
- * Loads .env files based on NODE_ENV.
745
+ * Loads .env files based on NODE_ENV with Next.js-style priority:
746
+ *
747
+ * .env.local (always loaded first — gitignored, machine-specific overrides)
748
+ * .env.{environment} (e.g., .env.production, .env.dev, .env.test)
749
+ * .env (fallback defaults)
750
+ *
751
+ * Supports both long-form (production, development, test) and
752
+ * short-form (prod, dev, test) env file names.
637
753
  *
638
754
  * Usage:
639
- * import './config/env.js'; // First line of entry point
755
+ * import '#config/env.js'; // First line of entry point
640
756
  */
641
757
 
642
758
  import dotenv from 'dotenv';
643
759
  import { existsSync } from 'node:fs';
644
760
  import { resolve } from 'node:path';
645
761
 
646
- /**
647
- * Normalize environment string to short form
648
- */
649
- function normalizeEnv(env${ts ? ": string | undefined" : ""})${ts ? ": string" : ""} {
650
- const normalized = (env || '').toLowerCase();
651
- if (normalized === 'production' || normalized === 'prod') return 'prod';
652
- if (normalized === 'test' || normalized === 'qa') return 'test';
762
+ ${ts ? "type EnvName = 'prod' | 'dev' | 'test';\n" : ""}const ENV_ALIASES${ts ? ": Record<EnvName, string>" : ""} = {
763
+ prod: 'production',
764
+ dev: 'development',
765
+ test: 'test',
766
+ };
767
+
768
+ function normalizeEnv(env${ts ? ": string | undefined" : ""})${ts ? ": EnvName" : ""} {
769
+ const raw = (env || '').toLowerCase();
770
+ if (raw === 'production' || raw === 'prod') return 'prod';
771
+ if (raw === 'test' || raw === 'qa') return 'test';
653
772
  return 'dev';
654
773
  }
655
774
 
656
- // Determine environment
657
775
  const env = normalizeEnv(process.env.NODE_ENV);
776
+ const longForm = ENV_ALIASES[env];
777
+
778
+ // Priority: .env.local → .env.{long} → .env.{short} → .env
779
+ // Same convention as Next.js — .env.local always wins, never committed to git
780
+ const candidates = [
781
+ '.env.local',
782
+ \`.env.\${longForm}\`,
783
+ \`.env.\${env}\`,
784
+ '.env',
785
+ ].map((f) => resolve(process.cwd(), f));
786
+
787
+ const loaded${ts ? ": string[]" : ""} = [];
788
+ for (const file of candidates) {
789
+ if (existsSync(file)) {
790
+ // override: false means earlier files take priority (first loaded wins)
791
+ dotenv.config({ path: file, override: false });
792
+ loaded.push(file.split(/[\\\\/]/).pop()${ts ? "!" : ""});
793
+ }
794
+ }
658
795
 
659
- // Load environment-specific .env file
660
- const envFile = resolve(process.cwd(), \`.env.\${env}\`);
661
- const defaultEnvFile = resolve(process.cwd(), '.env');
662
-
663
- if (existsSync(envFile)) {
664
- dotenv.config({ path: envFile });
665
- console.log(\`Loaded: .env.\${env}\`);
666
- } else if (existsSync(defaultEnvFile)) {
667
- dotenv.config({ path: defaultEnvFile });
668
- console.log('Loaded: .env');
669
- } else {
670
- console.warn('Warning: No .env file found');
796
+ // Only log in development (silent in production/test)
797
+ if (env === 'dev' && loaded.length > 0) {
798
+ console.log(\`env: \${loaded.join(' + ')}\`);
799
+ } else if (loaded.length === 0) {
800
+ console.warn('No .env file found — using process environment only');
671
801
  }
672
802
 
673
- // Export for reference
674
803
  export const ENV = env;
675
804
  `;
676
805
  }
@@ -1461,14 +1590,16 @@ import {
1461
1590
  Repository,
1462
1591
  softDeletePlugin,
1463
1592
  methodRegistryPlugin,
1593
+ mongoOperationsPlugin,
1464
1594
  } from '@classytic/mongokit';
1465
1595
  ${ts ? "import type { ExampleDocument } from './example.model.js';\n" : ""}import Example from './example.model.js';
1466
1596
 
1467
1597
  class ExampleRepository extends Repository${ts ? "<ExampleDocument>" : ""} {
1468
1598
  constructor() {
1469
1599
  super(Example, [
1470
- methodRegistryPlugin(), // Required for plugin method registration
1471
- softDeletePlugin(), // Soft delete support
1600
+ methodRegistryPlugin(),
1601
+ softDeletePlugin(),
1602
+ mongoOperationsPlugin(),
1472
1603
  ]);
1473
1604
  }
1474
1605
 
@@ -1513,12 +1644,17 @@ function exampleResourceTemplate(config) {
1513
1644
  */
1514
1645
 
1515
1646
  import { defineResource } from '@classytic/arc';
1647
+ import { QueryParser } from '@classytic/mongokit';
1516
1648
  import { createAdapter } from '#shared/adapter.js';
1517
1649
  import { ${config.tenant === "multi" ? "orgStaffPermissions" : "publicReadPermissions"} } from '#shared/permissions.js';
1518
1650
  ${config.tenant === "multi" ? "import { flexibleMultiTenantPreset } from '#shared/presets/flexible-multi-tenant.js';\n" : ""}import Example${ts ? ", { type ExampleDocument }" : ""} from './example.model.js';
1519
1651
  import exampleRepository from './example.repository.js';
1520
1652
  import exampleController from './example.controller.js';
1521
1653
 
1654
+ const queryParser = new QueryParser({
1655
+ allowedFilterFields: ['isActive'],
1656
+ });
1657
+
1522
1658
  const exampleResource = defineResource${ts ? "<ExampleDocument>" : ""}({
1523
1659
  name: 'example',
1524
1660
  displayName: 'Examples',
@@ -1526,9 +1662,11 @@ const exampleResource = defineResource${ts ? "<ExampleDocument>" : ""}({
1526
1662
 
1527
1663
  adapter: createAdapter(Example, exampleRepository),
1528
1664
  controller: exampleController,
1665
+ queryParser,
1529
1666
 
1530
1667
  presets: [
1531
- 'softDelete',${config.tenant === "multi" ? `
1668
+ 'softDelete',
1669
+ 'bulk',${config.tenant === "multi" ? `
1532
1670
  flexibleMultiTenantPreset({ tenantField: 'organizationId' }),` : ""}
1533
1671
  ],
1534
1672
 
@@ -1549,6 +1687,7 @@ export default exampleResource;
1549
1687
  `;
1550
1688
  }
1551
1689
  function exampleControllerTemplate(config) {
1690
+ config.typescript;
1552
1691
  return `/**
1553
1692
  * Example Controller
1554
1693
  * Generated by Arc CLI
@@ -1565,7 +1704,7 @@ import { exampleSchemaOptions } from './example.schemas.js';
1565
1704
 
1566
1705
  class ExampleController extends BaseController {
1567
1706
  constructor() {
1568
- super(exampleRepository${config.typescript ? " as any" : ""}, {
1707
+ super(exampleRepository, {
1569
1708
  schemaOptions: exampleSchemaOptions,${config.tenant === "multi" ? `
1570
1709
  tenantField: 'organizationId', // Configurable tenant field for multi-tenant` : `
1571
1710
  // tenantField: 'organizationId', // For multi-tenant apps`}
@@ -2617,6 +2756,137 @@ Documentation:
2617
2756
  https://github.com/classytic/arc
2618
2757
  `);
2619
2758
  }
2759
+ function dockerignoreTemplate() {
2760
+ return `node_modules
2761
+ dist
2762
+ .env
2763
+ .env.*
2764
+ .git
2765
+ .vscode
2766
+ .idea
2767
+ Dockerfile
2768
+ docker-compose.yml
2769
+ coverage
2770
+ npm-debug.log*
2771
+ .DS_Store
2772
+ `;
2773
+ }
2774
+ function dockerfileTemplate(config) {
2775
+ return `# Multi-stage Dockerfile for Arc + Fastify
2776
+ # Optimized for production and caching
2777
+
2778
+ # 1. Build Stage
2779
+ FROM node:22-alpine AS builder
2780
+ WORKDIR /app
2781
+ COPY package*.json ./
2782
+ ${config.typescript ? "COPY tsconfig*.json ./" : ""}
2783
+ # If using pnpm, bun, or yarn, adjust the lockfile here
2784
+ RUN npm ci
2785
+
2786
+ COPY . .
2787
+ ${config.typescript ? "RUN npm run build" : ""}
2788
+
2789
+ # 2. Production Stage
2790
+ FROM node:22-alpine AS runner
2791
+ WORKDIR /app
2792
+ ENV NODE_ENV=production
2793
+
2794
+ COPY package*.json ./
2795
+ RUN npm ci --only=production
2796
+
2797
+ ${config.typescript ? "COPY --from=builder /app/dist ./dist" : "COPY src ./src"}
2620
2798
 
2799
+ EXPOSE 8040
2800
+ CMD ["npm", "start"]
2801
+ `;
2802
+ }
2803
+ function dockerComposeTemplate(config) {
2804
+ let content = `version: '3.8'
2805
+
2806
+ services:
2807
+ api:
2808
+ build:
2809
+ context: .
2810
+ dockerfile: Dockerfile
2811
+ ports:
2812
+ - "8040:8040"
2813
+ environment:
2814
+ - NODE_ENV=development
2815
+ - PORT=8040
2816
+ - HOST=0.0.0.0`;
2817
+ if (config.adapter === "mongokit") content += `
2818
+ - MONGODB_URI=mongodb://mongo:27017/${config.name}
2819
+ depends_on:
2820
+ - mongo
2821
+
2822
+ mongo:
2823
+ image: mongo:7
2824
+ ports:
2825
+ - "27017:27017"
2826
+ volumes:
2827
+ - mongo-data:/data/db`;
2828
+ content += `
2829
+
2830
+ volumes:`;
2831
+ if (config.adapter === "mongokit") content += `
2832
+ mongo-data:
2833
+ `;
2834
+ return content;
2835
+ }
2836
+ function wranglerTemplate(config) {
2837
+ const entry = config.typescript ? "dist/index.js" : "src/index.js";
2838
+ const compatFlag = config.adapter === "mongokit" ? "nodejs_compat_v2" : "nodejs_compat";
2839
+ let dbConfig = "";
2840
+ if (config.adapter === "mongokit") dbConfig = `
2841
+ # MongoDB Atlas — store URI as a secret:
2842
+ # npx wrangler secret put MONGODB_URI
2843
+ #
2844
+ # IMPORTANT: Mongoose does NOT work on Workers. Use the raw mongodb driver (6.15+).
2845
+ # For Lambda/Vercel (Node.js), Mongoose works normally.
2846
+ `;
2847
+ else dbConfig = `
2848
+ # Database options for Cloudflare Workers:
2849
+ #
2850
+ # PostgreSQL via Hyperdrive (recommended — connection pooling + caching):
2851
+ # npx wrangler hyperdrive create my-db --connection-string="postgres://user:pass@host:5432/db"
2852
+ # Then uncomment:
2853
+ # [[hyperdrive]]
2854
+ # binding = "HYPERDRIVE"
2855
+ # id = "<your-hyperdrive-id>"
2856
+ #
2857
+ # Turso (edge SQLite):
2858
+ # npx wrangler secret put TURSO_URL
2859
+ # npx wrangler secret put TURSO_AUTH_TOKEN
2860
+ #
2861
+ # Neon (serverless PostgreSQL via HTTP):
2862
+ # npx wrangler secret put DATABASE_URL
2863
+ #
2864
+ # D1 (Cloudflare's native SQLite):
2865
+ # [[d1_databases]]
2866
+ # binding = "DB"
2867
+ # database_name = "${config.name}-db"
2868
+ # database_id = "<run: npx wrangler d1 create ${config.name}-db>"
2869
+ `;
2870
+ return `# Cloudflare Workers configuration
2871
+ # Generated by Arc CLI — see https://developers.cloudflare.com/workers/
2872
+
2873
+ name = "${config.name}"
2874
+ main = "${entry}"
2875
+ compatibility_date = "2025-03-20"
2876
+
2877
+ # Required for Arc — enables node:crypto and AsyncLocalStorage
2878
+ compatibility_flags = ["${compatFlag}"]
2879
+
2880
+ [vars]
2881
+ NODE_ENV = "production"
2882
+
2883
+ # Secrets (never commit these — use wrangler secret put):
2884
+ # npx wrangler secret put JWT_SECRET
2885
+ ${dbConfig}
2886
+ # Custom domain:
2887
+ # [routes]
2888
+ # { pattern = "api.example.com/*", zone_name = "example.com" }
2889
+ `;
2890
+ }
2621
2891
  //#endregion
2622
- export { init as default, init };
2892
+ export { init as default, init };
@@ -1,7 +1,6 @@
1
- import { t as ResourceRegistry } from "../../ResourceRegistry-7Ic20ZMw.mjs";
1
+ import { t as ResourceRegistry } from "../../ResourceRegistry-DeCIFlix.mjs";
2
2
  import { resolve } from "node:path";
3
3
  import { pathToFileURL } from "node:url";
4
-
5
4
  //#region src/cli/commands/introspect.ts
6
5
  /**
7
6
  * Arc CLI - Introspect Command
@@ -70,6 +69,5 @@ async function introspect(args) {
70
69
  throw new Error(String(error));
71
70
  }
72
71
  }
73
-
74
72
  //#endregion
75
- export { introspect as default, introspect };
73
+ export { introspect as default, introspect };
@@ -1,16 +1,7 @@
1
1
  import describe from "./commands/describe.mjs";
2
2
  import { exportDocs } from "./commands/docs.mjs";
3
+ import { doctor } from "./commands/doctor.mjs";
3
4
  import generate from "./commands/generate.mjs";
4
5
  import init from "./commands/init.mjs";
5
6
  import introspect from "./commands/introspect.mjs";
6
-
7
- //#region src/cli/commands/doctor.d.ts
8
- /**
9
- * Arc CLI - Doctor Command
10
- *
11
- * Health check utility that validates the development environment.
12
- * Checks Node.js version, dependencies, configuration, and env variables.
13
- */
14
- declare function doctor(_args?: string[]): Promise<void>;
15
- //#endregion
16
7
  export { describe, doctor, exportDocs, generate, init, introspect };