@classytic/arc 2.34.0 → 2.36.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 (182) hide show
  1. package/dist/{BaseController-CbkuhNI2.mjs → BaseController-CTJKm6Cb.mjs} +95 -14
  2. package/dist/{EventTransport-tq4CEB-q.mjs → EventTransport-D23Uvhg4.mjs} +1 -1
  3. package/dist/{HookSystem-DqXkMXkB.mjs → HookSystem-DgwxNZTx.mjs} +6 -4
  4. package/dist/{ResourceRegistry-DoALUlOO.mjs → ResourceRegistry-DNuYVbmv.mjs} +2 -2
  5. package/dist/{pipeline-q69DZzgG.mjs → actionPermissions-D97DniUQ.mjs} +30 -7
  6. package/dist/audit/index.d.mts +2 -2
  7. package/dist/audit/index.mjs +2 -2
  8. package/dist/auth/audit.mjs +1 -1
  9. package/dist/auth/index.d.mts +2 -2
  10. package/dist/auth/index.mjs +6 -6
  11. package/dist/auth/redis-session.mjs +1 -1
  12. package/dist/{authorizationDecision-CMMLWmuZ.mjs → authorizationDecision-EsiY3nUu.mjs} +2 -2
  13. package/dist/{base-Csjc2oF-.d.mts → base-C-NL2fTi.d.mts} +16 -1
  14. package/dist/{buildHandler-BrdYrG_1.mjs → buildHandler-Dohd2JuY.mjs} +4 -4
  15. package/dist/cache/index.mjs +4 -4
  16. package/dist/{caching-BcGmvnHh.mjs → caching-CP3GLJ7U.mjs} +2 -2
  17. package/dist/cleanup/index.d.mts +2 -2
  18. package/dist/cleanup/index.mjs +4 -4
  19. package/dist/cli/commands/describe.d.mts +1 -1
  20. package/dist/cli/commands/describe.mjs +3 -3
  21. package/dist/cli/commands/docs.mjs +3 -3
  22. package/dist/cli/commands/doctor.mjs +1 -1
  23. package/dist/cli/commands/generate.mjs +2 -2
  24. package/dist/cli/commands/init.mjs +2 -2
  25. package/dist/cli/commands/introspect.mjs +2 -2
  26. package/dist/cli/index.mjs +3 -3
  27. package/dist/{constants-BxCoxmiN.mjs → constants-DUoKAGYD.mjs} +1 -1
  28. package/dist/context/index.d.mts +16 -1
  29. package/dist/context/index.mjs +64 -4
  30. package/dist/core/index.d.mts +2 -2
  31. package/dist/core/index.mjs +7 -8
  32. package/dist/{core-BYKa8wHu.mjs → core-DTXsiNTJ.mjs} +3 -3
  33. package/dist/{createActionRouter-D6T0jWhS.mjs → createActionRouter-CZz6JO93.mjs} +27 -64
  34. package/dist/{createAggregationRouter-QLV2PrWP.mjs → createAggregationRouter-DKrGv_EC.mjs} +7 -7
  35. package/dist/defineAggregation-CpQfcM8R.mjs +25 -0
  36. package/dist/{defineResource-ZhuLonLm.mjs → defineResource-DWzjyxb2.mjs} +62 -66
  37. package/dist/discovery/index.mjs +1 -1
  38. package/dist/docs/index.d.mts +1 -1
  39. package/dist/docs/index.mjs +3 -3
  40. package/dist/{elevation-B0tlY8y1.mjs → elevation-D_cmo80j.mjs} +4 -4
  41. package/dist/encryption/index.mjs +3 -3
  42. package/dist/{errorHandler-C7xBqHsI.mjs → errorHandler-2RT0hLSI.mjs} +3 -3
  43. package/dist/{errors-Cd_yy7aW.mjs → errors-DDKni6Tz.mjs} +1 -1
  44. package/dist/{eventPlugin-DSNEZWfV.d.mts → eventPlugin-B47sfCJ8.d.mts} +1 -1
  45. package/dist/{eventPlugin-Dar8qRaj.mjs → eventPlugin-c3YwGzz9.mjs} +5 -5
  46. package/dist/events/index.d.mts +2 -2
  47. package/dist/events/index.mjs +5 -5
  48. package/dist/events/transports/redis-stream-entry.mjs +1 -1
  49. package/dist/events/transports/redis.mjs +1 -1
  50. package/dist/factory/index.d.mts +5 -5
  51. package/dist/factory/index.mjs +5 -6
  52. package/dist/{fastify-augmentation-DVsTAy6X.d.mts → fastify-augmentation-Bk_9PoiV.d.mts} +3 -3
  53. package/dist/{fields-CojeUkS4.mjs → fields-DdP225wt.mjs} +1 -1
  54. package/dist/{generate-DiB0nHPR.mjs → generate-B1bKdcV9.mjs} +2 -2
  55. package/dist/{headers-CveNO9gR.mjs → headers-DKslnhey.mjs} +2 -2
  56. package/dist/{health-CasG2A28.mjs → health-k6NODS8f.mjs} +1 -1
  57. package/dist/hooks/index.d.mts +1 -1
  58. package/dist/hooks/index.mjs +2 -2
  59. package/dist/idempotency/index.mjs +22 -56
  60. package/dist/idempotency/redis.mjs +1 -1
  61. package/dist/{index-B-DvUzsr.d.mts → index-BtYoMip1.d.mts} +21 -3
  62. package/dist/{index-BcR2bFZQ.d.mts → index-C01wWGJt.d.mts} +33 -105
  63. package/dist/{index-C5NnMEKF.d.mts → index-C50BUxKs.d.mts} +1 -1
  64. package/dist/{index-CFAJEWCX.d.mts → index-RdpLP71b.d.mts} +7 -30
  65. package/dist/{index-ugr1tQlW.d.mts → index-eyg5WEcy.d.mts} +1 -1
  66. package/dist/index.d.mts +5 -5
  67. package/dist/index.mjs +11 -12
  68. package/dist/{init-mdJ-v4Xp.mjs → init-LuGxIbQ_.mjs} +1 -1
  69. package/dist/integrations/event-gateway.mjs +2 -2
  70. package/dist/integrations/index.d.mts +2 -2
  71. package/dist/integrations/index.mjs +1 -1
  72. package/dist/integrations/jobs/index.d.mts +1 -1
  73. package/dist/integrations/jobs/index.mjs +2 -2
  74. package/dist/integrations/mcp/index.d.mts +4 -4
  75. package/dist/integrations/mcp/index.mjs +2 -2
  76. package/dist/integrations/mcp/testing.d.mts +1 -1
  77. package/dist/integrations/mcp/testing.mjs +3 -3
  78. package/dist/integrations/streamline.mjs +3 -3
  79. package/dist/integrations/webhooks.d.mts +4 -2
  80. package/dist/integrations/webhooks.mjs +28 -27
  81. package/dist/integrations/websocket-pushref-redis.mjs +1 -1
  82. package/dist/integrations/websocket-redis.mjs +1 -1
  83. package/dist/integrations/websocket.mjs +1 -1
  84. package/dist/{interface-DznJrm9R.mjs → interface-B7awDx3b.mjs} +1 -1
  85. package/dist/{jsonBody-CfGH-GxO.mjs → jsonBody-CiHih3ZU.mjs} +2 -2
  86. package/dist/{keys-WeKvY1dE.mjs → keys-BcNl12HN.mjs} +1 -1
  87. package/dist/{loadResources-Bj-aSsXi.mjs → loadResources-DGlfRQc9.mjs} +2 -2
  88. package/dist/{loadResourcesFromEntry-DwaxNcE2.mjs → loadResourcesFromEntry-VYPGSas8.mjs} +2 -2
  89. package/dist/logger/index.d.mts +23 -4
  90. package/dist/logger/index.mjs +50 -7
  91. package/dist/{mcpPlugin-DX3PHD7K.mjs → mcpPlugin-NxSlc-Ks.mjs} +15 -16
  92. package/dist/{memory-qYSB7rMQ.mjs → memory-BU-C-MV3.mjs} +2 -2
  93. package/dist/{metrics-D7uchLjN.mjs → metrics-7_1GbJpl.mjs} +2 -2
  94. package/dist/middleware/index.d.mts +1 -1
  95. package/dist/middleware/index.mjs +3 -3
  96. package/dist/migrations/index.mjs +7 -4
  97. package/dist/{module-CwKsgZXp.mjs → module-CQNHzE7g.mjs} +1 -1
  98. package/dist/{multipartBody-4w-S3-PU.mjs → multipartBody-Bi5yqW0M.mjs} +1 -1
  99. package/dist/{openapi-CkV5mQ53.mjs → openapi-RpNbPBL-.mjs} +6 -6
  100. package/dist/outbox-admin/index.d.mts +2 -2
  101. package/dist/outbox-admin/index.mjs +4 -4
  102. package/dist/{outbox-module-D1QfHqO4.mjs → outbox-module-ClcFrq98.mjs} +6 -4
  103. package/dist/{outbox-module-C1Bs_FXF.d.mts → outbox-module-b1TyDGGi.d.mts} +24 -1
  104. package/dist/permissions/index.d.mts +2 -2
  105. package/dist/permissions/index.mjs +5 -5
  106. package/dist/{permissions-CP47jdi7.mjs → permissions-DgPrZsJh.mjs} +36 -9
  107. package/dist/{pipe-BzzoEhHj.mjs → pipe-Cjy1UiXX.mjs} +2 -2
  108. package/dist/pipeline/index.d.mts +1 -1
  109. package/dist/pipeline/index.mjs +2 -2
  110. package/dist/plugins/index.d.mts +92 -7
  111. package/dist/plugins/index.mjs +168 -17
  112. package/dist/plugins/response-cache.mjs +2 -2
  113. package/dist/plugins/tracing-entry.mjs +2 -2
  114. package/dist/{pluralize-B7xf8qZF.mjs → pluralize-B7m0dPm8.mjs} +1 -1
  115. package/dist/{preloadResources-g07ldtzh.d.mts → preloadResources-BuutdWgr.d.mts} +54 -270
  116. package/dist/{createApp-zR-DEesr.mjs → preloadResources-CjnW4iIX.mjs} +170 -34
  117. package/dist/presets/filesUpload.d.mts +3 -3
  118. package/dist/presets/filesUpload.mjs +5 -5
  119. package/dist/presets/index.d.mts +2 -2
  120. package/dist/presets/index.mjs +2 -2
  121. package/dist/presets/multiTenant.d.mts +1 -1
  122. package/dist/presets/multiTenant.mjs +3 -3
  123. package/dist/presets/search.d.mts +9 -47
  124. package/dist/presets/search.mjs +2 -2
  125. package/dist/{presets-BAPijK_A.mjs → presets-CsGg8B58.mjs} +4 -4
  126. package/dist/{projection-BKM0Ui-s.mjs → projection-koISNyQo.mjs} +2 -2
  127. package/dist/{queryCachePlugin-DhsZ0ZIc.mjs → queryCachePlugin-DHueQT9O.mjs} +5 -5
  128. package/dist/{rateLimitKey-XngqVNEV.mjs → rateLimitKey-Bma8VXDO.mjs} +1 -1
  129. package/dist/{realtime-G1TC6Yzc.mjs → realtime-BBoC91Bz.mjs} +9 -9
  130. package/dist/{rateLimit-D4-ASwsI.mjs → registerRoute-BTgORTlR.mjs} +114 -3
  131. package/dist/registry/index.d.mts +2 -2
  132. package/dist/registry/index.mjs +3 -3
  133. package/dist/{registry-CLxCR5LF.mjs → registry-BzvRCYS6.mjs} +15 -56
  134. package/dist/{renewingLease-BnWtbA2U.mjs → renewingLease-CgIGh0Ab.mjs} +14 -2
  135. package/dist/{replyHelpers-BSBVCwL3.mjs → replyHelpers-BN16KPHr.mjs} +2 -2
  136. package/dist/{requestContext-uYE35JxI.mjs → requestContext-B93dmpf0.mjs} +1 -1
  137. package/dist/{rolldown-runtime-DNg-KNCk.mjs → rolldown-runtime-JJWfp8rp.mjs} +1 -1
  138. package/dist/{runtimeCapabilities-Dm88DaTW.mjs → runtimeCapabilities-jK99Ciw_.mjs} +1 -1
  139. package/dist/{schedules-CAcZgia0.mjs → schedules-CKKEL21P.mjs} +6 -4
  140. package/dist/{schemaConverter-XH1RZRyu.mjs → schemaConverter-gg0_aUPF.mjs} +19 -32
  141. package/dist/{schemaIR-CDvclAew.mjs → schemaIR-CngYvpCF.mjs} +2 -2
  142. package/dist/schemas/index.mjs +1 -1
  143. package/dist/scim/index.mjs +2 -2
  144. package/dist/scope/index.d.mts +1 -1
  145. package/dist/scope/index.mjs +5 -5
  146. package/dist/{scope-BaWcqBvr.mjs → scope-B1RsHdjF.mjs} +4 -4
  147. package/dist/simpleEqualityMatcher-_9NAMHUD.mjs +43 -0
  148. package/dist/{sse-Cf0jUHsh.mjs → sse-Bxt03Hk5.mjs} +5 -5
  149. package/dist/{sseStream-Bvpi-UXQ.mjs → sseStream-BMHTLlhR.mjs} +2 -2
  150. package/dist/{store-helpers-B88p99Z2.mjs → store-helpers-Brt8OxWZ.mjs} +1 -1
  151. package/dist/{streaming-DsT1B5zJ.mjs → streaming-CiTJW8kZ.mjs} +1 -1
  152. package/dist/{subscribe-helpers-CLIOHsOK.mjs → subscribe-helpers-B4fNHRNc.mjs} +21 -49
  153. package/dist/sync/changelog.mjs +1 -1
  154. package/dist/testing/cleanupStoreContract.mjs +1 -1
  155. package/dist/testing/index.d.mts +6 -6
  156. package/dist/testing/index.mjs +10 -11
  157. package/dist/testing/outboxStoreContract.mjs +1 -1
  158. package/dist/testing/storageContract.mjs +1 -1
  159. package/dist/{transactionContext-BYtWnwAS.mjs → transactionContext-D2HwJM2z.mjs} +1 -1
  160. package/dist/{typeGuards-jjKJDN13.mjs → typeGuards-IE1laMDf.mjs} +1 -1
  161. package/dist/types/index.d.mts +3 -3
  162. package/dist/types/index.mjs +1 -1
  163. package/dist/types/storage.mjs +1 -1
  164. package/dist/{types-CoqIRrkY.mjs → types-B8lwJv9Q.mjs} +10 -53
  165. package/dist/{types-BfC83KsP.d.mts → types-BXnTOsXP.d.mts} +1 -1
  166. package/dist/{types-BcTumAz_.d.mts → types-DDD5RKTu.d.mts} +30 -87
  167. package/dist/{types-ChuShpCP.d.mts → types-Ddl9OjmE.d.mts} +18 -121
  168. package/dist/usage/index.d.mts +2 -2
  169. package/dist/usage/index.mjs +0 -0
  170. package/dist/{userHelpers-CWF5pbZD.mjs → userHelpers-xQAi5bPF.mjs} +1 -1
  171. package/dist/utils/index.d.mts +16 -42
  172. package/dist/utils/index.mjs +11 -11
  173. package/dist/{utils-h1V_sT-t.mjs → utils-DgNFsJ2c.mjs} +3 -3
  174. package/dist/{validate-CdQlz52F.mjs → validate-DI3L5V9w.mjs} +2 -2
  175. package/dist/{versioning-rZbyLVD-.mjs → versioning-COpAyfpZ.mjs} +3 -3
  176. package/package.json +8 -8
  177. package/dist/actionPermissions-CUeCmpUd.mjs +0 -25
  178. package/dist/defineAggregation-Nv-DwfOf.mjs +0 -48
  179. package/dist/preloadResources-BkyqeTDz.mjs +0 -46
  180. package/dist/registerRoute-Ba8v0PCa.mjs +0 -113
  181. package/dist/repositoryFilter-BANpz6BX.mjs +0 -78
  182. package/dist/simpleEqualityMatcher-Se8VrsaP.mjs +0 -69
