effect-app 4.0.0-beta.13 → 4.0.0-beta.131

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 (139) hide show
  1. package/CHANGELOG.md +503 -0
  2. package/dist/Config/SecretURL.js +2 -2
  3. package/dist/Config.d.ts +7 -0
  4. package/dist/Config.d.ts.map +1 -0
  5. package/dist/Config.js +6 -0
  6. package/dist/ConfigProvider.d.ts +39 -0
  7. package/dist/ConfigProvider.d.ts.map +1 -0
  8. package/dist/ConfigProvider.js +42 -0
  9. package/dist/{ServiceMap.d.ts → Context.d.ts} +14 -18
  10. package/dist/Context.d.ts.map +1 -0
  11. package/dist/Context.js +66 -0
  12. package/dist/Effect.d.ts +8 -9
  13. package/dist/Effect.d.ts.map +1 -1
  14. package/dist/Effect.js +3 -6
  15. package/dist/Layer.d.ts +5 -4
  16. package/dist/Layer.d.ts.map +1 -1
  17. package/dist/Layer.js +1 -1
  18. package/dist/Operations.d.ts +198 -33
  19. package/dist/Operations.d.ts.map +1 -1
  20. package/dist/Pure.d.ts +2 -2
  21. package/dist/Pure.d.ts.map +1 -1
  22. package/dist/Pure.js +13 -13
  23. package/dist/Schema/Class.d.ts +48 -10
  24. package/dist/Schema/Class.d.ts.map +1 -1
  25. package/dist/Schema/Class.js +120 -16
  26. package/dist/Schema/SpecialJsonSchema.d.ts +33 -0
  27. package/dist/Schema/SpecialJsonSchema.d.ts.map +1 -0
  28. package/dist/Schema/SpecialJsonSchema.js +122 -0
  29. package/dist/Schema/SpecialOpenApi.d.ts +32 -0
  30. package/dist/Schema/SpecialOpenApi.d.ts.map +1 -0
  31. package/dist/Schema/SpecialOpenApi.js +123 -0
  32. package/dist/Schema/brand.d.ts +10 -1
  33. package/dist/Schema/brand.d.ts.map +1 -1
  34. package/dist/Schema/brand.js +1 -1
  35. package/dist/Schema/email.d.ts.map +1 -1
  36. package/dist/Schema/email.js +9 -4
  37. package/dist/Schema/ext.d.ts +112 -47
  38. package/dist/Schema/ext.d.ts.map +1 -1
  39. package/dist/Schema/ext.js +115 -53
  40. package/dist/Schema/moreStrings.d.ts +110 -10
  41. package/dist/Schema/moreStrings.d.ts.map +1 -1
  42. package/dist/Schema/moreStrings.js +19 -10
  43. package/dist/Schema/numbers.d.ts +126 -14
  44. package/dist/Schema/numbers.d.ts.map +1 -1
  45. package/dist/Schema/numbers.js +10 -9
  46. package/dist/Schema/phoneNumber.d.ts.map +1 -1
  47. package/dist/Schema/phoneNumber.js +8 -3
  48. package/dist/Schema/strings.d.ts +36 -4
  49. package/dist/Schema/strings.d.ts.map +1 -1
  50. package/dist/Schema/strings.js +1 -1
  51. package/dist/Schema.d.ts +74 -55
  52. package/dist/Schema.d.ts.map +1 -1
  53. package/dist/Schema.js +85 -64
  54. package/dist/client/apiClientFactory.d.ts +12 -28
  55. package/dist/client/apiClientFactory.d.ts.map +1 -1
  56. package/dist/client/apiClientFactory.js +16 -17
  57. package/dist/client/clientFor.d.ts +6 -5
  58. package/dist/client/clientFor.d.ts.map +1 -1
  59. package/dist/client/errors.d.ts +18 -9
  60. package/dist/client/errors.d.ts.map +1 -1
  61. package/dist/client/errors.js +35 -10
  62. package/dist/client/makeClient.d.ts +73 -28
  63. package/dist/client/makeClient.d.ts.map +1 -1
  64. package/dist/client/makeClient.js +49 -23
  65. package/dist/http/Request.d.ts.map +1 -1
  66. package/dist/http/Request.js +5 -5
  67. package/dist/ids.d.ts +2 -2
  68. package/dist/ids.d.ts.map +1 -1
  69. package/dist/ids.js +3 -2
  70. package/dist/index.d.ts +3 -7
  71. package/dist/index.d.ts.map +1 -1
  72. package/dist/index.js +4 -8
  73. package/dist/middleware.d.ts +2 -2
  74. package/dist/middleware.d.ts.map +1 -1
  75. package/dist/middleware.js +3 -3
  76. package/dist/rpc/MiddlewareMaker.d.ts +4 -3
  77. package/dist/rpc/MiddlewareMaker.d.ts.map +1 -1
  78. package/dist/rpc/MiddlewareMaker.js +23 -24
  79. package/dist/rpc/RpcContextMap.d.ts +2 -2
  80. package/dist/rpc/RpcContextMap.d.ts.map +1 -1
  81. package/dist/rpc/RpcContextMap.js +4 -4
  82. package/dist/rpc/RpcMiddleware.d.ts +4 -3
  83. package/dist/rpc/RpcMiddleware.d.ts.map +1 -1
  84. package/dist/rpc/RpcMiddleware.js +1 -1
  85. package/dist/utils/gen.d.ts +1 -1
  86. package/dist/utils/gen.d.ts.map +1 -1
  87. package/dist/utils/logger.d.ts +2 -2
  88. package/dist/utils/logger.d.ts.map +1 -1
  89. package/dist/utils/logger.js +3 -3
  90. package/dist/utils.d.ts +7 -1
  91. package/dist/utils.d.ts.map +1 -1
  92. package/dist/utils.js +8 -2
  93. package/package.json +30 -14
  94. package/src/Config/SecretURL.ts +1 -1
  95. package/src/Config.ts +14 -0
  96. package/src/ConfigProvider.ts +48 -0
  97. package/src/{ServiceMap.ts → Context.ts} +51 -59
  98. package/src/Effect.ts +11 -14
  99. package/src/Layer.ts +5 -4
  100. package/src/Pure.ts +17 -18
  101. package/src/Schema/Class.ts +157 -30
  102. package/src/Schema/SpecialJsonSchema.ts +137 -0
  103. package/src/Schema/SpecialOpenApi.ts +130 -0
  104. package/src/Schema/brand.ts +18 -3
  105. package/src/Schema/email.ts +10 -2
  106. package/src/Schema/ext.ts +196 -87
  107. package/src/Schema/moreStrings.ts +31 -17
  108. package/src/Schema/numbers.ts +14 -13
  109. package/src/Schema/phoneNumber.ts +8 -1
  110. package/src/Schema/strings.ts +4 -4
  111. package/src/Schema.ts +195 -104
  112. package/src/client/apiClientFactory.ts +104 -112
  113. package/src/client/clientFor.ts +6 -1
  114. package/src/client/errors.ts +42 -17
  115. package/src/client/makeClient.ts +150 -61
  116. package/src/http/Request.ts +7 -4
  117. package/src/ids.ts +2 -1
  118. package/src/index.ts +3 -10
  119. package/src/middleware.ts +2 -2
  120. package/src/rpc/MiddlewareMaker.ts +33 -44
  121. package/src/rpc/RpcContextMap.ts +6 -5
  122. package/src/rpc/RpcMiddleware.ts +5 -4
  123. package/src/utils/gen.ts +1 -1
  124. package/src/utils/logger.ts +2 -2
  125. package/src/utils.ts +8 -4
  126. package/test/dist/moreStrings.test.d.ts.map +1 -0
  127. package/test/dist/rpc.test.d.ts.map +1 -1
  128. package/test/dist/secretURL.test.d.ts.map +1 -0
  129. package/test/dist/special.test.d.ts.map +1 -0
  130. package/test/moreStrings.test.ts +17 -0
  131. package/test/rpc.test.ts +30 -6
  132. package/test/schema.test.ts +517 -4
  133. package/test/secretURL.test.ts +157 -0
  134. package/test/special.test.ts +862 -0
  135. package/test/utils.test.ts +2 -2
  136. package/tsconfig.base.json +0 -1
  137. package/tsconfig.json +0 -1
  138. package/dist/ServiceMap.d.ts.map +0 -1
  139. package/dist/ServiceMap.js +0 -91
