@classytic/arc 1.1.0 → 2.1.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 (322) hide show
  1. package/README.md +247 -794
  2. package/bin/arc.js +91 -52
  3. package/dist/EventTransport-BD2U0BTc.d.mts +100 -0
  4. package/dist/EventTransport-BD2U0BTc.d.mts.map +1 -0
  5. package/dist/HookSystem-BsGV-j2l.mjs +405 -0
  6. package/dist/HookSystem-BsGV-j2l.mjs.map +1 -0
  7. package/dist/ResourceRegistry-DsN4KJjV.mjs +250 -0
  8. package/dist/ResourceRegistry-DsN4KJjV.mjs.map +1 -0
  9. package/dist/adapters/index.d.mts +5 -0
  10. package/dist/adapters/index.mjs +3 -0
  11. package/dist/audit/index.d.mts +82 -0
  12. package/dist/audit/index.d.mts.map +1 -0
  13. package/dist/audit/index.mjs +276 -0
  14. package/dist/audit/index.mjs.map +1 -0
  15. package/dist/audit/mongodb.d.mts +5 -0
  16. package/dist/audit/mongodb.mjs +3 -0
  17. package/dist/audited-C3T5DTUx.mjs +141 -0
  18. package/dist/audited-C3T5DTUx.mjs.map +1 -0
  19. package/dist/auth/index.d.mts +189 -0
  20. package/dist/auth/index.d.mts.map +1 -0
  21. package/dist/auth/index.mjs +1102 -0
  22. package/dist/auth/index.mjs.map +1 -0
  23. package/dist/auth/redis-session.d.mts +44 -0
  24. package/dist/auth/redis-session.d.mts.map +1 -0
  25. package/dist/auth/redis-session.mjs +76 -0
  26. package/dist/auth/redis-session.mjs.map +1 -0
  27. package/dist/betterAuthOpenApi-BrHKeSAx.mjs +250 -0
  28. package/dist/betterAuthOpenApi-BrHKeSAx.mjs.map +1 -0
  29. package/dist/cache/index.d.mts +146 -0
  30. package/dist/cache/index.d.mts.map +1 -0
  31. package/dist/cache/index.mjs +92 -0
  32. package/dist/cache/index.mjs.map +1 -0
  33. package/dist/caching-Bl28lYsR.mjs +94 -0
  34. package/dist/caching-Bl28lYsR.mjs.map +1 -0
  35. package/dist/chunk-C7Uep-_p.mjs +20 -0
  36. package/dist/circuitBreaker-DeY4FCjs.mjs +1097 -0
  37. package/dist/circuitBreaker-DeY4FCjs.mjs.map +1 -0
  38. package/dist/cli/commands/describe.d.mts +19 -0
  39. package/dist/cli/commands/describe.d.mts.map +1 -0
  40. package/dist/cli/commands/describe.mjs +239 -0
  41. package/dist/cli/commands/describe.mjs.map +1 -0
  42. package/dist/cli/commands/docs.d.mts +14 -0
  43. package/dist/cli/commands/docs.d.mts.map +1 -0
  44. package/dist/cli/commands/docs.mjs +53 -0
  45. package/dist/cli/commands/docs.mjs.map +1 -0
  46. package/dist/cli/commands/{generate.d.ts → generate.d.mts} +3 -1
  47. package/dist/cli/commands/generate.d.mts.map +1 -0
  48. package/dist/cli/commands/generate.mjs +358 -0
  49. package/dist/cli/commands/generate.mjs.map +1 -0
  50. package/dist/cli/commands/{init.d.ts → init.d.mts} +12 -8
  51. package/dist/cli/commands/init.d.mts.map +1 -0
  52. package/dist/cli/commands/{init.js → init.mjs} +807 -616
  53. package/dist/cli/commands/init.mjs.map +1 -0
  54. package/dist/cli/commands/introspect.d.mts +11 -0
  55. package/dist/cli/commands/introspect.d.mts.map +1 -0
  56. package/dist/cli/commands/introspect.mjs +76 -0
  57. package/dist/cli/commands/introspect.mjs.map +1 -0
  58. package/dist/cli/index.d.mts +17 -0
  59. package/dist/cli/index.d.mts.map +1 -0
  60. package/dist/cli/index.mjs +157 -0
  61. package/dist/cli/index.mjs.map +1 -0
  62. package/dist/constants-DdXFXQtN.mjs +85 -0
  63. package/dist/constants-DdXFXQtN.mjs.map +1 -0
  64. package/dist/core/index.d.mts +5 -0
  65. package/dist/core/index.mjs +4 -0
  66. package/dist/createApp-CUgNqegw.mjs +560 -0
  67. package/dist/createApp-CUgNqegw.mjs.map +1 -0
  68. package/dist/defineResource-k0_BDn8v.mjs +2197 -0
  69. package/dist/defineResource-k0_BDn8v.mjs.map +1 -0
  70. package/dist/discovery/index.d.mts +47 -0
  71. package/dist/discovery/index.d.mts.map +1 -0
  72. package/dist/discovery/index.mjs +110 -0
  73. package/dist/discovery/index.mjs.map +1 -0
  74. package/dist/docs/index.d.mts +163 -0
  75. package/dist/docs/index.d.mts.map +1 -0
  76. package/dist/docs/index.mjs +73 -0
  77. package/dist/docs/index.mjs.map +1 -0
  78. package/dist/elevation-BRy3yFWT.mjs +113 -0
  79. package/dist/elevation-BRy3yFWT.mjs.map +1 -0
  80. package/dist/elevation-B_2dRLVP.d.mts +88 -0
  81. package/dist/elevation-B_2dRLVP.d.mts.map +1 -0
  82. package/dist/errorHandler-BbcgBmIH.d.mts +73 -0
  83. package/dist/errorHandler-BbcgBmIH.d.mts.map +1 -0
  84. package/dist/errorHandler-C1okiriz.mjs +109 -0
  85. package/dist/errorHandler-C1okiriz.mjs.map +1 -0
  86. package/dist/errors-B9bZok84.mjs +212 -0
  87. package/dist/errors-B9bZok84.mjs.map +1 -0
  88. package/dist/errors-ChKiFz62.d.mts +125 -0
  89. package/dist/errors-ChKiFz62.d.mts.map +1 -0
  90. package/dist/eventPlugin-CTrLH3mt.d.mts +125 -0
  91. package/dist/eventPlugin-CTrLH3mt.d.mts.map +1 -0
  92. package/dist/eventPlugin-DGR_B2on.mjs +230 -0
  93. package/dist/eventPlugin-DGR_B2on.mjs.map +1 -0
  94. package/dist/events/index.d.mts +54 -0
  95. package/dist/events/index.d.mts.map +1 -0
  96. package/dist/events/index.mjs +52 -0
  97. package/dist/events/index.mjs.map +1 -0
  98. package/dist/events/transports/redis-stream-entry.d.mts +2 -0
  99. package/dist/events/transports/redis-stream-entry.mjs +178 -0
  100. package/dist/events/transports/redis-stream-entry.mjs.map +1 -0
  101. package/dist/events/transports/redis.d.mts +77 -0
  102. package/dist/events/transports/redis.d.mts.map +1 -0
  103. package/dist/events/transports/redis.mjs +125 -0
  104. package/dist/events/transports/redis.mjs.map +1 -0
  105. package/dist/externalPaths-DlINfKbP.d.mts +51 -0
  106. package/dist/externalPaths-DlINfKbP.d.mts.map +1 -0
  107. package/dist/factory/index.d.mts +64 -0
  108. package/dist/factory/index.d.mts.map +1 -0
  109. package/dist/factory/index.mjs +3 -0
  110. package/dist/fastifyAdapter-BkrGrlFi.d.mts +217 -0
  111. package/dist/fastifyAdapter-BkrGrlFi.d.mts.map +1 -0
  112. package/dist/fields-DyaDVX4J.d.mts +110 -0
  113. package/dist/fields-DyaDVX4J.d.mts.map +1 -0
  114. package/dist/fields-iagOozy0.mjs +115 -0
  115. package/dist/fields-iagOozy0.mjs.map +1 -0
  116. package/dist/hooks/index.d.mts +4 -0
  117. package/dist/hooks/index.mjs +3 -0
  118. package/dist/idempotency/index.d.mts +97 -0
  119. package/dist/idempotency/index.d.mts.map +1 -0
  120. package/dist/idempotency/index.mjs +320 -0
  121. package/dist/idempotency/index.mjs.map +1 -0
  122. package/dist/idempotency/mongodb.d.mts +2 -0
  123. package/dist/idempotency/mongodb.mjs +115 -0
  124. package/dist/idempotency/mongodb.mjs.map +1 -0
  125. package/dist/idempotency/redis.d.mts +2 -0
  126. package/dist/idempotency/redis.mjs +104 -0
  127. package/dist/idempotency/redis.mjs.map +1 -0
  128. package/dist/index.d.mts +261 -0
  129. package/dist/index.d.mts.map +1 -0
  130. package/dist/index.mjs +105 -0
  131. package/dist/index.mjs.map +1 -0
  132. package/dist/integrations/event-gateway.d.mts +47 -0
  133. package/dist/integrations/event-gateway.d.mts.map +1 -0
  134. package/dist/integrations/event-gateway.mjs +44 -0
  135. package/dist/integrations/event-gateway.mjs.map +1 -0
  136. package/dist/integrations/index.d.mts +5 -0
  137. package/dist/integrations/index.mjs +1 -0
  138. package/dist/integrations/jobs.d.mts +104 -0
  139. package/dist/integrations/jobs.d.mts.map +1 -0
  140. package/dist/integrations/jobs.mjs +124 -0
  141. package/dist/integrations/jobs.mjs.map +1 -0
  142. package/dist/integrations/streamline.d.mts +61 -0
  143. package/dist/integrations/streamline.d.mts.map +1 -0
  144. package/dist/integrations/streamline.mjs +126 -0
  145. package/dist/integrations/streamline.mjs.map +1 -0
  146. package/dist/integrations/websocket.d.mts +83 -0
  147. package/dist/integrations/websocket.d.mts.map +1 -0
  148. package/dist/integrations/websocket.mjs +289 -0
  149. package/dist/integrations/websocket.mjs.map +1 -0
  150. package/dist/interface-B01JvPVc.d.mts +78 -0
  151. package/dist/interface-B01JvPVc.d.mts.map +1 -0
  152. package/dist/interface-CZe8IkMf.d.mts +55 -0
  153. package/dist/interface-CZe8IkMf.d.mts.map +1 -0
  154. package/dist/interface-Ch8HU9uM.d.mts +1098 -0
  155. package/dist/interface-Ch8HU9uM.d.mts.map +1 -0
  156. package/dist/introspectionPlugin-rFdO8ZUa.mjs +54 -0
  157. package/dist/introspectionPlugin-rFdO8ZUa.mjs.map +1 -0
  158. package/dist/keys-BqNejWup.mjs +43 -0
  159. package/dist/keys-BqNejWup.mjs.map +1 -0
  160. package/dist/logger-Df2O2WsW.mjs +79 -0
  161. package/dist/logger-Df2O2WsW.mjs.map +1 -0
  162. package/dist/memory-cQgelFOj.mjs +144 -0
  163. package/dist/memory-cQgelFOj.mjs.map +1 -0
  164. package/dist/migrations/index.d.mts +157 -0
  165. package/dist/migrations/index.d.mts.map +1 -0
  166. package/dist/migrations/index.mjs +261 -0
  167. package/dist/migrations/index.mjs.map +1 -0
  168. package/dist/mongodb-BfJVlUJH.mjs +94 -0
  169. package/dist/mongodb-BfJVlUJH.mjs.map +1 -0
  170. package/dist/mongodb-CGzRbfAK.d.mts +119 -0
  171. package/dist/mongodb-CGzRbfAK.d.mts.map +1 -0
  172. package/dist/mongodb-JN-9JA7K.d.mts +72 -0
  173. package/dist/mongodb-JN-9JA7K.d.mts.map +1 -0
  174. package/dist/openapi-G3Cw7XuM.mjs +524 -0
  175. package/dist/openapi-G3Cw7XuM.mjs.map +1 -0
  176. package/dist/org/index.d.mts +69 -0
  177. package/dist/org/index.d.mts.map +1 -0
  178. package/dist/org/index.mjs +514 -0
  179. package/dist/org/index.mjs.map +1 -0
  180. package/dist/org/types.d.mts +83 -0
  181. package/dist/org/types.d.mts.map +1 -0
  182. package/dist/org/types.mjs +1 -0
  183. package/dist/permissions/index.d.mts +279 -0
  184. package/dist/permissions/index.d.mts.map +1 -0
  185. package/dist/permissions/index.mjs +579 -0
  186. package/dist/permissions/index.mjs.map +1 -0
  187. package/dist/plugins/index.d.mts +173 -0
  188. package/dist/plugins/index.d.mts.map +1 -0
  189. package/dist/plugins/index.mjs +523 -0
  190. package/dist/plugins/index.mjs.map +1 -0
  191. package/dist/plugins/response-cache.d.mts +88 -0
  192. package/dist/plugins/response-cache.d.mts.map +1 -0
  193. package/dist/plugins/response-cache.mjs +284 -0
  194. package/dist/plugins/response-cache.mjs.map +1 -0
  195. package/dist/plugins/tracing-entry.d.mts +2 -0
  196. package/dist/plugins/tracing-entry.mjs +186 -0
  197. package/dist/plugins/tracing-entry.mjs.map +1 -0
  198. package/dist/pluralize-CEweyOEm.mjs +87 -0
  199. package/dist/pluralize-CEweyOEm.mjs.map +1 -0
  200. package/dist/policies/{index.d.ts → index.d.mts} +204 -169
  201. package/dist/policies/index.d.mts.map +1 -0
  202. package/dist/policies/index.mjs +322 -0
  203. package/dist/policies/index.mjs.map +1 -0
  204. package/dist/presets/{index.d.ts → index.d.mts} +63 -131
  205. package/dist/presets/index.d.mts.map +1 -0
  206. package/dist/presets/index.mjs +144 -0
  207. package/dist/presets/index.mjs.map +1 -0
  208. package/dist/presets/multiTenant.d.mts +25 -0
  209. package/dist/presets/multiTenant.d.mts.map +1 -0
  210. package/dist/presets/multiTenant.mjs +114 -0
  211. package/dist/presets/multiTenant.mjs.map +1 -0
  212. package/dist/presets-BITljm96.mjs +120 -0
  213. package/dist/presets-BITljm96.mjs.map +1 -0
  214. package/dist/presets-DzSMwlKj.d.mts +58 -0
  215. package/dist/presets-DzSMwlKj.d.mts.map +1 -0
  216. package/dist/prisma-DJbMt3yf.mjs +628 -0
  217. package/dist/prisma-DJbMt3yf.mjs.map +1 -0
  218. package/dist/prisma-Dg9GoVdj.d.mts +275 -0
  219. package/dist/prisma-Dg9GoVdj.d.mts.map +1 -0
  220. package/dist/queryCachePlugin-7THaI5mt.d.mts +72 -0
  221. package/dist/queryCachePlugin-7THaI5mt.d.mts.map +1 -0
  222. package/dist/queryCachePlugin-DMBnp2Q0.mjs +139 -0
  223. package/dist/queryCachePlugin-DMBnp2Q0.mjs.map +1 -0
  224. package/dist/redis-D-JAeLtm.d.mts +50 -0
  225. package/dist/redis-D-JAeLtm.d.mts.map +1 -0
  226. package/dist/redis-stream-Bdh_vUU8.d.mts +104 -0
  227. package/dist/redis-stream-Bdh_vUU8.d.mts.map +1 -0
  228. package/dist/registry/index.d.mts +12 -0
  229. package/dist/registry/index.d.mts.map +1 -0
  230. package/dist/registry/index.mjs +4 -0
  231. package/dist/requestContext-QQD6ROJc.mjs +56 -0
  232. package/dist/requestContext-QQD6ROJc.mjs.map +1 -0
  233. package/dist/schemaConverter-BwrmWroW.mjs +99 -0
  234. package/dist/schemaConverter-BwrmWroW.mjs.map +1 -0
  235. package/dist/schemas/index.d.mts +64 -0
  236. package/dist/schemas/index.d.mts.map +1 -0
  237. package/dist/schemas/index.mjs +83 -0
  238. package/dist/schemas/index.mjs.map +1 -0
  239. package/dist/scope/index.d.mts +22 -0
  240. package/dist/scope/index.d.mts.map +1 -0
  241. package/dist/scope/index.mjs +66 -0
  242. package/dist/scope/index.mjs.map +1 -0
  243. package/dist/sessionManager-jPKLbHE0.d.mts +187 -0
  244. package/dist/sessionManager-jPKLbHE0.d.mts.map +1 -0
  245. package/dist/sse-B3c3_yZp.mjs +124 -0
  246. package/dist/sse-B3c3_yZp.mjs.map +1 -0
  247. package/dist/testing/index.d.mts +908 -0
  248. package/dist/testing/index.d.mts.map +1 -0
  249. package/dist/testing/index.mjs +1977 -0
  250. package/dist/testing/index.mjs.map +1 -0
  251. package/dist/tracing-Cc7vVQPp.d.mts +71 -0
  252. package/dist/tracing-Cc7vVQPp.d.mts.map +1 -0
  253. package/dist/typeGuards-DhMNLuvU.mjs +10 -0
  254. package/dist/typeGuards-DhMNLuvU.mjs.map +1 -0
  255. package/dist/types/index.d.mts +947 -0
  256. package/dist/types/index.d.mts.map +1 -0
  257. package/dist/types/index.mjs +15 -0
  258. package/dist/types/index.mjs.map +1 -0
  259. package/dist/types-Beqn1Un7.mjs +39 -0
  260. package/dist/types-Beqn1Un7.mjs.map +1 -0
  261. package/dist/types-CIgB7UUl.d.mts +446 -0
  262. package/dist/types-CIgB7UUl.d.mts.map +1 -0
  263. package/dist/types-aYB4V7uN.d.mts +87 -0
  264. package/dist/types-aYB4V7uN.d.mts.map +1 -0
  265. package/dist/utils/index.d.mts +748 -0
  266. package/dist/utils/index.d.mts.map +1 -0
  267. package/dist/utils/index.mjs +6 -0
  268. package/package.json +194 -68
  269. package/dist/BaseController-DVAiHxEQ.d.ts +0 -233
  270. package/dist/adapters/index.d.ts +0 -237
  271. package/dist/adapters/index.js +0 -668
  272. package/dist/arcCorePlugin-CsShQdyP.d.ts +0 -273
  273. package/dist/audit/index.d.ts +0 -195
  274. package/dist/audit/index.js +0 -319
  275. package/dist/auth/index.d.ts +0 -47
  276. package/dist/auth/index.js +0 -174
  277. package/dist/cli/commands/docs.d.ts +0 -11
  278. package/dist/cli/commands/docs.js +0 -474
  279. package/dist/cli/commands/generate.js +0 -334
  280. package/dist/cli/commands/introspect.d.ts +0 -8
  281. package/dist/cli/commands/introspect.js +0 -338
  282. package/dist/cli/index.d.ts +0 -4
  283. package/dist/cli/index.js +0 -3269
  284. package/dist/core/index.d.ts +0 -220
  285. package/dist/core/index.js +0 -2786
  286. package/dist/createApp-Ce9wl8W9.d.ts +0 -77
  287. package/dist/docs/index.d.ts +0 -166
  288. package/dist/docs/index.js +0 -658
  289. package/dist/errors-8WIxGS_6.d.ts +0 -122
  290. package/dist/events/index.d.ts +0 -117
  291. package/dist/events/index.js +0 -89
  292. package/dist/factory/index.d.ts +0 -38
  293. package/dist/factory/index.js +0 -1652
  294. package/dist/hooks/index.d.ts +0 -4
  295. package/dist/hooks/index.js +0 -199
  296. package/dist/idempotency/index.d.ts +0 -323
  297. package/dist/idempotency/index.js +0 -500
  298. package/dist/index-B4t03KQ0.d.ts +0 -1366
  299. package/dist/index.d.ts +0 -135
  300. package/dist/index.js +0 -4756
  301. package/dist/migrations/index.d.ts +0 -185
  302. package/dist/migrations/index.js +0 -274
  303. package/dist/org/index.d.ts +0 -129
  304. package/dist/org/index.js +0 -220
  305. package/dist/permissions/index.d.ts +0 -144
  306. package/dist/permissions/index.js +0 -103
  307. package/dist/plugins/index.d.ts +0 -46
  308. package/dist/plugins/index.js +0 -1069
  309. package/dist/policies/index.js +0 -196
  310. package/dist/presets/index.js +0 -384
  311. package/dist/presets/multiTenant.d.ts +0 -39
  312. package/dist/presets/multiTenant.js +0 -112
  313. package/dist/registry/index.d.ts +0 -16
  314. package/dist/registry/index.js +0 -253
  315. package/dist/testing/index.d.ts +0 -618
  316. package/dist/testing/index.js +0 -48020
  317. package/dist/types/index.d.ts +0 -4
  318. package/dist/types/index.js +0 -8
  319. package/dist/types-B99TBmFV.d.ts +0 -76
  320. package/dist/types-BvckRbs2.d.ts +0 -143
  321. package/dist/utils/index.d.ts +0 -679
  322. package/dist/utils/index.js +0 -931
