effect-app 4.0.0-beta.5 → 4.0.0-beta.51

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 (99) hide show
  1. package/CHANGELOG.md +237 -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/Effect.d.ts.map +1 -1
  9. package/dist/Effect.js +3 -2
  10. package/dist/Operations.d.ts +48 -12
  11. package/dist/Operations.d.ts.map +1 -1
  12. package/dist/Pure.d.ts.map +1 -1
  13. package/dist/Pure.js +11 -11
  14. package/dist/Schema/Class.d.ts +39 -1
  15. package/dist/Schema/Class.d.ts.map +1 -1
  16. package/dist/Schema/Class.js +89 -12
  17. package/dist/Schema/SpecialJsonSchema.d.ts +40 -0
  18. package/dist/Schema/SpecialJsonSchema.d.ts.map +1 -0
  19. package/dist/Schema/SpecialJsonSchema.js +199 -0
  20. package/dist/Schema/SpecialOpenApi.d.ts +30 -0
  21. package/dist/Schema/SpecialOpenApi.d.ts.map +1 -0
  22. package/dist/Schema/SpecialOpenApi.js +120 -0
  23. package/dist/Schema/brand.d.ts +8 -5
  24. package/dist/Schema/brand.d.ts.map +1 -1
  25. package/dist/Schema/brand.js +1 -1
  26. package/dist/Schema/email.d.ts.map +1 -1
  27. package/dist/Schema/email.js +4 -3
  28. package/dist/Schema/ext.d.ts +43 -27
  29. package/dist/Schema/ext.d.ts.map +1 -1
  30. package/dist/Schema/ext.js +26 -21
  31. package/dist/Schema/moreStrings.d.ts.map +1 -1
  32. package/dist/Schema/moreStrings.js +6 -4
  33. package/dist/Schema/numbers.d.ts +8 -8
  34. package/dist/Schema/numbers.js +2 -2
  35. package/dist/Schema/phoneNumber.d.ts.map +1 -1
  36. package/dist/Schema/phoneNumber.js +3 -2
  37. package/dist/Schema.d.ts +21 -54
  38. package/dist/Schema.d.ts.map +1 -1
  39. package/dist/Schema.js +43 -64
  40. package/dist/ServiceMap.d.ts +3 -3
  41. package/dist/ServiceMap.d.ts.map +1 -1
  42. package/dist/ServiceMap.js +1 -1
  43. package/dist/client/apiClientFactory.d.ts +1 -1
  44. package/dist/client/apiClientFactory.d.ts.map +1 -1
  45. package/dist/client/apiClientFactory.js +8 -9
  46. package/dist/client/errors.d.ts +8 -0
  47. package/dist/client/errors.d.ts.map +1 -1
  48. package/dist/client/errors.js +34 -10
  49. package/dist/client/makeClient.d.ts +13 -12
  50. package/dist/client/makeClient.d.ts.map +1 -1
  51. package/dist/client/makeClient.js +7 -15
  52. package/dist/http/Request.d.ts.map +1 -1
  53. package/dist/http/Request.js +5 -5
  54. package/dist/ids.d.ts +1 -1
  55. package/dist/ids.d.ts.map +1 -1
  56. package/dist/ids.js +1 -1
  57. package/dist/index.d.ts +2 -1
  58. package/dist/index.d.ts.map +1 -1
  59. package/dist/index.js +3 -2
  60. package/dist/utils.d.ts +18 -0
  61. package/dist/utils.d.ts.map +1 -1
  62. package/dist/utils.js +24 -5
  63. package/package.json +24 -12
  64. package/src/Config.ts +14 -0
  65. package/src/ConfigProvider.ts +48 -0
  66. package/src/Effect.ts +3 -2
  67. package/src/Pure.ts +12 -13
  68. package/src/Schema/Class.ts +114 -16
  69. package/src/Schema/SpecialJsonSchema.ts +216 -0
  70. package/src/Schema/SpecialOpenApi.ts +126 -0
  71. package/src/Schema/brand.ts +13 -7
  72. package/src/Schema/email.ts +4 -2
  73. package/src/Schema/ext.ts +61 -39
  74. package/src/Schema/moreStrings.ts +10 -6
  75. package/src/Schema/numbers.ts +2 -2
  76. package/src/Schema/phoneNumber.ts +3 -1
  77. package/src/Schema.ts +79 -103
  78. package/src/ServiceMap.ts +7 -6
  79. package/src/client/apiClientFactory.ts +12 -15
  80. package/src/client/errors.ts +45 -12
  81. package/src/client/makeClient.ts +33 -26
  82. package/src/http/Request.ts +7 -4
  83. package/src/ids.ts +1 -1
  84. package/src/index.ts +2 -1
  85. package/src/utils.ts +26 -4
  86. package/test/dist/moreStrings.test.d.ts.map +1 -0
  87. package/test/dist/rpc.test.d.ts.map +1 -1
  88. package/test/dist/special.test.d.ts.map +1 -0
  89. package/test/moreStrings.test.ts +17 -0
  90. package/test/rpc.test.ts +26 -5
  91. package/test/schema.test.ts +178 -1
  92. package/test/special.test.ts +525 -0
  93. package/test/utils.test.ts +1 -1
  94. package/tsconfig.base.json +0 -1
  95. package/tsconfig.json +0 -1
  96. package/dist/Struct.d.ts +0 -44
  97. package/dist/Struct.d.ts.map +0 -1
  98. package/dist/Struct.js +0 -29
  99. package/src/Struct.ts +0 -54