package/src/Schema/ext.ts CHANGED
@@ -1,56 +1,94 @@
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, Function, Option, pipe, type SchemaAST, SchemaIssue, SchemaTransformation } from "effect"
4
4
  import * as S from "effect/Schema"
5
+ import { isDateValid } from "effect/Schema"
5
6
  import { type NonEmptyReadonlyArray } from "../Array.js"
7
+ import * as Context from "../Context.js"
6
8
  import { extendM, typedKeysOf } from "../utils.js"
7
9
  import { type AST } from "./schema.js"
8
10
 
9
- // TODO: v4 migration withConstructorDefault signature changed, propertySignature removed
10
- // Constraint relaxed from `Self extends S.Top & S.WithoutConstructorDefault` to `Self extends S.Top`
11
- // because `.pipe()` widens the schema type to `Top` which doesn't satisfy `WithoutConstructorDefault`.
12
- // The narrowing assertions below are safe — we're asserting "this schema hasn't had a default applied yet".
13
- export const withDefaultConstructor = <A>(
14
- makeDefault: () => NoInfer<A>
15
- ) =>
16
- <Self extends S.Top>(self: Self): S.withConstructorDefault<Self & S.WithoutConstructorDefault> => {
17
- type Narrowed = Self & S.WithoutConstructorDefault
18
- return S.withConstructorDefault<Narrowed>(
19
- () => Option.some(makeDefault() as Narrowed["~type.make.in"])
20
- )(self as Narrowed)
21
- }
11
+ type ProvidedCodec<Self extends S.Top, R> = S.Codec<
12
+ Self["Type"],
13
+ Self["Encoded"],
14
+ Exclude<Self["DecodingServices"], R>,
15
+ Exclude<Self["EncodingServices"], R>
16
+ >
22
17
 
