@classytic/arc 1.1.0 → 2.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. package/README.md +247 -794
  2. package/bin/arc.js +91 -52
  3. package/dist/EventTransport-BkUDYZEb.d.mts +99 -0
  4. package/dist/HookSystem-BsGV-j2l.mjs +404 -0
  5. package/dist/ResourceRegistry-7Ic20ZMw.mjs +249 -0
  6. package/dist/adapters/index.d.mts +5 -0
  7. package/dist/adapters/index.mjs +3 -0
  8. package/dist/audit/index.d.mts +81 -0
  9. package/dist/audit/index.mjs +275 -0
  10. package/dist/audit/mongodb.d.mts +5 -0
  11. package/dist/audit/mongodb.mjs +3 -0
  12. package/dist/audited-CGdLiSlE.mjs +140 -0
  13. package/dist/auth/index.d.mts +188 -0
  14. package/dist/auth/index.mjs +1096 -0
  15. package/dist/auth/redis-session.d.mts +43 -0
  16. package/dist/auth/redis-session.mjs +75 -0
  17. package/dist/betterAuthOpenApi-DjWDddNc.mjs +249 -0
  18. package/dist/cache/index.d.mts +145 -0
  19. package/dist/cache/index.mjs +91 -0
  20. package/dist/caching-GSDJcA6-.mjs +93 -0
  21. package/dist/chunk-C7Uep-_p.mjs +20 -0
  22. package/dist/circuitBreaker-DYhWBW_D.mjs +1096 -0
  23. package/dist/cli/commands/describe.d.mts +18 -0
  24. package/dist/cli/commands/describe.mjs +238 -0
  25. package/dist/cli/commands/docs.d.mts +13 -0
  26. package/dist/cli/commands/docs.mjs +52 -0
  27. package/dist/cli/commands/{generate.d.ts → generate.d.mts} +3 -2
  28. package/dist/cli/commands/generate.mjs +357 -0
  29. package/dist/cli/commands/{init.d.ts → init.d.mts} +11 -8
  30. package/dist/cli/commands/{init.js → init.mjs} +807 -617
  31. package/dist/cli/commands/introspect.d.mts +10 -0
  32. package/dist/cli/commands/introspect.mjs +75 -0
  33. package/dist/cli/index.d.mts +16 -0
  34. package/dist/cli/index.mjs +156 -0
  35. package/dist/constants-DdXFXQtN.mjs +84 -0
  36. package/dist/core/index.d.mts +5 -0
  37. package/dist/core/index.mjs +4 -0
  38. package/dist/createApp-D2D5XXaV.mjs +559 -0
  39. package/dist/defineResource-PXzSJ15_.mjs +2197 -0
  40. package/dist/discovery/index.d.mts +46 -0
  41. package/dist/discovery/index.mjs +109 -0
  42. package/dist/docs/index.d.mts +162 -0
  43. package/dist/docs/index.mjs +74 -0
  44. package/dist/elevation-DGo5shaX.d.mts +87 -0
  45. package/dist/elevation-DSTbVvYj.mjs +113 -0
  46. package/dist/errorHandler-C3GY3_ow.mjs +108 -0
  47. package/dist/errorHandler-CW3OOeYq.d.mts +72 -0
  48. package/dist/errors-DAWRdiYP.d.mts +124 -0
  49. package/dist/errors-DBANPbGr.mjs +211 -0
  50. package/dist/eventPlugin-BEOvaDqo.mjs +229 -0
  51. package/dist/eventPlugin-H6wDDjGO.d.mts +124 -0
  52. package/dist/events/index.d.mts +53 -0
  53. package/dist/events/index.mjs +51 -0
  54. package/dist/events/transports/redis-stream-entry.d.mts +2 -0
  55. package/dist/events/transports/redis-stream-entry.mjs +177 -0
  56. package/dist/events/transports/redis.d.mts +76 -0
  57. package/dist/events/transports/redis.mjs +124 -0
  58. package/dist/externalPaths-SyPF2tgK.d.mts +50 -0
  59. package/dist/factory/index.d.mts +63 -0
  60. package/dist/factory/index.mjs +3 -0
  61. package/dist/fastifyAdapter-C8DlE0YH.d.mts +216 -0
  62. package/dist/fields-Bi_AVKSo.d.mts +109 -0
  63. package/dist/fields-CTd_CrKr.mjs +114 -0
  64. package/dist/hooks/index.d.mts +4 -0
  65. package/dist/hooks/index.mjs +3 -0
  66. package/dist/idempotency/index.d.mts +96 -0
  67. package/dist/idempotency/index.mjs +319 -0
  68. package/dist/idempotency/mongodb.d.mts +2 -0
  69. package/dist/idempotency/mongodb.mjs +114 -0
  70. package/dist/idempotency/redis.d.mts +2 -0
  71. package/dist/idempotency/redis.mjs +103 -0
  72. package/dist/index.d.mts +260 -0
  73. package/dist/index.mjs +104 -0
  74. package/dist/integrations/event-gateway.d.mts +46 -0
  75. package/dist/integrations/event-gateway.mjs +43 -0
  76. package/dist/integrations/index.d.mts +5 -0
  77. package/dist/integrations/index.mjs +1 -0
  78. package/dist/integrations/jobs.d.mts +103 -0
  79. package/dist/integrations/jobs.mjs +123 -0
  80. package/dist/integrations/streamline.d.mts +60 -0
  81. package/dist/integrations/streamline.mjs +125 -0
  82. package/dist/integrations/websocket.d.mts +82 -0
  83. package/dist/integrations/websocket.mjs +288 -0
  84. package/dist/interface-CSNjltAc.d.mts +77 -0
  85. package/dist/interface-DTbsvIWe.d.mts +54 -0
  86. package/dist/interface-e9XfSsUV.d.mts +1097 -0
  87. package/dist/introspectionPlugin-B3JkrjwU.mjs +53 -0
  88. package/dist/keys-DhqDRxv3.mjs +42 -0
  89. package/dist/logger-ByrvQWZO.mjs +78 -0
  90. package/dist/memory-B2v7KrCB.mjs +143 -0
  91. package/dist/migrations/index.d.mts +156 -0
  92. package/dist/migrations/index.mjs +260 -0
  93. package/dist/mongodb-ClykrfGo.d.mts +118 -0
  94. package/dist/mongodb-DNKEExbf.mjs +93 -0
  95. package/dist/mongodb-Dg8O_gvd.d.mts +71 -0
  96. package/dist/openapi-9nB_kiuR.mjs +525 -0
  97. package/dist/org/index.d.mts +68 -0
  98. package/dist/org/index.mjs +513 -0
  99. package/dist/org/types.d.mts +82 -0
  100. package/dist/org/types.mjs +1 -0
  101. package/dist/permissions/index.d.mts +278 -0
  102. package/dist/permissions/index.mjs +579 -0
  103. package/dist/plugins/index.d.mts +172 -0
  104. package/dist/plugins/index.mjs +522 -0
  105. package/dist/plugins/response-cache.d.mts +87 -0
  106. package/dist/plugins/response-cache.mjs +283 -0
  107. package/dist/plugins/tracing-entry.d.mts +2 -0
  108. package/dist/plugins/tracing-entry.mjs +185 -0
  109. package/dist/pluralize-CM-jZg7p.mjs +86 -0
  110. package/dist/policies/{index.d.ts → index.d.mts} +204 -170
  111. package/dist/policies/index.mjs +321 -0
  112. package/dist/presets/{index.d.ts → index.d.mts} +62 -131
  113. package/dist/presets/index.mjs +143 -0
  114. package/dist/presets/multiTenant.d.mts +24 -0
  115. package/dist/presets/multiTenant.mjs +113 -0
  116. package/dist/presets-BTeYbw7h.d.mts +57 -0
  117. package/dist/presets-CeFtfDR8.mjs +119 -0
  118. package/dist/prisma-C3iornoK.d.mts +274 -0
  119. package/dist/prisma-DJbMt3yf.mjs +627 -0
  120. package/dist/queryCachePlugin-B6R0d4av.mjs +138 -0
  121. package/dist/queryCachePlugin-Q6SYuHZ6.d.mts +71 -0
  122. package/dist/redis-UwjEp8Ea.d.mts +49 -0
  123. package/dist/redis-stream-CBg0upHI.d.mts +103 -0
  124. package/dist/registry/index.d.mts +11 -0
  125. package/dist/registry/index.mjs +4 -0
  126. package/dist/requestContext-xi6OKBL-.mjs +55 -0
  127. package/dist/schemaConverter-Dtg0Kt9T.mjs +98 -0
  128. package/dist/schemas/index.d.mts +63 -0
  129. package/dist/schemas/index.mjs +82 -0
  130. package/dist/scope/index.d.mts +21 -0
  131. package/dist/scope/index.mjs +65 -0
  132. package/dist/sessionManager-D_iEHjQl.d.mts +186 -0
  133. package/dist/sse-DkqQ1uxb.mjs +123 -0
  134. package/dist/testing/index.d.mts +907 -0
  135. package/dist/testing/index.mjs +1976 -0
  136. package/dist/tracing-8CEbhF0w.d.mts +70 -0
  137. package/dist/typeGuards-DwxA1t_L.mjs +9 -0
  138. package/dist/types/index.d.mts +946 -0
  139. package/dist/types/index.mjs +14 -0
  140. package/dist/types-B0dhNrnd.d.mts +445 -0
  141. package/dist/types-Beqn1Un7.mjs +38 -0
  142. package/dist/types-DelU6kln.mjs +25 -0
  143. package/dist/types-RLkFVgaw.d.mts +101 -0
  144. package/dist/utils/index.d.mts +747 -0
  145. package/dist/utils/index.mjs +6 -0
  146. package/package.json +194 -68
  147. package/dist/BaseController-DVAiHxEQ.d.ts +0 -233
  148. package/dist/adapters/index.d.ts +0 -237
  149. package/dist/adapters/index.js +0 -668
  150. package/dist/arcCorePlugin-CsShQdyP.d.ts +0 -273
  151. package/dist/audit/index.d.ts +0 -195
  152. package/dist/audit/index.js +0 -319
  153. package/dist/auth/index.d.ts +0 -47
  154. package/dist/auth/index.js +0 -174
  155. package/dist/cli/commands/docs.d.ts +0 -11
  156. package/dist/cli/commands/docs.js +0 -474
  157. package/dist/cli/commands/generate.js +0 -334
  158. package/dist/cli/commands/introspect.d.ts +0 -8
  159. package/dist/cli/commands/introspect.js +0 -338
  160. package/dist/cli/index.d.ts +0 -4
  161. package/dist/cli/index.js +0 -3269
  162. package/dist/core/index.d.ts +0 -220
  163. package/dist/core/index.js +0 -2786
  164. package/dist/createApp-Ce9wl8W9.d.ts +0 -77
  165. package/dist/docs/index.d.ts +0 -166
  166. package/dist/docs/index.js +0 -658
  167. package/dist/errors-8WIxGS_6.d.ts +0 -122
  168. package/dist/events/index.d.ts +0 -117
  169. package/dist/events/index.js +0 -89
  170. package/dist/factory/index.d.ts +0 -38
  171. package/dist/factory/index.js +0 -1652
  172. package/dist/hooks/index.d.ts +0 -4
  173. package/dist/hooks/index.js +0 -199
  174. package/dist/idempotency/index.d.ts +0 -323
  175. package/dist/idempotency/index.js +0 -500
  176. package/dist/index-B4t03KQ0.d.ts +0 -1366
  177. package/dist/index.d.ts +0 -135
  178. package/dist/index.js +0 -4756
  179. package/dist/migrations/index.d.ts +0 -185
  180. package/dist/migrations/index.js +0 -274
  181. package/dist/org/index.d.ts +0 -129
  182. package/dist/org/index.js +0 -220
  183. package/dist/permissions/index.d.ts +0 -144
  184. package/dist/permissions/index.js +0 -103
  185. package/dist/plugins/index.d.ts +0 -46
  186. package/dist/plugins/index.js +0 -1069
  187. package/dist/policies/index.js +0 -196
  188. package/dist/presets/index.js +0 -384
  189. package/dist/presets/multiTenant.d.ts +0 -39
  190. package/dist/presets/multiTenant.js +0 -112
  191. package/dist/registry/index.d.ts +0 -16
  192. package/dist/registry/index.js +0 -253
  193. package/dist/testing/index.d.ts +0 -618
  194. package/dist/testing/index.js +0 -48020
  195. package/dist/types/index.d.ts +0 -4
  196. package/dist/types/index.js +0 -8
  197. package/dist/types-B99TBmFV.d.ts +0 -76
  198. package/dist/types-BvckRbs2.d.ts +0 -143
  199. package/dist/utils/index.d.ts +0 -679
  200. package/dist/utils/index.js +0 -931
