@softprobe/softprobe-js 2.0.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 (328) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +202 -0
  3. package/bin/softprobe +15 -0
  4. package/dist/api/baggage.d.ts +13 -0
  5. package/dist/api/baggage.d.ts.map +1 -0
  6. package/dist/api/baggage.js +32 -0
  7. package/dist/api/baggage.js.map +1 -0
  8. package/dist/api/compare.d.ts +16 -0
  9. package/dist/api/compare.d.ts.map +1 -0
  10. package/dist/api/compare.js +77 -0
  11. package/dist/api/compare.js.map +1 -0
  12. package/dist/api.d.ts +63 -0
  13. package/dist/api.d.ts.map +1 -0
  14. package/dist/api.js +104 -0
  15. package/dist/api.js.map +1 -0
  16. package/dist/bindings/http-span.d.ts +6 -0
  17. package/dist/bindings/http-span.d.ts.map +1 -0
  18. package/dist/bindings/http-span.js +11 -0
  19. package/dist/bindings/http-span.js.map +1 -0
  20. package/dist/bindings/postgres-span.d.ts +6 -0
  21. package/dist/bindings/postgres-span.d.ts.map +1 -0
  22. package/dist/bindings/postgres-span.js +11 -0
  23. package/dist/bindings/postgres-span.js.map +1 -0
  24. package/dist/bindings/redis-span.d.ts +6 -0
  25. package/dist/bindings/redis-span.d.ts.map +1 -0
  26. package/dist/bindings/redis-span.js +11 -0
  27. package/dist/bindings/redis-span.js.map +1 -0
  28. package/dist/bindings/test-span.d.ts +6 -0
  29. package/dist/bindings/test-span.d.ts.map +1 -0
  30. package/dist/bindings/test-span.js +9 -0
  31. package/dist/bindings/test-span.js.map +1 -0
  32. package/dist/bootstrap/otel/framework-mutator.d.ts +20 -0
  33. package/dist/bootstrap/otel/framework-mutator.d.ts.map +1 -0
  34. package/dist/bootstrap/otel/framework-mutator.js +144 -0
  35. package/dist/bootstrap/otel/framework-mutator.js.map +1 -0
  36. package/dist/bootstrap/otel/inject.d.ts +17 -0
  37. package/dist/bootstrap/otel/inject.d.ts.map +1 -0
  38. package/dist/bootstrap/otel/inject.js +28 -0
  39. package/dist/bootstrap/otel/inject.js.map +1 -0
  40. package/dist/bootstrap/otel/mutator.d.ts +17 -0
  41. package/dist/bootstrap/otel/mutator.d.ts.map +1 -0
  42. package/dist/bootstrap/otel/mutator.js +51 -0
  43. package/dist/bootstrap/otel/mutator.js.map +1 -0
  44. package/dist/capture/express.d.ts +6 -0
  45. package/dist/capture/express.d.ts.map +1 -0
  46. package/dist/capture/express.js +11 -0
  47. package/dist/capture/express.js.map +1 -0
  48. package/dist/capture/fastify.d.ts +5 -0
  49. package/dist/capture/fastify.d.ts.map +1 -0
  50. package/dist/capture/fastify.js +9 -0
  51. package/dist/capture/fastify.js.map +1 -0
  52. package/dist/capture/framework-mutator.d.ts +20 -0
  53. package/dist/capture/framework-mutator.d.ts.map +1 -0
  54. package/dist/capture/framework-mutator.js +144 -0
  55. package/dist/capture/framework-mutator.js.map +1 -0
  56. package/dist/capture/http-inbound.d.ts +28 -0
  57. package/dist/capture/http-inbound.d.ts.map +1 -0
  58. package/dist/capture/http-inbound.js +40 -0
  59. package/dist/capture/http-inbound.js.map +1 -0
  60. package/dist/capture/inject.d.ts +17 -0
  61. package/dist/capture/inject.d.ts.map +1 -0
  62. package/dist/capture/inject.js +28 -0
  63. package/dist/capture/inject.js.map +1 -0
  64. package/dist/capture/mutator.d.ts +17 -0
  65. package/dist/capture/mutator.d.ts.map +1 -0
  66. package/dist/capture/mutator.js +51 -0
  67. package/dist/capture/mutator.js.map +1 -0
  68. package/dist/capture/postgres.d.ts +6 -0
  69. package/dist/capture/postgres.d.ts.map +1 -0
  70. package/dist/capture/postgres.js +11 -0
  71. package/dist/capture/postgres.js.map +1 -0
  72. package/dist/capture/redis.d.ts +5 -0
  73. package/dist/capture/redis.d.ts.map +1 -0
  74. package/dist/capture/redis.js +10 -0
  75. package/dist/capture/redis.js.map +1 -0
  76. package/dist/capture/store-accessor.d.ts +11 -0
  77. package/dist/capture/store-accessor.d.ts.map +1 -0
  78. package/dist/capture/store-accessor.js +19 -0
  79. package/dist/capture/store-accessor.js.map +1 -0
  80. package/dist/capture/stream-tap.d.ts +5 -0
  81. package/dist/capture/stream-tap.d.ts.map +1 -0
  82. package/dist/capture/stream-tap.js +9 -0
  83. package/dist/capture/stream-tap.js.map +1 -0
  84. package/dist/cli/diff-reporter.d.ts +26 -0
  85. package/dist/cli/diff-reporter.d.ts.map +1 -0
  86. package/dist/cli/diff-reporter.js +163 -0
  87. package/dist/cli/diff-reporter.js.map +1 -0
  88. package/dist/cli/diff.d.ts +18 -0
  89. package/dist/cli/diff.d.ts.map +1 -0
  90. package/dist/cli/diff.js +105 -0
  91. package/dist/cli/diff.js.map +1 -0
  92. package/dist/cli.d.ts +7 -0
  93. package/dist/cli.d.ts.map +1 -0
  94. package/dist/cli.js +84 -0
  95. package/dist/cli.js.map +1 -0
  96. package/dist/config/config-manager.d.ts +20 -0
  97. package/dist/config/config-manager.d.ts.map +1 -0
  98. package/dist/config/config-manager.js +46 -0
  99. package/dist/config/config-manager.js.map +1 -0
  100. package/dist/context.d.ts +98 -0
  101. package/dist/context.d.ts.map +1 -0
  102. package/dist/context.js +198 -0
  103. package/dist/context.js.map +1 -0
  104. package/dist/core/bindings/http-span.d.ts +39 -0
  105. package/dist/core/bindings/http-span.d.ts.map +1 -0
  106. package/dist/core/bindings/http-span.js +55 -0
  107. package/dist/core/bindings/http-span.js.map +1 -0
  108. package/dist/core/bindings/index.d.ts +12 -0
  109. package/dist/core/bindings/index.d.ts.map +1 -0
  110. package/dist/core/bindings/index.js +21 -0
  111. package/dist/core/bindings/index.js.map +1 -0
  112. package/dist/core/bindings/postgres-span.d.ts +41 -0
  113. package/dist/core/bindings/postgres-span.d.ts.map +1 -0
  114. package/dist/core/bindings/postgres-span.js +57 -0
  115. package/dist/core/bindings/postgres-span.js.map +1 -0
  116. package/dist/core/bindings/redis-span.d.ts +40 -0
  117. package/dist/core/bindings/redis-span.d.ts.map +1 -0
  118. package/dist/core/bindings/redis-span.js +65 -0
  119. package/dist/core/bindings/redis-span.js.map +1 -0
  120. package/dist/core/bindings/test-span.d.ts +15 -0
  121. package/dist/core/bindings/test-span.d.ts.map +1 -0
  122. package/dist/core/bindings/test-span.js +24 -0
  123. package/dist/core/bindings/test-span.js.map +1 -0
  124. package/dist/core/cassette/capture-store-accessor.d.ts +11 -0
  125. package/dist/core/cassette/capture-store-accessor.d.ts.map +1 -0
  126. package/dist/core/cassette/capture-store-accessor.js +19 -0
  127. package/dist/core/cassette/capture-store-accessor.js.map +1 -0
  128. package/dist/core/cassette/context-request-storage.d.ts +14 -0
  129. package/dist/core/cassette/context-request-storage.d.ts.map +1 -0
  130. package/dist/core/cassette/context-request-storage.js +24 -0
  131. package/dist/core/cassette/context-request-storage.js.map +1 -0
  132. package/dist/core/cassette/index.d.ts +8 -0
  133. package/dist/core/cassette/index.d.ts.map +1 -0
  134. package/dist/core/cassette/index.js +16 -0
  135. package/dist/core/cassette/index.js.map +1 -0
  136. package/dist/core/cassette/ndjson-cassette.d.ts +22 -0
  137. package/dist/core/cassette/ndjson-cassette.d.ts.map +1 -0
  138. package/dist/core/cassette/ndjson-cassette.js +56 -0
  139. package/dist/core/cassette/ndjson-cassette.js.map +1 -0
  140. package/dist/core/cassette/request-storage.d.ts +16 -0
  141. package/dist/core/cassette/request-storage.d.ts.map +1 -0
  142. package/dist/core/cassette/request-storage.js +22 -0
  143. package/dist/core/cassette/request-storage.js.map +1 -0
  144. package/dist/core/context/index.d.ts +5 -0
  145. package/dist/core/context/index.d.ts.map +1 -0
  146. package/dist/core/context/index.js +9 -0
  147. package/dist/core/context/index.js.map +1 -0
  148. package/dist/core/contracts/index.d.ts +5 -0
  149. package/dist/core/contracts/index.d.ts.map +1 -0
  150. package/dist/core/contracts/index.js +3 -0
  151. package/dist/core/contracts/index.js.map +1 -0
  152. package/dist/core/identifier.d.ts +20 -0
  153. package/dist/core/identifier.d.ts.map +1 -0
  154. package/dist/core/identifier.js +31 -0
  155. package/dist/core/identifier.js.map +1 -0
  156. package/dist/core/index.d.ts +11 -0
  157. package/dist/core/index.d.ts.map +1 -0
  158. package/dist/core/index.js +27 -0
  159. package/dist/core/index.js.map +1 -0
  160. package/dist/core/matcher/extract-key.d.ts +41 -0
  161. package/dist/core/matcher/extract-key.d.ts.map +1 -0
  162. package/dist/core/matcher/extract-key.js +80 -0
  163. package/dist/core/matcher/extract-key.js.map +1 -0
  164. package/dist/core/matcher/index.d.ts +9 -0
  165. package/dist/core/matcher/index.d.ts.map +1 -0
  166. package/dist/core/matcher/index.js +24 -0
  167. package/dist/core/matcher/index.js.map +1 -0
  168. package/dist/core/matcher/matcher.d.ts +25 -0
  169. package/dist/core/matcher/matcher.d.ts.map +1 -0
  170. package/dist/core/matcher/matcher.js +83 -0
  171. package/dist/core/matcher/matcher.js.map +1 -0
  172. package/dist/core/matcher/softprobe-matcher.d.ts +41 -0
  173. package/dist/core/matcher/softprobe-matcher.d.ts.map +1 -0
  174. package/dist/core/matcher/softprobe-matcher.js +92 -0
  175. package/dist/core/matcher/softprobe-matcher.js.map +1 -0
  176. package/dist/core/matcher/store-accessor.d.ts +14 -0
  177. package/dist/core/matcher/store-accessor.d.ts.map +1 -0
  178. package/dist/core/matcher/store-accessor.js +25 -0
  179. package/dist/core/matcher/store-accessor.js.map +1 -0
  180. package/dist/core/matcher/topology.d.ts +37 -0
  181. package/dist/core/matcher/topology.d.ts.map +1 -0
  182. package/dist/core/matcher/topology.js +72 -0
  183. package/dist/core/matcher/topology.js.map +1 -0
  184. package/dist/core/runtime/architecture-guard.d.ts +9 -0
  185. package/dist/core/runtime/architecture-guard.d.ts.map +1 -0
  186. package/dist/core/runtime/architecture-guard.js +131 -0
  187. package/dist/core/runtime/architecture-guard.js.map +1 -0
  188. package/dist/core/runtime/index.d.ts +6 -0
  189. package/dist/core/runtime/index.d.ts.map +1 -0
  190. package/dist/core/runtime/index.js +11 -0
  191. package/dist/core/runtime/index.js.map +1 -0
  192. package/dist/identifier.d.ts +5 -0
  193. package/dist/identifier.d.ts.map +1 -0
  194. package/dist/identifier.js +11 -0
  195. package/dist/identifier.js.map +1 -0
  196. package/dist/index.d.ts +5 -0
  197. package/dist/index.d.ts.map +1 -0
  198. package/dist/index.js +21 -0
  199. package/dist/index.js.map +1 -0
  200. package/dist/init.d.ts +21 -0
  201. package/dist/init.d.ts.map +1 -0
  202. package/dist/init.js +51 -0
  203. package/dist/init.js.map +1 -0
  204. package/dist/instrumentations/common/http/context-headers.d.ts +9 -0
  205. package/dist/instrumentations/common/http/context-headers.d.ts.map +1 -0
  206. package/dist/instrumentations/common/http/context-headers.js +16 -0
  207. package/dist/instrumentations/common/http/context-headers.js.map +1 -0
  208. package/dist/instrumentations/common/http/inbound-capture.d.ts +17 -0
  209. package/dist/instrumentations/common/http/inbound-capture.d.ts.map +1 -0
  210. package/dist/instrumentations/common/http/inbound-capture.js +41 -0
  211. package/dist/instrumentations/common/http/inbound-capture.js.map +1 -0
  212. package/dist/instrumentations/common/http/inbound-record.d.ts +28 -0
  213. package/dist/instrumentations/common/http/inbound-record.d.ts.map +1 -0
  214. package/dist/instrumentations/common/http/inbound-record.js +40 -0
  215. package/dist/instrumentations/common/http/inbound-record.js.map +1 -0
  216. package/dist/instrumentations/common/http/span-adapter.d.ts +6 -0
  217. package/dist/instrumentations/common/http/span-adapter.d.ts.map +1 -0
  218. package/dist/instrumentations/common/http/span-adapter.js +12 -0
  219. package/dist/instrumentations/common/http/span-adapter.js.map +1 -0
  220. package/dist/instrumentations/common/http/stream-tap.d.ts +28 -0
  221. package/dist/instrumentations/common/http/stream-tap.d.ts.map +1 -0
  222. package/dist/instrumentations/common/http/stream-tap.js +61 -0
  223. package/dist/instrumentations/common/http/stream-tap.js.map +1 -0
  224. package/dist/instrumentations/express/capture.d.ts +26 -0
  225. package/dist/instrumentations/express/capture.d.ts.map +1 -0
  226. package/dist/instrumentations/express/capture.js +80 -0
  227. package/dist/instrumentations/express/capture.js.map +1 -0
  228. package/dist/instrumentations/express/index.d.ts +7 -0
  229. package/dist/instrumentations/express/index.d.ts.map +1 -0
  230. package/dist/instrumentations/express/index.js +15 -0
  231. package/dist/instrumentations/express/index.js.map +1 -0
  232. package/dist/instrumentations/express/replay.d.ts +10 -0
  233. package/dist/instrumentations/express/replay.d.ts.map +1 -0
  234. package/dist/instrumentations/express/replay.js +16 -0
  235. package/dist/instrumentations/express/replay.js.map +1 -0
  236. package/dist/instrumentations/fastify/capture.d.ts +13 -0
  237. package/dist/instrumentations/fastify/capture.d.ts.map +1 -0
  238. package/dist/instrumentations/fastify/capture.js +65 -0
  239. package/dist/instrumentations/fastify/capture.js.map +1 -0
  240. package/dist/instrumentations/fastify/index.d.ts +7 -0
  241. package/dist/instrumentations/fastify/index.d.ts.map +1 -0
  242. package/dist/instrumentations/fastify/index.js +13 -0
  243. package/dist/instrumentations/fastify/index.js.map +1 -0
  244. package/dist/instrumentations/fastify/replay.d.ts +13 -0
  245. package/dist/instrumentations/fastify/replay.d.ts.map +1 -0
  246. package/dist/instrumentations/fastify/replay.js +24 -0
  247. package/dist/instrumentations/fastify/replay.js.map +1 -0
  248. package/dist/instrumentations/fetch/index.d.ts +5 -0
  249. package/dist/instrumentations/fetch/index.d.ts.map +1 -0
  250. package/dist/instrumentations/fetch/index.js +10 -0
  251. package/dist/instrumentations/fetch/index.js.map +1 -0
  252. package/dist/instrumentations/fetch/replay.d.ts +19 -0
  253. package/dist/instrumentations/fetch/replay.d.ts.map +1 -0
  254. package/dist/instrumentations/fetch/replay.js +259 -0
  255. package/dist/instrumentations/fetch/replay.js.map +1 -0
  256. package/dist/instrumentations/postgres/capture.d.ts +46 -0
  257. package/dist/instrumentations/postgres/capture.d.ts.map +1 -0
  258. package/dist/instrumentations/postgres/capture.js +68 -0
  259. package/dist/instrumentations/postgres/capture.js.map +1 -0
  260. package/dist/instrumentations/postgres/index.d.ts +6 -0
  261. package/dist/instrumentations/postgres/index.d.ts.map +1 -0
  262. package/dist/instrumentations/postgres/index.js +14 -0
  263. package/dist/instrumentations/postgres/index.js.map +1 -0
  264. package/dist/instrumentations/postgres/replay.d.ts +20 -0
  265. package/dist/instrumentations/postgres/replay.d.ts.map +1 -0
  266. package/dist/instrumentations/postgres/replay.js +163 -0
  267. package/dist/instrumentations/postgres/replay.js.map +1 -0
  268. package/dist/instrumentations/redis/capture.d.ts +20 -0
  269. package/dist/instrumentations/redis/capture.d.ts.map +1 -0
  270. package/dist/instrumentations/redis/capture.js +61 -0
  271. package/dist/instrumentations/redis/capture.js.map +1 -0
  272. package/dist/instrumentations/redis/index.d.ts +6 -0
  273. package/dist/instrumentations/redis/index.d.ts.map +1 -0
  274. package/dist/instrumentations/redis/index.js +13 -0
  275. package/dist/instrumentations/redis/index.js.map +1 -0
  276. package/dist/instrumentations/redis/replay.d.ts +24 -0
  277. package/dist/instrumentations/redis/replay.d.ts.map +1 -0
  278. package/dist/instrumentations/redis/replay.js +173 -0
  279. package/dist/instrumentations/redis/replay.js.map +1 -0
  280. package/dist/replay/express.d.ts +5 -0
  281. package/dist/replay/express.d.ts.map +1 -0
  282. package/dist/replay/express.js +9 -0
  283. package/dist/replay/express.js.map +1 -0
  284. package/dist/replay/extract-key.d.ts +41 -0
  285. package/dist/replay/extract-key.d.ts.map +1 -0
  286. package/dist/replay/extract-key.js +80 -0
  287. package/dist/replay/extract-key.js.map +1 -0
  288. package/dist/replay/fastify.d.ts +5 -0
  289. package/dist/replay/fastify.d.ts.map +1 -0
  290. package/dist/replay/fastify.js +9 -0
  291. package/dist/replay/fastify.js.map +1 -0
  292. package/dist/replay/http.d.ts +5 -0
  293. package/dist/replay/http.d.ts.map +1 -0
  294. package/dist/replay/http.js +10 -0
  295. package/dist/replay/http.js.map +1 -0
  296. package/dist/replay/matcher.d.ts +25 -0
  297. package/dist/replay/matcher.d.ts.map +1 -0
  298. package/dist/replay/matcher.js +83 -0
  299. package/dist/replay/matcher.js.map +1 -0
  300. package/dist/replay/postgres.d.ts +5 -0
  301. package/dist/replay/postgres.d.ts.map +1 -0
  302. package/dist/replay/postgres.js +10 -0
  303. package/dist/replay/postgres.js.map +1 -0
  304. package/dist/replay/redis.d.ts +5 -0
  305. package/dist/replay/redis.d.ts.map +1 -0
  306. package/dist/replay/redis.js +10 -0
  307. package/dist/replay/redis.js.map +1 -0
  308. package/dist/replay/softprobe-matcher.d.ts +41 -0
  309. package/dist/replay/softprobe-matcher.d.ts.map +1 -0
  310. package/dist/replay/softprobe-matcher.js +92 -0
  311. package/dist/replay/softprobe-matcher.js.map +1 -0
  312. package/dist/replay/store-accessor.d.ts +14 -0
  313. package/dist/replay/store-accessor.d.ts.map +1 -0
  314. package/dist/replay/store-accessor.js +26 -0
  315. package/dist/replay/store-accessor.js.map +1 -0
  316. package/dist/replay/topology.d.ts +37 -0
  317. package/dist/replay/topology.d.ts.map +1 -0
  318. package/dist/replay/topology.js +72 -0
  319. package/dist/replay/topology.js.map +1 -0
  320. package/dist/store/cassette-store.d.ts +36 -0
  321. package/dist/store/cassette-store.d.ts.map +1 -0
  322. package/dist/store/cassette-store.js +66 -0
  323. package/dist/store/cassette-store.js.map +1 -0
  324. package/dist/types/schema.d.ts +114 -0
  325. package/dist/types/schema.d.ts.map +1 -0
  326. package/dist/types/schema.js +13 -0
  327. package/dist/types/schema.js.map +1 -0
  328. package/package.json +88 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Softprobe
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,202 @@
1
+ # Softprobe
2
+
3
+ Topology-aware record & replay testing framework for Node.js via OpenTelemetry. Record real traffic (Postgres, Redis, HTTP) into NDJSON cassettes and replay it in tests without live dependencies.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @softprobe/softprobe-js
9
+ ```
10
+
11
+ ## Global CLI (Recommended)
12
+
13
+ Install once, then use `softprobe` directly:
14
+
15
+ ```bash
16
+ npm install -g @softprobe/softprobe-js
17
+ softprobe --help
18
+ softprobe --version
19
+ ```
20
+
21
+ Fallback (no global install):
22
+
23
+ ```bash
24
+ npx @softprobe/softprobe-js --help
25
+ ```
26
+
27
+ ## Quick Start (Library + CLI)
28
+
29
+ 1. Install and add an instrumentation bootstrap that imports Softprobe first:
30
+
31
+ ```ts
32
+ // instrumentation.ts
33
+ import "@softprobe/softprobe-js/init";
34
+
35
+ import { NodeSDK } from "@opentelemetry/sdk-node";
36
+ import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
37
+ ```
38
+
39
+ 2. Start your service with that bootstrap:
40
+
41
+ ```bash
42
+ node -r ./instrumentation.ts ./server.ts
43
+ ```
44
+
45
+ 3. Capture a real request as a cassette:
46
+
47
+ ```bash
48
+ TRACE_ID=11111111111111111111111111111111
49
+ curl -H "x-softprobe-mode: CAPTURE" \
50
+ -H "x-softprobe-trace-id: ${TRACE_ID}" \
51
+ http://localhost:3000/your-route
52
+ ```
53
+
54
+ 4. Replay in tests with `run({ mode, storage, traceId }, fn)`:
55
+
56
+ ```ts
57
+ import { softprobe } from "@softprobe/softprobe-js";
58
+
59
+ it("replays from cassette", async () => {
60
+ const storage = {
61
+ async loadTrace() {
62
+ // Load and return records for the trace bound to this cassette.
63
+ return [];
64
+ },
65
+ async saveRecord(_record) {
66
+ // Replay-only example: no-op for writes.
67
+ },
68
+ };
69
+
70
+ await softprobe.run(
71
+ { mode: "REPLAY", storage: storage, traceId: "prod-trace-345" },
72
+ async () => {
73
+ const res = await fetch("http://localhost:3000/users/1");
74
+ expect(res.status).toBe(200);
75
+ }
76
+ );
77
+ });
78
+ ```
79
+
80
+ 5. Compare current behavior vs recorded behavior with CLI diff:
81
+
82
+ ```bash
83
+ softprobe diff ./cassettes/11111111111111111111111111111111.ndjson http://localhost:3000
84
+ ```
85
+
86
+ For a full walkthrough, see [examples/basic-app/README.md](examples/basic-app/README.md) and [examples/pricing-regression-demo/README.md](examples/pricing-regression-demo/README.md).
87
+
88
+ ## CLI — `softprobe diff`
89
+
90
+ Replay the recorded inbound request against a running service and compare. The CLI sends the request with **coordination headers** so the service can run in replay mode for that request.
91
+
92
+ ### Usage
93
+
94
+ ```bash
95
+ softprobe diff <cassette.ndjson> <targetUrl>
96
+ ```
97
+
98
+ - **`cassette.ndjson`** — Path to the NDJSON cassette file (must contain an `inbound` record).
99
+ - **`targetUrl`** — Base URL of the target service (e.g. `http://localhost:3000`).
100
+
101
+ ### How to generate the cassette
102
+
103
+ Send requests to your app with **coordination headers** so it records that traffic into an NDJSON file. No environment variables are required.
104
+
105
+ 1. Run your app with Softprobe middleware (Express or Fastify) and `import "softprobe/init"` at startup. Configure **cassetteDirectory** in config so the server knows where to read/write cassette files. Per-trace files are always `{cassetteDirectory}/{traceId}.ndjson`. (Config may still contain `cassettePath` for backward compatibility; init derives the directory from it.)
106
+ 2. For each request you want to record, send these HTTP headers:
107
+ - **`x-softprobe-mode: CAPTURE`** — this request (and its outbound calls) will be recorded.
108
+ - **`x-softprobe-trace-id: <id>`** — trace id for this request; the cassette file will be `{cassetteDirectory}/{id}.ndjson`.
109
+ 3. The middleware uses these headers and writes the inbound request/response (and any outbound records for that trace) to `{cassetteDirectory}/{traceId}.ndjson`. The cassette will contain at least one `inbound` record.
110
+
111
+ Example with curl (server must have cassetteDirectory configured, e.g. via config):
112
+
113
+ ```bash
114
+ curl -H "x-softprobe-mode: CAPTURE" \
115
+ -H "x-softprobe-trace-id: my-trace-1" \
116
+ http://localhost:3000/your-route
117
+ ```
118
+
119
+ Repeat for other routes or use the same path to append more records to the same cassette.
120
+
121
+ ### How to run the CLI
122
+
123
+ **Global install (recommended):**
124
+
125
+ ```bash
126
+ softprobe diff <cassette.ndjson> <targetUrl>
127
+ ```
128
+
129
+ **No global install (fallback):**
130
+
131
+ ```bash
132
+ npx @softprobe/softprobe-js diff <cassette.ndjson> <targetUrl>
133
+ ```
134
+
135
+ **If you're in the repo** (no install, no build needed):
136
+
137
+ ```bash
138
+ ./bin/softprobe diff <cassette.ndjson> <targetUrl>
139
+ ```
140
+
141
+ The `bin/softprobe` script uses the built CLI when present, otherwise runs from source.
142
+
143
+ ### What the CLI does
144
+
145
+ 1. Loads the cassette and finds the **inbound** HTTP record.
146
+ 2. Sends the same method and path to `<targetUrl>` with these headers:
147
+ - `x-softprobe-mode: REPLAY`
148
+ - `x-softprobe-trace-id: <traceId from record>`
149
+ 3. The target server must have **cassetteDirectory** set (e.g. via config) so it resolves the cassette as `{cassetteDirectory}/{traceId}.ndjson`. Compares the live response to the recording (the **diff reporter**). Writes **PASS** or **FAIL** to stderr; on failure, prints a colored diff of what differed. Writes the response body to stdout. Exit code 0 = pass, 1 = fail or error.
150
+
151
+ Your service must use Softprobe middleware (Express or Fastify) so it reads these headers and runs that request in replay context. See [design.md](design.md) for the full coordination flow.
152
+
153
+ ### Example
154
+
155
+ ```bash
156
+ # Start your app (with Softprobe middleware) on port 3000, then:
157
+ softprobe diff ./softprobe-cassettes.ndjson http://localhost:3000
158
+ # Fallback:
159
+ npx @softprobe/softprobe-js diff ./softprobe-cassettes.ndjson http://localhost:3000
160
+ # Or from repo: ./bin/softprobe diff ./softprobe-cassettes.ndjson http://localhost:3000
161
+ ```
162
+
163
+ ## Release
164
+
165
+ This repository publishes to npm as `@softprobe/softprobe-js` via GitHub Actions workflow [`.github/workflows/release.yml`](.github/workflows/release.yml).
166
+
167
+ Setup once:
168
+
169
+ 1. Configure npm Trusted Publishing for this package and this GitHub repository/workflow.
170
+ 2. Ensure npm package access is public for the scoped package.
171
+ 3. Keep workflow permissions with `id-token: write` (already set in release workflow).
172
+
173
+ Reference:
174
+ - npm trusted publishing docs: https://docs.npmjs.com/trusted-publishers/
175
+ - npm classic token revocation announcement: https://github.blog/changelog/2025-12-09-npm-classic-tokens-revoked-session-based-auth-and-cli-token-management-now-available/
176
+
177
+ Release flow:
178
+
179
+ 1. Merge changes to `main`.
180
+ 2. Create and push a version tag, for example `v2.0.1`.
181
+ 3. GitHub Action builds and publishes automatically.
182
+
183
+ Manual validation flow:
184
+
185
+ 1. Run the `Release` workflow with `dry_run=true` to verify publish steps without publishing.
186
+
187
+ No `NPM_TOKEN` repository secret is required for this workflow.
188
+
189
+ ## Package Layout
190
+
191
+ - `src/core` contains shared framework-agnostic contracts and runtime helpers.
192
+ - `src/instrumentations/<package>` contains package-specific hooks/patches (for example `express`, `fastify`, `redis`, `postgres`, `fetch`).
193
+ - `src/instrumentations/common` contains shared protocol helpers used by multiple instrumentation packages.
194
+
195
+ ## More
196
+
197
+ - **Example app:** `examples/basic-app` — capture, replay, and custom matcher.
198
+ - **Example walkthrough:** [examples/basic-app/README.md](examples/basic-app/README.md)
199
+ - **Design index:** [design.md](design.md) — architecture, cassette format, and coordination headers.
200
+ - **Context design:** [design-context.md](design-context.md)
201
+ - **Cassette design:** [design-cassette.md](design-cassette.md)
202
+ - **Matcher design:** [design-matcher.md](design-matcher.md)
package/bin/softprobe ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env bash
2
+ # Run the Softprobe CLI. Works from the repo without building (uses dist if present, else ts-node).
3
+ # Usage: softprobe diff <cassette.ndjson> <targetUrl>
4
+ # From repo: ./bin/softprobe diff ./cassette.ndjson http://localhost:3000
5
+ # After npm install: npx softprobe diff ./cassette.ndjson http://localhost:3000
6
+
7
+ set -e
8
+ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
9
+ cd "$REPO_ROOT"
10
+
11
+ if [ -f dist/cli.js ]; then
12
+ exec node dist/cli.js "$@"
13
+ else
14
+ exec npx ts-node --transpile-only src/cli.ts "$@"
15
+ fi
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Task 15.1.1: Inject softprobe-mode into OTel Baggage for downstream propagation.
3
+ * Design §15.2: when mode=REPLAY, inject so internal microservice calls are also mocked.
4
+ */
5
+ import { context } from '@opentelemetry/api';
6
+ /**
7
+ * Returns the current OTel context with `softprobe-mode: REPLAY` in baggage when
8
+ * SoftprobeContext.getMode() is REPLAY. Otherwise returns the active context unchanged.
9
+ * Middleware (Express/Fastify) should run the request in this context so
10
+ * outbound calls propagate the mode to downstream services.
11
+ */
12
+ export declare function getContextWithReplayBaggage(): ReturnType<typeof context.active>;
13
+ //# sourceMappingURL=baggage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"baggage.d.ts","sourceRoot":"","sources":["../../src/api/baggage.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EACL,OAAO,EAGR,MAAM,oBAAoB,CAAC;AAO5B;;;;;GAKG;AACH,wBAAgB,2BAA2B,IAAI,UAAU,CAAC,OAAO,OAAO,CAAC,MAAM,CAAC,CAe/E"}
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getContextWithReplayBaggage = getContextWithReplayBaggage;
4
+ /**
5
+ * Task 15.1.1: Inject softprobe-mode into OTel Baggage for downstream propagation.
6
+ * Design §15.2: when mode=REPLAY, inject so internal microservice calls are also mocked.
7
+ */
8
+ const api_1 = require("@opentelemetry/api");
9
+ const context_1 = require("../context");
10
+ const SOFTPROBE_MODE_KEY = 'softprobe-mode';
11
+ const REPLAY_VALUE = 'REPLAY';
12
+ const METADATA_STR = 'softprobe';
13
+ /**
14
+ * Returns the current OTel context with `softprobe-mode: REPLAY` in baggage when
15
+ * SoftprobeContext.getMode() is REPLAY. Otherwise returns the active context unchanged.
16
+ * Middleware (Express/Fastify) should run the request in this context so
17
+ * outbound calls propagate the mode to downstream services.
18
+ */
19
+ function getContextWithReplayBaggage() {
20
+ const activeContext = api_1.context.active();
21
+ if (context_1.SoftprobeContext.getMode() !== REPLAY_VALUE) {
22
+ return activeContext;
23
+ }
24
+ const currentBaggage = api_1.propagation.getBaggage(activeContext);
25
+ const entry = {
26
+ value: REPLAY_VALUE,
27
+ metadata: (0, api_1.baggageEntryMetadataFromString)(METADATA_STR),
28
+ };
29
+ const newBaggage = (currentBaggage ?? api_1.propagation.createBaggage()).setEntry(SOFTPROBE_MODE_KEY, entry);
30
+ return api_1.propagation.setBaggage(activeContext, newBaggage);
31
+ }
32
+ //# sourceMappingURL=baggage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"baggage.js","sourceRoot":"","sources":["../../src/api/baggage.ts"],"names":[],"mappings":";;AAqBA,kEAeC;AApCD;;;GAGG;AACH,4CAI4B;AAC5B,wCAA8C;AAE9C,MAAM,kBAAkB,GAAG,gBAAgB,CAAC;AAC5C,MAAM,YAAY,GAAG,QAAQ,CAAC;AAC9B,MAAM,YAAY,GAAG,WAAW,CAAC;AAEjC;;;;;GAKG;AACH,SAAgB,2BAA2B;IACzC,MAAM,aAAa,GAAG,aAAO,CAAC,MAAM,EAAE,CAAC;IACvC,IAAI,0BAAgB,CAAC,OAAO,EAAE,KAAK,YAAY,EAAE,CAAC;QAChD,OAAO,aAAa,CAAC;IACvB,CAAC;IACD,MAAM,cAAc,GAAG,iBAAW,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;IAC7D,MAAM,KAAK,GAAG;QACZ,KAAK,EAAE,YAAY;QACnB,QAAQ,EAAE,IAAA,oCAA8B,EAAC,YAAY,CAAC;KACvD,CAAC;IACF,MAAM,UAAU,GAAG,CAAC,cAAc,IAAI,iBAAW,CAAC,aAAa,EAAE,CAAC,CAAC,QAAQ,CACzE,kBAAkB,EAClB,KAAK,CACN,CAAC;IACF,OAAO,iBAAW,CAAC,UAAU,CAAC,aAAa,EAAE,UAAU,CAAC,CAAC;AAC3D,CAAC"}
@@ -0,0 +1,16 @@
1
+ import type { SoftprobeCassetteRecord } from '../types/schema';
2
+ export type CompareInboundInput = {
3
+ /** HTTP status code of the actual response. */
4
+ status: number;
5
+ /** Parsed response body (e.g. from res.json() or res.text()). */
6
+ body: unknown;
7
+ /** Optional response headers. Compared when SoftprobeContext.getStrictComparison() is set (Task 15.2.2). */
8
+ headers?: Record<string, string>;
9
+ };
10
+ /**
11
+ * Compares the actual response (status and body) to the given recorded inbound record.
12
+ * Deep equality is used for body. Used by softprobe.compareInbound() with getRecordedInboundResponse().
13
+ * @throws When recorded is missing, or when status or body does not match.
14
+ */
15
+ export declare function compareInboundWithRecord(actual: CompareInboundInput, recorded: SoftprobeCassetteRecord | undefined): void;
16
+ //# sourceMappingURL=compare.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compare.d.ts","sourceRoot":"","sources":["../../src/api/compare.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAE/D,MAAM,MAAM,mBAAmB,GAAG;IAChC,+CAA+C;IAC/C,MAAM,EAAE,MAAM,CAAC;IACf,iEAAiE;IACjE,IAAI,EAAE,OAAO,CAAC;IACd,4GAA4G;IAC5G,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC,CAAC;AAiCF;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,mBAAmB,EAC3B,QAAQ,EAAE,uBAAuB,GAAG,SAAS,GAC5C,IAAI,CA6BN"}
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.compareInboundWithRecord = compareInboundWithRecord;
4
+ /**
5
+ * Task 15.2.1: Inbound comparison utility.
6
+ * Retrieves the recorded inbound record and performs deep equality check on status and body.
7
+ */
8
+ const context_1 = require("../context");
9
+ /**
10
+ * Recorded inbound may use responsePayload.status (Express) or responsePayload.statusCode (http-inbound).
11
+ */
12
+ function getRecordedStatus(record) {
13
+ const payload = record.responsePayload;
14
+ return payload?.status ?? payload?.statusCode ?? record.statusCode;
15
+ }
16
+ function getRecordedBody(record) {
17
+ const payload = record.responsePayload;
18
+ return payload?.body;
19
+ }
20
+ function getRecordedHeaders(record) {
21
+ const payload = record.responsePayload;
22
+ return payload?.headers;
23
+ }
24
+ function isStrictComparison() {
25
+ return context_1.SoftprobeContext.getStrictComparison() === true;
26
+ }
27
+ /** Normalize header keys to lowercase for comparison. */
28
+ function normalizeHeaders(h) {
29
+ const out = {};
30
+ for (const [k, v] of Object.entries(h)) {
31
+ out[k.toLowerCase()] = v;
32
+ }
33
+ return out;
34
+ }
35
+ /**
36
+ * Compares the actual response (status and body) to the given recorded inbound record.
37
+ * Deep equality is used for body. Used by softprobe.compareInbound() with getRecordedInboundResponse().
38
+ * @throws When recorded is missing, or when status or body does not match.
39
+ */
40
+ function compareInboundWithRecord(actual, recorded) {
41
+ if (!recorded) {
42
+ throw new Error('compareInbound: no recorded inbound response for this trace');
43
+ }
44
+ const expectedStatus = getRecordedStatus(recorded);
45
+ const expectedBody = getRecordedBody(recorded);
46
+ if (expectedStatus !== undefined && actual.status !== expectedStatus) {
47
+ throw new Error(`compareInbound: status mismatch (actual ${actual.status}, expected ${expectedStatus})`);
48
+ }
49
+ if (!deepEqual(actual.body, expectedBody)) {
50
+ throw new Error('compareInbound: body mismatch (actual and recorded response bodies are not deep equal)');
51
+ }
52
+ if (isStrictComparison()) {
53
+ const expectedHeaders = getRecordedHeaders(recorded);
54
+ if (expectedHeaders !== undefined && Object.keys(expectedHeaders).length > 0) {
55
+ const actualHeaders = actual.headers ?? {};
56
+ const normExpected = normalizeHeaders(expectedHeaders);
57
+ const normActual = normalizeHeaders(actualHeaders);
58
+ if (!deepEqual(normActual, normExpected)) {
59
+ throw new Error('compareInbound: header mismatch (actual and recorded response headers are not equal in strict mode)');
60
+ }
61
+ }
62
+ }
63
+ }
64
+ function deepEqual(a, b) {
65
+ if (a === b)
66
+ return true;
67
+ if (a === null || b === null || typeof a !== 'object' || typeof b !== 'object') {
68
+ return false;
69
+ }
70
+ const keysA = Object.keys(a).sort();
71
+ const keysB = Object.keys(b).sort();
72
+ if (keysA.length !== keysB.length || keysA.some((k, i) => k !== keysB[i])) {
73
+ return false;
74
+ }
75
+ return keysA.every((k) => deepEqual(a[k], b[k]));
76
+ }
77
+ //# sourceMappingURL=compare.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compare.js","sourceRoot":"","sources":["../../src/api/compare.ts"],"names":[],"mappings":"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"}
package/dist/api.d.ts ADDED
@@ -0,0 +1,63 @@
1
+ import type { SoftprobeCassetteRecord, SoftprobeRunOptions } from './types/schema';
2
+ import type { SemanticMatcher } from './core/matcher/matcher';
3
+ import { SoftprobeMatcher } from './core/matcher/softprobe-matcher';
4
+ import { getContextWithReplayBaggage } from './api/baggage';
5
+ import { type CompareInboundInput } from './api/compare';
6
+ import { SoftprobeContext } from './context';
7
+ /**
8
+ * Returns the current replay context for the active async scope from OTel context.
9
+ * Stored in OTel context by SoftprobeContext.withData/run.
10
+ */
11
+ export declare function getContext(): ReturnType<typeof SoftprobeContext.active> | undefined;
12
+ /**
13
+ * Returns the active matcher for the current replay context, if any.
14
+ * In REPLAY mode, returns global matcher when context has no matcher; includes baggage fallback.
15
+ */
16
+ export declare function getActiveMatcher(): SemanticMatcher | SoftprobeMatcher | undefined;
17
+ /**
18
+ * Returns the recorded inbound response for the current trace, if any.
19
+ * Set by runWithContext when loading a cassette that contains a record with type "inbound".
20
+ */
21
+ export declare function getRecordedInboundResponse(): SoftprobeCassetteRecord | undefined;
22
+ /**
23
+ * Task 15.2.1: Compares the actual response (status and body) to the recorded inbound for the current trace.
24
+ * Task 15.2.2: When SOFTPROBE_STRICT_COMPARISON is set, also compares headers.
25
+ * @throws When no recorded inbound exists, or when status, body, or (if strict) headers do not match.
26
+ */
27
+ export declare function compareInbound(actual: CompareInboundInput): void;
28
+ /**
29
+ * Returns recorded cassette records for the given traceId from the active context only.
30
+ * Records are context-scoped (matcher is created by SoftprobeContext.run(REPLAY)). Task 15.3.1.
31
+ */
32
+ export declare function getRecordsForTrace(traceId: string): SoftprobeCassetteRecord[];
33
+ /**
34
+ * Sets the global replay matcher. For test/cleanup only; the framework creates matchers in SoftprobeContext.run(REPLAY).
35
+ * getMatcher() returns only the context matcher, not this global.
36
+ */
37
+ export declare function setGlobalReplayMatcher(matcher: SoftprobeMatcher | undefined): void;
38
+ /**
39
+ * Primes the active matcher with records for the given traceId.
40
+ * Called by server middleware (Express/Fastify) after run(); the matcher was already seeded in run(), so this is a no-op when records are present.
41
+ */
42
+ export declare function activateReplayForContext(traceId: string): void;
43
+ /**
44
+ * No-op. Replay is context-scoped; only SoftprobeContext.run(REPLAY) creates and seeds the matcher.
45
+ * Use cassetteDirectory + traceId and middleware so each request gets its own context and matcher.
46
+ */
47
+ export declare function ensureReplayLoadedForRequest(): Promise<void>;
48
+ /** Runs callback inside a scoped Softprobe context using SoftprobeRunOptions. */
49
+ export declare function run<T>(options: SoftprobeRunOptions, fn: () => T | Promise<T>): T | Promise<T>;
50
+ export { getContextWithReplayBaggage } from './api/baggage';
51
+ export declare const softprobe: {
52
+ getContext: typeof getContext;
53
+ getActiveMatcher: typeof getActiveMatcher;
54
+ getRecordedInboundResponse: typeof getRecordedInboundResponse;
55
+ compareInbound: typeof compareInbound;
56
+ getRecordsForTrace: typeof getRecordsForTrace;
57
+ setGlobalReplayMatcher: typeof setGlobalReplayMatcher;
58
+ activateReplayForContext: typeof activateReplayForContext;
59
+ ensureReplayLoadedForRequest: typeof ensureReplayLoadedForRequest;
60
+ run: typeof run;
61
+ getContextWithReplayBaggage: typeof getContextWithReplayBaggage;
62
+ };
63
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACnF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACpE,OAAO,EAAE,2BAA2B,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,EAA4B,KAAK,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAEnF,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAE7C;;;GAGG;AACH,wBAAgB,UAAU,IACtB,UAAU,CAAC,OAAO,gBAAgB,CAAC,MAAM,CAAC,GAC1C,SAAS,CAUZ;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,eAAe,GAAG,gBAAgB,GAAG,SAAS,CAEjF;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,IAAI,uBAAuB,GAAG,SAAS,CAEhF;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,mBAAmB,GAAG,IAAI,CAEhE;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,uBAAuB,EAAE,CAE7E;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,gBAAgB,GAAG,SAAS,GAAG,IAAI,CAElF;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAQ9D;AAED;;;GAGG;AACH,wBAAsB,4BAA4B,IAAI,OAAO,CAAC,IAAI,CAAC,CAAG;AAEtE,iFAAiF;AACjF,wBAAgB,GAAG,CAAC,CAAC,EAAE,OAAO,EAAE,mBAAmB,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAE7F;AAED,OAAO,EAAE,2BAA2B,EAAE,MAAM,eAAe,CAAC;AAE5D,eAAO,MAAM,SAAS;;;;;;;;;;;CAWrB,CAAC"}
package/dist/api.js ADDED
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.softprobe = exports.getContextWithReplayBaggage = void 0;
4
+ exports.getContext = getContext;
5
+ exports.getActiveMatcher = getActiveMatcher;
6
+ exports.getRecordedInboundResponse = getRecordedInboundResponse;
7
+ exports.compareInbound = compareInbound;
8
+ exports.getRecordsForTrace = getRecordsForTrace;
9
+ exports.setGlobalReplayMatcher = setGlobalReplayMatcher;
10
+ exports.activateReplayForContext = activateReplayForContext;
11
+ exports.ensureReplayLoadedForRequest = ensureReplayLoadedForRequest;
12
+ exports.run = run;
13
+ const baggage_1 = require("./api/baggage");
14
+ const compare_1 = require("./api/compare");
15
+ const store_accessor_1 = require("./core/matcher/store-accessor");
16
+ const context_1 = require("./context");
17
+ /**
18
+ * Returns the current replay context for the active async scope from OTel context.
19
+ * Stored in OTel context by SoftprobeContext.withData/run.
20
+ */
21
+ function getContext() {
22
+ return {
23
+ mode: context_1.SoftprobeContext.getMode(),
24
+ storage: context_1.SoftprobeContext.getCassette(),
25
+ traceId: context_1.SoftprobeContext.getTraceId(),
26
+ strictReplay: context_1.SoftprobeContext.getStrictReplay(),
27
+ strictComparison: context_1.SoftprobeContext.getStrictComparison(),
28
+ matcher: context_1.SoftprobeContext.getMatcher(),
29
+ inboundRecord: context_1.SoftprobeContext.getInboundRecord(),
30
+ };
31
+ }
32
+ /**
33
+ * Returns the active matcher for the current replay context, if any.
34
+ * In REPLAY mode, returns global matcher when context has no matcher; includes baggage fallback.
35
+ */
36
+ function getActiveMatcher() {
37
+ return context_1.SoftprobeContext.getMatcher();
38
+ }
39
+ /**
40
+ * Returns the recorded inbound response for the current trace, if any.
41
+ * Set by runWithContext when loading a cassette that contains a record with type "inbound".
42
+ */
43
+ function getRecordedInboundResponse() {
44
+ return context_1.SoftprobeContext.getInboundRecord();
45
+ }
46
+ /**
47
+ * Task 15.2.1: Compares the actual response (status and body) to the recorded inbound for the current trace.
48
+ * Task 15.2.2: When SOFTPROBE_STRICT_COMPARISON is set, also compares headers.
49
+ * @throws When no recorded inbound exists, or when status, body, or (if strict) headers do not match.
50
+ */
51
+ function compareInbound(actual) {
52
+ (0, compare_1.compareInboundWithRecord)(actual, getRecordedInboundResponse());
53
+ }
54
+ /**
55
+ * Returns recorded cassette records for the given traceId from the active context only.
56
+ * Records are context-scoped (matcher is created by SoftprobeContext.run(REPLAY)). Task 15.3.1.
57
+ */
58
+ function getRecordsForTrace(traceId) {
59
+ return (0, store_accessor_1.getRecordsForTrace)(traceId);
60
+ }
61
+ /**
62
+ * Sets the global replay matcher. For test/cleanup only; the framework creates matchers in SoftprobeContext.run(REPLAY).
63
+ * getMatcher() returns only the context matcher, not this global.
64
+ */
65
+ function setGlobalReplayMatcher(matcher) {
66
+ context_1.SoftprobeContext.setGlobalReplayMatcher(matcher);
67
+ }
68
+ /**
69
+ * Primes the active matcher with records for the given traceId.
70
+ * Called by server middleware (Express/Fastify) after run(); the matcher was already seeded in run(), so this is a no-op when records are present.
71
+ */
72
+ function activateReplayForContext(traceId) {
73
+ const matcher = getActiveMatcher();
74
+ if (matcher && '_setRecords' in matcher) {
75
+ const records = getRecordsForTrace(traceId);
76
+ if (records.length > 0) {
77
+ matcher._setRecords(records);
78
+ }
79
+ }
80
+ }
81
+ /**
82
+ * No-op. Replay is context-scoped; only SoftprobeContext.run(REPLAY) creates and seeds the matcher.
83
+ * Use cassetteDirectory + traceId and middleware so each request gets its own context and matcher.
84
+ */
85
+ async function ensureReplayLoadedForRequest() { }
86
+ /** Runs callback inside a scoped Softprobe context using SoftprobeRunOptions. */
87
+ function run(options, fn) {
88
+ return context_1.SoftprobeContext.run(options, fn);
89
+ }
90
+ var baggage_2 = require("./api/baggage");
91
+ Object.defineProperty(exports, "getContextWithReplayBaggage", { enumerable: true, get: function () { return baggage_2.getContextWithReplayBaggage; } });
92
+ exports.softprobe = {
93
+ getContext,
94
+ getActiveMatcher,
95
+ getRecordedInboundResponse,
96
+ compareInbound,
97
+ getRecordsForTrace,
98
+ setGlobalReplayMatcher,
99
+ activateReplayForContext,
100
+ ensureReplayLoadedForRequest,
101
+ run,
102
+ getContextWithReplayBaggage: baggage_1.getContextWithReplayBaggage,
103
+ };
104
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":";;;AAYA,gCAYC;AAMD,4CAEC;AAMD,gEAEC;AAOD,wCAEC;AAMD,gDAEC;AAMD,wDAEC;AAMD,4DAQC;AAMD,oEAAsE;AAGtE,kBAEC;AAvFD,2CAA4D;AAC5D,2CAAmF;AACnF,kEAAkG;AAClG,uCAA6C;AAE7C;;;GAGG;AACH,SAAgB,UAAU;IAGxB,OAAO;QACL,IAAI,EAAE,0BAAgB,CAAC,OAAO,EAAE;QAChC,OAAO,EAAE,0BAAgB,CAAC,WAAW,EAAE;QACvC,OAAO,EAAE,0BAAgB,CAAC,UAAU,EAAE;QACtC,YAAY,EAAE,0BAAgB,CAAC,eAAe,EAAE;QAChD,gBAAgB,EAAE,0BAAgB,CAAC,mBAAmB,EAAE;QACxD,OAAO,EAAE,0BAAgB,CAAC,UAAU,EAAE;QACtC,aAAa,EAAE,0BAAgB,CAAC,gBAAgB,EAAE;KACnD,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,SAAgB,gBAAgB;IAC9B,OAAO,0BAAgB,CAAC,UAAU,EAAE,CAAC;AACvC,CAAC;AAED;;;GAGG;AACH,SAAgB,0BAA0B;IACxC,OAAO,0BAAgB,CAAC,gBAAgB,EAAE,CAAC;AAC7C,CAAC;AAED;;;;GAIG;AACH,SAAgB,cAAc,CAAC,MAA2B;IACxD,IAAA,kCAAwB,EAAC,MAAM,EAAE,0BAA0B,EAAE,CAAC,CAAC;AACjE,CAAC;AAED;;;GAGG;AACH,SAAgB,kBAAkB,CAAC,OAAe;IAChD,OAAO,IAAA,mCAA2B,EAAC,OAAO,CAAC,CAAC;AAC9C,CAAC;AAED;;;GAGG;AACH,SAAgB,sBAAsB,CAAC,OAAqC;IAC1E,0BAAgB,CAAC,sBAAsB,CAAC,OAAO,CAAC,CAAC;AACnD,CAAC;AAED;;;GAGG;AACH,SAAgB,wBAAwB,CAAC,OAAe;IACtD,MAAM,OAAO,GAAG,gBAAgB,EAAE,CAAC;IACnC,IAAI,OAAO,IAAI,aAAa,IAAI,OAAO,EAAE,CAAC;QACxC,MAAM,OAAO,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAC;QAC5C,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtB,OAA4B,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;QACrD,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;GAGG;AACI,KAAK,UAAU,4BAA4B,KAAmB,CAAC;AAEtE,iFAAiF;AACjF,SAAgB,GAAG,CAAI,OAA4B,EAAE,EAAwB;IAC3E,OAAO,0BAAgB,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AAC3C,CAAC;AAED,yCAA4D;AAAnD,sHAAA,2BAA2B,OAAA;AAEvB,QAAA,SAAS,GAAG;IACvB,UAAU;IACV,gBAAgB;IAChB,0BAA0B;IAC1B,cAAc;IACd,kBAAkB;IAClB,sBAAsB;IACtB,wBAAwB;IACxB,4BAA4B;IAC5B,GAAG;IACH,2BAA2B,EAA3B,qCAA2B;CAC5B,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Legacy compatibility re-export for HTTP span helpers.
3
+ */
4
+ export { HttpSpan, fromSpan, tagRequest } from '../core/bindings/http-span';
5
+ export type { HttpSpanData } from '../core/bindings/http-span';
6
+ //# sourceMappingURL=http-span.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-span.d.ts","sourceRoot":"","sources":["../../src/bindings/http-span.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAC5E,YAAY,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC"}
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.tagRequest = exports.fromSpan = exports.HttpSpan = void 0;
4
+ /**
5
+ * Legacy compatibility re-export for HTTP span helpers.
6
+ */
7
+ var http_span_1 = require("../core/bindings/http-span");
8
+ Object.defineProperty(exports, "HttpSpan", { enumerable: true, get: function () { return http_span_1.HttpSpan; } });
9
+ Object.defineProperty(exports, "fromSpan", { enumerable: true, get: function () { return http_span_1.fromSpan; } });
10
+ Object.defineProperty(exports, "tagRequest", { enumerable: true, get: function () { return http_span_1.tagRequest; } });
11
+ //# sourceMappingURL=http-span.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-span.js","sourceRoot":"","sources":["../../src/bindings/http-span.ts"],"names":[],"mappings":";;;AAAA;;GAEG;AACH,wDAA4E;AAAnE,qGAAA,QAAQ,OAAA;AAAE,qGAAA,QAAQ,OAAA;AAAE,uGAAA,UAAU,OAAA"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Legacy compatibility re-export for Postgres span helpers.
3
+ */
4
+ export { PostgresSpan, fromSpan, tagQuery } from '../core/bindings/postgres-span';
5
+ export type { PostgresSpanData } from '../core/bindings/postgres-span';
6
+ //# sourceMappingURL=postgres-span.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres-span.d.ts","sourceRoot":"","sources":["../../src/bindings/postgres-span.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAClF,YAAY,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC"}
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.tagQuery = exports.fromSpan = exports.PostgresSpan = void 0;
4
+ /**
5
+ * Legacy compatibility re-export for Postgres span helpers.
6
+ */
7
+ var postgres_span_1 = require("../core/bindings/postgres-span");
8
+ Object.defineProperty(exports, "PostgresSpan", { enumerable: true, get: function () { return postgres_span_1.PostgresSpan; } });
9
+ Object.defineProperty(exports, "fromSpan", { enumerable: true, get: function () { return postgres_span_1.fromSpan; } });
10
+ Object.defineProperty(exports, "tagQuery", { enumerable: true, get: function () { return postgres_span_1.tagQuery; } });
11
+ //# sourceMappingURL=postgres-span.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres-span.js","sourceRoot":"","sources":["../../src/bindings/postgres-span.ts"],"names":[],"mappings":";;;AAAA;;GAEG;AACH,gEAAkF;AAAzE,6GAAA,YAAY,OAAA;AAAE,yGAAA,QAAQ,OAAA;AAAE,yGAAA,QAAQ,OAAA"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Legacy compatibility re-export for Redis span helpers.
3
+ */
4
+ export { RedisSpan, fromSpan, tagCommand } from '../core/bindings/redis-span';
5
+ export type { RedisSpanData } from '../core/bindings/redis-span';
6
+ //# sourceMappingURL=redis-span.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis-span.d.ts","sourceRoot":"","sources":["../../src/bindings/redis-span.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAC9E,YAAY,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC"}