23
18
  // 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
- })
29
- )
19
+
20
+ const DateString = S.String.annotate({
21
+ identifier: "Date",
22
+ description: "a string in ISO 8601 format that will be decoded as a Date",
23
+ format: "date-time"
24
+ })
25
+
26
+ /**
27
+ * Schema type for {@link DateFromString}.
28
+ *
29
+ * @category Schemas
30
+ * @since 4.0.0
31
+ */
32
+ export interface DateFromString extends S.decodeTo<S.Date, S.String> {}
33
+
34
+ /**
35
+ * A transformation schema that parses an ISO 8601 string into a `Date`.
36
+ *
37
+ * Decoding:
38
+ * - A `string` is decoded as a `Date`.
39
+ *
40
+ * Encoding:
41
+ * - A `Date` is encoded as a `string`.
42
+ *
43
+ * @since 4.0.0
44
+ */
45
+ export const DateFromString: DateFromString = DateString.pipe(S.decodeTo(S.Date, SchemaTransformation.dateFromString))
30
46
 
31
47
  /**
32
48
  * Like the default Schema `Date` but from String with `withDefault` => now
33
49
  */
34
50
  export const Date = Object.assign(DateFromString, {
35
- withDefault: DateFromString.pipe(withDefaultConstructor(() => new global.Date()))
51
+ withDefault: DateFromString.pipe(S.withConstructorDefault(Effect.sync(() => new global.Date()))),
52
+ withDecodingDefaultType: DateFromString.pipe(S.withDecodingDefaultType(Effect.sync(() => new global.Date())))
53
+ })
54
+
55
+ /**
56
+ * Like the default Schema `DateValid` but from String with `withDefault` => now
57
+ */
58
+ export const DateValid = Object.assign(Date.check(isDateValid()), {
59
+ withDefault: DateFromString.pipe(S.withConstructorDefault(Effect.sync(() => new global.Date()))),
60
+ withDecodingDefaultType: DateFromString.pipe(S.withDecodingDefaultType(Effect.sync(() => new global.Date())))
36
61
  })
37
62
 
38
63
  /**
39
64
  * Like the default Schema `Boolean` but with `withDefault` => false
40
65
  */
41
66
  export const Boolean = Object.assign(S.Boolean, {
42
- withDefault: S.Boolean.pipe(withDefaultConstructor(() => false))
67
+ withDefault: S.Boolean.pipe(S.withConstructorDefault(Effect.succeed(false))),
68
+ withDecodingDefaultType: S.Boolean.pipe(S.withDecodingDefaultType(Effect.succeed(false)))
43
69
  })
44
70
 
45
71
  /**
72
+ * You probably want to use `Finite` instead of this.
46
73
  * Like the default Schema `Number` but with `withDefault` => 0
47
74
  */
48
- export const Number = Object.assign(S.Number, { withDefault: S.Number.pipe(withDefaultConstructor(() => 0)) })
75
+ export const Number = Object.assign(S.Number, {
76
+ withDefault: S.Number.pipe(S.withConstructorDefault(Effect.succeed(0))),
77
+ withDecodingDefaultType: S.Number.pipe(S.withDecodingDefaultType(Effect.succeed(0)))
78
+ })
49
79
 
50
80
  /**
51
- * Like the default Schema `Literal` but with `withDefault` => literals[0]
81
+ * Like the default Schema `Finite` but with `withDefault` => 0
52
82
  */
53
- export const Literal = <Literals extends NonEmptyReadonlyArray<AST.LiteralValue>>(...literals: Literals) =>
83
+ export const Finite = Object.assign(S.Finite, {
84
+ withDefault: S.Finite.pipe(S.withConstructorDefault(Effect.succeed(0))),
85
+ withDecodingDefaultType: S.Finite.pipe(S.withDecodingDefaultType(Effect.succeed(0)))
86
+ })
87
+
88
+ /**
89
+ * Like the default Schema `Literals` but with `withDefault` => literals[0]
90
+ */
91
+ export const Literals = <const Literals extends NonEmptyReadonlyArray<AST.LiteralValue>>(literals: Literals) =>
54
92
  pipe(
55
93
  S.Literals(literals),
56
94
  (s) =>
@@ -58,79 +96,144 @@ export const Literal = <Literals extends NonEmptyReadonlyArray<AST.LiteralValue>
58
96
  changeDefault: <A extends Literals[number]>(a: A) => {
59
97
  return Object.assign(S.Literals(literals), {
60
98
  Default: a,
61
- withDefault: s.pipe(withDefaultConstructor(() => a))
99
+ withDefault: s.pipe(S.withConstructorDefault(Effect.succeed(a))),
100
+ withDecodingDefaultType: s.pipe(S.withDecodingDefaultType(Effect.succeed(a)))
62
101
  }) // todo: copy annotations from original?
63
102
  },
64
- Default: literals[0] as typeof literals[0],
65
- withDefault: s.pipe(withDefaultConstructor(() => literals[0]))
103
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion -- load-bearing: Object.assign widens the field type without it, breaking `expectTypeOf(l.Default).toEqualTypeOf<"a">()` in tests
104
+ Default: literals[0] as Literals[0],
105
+ withDefault: s.pipe(S.withConstructorDefault(Effect.succeed(literals[0]))),
106
+ withDecodingDefaultType: s.pipe(S.withDecodingDefaultType(Effect.succeed(literals[0])))
66
107
  })
