effect-app 4.0.0-beta.6 → 4.0.0-beta.60

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 (130) hide show
  1. package/CHANGELOG.md +265 -0
  2. package/dist/Config.d.ts +7 -0
  3. package/dist/Config.d.ts.map +1 -0
  4. package/dist/Config.js +6 -0
  5. package/dist/ConfigProvider.d.ts +39 -0
  6. package/dist/ConfigProvider.d.ts.map +1 -0
  7. package/dist/ConfigProvider.js +42 -0
  8. package/dist/{ServiceMap.d.ts → Context.d.ts} +9 -12
  9. package/dist/Context.d.ts.map +1 -0
  10. package/dist/Context.js +87 -0
  11. package/dist/Effect.d.ts +8 -7
  12. package/dist/Effect.d.ts.map +1 -1
  13. package/dist/Effect.js +3 -2
  14. package/dist/Layer.d.ts +5 -4
  15. package/dist/Layer.d.ts.map +1 -1
  16. package/dist/Layer.js +1 -1
  17. package/dist/Operations.d.ts +51 -15
  18. package/dist/Operations.d.ts.map +1 -1
  19. package/dist/Pure.d.ts +2 -2
  20. package/dist/Pure.d.ts.map +1 -1
  21. package/dist/Pure.js +13 -13
  22. package/dist/Schema/Class.d.ts +39 -1
  23. package/dist/Schema/Class.d.ts.map +1 -1
  24. package/dist/Schema/Class.js +89 -12
  25. package/dist/Schema/SpecialJsonSchema.d.ts +40 -0
  26. package/dist/Schema/SpecialJsonSchema.d.ts.map +1 -0
  27. package/dist/Schema/SpecialJsonSchema.js +199 -0
  28. package/dist/Schema/SpecialOpenApi.d.ts +30 -0
  29. package/dist/Schema/SpecialOpenApi.d.ts.map +1 -0
  30. package/dist/Schema/SpecialOpenApi.js +120 -0
  31. package/dist/Schema/brand.d.ts +8 -5
  32. package/dist/Schema/brand.d.ts.map +1 -1
  33. package/dist/Schema/brand.js +1 -1
  34. package/dist/Schema/email.d.ts.map +1 -1
  35. package/dist/Schema/email.js +4 -3
  36. package/dist/Schema/ext.d.ts +142 -44
  37. package/dist/Schema/ext.d.ts.map +1 -1
  38. package/dist/Schema/ext.js +145 -35
  39. package/dist/Schema/moreStrings.d.ts.map +1 -1
  40. package/dist/Schema/moreStrings.js +6 -4
  41. package/dist/Schema/numbers.d.ts +8 -8
  42. package/dist/Schema/numbers.js +2 -2
  43. package/dist/Schema/phoneNumber.d.ts.map +1 -1
  44. package/dist/Schema/phoneNumber.js +3 -2
  45. package/dist/Schema.d.ts +21 -54
  46. package/dist/Schema.d.ts.map +1 -1
  47. package/dist/Schema.js +43 -64
  48. package/dist/client/apiClientFactory.d.ts +3 -3
  49. package/dist/client/apiClientFactory.d.ts.map +1 -1
  50. package/dist/client/apiClientFactory.js +12 -13
  51. package/dist/client/errors.d.ts +8 -0
  52. package/dist/client/errors.d.ts.map +1 -1
  53. package/dist/client/errors.js +35 -10
  54. package/dist/client/makeClient.d.ts +13 -12
  55. package/dist/client/makeClient.d.ts.map +1 -1
  56. package/dist/client/makeClient.js +5 -2
  57. package/dist/http/Request.d.ts.map +1 -1
  58. package/dist/http/Request.js +5 -5
  59. package/dist/ids.d.ts +1 -1
  60. package/dist/ids.d.ts.map +1 -1
  61. package/dist/ids.js +1 -1
  62. package/dist/index.d.ts +6 -8
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +7 -9
  65. package/dist/middleware.d.ts +2 -2
  66. package/dist/middleware.d.ts.map +1 -1
  67. package/dist/middleware.js +3 -3
  68. package/dist/rpc/MiddlewareMaker.d.ts +4 -3
  69. package/dist/rpc/MiddlewareMaker.d.ts.map +1 -1
  70. package/dist/rpc/MiddlewareMaker.js +6 -5
  71. package/dist/rpc/RpcContextMap.d.ts +2 -2
  72. package/dist/rpc/RpcContextMap.d.ts.map +1 -1
  73. package/dist/rpc/RpcContextMap.js +4 -4
  74. package/dist/rpc/RpcMiddleware.d.ts +4 -3
  75. package/dist/rpc/RpcMiddleware.d.ts.map +1 -1
  76. package/dist/rpc/RpcMiddleware.js +1 -1
  77. package/dist/utils/gen.d.ts +1 -1
  78. package/dist/utils/gen.d.ts.map +1 -1
  79. package/dist/utils/logger.d.ts +2 -2
  80. package/dist/utils/logger.d.ts.map +1 -1
  81. package/dist/utils/logger.js +3 -3
  82. package/dist/utils.d.ts +18 -0
  83. package/dist/utils.d.ts.map +1 -1
  84. package/dist/utils.js +24 -5
  85. package/package.json +29 -17
  86. package/src/Config.ts +14 -0
  87. package/src/ConfigProvider.ts +48 -0
  88. package/src/{ServiceMap.ts → Context.ts} +16 -17
  89. package/src/Effect.ts +11 -9
  90. package/src/Layer.ts +5 -4
  91. package/src/Pure.ts +17 -18
  92. package/src/Schema/Class.ts +114 -16
  93. package/src/Schema/SpecialJsonSchema.ts +216 -0
  94. package/src/Schema/SpecialOpenApi.ts +126 -0
  95. package/src/Schema/brand.ts +13 -7
  96. package/src/Schema/email.ts +4 -2
  97. package/src/Schema/ext.ts +222 -57
  98. package/src/Schema/moreStrings.ts +10 -6
  99. package/src/Schema/numbers.ts +2 -2
  100. package/src/Schema/phoneNumber.ts +3 -1
  101. package/src/Schema.ts +79 -103
  102. package/src/client/apiClientFactory.ts +16 -19
  103. package/src/client/errors.ts +46 -12
  104. package/src/client/makeClient.ts +32 -12
  105. package/src/http/Request.ts +7 -4
  106. package/src/ids.ts +1 -1
  107. package/src/index.ts +6 -9
  108. package/src/middleware.ts +2 -2
  109. package/src/rpc/MiddlewareMaker.ts +7 -6
  110. package/src/rpc/RpcContextMap.ts +6 -5
  111. package/src/rpc/RpcMiddleware.ts +5 -4
  112. package/src/utils/gen.ts +1 -1
  113. package/src/utils/logger.ts +2 -2
  114. package/src/utils.ts +26 -4
  115. package/test/dist/moreStrings.test.d.ts.map +1 -0
  116. package/test/dist/rpc.test.d.ts.map +1 -1
  117. package/test/dist/special.test.d.ts.map +1 -0
  118. package/test/moreStrings.test.ts +17 -0
  119. package/test/rpc.test.ts +26 -5
  120. package/test/schema.test.ts +292 -1
  121. package/test/special.test.ts +525 -0
  122. package/test/utils.test.ts +1 -1
  123. package/tsconfig.base.json +0 -1
  124. package/tsconfig.json +0 -1
  125. package/dist/ServiceMap.d.ts.map +0 -1
  126. package/dist/ServiceMap.js +0 -91
  127. package/dist/Struct.d.ts +0 -44
  128. package/dist/Struct.d.ts.map +0 -1
  129. package/dist/Struct.js +0 -29
  130. package/src/Struct.ts +0 -54