@@ -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,19 @@
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
+ import { isDateValid } from "effect/Schema"
5
6
  import { type NonEmptyReadonlyArray } from "../Array.js"
6
7
  import { extendM, typedKeysOf } from "../utils.js"
7
8
  import { type AST } from "./schema.js"
8
9
 
10
+ type ProvidedCodec<Self extends S.Top, R> = S.Codec<
11
+ Self["Type"],
12
+ Self["Encoded"],
13
+ Exclude<Self["DecodingServices"], R>,
14
+ Exclude<Self["EncodingServices"], R>
15
+ >
16
+
9
17
  // TODO: v4 migration — withConstructorDefault signature changed, propertySignature removed
10
18
  // Constraint relaxed from `Self extends S.Top & S.WithoutConstructorDefault` to `Self extends S.Top`
11
19
  // because `.pipe()` widens the schema type to `Top` which doesn't satisfy `WithoutConstructorDefault`.
@@ -35,6 +43,13 @@ export const Date = Object.assign(DateFromString, {
35
43
  withDefault: DateFromString.pipe(withDefaultConstructor(() => new global.Date()))
36
44
  })
37
45
 
46
+ /**
47
+ * Like the default Schema `DateValid` but from String with `withDefault` => now
48
+ */
49
+ export const DateValid = Object.assign(Date.check(isDateValid()), {
50
+ withDefault: DateFromString.pipe(withDefaultConstructor(() => new global.Date()))
51
+ })
52
+
38
53
  /**
39
54
  * Like the default Schema `Boolean` but with `withDefault` => false
40
55
  */
@@ -43,10 +58,16 @@ export const Boolean = Object.assign(S.Boolean, {
43
58
  })
44
59
 
45
60
  /**
61
+ * You probably want to use `Finite` instead of this.
46
62
  * Like the default Schema `Number` but with `withDefault` => 0
47
63
  */
48
64
  export const Number = Object.assign(S.Number, { withDefault: S.Number.pipe(withDefaultConstructor(() => 0)) })
49
65
 
66
+ /**
67
+ * Like the default Schema `Finite` but with `withDefault` => 0
68
+ */
69
+ export const Finite = Object.assign(S.Finite, { withDefault: S.Finite.pipe(withDefaultConstructor(() => 0)) })
70
+
50
71
  /**
51
72
  * Like the default Schema `Literal` but with `withDefault` => literals[0]
52
73
  */
@@ -69,7 +90,7 @@ export const Literal = <Literals extends NonEmptyReadonlyArray<AST.LiteralValue>
69
90
  /**
70
91
  * Like the default Schema `Array` but with `withDefault` => []
71
92
  */
72
- export function Array<Value extends S.Top>(value: Value) {
93
+ export function Array<ValueSchema extends S.Top>(value: ValueSchema) {
73
94
  return pipe(
74
95
  S.Array(value),
75
96
  (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => [])) })