67
108
  )
68
109
 
69
110
  /**
70
111
  * Like the default Schema `Array` but with `withDefault` => []
71
112
  */
72
- export function Array<Value extends S.Top>(value: Value) {
113
+ const co = { parseOptions: { concurrency: "unbounded" as const } }
114
+ export { co as concurrencyUnbounded }
115
+
116
+ export function Array<ValueSchema extends S.Top>(value: ValueSchema) {
73
117
  return pipe(
74
- S.Array(value),
75
- (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => [])) })
118
+ S.Array(value).annotate(co),
119
+ (s) =>
120
+ Object.assign(s, {
121
+ withDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => []))),
122
+ withDecodingDefaultType: s.pipe(S.withDecodingDefaultType(Effect.sync(() => [])))
123
+ })
76
124
  )
77
125
  }
78
126
 
79
127
  /**
80
- * Like the default Schema `Map` but with `withDefault` => []
128
+ * An annotated `S.Array` of unique items that decodes to a `ReadonlySet`.
81
129
  */
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())) })
130
+ export const ReadonlySetFromArray = <ValueSchema extends S.Top>(value: ValueSchema) => {
131
+ const from = S
132
+ .Array(value)
133
+ .annotate({ ...co, expected: "an array of unique items that will be decoded as a ReadonlySet" })
134
+ const to = S.instanceOf(Set) as S.instanceOf<ReadonlySet<S.Schema.Type<ValueSchema>>>
135
+ const schema = from.pipe(
136
+ S.decodeTo(
137
+ to,
138
+ SchemaTransformation.transform({
139
+ decode: (arr) => new Set(arr) as ReadonlySet<S.Schema.Type<ValueSchema>>,
140
+ encode: (set) => [...set]
141
+ })
142
+ )
86
143
  )
144
+ return schema
87
145
  }
88
146
 
89
- export { Map_ as Map }
147
+ /**
148
+ * An annotated `S.Array` of key-value tuples that decodes to a `ReadonlyMap`.
149
+ */
150
+ export const ReadonlyMapFromArray = <KeySchema extends S.Top, ValueSchema extends S.Top>(pair: {
151
+ readonly key: KeySchema
152
+ readonly value: ValueSchema
153
+ }) => {
154
+ const from = S
155
+ .Array(S.Tuple([pair.key, pair.value]))
156
+ .annotate({ ...co, expected: "an array of key-value tuples that will be decoded as a ReadonlyMap" })
157
+ const to = S.instanceOf(Map) as S.instanceOf<
158
+ ReadonlyMap<S.Schema.Type<KeySchema>, S.Schema.Type<ValueSchema>>
159
+ >
160
+ const schema = from.pipe(
161
+ S.decodeTo(
162
+ to,
163
+ SchemaTransformation.transform({
164
+ decode: (
165
+ arr
166
+ ) => new Map(arr) as ReadonlyMap<S.Schema.Type<KeySchema>, S.Schema.Type<ValueSchema>>,
167
+ encode: (
168
+ map
169
+ ) => [...map.entries()] as any // fu
170
+ })
171
+ )
172
+ )
173
+ return schema
174
+ }
90
175
 
91
176
  /**
92
- * Like the default Schema `ReadonlySet` but with `withDefault` => new Set()
177
+ * Like the default Schema `ReadonlySet` but from Array with `withDefault` => new Set()
93
178
  */
94
- export const ReadonlySet = <Value extends S.Top>(value: Value) =>
179
+ export const ReadonlySet = <ValueSchema extends S.Top>(value: ValueSchema) =>
95
180
  pipe(
96
- S.ReadonlySet(value),
97
- (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => new Set<S.Schema.Type<Value>>())) })
181
+ ReadonlySetFromArray(value),
182
+ (s) =>
183
+ Object.assign(s, {
184
+ withDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => new Set<S.Schema.Type<ValueSchema>>()))),
185
+ withDecodingDefaultType: s.pipe(
186
+ S.withDecodingDefaultType(Effect.sync(() => new Set<S.Schema.Type<ValueSchema>>()))
187
+ )
188
+ })
98
189
  )
99
190
 
100
191
  /**
101
- * Like the default Schema `ReadonlyMap` but with `withDefault` => new Map()
192
+ * Like the default Schema `ReadonlyMap` but from Array with `withDefault` => new Map()
102
193
  */
