effect-start 0.23.0 → 0.25.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 (356) hide show
  1. package/dist/ChildProcess.js +32 -20
  2. package/dist/Commander.js +377 -293
  3. package/dist/ContentNegotiation.js +424 -318
  4. package/dist/Cookies.js +340 -271
  5. package/dist/Development.js +85 -49
  6. package/dist/Effectify.js +22 -14
  7. package/dist/Entity.js +260 -195
  8. package/dist/Fetch.js +192 -0
  9. package/dist/FilePathPattern.js +88 -77
  10. package/dist/FileRouter.js +192 -136
  11. package/dist/FileRouterCodegen.js +262 -191
  12. package/dist/FileSystem.js +126 -64
  13. package/dist/Http.js +96 -77
  14. package/dist/PathPattern.js +311 -273
  15. package/dist/PlatformError.js +36 -21
  16. package/dist/PlatformRuntime.js +65 -40
  17. package/dist/Route.js +122 -79
  18. package/dist/RouteBody.js +83 -58
  19. package/dist/RouteError.js +46 -25
  20. package/dist/RouteHook.js +58 -34
  21. package/dist/RouteHttp.js +346 -237
  22. package/dist/RouteHttpTracer.js +86 -58
  23. package/dist/RouteMount.js +81 -58
  24. package/dist/RouteSchema.js +253 -170
  25. package/dist/RouteSse.js +87 -72
  26. package/dist/RouteTree.js +99 -73
  27. package/dist/RouteTrie.js +160 -133
  28. package/dist/SchemaExtra.js +87 -62
  29. package/dist/Socket.js +32 -21
  30. package/dist/SqlIntrospect.js +317 -268
  31. package/dist/Start.js +55 -25
  32. package/dist/StartApp.js +3 -21
  33. package/dist/StreamExtra.js +109 -74
  34. package/dist/System.js +37 -21
  35. package/dist/TuplePathPattern.js +64 -58
  36. package/dist/Unique.js +159 -120
  37. package/dist/Values.js +48 -32
  38. package/dist/bun/BunBundle.js +158 -109
  39. package/dist/bun/BunChildProcessSpawner.js +121 -82
  40. package/dist/bun/BunImportTrackerPlugin.js +85 -63
  41. package/dist/bun/BunRoute.js +135 -99
  42. package/dist/bun/BunRuntime.js +39 -29
  43. package/dist/bun/BunServer.js +268 -140
  44. package/dist/bun/BunVirtualFilesPlugin.js +47 -33
  45. package/dist/bun/_BunEnhancedResolve.js +107 -82
  46. package/dist/bun/index.js +5 -5
  47. package/dist/bundler/Bundle.js +82 -38
  48. package/dist/bundler/BundleFiles.js +140 -82
  49. package/dist/bundler/BundleRoute.js +49 -38
  50. package/dist/client/Overlay.js +29 -28
  51. package/dist/client/ScrollState.js +94 -82
  52. package/dist/client/index.js +79 -61
  53. package/dist/console/Console.js +40 -24
  54. package/dist/console/ConsoleErrors.js +189 -178
  55. package/dist/console/ConsoleLogger.js +52 -43
  56. package/dist/console/ConsoleMetrics.js +69 -58
  57. package/dist/console/ConsoleProcess.js +57 -47
  58. package/dist/console/ConsoleStore.js +56 -45
  59. package/dist/console/ConsoleTracer.js +101 -88
  60. package/dist/console/Simulation.js +714 -563
  61. package/dist/console/index.js +3 -3
  62. package/dist/console/routes/tree.js +29 -28
  63. package/dist/datastar/actions/fetch.js +514 -381
  64. package/dist/datastar/actions/peek.js +12 -12
  65. package/dist/datastar/actions/setAll.js +18 -11
  66. package/dist/datastar/actions/toggleAll.js +18 -11
  67. package/dist/datastar/attributes/attr.js +48 -47
  68. package/dist/datastar/attributes/bind.js +186 -167
  69. package/dist/datastar/attributes/class.js +51 -44
  70. package/dist/datastar/attributes/computed.js +23 -24
  71. package/dist/datastar/attributes/effect.js +9 -8
  72. package/dist/datastar/attributes/indicator.js +32 -29
  73. package/dist/datastar/attributes/init.js +26 -25
  74. package/dist/datastar/attributes/jsonSignals.js +32 -29
  75. package/dist/datastar/attributes/on.js +76 -73
  76. package/dist/datastar/attributes/onIntersect.js +51 -51
  77. package/dist/datastar/attributes/onInterval.js +30 -29
  78. package/dist/datastar/attributes/onSignalPatch.js +49 -40
  79. package/dist/datastar/attributes/ref.js +10 -9
  80. package/dist/datastar/attributes/show.js +31 -30
  81. package/dist/datastar/attributes/signals.js +17 -16
  82. package/dist/datastar/attributes/style.js +56 -49
  83. package/dist/datastar/attributes/text.js +28 -25
  84. package/dist/datastar/engine.js +1079 -933
  85. package/dist/datastar/index.js +25 -24
  86. package/dist/datastar/utils.js +203 -155
  87. package/dist/datastar/watchers/patchElements.js +459 -372
  88. package/dist/datastar/watchers/patchSignals.js +13 -13
  89. package/dist/experimental/EncryptedCookies.js +305 -189
  90. package/dist/experimental/index.js +1 -1
  91. package/dist/hyper/Hyper.js +22 -17
  92. package/dist/hyper/HyperHtml.js +138 -123
  93. package/dist/hyper/HyperNode.js +11 -9
  94. package/dist/hyper/HyperRoute.js +41 -28
  95. package/dist/hyper/html.js +27 -27
  96. package/dist/hyper/index.js +5 -5
  97. package/dist/hyper/jsx-runtime.js +11 -5
  98. package/dist/index.js +8 -8
  99. package/dist/node/NodeFileSystem.js +606 -341
  100. package/dist/node/NodeUtils.js +21 -18
  101. package/dist/sql/Sql.js +8 -0
  102. package/dist/sql/bun/index.js +134 -67
  103. package/dist/sql/index.js +1 -0
  104. package/dist/sql/libsql/index.js +156 -0
  105. package/dist/sql/mssql/docker.js +103 -60
  106. package/dist/sql/mssql/index.js +182 -101
  107. package/dist/testing/TestLogger.js +39 -29
  108. package/dist/testing/index.js +2 -2
  109. package/dist/testing/utils.js +45 -23
  110. package/dist/x/cloudflare/CloudflareTunnel.js +61 -28
  111. package/dist/x/cloudflare/index.js +1 -1
  112. package/dist/x/tailscale/TailscaleTunnel.js +86 -60
  113. package/dist/x/tailscale/index.js +1 -1
  114. package/dist/x/tailwind/TailwindPlugin.js +280 -205
  115. package/dist/x/tailwind/compile.js +185 -129
  116. package/dist/x/tailwind/plugin.js +13 -11
  117. package/package.json +1 -1
  118. package/src/Development.ts +3 -1
  119. package/src/Entity.ts +17 -0
  120. package/src/Fetch.ts +271 -0
  121. package/src/SqlIntrospect.ts +64 -70
  122. package/src/Start.ts +24 -22
  123. package/src/StartApp.ts +11 -0
  124. package/src/bun/BunServer.ts +89 -16
  125. package/src/hyper/HyperHtml.ts +0 -1
  126. package/src/sql/bun/index.ts +1 -1
  127. package/src/sql/index.ts +1 -0
  128. package/src/sql/libsql/index.ts +173 -0
  129. package/src/sql/libsql/libsql.d.ts +39 -0
  130. package/src/sql/mssql/index.ts +1 -1
  131. package/src/x/tailscale/TailscaleTunnel.ts +7 -5
  132. package/dist/BlobStore.d.ts +0 -80
  133. package/dist/BlobStore.js +0 -19
  134. package/dist/ChildProcess.d.ts +0 -60
  135. package/dist/Commander.d.ts +0 -100
  136. package/dist/ContentNegotiation.d.ts +0 -12
  137. package/dist/Cookies.d.ts +0 -47
  138. package/dist/Development.d.ts +0 -39
  139. package/dist/Effectify.d.ts +0 -209
  140. package/dist/Entity.d.ts +0 -47
  141. package/dist/FilePathPattern.d.ts +0 -29
  142. package/dist/FileRouter.d.ts +0 -56
  143. package/dist/FileRouterCodegen.d.ts +0 -18
  144. package/dist/FileRouterPattern.d.ts +0 -9
  145. package/dist/FileRouterPattern.js +0 -35
  146. package/dist/FileSystem.d.ts +0 -158
  147. package/dist/Http.d.ts +0 -37
  148. package/dist/HttpAppExtra.d.ts +0 -7
  149. package/dist/HttpAppExtra.js +0 -320
  150. package/dist/HttpUtils.d.ts +0 -3
  151. package/dist/HttpUtils.js +0 -11
  152. package/dist/PathPattern.d.ts +0 -134
  153. package/dist/PlatformError.d.ts +0 -38
  154. package/dist/PlatformRuntime.d.ts +0 -27
  155. package/dist/Route.d.ts +0 -97
  156. package/dist/RouteBody.d.ts +0 -47
  157. package/dist/RouteError.d.ts +0 -98
  158. package/dist/RouteHook.d.ts +0 -12
  159. package/dist/RouteHttp.d.ts +0 -21
  160. package/dist/RouteHttpTracer.d.ts +0 -10
  161. package/dist/RouteMount.d.ts +0 -86
  162. package/dist/RouteSchema.d.ts +0 -86
  163. package/dist/RouteSse.d.ts +0 -21
  164. package/dist/RouteTree.d.ts +0 -57
  165. package/dist/RouteTrie.d.ts +0 -20
  166. package/dist/RouterPattern.d.ts +0 -118
  167. package/dist/RouterPattern.js +0 -269
  168. package/dist/SchemaExtra.d.ts +0 -7
  169. package/dist/Socket.d.ts +0 -27
  170. package/dist/Sql.d.ts +0 -34
  171. package/dist/Sql.js +0 -5
  172. package/dist/SqlIntrospect.d.ts +0 -91
  173. package/dist/Start.d.ts +0 -44
  174. package/dist/StartApp.d.ts +0 -19
  175. package/dist/StreamExtra.d.ts +0 -28
  176. package/dist/System.d.ts +0 -7
  177. package/dist/TuplePathPattern.d.ts +0 -9
  178. package/dist/Unique.d.ts +0 -50
  179. package/dist/Values.d.ts +0 -27
  180. package/dist/bun/BunBlobStoreDisk.d.ts +0 -6
  181. package/dist/bun/BunBlobStoreDisk.js +0 -116
  182. package/dist/bun/BunBlobStoreS3.d.ts +0 -11
  183. package/dist/bun/BunBlobStoreS3.js +0 -89
  184. package/dist/bun/BunBlobWatcherDisk.d.ts +0 -6
  185. package/dist/bun/BunBlobWatcherDisk.js +0 -60
  186. package/dist/bun/BunBlobWatcherQueue.d.ts +0 -6
  187. package/dist/bun/BunBlobWatcherQueue.js +0 -17
  188. package/dist/bun/BunBundle.d.ts +0 -11
  189. package/dist/bun/BunChildProcessSpawner.d.ts +0 -3
  190. package/dist/bun/BunHttpServer.d.ts +0 -44
  191. package/dist/bun/BunHttpServer.js +0 -186
  192. package/dist/bun/BunHttpServer_web.d.ts +0 -60
  193. package/dist/bun/BunHttpServer_web.js +0 -252
  194. package/dist/bun/BunImportTrackerPlugin.d.ts +0 -13
  195. package/dist/bun/BunPlatformHttpServer.d.ts +0 -10
  196. package/dist/bun/BunPlatformHttpServer.js +0 -53
  197. package/dist/bun/BunRoute.d.ts +0 -48
  198. package/dist/bun/BunRuntime.d.ts +0 -2
  199. package/dist/bun/BunServer.d.ts +0 -40
  200. package/dist/bun/BunServerRequest.d.ts +0 -60
  201. package/dist/bun/BunServerRequest.js +0 -252
  202. package/dist/bun/BunSql.d.ts +0 -4
  203. package/dist/bun/BunSql.js +0 -81
  204. package/dist/bun/BunVirtualFilesPlugin.d.ts +0 -4
  205. package/dist/bun/_BunEnhancedResolve.d.ts +0 -45
  206. package/dist/bun/index.d.ts +0 -5
  207. package/dist/bundler/Bundle.d.ts +0 -61
  208. package/dist/bundler/BundleFiles.d.ts +0 -13
  209. package/dist/bundler/BundleHttp.d.ts +0 -45
  210. package/dist/bundler/BundleHttp.js +0 -176
  211. package/dist/bundler/BundleRoute.d.ts +0 -27
  212. package/dist/client/Overlay.d.ts +0 -2
  213. package/dist/client/ScrollState.d.ts +0 -6
  214. package/dist/client/index.d.ts +0 -6
  215. package/dist/console/Console.d.ts +0 -6
  216. package/dist/console/ConsoleErrors.d.ts +0 -3
  217. package/dist/console/ConsoleLogger.d.ts +0 -3
  218. package/dist/console/ConsoleMetrics.d.ts +0 -3
  219. package/dist/console/ConsoleProcess.d.ts +0 -3
  220. package/dist/console/ConsoleStore.d.ts +0 -144
  221. package/dist/console/ConsoleTracer.d.ts +0 -3
  222. package/dist/console/Simulation.d.ts +0 -2
  223. package/dist/console/index.d.ts +0 -3
  224. package/dist/console/routes/errors/route.d.ts +0 -10
  225. package/dist/console/routes/errors/route.js +0 -47
  226. package/dist/console/routes/fiberDetail.d.ts +0 -16
  227. package/dist/console/routes/fiberDetail.js +0 -38
  228. package/dist/console/routes/fibers/route.d.ts +0 -10
  229. package/dist/console/routes/fibers/route.js +0 -19
  230. package/dist/console/routes/git/route.d.ts +0 -11
  231. package/dist/console/routes/git/route.js +0 -33
  232. package/dist/console/routes/layout.d.ts +0 -9
  233. package/dist/console/routes/layout.js +0 -3
  234. package/dist/console/routes/logs/route.d.ts +0 -10
  235. package/dist/console/routes/logs/route.js +0 -32
  236. package/dist/console/routes/metrics/route.d.ts +0 -10
  237. package/dist/console/routes/metrics/route.js +0 -17
  238. package/dist/console/routes/route.d.ts +0 -6
  239. package/dist/console/routes/route.js +0 -5
  240. package/dist/console/routes/routes/route.d.ts +0 -6
  241. package/dist/console/routes/routes/route.js +0 -20
  242. package/dist/console/routes/services/route.d.ts +0 -6
  243. package/dist/console/routes/services/route.js +0 -12
  244. package/dist/console/routes/system/route.d.ts +0 -10
  245. package/dist/console/routes/system/route.js +0 -18
  246. package/dist/console/routes/traceDetail.d.ts +0 -16
  247. package/dist/console/routes/traceDetail.js +0 -14
  248. package/dist/console/routes/traces/route.d.ts +0 -10
  249. package/dist/console/routes/traces/route.js +0 -39
  250. package/dist/console/routes/tree.d.ts +0 -153
  251. package/dist/console/ui/Errors.d.ts +0 -4
  252. package/dist/console/ui/Errors.js +0 -15
  253. package/dist/console/ui/Fibers.d.ts +0 -24
  254. package/dist/console/ui/Fibers.js +0 -121
  255. package/dist/console/ui/Git.d.ts +0 -20
  256. package/dist/console/ui/Git.js +0 -95
  257. package/dist/console/ui/Logs.d.ts +0 -4
  258. package/dist/console/ui/Logs.js +0 -25
  259. package/dist/console/ui/Metrics.d.ts +0 -4
  260. package/dist/console/ui/Metrics.js +0 -26
  261. package/dist/console/ui/Routes.d.ts +0 -8
  262. package/dist/console/ui/Routes.js +0 -70
  263. package/dist/console/ui/Services.d.ts +0 -10
  264. package/dist/console/ui/Services.js +0 -246
  265. package/dist/console/ui/Shell.d.ts +0 -10
  266. package/dist/console/ui/Shell.js +0 -7
  267. package/dist/console/ui/System.d.ts +0 -4
  268. package/dist/console/ui/System.js +0 -35
  269. package/dist/console/ui/Traces.d.ts +0 -12
  270. package/dist/console/ui/Traces.js +0 -179
  271. package/dist/datastar/actions/fetch.d.ts +0 -30
  272. package/dist/datastar/actions/peek.d.ts +0 -1
  273. package/dist/datastar/actions/setAll.d.ts +0 -1
  274. package/dist/datastar/actions/toggleAll.d.ts +0 -1
  275. package/dist/datastar/attributes/attr.d.ts +0 -1
  276. package/dist/datastar/attributes/bind.d.ts +0 -1
  277. package/dist/datastar/attributes/class.d.ts +0 -1
  278. package/dist/datastar/attributes/computed.d.ts +0 -1
  279. package/dist/datastar/attributes/effect.d.ts +0 -1
  280. package/dist/datastar/attributes/indicator.d.ts +0 -1
  281. package/dist/datastar/attributes/init.d.ts +0 -1
  282. package/dist/datastar/attributes/jsonSignals.d.ts +0 -1
  283. package/dist/datastar/attributes/on.d.ts +0 -1
  284. package/dist/datastar/attributes/onIntersect.d.ts +0 -1
  285. package/dist/datastar/attributes/onInterval.d.ts +0 -1
  286. package/dist/datastar/attributes/onSignalPatch.d.ts +0 -1
  287. package/dist/datastar/attributes/ref.d.ts +0 -1
  288. package/dist/datastar/attributes/show.d.ts +0 -1
  289. package/dist/datastar/attributes/signals.d.ts +0 -1
  290. package/dist/datastar/attributes/style.d.ts +0 -1
  291. package/dist/datastar/attributes/text.d.ts +0 -1
  292. package/dist/datastar/engine.d.ts +0 -162
  293. package/dist/datastar/happydom.d.ts +0 -1
  294. package/dist/datastar/happydom.js +0 -8
  295. package/dist/datastar/index.d.ts +0 -24
  296. package/dist/datastar/load.d.ts +0 -24
  297. package/dist/datastar/load.js +0 -24
  298. package/dist/datastar/utils.d.ts +0 -51
  299. package/dist/datastar/watchers/patchElements.d.ts +0 -1
  300. package/dist/datastar/watchers/patchSignals.d.ts +0 -1
  301. package/dist/experimental/EncryptedCookies.d.ts +0 -48
  302. package/dist/experimental/SseHttpResponse.d.ts +0 -7
  303. package/dist/experimental/SseHttpResponse.js +0 -28
  304. package/dist/experimental/index.d.ts +0 -1
  305. package/dist/hyper/Hyper.d.ts +0 -25
  306. package/dist/hyper/HyperHtml.d.ts +0 -23
  307. package/dist/hyper/HyperHtml.test.d.ts +0 -1
  308. package/dist/hyper/HyperHtml.test.js +0 -197
  309. package/dist/hyper/HyperNode.d.ts +0 -14
  310. package/dist/hyper/HyperRoute.d.ts +0 -8
  311. package/dist/hyper/HyperRoute.test.d.ts +0 -1
  312. package/dist/hyper/HyperRoute.test.js +0 -83
  313. package/dist/hyper/html.d.ts +0 -11
  314. package/dist/hyper/index.d.ts +0 -6
  315. package/dist/hyper/jsx-runtime.d.ts +0 -7
  316. package/dist/index.d.ts +0 -8
  317. package/dist/inference_check.d.ts +0 -1
  318. package/dist/inference_check.js +0 -15
  319. package/dist/lint/plugin.d.ts +0 -86
  320. package/dist/lint/plugin.js +0 -341
  321. package/dist/middlewares/BasicAuthMiddleware.d.ts +0 -8
  322. package/dist/middlewares/BasicAuthMiddleware.js +0 -22
  323. package/dist/middlewares/index.d.ts +0 -1
  324. package/dist/middlewares/index.js +0 -1
  325. package/dist/node/Effectify.d.ts +0 -209
  326. package/dist/node/Effectify.js +0 -19
  327. package/dist/node/FileSystem.d.ts +0 -7
  328. package/dist/node/FileSystem.js +0 -420
  329. package/dist/node/NodeFileSystem.d.ts +0 -7
  330. package/dist/node/NodeUtils.d.ts +0 -2
  331. package/dist/node/PlatformError.d.ts +0 -46
  332. package/dist/node/PlatformError.js +0 -43
  333. package/dist/node/Utils.d.ts +0 -1
  334. package/dist/node/Utils.js +0 -19
  335. package/dist/repro_fail.d.ts +0 -1
  336. package/dist/repro_fail.js +0 -14
  337. package/dist/sql/bun/index.d.ts +0 -3
  338. package/dist/sql/mssql/docker.d.ts +0 -2
  339. package/dist/sql/mssql/index.d.ts +0 -21
  340. package/dist/testing/TestHttpClient.d.ts +0 -13
  341. package/dist/testing/TestHttpClient.js +0 -68
  342. package/dist/testing/TestLogger.d.ts +0 -13
  343. package/dist/testing/index.d.ts +0 -2
  344. package/dist/testing/utils.d.ts +0 -9
  345. package/dist/x/cloudflare/CloudflareTunnel.d.ts +0 -10
  346. package/dist/x/cloudflare/index.d.ts +0 -1
  347. package/dist/x/datastar/Datastar.d.ts +0 -6
  348. package/dist/x/datastar/Datastar.js +0 -47
  349. package/dist/x/datastar/index.d.ts +0 -1
  350. package/dist/x/datastar/index.js +0 -1
  351. package/dist/x/tailscale/TailscaleTunnel.d.ts +0 -15
  352. package/dist/x/tailscale/index.d.ts +0 -1
  353. package/dist/x/tailwind/TailwindPlugin.d.ts +0 -23
  354. package/dist/x/tailwind/compile.d.ts +0 -19
  355. package/dist/x/tailwind/plugin.d.ts +0 -2
  356. /package/src/{Sql.ts → sql/Sql.ts} +0 -0