@@ -1,10 +1,10 @@
1
- /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
- import { t as __exportAll } from "./rolldown-runtime-DNg-KNCk.mjs";
1
+ /*! @classytic/arc v2.36.0 | MIT | https://classytic.com */
2
+ import { t as __exportAll } from "./rolldown-runtime-JJWfp8rp.mjs";
3
3
  import { arcLog } from "./logger/index.mjs";
4
- import { a as conjoinPolicyFilters, i as normalizeToDecision } from "./authorizationDecision-CMMLWmuZ.mjs";
5
- import { n as getUserRoles } from "./userHelpers-CWF5pbZD.mjs";
6
- 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, n as PUBLIC_SCOPE, o as getMandate, p as getServiceScopes, u as getRequestScope, v as hasOrgAccess, x as isMember } from "./types-CoqIRrkY.mjs";
7
- import { t as MemoryCacheStore } from "./memory-qYSB7rMQ.mjs";
4
+ import { a as conjoinPolicyFilters, i as normalizeToDecision } from "./authorizationDecision-EsiY3nUu.mjs";
5
+ import { n as getUserRoles } from "./userHelpers-xQAi5bPF.mjs";
6
+ 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, n as PUBLIC_SCOPE, o as getMandate, p as getServiceScopes, u as getRequestScope, v as hasOrgAccess, x as isMember } from "./types-B8lwJv9Q.mjs";
7
+ import { t as MemoryCacheStore } from "./memory-BU-C-MV3.mjs";
8
8
  import { randomUUID } from "node:crypto";