103
- export const ReadonlyMap = <K extends S.Top, V extends S.Top>(pair: {
104
- readonly key: K
105
- readonly value: V
194
+ export const ReadonlyMap = <KeySchema extends S.Top, ValueSchema extends S.Top>(pair: {
195
+ readonly key: KeySchema
196
+ readonly value: ValueSchema
106
197
  }) =>
107
198
  pipe(
108
- S.ReadonlyMap(pair.key, pair.value),
109
- (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => new Map())) })
199
+ ReadonlyMapFromArray(pair),
200
+ (s) =>
201
+ Object.assign(s, {
202
+ withDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => new Map()))),
203
+ withDecodingDefaultType: s.pipe(S.withDecodingDefaultType(Effect.sync(() => new Map())))
204
+ })
110
205
  )
111
206
 
112
207
  /**
113
208
  * Like the default Schema `NullOr` but with `withDefault` => null
114
209
  */
115
- export const NullOr = <S extends S.Top>(self: S) =>
210
+ export const NullOr = <Schema extends S.Top>(self: Schema) =>
116
211
  pipe(
117
212
  S.NullOr(self),
118
- (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => null)) })
213
+ (s) =>
214
+ Object.assign(s, {
215
+ withDefault: s.pipe(S.withConstructorDefault(Effect.succeed(null))),
216
+ withDecodingDefaultType: s.pipe(S.withDecodingDefaultType(Effect.succeed(null)))
217
+ })
119
218
  )
120
219
 
121
- export const defaultDate = (s: S.Top) => s.pipe(withDefaultConstructor(() => new global.Date()))
220
+ export const defaultDate = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
221
+ schema.pipe(S.withConstructorDefault(Effect.sync(() => new global.Date())))
122
222
 
123
- export const defaultBool = (s: S.Top) => s.pipe(withDefaultConstructor(() => false))
223
+ export const defaultBool = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
224
+ schema.pipe(S.withConstructorDefault(Effect.succeed(false)))
124
225
 
125
- export const defaultNullable = (
126
- s: S.Top
127
- ) => s.pipe(withDefaultConstructor(() => null))
226
+ export const defaultNullable = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
227
+ schema.pipe(S.withConstructorDefault(Effect.succeed(null)))
128
228
 
129
- export const defaultArray = (s: S.Top) => s.pipe(withDefaultConstructor(() => []))
229
+ export const defaultArray = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
230
+ schema.pipe(S.withConstructorDefault(Effect.sync(() => [])))
130
231
 
131
- export const defaultMap = (s: S.Top) => s.pipe(withDefaultConstructor(() => new Map()))
232
+ export const defaultMap = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
233
+ schema.pipe(S.withConstructorDefault(Effect.sync(() => new Map())))
132
234
 
133
- export const defaultSet = (s: S.Top) => s.pipe(withDefaultConstructor(() => new Set()))
235
+ export const defaultSet = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
236
+ schema.pipe(S.withConstructorDefault(Effect.sync(() => new Set())))
134
237
 
135
238
  export const withDefaultMake = <Self extends S.Top>(s: Self) => {
136
239
  const a = Object.assign(S.decodeSync(s as any) as WithDefaults<Self>, s)
@@ -160,8 +263,11 @@ export type WithDefaults<Self extends S.Top> = (
160
263
  // : never
161
264
 
162
265
  export const inputDate = extendM(
163
- S.Union([S.DateValid, S.Date]),
164
- (s) => ({ withDefault: s.pipe(withDefaultConstructor(() => new globalThis.Date())) })
266
+ S.Union([S.DateValid, Date]),
267
+ (s) => ({
268
+ withDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => new globalThis.Date()))),
269
+ withDecodingDefaultType: s.pipe(S.withDecodingDefaultType(Effect.sync(() => new globalThis.Date())))
270
+ })
165
271
  )
166
272
 
167
273
  export interface UnionBrand {}
@@ -211,7 +317,7 @@ export const transformTo = <To extends S.Top, From extends S.Top>(
211
317
  { message: "One way schema transformation, encoding is not allowed" }
212
318
  )
213
319
  )
214
- }) as any
320
+ })
215
321
  )
216
322
  )
217
323
 
@@ -228,7 +334,7 @@ export const transformToOrFail = <To extends S.Top, From extends S.Top, RD>(
228
334
  S.decodeTo(
229
335
  to,
230
336
  SchemaTransformation.transformOrFail({
231
- decode: decode as any,
337
+ decode,
232
338
  encode: (i: any) =>
233
339
  Effect.fail(
234
340
  new SchemaIssue.Forbidden(
@@ -236,33 +342,36 @@ export const transformToOrFail = <To extends S.Top, From extends S.Top, RD>(
236
342
  { message: "One way schema transformation, encoding is not allowed" }
237
343
  )
238
344
  )
239
- }) as any
345
+ })
240
346
  )
241
347
  )
242
348
 
