@classytic/arc 2.10.8 → 2.11.0

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 (136) hide show
  1. package/dist/{BaseController-DVNKvoX4.mjs → BaseController-JNV08qOT.mjs} +480 -442
  2. package/dist/{queryCachePlugin-Dumka73q.d.mts → QueryCache-DOBNHBE0.d.mts} +2 -32
  3. package/dist/adapters/index.d.mts +2 -2
  4. package/dist/adapters/index.mjs +1 -1
  5. package/dist/{adapters-BXY4i-hw.mjs → adapters-D0tT2Tyo.mjs} +54 -0
  6. package/dist/audit/index.d.mts +1 -1
  7. package/dist/auth/index.d.mts +1 -1
  8. package/dist/auth/index.mjs +5 -5
  9. package/dist/{betterAuthOpenApi--rdY15Ld.mjs → betterAuthOpenApi-DwxtK3uG.mjs} +1 -1
  10. package/dist/cache/index.d.mts +3 -2
  11. package/dist/cache/index.mjs +3 -3
  12. package/dist/cli/commands/docs.mjs +2 -2
  13. package/dist/cli/commands/generate.mjs +37 -27
  14. package/dist/cli/commands/init.mjs +46 -33
  15. package/dist/cli/commands/introspect.mjs +1 -1
  16. package/dist/context/index.mjs +1 -1
  17. package/dist/core/index.d.mts +3 -3
  18. package/dist/core/index.mjs +4 -3
  19. package/dist/core-DXdSSFW-.mjs +1037 -0
  20. package/dist/createActionRouter-BwaSM0No.mjs +166 -0
  21. package/dist/{createApp-BwnEAO2h.mjs → createApp-DvNYEhpb.mjs} +75 -27
  22. package/dist/docs/index.d.mts +1 -1
  23. package/dist/docs/index.mjs +2 -2
  24. package/dist/{elevation-Dci0AYLT.mjs → elevation-DOFoxoDs.mjs} +1 -1
  25. package/dist/{errorHandler-CSxe7KIM.mjs → errorHandler-BQm8ZxTK.mjs} +1 -1
  26. package/dist/{eventPlugin-ByU4Cv0e.mjs → eventPlugin--5HIkdPU.mjs} +1 -1
  27. package/dist/events/index.d.mts +3 -3
  28. package/dist/events/index.mjs +2 -2
  29. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  30. package/dist/factory/index.d.mts +1 -1
  31. package/dist/factory/index.mjs +2 -2
  32. package/dist/hooks/index.d.mts +1 -1
  33. package/dist/hooks/index.mjs +1 -1
  34. package/dist/idempotency/index.d.mts +3 -3
  35. package/dist/idempotency/index.mjs +1 -1
  36. package/dist/idempotency/redis.d.mts +1 -1
  37. package/dist/{index-C_Noptz-.d.mts → index-BYCqHCVu.d.mts} +2 -2
  38. package/dist/{index-BGbpGVyM.d.mts → index-Cm0vUrr_.d.mts} +699 -494
  39. package/dist/{index-BziRPS4H.d.mts → index-DAushRTt.d.mts} +29 -10
  40. package/dist/index-DsJ1MNfC.d.mts +1179 -0
  41. package/dist/{index-EqQN6p0W.d.mts → index-t8pLpPFW.d.mts} +11 -8
  42. package/dist/index.d.mts +6 -38
  43. package/dist/index.mjs +9 -9
  44. package/dist/integrations/event-gateway.d.mts +1 -1
  45. package/dist/integrations/event-gateway.mjs +1 -1
  46. package/dist/integrations/index.d.mts +2 -2
  47. package/dist/integrations/mcp/index.d.mts +2 -2
  48. package/dist/integrations/mcp/index.mjs +1 -1
  49. package/dist/integrations/mcp/testing.d.mts +1 -1
  50. package/dist/integrations/mcp/testing.mjs +1 -1
  51. package/dist/integrations/streamline.d.mts +46 -5
  52. package/dist/integrations/streamline.mjs +50 -21
  53. package/dist/integrations/websocket-redis.d.mts +1 -1
  54. package/dist/integrations/websocket.d.mts +2 -154
  55. package/dist/integrations/websocket.mjs +292 -224
  56. package/dist/{keys-nWQGUTu1.mjs → keys-CARyUjiR.mjs} +2 -0
  57. package/dist/{loadResources-Bksk8ydA.mjs → loadResources-YNwKHvRA.mjs} +3 -1
  58. package/dist/middleware/index.d.mts +1 -1
  59. package/dist/middleware/index.mjs +1 -1
  60. package/dist/{openapi-DpNpqBmo.mjs → openapi-C0L9ar7m.mjs} +4 -4
  61. package/dist/org/index.d.mts +1 -1
  62. package/dist/permissions/index.d.mts +1 -1
  63. package/dist/permissions/index.mjs +2 -4
  64. package/dist/{permissions-wkqRwicB.mjs → permissions-B4vU9L0Q.mjs} +221 -3
  65. package/dist/{pipe-CGJxqDGx.mjs → pipe-DVoIheVC.mjs} +1 -1
  66. package/dist/pipeline/index.d.mts +1 -1
  67. package/dist/pipeline/index.mjs +1 -1
  68. package/dist/plugins/index.d.mts +4 -4
  69. package/dist/plugins/index.mjs +10 -10
  70. package/dist/plugins/response-cache.mjs +1 -1
  71. package/dist/plugins/tracing-entry.d.mts +1 -1
  72. package/dist/plugins/tracing-entry.mjs +42 -24
  73. package/dist/presets/filesUpload.d.mts +1 -1
  74. package/dist/presets/filesUpload.mjs +3 -3
  75. package/dist/presets/index.d.mts +1 -1
  76. package/dist/presets/index.mjs +1 -1
  77. package/dist/presets/multiTenant.d.mts +1 -1
  78. package/dist/presets/multiTenant.mjs +6 -0
  79. package/dist/presets/search.d.mts +1 -1
  80. package/dist/presets/search.mjs +1 -1
  81. package/dist/{presets-CrwOvuXI.mjs → presets-k604Lj99.mjs} +1 -1
  82. package/dist/queryCachePlugin-BUXBSm4F.d.mts +34 -0
  83. package/dist/{queryCachePlugin-ChLNZvFT.mjs → queryCachePlugin-Bq6bO6vc.mjs} +3 -3
  84. package/dist/{redis-MXLp1oOf.d.mts → redis-Cm1gnRDf.d.mts} +1 -1
  85. package/dist/registry/index.d.mts +1 -1
  86. package/dist/registry/index.mjs +2 -2
  87. package/dist/{resourceToTools-BhF3JV5p.mjs → resourceToTools--okX6QBr.mjs} +534 -420
  88. package/dist/routerShared-DeESFp4a.mjs +515 -0
  89. package/dist/schemaIR-BlG9bY7v.mjs +137 -0
  90. package/dist/scope/index.mjs +2 -2
  91. package/dist/testing/index.d.mts +367 -711
  92. package/dist/testing/index.mjs +637 -1434
  93. package/dist/{tracing-xqXzWeaf.d.mts → tracing-DokiEsuz.d.mts} +9 -4
  94. package/dist/types/index.d.mts +3 -3
  95. package/dist/types/index.mjs +1 -3
  96. package/dist/{types-CVdgPXBW.d.mts → types-CgikqKAj.d.mts} +118 -19
  97. package/dist/{types-CVKBssX5.d.mts → types-D9NqiYIw.d.mts} +1 -1
  98. package/dist/utils/index.d.mts +2 -968
  99. package/dist/utils/index.mjs +5 -6
  100. package/dist/utils-D3Yxnrwr.mjs +1639 -0
  101. package/dist/websocket-CyJ1VIFI.d.mts +186 -0
  102. package/package.json +7 -5
  103. package/skills/arc/SKILL.md +123 -38
  104. package/skills/arc/references/testing.md +212 -183
  105. package/dist/applyPermissionResult-QhV1Pa-g.mjs +0 -37
  106. package/dist/core-3MWJosCH.mjs +0 -1459
  107. package/dist/createActionRouter-C8UUB3Px.mjs +0 -249
  108. package/dist/errors-BI8kEKsO.d.mts +0 -140
  109. package/dist/fields-CTMWOUDt.mjs +0 -126
  110. package/dist/queryParser-NR__Qiju.mjs +0 -419
  111. package/dist/types-CDnTEpga.mjs +0 -27
  112. package/dist/utils-LMwVidKy.mjs +0 -947
  113. /package/dist/{HookSystem-BjFu7zf1.mjs → HookSystem-CGsMd6oK.mjs} +0 -0
  114. /package/dist/{ResourceRegistry-CcN2LVrc.mjs → ResourceRegistry-DkAeAuTX.mjs} +0 -0
  115. /package/dist/{actionPermissions-TUVR3uiZ.mjs → actionPermissions-C8YYU92K.mjs} +0 -0
  116. /package/dist/{caching-3h93rkJM.mjs → caching-CheW3m-S.mjs} +0 -0
  117. /package/dist/{errorHandler-2ii4RIYr.d.mts → errorHandler-Co3lnVmJ.d.mts} +0 -0
  118. /package/dist/{errors-BqdUDja_.mjs → errors-D5c-5BJL.mjs} +0 -0
  119. /package/dist/{eventPlugin-D1ThQ1Pp.d.mts → eventPlugin-CUNjYYRY.d.mts} +0 -0
  120. /package/dist/{interface-B-pe8fhj.d.mts → interface-CkkWm5uR.d.mts} +0 -0
  121. /package/dist/{interface-yhyb_pLY.d.mts → interface-Da0r7Lna.d.mts} +0 -0
  122. /package/dist/{memory-DqI-449b.mjs → memory-DikHSvWa.mjs} +0 -0
  123. /package/dist/{metrics-TuOmguhi.mjs → metrics-Csh4nsvv.mjs} +0 -0
  124. /package/dist/{multipartBody-CUQGVlM_.mjs → multipartBody-CvTR1Un6.mjs} +0 -0
  125. /package/dist/{pluralize-CWP6MB39.mjs → pluralize-BneOJkpi.mjs} +0 -0
  126. /package/dist/{redis-stream-bkO88VHx.d.mts → redis-stream-CM8TXTix.d.mts} +0 -0
  127. /package/dist/{registry-B0Wl7uVV.mjs → registry-D63ee7fl.mjs} +0 -0
  128. /package/dist/{replyHelpers-BLojtuvR.mjs → replyHelpers-ByllIXXV.mjs} +0 -0
  129. /package/dist/{requestContext-C38GskNt.mjs → requestContext-CfRkaxwf.mjs} +0 -0
  130. /package/dist/{schemaConverter-BxFDdtXu.mjs → schemaConverter-B0oKLuqI.mjs} +0 -0
  131. /package/dist/{sse-D8UeDwis.mjs → sse-V7aXc3bW.mjs} +0 -0
  132. /package/dist/{store-helpers-DYYUQbQN.mjs → store-helpers-BhrzxvyQ.mjs} +0 -0
  133. /package/dist/{typeGuards-Cj5Rgvlg.mjs → typeGuards-CcFZXgU7.mjs} +0 -0
  134. /package/dist/{types-D57iXYb8.mjs → types-DV9WDfeg.mjs} +0 -0
  135. /package/dist/{versioning-B6mimogM.mjs → versioning-CGPjkqAg.mjs} +0 -0
  136. /package/dist/{versioning-CeUXHfjw.d.mts → versioning-M9lNLhO8.d.mts} +0 -0
