effect-app 4.0.0-beta.27 → 4.0.0-beta.272

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 (342) hide show
  1. package/CHANGELOG.md +1275 -0
  2. package/dist/Array.d.ts +2 -1
  3. package/dist/Array.d.ts.map +1 -1
  4. package/dist/Array.js +4 -4
  5. package/dist/Chunk.d.ts.map +1 -1
  6. package/dist/Config/SecretURL.d.ts +3 -1
  7. package/dist/Config/SecretURL.d.ts.map +1 -1
  8. package/dist/Config/SecretURL.js +3 -6
  9. package/dist/Config/internal/configSecretURL.d.ts.map +1 -1
  10. package/dist/Config/internal/configSecretURL.js +2 -2
  11. package/dist/Config.d.ts +2 -0
  12. package/dist/Config.d.ts.map +1 -0
  13. package/dist/Config.js +8 -0
  14. package/dist/ConfigProvider.d.ts +2 -0
  15. package/dist/ConfigProvider.d.ts.map +1 -0
  16. package/dist/ConfigProvider.js +6 -0
  17. package/dist/{ServiceMap.d.ts → Context.d.ts} +18 -20
  18. package/dist/Context.d.ts.map +1 -0
  19. package/dist/Context.js +67 -0
  20. package/dist/Effect.d.ts +10 -9
  21. package/dist/Effect.d.ts.map +1 -1
  22. package/dist/Effect.js +5 -8
  23. package/dist/Emailer.d.ts +51 -0
  24. package/dist/Emailer.d.ts.map +1 -0
  25. package/dist/Emailer.js +7 -0
  26. package/dist/Function.d.ts.map +1 -1
  27. package/dist/Layer.d.ts +10 -6
  28. package/dist/Layer.d.ts.map +1 -1
  29. package/dist/Layer.js +3 -2
  30. package/dist/Model/Repository/Registry.d.ts +22 -0
  31. package/dist/Model/Repository/Registry.d.ts.map +1 -0
  32. package/dist/Model/Repository/Registry.js +18 -0
  33. package/dist/Model/Repository/ext.d.ts +60 -0
  34. package/dist/Model/Repository/ext.d.ts.map +1 -0
  35. package/dist/Model/Repository/ext.js +122 -0
  36. package/dist/Model/Repository/internal/internal.d.ts +63 -0
  37. package/dist/Model/Repository/internal/internal.d.ts.map +1 -0
  38. package/dist/Model/Repository/internal/internal.js +430 -0
  39. package/dist/Model/Repository/legacy.d.ts +21 -0
  40. package/dist/Model/Repository/legacy.d.ts.map +1 -0
  41. package/dist/Model/Repository/legacy.js +2 -0
  42. package/dist/Model/Repository/makeRepo.d.ts +54 -0
  43. package/dist/Model/Repository/makeRepo.d.ts.map +1 -0
  44. package/dist/Model/Repository/makeRepo.js +27 -0
  45. package/dist/Model/Repository/service.d.ts +121 -0
  46. package/dist/Model/Repository/service.d.ts.map +1 -0
  47. package/dist/Model/Repository/service.js +2 -0
  48. package/dist/Model/Repository/validation.d.ts +58 -0
  49. package/dist/Model/Repository/validation.d.ts.map +1 -0
  50. package/dist/Model/Repository/validation.js +32 -0
  51. package/dist/Model/Repository.d.ts +7 -0
  52. package/dist/Model/Repository.d.ts.map +1 -0
  53. package/dist/Model/Repository.js +7 -0
  54. package/dist/Model/dsl.d.ts +33 -0
  55. package/dist/Model/dsl.d.ts.map +1 -0
  56. package/dist/Model/dsl.js +43 -0
  57. package/dist/Model/filter/filterApi.d.ts +30 -0
  58. package/dist/Model/filter/filterApi.d.ts.map +1 -0
  59. package/dist/Model/filter/filterApi.js +2 -0
  60. package/dist/Model/filter/types/errors.d.ts +29 -0
  61. package/dist/Model/filter/types/errors.d.ts.map +1 -0
  62. package/dist/Model/filter/types/errors.js +2 -0
  63. package/dist/Model/filter/types/fields.d.ts +15 -0
  64. package/dist/Model/filter/types/fields.d.ts.map +1 -0
  65. package/dist/Model/filter/types/fields.js +2 -0
  66. package/dist/Model/filter/types/path/common.d.ts +316 -0
  67. package/dist/Model/filter/types/path/common.d.ts.map +1 -0
  68. package/dist/Model/filter/types/path/common.js +2 -0
  69. package/dist/Model/filter/types/path/eager.d.ts +94 -0
  70. package/dist/Model/filter/types/path/eager.d.ts.map +1 -0
  71. package/dist/Model/filter/types/path/eager.js +36 -0
  72. package/dist/Model/filter/types/path/index.d.ts +4 -0
  73. package/dist/Model/filter/types/path/index.d.ts.map +1 -0
  74. package/dist/Model/filter/types/path/index.js +3 -0
  75. package/dist/Model/filter/types/utils.d.ts +79 -0
  76. package/dist/Model/filter/types/utils.d.ts.map +1 -0
  77. package/dist/Model/filter/types/utils.js +2 -0
  78. package/dist/Model/filter/types/validator.d.ts +30 -0
  79. package/dist/Model/filter/types/validator.d.ts.map +1 -0
  80. package/dist/Model/filter/types/validator.js +2 -0
  81. package/dist/Model/filter/types.d.ts +5 -0
  82. package/dist/Model/filter/types.d.ts.map +1 -0
  83. package/dist/Model/filter/types.js +7 -0
  84. package/dist/Model/query/dsl.d.ts +493 -0
  85. package/dist/Model/query/dsl.d.ts.map +1 -0
  86. package/dist/Model/query/dsl.js +376 -0
  87. package/dist/Model/query/new-kid-interpreter.d.ts +136 -0
  88. package/dist/Model/query/new-kid-interpreter.d.ts.map +1 -0
  89. package/dist/Model/query/new-kid-interpreter.js +336 -0
  90. package/dist/Model/query.d.ts +15 -0
  91. package/dist/Model/query.d.ts.map +1 -0
  92. package/dist/Model/query.js +3 -0
  93. package/dist/Model.d.ts +5 -0
  94. package/dist/Model.d.ts.map +1 -0
  95. package/dist/Model.js +5 -0
  96. package/dist/NonEmptySet.d.ts +3 -1
  97. package/dist/NonEmptySet.d.ts.map +1 -1
  98. package/dist/NonEmptySet.js +2 -2
  99. package/dist/Option.d.ts +1 -0
  100. package/dist/Option.d.ts.map +1 -1
  101. package/dist/Option.js +3 -1
  102. package/dist/Pure.d.ts +7 -5
  103. package/dist/Pure.d.ts.map +1 -1
  104. package/dist/Pure.js +17 -14
  105. package/dist/QueueMaker.d.ts +13 -0
  106. package/dist/QueueMaker.d.ts.map +1 -0
  107. package/dist/QueueMaker.js +4 -0
  108. package/dist/RequestContext.d.ts +154 -0
  109. package/dist/RequestContext.d.ts.map +1 -0
  110. package/dist/RequestContext.js +54 -0
  111. package/dist/Schema/Class.d.ts +160 -19
  112. package/dist/Schema/Class.d.ts.map +1 -1
  113. package/dist/Schema/Class.js +260 -17
  114. package/dist/Schema/FastCheck.d.ts.map +1 -1
  115. package/dist/Schema/SchemaParser.d.ts +5 -0
  116. package/dist/Schema/SchemaParser.d.ts.map +1 -0
  117. package/dist/Schema/SchemaParser.js +6 -0
  118. package/dist/Schema/SpecialJsonSchema.d.ts +34 -0
  119. package/dist/Schema/SpecialJsonSchema.d.ts.map +1 -0
  120. package/dist/Schema/SpecialJsonSchema.js +118 -0
  121. package/dist/Schema/SpecialOpenApi.d.ts +32 -0
  122. package/dist/Schema/SpecialOpenApi.d.ts.map +1 -0
  123. package/dist/Schema/SpecialOpenApi.js +123 -0
  124. package/dist/Schema/brand.d.ts +4 -2
  125. package/dist/Schema/brand.d.ts.map +1 -1
  126. package/dist/Schema/brand.js +3 -1
  127. package/dist/Schema/email.d.ts.map +1 -1
  128. package/dist/Schema/email.js +7 -4
  129. package/dist/Schema/ext.d.ts +338 -55
  130. package/dist/Schema/ext.d.ts.map +1 -1
  131. package/dist/Schema/ext.js +358 -53
  132. package/dist/Schema/moreStrings.d.ts +82 -36
  133. package/dist/Schema/moreStrings.d.ts.map +1 -1
  134. package/dist/Schema/moreStrings.js +49 -42
  135. package/dist/Schema/numbers.d.ts +34 -21
  136. package/dist/Schema/numbers.d.ts.map +1 -1
  137. package/dist/Schema/numbers.js +55 -12
  138. package/dist/Schema/phoneNumber.d.ts.map +1 -1
  139. package/dist/Schema/phoneNumber.js +6 -3
  140. package/dist/Schema/strings.d.ts +18 -4
  141. package/dist/Schema/strings.d.ts.map +1 -1
  142. package/dist/Schema/strings.js +1 -5
  143. package/dist/Schema.d.ts +213 -7
  144. package/dist/Schema.d.ts.map +1 -1
  145. package/dist/Schema.js +190 -11
  146. package/dist/Set.d.ts +4 -1
  147. package/dist/Set.d.ts.map +1 -1
  148. package/dist/Set.js +3 -2
  149. package/dist/Store.d.ts +170 -0
  150. package/dist/Store.d.ts.map +1 -0
  151. package/dist/Store.js +121 -0
  152. package/dist/_ext/Array.d.ts +1 -1
  153. package/dist/_ext/Array.d.ts.map +1 -1
  154. package/dist/_ext/Array.js +4 -2
  155. package/dist/_ext/misc.d.ts +4 -1
  156. package/dist/_ext/misc.d.ts.map +1 -1
  157. package/dist/_ext/misc.js +4 -2
  158. package/dist/_ext/ord.ext.d.ts +2 -1
  159. package/dist/_ext/ord.ext.d.ts.map +1 -1
  160. package/dist/_ext/ord.ext.js +2 -2
  161. package/dist/client/InvalidationKeys.d.ts +29 -0
  162. package/dist/client/InvalidationKeys.d.ts.map +1 -0
  163. package/dist/client/InvalidationKeys.js +33 -0
  164. package/dist/client/apiClientFactory.d.ts +19 -31
  165. package/dist/client/apiClientFactory.d.ts.map +1 -1
  166. package/dist/client/apiClientFactory.js +104 -34
  167. package/dist/client/clientFor.d.ts +52 -18
  168. package/dist/client/clientFor.d.ts.map +1 -1
  169. package/dist/client/clientFor.js +9 -1
  170. package/dist/client/errors.d.ts +82 -27
  171. package/dist/client/errors.d.ts.map +1 -1
  172. package/dist/client/errors.js +75 -19
  173. package/dist/client/makeClient.d.ts +494 -32
  174. package/dist/client/makeClient.d.ts.map +1 -1
  175. package/dist/client/makeClient.js +66 -24
  176. package/dist/client.d.ts +1 -0
  177. package/dist/client.d.ts.map +1 -1
  178. package/dist/client.js +2 -1
  179. package/dist/faker.d.ts.map +1 -1
  180. package/dist/http/Request.d.ts +1 -1
  181. package/dist/http/Request.d.ts.map +1 -1
  182. package/dist/http/Request.js +2 -2
  183. package/dist/ids.d.ts +42 -14
  184. package/dist/ids.d.ts.map +1 -1
  185. package/dist/ids.js +30 -5
  186. package/dist/index.d.ts +6 -7
  187. package/dist/index.d.ts.map +1 -1
  188. package/dist/index.js +8 -8
  189. package/dist/middleware.d.ts +13 -7
  190. package/dist/middleware.d.ts.map +1 -1
  191. package/dist/middleware.js +14 -8
  192. package/dist/rpc/Invalidation.d.ts +420 -0
  193. package/dist/rpc/Invalidation.d.ts.map +1 -0
  194. package/dist/rpc/Invalidation.js +168 -0
  195. package/dist/rpc/MiddlewareMaker.d.ts +11 -7
  196. package/dist/rpc/MiddlewareMaker.d.ts.map +1 -1
  197. package/dist/rpc/MiddlewareMaker.js +59 -38
  198. package/dist/rpc/RpcContextMap.d.ts +3 -3
  199. package/dist/rpc/RpcContextMap.d.ts.map +1 -1
  200. package/dist/rpc/RpcContextMap.js +4 -4
  201. package/dist/rpc/RpcMiddleware.d.ts +14 -10
  202. package/dist/rpc/RpcMiddleware.d.ts.map +1 -1
  203. package/dist/rpc/RpcMiddleware.js +1 -1
  204. package/dist/rpc.d.ts +1 -1
  205. package/dist/rpc.d.ts.map +1 -1
  206. package/dist/rpc.js +2 -2
  207. package/dist/runtime.d.ts +19 -0
  208. package/dist/runtime.d.ts.map +1 -0
  209. package/dist/runtime.js +40 -0
  210. package/dist/setupRequest.d.ts +19 -0
  211. package/dist/setupRequest.d.ts.map +1 -0
  212. package/dist/setupRequest.js +69 -0
  213. package/dist/toast.d.ts +51 -0
  214. package/dist/toast.d.ts.map +1 -0
  215. package/dist/toast.js +34 -0
  216. package/dist/transform.d.ts +1 -1
  217. package/dist/transform.d.ts.map +1 -1
  218. package/dist/transform.js +4 -5
  219. package/dist/utils/effectify.d.ts +1 -1
  220. package/dist/utils/effectify.d.ts.map +1 -1
  221. package/dist/utils/effectify.js +2 -2
  222. package/dist/utils/extend.d.ts.map +1 -1
  223. package/dist/utils/gen.d.ts +4 -4
  224. package/dist/utils/gen.d.ts.map +1 -1
  225. package/dist/utils/logLevel.d.ts +2 -2
  226. package/dist/utils/logLevel.d.ts.map +1 -1
  227. package/dist/utils/logger.d.ts +4 -3
  228. package/dist/utils/logger.d.ts.map +1 -1
  229. package/dist/utils/logger.js +4 -4
  230. package/dist/utils.d.ts +34 -39
  231. package/dist/utils.d.ts.map +1 -1
  232. package/dist/utils.js +33 -37
  233. package/dist/validation/validators.d.ts.map +1 -1
  234. package/dist/validation.d.ts.map +1 -1
  235. package/dist/withToast.d.ts +30 -0
  236. package/dist/withToast.d.ts.map +1 -0
  237. package/dist/withToast.js +64 -0
  238. package/package.json +22 -247
  239. package/src/Array.ts +5 -5
  240. package/src/Chunk.ts +3 -3
  241. package/src/Config/SecretURL.ts +6 -3
  242. package/src/Config/internal/configSecretURL.ts +2 -2
  243. package/src/Config.ts +7 -0
  244. package/src/ConfigProvider.ts +5 -0
  245. package/src/{ServiceMap.ts → Context.ts} +56 -63
  246. package/src/Effect.ts +14 -16
  247. package/src/Emailer.ts +51 -0
  248. package/src/Inputify.type.ts +1 -1
  249. package/src/Layer.ts +11 -7
  250. package/src/Model/Repository/Registry.ts +35 -0
  251. package/src/Model/Repository/ext.ts +375 -0
  252. package/src/Model/Repository/internal/internal.ts +741 -0
  253. package/src/Model/Repository/legacy.ts +29 -0
  254. package/src/Model/Repository/makeRepo.ts +145 -0
  255. package/src/Model/Repository/service.ts +676 -0
  256. package/src/Model/Repository/validation.ts +31 -0
  257. package/src/Model/Repository.ts +6 -0
  258. package/src/Model/dsl.ts +129 -0
  259. package/src/Model/filter/filterApi.ts +60 -0
  260. package/src/Model/filter/types/errors.ts +47 -0
  261. package/src/Model/filter/types/fields.ts +50 -0
  262. package/src/Model/filter/types/path/common.ts +404 -0
  263. package/src/Model/filter/types/path/eager.ts +329 -0
  264. package/src/Model/filter/types/path/index.ts +4 -0
  265. package/src/Model/filter/types/utils.ts +128 -0
  266. package/src/Model/filter/types/validator.ts +46 -0
  267. package/src/Model/filter/types.ts +6 -0
  268. package/src/Model/query/dsl.ts +2694 -0
  269. package/src/Model/query/new-kid-interpreter.ts +484 -0
  270. package/src/Model/query.ts +13 -0
  271. package/src/Model.ts +4 -0
  272. package/src/NonEmptySet.ts +6 -4
  273. package/src/Option.ts +2 -0
  274. package/src/Pure.ts +22 -20
  275. package/src/QueueMaker.ts +19 -0
  276. package/src/RequestContext.ts +95 -0
  277. package/src/Schema/Class.ts +593 -59
  278. package/src/Schema/SchemaParser.ts +12 -0
  279. package/src/Schema/SpecialJsonSchema.ts +139 -0
  280. package/src/Schema/SpecialOpenApi.ts +130 -0
  281. package/src/Schema/brand.ts +22 -2
  282. package/src/Schema/email.ts +9 -4
  283. package/src/Schema/ext.ts +446 -91
  284. package/src/Schema/moreStrings.ts +147 -68
  285. package/src/Schema/numbers.ts +97 -28
  286. package/src/Schema/phoneNumber.ts +9 -5
  287. package/src/Schema/strings.ts +23 -14
  288. package/src/Schema.ts +389 -25
  289. package/src/Set.ts +6 -2
  290. package/src/Store.ts +277 -0
  291. package/src/_ext/Array.ts +4 -2
  292. package/src/_ext/misc.ts +4 -1
  293. package/src/_ext/ord.ext.ts +2 -1
  294. package/src/client/InvalidationKeys.ts +50 -0
  295. package/src/client/apiClientFactory.ts +234 -135
  296. package/src/client/clientFor.ts +105 -34
  297. package/src/client/errors.ts +100 -29
  298. package/src/client/makeClient.ts +594 -73
  299. package/src/client.ts +5 -4
  300. package/src/http/Request.ts +3 -3
  301. package/src/http.ts +1 -1
  302. package/src/ids.ts +33 -6
  303. package/src/index.ts +20 -23
  304. package/src/middleware.ts +13 -9
  305. package/src/rpc/Invalidation.ts +261 -0
  306. package/src/rpc/MiddlewareMaker.ts +88 -80
  307. package/src/rpc/README.md +2 -2
  308. package/src/rpc/RpcContextMap.ts +7 -6
  309. package/src/rpc/RpcMiddleware.ts +19 -13
  310. package/src/rpc.ts +4 -4
  311. package/src/runtime.ts +56 -0
  312. package/src/setupRequest.ts +134 -0
  313. package/src/toast.ts +54 -0
  314. package/src/transform.ts +4 -4
  315. package/src/utils/effectify.ts +1 -1
  316. package/src/utils/gen.ts +8 -8
  317. package/src/utils/logLevel.ts +1 -1
  318. package/src/utils/logger.ts +4 -3
  319. package/src/utils.ts +85 -158
  320. package/src/validation.ts +2 -2
  321. package/src/withToast.ts +133 -0
  322. package/test/dist/rpc.test.d.ts.map +1 -1
  323. package/test/dist/secretURL.test.d.ts.map +1 -0
  324. package/test/dist/special.test.d.ts.map +1 -0
  325. package/test/moreStrings.test.ts +1 -1
  326. package/test/rpc.test.ts +46 -6
  327. package/test/schema.test.ts +459 -30
  328. package/test/secretURL.test.ts +160 -0
  329. package/test/special.test.ts +1258 -0
  330. package/test/utils.test.ts +7 -7
  331. package/tsconfig.base.json +6 -5
  332. package/tsconfig.json +3 -1
  333. package/tsconfig.json.bak +2 -2
  334. package/tsconfig.src.json +29 -29
  335. package/tsconfig.test.json +2 -2
  336. package/dist/Operations.d.ts +0 -123
  337. package/dist/Operations.d.ts.map +0 -1
  338. package/dist/Operations.js +0 -29
  339. package/dist/ServiceMap.d.ts.map +0 -1
  340. package/dist/ServiceMap.js +0 -91
  341. package/eslint.config.mjs +0 -26
  342. package/src/Operations.ts +0 -55