243
- // TODO: v4 migration — S.declare API changed (no [self] + decode/encode pattern)
244
- // Need to find v4 equivalent for contextual schema wrapping
245
- export const provide = <Self extends S.Top, R>(
246
- self: Self,
247
- context: ServiceMap.ServiceMap<R>
248
- ): any => {
349
+ export const provide: {
350
+ <R>(context: Context.Context<R>): <Self extends S.Top>(self: Self) => ProvidedCodec<Self, R>
351
+ <Self extends S.Top, R>(self: Self, context: Context.Context<R>): ProvidedCodec<Self, R>
352
+ } = Function.dual(2, <Self extends S.Top, R>(self: Self, context: Context.Context<R>): ProvidedCodec<Self, R> => {
249
353
  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")
268
- }
354
+ return self.pipe(
355
+ S.middlewareDecoding((effect) => prov(effect)),
356
+ S.middlewareEncoding((effect) => prov(effect))
357
+ )
358
+ })
359
+ export const contextFromServices = Effect.fnUntraced(function*<
360
+ Self extends S.Top,
361
+ Tags extends ReadonlyArray<Context.Key<any, any>>
362
+ >(self: Self, ...services: Tags) {
363
+ const context: Context.Context<Context.Service.Identifier<Tags[number]>> = Context.pick(...services)(
364
+ yield* Effect.context<Context.Service.Identifier<Tags[number]>>()
365
+ )
366
+ return provide(self, context)
367
+ }) as <
368
+ Self extends S.Top,
369
+ Tags extends ReadonlyArray<Context.Key<any, any>>
370
+ >(
371
+ self: Self,
372
+ ...services: Tags
373
+ ) => Effect.Effect<
374
+ ProvidedCodec<Self, Context.Service.Identifier<Tags[number]>>,
375
+ never,
376
+ Context.Service.Identifier<Tags[number]>
377
+ >
@@ -1,4 +1,4 @@
1
- import { pipe } from "effect"
1
+ import { Effect, pipe } from "effect"
2
2
  import type { Refinement } from "effect-app/Function"
3
3
  import { extendM } from "effect-app/utils"
4
4
  import * as S from "effect/Schema"
@@ -6,7 +6,7 @@ import type { Simplify } from "effect/Types"
6
6
  import { customRandom, nanoid, urlAlphabet } from "nanoid"
7
7
  import validator from "validator"
8
8
  import { fromBrand, nominal } from "./brand.js"
9
- import { withDefaultConstructor, withDefaultMake, type WithDefaults } from "./ext.js"
9
+ import { withDefaultMake, type WithDefaults } from "./ext.js"
10
10
  import { type B } from "./schema.js"
11
11
  import type { NonEmptyString255Brand, NonEmptyStringBrand } from "./strings.js"
12
12
 
@@ -27,7 +27,7 @@ export type NonEmptyString50 = string & NonEmptyString50Brand
27
27
  */