9
9
  //#region src/permissions/context.ts
10
10
  /**
@@ -260,7 +260,8 @@ function allOf(...checks) {
260
260
  });
261
261
  };
262
262
  const roleBranches = checks.filter((c) => (c._roles?.length ?? 0) > 0 || (c._orgRoles?.length ?? 0) > 0);
263
- if (checks.length > 0 && checks.every((c) => c._isPublic)) check._isPublic = true;
263
+ if (checks.some((c) => c._isDenied)) check._isDenied = true;
264
+ else if (checks.length > 0 && checks.every((c) => c._isPublic)) check._isPublic = true;
264
265
  else if (roleBranches.length === 1) {
265
266
  const [rb] = roleBranches;
266
267
  if (rb?._roles?.length) check._roles = [...rb._roles];
@@ -300,7 +301,8 @@ function anyOf(...checks) {
300
301
  }
301
302
  return deny(reasons.join("; "));
302
303
  };
303
- if (checks.some((c) => c._isPublic)) check._isPublic = true;
304
+ if (checks.length > 0 && checks.every((c) => c._isDenied)) check._isDenied = true;
305
+ else if (checks.some((c) => c._isPublic)) check._isPublic = true;
304
306
  else if (checks.every((c) => (c._roles?.length ?? 0) > 0 || (c._orgRoles?.length ?? 0) > 0)) {
305
307
  const roles = /* @__PURE__ */ new Set();
