effect-app 4.0.0-beta.2 → 4.0.0-beta.21

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 (72) hide show
  1. package/CHANGELOG.md +108 -0
  2. package/dist/Operations.d.ts +59 -23
  3. package/dist/Operations.d.ts.map +1 -1
  4. package/dist/Pure.js +1 -1
  5. package/dist/Schema/brand.d.ts +8 -5
  6. package/dist/Schema/brand.d.ts.map +1 -1
  7. package/dist/Schema/brand.js +1 -1
  8. package/dist/Schema/email.d.ts.map +1 -1
  9. package/dist/Schema/email.js +4 -3
  10. package/dist/Schema/ext.d.ts +26 -25
  11. package/dist/Schema/ext.d.ts.map +1 -1
  12. package/dist/Schema/ext.js +13 -20
  13. package/dist/Schema/moreStrings.d.ts +6 -6
  14. package/dist/Schema/moreStrings.d.ts.map +1 -1
  15. package/dist/Schema/moreStrings.js +6 -4
  16. package/dist/Schema/phoneNumber.d.ts.map +1 -1
  17. package/dist/Schema/phoneNumber.js +3 -2
  18. package/dist/Schema.d.ts +18 -49
  19. package/dist/Schema.d.ts.map +1 -1
  20. package/dist/Schema.js +15 -62
  21. package/dist/ServiceMap.d.ts +3 -3
  22. package/dist/ServiceMap.d.ts.map +1 -1
  23. package/dist/ServiceMap.js +1 -1
  24. package/dist/client/apiClientFactory.d.ts.map +1 -1
  25. package/dist/client/apiClientFactory.js +8 -9
  26. package/dist/client/errors.d.ts.map +1 -1
  27. package/dist/client/errors.js +1 -1
  28. package/dist/client/makeClient.d.ts +6 -6
  29. package/dist/client/makeClient.d.ts.map +1 -1
  30. package/dist/client/makeClient.js +3 -14
  31. package/dist/http/Request.d.ts.map +1 -1
  32. package/dist/http/Request.js +5 -5
  33. package/dist/ids.d.ts +6 -6
  34. package/dist/ids.d.ts.map +1 -1
  35. package/dist/ids.js +1 -1
  36. package/dist/index.d.ts +0 -1
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +1 -2
  39. package/dist/rpc/MiddlewareMaker.d.ts +1 -1
  40. package/dist/rpc/MiddlewareMaker.d.ts.map +1 -1
  41. package/dist/rpc/MiddlewareMaker.js +1 -1
  42. package/dist/rpc/RpcMiddleware.d.ts +1 -1
  43. package/dist/rpc/RpcMiddleware.d.ts.map +1 -1
  44. package/dist/utils.d.ts +18 -0
  45. package/dist/utils.d.ts.map +1 -1
  46. package/dist/utils.js +24 -5
  47. package/package.json +3 -7
  48. package/src/Pure.ts +2 -2
  49. package/src/Schema/brand.ts +13 -7
  50. package/src/Schema/email.ts +4 -2
  51. package/src/Schema/ext.ts +46 -38
  52. package/src/Schema/moreStrings.ts +16 -13
  53. package/src/Schema/phoneNumber.ts +3 -1
  54. package/src/Schema.ts +48 -99
  55. package/src/ServiceMap.ts +7 -6
  56. package/src/client/apiClientFactory.ts +12 -15
  57. package/src/client/errors.ts +12 -3
  58. package/src/client/makeClient.ts +6 -19
  59. package/src/http/Request.ts +7 -4
  60. package/src/ids.ts +1 -1
  61. package/src/index.ts +0 -1
  62. package/src/rpc/MiddlewareMaker.ts +2 -1
  63. package/src/rpc/RpcMiddleware.ts +2 -2
  64. package/src/utils.ts +26 -4
  65. package/test/dist/moreStrings.test.d.ts.map +1 -0
  66. package/test/dist/rpc.test.d.ts.map +1 -1
  67. package/test/moreStrings.test.ts +17 -0
  68. package/test/schema.test.ts +32 -1
  69. package/dist/Struct.d.ts +0 -44
  70. package/dist/Struct.d.ts.map +0 -1
  71. package/dist/Struct.js +0 -29
  72. package/src/Struct.ts +0 -54
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "effect-app",
3
- "version": "4.0.0-beta.2",
3
+ "version": "4.0.0-beta.21",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "dependencies": {
@@ -22,10 +22,10 @@
22
22
  "fast-check": "~4.5.3",
23
23
  "typescript": "~5.9.3",
24
24
  "vitest": "^4.0.18",
25
- "@effect-app/eslint-shared-config": "0.5.7-beta.1"
25
+ "@effect-app/eslint-shared-config": "0.5.7-beta.2"
26
26
  },