@@ -1,19 +1,22 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import * as Config from "effect/Config"
3
- import { flow } from "effect/Function"
2
+ import { constant, flow } from "effect/Function"
4
3
  import * as Layer from "effect/Layer"
5
4
  import * as ManagedRuntime from "effect/ManagedRuntime"
5
+ import * as Option from "effect/Option"
6
6
  import * as Predicate from "effect/Predicate"
7
7
  import * as Schema from "effect/Schema"
8
+ import * as Stream from "effect/Stream"
8
9
  import * as Struct from "effect/Struct"
9
10
  import { Rpc, RpcClient, RpcGroup, RpcSerialization } from "effect/unstable/rpc"
10
- import * as Effect from "../Effect.js"
11
- import { HttpClient, HttpClientRequest } from "../http.js"
12
- import * as Option from "../Option.js"
13
- import type * as S from "../Schema.js"
14
- import * as ServiceMap from "../ServiceMap.js"
15
- import { typedKeysOf, typedValuesOf } from "../utils.js"
16
- import type { Client, ClientForOptions, Requests, RequestsAny } from "./clientFor.js"
11
+ import * as Config from "../Config.ts"
12
+ import * as Context from "../Context.ts"
13
+ import * as Effect from "../Effect.ts"
14
+ import { HttpClient, HttpClientRequest } from "../http.ts"
15
+ import { Invalidation } from "../rpc.ts"
16
+ import type * as S from "../Schema.ts"
17
+ import { typedKeysOf, typedValuesOf } from "../utils.ts"
18
+ import type { Client, ClientForOptions, ExtractModuleName, RequestsAny } from "./clientFor.ts"
19
+ import { InvalidationKeysFromServer } from "./InvalidationKeys.ts"
17
20
 
