@classytic/arc 2.2.5 → 2.4.1

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 (174) hide show
  1. package/README.md +187 -18
  2. package/bin/arc.js +11 -3
  3. package/dist/BaseController-CkM5dUh_.mjs +1031 -0
  4. package/dist/{EventTransport-BkUDYZEb.d.mts → EventTransport-wc5hSLik.d.mts} +1 -1
  5. package/dist/{HookSystem-BsGV-j2l.mjs → HookSystem-COkyWztM.mjs} +2 -3
  6. package/dist/{ResourceRegistry-7Ic20ZMw.mjs → ResourceRegistry-DeCIFlix.mjs} +8 -5
  7. package/dist/adapters/index.d.mts +3 -5
  8. package/dist/adapters/index.mjs +2 -3
  9. package/dist/{prisma-DJbMt3yf.mjs → adapters-DTC4Ug66.mjs} +45 -12
  10. package/dist/audit/index.d.mts +4 -7
  11. package/dist/audit/index.mjs +2 -29
  12. package/dist/audit/mongodb.d.mts +1 -4
  13. package/dist/audit/mongodb.mjs +2 -3
  14. package/dist/auth/index.d.mts +7 -9
  15. package/dist/auth/index.mjs +65 -63
  16. package/dist/auth/redis-session.d.mts +1 -1
  17. package/dist/auth/redis-session.mjs +1 -2
  18. package/dist/{betterAuthOpenApi-DjWDddNc.mjs → betterAuthOpenApi-lz0IRbXJ.mjs} +4 -6
  19. package/dist/cache/index.d.mts +23 -23
  20. package/dist/cache/index.mjs +4 -6
  21. package/dist/{caching-GSDJcA6-.mjs → caching-BSXB-Xr7.mjs} +2 -24
  22. package/dist/chunk-BpYLSNr0.mjs +14 -0
  23. package/dist/circuitBreaker-BOBOpN2w.mjs +284 -0
  24. package/dist/circuitBreaker-JP2GdJ4b.d.mts +206 -0
  25. package/dist/cli/commands/describe.mjs +24 -7
  26. package/dist/cli/commands/docs.mjs +6 -7
  27. package/dist/cli/commands/doctor.d.mts +10 -0
  28. package/dist/cli/commands/doctor.mjs +156 -0
  29. package/dist/cli/commands/generate.mjs +66 -17
  30. package/dist/cli/commands/init.mjs +315 -45
  31. package/dist/cli/commands/introspect.mjs +2 -4
  32. package/dist/cli/index.d.mts +1 -10
  33. package/dist/cli/index.mjs +4 -153
  34. package/dist/{constants-DdXFXQtN.mjs → constants-Cxde4rpC.mjs} +1 -2
  35. package/dist/core/index.d.mts +3 -5
  36. package/dist/core/index.mjs +5 -4
  37. package/dist/core-C1XCMtqM.mjs +185 -0
  38. package/dist/{createApp-BKHSl2nT.mjs → createApp-ByWNRsZj.mjs} +65 -36
  39. package/dist/{defineResource-DO9ONe_D.mjs → defineResource-D9aY5Cy6.mjs} +154 -1165
  40. package/dist/discovery/index.mjs +37 -5
  41. package/dist/docs/index.d.mts +6 -9
  42. package/dist/docs/index.mjs +3 -21
  43. package/dist/dynamic/index.d.mts +93 -0
  44. package/dist/dynamic/index.mjs +122 -0
  45. package/dist/{elevation-DSTbVvYj.mjs → elevation-BEdACOLB.mjs} +5 -36
  46. package/dist/{elevation-DGo5shaX.d.mts → elevation-Ca_yveIO.d.mts} +41 -7
  47. package/dist/{errorHandler-C3GY3_ow.mjs → errorHandler--zp54tGc.mjs} +3 -5
  48. package/dist/errorHandler-Do4vVQ1f.d.mts +139 -0
  49. package/dist/{errors-DBANPbGr.mjs → errors-rxhfP7Hf.mjs} +1 -2
  50. package/dist/{eventPlugin-BEOvaDqo.mjs → eventPlugin-Ba00swHF.mjs} +25 -27
  51. package/dist/{eventPlugin-H6wDDjGO.d.mts → eventPlugin-iGrSEmwJ.d.mts} +105 -5
  52. package/dist/events/index.d.mts +72 -7
  53. package/dist/events/index.mjs +216 -4
  54. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  55. package/dist/events/transports/redis-stream-entry.mjs +19 -7
  56. package/dist/events/transports/redis.d.mts +1 -1
  57. package/dist/events/transports/redis.mjs +3 -4
  58. package/dist/factory/index.d.mts +23 -9
  59. package/dist/factory/index.mjs +48 -3
  60. package/dist/{fields-Bi_AVKSo.d.mts → fields-DFwdaWCq.d.mts} +1 -1
  61. package/dist/{fields-CTd_CrKr.mjs → fields-ipsbIRPK.mjs} +1 -2
  62. package/dist/hooks/index.d.mts +1 -3
  63. package/dist/hooks/index.mjs +2 -3
  64. package/dist/idempotency/index.d.mts +5 -5
  65. package/dist/idempotency/index.mjs +3 -7
  66. package/dist/idempotency/mongodb.d.mts +1 -1
  67. package/dist/idempotency/mongodb.mjs +4 -5
  68. package/dist/idempotency/redis.d.mts +1 -1
  69. package/dist/idempotency/redis.mjs +2 -5
  70. package/dist/{fastifyAdapter-CyAA2zlB.d.mts → index-BL8CaQih.d.mts} +56 -57
  71. package/dist/index-Diqcm14c.d.mts +369 -0
  72. package/dist/{prisma-xjhMEq_S.d.mts → index-yhxyjqNb.d.mts} +4 -5
  73. package/dist/index.d.mts +100 -105
  74. package/dist/index.mjs +85 -58
  75. package/dist/integrations/event-gateway.d.mts +1 -1
  76. package/dist/integrations/event-gateway.mjs +8 -4
  77. package/dist/integrations/index.d.mts +4 -2
  78. package/dist/integrations/index.mjs +1 -1
  79. package/dist/integrations/jobs.d.mts +2 -2
  80. package/dist/integrations/jobs.mjs +63 -14
  81. package/dist/integrations/mcp/index.d.mts +219 -0
  82. package/dist/integrations/mcp/index.mjs +572 -0
  83. package/dist/integrations/mcp/testing.d.mts +53 -0
  84. package/dist/integrations/mcp/testing.mjs +104 -0
  85. package/dist/integrations/streamline.mjs +39 -19
  86. package/dist/integrations/webhooks.d.mts +56 -0
  87. package/dist/integrations/webhooks.mjs +139 -0
  88. package/dist/integrations/websocket-redis.d.mts +46 -0
  89. package/dist/integrations/websocket-redis.mjs +50 -0
  90. package/dist/integrations/websocket.d.mts +68 -2
  91. package/dist/integrations/websocket.mjs +96 -13
  92. package/dist/{interface-CSNjltAc.d.mts → interface-B4awm1RJ.d.mts} +2 -2
  93. package/dist/interface-DGmPxakH.d.mts +2213 -0
  94. package/dist/{keys-DhqDRxv3.mjs → keys-qcD-TVJl.mjs} +3 -4
  95. package/dist/{logger-ByrvQWZO.mjs → logger-Dz3j1ItV.mjs} +2 -4
  96. package/dist/{memory-B2v7KrCB.mjs → memory-Cb_7iy9e.mjs} +2 -4
  97. package/dist/metrics-Csh4nsvv.mjs +224 -0
  98. package/dist/migrations/index.mjs +3 -7
  99. package/dist/{mongodb-DNKEExbf.mjs → mongodb-BuQ7fNTg.mjs} +1 -4
  100. package/dist/{mongodb-ClykrfGo.d.mts → mongodb-CUpYfxfD.d.mts} +2 -3
  101. package/dist/{mongodb-Dg8O_gvd.d.mts → mongodb-bga9AbkD.d.mts} +2 -2
  102. package/dist/{openapi-9nB_kiuR.mjs → openapi-CBmZ6EQN.mjs} +4 -21
  103. package/dist/org/index.d.mts +12 -14
  104. package/dist/org/index.mjs +92 -119
  105. package/dist/org/types.d.mts +2 -2
  106. package/dist/org/types.mjs +1 -1
  107. package/dist/permissions/index.d.mts +4 -278
  108. package/dist/permissions/index.mjs +4 -579
  109. package/dist/permissions-CA5zg0yK.mjs +751 -0
  110. package/dist/plugins/index.d.mts +104 -107
  111. package/dist/plugins/index.mjs +203 -313
  112. package/dist/plugins/response-cache.mjs +4 -69
  113. package/dist/plugins/tracing-entry.d.mts +1 -1
  114. package/dist/plugins/tracing-entry.mjs +24 -11
  115. package/dist/{pluralize-CM-jZg7p.mjs → pluralize-CcT6qF0a.mjs} +12 -13
  116. package/dist/policies/index.d.mts +2 -2
  117. package/dist/policies/index.mjs +80 -83
  118. package/dist/presets/index.d.mts +26 -19
  119. package/dist/presets/index.mjs +2 -142
  120. package/dist/presets/multiTenant.d.mts +1 -4
  121. package/dist/presets/multiTenant.mjs +4 -6
  122. package/dist/presets-C9QXJV1u.mjs +422 -0
  123. package/dist/{queryCachePlugin-B6R0d4av.mjs → queryCachePlugin-ClosZdNS.mjs} +6 -27
  124. package/dist/{queryCachePlugin-Q6SYuHZ6.d.mts → queryCachePlugin-DcmETvcB.d.mts} +3 -3
  125. package/dist/queryParser-CgCtsjti.mjs +352 -0
  126. package/dist/{redis-UwjEp8Ea.d.mts → redis-CQ5YxMC5.d.mts} +2 -2
  127. package/dist/{redis-stream-CBg0upHI.d.mts → redis-stream-BW9UKLZM.d.mts} +9 -2
  128. package/dist/registry/index.d.mts +1 -4
  129. package/dist/registry/index.mjs +3 -4
  130. package/dist/{introspectionPlugin-B3JkrjwU.mjs → registry-I-ogLgL9.mjs} +1 -8
  131. package/dist/{requestContext-xi6OKBL-.mjs → requestContext-DYtmNpm5.mjs} +1 -3
  132. package/dist/resourceToTools-B6ZN9Ing.mjs +489 -0
  133. package/dist/rpc/index.d.mts +90 -0
  134. package/dist/rpc/index.mjs +248 -0
  135. package/dist/{schemaConverter-Dtg0Kt9T.mjs → schemaConverter-DjzHpFam.mjs} +1 -2
  136. package/dist/schemas/index.d.mts +30 -30
  137. package/dist/schemas/index.mjs +4 -6
  138. package/dist/scope/index.d.mts +13 -2
  139. package/dist/scope/index.mjs +18 -5
  140. package/dist/{sessionManager-D_iEHjQl.d.mts → sessionManager-wbkYj2HL.d.mts} +2 -2
  141. package/dist/{sse-DkqQ1uxb.mjs → sse-BkViJPlT.mjs} +4 -25
  142. package/dist/testing/index.d.mts +551 -567
  143. package/dist/testing/index.mjs +1744 -1799
  144. package/dist/{tracing-8CEbhF0w.d.mts → tracing-bz_U4EM1.d.mts} +6 -1
  145. package/dist/{typeGuards-DwxA1t_L.mjs → typeGuards-Cj5Rgvlg.mjs} +1 -2
  146. package/dist/types/index.d.mts +4 -946
  147. package/dist/types/index.mjs +2 -4
  148. package/dist/types-BJmgxNbF.d.mts +275 -0
  149. package/dist/{types-RLkFVgaw.d.mts → types-BNUccdcf.d.mts} +2 -2
  150. package/dist/{types-Beqn1Un7.mjs → types-C6TQjtdi.mjs} +30 -2
  151. package/dist/{types-DMSBMkaZ.d.mts → types-Dt0-AI6E.d.mts} +85 -27
  152. package/dist/{types-DelU6kln.mjs → types-ZUu_h0jp.mjs} +1 -2
  153. package/dist/utils/index.d.mts +255 -352
  154. package/dist/utils/index.mjs +7 -6
  155. package/dist/utils-Dc0WhlIl.mjs +594 -0
  156. package/dist/versioning-BzfeHmhj.mjs +37 -0
  157. package/package.json +46 -12
  158. package/skills/arc/SKILL.md +506 -0
  159. package/skills/arc/references/auth.md +250 -0
  160. package/skills/arc/references/events.md +272 -0
  161. package/skills/arc/references/integrations.md +385 -0
  162. package/skills/arc/references/mcp.md +386 -0
  163. package/skills/arc/references/production.md +610 -0
  164. package/skills/arc/references/testing.md +183 -0
  165. package/dist/audited-CGdLiSlE.mjs +0 -140
  166. package/dist/chunk-C7Uep-_p.mjs +0 -20
  167. package/dist/circuitBreaker-DYhWBW_D.mjs +0 -1096
  168. package/dist/errorHandler-CW3OOeYq.d.mts +0 -72
  169. package/dist/interface-DZYNK9bb.d.mts +0 -1112
  170. package/dist/presets-BTeYbw7h.d.mts +0 -57
  171. package/dist/presets-CeFtfDR8.mjs +0 -119
  172. /package/dist/{errors-DAWRdiYP.d.mts → errors-CPpvPHT0.d.mts} +0 -0
  173. /package/dist/{externalPaths-SyPF2tgK.d.mts → externalPaths-DpO-s7r8.d.mts} +0 -0
  174. /package/dist/{interface-DTbsvIWe.d.mts → interface-D_BWALyZ.d.mts} +0 -0