27
27
  "peerDependencies": {
28
- "effect": "^4.0.0-beta.25"
28
+ "effect": "^4.0.0-beta.36"
29
29
  },
30
30
  "typesVersions": {
31
31
  "*": {
@@ -139,10 +139,6 @@
139
139
  "types": "./dist/Set.d.ts",
140
140
  "default": "./dist/Set.js"
141
141
  },
142
- "./Struct": {
143
- "types": "./dist/Struct.d.ts",
144
- "default": "./dist/Struct.js"
145
- },
146
142
  "./TypeTest": {
147
143
  "types": "./dist/TypeTest.d.ts",
148
144
  "default": "./dist/TypeTest.js"
package/src/Pure.ts CHANGED
@@ -202,7 +202,7 @@ export function modify<S2, A, S3>(
202
202
  const [s, a] = mod(_.env.state)
203
203
  _.env.state = s as any
204
204
  return a
205
- }) as any
205
+ })
206
206
  }
207
207
 
208
208
  export function modifyM<W, R, E, A, S2, S3>(
@@ -213,7 +213,7 @@ export function modifyM<W, R, E, A, S2, S3>(
213
213
  (castTag<W, S3, S2>() as any).use((_: any) => _),
214
214
  (_: any) =>
215
215
  Effect.map(mod(_.env.state), ([s, a]: any) => {
216
- _.env.state = s as any
216
+ _.env.state = s
217
217
  return a
218
218
  })
219
219
  ) as any
@@ -2,7 +2,6 @@
2
2
  /* eslint-disable @typescript-eslint/no-unsafe-return */
3
3
  import type { Option } from "effect"
4
4
  import * as B from "effect/Brand"
5
- import type * as Brand from "effect/Brand"
6
5
  import type * as Result from "effect/Result"
7
6
  import * as S from "effect/Schema"
8
7
 
@@ -21,7 +20,7 @@ export interface Constructor<in out A extends B.Brand<any>> {
21
20
  * Constructs a branded type from a value of type `A`, returning `Result.succeed`
22
21
  * if the provided `A` is valid, `Result.fail` otherwise.
23
22
  */
24
- result(args: Unbranded<A>): Result.Result<A, Brand.BrandError>
23
+ result(args: Unbranded<A>): Result.Result<A, B.BrandError>
25
24
  /**
26
25
  * Attempts to refine the provided value of type `A`, returning `true` if
27
26
  * the provided `A` is valid, `false` otherwise.
@@ -29,19 +28,26 @@ export interface Constructor<in out A extends B.Brand<any>> {
29
28
  is(a: Unbranded<A>): a is Unbranded<A> & A
30
29
  }
31
30
 
32
- export const fromBrand = <C extends Brand.Brand<string>>(
31
+ type BrandAnnotations<C extends B.Brand<any>> =
32
+ & S.Annotations.Filter
33
+ & (
34
+ C extends string ? { readonly toArbitrary?: S.Annotations.ToArbitrary.Declaration<C, readonly []> }
35
+ : {}
36
+ )
37
+
38
+ export const fromBrand = <C extends B.Brand<any>>(
33
39
  constructor: Constructor<C>,
34
- options?: S.Annotations.Filter
40
+ options?: BrandAnnotations<C>
35
41
  ) =>
36
- <Self extends S.Top>(self: Self): S.brand<Self["~rebuild.out"], Brand.Brand.Keys<C>> => {
42
+ <Self extends S.Top>(self: Self): S.brand<Self["~rebuild.out"], B.Brand.Keys<C>> => {
37
43
  const branded = S.fromBrand(options?.identifier ?? "Brand", constructor as any)(self as any)
38
44
  return options ? (branded as any).pipe(S.annotate(options)) : branded as any
39
45
  }
40
46
 
41
- export type Brands<P> = P extends B.Brand<any> ? Brand.Brand.Brands<P>
47
+ export type Brands<P> = P extends B.Brand<any> ? B.Brand.Brands<P>
42
48
  : never
43
49
 
44
- export type Unbranded<P> = P extends B.Brand<any> ? Brand.Brand.Unbranded<P> : P
50
+ export type Unbranded<P> = P extends B.Brand<any> ? B.Brand.Unbranded<P> : P
45
51
 
46
52
  export const nominal: <A extends B.Brand<any>>() => Constructor<A> = <
47
53
  A extends B.Brand<any>
@@ -16,7 +16,9 @@ export const Email = S
16
16
  identifier: "Email",
17
17
  title: "Email",
18
18
  description: "an email according to RFC 5322",
19
- jsonSchema: { format: "email", minLength: 3, /* a@b */ maxLength: 998 },
20
- arbitrary: () => (fc: any) => fc.emailAddress().map((_: any) => _ as Email)
19
+ jsonSchema: { format: "email", minLength: 3, /* a@b */ maxLength: 998 }
20
+ }),
21
+ S.annotate({
22
+ toArbitrary: () => (fc) => fc.emailAddress().map((_) => _ as Email)
21
23
  })
