@classytic/arc 2.16.0 → 2.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/README.md +22 -2
  2. package/dist/{BaseController-DlCCTIxJ.mjs → BaseController-Dmeh4Y_y.mjs} +5 -47
  3. package/dist/{QueryCache-SvmT_9ti.d.mts → QueryCache-8YmAQ-zW.d.mts} +1 -1
  4. package/dist/{ResourceRegistry-f48hFk3m.mjs → ResourceRegistry-CzSzVK1m.mjs} +3 -0
  5. package/dist/audit/index.d.mts +1 -1
  6. package/dist/audit/index.mjs +2 -1
  7. package/dist/auth/index.d.mts +4 -4
  8. package/dist/auth/index.mjs +2 -2
  9. package/dist/auth/redis-session.d.mts +1 -1
  10. package/dist/auth/redis-session.mjs +28 -2
  11. package/dist/{buildHandler-BZX6zzDM.mjs → buildHandler-C_SkBNF3.mjs} +6 -4
  12. package/dist/cache/index.d.mts +3 -3
  13. package/dist/cli/commands/describe.d.mts +1 -1
  14. package/dist/cli/commands/describe.mjs +1 -1
  15. package/dist/cli/commands/docs.mjs +2 -2
  16. package/dist/cli/commands/generate.d.mts +1 -1
  17. package/dist/cli/commands/generate.mjs +1 -1
  18. package/dist/cli/commands/init.d.mts +1 -1
  19. package/dist/cli/commands/init.mjs +1 -1
  20. package/dist/cli/commands/introspect.mjs +1 -1
  21. package/dist/cli/index.d.mts +2 -2
  22. package/dist/cli/index.mjs +2 -2
  23. package/dist/core/index.d.mts +2 -2
  24. package/dist/core/index.mjs +3 -3
  25. package/dist/{core-DBJ_j6rX.mjs → core-DYJvu7aI.mjs} +174 -31
  26. package/dist/{createActionRouter-DUpN3Dd1.mjs → createActionRouter-BHUMb1-y.mjs} +6 -3
  27. package/dist/{createAggregationRouter-Dq-TUCuY.mjs → createAggregationRouter-BYVvhfc5.mjs} +3 -3
  28. package/dist/{createApp-DNccuhyI.mjs → createApp-CzCEEsEm.mjs} +9 -9
  29. package/dist/{defineEvent-DRwY0fYm.mjs → defineEvent-NPKUJRt8.mjs} +1 -1
  30. package/dist/docs/index.d.mts +2 -2
  31. package/dist/docs/index.mjs +2 -2
  32. package/dist/{elevation-Dci0AYLT.mjs → elevation-6mfTLBC-.mjs} +1 -1
  33. package/dist/{errorHandler-DpoXQHZ9.mjs → errorHandler-wQejMBT3.mjs} +45 -4
  34. package/dist/{eventPlugin-C2cGqtRO.mjs → eventPlugin-CnJCUyM0.mjs} +1 -1
  35. package/dist/{eventPlugin-CtHC_av1.d.mts → eventPlugin-DjEx_J3j.d.mts} +1 -1
  36. package/dist/events/index.d.mts +3 -3
  37. package/dist/events/index.mjs +5 -5
  38. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  39. package/dist/events/transports/redis-stream-entry.mjs +34 -1
  40. package/dist/events/transports/redis.d.mts +1 -1
  41. package/dist/events/transports/redis.mjs +17 -1
  42. package/dist/factory/index.d.mts +2 -2
  43. package/dist/factory/index.mjs +4 -3
  44. package/dist/fastify-augmentation-7LIR0nDv.d.mts +42 -0
  45. package/dist/{fields-Anj0xdih.d.mts → fields-DqZLp5d6.d.mts} +1 -1
  46. package/dist/hooks/index.d.mts +1 -1
  47. package/dist/idempotency/index.d.mts +2 -2
  48. package/dist/idempotency/index.mjs +21 -5
  49. package/dist/idempotency/redis.d.mts +1 -1
  50. package/dist/{index-3oIimXQn.d.mts → index-3L-adFJO.d.mts} +3 -3
  51. package/dist/{index-CkW0flkU.d.mts → index-C1JSBan-.d.mts} +256 -8
  52. package/dist/{index-B-ulKx5P.d.mts → index-CruZir1o.d.mts} +36 -16
  53. package/dist/index.d.mts +5 -5
  54. package/dist/index.mjs +5 -5
  55. package/dist/integrations/event-gateway.d.mts +2 -2
  56. package/dist/integrations/event-gateway.mjs +1 -1
  57. package/dist/integrations/index.d.mts +2 -2
  58. package/dist/integrations/jobs.d.mts +36 -1
  59. package/dist/integrations/jobs.mjs +82 -9
  60. package/dist/integrations/mcp/index.d.mts +67 -5
  61. package/dist/integrations/mcp/index.mjs +93 -2
  62. package/dist/integrations/mcp/testing.d.mts +1 -1
  63. package/dist/integrations/mcp/testing.mjs +1 -1
  64. package/dist/integrations/websocket-redis.d.mts +1 -1
  65. package/dist/integrations/websocket.d.mts +1 -1
  66. package/dist/integrations/websocket.mjs +2 -1
  67. package/dist/{loadResourcesFromEntry-BLMEI2Xa.mjs → loadResourcesFromEntry-DCjYMgfC.mjs} +1 -1
  68. package/dist/{mcpPlugin-7vGV51ED.mjs → mcpPlugin-hZE00H74.mjs} +114 -11
  69. package/dist/middleware/index.d.mts +1 -1
  70. package/dist/middleware/index.mjs +1 -1
  71. package/dist/{multipartBody-BOvVSVCD.mjs → multipartBody-VjVWmZl8.mjs} +2 -1
  72. package/dist/{openapi-34T9yNwd.mjs → openapi-DKDs9NfD.mjs} +37 -10
  73. package/dist/permissions/index.d.mts +2 -2
  74. package/dist/permissions/index.mjs +2 -2
  75. package/dist/{permissions-CTxMrreC.mjs → permissions-B1Qy55fg.mjs} +169 -169
  76. package/dist/pipeline/index.d.mts +1 -1
  77. package/dist/plugins/index.d.mts +5 -59
  78. package/dist/plugins/index.mjs +7 -7
  79. package/dist/plugins/tracing-entry.d.mts +1 -1
  80. package/dist/plugins/tracing-entry.mjs +1 -1
  81. package/dist/preloadResources-BOXzPhKU.mjs +45 -0
  82. package/dist/presets/filesUpload.d.mts +4 -4
  83. package/dist/presets/filesUpload.mjs +2 -2
  84. package/dist/presets/index.d.mts +1 -1
  85. package/dist/presets/index.mjs +1 -1
  86. package/dist/presets/multiTenant.d.mts +1 -1
  87. package/dist/presets/search.d.mts +2 -2
  88. package/dist/presets/search.mjs +1 -1
  89. package/dist/{presets-C9BE6WaZ.mjs → presets-BYbPobbF.mjs} +5 -3
  90. package/dist/{queryCachePlugin-Biqzfbi5.d.mts → queryCachePlugin-Do4P_WBC.d.mts} +2 -2
  91. package/dist/{redis-Cyzrz6SX.d.mts → redis-cpqVb85w.d.mts} +1 -1
  92. package/dist/{redis-stream-DT-YjzrB.d.mts → redis-stream-BuiAS3aE.d.mts} +1 -1
  93. package/dist/registry/index.d.mts +30 -8
  94. package/dist/registry/index.mjs +2 -2
  95. package/dist/{registry-BBE23CDj.mjs → registry-BrlHkrRS.mjs} +14 -8
  96. package/dist/{routerShared-CZV5aabX.mjs → routerShared-nCyJGOKX.mjs} +164 -17
  97. package/dist/scope/index.d.mts +2 -2
  98. package/dist/scope/index.mjs +2 -2
  99. package/dist/testing/index.d.mts +2 -25
  100. package/dist/testing/index.mjs +2 -45
  101. package/dist/testing/storageContract.d.mts +1 -1
  102. package/dist/types/index.d.mts +4 -4
  103. package/dist/types/storage.d.mts +1 -1
  104. package/dist/{types-D-fYtKjb.d.mts → types-BRmS8TNa.d.mts} +34 -11
  105. package/dist/{types-BsJMEQ4D.d.mts → types-CoMjNmkF.d.mts} +12 -1
  106. package/dist/utils/index.d.mts +83 -5
  107. package/dist/utils/index.mjs +2 -2
  108. package/dist/{utils-DC5ycPfr.mjs → utils-CvD3glpM.mjs} +168 -3
  109. package/dist/{validate-By96rH0r.mjs → validate-DFIP2VrM.mjs} +65 -3
  110. package/dist/{versioning-ZwX9tmbS.d.mts → versioning-5H4930tj.d.mts} +1 -1
  111. package/package.json +3 -3
  112. package/skills/arc/SKILL.md +43 -1
  113. package/skills/arc/references/api-reference.md +2 -0
  114. package/skills/arc/references/mcp.md +30 -1
  115. /package/dist/{EventTransport-C-2oAHtw.d.mts → EventTransport-B1GFiYUy.d.mts} +0 -0
  116. /package/dist/{EventTransport-Hxvv5QQz.mjs → EventTransport-ClEDfiMS.mjs} +0 -0
  117. /package/dist/{caching-TeHE8G-v.mjs → caching-8MfrrUYU.mjs} +0 -0
  118. /package/dist/{elevation-0YBpa663.d.mts → elevation-DvwtjDwu.d.mts} +0 -0
  119. /package/dist/{errorHandler-mHuyWzZE.d.mts → errorHandler-DuqTqeqn.d.mts} +0 -0
  120. /package/dist/{errors-C1lX_jlm.d.mts → errors-4ZloYFvm.d.mts} +0 -0
  121. /package/dist/{externalPaths-DFg-2KTp.d.mts → externalPaths-ClykOQqa.d.mts} +0 -0
  122. /package/dist/{generate-CYac-OLv.mjs → generate-CcM24hoq.mjs} +0 -0
  123. /package/dist/{generate-BWFwgcCM.d.mts → generate-cRnsafMS.d.mts} +0 -0
  124. /package/dist/{init-Dv71MsJr.d.mts → init-D-vrTZ1G.d.mts} +0 -0
  125. /package/dist/{init-HDvoO9L5.mjs → init-DYzfniuX.mjs} +0 -0
  126. /package/dist/{interface-CH0OQudo.d.mts → interface-BVkZsvEe.d.mts} +0 -0
  127. /package/dist/{interface-NwJ_qPlY.d.mts → interface-DoE6UoC2.d.mts} +0 -0
  128. /package/dist/{loadResources-ChQEj8ih.mjs → loadResources-fol6JuvW.mjs} +0 -0
  129. /package/dist/{metrics-TuOmguhi.mjs → metrics-B-PU4-Yu.mjs} +0 -0
  130. /package/dist/{replyHelpers-C-gD32oF.mjs → replyHelpers-boS5Rx26.mjs} +0 -0
  131. /package/dist/{schemaIR-Ctc89DSn.mjs → schemaIR-DAOvBWgT.mjs} +0 -0
  132. /package/dist/{sessionManager-BqFegc0W.d.mts → sessionManager-DlmaHUAl.d.mts} +0 -0
  133. /package/dist/{sse-BY6sTy4P.mjs → sse-jh5OqFjR.mjs} +0 -0
  134. /package/dist/{storage-D2KZJAmn.d.mts → storage-HcAgP5Ni.d.mts} +0 -0
  135. /package/dist/{store-helpers-B0sunfZZ.mjs → store-helpers-5JgaxieH.mjs} +0 -0
  136. /package/dist/{tracing-Dm8n7Cnn.d.mts → tracing-bRqqUxFO.d.mts} +0 -0
  137. /package/dist/{types-DVfpSfx2.d.mts → types-CfapW2t8.d.mts} +0 -0
  138. /package/dist/{types-D57iXYb8.mjs → types-DV9WDfeg.mjs} +0 -0
  139. /package/dist/{versioning-B6mimogM.mjs → versioning-CuIwUQZ2.mjs} +0 -0
  140. /package/dist/{websocket-BkjeGZRn.d.mts → websocket-BJ0w-3Dt.d.mts} +0 -0