@@ -1,1652 +0,0 @@
1
- import fp from 'fastify-plugin';
2
- import { randomUUID } from 'crypto';
3
- import { createRequire } from 'module';
4
- import Fastify from 'fastify';
5
- import qs from 'qs';
6
-
7
- var __defProp = Object.defineProperty;
8
- var __getOwnPropNames = Object.getOwnPropertyNames;
9
- var __esm = (fn, res) => function __init() {
10
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
- };
12
- var __export = (target, all) => {
13
- for (var name in all)
14
- __defProp(target, name, { get: all[name], enumerable: true });
15
- };
16
- var requestIdPlugin, requestId_default;
17
- var init_requestId = __esm({
18
- "src/plugins/requestId.ts"() {
19
- requestIdPlugin = async (fastify, opts = {}) => {
20
- const {
21
- header = "x-request-id",
22
- generator = randomUUID,
23
- setResponseHeader = true
24
- } = opts;
25
- if (!fastify.hasRequestDecorator("requestId")) {
26
- fastify.decorateRequest("requestId", "");
27
- }
28
- fastify.addHook("onRequest", async (request) => {
29
- const incomingId = request.headers[header];
30
- const requestId = typeof incomingId === "string" && incomingId.trim() ? incomingId.trim() : generator();
31
- request.id = requestId;
32
- request.requestId = requestId;
33
- });
34
- if (setResponseHeader) {
35
- fastify.addHook("onSend", async (request, reply) => {
36
- reply.header(header, request.requestId);
37
- });
38
- }
39
- fastify.log?.debug?.("Request ID plugin registered");
40
- };
41
- requestId_default = fp(requestIdPlugin, {
42
- name: "arc-request-id",
43
- fastify: "5.x"
44
- });
45
- }
46
- });
47
- async function runChecks(checks) {
48
- const results = [];
49
- for (const check of checks) {
50
- const start = Date.now();
51
- const timeout = check.timeout ?? 5e3;
52
- try {
53
- const checkPromise = Promise.resolve(check.check());
54
- const timeoutPromise = new Promise((_, reject) => {
55
- setTimeout(() => reject(new Error("Health check timeout")), timeout);
56
- });
57
- const healthy = await Promise.race([checkPromise, timeoutPromise]);
58
- results.push({
59
- name: check.name,
60
- healthy: Boolean(healthy),
61
- duration: Date.now() - start
62
- });
63
- } catch (err) {
64
- results.push({
65
- name: check.name,
66
- healthy: false,
67
- duration: Date.now() - start,
68
- error: err.message
69
- });
70
- }
71
- }
72
- return results;
73
- }
74
- var httpMetrics, healthPlugin, health_default;
75
- var init_health = __esm({
76
- "src/plugins/health.ts"() {
77
- httpMetrics = {
78
- requestsTotal: {},
79
- requestDurations: [],
80
- startTime: Date.now()
81
- };
82
- healthPlugin = async (fastify, opts = {}) => {
83
- const {
84
- prefix = "/_health",
85
- checks = [],
86
- metrics = false,
87
- metricsCollector,
88
- version,
89
- collectHttpMetrics = metrics
90
- } = opts;
91
- fastify.get(`${prefix}/live`, {
92
- schema: {
93
- tags: ["Health"],
94
- summary: "Liveness probe",
95
- description: "Returns 200 if the process is alive",
96
- response: {
97
- 200: {
98
- type: "object",
99
- properties: {
100
- status: { type: "string", enum: ["ok"] },
101
- timestamp: { type: "string" },
102
- version: { type: "string" }
103
- }
104
- }
105
- }
106
- }
107
- }, async () => {
108
- return {
109
- status: "ok",
110
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
111
- ...version ? { version } : {}
112
- };
113
- });
114
- fastify.get(`${prefix}/ready`, {
115
- schema: {
116
- tags: ["Health"],
117
- summary: "Readiness probe",
118
- description: "Returns 200 if all dependencies are healthy",
119
- response: {
120
- 200: {
121
- type: "object",
122
- properties: {
123
- status: { type: "string", enum: ["ready", "not_ready"] },
124
- timestamp: { type: "string" },
125
- checks: {
126
- type: "array",
127
- items: {
128
- type: "object",
129
- properties: {
130
- name: { type: "string" },
131
- healthy: { type: "boolean" },
132
- duration: { type: "number" },
133
- error: { type: "string" }
134
- }
135
- }
136
- }
137
- }
138
- },
139
- 503: {
140
- type: "object",
141
- properties: {
142
- status: { type: "string", enum: ["not_ready"] },
143
- timestamp: { type: "string" },
144
- checks: { type: "array" }
145
- }
146
- }
147
- }
148
- }
149
- }, async (_, reply) => {
150
- const results = await runChecks(checks);
151
- const criticalFailed = results.some(
152
- (r) => !r.healthy && (checks.find((c) => c.name === r.name)?.critical ?? true)
153
- );
154
- const response = {
155
- status: criticalFailed ? "not_ready" : "ready",
156
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
157
- checks: results
158
- };
159
- if (criticalFailed) {
160
- reply.code(503);
161
- }
162
- return response;
163
- });
164
- if (metrics) {
165
- fastify.get(`${prefix}/metrics`, async (_, reply) => {
166
- reply.type("text/plain; charset=utf-8");
167
- if (metricsCollector) {
168
- return await metricsCollector();
169
- }
170
- const uptime = process.uptime();
171
- const memory = process.memoryUsage();
172
- const cpu = process.cpuUsage();
173
- const lines = [
174
- "# HELP process_uptime_seconds Process uptime in seconds",
175
- "# TYPE process_uptime_seconds gauge",
176
- `process_uptime_seconds ${uptime.toFixed(2)}`,
177
- "",
178
- "# HELP process_memory_heap_bytes Heap memory usage in bytes",
179
- "# TYPE process_memory_heap_bytes gauge",
180
- `process_memory_heap_bytes{type="used"} ${memory.heapUsed}`,
181
- `process_memory_heap_bytes{type="total"} ${memory.heapTotal}`,
182
- "",
183
- "# HELP process_memory_rss_bytes RSS memory in bytes",
184
- "# TYPE process_memory_rss_bytes gauge",
185
- `process_memory_rss_bytes ${memory.rss}`,
186
- "",
187
- "# HELP process_memory_external_bytes External memory in bytes",
188
- "# TYPE process_memory_external_bytes gauge",
189
- `process_memory_external_bytes ${memory.external}`,
190
- "",
191
- "# HELP process_cpu_user_microseconds User CPU time in microseconds",
192
- "# TYPE process_cpu_user_microseconds counter",
193
- `process_cpu_user_microseconds ${cpu.user}`,
194
- "",
195
- "# HELP process_cpu_system_microseconds System CPU time in microseconds",
196
- "# TYPE process_cpu_system_microseconds counter",
197
- `process_cpu_system_microseconds ${cpu.system}`,
198
- ""
199
- ];
200
- if (collectHttpMetrics && Object.keys(httpMetrics.requestsTotal).length > 0) {
201
- lines.push(
202
- "# HELP http_requests_total Total HTTP requests by status code",
203
- "# TYPE http_requests_total counter"
204
- );
205
- for (const [status, count] of Object.entries(httpMetrics.requestsTotal)) {
206
- lines.push(`http_requests_total{status="${status}"} ${count}`);
207
- }
208
- lines.push("");
209
- if (httpMetrics.requestDurations.length > 0) {
210
- const sorted = [...httpMetrics.requestDurations].sort((a, b) => a - b);
211
- const p50 = sorted[Math.floor(sorted.length * 0.5)] || 0;
212
- const p95 = sorted[Math.floor(sorted.length * 0.95)] || 0;
213
- const p99 = sorted[Math.floor(sorted.length * 0.99)] || 0;
214
- const sum = sorted.reduce((a, b) => a + b, 0);
215
- lines.push(
216
- "# HELP http_request_duration_milliseconds HTTP request duration",
217
- "# TYPE http_request_duration_milliseconds summary",
218
- `http_request_duration_milliseconds{quantile="0.5"} ${p50.toFixed(2)}`,
219
- `http_request_duration_milliseconds{quantile="0.95"} ${p95.toFixed(2)}`,
220
- `http_request_duration_milliseconds{quantile="0.99"} ${p99.toFixed(2)}`,
221
- `http_request_duration_milliseconds_sum ${sum.toFixed(2)}`,
222
- `http_request_duration_milliseconds_count ${sorted.length}`,
223
- ""
224
- );
225
- }
226
- }
227
- return lines.join("\n");
228
- });
229
- }
230
- if (collectHttpMetrics) {
231
- fastify.addHook("onRequest", async (request) => {
232
- request._startTime = Date.now();
233
- });
234
- fastify.addHook("onResponse", async (request, reply) => {
235
- const duration = Date.now() - (request._startTime || Date.now());
236
- const statusBucket = `${Math.floor(reply.statusCode / 100)}xx`;
237
- httpMetrics.requestsTotal[statusBucket] = (httpMetrics.requestsTotal[statusBucket] || 0) + 1;
238
- httpMetrics.requestDurations.push(duration);
239
- if (httpMetrics.requestDurations.length > 1e4) {
240
- httpMetrics.requestDurations.shift();
241
- }
242
- });
243
- }
244
- fastify.log?.info?.(`Health plugin registered at ${prefix}`);
245
- };
246
- health_default = fp(healthPlugin, {
247
- name: "arc-health",
248
- fastify: "5.x"
249
- });
250
- }
251
- });
252
- function createTracerProvider(options) {
253
- if (!isAvailable) {
254
- return null;
255
- }
256
- const { serviceName = "@classytic/arc", exporterUrl = "http://localhost:4318/v1/traces" } = options;
257
- const exporter = new OTLPTraceExporter({
258
- url: exporterUrl
259
- });
260
- const provider = new NodeTracerProvider({
261
- resource: {
262
- attributes: {
263
- "service.name": serviceName,
264
- "service.version": "1.0.0"
265
- }
266
- }
267
- });
268
- provider.addSpanProcessor(new BatchSpanProcessor(exporter));
269
- provider.register();
270
- return provider;
271
- }
272
- async function tracingPlugin(fastify, options = {}) {
273
- const {
274
- serviceName = "@classytic/arc",
275
- autoInstrumentation = true,
276
- traceRepository = true,
277
- traceController = true,
278
- sampleRate = 1
279
- } = options;
280
- if (!isAvailable) {
281
- fastify.log.warn("OpenTelemetry not installed. Tracing disabled.");
282
- fastify.log.warn("Install: npm install @opentelemetry/api @opentelemetry/sdk-node");
283
- return;
284
- }
285
- const provider = createTracerProvider(options);
286
- if (!provider) {
287
- return;
288
- }
289
- if (autoInstrumentation && getNodeAutoInstrumentations) {
290
- getNodeAutoInstrumentations({
291
- "@opentelemetry/instrumentation-http": {
292
- enabled: true
293
- },
294
- "@opentelemetry/instrumentation-mongodb": {
295
- enabled: true
296
- }
297
- });
298
- }
299
- const tracer = trace.getTracer(serviceName);
300
- fastify.decorateRequest("tracer", void 0);
301
- fastify.addHook("onRequest", async (request, reply) => {
302
- if (Math.random() > sampleRate) {
303
- return;
304
- }
305
- const span = tracer.startSpan(`HTTP ${request.method} ${request.url}`, {
306
- kind: 1,
307
- // SpanKind.SERVER
308
- attributes: {
309
- "http.method": request.method,
310
- "http.url": request.url,
311
- "http.target": request.routeOptions?.url ?? request.url,
312
- "http.host": request.hostname,
313
- "http.scheme": request.protocol,
314
- "http.user_agent": request.headers["user-agent"]
315
- }
316
- });
317
- request.tracer = {
318
- tracer,
319
- currentSpan: span
320
- };
321
- context.with(trace.setSpan(context.active(), span), () => {
322
- });
323
- });
324
- fastify.addHook("onResponse", async (request, reply) => {
325
- if (!request.tracer?.currentSpan) {
326
- return;
327
- }
328
- const span = request.tracer.currentSpan;
329
- span.setAttributes({
330
- "http.status_code": reply.statusCode,
331
- "http.response_content_length": reply.getHeader("content-length")
332
- });
333
- if (reply.statusCode >= 500) {
334
- span.setStatus({
335
- code: SpanStatusCode.ERROR,
336
- message: `HTTP ${reply.statusCode}`
337
- });
338
- } else {
339
- span.setStatus({ code: SpanStatusCode.OK });
340
- }
341
- span.end();
342
- });
343
- fastify.addHook("onError", async (request, reply, error) => {
344
- if (!request.tracer?.currentSpan) {
345
- return;
346
- }
347
- const span = request.tracer.currentSpan;
348
- span.recordException(error);
349
- span.setStatus({
350
- code: SpanStatusCode.ERROR,
351
- message: error.message
352
- });
353
- });
354
- fastify.log.info({ serviceName }, "OpenTelemetry tracing enabled");
355
- }
356
- function createSpan(request, name, fn, attributes) {
357
- if (!isAvailable || !request.tracer) {
358
- return fn(null);
359
- }
360
- const { tracer, currentSpan } = request.tracer;
361
- const span = tracer.startSpan(
362
- name,
363
- {
364
- parent: currentSpan,
365
- attributes: attributes || {}
366
- },
367
- trace.setSpan(context.active(), currentSpan)
368
- );
369
- return context.with(trace.setSpan(context.active(), span), async () => {
370
- try {
371
- const result = await fn(span);
372
- span.setStatus({ code: SpanStatusCode.OK });
373
- return result;
374
- } catch (error) {
375
- span.recordException(error);
376
- span.setStatus({
377
- code: SpanStatusCode.ERROR,
378
- message: error.message
379
- });
380
- throw error;
381
- } finally {
382
- span.end();
383
- }
384
- });
385
- }
386
- function traced(spanName) {
387
- return function(target, propertyKey, descriptor) {
388
- const originalMethod = descriptor.value;
389
- descriptor.value = async function(...args) {
390
- const request = args.find((arg) => arg && arg.tracer);
391
- if (!request?.tracer) {
392
- return originalMethod.apply(this, args);
393
- }
394
- const name = spanName || `${target.constructor.name}.${propertyKey}`;
395
- return createSpan(request, name, async (span) => {
396
- if (span) {
397
- span.setAttribute("db.operation", propertyKey);
398
- span.setAttribute("db.system", "mongodb");
399
- }
400
- return originalMethod.apply(this, args);
401
- });
402
- };
403
- return descriptor;
404
- };
405
- }
406
- function isTracingAvailable() {
407
- return isAvailable;
408
- }
409
- var require2, trace, context, SpanStatusCode, NodeTracerProvider, BatchSpanProcessor, OTLPTraceExporter, HttpInstrumentation, MongoDBInstrumentation, getNodeAutoInstrumentations, isAvailable, tracing_default;
410
- var init_tracing = __esm({
411
- "src/plugins/tracing.ts"() {
412
- require2 = createRequire(import.meta.url);
413
- isAvailable = false;
414
- try {
415
- const api = require2("@opentelemetry/api");
416
- trace = api.trace;
417
- context = api.context;
418
- SpanStatusCode = api.SpanStatusCode;
419
- const sdkNode = require2("@opentelemetry/sdk-node");
420
- NodeTracerProvider = sdkNode.NodeTracerProvider;
421
- BatchSpanProcessor = sdkNode.BatchSpanProcessor;
422
- const exporterTraceOtlp = require2("@opentelemetry/exporter-trace-otlp-http");
423
- OTLPTraceExporter = exporterTraceOtlp.OTLPTraceExporter;
424
- const instrHttp = require2("@opentelemetry/instrumentation-http");
425
- HttpInstrumentation = instrHttp.HttpInstrumentation;
426
- const instrMongo = require2("@opentelemetry/instrumentation-mongodb");
427
- MongoDBInstrumentation = instrMongo.MongoDBInstrumentation;
428
- const autoInstr = require2("@opentelemetry/auto-instrumentations-node");
429
- getNodeAutoInstrumentations = autoInstr.getNodeAutoInstrumentations;
430
- isAvailable = true;
431
- } catch (e) {
432
- }
433
- tracing_default = fp(tracingPlugin, {
434
- name: "arc-tracing",
435
- fastify: "5.x"
436
- });
437
- }
438
- });
439
- var gracefulShutdownPlugin, gracefulShutdown_default;
440
- var init_gracefulShutdown = __esm({
441
- "src/plugins/gracefulShutdown.ts"() {
442
- gracefulShutdownPlugin = async (fastify, opts = {}) => {
443
- const {
444
- timeout = 3e4,
445
- onShutdown,
446
- signals = ["SIGTERM", "SIGINT"],
447
- logEvents = true
448
- } = opts;
449
- let isShuttingDown = false;
450
- const shutdown = async (signal) => {
451
- if (isShuttingDown) {
452
- if (logEvents) {
453
- fastify.log?.warn?.({ signal }, "Shutdown already in progress, ignoring signal");
454
- }
455
- return;
456
- }
457
- isShuttingDown = true;
458
- if (logEvents) {
459
- fastify.log?.info?.({ signal, timeout }, "Shutdown signal received, starting graceful shutdown");
460
- }
461
- const forceExitTimer = setTimeout(() => {
462
- if (logEvents) {
463
- fastify.log?.error?.("Graceful shutdown timeout exceeded, forcing exit");
464
- }
465
- process.exit(1);
466
- }, timeout);
467
- forceExitTimer.unref();
468
- try {
469
- if (logEvents) {
470
- fastify.log?.info?.("Closing server to new connections");
471
- }
472
- await fastify.close();
473
- if (onShutdown) {
474
- if (logEvents) {
475
- fastify.log?.info?.("Running custom shutdown handler");
476
- }
477
- await onShutdown();
478
- }
479
- if (logEvents) {
480
- fastify.log?.info?.("Graceful shutdown complete");
481
- }
482
- clearTimeout(forceExitTimer);
483
- process.exit(0);
484
- } catch (err) {
485
- if (logEvents) {
486
- fastify.log?.error?.({ error: err.message }, "Error during shutdown");
487
- }
488
- clearTimeout(forceExitTimer);
489
- process.exit(1);
490
- }
491
- };
492
- for (const signal of signals) {
493
- process.on(signal, () => {
494
- void shutdown(signal);
495
- });
496
- }
497
- fastify.decorate("shutdown", async () => {
498
- await shutdown("MANUAL");
499
- });
500
- if (logEvents) {
501
- fastify.log?.debug?.({ signals }, "Graceful shutdown plugin registered");
502
- }
503
- };
504
- gracefulShutdown_default = fp(gracefulShutdownPlugin, {
505
- name: "arc-graceful-shutdown",
506
- fastify: "5.x"
507
- });
508
- }
509
- });
510
-
511
- // src/utils/errors.ts
512
- function isArcError(error) {
513
- return error instanceof ArcError;
514
- }
515
- var ArcError;
516
- var init_errors = __esm({
517
- "src/utils/errors.ts"() {
518
- ArcError = class extends Error {
519
- name;
520
- code;
521
- statusCode;
522
- details;
523
- cause;
524
- timestamp;
525
- requestId;
526
- constructor(message, options = {}) {
527
- super(message);
528
- this.name = "ArcError";
529
- this.code = options.code ?? "ARC_ERROR";
530
- this.statusCode = options.statusCode ?? 500;
531
- this.details = options.details;
532
- this.cause = options.cause;
533
- this.timestamp = (/* @__PURE__ */ new Date()).toISOString();
534
- this.requestId = options.requestId;
535
- if (Error.captureStackTrace) {
536
- Error.captureStackTrace(this, this.constructor);
537
- }
538
- }
539
- /**
540
- * Set request ID (typically from request context)
541
- */
542
- withRequestId(requestId) {
543
- this.requestId = requestId;
544
- return this;
545
- }
546
- /**
547
- * Convert to JSON response
548
- */
549
- toJSON() {
550
- return {
551
- success: false,
552
- error: this.message,
553
- code: this.code,
554
- timestamp: this.timestamp,
555
- ...this.requestId && { requestId: this.requestId },
556
- ...this.details && { details: this.details }
557
- };
558
- }
559
- };
560
- }
561
- });
562
- async function errorHandlerPluginFn(fastify, options = {}) {
563
- const {
564
- includeStack = process.env.NODE_ENV !== "production",
565
- onError,
566
- errorMap = {}
567
- } = options;
568
- fastify.setErrorHandler(async (error, request, reply) => {
569
- if (onError) {
570
- try {
571
- await onError(error, request);
572
- } catch (callbackError) {
573
- request.log.error({ err: callbackError }, "Error in onError callback");
574
- }
575
- }
576
- const requestId = request.id;
577
- const response = {
578
- success: false,
579
- error: error.message || "Internal Server Error",
580
- code: "INTERNAL_ERROR",
581
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
582
- ...requestId && { requestId }
583
- };
584
- let statusCode = 500;
585
- if (isArcError(error)) {
586
- statusCode = error.statusCode;
587
- response.code = error.code;
588
- if (error.details) {
589
- response.details = error.details;
590
- }
591
- if (error.requestId) {
592
- response.requestId = error.requestId;
593
- }
594
- } else if ("validation" in error && Array.isArray(error.validation)) {
595
- statusCode = 400;
596
- response.code = "VALIDATION_ERROR";
597
- response.error = "Validation failed";
598
- response.details = {
599
- errors: error.validation?.map((v) => ({
600
- field: v.instancePath?.replace(/^\//, "") || v.params?.missingProperty || "unknown",
601
- message: v.message || "Invalid value",
602
- keyword: v.keyword
603
- }))
604
- };
605
- } else if ("statusCode" in error && typeof error.statusCode === "number") {
606
- statusCode = error.statusCode;
607
- response.code = statusCodeToCode(statusCode);
608
- } else if (error.name && errorMap[error.name]) {
609
- const mapping = errorMap[error.name];
610
- statusCode = mapping.statusCode;
611
- response.code = mapping.code;
612
- if (mapping.message) {
613
- response.error = mapping.message;
614
- }
615
- } else if (error.name === "ValidationError" && "errors" in error) {
616
- statusCode = 400;
617
- response.code = "VALIDATION_ERROR";
618
- const mongooseErrors = error.errors;
619
- if (process.env.NODE_ENV === "production") {
620
- response.details = { errorCount: Object.keys(mongooseErrors).length };
621
- } else {
622
- response.details = {
623
- errors: Object.entries(mongooseErrors).map(([field, err]) => ({
624
- field: err.path || field,
625
- message: err.message
626
- }))
627
- };
628
- }
629
- } else if (error.name === "CastError") {
630
- statusCode = 400;
631
- response.code = "INVALID_ID";
632
- response.error = "Invalid identifier format";
633
- } else if (error.name === "MongoServerError" && error.code === 11e3) {
634
- statusCode = 409;
635
- response.code = "DUPLICATE_KEY";
636
- response.error = "Resource already exists";
637
- const keyValue = error.keyValue;
638
- if (keyValue && process.env.NODE_ENV !== "production") {
639
- response.details = { duplicateFields: Object.keys(keyValue) };
640
- }
641
- }
642
- if (includeStack && error.stack) {
643
- response.stack = error.stack;
644
- }
645
- if (statusCode >= 500) {
646
- request.log.error({ err: error, statusCode }, "Server error");
647
- } else if (statusCode >= 400) {
648
- request.log.warn({ err: error, statusCode }, "Client error");
649
- }
650
- return reply.status(statusCode).send(response);
651
- });
652
- }
653
- function statusCodeToCode(statusCode) {
654
- const codes = {
655
- 400: "BAD_REQUEST",
656
- 401: "UNAUTHORIZED",
657
- 403: "FORBIDDEN",
658
- 404: "NOT_FOUND",
659
- 405: "METHOD_NOT_ALLOWED",
660
- 409: "CONFLICT",
661
- 422: "UNPROCESSABLE_ENTITY",
662
- 429: "RATE_LIMITED",
663
- 500: "INTERNAL_ERROR",
664
- 502: "BAD_GATEWAY",
665
- 503: "SERVICE_UNAVAILABLE",
666
- 504: "GATEWAY_TIMEOUT"
667
- };
668
- return codes[statusCode] ?? "ERROR";
669
- }
670
- var errorHandlerPlugin, errorHandler_default;
671
- var init_errorHandler = __esm({
672
- "src/plugins/errorHandler.ts"() {
673
- init_errors();
674
- errorHandlerPlugin = fp(errorHandlerPluginFn, {
675
- name: "arc-error-handler",
676
- fastify: "5.x"
677
- });
678
- errorHandler_default = errorHandlerPlugin;
679
- }
680
- });
681
-
682
- // src/hooks/HookSystem.ts
683
- var HookSystem, hookSystem;
684
- var init_HookSystem = __esm({
685
- "src/hooks/HookSystem.ts"() {
686
- HookSystem = class {
687
- hooks;
688
- logger;
689
- constructor(options) {
690
- this.hooks = /* @__PURE__ */ new Map();
691
- this.logger = options?.logger ?? { error: (...args) => console.error(...args) };
692
- }
693
- /**
694
- * Generate hook key
695
- */
696
- getKey(resource, operation, phase) {
697
- return `${resource}:${operation}:${phase}`;
698
- }
699
- /**
700
- * Register a hook
701
- * Supports both object parameter and positional arguments
702
- */
703
- register(resourceOrOptions, operation, phase, handler, priority = 10) {
704
- let resource;
705
- let finalOperation;
706
- let finalPhase;
707
- let finalHandler;
708
- let finalPriority;
709
- if (typeof resourceOrOptions === "object") {
710
- resource = resourceOrOptions.resource;
711
- finalOperation = resourceOrOptions.operation;
712
- finalPhase = resourceOrOptions.phase;
713
- finalHandler = resourceOrOptions.handler;
714
- finalPriority = resourceOrOptions.priority ?? 10;
715
- } else {
716
- resource = resourceOrOptions;
717
- finalOperation = operation;
718
- finalPhase = phase;
719
- finalHandler = handler;
720
- finalPriority = priority;
721
- }
722
- const key = this.getKey(resource, finalOperation, finalPhase);
723
- if (!this.hooks.has(key)) {
724
- this.hooks.set(key, []);
725
- }
726
- const registration = {
727
- resource,
728
- operation: finalOperation,
729
- phase: finalPhase,
730
- handler: finalHandler,
731
- priority: finalPriority
732
- };
733
- const hooks = this.hooks.get(key);
734
- hooks.push(registration);
735
- hooks.sort((a, b) => a.priority - b.priority);
736
- return () => {
737
- const idx = hooks.indexOf(registration);
738
- if (idx !== -1) {
739
- hooks.splice(idx, 1);
740
- }
741
- };
742
- }
743
- /**
744
- * Register before hook
745
- */
746
- before(resource, operation, handler, priority = 10) {
747
- return this.register(resource, operation, "before", handler, priority);
748
- }
749
- /**
750
- * Register after hook
751
- */
752
- after(resource, operation, handler, priority = 10) {
753
- return this.register(resource, operation, "after", handler, priority);
754
- }
755
- /**
756
- * Execute hooks for a given context
757
- */
758
- async execute(ctx) {
759
- const key = this.getKey(ctx.resource, ctx.operation, ctx.phase);
760
- const hooks = this.hooks.get(key) ?? [];
761
- const wildcardKey = this.getKey("*", ctx.operation, ctx.phase);
762
- const wildcardHooks = this.hooks.get(wildcardKey) ?? [];
763
- const allHooks = [...wildcardHooks, ...hooks];
764
- allHooks.sort((a, b) => a.priority - b.priority);
765
- let result = ctx.data;
766
- for (const hook of allHooks) {
767
- const handlerContext = {
768
- resource: ctx.resource,
769
- operation: ctx.operation,
770
- phase: ctx.phase,
771
- data: result,
772
- result: ctx.result,
773
- user: ctx.user,
774
- context: ctx.context,
775
- meta: ctx.meta
776
- };
777
- const hookResult = await hook.handler(handlerContext);
778
- if (hookResult !== void 0 && hookResult !== null) {
779
- result = hookResult;
780
- }
781
- }
782
- return result;
783
- }
784
- /**
785
- * Execute before hooks
786
- */
787
- async executeBefore(resource, operation, data, options) {
788
- const result = await this.execute({
789
- resource,
790
- operation,
791
- phase: "before",
792
- data,
793
- user: options?.user,
794
- context: options?.context,
795
- meta: options?.meta
796
- });
797
- return result ?? data;
798
- }
799
- /**
800
- * Execute after hooks
801
- * Errors in after hooks are logged but don't fail the request
802
- */
803
- async executeAfter(resource, operation, result, options) {
804
- try {
805
- await this.execute({
806
- resource,
807
- operation,
808
- phase: "after",
809
- result,
810
- user: options?.user,
811
- context: options?.context,
812
- meta: options?.meta
813
- });
814
- } catch (error) {
815
- this.logger.error(
816
- `[HookSystem] Error in after hook for ${resource}:${operation}:`,
817
- error
818
- );
819
- }
820
- }
821
- /**
822
- * Get all registered hooks
823
- */
824
- getAll() {
825
- const all = [];
826
- for (const hooks of this.hooks.values()) {
827
- all.push(...hooks);
828
- }
829
- return all;
830
- }
831
- /**
832
- * Get hooks for a specific resource
833
- */
834
- getForResource(resource) {
835
- const all = [];
836
- for (const [key, hooks] of this.hooks.entries()) {
837
- if (key.startsWith(`${resource}:`)) {
838
- all.push(...hooks);
839
- }
840
- }
841
- return all;
842
- }
843
- /**
844
- * Clear all hooks
845
- */
846
- clear() {
847
- this.hooks.clear();
848
- }
849
- /**
850
- * Clear hooks for a specific resource
851
- */
852
- clearResource(resource) {
853
- for (const key of this.hooks.keys()) {
854
- if (key.startsWith(`${resource}:`)) {
855
- this.hooks.delete(key);
856
- }
857
- }
858
- }
859
- };
860
- hookSystem = new HookSystem();
861
- }
862
- });
863
-
864
- // src/registry/ResourceRegistry.ts
865
- var ResourceRegistry, registryKey, globalScope, resourceRegistry;
866
- var init_ResourceRegistry = __esm({
867
- "src/registry/ResourceRegistry.ts"() {
868
- ResourceRegistry = class {
869
- _resources;
870
- _frozen;
871
- constructor() {
872
- this._resources = /* @__PURE__ */ new Map();
873
- this._frozen = false;
874
- }
875
- /**
876
- * Register a resource
877
- */
878
- register(resource, options = {}) {
879
- if (this._frozen) {
880
- throw new Error(
881
- `Registry frozen. Cannot register '${resource.name}' after startup.`
882
- );
883
- }
884
- if (this._resources.has(resource.name)) {
885
- throw new Error(`Resource '${resource.name}' already registered.`);
886
- }
887
- const entry = {
888
- name: resource.name,
889
- displayName: resource.displayName,
890
- tag: resource.tag,
891
- prefix: resource.prefix,
892
- module: options.module ?? void 0,
893
- adapter: resource.adapter ? {
894
- type: resource.adapter.type,
895
- name: resource.adapter.name
896
- } : null,
897
- permissions: resource.permissions,
898
- presets: resource._appliedPresets ?? [],
899
- routes: [],
900
- // Populated later by getIntrospection()
901
- additionalRoutes: resource.additionalRoutes.map((r) => ({
902
- method: r.method,
903
- path: r.path,
904
- handler: typeof r.handler === "string" ? r.handler : r.handler.name || "anonymous",
905
- summary: r.summary,
906
- description: r.description,
907
- permissions: r.permissions,
908
- wrapHandler: r.wrapHandler,
909
- schema: r.schema
910
- // Include schema for OpenAPI docs
911
- })),
912
- events: Object.keys(resource.events ?? {}),
913
- registeredAt: (/* @__PURE__ */ new Date()).toISOString(),
914
- disableDefaultRoutes: resource.disableDefaultRoutes,
915
- openApiSchemas: options.openApiSchemas,
916
- plugin: resource.toPlugin()
917
- // Store plugin factory
918
- };
919
- this._resources.set(resource.name, entry);
920
- return this;
921
- }
922
- /**
923
- * Get resource by name
924
- */
925
- get(name) {
926
- return this._resources.get(name);
927
- }
928
- /**
929
- * Get all resources
930
- */
931
- getAll() {
932
- return Array.from(this._resources.values());
933
- }
934
- /**
935
- * Get resources by module
936
- */
937
- getByModule(moduleName) {
938
- return this.getAll().filter((r) => r.module === moduleName);
939
- }
940
- /**
941
- * Get resources by preset
942
- */
943
- getByPreset(presetName) {
944
- return this.getAll().filter((r) => r.presets.includes(presetName));
945
- }
946
- /**
947
- * Check if resource exists
948
- */
949
- has(name) {
950
- return this._resources.has(name);
951
- }
952
- /**
953
- * Get registry statistics
954
- */
955
- getStats() {
956
- const resources = this.getAll();
957
- const presetCounts = {};
958
- for (const r of resources) {
959
- for (const preset of r.presets) {
960
- presetCounts[preset] = (presetCounts[preset] ?? 0) + 1;
961
- }
962
- }
963
- return {
964
- totalResources: resources.length,
965
- byModule: this._groupBy(resources, "module"),
966
- presetUsage: presetCounts,
967
- totalRoutes: resources.reduce((sum, r) => {
968
- const defaultRouteCount = r.disableDefaultRoutes ? 0 : 5;
969
- return sum + (r.additionalRoutes?.length ?? 0) + defaultRouteCount;
970
- }, 0),
971
- totalEvents: resources.reduce((sum, r) => sum + (r.events?.length ?? 0), 0)
972
- };
973
- }
974
- /**
975
- * Get full introspection data
976
- */
977
- getIntrospection() {
978
- return {
979
- resources: this.getAll().map((r) => {
980
- const defaultRoutes = r.disableDefaultRoutes ? [] : [
981
- { method: "GET", path: r.prefix, operation: "list" },
982
- { method: "GET", path: `${r.prefix}/:id`, operation: "get" },
983
- { method: "POST", path: r.prefix, operation: "create" },
984
- { method: "PATCH", path: `${r.prefix}/:id`, operation: "update" },
985
- { method: "DELETE", path: `${r.prefix}/:id`, operation: "delete" }
986
- ];
987
- return {
988
- name: r.name,
989
- displayName: r.displayName,
990
- prefix: r.prefix,
991
- module: r.module,
992
- presets: r.presets,
993
- permissions: r.permissions,
994
- routes: [
995
- ...defaultRoutes,
996
- ...r.additionalRoutes?.map((ar) => ({
997
- method: ar.method,
998
- path: `${r.prefix}${ar.path}`,
999
- operation: typeof ar.handler === "string" ? ar.handler : "custom",
1000
- handler: typeof ar.handler === "string" ? ar.handler : void 0,
1001
- summary: ar.summary
1002
- })) ?? []
1003
- ],
1004
- events: r.events
1005
- };
1006
- }),
1007
- stats: this.getStats(),
1008
- generatedAt: (/* @__PURE__ */ new Date()).toISOString()
1009
- };
1010
- }
1011
- /**
1012
- * Freeze registry (prevent further registrations)
1013
- */
1014
- freeze() {
1015
- this._frozen = true;
1016
- }
1017
- /**
1018
- * Check if frozen
1019
- */
1020
- isFrozen() {
1021
- return this._frozen;
1022
- }
1023
- /**
1024
- * Unfreeze registry (for testing)
1025
- */
1026
- _unfreeze() {
1027
- this._frozen = false;
1028
- }
1029
- /**
1030
- * Clear all resources (for testing)
1031
- */
1032
- _clear() {
1033
- this._resources.clear();
1034
- this._frozen = false;
1035
- }
1036
- /**
1037
- * Group by key
1038
- */
1039
- _groupBy(arr, key) {
1040
- const result = {};
1041
- for (const item of arr) {
1042
- const k = String(item[key] ?? "uncategorized");
1043
- result[k] = (result[k] ?? 0) + 1;
1044
- }
1045
- return result;
1046
- }
1047
- };
1048
- registryKey = /* @__PURE__ */ Symbol.for("arc.resourceRegistry");
1049
- globalScope = globalThis;
1050
- resourceRegistry = globalScope[registryKey] ?? new ResourceRegistry();
1051
- if (!globalScope[registryKey]) {
1052
- globalScope[registryKey] = resourceRegistry;
1053
- }
1054
- }
1055
- });
1056
- function hasEvents(instance) {
1057
- return "events" in instance && instance.events != null && typeof instance.events.publish === "function";
1058
- }
1059
- var arcCorePlugin, arcCorePlugin_default;
1060
- var init_arcCorePlugin = __esm({
1061
- "src/core/arcCorePlugin.ts"() {
1062
- init_HookSystem();
1063
- init_ResourceRegistry();
1064
- arcCorePlugin = async (fastify, opts = {}) => {
1065
- const {
1066
- emitEvents = true,
1067
- hookSystem: hookSystem2,
1068
- registry,
1069
- useGlobalSingletons = false
1070
- } = opts;
1071
- const actualHookSystem = useGlobalSingletons ? hookSystem : hookSystem2 ?? new HookSystem();
1072
- const actualRegistry = useGlobalSingletons ? resourceRegistry : registry ?? new ResourceRegistry();
1073
- fastify.decorate("arc", {
1074
- hooks: actualHookSystem,
1075
- registry: actualRegistry,
1076
- emitEvents
1077
- });
1078
- if (emitEvents) {
1079
- const eventOperations = ["create", "update", "delete"];
1080
- for (const operation of eventOperations) {
1081
- actualHookSystem.after("*", operation, async (ctx) => {
1082
- if (!hasEvents(fastify)) return;
1083
- const eventType = `${ctx.resource}.${operation}d`;
1084
- const payload = {
1085
- resource: ctx.resource,
1086
- operation: ctx.operation,
1087
- data: ctx.result,
1088
- userId: ctx.user?.id ?? ctx.user?._id,
1089
- organizationId: ctx.context?.organizationId,
1090
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
1091
- };
1092
- try {
1093
- await fastify.events.publish(eventType, payload);
1094
- } catch (error) {
1095
- fastify.log?.warn?.(
1096
- { eventType, error },
1097
- "Failed to emit event"
1098
- );
1099
- }
1100
- });
1101
- }
1102
- }
1103
- fastify.addHook("onClose", async () => {
1104
- actualHookSystem.clear();
1105
- actualRegistry._clear();
1106
- });
1107
- fastify.log?.info?.("✅ Arc core plugin enabled (instance-scoped hooks & registry)");
1108
- };
1109
- arcCorePlugin_default = fp(arcCorePlugin, {
1110
- name: "arc-core",
1111
- fastify: "5.x"
1112
- });
1113
- }
1114
- });
1115
-
1116
- // src/plugins/index.ts
1117
- var plugins_exports = {};
1118
- __export(plugins_exports, {
1119
- arcCorePlugin: () => arcCorePlugin_default,
1120
- arcCorePluginFn: () => arcCorePlugin,
1121
- createSpan: () => createSpan,
1122
- errorHandlerPlugin: () => errorHandler_default,
1123
- errorHandlerPluginFn: () => errorHandlerPlugin,
1124
- gracefulShutdownPlugin: () => gracefulShutdown_default,
1125
- gracefulShutdownPluginFn: () => gracefulShutdownPlugin,
1126
- healthPlugin: () => health_default,
1127
- healthPluginFn: () => healthPlugin,
1128
- isTracingAvailable: () => isTracingAvailable,
1129
- requestIdPlugin: () => requestId_default,
1130
- requestIdPluginFn: () => requestIdPlugin,
1131
- traced: () => traced,
1132
- tracingPlugin: () => tracing_default
1133
- });
1134
- var init_plugins = __esm({
1135
- "src/plugins/index.ts"() {
1136
- init_requestId();
1137
- init_health();
1138
- init_tracing();
1139
- init_gracefulShutdown();
1140
- init_errorHandler();
1141
- init_arcCorePlugin();
1142
- }
1143
- });
1144
- function parseExpiresIn(input, defaultValue) {
1145
- if (!input) return defaultValue;
1146
- if (/^\d+$/.test(input)) return parseInt(input, 10);
1147
- const match = /^(\d+)\s*([smhd])$/i.exec(input);
1148
- if (!match) return defaultValue;
1149
- const value = parseInt(match[1], 10);
1150
- const unit = match[2].toLowerCase();
1151
- const multipliers = { s: 1, m: 60, h: 3600, d: 86400 };
1152
- return value * (multipliers[unit] ?? 1);
1153
- }
1154
- function extractBearerToken(request) {
1155
- const auth = request.headers.authorization;
1156
- if (!auth?.startsWith("Bearer ")) return null;
1157
- return auth.slice(7);
1158
- }
1159
- var authPlugin, authPlugin_default;
1160
- var init_authPlugin = __esm({
1161
- "src/auth/authPlugin.ts"() {
1162
- authPlugin = async (fastify, opts = {}) => {
1163
- const { jwt: jwtConfig, authenticate: appAuthenticator, onFailure, userProperty = "user" } = opts;
1164
- let jwtContext = null;
1165
- if (jwtConfig?.secret) {
1166
- if (jwtConfig.secret.length < 32) {
1167
- throw new Error(
1168
- `JWT secret must be at least 32 characters (current: ${jwtConfig.secret.length}).
1169
- Use a strong random secret for production.`
1170
- );
1171
- }
1172
- const jwtPlugin = await import('@fastify/jwt');
1173
- await fastify.register(jwtPlugin.default ?? jwtPlugin, {
1174
- secret: jwtConfig.secret,
1175
- sign: {
1176
- expiresIn: jwtConfig.expiresIn ?? "15m",
1177
- ...jwtConfig.sign ?? {}
1178
- },
1179
- verify: { ...jwtConfig.verify ?? {} }
1180
- });
1181
- const fastifyWithJwt = fastify;
1182
- jwtContext = {
1183
- verify: (token) => {
1184
- return fastifyWithJwt.jwt.verify(token);
1185
- },
1186
- sign: (payload, options) => {
1187
- return fastifyWithJwt.jwt.sign(payload, options);
1188
- },
1189
- decode: (token) => {
1190
- try {
1191
- return fastifyWithJwt.jwt.decode(token);
1192
- } catch {
1193
- return null;
1194
- }
1195
- }
1196
- };
1197
- fastify.log.info("Auth: JWT infrastructure enabled");
1198
- }
1199
- const authContext = {
1200
- jwt: jwtContext,
1201
- fastify
1202
- };
1203
- const authenticate = async (request, reply) => {
1204
- try {
1205
- let user = null;
1206
- if (appAuthenticator) {
1207
- user = await appAuthenticator(request, authContext);
1208
- } else if (jwtContext) {
1209
- const token = extractBearerToken(request);
1210
- if (token) {
1211
- const decoded = jwtContext.verify(token);
1212
- user = decoded;
1213
- }
1214
- } else {
1215
- throw new Error(
1216
- "No authenticator configured. Provide auth.authenticate function or auth.jwt.secret."
1217
- );
1218
- }
1219
- if (!user) {
1220
- throw new Error("Authentication required");
1221
- }
1222
- request[userProperty] = user;
1223
- } catch (err) {
1224
- const error = err instanceof Error ? err : new Error(String(err));
1225
- if (onFailure) {
1226
- await onFailure(request, reply, error);
1227
- return;
1228
- }
1229
- const message = process.env.NODE_ENV === "production" ? "Authentication required" : error.message;
1230
- reply.code(401).send({
1231
- success: false,
1232
- error: "Unauthorized",
1233
- message
1234
- });
1235
- }
1236
- };
1237
- const refreshSecret = jwtConfig?.refreshSecret ?? jwtConfig?.secret;
1238
- const accessExpiresIn = jwtConfig?.expiresIn ?? "15m";
1239
- const refreshExpiresIn = jwtConfig?.refreshExpiresIn ?? "7d";
1240
- const issueTokens = (payload, options) => {
1241
- if (!jwtContext) {
1242
- throw new Error("JWT not configured. Provide auth.jwt.secret to use issueTokens.");
1243
- }
1244
- const accessTtl = options?.expiresIn ?? accessExpiresIn;
1245
- const refreshTtl = options?.refreshExpiresIn ?? refreshExpiresIn;
1246
- const accessToken = jwtContext.sign(payload, { expiresIn: accessTtl });
1247
- const refreshPayload = payload.id ? { id: payload.id, type: "refresh" } : payload._id ? { id: payload._id, type: "refresh" } : { ...payload, type: "refresh" };
1248
- let refreshToken;
1249
- if (refreshSecret) {
1250
- const fastifyWithJwt = fastify;
1251
- refreshToken = fastifyWithJwt.jwt.sign(refreshPayload, {
1252
- expiresIn: refreshTtl,
1253
- // Use refresh secret if different from main secret
1254
- ...refreshSecret !== jwtConfig?.secret ? { secret: refreshSecret } : {}
1255
- });
1256
- }
1257
- return {
1258
- accessToken,
1259
- refreshToken,
1260
- expiresIn: parseExpiresIn(accessTtl, 900),
1261
- refreshExpiresIn: refreshToken ? parseExpiresIn(refreshTtl, 604800) : void 0,
1262
- tokenType: "Bearer"
1263
- };
1264
- };
1265
- const verifyRefreshToken = (token) => {
1266
- if (!jwtContext) {
1267
- throw new Error("JWT not configured. Provide auth.jwt.secret to use verifyRefreshToken.");
1268
- }
1269
- const fastifyWithJwt = fastify;
1270
- return fastifyWithJwt.jwt.verify(token, {
1271
- ...refreshSecret !== jwtConfig?.secret ? { secret: refreshSecret } : {}
1272
- });
1273
- };
1274
- const authorize = (...allowedRoles) => {
1275
- return async (request, reply) => {
1276
- const user = request[userProperty];
1277
- if (!user) {
1278
- reply.code(401).send({
1279
- success: false,
1280
- error: "Unauthorized",
1281
- message: "No user context"
1282
- });
1283
- return;
1284
- }
1285
- const userRoles = user.roles ?? [];
1286
- if (allowedRoles.length === 1 && allowedRoles[0] === "*") {
1287
- return;
1288
- }
1289
- const hasRole = allowedRoles.some((role) => userRoles.includes(role));
1290
- if (!hasRole) {
1291
- reply.code(403).send({
1292
- success: false,
1293
- error: "Forbidden",
1294
- message: `Requires one of: ${allowedRoles.join(", ")}`
1295
- });
1296
- return;
1297
- }
1298
- };
1299
- };
1300
- const authHelpers = {
1301
- jwt: jwtContext,
1302
- issueTokens,
1303
- verifyRefreshToken
1304
- };
1305
- fastify.decorate("authenticate", authenticate);
1306
- fastify.decorate("authorize", authorize);
1307
- fastify.decorate("auth", authHelpers);
1308
- fastify.log.info(
1309
- `Auth: Plugin registered (jwt=${!!jwtContext}, customAuth=${!!appAuthenticator})`
1310
- );
1311
- };
1312
- authPlugin_default = fp(authPlugin, {
1313
- name: "arc-auth",
1314
- fastify: "5.x"
1315
- });
1316
- }
1317
- });
1318
-
1319
- // src/auth/index.ts
1320
- var auth_exports = {};
1321
- __export(auth_exports, {
1322
- authPlugin: () => authPlugin_default,
1323
- authPluginFn: () => authPlugin
1324
- });
1325
- var init_auth = __esm({
1326
- "src/auth/index.ts"() {
1327
- init_authPlugin();
1328
- }
1329
- });
1330
-
1331
- // src/factory/presets.ts
1332
- var productionPreset = {
1333
- // Raw JSON logs for production (log aggregators like Datadog, CloudWatch, etc.)
1334
- logger: {
1335
- level: "info"
1336
- },
1337
- trustProxy: true,
1338
- // Security
1339
- helmet: {
1340
- contentSecurityPolicy: {
1341
- directives: {
1342
- defaultSrc: ["'self'"],
1343
- styleSrc: ["'self'", "'unsafe-inline'"],
1344
- scriptSrc: ["'self'"],
1345
- imgSrc: ["'self'", "data:", "https:"]
1346
- }
1347
- }
1348
- },
1349
- // CORS - must be explicitly configured
1350
- cors: {
1351
- origin: false,
1352
- // Disabled by default in production
1353
- credentials: true,
1354
- methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
1355
- allowedHeaders: ["Content-Type", "Authorization", "Accept"]
1356
- },
1357
- // Rate limiting - strict
1358
- rateLimit: {
1359
- max: 100,
1360
- timeWindow: "1 minute"
1361
- },
1362
- // Note: Compression not included (use proxy/CDN instead)
1363
- // Under pressure - health monitoring
1364
- underPressure: {
1365
- exposeStatusRoute: true,
1366
- maxEventLoopDelay: 1e3,
1367
- maxHeapUsedBytes: 1024 * 1024 * 1024,
1368
- // 1GB
1369
- maxRssBytes: 1024 * 1024 * 1024
1370
- // 1GB
1371
- }
1372
- };
1373
- var developmentPreset = {
1374
- logger: {
1375
- level: "debug",
1376
- transport: {
1377
- target: "pino-pretty",
1378
- options: {
1379
- colorize: true,
1380
- translateTime: "SYS:HH:MM:ss",
1381
- ignore: "pid,hostname"
1382
- }
1383
- }
1384
- },
1385
- trustProxy: true,
1386
- // Security - relaxed for development
1387
- helmet: {
1388
- contentSecurityPolicy: false
1389
- // Disable CSP in dev
1390
- },
1391
- // CORS - allow all origins in development
1392
- cors: {
1393
- origin: true,
1394
- // Allow all origins
1395
- credentials: true,
1396
- methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
1397
- allowedHeaders: ["Content-Type", "Authorization", "Accept"]
1398
- },
1399
- // Rate limiting - very relaxed
1400
- rateLimit: {
1401
- max: 1e3,
1402
- timeWindow: "1 minute"
1403
- },
1404
- // Note: Compression not included (use proxy/CDN instead)
1405
- // Under pressure - relaxed
1406
- underPressure: {
1407
- exposeStatusRoute: true,
1408
- maxEventLoopDelay: 5e3
1409
- }
1410
- };
1411
- var testingPreset = {
1412
- logger: false,
1413
- // Disable logging in tests
1414
- trustProxy: false,
1415
- // Security - disabled for tests
1416
- helmet: false,
1417
- cors: false,
1418
- rateLimit: false,
1419
- underPressure: false,
1420
- // Sensible plugins still enabled
1421
- sensible: true,
1422
- multipart: {
1423
- limits: {
1424
- fileSize: 1024 * 1024,
1425
- // 1MB
1426
- files: 5
1427
- }
1428
- }
1429
- };
1430
- function getPreset(name) {
1431
- switch (name) {
1432
- case "production":
1433
- return productionPreset;
1434
- case "development":
1435
- return developmentPreset;
1436
- case "testing":
1437
- return testingPreset;
1438
- default:
1439
- throw new Error(`Unknown preset: ${name}`);
1440
- }
1441
- }
1442
-
1443
- // src/factory/createApp.ts
1444
- var PLUGIN_PACKAGES = {
1445
- cors: "@fastify/cors",
1446
- helmet: "@fastify/helmet",
1447
- rateLimit: "@fastify/rate-limit",
1448
- underPressure: "@fastify/under-pressure",
1449
- sensible: "@fastify/sensible",
1450
- multipart: "@fastify/multipart",
1451
- rawBody: "fastify-raw-body"
1452
- };
1453
- var OPTIONAL_PLUGINS = /* @__PURE__ */ new Set(["multipart", "rawBody"]);
1454
- async function loadPlugin(name, logger) {
1455
- const packageName = PLUGIN_PACKAGES[name];
1456
- if (!packageName) {
1457
- throw new Error(`Unknown plugin: ${name}`);
1458
- }
1459
- try {
1460
- switch (name) {
1461
- case "cors":
1462
- return (await import('@fastify/cors')).default;
1463
- case "helmet":
1464
- return (await import('@fastify/helmet')).default;
1465
- case "rateLimit":
1466
- return (await import('@fastify/rate-limit')).default;
1467
- case "underPressure":
1468
- return (await import('@fastify/under-pressure')).default;
1469
- case "sensible":
1470
- return (await import('@fastify/sensible')).default;
1471
- case "multipart":
1472
- return (await import('@fastify/multipart')).default;
1473
- case "rawBody":
1474
- return (await import('fastify-raw-body')).default;
1475
- default:
1476
- throw new Error(`Unknown plugin: ${name}`);
1477
- }
1478
- } catch (error) {
1479
- const err = error;
1480
- const isModuleNotFound = err.message.includes("Cannot find module") || err.message.includes("Cannot find package") || err.message.includes("MODULE_NOT_FOUND") || err.message.includes("Could not resolve");
1481
- if (isModuleNotFound && OPTIONAL_PLUGINS.has(name)) {
1482
- logger?.warn(`ℹ️ Optional plugin '${name}' skipped (${packageName} not installed)`);
1483
- return null;
1484
- }
1485
- if (isModuleNotFound) {
1486
- throw new Error(
1487
- `Plugin '${name}' requires package '${packageName}' which is not installed.
1488
- Install it with: npm install ${packageName}
1489
- Or disable this plugin by setting ${name}: false in createApp options.`
1490
- );
1491
- }
1492
- throw new Error(`Failed to load plugin '${name}': ${err.message}`);
1493
- }
1494
- }
1495
- async function createApp(options) {
1496
- const authConfig = options.auth;
1497
- const isAuthDisabled = authConfig === false;
1498
- const hasCustomPlugin = typeof authConfig === "object" && "plugin" in authConfig && authConfig.plugin;
1499
- const hasCustomAuthenticator = typeof authConfig === "object" && "authenticate" in authConfig;
1500
- const jwtSecret = typeof authConfig === "object" && "jwt" in authConfig ? authConfig.jwt?.secret : void 0;
1501
- if (!isAuthDisabled && !hasCustomPlugin && !jwtSecret && !hasCustomAuthenticator) {
1502
- throw new Error(
1503
- "createApp: JWT secret required when Arc auth is enabled.\nProvide auth.jwt.secret, auth.authenticate, or set auth: false to disable.\nExample: auth: { jwt: { secret: process.env.JWT_SECRET } }"
1504
- );
1505
- }
1506
- const presetConfig = options.preset ? getPreset(options.preset) : {};
1507
- const config = { ...presetConfig, ...options };
1508
- const fastify = Fastify({
1509
- logger: config.logger ?? true,
1510
- trustProxy: config.trustProxy ?? false,
1511
- // Use qs parser to support nested bracket notation in query strings
1512
- // e.g., ?populate[author][select]=name,email → { populate: { author: { select: 'name,email' } } }
1513
- // This is required for MongoKit's advanced populate options to work
1514
- querystringParser: (str) => qs.parse(str),
1515
- ajv: {
1516
- customOptions: {
1517
- coerceTypes: true,
1518
- useDefaults: true,
1519
- removeAdditional: false
1520
- }
1521
- }
1522
- });
1523
- if (config.helmet !== false) {
1524
- const helmet = await loadPlugin("helmet");
1525
- await fastify.register(helmet, config.helmet ?? {});
1526
- fastify.log.info("✅ Helmet (security headers) enabled");
1527
- } else {
1528
- fastify.log.warn("⚠️ Helmet disabled - security headers not applied");
1529
- }
1530
- if (config.cors !== false) {
1531
- const cors = await loadPlugin("cors");
1532
- const corsOptions = config.cors ?? {};
1533
- if (config.preset === "production" && (!corsOptions || !("origin" in corsOptions))) {
1534
- throw new Error(
1535
- "CORS origin must be explicitly configured in production.\nSet cors.origin to allowed domains or set cors: false to disable.\nExample: cors: { origin: ['https://yourdomain.com'] }\nDocs: https://github.com/classytic/arc#security"
1536
- );
1537
- }
1538
- await fastify.register(cors, corsOptions);
1539
- fastify.log.info("✅ CORS enabled");
1540
- } else {
1541
- fastify.log.warn("⚠️ CORS disabled");
1542
- }
1543
- if (config.rateLimit !== false) {
1544
- const rateLimit = await loadPlugin("rateLimit");
1545
- await fastify.register(rateLimit, config.rateLimit ?? { max: 100, timeWindow: "1 minute" });
1546
- fastify.log.info("✅ Rate limiting enabled");
1547
- } else {
1548
- fastify.log.warn("⚠️ Rate limiting disabled");
1549
- }
1550
- if (config.underPressure !== false) {
1551
- const underPressure = await loadPlugin("underPressure");
1552
- await fastify.register(underPressure, config.underPressure ?? { exposeStatusRoute: true });
1553
- fastify.log.info("✅ Health monitoring (under-pressure) enabled");
1554
- } else {
1555
- fastify.log.info("ℹ️ Health monitoring disabled");
1556
- }
1557
- if (config.sensible !== false) {
1558
- const sensible = await loadPlugin("sensible");
1559
- await fastify.register(sensible);
1560
- fastify.log.info("✅ Sensible (HTTP helpers) enabled");
1561
- }
1562
- if (config.multipart !== false) {
1563
- const multipart = await loadPlugin("multipart", fastify.log);
1564
- if (multipart) {
1565
- const multipartDefaults = {
1566
- limits: {
1567
- fileSize: 10 * 1024 * 1024,
1568
- // 10MB
1569
- files: 10
1570
- }
1571
- };
1572
- await fastify.register(multipart, { ...multipartDefaults, ...config.multipart });
1573
- fastify.log.info("✅ Multipart (file uploads) enabled");
1574
- }
1575
- }
1576
- if (config.rawBody !== false) {
1577
- const rawBody = await loadPlugin("rawBody", fastify.log);
1578
- if (rawBody) {
1579
- const rawBodyDefaults = {
1580
- field: "rawBody",
1581
- global: false,
1582
- encoding: "utf8",
1583
- runFirst: true
1584
- };
1585
- await fastify.register(rawBody, { ...rawBodyDefaults, ...config.rawBody });
1586
- fastify.log.info("✅ Raw body parsing enabled");
1587
- }
1588
- }
1589
- const { arcCorePlugin: arcCorePlugin2 } = await Promise.resolve().then(() => (init_plugins(), plugins_exports));
1590
- await fastify.register(arcCorePlugin2, {
1591
- emitEvents: config.arcPlugins?.emitEvents !== false
1592
- });
1593
- if (config.arcPlugins?.requestId !== false) {
1594
- const { requestIdPlugin: requestIdPlugin2 } = await Promise.resolve().then(() => (init_plugins(), plugins_exports));
1595
- await fastify.register(requestIdPlugin2);
1596
- fastify.log.info("✅ Arc requestId plugin enabled");
1597
- }
1598
- if (config.arcPlugins?.health !== false) {
1599
- const { healthPlugin: healthPlugin2 } = await Promise.resolve().then(() => (init_plugins(), plugins_exports));
1600
- await fastify.register(healthPlugin2);
1601
- fastify.log.info("✅ Arc health plugin enabled");
1602
- }
1603
- if (config.arcPlugins?.gracefulShutdown !== false) {
1604
- const { gracefulShutdownPlugin: gracefulShutdownPlugin2 } = await Promise.resolve().then(() => (init_plugins(), plugins_exports));
1605
- await fastify.register(gracefulShutdownPlugin2);
1606
- fastify.log.info("✅ Arc gracefulShutdown plugin enabled");
1607
- }
1608
- if (!isAuthDisabled) {
1609
- if (hasCustomPlugin) {
1610
- const pluginFn = authConfig.plugin;
1611
- await pluginFn(fastify);
1612
- fastify.log.info("✅ Custom authentication plugin enabled");
1613
- } else {
1614
- const { authPlugin: authPlugin2 } = await Promise.resolve().then(() => (init_auth(), auth_exports));
1615
- const { plugin: _, ...authOpts } = typeof authConfig === "object" ? authConfig : {};
1616
- await fastify.register(authPlugin2, authOpts);
1617
- fastify.log.info("✅ Arc authentication plugin enabled");
1618
- }
1619
- } else {
1620
- fastify.log.info("ℹ️ Authentication disabled");
1621
- }
1622
- if (config.plugins) {
1623
- await config.plugins(fastify);
1624
- fastify.log.info("✅ Custom plugins registered");
1625
- }
1626
- fastify.log.info(
1627
- `🚀 Arc application created successfully (preset: ${config.preset ?? "custom"}, security: helmet=${config.helmet !== false}, cors=${config.cors !== false}, rateLimit=${config.rateLimit !== false})`
1628
- );
1629
- return fastify;
1630
- }
1631
- var ArcFactory = {
1632
- /**
1633
- * Create production app with strict security
1634
- */
1635
- async production(options) {
1636
- return createApp({ ...options, preset: "production" });
1637
- },
1638
- /**
1639
- * Create development app with relaxed security
1640
- */
1641
- async development(options) {
1642
- return createApp({ ...options, preset: "development" });
1643
- },
1644
- /**
1645
- * Create testing app with minimal setup
1646
- */
1647
- async testing(options) {
1648
- return createApp({ ...options, preset: "testing" });
1649
- }
1650
- };
1651
-
1652
- export { ArcFactory, createApp, developmentPreset, getPreset, productionPreset, testingPreset };