22
24
  )
package/src/Schema/ext.ts CHANGED
@@ -1,11 +1,18 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
  /* eslint-disable @typescript-eslint/no-unsafe-return */
3
- import { Effect, Option, pipe, Schema, type SchemaAST, SchemaGetter, SchemaIssue, SchemaParser, SchemaTransformation, type ServiceMap } from "effect"
3
+ import { Effect, Option, pipe, Schema, type SchemaAST, SchemaGetter, SchemaIssue, SchemaTransformation, ServiceMap } from "effect"
4
4
  import * as S from "effect/Schema"
5
5
  import { type NonEmptyReadonlyArray } from "../Array.js"
6
6
  import { extendM, typedKeysOf } from "../utils.js"
7
7
  import { type AST } from "./schema.js"
8
8
 
9
+ type ProvidedCodec<Self extends S.Top, R> = S.Codec<
10
+ Self["Type"],
11
+ Self["Encoded"],
12
+ Exclude<Self["DecodingServices"], R>,
13
+ Exclude<Self["EncodingServices"], R>
14
+ >
15
+
9
16
  // TODO: v4 migration — withConstructorDefault signature changed, propertySignature removed
10
17
  // Constraint relaxed from `Self extends S.Top & S.WithoutConstructorDefault` to `Self extends S.Top`
11
18
  // because `.pipe()` widens the schema type to `Top` which doesn't satisfy `WithoutConstructorDefault`.
@@ -69,7 +76,7 @@ export const Literal = <Literals extends NonEmptyReadonlyArray<AST.LiteralValue>
69
76
  /**
70
77
  * Like the default Schema `Array` but with `withDefault` => []
71
78
  */
72
- export function Array<Value extends S.Top>(value: Value) {
79
+ export function Array<ValueSchema extends S.Top>(value: ValueSchema) {
73
80
  return pipe(
74
81
  S.Array(value),
75
82
  (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => [])) })