28
28
  export const NonEmptyString50 = nonEmptyString.pipe(
29
29
  S.check(S.isMaxLength(50)),
30
- fromBrand(nominal<NonEmptyString50>(), {
30
+ fromBrand<NonEmptyString50>(nominal<NonEmptyString50>(), {
31
31
  identifier: "NonEmptyString50",
32
32
  title: "NonEmptyString50",
33
33
  jsonSchema: {}
@@ -50,7 +50,7 @@ export type NonEmptyString64 = string & NonEmptyString64Brand
50
50
  */
51
51
  export const NonEmptyString64 = nonEmptyString.pipe(
52
52
  S.check(S.isMaxLength(64)),
53
- fromBrand(nominal<NonEmptyString64>(), {
53
+ fromBrand<NonEmptyString64>(nominal<NonEmptyString64>(), {
54
54
  identifier: "NonEmptyString64",
55
55
  title: "NonEmptyString64",
56
56
  jsonSchema: {}
@@ -74,7 +74,7 @@ export type NonEmptyString80 = string & NonEmptyString80Brand
74
74
 
75
75
  export const NonEmptyString80 = nonEmptyString.pipe(
76
76
  S.check(S.isMaxLength(80)),
77
- fromBrand(nominal<NonEmptyString80>(), {
77
+ fromBrand<NonEmptyString80>(nominal<NonEmptyString80>(), {
78
78
  identifier: "NonEmptyString80",
79
79
  title: "NonEmptyString80",
80
80
  jsonSchema: {}
@@ -97,7 +97,7 @@ export type NonEmptyString100 = string & NonEmptyString100Brand
97
97
  */
98
98
  export const NonEmptyString100 = nonEmptyString.pipe(
99
99
  S.check(S.isMaxLength(100)),
100
- fromBrand(nominal<NonEmptyString100>(), {
100
+ fromBrand<NonEmptyString100>(nominal<NonEmptyString100>(), {
101
101
  identifier: "NonEmptyString100",
102
102
  title: "NonEmptyString100",
103
103
  jsonSchema: {}
@@ -121,7 +121,11 @@ export type Min3String255 = string & Min3String255Brand
121
121
  export const Min3String255 = pipe(
122
122
  S.String,
123
123
  S.check(S.isMinLength(3), S.isMaxLength(255)),
124
- fromBrand(nominal<Min3String255>(), { identifier: "Min3String255", title: "Min3String255", jsonSchema: {} }),
124
+ fromBrand<Min3String255>(nominal<Min3String255>(), {
125
+ identifier: "Min3String255",
126
+ title: "Min3String255",
127
+ jsonSchema: {}
128
+ }),
125
129
  withDefaultMake
126
130
  )
127
131
 
@@ -140,10 +144,10 @@ const minLength = 6
140
144
  const maxLength = 50
141
145
  const size = 21
142
146
  const length = 10 * size
143
- const StringIdArb = (): S.LazyArbitrary<string> => (fc) =>
147
+ const StringIdArb = (): S.LazyArbitrary<StringId> => (fc) =>
144
148
  fc
145
149
  .uint8Array({ minLength: length, maxLength: length })
146
- .map((_) => customRandom(urlAlphabet, size, (size) => _.subarray(0, size))())
150
+ .map((_) => customRandom(urlAlphabet, size, (size) => _.subarray(0, size))() as StringId)
147
151
  /**
148
152
  * A string that is at least 6 characters long and a maximum of 50.
149
153
  */
@@ -151,16 +155,16 @@ export const StringId = extendM(
151
155
  pipe(
152
156
  S.String,
153
157
  S.check(S.isMinLength(minLength), S.isMaxLength(maxLength)),
154
- fromBrand(nominal<StringId>(), {
158
+ fromBrand<StringId>(nominal<StringId>(), {
155
159
  identifier: "StringId",
156
160
  title: "StringId",
157
- arbitrary: StringIdArb,
161
+ toArbitrary: () => (fc) => StringIdArb()(fc),
158
162
  jsonSchema: {}
159
163
  })
160
164
  ),
161
165
  (s) => ({
162
166
  make: makeStringId,
163
- withDefault: s.pipe(withDefaultConstructor(makeStringId))
167
+ withDefault: s.pipe(S.withConstructorDefault(Effect.sync(makeStringId)))
164
168
  })
165
169
  )
166
170
  .pipe(withDefaultMake)
@@ -182,14 +186,16 @@ export function prefixedStringId<Brand extends StringId>() {
182
186
  (x) => (pref + x.substring(0, 50 - pref.length)) as Brand
183
187
  )
184
188
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
185
- const s: S.Codec<string & Brand, string> = StringId
189
+ const s = StringId
186
190
  .pipe(
187
191
  S.refine((x: string): x is string & Brand => x.startsWith(pref), {
188
- arbitrary: arb,
189
192
  identifier: name,
190
193
  title: name
194
+ }),
195
+ S.annotate({
196
+ toArbitrary: () => (fc) => arb()(fc)
191
197
  })
192
- ) as any
198
+ )
193
199
  const schema = s.pipe(withDefaultMake)
194
200
  const make = () => (pref + StringId.make().substring(0, 50 - pref.length)) as Brand
195
201
 
@@ -206,7 +212,9 @@ export function prefixedStringId<Brand extends StringId>() {
206
212
  */
207
213
  prefixSafe: <REST extends string>(str: `${Prefix}${Separator}${REST}`) => ex(str),
208
214
  prefix,
209
- withDefault: schema.pipe(withDefaultConstructor(make))
215
+ withDefault: schema.pipe(S.withConstructorDefault<S.Codec<Brand, string> & S.WithoutConstructorDefault>(
216
+ Effect.sync(make)
217
+ ))
210
218
  })
211
219
  )
212
220
  }
@@ -245,11 +253,17 @@ const isUrl: Refinement<string, Url> = (s: string): s is Url => {
245
253
  export const Url = S
246
254
  .String
247
255
  .pipe(
256
+ S.annotate({
257
+ title: "Url",
258
+ format: "uri"
259
+ }),
248
260
  S.refine(isUrl, {
249
- arbitrary: (): S.LazyArbitrary<Url> => (fc) => fc.webUrl().map((_) => _ as Url),
250
261
  identifier: "Url",
251
262
  title: "Url",
252
263
  jsonSchema: { format: "uri" }
253
264
  }),
265
+ S.annotate({
266
+ toArbitrary: () => (fc) => fc.webUrl().map((_) => _ as Url)
267
+ }),
254
268
  withDefaultMake
255
269
  )
@@ -1,8 +1,9 @@
1
+ import { Effect } from "effect"
1
2
  import { extendM } from "effect-app/utils"
2
3
  import * as S from "effect/Schema"
3
4
  import type { Simplify } from "effect/Types"
4
5
  import { fromBrand, nominal } from "./brand.js"
5
- import { withDefaultConstructor, withDefaultMake } from "./ext.js"
6
+ import { withDefaultMake } from "./ext.js"
6
7
  import { type B } from "./schema.js"
7
8
 
8
9
  export interface PositiveIntBrand
@@ -11,10 +12,10 @@ export interface PositiveIntBrand
11
12
  export const PositiveInt = extendM(
12
13
  S.Int.pipe(
13
14
  S.check(S.isGreaterThan(0)),
14
- fromBrand(nominal<PositiveInt>(), { identifier: "PositiveInt", title: "PositiveInt", jsonSchema: {} }),
15
+ fromBrand<PositiveInt>(nominal<PositiveInt>(), { identifier: "PositiveInt", title: "PositiveInt", jsonSchema: {} }),
15
16
  withDefaultMake
16
17
  ),
17
- (s) => ({ withDefault: s.pipe(withDefaultConstructor(() => s(1))) })
18
+ (s) => ({ withDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => s(1)))) })
18
19
  )
19
20
  export type PositiveInt = number & PositiveIntBrand
20
21
 
@@ -22,53 +23,53 @@ export interface NonNegativeIntBrand extends Simplify<B.Brand<"NonNegativeInt">
22
23
  export const NonNegativeInt = extendM(
23
24
  S.Int.pipe(
24
25
  S.check(S.isGreaterThanOrEqualTo(0)),
25
- fromBrand(nominal<NonNegativeInt>(), {
26
+ fromBrand<NonNegativeInt>(nominal<NonNegativeInt>(), {
26
27
  identifier: "NonNegativeInt",
27
28
  title: "NonNegativeInt",
28
29
  jsonSchema: {}
29
30
  }),
30
31
  withDefaultMake
31
32
  ),
32
- (s) => ({ withDefault: s.pipe(withDefaultConstructor(() => s(0))) })
33
+ (s) => ({ withDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => s(0)))) })
33
34
  )
34
35
  export type NonNegativeInt = number & NonNegativeIntBrand
35
36
 
36
37
  export interface IntBrand extends Simplify<B.Brand<"Int">> {}
37
38
  export const Int = extendM(
38
- S.Int.pipe(fromBrand(nominal<Int>(), { identifier: "Int", title: "Int", jsonSchema: {} }), withDefaultMake),
39
- (s) => ({ withDefault: s.pipe(withDefaultConstructor(() => s(0))) })
39
+ S.Int.pipe(fromBrand<Int>(nominal<Int>(), { identifier: "Int", title: "Int", jsonSchema: {} }), withDefaultMake),
40
+ (s) => ({ withDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => s(0)))) })
40
41
  )
41
42
  export type Int = number & IntBrand
42
43
 
43
44
  export interface PositiveNumberBrand extends Simplify<B.Brand<"PositiveNumber"> & NonNegativeNumberBrand> {}
44
45
  export const PositiveNumber = extendM(
45
- S.Number.pipe(
46
+ S.Finite.pipe(
46
47
  S.check(S.isGreaterThan(0)),
47
- fromBrand(nominal<PositiveNumber>(), {
48
+ fromBrand<PositiveNumber>(nominal<PositiveNumber>(), {
48
49
  identifier: "PositiveNumber",
49
50
  title: "PositiveNumber",
50
51
  jsonSchema: {}
51
52
  }),
52
53
  withDefaultMake
53
54
  ),
54
- (s) => ({ withDefault: s.pipe(withDefaultConstructor(() => s(1))) })
55
+ (s) => ({ withDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => s(1)))) })
55
56
  )
56
57
  export type PositiveNumber = number & PositiveNumberBrand
57
58
 
58
59
  export interface NonNegativeNumberBrand extends Simplify<B.Brand<"NonNegativeNumber">> {}
59
60
  export const NonNegativeNumber = extendM(
60
61
  S
61
- .Number
62
+ .Finite
62
63
  .pipe(
63
64
  S.check(S.isGreaterThanOrEqualTo(0)),
64
- fromBrand(nominal<NonNegativeNumber>(), {
65
+ fromBrand<NonNegativeNumber>(nominal<NonNegativeNumber>(), {
65
66
  identifier: "NonNegativeNumber",
66
67
  title: "NonNegativeNumber",
67
68
  jsonSchema: {}
68
69
  }),
69
70
  withDefaultMake
70
71
  ),
71
- (s) => ({ withDefault: s.pipe(withDefaultConstructor(() => s(0))) })
72
+ (s) => ({ withDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => s(0)))) })
72
73
  )
73
74
  export type NonNegativeNumber = number & NonNegativeNumberBrand
74
75
 
@@ -13,12 +13,19 @@ export type PhoneNumber = string & PhoneNumberBrand
13
13
  export const PhoneNumber = S
14
14
  .String
15
15
  .pipe(
16
+ S.annotate({
17
+ title: "PhoneNumber",
18
+ description: "a phone number with at least 7 digits",
19
+ format: "phone"
20
+ }),
16
21
  S.refine(isValidPhone as Refinement<string, PhoneNumber>, {
17
22
  identifier: "PhoneNumber",
18
23
  title: "PhoneNumber",
19
24
  description: "a phone number with at least 7 digits",
20
- arbitrary: () => (fc: any) => Numbers(7, 10)(fc).map((_: any) => _ as PhoneNumber),
21
25
  jsonSchema: { format: "phone" }
22
26
  }),
27
+ S.annotate({
28
+ toArbitrary: () => (fc) => Numbers(7, 10)(fc).map((_) => _ as PhoneNumber)
29
+ }),
23
30
  withDefaultMake
24
31
  )