@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,81 @@
1
+ import "../elevation-DGo5shaX.mjs";
2
+ import "../interface-e9XfSsUV.mjs";
3
+ import "../types-RLkFVgaw.mjs";
4
+ import { a as AuditContext, c as AuditStore, i as AuditAction, l as AuditStoreOptions, n as MongoAuditStoreOptions, o as AuditEntry, r as MongoConnection, s as AuditQueryOptions, u as createAuditEntry } from "../mongodb-ClykrfGo.mjs";
5
+ import { FastifyPluginAsync } from "fastify";
6
+
7
+ //#region src/audit/auditPlugin.d.ts
8
+ interface AuditPluginOptions {
9
+ /** Enable audit logging (default: false) */
10
+ enabled?: boolean;
11
+ /** Storage backends to use */
12
+ stores?: ('memory' | 'mongodb')[];
13
+ /** MongoDB connection (required if using mongodb store) */
14
+ mongoConnection?: MongoConnection;
15
+ /** MongoDB collection name (default: 'audit_logs') */
16
+ mongoCollection?: string;
17
+ /** TTL in days for MongoDB (default: 90) */
18
+ ttlDays?: number;
19
+ /** Custom stores (advanced) */
20
+ customStores?: AuditStore[];
21
+ /**
22
+ * Automatically audit CRUD operations via the hook system (default: true when enabled).
23
+ * When enabled, create/update/delete operations are auto-logged without manual calls.
24
+ *
25
+ * - `true`: Auto-audit all CRUD operations on all resources
26
+ * - `{ operations: ['create', 'delete'] }`: Only auto-audit specific operations
27
+ * - `{ exclude: ['health', 'metrics'] }`: Skip specific resources
28
+ * - `false`: Disable auto-audit (manual calls only)
29
+ */
30
+ autoAudit?: boolean | {
31
+ operations?: ('create' | 'update' | 'delete')[];
32
+ exclude?: string[];
33
+ };
34
+ }
35
+ declare module 'fastify' {
36
+ interface FastifyInstance {
37
+ /** Log an audit entry */
38
+ audit: AuditLogger;
39
+ }
40
+ interface FastifyRequest {
41
+ /** Audit context for current request */
42
+ auditContext?: AuditContext;
43
+ }
44
+ }
45
+ interface AuditLogger {
46
+ /** Log a create action */
47
+ create: (resource: string, documentId: string, data: Record<string, unknown>, context?: AuditContext) => Promise<void>;
48
+ /** Log an update action */
49
+ update: (resource: string, documentId: string, before: Record<string, unknown>, after: Record<string, unknown>, context?: AuditContext) => Promise<void>;
50
+ /** Log a delete action */
51
+ delete: (resource: string, documentId: string, data: Record<string, unknown>, context?: AuditContext) => Promise<void>;
52
+ /** Log a restore action (soft delete undo) */
53
+ restore: (resource: string, documentId: string, data: Record<string, unknown>, context?: AuditContext) => Promise<void>;
54
+ /** Log a custom action */
55
+ custom: (resource: string, documentId: string, action: string, data?: Record<string, unknown>, context?: AuditContext) => Promise<void>;
56
+ /** Query audit logs (if stores support it) */
57
+ query: (options: AuditQueryOptions) => Promise<AuditEntry[]>;
58
+ }
59
+ declare const auditPlugin: FastifyPluginAsync<AuditPluginOptions>;
60
+ declare const _default: FastifyPluginAsync<AuditPluginOptions>;
61
+ //#endregion
62
+ //#region src/audit/stores/memory.d.ts
63
+ interface MemoryAuditStoreOptions {
64
+ /** Maximum entries to keep (default: 1000) */
65
+ maxEntries?: number;
66
+ }
67
+ declare class MemoryAuditStore implements AuditStore {
68
+ readonly name = "memory";
69
+ private entries;
70
+ private maxEntries;
71
+ constructor(options?: MemoryAuditStoreOptions);
72
+ log(entry: AuditEntry): Promise<void>;
73
+ query(options?: AuditQueryOptions): Promise<AuditEntry[]>;
74
+ close(): Promise<void>;
75
+ /** Get all entries (for testing) */
76
+ getAll(): AuditEntry[];
77
+ /** Clear all entries (for testing) */
78
+ clear(): void;
79
+ }
80
+ //#endregion
81
+ export { type AuditAction, type AuditContext, type AuditEntry, type AuditLogger, type AuditPluginOptions, type AuditQueryOptions, type AuditStore, type AuditStoreOptions, MemoryAuditStore, type MemoryAuditStoreOptions, type MongoAuditStoreOptions, _default as auditPlugin, auditPlugin as auditPluginFn, createAuditEntry };
@@ -0,0 +1,275 @@
1
+ import { t as MongoAuditStore } from "../mongodb-DNKEExbf.mjs";
2
+ import fp from "fastify-plugin";
3
+
4
+ //#region src/audit/stores/interface.ts
5
+ /**
6
+ * Create audit entry from context
7
+ */
8
+ function createAuditEntry(resource, documentId, action, context, data) {
9
+ const changes = data?.before && data?.after ? detectChanges(data.before, data.after) : void 0;
10
+ return {
11
+ id: generateAuditId(),
12
+ resource,
13
+ documentId,
14
+ action,
15
+ userId: context.user?._id?.toString() ?? context.user?.id,
16
+ organizationId: context.organizationId,
17
+ before: data?.before,
18
+ after: data?.after,
19
+ changes,
20
+ requestId: context.requestId,
21
+ ipAddress: context.ipAddress,
22
+ userAgent: context.userAgent,
23
+ metadata: data?.metadata,
24
+ timestamp: /* @__PURE__ */ new Date()
25
+ };
26
+ }
27
+ /**
28
+ * Detect changed fields between two objects
29
+ */
30
+ function detectChanges(before, after) {
31
+ const changes = [];
32
+ const allKeys = new Set([...Object.keys(before), ...Object.keys(after)]);
33
+ for (const key of allKeys) {
34
+ if (key.startsWith("_") || key === "updatedAt") continue;
35
+ if (JSON.stringify(before[key]) !== JSON.stringify(after[key])) changes.push(key);
36
+ }
37
+ return changes;
38
+ }
39
+ /**
40
+ * Generate unique audit ID
41
+ */
42
+ function generateAuditId() {
43
+ return `aud_${Date.now().toString(36)}${Math.random().toString(36).substring(2, 10)}`;
44
+ }
45
+
46
+ //#endregion
47
+ //#region src/audit/stores/memory.ts
48
+ var MemoryAuditStore = class {
49
+ name = "memory";
50
+ entries = [];
51
+ maxEntries;
52
+ constructor(options = {}) {
53
+ this.maxEntries = options.maxEntries ?? 1e3;
54
+ }
55
+ async log(entry) {
56
+ this.entries.unshift(entry);
57
+ if (this.entries.length > this.maxEntries) this.entries = this.entries.slice(0, this.maxEntries);
58
+ }
59
+ async query(options = {}) {
60
+ let results = [...this.entries];
61
+ if (options.resource) results = results.filter((e) => e.resource === options.resource);
62
+ if (options.documentId) results = results.filter((e) => e.documentId === options.documentId);
63
+ if (options.userId) results = results.filter((e) => e.userId === options.userId);
64
+ if (options.organizationId) results = results.filter((e) => e.organizationId === options.organizationId);
65
+ if (options.action) {
66
+ const actions = Array.isArray(options.action) ? options.action : [options.action];
67
+ results = results.filter((e) => actions.includes(e.action));
68
+ }
69
+ if (options.from) results = results.filter((e) => e.timestamp >= options.from);
70
+ if (options.to) results = results.filter((e) => e.timestamp <= options.to);
71
+ const offset = options.offset ?? 0;
72
+ const limit = options.limit ?? 100;
73
+ results = results.slice(offset, offset + limit);
74
+ return results;
75
+ }
76
+ async close() {
77
+ this.entries = [];
78
+ }
79
+ /** Get all entries (for testing) */
80
+ getAll() {
81
+ return [...this.entries];
82
+ }
83
+ /** Clear all entries (for testing) */
84
+ clear() {
85
+ this.entries = [];
86
+ }
87
+ };
88
+
89
+ //#endregion
90
+ //#region src/audit/auditPlugin.ts
91
+ /**
92
+ * Audit Plugin
93
+ *
94
+ * Optional audit trail with flexible storage options.
95
+ * Disabled by default - enable explicitly for enterprise use cases.
96
+ *
97
+ * @example
98
+ * import { auditPlugin } from '@classytic/arc/audit';
99
+ *
100
+ * // Development: in-memory
101
+ * await fastify.register(auditPlugin, {
102
+ * enabled: true,
103
+ * stores: ['memory'],
104
+ * });
105
+ *
106
+ * // Production: MongoDB with TTL
107
+ * await fastify.register(auditPlugin, {
108
+ * enabled: true,
109
+ * stores: ['mongodb'],
110
+ * mongoConnection: mongoose.connection,
111
+ * ttlDays: 90,
112
+ * });
113
+ */
114
+ const auditPlugin = async (fastify, opts = {}) => {
115
+ const { enabled = false, stores: storeTypes = ["memory"], mongoConnection, mongoCollection = "audit_logs", ttlDays = 90, customStores = [] } = opts;
116
+ if (!enabled) {
117
+ fastify.decorate("audit", createNoopLogger());
118
+ fastify.decorateRequest("auditContext", void 0);
119
+ fastify.log?.debug?.("Audit plugin disabled");
120
+ return;
121
+ }
122
+ const stores = [...customStores];
123
+ for (const type of storeTypes) switch (type) {
124
+ case "memory":
125
+ stores.push(new MemoryAuditStore());
126
+ break;
127
+ case "mongodb":
128
+ if (!mongoConnection) throw new Error("Audit: mongoConnection required for mongodb store");
129
+ stores.push(new MongoAuditStore({
130
+ connection: mongoConnection,
131
+ collection: mongoCollection,
132
+ ttlDays
133
+ }));
134
+ break;
135
+ }
136
+ if (stores.length === 0) throw new Error("Audit: at least one store must be configured");
137
+ async function logToStores(entry) {
138
+ await Promise.all(stores.map((store) => store.log(entry)));
139
+ }
140
+ const audit = {
141
+ async create(resource, documentId, data, context) {
142
+ await logToStores(createAuditEntry(resource, documentId, "create", context ?? {}, { after: data }));
143
+ },
144
+ async update(resource, documentId, before, after, context) {
145
+ await logToStores(createAuditEntry(resource, documentId, "update", context ?? {}, {
146
+ before,
147
+ after
148
+ }));
149
+ },
150
+ async delete(resource, documentId, data, context) {
151
+ await logToStores(createAuditEntry(resource, documentId, "delete", context ?? {}, { before: data }));
152
+ },
153
+ async restore(resource, documentId, data, context) {
154
+ await logToStores(createAuditEntry(resource, documentId, "restore", context ?? {}, { after: data }));
155
+ },
156
+ async custom(resource, documentId, action, data, context) {
157
+ await logToStores(createAuditEntry(resource, documentId, "custom", context ?? {}, { metadata: {
158
+ customAction: action,
159
+ ...data
160
+ } }));
161
+ },
162
+ async query(options) {
163
+ for (const store of stores) if (store.query) return store.query(options);
164
+ return [];
165
+ }
166
+ };
167
+ fastify.decorate("audit", audit);
168
+ fastify.decorateRequest("auditContext", void 0);
169
+ fastify.addHook("onRequest", async (request) => {
170
+ const user = request.user;
171
+ request.context;
172
+ const scope = request.scope;
173
+ request.auditContext = {
174
+ user,
175
+ organizationId: scope?.kind === "member" ? scope.organizationId : scope?.kind === "elevated" ? scope.organizationId : void 0,
176
+ requestId: request.id,
177
+ ipAddress: request.ip,
178
+ userAgent: request.headers["user-agent"],
179
+ endpoint: void 0,
180
+ duration: void 0
181
+ };
182
+ });
183
+ fastify.addHook("onResponse", async (request, reply) => {
184
+ if (request.auditContext) {
185
+ request.auditContext.endpoint = `${request.method} ${request.routeOptions?.url ?? request.url}`;
186
+ request.auditContext.duration = Math.round(reply.elapsedTime);
187
+ }
188
+ });
189
+ fastify.addHook("onClose", async () => {
190
+ await Promise.all(stores.map((store) => store.close?.()));
191
+ });
192
+ const autoAuditConfig = opts.autoAudit ?? true;
193
+ if (autoAuditConfig !== false) {
194
+ const defaultOps = [
195
+ "create",
196
+ "update",
197
+ "delete"
198
+ ];
199
+ const ops = typeof autoAuditConfig === "object" ? autoAuditConfig.operations ?? defaultOps : defaultOps;
200
+ const excludeResources = new Set(typeof autoAuditConfig === "object" ? autoAuditConfig.exclude ?? [] : []);
201
+ fastify.addHook("onReady", async () => {
202
+ const arc = "arc" in fastify ? fastify.arc : void 0;
203
+ if (!arc?.hooks) {
204
+ fastify.log?.debug?.("Auto-audit skipped: arc-core plugin not registered");
205
+ return;
206
+ }
207
+ for (const op of ops) arc.hooks.after("*", op, async (ctx) => {
208
+ if (excludeResources.has(ctx.resource)) return;
209
+ const docId = autoAuditExtractId(ctx.result);
210
+ const scope = ctx.context?._scope;
211
+ const auditCtx = {
212
+ user: ctx.user,
213
+ organizationId: scope ? autoAuditGetOrgId(scope) : void 0
214
+ };
215
+ try {
216
+ if (op === "create") await audit.create(ctx.resource, docId, autoAuditToPlain(ctx.result), auditCtx);
217
+ else if (op === "update") await audit.update(ctx.resource, docId, autoAuditToPlain(ctx.meta?.existing), autoAuditToPlain(ctx.result), auditCtx);
218
+ else if (op === "delete") await audit.delete(ctx.resource, docId, autoAuditToPlain(ctx.result), auditCtx);
219
+ } catch (err) {
220
+ fastify.log?.warn?.({
221
+ resource: ctx.resource,
222
+ op,
223
+ err
224
+ }, "Auto-audit failed");
225
+ }
226
+ }, 90);
227
+ fastify.log?.debug?.({
228
+ ops,
229
+ exclude: [...excludeResources]
230
+ }, "Auto-audit hooks registered");
231
+ });
232
+ }
233
+ fastify.log?.debug?.({ stores: storeTypes }, "Audit plugin enabled");
234
+ };
235
+ /** Extract document ID from a result */
236
+ function autoAuditExtractId(doc) {
237
+ if (!doc || typeof doc !== "object") return "";
238
+ const d = doc;
239
+ const rawId = d._id ?? d.id;
240
+ return rawId != null ? String(rawId) : "";
241
+ }
242
+ /** Convert Mongoose doc or plain object to plain object */
243
+ function autoAuditToPlain(doc) {
244
+ if (!doc || typeof doc !== "object") return {};
245
+ if (typeof doc.toObject === "function") return doc.toObject();
246
+ return doc;
247
+ }
248
+ /** Extract org ID from scope (avoids importing scope module to prevent circular deps) */
249
+ function autoAuditGetOrgId(scope) {
250
+ if (!scope || typeof scope !== "object") return void 0;
251
+ const s = scope;
252
+ if (s.kind === "member" || s.kind === "elevated") return s.organizationId;
253
+ }
254
+ /**
255
+ * Create no-op logger for when audit is disabled
256
+ */
257
+ function createNoopLogger() {
258
+ const noop = async () => {};
259
+ return {
260
+ create: noop,
261
+ update: noop,
262
+ delete: noop,
263
+ restore: noop,
264
+ custom: noop,
265
+ query: async () => []
266
+ };
267
+ }
268
+ var auditPlugin_default = fp(auditPlugin, {
269
+ name: "arc-audit",
270
+ fastify: "5.x",
271
+ dependencies: ["arc-core"]
272
+ });
273
+
274
+ //#endregion
275
+ export { MemoryAuditStore, auditPlugin_default as auditPlugin, auditPlugin as auditPluginFn, createAuditEntry };
@@ -0,0 +1,5 @@
1
+ import "../elevation-DGo5shaX.mjs";
2
+ import "../interface-e9XfSsUV.mjs";
3
+ import "../types-RLkFVgaw.mjs";
4
+ import { n as MongoAuditStoreOptions, t as MongoAuditStore } from "../mongodb-ClykrfGo.mjs";
5
+ export { MongoAuditStore, type MongoAuditStoreOptions };
@@ -0,0 +1,3 @@
1
+ import { t as MongoAuditStore } from "../mongodb-DNKEExbf.mjs";
2
+
3
+ export { MongoAuditStore };
@@ -0,0 +1,140 @@
1
+ import { c as isElevated, n as PUBLIC_SCOPE } from "./types-Beqn1Un7.mjs";
2
+ import { allowPublic, requireRoles } from "./permissions/index.mjs";
3
+
4
+ //#region src/presets/softDelete.ts
5
+ function softDeletePreset() {
6
+ return {
7
+ name: "softDelete",
8
+ additionalRoutes: (permissions) => [{
9
+ method: "GET",
10
+ path: "/deleted",
11
+ handler: "getDeleted",
12
+ summary: "Get soft-deleted items",
13
+ permissions: permissions.list ?? requireRoles(["admin"]),
14
+ wrapHandler: true,
15
+ operation: "listDeleted"
16
+ }, {
17
+ method: "POST",
18
+ path: "/:id/restore",
19
+ handler: "restore",
20
+ summary: "Restore soft-deleted item",
21
+ permissions: permissions.update ?? requireRoles(["admin"]),
22
+ wrapHandler: true,
23
+ operation: "restore"
24
+ }]
25
+ };
26
+ }
27
+
28
+ //#endregion
29
+ //#region src/presets/slugLookup.ts
30
+ function slugLookupPreset(options = {}) {
31
+ const { slugField = "slug" } = options;
32
+ return {
33
+ name: "slugLookup",
34
+ additionalRoutes: (permissions) => [{
35
+ method: "GET",
36
+ path: `/slug/:${slugField}`,
37
+ handler: "getBySlug",
38
+ summary: "Get by slug",
39
+ permissions: permissions.get ?? allowPublic(),
40
+ wrapHandler: true,
41
+ operation: "getBySlug"
42
+ }],
43
+ controllerOptions: { slugField }
44
+ };
45
+ }
46
+
47
+ //#endregion
48
+ //#region src/presets/ownedByUser.ts
49
+ /**
50
+ * Create ownership check middleware.
51
+ * Elevated scope (platform admin) bypasses ownership checks.
52
+ */
53
+ function createOwnershipCheck(ownerField) {
54
+ return async (request, _reply) => {
55
+ const user = request.user;
56
+ if (!user) return;
57
+ if (isElevated(request.scope ?? PUBLIC_SCOPE)) return;
58
+ const userWithId = user;
59
+ const userId = userWithId._id ?? userWithId.id;
60
+ if (userId) request._ownershipCheck = {
61
+ field: ownerField,
62
+ userId
63
+ };
64
+ };
65
+ }
66
+ function ownedByUserPreset(options = {}) {
67
+ const { ownerField = "userId" } = options;
68
+ const ownershipMiddleware = createOwnershipCheck(ownerField);
69
+ return {
70
+ name: "ownedByUser",
71
+ middlewares: {
72
+ update: [ownershipMiddleware],
73
+ delete: [ownershipMiddleware]
74
+ }
75
+ };
76
+ }
77
+
78
+ //#endregion
79
+ //#region src/presets/tree.ts
80
+ function treePreset(options = {}) {
81
+ const { parentField = "parent" } = options;
82
+ return {
83
+ name: "tree",
84
+ additionalRoutes: (permissions) => [{
85
+ method: "GET",
86
+ path: "/tree",
87
+ handler: "getTree",
88
+ summary: "Get hierarchical tree",
89
+ permissions: permissions.list ?? allowPublic(),
90
+ wrapHandler: true,
91
+ operation: "getTree"
92
+ }, {
93
+ method: "GET",
94
+ path: `/:${parentField}/children`,
95
+ handler: "getChildren",
96
+ summary: "Get children of parent",
97
+ permissions: permissions.list ?? allowPublic(),
98
+ wrapHandler: true,
99
+ operation: "getChildren"
100
+ }],
101
+ controllerOptions: { parentField }
102
+ };
103
+ }
104
+
105
+ //#endregion
106
+ //#region src/presets/audited.ts
107
+ /**
108
+ * Audited preset - adds createdBy/updatedBy tracking
109
+ */
110
+ function auditedPreset(options = {}) {
111
+ const { createdByField = "createdBy", updatedByField = "updatedBy" } = options;
112
+ const injectCreatedBy = async (request, _reply) => {
113
+ const userWithId = request.user;
114
+ if (userWithId?._id || userWithId?.id) {
115
+ const userId = userWithId._id ?? userWithId.id;
116
+ request.body[createdByField] = userId;
117
+ request.body[updatedByField] = userId;
118
+ }
119
+ };
120
+ const injectUpdatedBy = async (request, _reply) => {
121
+ const userWithId = request.user;
122
+ if (userWithId?._id || userWithId?.id) request.body[updatedByField] = userWithId._id ?? userWithId.id;
123
+ };
124
+ return {
125
+ name: "audited",
126
+ schemaOptions: { fieldRules: {
127
+ [createdByField]: { systemManaged: true },
128
+ [updatedByField]: { systemManaged: true },
129
+ createdAt: { systemManaged: true },
130
+ updatedAt: { systemManaged: true }
131
+ } },
132
+ middlewares: {
133
+ create: [injectCreatedBy],
134
+ update: [injectUpdatedBy]
135
+ }
136
+ };
137
+ }
138
+
139
+ //#endregion
140
+ export { softDeletePreset as a, slugLookupPreset as i, treePreset as n, ownedByUserPreset as r, auditedPreset as t };
@@ -0,0 +1,188 @@
1
+ import "../elevation-DGo5shaX.mjs";
2
+ import "../interface-e9XfSsUV.mjs";
3
+ import { t as PermissionCheck } from "../types-RLkFVgaw.mjs";
4
+ import { AuthHelpers, AuthPluginOptions } from "../types/index.mjs";
5
+ import { t as ExternalOpenApiPaths } from "../externalPaths-SyPF2tgK.mjs";
6
+ import { a as SessionManagerOptions, c as createSessionManager, i as SessionData, n as MemorySessionStoreOptions, o as SessionManagerResult, r as SessionCookieOptions, s as SessionStore, t as MemorySessionStore } from "../sessionManager-D_iEHjQl.mjs";
7
+ import { FastifyPluginAsync, FastifyReply as FastifyReply$1, FastifyRequest as FastifyRequest$1 } from "fastify";
8
+
9
+ //#region src/auth/authPlugin.d.ts
10
+ declare module 'fastify' {
11
+ interface FastifyInstance {
12
+ /** Authenticate middleware - use in preHandler for protected routes */
13
+ authenticate: (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
14
+ /** Optional authenticate - parses JWT if present, doesn't fail if absent */
15
+ optionalAuthenticate: (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
16
+ /** Authorize middleware factory - checks if user has required roles */
17
+ authorize: (...roles: string[]) => (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
18
+ /** Auth helpers - issueTokens, jwt utilities */
19
+ auth: AuthHelpers;
20
+ }
21
+ }
22
+ declare const authPlugin: FastifyPluginAsync<AuthPluginOptions>;
23
+ declare const _default: FastifyPluginAsync<AuthPluginOptions>;
24
+ //#endregion
25
+ //#region src/auth/betterAuth.d.ts
26
+ declare module 'fastify' {
27
+ interface FastifyRequest {
28
+ /** Raw request body (from @fastify/raw-body plugin, if registered) */
29
+ rawBody?: Buffer | string;
30
+ }
31
+ }
32
+ /**
33
+ * Minimal interface for a Better Auth instance.
34
+ * We only require the `handler` method -- the full Better Auth type
35
+ * comes from the user's `better-auth` installation.
36
+ */
37
+ interface BetterAuthHandler {
38
+ handler: (request: Request) => Promise<Response>;
39
+ /** The API endpoint map — each value has .path and .options. Used for OpenAPI docs extraction. */
40
+ api?: Record<string, unknown>;
41
+ }
42
+ interface BetterAuthAdapterOptions {
43
+ /** Better Auth instance (from betterAuth() in user's app) */
44
+ auth: BetterAuthHandler;
45
+ /** Base path for auth routes (default: '/api/auth') */
46
+ basePath?: string;
47
+ /**
48
+ * Enable org context extraction from Better Auth's organization plugin.
49
+ * When enabled, the adapter will look up the user's active organization
50
+ * membership and populate `request.scope` with org roles.
51
+ *
52
+ * @default false
53
+ */
54
+ orgContext?: boolean;
55
+ /**
56
+ * OpenAPI documentation for auth endpoints.
57
+ * - `true` (default): auto-extract from auth.api if available
58
+ * - `false`: disable (auth routes won't appear in OpenAPI docs)
59
+ * - `ExternalOpenApiPaths`: manual spec override
60
+ */
61
+ openapi?: boolean | ExternalOpenApiPaths;
62
+ /**
63
+ * Additional user fields from Better Auth config.
64
+ * These get merged into signUpEmail/updateUser request body schemas
65
+ * and the User component schema in OpenAPI docs.
66
+ *
67
+ * Fields with `input: false` are excluded from request bodies
68
+ * but still appear in the User component schema (output-only).
69
+ *
70
+ * @example
71
+ * ```typescript
72
+ * userFields: {
73
+ * department: { type: 'string', description: 'Department', required: true },
74
+ * roles: { type: 'array', description: 'User roles', input: false },
75
+ * }
76
+ * ```
77
+ */
78
+ userFields?: Record<string, {
79
+ type: string;
80
+ description?: string;
81
+ required?: boolean;
82
+ input?: boolean;
83
+ }>;
84
+ /**
85
+ * Expose detailed auth error messages in 401 responses.
86
+ * When false (default), returns generic "Authentication required".
87
+ * When true, includes the actual error message for debugging.
88
+ */
89
+ exposeAuthErrors?: boolean;
90
+ }
91
+ interface BetterAuthAdapterResult {
92
+ /** Fastify plugin that registers catch-all auth routes */
93
+ plugin: FastifyPluginAsync;
94
+ /** Authenticate preHandler -- validates session via Better Auth */
95
+ authenticate: (request: FastifyRequest$1, reply: FastifyReply$1) => Promise<void>;
96
+ /** Optional authenticate -- resolves session silently, continues as unauthenticated on failure */
97
+ optionalAuthenticate: (request: FastifyRequest$1, reply: FastifyReply$1) => Promise<void>;
98
+ /** Permission helpers bound to this auth adapter (available when orgContext is enabled) */
99
+ permissions: {
100
+ requireOrgRole: (...roles: string[]) => PermissionCheck;
101
+ requireOrgMembership: () => PermissionCheck;
102
+ requireTeamMembership: () => PermissionCheck;
103
+ };
104
+ /** OpenAPI paths extracted from Better Auth endpoints (undefined if openapi: false) */
105
+ openapi?: ExternalOpenApiPaths;
106
+ }
107
+ declare module 'fastify' {
108
+ interface FastifyInstance {
109
+ /**
110
+ * Authenticate middleware (Better Auth variant).
111
+ * Validates session by calling Better Auth's session endpoint internally.
112
+ * Set by the Better Auth adapter plugin.
113
+ */
114
+ authenticate: (request: FastifyRequest$1, reply: FastifyReply$1) => Promise<void>;
115
+ /**
116
+ * Optional authenticate middleware (Better Auth variant).
117
+ * Tries to resolve session silently — populates request.user if valid,
118
+ * continues as unauthenticated if no session or invalid session.
119
+ * Used on allowPublic() routes so downstream middleware can apply
120
+ * org-scoped queries when a user IS authenticated.
121
+ */
122
+ optionalAuthenticate: (request: FastifyRequest$1, reply: FastifyReply$1) => Promise<void>;
123
+ }
124
+ }
125
+ /**
126
+ * Create a Better Auth adapter for Arc/Fastify.
127
+ *
128
+ * Returns a Fastify plugin (registers catch-all auth routes) and an
129
+ * `authenticate` preHandler that validates sessions via Better Auth.
130
+ *
131
+ * @example
132
+ * ```typescript
133
+ * import { betterAuth } from 'better-auth';
134
+ * import { createBetterAuthAdapter } from '@classytic/arc/auth';
135
+ *
136
+ * const auth = betterAuth({
137
+ * database: ...,
138
+ * emailAndPassword: { enabled: true },
139
+ * });
140
+ *
141
+ * const { plugin, authenticate } = createBetterAuthAdapter({ auth });
142
+ *
143
+ * // Register the plugin (catch-all auth routes)
144
+ * await fastify.register(plugin);
145
+ *
146
+ * // Use authenticate as a preHandler on protected routes
147
+ * fastify.get('/me', { preHandler: [authenticate] }, handler);
148
+ * ```
149
+ */
150
+ declare function createBetterAuthAdapter(options: BetterAuthAdapterOptions): BetterAuthAdapterResult;
151
+ //#endregion
152
+ //#region src/auth/betterAuthOpenApi.d.ts
153
+ interface BetterAuthOpenApiOptions {
154
+ /** Base path prefix for auth routes (default: '/api/auth') */
155
+ basePath?: string;
156
+ /** Tag name for auth routes in OpenAPI (default: 'Authentication') */
157
+ tagName?: string;
158
+ /** Tag description */
159
+ tagDescription?: string;
160
+ /** Exclude specific paths from the spec (e.g. ['/ok', '/error']) */
161
+ excludePaths?: string[];
162
+ /** Exclude SERVER_ONLY endpoints (default: true) */
163
+ excludeServerOnly?: boolean;
164
+ /**
165
+ * Additional user fields from Better Auth config.
166
+ * These get merged into signUpEmail/updateUser request body schemas
167
+ * and the User component schema for $ref resolution.
168
+ *
169
+ * Fields with `input: false` are excluded from request bodies
170
+ * but still appear in the User component schema (output-only).
171
+ */
172
+ userFields?: Record<string, {
173
+ type: string;
174
+ description?: string; /** Whether this field is required in sign-up (default: false) */
175
+ required?: boolean; /** Whether this field is accepted in request body (default: true). Set false for output-only fields. */
176
+ input?: boolean;
177
+ }>;
178
+ }
179
+ /**
180
+ * Extract OpenAPI paths from a Better Auth instance's API object.
181
+ *
182
+ * Walks `authApi` (the `auth.api` object from Better Auth), discovers
183
+ * endpoints, converts their Zod schemas to JSON Schema via `z.toJSONSchema()`,
184
+ * and returns a complete `ExternalOpenApiPaths` object ready for Arc's spec builder.
185
+ */
186
+ declare function extractBetterAuthOpenApi(authApi: Record<string, unknown>, options?: BetterAuthOpenApiOptions): ExternalOpenApiPaths;
187
+ //#endregion
188
+ export { type AuthPluginOptions, type BetterAuthAdapterOptions, type BetterAuthAdapterResult, type BetterAuthHandler, type BetterAuthOpenApiOptions, MemorySessionStore, type MemorySessionStoreOptions, type SessionCookieOptions, type SessionData, type SessionManagerOptions, type SessionManagerResult, type SessionStore, _default as authPlugin, authPlugin as authPluginFn, createBetterAuthAdapter, createSessionManager, extractBetterAuthOpenApi };