@@ -79,7 +86,7 @@ export function Array<Value extends S.Top>(value: Value) {
79
86
  /**
80
87
  * Like the default Schema `Map` but with `withDefault` => []
81
88
  */
82
- function Map_<Key extends S.Top, Value extends S.Top>(input: { key: Key; value: Value }) {
89
+ function Map_<KeySchema extends S.Top, ValueSchema extends S.Top>(input: { key: KeySchema; value: ValueSchema }) {
83
90
  return pipe(
84
91
  S.ReadonlyMap(input.key, input.value),
85
92
  (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => new global.Map())) })
@@ -91,18 +98,19 @@ export { Map_ as Map }
91
98
  /**
92
99
  * Like the default Schema `ReadonlySet` but with `withDefault` => new Set()
93
100
  */
94
- export const ReadonlySet = <Value extends S.Top>(value: Value) =>
101
+ export const ReadonlySet = <ValueSchema extends S.Top>(value: ValueSchema) =>
95
102
  pipe(
96
103
  S.ReadonlySet(value),
97
- (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => new Set<S.Schema.Type<Value>>())) })
104
+ (s) =>
105
+ Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => new Set<S.Schema.Type<ValueSchema>>())) })
98
106
  )
99
107
 
100
108
  /**
101
109
  * Like the default Schema `ReadonlyMap` but with `withDefault` => new Map()
102
110
  */
103
- export const ReadonlyMap = <K extends S.Top, V extends S.Top>(pair: {
104
- readonly key: K
105
- readonly value: V
111
+ export const ReadonlyMap = <KeySchema extends S.Top, ValueSchema extends S.Top>(pair: {
112
+ readonly key: KeySchema
113
+ readonly value: ValueSchema
106
114
  }) =>
107
115
  pipe(
108
116
  S.ReadonlyMap(pair.key, pair.value),
@@ -112,25 +120,24 @@ export const ReadonlyMap = <K extends S.Top, V extends S.Top>(pair: {
112
120
  /**
113
121
  * Like the default Schema `NullOr` but with `withDefault` => null
114
122
  */
115
- export const NullOr = <S extends S.Top>(self: S) =>
123
+ export const NullOr = <Schema extends S.Top>(self: Schema) =>
116
124
  pipe(
117
125
  S.NullOr(self),
118
126
  (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => null)) })
119
127
  )
120
128
 
121
- export const defaultDate = (s: S.Top) => s.pipe(withDefaultConstructor(() => new global.Date()))
129
+ export const defaultDate = <Schema extends S.Top>(schema: Schema) =>
130
+ schema.pipe(withDefaultConstructor(() => new global.Date()))
122
131
 
123
- export const defaultBool = (s: S.Top) => s.pipe(withDefaultConstructor(() => false))
132
+ export const defaultBool = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => false))
124
133
 
125
- export const defaultNullable = (
126
- s: S.Top
127
- ) => s.pipe(withDefaultConstructor(() => null))
134
+ export const defaultNullable = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => null))
128
135
 
129
- export const defaultArray = (s: S.Top) => s.pipe(withDefaultConstructor(() => []))
136
+ export const defaultArray = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => []))
130
137
 
131
- export const defaultMap = (s: S.Top) => s.pipe(withDefaultConstructor(() => new Map()))
138
+ export const defaultMap = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => new Map()))
132
139
 
133
- export const defaultSet = (s: S.Top) => s.pipe(withDefaultConstructor(() => new Set()))
140
+ export const defaultSet = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => new Set()))
134
141
 
135
142
  export const withDefaultMake = <Self extends S.Top>(s: Self) => {
136
143
  const a = Object.assign(S.decodeSync(s as any) as WithDefaults<Self>, s)
@@ -240,29 +247,30 @@ export const transformToOrFail = <To extends S.Top, From extends S.Top, RD>(
240
247
  )
241
248
  )
242
249
 
