@effect-app/infra 4.0.0-beta.22 → 4.0.0-beta.220

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 (310) hide show
  1. package/CHANGELOG.md +1640 -0
  2. package/_check.sh +1 -1
  3. package/dist/CUPS.d.ts +7 -7
  4. package/dist/CUPS.d.ts.map +1 -1
  5. package/dist/CUPS.js +10 -12
  6. package/dist/Emailer/Sendgrid.d.ts +14 -14
  7. package/dist/Emailer/Sendgrid.d.ts.map +1 -1
  8. package/dist/Emailer/Sendgrid.js +16 -15
  9. package/dist/Emailer/fake.d.ts +1 -1
  10. package/dist/Emailer/service.d.ts +10 -4
  11. package/dist/Emailer/service.d.ts.map +1 -1
  12. package/dist/Emailer/service.js +3 -3
  13. package/dist/Emailer.d.ts +1 -1
  14. package/dist/MainFiberSet.d.ts +9 -9
  15. package/dist/MainFiberSet.d.ts.map +1 -1
  16. package/dist/MainFiberSet.js +3 -3
  17. package/dist/Model/Repository/Registry.d.ts +20 -0
  18. package/dist/Model/Repository/Registry.d.ts.map +1 -0
  19. package/dist/Model/Repository/Registry.js +17 -0
  20. package/dist/Model/Repository/ext.d.ts +33 -15
  21. package/dist/Model/Repository/ext.d.ts.map +1 -1
  22. package/dist/Model/Repository/ext.js +54 -2
  23. package/dist/Model/Repository/internal/internal.d.ts +6 -6
  24. package/dist/Model/Repository/internal/internal.d.ts.map +1 -1
  25. package/dist/Model/Repository/internal/internal.js +103 -51
  26. package/dist/Model/Repository/legacy.d.ts +1 -1
  27. package/dist/Model/Repository/makeRepo.d.ts +7 -6
  28. package/dist/Model/Repository/makeRepo.d.ts.map +1 -1
  29. package/dist/Model/Repository/makeRepo.js +5 -1
  30. package/dist/Model/Repository/service.d.ts +28 -23
  31. package/dist/Model/Repository/service.d.ts.map +1 -1
  32. package/dist/Model/Repository/validation.d.ts +46 -17
  33. package/dist/Model/Repository/validation.d.ts.map +1 -1
  34. package/dist/Model/Repository/validation.js +5 -5
  35. package/dist/Model/Repository.d.ts +2 -1
  36. package/dist/Model/Repository.d.ts.map +1 -1
  37. package/dist/Model/Repository.js +2 -1
  38. package/dist/Model/dsl.d.ts +4 -4
  39. package/dist/Model/dsl.d.ts.map +1 -1
  40. package/dist/Model/filter/filterApi.d.ts +5 -5
  41. package/dist/Model/filter/filterApi.d.ts.map +1 -1
  42. package/dist/Model/filter/types/errors.d.ts +1 -1
  43. package/dist/Model/filter/types/fields.d.ts +1 -1
  44. package/dist/Model/filter/types/path/common.d.ts +1 -1
  45. package/dist/Model/filter/types/path/eager.d.ts +1 -1
  46. package/dist/Model/filter/types/path/eager.d.ts.map +1 -1
  47. package/dist/Model/filter/types/path/eager.js +1 -1
  48. package/dist/Model/filter/types/path/index.d.ts +1 -1
  49. package/dist/Model/filter/types/utils.d.ts +1 -1
  50. package/dist/Model/filter/types/validator.d.ts +1 -1
  51. package/dist/Model/filter/types.d.ts +1 -1
  52. package/dist/Model/query/dsl.d.ts +139 -16
  53. package/dist/Model/query/dsl.d.ts.map +1 -1
  54. package/dist/Model/query/dsl.js +187 -1
  55. package/dist/Model/query/new-kid-interpreter.d.ts +76 -7
  56. package/dist/Model/query/new-kid-interpreter.d.ts.map +1 -1
  57. package/dist/Model/query/new-kid-interpreter.js +122 -6
  58. package/dist/Model/query.d.ts +1 -1
  59. package/dist/Model.d.ts +2 -1
  60. package/dist/Model.d.ts.map +1 -1
  61. package/dist/Model.js +2 -1
  62. package/dist/QueueMaker/SQLQueue.d.ts +5 -7
  63. package/dist/QueueMaker/SQLQueue.d.ts.map +1 -1
  64. package/dist/QueueMaker/SQLQueue.js +130 -116
  65. package/dist/QueueMaker/errors.d.ts +2 -2
  66. package/dist/QueueMaker/errors.d.ts.map +1 -1
  67. package/dist/QueueMaker/memQueue.d.ts +7 -4
  68. package/dist/QueueMaker/memQueue.d.ts.map +1 -1
  69. package/dist/QueueMaker/memQueue.js +75 -63
  70. package/dist/QueueMaker/sbqueue.d.ts +6 -3
  71. package/dist/QueueMaker/sbqueue.d.ts.map +1 -1
  72. package/dist/QueueMaker/sbqueue.js +52 -53
  73. package/dist/QueueMaker/service.d.ts +1 -1
  74. package/dist/RequestContext.d.ts +74 -35
  75. package/dist/RequestContext.d.ts.map +1 -1
  76. package/dist/RequestContext.js +13 -14
  77. package/dist/RequestFiberSet.d.ts +7 -7
  78. package/dist/RequestFiberSet.d.ts.map +1 -1
  79. package/dist/RequestFiberSet.js +3 -3
  80. package/dist/Store/ContextMapContainer.d.ts +19 -3
  81. package/dist/Store/ContextMapContainer.d.ts.map +1 -1
  82. package/dist/Store/ContextMapContainer.js +13 -3
  83. package/dist/Store/Cosmos/query.d.ts +5 -1
  84. package/dist/Store/Cosmos/query.d.ts.map +1 -1
  85. package/dist/Store/Cosmos/query.js +113 -34
  86. package/dist/Store/Cosmos.d.ts +1 -1
  87. package/dist/Store/Cosmos.d.ts.map +1 -1
  88. package/dist/Store/Cosmos.js +335 -243
  89. package/dist/Store/Disk.d.ts +2 -2
  90. package/dist/Store/Disk.d.ts.map +1 -1
  91. package/dist/Store/Disk.js +72 -35
  92. package/dist/Store/Memory.d.ts +6 -4
  93. package/dist/Store/Memory.d.ts.map +1 -1
  94. package/dist/Store/Memory.js +242 -58
  95. package/dist/Store/SQL/Pg.d.ts +4 -0
  96. package/dist/Store/SQL/Pg.d.ts.map +1 -0
  97. package/dist/Store/SQL/Pg.js +231 -0
  98. package/dist/Store/SQL/query.d.ts +42 -0
  99. package/dist/Store/SQL/query.d.ts.map +1 -0
  100. package/dist/Store/SQL/query.js +479 -0
  101. package/dist/Store/SQL.d.ts +20 -0
  102. package/dist/Store/SQL.d.ts.map +1 -0
  103. package/dist/Store/SQL.js +446 -0
  104. package/dist/Store/codeFilter.d.ts +1 -1
  105. package/dist/Store/codeFilter.d.ts.map +1 -1
  106. package/dist/Store/codeFilter.js +4 -2
  107. package/dist/Store/index.d.ts +5 -2
  108. package/dist/Store/index.d.ts.map +1 -1
  109. package/dist/Store/index.js +15 -3
  110. package/dist/Store/service.d.ts +22 -8
  111. package/dist/Store/service.d.ts.map +1 -1
  112. package/dist/Store/service.js +24 -6
  113. package/dist/Store/utils.d.ts +1 -1
  114. package/dist/Store/utils.d.ts.map +1 -1
  115. package/dist/Store/utils.js +3 -4
  116. package/dist/Store.d.ts +1 -1
  117. package/dist/adapters/SQL/Model.d.ts +31 -42
  118. package/dist/adapters/SQL/Model.d.ts.map +1 -1
  119. package/dist/adapters/SQL/Model.js +29 -38
  120. package/dist/adapters/SQL.d.ts +1 -1
  121. package/dist/adapters/ServiceBus.d.ts +11 -11
  122. package/dist/adapters/ServiceBus.d.ts.map +1 -1
  123. package/dist/adapters/ServiceBus.js +25 -21
  124. package/dist/adapters/cosmos-client.d.ts +3 -3
  125. package/dist/adapters/cosmos-client.d.ts.map +1 -1
  126. package/dist/adapters/cosmos-client.js +3 -3
  127. package/dist/adapters/index.d.ts +8 -2
  128. package/dist/adapters/index.d.ts.map +1 -1
  129. package/dist/adapters/index.js +8 -2
  130. package/dist/adapters/logger.d.ts +1 -1
  131. package/dist/adapters/logger.d.ts.map +1 -1
  132. package/dist/adapters/memQueue.d.ts +3 -3
  133. package/dist/adapters/memQueue.d.ts.map +1 -1
  134. package/dist/adapters/memQueue.js +3 -3
  135. package/dist/adapters/mongo-client.d.ts +3 -3
  136. package/dist/adapters/mongo-client.d.ts.map +1 -1
  137. package/dist/adapters/mongo-client.js +3 -3
  138. package/dist/adapters/redis-client.d.ts +3 -3
  139. package/dist/adapters/redis-client.d.ts.map +1 -1
  140. package/dist/adapters/redis-client.js +3 -3
  141. package/dist/api/ContextProvider.d.ts +8 -8
  142. package/dist/api/ContextProvider.d.ts.map +1 -1
  143. package/dist/api/ContextProvider.js +6 -6
  144. package/dist/api/codec.d.ts +1 -1
  145. package/dist/api/internal/RequestContextMiddleware.d.ts +2 -2
  146. package/dist/api/internal/RequestContextMiddleware.d.ts.map +1 -1
  147. package/dist/api/internal/RequestContextMiddleware.js +9 -6
  148. package/dist/api/internal/auth.d.ts +44 -6
  149. package/dist/api/internal/auth.d.ts.map +1 -1
  150. package/dist/api/internal/auth.js +160 -29
  151. package/dist/api/internal/events.d.ts +3 -3
  152. package/dist/api/internal/events.d.ts.map +1 -1
  153. package/dist/api/internal/events.js +10 -8
  154. package/dist/api/internal/health.d.ts +1 -1
  155. package/dist/api/layerUtils.d.ts +6 -6
  156. package/dist/api/layerUtils.d.ts.map +1 -1
  157. package/dist/api/layerUtils.js +5 -5
  158. package/dist/api/middlewares.d.ts +1 -1
  159. package/dist/api/reportError.d.ts +1 -1
  160. package/dist/api/routing/middleware/RouterMiddleware.d.ts +4 -4
  161. package/dist/api/routing/middleware/RouterMiddleware.d.ts.map +1 -1
  162. package/dist/api/routing/middleware/middleware.d.ts +39 -3
  163. package/dist/api/routing/middleware/middleware.d.ts.map +1 -1
  164. package/dist/api/routing/middleware/middleware.js +48 -16
  165. package/dist/api/routing/middleware.d.ts +1 -2
  166. package/dist/api/routing/middleware.d.ts.map +1 -1
  167. package/dist/api/routing/middleware.js +1 -2
  168. package/dist/api/routing/schema/jwt.d.ts +1 -1
  169. package/dist/api/routing/schema/jwt.d.ts.map +1 -1
  170. package/dist/api/routing/tsort.d.ts +1 -1
  171. package/dist/api/routing/tsort.d.ts.map +1 -1
  172. package/dist/api/routing/utils.d.ts +3 -3
  173. package/dist/api/routing/utils.d.ts.map +1 -1
  174. package/dist/api/routing.d.ts +80 -37
  175. package/dist/api/routing.d.ts.map +1 -1
  176. package/dist/api/routing.js +109 -41
  177. package/dist/api/setupRequest.d.ts +8 -5
  178. package/dist/api/setupRequest.d.ts.map +1 -1
  179. package/dist/api/setupRequest.js +12 -7
  180. package/dist/api/util.d.ts +1 -1
  181. package/dist/arbs.d.ts +1 -1
  182. package/dist/arbs.d.ts.map +1 -1
  183. package/dist/arbs.js +5 -3
  184. package/dist/errorReporter.d.ts +4 -4
  185. package/dist/errorReporter.d.ts.map +1 -1
  186. package/dist/errorReporter.js +20 -25
  187. package/dist/errors.d.ts +1 -1
  188. package/dist/fileUtil.d.ts +1 -1
  189. package/dist/fileUtil.d.ts.map +1 -1
  190. package/dist/index.d.ts +1 -1
  191. package/dist/logger/jsonLogger.d.ts +1 -1
  192. package/dist/logger/logFmtLogger.d.ts +1 -1
  193. package/dist/logger/shared.d.ts +1 -1
  194. package/dist/logger/shared.js +2 -2
  195. package/dist/logger.d.ts +1 -1
  196. package/dist/logger.d.ts.map +1 -1
  197. package/dist/otel.d.ts +75 -0
  198. package/dist/otel.d.ts.map +1 -0
  199. package/dist/otel.js +65 -0
  200. package/dist/rateLimit.d.ts +9 -3
  201. package/dist/rateLimit.d.ts.map +1 -1
  202. package/dist/rateLimit.js +5 -11
  203. package/dist/test.d.ts +2 -2
  204. package/dist/test.d.ts.map +1 -1
  205. package/dist/test.js +1 -1
  206. package/dist/vitest.d.ts +1 -1
  207. package/examples/query.ts +42 -38
  208. package/package.json +46 -37
  209. package/src/CUPS.ts +9 -11
  210. package/src/Emailer/Sendgrid.ts +17 -14
  211. package/src/Emailer/service.ts +9 -3
  212. package/src/MainFiberSet.ts +5 -6
  213. package/src/Model/Repository/Registry.ts +33 -0
  214. package/src/Model/Repository/ext.ts +96 -10
  215. package/src/Model/Repository/internal/internal.ts +218 -149
  216. package/src/Model/Repository/makeRepo.ts +12 -10
  217. package/src/Model/Repository/service.ts +31 -22
  218. package/src/Model/Repository/validation.ts +4 -4
  219. package/src/Model/Repository.ts +1 -0
  220. package/src/Model/dsl.ts +3 -3
  221. package/src/Model/filter/types/path/eager.ts +1 -2
  222. package/src/Model/query/dsl.ts +348 -18
  223. package/src/Model/query/new-kid-interpreter.ts +206 -6
  224. package/src/Model.ts +1 -0
  225. package/src/QueueMaker/SQLQueue.ts +144 -152
  226. package/src/QueueMaker/memQueue.ts +104 -103
  227. package/src/QueueMaker/sbqueue.ts +70 -86
  228. package/src/RequestContext.ts +14 -16
  229. package/src/RequestFiberSet.ts +2 -2
  230. package/src/Store/ContextMapContainer.ts +41 -2
  231. package/src/Store/Cosmos/query.ts +140 -43
  232. package/src/Store/Cosmos.ts +482 -349
  233. package/src/Store/Disk.ts +102 -65
  234. package/src/Store/Memory.ts +275 -87
  235. package/src/Store/SQL/Pg.ts +361 -0
  236. package/src/Store/SQL/query.ts +539 -0
  237. package/src/Store/SQL.ts +731 -0
  238. package/src/Store/codeFilter.ts +3 -1
  239. package/src/Store/index.ts +17 -2
  240. package/src/Store/service.ts +41 -10
  241. package/src/Store/utils.ts +23 -22
  242. package/src/adapters/SQL/Model.ts +41 -40
  243. package/src/adapters/ServiceBus.ts +125 -121
  244. package/src/adapters/cosmos-client.ts +2 -2
  245. package/src/adapters/index.ts +7 -0
  246. package/src/adapters/memQueue.ts +2 -2
  247. package/src/adapters/mongo-client.ts +2 -2
  248. package/src/adapters/redis-client.ts +2 -2
  249. package/src/api/ContextProvider.ts +12 -13
  250. package/src/api/internal/RequestContextMiddleware.ts +15 -5
  251. package/src/api/internal/auth.ts +246 -44
  252. package/src/api/internal/events.ts +13 -9
  253. package/src/api/layerUtils.ts +8 -8
  254. package/src/api/routing/middleware/RouterMiddleware.ts +4 -4
  255. package/src/api/routing/middleware/middleware.ts +55 -14
  256. package/src/api/routing/middleware.ts +0 -2
  257. package/src/api/routing.ts +296 -131
  258. package/src/api/setupRequest.ts +28 -8
  259. package/src/arbs.ts +4 -2
  260. package/src/errorReporter.ts +62 -74
  261. package/src/logger/shared.ts +1 -1
  262. package/src/otel.ts +152 -0
  263. package/src/rateLimit.ts +30 -22
  264. package/src/test.ts +1 -1
  265. package/test/auth.test.ts +101 -0
  266. package/test/contextProvider.test.ts +11 -11
  267. package/test/controller.test.ts +21 -30
  268. package/test/dist/auth.test.d.ts.map +1 -0
  269. package/test/dist/contextProvider.test.d.ts.map +1 -1
  270. package/test/dist/controller.test.d.ts.map +1 -1
  271. package/test/dist/date-query.test.d.ts.map +1 -0
  272. package/test/dist/fixtures.d.ts +26 -12
  273. package/test/dist/fixtures.d.ts.map +1 -1
  274. package/test/dist/fixtures.js +12 -10
  275. package/test/dist/query.test.d.ts.map +1 -1
  276. package/test/dist/rawQuery.test.d.ts.map +1 -1
  277. package/test/dist/repository-ext.test.d.ts.map +1 -0
  278. package/test/dist/requires.test.d.ts.map +1 -1
  279. package/test/dist/router-generator.test.d.ts.map +1 -0
  280. package/test/dist/routing-interruptibility.test.d.ts.map +1 -0
  281. package/test/dist/rpc-e2e-invalidation.test.d.ts.map +1 -0
  282. package/test/dist/rpc-multi-middleware.test.d.ts.map +1 -1
  283. package/test/dist/rpc-stream-fullstack.test.d.ts.map +1 -0
  284. package/test/dist/sql-store.test.d.ts.map +1 -0
  285. package/test/fixtures.ts +11 -9
  286. package/test/query.test.ts +813 -38
  287. package/test/rawQuery.test.ts +301 -20
  288. package/test/repository-ext.test.ts +60 -0
  289. package/test/requires.test.ts +6 -6
  290. package/test/router-generator.test.ts +183 -0
  291. package/test/routing-interruptibility.test.ts +63 -0
  292. package/test/rpc-e2e-invalidation.test.ts +251 -0
  293. package/test/rpc-multi-middleware.test.ts +78 -9
  294. package/test/rpc-stream-fullstack.test.ts +300 -0
  295. package/test/sql-store.test.ts +1592 -0
  296. package/test/validateSample.test.ts +15 -12
  297. package/tsconfig.examples.json +1 -1
  298. package/tsconfig.json +0 -1
  299. package/tsconfig.json.bak +2 -2
  300. package/tsconfig.src.json +35 -35
  301. package/tsconfig.test.json +2 -2
  302. package/dist/Operations.d.ts +0 -55
  303. package/dist/Operations.d.ts.map +0 -1
  304. package/dist/Operations.js +0 -102
  305. package/dist/OperationsRepo.d.ts +0 -41
  306. package/dist/OperationsRepo.d.ts.map +0 -1
  307. package/dist/OperationsRepo.js +0 -14
  308. package/eslint.config.mjs +0 -24
  309. package/src/Operations.ts +0 -235
  310. package/src/OperationsRepo.ts +0 -16
