@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,6 +1,27 @@
1
- import { i as CacheStore, n as CacheSetOptions, r as CacheStats, t as CacheLogger } from "../interface-DTbsvIWe.mjs";
2
- import { a as CacheEnvelope, c as QueryCache, i as queryCachePlugin, l as QueryCacheConfig, n as QueryCacheDefaults, o as CacheResult, r as QueryCachePluginOptions, s as CacheStatus, t as CrossResourceRule } from "../queryCachePlugin-Q6SYuHZ6.mjs";
1
+ import { i as CacheStore, n as CacheSetOptions, r as CacheStats, t as CacheLogger } from "../interface-D_BWALyZ.mjs";
2
+ import { a as CacheEnvelope, c as QueryCache, i as queryCachePlugin, l as QueryCacheConfig, n as QueryCacheDefaults, o as CacheResult, r as QueryCachePluginOptions, s as CacheStatus, t as CrossResourceRule } from "../queryCachePlugin-DcmETvcB.mjs";
3
3
 
4
+ //#region src/cache/keys.d.ts
5
+ /**
6
+ * Cache Key Utilities
7
+ *
8
+ * Deterministic, scope-safe key generation for QueryCache.
9
+ * Keys include resource version, operation, params hash, and user/org scope
10
+ * to ensure multi-tenant isolation and O(1) version-based invalidation.
11
+ */
12
+ /** Build a deterministic cache key for a query */
13
+ declare function buildQueryKey(resource: string, operation: string, resourceVersion: number, params: Record<string, unknown>, userId?: string, orgId?: string): string;
14
+ /** Resource version key — stored in CacheStore, bumped on mutations */
15
+ declare function versionKey(resource: string): string;
16
+ /** Tag version key — stored in CacheStore, bumped on cross-resource invalidation */
17
+ declare function tagVersionKey(tag: string): string;
18
+ /**
19
+ * Stable hash for query params.
20
+ * Sorts keys recursively, serializes to JSON, then applies djb2 hash.
21
+ * Returns hex string.
22
+ */
23
+ declare function hashParams(params: Record<string, unknown>): string;
24
+ //#endregion
4
25
  //#region src/cache/memory.d.ts