@@ -1,633 +1,784 @@
1
- import * as Data from "effect/Data";
2
- import * as Duration from "effect/Duration";
3
- import * as Effect from "effect/Effect";
4
- import * as Layer from "effect/Layer";
5
- import * as Metric from "effect/Metric";
6
- import * as MetricBoundaries from "effect/MetricBoundaries";
7
- import * as Random from "effect/Random";
8
- import * as Schedule from "effect/Schedule";
1
+ import * as Data from "effect/Data"
2
+ import * as Duration from "effect/Duration"
3
+ import * as Effect from "effect/Effect"
4
+ import * as Layer from "effect/Layer"
5
+ import * as Metric from "effect/Metric"
6
+ import * as MetricBoundaries from "effect/MetricBoundaries"
7
+ import * as Random from "effect/Random"
8
+ import * as Schedule from "effect/Schedule"
9
+
9
10
  // ---------------------------------------------------------------------------
10
11
  // Helpers
11
12
  // ---------------------------------------------------------------------------
12
- const pick = (arr) => Random.nextIntBetween(0, arr.length).pipe(Effect.map((i) => arr[i]));
13
- const randomMs = (min, max) => Random.nextIntBetween(min, max).pipe(Effect.map((ms) => Duration.millis(ms)));
14
- const maybe = (pct, op) => Effect.gen(function* () {
15
- if ((yield* Random.nextIntBetween(0, 100)) < pct)
16
- yield* op;
17
- });
13
+
14
+ const pick = (arr) =>
15
+ Random.nextIntBetween(0, arr.length).pipe(Effect.map((i) => arr[i]))
16
+
17
+ const randomMs = (min, max) =>
18
+ Random.nextIntBetween(min, max).pipe(Effect.map((ms) => Duration.millis(ms)))
19
+
20
+ const maybe = (pct, op) =>
21
+ Effect.gen(function* () {
22
+ if ((yield* Random.nextIntBetween(0, 100)) < pct) yield* op
23
+ })
24
+
18
25
  // ---------------------------------------------------------------------------