306
308
  const orgRoles = /* @__PURE__ */ new Set();
@@ -344,7 +346,16 @@ function not(check, reason = "Access denied") {
344
346
  * ```
345
347
  */
346
348
  function denyAll(reason = "Access denied") {
347
- return () => deny(reason);
349
+ const check = () => deny(reason);
350
+ /**
351
+ * MARKED, not bare. Introspection (`describePermission`) reads metadata, and a
352
+ * closure with none fell through to `{ kind: "authenticated" }` — publishing
353
+ * the library's most restrictive gate as "any signed-in user may do this".
354
+ * Enforcement was never wrong; the description was, in the direction that
355
+ * makes a UI offer an action nobody can perform.
356
+ */
357
+ check._isDenied = true;
358
+ return check;
348
359
  }
349
360
  /**
350
361
  * Dynamic permission based on a condition function.
@@ -1068,6 +1079,7 @@ function createDynamicPermissionMatrix(config) {
1068
1079
  * fully captured statically carries `conditional: true` (see `_conditional`).
1069
1080
  */
1070
1081
  function describePermission(check) {
1082
+ if (check._isDenied) return { kind: "denied" };
1071
1083
  if (check._isPublic) return { kind: "public" };
1072
1084
  const roles = [...check._roles ?? [], ...check._orgRoles ?? []];
1073
1085
  const scope = check._scopeContext;
@@ -1100,6 +1112,17 @@ function describePermission(check) {
1100
1112
  */
1101
1113
  function explainAccess(check, principal) {
1102
1114
  const requirement = describePermission(check);
1115
+ /**
1116
+ * DEFINITIVE, and checked before everything else. `denyAll()` is the one gate
1117
+ * whose answer needs no principal and no request — reporting it as
1118
+ * `conditional` (which "authenticated" does) tells a UI to render the action
1119
+ * and find out later, and later is a 403 the user cannot act on.
1120
+ */
1121
+ if (requirement.kind === "denied") return {
1122
+ decision: "deny",
1123
+ reason: "denied to everyone",
1124
+ requirement
1125
+ };
1103
1126
  if (requirement.kind === "public") return {
1104
1127
  decision: "allow",
1105
1128
  reason: "public — no authentication required",
@@ -1594,6 +1617,10 @@ function introspectCheck(check) {
1594
1617
  roles: req.roles ? [...req.roles] : [],
1595
1618
  scope: { ...req.scope }
1596
1619
  };
1620
+ if (req.kind === "denied") return {
1621
+ type: "denied",
1622
+ roles: []
1623
+ };
1597
1624
  return {
1598
1625
  type: "authenticated",
1599
1626
  roles: []
@@ -1,5 +1,5 @@
1
- /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
- import { r as ForbiddenError } from "./errors-Cd_yy7aW.mjs";
1
+ /*! @classytic/arc v2.36.0 | MIT | https://classytic.com */
2
+ import { r as ForbiddenError } from "./errors-DDKni6Tz.mjs";
3
3
  //#region src/pipeline/pipe.ts
4
4
  /**
5
5
  * Compose pipeline steps into an ordered array.
@@ -1,5 +1,5 @@
1
1
 
2
- import { Ct as PipelineStep, Fn as IControllerResponse, St as PipelineContext, _t as Guard, bt as OperationFilter, vt as Interceptor, wt as Transform, xt as PipelineConfig, yt as NextFunction } from "../index-BcR2bFZQ.mjs";
2
+ import { Ct as PipelineStep, Fn as IControllerResponse, St as PipelineContext, _t as Guard, bt as OperationFilter, vt as Interceptor, wt as Transform, xt as PipelineConfig, yt as NextFunction } from "../index-C01wWGJt.mjs";
3
3
  //#region src/pipeline/guard.d.ts
4
4
  interface GuardOptions {
5
5
  operations?: OperationFilter;
@@ -1,5 +1,5 @@
1
- /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
- import { n as pipe, t as executePipeline } from "../pipe-BzzoEhHj.mjs";
1
+ /*! @classytic/arc v2.36.0 | MIT | https://classytic.com */
2
+ import { n as pipe, t as executePipeline } from "../pipe-Cjy1UiXX.mjs";
3
3
  //#region src/pipeline/guard.ts
4
4
  /**
5
5
  * Create a named guard.
@@ -1,10 +1,10 @@
1
1
 
2
- import { t as AnyRecord } from "../base-Csjc2oF-.mjs";
3
- import { At as RouteDefinition, J as PresetHook, Q as MiddlewareConfig, et as RouteSchemaOptions } from "../index-BcR2bFZQ.mjs";
2
+ import { t as AnyRecord } from "../base-C-NL2fTi.mjs";
3
+ import { At as RouteDefinition, J as PresetHook, Q as MiddlewareConfig, et as RouteSchemaOptions } from "../index-C01wWGJt.mjs";
4
4
  import { i as errorHandlerPlugin, n as ErrorMapper, r as defaultIsDuplicateKeyError, t as ErrorHandlerOptions } from "../errorHandler-DI5AN2b5.mjs";
5
- import { _ as HealthCheck, b as healthPlugin, f as ScheduleDefinition, g as schedulesPlugin, h as _default$7, m as SchedulesPluginOptions, p as ScheduleStats, v as HealthOptions, y as _default$3 } from "../types-BcTumAz_.mjs";
6
- import { a as arcCorePlugin, i as _default, n as ArcCorePluginOptions, r as PluginMeta, t as ArcCore } from "../fastify-augmentation-DVsTAy6X.mjs";
7
- import { _ as CachingRule, a as MetricsCollector, c as metricsPlugin, d as _default$5, f as realtimePlugin, g as CachingOptions, h as ssePlugin, i as MetricEntry, l as ChangeOperation, m as _default$8, n as _default$9, o as MetricsOptions, p as SSEOptions, r as versioningPlugin, s as _default$4, t as VersioningOptions, u as RealtimeOptions, v as _default$1, y as cachingPlugin } from "../versioning-DEs-_hFi.mjs";
5
+ import { _ as HealthCheck, b as healthPlugin, f as ScheduleDefinition, g as schedulesPlugin, h as _default$8, m as SchedulesPluginOptions, p as ScheduleStats, v as HealthOptions, y as _default$3 } from "../types-DDD5RKTu.mjs";
6
+ import { a as arcCorePlugin, i as _default, n as ArcCorePluginOptions, r as PluginMeta, t as ArcCore } from "../fastify-augmentation-Bk_9PoiV.mjs";
7
+ import { _ as CachingRule, a as MetricsCollector, c as metricsPlugin, d as _default$6, f as realtimePlugin, g as CachingOptions, h as ssePlugin, i as MetricEntry, l as ChangeOperation, m as _default$9, n as _default$10, o as MetricsOptions, p as SSEOptions, r as versioningPlugin, s as _default$4, t as VersioningOptions, u as RealtimeOptions, v as _default$1, y as cachingPlugin } from "../versioning-DEs-_hFi.mjs";
8
8
  import { t as TracingOptions } from "../tracing-Dro5eH6j.mjs";
9
9
  import { PaginatedResult } from "@classytic/repo-core/pagination";
10
10
  import { FastifyInstance, FastifyPluginAsync } from "fastify";
@@ -89,6 +89,91 @@ declare module "fastify" {
89
89
  }
90
90
  declare const _default$2: FastifyPluginAsync<GracefulShutdownOptions>;
91
91
  //#endregion
92
+ //#region src/plugins/pressure.d.ts
93
+ /**
94
+ * One measurable source of load, normalised by whoever owns it.
95
+ *
96
+ * A throwing `read()` reports 0, never 1 — a broken thermometer is not a fire.
97
+ * Logged once per signal, not per sample.
98
+ */
99
+ interface PressureSignal {
100
+ /** Stable identity — appears in snapshots and logs. */
101
+ readonly name: string;
102
+ /** Saturation in [0, 1]. Values outside the range are clamped. */
103
+ read(): number | Promise<number>;
104
+ }
105
+ /** `ok` → serve · `degraded` → serve, shed what is optional · `saturated` → refuse. */
106
+ type PressureState = "ok" | "degraded" | "saturated";
107
+ interface PressureThresholds {
108
+ /** Enter `degraded` at or above this. Default 0.8. */
109
+ degraded?: number;
110
+ /** Enter `saturated` at or above this. Default 0.95. */
111
+ saturated?: number;
112
+ }
113
+ interface PressureSnapshot {
114
+ readonly state: PressureState;
115
+ /** Highest signal — the one that decided the state. */
116
+ readonly worst: {
117
+ name: string;
118
+ value: number;
119
+ } | undefined;
120
+ readonly signals: Readonly<Record<string, number>>;
121
+ }
122
+ interface PressureOptions {
123
+ /** Extra signals beyond the built-in ELU reader. */
124
+ signals?: PressureSignal[];
125
+ thresholds?: PressureThresholds;
126
+ /** Sampling cadence in ms. Default 1000. */
127
+ intervalMs?: number;
128
+ /** Include the built-in event-loop-utilisation signal. Default true. */
129
+ eventLoopUtilization?: boolean;
130
+ /** Fired when the state CHANGES — not on every sample. */
131
+ onStateChange?: (next: PressureState, snapshot: PressureSnapshot) => void;
132
+ /**
133
+ * Refuse with 503 while `saturated`. Default OFF — arc cannot know which
134
+ * routes are sheddable, and refusing a checkout because a dashboard drained
135
+ * the pool is worse than serving it slowly.
136
+ *
137
+ * `exclude` matches `request.url` by prefix. Health routes are ALWAYS exempt:
138
+ * a 503 on `/_health/ready` reads as "process dead", not "process full".
139
+ */
140
+ shed?: boolean | {
141
+ statusCode?: number;
142
+ retryAfterSeconds?: number;
143
+ exclude?: string[];
144
+ };
145
+ }
146
+ interface PressureApi {
147
+ state(): PressureState;
148
+ snapshot(): PressureSnapshot;
149
+ /** Register a signal after boot — for modules that resolve their source late. */
150
+ register(signal: PressureSignal): void;
151
+ /** True at `saturated`. The check a handler makes before admitting work. */
152
+ shouldShed(): boolean;
153
+ /**
154
+ * A readiness check for `healthPlugin({ checks })`. NOT auto-registered:
155
+ * flipping readiness pulls the instance from its load balancer — right in a
156
+ * fleet, a self-inflicted outage for a single instance. Unhealthy only at
157
+ * `saturated`; `degraded` still serves.
158
+ */
159
+ readinessCheck(options?: {
160
+ name?: string;
161
+ critical?: boolean;
162
+ }): HealthCheck;
163
+ }
164
+ declare module "fastify" {
165
+ interface FastifyInstance {
166
+ pressure: PressureApi;
167
+ }
168
+ }
169
+ declare const pressurePlugin: FastifyPluginAsync<PressureOptions>;
170
+ /**
171
+ * `fp()`-wrapped so `fastify.pressure` is visible to the whole app rather than
172
+ * trapped in this plugin's encapsulation context — the named export above is
173
+ * the raw plugin, matching every other arc plugin entry.
174
+ */
175
+ declare const _default$5: FastifyPluginAsync<PressureOptions>;
176
+ //#endregion
92
177
  //#region src/plugins/replyHelpers.d.ts
93
178
  declare module "fastify" {
94
179
  interface FastifyReply {
@@ -166,6 +251,6 @@ declare function createRequestIdGenerator(opts?: Pick<RequestIdOptions, "header"
166
251
  headers: Record<string, string | string[] | undefined>;
167
252
  }) => string;
168
253
  declare const requestIdPlugin: FastifyPluginAsync<RequestIdOptions>;
169
- declare const _default$6: FastifyPluginAsync<RequestIdOptions>;
254
+ declare const _default$7: FastifyPluginAsync<RequestIdOptions>;
170
255
  //#endregion
171
- export { type ArcCore, type ArcCorePluginOptions, type ArcPlugin, type CachingOptions, type CachingRule, type ChangeOperation, type CreatePluginDefinition, type ErrorHandlerOptions, type ErrorMapper, type GracefulShutdownOptions, type HealthCheck, type HealthOptions, type MetricEntry, type MetricsCollector, type MetricsOptions, type PluginMeta, type PluginResourceResult, type RealtimeOptions, type RequestIdOptions, type SSEOptions, type ScheduleDefinition, type ScheduleStats, type SchedulesPluginOptions, type TracingOptions, type VersioningOptions, _default as arcCorePlugin, arcCorePlugin as arcCorePluginFn, _default$1 as cachingPlugin, cachingPlugin as cachingPluginFn, createPlugin, createRequestIdGenerator, defaultIsDuplicateKeyError, errorHandlerPlugin, errorHandlerPlugin as errorHandlerPluginFn, _default$2 as gracefulShutdownPlugin, gracefulShutdownPlugin as gracefulShutdownPluginFn, _default$3 as healthPlugin, healthPlugin as healthPluginFn, _default$4 as metricsPlugin, metricsPlugin as metricsPluginFn, _default$5 as realtimePlugin, realtimePlugin as realtimePluginFn, replyHelpersPlugin, _default$6 as requestIdPlugin, requestIdPlugin as requestIdPluginFn, sanitizeRequestId, _default$7 as schedulesPlugin, schedulesPlugin as schedulesPluginFn, _default$8 as ssePlugin, ssePlugin as ssePluginFn, _default$9 as versioningPlugin, versioningPlugin as versioningPluginFn };
256
+ export { type ArcCore, type ArcCorePluginOptions, type ArcPlugin, type CachingOptions, type CachingRule, type ChangeOperation, type CreatePluginDefinition, type ErrorHandlerOptions, type ErrorMapper, type GracefulShutdownOptions, type HealthCheck, type HealthOptions, type MetricEntry, type MetricsCollector, type MetricsOptions, type PluginMeta, type PluginResourceResult, type PressureApi, type PressureOptions, type PressureSignal, type PressureSnapshot, type PressureState, type PressureThresholds, type RealtimeOptions, type RequestIdOptions, type SSEOptions, type ScheduleDefinition, type ScheduleStats, type SchedulesPluginOptions, type TracingOptions, type VersioningOptions, _default as arcCorePlugin, arcCorePlugin as arcCorePluginFn, _default$1 as cachingPlugin, cachingPlugin as cachingPluginFn, createPlugin, createRequestIdGenerator, defaultIsDuplicateKeyError, errorHandlerPlugin, errorHandlerPlugin as errorHandlerPluginFn, _default$2 as gracefulShutdownPlugin, gracefulShutdownPlugin as gracefulShutdownPluginFn, _default$3 as healthPlugin, healthPlugin as healthPluginFn, _default$4 as metricsPlugin, metricsPlugin as metricsPluginFn, _default$5 as pressurePlugin, pressurePlugin as pressurePluginFn, _default$6 as realtimePlugin, realtimePlugin as realtimePluginFn, replyHelpersPlugin, _default$7 as requestIdPlugin, requestIdPlugin as requestIdPluginFn, sanitizeRequestId, _default$8 as schedulesPlugin, schedulesPlugin as schedulesPluginFn, _default$9 as ssePlugin, ssePlugin as ssePluginFn, _default$10 as versioningPlugin, versioningPlugin as versioningPluginFn };
@@ -1,20 +1,22 @@
1
- /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
- import { p as MUTATION_OPERATIONS } from "../constants-BxCoxmiN.mjs";
3
- import { c as getOrgId } from "../types-CoqIRrkY.mjs";
4
- import { n as requestContext } from "../requestContext-uYE35JxI.mjs";
5
- import { t as hasEvents } from "../typeGuards-jjKJDN13.mjs";
6
- import { t as HookSystem } from "../HookSystem-DqXkMXkB.mjs";
7
- import { t as ResourceRegistry } from "../ResourceRegistry-DoALUlOO.mjs";
8
- import { a as requestIdPlugin, i as createRequestIdGenerator, n as health_default, o as requestId_default, s as sanitizeRequestId, t as healthPlugin } from "../health-CasG2A28.mjs";
9
- import { n as caching_default, t as cachingPlugin } from "../caching-BcGmvnHh.mjs";
10
- import { n as errorHandlerPlugin, t as defaultIsDuplicateKeyError } from "../errorHandler-C7xBqHsI.mjs";
11
- import { n as metrics_default, t as metricsPlugin } from "../metrics-D7uchLjN.mjs";
12
- import { n as realtime_default, t as realtimePlugin } from "../realtime-G1TC6Yzc.mjs";
13
- import { t as replyHelpersPlugin } from "../replyHelpers-BSBVCwL3.mjs";
14
- import { n as schedules_default, t as schedulesPlugin } from "../schedules-CAcZgia0.mjs";
15
- import { n as sse_default, t as ssePlugin } from "../sse-Cf0jUHsh.mjs";
16
- import { n as versioning_default, t as versioningPlugin } from "../versioning-rZbyLVD-.mjs";
1
+ /*! @classytic/arc v2.36.0 | MIT | https://classytic.com */
2
+ import { p as MUTATION_OPERATIONS } from "../constants-DUoKAGYD.mjs";
3
+ import { arcLog } from "../logger/index.mjs";
4
+ import { c as getOrgId } from "../types-B8lwJv9Q.mjs";
5
+ import { n as requestContext } from "../requestContext-B93dmpf0.mjs";
6
+ import { t as hasEvents } from "../typeGuards-IE1laMDf.mjs";
7
+ import { t as HookSystem } from "../HookSystem-DgwxNZTx.mjs";
8
+ import { t as ResourceRegistry } from "../ResourceRegistry-DNuYVbmv.mjs";
9
+ import { a as requestIdPlugin, i as createRequestIdGenerator, n as health_default, o as requestId_default, s as sanitizeRequestId, t as healthPlugin } from "../health-k6NODS8f.mjs";
10
+ import { n as caching_default, t as cachingPlugin } from "../caching-CP3GLJ7U.mjs";
11
+ import { n as errorHandlerPlugin, t as defaultIsDuplicateKeyError } from "../errorHandler-2RT0hLSI.mjs";
12
+ import { n as metrics_default, t as metricsPlugin } from "../metrics-7_1GbJpl.mjs";
13
+ import { n as realtime_default, t as realtimePlugin } from "../realtime-BBoC91Bz.mjs";
14
+ import { t as replyHelpersPlugin } from "../replyHelpers-BN16KPHr.mjs";
15
+ import { n as schedules_default, t as schedulesPlugin } from "../schedules-CKKEL21P.mjs";
16
+ import { n as sse_default, t as ssePlugin } from "../sse-Bxt03Hk5.mjs";
17
+ import { n as versioning_default, t as versioningPlugin } from "../versioning-COpAyfpZ.mjs";
17
18
  import fp from "fastify-plugin";
19
+ import { performance as performance$1 } from "node:perf_hooks";
18
20
  //#region src/core/arcCorePlugin.ts
19
21
  const arcCorePlugin = async (fastify, opts = {}) => {
20
22
  const { emitEvents = true, hookSystem, registry } = opts;
@@ -205,4 +207,153 @@ var gracefulShutdown_default = fp(gracefulShutdownPlugin, {
205
207
  fastify: "5.x"
206
208
  });
207
209
  //#endregion
208
- export { arcCorePlugin_default as arcCorePlugin, arcCorePlugin as arcCorePluginFn, caching_default as cachingPlugin, cachingPlugin as cachingPluginFn, createPlugin, createRequestIdGenerator, defaultIsDuplicateKeyError, errorHandlerPlugin, errorHandlerPlugin as errorHandlerPluginFn, gracefulShutdown_default as gracefulShutdownPlugin, gracefulShutdownPlugin as gracefulShutdownPluginFn, health_default as healthPlugin, healthPlugin as healthPluginFn, metrics_default as metricsPlugin, metricsPlugin as metricsPluginFn, realtime_default as realtimePlugin, realtimePlugin as realtimePluginFn, replyHelpersPlugin, requestId_default as requestIdPlugin, requestIdPlugin as requestIdPluginFn, sanitizeRequestId, schedules_default as schedulesPlugin, schedulesPlugin as schedulesPluginFn, sse_default as ssePlugin, ssePlugin as ssePluginFn, versioning_default as versioningPlugin, versioningPlugin as versioningPluginFn };
210
+ //#region src/plugins/pressure.ts
211
+ /**
212
+ * Admission control — ONE place that decides "are we saturated?".
213
+ *
214
+ * Arc already sheds on event-loop lag (`@fastify/under-pressure`), but lag is
215
+ * LATE: by the time the loop is behind, the work causing it is already
216
+ * admitted. What actually saturates a backend — a drained pool, an undrained
217
+ * queue, an outbox falling behind — is invisible to it. Rate limiting is a
218
+ * different axis: it bounds request FREQUENCY, this bounds concurrent
219
+ * EXPENSIVE WORK.
220
+ *
221
+ * Signals are REGISTERED, not enumerated, because only ELU has a source arc
222
+ * can read today — no kit exposes pool stats, no queue exposes depth. Arc owns
223
+ * the DECISION; signals come from whoever can produce them.
224
+ *
225
+ * `read()` returns SATURATION in [0,1]. Each signal normalises itself: 90% of
226
+ * a pool is nearly fatal, 90% ELU is merely busy, and only the signal's author
227
+ * knows which.
228
+ *
229
+ * @example
230
+ * ```typescript
231
+ * await app.register(pressurePlugin, {
232
+ * signals: [{ name: 'db-pool', read: () => pool.borrowed / pool.size }],
233
+ * shed: true,
234
+ * });
235
+ * ```
236
+ */
237
+ const log = arcLog("pressure");
238
+ /**
239
+ * Event-loop utilisation as saturation — already a [0,1] ratio, and measured
240
+ * ACROSS the interval rather than instantaneously: one lag reading is a spike,
241
+ * utilisation over a second is load.
242
+ */
243
+ function eluSignal() {
244
+ let previous = performance$1.eventLoopUtilization();
245
+ return {
246
+ name: "event-loop",
247
+ read() {
248
+ const current = performance$1.eventLoopUtilization();
249
+ const delta = performance$1.eventLoopUtilization(current, previous);
250
+ previous = current;
251
+ return delta.utilization;
252
+ }
253
+ };
254
+ }
255
+ const clamp = (n) => Number.isFinite(n) ? Math.min(1, Math.max(0, n)) : 0;
256
+ const pressurePlugin = async (fastify, options) => {
257
+ const degradedAt = options.thresholds?.degraded ?? .8;
258
+ const saturatedAt = options.thresholds?.saturated ?? .95;
259
+ if (degradedAt > saturatedAt) throw new Error(`pressurePlugin: thresholds.degraded (${degradedAt}) is above thresholds.saturated (${saturatedAt}) — 'saturated' would be unreachable and the app would never shed.`);
260
+ const signals = [...options.eventLoopUtilization === false ? [] : [eluSignal()], ...options.signals ?? []];
261
+ /** Signals that threw. Logged once each — a broken reader must not spam. */
262
+ const broken = /* @__PURE__ */ new Set();
263
+ let snapshot = {
264
+ state: "ok",
265
+ worst: void 0,
266
+ signals: {}
267
+ };
268
+ async function sample() {
269
+ const values = {};
270
+ let worst;
271
+ for (const signal of signals) {
272
+ let value = 0;
273
+ try {
274
+ value = clamp(await signal.read());
275
+ broken.delete(signal.name);
276
+ } catch (error) {
277
+ if (!broken.has(signal.name)) {
278
+ broken.add(signal.name);
279
+ log.warn("pressure signal failed to read; treating as 0", {
280
+ signal: signal.name,
281
+ error
282
+ });
283
+ }
284
+ }
285
+ values[signal.name] = value;
286
+ if (!worst || value > worst.value) worst = {
287
+ name: signal.name,
288
+ value
289
+ };
290
+ }
291
+ const peak = worst?.value ?? 0;
292
+ const next = peak >= saturatedAt ? "saturated" : peak >= degradedAt ? "degraded" : "ok";
293
+ const previous = snapshot.state;
294
+ snapshot = {
295
+ state: next,
296
+ worst,
297
+ signals: values
298
+ };
299
+ if (next !== previous) {
300
+ log.info("pressure state changed", {
301
+ from: previous,
302
+ to: next,
303
+ worst
304
+ });
305
+ options.onStateChange?.(next, snapshot);
306
+ }
307
+ }
308
+ const timer = setInterval(() => {
309
+ sample();
310
+ }, options.intervalMs ?? 1e3);
311
+ timer.unref?.();
312
+ fastify.addHook("onClose", async () => clearInterval(timer));
313
+ fastify.decorate("pressure", {
314
+ state: () => snapshot.state,
315
+ snapshot: () => snapshot,
316
+ register: (signal) => {
317
+ signals.push(signal);
318
+ },
319
+ shouldShed: () => snapshot.state === "saturated",
320
+ readinessCheck: (checkOptions) => ({
321
+ name: checkOptions?.name ?? "pressure",
322
+ critical: checkOptions?.critical ?? true,
323
+ check: () => snapshot.state !== "saturated"
324
+ })
325
+ });
326
+ if (options.shed) {
327
+ const shedConfig = options.shed === true ? {} : options.shed;
328
+ const statusCode = shedConfig.statusCode ?? 503;
329
+ const retryAfter = shedConfig.retryAfterSeconds ?? 1;
330
+ const exempt = [
331
+ "/_health",
332
+ "/health",
333
+ ...shedConfig.exclude ?? []
334
+ ];
335
+ fastify.addHook("onRequest", async (request, reply) => {
336
+ if (snapshot.state !== "saturated") return;
337
+ const url = request.url;
338
+ if (exempt.some((prefix) => url.startsWith(prefix))) return;
339
+ reply.header("Retry-After", String(retryAfter));
340
+ return reply.code(statusCode).send({
341
+ code: "arc.unavailable",
342
+ message: "Server is at capacity — retry shortly.",
343
+ status: statusCode
344
+ });
345
+ });
346
+ }
347
+ await sample();
348
+ };
349
+ /**
350
+ * `fp()`-wrapped so `fastify.pressure` is visible to the whole app rather than
351
+ * trapped in this plugin's encapsulation context — the named export above is
352
+ * the raw plugin, matching every other arc plugin entry.
353
+ */
354
+ var pressure_default = fp(pressurePlugin, {
355
+ name: "arc-pressure",
356
+ fastify: "5.x"
357
+ });
358
+ //#endregion
359
+ export { arcCorePlugin_default as arcCorePlugin, arcCorePlugin as arcCorePluginFn, caching_default as cachingPlugin, cachingPlugin as cachingPluginFn, createPlugin, createRequestIdGenerator, defaultIsDuplicateKeyError, errorHandlerPlugin, errorHandlerPlugin as errorHandlerPluginFn, gracefulShutdown_default as gracefulShutdownPlugin, gracefulShutdownPlugin as gracefulShutdownPluginFn, health_default as healthPlugin, healthPlugin as healthPluginFn, metrics_default as metricsPlugin, metricsPlugin as metricsPluginFn, pressure_default as pressurePlugin, pressurePlugin as pressurePluginFn, realtime_default as realtimePlugin, realtimePlugin as realtimePluginFn, replyHelpersPlugin, requestId_default as requestIdPlugin, requestIdPlugin as requestIdPluginFn, sanitizeRequestId, schedules_default as schedulesPlugin, schedulesPlugin as schedulesPluginFn, sse_default as ssePlugin, ssePlugin as ssePluginFn, versioning_default as versioningPlugin, versioningPlugin as versioningPluginFn };
@@ -1,5 +1,5 @@
1
- /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
- import { t as hasEvents } from "../typeGuards-jjKJDN13.mjs";
1
+ /*! @classytic/arc v2.36.0 | MIT | https://classytic.com */
2
+ import { t as hasEvents } from "../typeGuards-IE1laMDf.mjs";
3
3
  import fp from "fastify-plugin";
4
4
  //#region src/plugins/response-cache.ts
5
5
  /**
@@ -1,4 +1,4 @@
1
- /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.36.0 | MIT | https://classytic.com */
2
2
  import { createRequire } from "node:module";
3
3
  import fp from "fastify-plugin";
4
4
  //#region src/plugins/tracing.ts
@@ -59,7 +59,7 @@ try {
59
59
  function createTracerProvider(options) {
60
60
  if (!isAvailable || !NodeTracerProvider || !BatchSpanProcessor || !OTLPTraceExporter) return null;
61
61
  const { serviceName = "@classytic/arc", serviceVersion, exporterUrl = "http://localhost:4318/v1/traces" } = options;
62
- const resolvedVersion = serviceVersion ?? "2.34.0";
62
+ const resolvedVersion = serviceVersion ?? "2.36.0";
63
63
  const exporter = new OTLPTraceExporter({ url: exporterUrl });
64
64
  const provider = new NodeTracerProvider({ resource: { attributes: {
65
65
  "service.name": serviceName,
@@ -1,4 +1,4 @@
1
- /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.36.0 | MIT | https://classytic.com */
2
2
  //#region src/utils/pluralize.ts
3
3
  /**
4
4
  * Lightweight English pluralization for the Arc CLI.