@@ -0,0 +1,422 @@
1
+ import { n as PUBLIC_SCOPE, u as isElevated } from "./types-C6TQjtdi.mjs";
2
+ import multiTenantPreset from "./presets/multiTenant.mjs";
3
+ import { d as requireRoles, n as allowPublic, s as requireAuth } from "./permissions-CA5zg0yK.mjs";
4
+ //#region src/presets/ownedByUser.ts
5
+ /**
6
+ * Create ownership check middleware.
7
+ * Elevated scope (platform admin) bypasses ownership checks.
8
+ */
9
+ function createOwnershipCheck(ownerField) {
10
+ return async (request, _reply) => {
11
+ const user = request.user;
12
+ if (!user) return;
13
+ if (isElevated(request.scope ?? PUBLIC_SCOPE)) return;
14
+ const userWithId = user;
15
+ const userId = userWithId._id ?? userWithId.id;
16
+ if (userId) request._ownershipCheck = {
17
+ field: ownerField,
18
+ userId
19
+ };
20
+ };
21
+ }
22
+ function ownedByUserPreset(options = {}) {
23
+ const { ownerField = "userId" } = options;
24
+ const ownershipMiddleware = createOwnershipCheck(ownerField);
25
+ return {
26
+ name: "ownedByUser",
27
+ middlewares: {
28
+ update: [ownershipMiddleware],
29
+ delete: [ownershipMiddleware]
30
+ }
31
+ };
32
+ }
33
+ //#endregion
34
+ //#region src/presets/slugLookup.ts
35
+ /**
36
+ * Slug Lookup Preset
37
+ *
38
+ * Adds a route to get resource by slug.
39
+ */
40
+ function slugLookupPreset(options = {}) {
41
+ const { slugField = "slug" } = options;
42
+ return {
43
+ name: "slugLookup",
44
+ additionalRoutes: (permissions) => [{
45
+ method: "GET",
46
+ path: `/slug/:${slugField}`,
47
+ handler: "getBySlug",
48
+ summary: "Get by slug",
49
+ permissions: permissions.get ?? allowPublic(),
50
+ wrapHandler: true,
51
+ operation: "getBySlug"
52
+ }],
53
+ controllerOptions: { slugField }
54
+ };
55
+ }
56
+ //#endregion
57
+ //#region src/presets/softDelete.ts
58
+ /**
59
+ * Soft Delete Preset
60
+ *
61
+ * Adds routes for listing deleted items and restoring them.
62
+ * The actual soft-delete behavior (deletedAt field, query filtering)
63
+ * is handled by the repository/adapter layer (e.g., MongoKit's softDelete plugin).
64
+ */
65
+ function softDeletePreset() {
66
+ return {
67
+ name: "softDelete",
68
+ additionalRoutes: (permissions) => [{
69
+ method: "GET",
70
+ path: "/deleted",
71
+ handler: "getDeleted",
72
+ summary: "Get soft-deleted items",
73
+ permissions: permissions.list ?? requireRoles(["admin"]),
74
+ wrapHandler: true,
75
+ operation: "listDeleted"
76
+ }, {
77
+ method: "POST",
78
+ path: "/:id/restore",
79
+ handler: "restore",
80
+ summary: "Restore soft-deleted item",
81
+ permissions: permissions.update ?? requireRoles(["admin"]),
82
+ wrapHandler: true,
83
+ operation: "restore"
84
+ }]
85
+ };
86
+ }
87
+ //#endregion
88
+ //#region src/presets/audited.ts
89
+ /**
90
+ * Audited preset - adds createdBy/updatedBy tracking
91
+ */
92
+ function auditedPreset(options = {}) {
93
+ const { createdByField = "createdBy", updatedByField = "updatedBy" } = options;
94
+ const injectCreatedBy = async (request, _reply) => {
95
+ const userWithId = request.user;
96
+ if (userWithId?._id || userWithId?.id) {
97
+ const userId = userWithId._id ?? userWithId.id;
98
+ request.body[createdByField] = userId;
99
+ request.body[updatedByField] = userId;
100
+ }
101
+ };
102
+ const injectUpdatedBy = async (request, _reply) => {
103
+ const userWithId = request.user;
104
+ if (userWithId?._id || userWithId?.id) request.body[updatedByField] = userWithId._id ?? userWithId.id;
105
+ };
106
+ return {
107
+ name: "audited",
108
+ schemaOptions: { fieldRules: {
109
+ [createdByField]: { systemManaged: true },
110
+ [updatedByField]: { systemManaged: true },
111
+ createdAt: { systemManaged: true },
112
+ updatedAt: { systemManaged: true }
113
+ } },
114
+ middlewares: {
115
+ create: [injectCreatedBy],
116
+ update: [injectUpdatedBy]
117
+ }
118
+ };
119
+ }
120
+ //#endregion
121
+ //#region src/presets/bulk.ts
122
+ /**
123
+ * Bulk Operations Preset
124
+ *
125
+ * Adds bulk CRUD routes to a resource:
126
+ * - POST /{resource}/bulk → bulkCreate
127
+ * - PATCH /{resource}/bulk → bulkUpdate
128
+ * - DELETE /{resource}/bulk → bulkDelete
129
+ *
130
+ * Permissions inherit from the resource's create/update/delete permissions.
131
+ * Handlers delegate to BaseController.bulkCreate/bulkUpdate/bulkDelete,
132
+ * which call the repository's createMany/updateMany/deleteMany.
133
+ *
134
+ * DB-agnostic — works with any repository that provides these methods.
135
+ * MongoKit provides them via batchOperationsPlugin.
136
+ *
137
+ * @example
138
+ * ```typescript
139
+ * defineResource({
140
+ * name: 'product',
141
+ * adapter: createMongooseAdapter({ model, repository }),
142
+ * presets: ['bulk'],
143
+ * });
144
+ * // Adds: POST /products/bulk, PATCH /products/bulk, DELETE /products/bulk
145
+ * ```
146
+ */
147
+ function bulkPreset(opts) {
148
+ const operations = opts?.operations ?? [
149
+ "createMany",
150
+ "updateMany",
151
+ "deleteMany"
152
+ ];
153
+ const maxCreateItems = opts?.maxCreateItems ?? 1e3;
154
+ return {
155
+ name: "bulk",
156
+ additionalRoutes: (permissions) => {
157
+ const routes = [];
158
+ if (operations.includes("createMany")) routes.push({
159
+ method: "POST",
160
+ path: "/bulk",
161
+ handler: "bulkCreate",
162
+ wrapHandler: true,
163
+ operation: "bulkCreate",
164
+ summary: "Create multiple items",
165
+ permissions: permissions.create ?? requireAuth(),
166
+ schema: {
167
+ body: {
168
+ type: "object",
169
+ properties: { items: {
170
+ type: "array",
171
+ maxItems: maxCreateItems,
172
+ minItems: 1
173
+ } },
174
+ required: ["items"]
175
+ },
176
+ response: { 201: {
177
+ type: "object",
178
+ properties: {
179
+ success: { type: "boolean" },
180
+ data: { type: "array" },
181
+ meta: {
182
+ type: "object",
183
+ properties: { count: { type: "number" } }
184
+ }
185
+ }
186
+ } }
187
+ }
188
+ });
189
+ if (operations.includes("updateMany")) routes.push({
190
+ method: "PATCH",
191
+ path: "/bulk",
192
+ handler: "bulkUpdate",
193
+ wrapHandler: true,
194
+ operation: "bulkUpdate",
195
+ summary: "Update multiple items matching filter",
196
+ permissions: permissions.update ?? requireAuth(),
197
+ schema: {
198
+ body: {
199
+ type: "object",
200
+ properties: {
201
+ filter: { type: "object" },
202
+ data: { type: "object" }
203
+ },
204
+ required: ["filter", "data"]
205
+ },
206
+ response: { 200: {
207
+ type: "object",
208
+ properties: {
209
+ success: { type: "boolean" },
210
+ data: {
211
+ type: "object",
212
+ properties: {
213
+ matchedCount: { type: "number" },
214
+ modifiedCount: { type: "number" }
215
+ }
216
+ }
217
+ }
218
+ } }
219
+ }
220
+ });
221
+ if (operations.includes("deleteMany")) routes.push({
222
+ method: "DELETE",
223
+ path: "/bulk",
224
+ handler: "bulkDelete",
225
+ wrapHandler: true,
226
+ operation: "bulkDelete",
227
+ summary: "Delete multiple items matching filter",
228
+ permissions: permissions.delete ?? requireAuth(),
229
+ schema: {
230
+ body: {
231
+ type: "object",
232
+ properties: { filter: { type: "object" } },
233
+ required: ["filter"]
234
+ },
235
+ response: { 200: {
236
+ type: "object",
237
+ properties: {
238
+ success: { type: "boolean" },
239
+ data: {
240
+ type: "object",
241
+ properties: { deletedCount: { type: "number" } }
242
+ }
243
+ }
244
+ } }
245
+ }
246
+ });
247
+ return routes;
248
+ }
249
+ };
250
+ }
251
+ //#endregion
252
+ //#region src/presets/tree.ts
253
+ /**
254
+ * Tree Preset
255
+ *
256
+ * Adds routes for hierarchical tree structures.
257
+ */
258
+ function treePreset(options = {}) {
259
+ const { parentField = "parent" } = options;
260
+ return {
261
+ name: "tree",
262
+ additionalRoutes: (permissions) => [{
263
+ method: "GET",
264
+ path: "/tree",
265
+ handler: "getTree",
266
+ summary: "Get hierarchical tree",
267
+ permissions: permissions.list ?? allowPublic(),
268
+ wrapHandler: true,
269
+ operation: "getTree"
270
+ }, {
271
+ method: "GET",
272
+ path: `/:${parentField}/children`,
273
+ handler: "getChildren",
274
+ summary: "Get children of parent",
275
+ permissions: permissions.list ?? allowPublic(),
276
+ wrapHandler: true,
277
+ operation: "getChildren"
278
+ }],
279
+ controllerOptions: { parentField }
280
+ };
281
+ }
282
+ //#endregion
283
+ //#region src/presets/index.ts
284
+ /**
285
+ * Convenience alias for multiTenantPreset with public list/get routes
286
+ * Equivalent to: multiTenantPreset({ allowPublic: ['list', 'get'] })
287
+ */
288
+ const flexibleMultiTenantPreset = (options = {}) => multiTenantPreset({
289
+ ...options,
290
+ allowPublic: ["list", "get"]
291
+ });
292
+ const presetRegistry = {
293
+ softDelete: softDeletePreset,
294
+ slugLookup: slugLookupPreset,
295
+ ownedByUser: ownedByUserPreset,
296
+ multiTenant: multiTenantPreset,
297
+ tree: treePreset,
298
+ audited: auditedPreset,
299
+ bulk: bulkPreset
300
+ };
301
+ /**
302
+ * Get preset by name with options
303
+ */
304
+ function getPreset(nameOrConfig) {
305
+ if (typeof nameOrConfig === "object" && nameOrConfig.name) {
306
+ const { name, ...options } = nameOrConfig;
307
+ return resolvePreset(name, options);
308
+ }
309
+ return resolvePreset(nameOrConfig);
310
+ }
311
+ /**
312
+ * Resolve preset by name
313
+ */
314
+ function resolvePreset(name, options = {}) {
315
+ const factory = presetRegistry[name];
316
+ if (!factory) {
317
+ const available = Object.keys(presetRegistry).join(", ");
318
+ throw new Error(`Unknown preset: '${name}'\nAvailable presets: ${available}\nDocs: https://github.com/classytic/arc#presets`);
319
+ }
320
+ return factory(options);
321
+ }
322
+ /**
323
+ * Register a custom preset
324
+ */
325
+ function registerPreset(name, factory, options) {
326
+ if (presetRegistry[name] && !options?.override) throw new Error(`Preset '${name}' already exists. Pass { override: true } to replace.`);
327
+ presetRegistry[name] = factory;
328
+ }
329
+ /**
330
+ * Get all available preset names
331
+ */
332
+ function getAvailablePresets() {
333
+ return Object.keys(presetRegistry);
334
+ }
335
+ /**
336
+ * Validate that preset combinations don't conflict.
337
+ * Detects route collisions (same method + path from different presets).
338
+ */
339
+ function validatePresetCombination(presets) {
340
+ const conflicts = [];
341
+ const routeMap = /* @__PURE__ */ new Map();
342
+ for (const preset of presets) {
343
+ const name = preset.name ?? "unknown";
344
+ const routes = typeof preset.additionalRoutes === "function" ? preset.additionalRoutes({}) : preset.additionalRoutes ?? [];
345
+ for (const route of routes) {
346
+ const key = `${route.method} ${route.path}`;
347
+ const existing = routeMap.get(key);
348
+ if (existing) conflicts.push({
349
+ presets: [existing, name],
350
+ message: `Both '${existing}' and '${name}' define route ${key}`,
351
+ severity: "error"
352
+ });
353
+ routeMap.set(key, name);
354
+ }
355
+ }
356
+ return conflicts;
357
+ }
358
+ /**
359
+ * Apply presets to resource config.
360
+ * Validates preset combinations for conflicts before merging.
361
+ */
362
+ function applyPresets(config, presets = []) {
363
+ let result = { ...config };
364
+ const resolved = presets.map(resolvePresetInput);
365
+ const errors = validatePresetCombination(resolved).filter((c) => c.severity === "error");
366
+ if (errors.length > 0) throw new Error(`[Arc] Resource '${config.name}' preset conflicts:\n` + errors.map((c) => ` - ${c.message}`).join("\n"));
367
+ for (const preset of resolved) result = mergePreset(result, preset);
368
+ return result;
369
+ }
370
+ /**
371
+ * Resolve preset input to PresetResult
372
+ */
373
+ function resolvePresetInput(preset) {
374
+ if (typeof preset === "object" && ("middlewares" in preset || "additionalRoutes" in preset)) return preset;
375
+ if (typeof preset === "object" && "name" in preset) {
376
+ const { name, ...options } = preset;
377
+ return resolvePreset(name, options);
378
+ }
379
+ return resolvePreset(preset);
380
+ }
381
+ /**
382
+ * Merge preset into config
383
+ */
384
+ function mergePreset(config, preset) {
385
+ const result = { ...config };
386
+ if (preset.additionalRoutes) {
387
+ const routes = typeof preset.additionalRoutes === "function" ? preset.additionalRoutes(config.permissions ?? {}) : preset.additionalRoutes;
388
+ result.additionalRoutes = [...result.additionalRoutes ?? [], ...routes];
389
+ }
390
+ if (preset.middlewares) {
391
+ result.middlewares = result.middlewares ?? {};
392
+ for (const [op, mws] of Object.entries(preset.middlewares)) {
393
+ const key = op;
394
+ result.middlewares[key] = [...result.middlewares[key] ?? [], ...mws ?? []];
395
+ }
396
+ }
397
+ if (preset.schemaOptions) result.schemaOptions = {
398
+ ...result.schemaOptions,
399
+ ...preset.schemaOptions,
400
+ fieldRules: {
401
+ ...result.schemaOptions?.fieldRules,
402
+ ...preset.schemaOptions?.fieldRules
403
+ }
404
+ };
405
+ if (preset.controllerOptions) result._controllerOptions = {
406
+ ...result._controllerOptions,
407
+ ...preset.controllerOptions
408
+ };
409
+ if (preset.hooks && preset.hooks.length > 0) {
410
+ result._hooks = result._hooks ?? [];
411
+ for (const hook of preset.hooks) result._hooks.push({
412
+ presetName: preset.name,
413
+ operation: hook.operation,
414
+ phase: hook.phase,
415
+ handler: hook.handler,
416
+ priority: hook.priority
417
+ });
418
+ }
419
+ return result;
420
+ }
421
+ //#endregion
422
+ export { registerPreset as a, auditedPreset as c, ownedByUserPreset as d, getPreset as i, softDeletePreset as l, flexibleMultiTenantPreset as n, treePreset as o, getAvailablePresets as r, bulkPreset as s, applyPresets as t, slugLookupPreset as u };
@@ -1,9 +1,8 @@
1
- import { t as __exportAll } from "./chunk-C7Uep-_p.mjs";
2
- import { i as versionKey, r as tagVersionKey } from "./keys-DhqDRxv3.mjs";
3
- import { t as hasEvents } from "./typeGuards-DwxA1t_L.mjs";
4
- import { t as MemoryCacheStore } from "./memory-B2v7KrCB.mjs";
1
+ import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
2
+ import { i as versionKey, r as tagVersionKey } from "./keys-qcD-TVJl.mjs";
3
+ import { t as hasEvents } from "./typeGuards-Cj5Rgvlg.mjs";
4
+ import { t as MemoryCacheStore } from "./memory-Cb_7iy9e.mjs";
5
5
  import fp from "fastify-plugin";