243
- // TODO: v4 migration — S.declare API changed (no [self] + decode/encode pattern)
244
- // Need to find v4 equivalent for contextual schema wrapping
245
250
  export const provide = <Self extends S.Top, R>(
246
251
  self: Self,
247
252
  context: ServiceMap.ServiceMap<R>
248
- ): any => {
253
+ ): ProvidedCodec<Self, R> => {
249
254
  const prov = Effect.provide(context)
250
- return S
251
- .declare((_u: unknown): _u is unknown => true) // placeholder — needs proper v4 declare
252
- .pipe(
253
- S.decodeTo(
254
- self,
255
- SchemaTransformation.transformOrFail({
256
- decode: (n: any) => prov(SchemaParser.decodeUnknownEffect(self)(n)),
257
- encode: (n: any) => prov(SchemaParser.encodeUnknownEffect(self)(n))
258
- }) as any
259
- ) as any
260
- )
261
- }
262
- // TODO: v4 migration — ServiceMap.pick and S.declare pattern removed
263
- export const contextFromServices = <Self extends S.Top, Tags extends readonly any[]>(
264
- _self: Self,
265
- ..._services: Tags
266
- ): any => {
267
- throw new Error("contextFromServices: not yet migrated to v4")
255
+ return self.pipe(
256
+ S.middlewareDecoding((effect) => prov(effect)),
257
+ S.middlewareEncoding((effect) => prov(effect))
258
+ ) as ProvidedCodec<Self, R>
268
259
  }
260
+ export const contextFromServices = <
261
+ Self extends S.Top,
262
+ Tags extends ReadonlyArray<ServiceMap.Key<any, any>>
263
+ >(
264
+ self: Self,
265
+ ...services: Tags
266
+ ): Effect.Effect<
267
+ ProvidedCodec<Self, ServiceMap.Service.Identifier<Tags[number]>>,
268
+ never,
269
+ ServiceMap.Service.Identifier<Tags[number]>
270
+ > =>
271
+ Effect.gen(function*() {
272
+ const context: ServiceMap.ServiceMap<ServiceMap.Service.Identifier<Tags[number]>> = ServiceMap.pick(...services)(
273
+ yield* Effect.services<ServiceMap.Service.Identifier<Tags[number]>>()
274
+ )
275
+ return provide(self, context)
276
+ })
@@ -140,11 +140,10 @@ const minLength = 6
140
140
  const maxLength = 50
141
141
  const size = 21
142
142
  const length = 10 * size
143
- const StringIdArb = (): any => (fc: any) =>
143
+ const StringIdArb = (): S.LazyArbitrary<StringId> => (fc) =>
144
144
  fc
145
145
  .uint8Array({ minLength: length, maxLength: length })
146
- .map((_: any) => customRandom(urlAlphabet, size, (size: number) => _.subarray(0, size))())
147
-
146
+ .map((_) => customRandom(urlAlphabet, size, (size) => _.subarray(0, size))() as StringId)
148
147
  /**
149
148
  * A string that is at least 6 characters long and a maximum of 50.
150
149
  */
@@ -155,7 +154,7 @@ export const StringId = extendM(
155
154
  fromBrand(nominal<StringId>(), {
156
155
  identifier: "StringId",
157
156
  title: "StringId",
158
- arbitrary: StringIdArb,
157
+ toArbitrary: () => (fc) => StringIdArb()(fc),
159
158
  jsonSchema: {}
160
159
  })
161
160
  ),
@@ -178,19 +177,21 @@ export function prefixedStringId<Brand extends StringId>() {
178
177
  ) => {
179
178
  type FullPrefix = `${Prefix}${Separator}`
180
179
  const pref = `${prefix}${separator ?? "-"}` as FullPrefix
181
- const arb = (): any => (fc: any) =>
180
+ const arb = (): S.LazyArbitrary<string & Brand> => (fc) =>
182
181
  StringIdArb()(fc).map(
183
- (x: any) => (pref + x.substring(0, 50 - pref.length)) as Brand
182
+ (x) => (pref + x.substring(0, 50 - pref.length)) as Brand
184
183
  )
185
184
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
186
185
  const s: S.Codec<string & Brand, string> = StringId
187
186
  .pipe(
188
187
  S.refine((x: string): x is string & Brand => x.startsWith(pref), {
189
- arbitrary: arb,
190
188
  identifier: name,
191
189
  title: name
190
+ }),
191
+ S.annotate({
192
+ toArbitrary: () => (fc) => arb()(fc)
192
193
  })
193
- ) as any
194
+ ) as S.Codec<string & Brand, string>
194
195
  const schema = s.pipe(withDefaultMake)
195
196
  const make = () => (pref + StringId.make().substring(0, 50 - pref.length)) as Brand