18
21
  export interface ApiConfig {
19
22
  url: string
@@ -31,16 +34,22 @@ export const DefaultApiConfig = Config.all({
31
34
  })
32
35
 
33
36
  export type Req = S.Top & {
34
- new(...args: any[]): any
37
+ readonly make: (...args: any[]) => any
35
38
  _tag: string
36
39
  fields: S.Struct.Fields
37
40
  success: S.Top
38
41
  error: S.Top
42
+ /** Optional final-value schema for stream requests. When set, the execute effect resolves with the last stream value decoded to this type. */
43
+ final?: S.Top
39
44
  config?: Record<string, any>
40
- readonly "~decodingServices"?: unknown
45
+ readonly id: string
46
+ readonly moduleName: string
47
+ readonly type: "command" | "query"
48
+ readonly stream: boolean
49
+ readonly middleware?: unknown
41
50
  }
42
51
 
43
- class RequestName extends ServiceMap.Reference("RequestName", {
52
+ class RequestName extends Context.Reference("RequestName", {
44
53
  defaultValue: () => ({ requestName: "Unspecified", moduleName: "Error" })
45
54
  }) {}
46
55
 
@@ -56,7 +65,7 @@ export const HttpClientLayer = (config: ApiConfig) =>
56
65
  HttpClientRequest.setHeaders(config.headers.pipe(Option.getOrElse(() => ({}))))
57
66
  ),
58
67
  HttpClient.mapRequestEffect((req) =>
59
- Effect.map(RequestName.asEffect(), (ctx) =>
68
+ Effect.map(RequestName, (ctx) =>
60
69
  flow(
61
70
  HttpClientRequest.appendUrlParam("action", ctx.requestName),
62
71
  HttpClientRequest.appendUrl("/" + ctx.moduleName)
@@ -84,7 +93,7 @@ type RpcHandlers<M extends RequestsAny> = {
84
93
  [K in keyof M]: Rpc.Rpc<M[K]["_tag"], M[K], M[K]["success"], M[K]["error"]>
85
94
  }
86
95
 
87
- const getFiltered = <M extends Requests>(resource: M) => {
96
+ const getFiltered = <M extends RequestsAny>(resource: M) => {
88
97
  type Filtered = {
89
98
  [K in keyof M as M[K] extends Req ? K : never]: M[K] extends Req ? M[K] : never
90
99
  }
@@ -102,176 +111,266 @@ const getFiltered = <M extends Requests>(resource: M) => {
102
111
  return filtered as unknown as Filtered
103
112
  }
104
113
 
105
- export const getMeta = <M extends Requests>(resource: M) => {
106
- const meta = (resource as any).meta as { moduleName: string }
107
- if (!meta) throw new Error("No meta defined in Resource!")
108
- return meta as M["meta"]
114
+ export const getMeta = <M extends RequestsAny>(
115
+ resource: M
116
+ ): { moduleName: ExtractModuleName<M>; middleware?: unknown } => {
117
+ const first = typedValuesOf(getFiltered(resource))[0]
118
+ if (first && "moduleName" in first) return { moduleName: first.moduleName, middleware: (first as any).middleware }
119
+ throw new Error("No moduleName on requests!")
109
120
  }
110
121
 
111
- export const makeRpcGroupFromRequestsAndModuleName = <M extends Requests, const ModuleName extends string>(
122
+ export const makeRpcGroupFromRequestsAndModuleName = <M extends RequestsAny, const ModuleName extends string>(
112
123
  resource: M,
113
- moduleName: ModuleName
124
+ moduleName: ModuleName,
125
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
126
+ middleware?: any
114
127
  ) => {
115
128
  const filtered = getFiltered(resource)
116
129
  type newM = typeof filtered
117
- const rpcs = RpcGroup
130
+ const baseRpcs = RpcGroup
118
131
  .make(
119
132
  ...typedValuesOf(filtered).map((_) => {
120
- return Rpc.make((_ as any)._tag, { payload: _ as any, success: (_ as any).success, error: (_ as any).error })
133
+ const r = _ as any
134
+ const isStream = r.stream
135
+ const isCommand = r.type === "command"
136
+ return (isCommand
137
+ ? isStream
138
+ ? Invalidation.makeStreamRpc(r._tag, {
139
+ payload: r,
140
+ success: r.success,
141
+ error: r.error,
142
+ stream: true as const
143
+ })
144
+ : Invalidation.makeCommandRpc(r._tag, { payload: r, success: r.success, error: r.error })
145
+ : Rpc.make(r._tag, { payload: r, success: r.success, error: r.error, stream: isStream })) as any
121
146
  })
122
147
  )
123
- .prefix(`${moduleName}.`) as unknown as RpcGroup.RpcGroup<
124
- Rpc.Prefixed<RpcHandlers<newM>[keyof newM], `${ModuleName}.`>
125
- >
148
+ .prefix(`${moduleName}.`)
149
+ // Attach the middleware tag (schema-only on the client — no Live invoked)
150
+ // so its declared `error` joins the rpc failure union via
151
+ // `Rpc.exitSchema`'s `rpc.middlewares[*].error` walk. Required for stream
152
+ // rpcs whose top-level `errorSchema` is forced to `Never` by effect-rpc;
153
+ // without it, middleware-thrown errors fail client decode.
154
+ const rpcs = (middleware ? baseRpcs.middleware(middleware) : baseRpcs) as unknown as RpcGroup.RpcGroup<
155
+ Rpc.Prefixed<RpcHandlers<newM>[keyof newM], `${ModuleName}.`>
156
+ >
126
157
  return rpcs
127
158
  }
128
159
 
129
- export const makeRpcGroup = <
130
- M extends Requests,
131
- const ModuleName extends string
132
- >(
133
- resource: M & { meta: { moduleName: ModuleName } }
134
- ) => makeRpcGroupFromRequestsAndModuleName(resource, resource.meta.moduleName)
135
-
136
- const makeRpcTag = <M extends Requests>(resource: M) => {
160
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
161
+ const makeRpcTag = <M extends RequestsAny>(resource: M, middleware?: any) => {
137
162
  const meta = getMeta(resource)
138
- const rpcs = makeRpcGroupFromRequestsAndModuleName(resource, meta.moduleName)
163
+ const rpcs = makeRpcGroupFromRequestsAndModuleName(resource, meta.moduleName, middleware)
139
164
 
140
165
  // Use Object.assign instead of class extension to avoid TS2509 with complex generic return types.
141
166
  // The first type arg is `any` because this is a dynamically created tag — its identity is the string key.
142
- const TheClient = ServiceMap.Opaque<
167
+ const TheClient = Context.Opaque<
143
168
  any,
144
169
  RpcClient.RpcClient<RpcGroup.Rpcs<typeof rpcs>>
145
170
  >()(`RpcClient.${meta.moduleName}`)
146
171
  // Use Layer.effect directly (not TheClient.toLayer) so TypeScript properly excludes Scope
147
172
  const layer = Layer.effect(
148
173
  TheClient,
149
- RpcClient.make(rpcs, { spanPrefix: "RpcClient." + meta.moduleName })
174
+ RpcClient.make(rpcs)
150
175
  )
151
176
  return Object.assign(TheClient, { layer })
152
177
  }
153
178
 
154
179
  const makeApiClientFactory = Effect
155
180
  .gen(function*() {
156
- const ctx = yield* Effect.services<RpcSerialization.RpcSerialization | HttpClient.HttpClient>()
157
- const makeClientFor = <M extends Requests>(
181
+ const ctx = yield* Effect.context<RpcSerialization.RpcSerialization | HttpClient.HttpClient>()
182
+ const makeClientFor = Effect.fnUntraced(function*<M extends RequestsAny>(
158
183
  resource: M,
159
184
  requestLevelLayers = Layer.empty,
160
185
  options?: ClientForOptions
161
- ) =>
162
- Effect.gen(function*() {
163
- const TheClient = makeRpcTag(resource)
164
-
165
- const meta = getMeta(resource)
166
-
167
- // TODO: somehow we need a protocol per REQUEST kind of it seems ...
168
- // otherwise it locks up on the client, navigation remains empty...
169
- const clientLayer = TheClient.layer.pipe(
170
- // add ApiClientFactory for nested schemas
171
- // eslint-disable-next-line @typescript-eslint/no-use-before-define
172
- Layer.provide(Layer.succeed(ApiClientFactory, makeClientForCached as any)),
173
- Layer.provide(
174
- RpcClient
175
- .layerProtocolHttp({
176
- url: "" // why not here set meta.moduleName as root?
186
+ ) {
187
+ const meta = getMeta(resource)
188
+
189
+ const TheClient = makeRpcTag(resource, meta.middleware)
190
+
191
+ // TODO: somehow we need a protocol per REQUEST kind of it seems ...
192
+ // otherwise it locks up on the client, navigation remains empty...
193
+ const clientLayer = TheClient.layer.pipe(
194
+ // add ApiClientFactory for nested schemas
195
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
196
+ Layer.provide(Layer.succeed(ApiClientFactory, makeClientForCached as any)),
197
+ Layer.provide(
198
+ RpcClient
199
+ .layerProtocolHttp({ url: "" }) // why not here set meta.moduleName as root?
200
+ .pipe(
201
+ Layer.provideMerge(Layer.succeedContext(ctx))
202
+ )
203
+ )
204
+ )
205
+ const mr = ManagedRuntime.make(clientLayer)
206
+
207
+ const filtered = getFiltered(resource)
208
+
209
+ // Unwrap `CommandResponseWithMetaData` (success) and `CommandFailureWithMetaData`
210
+ // (handler-thrown failure): forward accumulated invalidation keys to
211
+ // `InvalidationKeysFromServer` and yield the raw payload / re-fail with the raw
212
+ // error. Middleware-thrown failures arrive raw on the Cause already (no wrap to
213
+ // strip) — the `else` branch passes them through.
214
+ const unwrapCommand = (eff: Effect.Effect<any, any, any>): Effect.Effect<any, any, any> =>
215
+ eff.pipe(
216
+ Effect.flatMap((result: any) =>
217
+ Effect.gen(function*() {
218
+ const keys: ReadonlyArray<Invalidation.InvalidationKey> = result?.metadata?.invalidateQueries ?? []
219
+ const invalidationKeys = yield* InvalidationKeysFromServer
220
+ yield* Effect.forEach(keys, (key) => invalidationKeys.add(key), { discard: true })
221
+ return result.payload
222
+ })
223
+ ),
224
+ Effect.catch((result: any) =>
225
+ result?._tag === "CommandFailureWithMetaData"
226
+ ? Effect.gen(function*() {
227
+ const keys: ReadonlyArray<Invalidation.InvalidationKey> = result.metadata?.invalidateQueries ?? []
228
+ const invalidationKeys = yield* InvalidationKeysFromServer
229
+ yield* Effect.forEach(keys, (key) => invalidationKeys.add(key), { discard: true })
230
+ return yield* Effect.fail(result.error)
177
231
  })
178
- .pipe(
179
- Layer.provideMerge(Layer.succeedServices(ctx))
180
- )
232
+ : Effect.fail(result)
181
233
  )
182
234
  )
183
- const mr = ManagedRuntime.make(clientLayer)
184
235
 
185
- const filtered = getFiltered(resource)
186
- return {
187
- mr,
188
- client: typedKeysOf(filtered)
189
- .reduce((prev, cur) => {
190
- const h = filtered[cur]!
191
-
192
- const Request = h
236
+ return {
237
+ mr,
238
+ client: typedKeysOf(filtered)
239
+ .reduce((prev, cur) => {
240
+ const h = filtered[cur]!
241
+
242
+ const Request = h
243
+
244
+ const id = `${meta.moduleName}.${cur as string}`
245
+ .replaceAll(".js", "")
246
+
247
+ const requestMeta = {
248
+ Request,
249
+ id,
250
+ options
251
+ }
252
+
253
+ const requestNameLayer = Layer.succeed(RequestName, {
254
+ requestName: cur as string,
255
+ moduleName: meta.moduleName
256
+ })
257
+
258
+ const layers = requestLevelLayers.pipe(Layer.provideMerge(requestNameLayer))
259
+
260
+ const fields = Struct.omit(Request.fields, ["_tag"] as const)
261
+ const requestAttr = `${meta.moduleName}.${h._tag}`
262
+ const isCommand = h.type === "command"
263
+ const isStream = h.stream
264
+
265
+ const buildEffect = (input: any) =>
266
+ mr.contextEffect.pipe(
267
+ Effect.flatMap((svcs) => {
268
+ const rpcEffect = TheClient
269
+ .use((client) => (client as any)[requestAttr]!(Request.make(input)) as Effect.Effect<any, any>)
270
+ .pipe(
271
+ // local: true → isolate to this RPC call. `layers` is pure today
272
+ // (RequestName + caller-supplied requestLevelLayers), but the
273
+ // flag prevents a stateful caller-supplied layer from leaking
274
+ // across RPC calls via the ambient fiber MemoMap.
275
+ Effect.provide(layers, { local: true }),
276
+ Effect.provide(svcs)
277
+ )
278
+ return isCommand ? unwrapCommand(rpcEffect) : rpcEffect
279
+ })
280
+ )
193
281
 
194
- const id = `${meta.moduleName}.${cur as string}`
195
- .replaceAll(".js", "")
282
+ const buildStream = (input: any) =>
283
+ Stream.unwrap(
284
+ mr.contextEffect.pipe(
285
+ Effect.flatMap((svcs) =>
286
+ TheClient
287
+ .useSync((client) => {
288
+ const rpcStream = (client as any)[requestAttr]!(
289
+ Request.make(input)
290
+ ) as Stream.Stream<any, any, any>
291
+ return rpcStream.pipe(
292
+ // Collect server invalidation keys from the "done" chunk, then discard it.
293
+ Stream.tap((item: any) =>
294
+ item._tag === "done" || item._tag === "metadata"
295
+ ? InvalidationKeysFromServer.use((svc) =>
296
+ Effect.forEach(
297
+ (item.metadata as Invalidation.CommandMetaData).invalidateQueries,
298
+ svc.add,
299
+ { discard: true }
300
+ )
301
+ )
302
+ : Effect.void
303
+ ),
304
+ Stream.filter((item: any) => item._tag === "value"),
305
+ Stream.map((item: any) => item.value),
306
+ // V2: unwrap StreamFailureChunk — forward keys from failures too,
307
+ // then re-fail with the original error so callers see the unmodified
308
+ // error type.
309
+ Stream.catch((err: any) =>
310
+ err?._tag === "error" && err?.metadata
311
+ ? Stream.fromEffect(
312
+ InvalidationKeysFromServer.use((svc) =>
313
+ Effect
314
+ .forEach(
315
+ (err.metadata as Invalidation.CommandMetaData).invalidateQueries,
316
+ svc.add,
317
+ { discard: true }
318
+ )
319
+ .pipe(Effect.flatMap(() => Effect.fail(err.error)))
320
+ )
321
+ )
322
+ : Stream.fail(err)
323
+ ),
324
+ // local: true — see buildEffect above.
325
+ Stream.provide(layers, { local: true }),
326
+ Stream.provide(svcs)
327
+ )
328
+ })
329
+ .pipe(Effect.provide(svcs))
330
+ )
331
+ )
332
+ )
196
333
 
197
- const requestMeta = {
198
- Request,
199
- id,
200
- options
334
+ // @ts-expect-error doc
335
+ prev[cur] = Object.keys(fields).length === 0
336
+ ? {
337
+ handler: isStream ? constant(buildStream({})) : constant(buildEffect({})),
338
+ ...requestMeta
339
+ }
340
+ : {
341
+ handler: isStream
342
+ ? (req: any) => buildStream(req)
343
+ : (req: any) => buildEffect(req),
344
+ ...requestMeta
201
345
  }
202
346
 
203
- const requestNameLayer = Layer.succeed(RequestName, {
204
- requestName: cur as string,
205
- moduleName: meta.moduleName
206
- })
207
-
208
- const layers = requestLevelLayers.pipe(Layer.provideMerge(requestNameLayer))
209
-
210
- const fields = Struct.omit(Request.fields, ["_tag"] as const)
211
- const requestAttr = `${meta.moduleName}.${h._tag}`
212
- // @ts-expect-error doc
213
- prev[cur] = Object.keys(fields).length === 0
214
- ? {
215
- handler: mr.servicesEffect.pipe(
216
- Effect.flatMap((svcs) =>
217
- TheClient
218
- .use((client) => (client as any)[requestAttr]!(new Request()) as Effect.Effect<any, any, never>)
219
- .pipe(
220
- Effect.provide(layers),
221
- Effect.provide(svcs)
222
- )
223
- )
224
- ),
225
- ...requestMeta
226
- }
227
- : {
228
- handler: (req: any) =>
229
- mr.servicesEffect.pipe(
230
- Effect.flatMap((svcs) =>
231
- TheClient
232
- .use((client) =>
233
- (client as any)[requestAttr]!(new Request(req)) as Effect.Effect<any, any, never>
234
- )
235
- .pipe(
236
- Effect.provide(layers),
237
- Effect.provide(svcs)
238
- )
239
- )
240
- ),
241
-
242
- ...requestMeta
243
- }
244
-
245
- return prev
246
- }, {} as Client<M, M["meta"]["moduleName"]>)
247
- }
248
- })
347
+ return prev
348
+ }, {} as Client<M, ExtractModuleName<M>>)
349
+ }
350
+ })
249
351
 
250
352
  const register: ManagedRuntime.ManagedRuntime<any, any>[] = []
251
353
  yield* Effect.addFinalizer(() => Effect.forEach(register, (mr) => mr.disposeEffect))
252
354
 
253
355
  const cacheL = new Map<any, Map<any, Client<any, any>>>()
254
356
 
255
- function makeClientForCached(requestLevelLayers: Layer.Layer<never, never, never>, options?: ClientForOptions) {
357
+ function makeClientForCached(requestLevelLayers: Layer.Layer<never>, options?: ClientForOptions) {
256
358
  let cache = cacheL.get(requestLevelLayers)
257
359
  if (!cache) {
258
360
  cache = new Map<any, Client<any, any>>()
259
361
  cacheL.set(requestLevelLayers, cache)
260
362
  }
261
363
 
262
- return <M extends Requests>(
263
- models: M
264
- ): Effect.Effect<Client<M, M["meta"]["moduleName"]>> =>
265
- Effect.gen(function*() {
266
- const found = cache.get(models)
267
- if (found) {
268
- return found
269
- }
270
- const m = yield* makeClientFor(models, requestLevelLayers, options)
271
- cache.set(models, m.client)
272
- register.push(m.mr)
273
- return m.client
274
- })
364
+ return Effect.fnUntraced(function*<M extends RequestsAny>(models: M) {
365
+ const found = cache.get(models) as Client<M, ExtractModuleName<M>> | undefined
366
+ if (found) {
367
+ return found
368
+ }
369
+ const m = yield* makeClientFor(models, requestLevelLayers, options)
370
+ cache.set(models, m.client)
371
+ register.push(m.mr)
372
+ return m.client
373
+ })
275
374
  }
276
375
 
277
376
  return makeClientForCached
@@ -281,7 +380,7 @@ const makeApiClientFactory = Effect
281
380
  * Used to create clients for resource modules.
282
381
  */
283
382
  export class ApiClientFactory
284
- extends ServiceMap.Opaque<ApiClientFactory, Effect.Success<typeof makeApiClientFactory>>()("ApiClientFactory")
383
+ extends Context.Opaque<ApiClientFactory, Effect.Success<typeof makeApiClientFactory>>()("ApiClientFactory")
285
384
  {
286
385
  static readonly layer = (config: ApiConfig) =>
287
386
  ApiClientFactory.toLayer(makeApiClientFactory).pipe(Layer.provide(RpcSerializationLayer(config)))
@@ -293,8 +392,8 @@ export class ApiClientFactory
293
392
  )
294
393
 
295
394
  static readonly makeFor =
296
- (requestLevelLayers: Layer.Layer<never, never, never>, options?: ClientForOptions) =>
297
- <M extends Requests>(
395
+ (requestLevelLayers: Layer.Layer<never>, options?: ClientForOptions) =>
396
+ <M extends RequestsAny>(
298
397
  resource: M
299
398
  ) =>
300
399
  ApiClientFactory.use((apiClientFactory) => {
@@ -2,12 +2,12 @@
2
2
  /* eslint-disable @typescript-eslint/no-explicit-any */
3
3
 
4
4
  import * as Record from "effect/Record"
5
- import type * as Request from "effect/Request"
5
+ import type * as Stream from "effect/Stream"
6
6
  import type { Path } from "path-parser"
7
7
  import qs from "query-string"
8
- import type * as Effect from "../Effect.js"
9
- import type * as S from "../Schema.js"
10
- import { type Req } from "./apiClientFactory.js"
8
+ import type * as Effect from "../Effect.ts"
9
+ import type * as S from "../Schema.ts"
10
+ import { type Req } from "./apiClientFactory.ts"
11
11
 
12
12
  export function makePathWithQuery(
13
13
  path: Path,
@@ -48,9 +48,14 @@ export function makePathWithBody(
48
48
  return path.build(pars, { ignoreSearch: true, ignoreConstraints: true })
49
49
  }
50
50
 
51
- export type Requests<ModuleName extends string = string> = { meta: { moduleName: ModuleName } } & RequestsAny
51
+ export type Requests = RequestsAny
52
52
  export type RequestsAny = Record<string, any>
53
53
 
54
+ export type ExtractModuleName<M extends RequestsAny> =
55
+ { [K in keyof M]: M[K] extends { moduleName: infer N extends string } ? N : never }[keyof M] extends
56
+ infer R extends string ? R
57
+ : string
58
+
54
59
  export type Client<M extends RequestsAny, ModuleName extends string> = RequestHandlers<
55
60
  never,
56
61
  never,
@@ -58,56 +63,122 @@ export type Client<M extends RequestsAny, ModuleName extends string> = RequestHa
58
63
  ModuleName
59
64
  >
60
65
 
61
- export type ExtractResponse<T> = T extends S.Codec<any> ? S.Schema.Type<T>
66
+ export type ExtractResponse<T> = T extends S.Codec<any> ? T["Type"]
62
67
  : T extends unknown ? void
63
68
  : never
64
69
 
65
- export type ExtractEResponse<T> = T extends S.Codec<any> ? S.Codec.Encoded<T>
70
+ export type ExtractEResponse<T> = T extends S.Codec<any> ? T["Encoded"]
66
71
  : T extends unknown ? void
67
72
  : never
68
73
 
69
- type IsEmpty<T> = keyof T extends never ? true
70
- : false
71
-
72
- // v4: Request.RequestTypeId, S.symbolSerializable, S.symbolWithResult removed — use keyof Request to filter internal props
73
- type Cruft = "_tag" | keyof Request.Request<any, any, any>
74
-
75
74
  export interface ClientForOptions {
76
75
  readonly skipQueryKey?: readonly string[]
76
+ /**
77
+ * Middleware tag to attach to every rpc on the client. Schema-only — the
78
+ * client never invokes the middleware (no Live impl required), but its
79
+ * declared `error` schema joins the rpc failure union via
80
+ * `Rpc.exitSchema`'s `rpc.middlewares[*].error` walk. Required when
81
+ * middleware can throw errors that aren't part of the resource's declared
82
+ * error union (e.g. auth middleware throwing `NotLoggedInError`); without
83
+ * it the client decode would fail with a `SchemaError` for stream rpcs.
84
+ */
77
85
  }
78
86
 
79
- export interface RequestHandler<A, E, R, Request extends Req, Id extends string> {
80
- handler: Effect.Effect<A, E, R>
87
+ // $Project/$Configuration.Index
88
+ // -> "$Project", "$Configuration", "Index"
89
+ export const makeQueryKey = ({ id, options }: { id: string; options?: ClientForOptions }) =>
90
+ id
91
+ .split("/")
92
+ .filter((segment: string) => !options || !options.skipQueryKey?.includes(segment))
93
+ .map((segment: string) => "$" + segment)
94
+ .join(".")
95
+ .split(".")
96
+
97
+ export interface RequestHandlerWithInput<I, A, E, R, Request extends Req, Id extends string> {
98
+ handler: (i: I) => Effect.Effect<A, E, R>
81
99
  id: Id
82
100
  options?: ClientForOptions
83
101
  Request: Request
84
102
  }
85
103
 
86
- export interface RequestHandlerWithInput<I, A, E, R, Request extends Req, Id extends string> {
87
- handler: (i: I) => Effect.Effect<A, E, R>
104
+ /** Type alias: a no-input handler is simply `RequestHandlerWithInput<void, …>`. */
105
+ export type RequestHandler<A, E, R, Request extends Req, Id extends string> = RequestHandlerWithInput<
106
+ void,
107
+ A,
108
+ E,
109
+ R,
110
+ Request,
111
+ Id
112
+ >
113
+
114
+ export interface RequestStreamHandlerWithInput<I, A, E, R, Request extends Req, Id extends string, Final = A> {
115
+ handler: (i: I) => Stream.Stream<A, E, R>
88
116
  id: Id
89
117
  options?: ClientForOptions
90
118
  Request: Request
119
+ /**
120
+ * Phantom type property (never set at runtime) that carries the `Final` type to
121
+ * `StreamMutationWithExtensions`. The tilde prefix follows the Effect convention for
122
+ * phantom/virtual properties and prevents accidental runtime access.
123
+ * Stream failures bubble through the execute effect's typed error channel `E`;
124
+ * the reactive `AsyncResult` ref also mirrors the failure for live progress UI.
125
+ */
126
+ readonly "~final"?: Final
91
127
  }
92
128
 
129
+ /** Type alias: a no-input stream handler is simply `RequestStreamHandlerWithInput<void, …>`. */
130
+ export type RequestStreamHandler<A, E, R, Request extends Req, Id extends string, Final = A> =
131
+ RequestStreamHandlerWithInput<void, A, E, R, Request, Id, Final>
132
+
93
133
  // make sure this is exported or d.ts of apiClientFactory breaks?!
94
- type ReqDecodingServices<M> = M extends { readonly "~decodingServices": infer DS } ? DS : never
134
+ export type RequestInputFromMake<I extends { readonly make: (...args: any[]) => any }> = Parameters<I["make"]> extends
135
+ [] ? void : Parameters<I["make"]>[0]
136
+
137
+ // Has no input only when the request schema declares no payload fields (the auto-added
138
+ // `_tag` field is ignored). Any payload fields (even all-optional) produce a function handler.
139
+ type HasNoFields<I> = I extends { readonly fields: infer F extends S.Struct.Fields }
140
+ ? [Exclude<keyof F, "_tag">] extends [never] ? true : false
141
+ : false
142
+
143
+ type RequestInput<I extends { readonly make: (...args: any[]) => any }> = Parameters<I["make"]>[0]
144
+
145
+ /**
146
+ * Caller-facing input type for a request. `void` when the request schema has no fields;
147
+ * otherwise `make`'s first param type.
148
+ */
149
+ export type HandlerInput<I extends { readonly make: (...args: any[]) => any }> = HasNoFields<I> extends true ? void
150
+ : RequestInput<I>
151
+
152
+ /** Extracts the final-value type from a stream request. Defaults to the success type when no `final` schema is set. */
153
+ type FinalTypeOf<T extends Req> = T extends { readonly final: infer F extends S.Top } ? F["Type"]
154
+ : T["success"]["Type"]
155
+
156
+ // `T["success"]` / `T["error"]` are constrained to `S.Top` via `Req`, so we
157
+ // can read `["DecodingServices"]` directly. Avoids the conditional in
158
+ // `S.Codec.DecodingServices<X> = X extends Top ? X["DecodingServices"] : never`,
159
+ // which tsgo (native) fails to reduce in this generic position and leaves as
160
+ // `unknown`, polluting the `R` channel of every client handler.
161
+ type RequestHandlerFor<R, E, T extends Req, Id extends string> = T["stream"] extends true
162
+ ? RequestStreamHandlerWithInput<
163
+ HandlerInput<T>,
164
+ T["success"]["Type"],
165
+ T["error"]["Type"] | E,
166
+ R | T["success"]["DecodingServices"] | T["error"]["DecodingServices"],
167
+ T,
168
+ Id,
169
+ FinalTypeOf<T>
170
+ >
171
+ : RequestHandlerWithInput<
172
+ HandlerInput<T>,
173
+ T["success"]["Type"],
174
+ T["error"]["Type"] | E,
175
+ R | T["success"]["DecodingServices"] | T["error"]["DecodingServices"],
176
+ T,
177
+ Id
178
+ >
95
179
 
96
180
  export type RequestHandlers<R, E, M extends RequestsAny, ModuleName extends string> = {
97
- [K in keyof M as M[K] extends Req ? K : never]: IsEmpty<Omit<S.Schema.Type<M[K]>, Cruft>> extends true
98
- ? RequestHandler<
99
- S.Schema.Type<M[K]["success"]>,
100
- S.Schema.Type<M[K]["error"]> | E,
101
- R | ReqDecodingServices<M[K]>,
102
- M[K],
103
- `${ModuleName}.${K & string}`
104
- >
105
- : RequestHandlerWithInput<
106
- Omit<S.Schema.Type<M[K]>, Cruft>,
107
- S.Schema.Type<M[K]["success"]>,
108
- S.Schema.Type<M[K]["error"]> | E,
109
- R | ReqDecodingServices<M[K]>,
110
- M[K],
111
- `${ModuleName}.${K & string}`
112
- >
181
+ [K in keyof M as M[K] extends Req ? K : never]: Extract<M[K], Req> extends infer T extends Req
182
+ ? RequestHandlerFor<R, E, T, `${ModuleName}.${K & string}`>
183
+ : never
113
184
  }