effect-app 4.0.0-beta.7 → 4.0.0-beta.71

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 +313 -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/Context.d.ts +40 -0
  10. package/dist/Context.d.ts.map +1 -0
  11. package/dist/Context.js +66 -0
  12. package/dist/Effect.d.ts +8 -7
  13. package/dist/Effect.d.ts.map +1 -1
  14. package/dist/Effect.js +3 -2
  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 +61 -25
  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 +39 -1
  24. package/dist/Schema/Class.d.ts.map +1 -1
  25. package/dist/Schema/Class.js +89 -12
  26. package/dist/Schema/SpecialJsonSchema.d.ts +21 -0
  27. package/dist/Schema/SpecialJsonSchema.d.ts.map +1 -0
  28. package/dist/Schema/SpecialJsonSchema.js +59 -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 +8 -5
  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 +81 -45
  38. package/dist/Schema/ext.d.ts.map +1 -1
  39. package/dist/Schema/ext.js +94 -49
  40. package/dist/Schema/moreStrings.d.ts +6 -6
  41. package/dist/Schema/moreStrings.d.ts.map +1 -1
  42. package/dist/Schema/moreStrings.js +14 -9
  43. package/dist/Schema/numbers.d.ts +11 -11
  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 +4 -4
  49. package/dist/Schema/strings.d.ts.map +1 -1
  50. package/dist/Schema.d.ts +22 -55
  51. package/dist/Schema.d.ts.map +1 -1
  52. package/dist/Schema.js +43 -64
  53. package/dist/client/apiClientFactory.d.ts +3 -3
  54. package/dist/client/apiClientFactory.d.ts.map +1 -1
  55. package/dist/client/apiClientFactory.js +14 -15
  56. package/dist/client/errors.d.ts +17 -9
  57. package/dist/client/errors.d.ts.map +1 -1
  58. package/dist/client/errors.js +35 -10
  59. package/dist/client/makeClient.d.ts +13 -12
  60. package/dist/client/makeClient.d.ts.map +1 -1
  61. package/dist/client/makeClient.js +5 -2
  62. package/dist/http/Request.d.ts.map +1 -1
  63. package/dist/http/Request.js +5 -5
  64. package/dist/ids.d.ts +3 -3
  65. package/dist/ids.d.ts.map +1 -1
  66. package/dist/ids.js +3 -2
  67. package/dist/index.d.ts +3 -8
  68. package/dist/index.d.ts.map +1 -1
  69. package/dist/index.js +4 -9
  70. package/dist/middleware.d.ts +2 -2
  71. package/dist/middleware.d.ts.map +1 -1
  72. package/dist/middleware.js +3 -3
  73. package/dist/rpc/MiddlewareMaker.d.ts +4 -3
  74. package/dist/rpc/MiddlewareMaker.d.ts.map +1 -1
  75. package/dist/rpc/MiddlewareMaker.js +7 -6
  76. package/dist/rpc/RpcContextMap.d.ts +2 -2
  77. package/dist/rpc/RpcContextMap.d.ts.map +1 -1
  78. package/dist/rpc/RpcContextMap.js +4 -4
  79. package/dist/rpc/RpcMiddleware.d.ts +4 -3
  80. package/dist/rpc/RpcMiddleware.d.ts.map +1 -1
  81. package/dist/rpc/RpcMiddleware.js +1 -1
  82. package/dist/utils/gen.d.ts +1 -1
  83. package/dist/utils/gen.d.ts.map +1 -1
  84. package/dist/utils/logger.d.ts +2 -2
  85. package/dist/utils/logger.d.ts.map +1 -1
  86. package/dist/utils/logger.js +3 -3
  87. package/dist/utils.d.ts +18 -0
  88. package/dist/utils.d.ts.map +1 -1
  89. package/dist/utils.js +24 -5
  90. package/package.json +29 -17
  91. package/src/Config/SecretURL.ts +1 -1
  92. package/src/Config.ts +14 -0
  93. package/src/ConfigProvider.ts +48 -0
  94. package/src/{ServiceMap.ts → Context.ts} +57 -64
  95. package/src/Effect.ts +11 -9
  96. package/src/Layer.ts +5 -4
  97. package/src/Pure.ts +17 -18
  98. package/src/Schema/Class.ts +114 -16
  99. package/src/Schema/SpecialJsonSchema.ts +69 -0
  100. package/src/Schema/SpecialOpenApi.ts +130 -0
  101. package/src/Schema/brand.ts +13 -7
  102. package/src/Schema/email.ts +10 -2
  103. package/src/Schema/ext.ts +182 -80
  104. package/src/Schema/moreStrings.ts +20 -10
  105. package/src/Schema/numbers.ts +9 -8
  106. package/src/Schema/phoneNumber.ts +8 -1
  107. package/src/Schema.ts +79 -103
  108. package/src/client/apiClientFactory.ts +18 -18
  109. package/src/client/errors.ts +46 -12
  110. package/src/client/makeClient.ts +32 -12
  111. package/src/http/Request.ts +7 -4
  112. package/src/ids.ts +3 -2
  113. package/src/index.ts +3 -11
  114. package/src/middleware.ts +2 -2
  115. package/src/rpc/MiddlewareMaker.ts +8 -7
  116. package/src/rpc/RpcContextMap.ts +6 -5
  117. package/src/rpc/RpcMiddleware.ts +5 -4
  118. package/src/utils/gen.ts +1 -1
  119. package/src/utils/logger.ts +2 -2
  120. package/src/utils.ts +26 -4
  121. package/test/dist/moreStrings.test.d.ts.map +1 -0
  122. package/test/dist/rpc.test.d.ts.map +1 -1
  123. package/test/dist/secretURL.test.d.ts.map +1 -0
  124. package/test/dist/special.test.d.ts.map +1 -0
  125. package/test/moreStrings.test.ts +17 -0
  126. package/test/rpc.test.ts +26 -5
  127. package/test/schema.test.ts +396 -3
  128. package/test/secretURL.test.ts +157 -0
  129. package/test/special.test.ts +732 -0
  130. package/test/utils.test.ts +1 -1
  131. package/tsconfig.base.json +0 -1
  132. package/tsconfig.json +0 -1
  133. package/dist/ServiceMap.d.ts +0 -44
  134. package/dist/ServiceMap.d.ts.map +0 -1
  135. package/dist/ServiceMap.js +0 -91
  136. package/dist/Struct.d.ts +0 -44
  137. package/dist/Struct.d.ts.map +0 -1
  138. package/dist/Struct.js +0 -29
  139. package/src/Struct.ts +0 -54