@@ -0,0 +1,300 @@
1
+ /**
2
+ * Full-stack stream test exercising the entire wrapper:
3
+ * resources (TaggedRequestFor)
4
+ * → controllers (Router(...)({ effect }))
5
+ * → router (makeRouter / matchAll)
6
+ * → api ClientFactory (ApiClientFactory.makeFor)
7
+ *
8
+ * Server runs over real HTTP (NodeHttpServer on a loopback port). Client uses
9
+ * FetchHttpClient through ApiClientFactory. This covers the wrapper-level
10
+ * `Stream` request constructor end-to-end.
11
+ */
12
+ import { NodeHttpServer } from "@effect/platform-node"
13
+ import { expect, it } from "@effect/vitest"
14
+ import { Effect, Exit, Layer, Option, Stream } from "effect"
15
+ import { S } from "effect-app"
16
+ import { ApiClientFactory, makeRpcClient } from "effect-app/client"
17
+ import { HttpRouter, HttpServer } from "effect-app/http"
18
+ import { DefaultGenericMiddlewares } from "effect-app/middleware"
19
+ import { MiddlewareMaker } from "effect-app/rpc"
20
+ import { TaggedErrorClass } from "effect-app/Schema"
21
+ import { FetchHttpClient } from "effect/unstable/http"
22
+ import { RpcSerialization } from "effect/unstable/rpc"
23
+ import { createServer } from "http"
24
+ import { makeRouter } from "../src/api/routing.js"
25
+ import { DefaultGenericMiddlewaresLive } from "../src/api/routing/middleware.js"
26
+ import { AllowAnonymous, AllowAnonymousLive, RequestContextMap, RequireRoles, RequireRolesLive, SomeElseMiddleware, SomeElseMiddlewareLive, SomeService, Test, TestLive } from "./fixtures.js"
27
+
28
+ // ---------------------------------------------------------------------------
29
+ // Middleware (mirrors the boilerplate AppMiddleware shape).
30
+ // ---------------------------------------------------------------------------
31
+
32
+ class AppMiddleware extends MiddlewareMaker
33
+ .Tag<AppMiddleware>()("AppMiddleware", RequestContextMap)
34
+ .middleware(RequireRoles, Test)
35
+ .middleware(AllowAnonymous)
36
+ .middleware(SomeElseMiddleware)
37
+ .middleware(...DefaultGenericMiddlewares)
38
+ {
39
+ static Default = this.layer.pipe(
40
+ Layer.provide(
41
+ [
42
+ RequireRolesLive.pipe(Layer.provide(SomeService.Default)),
43
+ AllowAnonymousLive,
44
+ TestLive,
45
+ SomeElseMiddlewareLive,
46
+ DefaultGenericMiddlewaresLive
47
+ ] as const
48
+ )
49
+ )
50
+ }
51
+
52
+ const { Router, matchAll } = makeRouter(AppMiddleware.Default)
53
+
54
+ // ---------------------------------------------------------------------------
55
+ // Resources — Stream with and without payload.
56
+ // ---------------------------------------------------------------------------
57
+
58
+ const { TaggedRequestFor } = makeRpcClient(AppMiddleware)
59
+ const Req = TaggedRequestFor("Streamy")
60
+
61
+ class StreamTicks extends Req.Command<StreamTicks>()("StreamTicks", {}, {
62
+ stream: true,
63
+ allowAnonymous: true,
64
+ success: S.Number
65
+ }) {}
66
+
67
+ class StreamCountTo extends Req.Command<StreamCountTo>()("StreamCountTo", {
68
+ to: S.Number
69
+ }, {
70
+ stream: true,
71
+ allowAnonymous: true,
72
+ success: S.Number
73
+ }) {}
74
+
75
+ class StreamRealtime extends Req.Command<StreamRealtime>()("StreamRealtime", {}, {
76
+ stream: true,
77
+ allowAnonymous: true,
78
+ success: S.Number
79
+ }) {}
80
+
81
+ class StreamBoom extends TaggedErrorClass<StreamBoom>()("StreamBoom", { reason: S.String }) {}
82
+
83
+ class StreamFailStream extends Req.Command<StreamFailStream>()("StreamFailStream", {}, {
84
+ stream: true,
85
+ allowAnonymous: true,
86
+ success: S.Number,
87
+ error: StreamBoom
88
+ }) {}
89
+
90
+ class StreamNoSuccess extends Req.Command<StreamNoSuccess>()("StreamNoSuccess", {}, {
91
+ stream: true,
92
+ allowAnonymous: true
93
+ }) {}
94
+
95
+ // Defaults to allowAnonymous: false → AllowAnonymous middleware fails with NotLoggedInError
96
+ // when the request lacks the `x-user` header.
97
+ class StreamRequiresAuth extends Req.Command<StreamRequiresAuth>()("StreamRequiresAuth", {}, {
98
+ stream: true,
99
+ success: S.Number
100
+ }) {}
101
+
102
+ class CommandRequiresAuth extends Req.Command<CommandRequiresAuth>()("CommandRequiresAuth", {}, {
103
+ success: S.Number
104
+ }) {}
105
+
106
+ class QueryRequiresAuth extends Req.Query<QueryRequiresAuth>()("QueryRequiresAuth", {}, {
107
+ success: S.Number
108
+ }) {}
109
+
110
+ const StreamyRsc = {
111
+ StreamTicks,
112
+ StreamCountTo,
113
+ StreamRealtime,
114
+ StreamFailStream,
115
+ StreamNoSuccess,
116
+ StreamRequiresAuth,
117
+ CommandRequiresAuth,
118
+ QueryRequiresAuth
119
+ }
120
+
121
+ // ---------------------------------------------------------------------------
122
+ // Controllers / router — Stream impls returned from the match callback.
123
+ // ---------------------------------------------------------------------------
124
+
125
+ const router = Router(StreamyRsc)({
126
+ *effect(match) {
127
+ return match({
128
+ StreamTicks: () => Stream.fromIterable([10, 20, 30]),
129
+ StreamCountTo: ({ to }: { readonly to: number }) =>
130
+ Effect
131
+ .gen(function*() {
132
+ return Stream.range(1, to)
133
+ })
134
+ .pipe(Stream.unwrap),
135
+ // emits 3 values 100ms apart so the test can prove element-by-element
136
+ // delivery rather than a single batched response
137
+ StreamRealtime: () =>
138
+ Stream.fromIterable([1, 2, 3]).pipe(
139
+ Stream.mapEffect((n) => Effect.sleep("100 millis").pipe(Effect.as(n)))
140
+ ),
141
+ StreamFailStream: () => Stream.fail(new StreamBoom({ reason: "from-stream" })),
142
+ StreamNoSuccess: () => Stream.empty,
143
+ // handlers below are unreachable when middleware-auth fails; bodies exist
144
+ // only so the resource type-checks
145
+ StreamRequiresAuth: () => Stream.fromIterable([1, 2, 3]),
146
+ CommandRequiresAuth: () => Effect.succeed(1),
147
+ QueryRequiresAuth: () => Effect.succeed(1)
148
+ })
149
+ }
150
+ })
151
+
152
+ const RpcRouterLayer = matchAll({ router })
153
+
154
+ // ---------------------------------------------------------------------------
155
+ // HTTP wiring — real server on a loopback port + FetchHttpClient on the client.
156
+ // ---------------------------------------------------------------------------
157
+
158
+ // Server binds an ephemeral port (port: 0). The actual URL is read from the
159
+ // `HttpServer` service after binding, then fed into `ApiClientFactory.layer` so
160
+ // each `it.live` scope gets a fresh server without colliding on a fixed port.
161
+ const NodeServerLayer = NodeHttpServer.layer(() => createServer(), { port: 0 })
162
+
163
+ const ServerLayer = HttpRouter
164
+ .serve(RpcRouterLayer)
165
+ .pipe(
166
+ Layer.provide(NodeServerLayer),
167
+ Layer.provide(RpcSerialization.layerNdjson)
168
+ )
169
+
170
+ const ClientLayer = Layer
171
+ .unwrap(
172
+ Effect.gen(function*() {
173
+ const server = yield* HttpServer.HttpServer
174
+ const addr = server.address
175
+ if (addr._tag !== "TcpAddress") return yield* Effect.die(new Error("expected TcpAddress"))
176
+ const host = addr.hostname === "0.0.0.0" ? "127.0.0.1" : addr.hostname
177
+ const url = `http://${host}:${addr.port}`
178
+ return ApiClientFactory
179
+ .layer({ url, headers: Option.none() })
180
+ .pipe(Layer.provide(FetchHttpClient.layer))
181
+ })
182
+ )
183
+ .pipe(Layer.provide(NodeServerLayer))
184
+
185
+ const TestLayer = Layer.mergeAll(ServerLayer, ClientLayer)
186
+
187
+ // ---------------------------------------------------------------------------
188
+ // Tests
189
+ // ---------------------------------------------------------------------------
190
+
191
+ it.live(
192
+ "stream resource without input: ApiClientFactory client emits all values",
193
+ Effect.fnUntraced(function*() {
194
+ const client = yield* ApiClientFactory.makeFor(Layer.empty)(StreamyRsc)
195
+ const values = yield* Stream.runCollect(client.StreamTicks.handler())
196
+ expect(values).toStrictEqual([10, 20, 30])
197
+ }, Effect.provide(TestLayer)),
198
+ { timeout: 10_000 }
199
+ )
200
+
201
+ it.live(
202
+ "stream resource with input: payload drives the emitted values",
203
+ Effect.fnUntraced(function*() {
204
+ const client = yield* ApiClientFactory.makeFor(Layer.empty)(StreamyRsc)
205
+ const values = yield* Stream.runCollect(client.StreamCountTo.handler({ to: 4 }))
206
+ expect(values).toStrictEqual([1, 2, 3, 4])
207
+ }, Effect.provide(TestLayer)),
208
+ { timeout: 10_000 }
209
+ )
210
+
211
+ it.live(
212
+ "stream resource is delivered element-by-element in real time (not batched)",
213
+ Effect.fnUntraced(function*() {
214
+ const client = yield* ApiClientFactory.makeFor(Layer.empty)(StreamyRsc)
215
+ const start = Date.now()
216
+ const arrivals = yield* Stream.runCollect(
217
+ client.StreamRealtime.handler().pipe(
218
+ Stream.map((n) => ({ n, at: Date.now() - start }))
219
+ )
220
+ )
221
+ expect(arrivals.map((_) => _.n)).toStrictEqual([1, 2, 3])
222
+ // server emits each value 100ms after the previous one. If the response
223
+ // were batched, deltas would be ~0ms. Allow generous slack for CI jitter
224
+ // but require clear separation between consecutive arrivals.
225
+ const delta1 = arrivals[1]!.at - arrivals[0]!.at
226
+ const delta2 = arrivals[2]!.at - arrivals[1]!.at
227
+ expect(delta1).toBeGreaterThan(50)
228
+ expect(delta2).toBeGreaterThan(50)
229
+ // first element should not be withheld until the whole stream completes
230
+ expect(arrivals[0]!.at).toBeLessThan(arrivals[2]!.at - 50)
231
+ }, Effect.provide(TestLayer)),
232
+ { timeout: 10_000 }
233
+ )
234
+
235
+ it.live(
236
+ "stream handler returning Stream.fail surfaces as failing stream on client",
237
+ Effect.fnUntraced(function*() {
238
+ const client = yield* ApiClientFactory.makeFor(Layer.empty)(StreamyRsc)
239
+ const exit = yield* Stream.runCollect(client.StreamFailStream.handler()).pipe(Effect.exit)
240
+ expect(Exit.isFailure(exit)).toBe(true)
241
+ if (Exit.isFailure(exit)) {
242
+ const failures = (exit.cause as any).reasons as ReadonlyArray<{ _tag: "Fail"; error: StreamBoom }>
243
+ expect(failures.length).toBeGreaterThan(0)
244
+ expect(failures[0]!.error._tag).toBe("StreamBoom")
245
+ expect(failures[0]!.error.reason).toBe("from-stream")
246
+ }
247
+ }, Effect.provide(TestLayer)),
248
+ { timeout: 10_000 }
249
+ )
250
+
251
+ it.live(
252
+ "stream resource without `success` exposes handler as a Stream on the client",
253
+ Effect.fnUntraced(function*() {
254
+ const client = yield* ApiClientFactory.makeFor(Layer.empty)(StreamyRsc)
255
+ const exit = yield* Stream.runCollect(client.StreamNoSuccess.handler()).pipe(Effect.exit)
256
+ expect(Exit.isSuccess(exit)).toBe(true)
257
+ }, Effect.provide(TestLayer)),
258
+ { timeout: 10_000 }
259
+ )
260
+
261
+ const expectNotLoggedIn = (exit: Exit.Exit<unknown, unknown>) => {
262
+ expect(Exit.isFailure(exit)).toBe(true)
263
+ if (!Exit.isFailure(exit)) return
264
+ const failures = (exit.cause as any).reasons as ReadonlyArray<{ _tag: "Fail"; error: any }>
265
+ expect(failures.length).toBeGreaterThan(0)
266
+ // The bug surfaces here as a SchemaError ("Expected never | { _tag: 'error', ... }")
267
+ // because the middleware-thrown NotLoggedInError doesn't match the
268
+ // wire StreamFailureChunk / CommandFailureWithMetaData wrapping.
269
+ expect(failures[0]!.error?._tag).toBe("NotLoggedInError")
270
+ }
271
+
272
+ it.live(
273
+ "stream resource: middleware-emitted NotLoggedInError surfaces cleanly on the client",
274
+ Effect.fnUntraced(function*() {
275
+ const client = yield* ApiClientFactory.makeFor(Layer.empty)(StreamyRsc)
276
+ const exit = yield* Stream.runCollect(client.StreamRequiresAuth.handler()).pipe(Effect.exit)
277
+ expectNotLoggedIn(exit)
278
+ }, Effect.provide(TestLayer)),
279
+ { timeout: 10_000 }
280
+ )
281
+
282
+ it.live(
283
+ "command resource: middleware-emitted NotLoggedInError surfaces cleanly on the client",
284
+ Effect.fnUntraced(function*() {
285
+ const client = yield* ApiClientFactory.makeFor(Layer.empty)(StreamyRsc)
286
+ const exit = yield* client.CommandRequiresAuth.handler().pipe(Effect.exit)
287
+ expectNotLoggedIn(exit)
288
+ }, Effect.provide(TestLayer)),
289
+ { timeout: 10_000 }
290
+ )
291
+
292
+ it.live(
293
+ "query resource: middleware-emitted NotLoggedInError surfaces cleanly on the client",
294
+ Effect.fnUntraced(function*() {
295
+ const client = yield* ApiClientFactory.makeFor(Layer.empty)(StreamyRsc)
296
+ const exit = yield* client.QueryRequiresAuth.handler().pipe(Effect.exit)
297
+ expectNotLoggedIn(exit)
298
+ }, Effect.provide(TestLayer)),
299
+ { timeout: 10_000 }
300
+ )