package/src/Schema.ts CHANGED
@@ -1,22 +1,19 @@
1
- import { Array, Option, pipe, SchemaAST, type Tracer } from "effect"
1
+ import { SchemaAST, type Tracer } from "effect"
2
2
  import * as S from "effect/Schema"
3
3
  import type { NonEmptyReadonlyArray } from "./Array.js"
4
4
  import { fakerArb } from "./faker.js"
5
- import { Email as EmailT } from "./Schema/email.js"
5
+ import { Email as EmailT, type Email as EmailType } from "./Schema/email.js"
6
6
  import { withDefaultMake } from "./Schema/ext.js"
7
- import { PhoneNumber as PhoneNumberT } from "./Schema/phoneNumber.js"
8
- import type { AST } from "./Schema/schema.js"
7
+ import { PhoneNumber as PhoneNumberT, type PhoneNumber as PhoneNumberType } from "./Schema/phoneNumber.js"
9
8
  import { extendM } from "./utils.js"
10
9
 
11
10
  export * from "effect/Schema"
12
- // v4: TaggedError renamed to TaggedErrorClass
13
- export { TaggedErrorClass as TaggedError } from "effect/Schema"
14
11
 
15
12
  export * from "./Schema/Class.js"
16
13
  export { Class, TaggedClass } from "./Schema/Class.js"