@@ -79,7 +100,7 @@ export function Array<Value extends S.Top>(value: Value) {
79
100
  /**
80
101
  * Like the default Schema `Map` but with `withDefault` => []
81
102
  */
82
- function Map_<Key extends S.Top, Value extends S.Top>(input: { key: Key; value: Value }) {
103
+ function Map_<KeySchema extends S.Top, ValueSchema extends S.Top>(input: { key: KeySchema; value: ValueSchema }) {
83
104
  return pipe(
84
105
  S.ReadonlyMap(input.key, input.value),
85
106
  (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => new global.Map())) })
@@ -91,18 +112,19 @@ export { Map_ as Map }
91
112
  /**
92
113
  * Like the default Schema `ReadonlySet` but with `withDefault` => new Set()
93
114
  */
94
- export const ReadonlySet = <Value extends S.Top>(value: Value) =>
115
+ export const ReadonlySet = <ValueSchema extends S.Top>(value: ValueSchema) =>
95
116
  pipe(
96
117
  S.ReadonlySet(value),
97
- (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => new Set<S.Schema.Type<Value>>())) })
118
+ (s) =>
119
+ Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => new Set<S.Schema.Type<ValueSchema>>())) })
98
120
  )
99
121
 
100
122
  /**
101
123
  * Like the default Schema `ReadonlyMap` but with `withDefault` => new Map()
102
124
  */
103
- export const ReadonlyMap = <K extends S.Top, V extends S.Top>(pair: {
104
- readonly key: K
105
- readonly value: V
125
+ export const ReadonlyMap = <KeySchema extends S.Top, ValueSchema extends S.Top>(pair: {
126
+ readonly key: KeySchema
127
+ readonly value: ValueSchema
106
128
  }) =>
107
129
  pipe(
108
130
  S.ReadonlyMap(pair.key, pair.value),
@@ -112,25 +134,24 @@ export const ReadonlyMap = <K extends S.Top, V extends S.Top>(pair: {
112
134
  /**
113
135
  * Like the default Schema `NullOr` but with `withDefault` => null
114
136
  */
115
- export const NullOr = <S extends S.Top>(self: S) =>
137
+ export const NullOr = <Schema extends S.Top>(self: Schema) =>
116
138
  pipe(
117
139
  S.NullOr(self),
118
140
  (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => null)) })
119
141
  )
120
142
 
121
- export const defaultDate = (s: S.Top) => s.pipe(withDefaultConstructor(() => new global.Date()))
143
+ export const defaultDate = <Schema extends S.Top>(schema: Schema) =>
144
+ schema.pipe(withDefaultConstructor(() => new global.Date()))
122
145
 
123
- export const defaultBool = (s: S.Top) => s.pipe(withDefaultConstructor(() => false))
146
+ export const defaultBool = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => false))
124
147
 
125
- export const defaultNullable = (
126
- s: S.Top
127
- ) => s.pipe(withDefaultConstructor(() => null))
148
+ export const defaultNullable = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => null))
128
149
 
129
- export const defaultArray = (s: S.Top) => s.pipe(withDefaultConstructor(() => []))
150
+ export const defaultArray = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => []))
130
151
 
131
- export const defaultMap = (s: S.Top) => s.pipe(withDefaultConstructor(() => new Map()))
152
+ export const defaultMap = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => new Map()))
132
153
 
133
- export const defaultSet = (s: S.Top) => s.pipe(withDefaultConstructor(() => new Set()))
154
+ export const defaultSet = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => new Set()))
134
155
 
