@classytic/arc 2.30.0 → 2.32.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 (242) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +7 -0
  3. package/TRADEMARK.md +22 -0
  4. package/dist/{BaseController-B5DwufbB.mjs → BaseController-Dk34dxhI.mjs} +21 -19
  5. package/dist/{EventTransport-QmY7A258.d.mts → EventTransport-2uVV34WU.d.mts} +1 -0
  6. package/dist/{EventTransport-C6YA8xY9.mjs → EventTransport-BV10D0oG.mjs} +1 -0
  7. package/dist/{HookSystem-WkIougo6.mjs → HookSystem-vimw5D7Z.mjs} +2 -1
  8. package/dist/{QueryCache-DOMT_iq7.d.mts → QueryCache-DFA-QrU9.d.mts} +2 -1
  9. package/dist/{ResourceRegistry-y9Hnypus.mjs → ResourceRegistry-BVr7PgA0.mjs} +18 -15
  10. package/dist/{actionPermissions-CAphcmUG.mjs → actionPermissions-Dti7ebyv.mjs} +1 -0
  11. package/dist/audit/index.d.mts +3 -2
  12. package/dist/audit/index.mjs +2 -1
  13. package/dist/auth/audit.d.mts +1 -0
  14. package/dist/auth/audit.mjs +1 -0
  15. package/dist/auth/index.d.mts +5 -4
  16. package/dist/auth/index.mjs +11 -9
  17. package/dist/auth/redis-session.d.mts +2 -1
  18. package/dist/auth/redis-session.mjs +1 -0
  19. package/dist/{applyPermissionResult-Cf6zUCvb.mjs → authorizationDecision-__T3EJDA.mjs} +19 -10
  20. package/dist/{buildHandler-BYS3Vkfz.mjs → buildHandler-Dn7rzUJo.mjs} +6 -4
  21. package/dist/cache/index.d.mts +4 -3
  22. package/dist/cache/index.mjs +4 -3
  23. package/dist/{caching-_NJeDd1_.mjs → caching-DFJqHyFs.mjs} +2 -1
  24. package/dist/cleanup/index.d.mts +39 -16
  25. package/dist/cleanup/index.mjs +50 -38
  26. package/dist/cli/commands/describe.d.mts +2 -1
  27. package/dist/cli/commands/describe.mjs +4 -5
  28. package/dist/cli/commands/docs.d.mts +1 -0
  29. package/dist/cli/commands/docs.mjs +3 -2
  30. package/dist/cli/commands/doctor.d.mts +1 -0
  31. package/dist/cli/commands/doctor.mjs +1 -0
  32. package/dist/cli/commands/generate.d.mts +2 -1
  33. package/dist/cli/commands/generate.mjs +2 -1
  34. package/dist/cli/commands/init.d.mts +2 -1
  35. package/dist/cli/commands/init.mjs +2 -1
  36. package/dist/cli/commands/introspect.d.mts +1 -0
  37. package/dist/cli/commands/introspect.mjs +2 -1
  38. package/dist/cli/index.d.mts +3 -2
  39. package/dist/cli/index.mjs +3 -2
  40. package/dist/{constants-DNre0wRg.mjs → constants-J99LGTJI.mjs} +1 -0
  41. package/dist/context/index.d.mts +1 -0
  42. package/dist/context/index.mjs +3 -2
  43. package/dist/core/index.d.mts +3 -2
  44. package/dist/core/index.mjs +7 -6
  45. package/dist/{core-poalJLvp.mjs → core-BwwmNSp7.mjs} +3 -2
  46. package/dist/{createActionRouter-BxuywVRB.mjs → createActionRouter-DkJZWo0m.mjs} +14 -10
  47. package/dist/{createAggregationRouter-BiY-lGdY.mjs → createAggregationRouter-B1exOVD8.mjs} +10 -7
  48. package/dist/{createApp-CXrKFciH.mjs → createApp-Bv0m-Q6_.mjs} +625 -229
  49. package/dist/{defineAggregation-ufWjUwml.mjs → defineAggregation-gX4No1gt.mjs} +1 -0
  50. package/dist/{defineResource-CxWqDCAy.mjs → defineResource-XFPPUu2c.mjs} +84 -50
  51. package/dist/discovery/index.d.mts +1 -0
  52. package/dist/discovery/index.mjs +1 -0
  53. package/dist/docs/index.d.mts +3 -2
  54. package/dist/docs/index.mjs +3 -2
  55. package/dist/{elevation-C9s5hFP_.d.mts → elevation-BXCfVcNG.d.mts} +1 -0
  56. package/dist/{elevation-CIz1GjGm.mjs → elevation-DNXXxgjl.mjs} +4 -3
  57. package/dist/encryption/index.d.mts +2 -1
  58. package/dist/encryption/index.mjs +5 -3
  59. package/dist/{errorHandler-Bq2OeEQL.mjs → errorHandler-B2AZUJUj.mjs} +3 -2
  60. package/dist/{errorHandler-C6yv4ZTW.d.mts → errorHandler-CpYUJmPA.d.mts} +1 -0
  61. package/dist/{errors-DHcHVAgk.d.mts → errors-BGdVwP25.d.mts} +1 -0
  62. package/dist/{errors-DkO8Nk6-.mjs → errors-Bh1KK5ps.mjs} +1 -0
  63. package/dist/{eventPlugin-D2NNR4py.mjs → eventPlugin-Dk_TyXeE.mjs} +8 -6
  64. package/dist/{eventPlugin-Bg3cP2_u.d.mts → eventPlugin-GF5nnBIq.d.mts} +2 -1
  65. package/dist/events/index.d.mts +112 -44
  66. package/dist/events/index.mjs +177 -141
  67. package/dist/events/transports/redis-stream-entry.d.mts +2 -1
  68. package/dist/events/transports/redis-stream-entry.mjs +14 -9
  69. package/dist/events/transports/redis.d.mts +2 -1
  70. package/dist/events/transports/redis.mjs +1 -0
  71. package/dist/{externalPaths-BE6wFpYv.d.mts → externalPaths-CmGOxOtD.d.mts} +1 -0
  72. package/dist/factory/index.d.mts +9 -5
  73. package/dist/factory/index.mjs +131 -7
  74. package/dist/fastify-augmentation-BppG5bMT.d.mts +179 -0
  75. package/dist/{fields-CQ_7KQWw.mjs → fields-DEi65pGj.mjs} +6 -10
  76. package/dist/{fields-D3lhgzhD.d.mts → fields-I_EeRWND.d.mts} +1 -0
  77. package/dist/{generate-D0wZupBg.mjs → generate-CRSG0jTv.mjs} +2 -1
  78. package/dist/{generate-BnsL9GBm.d.mts → generate-DCGuEbqI.d.mts} +1 -0
  79. package/dist/{headers-BvlQSDMB.mjs → headers-BXjQYbeC.mjs} +2 -1
  80. package/dist/{health-CB_m4S32.mjs → health-5XnTa2uo.mjs} +1 -0
  81. package/dist/hooks/index.d.mts +2 -1
  82. package/dist/hooks/index.mjs +2 -1
  83. package/dist/idempotency/index.d.mts +3 -2
  84. package/dist/idempotency/index.mjs +8 -5
  85. package/dist/idempotency/redis.d.mts +2 -1
  86. package/dist/idempotency/redis.mjs +1 -0
  87. package/dist/{index-CG4yqMDB.d.mts → index-C-481Zf2.d.mts} +74 -18
  88. package/dist/{index-Q5gzEIWY.d.mts → index-CpzxqePp.d.mts} +107 -32
  89. package/dist/{index-Cx7bro4R.d.mts → index-DXlHwvuE.d.mts} +5 -4
  90. package/dist/index-ZWhLTp_s.d.mts +290 -0
  91. package/dist/{index-Ch16aAah.d.mts → index-zq6VrjaB.d.mts} +122 -15
  92. package/dist/index.d.mts +9 -8
  93. package/dist/index.mjs +11 -10
  94. package/dist/{init-tlRSt667.mjs → init-BbfP8aky.mjs} +25 -27
  95. package/dist/{init-BPEu8cs-.d.mts → init-Cscs4lab.d.mts} +1 -0
  96. package/dist/integrations/event-gateway.d.mts +3 -2
  97. package/dist/integrations/event-gateway.mjs +2 -1
  98. package/dist/integrations/index.d.mts +4 -3
  99. package/dist/integrations/index.mjs +1 -0
  100. package/dist/integrations/jobs/index.d.mts +2 -1
  101. package/dist/integrations/jobs/index.mjs +109 -33
  102. package/dist/integrations/mcp/index.d.mts +6 -6
  103. package/dist/integrations/mcp/index.mjs +2 -1
  104. package/dist/integrations/mcp/testing.d.mts +3 -2
  105. package/dist/integrations/mcp/testing.mjs +2 -2
  106. package/dist/integrations/streamline.d.mts +1 -0
  107. package/dist/integrations/streamline.mjs +3 -2
  108. package/dist/integrations/webhooks.d.mts +1 -0
  109. package/dist/integrations/webhooks.mjs +1 -0
  110. package/dist/integrations/websocket-pushref-redis.d.mts +2 -1
  111. package/dist/integrations/websocket-pushref-redis.mjs +1 -0
  112. package/dist/integrations/websocket-redis.d.mts +2 -1
  113. package/dist/integrations/websocket-redis.mjs +1 -0
  114. package/dist/integrations/websocket.d.mts +3 -2
  115. package/dist/integrations/websocket.mjs +38 -34
  116. package/dist/{interface-BGZCzOGL.d.mts → interface-BOk3YnTz.d.mts} +1 -0
  117. package/dist/{interface-n5hpKg1d.mjs → interface-BP4IJCBO.mjs} +1 -0
  118. package/dist/{interface-Uk8ONJv7.d.mts → interface-DEWNOMza.d.mts} +1 -0
  119. package/dist/{jsonBody-BlzAoGCe.mjs → jsonBody-DDSR2HFl.mjs} +2 -1
  120. package/dist/{keys-Cd316u_y.mjs → keys-B-HBTGXl.mjs} +1 -0
  121. package/dist/{loadResources-DpU8pfjV.mjs → loadResources-DisEAmkm.mjs} +4 -2
  122. package/dist/{loadResourcesFromEntry-kvGT6EcO.mjs → loadResourcesFromEntry-D1MJp13f.mjs} +2 -1
  123. package/dist/logger/index.d.mts +1 -0
  124. package/dist/logger/index.mjs +1 -0
  125. package/dist/{mcpPlugin-nRzm5g2s.mjs → mcpPlugin-CpfVpKHE.mjs} +38 -38
  126. package/dist/{memory-CmnzXE5Z.mjs → memory-DsuWnj4T.mjs} +5 -4
  127. package/dist/{metrics-fdxI5mn_.mjs → metrics-DdQL8syd.mjs} +2 -1
  128. package/dist/middleware/index.d.mts +2 -1
  129. package/dist/middleware/index.mjs +3 -2
  130. package/dist/migrations/index.d.mts +1 -0
  131. package/dist/migrations/index.mjs +3 -2
  132. package/dist/module-Br81am6m.mjs +78 -0
  133. package/dist/{multipartBody-D0eBvkeq.mjs → multipartBody-BnkBLN7v.mjs} +3 -2
  134. package/dist/{openapi-B8Vkbxbn.mjs → openapi-DIRzvxC0.mjs} +6 -5
  135. package/dist/outbox-admin/index.d.mts +85 -0
  136. package/dist/outbox-admin/index.mjs +115 -0
  137. package/dist/permissions/index.d.mts +5 -4
  138. package/dist/permissions/index.mjs +6 -5
  139. package/dist/{permissions-Bfu_8y1U.mjs → permissions-Dl_m0NCe.mjs} +308 -42
  140. package/dist/{pipe-BOLmGIxa.mjs → pipe-ELHCP3zj.mjs} +3 -4
  141. package/dist/pipeline/index.d.mts +2 -1
  142. package/dist/pipeline/index.mjs +2 -1
  143. package/dist/{pipeline-BJnpkY9i.mjs → pipeline-CPA-jx0M.mjs} +17 -14
  144. package/dist/plugins/index.d.mts +8 -7
  145. package/dist/plugins/index.mjs +16 -15
  146. package/dist/plugins/response-cache.d.mts +1 -0
  147. package/dist/plugins/response-cache.mjs +2 -1
  148. package/dist/plugins/tracing-entry.d.mts +2 -1
  149. package/dist/plugins/tracing-entry.mjs +2 -1
  150. package/dist/{pluralize-BhrgwZb9.mjs → pluralize-C2ZkKbsn.mjs} +1 -0
  151. package/dist/{preloadResources-DSY5HGpS.mjs → preloadResources-CDs87fTw.mjs} +1 -0
  152. package/dist/{preloadResources-DCfa2egz.d.mts → preloadResources-T1hmlTEZ.d.mts} +117 -12
  153. package/dist/presets/filesUpload.d.mts +6 -5
  154. package/dist/presets/filesUpload.mjs +10 -12
  155. package/dist/presets/index.d.mts +3 -2
  156. package/dist/presets/index.mjs +2 -1
  157. package/dist/presets/multiTenant.d.mts +2 -1
  158. package/dist/presets/multiTenant.mjs +3 -2
  159. package/dist/presets/search.d.mts +3 -2
  160. package/dist/presets/search.mjs +2 -1
  161. package/dist/{presets-CJOEXLuk.mjs → presets-Dntd_pUO.mjs} +6 -4
  162. package/dist/{projection-UBDgfMwu.mjs → projection-CjHhM5W1.mjs} +2 -1
  163. package/dist/{pushref-registry-BbcnpNTm.d.mts → pushref-registry-Dov9l4qE.d.mts} +1 -0
  164. package/dist/{queryCachePlugin-br4otiqF.d.mts → queryCachePlugin-BzqxconG.d.mts} +3 -2
  165. package/dist/{queryCachePlugin-DSGuSVLW.mjs → queryCachePlugin-Dhps-Ldt.mjs} +6 -5
  166. package/dist/{rateLimit-DrED-C4v.mjs → rateLimit-C7mmAGs3.mjs} +7 -3
  167. package/dist/{rateLimitKey-BoWRH3e7.mjs → rateLimitKey-CccEFBfk.mjs} +1 -0
  168. package/dist/{realtime-B9nmIY5V.mjs → realtime-DVvVZZFz.mjs} +20 -9
  169. package/dist/{redis-UDtLaNj6.d.mts → redis-CduhKsJi.d.mts} +2 -1
  170. package/dist/{redis-stream-uIaauNpZ.d.mts → redis-stream-qN6A-RFv.d.mts} +2 -1
  171. package/dist/{registerRoute-JkxIEBJq.mjs → registerRoute-B_QrjYZS.mjs} +10 -7
  172. package/dist/registry/index.d.mts +3 -2
  173. package/dist/registry/index.mjs +3 -2
  174. package/dist/{registry-BYXUUg21.mjs → registry-DqUmzA9W.mjs} +2 -1
  175. package/dist/{renewingLease-3cuQH7h8.mjs → renewingLease-BpP9wwj7.mjs} +1 -0
  176. package/dist/{replyHelpers-67tRawQZ.mjs → replyHelpers-DS-P0b32.mjs} +2 -1
  177. package/dist/{repositoryFilter-Dz8k3S4P.mjs → repositoryFilter-DotlxG3L.mjs} +1 -0
  178. package/dist/{requestContext-MrlJwECf.mjs → requestContext-B6vQF-sy.mjs} +1 -0
  179. package/dist/{rolldown-runtime-8H4AJuhK.mjs → rolldown-runtime-BhflRrBs.mjs} +1 -0
  180. package/dist/{schedules-CjOd2MG9.mjs → schedules-C2Q3uv2d.mjs} +3 -2
  181. package/dist/{schemaConverter-DJvodiHj.mjs → schemaConverter-Crjvczi_.mjs} +70 -17
  182. package/dist/{schemaIR-Ca7ySNPx.mjs → schemaIR-CnyKK_1j.mjs} +2 -1
  183. package/dist/schemas/index.d.mts +1 -0
  184. package/dist/schemas/index.mjs +1 -0
  185. package/dist/scim/index.d.mts +1 -0
  186. package/dist/scim/index.mjs +2 -1
  187. package/dist/scope/index.d.mts +3 -2
  188. package/dist/scope/index.mjs +5 -4
  189. package/dist/{scope-DfaPntpo.mjs → scope-afz_Ieek.mjs} +4 -3
  190. package/dist/{sessionManager-BHOM3N_0.d.mts → sessionManager-qimlKV0P.d.mts} +1 -0
  191. package/dist/{simpleEqualityMatcher-Cg05HPsn.mjs → simpleEqualityMatcher-C4KiMWUa.mjs} +1 -0
  192. package/dist/{sse-BQ1w0dfl.mjs → sse-B9P4Y-UI.mjs} +7 -5
  193. package/dist/{sseStream-BQ7Qaynp.mjs → sseStream-B3zb_d1h.mjs} +2 -1
  194. package/dist/{storage-DLUZ04kK.d.mts → storage-Cx9Sz3Ou.d.mts} +1 -0
  195. package/dist/{store-helpers-DY6xq4sM.mjs → store-helpers-DmEMHlPQ.mjs} +1 -0
  196. package/dist/{streaming-BQcW6_Kq.mjs → streaming-DPZdQmDz.mjs} +1 -0
  197. package/dist/{subscribe-helpers-DfzsivM6.mjs → subscribe-helpers-sp5xTDVA.mjs} +2 -1
  198. package/dist/sync/changelog.d.mts +1 -0
  199. package/dist/sync/changelog.mjs +1 -0
  200. package/dist/testing/cleanupStoreContract.d.mts +2 -1
  201. package/dist/testing/cleanupStoreContract.mjs +10 -5
  202. package/dist/testing/index.d.mts +166 -12
  203. package/dist/testing/index.mjs +315 -43
  204. package/dist/testing/outboxStoreContract.d.mts +12 -2
  205. package/dist/testing/outboxStoreContract.mjs +110 -20
  206. package/dist/testing/storageContract.d.mts +2 -1
  207. package/dist/testing/storageContract.mjs +11 -5
  208. package/dist/{tracing-CsGKQJGg.d.mts → tracing-Dro5eH6j.d.mts} +1 -0
  209. package/dist/{transactionContext-PNdoyLLm.mjs → transactionContext-BvLKvOIL.mjs} +1 -0
  210. package/dist/{typeGuards-C9KXUly6.mjs → typeGuards-B_5-hKjJ.mjs} +1 -0
  211. package/dist/types/index.d.mts +6 -5
  212. package/dist/types/index.mjs +1 -0
  213. package/dist/types/storage.d.mts +2 -1
  214. package/dist/types/storage.mjs +1 -0
  215. package/dist/{types-ChvvVNP8.d.mts → types-B7XeLEgK.d.mts} +34 -2
  216. package/dist/{types-Cc91nXuA.d.mts → types-ByHn9rEO.d.mts} +1 -0
  217. package/dist/{types-C_ZZRcD6.d.mts → types-C4QE8qnm.d.mts} +1 -0
  218. package/dist/{types-D_n8L6VY.d.mts → types-CT5vu6M0.d.mts} +132 -17
  219. package/dist/{types-DjC-KUCz.d.mts → types-ChuShpCP.d.mts} +1 -0
  220. package/dist/{types-Bmmn-fO0.d.mts → types-CwcUQ610.d.mts} +2 -6
  221. package/dist/{types-CSyCxl2V.mjs → types-DXYX7dIR.mjs} +4 -3
  222. package/dist/usage/index.d.mts +3 -2
  223. package/dist/usage/index.mjs +0 -0
  224. package/dist/{userHelpers-Cg_y3fBz.mjs → userHelpers-BNsOXzOd.mjs} +1 -0
  225. package/dist/utils/index.d.mts +16 -7
  226. package/dist/utils/index.mjs +10 -9
  227. package/dist/{utils-taUVD4p3.mjs → utils-BtOha_Sp.mjs} +3 -2
  228. package/dist/{validate-Bk44F2yT.mjs → validate-PMCPR9El.mjs} +2 -1
  229. package/dist/{versioning-gKWyryT3.mjs → versioning-CKv2jz_U.mjs} +4 -3
  230. package/dist/{versioning-DXgY5BUm.d.mts → versioning-D_rhHfZm.d.mts} +5 -2
  231. package/dist/{websocket-_Pd0CgR0.d.mts → websocket-BOXVggMf.d.mts} +2 -1
  232. package/package.json +557 -539
  233. package/skills/arc/SKILL.md +22 -8
  234. package/skills/arc/references/api-reference.md +4 -0
  235. package/skills/arc/references/auth.md +3 -0
  236. package/skills/arc/references/integrations.md +33 -0
  237. package/skills/arc/references/mcp.md +6 -7
  238. package/skills/arc/references/production.md +24 -25
  239. package/skills/arc-code-review/references/anti-patterns.md +3 -3
  240. package/dist/fastify-augmentation-0WhPnUH7.d.mts +0 -76
  241. package/dist/index-CCzVR-N3.d.mts +0 -107
  242. package/dist/module-_-M7C5ES.mjs +0 -29