6
-
7
6
  //#region src/cache/QueryCache.ts
8
7
  var QueryCache = class {
9
8
  store;
@@ -12,7 +11,7 @@ var QueryCache = class {
12
11
  }
13
12
  async get(key) {
14
13
  const envelope = await this.store.get(key);
15
- if (!envelope || !envelope.createdAt) return {
14
+ if (!envelope?.createdAt) return {
16
15
  data: void 0,
17
16
  status: "miss"
18
17
  };
@@ -70,27 +69,8 @@ var QueryCache = class {
70
69
  await this.store.set(key, newVersion, { ttlMs: 1440 * 60 * 1e3 });
71
70
  }
72
71
  };
73
-
74
72
  //#endregion
75
73
  //#region src/cache/queryCachePlugin.ts
76
- /**
77
- * QueryCache Fastify Plugin
78
- *
79
- * Registers QueryCache on `fastify.queryCache` and wires automatic
80
- * cache invalidation via CRUD events. Zero config for memory mode.
81
- *
82
- * @example
83
- * ```typescript
84
- * // Memory mode (default)
85
- * await fastify.register(queryCachePlugin);
86
- *
87
- * // With Redis store
88
- * await fastify.register(queryCachePlugin, {
89
- * store: new RedisCacheStore({ client: redis, prefix: 'arc:qc:' }),
90
- * defaults: { staleTime: 30, gcTime: 300 },
91
- * });
92
- * ```
93
- */
94
74
  var queryCachePlugin_exports = /* @__PURE__ */ __exportAll({ queryCachePlugin: () => queryCachePlugin });
95
75
  const CRUD_SUFFIXES = new Set([
96
76
  "created",
@@ -133,6 +113,5 @@ const queryCachePlugin = fp(queryCachePluginImpl, {
133
113
  name: "arc-query-cache",
134
114
  fastify: "5.x"
135
115
  });
136
-
137
116
  //#endregion
138
- export { queryCachePlugin_exports as n, QueryCache as r, queryCachePlugin as t };
117
+ export { queryCachePlugin_exports as n, QueryCache as r, queryCachePlugin as t };
@@ -1,4 +1,4 @@
1
- import { i as CacheStore } from "./interface-DTbsvIWe.mjs";
1
+ import { i as CacheStore } from "./interface-D_BWALyZ.mjs";
2
2
  import { FastifyPluginAsync } from "fastify";
3
3
 
4
4
  //#region src/cache/QueryCache.d.ts
@@ -18,7 +18,7 @@ interface QueryCacheConfig {
18
18
  /** Tags for group invalidation */
19
19
  tags?: string[];
20
20
  }
21
- type CacheStatus = 'fresh' | 'stale' | 'miss';
21
+ type CacheStatus = "fresh" | "stale" | "miss";
22
22
  interface CacheResult<T> {
23
23
  data: T;
24
24
  status: CacheStatus;
@@ -58,7 +58,7 @@ interface CrossResourceRule {
58
58
  pattern: string;
59
59
  tags: string[];
60
60
  }
61
- declare module 'fastify' {
61
+ declare module "fastify" {
62
62
  interface FastifyInstance {
63
63
  queryCache: QueryCache;
64
64
  queryCacheConfig: QueryCacheDefaults;