package/src/Schema/ext.ts CHANGED
@@ -1,51 +1,84 @@
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, 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
+ })
53
+
54
+ /**
55
+ * Like the default Schema `DateValid` but from String with `withDefault` => now
56
+ */
57
+ export const DateValid = Object.assign(Date.check(isDateValid()), {
58
+ withDefault: DateFromString.pipe(S.withConstructorDefault(Effect.sync(() => new global.Date())))
36
59
  })
37
60
 
38
61
  /**
39
62
  * Like the default Schema `Boolean` but with `withDefault` => false
40
63
  */
41
64
  export const Boolean = Object.assign(S.Boolean, {
42
- withDefault: S.Boolean.pipe(withDefaultConstructor(() => false))
65
+ withDefault: S.Boolean.pipe(S.withConstructorDefault(Effect.succeed(false)))
43
66
  })
44
67
 
45
68
  /**
69
+ * You probably want to use `Finite` instead of this.
46
70
  * Like the default Schema `Number` but with `withDefault` => 0
47
71
  */
48
- export const Number = Object.assign(S.Number, { withDefault: S.Number.pipe(withDefaultConstructor(() => 0)) })
72
+ export const Number = Object.assign(S.Number, {
73
+ withDefault: S.Number.pipe(S.withConstructorDefault(Effect.succeed(0)))
74
+ })
75
+
76
+ /**
77
+ * Like the default Schema `Finite` but with `withDefault` => 0
78
+ */
79
+ export const Finite = Object.assign(S.Finite, {
80
+ withDefault: S.Finite.pipe(S.withConstructorDefault(Effect.succeed(0)))
81
+ })
49
82
 
50
83
  /**
51
84
  * Like the default Schema `Literal` but with `withDefault` => literals[0]
@@ -58,79 +91,143 @@ export const Literal = <Literals extends NonEmptyReadonlyArray<AST.LiteralValue>
58
91
  changeDefault: <A extends Literals[number]>(a: A) => {
59
92
  return Object.assign(S.Literals(literals), {
60
93
  Default: a,
61
- withDefault: s.pipe(withDefaultConstructor(() => a))
94
+ withDefault: s.pipe(S.withConstructorDefault(Effect.succeed(a)))
62
95
  }) // todo: copy annotations from original?
63
96
  },
64
97
  Default: literals[0] as typeof literals[0],
65
- withDefault: s.pipe(withDefaultConstructor(() => literals[0]))
98
+ withDefault: s.pipe(S.withConstructorDefault(Effect.succeed(literals[0])))
66
99
  })
67
100
  )
68
101
 
69
102
  /**
70
103
  * Like the default Schema `Array` but with `withDefault` => []
71
104
  */
