@effect-app/infra 4.0.0-beta.2 → 4.0.0-beta.200

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