5
26
  interface MemoryCacheStoreOptions {
6
27
  /** Default TTL in milliseconds (default: 60_000) */
@@ -121,25 +142,4 @@ declare class RedisCacheStore<TValue = unknown> implements CacheStore<TValue> {
121
142
  private withPrefix;
122
143
  }
123
144
  //#endregion
124
- //#region src/cache/keys.d.ts
125
- /**
126
- * Cache Key Utilities
127
- *
128
- * Deterministic, scope-safe key generation for QueryCache.
129
- * Keys include resource version, operation, params hash, and user/org scope
130
- * to ensure multi-tenant isolation and O(1) version-based invalidation.
131
- */
132
- /** Build a deterministic cache key for a query */
133
- declare function buildQueryKey(resource: string, operation: string, resourceVersion: number, params: Record<string, unknown>, userId?: string, orgId?: string): string;
134
- /** Resource version key — stored in CacheStore, bumped on mutations */
135
- declare function versionKey(resource: string): string;
136
- /** Tag version key — stored in CacheStore, bumped on cross-resource invalidation */
137
- declare function tagVersionKey(tag: string): string;
138
- /**
139
- * Stable hash for query params.
140
- * Sorts keys recursively, serializes to JSON, then applies djb2 hash.
141
- * Returns hex string.
142
- */
143
- declare function hashParams(params: Record<string, unknown>): string;
144
- //#endregion
145
145
  export { type CacheEnvelope, type CacheLogger, type CacheResult, type CacheSetOptions, type CacheStats, type CacheStatus, type CacheStore, type CrossResourceRule, MemoryCacheStore, type MemoryCacheStoreOptions, QueryCache, type QueryCacheConfig, type QueryCacheDefaults, type QueryCachePluginOptions, type RedisCacheClient, RedisCacheStore, type RedisCacheStoreOptions, type RedisPipeline, buildQueryKey, hashParams, queryCachePlugin, tagVersionKey, versionKey };
@@ -1,7 +1,6 @@
1
- import { i as versionKey, n as hashParams, r as tagVersionKey, t as buildQueryKey } from "../keys-DhqDRxv3.mjs";
2
- import { t as MemoryCacheStore } from "../memory-B2v7KrCB.mjs";
3
- import { r as QueryCache, t as queryCachePlugin } from "../queryCachePlugin-B6R0d4av.mjs";
4
-
1
+ import { i as versionKey, n as hashParams, r as tagVersionKey, t as buildQueryKey } from "../keys-qcD-TVJl.mjs";
2
+ import { t as MemoryCacheStore } from "../memory-Cb_7iy9e.mjs";
3
+ import { r as QueryCache, t as queryCachePlugin } from "../queryCachePlugin-ClosZdNS.mjs";
5
4
  //#region src/cache/redis.ts
6
5
  /**
7
6
  * Redis-backed cache store.
@@ -86,6 +85,5 @@ var RedisCacheStore = class {
86
85
  return `${this.prefix}${key}`;
87
86
  }
88
87
  };
89
-
90
88
  //#endregion
91
- export { MemoryCacheStore, QueryCache, RedisCacheStore, buildQueryKey, hashParams, queryCachePlugin, tagVersionKey, versionKey };
89
+ export { MemoryCacheStore, QueryCache, RedisCacheStore, buildQueryKey, hashParams, queryCachePlugin, tagVersionKey, versionKey };
@@ -1,27 +1,6 @@
1
- import { t as __exportAll } from "./chunk-C7Uep-_p.mjs";
1
+ import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
2
2
  import fp from "fastify-plugin";
3
-
4
3
  //#region src/plugins/caching.ts
5
- /**
6
- * Caching Plugin
7
- *
8
- * Adds ETag and Cache-Control headers to GET/HEAD responses.
9
- * Supports conditional requests (304 Not Modified) for bandwidth savings.
10
- *
11
- * @example
12
- * import { cachingPlugin } from '@classytic/arc/plugins';
13
- *
14
- * // Basic — ETag + conditional requests, no browser caching
15
- * await fastify.register(cachingPlugin);
16
- *
17
- * // With cache rules per path
18
- * await fastify.register(cachingPlugin, {
19
- * rules: [
20
- * { match: '/api/products', maxAge: 60 },
21
- * { match: '/api/categories', maxAge: 300, staleWhileRevalidate: 60 },
22
- * ],
23
- * });
24
- */
25
4
  var caching_exports = /* @__PURE__ */ __exportAll({
26
5
  cachingPlugin: () => cachingPlugin,
27
6
  default: () => caching_default
@@ -88,6 +67,5 @@ var caching_default = fp(cachingPlugin, {
88
67
  name: "arc-caching",
89
68
  fastify: "5.x"
90
69
  });
91
-
92
70
  //#endregion
93
- export { caching_default as n, caching_exports as r, cachingPlugin as t };
71
+ export { caching_default as n, caching_exports as r, cachingPlugin as t };
@@ -0,0 +1,14 @@
1
+ import "node:module";
2
+ //#region \0rolldown/runtime.js
3
+ var __defProp = Object.defineProperty;
4
+ var __exportAll = (all, no_symbols) => {
5
+ let target = {};
6
+ for (var name in all) __defProp(target, name, {
7
+ get: all[name],
8
+ enumerable: true
9
+ });
10
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
11
+ return target;
12
+ };
13
+ //#endregion
14
+ export { __exportAll as t };
@@ -0,0 +1,284 @@
1
+ //#region src/utils/circuitBreaker.ts
2
+ /**
3
+ * Circuit Breaker Pattern
4
+ *
5
+ * Wraps external service calls with failure protection.
6
+ * Prevents cascading failures by "opening" the circuit when
7
+ * a service is failing, allowing it time to recover.
8
+ *
9
+ * States:
10
+ * - CLOSED: Normal operation, requests pass through
11
+ * - OPEN: Too many failures, all requests fail fast
12
+ * - HALF_OPEN: Testing if service recovered, limited requests
13
+ *
14
+ * @example
15
+ * import { CircuitBreaker } from '@classytic/arc/utils';
16
+ *
17
+ * const paymentBreaker = new CircuitBreaker(async (amount) => {
18
+ * return await stripe.charges.create({ amount });
19
+ * }, {
20
+ * failureThreshold: 5,
21
+ * resetTimeout: 30000,
22
+ * timeout: 5000,
23
+ * });
24
+ *
25
+ * try {
26
+ * const result = await paymentBreaker.call(100);
27
+ * } catch (error) {
28
+ * // Handle failure or circuit open
29
+ * }
30
+ */
31
+ const CircuitState = {
32
+ CLOSED: "CLOSED",
33
+ OPEN: "OPEN",
34
+ HALF_OPEN: "HALF_OPEN"
35
+ };
36
+ var CircuitBreakerError = class extends Error {
37
+ state;
38
+ constructor(message, state) {
39
+ super(message);
40
+ this.name = "CircuitBreakerError";
41
+ this.state = state;
42
+ }
43
+ };
44
+ var CircuitBreaker = class {
45
+ state = CircuitState.CLOSED;
46
+ failures = 0;
47
+ successes = 0;
48
+ totalCalls = 0;
49
+ nextAttempt = 0;
50
+ lastCallAt = null;
51
+ openedAt = null;
52
+ failureThreshold;
53
+ resetTimeout;
54
+ timeout;
55
+ successThreshold;
56
+ fallback;
57
+ onStateChange;
58
+ onError;
59
+ name;
60
+ fn;
61
+ constructor(fn, options = {}) {
62
+ this.fn = fn;
63
+ this.failureThreshold = options.failureThreshold ?? 5;
64
+ this.resetTimeout = options.resetTimeout ?? 6e4;
65
+ this.timeout = options.timeout ?? 1e4;
66
+ this.successThreshold = options.successThreshold ?? 1;
67
+ this.fallback = options.fallback;
68
+ this.onStateChange = options.onStateChange;
69
+ this.onError = options.onError;
70
+ this.name = options.name ?? "CircuitBreaker";
71
+ }
72
+ /**
73
+ * Call the wrapped function with circuit breaker protection
74
+ */
75
+ async call(...args) {
76
+ this.totalCalls++;
77
+ this.lastCallAt = Date.now();
78
+ if (this.state === CircuitState.OPEN) {
79
+ if (Date.now() < this.nextAttempt) {
80
+ const error = new CircuitBreakerError(`Circuit breaker is OPEN for ${this.name}`, CircuitState.OPEN);
81
+ if (this.fallback) return this.fallback(...args);
82
+ throw error;
83
+ }
84
+ this.setState(CircuitState.HALF_OPEN);
85
+ }
86
+ try {
87
+ const result = await this.executeWithTimeout(args);
88
+ this.onSuccess();
89
+ return result;
90
+ } catch (err) {
91
+ this.onFailure(err instanceof Error ? err : new Error(String(err)));
92
+ throw err;
93
+ }
94
+ }
95
+ /**
96
+ * Execute function with timeout
97
+ */
98
+ async executeWithTimeout(args) {
99
+ return new Promise((resolve, reject) => {
100
+ const timeoutId = setTimeout(() => {
101
+ reject(/* @__PURE__ */ new Error(`Request timeout after ${this.timeout}ms`));
102
+ }, this.timeout);
103
+ this.fn(...args).then((result) => {
104
+ clearTimeout(timeoutId);
105
+ resolve(result);
106
+ }).catch((error) => {
107
+ clearTimeout(timeoutId);
108
+ reject(error);
109
+ });
110
+ });
111
+ }
112
+ /**
113
+ * Handle successful call
114
+ */
115
+ onSuccess() {
116
+ this.failures = 0;
117
+ this.successes++;
118
+ if (this.state === CircuitState.HALF_OPEN) {
119
+ if (this.successes >= this.successThreshold) {
120
+ this.setState(CircuitState.CLOSED);
121
+ this.successes = 0;
122
+ }
123
+ }
124
+ }
125
+ /**
126
+ * Handle failed call
127
+ */
128
+ onFailure(error) {
129
+ this.failures++;
130
+ this.successes = 0;
131
+ if (this.onError) this.onError(error);
132
+ if (this.state === CircuitState.HALF_OPEN || this.failures >= this.failureThreshold) {
133
+ this.setState(CircuitState.OPEN);
134
+ this.nextAttempt = Date.now() + this.resetTimeout;
135
+ this.openedAt = Date.now();
136
+ }
137
+ }
138
+ /**
139
+ * Change circuit state
140
+ */
141
+ setState(newState) {
142
+ const oldState = this.state;
143
+ if (oldState !== newState) {
144
+ this.state = newState;
145
+ if (this.onStateChange) this.onStateChange(oldState, newState);
146
+ }
147
+ }
148
+ /**
149
+ * Manually open the circuit
150
+ */
151
+ open() {
152
+ this.setState(CircuitState.OPEN);
153
+ this.nextAttempt = Date.now() + this.resetTimeout;
154
+ this.openedAt = Date.now();
155
+ }
156
+ /**
157
+ * Manually close the circuit
158
+ */
159
+ close() {
160
+ this.failures = 0;
161
+ this.successes = 0;
162
+ this.setState(CircuitState.CLOSED);
163
+ this.openedAt = null;
164
+ }
165
+ /**
166
+ * Get current statistics
167
+ */
168
+ getStats() {
169
+ return {
170
+ name: this.name,
171
+ state: this.state,
172
+ failures: this.failures,
173
+ successes: this.successes,
174
+ totalCalls: this.totalCalls,
175
+ openedAt: this.openedAt,
176
+ lastCallAt: this.lastCallAt
177
+ };
178
+ }
179
+ /**
180
+ * Get current state
181
+ */
182
+ getState() {
183
+ return this.state;
184
+ }
185
+ /**
186
+ * Check if circuit is open
187
+ */
188
+ isOpen() {
189
+ return this.state === CircuitState.OPEN;
190
+ }
191
+ /**
192
+ * Check if circuit is closed
193
+ */
194
+ isClosed() {
195
+ return this.state === CircuitState.CLOSED;
196
+ }
197
+ /**
198
+ * Reset statistics
199
+ */
200
+ reset() {
201
+ this.failures = 0;
202
+ this.successes = 0;
203
+ this.totalCalls = 0;
204
+ this.lastCallAt = null;
205
+ this.openedAt = null;
206
+ this.setState(CircuitState.CLOSED);
207
+ }
208
+ };
209
+ /**
210
+ * Create a circuit breaker with sensible defaults
211
+ *
212
+ * @example
213
+ * const emailBreaker = createCircuitBreaker(
214
+ * async (to, subject, body) => sendEmail(to, subject, body),
215
+ * { name: 'email-service' }
216
+ * );
217
+ */
218
+ function createCircuitBreaker(fn, options) {
219
+ return new CircuitBreaker(fn, options);
220
+ }
221
+ /**
222
+ * Circuit breaker registry for managing multiple breakers
223
+ */
224
+ var CircuitBreakerRegistry = class {
225
+ breakers = /* @__PURE__ */ new Map();
226
+ /**
227
+ * Register a circuit breaker
228
+ */
229
+ register(name, fn, options) {
230
+ const breaker = new CircuitBreaker(fn, {
231
+ ...options,
232
+ name
233
+ });
234
+ this.breakers.set(name, breaker);
235
+ return breaker;
236
+ }
237
+ /**
238
+ * Get a circuit breaker by name
239
+ */
240
+ get(name) {
241
+ return this.breakers.get(name);
242
+ }
243
+ /**
244
+ * Get all breakers
245
+ */
246
+ getAll() {
247
+ return this.breakers;
248
+ }
249
+ /**
250
+ * Get statistics for all breakers
251
+ */
252
+ getAllStats() {
253
+ const stats = {};
254
+ for (const [name, breaker] of this.breakers.entries()) stats[name] = breaker.getStats();
255
+ return stats;
256
+ }
257
+ /**
258
+ * Reset all breakers
259
+ */
260
+ resetAll() {
261
+ for (const breaker of this.breakers.values()) breaker.reset();
262
+ }
263
+ /**
264
+ * Open all breakers
265
+ */
266
+ openAll() {
267
+ for (const breaker of this.breakers.values()) breaker.open();
268
+ }
269
+ /**
270
+ * Close all breakers
271
+ */
272
+ closeAll() {
273
+ for (const breaker of this.breakers.values()) breaker.close();
274
+ }
275
+ };
276
+ /**
277
+ * Create a new CircuitBreakerRegistry instance.
278
+ * Use this instead of a global singleton — attach to fastify.arc or pass explicitly.
279
+ */
280
+ function createCircuitBreakerRegistry() {
281
+ return new CircuitBreakerRegistry();
282
+ }
283
+ //#endregion
284
+ export { createCircuitBreaker as a, CircuitState as i, CircuitBreakerError as n, createCircuitBreakerRegistry as o, CircuitBreakerRegistry as r, CircuitBreaker as t };
@@ -0,0 +1,206 @@
1
+ //#region src/utils/circuitBreaker.d.ts
2
+ /**
3
+ * Circuit Breaker Pattern
4
+ *
5
+ * Wraps external service calls with failure protection.
6
+ * Prevents cascading failures by "opening" the circuit when
7
+ * a service is failing, allowing it time to recover.
8
+ *
9
+ * States:
10
+ * - CLOSED: Normal operation, requests pass through
11
+ * - OPEN: Too many failures, all requests fail fast
12
+ * - HALF_OPEN: Testing if service recovered, limited requests
13
+ *
14
+ * @example
15
+ * import { CircuitBreaker } from '@classytic/arc/utils';
16
+ *
17
+ * const paymentBreaker = new CircuitBreaker(async (amount) => {
18
+ * return await stripe.charges.create({ amount });
19
+ * }, {
20
+ * failureThreshold: 5,
21
+ * resetTimeout: 30000,
22
+ * timeout: 5000,
23
+ * });
24
+ *
25
+ * try {
26
+ * const result = await paymentBreaker.call(100);
27
+ * } catch (error) {
28
+ * // Handle failure or circuit open
29
+ * }
30
+ */
31
+ declare const CircuitState: {
32
+ readonly CLOSED: "CLOSED";
33
+ readonly OPEN: "OPEN";
34
+ readonly HALF_OPEN: "HALF_OPEN";
35
+ };
36
+ type CircuitState = (typeof CircuitState)[keyof typeof CircuitState];
37
+ interface CircuitBreakerOptions {
38
+ /**
39
+ * Number of failures before opening circuit
40
+ * @default 5
41
+ */
42
+ failureThreshold?: number;
43
+ /**
44
+ * Time in ms before attempting to close circuit
45
+ * @default 60000 (60 seconds)
46
+ */
47
+ resetTimeout?: number;
48
+ /**
49
+ * Request timeout in ms
50
+ * @default 10000 (10 seconds)
51
+ */
52
+ timeout?: number;
53
+ /**
54
+ * Number of successful requests in HALF_OPEN before closing
55
+ * @default 1
56
+ */
57
+ successThreshold?: number;
58
+ /**
59
+ * Fallback function when circuit is open.
60
+ * Receives the same arguments as the wrapped function.
61
+ */
62
+ fallback?: (...args: unknown[]) => Promise<unknown>;
63
+ /**
64
+ * Callback when state changes
65
+ */
66
+ onStateChange?: (from: CircuitState, to: CircuitState) => void;
67
+ /**
68
+ * Callback on error
69
+ */
70
+ onError?: (error: Error) => void;
71
+ /**
72
+ * Name for logging/monitoring
73
+ */
74
+ name?: string;
75
+ }
76
+ interface CircuitBreakerStats {
77
+ name?: string;
78
+ state: CircuitState;
79
+ failures: number;
80
+ successes: number;
81
+ totalCalls: number;
82
+ openedAt: number | null;
83
+ lastCallAt: number | null;
84
+ }
85
+ declare class CircuitBreakerError extends Error {
86
+ state: CircuitState;
87
+ constructor(message: string, state: CircuitState);
88
+ }
89
+ declare class CircuitBreaker<T extends (...args: any[]) => Promise<any>> {
90
+ private state;
91
+ private failures;
92
+ private successes;
93
+ private totalCalls;
94
+ private nextAttempt;
95
+ private lastCallAt;
96
+ private openedAt;
97
+ private readonly failureThreshold;
98
+ private readonly resetTimeout;
99
+ private readonly timeout;
100
+ private readonly successThreshold;
101
+ private readonly fallback?;
102
+ private readonly onStateChange?;
103
+ private readonly onError?;
104
+ private readonly name;
105
+ private readonly fn;
106
+ constructor(fn: T, options?: CircuitBreakerOptions);
107
+ /**
108
+ * Call the wrapped function with circuit breaker protection
109
+ */
110
+ call(...args: Parameters<T>): Promise<ReturnType<T>>;
111
+ /**
112
+ * Execute function with timeout
113
+ */
114
+ private executeWithTimeout;
115
+ /**
116
+ * Handle successful call
117
+ */
118
+ private onSuccess;
119
+ /**
120
+ * Handle failed call
121
+ */
122
+ private onFailure;
123
+ /**
124
+ * Change circuit state
125
+ */
126
+ private setState;
127
+ /**
128
+ * Manually open the circuit
129
+ */
130
+ open(): void;
131
+ /**
132
+ * Manually close the circuit
133
+ */
134
+ close(): void;
135
+ /**
136
+ * Get current statistics
137
+ */
138
+ getStats(): CircuitBreakerStats;
139
+ /**
140
+ * Get current state
141
+ */
142
+ getState(): CircuitState;
143
+ /**
144
+ * Check if circuit is open
145
+ */
146
+ isOpen(): boolean;
147
+ /**
148
+ * Check if circuit is closed
149
+ */
150
+ isClosed(): boolean;
151
+ /**
152
+ * Reset statistics
153
+ */
154
+ reset(): void;
155
+ }
156
+ /**
157
+ * Create a circuit breaker with sensible defaults
158
+ *
159
+ * @example
160
+ * const emailBreaker = createCircuitBreaker(
161
+ * async (to, subject, body) => sendEmail(to, subject, body),
162
+ * { name: 'email-service' }
163
+ * );
164
+ */
165
+ declare function createCircuitBreaker<T extends (...args: any[]) => Promise<any>>(fn: T, options?: CircuitBreakerOptions): CircuitBreaker<T>;
166
+ /**
167
+ * Circuit breaker registry for managing multiple breakers
168
+ */
169
+ declare class CircuitBreakerRegistry {
170
+ private breakers;
171
+ /**
172
+ * Register a circuit breaker
173
+ */
174
+ register<T extends (...args: any[]) => Promise<any>>(name: string, fn: T, options?: Omit<CircuitBreakerOptions, "name">): CircuitBreaker<T>;
175
+ /**
176
+ * Get a circuit breaker by name
177
+ */
178
+ get(name: string): CircuitBreaker<any> | undefined;
179
+ /**
180
+ * Get all breakers
181
+ */
182
+ getAll(): Map<string, CircuitBreaker<any>>;
183
+ /**
184
+ * Get statistics for all breakers
185
+ */
186
+ getAllStats(): Record<string, CircuitBreakerStats>;
187
+ /**
188
+ * Reset all breakers
189
+ */
190
+ resetAll(): void;
191
+ /**
192
+ * Open all breakers
193
+ */
194
+ openAll(): void;
195
+ /**
196
+ * Close all breakers
197
+ */
198
+ closeAll(): void;
199
+ }
200
+ /**
201
+ * Create a new CircuitBreakerRegistry instance.
202
+ * Use this instead of a global singleton — attach to fastify.arc or pass explicitly.
203
+ */
204
+ declare function createCircuitBreakerRegistry(): CircuitBreakerRegistry;
205
+ //#endregion
206
+ export { CircuitBreakerStats as a, createCircuitBreakerRegistry as c, CircuitBreakerRegistry as i, CircuitBreakerError as n, CircuitState as o, CircuitBreakerOptions as r, createCircuitBreaker as s, CircuitBreaker as t };
@@ -1,7 +1,6 @@
1
- import { t as CRUD_OPERATIONS } from "../../constants-DdXFXQtN.mjs";
1
+ import { t as CRUD_OPERATIONS } from "../../constants-Cxde4rpC.mjs";
2
2
  import { resolve } from "node:path";
3
3
  import { pathToFileURL } from "node:url";
4
-
5
4
  //#region src/cli/commands/describe.ts
6
5
  /**
7
6
  * Arc CLI - Describe Command
@@ -144,7 +143,8 @@ function describeEvents(resourceName, events) {
144
143
  return Object.entries(events).map(([action, def]) => ({
145
144
  name: `${resourceName}:${action}`,
146
145
  description: def.description,
147
- hasSchema: !!def.schema
146
+ hasSchema: !!def.schema,
147
+ hasHandler: !!def.handler
148
148
  }));
149
149
  }
150
150
  function describeMiddlewares(middlewares) {
@@ -179,7 +179,7 @@ async function describe(args) {
179
179
  try {
180
180
  const flags = new Set(args.filter((a) => a.startsWith("--")));
181
181
  const positional = args.filter((a) => !a.startsWith("--"));
182
- const pretty = flags.has("--pretty") || !flags.has("--json");
182
+ const pretty = flags.has("--pretty") || !flags.has("--json") && (process.stdout.isTTY ?? true);
183
183
  const filterResource = positional[1];
184
184
  const entryPath = positional[0];
185
185
  if (!entryPath) {
@@ -196,11 +196,18 @@ async function describe(args) {
196
196
  }
197
197
  const entryModule = await import(pathToFileURL(resolve(process.cwd(), entryPath)).href);
198
198
  const resources = [];
199
+ let eventRegistry;
199
200
  function tryCollect(value) {
200
201
  if (value && typeof value === "object" && "name" in value && "_registryMeta" in value && "toPlugin" in value) resources.push(value);
201
202
  }
203
+ function tryCollectRegistry(value) {
204
+ if (value && typeof value === "object" && "catalog" in value && "register" in value && "validate" in value && typeof value.catalog === "function") eventRegistry = value;
205
+ }
202
206
  for (const exported of Object.values(entryModule)) if (Array.isArray(exported)) exported.forEach(tryCollect);
203
- else tryCollect(exported);
207
+ else {
208
+ tryCollect(exported);
209
+ tryCollectRegistry(exported);
210
+ }
204
211
  if (resources.length === 0) throw new Error("No resource definitions found in entry file.\nMake sure your file exports defineResource() results:\n export const productResource = defineResource({ ... });");
205
212
  const filtered = filterResource ? resources.filter((r) => r.name === filterResource) : resources;
206
213
  if (filterResource && filtered.length === 0) throw new Error(`Resource '${filterResource}' not found.\nAvailable: ${resources.map((r) => r.name).join(", ")}`);
@@ -213,14 +220,25 @@ async function describe(args) {
213
220
  if (res.pipeline) totalPipelineSteps += res.pipeline.guards.length + res.pipeline.transforms.length + res.pipeline.interceptors.length;
214
221
  if (res.fields) totalFields += Object.keys(res.fields).length;
215
222
  }
223
+ let eventCatalog;
224
+ if (eventRegistry) eventCatalog = eventRegistry.catalog().map((e) => ({
225
+ name: e.name,
226
+ version: e.version,
227
+ description: e.description,
228
+ hasSchema: !!e.schema,
229
+ schemaFields: e.schema?.properties ? Object.keys(e.schema.properties) : [],
230
+ requiredFields: e.schema?.required ?? []
231
+ }));
216
232
  const output = {
217
233
  $schema: "arc-describe/v1",
218
234
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
219
235
  resources: described,
236
+ ...eventCatalog?.length ? { eventCatalog } : {},
220
237
  stats: {
221
238
  totalResources: described.length,
222
239
  totalRoutes: described.reduce((sum, r) => sum + r.routes.length, 0),
223
240
  totalEvents: described.reduce((sum, r) => sum + r.events.length, 0),
241
+ totalCatalogedEvents: eventCatalog?.length ?? 0,
224
242
  totalFields,
225
243
  presetUsage: presetCounts,
226
244
  pipelineSteps: totalPipelineSteps
@@ -233,6 +251,5 @@ async function describe(args) {
233
251
  throw new Error(String(error));
234
252
  }
235
253
  }
236
-
237
254
  //#endregion
238
- export { describe as default, describe };
255
+ export { describe as default, describe };