effect-app 4.0.0-beta.1 → 4.0.0-beta.100

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 (142) hide show
  1. package/CHANGELOG.md +444 -0
  2. package/dist/Config/SecretURL.js +2 -2
  3. package/dist/Config.d.ts +7 -0
  4. package/dist/Config.d.ts.map +1 -0
  5. package/dist/Config.js +6 -0
  6. package/dist/ConfigProvider.d.ts +39 -0
  7. package/dist/ConfigProvider.d.ts.map +1 -0
  8. package/dist/ConfigProvider.js +42 -0
  9. package/dist/Context.d.ts +40 -0
  10. package/dist/Context.d.ts.map +1 -0
  11. package/dist/Context.js +66 -0
  12. package/dist/Effect.d.ts +8 -7
  13. package/dist/Effect.d.ts.map +1 -1
  14. package/dist/Effect.js +3 -2
  15. package/dist/Layer.d.ts +5 -4
  16. package/dist/Layer.d.ts.map +1 -1
  17. package/dist/Layer.js +1 -1
  18. package/dist/Operations.d.ts +62 -25
  19. package/dist/Operations.d.ts.map +1 -1
  20. package/dist/Pure.d.ts +2 -2
  21. package/dist/Pure.d.ts.map +1 -1
  22. package/dist/Pure.js +13 -13
  23. package/dist/Schema/Class.d.ts +43 -10
  24. package/dist/Schema/Class.d.ts.map +1 -1
  25. package/dist/Schema/Class.js +89 -16
  26. package/dist/Schema/SpecialJsonSchema.d.ts +21 -0
  27. package/dist/Schema/SpecialJsonSchema.d.ts.map +1 -0
  28. package/dist/Schema/SpecialJsonSchema.js +59 -0
  29. package/dist/Schema/SpecialOpenApi.d.ts +32 -0
  30. package/dist/Schema/SpecialOpenApi.d.ts.map +1 -0
  31. package/dist/Schema/SpecialOpenApi.js +123 -0
  32. package/dist/Schema/brand.d.ts +8 -5
  33. package/dist/Schema/brand.d.ts.map +1 -1
  34. package/dist/Schema/brand.js +1 -1
  35. package/dist/Schema/email.d.ts.map +1 -1
  36. package/dist/Schema/email.js +9 -4
  37. package/dist/Schema/ext.d.ts +103 -46
  38. package/dist/Schema/ext.d.ts.map +1 -1
  39. package/dist/Schema/ext.js +110 -51
  40. package/dist/Schema/moreStrings.d.ts +25 -13
  41. package/dist/Schema/moreStrings.d.ts.map +1 -1
  42. package/dist/Schema/moreStrings.js +14 -9
  43. package/dist/Schema/numbers.d.ts +11 -11
  44. package/dist/Schema/numbers.d.ts.map +1 -1
  45. package/dist/Schema/numbers.js +10 -9
  46. package/dist/Schema/phoneNumber.d.ts.map +1 -1
  47. package/dist/Schema/phoneNumber.js +8 -3
  48. package/dist/Schema/strings.d.ts +4 -4
  49. package/dist/Schema/strings.d.ts.map +1 -1
  50. package/dist/Schema.d.ts +22 -55
  51. package/dist/Schema.d.ts.map +1 -1
  52. package/dist/Schema.js +43 -64
  53. package/dist/client/apiClientFactory.d.ts +11 -28
  54. package/dist/client/apiClientFactory.d.ts.map +1 -1
  55. package/dist/client/apiClientFactory.js +21 -23
  56. package/dist/client/clientFor.d.ts +8 -7
  57. package/dist/client/clientFor.d.ts.map +1 -1
  58. package/dist/client/errors.d.ts +18 -9
  59. package/dist/client/errors.d.ts.map +1 -1
  60. package/dist/client/errors.js +35 -10
  61. package/dist/client/makeClient.d.ts +21 -16
  62. package/dist/client/makeClient.d.ts.map +1 -1
  63. package/dist/client/makeClient.js +32 -34
  64. package/dist/http/Request.d.ts.map +1 -1
  65. package/dist/http/Request.js +5 -5
  66. package/dist/ids.d.ts +10 -10
  67. package/dist/ids.d.ts.map +1 -1
  68. package/dist/ids.js +3 -2
  69. package/dist/index.d.ts +3 -8
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.js +4 -9
  72. package/dist/middleware.d.ts +2 -2
  73. package/dist/middleware.d.ts.map +1 -1
  74. package/dist/middleware.js +3 -3
  75. package/dist/rpc/MiddlewareMaker.d.ts +6 -5
  76. package/dist/rpc/MiddlewareMaker.d.ts.map +1 -1
  77. package/dist/rpc/MiddlewareMaker.js +7 -6
  78. package/dist/rpc/RpcContextMap.d.ts +2 -2
  79. package/dist/rpc/RpcContextMap.d.ts.map +1 -1
  80. package/dist/rpc/RpcContextMap.js +4 -4
  81. package/dist/rpc/RpcMiddleware.d.ts +6 -5
  82. package/dist/rpc/RpcMiddleware.d.ts.map +1 -1
  83. package/dist/rpc/RpcMiddleware.js +1 -1
  84. package/dist/utils/gen.d.ts +1 -1
  85. package/dist/utils/gen.d.ts.map +1 -1
  86. package/dist/utils/logger.d.ts +2 -2
  87. package/dist/utils/logger.d.ts.map +1 -1
  88. package/dist/utils/logger.js +3 -3
  89. package/dist/utils.d.ts +18 -0
  90. package/dist/utils.d.ts.map +1 -1
  91. package/dist/utils.js +24 -5
  92. package/package.json +29 -17
  93. package/src/Config/SecretURL.ts +1 -1
  94. package/src/Config.ts +14 -0
  95. package/src/ConfigProvider.ts +48 -0
  96. package/src/{ServiceMap.ts → Context.ts} +57 -64
  97. package/src/Effect.ts +11 -9
  98. package/src/Layer.ts +5 -4
  99. package/src/Pure.ts +17 -18
  100. package/src/Schema/Class.ts +118 -30
  101. package/src/Schema/SpecialJsonSchema.ts +69 -0
  102. package/src/Schema/SpecialOpenApi.ts +130 -0
  103. package/src/Schema/brand.ts +13 -7
  104. package/src/Schema/email.ts +10 -2
  105. package/src/Schema/ext.ts +185 -82
  106. package/src/Schema/moreStrings.ts +27 -18
  107. package/src/Schema/numbers.ts +9 -8
  108. package/src/Schema/phoneNumber.ts +8 -1
  109. package/src/Schema.ts +79 -103
  110. package/src/client/apiClientFactory.ts +35 -43
  111. package/src/client/clientFor.ts +8 -3
  112. package/src/client/errors.ts +46 -12
  113. package/src/client/makeClient.ts +122 -75
  114. package/src/http/Request.ts +7 -4
  115. package/src/ids.ts +4 -3
  116. package/src/index.ts +3 -11
  117. package/src/middleware.ts +2 -2
  118. package/src/rpc/MiddlewareMaker.ts +12 -10
  119. package/src/rpc/RpcContextMap.ts +6 -5
  120. package/src/rpc/RpcMiddleware.ts +7 -6
  121. package/src/utils/gen.ts +1 -1
  122. package/src/utils/logger.ts +2 -2
  123. package/src/utils.ts +26 -4
  124. package/test/dist/moreStrings.test.d.ts.map +1 -0
  125. package/test/dist/rpc.test.d.ts.map +1 -1
  126. package/test/dist/secretURL.test.d.ts.map +1 -0
  127. package/test/dist/special.test.d.ts.map +1 -0
  128. package/test/moreStrings.test.ts +17 -0
  129. package/test/rpc.test.ts +28 -6
  130. package/test/schema.test.ts +397 -4
  131. package/test/secretURL.test.ts +157 -0
  132. package/test/special.test.ts +729 -0
  133. package/test/utils.test.ts +2 -2
  134. package/tsconfig.base.json +0 -1
  135. package/tsconfig.json +0 -1
  136. package/dist/ServiceMap.d.ts +0 -44
  137. package/dist/ServiceMap.d.ts.map +0 -1
  138. package/dist/ServiceMap.js +0 -91
  139. package/dist/Struct.d.ts +0 -44
  140. package/dist/Struct.d.ts.map +0 -1
  141. package/dist/Struct.js +0 -29
  142. package/src/Struct.ts +0 -54