@@ -0,0 +1,229 @@
1
+ import { t as __exportAll } from "./chunk-C7Uep-_p.mjs";
2
+ import { t as requestContext } from "./requestContext-xi6OKBL-.mjs";
3
+ import fp from "fastify-plugin";
4
+
5
+ //#region src/events/EventTransport.ts
6
+ /**
7
+ * In-memory event transport (default)
8
+ * Events are delivered synchronously within the process.
9
+ * Not suitable for multi-instance deployments.
10
+ */
11
+ var MemoryEventTransport = class {
12
+ name = "memory";
13
+ handlers = /* @__PURE__ */ new Map();
14
+ logger;
15
+ constructor(options) {
16
+ this.logger = options?.logger ?? console;
17
+ }
18
+ async publish(event) {
19
+ const exactHandlers = this.handlers.get(event.type) ?? /* @__PURE__ */ new Set();
20
+ const wildcardHandlers = this.handlers.get("*") ?? /* @__PURE__ */ new Set();
21
+ const patternHandlers = /* @__PURE__ */ new Set();
22
+ for (const [pattern, handlers] of this.handlers.entries()) if (pattern.endsWith(".*")) {
23
+ const prefix = pattern.slice(0, -2);
24
+ if (event.type.startsWith(prefix + ".")) handlers.forEach((h) => patternHandlers.add(h));
25
+ }
26
+ const allHandlers = new Set([
27
+ ...exactHandlers,
28
+ ...wildcardHandlers,
29
+ ...patternHandlers
30
+ ]);
31
+ for (const handler of allHandlers) try {
32
+ await handler(event);
33
+ } catch (err) {
34
+ this.logger.error(`[EventTransport] Handler error for ${event.type}:`, err);
35
+ }
36
+ }
37
+ async subscribe(pattern, handler) {
38
+ if (!this.handlers.has(pattern)) this.handlers.set(pattern, /* @__PURE__ */ new Set());
39
+ this.handlers.get(pattern).add(handler);
40
+ return () => {
41
+ this.handlers.get(pattern)?.delete(handler);
42
+ };
43
+ }
44
+ async close() {
45
+ this.handlers.clear();
46
+ }
47
+ };
48
+ /**
49
+ * Create a domain event with auto-generated metadata
50
+ */
51
+ function createEvent(type, payload, meta) {
52
+ return {
53
+ type,
54
+ payload,
55
+ meta: {
56
+ id: crypto.randomUUID(),
57
+ timestamp: /* @__PURE__ */ new Date(),
58
+ ...meta
59
+ }
60
+ };
61
+ }
62
+
63
+ //#endregion
64
+ //#region src/events/retry.ts
65
+ /**
66
+ * Wrap an event handler with retry logic and dead letter support.
67
+ *
68
+ * On failure, retries with exponential backoff (with jitter).
69
+ * After all retries exhausted, calls `onDead` callback if provided.
70
+ */
71
+ function withRetry(handler, options = {}) {
72
+ const { maxRetries = 3, backoffMs = 1e3, maxBackoffMs = 3e4, jitter = .1, onDead, name, logger = console } = options;
73
+ const label = name ?? handler.name ?? "anonymous";
74
+ return async (event) => {
75
+ const errors = [];
76
+ for (let attempt = 0; attempt <= maxRetries; attempt++) try {
77
+ await handler(event);
78
+ return;
79
+ } catch (err) {
80
+ const error = err instanceof Error ? err : new Error(String(err));
81
+ errors.push(error);
82
+ if (attempt < maxRetries) {
83
+ const baseDelay = Math.min(backoffMs * 2 ** attempt, maxBackoffMs);
84
+ const delay = baseDelay + jitter * baseDelay * Math.random();
85
+ logger.warn(`[Arc Events] Handler '${label}' failed for ${event.type} (attempt ${attempt + 1}/${maxRetries + 1}), retrying in ${Math.round(delay)}ms: ${error.message}`);
86
+ await sleep(delay);
87
+ }
88
+ }
89
+ logger.error(`[Arc Events] Handler '${label}' permanently failed for ${event.type} after ${maxRetries + 1} attempts. ${errors.length} errors.`);
90
+ if (onDead) try {
91
+ await onDead(event, errors);
92
+ } catch (dlqErr) {
93
+ logger.error("[Arc Events] Dead letter callback failed:", dlqErr);
94
+ }
95
+ };
96
+ }
97
+ /**
98
+ * Create a dead letter publisher that sends failed events to a `$deadLetter` channel.
99
+ *
100
+ * @example
101
+ * ```typescript
102
+ * import { withRetry, createDeadLetterPublisher } from '@classytic/arc/events';
103
+ *
104
+ * const toDlq = createDeadLetterPublisher(fastify.events);
105
+ *
106
+ * await fastify.events.subscribe('order.created', withRetry(handler, {
107
+ * maxRetries: 3,
108
+ * onDead: toDlq,
109
+ * }));
110
+ *
111
+ * // Monitor dead letters
112
+ * await fastify.events.subscribe('$deadLetter', async (event) => {
113
+ * console.error('Dead letter:', event.payload);
114
+ * await alertOps(event.payload);
115
+ * });
116
+ * ```
117
+ */
118
+ function createDeadLetterPublisher(events) {
119
+ return async (event, errors) => {
120
+ await events.publish("$deadLetter", {
121
+ originalEvent: event,
122
+ errors: errors.map((e) => ({
123
+ message: e.message,
124
+ stack: e.stack
125
+ })),
126
+ failedAt: (/* @__PURE__ */ new Date()).toISOString()
127
+ });
128
+ };
129
+ }
130
+ function sleep(ms) {
131
+ return new Promise((resolve) => setTimeout(resolve, ms));
132
+ }
133
+
134
+ //#endregion
135
+ //#region src/events/eventPlugin.ts
136
+ /**
137
+ * Event Plugin
138
+ *
139
+ * Integrates event transport with Fastify.
140
+ * Defaults to in-memory transport; configure durable transport for production.
141
+ *
142
+ * @example
143
+ * // Development (in-memory)
144
+ * await fastify.register(eventPlugin);
145
+ *
146
+ * // Production (Redis)
147
+ * await fastify.register(eventPlugin, {
148
+ * transport: new RedisEventTransport({ url: process.env.REDIS_URL }),
149
+ * });
150
+ */
151
+ var eventPlugin_exports = /* @__PURE__ */ __exportAll({
152
+ default: () => eventPlugin_default,
153
+ eventPlugin: () => eventPlugin
154
+ });
155
+ const eventPlugin = async (fastify, opts = {}) => {
156
+ const { transport = new MemoryEventTransport(), logEvents = false, failOpen = true, retry: retryOpts, deadLetterQueue: dlqOpts, wal, onPublish, onPublishError } = opts;
157
+ fastify.decorate("events", {
158
+ publish: async (type, payload, meta) => {
159
+ const store = requestContext.get();
160
+ const event = createEvent(type, payload, {
161
+ ...store?.requestId && !meta?.correlationId ? { correlationId: store.requestId } : {},
162
+ ...meta
163
+ });
164
+ if (logEvents) fastify.log?.info?.({
165
+ eventType: type,
166
+ eventId: event.meta.id,
167
+ correlationId: event.meta.correlationId
168
+ }, "Publishing event");
169
+ try {
170
+ if (wal) await wal.save(event);
171
+ await transport.publish(event);
172
+ if (wal?.acknowledge) await wal.acknowledge(event.meta.id);
173
+ onPublish?.(event);
174
+ } catch (error) {
175
+ fastify.log?.error?.({
176
+ transport: transport.name,
177
+ eventType: type,
178
+ error
179
+ }, "[Arc Events] Failed to publish event");
180
+ onPublishError?.(event, error);
181
+ if (!failOpen) throw error;
182
+ }
183
+ },
184
+ subscribe: async (pattern, handler) => {
185
+ let wrappedHandler = handler;
186
+ if (retryOpts && pattern !== "$deadLetter") wrappedHandler = withRetry(handler, {
187
+ ...retryOpts,
188
+ onDead: dlqOpts?.store ?? createDeadLetterPublisher(fastify.events),
189
+ logger: fastify.log
190
+ });
191
+ if (logEvents) fastify.log?.info?.({
192
+ pattern,
193
+ retry: !!retryOpts
194
+ }, "Subscribing to events");
195
+ try {
196
+ return await transport.subscribe(pattern, wrappedHandler);
197
+ } catch (error) {
198
+ fastify.log?.error?.({
199
+ transport: transport.name,
200
+ pattern,
201
+ error
202
+ }, "[Arc Events] Failed to subscribe to events");
203
+ if (!failOpen) throw error;
204
+ return () => {};
205
+ }
206
+ },
207
+ transportName: transport.name
208
+ });
209
+ fastify.addHook("onClose", async () => {
210
+ try {
211
+ await transport.close?.();
212
+ } catch (error) {
213
+ fastify.log?.warn?.({
214
+ transport: transport.name,
215
+ error
216
+ }, "[Arc Events] Transport close failed");
217
+ if (!failOpen) throw error;
218
+ }
219
+ });
220
+ if (transport.name === "memory") fastify.log?.warn?.("[Arc Events] Using in-memory transport. Events will not persist or scale across instances. For production, configure a durable transport (Redis, RabbitMQ, etc.)");
221
+ else fastify.log?.debug?.(`[Arc Events] Using ${transport.name} transport`);
222
+ };
223
+ var eventPlugin_default = fp(eventPlugin, {
224
+ name: "arc-events",
225
+ fastify: "5.x"
226
+ });
227
+
228
+ //#endregion
229
+ export { MemoryEventTransport as a, withRetry as i, eventPlugin_exports as n, createEvent as o, createDeadLetterPublisher as r, eventPlugin as t };
@@ -0,0 +1,124 @@
1
+ import { i as EventTransport, n as EventHandler, r as EventLogger, t as DomainEvent } from "./EventTransport-BkUDYZEb.mjs";
2
+ import { FastifyPluginAsync } from "fastify";
3
+
4
+ //#region src/events/retry.d.ts
5
+ interface RetryOptions {
6
+ /**
7
+ * Max retry attempts (not counting the initial attempt).
8
+ * @default 3
9
+ */
10
+ maxRetries?: number;
11
+ /**
12
+ * Initial backoff delay in ms. Doubles on each retry (exponential backoff).
13
+ * @default 1000
14
+ */
15
+ backoffMs?: number;
16
+ /**
17
+ * Maximum backoff delay in ms (caps exponential growth).
18
+ * @default 30000
19
+ */
20
+ maxBackoffMs?: number;
21
+ /**
22
+ * Jitter factor (0-1). Adds randomness to prevent thundering herd.
23
+ * 0 = no jitter, 1 = full jitter (delay ∈ [0, calculated]).
24
+ * @default 0.1
25
+ */
26
+ jitter?: number;
27
+ /**
28
+ * Callback when all retries are exhausted. The event is "dead".
29
+ * Use this to publish to a `$deadLetter` channel, log, alert, etc.
30
+ */
31
+ onDead?: (event: DomainEvent, errors: Error[]) => void | Promise<void>;
32
+ /**
33
+ * Optional name for logging/debugging.
34
+ */
35
+ name?: string;
36
+ /**
37
+ * Logger for retry warnings and error messages (default: console).
38
+ * Pass `fastify.log` to integrate with your application logger.
39
+ */
40
+ logger?: EventLogger;
41
+ }
42
+ /**
43
+ * Wrap an event handler with retry logic and dead letter support.
44
+ *
45
+ * On failure, retries with exponential backoff (with jitter).
46
+ * After all retries exhausted, calls `onDead` callback if provided.
47
+ */
48
+ declare function withRetry(handler: EventHandler, options?: RetryOptions): EventHandler;
49
+ /**
50
+ * Create a dead letter publisher that sends failed events to a `$deadLetter` channel.
51
+ *
52
+ * @example
53
+ * ```typescript
54
+ * import { withRetry, createDeadLetterPublisher } from '@classytic/arc/events';
55
+ *
56
+ * const toDlq = createDeadLetterPublisher(fastify.events);
57
+ *
58
+ * await fastify.events.subscribe('order.created', withRetry(handler, {
59
+ * maxRetries: 3,
60
+ * onDead: toDlq,
61
+ * }));
62
+ *
63
+ * // Monitor dead letters
64
+ * await fastify.events.subscribe('$deadLetter', async (event) => {
65
+ * console.error('Dead letter:', event.payload);
66
+ * await alertOps(event.payload);
67
+ * });
68
+ * ```
69
+ */
70
+ declare function createDeadLetterPublisher(events: {
71
+ publish: <T>(type: string, payload: T, meta?: Record<string, unknown>) => Promise<void>;
72
+ }): (event: DomainEvent, errors: Error[]) => Promise<void>;
73
+ //#endregion
74
+ //#region src/events/eventPlugin.d.ts
75
+ interface EventPluginOptions {
76
+ /** Event transport (default: MemoryEventTransport) */
77
+ transport?: EventTransport;
78
+ /** Enable event logging (default: false) */
79
+ logEvents?: boolean;
80
+ /**
81
+ * Fail-open mode for runtime resilience (default: true).
82
+ * - true: publish/subscribe/close errors are logged and suppressed
83
+ * - false: errors are thrown to caller
84
+ */
85
+ failOpen?: boolean;
86
+ /**
87
+ * Write-Ahead Log (WAL) configuration for at-least-once delivery guarantees.
88
+ * If provided, events will be saved to the WAL *before* passing to the transport.
89
+ * After a successful publish, they are acknowledged.
90
+ */
91
+ wal?: {
92
+ save: (event: DomainEvent) => Promise<void>;
93
+ acknowledge?: (eventId: string) => Promise<void>;
94
+ };
95
+ /**
96
+ * Auto-wrap all subscribed handlers with retry logic.
97
+ * When enabled, failed handler invocations are retried with exponential backoff.
98
+ */
99
+ retry?: Pick<RetryOptions, "maxRetries" | "backoffMs" | "maxBackoffMs" | "jitter">;
100
+ /**
101
+ * Dead letter queue for events that exhaust all retries.
102
+ * Requires `retry` to be enabled. If `retry` is set but no custom `store`,
103
+ * failed events are published to the `$deadLetter` event type by default.
104
+ */
105
+ deadLetterQueue?: {
106
+ /** Custom store function. If omitted, publishes to '$deadLetter' event type. */store?: (event: DomainEvent, errors: Error[]) => void | Promise<void>;
107
+ };
108
+ /** Callback after successful publish (for metrics/tracking) */
109
+ onPublish?: (event: DomainEvent) => void;
110
+ /** Callback on publish failure (for metrics/alerting) */
111
+ onPublishError?: (event: DomainEvent, error: Error) => void;
112
+ }
113
+ declare module "fastify" {
114
+ interface FastifyInstance {
115
+ events: {
116
+ /** Publish an event */publish: <T>(type: string, payload: T, meta?: Partial<DomainEvent["meta"]>) => Promise<void>; /** Subscribe to events */
117
+ subscribe: (pattern: string, handler: EventHandler) => Promise<() => void>; /** Get transport name */
118
+ transportName: string;
119
+ };
120
+ }
121
+ }
122
+ declare const eventPlugin: FastifyPluginAsync<EventPluginOptions>;
123
+ //#endregion
124
+ export { withRetry as a, createDeadLetterPublisher as i, eventPlugin as n, RetryOptions as r, EventPluginOptions as t };
@@ -0,0 +1,53 @@
1
+ import { a as MemoryEventTransport, i as EventTransport, n as EventHandler, o as MemoryEventTransportOptions, r as EventLogger, s as createEvent, t as DomainEvent } from "../EventTransport-BkUDYZEb.mjs";
2
+ import { a as withRetry, i as createDeadLetterPublisher, n as eventPlugin, r as RetryOptions, t as EventPluginOptions } from "../eventPlugin-H6wDDjGO.mjs";
3
+ import { RedisEventTransportOptions, RedisLike } from "./transports/redis.mjs";
4
+ import { r as RedisStreamTransportOptions, t as RedisStreamLike } from "../redis-stream-CBg0upHI.mjs";
5
+
6
+ //#region src/events/eventTypes.d.ts
7
+ /**
8
+ * Event Type Constants and Helpers
9
+ *
10
+ * Provides well-typed event names for CRUD operations and lifecycle events.
11
+ * Use these instead of hand-typing event strings to prevent typos.
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * import { crudEventType, ARC_LIFECYCLE_EVENTS } from '@classytic/arc/events';
16
+ *
17
+ * // Subscribe to product creation events
18
+ * events.subscribe(crudEventType('product', 'created'), handler);
19
+ *
20
+ * // Subscribe to Arc lifecycle events
21
+ * events.subscribe(ARC_LIFECYCLE_EVENTS.READY, handler);
22
+ * ```
23
+ */
24
+ /** Suffixes for auto-emitted CRUD events */
25
+ declare const CRUD_EVENT_SUFFIXES: readonly ["created", "updated", "deleted"];
26
+ /** Type for CRUD event suffixes */
27
+ type CrudEventSuffix = typeof CRUD_EVENT_SUFFIXES[number];
28
+ /**
29
+ * Build a CRUD event type string.
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * crudEventType('product', 'created') // 'product.created'
34
+ * crudEventType('order', 'deleted') // 'order.deleted'
35
+ * ```
36
+ */
37
+ declare function crudEventType(resource: string, suffix: CrudEventSuffix): string;
38
+ /** Arc framework lifecycle events — emitted automatically by the framework */
39
+ declare const ARC_LIFECYCLE_EVENTS: Readonly<{
40
+ /** Emitted when a resource plugin is registered */readonly RESOURCE_REGISTERED: "arc.resource.registered"; /** Emitted when Arc is fully ready (all resources registered, onReady fired) */
41
+ readonly READY: "arc.ready";
42
+ }>;
43
+ /** Type for Arc lifecycle event names */
44
+ type ArcLifecycleEvent = typeof ARC_LIFECYCLE_EVENTS[keyof typeof ARC_LIFECYCLE_EVENTS];
45
+ /** Cache-specific event types for observability and external triggers */
46
+ declare const CACHE_EVENTS: Readonly<{
47
+ /** Emitted when a resource's cache version is bumped */readonly VERSION_BUMPED: "arc.cache.version.bumped"; /** Emitted when a tag version is bumped */
48
+ readonly TAG_VERSION_BUMPED: "arc.cache.tag.bumped";
49
+ }>;
50
+ /** Type for cache event names */
51
+ type CacheEvent = typeof CACHE_EVENTS[keyof typeof CACHE_EVENTS];
52
+ //#endregion
53
+ export { ARC_LIFECYCLE_EVENTS, type ArcLifecycleEvent, CACHE_EVENTS, CRUD_EVENT_SUFFIXES, type CacheEvent, type CrudEventSuffix, type DomainEvent, type EventHandler, type EventLogger, type EventPluginOptions, type EventTransport, MemoryEventTransport, type MemoryEventTransportOptions, type RedisEventTransportOptions, type RedisLike, type RedisStreamLike, type RedisStreamTransportOptions, type RetryOptions, createDeadLetterPublisher, createEvent, crudEventType, eventPlugin, withRetry };
@@ -0,0 +1,51 @@
1
+ import { a as MemoryEventTransport, i as withRetry, o as createEvent, r as createDeadLetterPublisher, t as eventPlugin } from "../eventPlugin-BEOvaDqo.mjs";
2
+
3
+ //#region src/events/eventTypes.ts
4
+ /**
5
+ * Event Type Constants and Helpers
6
+ *
7
+ * Provides well-typed event names for CRUD operations and lifecycle events.
8
+ * Use these instead of hand-typing event strings to prevent typos.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { crudEventType, ARC_LIFECYCLE_EVENTS } from '@classytic/arc/events';
13
+ *
14
+ * // Subscribe to product creation events
15
+ * events.subscribe(crudEventType('product', 'created'), handler);
16
+ *
17
+ * // Subscribe to Arc lifecycle events
18
+ * events.subscribe(ARC_LIFECYCLE_EVENTS.READY, handler);
19
+ * ```
20
+ */
21
+ /** Suffixes for auto-emitted CRUD events */
22
+ const CRUD_EVENT_SUFFIXES = Object.freeze([
23
+ "created",
24
+ "updated",
25
+ "deleted"
26
+ ]);
27
+ /**
28
+ * Build a CRUD event type string.
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * crudEventType('product', 'created') // 'product.created'
33
+ * crudEventType('order', 'deleted') // 'order.deleted'
34
+ * ```
35
+ */
36
+ function crudEventType(resource, suffix) {
37
+ return `${resource}.${suffix}`;
38
+ }
39
+ /** Arc framework lifecycle events — emitted automatically by the framework */
40
+ const ARC_LIFECYCLE_EVENTS = Object.freeze({
41
+ RESOURCE_REGISTERED: "arc.resource.registered",
42
+ READY: "arc.ready"
43
+ });
44
+ /** Cache-specific event types for observability and external triggers */
45
+ const CACHE_EVENTS = Object.freeze({
46
+ VERSION_BUMPED: "arc.cache.version.bumped",
47
+ TAG_VERSION_BUMPED: "arc.cache.tag.bumped"
48
+ });
49
+
50
+ //#endregion
51
+ export { ARC_LIFECYCLE_EVENTS, CACHE_EVENTS, CRUD_EVENT_SUFFIXES, MemoryEventTransport, createDeadLetterPublisher, createEvent, crudEventType, eventPlugin, withRetry };
@@ -0,0 +1,2 @@
1
+ import { n as RedisStreamTransport, r as RedisStreamTransportOptions, t as RedisStreamLike } from "../../redis-stream-CBg0upHI.mjs";
2
+ export { type RedisStreamLike, RedisStreamTransport, type RedisStreamTransportOptions };
@@ -0,0 +1,177 @@
1
+ //#region src/events/transports/redis-stream.ts
2
+ var RedisStreamTransport = class {
3
+ name = "redis-stream";
4
+ redis;
5
+ stream;
6
+ group;
7
+ consumer;
8
+ blockTimeMs;
9
+ batchSize;
10
+ maxRetries;
11
+ claimTimeoutMs;
12
+ deadLetterStream;
13
+ maxLen;
14
+ logger;
15
+ handlers = /* @__PURE__ */ new Map();
16
+ running = false;
17
+ pollPromise = null;
18
+ groupCreated = false;
19
+ constructor(redis, options = {}) {
20
+ this.redis = redis;
21
+ this.stream = options.stream ?? "arc:events";
22
+ this.group = options.group ?? "default";
23
+ this.consumer = options.consumer ?? `consumer-${crypto.randomUUID().slice(0, 8)}`;
24
+ this.blockTimeMs = options.blockTimeMs ?? 5e3;
25
+ this.batchSize = options.batchSize ?? 10;
26
+ this.maxRetries = options.maxRetries ?? 5;
27
+ this.claimTimeoutMs = options.claimTimeoutMs ?? 3e4;
28
+ this.deadLetterStream = options.deadLetterStream ?? "arc:events:dlq";
29
+ this.maxLen = options.maxLen ?? 1e4;
30
+ this.logger = options.logger ?? console;
31
+ }
32
+ async publish(event) {
33
+ const args = [
34
+ this.stream,
35
+ ...this.maxLen > 0 ? [
36
+ "MAXLEN",
37
+ "~",
38
+ String(this.maxLen)
39
+ ] : [],
40
+ "*",
41
+ "type",
42
+ event.type,
43
+ "data",
44
+ JSON.stringify(event)
45
+ ];
46
+ await this.redis.xadd(...args);
47
+ }
48
+ async subscribe(pattern, handler) {
49
+ if (!this.handlers.has(pattern)) this.handlers.set(pattern, /* @__PURE__ */ new Set());
50
+ this.handlers.get(pattern).add(handler);
51
+ if (!this.running) {
52
+ await this.ensureGroup();
53
+ this.running = true;
54
+ this.pollPromise = this.pollLoop();
55
+ }
56
+ return () => {
57
+ const set = this.handlers.get(pattern);
58
+ if (set) {
59
+ set.delete(handler);
60
+ if (set.size === 0) this.handlers.delete(pattern);
61
+ }
62
+ if (this.handlers.size === 0 && this.running) this.running = false;
63
+ };
64
+ }
65
+ async close() {
66
+ this.running = false;
67
+ this.handlers.clear();
68
+ if (this.pollPromise) {
69
+ await this.pollPromise;
70
+ this.pollPromise = null;
71
+ }
72
+ }
73
+ async ensureGroup() {
74
+ if (this.groupCreated) return;
75
+ try {
76
+ await this.redis.xgroup("CREATE", this.stream, this.group, "$", "MKSTREAM");
77
+ } catch (err) {
78
+ if (err instanceof Error && !err.message.includes("BUSYGROUP")) throw err;
79
+ }
80
+ this.groupCreated = true;
81
+ }
82
+ async pollLoop() {
83
+ while (this.running) try {
84
+ await this.claimPending();
85
+ await this.readNewMessages();
86
+ } catch (err) {
87
+ if (this.running) {
88
+ this.logger.error("[RedisStreamTransport] Poll error:", err);
89
+ await this.sleep(1e3);
90
+ }
91
+ }
92
+ }
93
+ async readNewMessages() {
94
+ const result = await this.redis.xreadgroup("GROUP", this.group, this.consumer, "COUNT", this.batchSize, "BLOCK", this.blockTimeMs, "STREAMS", this.stream, ">");
95
+ if (!result) return;
96
+ for (const [, entries] of result) for (const [messageId, fields] of entries) await this.processEntry(messageId, fields);
97
+ }
98
+ async claimPending() {
99
+ try {
100
+ const pending = await this.redis.xpending(this.stream, this.group, "-", "+", 10);
101
+ if (!pending || pending.length === 0) return;
102
+ const staleIds = [];
103
+ const overRetryIds = [];
104
+ for (const entry of pending) {
105
+ const [id, , idleTime, deliveryCount] = entry;
106
+ if (idleTime > this.claimTimeoutMs) if (deliveryCount >= this.maxRetries) overRetryIds.push(id);
107
+ else staleIds.push(id);
108
+ }
109
+ if (overRetryIds.length > 0) await this.moveToDlq(overRetryIds);
110
+ if (staleIds.length > 0) {
111
+ const claimed = await this.redis.xclaim(this.stream, this.group, this.consumer, this.claimTimeoutMs, ...staleIds);
112
+ for (const [messageId, fields] of claimed) await this.processEntry(messageId, fields);
113
+ }
114
+ } catch {}
115
+ }
116
+ async processEntry(messageId, fields) {
117
+ const fieldMap = /* @__PURE__ */ new Map();
118
+ for (let i = 0; i < fields.length; i += 2) fieldMap.set(fields[i], fields[i + 1]);
119
+ const eventType = fieldMap.get("type");
120
+ const rawData = fieldMap.get("data");
121
+ if (!eventType || !rawData) {
122
+ await this.redis.xack(this.stream, this.group, messageId);
123
+ return;
124
+ }
125
+ let event;
126
+ try {
127
+ event = JSON.parse(rawData, (key, value) => {
128
+ if (key === "timestamp" && typeof value === "string") return new Date(value);
129
+ return value;
130
+ });
131
+ } catch {
132
+ await this.redis.xack(this.stream, this.group, messageId);
133
+ return;
134
+ }
135
+ const matchingHandlers = this.getMatchingHandlers(event.type);
136
+ let allSucceeded = true;
137
+ for (const handler of matchingHandlers) try {
138
+ await handler(event);
139
+ } catch (err) {
140
+ allSucceeded = false;
141
+ this.logger.error(`[RedisStreamTransport] Handler error for ${event.type}:`, err);
142
+ }
143
+ if (allSucceeded) await this.redis.xack(this.stream, this.group, messageId);
144
+ }
145
+ getMatchingHandlers(eventType) {
146
+ const matched = [];
147
+ for (const [pattern, handlers] of this.handlers) if (this.matchesPattern(pattern, eventType)) for (const h of handlers) matched.push(h);
148
+ return matched;
149
+ }
150
+ matchesPattern(pattern, eventType) {
151
+ if (pattern === "*") return true;
152
+ if (pattern === eventType) return true;
153
+ if (pattern.endsWith(".*")) {
154
+ const prefix = pattern.slice(0, -2);
155
+ return eventType.startsWith(prefix + ".");
156
+ }
157
+ return false;
158
+ }
159
+ async moveToDlq(ids) {
160
+ if (this.deadLetterStream === false) {
161
+ for (const id of ids) await this.redis.xack(this.stream, this.group, id);
162
+ return;
163
+ }
164
+ for (const id of ids) try {
165
+ await this.redis.xadd(this.deadLetterStream, "*", "originalStream", this.stream, "originalId", id, "group", this.group, "failedAt", (/* @__PURE__ */ new Date()).toISOString());
166
+ await this.redis.xack(this.stream, this.group, id);
167
+ } catch (err) {
168
+ this.logger.error(`[RedisStreamTransport] DLQ write failed for ${id}:`, err);
169
+ }
170
+ }
171
+ sleep(ms) {
172
+ return new Promise((resolve) => setTimeout(resolve, ms));
173
+ }
174
+ };
175
+
176
+ //#endregion
177
+ export { RedisStreamTransport };