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

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 +243 -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 +51 -15
  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 +177 -44
  29. package/dist/Schema/ext.d.ts.map +1 -1
  30. package/dist/Schema/ext.js +144 -35
  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 +213 -56
  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 +292 -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, 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`.
@@ -21,13 +29,108 @@ export const withDefaultConstructor = <A>(
21
29
  }
22
30
 
23
31
  // TODO: v4 migration - Date is no longer by default encoded to string.
24
- const DateFromString = Schema.Date.pipe(
25
- Schema.encodeTo(Schema.String, {
26
- decode: SchemaGetter.Date(),
27
- encode: SchemaGetter.transform((_) => _.toISOString())
28
- })
32
+ /*
33
+ in v4, there's the notion of `toCodecJson`, as a declaration and as a schema transformer.
34
+ this means that Date, Map/Set, etc, remain the same type Encoded as Decoded, but when transformed to and from JSON, will go through
35
+ the toCodecJson transformation, which for e.g Date will be the dateFromString transformation.
36
+
37
+ While this is a cool feature, our stack (especially the Store/Repository api) is based on having an Encoded shape representing the JSON shape, so we revert back to that for now.
38
+ */
39
+
40
+ /**
41
+ * Formats a `Date` as an ISO 8601 string, returning `"Invalid Date"` for
42
+ * invalid dates instead of throwing.
43
+ *
44
+ * When to use:
45
+ * - You want a safe `toISOString()` that never throws.
46
+ *
47
+ * Behavior:
48
+ * - Returns `date.toISOString()` on success.
49
+ * - Returns `"Invalid Date"` if `toISOString()` throws (e.g. for
50
+ * `new Date(NaN)`).
51
+ * - Pure function; does not mutate input.
52
+ *
53
+ * **Example** (Safe date formatting)
54
+ *
55
+ * ```ts
56
+ * import { Formatter } from "effect"
57
+ *
58
+ * console.log(Formatter.formatDate(new Date("2024-01-15T10:30:00Z")))
59
+ * // 2024-01-15T10:30:00.000Z
60
+ *
61
+ * console.log(Formatter.formatDate(new Date("invalid")))
62
+ * // Invalid Date
63
+ * ```
64
+ *
65
+ * See also: {@link format}
66
+ *
67
+ * @internal
68
+ */
69
+ export function formatDate(date: Date): string {
70
+ try {
71
+ return date.toISOString()
72
+ } catch {
73
+ return "Invalid Date"
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Decodes a `string` into a `Date` and encodes a `Date` back to a `string`.
79
+ *
80
+ * When to use this:
81
+ * - Parsing ISO 8601 date strings from APIs or user input.
82
+ *
83
+ * Behavior:
84
+ * - Decode: creates a `Date` from the string (like `new Date(s)`).
85
+ * - Encode: converts the `Date` to an ISO string (like `date.toISOString()`),
86
+ * returning `"Invalid Date"` for invalid dates.
87
+ *
88
+ * **Example** (Date from string)
89
+ *
90
+ * ```ts
91
+ * import { Schema, SchemaTransformation } from "effect"
92
+ *
93
+ * const schema = Schema.String.pipe(
94
+ * Schema.decodeTo(Schema.Date, SchemaTransformation.dateFromString)
95
+ * )
96
+ * ```
97
+ *
98
+ * See also:
99
+ * - {@link numberFromString}
100
+ * - {@link dateTimeUtcFromString}
101
+ *
102
+ * @category Coercions
103
+ * @since 4.0.0
104
+ */
105
+ export const dateFromString: SchemaTransformation.Transformation<globalThis.Date, string> = new SchemaTransformation
106
+ .Transformation(
107
+ SchemaGetter.Date(),
108
+ SchemaGetter.transform(formatDate)
29
109
  )
30
110
 
111
+ const DateString = S.String.annotate({ expected: "a string in ISO 8601 format that will be decoded as a Date" })
112
+
113
+ /**
114
+ * Schema type for {@link DateFromString}.
115
+ *
116
+ * @category Schemas
117
+ * @since 4.0.0
118
+ */
119
+ export interface DateFromString extends S.decodeTo<S.Date, S.String> {}
120
+
121
+ /**
122
+ * A transformation schema that parses an ISO 8601 string into a `Date`.
123
+ *
124
+ * Decoding:
125
+ * - A `string` is decoded as a `Date`.
126
+ *
127
+ * Encoding:
128
+ * - A `Date` is encoded as a `string`.
129
+ *
130
+ * @since 4.0.0
131
+ */
132
+ export const DateFromString: DateFromString = DateString.pipe(S.decodeTo(S.Date, dateFromString))
133
+
31
134
  /**
32
135
  * Like the default Schema `Date` but from String with `withDefault` => now
33
136
  */
@@ -35,6 +138,13 @@ export const Date = Object.assign(DateFromString, {
35
138
  withDefault: DateFromString.pipe(withDefaultConstructor(() => new global.Date()))
36
139
  })
37
140
 
141
+ /**
142
+ * Like the default Schema `DateValid` but from String with `withDefault` => now
143
+ */
144
+ export const DateValid = Object.assign(Date.check(isDateValid()), {
145
+ withDefault: DateFromString.pipe(withDefaultConstructor(() => new global.Date()))
146
+ })
147
+
38
148
  /**
39
149
  * Like the default Schema `Boolean` but with `withDefault` => false
40
150
  */
@@ -43,10 +153,16 @@ export const Boolean = Object.assign(S.Boolean, {
43
153
  })
44
154
 
45
155
  /**
156
+ * You probably want to use `Finite` instead of this.
46
157
  * Like the default Schema `Number` but with `withDefault` => 0
47
158
  */
48
159
  export const Number = Object.assign(S.Number, { withDefault: S.Number.pipe(withDefaultConstructor(() => 0)) })
49
160
 
161
+ /**
162
+ * Like the default Schema `Finite` but with `withDefault` => 0
163
+ */
164
+ export const Finite = Object.assign(S.Finite, { withDefault: S.Finite.pipe(withDefaultConstructor(() => 0)) })
165
+
50
166
  /**
51
167
  * Like the default Schema `Literal` but with `withDefault` => literals[0]
52
168
  */
@@ -69,7 +185,7 @@ export const Literal = <Literals extends NonEmptyReadonlyArray<AST.LiteralValue>
69
185
  /**
70
186
  * Like the default Schema `Array` but with `withDefault` => []
71
187
  */
72
- export function Array<Value extends S.Top>(value: Value) {
188
+ export function Array<ValueSchema extends S.Top>(value: ValueSchema) {
73
189
  return pipe(
74
190
  S.Array(value),
75
191
  (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => [])) })
@@ -77,60 +193,100 @@ export function Array<Value extends S.Top>(value: Value) {
77
193
  }
78
194
 
79
195
  /**
80
- * Like the default Schema `Map` but with `withDefault` => []
196
+ * An annotated `S.Array` of unique items that decodes to a `ReadonlySet`.
81
197
  */
82
- function Map_<Key extends S.Top, Value extends S.Top>(input: { key: Key; value: Value }) {
83
- return pipe(
84
- S.ReadonlyMap(input.key, input.value),
85
- (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => new global.Map())) })
198
+ export const ReadonlySetFromArray = <ValueSchema extends S.Top>(value: ValueSchema) => {
199
+ const from = S
200
+ .Array(value)
201
+ .annotate({ expected: "an array of unique items that will be decoded as a ReadonlySet" })
202
+ const to = S.instanceOf(Set) as S.instanceOf<ReadonlySet<S.Schema.Type<ValueSchema>>>
203
+ const schema = from.pipe(
204
+ S.decodeTo(
205
+ to,
206
+ SchemaTransformation.transform({
207
+ decode: (arr: globalThis.Array<S.Schema.Type<ValueSchema>>) => new Set<S.Schema.Type<ValueSchema>>(arr),
208
+ encode: (set: Set<S.Schema.Type<ValueSchema>>) => [...set] as globalThis.Array<S.Schema.Type<ValueSchema>>
209
+ }) as any
210
+ )
86
211
  )
212
+ return S.revealCodec(schema)
87
213
  }
88
214
 
89
- export { Map_ as Map }
215
+ /**
216
+ * An annotated `S.Array` of key-value tuples that decodes to a `ReadonlyMap`.
217
+ */
218
+ export const ReadonlyMapFromArray = <KeySchema extends S.Top, ValueSchema extends S.Top>(pair: {
219
+ readonly key: KeySchema
220
+ readonly value: ValueSchema
221
+ }) => {
222
+ const from = S
223
+ .Array(S.Tuple([pair.key, pair.value]))
224
+ .annotate({ expected: "an array of key-value tuples that will be decoded as a ReadonlyMap" })
225
+ const to = S.instanceOf(Map) as S.instanceOf<
226
+ ReadonlyMap<S.Schema.Type<KeySchema>, S.Schema.Type<ValueSchema>>
227
+ >
228
+ const schema = from.pipe(
229
+ S.decodeTo(
230
+ to,
231
+ SchemaTransformation.transform({
232
+ decode: (
233
+ arr: globalThis.Array<readonly [S.Schema.Type<KeySchema>, S.Schema.Type<ValueSchema>]>
234
+ ) => new Map<S.Schema.Type<KeySchema>, S.Schema.Type<ValueSchema>>(arr),
235
+ encode: (
236
+ map: Map<S.Schema.Type<KeySchema>, S.Schema.Type<ValueSchema>>
237
+ ) =>
238
+ [...map.entries()] as globalThis.Array<
239
+ readonly [S.Schema.Type<KeySchema>, S.Schema.Type<ValueSchema>]
240
+ >
241
+ }) as any
242
+ )
243
+ )
244
+ return S.revealCodec(schema)
245
+ }
90
246
 
91
247
  /**
92
- * Like the default Schema `ReadonlySet` but with `withDefault` => new Set()
248
+ * Like the default Schema `ReadonlySet` but from Array with `withDefault` => new Set()
93
249
  */
94
- export const ReadonlySet = <Value extends S.Top>(value: Value) =>
250
+ export const ReadonlySet = <ValueSchema extends S.Top>(value: ValueSchema) =>
95
251
  pipe(
96
- S.ReadonlySet(value),
97
- (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => new Set<S.Schema.Type<Value>>())) })
252
+ ReadonlySetFromArray(value),
253
+ (s) =>
254
+ Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => new Set<S.Schema.Type<ValueSchema>>())) })
98
255
  )
99
256
 
100
257
  /**
101
- * Like the default Schema `ReadonlyMap` but with `withDefault` => new Map()
258
+ * Like the default Schema `ReadonlyMap` but from Array with `withDefault` => new Map()
102
259
  */
103
- export const ReadonlyMap = <K extends S.Top, V extends S.Top>(pair: {
104
- readonly key: K
105
- readonly value: V
260
+ export const ReadonlyMap = <KeySchema extends S.Top, ValueSchema extends S.Top>(pair: {
261
+ readonly key: KeySchema
262
+ readonly value: ValueSchema
106
263
  }) =>
107
264
  pipe(
108
- S.ReadonlyMap(pair.key, pair.value),
265
+ ReadonlyMapFromArray(pair),
109
266
  (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => new Map())) })
110
267
  )
111
268
 
112
269
  /**
113
270
  * Like the default Schema `NullOr` but with `withDefault` => null
114
271
  */
115
- export const NullOr = <S extends S.Top>(self: S) =>
272
+ export const NullOr = <Schema extends S.Top>(self: Schema) =>
116
273
  pipe(
117
274
  S.NullOr(self),
118
275
  (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => null)) })
119
276
  )
120
277
 
121
- export const defaultDate = (s: S.Top) => s.pipe(withDefaultConstructor(() => new global.Date()))
278
+ export const defaultDate = <Schema extends S.Top>(schema: Schema) =>
279
+ schema.pipe(withDefaultConstructor(() => new global.Date()))
122
280
 
123
- export const defaultBool = (s: S.Top) => s.pipe(withDefaultConstructor(() => false))
281
+ export const defaultBool = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => false))
124
282
 
125
- export const defaultNullable = (
126
- s: S.Top
127
- ) => s.pipe(withDefaultConstructor(() => null))
283
+ export const defaultNullable = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => null))
128
284
 
129
- export const defaultArray = (s: S.Top) => s.pipe(withDefaultConstructor(() => []))
285
+ export const defaultArray = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => []))
130
286
 
131
- export const defaultMap = (s: S.Top) => s.pipe(withDefaultConstructor(() => new Map()))
287
+ export const defaultMap = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => new Map()))
132
288
 
133
- export const defaultSet = (s: S.Top) => s.pipe(withDefaultConstructor(() => new Set()))
289
+ export const defaultSet = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => new Set()))
134
290
 
135
291
  export const withDefaultMake = <Self extends S.Top>(s: Self) => {
136
292
  const a = Object.assign(S.decodeSync(s as any) as WithDefaults<Self>, s)
@@ -160,7 +316,7 @@ export type WithDefaults<Self extends S.Top> = (
160
316
  // : never
161
317
 
162
318
  export const inputDate = extendM(
163
- S.Union([S.DateValid, S.Date]),
319
+ S.Union([S.DateValid, Date]).pipe(S.revealCodec),
164
320
  (s) => ({ withDefault: s.pipe(withDefaultConstructor(() => new globalThis.Date())) })
165
321
  )
166
322
 
@@ -211,7 +367,7 @@ export const transformTo = <To extends S.Top, From extends S.Top>(
211
367
  { message: "One way schema transformation, encoding is not allowed" }
212
368
  )
213
369
  )
214
- }) as any
370
+ })
215
371
  )
216
372
  )
217
373
 
@@ -228,7 +384,7 @@ export const transformToOrFail = <To extends S.Top, From extends S.Top, RD>(
228
384
  S.decodeTo(
229
385
  to,
230
386
  SchemaTransformation.transformOrFail({
231
- decode: decode as any,
387
+ decode,
232
388
  encode: (i: any) =>
233
389
  Effect.fail(
234
390
  new SchemaIssue.Forbidden(
@@ -236,33 +392,34 @@ export const transformToOrFail = <To extends S.Top, From extends S.Top, RD>(
236
392
  { message: "One way schema transformation, encoding is not allowed" }
237
393
  )
238
394
  )
239
- }) as any
395
+ })
240
396
  )
241
397
  )
242
398
 
243
- // TODO: v4 migration — S.declare API changed (no [self] + decode/encode pattern)
244
- // Need to find v4 equivalent for contextual schema wrapping
245
399
  export const provide = <Self extends S.Top, R>(
246
400
  self: Self,
247
401
  context: ServiceMap.ServiceMap<R>
248
- ): any => {
402
+ ): ProvidedCodec<Self, R> => {
249
403
  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")
404
+ return self.pipe(
405
+ S.middlewareDecoding((effect) => prov(effect)),
406
+ S.middlewareEncoding((effect) => prov(effect))
407
+ ) as ProvidedCodec<Self, R>
268
408
  }
409
+ export const contextFromServices = <
410
+ Self extends S.Top,
411
+ Tags extends ReadonlyArray<ServiceMap.Key<any, any>>
412
+ >(
413
+ self: Self,
414
+ ...services: Tags
415
+ ): Effect.Effect<
416
+ ProvidedCodec<Self, ServiceMap.Service.Identifier<Tags[number]>>,
417
+ never,
418
+ ServiceMap.Service.Identifier<Tags[number]>
419
+ > =>
420
+ Effect.gen(function*() {
421
+ const context: ServiceMap.ServiceMap<ServiceMap.Service.Identifier<Tags[number]>> = ServiceMap.pick(...services)(
422
+ yield* Effect.services<ServiceMap.Service.Identifier<Tags[number]>>()
423
+ )
424
+ return provide(self, context)
425
+ })
@@ -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
  )