@@ -1,6 +1,6 @@
1
1
  import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
2
2
  import { C as isService, S as isOrgInScope, a as getDPoPJkt, b as isElevated, d as getScopeContext, f as getScopeContextMap, g as getUserId, m as getTeamId, o as getMandate, p as getServiceScopes, u as getRequestScope, v as hasOrgAccess, x as isMember } from "./types-Bi0r0vjG.mjs";
3
- import { t as getUserRoles } from "./types-D57iXYb8.mjs";
3
+ import { t as getUserRoles } from "./types-DV9WDfeg.mjs";
4
4
  import { t as MemoryCacheStore } from "./memory-QOLe11D5.mjs";
5
5
  import { randomUUID } from "node:crypto";
6
6
  //#region src/permissions/applyPermissionResult.ts
@@ -99,6 +99,173 @@ async function evaluateAndApplyPermission(check, context, request, reply, opts)
99
99
  return true;
100
100
  }
101
101
  //#endregion
102
+ //#region src/permissions/fields.ts
103
+ /**
104
+ * Field-Level Permissions
105
+ *
106
+ * Control field visibility and writability per role.
107
+ * Integrated into the response path (read) and sanitization path (write).
108
+ *
109
+ * @example
110
+ * ```typescript
111
+ * import { fields, defineResource } from '@classytic/arc';
112
+ *
113
+ * const userResource = defineResource({
114
+ * name: 'user',
115
+ * adapter: userAdapter,
116
+ * fields: {
117
+ * salary: fields.visibleTo(['admin', 'hr']),
118
+ * internalNotes: fields.writableBy(['admin']),
119
+ * email: fields.redactFor(['viewer']),
120
+ * password: fields.hidden(),
121
+ * },
122
+ * });
123
+ * ```
124
+ */
125
+ /** Type guard for Mongoose-like documents with toObject() */
126
+ function isMongooseDoc(obj) {
127
+ return !!obj && typeof obj === "object" && "toObject" in obj && typeof obj.toObject === "function";
128
+ }
129
+ const fields = {
130
+ /**
131
+ * Field is never included in responses. Not writable via API.
132
+ *
133
+ * @example
134
+ * ```typescript
135
+ * fields: { password: fields.hidden() }
136
+ * ```
137
+ */
138
+ hidden() {
139
+ return { _type: "hidden" };
140
+ },
141
+ /**
142
+ * Field is only visible to users with specified roles.
143
+ * Other users don't see the field at all.
144
+ *
145
+ * @example
146
+ * ```typescript
147
+ * fields: { salary: fields.visibleTo(['admin', 'hr']) }
148
+ * ```
149
+ */
150
+ visibleTo(roles) {
151
+ return {
152
+ _type: "visibleTo",
153
+ roles
154
+ };
155
+ },
156
+ /**
157
+ * Field is only writable by users with specified roles.
158
+ * All users can still read the field. Users without the role
159
+ * have the field silently stripped from write operations.
160
+ *
161
+ * @example
162
+ * ```typescript
163
+ * fields: { role: fields.writableBy(['admin']) }
164
+ * ```
165
+ */
166
+ writableBy(roles) {
167
+ return {
168
+ _type: "writableBy",
169
+ roles
170
+ };
171
+ },
172
+ /**
173
+ * Field is redacted (replaced with a placeholder) for specified roles.
174
+ * Other users see the real value.
175
+ *
176
+ * @param roles - Roles that see the redacted value
177
+ * @param redactValue - Replacement value (default: '***')
178
+ *
179
+ * @example
180
+ * ```typescript
181
+ * fields: {
182
+ * email: fields.redactFor(['viewer']),
183
+ * ssn: fields.redactFor(['basic'], '***-**-****'),
184
+ * }
185
+ * ```
186
+ */
187
+ redactFor(roles, redactValue = "***") {
188
+ return {
189
+ _type: "redactFor",
190
+ roles,
191
+ redactValue
192
+ };
193
+ }
194
+ };
195
+ /**
196
+ * Apply field-level READ permissions to a response object.
197
+ * Strips hidden fields, enforces visibility, and applies redaction.
198
+ *
199
+ * @param data - The response object (mutated in place for performance)
200
+ * @param fieldPermissions - Field permission map from resource config
201
+ * @param userRoles - Current user's roles (empty array for unauthenticated)
202
+ * @returns The filtered object
203
+ */
204
+ function applyFieldReadPermissions(data, fieldPermissions, userRoles) {
205
+ if (!data || typeof data !== "object") return data;
206
+ const result = { ...isMongooseDoc(data) ? data.toObject() : data };
207
+ for (const [field, perm] of Object.entries(fieldPermissions)) switch (perm._type) {
208
+ case "hidden":
209
+ delete result[field];
210
+ break;
211
+ case "visibleTo":
212
+ if (!perm.roles?.some((r) => userRoles.includes(r))) delete result[field];
213
+ break;
214
+ case "redactFor":
215
+ if (perm.roles?.some((r) => userRoles.includes(r))) result[field] = perm.redactValue ?? "***";
216
+ break;
217
+ case "writableBy": break;
218
+ }
219
+ return result;
220
+ }
221
+ /**
222
+ * Apply field-level WRITE permissions to request body.
223
+ *
224
+ * Returns both the filtered body and the list of denied fields. Callers are
225
+ * expected to reject the request when `deniedFields.length > 0` — silently
226
+ * stripping fields hides misconfigurations and real attacks. See
227
+ * `BodySanitizer` for the default policy.
228
+ *
229
+ * @param body - The request body (returns a new filtered copy)
230
+ * @param fieldPermissions - Field permission map from resource config
231
+ * @param userRoles - Current user's roles
232
+ */
233
+ function applyFieldWritePermissions(body, fieldPermissions, userRoles) {
234
+ const result = { ...body };
235
+ const deniedFields = [];
236
+ for (const [field, perm] of Object.entries(fieldPermissions)) switch (perm._type) {
237
+ case "hidden":
238
+ if (field in result) {
239
+ deniedFields.push(field);
240
+ delete result[field];
241
+ }
242
+ break;
243
+ case "writableBy":
244
+ if (field in result && !perm.roles?.some((r) => userRoles.includes(r))) {
245
+ deniedFields.push(field);
246
+ delete result[field];
247
+ }
248
+ break;
249
+ }
250
+ return {
251
+ body: result,
252
+ deniedFields
253
+ };
254
+ }
255
+ /**
256
+ * Resolve effective roles by merging global user roles with org-level roles.
257
+ *
258
+ * Global roles come from `req.user.role` (normalized via getUserRoles()).
259
+ * Org roles come from `req.context.orgRoles` (set by BA adapter's org bridge).
260
+ *
261
+ * When no org context exists, returns global roles only — backward compatible.
262
+ */
263
+ function resolveEffectiveRoles(userRoles, orgRoles) {
264
+ if (orgRoles.length === 0) return [...userRoles];
265
+ if (userRoles.length === 0) return [...orgRoles];
266
+ return [...new Set([...userRoles, ...orgRoles])];
267
+ }
268
+ //#endregion
102
269
  //#region src/permissions/agent.ts
