@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,172 @@
1
+ import "../elevation-DGo5shaX.mjs";
2
+ import { K as HookSystem, w as ResourceRegistry } from "../interface-e9XfSsUV.mjs";
3
+ import "../types-RLkFVgaw.mjs";
4
+ import { AdditionalRoute, AnyRecord, MiddlewareConfig, PresetHook, RouteSchemaOptions } from "../types/index.mjs";
5
+ import { t as ExternalOpenApiPaths } from "../externalPaths-SyPF2tgK.mjs";
6
+ import { a as ssePlugin, c as _default$1, i as _default$5, l as cachingPlugin, n as errorHandlerPlugin, o as CachingOptions, r as SSEOptions, s as CachingRule, t as ErrorHandlerOptions } from "../errorHandler-CW3OOeYq.mjs";
7
+ import { t as TracingOptions } from "../tracing-8CEbhF0w.mjs";
8
+ import { FastifyInstance, FastifyPluginAsync } from "fastify";
9
+
10
+ //#region src/plugins/requestId.d.ts
11
+ interface RequestIdOptions {
12
+ /** Header name to read/write request ID (default: 'x-request-id') */
13
+ header?: string;
14
+ /** Custom ID generator (default: crypto.randomUUID) */
15
+ generator?: () => string;
16
+ /** Whether to set response header (default: true) */
17
+ setResponseHeader?: boolean;
18
+ }
19
+ declare module 'fastify' {
20
+ interface FastifyRequest {
21
+ /** Unique request identifier for tracing */
22
+ requestId: string;
23
+ }
24
+ }
25
+ declare const requestIdPlugin: FastifyPluginAsync<RequestIdOptions>;
26
+ declare const _default$4: FastifyPluginAsync<RequestIdOptions>;
27
+ //#endregion
28
+ //#region src/plugins/health.d.ts
29
+ declare module 'fastify' {
30
+ interface FastifyRequest {
31
+ _startTime?: number;
32
+ }
33
+ }
34
+ interface HealthCheck {
35
+ /** Name of the dependency */
36
+ name: string;
37
+ /** Function that returns true if healthy, false otherwise */
38
+ check: () => Promise<boolean> | boolean;
39
+ /** Optional timeout in ms (default: 5000) */
40
+ timeout?: number;
41
+ /** Whether this check is critical for readiness (default: true) */
42
+ critical?: boolean;
43
+ }
44
+ interface HealthOptions {
45
+ /** Route prefix (default: '/_health') */
46
+ prefix?: string;
47
+ /** Health check dependencies */
48
+ checks?: HealthCheck[];
49
+ /** Enable metrics endpoint (default: false) */
50
+ metrics?: boolean;
51
+ /** Custom metrics collector function */
52
+ metricsCollector?: () => Promise<string> | string;
53
+ /** Version info to include in responses */
54
+ version?: string;
55
+ /** Collect HTTP request metrics (default: true if metrics enabled) */
56
+ collectHttpMetrics?: boolean;
57
+ }
58
+ declare const healthPlugin: FastifyPluginAsync<HealthOptions>;
59
+ declare const _default$3: FastifyPluginAsync<HealthOptions>;
60
+ //#endregion
61
+ //#region src/plugins/gracefulShutdown.d.ts
62
+ interface GracefulShutdownOptions {
63
+ /** Maximum time to wait for graceful shutdown in ms (default: 30000) */
64
+ timeout?: number;
65
+ /** Custom cleanup function called before exit */
66
+ onShutdown?: () => Promise<void> | void;
67
+ /** Signals to handle (default: ['SIGTERM', 'SIGINT']) */
68
+ signals?: NodeJS.Signals[];
69
+ /** Whether to log shutdown events (default: true) */
70
+ logEvents?: boolean;
71
+ /**
72
+ * Called when shutdown times out or encounters an error.
73
+ * Defaults to `process.exit(1)` — appropriate for production but dangerous in:
74
+ * - **Tests**: kills the test runner. Pass `() => {}` or `() => { throw … }`.
75
+ * - **Shared runtimes** (e.g., serverless): may kill unrelated handlers.
76
+ *
77
+ * @param reason - `'timeout'` if shutdown exceeded `timeout` ms,
78
+ * `'error'` if `onShutdown` or `fastify.close()` threw.
79
+ */
80
+ onForceExit?: (reason: 'timeout' | 'error') => void;
81
+ }
82
+ declare const gracefulShutdownPlugin: FastifyPluginAsync<GracefulShutdownOptions>;
83
+ declare module 'fastify' {
84
+ interface FastifyInstance {
85
+ /** Trigger graceful shutdown manually */
86
+ shutdown: () => Promise<void>;
87
+ }
88
+ }
89
+ declare const _default$2: FastifyPluginAsync<GracefulShutdownOptions>;
90
+ //#endregion
91
+ //#region src/plugins/createPlugin.d.ts
92
+ interface PluginResourceResult {
93
+ /** Additional routes to add to the resource */
94
+ additionalRoutes?: AdditionalRoute[];
95
+ /** Middlewares per operation */
96
+ middlewares?: MiddlewareConfig;
97
+ /** Hooks to register */
98
+ hooks?: PresetHook[];
99
+ /** Schema options to merge */
100
+ schemaOptions?: RouteSchemaOptions;
101
+ }
102
+ interface CreatePluginDefinition<TRootOpts extends AnyRecord = AnyRecord, TResourceOpts extends AnyRecord = AnyRecord> {
103
+ /**
104
+ * Global setup function. Called once when the plugin is registered on the Fastify instance.
105
+ * Use this for database connections, decorators, shared state, etc.
106
+ */
107
+ forRoot?: (fastify: FastifyInstance, opts: TRootOpts) => void | Promise<void>;
108
+ /**
109
+ * Per-resource configuration function. Called for each resource that uses this plugin.
110
+ * Returns hooks, routes, middlewares, etc. to merge into the resource config.
111
+ */
112
+ forResource?: (resourceConfig: AnyRecord, opts: TResourceOpts) => PluginResourceResult;
113
+ }
114
+ interface ArcPlugin<TRootOpts extends AnyRecord = AnyRecord, TResourceOpts extends AnyRecord = AnyRecord> {
115
+ /** Plugin name */
116
+ readonly name: string;
117
+ /**
118
+ * Register the plugin globally on a Fastify instance.
119
+ * Returns a Fastify plugin that can be passed to `app.register()`.
120
+ */
121
+ forRoot(opts?: TRootOpts): FastifyPluginAsync<TRootOpts>;
122
+ /**
123
+ * Apply per-resource configuration.
124
+ * Returns a partial resource config to spread into `defineResource()`.
125
+ */
126
+ forResource(opts?: TResourceOpts): PluginResourceResult;
127
+ }
128
+ /**
129
+ * Create a structured plugin with forRoot (global) and forResource (per-resource) support.
130
+ *
131
+ * @param name - Plugin name (used for Fastify registration and debugging)
132
+ * @param definition - Plugin setup functions
133
+ * @returns ArcPlugin with forRoot() and forResource() methods
134
+ */
135
+ declare function createPlugin<TRootOpts extends AnyRecord = AnyRecord, TResourceOpts extends AnyRecord = AnyRecord>(name: string, definition: CreatePluginDefinition<TRootOpts, TResourceOpts>): ArcPlugin<TRootOpts, TResourceOpts>;
136
+ //#endregion
137
+ //#region src/core/arcCorePlugin.d.ts
138
+ interface ArcCorePluginOptions {
139
+ /** Enable event emission for CRUD operations (requires eventPlugin) */
140
+ emitEvents?: boolean;
141
+ /** Hook system instance (for testing/custom setup) */
142
+ hookSystem?: HookSystem;
143
+ /** Resource registry instance (for testing/custom setup) */
144
+ registry?: ResourceRegistry;
145
+ }
146
+ interface PluginMeta {
147
+ name: string;
148
+ version?: string;
149
+ options?: Record<string, unknown>;
150
+ registeredAt: string;
151
+ }
152
+ interface ArcCore {
153
+ /** Instance-scoped hook system */
154
+ hooks: HookSystem;
155
+ /** Instance-scoped resource registry */
156
+ registry: ResourceRegistry;
157
+ /** Whether event emission is enabled */
158
+ emitEvents: boolean;
159
+ /** External OpenAPI paths contributed by auth adapters or third-party integrations */
160
+ externalOpenApiPaths: ExternalOpenApiPaths[];
161
+ /** Registered plugins for introspection */
162
+ plugins: Map<string, PluginMeta>;
163
+ }
164
+ declare module 'fastify' {
165
+ interface FastifyInstance {
166
+ arc: ArcCore;
167
+ }
168
+ }
169
+ declare const arcCorePlugin: FastifyPluginAsync<ArcCorePluginOptions>;
170
+ declare const _default: FastifyPluginAsync<ArcCorePluginOptions>;
171
+ //#endregion
172
+ export { type ArcCore, type ArcCorePluginOptions, type ArcPlugin, type CachingOptions, type CachingRule, type CreatePluginDefinition, type ErrorHandlerOptions, type GracefulShutdownOptions, type HealthCheck, type HealthOptions, type PluginMeta, type PluginResourceResult, type RequestIdOptions, type SSEOptions, type TracingOptions, _default as arcCorePlugin, arcCorePlugin as arcCorePluginFn, _default$1 as cachingPlugin, cachingPlugin as cachingPluginFn, createPlugin, errorHandlerPlugin, errorHandlerPlugin as errorHandlerPluginFn, _default$2 as gracefulShutdownPlugin, gracefulShutdownPlugin as gracefulShutdownPluginFn, _default$3 as healthPlugin, healthPlugin as healthPluginFn, _default$4 as requestIdPlugin, requestIdPlugin as requestIdPluginFn, _default$5 as ssePlugin, ssePlugin as ssePluginFn };
@@ -0,0 +1,522 @@
1
+ import { p as MUTATION_OPERATIONS } from "../constants-DdXFXQtN.mjs";
2
+ import { r as getOrgId } from "../types-Beqn1Un7.mjs";
3
+ import { t as HookSystem } from "../HookSystem-BsGV-j2l.mjs";
4
+ import { t as hasEvents } from "../typeGuards-DwxA1t_L.mjs";
5
+ import { t as requestContext } from "../requestContext-xi6OKBL-.mjs";
6
+ import { t as ResourceRegistry } from "../ResourceRegistry-7Ic20ZMw.mjs";
7
+ import { t as errorHandlerPlugin } from "../errorHandler-C3GY3_ow.mjs";
8
+ import { n as caching_default, t as cachingPlugin } from "../caching-GSDJcA6-.mjs";
9
+ import { n as sse_default, t as ssePlugin } from "../sse-DkqQ1uxb.mjs";
10
+ import fp from "fastify-plugin";
11
+ import { randomUUID } from "crypto";
12
+
13
+ //#region src/plugins/requestId.ts
14
+ /**
15
+ * Request ID Plugin
16
+ *
17
+ * Propagates request IDs for distributed tracing.
18
+ * - Accepts incoming x-request-id header
19
+ * - Generates UUID if not provided
20
+ * - Attaches to request.id and response header
21
+ *
22
+ * @example
23
+ * import { requestIdPlugin } from '@classytic/arc';
24
+ *
25
+ * await fastify.register(requestIdPlugin);
26
+ *
27
+ * // In handlers, access via request.id
28
+ * fastify.get('/', async (request) => {
29
+ * console.log(request.id); // UUID
30
+ * });
31
+ */
32
+ const requestIdPlugin = async (fastify, opts = {}) => {
33
+ const { header = "x-request-id", generator = randomUUID, setResponseHeader = true } = opts;
34
+ if (!fastify.hasRequestDecorator("requestId")) fastify.decorateRequest("requestId", "");
35
+ fastify.addHook("onRequest", async (request) => {
36
+ const incomingId = request.headers[header];
37
+ const sanitized = typeof incomingId === "string" ? incomingId.trim() : "";
38
+ const requestId = sanitized.length > 0 && sanitized.length <= 128 && /^[\w.:-]+$/.test(sanitized) ? sanitized : generator();
39
+ request.id = requestId;
40
+ request.requestId = requestId;
41
+ });
42
+ if (setResponseHeader) fastify.addHook("onSend", async (request, reply) => {
43
+ reply.header(header, request.requestId);
44
+ });
45
+ fastify.log?.debug?.("Request ID plugin registered");
46
+ };
47
+ var requestId_default = fp(requestIdPlugin, {
48
+ name: "arc-request-id",
49
+ fastify: "5.x"
50
+ });
51
+
52
+ //#endregion
53
+ //#region src/plugins/health.ts
54
+ /**
55
+ * Health Check Plugin
56
+ *
57
+ * Kubernetes-ready health endpoints:
58
+ * - /health/live - Liveness probe (is the process alive?)
59
+ * - /health/ready - Readiness probe (can we serve traffic?)
60
+ * - /health/metrics - Prometheus metrics (optional)
61
+ *
62
+ * @example
63
+ * import { healthPlugin } from '@classytic/arc';
64
+ *
65
+ * await fastify.register(healthPlugin, {
66
+ * prefix: '/_health',
67
+ * checks: [
68
+ * { name: 'mongodb', check: async () => mongoose.connection.readyState === 1 },
69
+ * { name: 'redis', check: async () => redis.ping() === 'PONG' },
70
+ * ],
71
+ * });
72
+ */
73
+ function createHttpMetrics() {
74
+ return {
75
+ requestsTotal: {},
76
+ requestDurations: [],
77
+ _ringIndex: 0,
78
+ startTime: Date.now()
79
+ };
80
+ }
81
+ const healthPlugin = async (fastify, opts = {}) => {
82
+ const { prefix = "/_health", checks = [], metrics = false, metricsCollector, version, collectHttpMetrics = metrics } = opts;
83
+ const httpMetrics = createHttpMetrics();
84
+ fastify.get(`${prefix}/live`, { schema: {
85
+ tags: ["Health"],
86
+ summary: "Liveness probe",
87
+ description: "Returns 200 if the process is alive",
88
+ response: { 200: {
89
+ type: "object",
90
+ properties: {
91
+ status: {
92
+ type: "string",
93
+ enum: ["ok"]
94
+ },
95
+ timestamp: { type: "string" },
96
+ version: { type: "string" }
97
+ }
98
+ } }
99
+ } }, async () => {
100
+ return {
101
+ status: "ok",
102
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
103
+ ...version ? { version } : {}
104
+ };
105
+ });
106
+ fastify.get(`${prefix}/ready`, { schema: {
107
+ tags: ["Health"],
108
+ summary: "Readiness probe",
109
+ description: "Returns 200 if all dependencies are healthy",
110
+ response: {
111
+ 200: {
112
+ type: "object",
113
+ properties: {
114
+ status: {
115
+ type: "string",
116
+ enum: ["ready", "not_ready"]
117
+ },
118
+ timestamp: { type: "string" },
119
+ checks: {
120
+ type: "array",
121
+ items: {
122
+ type: "object",
123
+ properties: {
124
+ name: { type: "string" },
125
+ healthy: { type: "boolean" },
126
+ duration: { type: "number" },
127
+ error: { type: "string" }
128
+ }
129
+ }
130
+ }
131
+ }
132
+ },
133
+ 503: {
134
+ type: "object",
135
+ properties: {
136
+ status: {
137
+ type: "string",
138
+ enum: ["not_ready"]
139
+ },
140
+ timestamp: { type: "string" },
141
+ checks: { type: "array" }
142
+ }
143
+ }
144
+ }
145
+ } }, async (_, reply) => {
146
+ const results = await runChecks(checks);
147
+ const criticalFailed = results.some((r) => !r.healthy && (checks.find((c) => c.name === r.name)?.critical ?? true));
148
+ const response = {
149
+ status: criticalFailed ? "not_ready" : "ready",
150
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
151
+ checks: results
152
+ };
153
+ if (criticalFailed) reply.code(503);
154
+ return response;
155
+ });
156
+ if (metrics) fastify.get(`${prefix}/metrics`, async (_, reply) => {
157
+ reply.type("text/plain; charset=utf-8");
158
+ if (metricsCollector) return await metricsCollector();
159
+ const uptime = process.uptime();
160
+ const memory = process.memoryUsage();
161
+ const cpu = process.cpuUsage();
162
+ const lines = [
163
+ "# HELP process_uptime_seconds Process uptime in seconds",
164
+ "# TYPE process_uptime_seconds gauge",
165
+ `process_uptime_seconds ${uptime.toFixed(2)}`,
166
+ "",
167
+ "# HELP process_memory_heap_bytes Heap memory usage in bytes",
168
+ "# TYPE process_memory_heap_bytes gauge",
169
+ `process_memory_heap_bytes{type="used"} ${memory.heapUsed}`,
170
+ `process_memory_heap_bytes{type="total"} ${memory.heapTotal}`,
171
+ "",
172
+ "# HELP process_memory_rss_bytes RSS memory in bytes",
173
+ "# TYPE process_memory_rss_bytes gauge",
174
+ `process_memory_rss_bytes ${memory.rss}`,
175
+ "",
176
+ "# HELP process_memory_external_bytes External memory in bytes",
177
+ "# TYPE process_memory_external_bytes gauge",
178
+ `process_memory_external_bytes ${memory.external}`,
179
+ "",
180
+ "# HELP process_cpu_user_microseconds User CPU time in microseconds",
181
+ "# TYPE process_cpu_user_microseconds counter",
182
+ `process_cpu_user_microseconds ${cpu.user}`,
183
+ "",
184
+ "# HELP process_cpu_system_microseconds System CPU time in microseconds",
185
+ "# TYPE process_cpu_system_microseconds counter",
186
+ `process_cpu_system_microseconds ${cpu.system}`,
187
+ ""
188
+ ];
189
+ if (collectHttpMetrics && Object.keys(httpMetrics.requestsTotal).length > 0) {
190
+ lines.push("# HELP http_requests_total Total HTTP requests by status code", "# TYPE http_requests_total counter");
191
+ for (const [status, count] of Object.entries(httpMetrics.requestsTotal)) lines.push(`http_requests_total{status="${status}"} ${count}`);
192
+ lines.push("");
193
+ if (httpMetrics.requestDurations.length > 0) {
194
+ const sorted = [...httpMetrics.requestDurations].sort((a, b) => a - b);
195
+ const p50 = sorted[Math.floor(sorted.length * .5)] || 0;
196
+ const p95 = sorted[Math.floor(sorted.length * .95)] || 0;
197
+ const p99 = sorted[Math.floor(sorted.length * .99)] || 0;
198
+ const sum = sorted.reduce((a, b) => a + b, 0);
199
+ lines.push("# HELP http_request_duration_milliseconds HTTP request duration", "# TYPE http_request_duration_milliseconds summary", `http_request_duration_milliseconds{quantile="0.5"} ${p50.toFixed(2)}`, `http_request_duration_milliseconds{quantile="0.95"} ${p95.toFixed(2)}`, `http_request_duration_milliseconds{quantile="0.99"} ${p99.toFixed(2)}`, `http_request_duration_milliseconds_sum ${sum.toFixed(2)}`, `http_request_duration_milliseconds_count ${sorted.length}`, "");
200
+ }
201
+ }
202
+ return lines.join("\n");
203
+ });
204
+ if (collectHttpMetrics) {
205
+ fastify.addHook("onRequest", async (request) => {
206
+ request._startTime = Date.now();
207
+ });
208
+ fastify.addHook("onResponse", async (request, reply) => {
209
+ const duration = Date.now() - (request._startTime ?? Date.now());
210
+ const statusBucket = `${Math.floor(reply.statusCode / 100)}xx`;
211
+ httpMetrics.requestsTotal[statusBucket] = (httpMetrics.requestsTotal[statusBucket] || 0) + 1;
212
+ if (httpMetrics.requestDurations.length < 1e4) httpMetrics.requestDurations.push(duration);
213
+ else httpMetrics.requestDurations[httpMetrics._ringIndex % 1e4] = duration;
214
+ httpMetrics._ringIndex = httpMetrics._ringIndex + 1;
215
+ });
216
+ }
217
+ fastify.log?.debug?.(`Health plugin registered at ${prefix}`);
218
+ };
219
+ /**
220
+ * Run all health checks with timeout
221
+ */
222
+ async function runChecks(checks) {
223
+ const results = [];
224
+ for (const check of checks) {
225
+ const start = Date.now();
226
+ const timeout = check.timeout ?? 5e3;
227
+ let timer;
228
+ try {
229
+ const checkPromise = Promise.resolve(check.check());
230
+ const timeoutPromise = new Promise((_, reject) => {
231
+ timer = setTimeout(() => reject(/* @__PURE__ */ new Error("Health check timeout")), timeout);
232
+ });
233
+ const healthy = await Promise.race([checkPromise, timeoutPromise]);
234
+ results.push({
235
+ name: check.name,
236
+ healthy: Boolean(healthy),
237
+ duration: Date.now() - start
238
+ });
239
+ } catch (err) {
240
+ results.push({
241
+ name: check.name,
242
+ healthy: false,
243
+ duration: Date.now() - start,
244
+ error: err.message
245
+ });
246
+ } finally {
247
+ if (timer) clearTimeout(timer);
248
+ }
249
+ }
250
+ return results;
251
+ }
252
+ var health_default = fp(healthPlugin, {
253
+ name: "arc-health",
254
+ fastify: "5.x"
255
+ });
256
+
257
+ //#endregion
258
+ //#region src/plugins/gracefulShutdown.ts
259
+ /**
260
+ * Graceful Shutdown Plugin
261
+ *
262
+ * Handles SIGTERM and SIGINT signals for clean shutdown:
263
+ * - Stops accepting new connections
264
+ * - Waits for in-flight requests to complete
265
+ * - Closes database connections
266
+ * - Exits cleanly
267
+ *
268
+ * Essential for Kubernetes deployments.
269
+ *
270
+ * @example
271
+ * import { gracefulShutdownPlugin } from '@classytic/arc';
272
+ *
273
+ * // Production
274
+ * await fastify.register(gracefulShutdownPlugin, {
275
+ * timeout: 30000, // 30 seconds max
276
+ * onShutdown: async () => {
277
+ * await mongoose.disconnect();
278
+ * await redis.quit();
279
+ * },
280
+ * });
281
+ *
282
+ * // Tests — prevent process.exit from killing the runner
283
+ * await fastify.register(gracefulShutdownPlugin, {
284
+ * onForceExit: () => {},
285
+ * });
286
+ */
287
+ const gracefulShutdownPlugin = async (fastify, opts = {}) => {
288
+ const { timeout = 3e4, onShutdown, signals = ["SIGTERM", "SIGINT"], logEvents = true, onForceExit = () => process.exit(1) } = opts;
289
+ let isShuttingDown = false;
290
+ const signalHandlers = /* @__PURE__ */ new Map();
291
+ const shutdown = async (signal) => {
292
+ if (isShuttingDown) {
293
+ if (logEvents) fastify.log?.warn?.({ signal }, "Shutdown already in progress, ignoring signal");
294
+ return;
295
+ }
296
+ isShuttingDown = true;
297
+ if (logEvents) fastify.log?.info?.({
298
+ signal,
299
+ timeout
300
+ }, "Shutdown signal received, starting graceful shutdown");
301
+ const forceExitTimer = setTimeout(() => {
302
+ if (logEvents) fastify.log?.error?.("Graceful shutdown timeout exceeded, forcing exit");
303
+ onForceExit("timeout");
304
+ }, timeout);
305
+ forceExitTimer.unref();
306
+ try {
307
+ if (logEvents) fastify.log?.info?.("Closing server to new connections");
308
+ await fastify.close();
309
+ if (onShutdown) {
310
+ if (logEvents) fastify.log?.info?.("Running custom shutdown handler");
311
+ await onShutdown();
312
+ }
313
+ if (logEvents) fastify.log?.info?.("Graceful shutdown complete");
314
+ clearTimeout(forceExitTimer);
315
+ } catch (err) {
316
+ if (logEvents) fastify.log?.error?.({ error: err.message }, "Error during shutdown");
317
+ clearTimeout(forceExitTimer);
318
+ onForceExit("error");
319
+ }
320
+ };
321
+ for (const signal of signals) {
322
+ const handler = () => {
323
+ shutdown(signal);
324
+ };
325
+ signalHandlers.set(signal, handler);
326
+ process.on(signal, handler);
327
+ }
328
+ fastify.addHook("onClose", async () => {
329
+ for (const [signal, handler] of signalHandlers) process.removeListener(signal, handler);
330
+ signalHandlers.clear();
331
+ });
332
+ fastify.decorate("shutdown", async () => {
333
+ await shutdown("MANUAL");
334
+ });
335
+ if (logEvents) fastify.log?.debug?.({ signals }, "Graceful shutdown plugin registered");
336
+ };
337
+ var gracefulShutdown_default = fp(gracefulShutdownPlugin, {
338
+ name: "arc-graceful-shutdown",
339
+ fastify: "5.x"
340
+ });
341
+
342
+ //#endregion
343
+ //#region src/plugins/createPlugin.ts
344
+ /**
345
+ * createPlugin() — forRoot/forFeature Pattern
346
+ *
347
+ * Standard pattern for plugins that need both global setup and per-resource configuration.
348
+ * Inspired by NestJS forRoot/forFeature but simpler — plain functions, no decorators.
349
+ *
350
+ * @example
351
+ * ```typescript
352
+ * // Define a plugin with global + per-resource config
353
+ * const analytics = createPlugin('analytics', {
354
+ * forRoot: async (fastify, opts) => {
355
+ * // Global setup: connect to analytics service, add decorators
356
+ * const client = new AnalyticsClient(opts.apiKey);
357
+ * fastify.decorate('analytics', client);
358
+ * },
359
+ * forResource: (resourceConfig, opts) => {
360
+ * // Per-resource: return hooks, middleware, or routes
361
+ * return {
362
+ * hooks: [{
363
+ * operation: 'create', phase: 'after', priority: 100,
364
+ * handler: (ctx) => client.track('created', ctx.result),
365
+ * }],
366
+ * };
367
+ * },
368
+ * });
369
+ *
370
+ * // Usage — register globally once
371
+ * await app.register(analytics.forRoot({ apiKey: 'xxx' }));
372
+ *
373
+ * // Then apply per-resource
374
+ * const productResource = defineResource({
375
+ * name: 'product',
376
+ * adapter: productAdapter,
377
+ * ...analytics.forResource({ trackEvents: true }),
378
+ * });
379
+ * ```
380
+ */
381
+ /**
382
+ * Create a structured plugin with forRoot (global) and forResource (per-resource) support.
383
+ *
384
+ * @param name - Plugin name (used for Fastify registration and debugging)
385
+ * @param definition - Plugin setup functions
386
+ * @returns ArcPlugin with forRoot() and forResource() methods
387
+ */
388
+ function createPlugin(name, definition) {
389
+ return {
390
+ name,
391
+ forRoot(opts) {
392
+ const plugin = async (fastify, pluginOpts) => {
393
+ const mergedOpts = {
394
+ ...opts,
395
+ ...pluginOpts
396
+ };
397
+ if (definition.forRoot) await definition.forRoot(fastify, mergedOpts);
398
+ };
399
+ return fp(plugin, {
400
+ name: `arc-plugin-${name}`,
401
+ fastify: "5.x"
402
+ });
403
+ },
404
+ forResource(opts) {
405
+ if (!definition.forResource) return {};
406
+ return definition.forResource({}, opts ?? {});
407
+ }
408
+ };
409
+ }
410
+
411
+ //#endregion
412
+ //#region src/core/arcCorePlugin.ts
413
+ /**
414
+ * Arc Core Plugin
415
+ *
416
+ * Sets up instance-scoped Arc systems:
417
+ * - HookSystem: Lifecycle hooks per app instance
418
+ * - ResourceRegistry: Resource tracking per app instance
419
+ * - Event integration: Wires CRUD operations to fastify.events
420
+ *
421
+ * This solves the global singleton leak problem where multiple
422
+ * app instances (e.g., in tests) would share state.
423
+ *
424
+ * @example
425
+ * import { arcCorePlugin } from '@classytic/arc';
426
+ *
427
+ * const app = Fastify();
428
+ * await app.register(arcCorePlugin);
429
+ *
430
+ * // Now use instance-scoped hooks
431
+ * app.arc.hooks.before('product', 'create', async (ctx) => {
432
+ * ctx.data.slug = slugify(ctx.data.name);
433
+ * });
434
+ */
435
+ const arcCorePlugin = async (fastify, opts = {}) => {
436
+ const { emitEvents = true, hookSystem, registry } = opts;
437
+ const actualHookSystem = hookSystem ?? new HookSystem();
438
+ const actualRegistry = registry ?? new ResourceRegistry();
439
+ fastify.decorate("arc", {
440
+ hooks: actualHookSystem,
441
+ registry: actualRegistry,
442
+ emitEvents,
443
+ externalOpenApiPaths: [],
444
+ plugins: /* @__PURE__ */ new Map()
445
+ });
446
+ fastify.addHook("onRequest", (request, _reply, done) => {
447
+ const store = {
448
+ requestId: request.id,
449
+ startTime: performance.now()
450
+ };
451
+ requestContext.storage.run(store, done);
452
+ });
453
+ fastify.addHook("preHandler", (request, _reply, done) => {
454
+ const store = requestContext.get();
455
+ if (store) {
456
+ store.user = request.user ?? null;
457
+ store.organizationId = request.scope?.kind === "member" ? request.scope.organizationId : request.scope?.kind === "elevated" ? request.scope.organizationId : void 0;
458
+ }
459
+ done();
460
+ });
461
+ if (emitEvents) {
462
+ const eventOperations = MUTATION_OPERATIONS;
463
+ for (const operation of eventOperations) actualHookSystem.after("*", operation, async (ctx) => {
464
+ if (!hasEvents(fastify)) return;
465
+ const store = requestContext.get();
466
+ const eventType = `${ctx.resource}.${operation}d`;
467
+ const userId = ctx.user?.id ?? ctx.user?._id;
468
+ const organizationId = ctx.context?._scope ? getOrgId(ctx.context._scope) : void 0;
469
+ const payload = {
470
+ resource: ctx.resource,
471
+ operation: ctx.operation,
472
+ data: ctx.result,
473
+ userId,
474
+ organizationId,
475
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
476
+ };
477
+ try {
478
+ await fastify.events.publish(eventType, payload, {
479
+ correlationId: store?.requestId,
480
+ resource: ctx.resource,
481
+ resourceId: extractId(ctx.result),
482
+ userId: userId ? String(userId) : void 0,
483
+ organizationId
484
+ });
485
+ } catch (error) {
486
+ fastify.log?.warn?.({
487
+ eventType,
488
+ error
489
+ }, "Failed to emit event");
490
+ }
491
+ });
492
+ }
493
+ fastify.addHook("onReady", async () => {
494
+ if (!hasEvents(fastify)) return;
495
+ try {
496
+ await fastify.events.publish("arc.ready", {
497
+ resources: actualRegistry.getAll().length,
498
+ hooks: actualHookSystem.getAll().length,
499
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
500
+ });
501
+ } catch {}
502
+ });
503
+ fastify.addHook("onClose", async () => {
504
+ actualHookSystem.clear();
505
+ actualRegistry._clear();
506
+ });
507
+ fastify.log?.debug?.("Arc core plugin enabled (instance-scoped hooks & registry)");
508
+ };
509
+ /** Extract document ID from a result (handles Mongoose docs and plain objects) */
510
+ function extractId(doc) {
511
+ if (!doc || typeof doc !== "object") return void 0;
512
+ const d = doc;
513
+ const rawId = d._id ?? d.id;
514
+ return rawId ? String(rawId) : void 0;
515
+ }
516
+ var arcCorePlugin_default = fp(arcCorePlugin, {
517
+ name: "arc-core",
518
+ fastify: "5.x"
519
+ });
520
+
521
+ //#endregion
522
+ export { arcCorePlugin_default as arcCorePlugin, arcCorePlugin as arcCorePluginFn, caching_default as cachingPlugin, cachingPlugin as cachingPluginFn, createPlugin, errorHandlerPlugin, errorHandlerPlugin as errorHandlerPluginFn, gracefulShutdown_default as gracefulShutdownPlugin, gracefulShutdownPlugin as gracefulShutdownPluginFn, health_default as healthPlugin, healthPlugin as healthPluginFn, requestId_default as requestIdPlugin, requestIdPlugin as requestIdPluginFn, sse_default as ssePlugin, ssePlugin as ssePluginFn };