196
197
 
@@ -217,10 +218,10 @@ export const brandedStringId = <
217
218
  Brand extends StringIdBrand
218
219
  >() =>
219
220
  withDefaultMake(
220
- Object.assign(Object.create(StringId), StringId) as S.Codec<string & Brand> & {
221
+ Object.assign(Object.create(StringId), StringId) as S.Codec<string & Brand, string> & {
221
222
  make: () => string & Brand
222
- withDefault: any
223
- } & WithDefaults<S.Codec<string & Brand>>
223
+ withDefault: S.withConstructorDefault<S.Codec<string & Brand, string> & S.WithoutConstructorDefault>
224
+ } & WithDefaults<S.Codec<string & Brand, string>>
224
225
  )
225
226
 
226
227
  export interface PrefixedStringUtils<
@@ -232,7 +233,7 @@ export interface PrefixedStringUtils<
232
233
  readonly unsafeFrom: (str: string) => Brand
233
234
  prefixSafe: <REST extends string>(str: `${Prefix}${Separator}${REST}`) => Brand
234
235
  readonly prefix: Prefix
235
- readonly withDefault: any
236
+ readonly withDefault: S.withConstructorDefault<S.Codec<Brand, string> & S.WithoutConstructorDefault>
236
237
  }
237
238
 
238
239
  export interface UrlBrand extends Simplify<B.Brand<"Url"> & NonEmptyStringBrand> {}
@@ -247,10 +248,12 @@ export const Url = S
247
248
  .String
248
249
  .pipe(
249
250
  S.refine(isUrl, {
250
- arbitrary: (): any => (fc: any) => fc.webUrl().map((_: any) => _ as Url),
251
251
  identifier: "Url",
252
252
  title: "Url",
253
253
  jsonSchema: { format: "uri" }
254
254
  }),
255
+ S.annotate({
256
+ toArbitrary: () => (fc) => fc.webUrl().map((_) => _ as Url)
257
+ }),
255
258
  withDefaultMake
256
259
  )
@@ -17,8 +17,10 @@ export const PhoneNumber = S
17
17
  identifier: "PhoneNumber",
18
18
  title: "PhoneNumber",
19
19
  description: "a phone number with at least 7 digits",
20
- arbitrary: () => (fc: any) => Numbers(7, 10)(fc).map((_: any) => _ as PhoneNumber),
21
20
  jsonSchema: { format: "phone" }
22
21
  }),
22
+ S.annotate({
23
+ toArbitrary: () => (fc) => Numbers(7, 10)(fc).map((_) => _ as PhoneNumber)
24
+ }),
23
25
  withDefaultMake
24
26
  )
package/src/Schema.ts CHANGED
@@ -1,11 +1,10 @@
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"
@@ -40,127 +39,77 @@ 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)
68
+ const getTagLiteral = <Tag extends string>(schema: S.tag<Tag>): Tag => {
69
+ if (!SchemaAST.isLiteral(schema.ast)) {
70
+ throw new Error("Unsupported _tag schema: expected a literal AST")
98
71
  }
99
- throw new Error("Unsupported")
100
- }
101
-
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>>
72
+ return schema.ast.literal as Tag
107
73
  }
108
74
 
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
75
  export const tags = <
125
76
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
126
77
  Members extends NonEmptyReadonlyArray<(S.Top & { fields: { _tag: S.tag<string> } })>