103
270
  /**
104
271
  * Agent-Auth Permission Helpers — DPoP + capability mandates for AI-agent flows
@@ -1148,173 +1315,6 @@ function createDynamicPermissionMatrix(config) {
1148
1315
  };
1149
1316
  }
1150
1317
  //#endregion
1151
- //#region src/permissions/fields.ts
1152
- /**
1153
- * Field-Level Permissions
1154
- *
1155
- * Control field visibility and writability per role.
1156
- * Integrated into the response path (read) and sanitization path (write).
1157
- *
1158
- * @example
1159
- * ```typescript
1160
- * import { fields, defineResource } from '@classytic/arc';
1161
- *
1162
- * const userResource = defineResource({
1163
- * name: 'user',
1164
- * adapter: userAdapter,
1165
- * fields: {
1166
- * salary: fields.visibleTo(['admin', 'hr']),
1167
- * internalNotes: fields.writableBy(['admin']),
1168
- * email: fields.redactFor(['viewer']),
1169
- * password: fields.hidden(),
1170
- * },
1171
- * });
1172
- * ```
1173
- */
1174
- /** Type guard for Mongoose-like documents with toObject() */
1175
- function isMongooseDoc(obj) {
1176
- return !!obj && typeof obj === "object" && "toObject" in obj && typeof obj.toObject === "function";
1177
- }
1178
- const fields = {
1179
- /**
1180
- * Field is never included in responses. Not writable via API.
1181
- *
1182
- * @example
1183
- * ```typescript
1184
- * fields: { password: fields.hidden() }
1185
- * ```
1186
- */
1187
- hidden() {
1188
- return { _type: "hidden" };
1189
- },
1190
- /**
1191
- * Field is only visible to users with specified roles.
1192
- * Other users don't see the field at all.
1193
- *
1194
- * @example
1195
- * ```typescript
1196
- * fields: { salary: fields.visibleTo(['admin', 'hr']) }
1197
- * ```
1198
- */
1199
- visibleTo(roles) {
1200
- return {
1201
- _type: "visibleTo",
1202
- roles
1203
- };
1204
- },
1205
- /**
1206
- * Field is only writable by users with specified roles.
1207
- * All users can still read the field. Users without the role
1208
- * have the field silently stripped from write operations.
1209
- *
1210
- * @example
1211
- * ```typescript
1212
- * fields: { role: fields.writableBy(['admin']) }
1213
- * ```
1214
- */
1215
- writableBy(roles) {
1216
- return {
1217
- _type: "writableBy",
1218
- roles
1219
- };
1220
- },
1221
- /**
1222
- * Field is redacted (replaced with a placeholder) for specified roles.
1223
- * Other users see the real value.
1224
- *
1225
- * @param roles - Roles that see the redacted value
1226
- * @param redactValue - Replacement value (default: '***')
1227
- *
1228
- * @example
1229
- * ```typescript
1230
- * fields: {
1231
- * email: fields.redactFor(['viewer']),
1232
- * ssn: fields.redactFor(['basic'], '***-**-****'),
1233
- * }
1234
- * ```
1235
- */
1236
- redactFor(roles, redactValue = "***") {
1237
- return {
1238
- _type: "redactFor",
1239
- roles,
1240
- redactValue
1241
- };
1242
- }
1243
- };
1244
- /**
1245
- * Apply field-level READ permissions to a response object.
1246
- * Strips hidden fields, enforces visibility, and applies redaction.
1247
- *
1248
- * @param data - The response object (mutated in place for performance)
1249
- * @param fieldPermissions - Field permission map from resource config
1250
- * @param userRoles - Current user's roles (empty array for unauthenticated)
1251
- * @returns The filtered object
1252
- */
1253
- function applyFieldReadPermissions(data, fieldPermissions, userRoles) {
1254
- if (!data || typeof data !== "object") return data;
1255
- const result = { ...isMongooseDoc(data) ? data.toObject() : data };
1256
- for (const [field, perm] of Object.entries(fieldPermissions)) switch (perm._type) {
1257
- case "hidden":
1258
- delete result[field];
1259
- break;
1260
- case "visibleTo":
1261
- if (!perm.roles?.some((r) => userRoles.includes(r))) delete result[field];
1262
- break;
1263
- case "redactFor":
1264
- if (perm.roles?.some((r) => userRoles.includes(r))) result[field] = perm.redactValue ?? "***";
1265
- break;
1266
- case "writableBy": break;
1267
- }
1268
- return result;
1269
- }
1270
- /**
1271
- * Apply field-level WRITE permissions to request body.
1272
- *
1273
- * Returns both the filtered body and the list of denied fields. Callers are
1274
- * expected to reject the request when `deniedFields.length > 0` — silently
1275
- * stripping fields hides misconfigurations and real attacks. See
1276
- * `BodySanitizer` for the default policy.
1277
- *
1278
- * @param body - The request body (returns a new filtered copy)
1279
- * @param fieldPermissions - Field permission map from resource config
1280
- * @param userRoles - Current user's roles
1281
- */
1282
- function applyFieldWritePermissions(body, fieldPermissions, userRoles) {
1283
- const result = { ...body };
1284
- const deniedFields = [];
1285
- for (const [field, perm] of Object.entries(fieldPermissions)) switch (perm._type) {
1286
- case "hidden":
1287
- if (field in result) {
1288
- deniedFields.push(field);
1289
- delete result[field];
1290
- }
1291
- break;
1292
- case "writableBy":
1293
- if (field in result && !perm.roles?.some((r) => userRoles.includes(r))) {
1294
- deniedFields.push(field);
1295
- delete result[field];
1296
- }
1297
- break;
1298
- }
1299
- return {
1300
- body: result,
1301
- deniedFields
1302
- };
1303
- }
1304
- /**
1305
- * Resolve effective roles by merging global user roles with org-level roles.
1306
- *
1307
- * Global roles come from `req.user.role` (normalized via getUserRoles()).
1308
- * Org roles come from `req.context.orgRoles` (set by BA adapter's org bridge).
1309
- *
1310
- * When no org context exists, returns global roles only — backward compatible.
1311
- */
1312
- function resolveEffectiveRoles(userRoles, orgRoles) {
1313
- if (orgRoles.length === 0) return [...userRoles];
1314
- if (userRoles.length === 0) return [...orgRoles];
1315
- return [...new Set([...userRoles, ...orgRoles])];
1316
- }
1317
- //#endregion
1318
1318
  //#region src/permissions/roleHierarchy.ts