19
26
  // Metrics
20
27
  // ---------------------------------------------------------------------------
21
- const httpRequestsTotal = Metric.counter("http.requests.total");
22
- const httpRequestDuration = Metric.histogram("http.request.duration_ms", MetricBoundaries.linear({ start: 0, width: 50, count: 20 }));
23
- const activeConnections = Metric.gauge("http.active_connections");
24
- const dbQueryDuration = Metric.histogram("db.query.duration_ms", MetricBoundaries.linear({ start: 0, width: 10, count: 25 }));
25
- const dbPoolSize = Metric.gauge("db.pool.active");
26
- const cacheHits = Metric.counter("cache.hits");
27
- const cacheMisses = Metric.counter("cache.misses");
28
- const queueDepth = Metric.gauge("queue.depth");
29
- const eventCount = Metric.counter("events.processed");
30
- const retryCount = Metric.counter("retry.total");
31
- const circuitBreakerTrips = Metric.counter("circuit_breaker.trips");
32
- const rateLimitRejections = Metric.counter("rate_limit.rejections");
33
- const serializationDuration = Metric.histogram("serialization.duration_ms", MetricBoundaries.linear({ start: 0, width: 5, count: 15 }));
28
+
29
+ const httpRequestsTotal = Metric.counter("http.requests.total")
30
+ const httpRequestDuration = Metric.histogram(
31
+ "http.request.duration_ms",
32
+ MetricBoundaries.linear({ start: 0, width: 50, count: 20 }),
33
+ )
34
+ const activeConnections = Metric.gauge("http.active_connections")
35
+ const dbQueryDuration = Metric.histogram(
36
+ "db.query.duration_ms",
37
+ MetricBoundaries.linear({ start: 0, width: 10, count: 25 }),
38
+ )
39
+ const dbPoolSize = Metric.gauge("db.pool.active")
40
+ const cacheHits = Metric.counter("cache.hits")
41
+ const cacheMisses = Metric.counter("cache.misses")
42
+ const queueDepth = Metric.gauge("queue.depth")
43
+ const eventCount = Metric.counter("events.processed")
44
+ const retryCount = Metric.counter("retry.total")
45
+ const circuitBreakerTrips = Metric.counter("circuit_breaker.trips")
46
+ const rateLimitRejections = Metric.counter("rate_limit.rejections")
47
+ const serializationDuration = Metric.histogram(
48
+ "serialization.duration_ms",
49
+ MetricBoundaries.linear({ start: 0, width: 5, count: 15 }),
50
+ )
51
+
34
52
  // ---------------------------------------------------------------------------
35
53
  // Simulated operations
36
54
  // ---------------------------------------------------------------------------
37
- const routes = [
38
- { method: "GET", path: "/api/users", handler: "UserController.list" },
39
- { method: "GET", path: "/api/users/:id", handler: "UserController.get" },
40
- { method: "POST", path: "/api/users", handler: "UserController.create" },
41
- { method: "PUT", path: "/api/users/:id", handler: "UserController.update" },
42
- { method: "DELETE", path: "/api/users/:id", handler: "UserController.delete" },
43
- { method: "GET", path: "/api/products", handler: "ProductController.list" },
44
- { method: "GET", path: "/api/products/:id", handler: "ProductController.get" },
45
- { method: "POST", path: "/api/orders", handler: "OrderController.create" },
46
- { method: "GET", path: "/api/orders/:id", handler: "OrderController.get" },
47
- { method: "PATCH", path: "/api/orders/:id/status", handler: "OrderController.updateStatus" },
48
- { method: "POST", path: "/api/auth/login", handler: "AuthController.login" },
49
- { method: "POST", path: "/api/auth/refresh", handler: "AuthController.refresh" },
50
- { method: "POST", path: "/api/auth/logout", handler: "AuthController.logout" },
51
- { method: "GET", path: "/api/search", handler: "SearchController.query" },
52
- { method: "GET", path: "/api/analytics/dashboard", handler: "AnalyticsController.dashboard" },
53
- { method: "POST", path: "/api/notifications/send", handler: "NotificationController.send" },
54
- { method: "POST", path: "/api/uploads/image", handler: "UploadController.image" },
55
- { method: "GET", path: "/api/health", handler: "HealthController.check" },
56
- { method: "GET", path: "/api/config", handler: "ConfigController.get" },
57
- { method: "POST", path: "/api/webhooks/stripe", handler: "WebhookController.stripe" },
58
- ];
55
+
56
+ const routes = /** @type {const} */ [
57
+ { method: "GET", path: "/api/users", handler: "UserController.list" },
58
+ { method: "GET", path: "/api/users/:id", handler: "UserController.get" },
59
+ { method: "POST", path: "/api/users", handler: "UserController.create" },
60
+ { method: "PUT", path: "/api/users/:id", handler: "UserController.update" },
61
+ { method: "DELETE", path: "/api/users/:id", handler: "UserController.delete" },
62
+ { method: "GET", path: "/api/products", handler: "ProductController.list" },
63
+ { method: "GET", path: "/api/products/:id", handler: "ProductController.get" },
64
+ { method: "POST", path: "/api/orders", handler: "OrderController.create" },
65
+ { method: "GET", path: "/api/orders/:id", handler: "OrderController.get" },
66
+ { method: "PATCH", path: "/api/orders/:id/status", handler: "OrderController.updateStatus" },
67
+ { method: "POST", path: "/api/auth/login", handler: "AuthController.login" },
68
+ { method: "POST", path: "/api/auth/refresh", handler: "AuthController.refresh" },
69
+ { method: "POST", path: "/api/auth/logout", handler: "AuthController.logout" },
70
+ { method: "GET", path: "/api/search", handler: "SearchController.query" },
71
+ { method: "GET", path: "/api/analytics/dashboard", handler: "AnalyticsController.dashboard" },
72
+ { method: "POST", path: "/api/notifications/send", handler: "NotificationController.send" },
73
+ { method: "POST", path: "/api/uploads/image", handler: "UploadController.image" },
74
+ { method: "GET", path: "/api/health", handler: "HealthController.check" },
75
+ { method: "GET", path: "/api/config", handler: "ConfigController.get" },
76
+ { method: "POST", path: "/api/webhooks/stripe", handler: "WebhookController.stripe" },
77
+ ]
78
+
59
79
  const dbQueries = [
60
- "SELECT * FROM users WHERE id = $1",
61
- "SELECT * FROM users ORDER BY created_at DESC LIMIT 20",
62
- "INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *",
63
- "UPDATE users SET name = $1 WHERE id = $2",
64
- "SELECT p.*, c.name AS category FROM products p JOIN categories c ON p.category_id = c.id",
65
- "SELECT * FROM orders WHERE user_id = $1 ORDER BY created_at DESC",
66
- "INSERT INTO orders (user_id, total, status) VALUES ($1, $2, $3)",
67
- "SELECT u.*, COUNT(o.id) AS order_count FROM users u LEFT JOIN orders o ON u.id = o.id GROUP BY u.id",
68
- "DELETE FROM sessions WHERE expires_at < NOW()",
69
- "SELECT * FROM products WHERE tsv @@ plainto_tsquery($1) LIMIT 50",
70
- "SELECT o.*, json_agg(oi.*) AS items FROM orders o JOIN order_items oi ON o.id = oi.order_id WHERE o.id = $1 GROUP BY o.id",
71
- "WITH ranked AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY category_id ORDER BY sales DESC) rn FROM products) SELECT * FROM ranked WHERE rn <= 5",
72
- "UPDATE inventory SET quantity = quantity - $1 WHERE product_id = $2 AND quantity >= $1 RETURNING quantity",
73
- ];
80
+ "SELECT * FROM users WHERE id = $1",
81
+ "SELECT * FROM users ORDER BY created_at DESC LIMIT 20",
82
+ "INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *",
83
+ "UPDATE users SET name = $1 WHERE id = $2",
84
+ "SELECT p.*, c.name AS category FROM products p JOIN categories c ON p.category_id = c.id",
85
+ "SELECT * FROM orders WHERE user_id = $1 ORDER BY created_at DESC",
86
+ "INSERT INTO orders (user_id, total, status) VALUES ($1, $2, $3)",
87
+ "SELECT u.*, COUNT(o.id) AS order_count FROM users u LEFT JOIN orders o ON u.id = o.id GROUP BY u.id",
88
+ "DELETE FROM sessions WHERE expires_at < NOW()",
89
+ "SELECT * FROM products WHERE tsv @@ plainto_tsquery($1) LIMIT 50",
90
+ "SELECT o.*, json_agg(oi.*) AS items FROM orders o JOIN order_items oi ON o.id = oi.order_id WHERE o.id = $1 GROUP BY o.id",
91
+ "WITH ranked AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY category_id ORDER BY sales DESC) rn FROM products) SELECT * FROM ranked WHERE rn <= 5",
92
+ "UPDATE inventory SET quantity = quantity - $1 WHERE product_id = $2 AND quantity >= $1 RETURNING quantity",
93
+ ]
94
+
74
95
  const cacheKeys = [
75
- "user:profile:42",
76
- "user:profile:108",
77
- "product:listing:page:1",
78
- "product:detail:77",
79
- "session:abc123",
80
- "rate_limit:192.168.1.1",
81
- "search:results:shoes",
82
- "config:feature_flags",
83
- "analytics:dashboard:daily",
84
- "inventory:stock:sku_4421",
85
- "cart:user:42",
86
- ];
96
+ "user:profile:42",
97
+ "user:profile:108",
98
+ "product:listing:page:1",
99
+ "product:detail:77",
100
+ "session:abc123",
101
+ "rate_limit:192.168.1.1",
102
+ "search:results:shoes",
103
+ "config:feature_flags",
104
+ "analytics:dashboard:daily",
105
+ "inventory:stock:sku_4421",
106
+ "cart:user:42",
107
+ ]
108
+
87
109
  const errorMessages = [
88
- "connection refused: upstream timeout after 30s",
89
- "UNIQUE constraint failed: users.email",
90
- "rate limit exceeded for client 10.0.3.44",
91
- "invalid JWT: token expired at 2025-12-01T00:00:00Z",
92
- "payment gateway returned 502",
93
- "deadlock detected on table orders",
94
- "request body exceeds 10MB limit",
95
- "foreign key constraint: order references missing user",
96
- "TLS handshake timeout with payment-service:443",
97
- "DNS resolution failed for analytics.internal.svc",
98
- ];
110
+ "connection refused: upstream timeout after 30s",
111
+ "UNIQUE constraint failed: users.email",
112
+ "rate limit exceeded for client 10.0.3.44",
113
+ "invalid JWT: token expired at 2025-12-01T00:00:00Z",
114
+ "payment gateway returned 502",
115
+ "deadlock detected on table orders",
116
+ "request body exceeds 10MB limit",
117
+ "foreign key constraint: order references missing user",
118
+ "TLS handshake timeout with payment-service:443",
119
+ "DNS resolution failed for analytics.internal.svc",
120
+ ]
121
+
99
122
  // ---------------------------------------------------------------------------