127
78
  >(
128
79
  self: Members
129
80
  ) =>
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
- )
81
+ S.Literals(
82
+ self.map((key) => getTagLiteral(key.fields._tag)) as {
83
+ [Index in keyof Members]: S.Schema.Type<Members[Index]["fields"]["_tag"]>
84
+ }
85
+ ) as S.Literals<
86
+ {
87
+ [Index in keyof Members]: S.Schema.Type<Members[Index]["fields"]["_tag"]>
88
+ }
89
+ >
90
+
91
+ type TaggedUnionMembers = NonEmptyReadonlyArray<
92
+ S.Top & { readonly Type: { readonly _tag: string }; fields: { _tag: S.tag<string> } }
93
+ >
94
+
95
+ type TaggedUnionTags<Members extends TaggedUnionMembers> = S.Literals<
96
+ {
97
+ [Index in keyof Members]: S.Schema.Type<Members[Index]["fields"]["_tag"]>
98
+ }
99
+ >
149
100
 
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
- )
101
+ type TaggedUnionWithTags<Members extends TaggedUnionMembers> = S.toTaggedUnion<"_tag", Members> & {
102
+ readonly tags: TaggedUnionTags<Members>
103
+ }
104
+
105
+ const extendTaggedUnionWithTags = <Members extends TaggedUnionMembers>(
106
+ schema: S.Union<Members>
107
+ ): TaggedUnionWithTags<Members> => extendM(schema.pipe(S.toTaggedUnion("_tag")), () => ({ tags: tags(schema.members) }))
165
108
 
166
- export type PhoneNumber = PhoneNumberT
109
+ export const ExtendTaggedUnion = <Members extends TaggedUnionMembers>(
110
+ schema: S.Union<Members>
111
+ ): TaggedUnionWithTags<Members> => extendTaggedUnionWithTags(schema)
112
+
113
+ export const TaggedUnion = <
114
+ Members extends TaggedUnionMembers
115
+ >(...a: Members): TaggedUnionWithTags<Members> => extendTaggedUnionWithTags(S.Union(a))
package/src/ServiceMap.ts CHANGED
@@ -7,13 +7,14 @@
7
7
 
8
8
  import { type Effect, Layer, type Scope, type Types } from "effect"
9
9
  import * as ServiceMap from "effect/ServiceMap"
10
- import { Yieldable } from "./Effect.js"
10
+ import { type Yieldable } from "./Effect.js"
11
11
 
12
12
  export * from "effect/ServiceMap"
13
13
 
14
- export interface Opaque<Self extends object, in out Shape extends object> extends ServiceMap.Key<Self, Self>, Yieldable<Opaque<Self, Shape>, Self, never, Self> {
15
- // temp while sorting out https://github.com/Effect-TS/effect-smol/pull/1534
16
- of(self: Shape): Self
14
+ export interface Opaque<Self extends object, in out Shape extends object>
15
+ extends ServiceMap.Key<Self, Self>, Yieldable<Opaque<Self, Shape>, Self, never, Self>
16
+ {
17
+ of(this: void, self: Shape): Self
17
18
  serviceMap(self: Shape): ServiceMap.ServiceMap<Self>
18
19
  // a version that leverages the Shape -> Self conversion
19
20
  toLayer: <E, R>(
@@ -151,7 +152,7 @@ export const Opaque: {
151
152
  id: Identifier,
152
153
  options?: {
153
154
  readonly make: ((...args: Args) => Effect.Effect<Shape, E, R>) | Effect.Effect<Shape, E, R> | undefined
154
- } | undefined
155
+ }
155
156
  ) =>
156
157
  & OpaqueClass<Self, Identifier, Shape>
157
158
  & ([Types.unassigned] extends [R] ? unknown
@@ -181,7 +182,7 @@ export const Opaque: {
181
182
  const svc = ServiceMap.Service()(id, options) as any
182
183
  return Object.assign(svc, {
183
184
  toLayer: (eff: Effect.Effect<any, any, any>) => {
184
- return Layer.effect(svc as any, eff)
185
+ return Layer.effect(svc, eff)
185
186
  }
186
187
  })
187
188
  }
@@ -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
 
@@ -91,7 +91,7 @@ const getFiltered = <M extends Requests>(resource: M) => {
91
91
  // TODO: Record.filter
92
92
  const filtered = typedKeysOf(resource).reduce((acc, cur) => {
93
93
  if (
94
- Predicate.isObject(resource[cur])
94
+ Predicate.isObjectKeyword(resource[cur])
95
95
  && (resource[cur].success)
96
96
  ) {
97
97
  acc[cur as keyof Filtered] = resource[cur] as any
@@ -146,10 +146,7 @@ const makeRpcTag = <M extends Requests>(resource: M) => {
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