17
14
 
18
15
  export { fromBrand, nominal } from "./Schema/brand.js"
19
- export { Array, Boolean, Date, Literal, Map, NullOr, Number, ReadonlyMap, ReadonlySet } from "./Schema/ext.js"
16
+ export { Array, Boolean, Date, DateValid, Finite, Literal, NullOr, Number, ReadonlyMap, ReadonlySet } from "./Schema/ext.js"
20
17
  export { Int, NonNegativeInt } from "./Schema/numbers.js"
21
18
 
22
19
  export * from "./Schema/email.js"
@@ -25,6 +22,8 @@ export * from "./Schema/moreStrings.js"
25
22
  export * from "./Schema/numbers.js"
26
23
  export * from "./Schema/phoneNumber.js"
27
24
  export * from "./Schema/schema.js"
25
+ export * from "./Schema/SpecialJsonSchema.js"
26
+ export * from "./Schema/SpecialOpenApi.js"
28
27
  export * from "./Schema/strings.js"
29
28
  export { NonEmptyString } from "./Schema/strings.js"
30
29
 
@@ -40,127 +39,104 @@ export interface WithOptionalSpan {
40
39
  [SpanId]?: Tracer.Span
41
40
  }
42
41
 
42
+ const makeEmail = S.decodeSync(EmailT as any) as (value: string) => EmailType
43
+ const makePhoneNumber = S.decodeSync(PhoneNumberT as any) as (value: string) => PhoneNumberType
44
+
43
45
  export const Email = EmailT
44
46
  .pipe(
45
47
  S.annotate({
46
48
  // eslint-disable-next-line @typescript-eslint/unbound-method
47
- arbitrary: (): any => (fc: any) => fakerArb((faker) => faker.internet.exampleEmail)(fc).map(Email)
49
+ toArbitrary: () => (fc) => fakerArb((faker) => faker.internet.exampleEmail)(fc).map(makeEmail)
48
50
  }),
49
51
  withDefaultMake
50
52
  )
51
53
 
52
- export type Email = EmailT
54
+ export type Email = EmailType
53
55
 
54
56
  export const PhoneNumber = PhoneNumberT
55
57
  .pipe(
56
58
  S.annotate({
57
- arbitrary: (): any => (fc: any) =>
59
+ toArbitrary: () => (fc) =>
58
60
  // eslint-disable-next-line @typescript-eslint/unbound-method
59
- fakerArb((faker) => faker.phone.number)(fc).map(PhoneNumber)
61
+ fakerArb((faker) => faker.phone.number)(fc).map(makePhoneNumber)
60
62
  }),
61
63
  withDefaultMake
62
64
  )
63
65
 
64
- export const makeIs = <A extends { _tag: string }, I, R>(
65
- schema: S.Codec<A, I, R>
66
- ) => {
67
- // In v4, transformations are stored as encoding on nodes, not as wrapper nodes.
68
- // Union member ASTs are directly Objects (TypeLiteral equivalent).
69
- if (SchemaAST.isUnion(schema.ast)) {
70
- return schema.ast.types.reduce((acc: any, t: AST.AST) => {
71
- if (!SchemaAST.isObjects(t)) return acc
72
- const tag = Array.findFirst(t.propertySignatures, (_: any) => {
73
- if (_.name === "_tag" && SchemaAST.isLiteral(_.type)) {
74
- return Option.some(_.type)
75
- }
76
- return Option.none()
77
- })
78
- const ast = Option.getOrUndefined(tag)
79
- if (!ast) {
80
- return acc
81
- }
82
- return {
83
- ...acc,
84
- [String((ast as SchemaAST.Literal).literal)]: (x: { _tag: string }) =>
85
- x._tag === (ast as SchemaAST.Literal).literal
86
- }
87
- }, {} as Is<A>)
88
- }
89
- throw new Error("Unsupported")
90
- }
66
+ export type PhoneNumber = PhoneNumberType
91
67
 