100
123
  // Errors
101
124
  // ---------------------------------------------------------------------------
102
- class DatabaseError extends Data.TaggedError("DatabaseError") {
103
- }
104
- class AuthenticationError extends Data.TaggedError("AuthenticationError") {
105
- }
106
- class ExternalServiceError extends Data.TaggedError("ExternalServiceError") {
107
- }
108
- class TimeoutError extends Data.TaggedError("TimeoutError") {
109
- }
110
- class ValidationError extends Data.TaggedError("ValidationError") {
111
- }
112
- class TaskError extends Data.TaggedError("TaskError") {
113
- }
114
- class HttpError extends Data.TaggedError("HttpError") {
115
- }
116
- class RateLimitError extends Data.TaggedError("RateLimitError") {
117
- }
118
- class CircuitBreakerError extends Data.TaggedError("CircuitBreakerError") {
119
- }
125
+
126
+ class DatabaseError extends Data.TaggedError("DatabaseError") {}
127
+
128
+ class AuthenticationError extends Data.TaggedError("AuthenticationError") {}
129
+
130
+ class ExternalServiceError extends Data.TaggedError("ExternalServiceError") {}
131
+
132
+ class TimeoutError extends Data.TaggedError("TimeoutError") {}
133
+
134
+ class ValidationError extends Data.TaggedError("ValidationError") {}
135
+
136
+ class TaskError extends Data.TaggedError("TaskError") {}
137
+
138
+ class HttpError extends Data.TaggedError("HttpError") {}
139
+
140
+ class RateLimitError extends Data.TaggedError("RateLimitError") {}
141
+
142
+ class CircuitBreakerError extends Data.TaggedError("CircuitBreakerError") {}
143
+
120
144
  // ---------------------------------------------------------------------------
121
145
  // Leaf-level spans
122
146
  // ---------------------------------------------------------------------------