@@ -1,10 +1,13 @@
1
- import { t as CRUD_OPERATIONS } from "../constants-DNre0wRg.mjs";
2
- import { t as BaseController } from "../BaseController-B5DwufbB.mjs";
3
- import { t as defineResource } from "../defineResource-CxWqDCAy.mjs";
4
- import { t as defineAggregation } from "../defineAggregation-ufWjUwml.mjs";
5
- import { n as createApp } from "../createApp-CXrKFciH.mjs";
6
- import { n as getModuleExports } from "../module-_-M7C5ES.mjs";
7
- import { n as preloadResourcesAsync, t as preloadResources } from "../preloadResources-DSY5HGpS.mjs";
1
+ /*! @classytic/arc v2.32.0 | MIT | https://classytic.com */
2
+ import { t as CRUD_OPERATIONS } from "../constants-J99LGTJI.mjs";
3
+ import { g as getUserId } from "../types-DXYX7dIR.mjs";
4
+ import { t as BaseController } from "../BaseController-Dk34dxhI.mjs";
5
+ import { t as defineResource } from "../defineResource-XFPPUu2c.mjs";
6
+ import { t as defineAggregation } from "../defineAggregation-gX4No1gt.mjs";
7
+ import { E as requireOrgRole } from "../permissions-Dl_m0NCe.mjs";
8
+ import { n as createApp } from "../createApp-Bv0m-Q6_.mjs";
9
+ import { n as getModuleExports } from "../module-Br81am6m.mjs";
10
+ import { n as preloadResourcesAsync, t as preloadResources } from "../preloadResources-CDs87fTw.mjs";
8
11
  import { runStorageContract } from "./storageContract.mjs";