@@ -1,5 +1,4 @@
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"
@@ -7,13 +6,14 @@ import * as Predicate from "effect/Predicate"
7
6
  import * as Schema from "effect/Schema"
8
7
  import * as Struct from "effect/Struct"
9
8
  import { Rpc, RpcClient, RpcGroup, RpcSerialization } from "effect/unstable/rpc"
9
+ import * as Config from "../Config.js"
10
+ import * as Context from "../Context.js"
10
11
  import * as Effect from "../Effect.js"
11
12
  import { HttpClient, HttpClientRequest } from "../http.js"
12
13
  import * as Option from "../Option.js"
13
14
  import type * as S from "../Schema.js"
14
- import * as ServiceMap from "../ServiceMap.js"
15
15
  import { typedKeysOf, typedValuesOf } from "../utils.js"
16
- import type { Client, ClientForOptions, Requests, RequestsAny } from "./clientFor.js"
16
+ import type { Client, ClientForOptions, ExtractModuleName, RequestsAny } from "./clientFor.js"
17
17
 
18
18
  export interface ApiConfig {
19
19
  url: string
@@ -37,10 +37,12 @@ export type Req = S.Top & {
37
37
  success: S.Top
38
38
  error: S.Top
39
39
  config?: Record<string, any>
40
+ readonly id: string
41
+ readonly moduleName: string
40
42
  readonly "~decodingServices"?: unknown
41
43
  }
42
44
 
43
- class RequestName extends ServiceMap.Reference("RequestName", {
45
+ class RequestName extends Context.Reference("RequestName", {
44
46
  defaultValue: () => ({ requestName: "Unspecified", moduleName: "Error" })
45
47
  }) {}
46
48
 
@@ -50,17 +52,17 @@ export const HttpClientLayer = (config: ApiConfig) =>
50
52
  Effect
51
53
  .gen(function*() {
52
54
  const baseClient = yield* HttpClient.HttpClient
53
- const ctx = yield* RequestName
54
55
  const client = baseClient.pipe(
55
56
  HttpClient.mapRequest(HttpClientRequest.prependUrl(config.url + "/rpc")),
56
57
  HttpClient.mapRequest(
57
58
  HttpClientRequest.setHeaders(config.headers.pipe(Option.getOrElse(() => ({}))))
58
59
  ),
59
- HttpClient.mapRequest((req) =>
60
- flow(
61
- HttpClientRequest.appendUrlParam("action", ctx.requestName),
62
- HttpClientRequest.appendUrl("/" + ctx.moduleName)
63
- )(req)
60
+ HttpClient.mapRequestEffect((req) =>
61
+ Effect.map(RequestName.asEffect(), (ctx) =>
62
+ flow(
63
+ HttpClientRequest.appendUrlParam("action", ctx.requestName),
64
+ HttpClientRequest.appendUrl("/" + ctx.moduleName)
65
+ )(req))
64
66
  )
65
67
  )
66
68
  return client
@@ -76,7 +78,7 @@ export const HttpClientFromConfigLayer = Layer.unwrap(
76
78
 
77
79
  export const RpcSerializationLayer = (config: ApiConfig) =>
78
80
  Layer.mergeAll(
79
- RpcSerialization.layerJson,
81
+ RpcSerialization.layerNdjson,
80
82
  HttpClientLayer(config)
81
83
  )
82
84
 
@@ -84,14 +86,14 @@ type RpcHandlers<M extends RequestsAny> = {
84
86
  [K in keyof M]: Rpc.Rpc<M[K]["_tag"], M[K], M[K]["success"], M[K]["error"]>
85
87
  }
86
88
 
87
- const getFiltered = <M extends Requests>(resource: M) => {
89
+ const getFiltered = <M extends RequestsAny>(resource: M) => {
88
90
  type Filtered = {
89
91
  [K in keyof M as M[K] extends Req ? K : never]: M[K] extends Req ? M[K] : never
90
92
  }
91
93
  // TODO: Record.filter
92
94
  const filtered = typedKeysOf(resource).reduce((acc, cur) => {
93
95
  if (
94
- Predicate.isObject(resource[cur])
96
+ Predicate.isObjectKeyword(resource[cur])
95
97
  && (resource[cur].success)
96
98
  ) {
97
99
  acc[cur as keyof Filtered] = resource[cur] as any
@@ -102,13 +104,13 @@ const getFiltered = <M extends Requests>(resource: M) => {
102
104
  return filtered as unknown as Filtered
103
105
  }
104
106
 
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"]
107
+ export const getMeta = <M extends RequestsAny>(resource: M): { moduleName: ExtractModuleName<M> } => {
108
+ const first = typedValuesOf(getFiltered(resource))[0]
109
+ if (first && "moduleName" in first) return { moduleName: first.moduleName } as any
110
+ throw new Error("No moduleName on requests!")
109
111
  }
110
112
 
111
- export const makeRpcGroupFromRequestsAndModuleName = <M extends Requests, const ModuleName extends string>(
113
+ export const makeRpcGroupFromRequestsAndModuleName = <M extends RequestsAny, const ModuleName extends string>(
112
114
  resource: M,
113
115
  moduleName: ModuleName
114
116
  ) => {
@@ -126,38 +128,28 @@ export const makeRpcGroupFromRequestsAndModuleName = <M extends Requests, const
126
128
  return rpcs
127
129
  }
128
130
 
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) => {
131
+ const makeRpcTag = <M extends RequestsAny>(resource: M) => {
137
132
  const meta = getMeta(resource)
138
133
  const rpcs = makeRpcGroupFromRequestsAndModuleName(resource, meta.moduleName)
139
134
 
140
135
  // Use Object.assign instead of class extension to avoid TS2509 with complex generic return types.
141
136
  // The first type arg is `any` because this is a dynamically created tag — its identity is the string key.
142
- const TheClient = ServiceMap.Opaque<
137
+ const TheClient = Context.Opaque<
143
138
  any,
144
139
  RpcClient.RpcClient<RpcGroup.Rpcs<typeof rpcs>>
145
140
  >()(`RpcClient.${meta.moduleName}`)
146
141
  // Use Layer.effect directly (not TheClient.toLayer) so TypeScript properly excludes Scope
147
142
  const layer = Layer.effect(
148
143
  TheClient,
149
- Effect.map(
150
- RpcClient.make(rpcs, { spanPrefix: "RpcClient." + meta.moduleName }),
151
- (cl) => (cl as any)[meta.moduleName]
152
- )
144
+ RpcClient.make(rpcs, { spanPrefix: "RpcClient." + meta.moduleName })
153
145
  )
154
146
  return Object.assign(TheClient, { layer })
155
147
  }
156
148
 
157
149
  const makeApiClientFactory = Effect
158
150
  .gen(function*() {
159
- const ctx = yield* Effect.services<RpcSerialization.RpcSerialization | HttpClient.HttpClient>()
160
- const makeClientFor = <M extends Requests>(
151
+ const ctx = yield* Effect.context<RpcSerialization.RpcSerialization | HttpClient.HttpClient>()
152
+ const makeClientFor = <M extends RequestsAny>(
161
153
  resource: M,
162
154
  requestLevelLayers = Layer.empty,
163
155
  options?: ClientForOptions
@@ -179,7 +171,7 @@ const makeApiClientFactory = Effect
179
171
  url: "" // why not here set meta.moduleName as root?
180
172
  })
181
173
  .pipe(
182
- Layer.provideMerge(Layer.succeedServices(ctx))
174
+ Layer.provideMerge(Layer.succeedContext(ctx))
183
175
  )
184
176
  )
185
177
  )
@@ -188,7 +180,7 @@ const makeApiClientFactory = Effect
188
180
  const filtered = getFiltered(resource)
189
181
  return {
190
182
  mr,
191
- client: (typedKeysOf(filtered)
183
+ client: typedKeysOf(filtered)
192
184
  .reduce((prev, cur) => {
193
185
  const h = filtered[cur]!
194
186
 
@@ -211,11 +203,11 @@ const makeApiClientFactory = Effect
211
203
  const layers = requestLevelLayers.pipe(Layer.provideMerge(requestNameLayer))
212
204
 
213
205
  const fields = Struct.omit(Request.fields, ["_tag"] as const)
214
- const requestAttr = h._tag
206
+ const requestAttr = `${meta.moduleName}.${h._tag}`
215
207
  // @ts-expect-error doc
216
208
  prev[cur] = Object.keys(fields).length === 0
217
209
  ? {
218
- handler: mr.servicesEffect.pipe(
210
+ handler: mr.contextEffect.pipe(
219
211
  Effect.flatMap((svcs) =>
220
212
  TheClient
221
213
  .use((client) => (client as any)[requestAttr]!(new Request()) as Effect.Effect<any, any, never>)
@@ -229,7 +221,7 @@ const makeApiClientFactory = Effect
229
221
  }
230
222
  : {
231
223
  handler: (req: any) =>
232
- mr.servicesEffect.pipe(
224
+ mr.contextEffect.pipe(
233
225
  Effect.flatMap((svcs) =>
234
226
  TheClient
235
227
  .use((client) =>
@@ -246,7 +238,7 @@ const makeApiClientFactory = Effect
246
238
  }
247
239
 
248
240
  return prev
249
- }, {} as Client<M, M["meta"]["moduleName"]>))
241
+ }, {} as Client<M, ExtractModuleName<M>>)
250
242
  }
251
243
  })
252
244
 
@@ -262,9 +254,9 @@ const makeApiClientFactory = Effect
262
254
  cacheL.set(requestLevelLayers, cache)
263
255
  }
264
256
 
265
- return <M extends Requests>(
257
+ return <M extends RequestsAny>(
266
258
  models: M
267
- ): Effect.Effect<Client<M, M["meta"]["moduleName"]>> =>
259
+ ): Effect.Effect<Client<M, ExtractModuleName<M>>> =>
268
260
  Effect.gen(function*() {
269
261
  const found = cache.get(models)
270
262
  if (found) {
@@ -284,7 +276,7 @@ const makeApiClientFactory = Effect
284
276
  * Used to create clients for resource modules.
285
277
  */
286
278
  export class ApiClientFactory
287
- extends ServiceMap.Opaque<ApiClientFactory, Effect.Success<typeof makeApiClientFactory>>()("ApiClientFactory")
279
+ extends Context.Opaque<ApiClientFactory, Effect.Success<typeof makeApiClientFactory>>()("ApiClientFactory")
288
280
  {
289
281
  static readonly layer = (config: ApiConfig) =>
290
282
  ApiClientFactory.toLayer(makeApiClientFactory).pipe(Layer.provide(RpcSerializationLayer(config)))
@@ -297,7 +289,7 @@ export class ApiClientFactory
297
289
 
298
290
  static readonly makeFor =
299
291
  (requestLevelLayers: Layer.Layer<never, never, never>, options?: ClientForOptions) =>
300
- <M extends Requests>(
292
+ <M extends RequestsAny>(
301
293
  resource: M
302
294
  ) =>
303
295
  ApiClientFactory.use((apiClientFactory) => {
@@ -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,11 +63,11 @@ 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.Schema<any> ? S.Schema.Type<T>
66
+ export type ExtractResponse<T> = T extends S.Codec<any> ? S.Schema.Type<T>
62
67
  : T extends unknown ? void
63
68
  : never
64
69
 
65
- export type ExtractEResponse<T> = T extends S.Schema<any> ? S.Codec.Encoded<T>
70
+ export type ExtractEResponse<T> = T extends S.Codec<any> ? S.Codec.Encoded<T>
66
71
  : T extends unknown ? void
67
72
  : never
68
73
 
@@ -1,5 +1,5 @@
1
1
  /** @effect-diagnostics overriddenSchemaConstructor:skip-file */
2
- import { TaggedError } from "effect-app/Schema"
2
+ import { TaggedErrorClass } from "effect-app/Schema"
3
3
  import * as Cause from "effect/Cause"
4
4
  import * as S from "../Schema.js"
5
5
 
@@ -21,7 +21,7 @@ export const tryToJson = (error: { toJSON(): unknown; toString(): string }) => {
21
21
 
22
22
  // eslint-disable-next-line unused-imports/no-unused-vars
23
23
  // @ts-expect-error type not used
24
- export class NotFoundError<ItemType = string> extends TaggedError<NotFoundError<ItemType>>()("NotFoundError", {
24
+ export class NotFoundError<ItemType = string> extends TaggedErrorClass<NotFoundError<ItemType>>()("NotFoundError", {
25
25
  type: S.String,
26
26
  id: S.Unknown
27
27
  }) {
@@ -34,28 +34,43 @@ export class NotFoundError<ItemType = string> extends TaggedError<NotFoundError<
34
34
  override get message() {
35
35
  return `Didn't find ${(this as any).type}#${JSON.stringify((this as any).id)}`
36
36
  }
37
+ override toString() {
38
+ return `NotFoundError: ${this.message}`
39
+ }
37
40
  }
38
41
 
39
42
  const messageFallback = (messageOrObject?: string | { message: string }) =>
40
43
  typeof messageOrObject === "object" ? messageOrObject : { message: messageOrObject ?? "" }
41
44
 
42
- export class InvalidStateError extends TaggedError<InvalidStateError>()("InvalidStateError", {
45
+ export class InvalidStateError extends TaggedErrorClass<InvalidStateError>()("InvalidStateError", {
43
46
  message: S.String
44
47
  }) {
45
48
  constructor(messageOrObject: string | { message: string; cause?: unknown }, disableValidation?: boolean) {
46
- super(typeof messageOrObject === "object" ? messageOrObject : { message: messageOrObject } as any, disableValidation as any)
49
+ super(
50
+ typeof messageOrObject === "object" ? messageOrObject : { message: messageOrObject } as any,
51
+ disableValidation as any
52
+ )
53
+ }
54
+ override toString() {
55
+ return `InvalidStateError: ${this.message}`
47
56
  }
48
57
  }
49
58
 
50
- export class ServiceUnavailableError extends TaggedError<ServiceUnavailableError>()("ServiceUnavailableError", {
59
+ export class ServiceUnavailableError extends TaggedErrorClass<ServiceUnavailableError>()("ServiceUnavailableError", {
51
60
  message: S.String
52
61
  }) {
53
62
  constructor(messageOrObject: string | { message: string; cause?: unknown }, disableValidation?: boolean) {
54
- super(typeof messageOrObject === "object" ? messageOrObject : { message: messageOrObject } as any, disableValidation as any)
63
+ super(
64
+ typeof messageOrObject === "object" ? messageOrObject : { message: messageOrObject } as any,
65
+ disableValidation as any
66
+ )
67
+ }
68
+ override toString() {
69
+ return `ServiceUnavailableError: ${this.message}`
55
70
  }
56
71
  }
57
72
 
58
- export class ValidationError extends TaggedError<ValidationError>()("ValidationError", {
73
+ export class ValidationError extends TaggedErrorClass<ValidationError>()("ValidationError", {
59
74
  errors: S.Array(S.Unknown)
60
75
  }) {
61
76
  constructor(
@@ -67,33 +82,45 @@ export class ValidationError extends TaggedError<ValidationError>()("ValidationE
67
82
  override get message() {
68
83
  return `Validation failed: ${(this as any).errors.map((e: any) => JSON.stringify(e, undefined, 2)).join(",\n")}`
69
84
  }
85
+ override toString() {
86
+ return `ValidationError: ${this.message}`
87
+ }
70
88
  }
71
89
 
72
- export class NotLoggedInError extends TaggedError<NotLoggedInError>()("NotLoggedInError", {
90
+ export class NotLoggedInError extends TaggedErrorClass<NotLoggedInError>()("NotLoggedInError", {
73
91
  message: S.String
74
92
  }) {
75
93
  constructor(messageOrObject?: string | { message: string; cause?: unknown }, disableValidation?: boolean) {
76
94
  super(messageFallback(messageOrObject) as any, disableValidation as any)
77
95
  }
96
+ override toString() {
97
+ return `NotLoggedInError: ${this.message}`
98
+ }
78
99
  }
79
100
 
80
101
  /**
81
102
  * The user carries a valid Userprofile, but there is a problem with the login none the less.
82
103
  */
83
- export class LoginError extends TaggedError<LoginError>()("NotLoggedInError", {
104
+ export class LoginError extends TaggedErrorClass<LoginError>()("NotLoggedInError", {
84
105
  message: S.String
85
106
  }) {
86
107
  constructor(messageOrObject?: string | { message: string; cause?: unknown }, disableValidation?: boolean) {
87
108
  super(messageFallback(messageOrObject) as any, disableValidation as any)
88
109
  }
110
+ override toString() {
111
+ return `LoginError: ${this.message}`
112
+ }
89
113
  }
90
114
 
91
- export class UnauthorizedError extends TaggedError<UnauthorizedError>()("UnauthorizedError", {
115
+ export class UnauthorizedError extends TaggedErrorClass<UnauthorizedError>()("UnauthorizedError", {
92
116
  message: S.String
93
117
  }) {
94
118
  constructor(messageOrObject?: string | { message: string; cause?: unknown }, disableValidation?: boolean) {
95
119
  super(messageFallback(messageOrObject) as any, disableValidation as any)
96
120
  }
121
+ override toString() {
122
+ return `UnauthorizedError: ${this.message}`
123
+ }
97
124
  }
98
125
 
99
126
  type OptimisticConcurrencyDetails = {
@@ -104,7 +131,7 @@ type OptimisticConcurrencyDetails = {
104
131
  readonly found?: string | undefined
105
132
  }
106
133
 
107
- export class OptimisticConcurrencyException extends TaggedError<OptimisticConcurrencyException>()(
134
+ export class OptimisticConcurrencyException extends TaggedErrorClass<OptimisticConcurrencyException>()(
108
135
  "OptimisticConcurrencyException",
109
136
  { message: S.String }
110
137
  ) {
@@ -116,11 +143,17 @@ export class OptimisticConcurrencyException extends TaggedError<OptimisticConcur
116
143
  | ({ message: string; cause?: unknown; raw?: unknown }),
117
144
  disableValidation?: boolean
118
145
  ) {
119
- super("message" in args ? args : { message: `Existing ${args.type} ${args.id} record changed` } as any, disableValidation as any)
146
+ super(
147
+ "message" in args ? args : { message: `Existing ${args.type} ${args.id} record changed` } as any,
148
+ disableValidation as any
149
+ )
120
150
  if (!("message" in args)) {
121
151
  this.details = args
122
152
  }
123
153
  }
154
+ override toString() {
155
+ return `OptimisticConcurrencyException: ${this.message}`
156
+ }
124
157
  }
125
158
 
126
159
  const MutationOnlyErrors = [
@@ -174,6 +207,7 @@ export class CauseException<E> extends Error {
174
207
  Error.stackTraceLimit = 0
175
208
  super()
176
209
  Error.stackTraceLimit = limit
210
+ this.cause = Cause.squash(originalCause)
177
211
  // v4: makeFiberFailure removed — use Cause.prettyErrors instead
178
212
  const errors = Cause.prettyErrors(originalCause)
179
213
  const first = errors[0]
@@ -1,4 +1,5 @@
1
- import { type GetContextConfig, type RequestContextMapTagAny } from "../rpc/RpcContextMap.js"
1
+ import { SchemaTransformation } from "effect"
2
+ import { type GetContextConfig, type GetEffectError, type RequestContextMapTagAny } from "../rpc/RpcContextMap.js"
2
3
  import * as S from "../Schema.js"
3
4
  import { AST } from "../Schema.js"
4
5
 
@@ -8,33 +9,40 @@ const merge = (a: any, b: Array<any>) =>
8
9
  /**
9
10
  * Whatever the input, we will only decode or encode to void
10
11
  */
11
- const ForceVoid: S.Schema<void> = S.Void as any
12
+ export const ForceVoid = S
13
+ .declare((_: unknown): _ is unknown => true)
14
+ .pipe(
15
+ S.decodeTo(S.Any, SchemaTransformation.transform<unknown, unknown>({ decode: () => void 0, encode: () => void 0 }))
16
+ )
12
17
 
13
18
  type SchemaOrFields<T> = T extends S.Top ? T : T extends S.Struct.Fields ? S.Struct<T> : S.Void
14
19
 
15
- type TaggedRequestResult<
20
+ type TaggedRequestForResult<
21
+ Self,
16
22
  Tag extends string,
17
23
  Payload extends S.Struct.Fields,
18
24
  Success extends S.Top,
19
25
  Error extends S.Top,
20
- Config = Record<string, never>
26
+ Config,
27
+ ModuleName extends string
21
28
  > =
22
- & S.TaggedStruct<Tag, Payload>
29
+ & S.EnhancedClass<Self, S.TaggedStruct<Tag, Payload>, {}>
23
30
  & {
24
- new(...args: any[]): any
25
31
  readonly _tag: Tag
26
- readonly fields: { readonly _tag: S.tag<Tag> } & Payload
27
32
  readonly success: Success
28
33
  readonly error: Error
29
34
  readonly config: Config
30
35
  readonly "~decodingServices": S.Codec.DecodingServices<Success> | S.Codec.DecodingServices<Error>
36
+ readonly "~encodingServices": S.Codec.EncodingServices<Success> | S.Codec.EncodingServices<Error>
37
+ readonly id: `${ModuleName}.${Tag}`
38
+ readonly moduleName: ModuleName
31
39
  }
32
40
 
33
41
  export const makeRpcClient = <
34
42
  RequestContextMap extends RequestContextMapTagAny,
35
43
  GeneralErrors extends S.Top = never
36
44
  >(rcs: RequestContextMap, generalErrors?: GeneralErrors) => {
37
- // Long way around ServiceMap/C extends etc to support actual jsdoc from passed in RequestConfig etc... (??)
45
+ // Long way around Context/C extends etc to support actual jsdoc from passed in RequestConfig etc... (??)
38
46
  type ServiceMap = {
39
47
  success: S.Top | S.Struct.Fields // SchemaOrFields will make a Schema type out of Struct.Fields
40
48
  error: S.Top | S.Struct.Fields // SchemaOrFields will make a Schema type out of Struct.Fields
@@ -44,80 +52,119 @@ export const makeRpcClient = <
44
52
 
45
53
  type MergeError<E> = [GeneralErrors] extends [never] ? SchemaOrFields<E> : S.Union<[SchemaOrFields<E>, GeneralErrors]>
46
54
  type ErrorResult<C> = C extends { error: infer E } ? MergeError<E>
47
- : [GeneralErrors] extends [never] ? S.Void
48
- : GeneralErrors
55
+ : [GeneralErrors] extends [never] ? GetEffectError<RequestContextMap["config"], C>
56
+ : MergeError<GetEffectError<RequestContextMap["config"], C>>
49
57
 
50
- function TaggedRequest<_Self>(): {
51
- <Tag extends string, Payload extends S.Struct.Fields, C extends ServiceMap>(
52
- tag: Tag,
53
- fields: Payload,
54
- config: RequestConfig & C
55
- ): TaggedRequestResult<Tag, Payload, SchemaOrFields<C["success"]>, ErrorResult<C>, Omit<C, "success" | "error">>
56
- <Tag extends string, Payload extends S.Struct.Fields, C extends Pick<ServiceMap, "success">>(
57
- tag: Tag,
58
- fields: Payload,
59
- config: RequestConfig & C
60
- ): TaggedRequestResult<Tag, Payload, SchemaOrFields<C["success"]>, ErrorResult<C>, Omit<C, "success" | "error">>
61
- <Tag extends string, Payload extends S.Struct.Fields, C extends Pick<ServiceMap, "error">>(
62
- tag: Tag,
63
- fields: Payload,
64
- config: RequestConfig & C
65
- ): TaggedRequestResult<Tag, Payload, S.Schema<void>, ErrorResult<C>, Omit<C, "success" | "error">>
66
- <Tag extends string, Payload extends S.Struct.Fields, C extends Record<string, any>>(
67
- tag: Tag,
68
- fields: Payload,
69
- config: C & RequestConfig
70
- ): TaggedRequestResult<Tag, Payload, S.Schema<void>, ErrorResult<C>, Omit<C, "success" | "error">>
71
- <Tag extends string, Payload extends S.Struct.Fields>(
72
- tag: Tag,
73
- fields: Payload
74
- ): TaggedRequestResult<Tag, Payload, S.Schema<void>, ErrorResult<never>, Record<string, never>>
75
- } {
76
- // TODO: filter errors based on config + take care of inversion
77
- const errorSchemas = Object.values(rcs.config).map((_) => _.error)
78
- return (<Tag extends string, Fields extends S.Struct.Fields, C extends ServiceMap>(
79
- tag: Tag,
80
- fields: Fields,
81
- config?: C
82
- ) => {
83
- // TODO: S.TaggedRequest removed in v4 — needs rework to use Rpc.make or Request.TaggedClass
84
- // For now, creating a simple tagged struct class with success/failure properties
85
- const failureSchema = merge(
86
- config?.error ? S.isSchema(config.error) ? config.error : S.Struct(config.error) : undefined,
87
- [...errorSchemas, generalErrors].filter(Boolean)
88
- )
89
- const successSchema = config?.success
90
- ? S.isSchema(config.success)
91
- ? AST.isVoid(config.success.ast) ? ForceVoid : config.success
92
- : S.Struct(config.success)
93
- : ForceVoid
58
+ // TODO: filter errors based on config + take care of inversion
59
+ const errorSchemas = Object.values(rcs.config).map((_) => _.error)
94
60
 
95
- const payloadSchema = S.Struct({ _tag: S.tag(tag), ...fields })
61
+ function makeRequestClass<Tag extends string, Fields extends S.Struct.Fields, C extends ServiceMap>(
62
+ tag: Tag,
63
+ fields: Fields,
64
+ config?: C
65
+ ) {
66
+ const failureSchema = merge(
67
+ config?.error ? S.isSchema(config.error) ? config.error : S.Struct(config.error) : undefined,
68
+ [...errorSchemas, generalErrors].filter(Boolean)
69
+ )
70
+ const successSchema = config?.success
71
+ ? S.isSchema(config.success)
72
+ ? AST.isVoid(config.success.ast) ? ForceVoid : config.success
73
+ : S.Struct(config.success)
74
+ : ForceVoid
96
75
 
97
- const taggedFields = { _tag: S.tag(tag), ...fields }
76
+ const RequestClass = S.TaggedClass<any>()(tag, fields)
77
+ Object.assign(RequestClass, {
78
+ _tag: tag,
79
+ success: successSchema,
80
+ error: failureSchema,
81
+ config
82
+ })
98
83
 
99
- const RequestClass = class {
100
- constructor(payload?: any) {
101
- if (payload) {
102
- Object.assign(this, payload)
103
- }
104
- ;(this as any)._tag = tag
105
- }
106
- }
107
-
108
- Object.assign(RequestClass, payloadSchema, {
109
- _tag: tag,
110
- fields: taggedFields,
111
- success: successSchema,
112
- error: failureSchema,
113
- config
114
- })
84
+ return RequestClass
85
+ }
115
86
 
116
- return RequestClass
117
- }) as any
87
+ function TaggedRequestFor<ModuleName extends string>(moduleName: ModuleName) {
88
+ function TaggedRequestWithMeta<Self>(): {
89
+ <Tag extends string, Payload extends S.Struct.Fields, C extends ServiceMap>(
90
+ tag: Tag,
91
+ fields: Payload,
92
+ config: RequestConfig & C
93
+ ): TaggedRequestForResult<
94
+ Self,
95
+ Tag,
96
+ Payload,
97
+ SchemaOrFields<C["success"]>,
98
+ ErrorResult<C>,
99
+ Omit<C, "success" | "error">,
100
+ ModuleName
101
+ >
102
+ <Tag extends string, Payload extends S.Struct.Fields, C extends Pick<ServiceMap, "success">>(
103
+ tag: Tag,
104
+ fields: Payload,
105
+ config: RequestConfig & C
106
+ ): TaggedRequestForResult<
107
+ Self,
108
+ Tag,
109
+ Payload,
110
+ SchemaOrFields<C["success"]>,
111
+ ErrorResult<C>,
112
+ Omit<C, "success" | "error">,
113
+ ModuleName
114
+ >
115
+ <Tag extends string, Payload extends S.Struct.Fields, C extends Pick<ServiceMap, "error">>(
116
+ tag: Tag,
117
+ fields: Payload,
118
+ config: RequestConfig & C
119
+ ): TaggedRequestForResult<
120
+ Self,
121
+ Tag,
122
+ Payload,
123
+ typeof ForceVoid,
124
+ ErrorResult<C>,
125
+ Omit<C, "success" | "error">,
126
+ ModuleName
127
+ >
128
+ <Tag extends string, Payload extends S.Struct.Fields, C extends Record<string, any>>(
129
+ tag: Tag,
130
+ fields: Payload,
131
+ config: C & RequestConfig
132
+ ): TaggedRequestForResult<
133
+ Self,
134
+ Tag,
135
+ Payload,
136
+ typeof ForceVoid,
137
+ ErrorResult<C>,
138
+ Omit<C, "success" | "error">,
139
+ ModuleName
140
+ >
141
+ <Tag extends string, Payload extends S.Struct.Fields>(
142
+ tag: Tag,
143
+ fields: Payload
144
+ ): TaggedRequestForResult<
145
+ Self,
146
+ Tag,
147
+ Payload,
148
+ typeof ForceVoid,
149
+ ErrorResult<{}>,
150
+ Record<string, never>,
151
+ ModuleName
152
+ >
153
+ } {
154
+ return (<Tag extends string, Fields extends S.Struct.Fields, C extends ServiceMap>(
155
+ tag: Tag,
156
+ fields: Fields,
157
+ config?: C
158
+ ) => {
159
+ const cls = makeRequestClass(tag, fields, config)
160
+ Object.assign(cls, { id: `${moduleName}.${tag}`, moduleName })
161
+ return cls
162
+ }) as any
163
+ }
164
+ return Object.assign(TaggedRequestWithMeta, { moduleName } as const)
118
165
  }
119
166
 
120
167
  return {
121
- TaggedRequest
168
+ TaggedRequestFor
122
169
  }
123
170
  }
@@ -1,3 +1,4 @@
1
+ import { Option } from "effect"
1
2
  import type { HttpClientResponse } from "effect/unstable/http/HttpClientResponse"
2
3
  import * as Effect from "../Effect.js"
3
4
  import { HttpClient, HttpClientError, HttpClientRequest, HttpHeaders } from "./internal/lib.js"
@@ -24,16 +25,18 @@ export const demandJson = (client: HttpClient.HttpClient) =>
24
25
  .mapRequest(client, (_) => HttpClientRequest.acceptJson(_))
25
26
  .pipe(HttpClient.transform((r, request) =>
26
27
  Effect.tap(r, (response) =>
27
- HttpHeaders
28
- .get(response.headers, "Content-Type")
29
- ?.startsWith("application/json")
28
+ Option
29
+ .exists(
30
+ HttpHeaders.get(response.headers, "Content-Type"),
31
+ (_) => _.startsWith("application/json")
32
+ )
30
33
  ? Effect.void
31
34
  : Effect.fail(
32
35
  new HttpClientError.DecodeError({
33
36
  request,
34
37
  response,
35
38
  description: "not json response: "
36
- + HttpHeaders.get(response.headers, "Content-Type")
39
+ + Option.getOrElse(HttpHeaders.get(response.headers, "Content-Type"), () => "<missing>")
37
40
  })
38
41
  ))
39
42
  ))