147
+
123
148
  const simulateDnsResolve = Effect.gen(function* () {
124
- const hosts = [
125
- "db-primary.internal",
126
- "cache-01.internal",
127
- "payment-service.prod",
128
- "queue.internal",
129
- "analytics.internal.svc",
130
- ];
131
- const host = yield* pick(hosts);
132
- yield* Effect.annotateCurrentSpan("dns.host", host);
133
- yield* Effect.sleep(yield* randomMs(0, 5));
134
- if ((yield* Random.nextIntBetween(0, 100)) < 2) {
135
- return yield* Effect.fail(new TimeoutError({ operation: `dns.resolve(${host})`, durationMs: 5000 }));
136
- }
137
- }).pipe(Effect.withSpan("dns.resolve"));
149
+ const hosts = [
150
+ "db-primary.internal",
151
+ "cache-01.internal",
152
+ "payment-service.prod",
153
+ "queue.internal",
154
+ "analytics.internal.svc",
155
+ ]
156
+ const host = yield* pick(hosts)
157
+ yield* Effect.annotateCurrentSpan("dns.host", host)
158
+ yield* Effect.sleep(yield* randomMs(0, 5))
159
+ if ((yield* Random.nextIntBetween(0, 100)) < 2) {
160
+ return yield* Effect.fail(
161
+ new TimeoutError({ operation: `dns.resolve(${host})`, durationMs: 5000 }),
162
+ )
163
+ }
164
+ }).pipe(Effect.withSpan("dns.resolve"))
165
+
138
166
  const simulateTlsHandshake = Effect.gen(function* () {
139
- yield* Effect.sleep(yield* randomMs(2, 20));
140
- yield* Effect.annotateCurrentSpan("tls.version", "1.3");
141
- yield* Effect.annotateCurrentSpan("tls.cipher", "TLS_AES_256_GCM_SHA384");
142
- if ((yield* Random.nextIntBetween(0, 100)) < 1) {
143
- return yield* Effect.fail(new TimeoutError({ operation: "tls.handshake", durationMs: 10000 }));
144
- }
145
- }).pipe(Effect.withSpan("tls.handshake"));
167
+ yield* Effect.sleep(yield* randomMs(2, 20))
168
+ yield* Effect.annotateCurrentSpan("tls.version", "1.3")
169
+ yield* Effect.annotateCurrentSpan("tls.cipher", "TLS_AES_256_GCM_SHA384")
170
+ if ((yield* Random.nextIntBetween(0, 100)) < 1) {
171
+ return yield* Effect.fail(new TimeoutError({ operation: "tls.handshake", durationMs: 10000 }))
172
+ }
173
+ }).pipe(Effect.withSpan("tls.handshake"))
174
+
146
175
  const simulateConnectionPoolAcquire = Effect.gen(function* () {
147
- const pool = yield* Random.nextIntBetween(1, 20);
148
- const maxPool = 20;
149
- yield* Effect.annotateCurrentSpan("pool.active", pool);
150
- yield* Effect.annotateCurrentSpan("pool.max", maxPool);
151
- yield* dbPoolSize.pipe(Metric.set(pool));
152
- yield* Effect.sleep(yield* randomMs(0, pool > 15 ? 50 : 5));
153
- if (pool >= 19 && (yield* Random.nextIntBetween(0, 100)) < 30) {
154
- return yield* Effect.die(new DatabaseError({ query: "", reason: "connection pool exhausted" }));
155
- }
156
- }).pipe(Effect.withSpan("db.pool.acquire"));
176
+ const pool = yield* Random.nextIntBetween(1, 20)
177
+ const maxPool = 20
178
+ yield* Effect.annotateCurrentSpan("pool.active", pool)
179
+ yield* Effect.annotateCurrentSpan("pool.max", maxPool)
180
+ yield* dbPoolSize.pipe(Metric.set(pool))
181
+ yield* Effect.sleep(yield* randomMs(0, pool > 15 ? 50 : 5))
182
+ if (pool >= 19 && (yield* Random.nextIntBetween(0, 100)) < 30) {
183
+ return yield* Effect.die(new DatabaseError({ query: "", reason: "connection pool exhausted" }))
184
+ }
185
+ }).pipe(Effect.withSpan("db.pool.acquire"))
186
+
157
187
  const simulateConnectionPoolRelease = Effect.gen(function* () {
158
- yield* Effect.sleep(yield* randomMs(0, 1));
159
- }).pipe(Effect.withSpan("db.pool.release"));
188
+ yield* Effect.sleep(yield* randomMs(0, 1))
189
+ }).pipe(Effect.withSpan("db.pool.release"))
190
+
160
191
  const simulateQueryParse = Effect.gen(function* () {
161
- yield* Effect.sleep(yield* randomMs(0, 3));
162
- yield* Effect.annotateCurrentSpan("db.parse.cached", (yield* Random.nextIntBetween(0, 100)) < 80);
163
- }).pipe(Effect.withSpan("db.query.parse"));
192
+ yield* Effect.sleep(yield* randomMs(0, 3))
193
+ yield* Effect.annotateCurrentSpan("db.parse.cached", (yield* Random.nextIntBetween(0, 100)) < 80)
194
+ }).pipe(Effect.withSpan("db.query.parse"))
195
+
164
196
  const simulateQueryExecute = Effect.gen(function* () {
165
- const query = yield* pick(dbQueries);
166
- const delay = yield* randomMs(1, 80);
167
- yield* Effect.annotateCurrentSpan("db.statement", query);
168
- yield* Metric.update(dbQueryDuration, Math.round(Duration.toMillis(delay)));
169
- yield* Effect.sleep(delay);
170
- const roll = yield* Random.nextIntBetween(0, 100);
171
- if (roll < 2) {
172
- return yield* Effect.fail(new DatabaseError({ query, reason: "deadlock detected on table orders" }));
173
- }
174
- if (roll < 4) {
175
- return yield* Effect.fail(new TimeoutError({
176
- operation: "db.query.execute",
177
- durationMs: Math.round(Duration.toMillis(delay)),
178
- }));
179
- }
180
- }).pipe(Effect.withSpan("db.query.execute"));
197
+ const query = yield* pick(dbQueries)
198
+ const delay = yield* randomMs(1, 80)
199
+ yield* Effect.annotateCurrentSpan("db.statement", query)
200
+ yield* Metric.update(dbQueryDuration, Math.round(Duration.toMillis(delay)))
201
+ yield* Effect.sleep(delay)
202
+ const roll = yield* Random.nextIntBetween(0, 100)
203
+ if (roll < 2) {
204
+ return yield* Effect.fail(
205
+ new DatabaseError({ query, reason: "deadlock detected on table orders" }),
206
+ )
207
+ }
208
+ if (roll < 4) {
209
+ return yield* Effect.fail(
210
+ new TimeoutError({
211
+ operation: "db.query.execute",
212
+ durationMs: Math.round(Duration.toMillis(delay)),
213
+ }),
214
+ )
215
+ }
216
+ }).pipe(Effect.withSpan("db.query.execute"))
217
+
181
218
  const simulateResultDeserialization = Effect.gen(function* () {
182
- const rowCount = yield* Random.nextIntBetween(0, 500);
183
- yield* Effect.annotateCurrentSpan("db.rows", rowCount);
184
- yield* Effect.sleep(yield* randomMs(0, rowCount > 100 ? 15 : 3));
185
- }).pipe(Effect.withSpan("db.result.deserialize"));
219
+ const rowCount = yield* Random.nextIntBetween(0, 500)
220
+ yield* Effect.annotateCurrentSpan("db.rows", rowCount)
221
+ yield* Effect.sleep(yield* randomMs(0, rowCount > 100 ? 15 : 3))
222
+ }).pipe(Effect.withSpan("db.result.deserialize"))
223
+
186
224
  const simulateDbQuery = Effect.gen(function* () {
187
- yield* simulateConnectionPoolAcquire;
188
- yield* simulateQueryParse;
189
- yield* simulateQueryExecute;
190
- yield* simulateResultDeserialization;
191
- yield* simulateConnectionPoolRelease;
192
- }).pipe(Effect.withSpan("db.query"));
225
+ yield* simulateConnectionPoolAcquire
226
+ yield* simulateQueryParse
227
+ yield* simulateQueryExecute
228
+ yield* simulateResultDeserialization
229
+ yield* simulateConnectionPoolRelease
230
+ }).pipe(Effect.withSpan("db.query"))
231
+
193
232
  const simulateCacheSerialize = Effect.gen(function* () {
194
- const ms = yield* Random.nextIntBetween(0, 5);
195
- yield* Metric.update(serializationDuration, ms);
196
- yield* Effect.sleep(Duration.millis(ms));
197
- }).pipe(Effect.withSpan("cache.serialize"));
233
+ const ms = yield* Random.nextIntBetween(0, 5)
234
+ yield* Metric.update(serializationDuration, ms)
235
+ yield* Effect.sleep(Duration.millis(ms))
236
+ }).pipe(Effect.withSpan("cache.serialize"))
237
+
198
238
  const simulateCacheDeserialize = Effect.gen(function* () {
199
- const ms = yield* Random.nextIntBetween(0, 4);
200
- yield* Metric.update(serializationDuration, ms);
201
- yield* Effect.sleep(Duration.millis(ms));
202
- }).pipe(Effect.withSpan("cache.deserialize"));
239
+ const ms = yield* Random.nextIntBetween(0, 4)
240
+ yield* Metric.update(serializationDuration, ms)
241
+ yield* Effect.sleep(Duration.millis(ms))
242
+ }).pipe(Effect.withSpan("cache.deserialize"))
243
+
203
244
  const simulateCache = Effect.gen(function* () {
204
- const key = yield* pick(cacheKeys);
205
- const hit = (yield* Random.nextIntBetween(0, 100)) < 75;
206
- yield* Effect.annotateCurrentSpan("cache.key", key);
207
- yield* Effect.annotateCurrentSpan("cache.hit", hit);
208
- yield* Effect.sleep(yield* randomMs(0, 3));
209
- if (hit) {
210
- yield* Metric.increment(cacheHits);
211
- yield* simulateCacheDeserialize;
212
- yield* Effect.logDebug(`cache hit: ${key}`);
213
- }
214
- else {
215
- yield* Metric.increment(cacheMisses);
216
- yield* Effect.logDebug(`cache miss: ${key}`);
217
- }
218
- }).pipe(Effect.withSpan("cache.lookup"));
245
+ const key = yield* pick(cacheKeys)
246
+ const hit = (yield* Random.nextIntBetween(0, 100)) < 75
247
+ yield* Effect.annotateCurrentSpan("cache.key", key)
248
+ yield* Effect.annotateCurrentSpan("cache.hit", hit)
249
+ yield* Effect.sleep(yield* randomMs(0, 3))
250
+ if (hit) {
251
+ yield* Metric.increment(cacheHits)
252
+ yield* simulateCacheDeserialize
253
+ yield* Effect.logDebug(`cache hit: ${key}`)
254
+ } else {
255
+ yield* Metric.increment(cacheMisses)
256
+ yield* Effect.logDebug(`cache miss: ${key}`)
257
+ }
258
+ }).pipe(Effect.withSpan("cache.lookup"))
259
+
219
260
  const simulateTokenDecode = Effect.gen(function* () {
220
- yield* Effect.sleep(yield* randomMs(0, 2));
221
- yield* Effect.annotateCurrentSpan("jwt.alg", "RS256");
222
- }).pipe(Effect.withSpan("jwt.decode"));
261
+ yield* Effect.sleep(yield* randomMs(0, 2))
262
+ yield* Effect.annotateCurrentSpan("jwt.alg", "RS256")
263
+ }).pipe(Effect.withSpan("jwt.decode"))
264
+
223
265
  const simulateTokenVerify = Effect.gen(function* () {
224
- yield* Effect.sleep(yield* randomMs(1, 8));
225
- const roll = yield* Random.nextIntBetween(0, 100);
226
- if (roll < 3) {
227
- return yield* Effect.fail(new AuthenticationError({ reason: "JWT expired at 2025-12-01T00:00:00Z" }));
228
- }
229
- if (roll < 5) {
230
- return yield* Effect.fail(new AuthenticationError({
231
- reason: "invalid signature",
232
- userId: "user_" + (yield* Random.nextIntBetween(100, 999)),
233
- }));
234
- }
235
- }).pipe(Effect.withSpan("jwt.verify"));
266
+ yield* Effect.sleep(yield* randomMs(1, 8))
267
+ const roll = yield* Random.nextIntBetween(0, 100)
268
+ if (roll < 3) {
269
+ return yield* Effect.fail(
270
+ new AuthenticationError({ reason: "JWT expired at 2025-12-01T00:00:00Z" }),
271
+ )
272
+ }
273
+ if (roll < 5) {
274
+ return yield* Effect.fail(
275
+ new AuthenticationError({
276
+ reason: "invalid signature",
277
+ userId: "user_" + (yield* Random.nextIntBetween(100, 999)),
278
+ }),
279
+ )
280
+ }
281
+ }).pipe(Effect.withSpan("jwt.verify"))
282
+
236
283
  const simulatePermissionCheck = Effect.gen(function* () {
237
- const roles = ["admin", "editor", "viewer", "moderator"];
238
- const role = yield* pick(roles);
239
- yield* Effect.annotateCurrentSpan("auth.role", role);
240
- yield* Effect.sleep(yield* randomMs(0, 3));
241
- if ((yield* Random.nextIntBetween(0, 100)) < 2) {
242
- return yield* Effect.fail(new AuthenticationError({ reason: `insufficient permissions for role: ${role}` }));
243
- }
244
- }).pipe(Effect.withSpan("auth.permission_check"));
284
+ const roles = ["admin", "editor", "viewer", "moderator"]
285
+ const role = yield* pick(roles)
286
+ yield* Effect.annotateCurrentSpan("auth.role", role)
287
+ yield* Effect.sleep(yield* randomMs(0, 3))
288
+ if ((yield* Random.nextIntBetween(0, 100)) < 2) {
289
+ return yield* Effect.fail(
290
+ new AuthenticationError({ reason: `insufficient permissions for role: ${role}` }),
291
+ )
292
+ }
293
+ }).pipe(Effect.withSpan("auth.permission_check"))
294
+
245
295
  const simulateAuth = Effect.gen(function* () {
246
- yield* simulateTokenDecode;
247
- yield* simulateTokenVerify;
248
- yield* simulatePermissionCheck;
249
- yield* Effect.logDebug("token validated");
250
- }).pipe(Effect.withSpan("auth.validate"));
296
+ yield* simulateTokenDecode
297
+ yield* simulateTokenVerify
298
+ yield* simulatePermissionCheck
299
+ yield* Effect.logDebug("token validated")
300
+ }).pipe(Effect.withSpan("auth.validate"))
301
+
251
302
  const simulateRateLimit = Effect.gen(function* () {
252
- const ips = ["10.0.3.44", "192.168.1.1", "172.16.0.55", "10.0.7.12", "203.0.113.42"];
253
- const ip = yield* pick(ips);
254
- const remaining = yield* Random.nextIntBetween(0, 100);
255
- yield* Effect.annotateCurrentSpan("rate_limit.client_ip", ip);
256
- yield* Effect.annotateCurrentSpan("rate_limit.remaining", remaining);
257
- yield* Effect.sleep(yield* randomMs(0, 2));
258
- if (remaining < 3) {
259
- yield* Metric.increment(rateLimitRejections);
260
- yield* Effect.logWarning(`rate limit near threshold for ${ip}`);
261
- if ((yield* Random.nextIntBetween(0, 100)) < 40) {
262
- return yield* Effect.fail(new RateLimitError({ clientIp: ip, limit: 100 }));
263
- }
303
+ const ips = ["10.0.3.44", "192.168.1.1", "172.16.0.55", "10.0.7.12", "203.0.113.42"]
304
+ const ip = yield* pick(ips)
305
+ const remaining = yield* Random.nextIntBetween(0, 100)
306
+ yield* Effect.annotateCurrentSpan("rate_limit.client_ip", ip)
307
+ yield* Effect.annotateCurrentSpan("rate_limit.remaining", remaining)
308
+ yield* Effect.sleep(yield* randomMs(0, 2))
309
+ if (remaining < 3) {
310
+ yield* Metric.increment(rateLimitRejections)
311
+ yield* Effect.logWarning(`rate limit near threshold for ${ip}`)
312
+ if ((yield* Random.nextIntBetween(0, 100)) < 40) {
313
+ return yield* Effect.fail(new RateLimitError({ clientIp: ip, limit: 100 }))
264
314
  }
265
- }).pipe(Effect.withSpan("middleware.rate_limit"));
315
+ }
316
+ }).pipe(Effect.withSpan("middleware.rate_limit"))
317
+
266
318
  const simulateCors = Effect.gen(function* () {
267
- const origins = [
268
- "https://app.example.com",
269
- "https://admin.example.com",
270
- "https://mobile.example.com",
271
- "null",
272
- ];
273
- const origin = yield* pick(origins);
274
- yield* Effect.annotateCurrentSpan("cors.origin", origin);
275
- yield* Effect.sleep(yield* randomMs(0, 1));
276
- if (origin === "null") {
277
- yield* Effect.logWarning("CORS: rejected null origin");
278
- }
279
- }).pipe(Effect.withSpan("middleware.cors"));
319
+ const origins = [
320
+ "https://app.example.com",
321
+ "https://admin.example.com",
322
+ "https://mobile.example.com",
323
+ "null",
324
+ ]
325
+ const origin = yield* pick(origins)
326
+ yield* Effect.annotateCurrentSpan("cors.origin", origin)
327
+ yield* Effect.sleep(yield* randomMs(0, 1))
328
+ if (origin === "null") {
329
+ yield* Effect.logWarning("CORS: rejected null origin")
330
+ }
331
+ }).pipe(Effect.withSpan("middleware.cors"))
332
+
280
333
  const simulateRequestParsing = Effect.gen(function* () {
281
- const contentTypes = [
282
- "application/json",
283
- "multipart/form-data",
284
- "application/x-www-form-urlencoded",
285
- "text/plain",
286
- ];
287
- const ct = yield* pick(contentTypes);
288
- yield* Effect.annotateCurrentSpan("http.content_type", ct);
289
- const bodySize = yield* Random.nextIntBetween(0, 50000);
290
- yield* Effect.annotateCurrentSpan("http.body_size", bodySize);
291
- yield* Effect.sleep(yield* randomMs(0, bodySize > 10000 ? 15 : 3));
292
- if (bodySize > 40000) {
293
- return yield* Effect.fail(new ValidationError({ field: "body", message: "request body exceeds 10MB limit" }));
294
- }
295
- if ((yield* Random.nextIntBetween(0, 100)) < 3) {
296
- return yield* Effect.fail(new ValidationError({ field: "body", message: "malformed JSON at position 42" }));
297
- }
298
- }).pipe(Effect.withSpan("http.parse_body"));
334
+ const contentTypes = [
335
+ "application/json",
336
+ "multipart/form-data",
337
+ "application/x-www-form-urlencoded",
338
+ "text/plain",
339
+ ]
340
+ const ct = yield* pick(contentTypes)
341
+ yield* Effect.annotateCurrentSpan("http.content_type", ct)
342
+ const bodySize = yield* Random.nextIntBetween(0, 50000)
343
+ yield* Effect.annotateCurrentSpan("http.body_size", bodySize)
344
+ yield* Effect.sleep(yield* randomMs(0, bodySize > 10000 ? 15 : 3))
345
+ if (bodySize > 40000) {
346
+ return yield* Effect.fail(
347
+ new ValidationError({ field: "body", message: "request body exceeds 10MB limit" }),
348
+ )
349
+ }
350
+ if ((yield* Random.nextIntBetween(0, 100)) < 3) {
351
+ return yield* Effect.fail(
352
+ new ValidationError({ field: "body", message: "malformed JSON at position 42" }),
353
+ )
354
+ }
355
+ }).pipe(Effect.withSpan("http.parse_body"))
356
+
299
357
  const simulateInputValidation = Effect.gen(function* () {
300
- yield* Effect.sleep(yield* randomMs(0, 3));
301
- const fields = ["email", "name", "amount", "quantity", "phone", "address.zip"];
302
- if ((yield* Random.nextIntBetween(0, 100)) < 5) {
303
- const field = yield* pick(fields);
304
- return yield* Effect.fail(new ValidationError({ field, message: `invalid format for ${field}` }));
305
- }
306
- }).pipe(Effect.withSpan("validation.input"));
358
+ yield* Effect.sleep(yield* randomMs(0, 3))
359
+ const fields = ["email", "name", "amount", "quantity", "phone", "address.zip"]
360
+ if ((yield* Random.nextIntBetween(0, 100)) < 5) {
361
+ const field = yield* pick(fields)
362
+ return yield* Effect.fail(
363
+ new ValidationError({ field, message: `invalid format for ${field}` }),
364
+ )
365
+ }
366
+ }).pipe(Effect.withSpan("validation.input"))
367
+
307
368
  const simulateResponseSerialization = Effect.gen(function* () {
308
- const formats = ["json", "msgpack", "protobuf"];
309
- const format = yield* pick(formats);
310
- yield* Effect.annotateCurrentSpan("serialization.format", format);
311
- const ms = yield* Random.nextIntBetween(0, 10);
312
- yield* Metric.update(serializationDuration, ms);
313
- yield* Effect.sleep(Duration.millis(ms));
314
- }).pipe(Effect.withSpan("http.serialize_response"));
369
+ const formats = ["json", "msgpack", "protobuf"]
370
+ const format = yield* pick(formats)
371
+ yield* Effect.annotateCurrentSpan("serialization.format", format)
372
+ const ms = yield* Random.nextIntBetween(0, 10)
373
+ yield* Metric.update(serializationDuration, ms)
374
+ yield* Effect.sleep(Duration.millis(ms))
375
+ }).pipe(Effect.withSpan("http.serialize_response"))
376
+
315
377
  const simulateCompression = Effect.gen(function* () {
316
- const algos = ["gzip", "br", "none"];
317
- const algo = yield* pick(algos);
318
- yield* Effect.annotateCurrentSpan("compression.algorithm", algo);
319
- yield* Effect.sleep(yield* randomMs(0, algo === "none" ? 1 : 8));
320
- }).pipe(Effect.withSpan("http.compress"));
378
+ const algos = ["gzip", "br", "none"]
379
+ const algo = yield* pick(algos)
380
+ yield* Effect.annotateCurrentSpan("compression.algorithm", algo)
381
+ yield* Effect.sleep(yield* randomMs(0, algo === "none" ? 1 : 8))
382
+ }).pipe(Effect.withSpan("http.compress"))
383
+
321
384
  const simulateAccessLog = Effect.gen(function* () {
322
- yield* Effect.sleep(yield* randomMs(0, 1));
323
- }).pipe(Effect.withSpan("middleware.access_log"));
385
+ yield* Effect.sleep(yield* randomMs(0, 1))
386
+ }).pipe(Effect.withSpan("middleware.access_log"))
387
+
324
388
  // ---------------------------------------------------------------------------