9
12
  import Fastify from "fastify";
10
13
  import { afterAll, describe, expect, it, vi } from "vitest";
@@ -61,7 +64,8 @@ function expectArc(response) {
61
64
  failed(status) {
62
65
  if (status !== void 0) expect(response.statusCode).toBe(status);
63
66
  else expect(response.statusCode).toBeGreaterThanOrEqual(400);
64
- expect(getBody().code, "expected ErrorContract.code on failed response").toBeDefined();
67
+ const body = getBody();
68
+ expect(body.code, "expected ErrorContract.code on failed response").toBeDefined();
65
69
  return assertion;
66
70
  },
67
71
  unauthorized() {
@@ -81,7 +85,8 @@ function expectArc(response) {
81
85
  },
82
86
  hasData() {
83
87
  const body = getBody();
84
- expect(Object.keys(body).length > 0, "expected response body to be defined").toBe(true);
88
+ const hasContent = Object.keys(body).length > 0;
89
+ expect(hasContent, "expected response body to be defined").toBe(true);
85
90
  return assertion;
86
91
  },
87
92
  hasStatus(status) {
@@ -89,11 +94,13 @@ function expectArc(response) {
89
94
  return assertion;
90
95
  },
91
96
  hidesField(field) {
92
- expect(getBody(), `expected field '${field}' to be hidden from response`).not.toHaveProperty(field);
97
+ const body = getBody();
98
+ expect(body, `expected field '${field}' to be hidden from response`).not.toHaveProperty(field);
93
99
  return assertion;
94
100
  },
95
101
  showsField(field) {
96
- expect(getBody(), `expected field '${field}' on response`).toHaveProperty(field);
102
+ const body = getBody();
103
+ expect(body, `expected field '${field}' on response`).toHaveProperty(field);
97
104
  return assertion;
98
105
  },
99
106
  paginated(expected) {
@@ -304,18 +311,30 @@ function createSpy(_name = "spy") {
304
311
  return spy;
305
312
  }
306
313
  /**
307
- * Wait for a condition to be true
314
+ * Wait until `condition` holds — the standard way to assert on asynchronous
315
+ * effects in arc's suites.
308
316
  *
309
- * Useful for async testing
317
+ * **Prefer this over `await sleep(n)` + assert.** A fixed delay encodes a guess
318
+ * about scheduling: too short and it flakes the moment the pool is busy, too
319
+ * long and every run pays for the worst case. Polling returns as soon as the
320
+ * effect lands, so the fast path stays fast and a loaded machine simply waits —
321
+ * and the failure mode becomes "this never happened", which is the thing worth
322
+ * asserting.
323
+ *
324
+ * Supply `label`: without it a timeout reports only its own duration, which is
325
+ * the least useful sentence a flaky test can print.
326
+ *
327
+ * @example
328
+ * await waitFor(() => runs >= 3, { label: "3 scheduler ticks" });
310
329
  */
311
330
  async function waitFor(condition, options = {}) {
312
- const { timeout = 5e3, interval = 100 } = options;
331
+ const { timeout = 5e3, interval = 25, label } = options;
313
332
  const startTime = Date.now();
314
333
  while (Date.now() - startTime < timeout) {
315
334
  if (await condition()) return;
316
335
  await new Promise((resolve) => setTimeout(resolve, interval));
317
336
  }
318
- throw new Error(`Timeout waiting for condition after ${timeout}ms`);
337
+ throw new Error(label ? `Timed out after ${timeout}ms waiting for: ${label}` : `Timed out after ${timeout}ms waiting for a condition (pass "label" to name it)`);
319
338
  }
320
339
  /**
321
340
  * Create a test timer that can be controlled
@@ -363,6 +382,7 @@ function createTestTimer() {
363
382
  async function runAuthorizationConformance(opts) {
364
383
  const rows = opts.rows ?? [{ _id: "1" }];
365
384
  let listFilter;
385
+ let getFilter;
366
386
  let aggFilter;
367
387
  const repo = createMockRepository({
368
388
  getAll: (async (params = {}) => {
@@ -378,6 +398,25 @@ async function runAuthorizationConformance(opts) {
378
398
  hasPrev: false
379
399
  };
380
400
  }),
401
+ getOne: (async (filter) => {
402
+ getFilter = filter;
403
+ return rows[0] ?? null;
404
+ }),
405
+ /**
406
+ * The single-record read has TWO paths, and the harness must be able to observe both.
407
+ *
408
+ * `fetchDetailed` only builds a compound filter and calls `getOne` when there is something to
409
+ * compound — a row policy, a tenant field, or a non-default id field. A permission that simply
410
+ * ALLOWS (no policy) leaves `{ _id }` alone and takes the plain `getById` path instead. The base
411
+ * mock stubs that as `null`, so before this stub the `get` surface reported **404 on every
412
+ * policy-free allow** while `list` and `aggregation` reported 200.
413
+ *
414
+ * That reads as partial enforcement and it is not: it is the harness failing to answer. A host
415
+ * asserting `get.status === 200` for its own allow-gate — the obvious falsification case to pair
416
+ * with a deny-parity suite — was told its gate broke the single-record surface. Found from
417
+ * be-prod, whose conformance suite does exactly that.
418
+ */
419
+ getById: (async () => rows[0] ?? null),
381
420
  aggregate: (async (req = {}) => {
382
421
  aggFilter = req.filter;
383
422
  return { rows: [{ tally: rows.length }] };
@@ -419,6 +458,10 @@ async function runAuthorizationConformance(opts) {
419
458
  method: "GET",
420
459
  url: "/confs"
421
460
  });
461
+ const get = await app.inject({
462
+ method: "GET",
463
+ url: "/confs/1"
464
+ });
422
465
  const aggregation = await app.inject({
423
466
  method: "GET",
424
467
  url: "/confs/aggregations/tally"
@@ -428,6 +471,10 @@ async function runAuthorizationConformance(opts) {
428
471
  status: list.statusCode,
429
472
  filter: listFilter
430
473
  },
474
+ get: {
475
+ status: get.statusCode,
476
+ filter: getFilter
477
+ },
431
478
  aggregation: {
432
479
  status: aggregation.statusCode,
433
480
  filter: aggFilter
@@ -775,6 +822,127 @@ async function setupBetterAuthTestApp(input) {
775
822
  };
776
823
  }
777
824
  //#endregion
825
+ //#region src/testing/testActor.ts
826
+ /**
827
+ * Test-only actor installation — how a package's HTTP tests say "run this
828
+ * request as a manager in org X".
829
+ *
830
+ * Identity in arc comes from `request.scope` and only from `request.scope`;
831
+ * headers like `x-role` are not identity. That rule left tests with no seam —
832
+ * `bootModuleApp` boots with `auth: false`, so every injected request arrived as
833
+ * `PUBLIC_SCOPE` and any role-gated route answered 401 with no supported way to
834
+ * be somebody.
835
+ *
836
+ * The `x-test-actor` header is a TRANSPORT for the test's intent, not a trusted
837
+ * claim. The authenticator that reads it is installed by `bootModuleApp` alone,
838
+ * behind the `@classytic/arc/testing` entrypoint; `createApp` never registers
839
+ * it, so no production build turns a forged header into identity. It is
840
+ * per-request rather than boot-only because the interesting authorization tests
841
+ * are the ones where a cashier is refused what a manager is allowed.
842
+ *
843
+ * Gate the routes under test with arc's own primitives — `requireRoles`,
844
+ * `requireOrgMembership`, `allOf(...)`. A test-specific gate would be a second
845
+ * implementation of authorization whose semantics could drift from the one
846
+ * actually shipping.
847
+ */
848
+ /** The header the testing harness uses to carry a request's actor. */
849
+ const TEST_ACTOR_HEADER = "x-test-actor";
850
+ /**
851
+ * Build the headers that make an injected request act as `actor`.
852
+ *
853
+ * A string is shorthand for a single role. `null` means "stay public", so a test
854
+ * can prove an endpoint is reachable anonymously.
855
+ *
856
+ * @example
857
+ * ```ts
858
+ * t.app.inject({ method: 'GET', url: '/pos/shifts/current',
859
+ * headers: testActorHeaders('manager', ORG) });
860
+ * ```
861
+ */
862
+ function testActorHeaders(actor, organizationId) {
863
+ if (actor === null) return {};
864
+ const org = organizationId !== void 0 ? { organizationId } : {};
865
+ const spec = typeof actor === "string" ? {
866
+ roles: [actor],
867
+ ...org
868
+ } : {
869
+ ...actor,
870
+ ...org
871
+ };
872
+ return { [TEST_ACTOR_HEADER]: JSON.stringify(spec) };
873
+ }
874
+ /**
875
+ * Turn a spec into a `RequestScope`.
876
+ *
877
+ * `orgRoles` defaults to `roles`: a test that says "manager" means it at both
878
+ * levels, and making the caller repeat itself only produces tests that pass a
879
+ * global gate and mysteriously fail an org one.
880
+ */
881
+ function scopeFromTestActor(spec) {
882
+ const roles = spec.roles ?? [];
883
+ const userId = spec.userId ?? `test-${roles.join("-") || "anon"}`;
884
+ if (spec.elevated !== void 0 && spec.elevated !== false) return {
885
+ kind: "elevated",
886
+ userId,
887
+ elevatedBy: spec.elevated === true ? "test" : spec.elevated,
888
+ ...spec.organizationId !== void 0 ? { organizationId: spec.organizationId } : {},
889
+ ...spec.context !== void 0 ? { context: Object.freeze({ ...spec.context }) } : {}
890
+ };
891
+ if (spec.organizationId !== void 0) return {
892
+ kind: "member",
893
+ userId,
894
+ userRoles: roles,
895
+ organizationId: spec.organizationId,
896
+ orgRoles: spec.orgRoles ?? roles,
897
+ ...spec.teamId !== void 0 ? { teamId: spec.teamId } : {},
898
+ ...spec.context !== void 0 ? { context: Object.freeze({ ...spec.context }) } : {}
899
+ };
900
+ return {
901
+ kind: "authenticated",
902
+ userId,
903
+ userRoles: roles
904
+ };
905
+ }
906
+ /**
907
+ * Build the `auth` option that makes the test actor arc's authenticator.
908
+ *
909
+ * It must be an authenticator, not a hook. Fastify encapsulates plugins and
910
+ * modules register DURING `createApp`, so a hook added to the root instance
911
+ * afterwards never runs for their routes — every request still resolves as
912
+ * public and every role gate still answers 401, which reads exactly like a
913
+ * broken authorization rule in the code under test. Registering as
914
+ * `auth: { type: 'authenticator' }` puts the scope where arc's own auth would,
915
+ * at the phase arc expects, so tenant filtering and permission checks behave
916
+ * identically to production.
917
+ *
918
+ * `defaultActor` applies to requests carrying no header. A malformed header
919
+ * REJECTS rather than falling back to public — a test that meant to be an admin
920
+ * and got quietly demoted is the most expensive way to fail.
921
+ */
922
+ function testActorAuth(defaultActor) {
923
+ const authenticate = async (request) => {
924
+ const req = request;
925
+ const raw = req.headers[TEST_ACTOR_HEADER];
926
+ let spec = defaultActor;
927
+ if (typeof raw === "string") try {
928
+ spec = JSON.parse(raw);
929
+ } catch {
930
+ throw new Error(`[arc-testing] malformed ${TEST_ACTOR_HEADER} header: ${raw}. Build it with testActorHeaders() rather than by hand.`);
931
+ }
932
+ if (spec === void 0) return;
933
+ req.scope = scopeFromTestActor(spec);
934
+ req.user = {
935
+ id: getUserId(req.scope) ?? "test-anon",
936
+ ...spec.roles !== void 0 ? { role: spec.roles } : {},
937
+ ...spec.organizationId !== void 0 ? { organizationId: spec.organizationId } : {}
938
+ };
939
+ };
940
+ return {
941
+ type: "authenticator",
942
+ authenticate
943
+ };
944
+ }
945
+ //#endregion
778
946
  //#region src/testing/bootModuleApp.ts
779
947
  /**
780
948
  * The built-in DB factory: in-memory MongoDB via lazy devDep imports.
@@ -807,7 +975,6 @@ async function bootModuleApp(modules, options = {}) {
807
975
  const db = await (options.database ?? mongoMemoryDatabase)({ replset: options.replset ?? false });
808
976
  const ctx = {
809
977
  uri: db.uri,
810
- mongoUri: db.uri,
811
978
  connection: db.connection
812
979
  };
813
980
  const resolved = typeof modules === "function" ? await modules(ctx) : modules;
@@ -816,7 +983,17 @@ async function bootModuleApp(modules, options = {}) {
816
983
  try {
817
984
  app = await createApp({
818
985
  preset: "testing",
819
- auth: false,
986
+ /**
987
+ * The test actor is arc's AUTHENTICATOR rather than a bolted-on hook, so the scope
988
+ * lands at the phase the rest of the pipeline expects — tenant filtering and
989
+ * permission checks then behave exactly as they do in production.
990
+ *
991
+ * This replaces the old `auth: false`. The authenticator is inert for a request
992
+ * carrying no `x-test-actor` header and no configured default, so a suite that
993
+ * never mentions an actor behaves as it did before. `options.app` still wins, so a
994
+ * suite exercising real auth can override it.
995
+ */
996
+ auth: testActorAuth(options.actor),
820
997
  sensible: false,
821
998
  ...options.app,
822
999
  modules: list
@@ -830,7 +1007,6 @@ async function bootModuleApp(modules, options = {}) {
830
1007
  return {
831
1008
  app,
832
1009
  uri: db.uri,
833
- mongoUri: db.uri,
834
1010
  connection: db.connection,
835
1011
  exports: (name) => getModuleExports(app, name),
836
1012
  close: async () => {
@@ -1009,12 +1185,13 @@ var HttpTestHarness = class {
1009
1185
  it(`${verb} /:id with non-existent ID should return 404`, async () => {
1010
1186
  const { app, fixtures } = this.getOptions();
1011
1187
  const payload = fixtures.update ?? fixtures.valid;
1012
- expect((await app.inject({
1188
+ const res = await app.inject({
1013
1189
  method: verb,
1014
1190
  url: `${this.getBaseUrl()}/000000000000000000000000`,
1015
1191
  headers: this.adminHeaders(),
1016
1192
  payload
1017
- })).statusCode).toBe(404);
1193
+ });
1194
+ expect(res.statusCode).toBe(404);
1018
1195
  });
1019
1196
  }
1020
1197
  if (enabledRoutes.has("delete")) {
@@ -1031,24 +1208,29 @@ var HttpTestHarness = class {
1031
1208
  deleteId = JSON.parse(createRes.body)._id;
1032
1209
  }
1033
1210
  if (!deleteId) return;
1034
- expect((await app.inject({
1211
+ const res = await app.inject({
1035
1212
  method: "DELETE",
1036
1213
  url: `${this.getBaseUrl()}/${deleteId}`,
1037
1214
  headers: this.adminHeaders()
1038
- })).statusCode).toBe(200);
1039
- if (enabledRoutes.has("get")) expect((await app.inject({
1040
- method: "GET",
1041
- url: `${this.getBaseUrl()}/${deleteId}`,
1042
- headers: this.adminHeaders()
1043
- })).statusCode).toBe(404);
1215
+ });
1216
+ expect(res.statusCode).toBe(200);
1217
+ if (enabledRoutes.has("get")) {
1218
+ const getRes = await app.inject({
1219
+ method: "GET",
1220
+ url: `${this.getBaseUrl()}/${deleteId}`,
1221
+ headers: this.adminHeaders()
1222
+ });
1223
+ expect(getRes.statusCode).toBe(404);
1224
+ }
1044
1225
  });
1045
1226
  it("DELETE /:id with non-existent ID should return 404", async () => {
1046
1227
  const { app } = this.getOptions();
1047
- expect((await app.inject({
1228
+ const res = await app.inject({
1048
1229
  method: "DELETE",
1049
1230
  url: `${this.getBaseUrl()}/000000000000000000000000`,
1050
1231
  headers: this.adminHeaders()
1051
- })).statusCode).toBe(404);
1232
+ });
1233
+ expect(res.statusCode).toBe(404);
1052
1234
  });
1053
1235
  }
1054
1236
  });
@@ -1067,49 +1249,55 @@ var HttpTestHarness = class {
1067
1249
  describe(`${resource.displayName} HTTP Permissions`, () => {
1068
1250
  if (enabledRoutes.has("list") && protectedOps.list) it("GET list without auth should return 401", async () => {
1069
1251
  const { app } = this.getOptions();
1070
- expect((await app.inject({
1252
+ const res = await app.inject({
1071
1253
  method: "GET",
1072
1254
  url: this.getBaseUrl()
1073
- })).statusCode).toBe(401);
1255
+ });
1256
+ expect(res.statusCode).toBe(401);
1074
1257
  });
1075
1258
  if (enabledRoutes.has("get") && protectedOps.get) it("GET /:id without auth should return 401", async () => {
1076
1259
  const { app } = this.getOptions();
1077
- expect((await app.inject({
1260
+ const res = await app.inject({
1078
1261
  method: "GET",
1079
1262
  url: `${this.getBaseUrl()}/000000000000000000000000`
1080
- })).statusCode).toBe(401);
1263
+ });
1264
+ expect(res.statusCode).toBe(401);
1081
1265
  });
1082
1266
  if (enabledRoutes.has("create") && protectedOps.create) it("POST without auth should return 401", async () => {
1083
1267
  const { app, fixtures } = this.getOptions();
1084
- expect((await app.inject({
1268
+ const res = await app.inject({
1085
1269
  method: "POST",
1086
1270
  url: this.getBaseUrl(),
1087
1271
  payload: fixtures.valid
1088
- })).statusCode).toBe(401);
1272
+ });
1273
+ expect(res.statusCode).toBe(401);
1089
1274
  });
1090
1275
  if (enabledRoutes.has("update") && protectedOps.update) for (const verb of updateMethods) it(`${verb} without auth should return 401`, async () => {
1091
1276
  const { app, fixtures } = this.getOptions();
1092
1277
  const payload = fixtures.update ?? fixtures.valid;
1093
- expect((await app.inject({
1278
+ const res = await app.inject({
1094
1279
  method: verb,
1095
1280
  url: `${this.getBaseUrl()}/000000000000000000000000`,
1096
1281
  payload
1097
- })).statusCode).toBe(401);
1282
+ });
1283
+ expect(res.statusCode).toBe(401);
1098
1284
  });
1099
1285
  if (enabledRoutes.has("delete") && protectedOps.delete) it("DELETE without auth should return 401", async () => {
1100
1286
  const { app } = this.getOptions();
1101
- expect((await app.inject({
1287
+ const res = await app.inject({
1102
1288
  method: "DELETE",
1103
1289
  url: `${this.getBaseUrl()}/000000000000000000000000`
1104
- })).statusCode).toBe(401);
1290
+ });
1291
+ expect(res.statusCode).toBe(401);
1105
1292
  });
1106
1293
  if (enabledRoutes.has("list")) it("admin should access list endpoint", async () => {
1107
1294
  const { app } = this.getOptions();
1108
- expect((await app.inject({
1295
+ const res = await app.inject({
1109
1296
  method: "GET",
1110
1297
  url: this.getBaseUrl(),
1111
1298
  headers: this.adminHeaders()
1112
- })).statusCode).toBeLessThan(400);
1299
+ });
1300
+ expect(res.statusCode).toBeLessThan(400);
1113
1301
  });
1114
1302
  if (enabledRoutes.has("create")) it("admin should access create endpoint", async () => {
1115
1303
  const { app, fixtures } = this.getOptions();
@@ -1156,6 +1344,90 @@ function createHttpTestHarness(resource, optionsOrGetter) {
1156
1344
  return new HttpTestHarness(resource, optionsOrGetter);
1157
1345
  }
1158
1346
  //#endregion
1347
+ //#region src/testing/moduleSetup.ts
1348
+ function createTestModuleSetup() {
1349
+ const deferred = [];
1350
+ return {
1351
+ context: { defer(disposer) {
1352
+ if (typeof disposer !== "function") throw new TypeError(`[arc/testing] defer() expected a function, received ${typeof disposer}.`);
1353
+ deferred.push(disposer);
1354
+ } },
1355
+ deferred,
1356
+ async dispose() {
1357
+ const errors = [];
1358
+ for (let i = deferred.length - 1; i >= 0; i--) {
1359
+ const dispose = deferred[i];
1360
+ if (!dispose) continue;
1361
+ try {
1362
+ await dispose();
1363
+ } catch (err) {
1364
+ errors.push(err);
1365
+ }
1366
+ }
1367
+ deferred.length = 0;
1368
+ if (errors.length === 1) throw errors[0];
1369
+ if (errors.length > 1) throw new AggregateError(errors, `[arc/testing] ${errors.length} disposers threw`);
1370
+ }
1371
+ };
1372
+ }
1373
+ //#endregion
1374
+ //#region src/testing/scopeRoleGate.ts
1375
+ /**
1376
+ * `scopeRoleGate` — the ONE role gate for package tests.
1377
+ *
1378
+ * ## Why this is in the framework
1379
+ *
1380
+ * Nine `@classytic/arc-*` packages had their own copy of this helper, and all nine were
1381
+ * wrong in the same two ways:
1382
+ *
1383
+ * 1. They read the role from an `x-role` HEADER and INSTALLED the request scope from
1384
+ * inside the check — so identity came from the caller's own claim, and it arrived too
1385
+ * late for arc's tenant-filtering pipeline to see it.
1386
+ * 2. They returned the pre-2.30 `{ granted: boolean }` shape, which is neither a boolean
1387
+ * nor an `AuthorizationDecision`. It passed through `normalizeToDecision` untouched,
1388
+ * arrived with `effect: undefined`, and was read as DENY — the gate said yes and arc
1389
+ * refused anyway. Nothing type-errored, because the gate is cast at the call site.
1390
+ *
1391
+ * Two of the nine had already been fixed by hand, each with a docstring describing this
1392
+ * exact discovery. The knowledge reached neither of the other seven. That is the whole
1393
+ * case for a shared seam: a copy does not fail, it diverges.
1394
+ *
1395
+ * A test role gate is generic — "does the verified scope hold one of these roles" has no
1396
+ * domain in it — so it belongs here, beside the contract it depends on.
1397
+ *
1398
+ * Identity itself comes from `testActorHeaders()` + `bootModuleApp`'s authenticator
1399
+ * (see `testActor.ts`), which puts the scope where production puts it. This stays a pure
1400
+ * predicate over what arc verified.
1401
+ *
1402
+ * Kept in its own module rather than beside `testActor.ts` so the two can be edited
1403
+ * independently.
1404
+ */
1405
+ /**
1406
+ * Grants iff the caller is an org member holding one of `allowed` as an ORG role.
1407
+ *
1408
+ * This is `requireOrgRole` under a name package tests reach for — deliberately a
1409
+ * delegation, not a reimplementation. It previously read `scope.userRoles` while
1410
+ * requiring an organization, so it disagreed with production for any actor whose
1411
+ * global and org roles differ: given
1412
+ * `{ roles: ["employee"], orgRoles: ["manager"], organizationId: "org-1" }`,
1413
+ * `requireOrgRole("manager")` allowed and this denied. The tests never caught it
1414
+ * because `scopeFromTestActor` defaults `orgRoles` to `roles`, making the two
1415
+ * dimensions identical for the common actor.
1416
+ *
1417
+ * Delegating also inherits the semantics a hand-rolled gate kept missing:
1418
+ * elevated scopes pass, and a service (API-key) identity is refused with a
1419
+ * message naming `requireServiceScope` instead of silently failing a role test.
1420
+ *
1421
+ * **Pair it with an org-bearing actor.** `testActorHeaders('manager', ORG)`
1422
+ * yields a `member` scope and passes; `testActorHeaders('manager')` yields an
1423
+ * `authenticated` scope with no tenant, which is refused — arc's generated CRUD
1424
+ * filters by tenant, so a gate that waves a tenant-less request through is how a
1425
+ * green test reads another org's rows.
1426
+ */
1427
+ function scopeRoleGate(...allowed) {
1428
+ return requireOrgRole(...allowed);
1429
+ }
1430
+ //#endregion
1159
1431
  //#region src/testing/testApp.ts
1160
1432
  /**
1161
1433
  * createTestApp — test app factory for arc
@@ -1221,7 +1493,7 @@ function pickDefaultAuth(authMode, callerAuth) {
1221
1493
  };
1222
1494
  }
1223
1495
  async function createTestApp(options = {}) {
1224
- const { createApp } = await import("../createApp-CXrKFciH.mjs").then((n) => n.r);
1496
+ const { createApp } = await import("../createApp-Bv0m-Q6_.mjs").then((n) => n.r);
1225
1497
  const { resources = [], db = "in-memory", connectMongoose = false, authMode = "jwt", defaultOrgId, plugins, auth: callerAuth, ...appOptions } = options;
1226
1498
  let dbHandle;
1227
1499
  let dbUri;
@@ -1291,4 +1563,4 @@ function createMinimalTestApp(options = {}) {
1291
1563
  });
1292
1564
  }
1293
1565
  //#endregion
1294
- export { HttpTestHarness, bootModuleApp, createBetterAuthProvider, createBetterAuthTestHelpers, createCustomAuthProvider, createDataFactory, createHttpTestHarness, createJwtAuthProvider, createMinimalTestApp, createMockController, createMockReply, createMockRepository, createMockRequest, createMockUser, createSpy, createTestApp, createTestFixtures, createTestTimer, expectArc, mongoMemoryDatabase, preloadResources, preloadResourcesAsync, runAuthorizationConformance, runStorageContract, safeParseBody, setupBetterAuthTestApp, waitFor };
1566
+ export { HttpTestHarness, TEST_ACTOR_HEADER, bootModuleApp, createBetterAuthProvider, createBetterAuthTestHelpers, createCustomAuthProvider, createDataFactory, createHttpTestHarness, createJwtAuthProvider, createMinimalTestApp, createMockController, createMockReply, createMockRepository, createMockRequest, createMockUser, createSpy, createTestApp, createTestFixtures, createTestModuleSetup, createTestTimer, expectArc, mongoMemoryDatabase, preloadResources, preloadResourcesAsync, runAuthorizationConformance, runStorageContract, safeParseBody, scopeFromTestActor, scopeRoleGate, setupBetterAuthTestApp, testActorAuth, testActorHeaders, waitFor };
@@ -1,11 +1,21 @@
1
+
1
2
  import { OutboxStore } from "@classytic/primitives/outbox";
2
3
  //#region src/testing/outboxStoreContract.d.ts
3
4
  interface OutboxStoreContractSetupResult {
4
5
  store: OutboxStore;
5
6
  /** Called once after the suite. */
6
7
  teardown?: () => Promise<void>;
7
- /** Empty the store between tests. Strongly recommended for isolation. */
8
- reset?: () => Promise<void>;
8
+ /**
9
+ * Empty the store. REQUIRED, and run before every test.
10
+ *
11
+ * Not a recommendation: several assertions are absolute rather than relative —
12
+ * "exactly zero pending", "exactly two", "`oldestPendingAgeMs()` is null on an
13
+ * empty queue". Without a reset those read whatever the previous test left, so the
14
+ * suite passes on ORDER rather than on behaviour, and inserting one unrelated case
15
+ * turns a conforming store red. A store that cannot empty itself cannot be
16
+ * conformance-tested.
17
+ */
18
+ reset: () => Promise<void>;
9
19
  }
10
20
  type OutboxStoreContractSetup = () => Promise<OutboxStoreContractSetupResult>;
11
21
  declare function runOutboxStoreContract(name: string, setup: OutboxStoreContractSetup): void;