1319
1319
  /**
1320
1320
  * Create a role hierarchy from a parent → children map.
@@ -1490,4 +1490,4 @@ function readOnly(overrides) {
1490
1490
  }, overrides);
1491
1491
  }
1492
1492
  //#endregion
1493
- export { roles as A, allowPublic as C, requireAuth as D, not as E, applyPermissionResult as F, evaluateAndApplyPermission as I, normalizePermissionResult as L, requireAgentScope as M, requireDPoP as N, requireOwnership as O, requireMandate as P, allOf as S, denyAll as T, requireOrgMembership as _, presets_exports as a, requireServiceScope as b, readOnly as c, applyFieldWritePermissions as d, fields as f, requireOrgInScope as g, createOrgPermissions as h, ownerWithAdminBypass as i, when as j, requireRoles as k, createRoleHierarchy as l, createDynamicPermissionMatrix as m, authenticated as n, publicRead as o, resolveEffectiveRoles as p, fullPublic as r, publicReadAdminWrite as s, adminOnly as t, applyFieldReadPermissions as u, requireOrgRole as v, anyOf as w, requireTeamMembership as x, requireScopeContext as y };
1493
+ export { requireMandate as A, requireAuth as C, when as D, roles as E, applyPermissionResult as F, evaluateAndApplyPermission as I, normalizePermissionResult as L, applyFieldWritePermissions as M, fields as N, requireAgentScope as O, resolveEffectiveRoles as P, not as S, requireRoles as T, requireTeamMembership as _, presets_exports as a, anyOf as b, readOnly as c, createOrgPermissions as d, requireOrgInScope as f, requireServiceScope as g, requireScopeContext as h, ownerWithAdminBypass as i, applyFieldReadPermissions as j, requireDPoP as k, createRoleHierarchy as l, requireOrgRole as m, authenticated as n, publicRead as o, requireOrgMembership as p, fullPublic as r, publicReadAdminWrite as s, adminOnly as t, createDynamicPermissionMatrix as u, allOf as v, requireOwnership as w, denyAll as x, allowPublic as y };
@@ -1,4 +1,4 @@
1
- import { Ft as OperationFilter, It as PipelineConfig, Lt as PipelineContext, Mt as Guard, Nt as Interceptor, Pt as NextFunction, Rt as PipelineStep, yt as IControllerResponse, zt as Transform } from "../index-CkW0flkU.mjs";
1
+ import { Ft as OperationFilter, It as PipelineConfig, Lt as PipelineContext, Mt as Guard, Nt as Interceptor, Pt as NextFunction, Rt as PipelineStep, yt as IControllerResponse, zt as Transform } from "../index-C1JSBan-.mjs";
2
2
 
3
3
  //#region src/pipeline/guard.d.ts
4
4
  interface GuardOptions {
@@ -1,66 +1,12 @@
1
- import { An as HookSystem, Kt as AnyRecord, Q as MiddlewareConfig, dt as RouteDefinition, mt as RouteSchemaOptions, nt as PresetHook, z as ResourceRegistry } from "../index-CkW0flkU.mjs";
2
- import { t as ExternalOpenApiPaths } from "../externalPaths-DFg-2KTp.mjs";
3
- import { _ as HealthOptions, a as MetricsCollector, c as metricsPlugin, d as ssePlugin, f as CachingOptions, g as HealthCheck, h as cachingPlugin, i as MetricEntry, l as SSEOptions, m as _default$1, n as _default$7, o as MetricsOptions, p as CachingRule, r as versioningPlugin, s as _default$4, t as VersioningOptions, u as _default$6, v as _default$3, y as healthPlugin } from "../versioning-ZwX9tmbS.mjs";
4
- import { i as errorHandlerPlugin, n as ErrorMapper, r as defaultIsDuplicateKeyError, t as ErrorHandlerOptions } from "../errorHandler-mHuyWzZE.mjs";
5
- import { t as TracingOptions } from "../tracing-Dm8n7Cnn.mjs";
1
+ import { Kt as AnyRecord, Q as MiddlewareConfig, dt as RouteDefinition, mt as RouteSchemaOptions, nt as PresetHook } from "../index-C1JSBan-.mjs";
2
+ import { a as arcCorePlugin, i as _default, n as ArcCorePluginOptions, r as PluginMeta, t as ArcCore } from "../fastify-augmentation-7LIR0nDv.mjs";
3
+ import { _ as HealthOptions, a as MetricsCollector, c as metricsPlugin, d as ssePlugin, f as CachingOptions, g as HealthCheck, h as cachingPlugin, i as MetricEntry, l as SSEOptions, m as _default$1, n as _default$7, o as MetricsOptions, p as CachingRule, r as versioningPlugin, s as _default$4, t as VersioningOptions, u as _default$6, v as _default$3, y as healthPlugin } from "../versioning-5H4930tj.mjs";
4
+ import { i as errorHandlerPlugin, n as ErrorMapper, r as defaultIsDuplicateKeyError, t as ErrorHandlerOptions } from "../errorHandler-DuqTqeqn.mjs";
5
+ import { t as TracingOptions } from "../tracing-bRqqUxFO.mjs";
6
6
  import { PaginatedResult } from "@classytic/repo-core/pagination";
7
7
  import { FastifyInstance, FastifyPluginAsync } from "fastify";
8
8
  import * as _$node_stream0 from "node:stream";
9
9
 
10
- //#region src/core/arcCorePlugin.d.ts
11
- interface ArcCorePluginOptions {
12
- /** Enable event emission for CRUD operations (requires eventPlugin) */
13
- emitEvents?: boolean;
14
- /** Hook system instance (for testing/custom setup) */
15
- hookSystem?: HookSystem;
16
- /** Resource registry instance (for testing/custom setup) */
17
- registry?: ResourceRegistry;
18
- }
19
- interface PluginMeta {
20
- name: string;
21
- version?: string;
22
- options?: Record<string, unknown>;
23
- registeredAt: string;
24
- }
25
- interface ArcCore {
26
- /** Instance-scoped hook system */
27
- hooks: HookSystem;
28
- /** Instance-scoped resource registry */
29
- registry: ResourceRegistry;
30
- /** Whether event emission is enabled */
31
- emitEvents: boolean;
32
- /** External OpenAPI paths contributed by auth adapters or third-party integrations */
33
- externalOpenApiPaths: ExternalOpenApiPaths[];
34
- /** Registered plugins for introspection */
35
- plugins: Map<string, PluginMeta>;
36
- }
37
- declare module "fastify" {
38
- interface FastifyInstance {
39
- /**
40
- * Arc core decorator. Optional because:
41
- *
42
- * 1. Consumers who import any `@classytic/arc/*` subpath get this module
43
- * augmentation merged into every `FastifyInstance` they see — even
44
- * instances on apps that never register {@link arcCorePlugin}. Making
45
- * it required would force those apps to assert or guard every
46
- * property access.
47
- * 2. Hosts that extend `FastifyInstance` to narrow `arc` to their own
48
- * type (`interface X extends FastifyInstance { arc?: MyArc }`) were
49
- * previously blocked because non-optional `arc: ArcCore` conflicts
50
- * with the re-declaration. An optional field lets hosts re-declare
51
- * with a compatible subtype without fighting TS.
52
- *
53
- * Inside Arc's own code, any call site that runs *after* `arcCorePlugin`
54
- * has registered treats this as non-null — see the call sites in
55
- * `factory/registerAuth.ts` and `factory/createApp.ts` which assert
56
- * with `fastify.arc!` or narrow explicitly.
57
- */
58
- arc?: ArcCore;
59
- }
60
- }
61
- declare const arcCorePlugin: FastifyPluginAsync<ArcCorePluginOptions>;
62
- declare const _default: FastifyPluginAsync<ArcCorePluginOptions>;
63
- //#endregion
64
10
  //#region src/plugins/createPlugin.d.ts