72
- export function Array<Value extends S.Top>(value: Value) {
105
+ export function Array<ValueSchema extends S.Top>(value: ValueSchema) {
73
106
  return pipe(
74
107
  S.Array(value),
75
- (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => [])) })
108
+ (s) => Object.assign(s, { withDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => []))) })
76
109
  )
77
110
  }
78
111
 
79
112
  /**
80
- * Like the default Schema `Map` but with `withDefault` => []
113
+ * An annotated `S.Array` of unique items that decodes to a `ReadonlySet`.
81
114
  */
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())) })
115
+ export const ReadonlySetFromArray = <ValueSchema extends S.Top>(value: ValueSchema): S.Codec<
116
+ ReadonlySet<ValueSchema["Type"]>,
117
+ readonly ValueSchema["Encoded"][],
118
+ ValueSchema["DecodingServices"],
119
+ ValueSchema["EncodingServices"]
120
+ > => {
121
+ const from = S
122
+ .Array(value)
123
+ .annotate({ expected: "an array of unique items that will be decoded as a ReadonlySet" })
124
+ const to = S.instanceOf(Set) as S.instanceOf<ReadonlySet<S.Schema.Type<ValueSchema>>>
125
+ const schema = from.pipe(
126
+ S.decodeTo(
127
+ to,
128
+ SchemaTransformation.transform({
129
+ decode: (arr) => new Set(arr) as ReadonlySet<S.Schema.Type<ValueSchema>>,
130
+ encode: (set) => [...set]
131
+ })
132
+ )
86
133
  )
134
+ return S.revealCodec(schema)
87
135
  }
88
136
 
89
- export { Map_ as Map }
137
+ /**
138
+ * An annotated `S.Array` of key-value tuples that decodes to a `ReadonlyMap`.
139
+ */
140
+ export const ReadonlyMapFromArray = <KeySchema extends S.Top, ValueSchema extends S.Top>(pair: {
141
+ readonly key: KeySchema
142
+ readonly value: ValueSchema
143
+ }): S.Codec<
144
+ ReadonlyMap<KeySchema["Type"], S.Schema.Type<ValueSchema>>,
145
+ readonly (readonly [KeySchema["Encoded"], ValueSchema["Encoded"]])[],
146
+ KeySchema["DecodingServices"] | ValueSchema["DecodingServices"],
147
+ KeySchema["EncodingServices"] | ValueSchema["EncodingServices"]
148
+ > => {
149
+ const from = S
150
+ .Array(S.Tuple([pair.key, pair.value]))
151
+ .annotate({ expected: "an array of key-value tuples that will be decoded as a ReadonlyMap" })
152
+ const to = S.instanceOf(Map) as S.instanceOf<
153
+ ReadonlyMap<S.Schema.Type<KeySchema>, S.Schema.Type<ValueSchema>>
154
+ >
155
+ const schema = from.pipe(
156
+ S.decodeTo(
157
+ to,
158
+ SchemaTransformation.transform({
159
+ decode: (
160
+ arr
161
+ ) => new Map(arr) as ReadonlyMap<S.Schema.Type<KeySchema>, S.Schema.Type<ValueSchema>>,
162
+ encode: (
163
+ map
164
+ ) => [...map.entries()] as any // fu
165
+ })
166
+ )
167
+ )
168
+ return S.revealCodec(schema)
169
+ }
90
170
 
91
171
  /**
92
- * Like the default Schema `ReadonlySet` but with `withDefault` => new Set()
172
+ * Like the default Schema `ReadonlySet` but from Array with `withDefault` => new Set()
93
173
  */
94
- export const ReadonlySet = <Value extends S.Top>(value: Value) =>
174
+ export const ReadonlySet = <ValueSchema extends S.Top>(value: ValueSchema) =>
95
175
  pipe(
96
- S.ReadonlySet(value),
97
- (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => new Set<S.Schema.Type<Value>>())) })
176
+ ReadonlySetFromArray(value),
177
+ (s) =>
178
+ Object.assign(s, {
179
+ withDefault: S.withConstructorDefault(Effect.sync(() => new Set<S.Schema.Type<ValueSchema>>()))(
180
+ s as typeof s & S.WithoutConstructorDefault
181
+ )
182
+ })
98
183
  )
99
184
 