325
389
  // Composite sub-operations with deep nesting
326
390
  // ---------------------------------------------------------------------------
327
- const simulateCircuitBreaker = (inner, service) => Effect.gen(function* () {
328
- const failures = yield* Random.nextIntBetween(0, 10);
329
- yield* Effect.annotateCurrentSpan("circuit_breaker.service", service);
330
- yield* Effect.annotateCurrentSpan("circuit_breaker.failure_count", failures);
391
+
392
+ const simulateCircuitBreaker = (inner, service) =>
393
+ Effect.gen(function* () {
394
+ const failures = yield* Random.nextIntBetween(0, 10)
395
+ yield* Effect.annotateCurrentSpan("circuit_breaker.service", service)
396
+ yield* Effect.annotateCurrentSpan("circuit_breaker.failure_count", failures)
331
397
  if (failures >= 8) {
332
- yield* Metric.increment(circuitBreakerTrips);
333
- yield* Effect.logWarning(`circuit breaker OPEN for ${service}`);
334
- return yield* Effect.fail(new CircuitBreakerError({ service, failureCount: failures }));
398
+ yield* Metric.increment(circuitBreakerTrips)
399
+ yield* Effect.logWarning(`circuit breaker OPEN for ${service}`)
400
+ return yield* Effect.fail(new CircuitBreakerError({ service, failureCount: failures }))
335
401
  }
336
- yield* inner;
337
- }).pipe(Effect.withSpan("circuit_breaker"));
338
- const simulateRetry = (inner, opName) => Effect.gen(function* () {
339
- const maxRetries = 3;
340
- let attempt = 0;
341
- let succeeded = false;
402
+ yield* inner
403
+ }).pipe(Effect.withSpan("circuit_breaker"))
404
+
405
+ const simulateRetry = (inner, opName) =>
406
+ Effect.gen(function* () {
407
+ const maxRetries = 3
408
+ let attempt = 0
409
+ let succeeded = false
342
410
  while (attempt < maxRetries && !succeeded) {
343
- attempt++;
344
- yield* Effect.annotateCurrentSpan("retry.attempt", attempt);
345
- const result = yield* inner.pipe(Effect.map(() => true), Effect.catchAll((e) => {
346
- if (attempt < maxRetries) {
347
- return Effect.gen(function* () {
348
- yield* Metric.increment(retryCount);
349
- yield* Effect.logWarning(`${opName} attempt ${attempt} failed, retrying`);
350
- yield* Effect.sleep(yield* randomMs(10, 50 * attempt));
351
- return false;
352
- });
353
- }
354
- return Effect.fail(e);
355
- }));
356
- succeeded = result;
411
+ attempt++
412
+ yield* Effect.annotateCurrentSpan("retry.attempt", attempt)
413
+ const result = yield* inner.pipe(
414
+ Effect.map(() => true),
415
+ Effect.catchAll((e) => {
416
+ if (attempt < maxRetries) {
417
+ return Effect.gen(function* () {
418
+ yield* Metric.increment(retryCount)
419
+ yield* Effect.logWarning(`${opName} attempt ${attempt} failed, retrying`)
420
+ yield* Effect.sleep(yield* randomMs(10, 50 * attempt))
421
+ return false
422
+ })
423
+ }
424
+ return Effect.fail(e)
425
+ }),
426
+ )
427
+ succeeded = result
357
428
  }
358
- }).pipe(Effect.withSpan("retry"));
429
+ }).pipe(Effect.withSpan("retry"))
430
+
359
431
  const simulateExternalCall = Effect.gen(function* () {
360
- const services = [
361
- "payment-service",
362
- "email-service",
363
- "notification-service",
364
- "inventory-service",
365
- "shipping-service",
366
- "tax-service",
367
- ];
368
- const service = yield* pick(services);
369
- yield* Effect.annotateCurrentSpan("peer.service", service);
370
- yield* simulateDnsResolve;
371
- yield* simulateTlsHandshake;
372
- const delay = yield* randomMs(10, 300);
373
- yield* Effect.annotateCurrentSpan("http.outgoing.duration_ms", Math.round(Duration.toMillis(delay)));
374
- yield* Effect.sleep(delay);
375
- const roll = yield* Random.nextIntBetween(0, 100);
376
- if (roll < 4) {
377
- yield* Effect.logError(`${service} responded with 503`);
378
- return yield* Effect.fail(new ExternalServiceError({ service, statusCode: 503, reason: "service unavailable" }));
379
- }
380
- if (roll < 6) {
381
- yield* Effect.logError(`${service} timed out after ${Math.round(Duration.toMillis(delay))}ms`);
382
- return yield* Effect.fail(new TimeoutError({
383
- operation: `${service} call`,
384
- durationMs: Math.round(Duration.toMillis(delay)),
385
- }));
386
- }
387
- if (roll < 8) {
388
- yield* Effect.logError(`${service} responded with 502`);
389
- return yield* Effect.fail(new ExternalServiceError({ service, statusCode: 502, reason: "bad gateway" }));
390
- }
391
- }).pipe(Effect.withSpan("http.outgoing"));
432
+ const services = [
433
+ "payment-service",
434
+ "email-service",
435
+ "notification-service",
436
+ "inventory-service",
437
+ "shipping-service",
438
+ "tax-service",
439
+ ]
440
+ const service = yield* pick(services)
441
+ yield* Effect.annotateCurrentSpan("peer.service", service)
442
+
443
+ yield* simulateDnsResolve
444
+ yield* simulateTlsHandshake
445
+
446
+ const delay = yield* randomMs(10, 300)
447
+ yield* Effect.annotateCurrentSpan(
448
+ "http.outgoing.duration_ms",
449
+ Math.round(Duration.toMillis(delay)),
450
+ )
451
+ yield* Effect.sleep(delay)
452
+
453
+ const roll = yield* Random.nextIntBetween(0, 100)
454
+ if (roll < 4) {
455
+ yield* Effect.logError(`${service} responded with 503`)
456
+ return yield* Effect.fail(
457
+ new ExternalServiceError({ service, statusCode: 503, reason: "service unavailable" }),
458
+ )
459
+ }
460
+ if (roll < 6) {
461
+ yield* Effect.logError(`${service} timed out after ${Math.round(Duration.toMillis(delay))}ms`)
462
+ return yield* Effect.fail(
463
+ new TimeoutError({
464
+ operation: `${service} call`,
465
+ durationMs: Math.round(Duration.toMillis(delay)),
466
+ }),
467
+ )
468
+ }
469
+ if (roll < 8) {
470
+ yield* Effect.logError(`${service} responded with 502`)
471
+ return yield* Effect.fail(
472
+ new ExternalServiceError({ service, statusCode: 502, reason: "bad gateway" }),
473
+ )
474
+ }
475
+ }).pipe(Effect.withSpan("http.outgoing"))
476
+
392
477
  const simulateExternalCallWithResilience = Effect.gen(function* () {
393
- const service = yield* pick([
394
- "payment-service",
395
- "email-service",
396
- "notification-service",
397
- "inventory-service",
398
- ]);
399
- yield* simulateCircuitBreaker(simulateRetry(simulateExternalCall, `external.${service}`), service);
400
- }).pipe(Effect.withSpan("external.resilient_call"));
478
+ const service = yield* pick([
479
+ "payment-service",
480
+ "email-service",
481
+ "notification-service",
482
+ "inventory-service",
483
+ ])
484
+ yield* simulateCircuitBreaker(simulateRetry(simulateExternalCall, `external.${service}`), service)
485
+ }).pipe(Effect.withSpan("external.resilient_call"))
486
+
401
487
  const simulateSearchQuery = Effect.gen(function* () {
402
- const terms = ["shoes", "laptop", "organic food", "headphones", "gift ideas"];
403
- const term = yield* pick(terms);
404
- yield* Effect.annotateCurrentSpan("search.query", term);
405
- yield* simulateCache;
406
- yield* Effect.gen(function* () {
407
- yield* Effect.sleep(yield* randomMs(5, 40));
408
- yield* Effect.annotateCurrentSpan("search.engine", "elasticsearch");
409
- yield* Effect.annotateCurrentSpan("search.index", "products_v3");
410
- const hits = yield* Random.nextIntBetween(0, 200);
411
- yield* Effect.annotateCurrentSpan("search.hits", hits);
412
- }).pipe(Effect.withSpan("search.execute"));
413
- yield* Effect.gen(function* () {
414
- yield* Effect.sleep(yield* randomMs(1, 10));
415
- yield* Effect.annotateCurrentSpan("search.boost", "relevance+recency");
416
- }).pipe(Effect.withSpan("search.rank"));
417
- yield* Effect.gen(function* () {
418
- yield* Effect.sleep(yield* randomMs(0, 5));
419
- }).pipe(Effect.withSpan("search.highlight"));
420
- }).pipe(Effect.withSpan("search.pipeline"));
488
+ const terms = ["shoes", "laptop", "organic food", "headphones", "gift ideas"]
489
+ const term = yield* pick(terms)
490
+ yield* Effect.annotateCurrentSpan("search.query", term)
491
+
492
+ yield* simulateCache
493
+
494
+ yield* Effect.gen(function* () {
495
+ yield* Effect.sleep(yield* randomMs(5, 40))
496
+ yield* Effect.annotateCurrentSpan("search.engine", "elasticsearch")
497
+ yield* Effect.annotateCurrentSpan("search.index", "products_v3")
498
+ const hits = yield* Random.nextIntBetween(0, 200)
499
+ yield* Effect.annotateCurrentSpan("search.hits", hits)
500
+ }).pipe(Effect.withSpan("search.execute"))
501
+
502
+ yield* Effect.gen(function* () {
503
+ yield* Effect.sleep(yield* randomMs(1, 10))
504
+ yield* Effect.annotateCurrentSpan("search.boost", "relevance+recency")
505
+ }).pipe(Effect.withSpan("search.rank"))
506
+
507
+ yield* Effect.gen(function* () {
508
+ yield* Effect.sleep(yield* randomMs(0, 5))
509
+ }).pipe(Effect.withSpan("search.highlight"))
510
+ }).pipe(Effect.withSpan("search.pipeline"))
511
+
421
512
  const simulateFileUpload = Effect.gen(function* () {
422
- const fileSize = yield* Random.nextIntBetween(1000, 5_000_000);
423
- yield* Effect.annotateCurrentSpan("upload.size_bytes", fileSize);
424
- yield* Effect.gen(function* () {
425
- yield* Effect.sleep(yield* randomMs(1, 10));
426
- const types = ["image/jpeg", "image/png", "application/pdf", "image/webp"];
427
- const mime = yield* pick(types);
428
- yield* Effect.annotateCurrentSpan("upload.mime", mime);
429
- if ((yield* Random.nextIntBetween(0, 100)) < 3) {
430
- return yield* Effect.fail(new ValidationError({ field: "file", message: "unsupported mime type" }));
431
- }
432
- }).pipe(Effect.withSpan("upload.validate_mime"));
433
- yield* Effect.gen(function* () {
434
- yield* Effect.sleep(yield* randomMs(0, 5));
435
- }).pipe(Effect.withSpan("upload.virus_scan"));
436
- yield* Effect.gen(function* () {
437
- yield* Effect.sleep(yield* randomMs(5, 50));
438
- yield* Effect.annotateCurrentSpan("upload.bucket", "user-uploads-prod");
439
- }).pipe(Effect.withSpan("upload.store_s3"));
440
- yield* Effect.gen(function* () {
441
- yield* simulateDbQuery;
442
- }).pipe(Effect.withSpan("upload.persist_metadata"));
443
- }).pipe(Effect.withSpan("upload.pipeline"));
513
+ const fileSize = yield* Random.nextIntBetween(1000, 5_000_000)
514
+ yield* Effect.annotateCurrentSpan("upload.size_bytes", fileSize)
515
+
516
+ yield* Effect.gen(function* () {
517
+ yield* Effect.sleep(yield* randomMs(1, 10))
518
+ const types = ["image/jpeg", "image/png", "application/pdf", "image/webp"]
519
+ const mime = yield* pick(types)
520
+ yield* Effect.annotateCurrentSpan("upload.mime", mime)
521
+ if ((yield* Random.nextIntBetween(0, 100)) < 3) {
522
+ return yield* Effect.fail(
523
+ new ValidationError({ field: "file", message: "unsupported mime type" }),
524
+ )
525
+ }
526
+ }).pipe(Effect.withSpan("upload.validate_mime"))
527
+
528
+ yield* Effect.gen(function* () {
529
+ yield* Effect.sleep(yield* randomMs(0, 5))
530
+ }).pipe(Effect.withSpan("upload.virus_scan"))
531
+
532
+ yield* Effect.gen(function* () {
533
+ yield* Effect.sleep(yield* randomMs(5, 50))
534
+ yield* Effect.annotateCurrentSpan("upload.bucket", "user-uploads-prod")
535
+ }).pipe(Effect.withSpan("upload.store_s3"))
536
+
537
+ yield* Effect.gen(function* () {
538
+ yield* simulateDbQuery
539
+ }).pipe(Effect.withSpan("upload.persist_metadata"))
540
+ }).pipe(Effect.withSpan("upload.pipeline"))
541
+
444
542
  const simulateOrderWorkflow = Effect.gen(function* () {
445
- yield* Effect.annotateCurrentSpan("order.workflow", "create");
446
- yield* simulateInputValidation;
447
- yield* Effect.gen(function* () {
448
- yield* simulateDbQuery;
449
- yield* Effect.sleep(yield* randomMs(1, 10));
450
- }).pipe(Effect.withSpan("order.check_inventory"));
451
- yield* Effect.gen(function* () {
452
- yield* simulateExternalCallWithResilience;
453
- }).pipe(Effect.withSpan("order.process_payment"));
454
- yield* Effect.gen(function* () {
455
- yield* simulateDbQuery;
456
- }).pipe(Effect.withSpan("order.persist"));
457
- yield* Effect.gen(function* () {
458
- yield* simulateCache;
459
- yield* simulateCacheSerialize;
460
- }).pipe(Effect.withSpan("order.invalidate_cache"));
461
- yield* Effect.gen(function* () {
462
- yield* Effect.sleep(yield* randomMs(1, 10));
463
- yield* Effect.logInfo("order confirmation email queued");
464
- }).pipe(Effect.withSpan("order.queue_notification"));
465
- }).pipe(Effect.withSpan("order.workflow"));
543
+ yield* Effect.annotateCurrentSpan("order.workflow", "create")
544
+
545
+ yield* simulateInputValidation
546
+
547
+ yield* Effect.gen(function* () {
548
+ yield* simulateDbQuery
549
+ yield* Effect.sleep(yield* randomMs(1, 10))
550
+ }).pipe(Effect.withSpan("order.check_inventory"))
551
+
552
+ yield* Effect.gen(function* () {
553
+ yield* simulateExternalCallWithResilience
554
+ }).pipe(Effect.withSpan("order.process_payment"))
555
+
556
+ yield* Effect.gen(function* () {
557
+ yield* simulateDbQuery
558
+ }).pipe(Effect.withSpan("order.persist"))
559
+
560
+ yield* Effect.gen(function* () {
561
+ yield* simulateCache
562
+ yield* simulateCacheSerialize
563
+ }).pipe(Effect.withSpan("order.invalidate_cache"))
564
+
565
+ yield* Effect.gen(function* () {
566
+ yield* Effect.sleep(yield* randomMs(1, 10))
567
+ yield* Effect.logInfo("order confirmation email queued")
568
+ }).pipe(Effect.withSpan("order.queue_notification"))
569
+ }).pipe(Effect.withSpan("order.workflow"))
570
+
466
571
  const simulateAnalyticsPipeline = Effect.gen(function* () {
467
- yield* Effect.gen(function* () {
468
- yield* simulateDbQuery;
469
- yield* simulateDbQuery;
470
- }).pipe(Effect.withSpan("analytics.fetch_raw"));
471
- yield* Effect.gen(function* () {
472
- yield* Effect.sleep(yield* randomMs(5, 30));
473
- yield* Effect.annotateCurrentSpan("analytics.aggregation", "time_series");
474
- }).pipe(Effect.withSpan("analytics.aggregate"));
475
- yield* Effect.gen(function* () {
476
- yield* simulateCache;
477
- }).pipe(Effect.withSpan("analytics.cache_result"));
478
- yield* Effect.gen(function* () {
479
- yield* Effect.sleep(yield* randomMs(2, 15));
480
- yield* Effect.annotateCurrentSpan("analytics.format", "dashboard_v2");
481
- }).pipe(Effect.withSpan("analytics.transform"));
482
- }).pipe(Effect.withSpan("analytics.pipeline"));
572
+ yield* Effect.gen(function* () {
573
+ yield* simulateDbQuery
574
+ yield* simulateDbQuery
575
+ }).pipe(Effect.withSpan("analytics.fetch_raw"))
576
+
577
+ yield* Effect.gen(function* () {
578
+ yield* Effect.sleep(yield* randomMs(5, 30))
579
+ yield* Effect.annotateCurrentSpan("analytics.aggregation", "time_series")
580
+ }).pipe(Effect.withSpan("analytics.aggregate"))
581
+
582
+ yield* Effect.gen(function* () {
583
+ yield* simulateCache
584
+ }).pipe(Effect.withSpan("analytics.cache_result"))
585
+
586
+ yield* Effect.gen(function* () {
587
+ yield* Effect.sleep(yield* randomMs(2, 15))
588
+ yield* Effect.annotateCurrentSpan("analytics.format", "dashboard_v2")
589
+ }).pipe(Effect.withSpan("analytics.transform"))
590
+ }).pipe(Effect.withSpan("analytics.pipeline"))
591
+
483
592
  // ---------------------------------------------------------------------------