65
11
  interface PluginResourceResult {
66
12
  /** Additional routes to add to the resource (v2.8 `RouteDefinition` shape) */
@@ -3,13 +3,13 @@ import { c as getOrgId } from "../types-Bi0r0vjG.mjs";
3
3
  import { t as requestContext } from "../requestContext-SSaaTgW8.mjs";
4
4
  import { t as hasEvents } from "../typeGuards-BzkXkvVv.mjs";
5
5
  import { t as HookSystem } from "../HookSystem-Cmf7-Etp.mjs";
6
- import { t as ResourceRegistry } from "../ResourceRegistry-f48hFk3m.mjs";
7
- import { n as caching_default, t as cachingPlugin } from "../caching-TeHE8G-v.mjs";
8
- import { n as errorHandlerPlugin, t as defaultIsDuplicateKeyError } from "../errorHandler-DpoXQHZ9.mjs";
9
- import { n as metrics_default, t as metricsPlugin } from "../metrics-TuOmguhi.mjs";
10
- import { t as replyHelpersPlugin } from "../replyHelpers-C-gD32oF.mjs";
11
- import { n as sse_default, t as ssePlugin } from "../sse-BY6sTy4P.mjs";
12
- import { n as versioning_default, t as versioningPlugin } from "../versioning-B6mimogM.mjs";
6
+ import { t as ResourceRegistry } from "../ResourceRegistry-CzSzVK1m.mjs";
7
+ import { n as caching_default, t as cachingPlugin } from "../caching-8MfrrUYU.mjs";
8
+ import { n as errorHandlerPlugin, t as defaultIsDuplicateKeyError } from "../errorHandler-wQejMBT3.mjs";
9
+ import { n as metrics_default, t as metricsPlugin } from "../metrics-B-PU4-Yu.mjs";
10
+ import { t as replyHelpersPlugin } from "../replyHelpers-boS5Rx26.mjs";
11
+ import { n as sse_default, t as ssePlugin } from "../sse-jh5OqFjR.mjs";
12
+ import { n as versioning_default, t as versioningPlugin } from "../versioning-CuIwUQZ2.mjs";
13
13
  import { randomUUID } from "node:crypto";
14
14
  import fp from "fastify-plugin";
15
15
  //#region src/core/arcCorePlugin.ts
@@ -1,2 +1,2 @@
1
- import { a as traced, i as isTracingAvailable, n as _default, r as createSpan, t as TracingOptions } from "../tracing-Dm8n7Cnn.mjs";
1
+ import { a as traced, i as isTracingAvailable, n as _default, r as createSpan, t as TracingOptions } from "../tracing-bRqqUxFO.mjs";
2
2
  export { type TracingOptions, createSpan, isTracingAvailable, traced, _default as tracingPlugin };
@@ -58,7 +58,7 @@ try {
58
58
  function createTracerProvider(options) {
59
59
  if (!isAvailable || !NodeTracerProvider || !BatchSpanProcessor || !OTLPTraceExporter) return null;
60
60
  const { serviceName = "@classytic/arc", serviceVersion, exporterUrl = "http://localhost:4318/v1/traces" } = options;
61
- const resolvedVersion = serviceVersion ?? "2.16.0";
61
+ const resolvedVersion = serviceVersion ?? "2.17.0";
62
62
  const exporter = new OTLPTraceExporter({ url: exporterUrl });
63
63
  const provider = new NodeTracerProvider({ resource: { attributes: {
64
64
  "service.name": serviceName,
@@ -0,0 +1,45 @@
1
+ //#region src/testing/preloadResources.ts
2
+ /**
3
+ * Normalize an eager `import.meta.glob` result into a `ResourceLike[]`.
4
+ *
5
+ * Accepts either:
6
+ * - `{ import: 'default' }` form: values are the resource directly
7
+ * - default form: values are the full module — picks first export with `toPlugin()`
8
+ *
9
+ * Throws if any module doesn't yield a valid `ResourceLike`.
10
+ */
11
+ function preloadResources(globResult) {
12
+ const resources = [];
13
+ for (const [path, value] of Object.entries(globResult)) {
14
+ const resource = pickResource(value);
15
+ if (!resource) throw new Error(`preloadResources: ${path} does not export a valid resource.\n Expected: a default export OR a named export with toPlugin().`);
16
+ resources.push(resource);
17
+ }
18
+ return resources.sort((a, b) => (a.name ?? "").localeCompare(b.name ?? ""));
19
+ }
20
+ /**
21
+ * Normalize a lazy `import.meta.glob` result into a `Promise<ResourceLike[]>`.
22
+ *
23
+ * Use this when resources depend on prior bootstrap (e.g., engine init) and
24
+ * cannot be evaluated at import time of the preload file.
25
+ */
26
+ async function preloadResourcesAsync(globResult) {
27
+ return (await Promise.all(Object.entries(globResult).map(async ([path, loader]) => {
28
+ const resource = pickResource(await loader());
29
+ if (!resource) throw new Error(`preloadResourcesAsync: ${path} does not export a valid resource.\n Expected: a default export OR a named export with toPlugin().`);
30
+ return resource;
31
+ }))).sort((a, b) => (a.name ?? "").localeCompare(b.name ?? ""));
32
+ }
33
+ function pickResource(value) {
34
+ if (!value || typeof value !== "object") return void 0;
35
+ if (typeof value.toPlugin === "function") return value;
36
+ const mod = value;
37
+ const candidates = [
38
+ mod.default,
39
+ mod.resource,
40
+ ...Object.values(mod)
41
+ ];
42
+ for (const c of candidates) if (c && typeof c === "object" && typeof c.toPlugin === "function") return c;
43
+ }
44
+ //#endregion
45
+ export { preloadResourcesAsync as n, preloadResources as t };
@@ -1,7 +1,7 @@
1
- import { rt as PresetResult } from "../index-CkW0flkU.mjs";
2
- import { i as RequestScope } from "../types-DVfpSfx2.mjs";
3
- import { c as PermissionCheck } from "../fields-Anj0xdih.mjs";
4
- import { a as StorageReadResult, i as StorageReadRange, n as StorageContext, o as StorageUploadInput, r as StorageFile, t as Storage } from "../storage-D2KZJAmn.mjs";
1
+ import { rt as PresetResult } from "../index-C1JSBan-.mjs";
2
+ import { i as RequestScope } from "../types-CfapW2t8.mjs";
3
+ import { c as PermissionCheck } from "../fields-DqZLp5d6.mjs";
4
+ import { a as StorageReadResult, i as StorageReadRange, n as StorageContext, o as StorageUploadInput, r as StorageFile, t as Storage } from "../storage-HcAgP5Ni.mjs";
5
5
 
6
6
  //#region src/presets/filesUpload.d.ts
7
7
  interface FilesUploadPresetRoutes {
@@ -1,7 +1,7 @@
1
1
  import { i as NotFoundError, u as ValidationError } from "../errors-j4aJm1Wg.mjs";
2
2
  import { c as getOrgId, g as getUserId } from "../types-Bi0r0vjG.mjs";
3
- import { C as allowPublic, D as requireAuth } from "../permissions-CTxMrreC.mjs";
4
- import { t as multipartBody } from "../multipartBody-BOvVSVCD.mjs";
3
+ import { C as requireAuth, y as allowPublic } from "../permissions-B1Qy55fg.mjs";
4
+ import { t as multipartBody } from "../multipartBody-VjVWmZl8.mjs";
5
5
  //#region src/presets/filesUpload.ts
6
6
  const DEFAULT_FIELD_NAME = "file";
7
7
  const DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024;
@@ -1,4 +1,4 @@
1
- import { Kt as AnyRecord, bt as IRequestContext, d as PaginationResult, rt as PresetResult, st as ResourceConfig, yt as IControllerResponse } from "../index-CkW0flkU.mjs";
1
+ import { Kt as AnyRecord, bt as IRequestContext, d as PaginationResult, rt as PresetResult, st as ResourceConfig, yt as IControllerResponse } from "../index-C1JSBan-.mjs";
2
2
  import { FilesUploadPresetOptions, FilesUploadPresetPermissions, FilesUploadPresetRoutes, filesUploadPreset } from "./filesUpload.mjs";
3
3
  import { MultiTenantOptions, TenantFieldSpec, multiTenantPreset } from "./multiTenant.mjs";
4
4
  import { SearchHandler, SearchPresetOptions, SearchRouteConfig, searchPreset } from "./search.mjs";
@@ -1,5 +1,5 @@
1
1
  import { multiTenantPreset } from "./multiTenant.mjs";
2
- import { a as registerPreset, c as auditedPreset, d as ownedByUserPreset, i as getPreset, l as softDeletePreset, n as flexibleMultiTenantPreset, o as treePreset, r as getAvailablePresets, s as bulkPreset, t as applyPresets, u as slugLookupPreset } from "../presets-C9BE6WaZ.mjs";
2
+ import { a as registerPreset, c as auditedPreset, d as ownedByUserPreset, i as getPreset, l as softDeletePreset, n as flexibleMultiTenantPreset, o as treePreset, r as getAvailablePresets, s as bulkPreset, t as applyPresets, u as slugLookupPreset } from "../presets-BYbPobbF.mjs";
3
3
  import { filesUploadPreset } from "./filesUpload.mjs";
4
4
  import { searchPreset } from "./search.mjs";
5
5
  export { applyPresets, auditedPreset, bulkPreset, filesUploadPreset, flexibleMultiTenantPreset, getAvailablePresets, getPreset, multiTenantPreset, ownedByUserPreset, registerPreset, searchPreset, slugLookupPreset, softDeletePreset, treePreset };
@@ -1,4 +1,4 @@
1
- import { J as CrudRouteKey, rt as PresetResult } from "../index-CkW0flkU.mjs";
1
+ import { J as CrudRouteKey, rt as PresetResult } from "../index-C1JSBan-.mjs";
2
2
 
3
3
  //#region src/presets/multiTenant.d.ts
4
4
  /**
@@ -1,5 +1,5 @@
1
- import { dt as RouteDefinition, ft as RouteMcpConfig, ht as ControllerHandler, rt as PresetResult } from "../index-CkW0flkU.mjs";
2
- import { c as PermissionCheck } from "../fields-Anj0xdih.mjs";
1
+ import { dt as RouteDefinition, ft as RouteMcpConfig, ht as ControllerHandler, rt as PresetResult } from "../index-C1JSBan-.mjs";
2
+ import { c as PermissionCheck } from "../fields-DqZLp5d6.mjs";
3
3
 
4
4
  //#region src/presets/search.d.ts
5
5
  /**
@@ -1,4 +1,4 @@
1
- import { C as allowPublic, D as requireAuth } from "../permissions-CTxMrreC.mjs";
1
+ import { C as requireAuth, y as allowPublic } from "../permissions-B1Qy55fg.mjs";
2
2
  //#region src/presets/search.ts
3
3
  const BUILTINS = [
4
4
  {
@@ -1,5 +1,5 @@
1
1
  import { b as isElevated, n as PUBLIC_SCOPE } from "./types-Bi0r0vjG.mjs";
2
- import { C as allowPublic, D as requireAuth, k as requireRoles } from "./permissions-CTxMrreC.mjs";
2
+ import { C as requireAuth, T as requireRoles, y as allowPublic } from "./permissions-B1Qy55fg.mjs";
3
3
  import { multiTenantPreset } from "./presets/multiTenant.mjs";
4
4
  //#region src/presets/ownedByUser.ts
5
5
  /**
@@ -70,14 +70,16 @@ function softDeletePreset() {
70
70
  handler: "getDeleted",
71
71
  summary: "Get soft-deleted items",
72
72
  permissions: permissions.list ?? requireRoles(["admin"]),
73
- operation: "listDeleted"
73
+ operation: "listDeleted",
74
+ _presetSource: "softDelete"
74
75
  }, {
75
76
  method: "POST",
76
77
  path: "/:id/restore",
77
78
  handler: "restore",
78
79
  summary: "Restore soft-deleted item",
79
80
  permissions: permissions.update ?? requireRoles(["admin"]),
80
- operation: "restore"
81
+ operation: "restore",
82
+ _presetSource: "softDelete"
81
83
  }]
82
84
  };
83
85
  }
@@ -1,5 +1,5 @@
1
- import { r as CacheStore } from "./interface-CH0OQudo.mjs";
2
- import { i as QueryCache } from "./QueryCache-SvmT_9ti.mjs";
1
+ import { r as CacheStore } from "./interface-BVkZsvEe.mjs";
2
+ import { i as QueryCache } from "./QueryCache-8YmAQ-zW.mjs";
3
3
  import { FastifyPluginAsync } from "fastify";
4
4
 
5
5
  //#region src/cache/queryCachePlugin.d.ts
@@ -1,4 +1,4 @@
1
- import { n as IdempotencyResult, r as IdempotencyStore } from "./interface-NwJ_qPlY.mjs";
1
+ import { n as IdempotencyResult, r as IdempotencyStore } from "./interface-DoE6UoC2.mjs";
2
2
 
3
3
  //#region src/idempotency/stores/redis.d.ts
4
4
  interface RedisClient {
@@ -1,4 +1,4 @@
1
- import { a as EventTransport, i as EventLogger, n as DomainEvent, r as EventHandler } from "./EventTransport-C-2oAHtw.mjs";
1
+ import { a as EventTransport, i as EventLogger, n as DomainEvent, r as EventHandler } from "./EventTransport-B1GFiYUy.mjs";
2
2
 
3
3
  //#region src/events/transports/redis-stream.d.ts
4
4
  interface RedisStreamLike {