100
185
  /**
101
- * Like the default Schema `ReadonlyMap` but with `withDefault` => new Map()
186
+ * Like the default Schema `ReadonlyMap` but from Array with `withDefault` => new Map()
102
187
  */
103
- export const ReadonlyMap = <K extends S.Top, V extends S.Top>(pair: {
104
- readonly key: K
105
- readonly value: V
188
+ export const ReadonlyMap = <KeySchema extends S.Top, ValueSchema extends S.Top>(pair: {
189
+ readonly key: KeySchema
190
+ readonly value: ValueSchema
106
191
  }) =>
107
192
  pipe(
108
- S.ReadonlyMap(pair.key, pair.value),
109
- (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => new Map())) })
193
+ ReadonlyMapFromArray(pair),
194
+ (s) =>
195
+ Object.assign(s, {
196
+ withDefault: S.withConstructorDefault(Effect.sync(() => new Map()))(
197
+ s as typeof s & S.WithoutConstructorDefault
198
+ )
199
+ })
110
200
  )
111
201
 
112
202
  /**
113
203
  * Like the default Schema `NullOr` but with `withDefault` => null
114
204
  */
115
- export const NullOr = <S extends S.Top>(self: S) =>
205
+ export const NullOr = <Schema extends S.Top>(self: Schema) =>
116
206
  pipe(
117
207
  S.NullOr(self),
118
- (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => null)) })
208
+ (s) =>
209
+ Object.assign(s, {
210
+ withDefault: s.pipe(S.withConstructorDefault(Effect.succeed(null)))
211
+ })
119
212
  )
120
213
 
121
- export const defaultDate = (s: S.Top) => s.pipe(withDefaultConstructor(() => new global.Date()))
214
+ export const defaultDate = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
215
+ schema.pipe(S.withConstructorDefault(Effect.sync(() => new global.Date())))
122
216
 
123
- export const defaultBool = (s: S.Top) => s.pipe(withDefaultConstructor(() => false))
217
+ export const defaultBool = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
218
+ schema.pipe(S.withConstructorDefault(Effect.succeed(false)))
124
219
 
125
- export const defaultNullable = (
126
- s: S.Top
127
- ) => s.pipe(withDefaultConstructor(() => null))
220
+ export const defaultNullable = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
221
+ schema.pipe(S.withConstructorDefault(Effect.succeed(null)))
128
222
 
129
- export const defaultArray = (s: S.Top) => s.pipe(withDefaultConstructor(() => []))
223
+ export const defaultArray = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
224
+ schema.pipe(S.withConstructorDefault(Effect.sync(() => [])))
130
225
 
131
- export const defaultMap = (s: S.Top) => s.pipe(withDefaultConstructor(() => new Map()))
226
+ export const defaultMap = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
227
+ schema.pipe(S.withConstructorDefault(Effect.sync(() => new Map())))
132
228
 
133
- export const defaultSet = (s: S.Top) => s.pipe(withDefaultConstructor(() => new Set()))
229
+ export const defaultSet = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
230
+ schema.pipe(S.withConstructorDefault(Effect.sync(() => new Set())))
134
231
 
135
232
  export const withDefaultMake = <Self extends S.Top>(s: Self) => {
136
233
  const a = Object.assign(S.decodeSync(s as any) as WithDefaults<Self>, s)
@@ -160,8 +257,12 @@ export type WithDefaults<Self extends S.Top> = (
160
257
  // : never
161
258
 
162
259
  export const inputDate = extendM(
163
- S.Union([S.DateValid, S.Date]),
164
- (s) => ({ withDefault: s.pipe(withDefaultConstructor(() => new globalThis.Date())) })
260
+ S.Union([S.DateValid, Date]).pipe(S.revealCodec),
261
+ (s) => ({
262
+ withDefault: S.withConstructorDefault(Effect.sync(() => new globalThis.Date()))(
263
+ s as typeof s & S.WithoutConstructorDefault
264
+ )
265
+ })
165
266
  )
166
267
 
167
268
  export interface UnionBrand {}
@@ -211,7 +312,7 @@ export const transformTo = <To extends S.Top, From extends S.Top>(
211
312
  { message: "One way schema transformation, encoding is not allowed" }
212
313
  )
213
314
  )
214
- }) as any
315
+ })
215
316
  )
216
317
  )
217
318
 