135
156
  export const withDefaultMake = <Self extends S.Top>(s: Self) => {
136
157
  const a = Object.assign(S.decodeSync(s as any) as WithDefaults<Self>, s)
@@ -160,7 +181,7 @@ export type WithDefaults<Self extends S.Top> = (
160
181
  // : never
161
182
 
162
183
  export const inputDate = extendM(
163
- S.Union([S.DateValid, S.Date]),
184
+ S.Union([S.DateValid, Date]).pipe(S.revealCodec),
164
185
  (s) => ({ withDefault: s.pipe(withDefaultConstructor(() => new globalThis.Date())) })
165
186
  )
166
187
 
@@ -240,29 +261,30 @@ export const transformToOrFail = <To extends S.Top, From extends S.Top, RD>(
240
261
  )
241
262
  )
242
263
 
243
- // TODO: v4 migration — S.declare API changed (no [self] + decode/encode pattern)
244
- // Need to find v4 equivalent for contextual schema wrapping
245
264
  export const provide = <Self extends S.Top, R>(
246
265
  self: Self,
247
266
  context: ServiceMap.ServiceMap<R>
248
- ): any => {
267
+ ): ProvidedCodec<Self, R> => {
249
268
  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")
269
+ return self.pipe(
270
+ S.middlewareDecoding((effect) => prov(effect)),
271
+ S.middlewareEncoding((effect) => prov(effect))
272
+ ) as ProvidedCodec<Self, R>
268
273
  }
274
+ export const contextFromServices = <
275
+ Self extends S.Top,
276
+ Tags extends ReadonlyArray<ServiceMap.Key<any, any>>
277
+ >(
278
+ self: Self,
279
+ ...services: Tags
280
+ ): Effect.Effect<
281
+ ProvidedCodec<Self, ServiceMap.Service.Identifier<Tags[number]>>,
282
+ never,
283
+ ServiceMap.Service.Identifier<Tags[number]>
284
+ > =>
285
+ Effect.gen(function*() {
286
+ const context: ServiceMap.ServiceMap<ServiceMap.Service.Identifier<Tags[number]>> = ServiceMap.pick(...services)(
287
+ yield* Effect.services<ServiceMap.Service.Identifier<Tags[number]>>()
288
+ )
289
+ return provide(self, context)
290
+ })
@@ -140,10 +140,10 @@ const minLength = 6
140
140
  const maxLength = 50
141
141
  const size = 21
142
142
  const length = 10 * size
143
- const StringIdArb = (): S.LazyArbitrary<string> => (fc) =>
143
+ const StringIdArb = (): S.LazyArbitrary<StringId> => (fc) =>
144
144
  fc
145
145
  .uint8Array({ minLength: length, maxLength: length })
146
- .map((_) => customRandom(urlAlphabet, size, (size) => _.subarray(0, size))())
146
+ .map((_) => customRandom(urlAlphabet, size, (size) => _.subarray(0, size))() as StringId)
147
147
  /**
148
148
  * A string that is at least 6 characters long and a maximum of 50.
149
149
  */
@@ -154,7 +154,7 @@ export const StringId = extendM(
154
154
  fromBrand(nominal<StringId>(), {
155
155
  identifier: "StringId",
156
156
  title: "StringId",
157
- arbitrary: StringIdArb,
157
+ toArbitrary: () => (fc) => StringIdArb()(fc),
158
158
  jsonSchema: {}
159
159
  })
160
160
  ),
@@ -185,11 +185,13 @@ export function prefixedStringId<Brand extends StringId>() {
185
185
  const s: S.Codec<string & Brand, string> = StringId
186
186
  .pipe(
187
187
  S.refine((x: string): x is string & Brand => x.startsWith(pref), {
188
- arbitrary: arb,
189
188
  identifier: name,
190
189
  title: name
190
+ }),
191
+ S.annotate({
192
+ toArbitrary: () => (fc) => arb()(fc)
191
193
  })
192
- ) as any
194
+ ) as S.Codec<string & Brand, string>
193
195
  const schema = s.pipe(withDefaultMake)
194
196
  const make = () => (pref + StringId.make().substring(0, 50 - pref.length)) as Brand
195
197
 
@@ -246,10 +248,12 @@ export const Url = S
246
248
  .String
247
249
  .pipe(
248
250
  S.refine(isUrl, {
249
- arbitrary: (): S.LazyArbitrary<Url> => (fc) => fc.webUrl().map((_) => _ as Url),
250
251
  identifier: "Url",
251
252
  title: "Url",
252
253
  jsonSchema: { format: "uri" }
253
254
  }),
255
+ S.annotate({
256
+ toArbitrary: () => (fc) => fc.webUrl().map((_) => _ as Url)
257
+ }),
254
258
  withDefaultMake
255
259
  )
@@ -42,7 +42,7 @@ export type Int = number & IntBrand
42
42
 
43
43
  export interface PositiveNumberBrand extends Simplify<B.Brand<"PositiveNumber"> & NonNegativeNumberBrand> {}
44
44
  export const PositiveNumber = extendM(
45
- S.Number.pipe(
45
+ S.Finite.pipe(
46
46
  S.check(S.isGreaterThan(0)),
47
47
  fromBrand(nominal<PositiveNumber>(), {
48
48
  identifier: "PositiveNumber",
@@ -58,7 +58,7 @@ export type PositiveNumber = number & PositiveNumberBrand
58
58
  export interface NonNegativeNumberBrand extends Simplify<B.Brand<"NonNegativeNumber">> {}
59
59
  export const NonNegativeNumber = extendM(
60
60
  S
61
- .Number
61
+ .Finite
62
62
  .pipe(
63
63
  S.check(S.isGreaterThanOrEqualTo(0)),
64
64
  fromBrand(nominal<NonNegativeNumber>(), {
@@ -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,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, Map, 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))
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
  }