effect-app 4.0.0-beta.18 → 4.0.0-beta.180

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 (218) hide show
  1. package/CHANGELOG.md +760 -0
  2. package/dist/Array.d.ts +1 -1
  3. package/dist/Chunk.d.ts +1 -1
  4. package/dist/Chunk.d.ts.map +1 -1
  5. package/dist/Config/SecretURL.d.ts +1 -1
  6. package/dist/Config/SecretURL.d.ts.map +1 -1
  7. package/dist/Config/SecretURL.js +2 -2
  8. package/dist/Config/internal/configSecretURL.d.ts +1 -1
  9. package/dist/Config/internal/configSecretURL.d.ts.map +1 -1
  10. package/dist/Config.d.ts +7 -0
  11. package/dist/Config.d.ts.map +1 -0
  12. package/dist/Config.js +6 -0
  13. package/dist/ConfigProvider.d.ts +39 -0
  14. package/dist/ConfigProvider.d.ts.map +1 -0
  15. package/dist/ConfigProvider.js +42 -0
  16. package/dist/Context.d.ts +40 -0
  17. package/dist/Context.d.ts.map +1 -0
  18. package/dist/Context.js +67 -0
  19. package/dist/Effect.d.ts +9 -10
  20. package/dist/Effect.d.ts.map +1 -1
  21. package/dist/Effect.js +3 -6
  22. package/dist/Function.d.ts +1 -1
  23. package/dist/Function.d.ts.map +1 -1
  24. package/dist/Inputify.type.d.ts +1 -1
  25. package/dist/Layer.d.ts +6 -5
  26. package/dist/Layer.d.ts.map +1 -1
  27. package/dist/Layer.js +1 -1
  28. package/dist/NonEmptySet.d.ts +1 -1
  29. package/dist/NonEmptySet.d.ts.map +1 -1
  30. package/dist/Operations.d.ts +369 -47
  31. package/dist/Operations.d.ts.map +1 -1
  32. package/dist/Operations.js +10 -10
  33. package/dist/Option.d.ts +1 -1
  34. package/dist/Option.d.ts.map +1 -1
  35. package/dist/Pure.d.ts +5 -5
  36. package/dist/Pure.d.ts.map +1 -1
  37. package/dist/Pure.js +13 -13
  38. package/dist/Schema/Class.d.ts +69 -20
  39. package/dist/Schema/Class.d.ts.map +1 -1
  40. package/dist/Schema/Class.js +190 -22
  41. package/dist/Schema/FastCheck.d.ts +1 -1
  42. package/dist/Schema/FastCheck.d.ts.map +1 -1
  43. package/dist/Schema/Methods.d.ts +1 -1
  44. package/dist/Schema/SchemaParser.d.ts +5 -0
  45. package/dist/Schema/SchemaParser.d.ts.map +1 -0
  46. package/dist/Schema/SchemaParser.js +6 -0
  47. package/dist/Schema/SpecialJsonSchema.d.ts +33 -0
  48. package/dist/Schema/SpecialJsonSchema.d.ts.map +1 -0
  49. package/dist/Schema/SpecialJsonSchema.js +122 -0
  50. package/dist/Schema/SpecialOpenApi.d.ts +32 -0
  51. package/dist/Schema/SpecialOpenApi.d.ts.map +1 -0
  52. package/dist/Schema/SpecialOpenApi.js +123 -0
  53. package/dist/Schema/brand.d.ts +7 -2
  54. package/dist/Schema/brand.d.ts.map +1 -1
  55. package/dist/Schema/brand.js +1 -1
  56. package/dist/Schema/email.d.ts +1 -1
  57. package/dist/Schema/email.d.ts.map +1 -1
  58. package/dist/Schema/email.js +7 -4
  59. package/dist/Schema/ext.d.ts +118 -45
  60. package/dist/Schema/ext.d.ts.map +1 -1
  61. package/dist/Schema/ext.js +129 -53
  62. package/dist/Schema/moreStrings.d.ts +111 -11
  63. package/dist/Schema/moreStrings.d.ts.map +1 -1
  64. package/dist/Schema/moreStrings.js +14 -15
  65. package/dist/Schema/numbers.d.ts +127 -15
  66. package/dist/Schema/numbers.d.ts.map +1 -1
  67. package/dist/Schema/numbers.js +10 -12
  68. package/dist/Schema/phoneNumber.d.ts +1 -1
  69. package/dist/Schema/phoneNumber.d.ts.map +1 -1
  70. package/dist/Schema/phoneNumber.js +6 -3
  71. package/dist/Schema/schema.d.ts +1 -1
  72. package/dist/Schema/strings.d.ts +37 -5
  73. package/dist/Schema/strings.d.ts.map +1 -1
  74. package/dist/Schema/strings.js +1 -5
  75. package/dist/Schema.d.ts +102 -56
  76. package/dist/Schema.d.ts.map +1 -1
  77. package/dist/Schema.js +128 -64
  78. package/dist/Set.d.ts +1 -1
  79. package/dist/Set.d.ts.map +1 -1
  80. package/dist/TypeTest.d.ts +1 -1
  81. package/dist/Types.d.ts +1 -1
  82. package/dist/Widen.type.d.ts +1 -1
  83. package/dist/_ext/Array.d.ts +1 -1
  84. package/dist/_ext/Array.d.ts.map +1 -1
  85. package/dist/_ext/date.d.ts +1 -1
  86. package/dist/_ext/misc.d.ts +1 -1
  87. package/dist/_ext/ord.ext.d.ts +1 -1
  88. package/dist/_ext/ord.ext.d.ts.map +1 -1
  89. package/dist/builtin.d.ts +1 -1
  90. package/dist/builtin.d.ts.map +1 -1
  91. package/dist/client/InvalidationKeys.d.ts +29 -0
  92. package/dist/client/InvalidationKeys.d.ts.map +1 -0
  93. package/dist/client/InvalidationKeys.js +33 -0
  94. package/dist/client/apiClientFactory.d.ts +17 -31
  95. package/dist/client/apiClientFactory.d.ts.map +1 -1
  96. package/dist/client/apiClientFactory.js +81 -26
  97. package/dist/client/clientFor.d.ts +63 -10
  98. package/dist/client/clientFor.d.ts.map +1 -1
  99. package/dist/client/clientFor.js +9 -1
  100. package/dist/client/errors.d.ts +49 -25
  101. package/dist/client/errors.d.ts.map +1 -1
  102. package/dist/client/errors.js +43 -17
  103. package/dist/client/makeClient.d.ts +360 -30
  104. package/dist/client/makeClient.d.ts.map +1 -1
  105. package/dist/client/makeClient.js +63 -23
  106. package/dist/client.d.ts +2 -1
  107. package/dist/client.d.ts.map +1 -1
  108. package/dist/client.js +2 -1
  109. package/dist/faker.d.ts +1 -1
  110. package/dist/faker.d.ts.map +1 -1
  111. package/dist/http/Request.d.ts +2 -2
  112. package/dist/http/Request.d.ts.map +1 -1
  113. package/dist/http/Request.js +5 -5
  114. package/dist/http/internal/lib.d.ts +1 -1
  115. package/dist/http.d.ts +1 -1
  116. package/dist/ids.d.ts +3 -3
  117. package/dist/ids.d.ts.map +1 -1
  118. package/dist/ids.js +3 -2
  119. package/dist/index.d.ts +5 -8
  120. package/dist/index.d.ts.map +1 -1
  121. package/dist/index.js +6 -8
  122. package/dist/logger.d.ts +1 -1
  123. package/dist/middleware.d.ts +16 -9
  124. package/dist/middleware.d.ts.map +1 -1
  125. package/dist/middleware.js +13 -9
  126. package/dist/rpc/Invalidation.d.ts +490 -0
  127. package/dist/rpc/Invalidation.d.ts.map +1 -0
  128. package/dist/rpc/Invalidation.js +129 -0
  129. package/dist/rpc/MiddlewareMaker.d.ts +5 -4
  130. package/dist/rpc/MiddlewareMaker.d.ts.map +1 -1
  131. package/dist/rpc/MiddlewareMaker.js +26 -27
  132. package/dist/rpc/RpcContextMap.d.ts +3 -3
  133. package/dist/rpc/RpcContextMap.d.ts.map +1 -1
  134. package/dist/rpc/RpcContextMap.js +4 -4
  135. package/dist/rpc/RpcMiddleware.d.ts +5 -4
  136. package/dist/rpc/RpcMiddleware.d.ts.map +1 -1
  137. package/dist/rpc/RpcMiddleware.js +1 -1
  138. package/dist/rpc.d.ts +2 -2
  139. package/dist/rpc.d.ts.map +1 -1
  140. package/dist/rpc.js +2 -2
  141. package/dist/transform.d.ts +1 -1
  142. package/dist/transform.d.ts.map +1 -1
  143. package/dist/transform.js +3 -3
  144. package/dist/utils/effectify.d.ts +1 -1
  145. package/dist/utils/extend.d.ts +1 -1
  146. package/dist/utils/extend.d.ts.map +1 -1
  147. package/dist/utils/gen.d.ts +2 -2
  148. package/dist/utils/gen.d.ts.map +1 -1
  149. package/dist/utils/logLevel.d.ts +2 -2
  150. package/dist/utils/logLevel.d.ts.map +1 -1
  151. package/dist/utils/logger.d.ts +3 -3
  152. package/dist/utils/logger.d.ts.map +1 -1
  153. package/dist/utils/logger.js +3 -3
  154. package/dist/utils.d.ts +30 -10
  155. package/dist/utils.d.ts.map +1 -1
  156. package/dist/utils.js +10 -4
  157. package/dist/validation/validators.d.ts +1 -1
  158. package/dist/validation/validators.d.ts.map +1 -1
  159. package/dist/validation.d.ts +1 -1
  160. package/dist/validation.d.ts.map +1 -1
  161. package/eslint.config.mjs +2 -2
  162. package/package.json +47 -19
  163. package/src/Config/SecretURL.ts +2 -1
  164. package/src/Config.ts +14 -0
  165. package/src/ConfigProvider.ts +48 -0
  166. package/src/{ServiceMap.ts → Context.ts} +52 -59
  167. package/src/Effect.ts +12 -14
  168. package/src/Layer.ts +5 -4
  169. package/src/Operations.ts +14 -14
  170. package/src/Pure.ts +17 -18
  171. package/src/Schema/Class.ts +279 -62
  172. package/src/Schema/SchemaParser.ts +12 -0
  173. package/src/Schema/SpecialJsonSchema.ts +137 -0
  174. package/src/Schema/SpecialOpenApi.ts +130 -0
  175. package/src/Schema/brand.ts +9 -1
  176. package/src/Schema/email.ts +7 -2
  177. package/src/Schema/ext.ts +210 -80
  178. package/src/Schema/moreStrings.ts +22 -20
  179. package/src/Schema/numbers.ts +14 -16
  180. package/src/Schema/phoneNumber.ts +5 -1
  181. package/src/Schema/strings.ts +4 -8
  182. package/src/Schema.ts +265 -105
  183. package/src/client/InvalidationKeys.ts +50 -0
  184. package/src/client/apiClientFactory.ts +203 -119
  185. package/src/client/clientFor.ts +112 -23
  186. package/src/client/errors.ts +52 -26
  187. package/src/client/makeClient.ts +339 -63
  188. package/src/client.ts +1 -0
  189. package/src/http/Request.ts +7 -4
  190. package/src/ids.ts +2 -1
  191. package/src/index.ts +5 -10
  192. package/src/middleware.ts +12 -10
  193. package/src/rpc/Invalidation.ts +174 -0
  194. package/src/rpc/MiddlewareMaker.ts +36 -47
  195. package/src/rpc/README.md +2 -2
  196. package/src/rpc/RpcContextMap.ts +6 -5
  197. package/src/rpc/RpcMiddleware.ts +5 -4
  198. package/src/rpc.ts +1 -1
  199. package/src/transform.ts +2 -2
  200. package/src/utils/gen.ts +1 -1
  201. package/src/utils/logger.ts +2 -2
  202. package/src/utils.ts +47 -11
  203. package/test/dist/rpc.test.d.ts.map +1 -1
  204. package/test/dist/secretURL.test.d.ts.map +1 -0
  205. package/test/dist/special.test.d.ts.map +1 -0
  206. package/test/rpc.test.ts +38 -6
  207. package/test/schema.test.ts +591 -17
  208. package/test/secretURL.test.ts +157 -0
  209. package/test/special.test.ts +1023 -0
  210. package/test/utils.test.ts +6 -6
  211. package/tsconfig.base.json +3 -4
  212. package/tsconfig.json +0 -1
  213. package/tsconfig.json.bak +2 -2
  214. package/tsconfig.src.json +29 -29
  215. package/tsconfig.test.json +2 -2
  216. package/dist/ServiceMap.d.ts +0 -44
  217. package/dist/ServiceMap.d.ts.map +0 -1
  218. package/dist/ServiceMap.js +0 -91