@@ -228,7 +329,7 @@ export const transformToOrFail = <To extends S.Top, From extends S.Top, RD>(
228
329
  S.decodeTo(
229
330
  to,
230
331
  SchemaTransformation.transformOrFail({
231
- decode: decode as any,
332
+ decode,
232
333
  encode: (i: any) =>
233
334
  Effect.fail(
234
335
  new SchemaIssue.Forbidden(
@@ -236,33 +337,34 @@ export const transformToOrFail = <To extends S.Top, From extends S.Top, RD>(
236
337
  { message: "One way schema transformation, encoding is not allowed" }
237
338
  )
238
339
  )
239
- }) as any
340
+ })
240
341
  )
241
342
  )
242
343
 
243
- // TODO: v4 migration — S.declare API changed (no [self] + decode/encode pattern)
244
- // Need to find v4 equivalent for contextual schema wrapping
245
344
  export const provide = <Self extends S.Top, R>(
246
345
  self: Self,
247
- context: ServiceMap.ServiceMap<R>
248
- ): any => {
346
+ context: Context.Context<R>
347
+ ): ProvidedCodec<Self, R> => {
249
348
  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")
349
+ return self.pipe(
350
+ S.middlewareDecoding((effect) => prov(effect)),
351
+ S.middlewareEncoding((effect) => prov(effect))
352
+ ) as ProvidedCodec<Self, R>
268
353
  }
354
+ export const contextFromServices = <
355
+ Self extends S.Top,
356
+ Tags extends ReadonlyArray<Context.Key<any, any>>
357
+ >(
358
+ self: Self,
359
+ ...services: Tags
360
+ ): Effect.Effect<
361
+ ProvidedCodec<Self, Context.Service.Identifier<Tags[number]>>,
362
+ never,
363
+ Context.Service.Identifier<Tags[number]>
364
+ > =>
365
+ Effect.gen(function*() {
366
+ const context: Context.Context<Context.Service.Identifier<Tags[number]>> = Context.pick(...services)(
367
+ yield* Effect.context<Context.Service.Identifier<Tags[number]>>()
368
+ )
369
+ return provide(self, context)
370
+ })
@@ -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
 
@@ -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,13 +154,13 @@ 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
  ),
161
161
  (s) => ({
162
162
  make: makeStringId,
163
- withDefault: s.pipe(withDefaultConstructor(makeStringId))
163
+ withDefault: s.pipe(S.withConstructorDefault(Effect.sync(makeStringId)))
164
164
  })
165
165
  )
166
166
  .pipe(withDefaultMake)
@@ -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
 
@@ -206,7 +208,9 @@ export function prefixedStringId<Brand extends StringId>() {
206
208
  */
207
209
  prefixSafe: <REST extends string>(str: `${Prefix}${Separator}${REST}`) => ex(str),
208
210
  prefix,
209
- withDefault: schema.pipe(withDefaultConstructor(make))
211
+ withDefault: S.withConstructorDefault<S.Codec<Brand, string> & S.WithoutConstructorDefault>(
212
+ Effect.sync(make)
213
+ )(schema as S.Codec<Brand, string> & S.WithoutConstructorDefault)
210
214
  })
211
215
  )
212
216
  }
@@ -245,11 +249,17 @@ const isUrl: Refinement<string, Url> = (s: string): s is Url => {
245
249
  export const Url = S
246
250
  .String
247
251
  .pipe(
252
+ S.annotate({
253
+ title: "Url",
254
+ format: "uri"
255
+ }),
248
256
  S.refine(isUrl, {
249
- arbitrary: (): S.LazyArbitrary<Url> => (fc) => fc.webUrl().map((_) => _ as Url),
250
257
  identifier: "Url",
251
258
  title: "Url",
252
259
  jsonSchema: { format: "uri" }
253
260
  }),
261
+ S.annotate({
262
+ toArbitrary: () => (fc) => fc.webUrl().map((_) => _ as Url)
263
+ }),
254
264
  withDefaultMake
255
265
  )
@@ -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
@@ -14,7 +15,7 @@ export const PositiveInt = extendM(
14
15
  fromBrand(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
 
@@ -29,20 +30,20 @@ export const NonNegativeInt = extendM(
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
39
  S.Int.pipe(fromBrand(nominal<Int>(), { identifier: "Int", title: "Int", jsonSchema: {} }), withDefaultMake),
39
- (s) => ({ withDefault: s.pipe(withDefaultConstructor(() => s(0))) })
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
48
  fromBrand(nominal<PositiveNumber>(), {
48
49
  identifier: "PositiveNumber",
@@ -51,14 +52,14 @@ export const PositiveNumber = extendM(
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
65
  fromBrand(nominal<NonNegativeNumber>(), {
@@ -68,7 +69,7 @@ export const NonNegativeNumber = extendM(
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
  )