92
- export const makeIsAnyOf = <A extends { _tag: string }, I, R>(
93
- schema: S.Codec<A, I, R>
94
- ): IsAny<A> => {
95
- if (SchemaAST.isUnion(schema.ast)) {
96
- return <Keys extends A["_tag"][]>(...keys: Keys) => (a: A): a is ExtractUnion<A, ElemType<Keys>> =>
97
- keys.includes(a._tag)
98
- }
99
- throw new Error("Unsupported")
68
+ // Copied from SchemaAST.collectSentinels (marked @internal in effect).
69
+ // Returns all { key, literal } pairs that can discriminate a union member.
70
+ const getTagFromAST = (schema: S.Top): string => {
71
+ const sentinels = collectSentinelsFromAST(schema.ast)
72
+ const sentinel = sentinels.find((s) => s.key === "_tag")
73
+ if (sentinel !== undefined && typeof sentinel.literal === "string") return sentinel.literal
74
+ throw new Error("No _tag literal found on schema member")
100
75
  }
101
76
 
102
- export type ExtractUnion<A extends { _tag: string }, Tags extends A["_tag"]> = Extract<A, Record<"_tag", Tags>>
103
- export type Is<A extends { _tag: string }> = { [K in A as K["_tag"]]: (a: A) => a is K }
104
- export type ElemType<A> = A extends Array<infer E> ? E : never
105
- export interface IsAny<A extends { _tag: string }> {
106
- <Keys extends A["_tag"][]>(...keys: Keys): (a: A) => a is ExtractUnion<A, ElemType<Keys>>
77
+ function collectSentinelsFromAST(
78
+ ast: SchemaAST.AST
79
+ ): Array<{ key: PropertyKey; literal: SchemaAST.LiteralValue | symbol }> {
80
+ switch (ast._tag) {
81
+ case "Declaration": {
82
+ const s = ast.annotations?.["~sentinels"]
83
+ return Array.isArray(s) ? s : []
84
+ }
85
+ case "Objects":
86
+ return ast.propertySignatures.flatMap(
87
+ (ps): Array<{ key: PropertyKey; literal: SchemaAST.LiteralValue | symbol }> => {
88
+ const type = ps.type
89
+ if (!SchemaAST.isOptional(type)) {
90
+ if (SchemaAST.isLiteral(type)) return [{ key: ps.name, literal: type.literal }]
91
+ if (SchemaAST.isUniqueSymbol(type)) return [{ key: ps.name, literal: type.symbol }]
92
+ }
93
+ return []
94
+ }
95
+ )
96
+ case "Suspend":
97
+ return collectSentinelsFromAST(ast.thunk())
98
+ default:
99
+ return []
100
+ }
107
101
  }
108
102
 
109
- export const taggedUnionMap = <
110
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
111
- Members extends readonly (S.Top & { fields: { _tag: S.tag<string> } })[]
112
- >(
113
- self: Members
114
- ) =>
115
- self.reduce((acc, key) => {
116
- // TODO: v4 migration — PropertySignatureDeclaration removed, need v4 AST traversal
117
- const ast = key.fields._tag.ast as any
118
- const tag = ((ast.type ?? ast) as SchemaAST.Literal).literal as string // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
119
- ;(acc as any)[tag] = key as any
120
- return acc
121
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
122
- }, {} as any)
123
-
124
103
  export const tags = <
125
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
126
- Members extends NonEmptyReadonlyArray<(S.Top & { fields: { _tag: S.tag<string> } })>
104
+ Members extends NonEmptyReadonlyArray<(S.Top & { readonly Type: { readonly _tag: string } })>
127
105
  >(
128
106
  self: Members
129
107
  ) =>
130
- S.Literals(self.map((key) => {
131
- // TODO: v4 migration — PropertySignatureDeclaration removed, need v4 AST traversal
132
- const ast = key.fields._tag.ast as any
133
- const tag = ((ast.type ?? ast) as SchemaAST.Literal).literal
134
- return tag
135
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
136
- })) as any
137
-
138
- export const ExtendTaggedUnion = <A extends { _tag: string }, I, R>(
139
- schema: S.Codec<A, I, R>
140
- ) =>
141
- extendM(
142
- schema,
143
- (_) => ({
144
- is: S.is(schema as any),
145
- isA: makeIs(_ as any),
146
- isAnyOf: makeIsAnyOf(_ as any) /*, map: taggedUnionMap(a) */
147
- })
148
- )
108
+ S.Literals(
109
+ self.map(getTagFromAST) as {
110
+ [Index in keyof Members]: Members[Index]["Type"]["_tag"]
111
+ }
112
+ ) as S.Literals<
113
+ {
114
+ [Index in keyof Members]: Members[Index]["Type"]["_tag"]
115
+ }
116
+ >
117
+
118
+ type TaggedUnionMembers = NonEmptyReadonlyArray<
119
+ S.Top & { readonly Type: { readonly _tag: string } }
120
+ >
121
+
122
+ type TaggedUnionTags<Members extends TaggedUnionMembers> = S.Literals<
123
+ {
124
+ [Index in keyof Members]: Members[Index]["Type"]["_tag"]
125
+ }
126
+ >
149
127
 
150
- export const TaggedUnion = <
151
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
152
- Members extends readonly (S.Top & { fields: { _tag: S.tag<any> } })[]
153
- >(...a: Members) =>
154
- pipe(
155
- S.Union(a),
156
- (_) =>
157
- extendM(_, (_) => ({
158
- is: S.is(_ as any),
159
- isA: makeIs(_ as any),
160
- isAnyOf: makeIsAnyOf(_ as any),
161
- tagMap: taggedUnionMap(a),
162
- tags: tags(a as any)
163
- }))
164
- )
128
+ type TaggedUnionWithTags<Members extends TaggedUnionMembers> = S.toTaggedUnion<"_tag", Members> & {
129
+ readonly tags: TaggedUnionTags<Members>
130
+ }
131
+
132
+ const extendTaggedUnionWithTags = <Members extends TaggedUnionMembers>(
133
+ schema: S.Union<Members>
134
+ ): TaggedUnionWithTags<Members> => extendM(schema.pipe(S.toTaggedUnion("_tag")), () => ({ tags: tags(schema.members) }))
165
135
 
166
- export type PhoneNumber = PhoneNumberT
136
+ export const ExtendTaggedUnion = <Members extends TaggedUnionMembers>(
137
+ schema: S.Union<Members>
138
+ ): TaggedUnionWithTags<Members> => extendTaggedUnionWithTags(schema)
139
+
140
+ export const TaggedUnion = <
141
+ Members extends TaggedUnionMembers
142
+ >(...a: Members): TaggedUnionWithTags<Members> => extendTaggedUnionWithTags(S.Union(a))
@@ -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,11 +6,12 @@ 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
16
  import type { Client, ClientForOptions, Requests, RequestsAny } from "./clientFor.js"
17
17
 
@@ -40,7 +40,7 @@ export type Req = S.Top & {
40
40
  readonly "~decodingServices"?: unknown
41
41
  }
42
42
 
43
- class RequestName extends ServiceMap.Reference("RequestName", {
43
+ class RequestName extends Context.Reference("RequestName", {
44
44
  defaultValue: () => ({ requestName: "Unspecified", moduleName: "Error" })
45
45
  }) {}
46
46
 
@@ -50,17 +50,17 @@ export const HttpClientLayer = (config: ApiConfig) =>
50
50
  Effect
51
51
  .gen(function*() {
52
52
  const baseClient = yield* HttpClient.HttpClient
53
- const ctx = yield* RequestName
54
53
  const client = baseClient.pipe(
55
54
  HttpClient.mapRequest(HttpClientRequest.prependUrl(config.url + "/rpc")),
56
55
  HttpClient.mapRequest(
57
56
  HttpClientRequest.setHeaders(config.headers.pipe(Option.getOrElse(() => ({}))))
58
57
  ),
59
- HttpClient.mapRequest((req) =>
60
- flow(
61
- HttpClientRequest.appendUrlParam("action", ctx.requestName),
62
- HttpClientRequest.appendUrl("/" + ctx.moduleName)
63
- )(req)
58
+ HttpClient.mapRequestEffect((req) =>
59
+ Effect.map(RequestName.asEffect(), (ctx) =>
60
+ flow(
61
+ HttpClientRequest.appendUrlParam("action", ctx.requestName),
62
+ HttpClientRequest.appendUrl("/" + ctx.moduleName)
63
+ )(req))
64
64
  )
65
65
  )
66
66
  return client
@@ -76,7 +76,7 @@ export const HttpClientFromConfigLayer = Layer.unwrap(
76
76
 
77
77
  export const RpcSerializationLayer = (config: ApiConfig) =>
78
78
  Layer.mergeAll(
79
- RpcSerialization.layerJson,
79
+ RpcSerialization.layerNdjson,
80
80
  HttpClientLayer(config)
81
81
  )
82
82
 
@@ -139,17 +139,14 @@ const makeRpcTag = <M extends Requests>(resource: M) => {
139
139
 
140
140
  // Use Object.assign instead of class extension to avoid TS2509 with complex generic return types.
141
141
  // The first type arg is `any` because this is a dynamically created tag — its identity is the string key.
142
- const TheClient = ServiceMap.Opaque<
142
+ const TheClient = Context.Opaque<
143
143
  any,
144
144
  RpcClient.RpcClient<RpcGroup.Rpcs<typeof rpcs>>
145
145
  >()(`RpcClient.${meta.moduleName}`)
146
146
  // Use Layer.effect directly (not TheClient.toLayer) so TypeScript properly excludes Scope
147
147
  const layer = Layer.effect(
148
148
  TheClient,
149
- Effect.map(
150
- RpcClient.make(rpcs, { spanPrefix: "RpcClient." + meta.moduleName }),
151
- (cl) => (cl as any)[meta.moduleName]
152
- )
149
+ RpcClient.make(rpcs, { spanPrefix: "RpcClient." + meta.moduleName })
153
150
  )
154
151
  return Object.assign(TheClient, { layer })
155
152
  }
@@ -188,7 +185,7 @@ const makeApiClientFactory = Effect
188
185
  const filtered = getFiltered(resource)
189
186
  return {
190
187
  mr,
191
- client: (typedKeysOf(filtered)
188
+ client: typedKeysOf(filtered)
192
189
  .reduce((prev, cur) => {
193
190
  const h = filtered[cur]!
194
191
 
@@ -211,7 +208,7 @@ const makeApiClientFactory = Effect
211
208
  const layers = requestLevelLayers.pipe(Layer.provideMerge(requestNameLayer))
212
209
 
213
210
  const fields = Struct.omit(Request.fields, ["_tag"] as const)
214
- const requestAttr = h._tag
211
+ const requestAttr = `${meta.moduleName}.${h._tag}`
215
212
  // @ts-expect-error doc
216
213
  prev[cur] = Object.keys(fields).length === 0
217
214
  ? {
@@ -246,7 +243,7 @@ const makeApiClientFactory = Effect
246
243
  }
247
244
 
248
245
  return prev
249
- }, {} as Client<M, M["meta"]["moduleName"]>))
246
+ }, {} as Client<M, M["meta"]["moduleName"]>)
250
247
  }
251
248
  })
252
249
 
@@ -284,7 +281,7 @@ const makeApiClientFactory = Effect
284
281
  * Used to create clients for resource modules.
285
282
  */
286
283
  export class ApiClientFactory
287
- extends ServiceMap.Opaque<ApiClientFactory, Effect.Success<typeof makeApiClientFactory>>()("ApiClientFactory")
284
+ extends Context.Opaque<ApiClientFactory, Effect.Success<typeof makeApiClientFactory>>()("ApiClientFactory")
288
285
  {
289
286
  static readonly layer = (config: ApiConfig) =>
290
287
  ApiClientFactory.toLayer(makeApiClientFactory).pipe(Layer.provide(RpcSerializationLayer(config)))
@@ -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 { GetEffectError, 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,38 @@ 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.Codec<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
20
  type TaggedRequestResult<
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
26
  Config = Record<string, never>
21
27
  > =
22
- & S.TaggedStruct<Tag, Payload>
28
+ & S.EnhancedClass<Self, S.TaggedStruct<Tag, Payload>, {}>
23
29
  & {
24
- new(...args: any[]): any
25
30
  readonly _tag: Tag
26
- readonly fields: { readonly _tag: S.tag<Tag> } & Payload
27
31
  readonly success: Success
28
32
  readonly error: Error
29
33
  readonly config: Config
34
+ // TODO: these two are wrong. Anything using this request's success/error, should however derive the Decoding/Encoding services from them..
30
35
  readonly "~decodingServices": S.Codec.DecodingServices<Success> | S.Codec.DecodingServices<Error>
36
+ readonly "~encodingServices": S.Codec.EncodingServices<Success> | S.Codec.EncodingServices<Error>
31
37
  }
32
38
 
33
39
  export const makeRpcClient = <
34
40
  RequestContextMap extends RequestContextMapTagAny,
35
41
  GeneralErrors extends S.Top = never
36
42
  >(rcs: RequestContextMap, generalErrors?: GeneralErrors) => {
37
- // Long way around ServiceMap/C extends etc to support actual jsdoc from passed in RequestConfig etc... (??)
43
+ // Long way around Context/C extends etc to support actual jsdoc from passed in RequestConfig etc... (??)
38
44
  type ServiceMap = {
39
45
  success: S.Top | S.Struct.Fields // SchemaOrFields will make a Schema type out of Struct.Fields
40
46
  error: S.Top | S.Struct.Fields // SchemaOrFields will make a Schema type out of Struct.Fields
@@ -47,31 +53,45 @@ export const makeRpcClient = <
47
53
  : [GeneralErrors] extends [never] ? GetEffectError<RequestContextMap["config"], C>
48
54
  : MergeError<GetEffectError<RequestContextMap["config"], C>>
49
55
 
50
- function TaggedRequest<_Self>(): {
56
+ function TaggedRequest<Self>(): {
51
57
  <Tag extends string, Payload extends S.Struct.Fields, C extends ServiceMap>(
52
58
  tag: Tag,
53
59
  fields: Payload,
54
60
  config: RequestConfig & C
55
- ): TaggedRequestResult<Tag, Payload, SchemaOrFields<C["success"]>, ErrorResult<C>, Omit<C, "success" | "error">>
61
+ ): TaggedRequestResult<
62
+ Self,
63
+ Tag,
64
+ Payload,
65
+ SchemaOrFields<C["success"]>,
66
+ ErrorResult<C>,
67
+ Omit<C, "success" | "error">
68
+ >
56
69
  <Tag extends string, Payload extends S.Struct.Fields, C extends Pick<ServiceMap, "success">>(
57
70
  tag: Tag,
58
71
  fields: Payload,
59
72
  config: RequestConfig & C
60
- ): TaggedRequestResult<Tag, Payload, SchemaOrFields<C["success"]>, ErrorResult<C>, Omit<C, "success" | "error">>
73
+ ): TaggedRequestResult<
74
+ Self,
75
+ Tag,
76
+ Payload,
77
+ SchemaOrFields<C["success"]>,
78
+ ErrorResult<C>,
79
+ Omit<C, "success" | "error">
80
+ >
61
81
  <Tag extends string, Payload extends S.Struct.Fields, C extends Pick<ServiceMap, "error">>(
62
82
  tag: Tag,
63
83
  fields: Payload,
64
84
  config: RequestConfig & C
65
- ): TaggedRequestResult<Tag, Payload, S.Codec<void>, ErrorResult<C>, Omit<C, "success" | "error">>
85
+ ): TaggedRequestResult<Self, Tag, Payload, typeof ForceVoid, ErrorResult<C>, Omit<C, "success" | "error">>
66
86
  <Tag extends string, Payload extends S.Struct.Fields, C extends Record<string, any>>(
67
87
  tag: Tag,
68
88
  fields: Payload,
69
89
  config: C & RequestConfig
70
- ): TaggedRequestResult<Tag, Payload, S.Codec<void>, ErrorResult<C>, Omit<C, "success" | "error">>
90
+ ): TaggedRequestResult<Self, Tag, Payload, typeof ForceVoid, ErrorResult<C>, Omit<C, "success" | "error">>
71
91
  <Tag extends string, Payload extends S.Struct.Fields>(
72
92
  tag: Tag,
73
93
  fields: Payload
74
- ): TaggedRequestResult<Tag, Payload, S.Codec<void>, ErrorResult<never>, Record<string, never>>
94
+ ): TaggedRequestResult<Self, Tag, Payload, typeof ForceVoid, ErrorResult<{}>, Record<string, never>>
75
95
  } {
76
96
  // TODO: filter errors based on config + take care of inversion
77
97
  const errorSchemas = Object.values(rcs.config).map((_) => _.error)
@@ -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
  ))
package/src/ids.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { brandedStringId, NonEmptyString255, StringId, type StringIdBrand, withDefaultMake, Codec } from "effect-app/Schema"
1
+ import { brandedStringId, type Codec, NonEmptyString255, StringId, type StringIdBrand, withDefaultMake } from "effect-app/Schema"
2
2
  import type { B } from "effect-app/Schema/schema"
3
3
  import type { Simplify } from "effect/Types"
4
4
  import { S } from "./index.js"