484
593
  // Background tasks (also deeper now)
485
594
  // ---------------------------------------------------------------------------
595
+
486
596
  const simulateBackgroundTask = Effect.gen(function* () {
487
- const tasks = [
488
- "process-webhook",
489
- "send-email",
490
- "generate-report",
491
- "sync-inventory",
492
- "cleanup-sessions",
493
- "rebuild-search-index",
494
- "process-refund",
495
- "generate-invoice-pdf",
496
- "sync-crm",
497
- ];
498
- const task = yield* pick(tasks);
499
- yield* Effect.logInfo(`starting background task: ${task}`);
500
- yield* Effect.gen(function* () {
501
- yield* Effect.sleep(yield* randomMs(0, 5));
502
- yield* Effect.annotateCurrentSpan("task.priority", yield* pick(["low", "normal", "high", "critical"]));
503
- }).pipe(Effect.withSpan("task.dequeue"));
504
- yield* Effect.gen(function* () {
505
- yield* simulateDbQuery;
506
- }).pipe(Effect.withSpan("task.load_context"));
507
- yield* Effect.gen(function* () {
508
- yield* Effect.sleep(yield* randomMs(20, 300));
509
- const roll = yield* Random.nextIntBetween(0, 100);
510
- if (roll < 4) {
511
- return yield* Effect.fail(new TaskError({ task, reason: "execution exceeded 30s deadline" }));
512
- }
513
- if (roll < 7) {
514
- const err = yield* pick(errorMessages);
515
- yield* Effect.logError(`task ${task} failed: ${err}`);
516
- return yield* Effect.fail(new TaskError({ task, reason: err }));
517
- }
518
- if (roll < 9) {
519
- return yield* Effect.die(new TaskError({ task, reason: "out of memory" }));
520
- }
521
- }).pipe(Effect.withSpan("task.execute"));
522
- yield* maybe(60, Effect.gen(function* () {
523
- yield* simulateExternalCall;
524
- }).pipe(Effect.withSpan("task.external_dependency")));
525
- yield* Effect.gen(function* () {
526
- yield* simulateDbQuery;
527
- }).pipe(Effect.withSpan("task.persist_result"));
528
- yield* Effect.gen(function* () {
529
- yield* Effect.sleep(yield* randomMs(0, 3));
530
- }).pipe(Effect.withSpan("task.ack"));
531
- yield* Metric.increment(eventCount);
532
- yield* Effect.logInfo(`completed background task: ${task}`);
533
- }).pipe(Effect.withSpan("task.background"));
597
+ const tasks = [
598
+ "process-webhook",
599
+ "send-email",
600
+ "generate-report",
601
+ "sync-inventory",
602
+ "cleanup-sessions",
603
+ "rebuild-search-index",
604
+ "process-refund",
605
+ "generate-invoice-pdf",
606
+ "sync-crm",
607
+ ]
608
+ const task = yield* pick(tasks)
609
+ yield* Effect.logInfo(`starting background task: ${task}`)
610
+
611
+ yield* Effect.gen(function* () {
612
+ yield* Effect.sleep(yield* randomMs(0, 5))
613
+ yield* Effect.annotateCurrentSpan(
614
+ "task.priority",
615
+ yield* pick(["low", "normal", "high", "critical"]),
616
+ )
617
+ }).pipe(Effect.withSpan("task.dequeue"))
618
+
619
+ yield* Effect.gen(function* () {
620
+ yield* simulateDbQuery
621
+ }).pipe(Effect.withSpan("task.load_context"))
622
+
623
+ yield* Effect.gen(function* () {
624
+ yield* Effect.sleep(yield* randomMs(20, 300))
625
+ const roll = yield* Random.nextIntBetween(0, 100)
626
+ if (roll < 4) {
627
+ return yield* Effect.fail(new TaskError({ task, reason: "execution exceeded 30s deadline" }))
628
+ }
629
+ if (roll < 7) {
630
+ const err = yield* pick(errorMessages)
631
+ yield* Effect.logError(`task ${task} failed: ${err}`)
632
+ return yield* Effect.fail(new TaskError({ task, reason: err }))
633
+ }
634
+ if (roll < 9) {
635
+ return yield* Effect.die(new TaskError({ task, reason: "out of memory" }))
636
+ }
637
+ }).pipe(Effect.withSpan("task.execute"))
638
+
639
+ yield* maybe(
640
+ 60,
641
+ Effect.gen(function* () {
642
+ yield* simulateExternalCall
643
+ }).pipe(Effect.withSpan("task.external_dependency")),
644
+ )
645
+
646
+ yield* Effect.gen(function* () {
647
+ yield* simulateDbQuery
648
+ }).pipe(Effect.withSpan("task.persist_result"))
649
+
650
+ yield* Effect.gen(function* () {
651
+ yield* Effect.sleep(yield* randomMs(0, 3))
652
+ }).pipe(Effect.withSpan("task.ack"))
653
+
654
+ yield* Metric.increment(eventCount)
655
+ yield* Effect.logInfo(`completed background task: ${task}`)
656
+ }).pipe(Effect.withSpan("task.background"))
657
+
534
658
  // ---------------------------------------------------------------------------