@@ -0,0 +1,1179 @@
1
+ import { $ as OpenApiSchemas, Pt as AnyRecord, S as QueryParserInterface, at as ResourceConfig, b as ParsedQuery, zt as UserLike } from "./index-Cm0vUrr_.mjs";
2
+ import { n as ErrorMapper } from "./errorHandler-Co3lnVmJ.mjs";
3
+ import { FastifyInstance, FastifyReply, FastifyRequest, RouteHandlerMethod } from "fastify";
4
+
5
+ //#region src/utils/errors.d.ts
6
+ /**
7
+ * Error Classes
8
+ *
9
+ * Standard error types for the Arc framework.
10
+ */
11
+ interface ErrorDetails {
12
+ code?: string;
13
+ statusCode?: number;
14
+ details?: Record<string, unknown>;
15
+ cause?: Error;
16
+ requestId?: string;
17
+ }
18
+ /**
19
+ * Base Arc Error
20
+ *
21
+ * All Arc errors extend this class and produce a consistent error envelope:
22
+ * {
23
+ * success: false,
24
+ * error: "Human-readable message",
25
+ * code: "MACHINE_CODE",
26
+ * requestId: "uuid", // For tracing
27
+ * timestamp: "ISO date", // When error occurred
28
+ * details: { ... } // Additional context
29
+ * }
30
+ */
31
+ declare class ArcError extends Error {
32
+ name: string;
33
+ readonly code: string;
34
+ readonly statusCode: number;
35
+ readonly details?: Record<string, unknown>;
36
+ readonly cause?: Error;
37
+ readonly timestamp: string;
38
+ requestId?: string;
39
+ constructor(message: string, options?: ErrorDetails);
40
+ /**
41
+ * Set request ID (typically from request context)
42
+ */
43
+ withRequestId(requestId: string): this;
44
+ /**
45
+ * Convert to JSON response.
46
+ * Includes cause chain when present for debugging visibility.
47
+ */
48
+ toJSON(): Record<string, unknown>;
49
+ }
50
+ /**
51
+ * Not Found Error - 404
52
+ */
53
+ declare class NotFoundError extends ArcError {
54
+ constructor(resource: string, identifier?: string);
55
+ }
56
+ /**
57
+ * Validation Error - 400
58
+ */
59
+ declare class ValidationError extends ArcError {
60
+ readonly errors: Array<{
61
+ field: string;
62
+ message: string;
63
+ }>;
64
+ constructor(message: string, errors?: Array<{
65
+ field: string;
66
+ message: string;
67
+ }>);
68
+ }
69
+ /**
70
+ * Unauthorized Error - 401
71
+ */
72
+ declare class UnauthorizedError extends ArcError {
73
+ constructor(message?: string);
74
+ }
75
+ /**
76
+ * Forbidden Error - 403
77
+ */
78
+ declare class ForbiddenError extends ArcError {
79
+ constructor(message?: string);
80
+ }
81
+ /**
82
+ * Conflict Error - 409
83
+ */
84
+ declare class ConflictError extends ArcError {
85
+ constructor(message: string, field?: string);
86
+ }
87
+ /**
88
+ * Organization Required Error - 403
89
+ */
90
+ declare class OrgRequiredError extends ArcError {
91
+ readonly organizations?: Array<{
92
+ id: string;
93
+ roles?: string[];
94
+ }>;
95
+ constructor(message: string, organizations?: Array<{
96
+ id: string;
97
+ roles?: string[];
98
+ }>);
99
+ }
100
+ /**
101
+ * Organization Access Denied Error - 403
102
+ */
103
+ declare class OrgAccessDeniedError extends ArcError {
104
+ constructor(orgId?: string);
105
+ }
106
+ /**
107
+ * Rate Limit Error - 429
108
+ */
109
+ declare class RateLimitError extends ArcError {
110
+ readonly retryAfter?: number;
111
+ constructor(message?: string, retryAfter?: number);
112
+ }
113
+ /**
114
+ * Service Unavailable Error - 503
115
+ */
116
+ declare class ServiceUnavailableError extends ArcError {
117
+ constructor(message?: string);
118
+ }
119
+ /**
120
+ * Create error from status code
121
+ */
122
+ declare function createError(statusCode: number, message: string, details?: Record<string, unknown>): ArcError;
123
+ /**
124
+ * Create a domain-specific error with automatic HTTP status mapping.
125
+ *
126
+ * Eliminates manual `if (err.code === 'X') return status` boilerplate.
127
+ * Arc's error handler automatically maps `statusCode` to HTTP response.
128
+ *
129
+ * @example
130
+ * ```typescript
131
+ * import { createDomainError } from '@classytic/arc';
132
+ *
133
+ * throw createDomainError('MEMBER_NOT_FOUND', 'Member does not exist', 404);
134
+ * throw createDomainError('SELF_REFERRAL', 'Cannot refer yourself', 422);
135
+ * throw createDomainError('INSUFFICIENT_BALANCE', 'Not enough credits', 402, { balance: 0 });
136
+ * ```
137
+ */
138
+ declare function createDomainError(code: string, message: string, statusCode?: number, details?: Record<string, unknown>): ArcError;
139
+ /**
140
+ * Check if error is an Arc error
141
+ */
142
+ declare function isArcError(error: unknown): error is ArcError;
143
+ //#endregion
144
+ //#region src/core/validateResourceConfig.d.ts
145
+ interface ConfigError {
146
+ field: string;
147
+ message: string;
148
+ suggestion?: string;
149
+ }
150
+ interface ValidationResult {
151
+ valid: boolean;
152
+ errors: ConfigError[];
153
+ warnings: ConfigError[];
154
+ }
155
+ interface ValidateOptions {
156
+ /** Skip controller method validation (for testing) */
157
+ skipControllerCheck?: boolean;
158
+ /** Allow unknown preset names */
159
+ allowUnknownPresets?: boolean;
160
+ /** Custom valid permission keys beyond CRUD */
161
+ additionalPermissionKeys?: string[];
162
+ }
163
+ /**
164
+ * Validate a resource configuration
165
+ */
166
+ declare function validateResourceConfig(config: ResourceConfig, options?: ValidateOptions): ValidationResult;
167
+ /**
168
+ * Format validation errors for display
169
+ */
170
+ declare function formatValidationErrors(resourceName: string, result: ValidationResult): string;
171
+ /**
172
+ * Validate and throw if invalid
173
+ */
174
+ declare function assertValidConfig(config: ResourceConfig, options?: ValidateOptions): void;
175
+ //#endregion
176
+ //#region src/utils/circuitBreaker.d.ts
177
+ /**
178
+ * Circuit Breaker Pattern
179
+ *
180
+ * Wraps external service calls with failure protection.
181
+ * Prevents cascading failures by "opening" the circuit when
182
+ * a service is failing, allowing it time to recover.
183
+ *
184
+ * States:
185
+ * - CLOSED: Normal operation, requests pass through
186
+ * - OPEN: Too many failures, all requests fail fast
187
+ * - HALF_OPEN: Testing if service recovered, limited requests
188
+ *
189
+ * @example
190
+ * import { CircuitBreaker } from '@classytic/arc/utils';
191
+ *
192
+ * const paymentBreaker = new CircuitBreaker(async (amount) => {
193
+ * return await stripe.charges.create({ amount });
194
+ * }, {
195
+ * failureThreshold: 5,
196
+ * resetTimeout: 30000,
197
+ * timeout: 5000,
198
+ * });
199
+ *
200
+ * try {
201
+ * const result = await paymentBreaker.call(100);
202
+ * } catch (error) {
203
+ * // Handle failure or circuit open
204
+ * }
205
+ */
206
+ declare const CircuitState: {
207
+ readonly CLOSED: "CLOSED";
208
+ readonly OPEN: "OPEN";
209
+ readonly HALF_OPEN: "HALF_OPEN";
210
+ };
211
+ type CircuitState = (typeof CircuitState)[keyof typeof CircuitState];
212
+ interface CircuitBreakerOptions {
213
+ /**
214
+ * Number of failures before opening circuit
215
+ * @default 5
216
+ */
217
+ failureThreshold?: number;
218
+ /**
219
+ * Time in ms before attempting to close circuit
220
+ * @default 60000 (60 seconds)
221
+ */
222
+ resetTimeout?: number;
223
+ /**
224
+ * Request timeout in ms
225
+ * @default 10000 (10 seconds)
226
+ */
227
+ timeout?: number;
228
+ /**
229
+ * Number of successful requests in HALF_OPEN before closing
230
+ * @default 1
231
+ */
232
+ successThreshold?: number;
233
+ /**
234
+ * Fallback function when circuit is open.
235
+ * Receives the same arguments as the wrapped function.
236
+ */
237
+ fallback?: (...args: unknown[]) => Promise<unknown>;
238
+ /**
239
+ * Callback when state changes
240
+ */
241
+ onStateChange?: (from: CircuitState, to: CircuitState) => void;
242
+ /**
243
+ * Callback on error
244
+ */
245
+ onError?: (error: Error) => void;
246
+ /**
247
+ * Name for logging/monitoring
248
+ */
249
+ name?: string;
250
+ }
251
+ interface CircuitBreakerStats {
252
+ name?: string;
253
+ state: CircuitState;
254
+ failures: number;
255
+ successes: number;
256
+ totalCalls: number;
257
+ openedAt: number | null;
258
+ lastCallAt: number | null;
259
+ }
260
+ declare class CircuitBreakerError extends Error {
261
+ state: CircuitState;
262
+ constructor(message: string, state: CircuitState);
263
+ }
264
+ declare class CircuitBreaker<T extends (...args: any[]) => Promise<any>> {
265
+ private state;
266
+ private failures;
267
+ private successes;
268
+ private totalCalls;
269
+ private nextAttempt;
270
+ private lastCallAt;
271
+ private openedAt;
272
+ private readonly failureThreshold;
273
+ private readonly resetTimeout;
274
+ private readonly timeout;
275
+ private readonly successThreshold;
276
+ private readonly fallback?;
277
+ private readonly onStateChange?;
278
+ private readonly onError?;
279
+ private readonly name;
280
+ private readonly fn;
281
+ constructor(fn: T, options?: CircuitBreakerOptions);
282
+ /**
283
+ * Call the wrapped function with circuit breaker protection
284
+ */
285
+ call(...args: Parameters<T>): Promise<ReturnType<T>>;
286
+ /**
287
+ * Execute function with timeout
288
+ */
289
+ private executeWithTimeout;
290
+ /**
291
+ * Handle successful call
292
+ */
293
+ private onSuccess;
294
+ /**
295
+ * Handle failed call
296
+ */
297
+ private onFailure;
298
+ /**
299
+ * Change circuit state
300
+ */
301
+ private setState;
302
+ /**
303
+ * Manually open the circuit
304
+ */
305
+ open(): void;
306
+ /**
307
+ * Manually close the circuit
308
+ */
309
+ close(): void;
310
+ /**
311
+ * Get current statistics
312
+ */
313
+ getStats(): CircuitBreakerStats;
314
+ /**
315
+ * Get current state
316
+ */
317
+ getState(): CircuitState;
318
+ /**
319
+ * Check if circuit is open
320
+ */
321
+ isOpen(): boolean;
322
+ /**
323
+ * Check if circuit is closed
324
+ */
325
+ isClosed(): boolean;
326
+ /**
327
+ * Reset statistics
328
+ */
329
+ reset(): void;
330
+ }
331
+ /**
332
+ * Create a circuit breaker with sensible defaults
333
+ *
334
+ * @example
335
+ * const emailBreaker = createCircuitBreaker(
336
+ * async (to, subject, body) => sendEmail(to, subject, body),
337
+ * { name: 'email-service' }
338
+ * );
339
+ */
340
+ declare function createCircuitBreaker<T extends (...args: any[]) => Promise<any>>(fn: T, options?: CircuitBreakerOptions): CircuitBreaker<T>;
341
+ /**
342
+ * Circuit breaker registry for managing multiple breakers
343
+ */
344
+ declare class CircuitBreakerRegistry {
345
+ private breakers;
346
+ /**
347
+ * Register a circuit breaker
348
+ */
349
+ register<T extends (...args: any[]) => Promise<any>>(name: string, fn: T, options?: Omit<CircuitBreakerOptions, "name">): CircuitBreaker<T>;
350
+ /**
351
+ * Get a circuit breaker by name
352
+ */
353
+ get(name: string): CircuitBreaker<any> | undefined;
354
+ /**
355
+ * Get all breakers
356
+ */
357
+ getAll(): Map<string, CircuitBreaker<any>>;
358
+ /**
359
+ * Get statistics for all breakers
360
+ */
361
+ getAllStats(): Record<string, CircuitBreakerStats>;
362
+ /**
363
+ * Reset all breakers
364
+ */
365
+ resetAll(): void;
366
+ /**
367
+ * Open all breakers
368
+ */
369
+ openAll(): void;
370
+ /**
371
+ * Close all breakers
372
+ */
373
+ closeAll(): void;
374
+ }
375
+ /**
376
+ * Create a new CircuitBreakerRegistry instance.
377
+ * Use this instead of a global singleton — attach to fastify.arc or pass explicitly.
378
+ */
379
+ declare function createCircuitBreakerRegistry(): CircuitBreakerRegistry;
380
+ //#endregion
381
+ //#region src/utils/compensation.d.ts
382
+ /**
383
+ * Compensating Transaction — In-Process Rollback Primitive
384
+ *
385
+ * Runs steps in order. If any step fails, runs compensating actions
386
+ * for already-completed steps in reverse. Zero dependencies.
387
+ *
388
+ * Type-safe: generic context type gives autocomplete across steps.
389
+ * Discriminated union result: compiler enforces checking success before
390
+ * accessing failedStep/error.
391
+ *
392
+ * For distributed sagas across services, use Temporal, Inngest, or Streamline.
393
+ *
394
+ * @example
395
+ * ```typescript
396
+ * interface CheckoutCtx {
397
+ * orderId: string;
398
+ * reservationId?: string;
399
+ * }
400
+ *
401
+ * const result = await withCompensation<CheckoutCtx>('checkout', [
402
+ * {
403
+ * name: 'reserve',
404
+ * execute: async (ctx) => {
405
+ * const res = await inventoryService.reserve(ctx.orderId);
406
+ * ctx.reservationId = res.id;
407
+ * return res;
408
+ * },
409
+ * compensate: async (ctx) => {
410
+ * await inventoryService.release(ctx.reservationId!);
411
+ * },
412
+ * },
413
+ * { name: 'notify', execute: sendEmail, fireAndForget: true },
414
+ * ], { orderId: 'ord-123' });
415
+ *
416
+ * if (result.success) {
417
+ * // result.results available, no failedStep
418
+ * } else {
419
+ * // result.failedStep and result.error guaranteed
420
+ * }
421
+ * ```
422
+ */
423
+ /** Step definition with typed context and typed result */
424
+ interface CompensationStep<TCtx = Record<string, unknown>, TResult = unknown> {
425
+ /** Step name — used in results, logs, and hooks */
426
+ readonly name: string;
427
+ /** Execute the step — return value stored in results[name] */
428
+ readonly execute: (ctx: TCtx) => Promise<TResult>;
429
+ /** Rollback on failure — receives context and this step's own result */
430
+ readonly compensate?: (ctx: TCtx, stepResult: TResult) => Promise<void>;
431
+ /** Fire-and-forget — don't await, don't block, swallow errors, skip in rollback */
432
+ readonly fireAndForget?: boolean;
433
+ }
434
+ /** Lifecycle hooks for observability — wire to Arc events, metrics, or logging */
435
+ interface CompensationHooks {
436
+ readonly onStepComplete?: (stepName: string, result: unknown) => void;
437
+ readonly onStepFailed?: (stepName: string, error: Error) => void;
438
+ readonly onCompensate?: (stepName: string) => void;
439
+ }
440
+ /** Error from a compensation action that failed during rollback */
441
+ interface CompensationError {
442
+ readonly step: string;
443
+ readonly error: string;
444
+ }
445
+ /** Discriminated union — success and failure are mutually exclusive */
446
+ type CompensationResult = {
447
+ readonly success: true;
448
+ readonly completedSteps: readonly string[];
449
+ readonly results: Readonly<Record<string, unknown>>;
450
+ } | {
451
+ readonly success: false;
452
+ readonly completedSteps: readonly string[];
453
+ readonly results: Readonly<Record<string, unknown>>;
454
+ readonly failedStep: string;
455
+ readonly error: string;
456
+ readonly compensationErrors?: readonly CompensationError[];
457
+ };
458
+ /**
459
+ * Run steps in order with automatic compensation on failure.
460
+ *
461
+ * @typeParam TCtx - Context type shared across steps (defaults to Record<string, unknown>)
462
+ */
463
+ declare function withCompensation<TCtx extends Record<string, unknown> = Record<string, unknown>>(_name: string, steps: readonly CompensationStep<TCtx>[], initialContext?: TCtx, hooks?: CompensationHooks): Promise<CompensationResult>;
464
+ interface CompensationDefinition<TCtx extends Record<string, unknown> = Record<string, unknown>> {
465
+ readonly name: string;
466
+ readonly execute: (initialContext?: TCtx, hooks?: CompensationHooks) => Promise<CompensationResult>;
467
+ }
468
+ declare function defineCompensation<TCtx extends Record<string, unknown> = Record<string, unknown>>(name: string, steps: readonly CompensationStep<TCtx>[]): CompensationDefinition<TCtx>;
469
+ //#endregion
470
+ //#region src/utils/defineErrorMapper.d.ts
471
+ /**
472
+ * Register an `ErrorMapper` with its domain-specific generic argument and
473
+ * have it assign cleanly into `ErrorMapper[]` (no `as unknown as ErrorMapper`).
474
+ *
475
+ * The returned mapper is identical at runtime — `type` and `toResponse` are
476
+ * passed through untouched. Only the declared type widens from
477
+ * `ErrorMapper<T>` to `ErrorMapper` so the array inference works.
478
+ *
479
+ * Safety: the `errorHandlerPlugin` dispatches via `error instanceof mapper.type`
480
+ * before invoking `toResponse`, so the widened callback signature is never
481
+ * called with a non-`T` error at runtime. This helper codifies that invariant
482
+ * in one place.
483
+ */
484
+ declare function defineErrorMapper<T extends Error>(mapper: ErrorMapper<T>): ErrorMapper;
485
+ //#endregion
486
+ //#region src/utils/defineGuard.d.ts
487
+ interface GuardConfig<T> {
488
+ /** Unique name — used as the storage key on the request. */
489
+ readonly name: string;
490
+ /**
491
+ * Resolve the guard context from the request. Throw to abort the request
492
+ * (Fastify's error handler will produce the appropriate HTTP response).
493
+ * Return a value to stash it for `from()` extraction.
494
+ */
495
+ readonly resolve: (req: FastifyRequest, reply: FastifyReply) => T | Promise<T>;
496
+ }
497
+ interface Guard<T> {
498
+ /** Use in `routeGuards` or per-route `preHandler` arrays. */
499
+ readonly preHandler: RouteHandlerMethod;
500
+ /**
501
+ * Extract the resolved context from a request. Throws if the guard
502
+ * hasn't run yet (i.e. not in the preHandler chain).
503
+ */
504
+ from(req: FastifyRequest): T;
505
+ /** The guard name (for debugging). */
506
+ readonly name: string;
507
+ }
508
+ /**
509
+ * Create a typed guard. See module JSDoc for usage.
510
+ */
511
+ declare function defineGuard<T>(config: GuardConfig<T>): Guard<T>;
512
+ //#endregion
513
+ //#region src/utils/envelope.d.ts
514
+ /**
515
+ * Standard response envelope helper.
516
+ *
517
+ * Wraps a handler return value in arc's `{ success: true, data, ...meta }`
518
+ * shape. Pure utility — no types module coupling, no runtime dependencies.
519
+ *
520
+ * @example
521
+ * ```ts
522
+ * import { envelope } from '@classytic/arc';
523
+ *
524
+ * handler: async (req, reply) => {
525
+ * const results = await search(req.query.q);
526
+ * return envelope(results, { took: performance.now() - t0 });
527
+ * }
528
+ * ```
529
+ */
530
+ /**
531
+ * Wrap data in arc's standard `{ success: true, data }` envelope, with
532
+ * optional top-level meta keys merged in.
533
+ */
534
+ declare function envelope<T>(data: T, meta?: Record<string, unknown>): {
535
+ success: true;
536
+ data: T;
537
+ [key: string]: unknown;
538
+ };
539
+ //#endregion
540
+ //#region src/utils/handleRaw.d.ts
541
+ /**
542
+ * Wrap a raw Fastify handler with Arc's response envelope and error handling.
543
+ *
544
+ * @param handler - Async function that receives `(request, reply)` and returns data.
545
+ * The return value is sent as `{ success: true, data }`. If it returns
546
+ * `undefined` or `null`, `{ success: true }` is sent (no `data` field).
547
+ * @param statusCode - HTTP status code for successful responses (default: 200)
548
+ */
549
+ declare function handleRaw<T>(handler: (request: FastifyRequest, reply: FastifyReply) => Promise<T>, statusCode?: number): (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
550
+ //#endregion
551
+ //#region src/utils/queryParser.d.ts
552
+ interface ArcQueryParserOptions {
553
+ /** Maximum allowed limit value (default: 1000) */
554
+ maxLimit?: number;
555
+ /** Default limit for pagination (default: 20) */
556
+ defaultLimit?: number;
557
+ /** Maximum regex pattern length (default: 500) */
558
+ maxRegexLength?: number;
559
+ /** Maximum search query length (default: 200) */
560
+ maxSearchLength?: number;
561
+ /** Maximum filter nesting depth (default: 10) */
562
+ maxFilterDepth?: number;
563
+ /**
564
+ * Whitelist of fields that can be filtered on.
565
+ * When set, only these fields are accepted as filters — all others are silently dropped.
566
+ * Also used by MCP to auto-derive filterable fields in tool schemas.
567
+ */
568
+ allowedFilterFields?: string[];
569
+ /**
570
+ * Whitelist of fields that can be sorted on.
571
+ * When set, sort fields not in this list are silently dropped.
572
+ * Also used by MCP to describe available sort options.
573
+ */
574
+ allowedSortFields?: string[];
575
+ /**
576
+ * Whitelist of filter operators (e.g. ['eq', 'ne', 'gt', 'lt', 'in']).
577
+ * When set, only these operators are accepted — all others are dropped.
578
+ * Also used by MCP to enrich list tool descriptions.
579
+ */
580
+ allowedOperators?: string[];
581
+ }
582
+ /**
583
+ * Arc's default query parser
584
+ *
585
+ * Converts URL query parameters to a structured query format:
586
+ * - Pagination: ?page=1&limit=20
587
+ * - Sorting: ?sort=-createdAt,name (- prefix = descending)
588
+ * - Filtering: ?status=active&price[gte]=100&price[lte]=500
589
+ * - Search: ?search=keyword
590
+ * - Populate: ?populate=author,category
591
+ * - Field selection: ?select=name,price,status
592
+ * - Keyset pagination: ?after=cursor_value
593
+ *
594
+ * For advanced MongoDB features ($lookup, aggregations), use MongoKit's QueryParser.
595
+ */
596
+ declare class ArcQueryParser implements QueryParserInterface {
597
+ private readonly maxLimit;
598
+ private readonly defaultLimit;
599
+ private readonly maxRegexLength;
600
+ private readonly maxSearchLength;
601
+ private readonly maxFilterDepth;
602
+ private readonly _allowedFilterFields?;
603
+ private readonly _allowedSortFields?;
604
+ private readonly _allowedOperators?;
605
+ /** Allowed filter fields (used by MCP for auto-derive) */
606
+ readonly allowedFilterFields?: readonly string[];
607
+ /** Allowed sort fields (used by MCP for sort descriptions) */
608
+ readonly allowedSortFields?: readonly string[];
609
+ /** Allowed operators (used by MCP for operator descriptions) */
610
+ readonly allowedOperators?: readonly string[];
611
+ /** Supported filter operators */
612
+ private readonly operators;
613
+ constructor(options?: ArcQueryParserOptions);
614
+ /**
615
+ * Parse URL query parameters into structured query options
616
+ */
617
+ parse(query: Record<string, unknown> | null | undefined): ParsedQuery;
618
+ private parseNumber;
619
+ private parseString;
620
+ /**
621
+ * Parse populate parameter — handles both simple string and bracket notation.
622
+ *
623
+ * Simple: ?populate=author,category → { populate: 'author,category' }
624
+ * Bracket: ?populate[author][select]=name,email → { populateOptions: [{ path: 'author', select: 'name email' }] }
625
+ */
626
+ private parsePopulate;
627
+ private parseSort;
628
+ private parseSearch;
629
+ private parseSelect;
630
+ /**
631
+ * Check if a value exceeds the maximum nesting depth.
632
+ * Prevents filter bombs where deeply nested objects consume excessive memory/CPU.
633
+ */
634
+ private exceedsDepth;
635
+ private parseFilters;
636
+ private parseFilterValue;
637
+ private coerceValue;
638
+ /**
639
+ * Generate OpenAPI-compatible JSON Schema for query parameters.
640
+ * Arc's defineResource() auto-detects this method and uses it
641
+ * to document list endpoint query parameters in OpenAPI/Swagger.
642
+ */
643
+ getQuerySchema(): {
644
+ type: "object";
645
+ properties: Record<string, unknown>;
646
+ required?: string[];
647
+ };
648
+ private sanitizeRegex;
649
+ }
650
+ /**
651
+ * Create a new ArcQueryParser instance
652
+ */
653
+ declare function createQueryParser(options?: ArcQueryParserOptions): ArcQueryParser;
654
+ //#endregion
655
+ //#region src/utils/responseSchemas.d.ts
656
+ interface JsonSchema {
657
+ type: string;
658
+ properties?: Record<string, JsonSchema | AnyRecord>;
659
+ required?: string[];
660
+ items?: JsonSchema | AnyRecord;
661
+ additionalProperties?: boolean | JsonSchema;
662
+ description?: string;
663
+ example?: unknown;
664
+ [key: string]: unknown;
665
+ }
666
+ /**
667
+ * Base success response schema
668
+ */
669
+ declare const successResponseSchema: JsonSchema;
670
+ /**
671
+ * Error response schema
672
+ */
673
+ declare const errorResponseSchema: JsonSchema;
674
+ /**
675
+ * Pagination schema - matches MongoKit/Arc runtime format
676
+ *
677
+ * Runtime format (flat fields):
678
+ * { page, limit, total, pages, hasNext, hasPrev }
679
+ */
680
+ declare const paginationSchema: JsonSchema;
681
+ /**
682
+ * Wrap a data schema in a success response
683
+ */
684
+ declare function wrapResponse(dataSchema: JsonSchema): JsonSchema;
685
+ /**
686
+ * Create a list response schema with pagination - matches MongoKit/Arc runtime format
687
+ *
688
+ * Runtime format:
689
+ * { success, docs: [...], page, limit, total, pages, hasNext, hasPrev }
690
+ *
691
+ * Note: Uses 'docs' array (not 'data') with flat pagination fields
692
+ */
693
+ declare function listResponse(itemSchema: JsonSchema): JsonSchema;
694
+ /**
695
+ * Create a single item response schema
696
+ *
697
+ * Runtime format: { success, data: {...} }
698
+ */
699
+ declare function itemResponse(itemSchema: JsonSchema): JsonSchema;
700
+ /**
701
+ * Create a create/update response schema
702
+ */
703
+ declare function mutationResponse(itemSchema: JsonSchema): JsonSchema;
704
+ /**
705
+ * Create a delete response schema
706
+ *
707
+ * Runtime format: { success, data: { message, id?, soft? } }
708
+ */
709
+ declare function deleteResponse(): JsonSchema;
710
+ declare const responses: {
711
+ 200: (schema: JsonSchema) => {
712
+ description: string;
713
+ content: {
714
+ "application/json": {
715
+ schema: JsonSchema;
716
+ };
717
+ };
718
+ };
719
+ 201: (schema: JsonSchema) => {
720
+ description: string;
721
+ content: {
722
+ "application/json": {
723
+ schema: JsonSchema;
724
+ };
725
+ };
726
+ };
727
+ 400: {
728
+ description: string;
729
+ content: {
730
+ "application/json": {
731
+ schema: {
732
+ properties: {
733
+ code: {
734
+ type: string;
735
+ example: string;
736
+ };
737
+ details: {
738
+ type: string;
739
+ properties: {
740
+ errors: {
741
+ type: string;
742
+ items: {
743
+ type: string;
744
+ properties: {
745
+ field: {
746
+ type: string;
747
+ };
748
+ message: {
749
+ type: string;
750
+ };
751
+ };
752
+ };
753
+ };
754
+ };
755
+ };
756
+ };
757
+ type: string;
758
+ required?: string[];
759
+ items?: JsonSchema | AnyRecord;
760
+ additionalProperties?: boolean | JsonSchema;
761
+ description?: string;
762
+ example?: unknown;
763
+ };
764
+ };
765
+ };
766
+ };
767
+ 401: {
768
+ description: string;
769
+ content: {
770
+ "application/json": {
771
+ schema: {
772
+ properties: {
773
+ code: {
774
+ type: string;
775
+ example: string;
776
+ };
777
+ };
778
+ type: string;
779
+ required?: string[];
780
+ items?: JsonSchema | AnyRecord;
781
+ additionalProperties?: boolean | JsonSchema;
782
+ description?: string;
783
+ example?: unknown;
784
+ };
785
+ };
786
+ };
787
+ };
788
+ 403: {
789
+ description: string;
790
+ content: {
791
+ "application/json": {
792
+ schema: {
793
+ properties: {
794
+ code: {
795
+ type: string;
796
+ example: string;
797
+ };
798
+ };
799
+ type: string;
800
+ required?: string[];
801
+ items?: JsonSchema | AnyRecord;
802
+ additionalProperties?: boolean | JsonSchema;
803
+ description?: string;
804
+ example?: unknown;
805
+ };
806
+ };
807
+ };
808
+ };
809
+ 404: {
810
+ description: string;
811
+ content: {
812
+ "application/json": {
813
+ schema: {
814
+ properties: {
815
+ code: {
816
+ type: string;
817
+ example: string;
818
+ };
819
+ };
820
+ type: string;
821
+ required?: string[];
822
+ items?: JsonSchema | AnyRecord;
823
+ additionalProperties?: boolean | JsonSchema;
824
+ description?: string;
825
+ example?: unknown;
826
+ };
827
+ };
828
+ };
829
+ };
830
+ 409: {
831
+ description: string;
832
+ content: {
833
+ "application/json": {
834
+ schema: {
835
+ properties: {
836
+ code: {
837
+ type: string;
838
+ example: string;
839
+ };
840
+ };
841
+ type: string;
842
+ required?: string[];
843
+ items?: JsonSchema | AnyRecord;
844
+ additionalProperties?: boolean | JsonSchema;
845
+ description?: string;
846
+ example?: unknown;
847
+ };
848
+ };
849
+ };
850
+ };
851
+ 500: {
852
+ description: string;
853
+ content: {
854
+ "application/json": {
855
+ schema: {
856
+ properties: {
857
+ code: {
858
+ type: string;
859
+ example: string;
860
+ };
861
+ };
862
+ type: string;
863
+ required?: string[];
864
+ items?: JsonSchema | AnyRecord;
865
+ additionalProperties?: boolean | JsonSchema;
866
+ description?: string;
867
+ example?: unknown;
868
+ };
869
+ };
870
+ };
871
+ };
872
+ };
873
+ declare const queryParams: {
874
+ pagination: {
875
+ page: {
876
+ type: string;
877
+ minimum: number;
878
+ default: number;
879
+ description: string;
880
+ };
881
+ limit: {
882
+ type: string;
883
+ minimum: number;
884
+ maximum: number;
885
+ default: number;
886
+ description: string;
887
+ };
888
+ };
889
+ sorting: {
890
+ sort: {
891
+ type: string;
892
+ description: string;
893
+ example: string;
894
+ };
895
+ };
896
+ filtering: {
897
+ select: {
898
+ description: string;
899
+ example: string;
900
+ };
901
+ populate: {
902
+ description: string;
903
+ example: string;
904
+ };
905
+ };
906
+ };
907
+ /**
908
+ * Get standard list query parameters schema
909
+ */
910
+ declare function getListQueryParams(): AnyRecord;
911
+ /**
912
+ * Get default response schemas for all CRUD operations.
913
+ *
914
+ * When routes have response schemas, Fastify compiles them with
915
+ * fast-json-stringify for 2-3x faster serialization and prevents
916
+ * accidental field disclosure.
917
+ *
918
+ * These defaults use `additionalProperties: true` so all fields pass through.
919
+ * Override with specific schemas for full serialization performance + safety.
920
+ *
921
+ * Note: `example` properties are stripped from defaults so they work with
922
+ * any Fastify instance (not just createApp which adds `keywords: ['example']`).
923
+ */
924
+ declare function getDefaultCrudSchemas(): Record<string, Record<string, unknown>>;
925
+ //#endregion
926
+ //#region src/utils/schemaConverter.d.ts
927
+ /**
928
+ * Supported JSON Schema output targets for Zod v4's `toJSONSchema()`.
929
+ * - `draft-7`: Fastify/AJV validation (default)
930
+ * - `draft-2020-12`: AJV 2020 (opt-in, requires ajv/dist/2020)
931
+ * - `openapi-3.0`: OpenAPI 3.0 document generation
932
+ * - `openapi-3.1`: OpenAPI 3.1 document generation
933
+ */
934
+ type JsonSchemaTarget = "draft-7" | "draft-2020-12" | "openapi-3.0" | "openapi-3.1";
935
+ /**
936
+ * Check if an object is already a plain JSON Schema.
937
+ * Returns true if it has JSON Schema markers (`type`, `properties`, `$ref`,
938
+ * `allOf`, `anyOf`, `oneOf`, `items`, `enum`) and does NOT have Zod markers.
939
+ */
940
+ declare function isJsonSchema(input: unknown): input is Record<string, unknown>;
941
+ /**
942
+ * Check if an object is a Zod schema (has `_zod` marker from Zod v4).
943
+ */
944
+ declare function isZodSchema(input: unknown): boolean;
945
+ /**
946
+ * Convert any schema input to JSON Schema.
947
+ *
948
+ * Detection order:
949
+ * 1. `null`/`undefined` → `undefined`
950
+ * 2. Already JSON Schema → pass through as-is (zero overhead)
951
+ * 3. Zod v4 schema → `z.toJSONSchema(schema, { target })`
952
+ * 4. Unrecognized object → return as-is (treat as opaque schema)
953
+ *
954
+ * @param input Schema (Zod, plain JSON Schema, or opaque object)
955
+ * @param target Output target — defaults to `draft-7` for Fastify compatibility.
956
+ * Pass `openapi-3.0`/`openapi-3.1` for OpenAPI document generation.
957
+ */
958
+ declare function toJsonSchema(input: unknown, target?: JsonSchemaTarget): Record<string, unknown> | undefined;
959
+ /**
960
+ * Convert all schema fields in an OpenApiSchemas object.
961
+ * JSON Schema values pass through unchanged. Only Zod schemas are converted.
962
+ *
963
+ * Defaults to the `openapi-3.0` target since this function feeds OpenAPI doc
964
+ * generation, not Fastify route validation.
965
+ */
966
+ declare function convertOpenApiSchemas(schemas: OpenApiSchemas, target?: JsonSchemaTarget): OpenApiSchemas;
967
+ /**
968
+ * Convert schema values in a Fastify route schema record.
969
+ *
970
+ * Handles `body`, `querystring`, `params`, `headers` (top-level conversion)
971
+ * and `response` (iterates by status code — each value converted individually).
972
+ *
973
+ * JSON Schema values pass through unchanged. Only Zod schemas are converted.
974
+ *
975
+ * Used for both custom routes and customSchemas (CRUD overrides).
976
+ *
977
+ * Defaults to `draft-7` so Fastify v5's bundled AJV 8 accepts the output.
978
+ * Pass `openapi-3.0` (or `openapi-3.1`) when generating OpenAPI documents.
979
+ */
980
+ declare function convertRouteSchema(schema: Record<string, unknown>, target?: JsonSchemaTarget): Record<string, unknown>;
981
+ //#endregion
982
+ //#region src/utils/simpleEqualityMatcher.d.ts
983
+ /**
984
+ * `simpleEqualityMatcher` — a minimal, dialect-agnostic flat-key equality
985
+ * matcher for `DataAdapter.matchesFilter` / `BaseController({ matchesFilter })`.
986
+ *
987
+ * **What it does:** for each `[key, expected]` in the filter, compares
988
+ * `item[key]` to `expected` via string coercion (so Mongo `ObjectId` values
989
+ * match their string representation) and returns `true` only if every
990
+ * filter entry matches. Array item values are matched implicitly (contains).
991
+ *
992
+ * **What it does NOT do:**
993
+ * - No `$eq` / `$ne` / `$in` / `$nin` / `$gt` / `$lt` / `$regex` / `$exists`
994
+ * - No `$and` / `$or`
995
+ * - No dot-path traversal (`"owner.id"`)
996
+ * - No schema-specific coercion
997
+ *
998
+ * **Why it exists:** 95%+ of arc's `_policyFilters` are produced by built-in
999
+ * permission helpers and are shaped like `{ ownerId: "u1" }` or
1000
+ * `{ organizationId: "org_x" }` — flat equality. For that common shape,
1001
+ * this helper is a safe, tested, 15-line defense-in-depth matcher that
1002
+ * hosts using minimal repos (no `getOne(compoundFilter)` DB path) can opt
1003
+ * into without arc shipping a full Mongo-syntax engine.
1004
+ *
1005
+ * **When to use:**
1006
+ * - Your adapter/repo doesn't natively filter on `getOne(compoundFilter)`
1007
+ * - Your `_policyFilters` are flat equality (from arc's built-in permission helpers)
1008
+ * - You want defense-in-depth on `validateItemAccess` / `fetchDetailed`'s `getById` fallback
1009
+ *
1010
+ * **When NOT to use:**
1011
+ * - Your `_policyFilters` use operators (`$in`, `$ne`, etc.) — supply a
1012
+ * native matcher (mongokit's repo does the filter at the DB layer; for
1013
+ * custom repos, wrap the kit's own predicate engine).
1014
+ * - You're a mongokit / sqlitekit / Prisma user — the DB-level filter
1015
+ * applied by `getOne(compoundFilter)` already covers this.
1016
+ *
1017
+ * @example
1018
+ * ```ts
1019
+ * import { simpleEqualityMatcher } from '@classytic/arc/utils';
1020
+ *
1021
+ * // On a custom adapter
1022
+ * const adapter: DataAdapter = {
1023
+ * repository,
1024
+ * type: 'custom',
1025
+ * name: 'in-memory',
1026
+ * matchesFilter: simpleEqualityMatcher,
1027
+ * };
1028
+ *
1029
+ * // Or directly on BaseController for ad-hoc controllers
1030
+ * new BaseController(repo, { matchesFilter: simpleEqualityMatcher });
1031
+ * ```
1032
+ */
1033
+ declare function simpleEqualityMatcher(item: unknown, filters: Record<string, unknown>): boolean;
1034
+ //#endregion
1035
+ //#region src/utils/stateMachine.d.ts
1036
+ /**
1037
+ * State Machine Utility
1038
+ *
1039
+ * Pure utility for validating state transitions in workflow systems.
1040
+ * Zero dependencies, framework-agnostic.
1041
+ *
1042
+ * @example
1043
+ * const orderState = createStateMachine('Order', {
1044
+ * approve: ['pending', 'draft'],
1045
+ * cancel: ['pending', 'approved'],
1046
+ * fulfill: ['approved'],
1047
+ * });
1048
+ *
1049
+ * // Check if transition is allowed
1050
+ * if (orderState.can('approve', currentStatus)) {
1051
+ * // Perform approval
1052
+ * }
1053
+ *
1054
+ * // Assert transition (throws if invalid)
1055
+ * orderState.assert('approve', currentStatus, ValidationError);
1056
+ */
1057
+ interface StateMachine {
1058
+ /**
1059
+ * Synchronously check if action can be performed from current status.
1060
+ * Only checks the transition map — does NOT evaluate guards.
1061
+ * Use `canAsync()` when guards need to be evaluated.
1062
+ */
1063
+ can(action: string, status: string | null | undefined): boolean;
1064
+ /**
1065
+ * Asynchronously check if action can be performed, including guard evaluation.
1066
+ * Falls back to simple transition check when no guard is defined.
1067
+ */
1068
+ canAsync(action: string, status: string | null | undefined, context?: Record<string, unknown>): Promise<boolean>;
1069
+ /**
1070
+ * Assert action can be performed, throw error if invalid
1071
+ * @param action - Action to perform
1072
+ * @param status - Current status
1073
+ * @param errorFactory - Optional error constructor
1074
+ * @param message - Optional custom error message
1075
+ */
1076
+ assert(action: string, status: string | null | undefined, errorFactory?: (msg: string) => Error, message?: string): void;
1077
+ /**
1078
+ * Get transition history
1079
+ */
1080
+ getHistory?(): TransitionHistoryEntry[];
1081
+ /**
1082
+ * Record a transition
1083
+ */
1084
+ recordTransition?(from: string, to: string, action: string, metadata?: Record<string, unknown>): void;
1085
+ /**
1086
+ * Clear history
1087
+ */
1088
+ clearHistory?(): void;
1089
+ /**
1090
+ * Get available actions for current status
1091
+ */
1092
+ getAvailableActions?(status: string): string[];
1093
+ }
1094
+ interface TransitionHistoryEntry {
1095
+ from: string;
1096
+ to: string;
1097
+ action: string;
1098
+ timestamp: Date;
1099
+ metadata?: Record<string, unknown>;
1100
+ }
1101
+ /** Context passed to transition guards and actions */
1102
+ interface TransitionContext {
1103
+ from: string;
1104
+ to: string;
1105
+ action: string;
1106
+ data?: Record<string, unknown>;
1107
+ }
1108
+ type TransitionGuard = (context: TransitionContext) => boolean | Promise<boolean>;
1109
+ type TransitionAction = (context: TransitionContext) => void | Promise<void>;
1110
+ type TransitionConfig = Record<string, string[] | {
1111
+ from: string[];
1112
+ to?: string;
1113
+ guard?: TransitionGuard;
1114
+ before?: TransitionAction;
1115
+ after?: TransitionAction;
1116
+ }>;
1117
+ /**
1118
+ * Create a state machine for validating transitions
1119
+ *
1120
+ * @param name - Name of the state machine (used in error messages)
1121
+ * @param transitions - Map of actions to allowed source statuses
1122
+ * @param options - Additional options (history, guards, actions)
1123
+ * @returns State machine with can() and assert() methods
1124
+ *
1125
+ * @example
1126
+ * // Basic usage
1127
+ * const transferState = createStateMachine('Transfer', {
1128
+ * approve: ['draft'],
1129
+ * dispatch: ['approved'],
1130
+ * receive: ['dispatched', 'in_transit'],
1131
+ * cancel: ['draft', 'approved'],
1132
+ * });
1133
+ *
1134
+ * @example
1135
+ * // With guards and actions
1136
+ * const orderState = createStateMachine('Order', {
1137
+ * approve: {
1138
+ * from: ['pending'],
1139
+ * to: 'approved',
1140
+ * guard: ({ data }) => data.paymentConfirmed,
1141
+ * before: ({ from, to }) => console.log(`Approving order from ${from} to ${to}`),
1142
+ * after: ({ data }) => sendApprovalEmail(data.customerId),
1143
+ * },
1144
+ * }, { trackHistory: true });
1145
+ */
1146
+ declare function createStateMachine(name: string, transitions?: TransitionConfig, options?: {
1147
+ trackHistory?: boolean;
1148
+ }): StateMachine;
1149
+ //#endregion
1150
+ //#region src/utils/typeGuards.d.ts
1151
+ interface EventsDecorator {
1152
+ publish: <T>(type: string, payload: T, meta?: Record<string, unknown>) => Promise<void>;
1153
+ subscribe: (pattern: string, handler: (event: {
1154
+ type: string;
1155
+ payload: unknown;
1156
+ meta: Record<string, unknown>;
1157
+ }) => Promise<void>) => Promise<() => void>;
1158
+ transportName: string;
1159
+ }
1160
+ /** Check if fastify has the events plugin registered */
1161
+ declare function hasEvents(instance: FastifyInstance): instance is FastifyInstance & {
1162
+ events: EventsDecorator;
1163
+ };
1164
+ //#endregion
1165
+ //#region src/utils/userHelpers.d.ts
1166
+ /**
1167
+ * Extract a user ID from a user object. Accepts `id` or `_id` — returns
1168
+ * `undefined` when neither is present. Used by arc's controllers to
1169
+ * populate `createdBy` / `updatedBy` fields and for cache scoping.
1170
+ *
1171
+ * @example
1172
+ * ```ts
1173
+ * import { getUserId } from '@classytic/arc/utils';
1174
+ * const uid = getUserId(request.user);
1175
+ * ```
1176
+ */
1177
+ declare function getUserId(user: UserLike | null | undefined): string | undefined;
1178
+ //#endregion
1179
+ export { ValidationResult as $, handleRaw as A, CompensationStep as B, queryParams as C, ArcQueryParser as D, wrapResponse as E, defineErrorMapper as F, CircuitBreakerOptions as G, withCompensation as H, CompensationDefinition as I, CircuitState as J, CircuitBreakerRegistry as K, CompensationError as L, Guard as M, GuardConfig as N, ArcQueryParserOptions as O, defineGuard as P, ValidateOptions as Q, CompensationHooks as R, paginationSchema as S, successResponseSchema as T, CircuitBreaker as U, defineCompensation as V, CircuitBreakerError as W, createCircuitBreakerRegistry as X, createCircuitBreaker as Y, ConfigError as Z, getDefaultCrudSchemas as _, TransitionConfig as a, ErrorDetails as at, listResponse as b, JsonSchemaTarget as c, OrgAccessDeniedError as ct, isJsonSchema as d, ServiceUnavailableError as dt, assertValidConfig as et, isZodSchema as f, UnauthorizedError as ft, errorResponseSchema as g, isArcError as gt, deleteResponse as h, createError as ht, StateMachine as i, ConflictError as it, envelope as j, createQueryParser as k, convertOpenApiSchemas as l, OrgRequiredError as lt, JsonSchema as m, createDomainError as mt, EventsDecorator as n, validateResourceConfig as nt, createStateMachine as o, ForbiddenError as ot, toJsonSchema as p, ValidationError as pt, CircuitBreakerStats as q, hasEvents as r, ArcError as rt, simpleEqualityMatcher as s, NotFoundError as st, getUserId as t, formatValidationErrors as tt, convertRouteSchema as u, RateLimitError as ut, getListQueryParams as v, responses as w, mutationResponse as x, itemResponse as y, CompensationResult as z };