@@ -1,19 +1,22 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import * as Config from "effect/Config"
3
2
  import { 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"
11
+ import * as Config from "../Config.js"
12
+ import * as Context from "../Context.js"
10
13
  import * as Effect from "../Effect.js"
11
14
  import { HttpClient, HttpClientRequest } from "../http.js"
12
- import * as Option from "../Option.js"
15
+ import { Invalidation } from "../rpc.js"
13
16
  import type * as S from "../Schema.js"
14
- import * as ServiceMap from "../ServiceMap.js"
15
17
  import { typedKeysOf, typedValuesOf } from "../utils.js"
16
- import type { Client, ClientForOptions, Requests, RequestsAny } from "./clientFor.js"
18
+ import type { Client, ClientForOptions, ExtractModuleName, RequestsAny } from "./clientFor.js"
19
+ import { InvalidationKeysFromServer } from "./InvalidationKeys.js"
17
20
 
18
21
  export interface ApiConfig {
19
22
  url: string
@@ -31,16 +34,21 @@ 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>
45
+ readonly id: string
46
+ readonly moduleName: string
47
+ readonly type: "command" | "query" | "stream"
40
48
  readonly "~decodingServices"?: unknown
41
49
  }
42
50
 
43
- class RequestName extends ServiceMap.Reference("RequestName", {
51
+ class RequestName extends Context.Reference("RequestName", {
44
52
  defaultValue: () => ({ requestName: "Unspecified", moduleName: "Error" })
45
53
  }) {}
46
54
 
@@ -84,7 +92,7 @@ type RpcHandlers<M extends RequestsAny> = {
84
92
  [K in keyof M]: Rpc.Rpc<M[K]["_tag"], M[K], M[K]["success"], M[K]["error"]>
85
93
  }
86
94
 
87
- const getFiltered = <M extends Requests>(resource: M) => {
95
+ const getFiltered = <M extends RequestsAny>(resource: M) => {
88
96
  type Filtered = {
89
97
  [K in keyof M as M[K] extends Req ? K : never]: M[K] extends Req ? M[K] : never
90
98
  }
@@ -102,13 +110,13 @@ const getFiltered = <M extends Requests>(resource: M) => {
102
110
  return filtered as unknown as Filtered
103
111
  }
104
112
 
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"]
113
+ export const getMeta = <M extends RequestsAny>(resource: M): { moduleName: ExtractModuleName<M> } => {
114
+ const first = typedValuesOf(getFiltered(resource))[0]
115
+ if (first && "moduleName" in first) return { moduleName: first.moduleName }
116
+ throw new Error("No moduleName on requests!")
109
117
  }
110
118
 
111
- export const makeRpcGroupFromRequestsAndModuleName = <M extends Requests, const ModuleName extends string>(
119
+ export const makeRpcGroupFromRequestsAndModuleName = <M extends RequestsAny, const ModuleName extends string>(
112
120
  resource: M,
113
121
  moduleName: ModuleName
114
122
  ) => {
@@ -117,7 +125,22 @@ export const makeRpcGroupFromRequestsAndModuleName = <M extends Requests, const
117
125
  const rpcs = RpcGroup
118
126
  .make(
119
127
  ...typedValuesOf(filtered).map((_) => {
120
- return Rpc.make((_ as any)._tag, { payload: _ as any, success: (_ as any).success, error: (_ as any).error })
128
+ const r = _ as any
129
+ const isStream = r.type === "stream"
130
+ return Rpc.make(r._tag, {
131
+ payload: r,
132
+ success: r.type === "command"
133
+ ? Invalidation.CommandResponseWithMetaData(r.success)
134
+ : isStream
135
+ ? Invalidation.StreamResponseChunk(r.success)
136
+ : r.success,
137
+ error: r.type === "command"
138
+ ? Invalidation.CommandFailureWithMetaData(r.error)
139
+ : isStream
140
+ ? Invalidation.StreamFailureChunk(r.error)
141
+ : r.error,
142
+ ...isStream ? { stream: true as const } : {}
143
+ })
121
144
  })
122
145
  )
123
146
  .prefix(`${moduleName}.`) as unknown as RpcGroup.RpcGroup<
@@ -126,20 +149,13 @@ export const makeRpcGroupFromRequestsAndModuleName = <M extends Requests, const
126
149
  return rpcs
127
150
  }
128
151
 
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) => {
152
+ const makeRpcTag = <M extends RequestsAny>(resource: M) => {
137
153
  const meta = getMeta(resource)
138
154
  const rpcs = makeRpcGroupFromRequestsAndModuleName(resource, meta.moduleName)
139
155
 
140
156
  // Use Object.assign instead of class extension to avoid TS2509 with complex generic return types.
141
157
  // The first type arg is `any` because this is a dynamically created tag — its identity is the string key.
142
- const TheClient = ServiceMap.Opaque<
158
+ const TheClient = Context.Opaque<
143
159
  any,
144
160
  RpcClient.RpcClient<RpcGroup.Rpcs<typeof rpcs>>
145
161
  >()(`RpcClient.${meta.moduleName}`)
@@ -153,99 +169,170 @@ const makeRpcTag = <M extends Requests>(resource: M) => {
153
169
 
154
170
  const makeApiClientFactory = Effect
155
171
  .gen(function*() {
156
- const ctx = yield* Effect.services<RpcSerialization.RpcSerialization | HttpClient.HttpClient>()
157
- const makeClientFor = <M extends Requests>(
172
+ const ctx = yield* Effect.context<RpcSerialization.RpcSerialization | HttpClient.HttpClient>()
173
+ const makeClientFor = Effect.fnUntraced(function*<M extends RequestsAny>(
158
174
  resource: M,
159
175
  requestLevelLayers = Layer.empty,
160
176
  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?
177
+ ) {
178
+ const TheClient = makeRpcTag(resource)
179
+
180
+ const meta = getMeta(resource)
181
+
182
+ // TODO: somehow we need a protocol per REQUEST kind of it seems ...
183
+ // otherwise it locks up on the client, navigation remains empty...
184
+ const clientLayer = TheClient.layer.pipe(
185
+ // add ApiClientFactory for nested schemas
186
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
187
+ Layer.provide(Layer.succeed(ApiClientFactory, makeClientForCached as any)),
188
+ Layer.provide(
189
+ RpcClient
190
+ .layerProtocolHttp({ url: "" }) // why not here set meta.moduleName as root?
191
+ .pipe(
192
+ Layer.provideMerge(Layer.succeedContext(ctx))
193
+ )
194
+ )
195
+ )
196
+ const mr = ManagedRuntime.make(clientLayer)
197
+
198
+ const filtered = getFiltered(resource)
199
+
200
+ const unwrapCommand = (eff: Effect.Effect<any, any, any>): Effect.Effect<any, any, any> =>
201
+ eff.pipe(
202
+ Effect.flatMap((result: any) =>
203
+ Effect.gen(function*() {
204
+ const keys: ReadonlyArray<Invalidation.InvalidationKey> = result?.metadata?.invalidateQueries ?? []
205
+ const invalidationKeys = yield* InvalidationKeysFromServer
206
+ yield* Effect.forEach(keys, (key) => invalidationKeys.add(key), { discard: true })
207
+ return result.payload
208
+ })
209
+ ),
210
+ // V2: unwrap CommandFailureWithMetaData failures — forward keys, re-fail with the
211
+ // original error so callers see the unmodified error type.
212
+ Effect.catch((result: any) =>
213
+ result?._tag === "CommandFailureWithMetaData"
214
+ ? Effect.gen(function*() {
215
+ const keys: ReadonlyArray<Invalidation.InvalidationKey> = result.metadata?.invalidateQueries ?? []
216
+ const invalidationKeys = yield* InvalidationKeysFromServer
217
+ yield* Effect.forEach(keys, (key) => invalidationKeys.add(key), { discard: true })
218
+ return yield* Effect.fail(result.error)
177
219
  })
178
- .pipe(
179
- Layer.provideMerge(Layer.succeedServices(ctx))
180
- )
220
+ : Effect.fail(result)
181
221
  )
182
222
  )
183
- const mr = ManagedRuntime.make(clientLayer)
184
223
 
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
224
+ return {
225
+ mr,
226
+ client: typedKeysOf(filtered)
227
+ .reduce((prev, cur) => {
228
+ const h = filtered[cur]!
229
+
230
+ const Request = h
231
+
232
+ const id = `${meta.moduleName}.${cur as string}`
233
+ .replaceAll(".js", "")
234
+
235
+ const requestMeta = {
236
+ Request,
237
+ id,
238
+ options
239
+ }
240
+
241
+ const requestNameLayer = Layer.succeed(RequestName, {
242
+ requestName: cur as string,
243
+ moduleName: meta.moduleName
244
+ })
245
+
246
+ const layers = requestLevelLayers.pipe(Layer.provideMerge(requestNameLayer))
247
+
248
+ const fields = Struct.omit(Request.fields, ["_tag"] as const)
249
+ const requestAttr = `${meta.moduleName}.${h._tag}`
250
+ const isCommand = h.type === "command"
251
+ const isStream = h.type === "stream"
252
+
253
+ const buildEffect = (input: any) =>
254
+ mr.contextEffect.pipe(
255
+ Effect.flatMap((svcs) => {
256
+ const rpcEffect = TheClient
257
+ .use((client) =>
258
+ (client as any)[requestAttr]!(Request.make(input)) as Effect.Effect<any, any, never>
259
+ )
260
+ .pipe(
261
+ Effect.provide(layers),
262
+ Effect.provide(svcs)
263
+ )
264
+ return isCommand ? unwrapCommand(rpcEffect) : rpcEffect
265
+ })
266
+ )
193
267
 
194
- const id = `${meta.moduleName}.${cur as string}`
195
- .replaceAll(".js", "")
268
+ const buildStream = (input: any) =>
269
+ Stream.unwrap(
270
+ mr.contextEffect.pipe(
271
+ Effect.flatMap((svcs) =>
272
+ TheClient
273
+ .useSync((client) => {
274
+ const rpcStream = (client as any)[requestAttr]!(
275
+ Request.make(input)
276
+ ) as Stream.Stream<any, any, any>
277
+ return rpcStream.pipe(
278
+ // Collect server invalidation keys from the "done" chunk, then discard it.
279
+ Stream.tap((item: any) =>
280
+ item._tag === "done" || item._tag === "metadata"
281
+ ? InvalidationKeysFromServer.use((svc) =>
282
+ Effect.forEach(
283
+ (item.metadata as Invalidation.CommandMetaData).invalidateQueries,
284
+ svc.add,
285
+ { discard: true }
286
+ )
287
+ )
288
+ : Effect.void
289
+ ),
290
+ Stream.filter((item: any) => item._tag === "value"),
291
+ Stream.map((item: any) => item.value),
292
+ // V2: unwrap StreamFailureChunk — forward keys from failures too,
293
+ // then re-fail with the original error so callers see the unmodified
294
+ // error type.
295
+ Stream.catch((err: any) =>
296
+ err?._tag === "error" && err?.metadata
297
+ ? Stream.fromEffect(
298
+ InvalidationKeysFromServer.use((svc) =>
299
+ Effect
300
+ .forEach(
301
+ (err.metadata as Invalidation.CommandMetaData).invalidateQueries,
302
+ svc.add,
303
+ { discard: true }
304
+ )
305
+ .pipe(Effect.flatMap(() => Effect.fail(err.error)))
306
+ )
307
+ )
308
+ : Stream.fail(err)
309
+ ),
310
+ Stream.provide(layers),
311
+ Stream.provide(svcs)
312
+ )
313
+ })
314
+ .pipe(Effect.provide(svcs))
315
+ )
316
+ )
317
+ )
196
318
 
197
- const requestMeta = {
198
- Request,
199
- id,
200
- options
319
+ // @ts-expect-error doc
320
+ prev[cur] = Object.keys(fields).length === 0
321
+ ? {
322
+ handler: isStream ? buildStream({}) : buildEffect({}),
323
+ ...requestMeta
324
+ }
325
+ : {
326
+ handler: isStream
327
+ ? (req: any) => buildStream(req)
328
+ : (req: any) => buildEffect(req),
329
+ ...requestMeta
201
330
  }
202
331
 
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
- })
332
+ return prev
333
+ }, {} as Client<M, ExtractModuleName<M>>)
334
+ }
335
+ })
249
336
 
250
337
  const register: ManagedRuntime.ManagedRuntime<any, any>[] = []
251
338
  yield* Effect.addFinalizer(() => Effect.forEach(register, (mr) => mr.disposeEffect))
@@ -259,19 +346,16 @@ const makeApiClientFactory = Effect
259
346
  cacheL.set(requestLevelLayers, cache)
260
347
  }
261
348
 
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
- })
349
+ return Effect.fnUntraced(function*<M extends RequestsAny>(models: M) {
350
+ const found = cache.get(models) as Client<M, ExtractModuleName<M>> | undefined
351
+ if (found) {
352
+ return found
353
+ }
354
+ const m = yield* makeClientFor(models, requestLevelLayers, options)
355
+ cache.set(models, m.client)
356
+ register.push(m.mr)
357
+ return m.client
358
+ })
275
359
  }
276
360
 
277
361
  return makeClientForCached
@@ -281,7 +365,7 @@ const makeApiClientFactory = Effect
281
365
  * Used to create clients for resource modules.
282
366
  */
283
367
  export class ApiClientFactory
284
- extends ServiceMap.Opaque<ApiClientFactory, Effect.Success<typeof makeApiClientFactory>>()("ApiClientFactory")
368
+ extends Context.Opaque<ApiClientFactory, Effect.Success<typeof makeApiClientFactory>>()("ApiClientFactory")
285
369
  {
286
370
  static readonly layer = (config: ApiConfig) =>
287
371
  ApiClientFactory.toLayer(makeApiClientFactory).pipe(Layer.provide(RpcSerializationLayer(config)))
@@ -294,7 +378,7 @@ export class ApiClientFactory
294
378
 
295
379
  static readonly makeFor =
296
380
  (requestLevelLayers: Layer.Layer<never, never, never>, options?: ClientForOptions) =>
297
- <M extends Requests>(
381
+ <M extends RequestsAny>(
298
382
  resource: M
299
383
  ) =>
300
384
  ApiClientFactory.use((apiClientFactory) => {
@@ -2,7 +2,7 @@
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
8
  import type * as Effect from "../Effect.js"
@@ -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,
@@ -66,16 +71,20 @@ export type ExtractEResponse<T> = T extends S.Codec<any> ? S.Codec.Encoded<T>
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[]
77
76
  }
78
77
 
78
+ // $Project/$Configuration.Index
79
+ // -> "$Project", "$Configuration", "Index"
80
+ export const makeQueryKey = ({ id, options }: { id: string; options?: ClientForOptions }) =>
81
+ id
82
+ .split("/")
83
+ .filter((segment: string) => !options || !options.skipQueryKey?.includes(segment))
84
+ .map((segment: string) => "$" + segment)
85
+ .join(".")
86
+ .split(".")
87
+
79
88
  export interface RequestHandler<A, E, R, Request extends Req, Id extends string> {
80
89
  handler: Effect.Effect<A, E, R>
81
90
  id: Id
@@ -90,24 +99,104 @@ export interface RequestHandlerWithInput<I, A, E, R, Request extends Req, Id ext
90
99
  Request: Request
91
100
  }
92
101
 
102
+ export interface RequestStreamHandler<A, E, R, Request extends Req, Id extends string, Final = void> {
103
+ handler: Stream.Stream<A, E, R>
104
+ id: Id
105
+ options?: ClientForOptions
106
+ Request: Request
107
+ /**
108
+ * Phantom type property (never set at runtime) that carries the `Final` type to
109
+ * `StreamMutationWithExtensions`. The tilde prefix follows the Effect convention for
110
+ * phantom/virtual properties and prevents accidental runtime access.
111
+ * When the stream fails, the execute effect still resolves (with `undefined`);
112
+ * check the reactive `AsyncResult` ref to distinguish success from failure.
113
+ */
114
+ readonly "~final"?: Final
115
+ }
116
+
117
+ export interface RequestStreamHandlerWithInput<I, A, E, R, Request extends Req, Id extends string, Final = void> {
118
+ handler: (i: I) => Stream.Stream<A, E, R>
119
+ id: Id
120
+ options?: ClientForOptions
121
+ Request: Request
122
+ /**
123
+ * Phantom type property (never set at runtime) that carries the `Final` type to
124
+ * `StreamMutationWithExtensions`. The tilde prefix follows the Effect convention for
125
+ * phantom/virtual properties and prevents accidental runtime access.
126
+ * When the stream fails, the execute effect still resolves (with `undefined`);
127
+ * check the reactive `AsyncResult` ref to distinguish success from failure.
128
+ */
129
+ readonly "~final"?: Final
130
+ }
131
+
93
132
  // make sure this is exported or d.ts of apiClientFactory breaks?!
94
133
  type ReqDecodingServices<M> = M extends { readonly "~decodingServices": infer DS } ? DS : never
95
134
 
96
- 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}`
135
+ type RequestFields<I> = I extends { readonly fields: infer F extends S.Struct.Fields } ? F : never
136
+
137
+ type RequestInputFromFields<I> = [RequestFields<I>] extends [never] ? never
138
+ : keyof RequestFields<I> extends never ? void
139
+ : S.Schema.Type<S.Struct<RequestFields<I>>>
140
+
141
+ type RequestInputFromOverloadedMake<I extends { readonly make: (...args: any[]) => any }> =
142
+ Parameters<I["make"]> extends [] ? void
143
+ : Parameters<I["make"]>[0]
144
+
145
+ export type RequestInputFromMake<I extends { readonly make: (...args: any[]) => any }> =
146
+ [RequestInputFromFields<I>] extends [never] ? RequestInputFromOverloadedMake<I>
147
+ : RequestInputFromFields<I>
148
+
149
+ type NormalizedRequestInput<T> = Omit<Exclude<T, undefined>, "_tag">
150
+
151
+ // If make's first param has only an optional _tag property, treat as no-input handler.
152
+ type IsTagOnly<T> = [Exclude<T, undefined>] extends [never] ? true
153
+ : [keyof NormalizedRequestInput<T>] extends [never] ? true
154
+ : false
155
+
156
+ type RequestInput<I extends { readonly make: (...args: any[]) => any }> = NormalizedRequestInput<
157
+ RequestInputFromMake<I>
158
+ >
159
+
160
+ /** Extracts the final-value type from a stream request. Defaults to `void` when no `final` schema is set. */
161
+ type FinalTypeOf<T extends Req> = T extends { readonly final: infer F extends S.Top } ? S.Schema.Type<F>
162
+ : void
163
+
164
+ type RequestHandlerFor<R, E, T extends Req, Id extends string> = T["type"] extends "stream"
165
+ ? IsTagOnly<RequestInputFromMake<T>> extends true ? RequestStreamHandler<
166
+ S.Schema.Type<T["success"]>,
167
+ S.Schema.Type<T["error"]> | E,
168
+ R | ReqDecodingServices<T>,
169
+ T,
170
+ Id,
171
+ FinalTypeOf<T>
104
172
  >
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}`
173
+ : RequestStreamHandlerWithInput<
174
+ RequestInput<T>,
175
+ S.Schema.Type<T["success"]>,
176
+ S.Schema.Type<T["error"]> | E,
177
+ R | ReqDecodingServices<T>,
178
+ T,
179
+ Id,
180
+ FinalTypeOf<T>
181
+ >
182
+ : IsTagOnly<RequestInputFromMake<T>> extends true ? RequestHandler<
183
+ S.Schema.Type<T["success"]>,
184
+ S.Schema.Type<T["error"]> | E,
185
+ R | ReqDecodingServices<T>,
186
+ T,
187
+ Id
112
188
  >
189
+ : RequestHandlerWithInput<
190
+ RequestInput<T>,
191
+ S.Schema.Type<T["success"]>,
192
+ S.Schema.Type<T["error"]> | E,
193
+ R | ReqDecodingServices<T>,
194
+ T,
195
+ Id
196
+ >
197
+
198
+ export type RequestHandlers<R, E, M extends RequestsAny, ModuleName extends string> = {
199
+ [K in keyof M as M[K] extends Req ? K : never]: Extract<M[K], Req> extends infer T extends Req
200
+ ? RequestHandlerFor<R, E, T, `${ModuleName}.${K & string}`>
201
+ : never
113
202
  }