535
659
  // Simulate a single HTTP request (deep middleware + handler pipeline)
536
660
  // ---------------------------------------------------------------------------
661
+
537
662
  const simulateRequest = Effect.gen(function* () {
538
- const route = yield* pick(routes);
539
- const statusCodes = [200, 200, 200, 200, 200, 201, 204, 301, 400, 401, 404, 500];
540
- yield* Metric.increment(httpRequestsTotal);
541
- yield* Metric.increment(activeConnections);
542
- const result = yield* Effect.gen(function* () {
543
- yield* Effect.annotateCurrentSpan("http.method", route.method);
544
- yield* Effect.annotateCurrentSpan("http.route", route.path);
545
- yield* Effect.annotateCurrentSpan("handler", route.handler);
546
- // middleware chain
547
- yield* simulateAccessLog;
548
- yield* simulateCors;
549
- yield* simulateRateLimit;
550
- yield* simulateRequestParsing;
551
- // auth (most routes)
552
- yield* maybe(70, simulateAuth);
553
- // handler body — pick a complex workflow or simple CRUD based on the route
554
- const handler = route.handler;
555
- if (handler === "OrderController.create" || handler === "OrderController.updateStatus") {
556
- yield* simulateOrderWorkflow;
557
- }
558
- else if (handler === "SearchController.query") {
559
- yield* simulateSearchQuery;
560
- }
561
- else if (handler === "UploadController.image") {
562
- yield* simulateFileUpload;
563
- }
564
- else if (handler === "AnalyticsController.dashboard") {
565
- yield* simulateAnalyticsPipeline;
566
- }
567
- else if (handler === "WebhookController.stripe") {
568
- yield* simulateInputValidation;
569
- yield* simulateExternalCallWithResilience;
570
- yield* simulateDbQuery;
571
- }
572
- else {
573
- // standard CRUD
574
- yield* maybe(50, simulateCache);
575
- yield* simulateDbQuery;
576
- yield* maybe(25, simulateExternalCall);
577
- yield* maybe(30, simulateDbQuery); // secondary query
578
- }
579
- // response pipeline
580
- yield* simulateResponseSerialization;
581
- yield* maybe(40, simulateCompression);
582
- const status = yield* pick(statusCodes);
583
- yield* Effect.annotateCurrentSpan("http.status_code", status);
584
- if (status >= 500) {
585
- const err = yield* pick(errorMessages);
586
- yield* Effect.logError(`${route.method} ${route.path} → ${status}: ${err}`);
587
- return yield* Effect.fail(new HttpError({
588
- method: route.method,
589
- path: route.path,
590
- statusCode: status,
591
- message: err,
592
- }));
593
- }
594
- if (status >= 400) {
595
- yield* Effect.logWarning(`${route.method} ${route.path} → ${status}`);
596
- }
597
- else {
598
- yield* Effect.logInfo(`${route.method} ${route.path} → ${status}`);
599
- }
600
- return status;
601
- }).pipe(Effect.withSpan(`${route.method} ${route.path}`));
602
- yield* activeConnections.pipe(Metric.set(0));
603
- const durationMs = yield* Random.nextIntBetween(5, 500);
604
- yield* Metric.update(httpRequestDuration, durationMs);
605
- return result;
606
- });
663
+ const route = yield* pick(routes)
664
+ const statusCodes = [200, 200, 200, 200, 200, 201, 204, 301, 400, 401, 404, 500]
665
+
666
+ yield* Metric.increment(httpRequestsTotal)
667
+ yield* Metric.increment(activeConnections)
668
+
669
+ const result = yield* Effect.gen(function* () {
670
+ yield* Effect.annotateCurrentSpan("http.method", route.method)
671
+ yield* Effect.annotateCurrentSpan("http.route", route.path)
672
+ yield* Effect.annotateCurrentSpan("handler", route.handler)
673
+
674
+ // middleware chain
675
+ yield* simulateAccessLog
676
+ yield* simulateCors
677
+ yield* simulateRateLimit
678
+ yield* simulateRequestParsing
679
+
680
+ // auth (most routes)
681
+ yield* maybe(70, simulateAuth)
682
+
683
+ // handler body pick a complex workflow or simple CRUD based on the route
684
+ const handler = route.handler
685
+ if (handler === "OrderController.create" || handler === "OrderController.updateStatus") {
686
+ yield* simulateOrderWorkflow
687
+ } else if (handler === "SearchController.query") {
688
+ yield* simulateSearchQuery
689
+ } else if (handler === "UploadController.image") {
690
+ yield* simulateFileUpload
691
+ } else if (handler === "AnalyticsController.dashboard") {
692
+ yield* simulateAnalyticsPipeline
693
+ } else if (handler === "WebhookController.stripe") {
694
+ yield* simulateInputValidation
695
+ yield* simulateExternalCallWithResilience
696
+ yield* simulateDbQuery
697
+ } else {
698
+ // standard CRUD
699
+ yield* maybe(50, simulateCache)
700
+ yield* simulateDbQuery
701
+ yield* maybe(25, simulateExternalCall)
702
+ yield* maybe(30, simulateDbQuery) // secondary query
703
+ }
704
+
705
+ // response pipeline
706
+ yield* simulateResponseSerialization
707
+ yield* maybe(40, simulateCompression)
708
+
709
+ const status = yield* pick(statusCodes)
710
+ yield* Effect.annotateCurrentSpan("http.status_code", status)
711
+
712
+ if (status >= 500) {
713
+ const err = yield* pick(errorMessages)
714
+ yield* Effect.logError(`${route.method} ${route.path} → ${status}: ${err}`)
715
+ return yield* Effect.fail(
716
+ new HttpError({
717
+ method: route.method,
718
+ path: route.path,
719
+ statusCode: status,
720
+ message: err,
721
+ }),
722
+ )
723
+ }
724
+
725
+ if (status >= 400) {
726
+ yield* Effect.logWarning(`${route.method} ${route.path} → ${status}`)
727
+ } else {
728
+ yield* Effect.logInfo(`${route.method} ${route.path} → ${status}`)
729
+ }
730
+
731
+ return status
732
+ }).pipe(Effect.withSpan(`${route.method} ${route.path}`))
733
+
734
+ yield* activeConnections.pipe(Metric.set(0))
735
+
736
+ const durationMs = yield* Random.nextIntBetween(5, 500)
737
+ yield* Metric.update(httpRequestDuration, durationMs)
738
+
739
+ return result
740
+ })
741
+
607
742
  // ---------------------------------------------------------------------------
608
743
  // Simulation loops
609
744
  // ---------------------------------------------------------------------------
745
+
610
746
  const requestLoop = Effect.gen(function* () {
611
- yield* Effect.logInfo("simulation: request loop started");
612
- yield* Effect.schedule(Effect.gen(function* () {
613
- const burst = yield* Random.nextIntBetween(1, 4);
614
- yield* Effect.forEach(Array.from({ length: burst }, (_, i) => i), () => simulateRequest.pipe(Effect.fork), { concurrency: "unbounded" });
615
- }), Schedule.jittered(Schedule.spaced("500 millis")));
616
- });
747
+ yield* Effect.logInfo("simulation: request loop started")
748
+ yield* Effect.schedule(
749
+ Effect.gen(function* () {
750
+ const burst = yield* Random.nextIntBetween(1, 4)
751
+ yield* Effect.forEach(
752
+ Array.from({ length: burst }, (_, i) => i),
753
+ () => simulateRequest.pipe(Effect.fork),
754
+ { concurrency: "unbounded" },
755
+ )
756
+ }),
757
+ Schedule.jittered(Schedule.spaced("500 millis")),
758
+ )
759
+ })
760
+
617
761
  const backgroundLoop = Effect.gen(function* () {
618
- yield* Effect.logInfo("simulation: background task loop started");
619
- yield* Effect.schedule(Effect.gen(function* () {
620
- yield* Effect.fork(simulateBackgroundTask);
621
- const depth = yield* Random.nextIntBetween(0, 25);
622
- yield* queueDepth.pipe(Metric.set(depth));
623
- }), Schedule.jittered(Schedule.spaced("2 seconds")));
624
- });
762
+ yield* Effect.logInfo("simulation: background task loop started")
763
+ yield* Effect.schedule(
764
+ Effect.gen(function* () {
765
+ yield* Effect.fork(simulateBackgroundTask)
766
+ const depth = yield* Random.nextIntBetween(0, 25)
767
+ yield* queueDepth.pipe(Metric.set(depth))
768
+ }),
769
+ Schedule.jittered(Schedule.spaced("2 seconds")),
770
+ )
771
+ })
772
+
625
773
  // ---------------------------------------------------------------------------
626
774
  // Public API
627
775
  // ---------------------------------------------------------------------------
628
- export const layer = Layer.scopedDiscard(Effect.gen(function* () {
629
- yield* Effect.logInfo("simulation layer starting");
630
- yield* Effect.forkScoped(requestLoop);
631
- yield* Effect.forkScoped(backgroundLoop);
632
- yield* Effect.logInfo("simulation layer ready");
633
- }));
776
+
777
+ export const layer = Layer.scopedDiscard(
778
+ Effect.gen(function* () {
779
+ yield* Effect.logInfo("simulation layer starting")
780
+ yield* Effect.forkScoped(requestLoop)
781
+ yield* Effect.forkScoped(backgroundLoop)
782
+ yield* Effect.logInfo("simulation layer ready")
783
+ }),
784
+ )