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

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 (138) hide show
  1. package/CHANGELOG.md +307 -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 +54 -18
  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 -44
  38. package/dist/Schema/ext.d.ts.map +1 -1
  39. package/dist/Schema/ext.js +76 -36
  40. package/dist/Schema/moreStrings.d.ts +4 -4
  41. package/dist/Schema/moreStrings.d.ts.map +1 -1
  42. package/dist/Schema/moreStrings.js +10 -5
  43. package/dist/Schema/numbers.d.ts +8 -8
  44. package/dist/Schema/numbers.js +2 -2
  45. package/dist/Schema/phoneNumber.d.ts.map +1 -1
  46. package/dist/Schema/phoneNumber.js +8 -3
  47. package/dist/Schema/strings.d.ts +4 -4
  48. package/dist/Schema/strings.d.ts.map +1 -1
  49. package/dist/Schema.d.ts +22 -55
  50. package/dist/Schema.d.ts.map +1 -1
  51. package/dist/Schema.js +43 -64
  52. package/dist/client/apiClientFactory.d.ts +3 -3
  53. package/dist/client/apiClientFactory.d.ts.map +1 -1
  54. package/dist/client/apiClientFactory.js +14 -15
  55. package/dist/client/errors.d.ts +16 -8
  56. package/dist/client/errors.d.ts.map +1 -1
  57. package/dist/client/errors.js +35 -10
  58. package/dist/client/makeClient.d.ts +13 -12
  59. package/dist/client/makeClient.d.ts.map +1 -1
  60. package/dist/client/makeClient.js +5 -2
  61. package/dist/http/Request.d.ts.map +1 -1
  62. package/dist/http/Request.js +5 -5
  63. package/dist/ids.d.ts +1 -1
  64. package/dist/ids.d.ts.map +1 -1
  65. package/dist/ids.js +1 -1
  66. package/dist/index.d.ts +3 -8
  67. package/dist/index.d.ts.map +1 -1
  68. package/dist/index.js +4 -9
  69. package/dist/middleware.d.ts +2 -2
  70. package/dist/middleware.d.ts.map +1 -1
  71. package/dist/middleware.js +3 -3
  72. package/dist/rpc/MiddlewareMaker.d.ts +4 -3
  73. package/dist/rpc/MiddlewareMaker.d.ts.map +1 -1
  74. package/dist/rpc/MiddlewareMaker.js +7 -6
  75. package/dist/rpc/RpcContextMap.d.ts +2 -2
  76. package/dist/rpc/RpcContextMap.d.ts.map +1 -1
  77. package/dist/rpc/RpcContextMap.js +4 -4
  78. package/dist/rpc/RpcMiddleware.d.ts +4 -3
  79. package/dist/rpc/RpcMiddleware.d.ts.map +1 -1
  80. package/dist/rpc/RpcMiddleware.js +1 -1
  81. package/dist/utils/gen.d.ts +1 -1
  82. package/dist/utils/gen.d.ts.map +1 -1
  83. package/dist/utils/logger.d.ts +2 -2
  84. package/dist/utils/logger.d.ts.map +1 -1
  85. package/dist/utils/logger.js +3 -3
  86. package/dist/utils.d.ts +18 -0
  87. package/dist/utils.d.ts.map +1 -1
  88. package/dist/utils.js +24 -5
  89. package/package.json +29 -17
  90. package/src/Config/SecretURL.ts +1 -1
  91. package/src/Config.ts +14 -0
  92. package/src/ConfigProvider.ts +48 -0
  93. package/src/{ServiceMap.ts → Context.ts} +57 -64
  94. package/src/Effect.ts +11 -9
  95. package/src/Layer.ts +5 -4
  96. package/src/Pure.ts +17 -18
  97. package/src/Schema/Class.ts +114 -16
  98. package/src/Schema/SpecialJsonSchema.ts +69 -0
  99. package/src/Schema/SpecialOpenApi.ts +130 -0
  100. package/src/Schema/brand.ts +13 -7
  101. package/src/Schema/email.ts +10 -2
  102. package/src/Schema/ext.ts +150 -59
  103. package/src/Schema/moreStrings.ts +14 -6
  104. package/src/Schema/numbers.ts +2 -2
  105. package/src/Schema/phoneNumber.ts +8 -1
  106. package/src/Schema.ts +79 -103
  107. package/src/client/apiClientFactory.ts +18 -18
  108. package/src/client/errors.ts +46 -12
  109. package/src/client/makeClient.ts +32 -12
  110. package/src/http/Request.ts +7 -4
  111. package/src/ids.ts +1 -1
  112. package/src/index.ts +3 -11
  113. package/src/middleware.ts +2 -2
  114. package/src/rpc/MiddlewareMaker.ts +8 -7
  115. package/src/rpc/RpcContextMap.ts +6 -5
  116. package/src/rpc/RpcMiddleware.ts +5 -4
  117. package/src/utils/gen.ts +1 -1
  118. package/src/utils/logger.ts +2 -2
  119. package/src/utils.ts +26 -4
  120. package/test/dist/moreStrings.test.d.ts.map +1 -0
  121. package/test/dist/rpc.test.d.ts.map +1 -1
  122. package/test/dist/secretURL.test.d.ts.map +1 -0
  123. package/test/dist/special.test.d.ts.map +1 -0
  124. package/test/moreStrings.test.ts +17 -0
  125. package/test/rpc.test.ts +26 -5
  126. package/test/schema.test.ts +396 -3
  127. package/test/secretURL.test.ts +157 -0
  128. package/test/special.test.ts +732 -0
  129. package/test/utils.test.ts +1 -1
  130. package/tsconfig.base.json +0 -1
  131. package/tsconfig.json +0 -1
  132. package/dist/ServiceMap.d.ts +0 -44
  133. package/dist/ServiceMap.d.ts.map +0 -1
  134. package/dist/ServiceMap.js +0 -91
  135. package/dist/Struct.d.ts +0 -44
  136. package/dist/Struct.d.ts.map +0 -1
  137. package/dist/Struct.js +0 -29
  138. package/src/Struct.ts +0 -54
package/src/Schema/ext.ts CHANGED
@@ -1,11 +1,20 @@
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
 
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
+ >
17
+
9
18
  // TODO: v4 migration — withConstructorDefault signature changed, propertySignature removed
10
19
  // Constraint relaxed from `Self extends S.Top & S.WithoutConstructorDefault` to `Self extends S.Top`
11
20
  // because `.pipe()` widens the schema type to `Top` which doesn't satisfy `WithoutConstructorDefault`.
@@ -16,17 +25,38 @@ export const withDefaultConstructor = <A>(
16
25
  <Self extends S.Top>(self: Self): S.withConstructorDefault<Self & S.WithoutConstructorDefault> => {
17
26
  type Narrowed = Self & S.WithoutConstructorDefault
18
27
  return S.withConstructorDefault<Narrowed>(
19
- () => Option.some(makeDefault() as Narrowed["~type.make.in"])
28
+ Effect.sync(() => makeDefault() as Narrowed["~type.make.in"])
20
29
  )(self as Narrowed)
21
30
  }
22
31
 
23
32
  // 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
- )
33
+
34
+ const DateString = S.String.annotate({
35
+ identifier: "Date",
36
+ description: "a string in ISO 8601 format that will be decoded as a Date",
37
+ format: "date-time"
38
+ })
39
+
40
+ /**
41
+ * Schema type for {@link DateFromString}.
42
+ *
43
+ * @category Schemas
44
+ * @since 4.0.0
45
+ */
46
+ export interface DateFromString extends S.decodeTo<S.Date, S.String> {}
47
+
48
+ /**
49
+ * A transformation schema that parses an ISO 8601 string into a `Date`.
50
+ *
51
+ * Decoding:
52
+ * - A `string` is decoded as a `Date`.
53
+ *
54
+ * Encoding:
55
+ * - A `Date` is encoded as a `string`.
56
+ *
57
+ * @since 4.0.0
58
+ */
59
+ export const DateFromString: DateFromString = DateString.pipe(S.decodeTo(S.Date, SchemaTransformation.dateFromString))
30
60
 
31
61
  /**
32
62
  * Like the default Schema `Date` but from String with `withDefault` => now
@@ -35,6 +65,13 @@ export const Date = Object.assign(DateFromString, {
35
65
  withDefault: DateFromString.pipe(withDefaultConstructor(() => new global.Date()))
36
66
  })
37
67
 
68
+ /**
69
+ * Like the default Schema `DateValid` but from String with `withDefault` => now
70
+ */
71
+ export const DateValid = Object.assign(Date.check(isDateValid()), {
72
+ withDefault: DateFromString.pipe(withDefaultConstructor(() => new global.Date()))
73
+ })
74
+
38
75
  /**
39
76
  * Like the default Schema `Boolean` but with `withDefault` => false
40
77
  */
@@ -43,10 +80,16 @@ export const Boolean = Object.assign(S.Boolean, {
43
80
  })
44
81
 
45
82
  /**
83
+ * You probably want to use `Finite` instead of this.
46
84
  * Like the default Schema `Number` but with `withDefault` => 0
47
85
  */
48
86
  export const Number = Object.assign(S.Number, { withDefault: S.Number.pipe(withDefaultConstructor(() => 0)) })
49
87
 
88
+ /**
89
+ * Like the default Schema `Finite` but with `withDefault` => 0
90
+ */
91
+ export const Finite = Object.assign(S.Finite, { withDefault: S.Finite.pipe(withDefaultConstructor(() => 0)) })
92
+
50
93
  /**
51
94
  * Like the default Schema `Literal` but with `withDefault` => literals[0]
52
95
  */
@@ -69,7 +112,7 @@ export const Literal = <Literals extends NonEmptyReadonlyArray<AST.LiteralValue>
69
112
  /**
70
113
  * Like the default Schema `Array` but with `withDefault` => []
71
114
  */
72
- export function Array<Value extends S.Top>(value: Value) {
115
+ export function Array<ValueSchema extends S.Top>(value: ValueSchema) {
73
116
  return pipe(
74
117
  S.Array(value),
75
118
  (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => [])) })
@@ -77,60 +120,107 @@ export function Array<Value extends S.Top>(value: Value) {
77
120
  }
78
121
 
79
122
  /**
80
- * Like the default Schema `Map` but with `withDefault` => []
123
+ * An annotated `S.Array` of unique items that decodes to a `ReadonlySet`.
81
124
  */
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())) })
125
+ export const ReadonlySetFromArray = <ValueSchema extends S.Top>(value: ValueSchema): S.Codec<
126
+ ReadonlySet<ValueSchema["Type"]>,
127
+ readonly ValueSchema["Encoded"][],
128
+ ValueSchema["DecodingServices"],
129
+ ValueSchema["EncodingServices"]
130
+ > => {
131
+ const from = S
132
+ .Array(value)
133
+ .annotate({ 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 S.revealCodec(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
+ }): S.Codec<
154
+ ReadonlyMap<KeySchema["Type"], S.Schema.Type<ValueSchema>>,
155
+ readonly (readonly [KeySchema["Encoded"], ValueSchema["Encoded"]])[],
156
+ KeySchema["DecodingServices"] | ValueSchema["DecodingServices"],
157
+ KeySchema["EncodingServices"] | ValueSchema["EncodingServices"]
158
+ > => {
159
+ const from = S
160
+ .Array(S.Tuple([pair.key, pair.value]))
161
+ .annotate({ expected: "an array of key-value tuples that will be decoded as a ReadonlyMap" })
162
+ const to = S.instanceOf(Map) as S.instanceOf<
163
+ ReadonlyMap<S.Schema.Type<KeySchema>, S.Schema.Type<ValueSchema>>
164
+ >
165
+ const schema = from.pipe(
166
+ S.decodeTo(
167
+ to,
168
+ SchemaTransformation.transform({
169
+ decode: (
170
+ arr
171
+ ) => new Map(arr) as ReadonlyMap<S.Schema.Type<KeySchema>, S.Schema.Type<ValueSchema>>,
172
+ encode: (
173
+ map
174
+ ) => [...map.entries()] as any // fu
175
+ })
176
+ )
177
+ )
178
+ return S.revealCodec(schema)
179
+ }
90
180
 
91
181
  /**
92
- * Like the default Schema `ReadonlySet` but with `withDefault` => new Set()
182
+ * Like the default Schema `ReadonlySet` but from Array with `withDefault` => new Set()
93
183
  */
94
- export const ReadonlySet = <Value extends S.Top>(value: Value) =>
184
+ export const ReadonlySet = <ValueSchema extends S.Top>(value: ValueSchema) =>
95
185
  pipe(
96
- S.ReadonlySet(value),
97
- (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => new Set<S.Schema.Type<Value>>())) })
186
+ ReadonlySetFromArray(value),
187
+ (s) =>
188
+ Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => new Set<S.Schema.Type<ValueSchema>>())) })
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),
199
+ ReadonlyMapFromArray(pair),
109
200
  (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => new Map())) })
110
201
  )
111
202
 
112
203
  /**
113
204
  * Like the default Schema `NullOr` but with `withDefault` => null
114
205
  */
115
- export const NullOr = <S extends S.Top>(self: S) =>
206
+ export const NullOr = <Schema extends S.Top>(self: Schema) =>
116
207
  pipe(
117
208
  S.NullOr(self),
118
209
  (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => null)) })
119
210
  )
120
211
 
121
- export const defaultDate = (s: S.Top) => s.pipe(withDefaultConstructor(() => new global.Date()))
212
+ export const defaultDate = <Schema extends S.Top>(schema: Schema) =>
213
+ schema.pipe(withDefaultConstructor(() => new global.Date()))
122
214
 
123
- export const defaultBool = (s: S.Top) => s.pipe(withDefaultConstructor(() => false))
215
+ export const defaultBool = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => false))
124
216
 
125
- export const defaultNullable = (
126
- s: S.Top
127
- ) => s.pipe(withDefaultConstructor(() => null))
217
+ export const defaultNullable = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => null))
128
218
 
129
- export const defaultArray = (s: S.Top) => s.pipe(withDefaultConstructor(() => []))
219
+ export const defaultArray = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => []))
130
220
 
131
- export const defaultMap = (s: S.Top) => s.pipe(withDefaultConstructor(() => new Map()))
221
+ export const defaultMap = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => new Map()))
132
222
 
133
- export const defaultSet = (s: S.Top) => s.pipe(withDefaultConstructor(() => new Set()))
223
+ export const defaultSet = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => new Set()))
134
224
 
135
225
  export const withDefaultMake = <Self extends S.Top>(s: Self) => {
136
226
  const a = Object.assign(S.decodeSync(s as any) as WithDefaults<Self>, s)
@@ -160,7 +250,7 @@ export type WithDefaults<Self extends S.Top> = (
160
250
  // : never
161
251
 
162
252
  export const inputDate = extendM(
163
- S.Union([S.DateValid, S.Date]),
253
+ S.Union([S.DateValid, Date]).pipe(S.revealCodec),
164
254
  (s) => ({ withDefault: s.pipe(withDefaultConstructor(() => new globalThis.Date())) })
165
255
  )
166
256
 
@@ -211,7 +301,7 @@ export const transformTo = <To extends S.Top, From extends S.Top>(
211
301
  { message: "One way schema transformation, encoding is not allowed" }
212
302
  )
213
303
  )
214
- }) as any
304
+ })
215
305
  )
216
306
  )
217
307
 
@@ -228,7 +318,7 @@ export const transformToOrFail = <To extends S.Top, From extends S.Top, RD>(
228
318
  S.decodeTo(
229
319
  to,
230
320
  SchemaTransformation.transformOrFail({
231
- decode: decode as any,
321
+ decode,
232
322
  encode: (i: any) =>
233
323
  Effect.fail(
234
324
  new SchemaIssue.Forbidden(
@@ -236,33 +326,34 @@ export const transformToOrFail = <To extends S.Top, From extends S.Top, RD>(
236
326
  { message: "One way schema transformation, encoding is not allowed" }
237
327
  )
238
328
  )
239
- }) as any
329
+ })
240
330
  )
241
331
  )
242
332
 
243
- // TODO: v4 migration — S.declare API changed (no [self] + decode/encode pattern)
244
- // Need to find v4 equivalent for contextual schema wrapping
245
333
  export const provide = <Self extends S.Top, R>(
246
334
  self: Self,
247
- context: ServiceMap.ServiceMap<R>
248
- ): any => {
335
+ context: Context.Context<R>
336
+ ): ProvidedCodec<Self, R> => {
249
337
  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")
338
+ return self.pipe(
339
+ S.middlewareDecoding((effect) => prov(effect)),
340
+ S.middlewareEncoding((effect) => prov(effect))
341
+ ) as ProvidedCodec<Self, R>
268
342
  }
343
+ export const contextFromServices = <
344
+ Self extends S.Top,
345
+ Tags extends ReadonlyArray<Context.Key<any, any>>
346
+ >(
347
+ self: Self,
348
+ ...services: Tags
349
+ ): Effect.Effect<
350
+ ProvidedCodec<Self, Context.Service.Identifier<Tags[number]>>,
351
+ never,
352
+ Context.Service.Identifier<Tags[number]>
353
+ > =>
354
+ Effect.gen(function*() {
355
+ const context: Context.Context<Context.Service.Identifier<Tags[number]>> = Context.pick(...services)(
356
+ yield* Effect.context<Context.Service.Identifier<Tags[number]>>()
357
+ )
358
+ return provide(self, context)
359
+ })
@@ -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
 
@@ -245,11 +247,17 @@ const isUrl: Refinement<string, Url> = (s: string): s is Url => {
245
247
  export const Url = S
246
248
  .String
247
249
  .pipe(
250
+ S.annotate({
251
+ title: "Url",
252
+ format: "uri"
253
+ }),
248
254
  S.refine(isUrl, {
249
- arbitrary: (): S.LazyArbitrary<Url> => (fc) => fc.webUrl().map((_) => _ as Url),
250
255
  identifier: "Url",
251
256
  title: "Url",
252
257
  jsonSchema: { format: "uri" }
253
258
  }),
259
+ S.annotate({
260
+ toArbitrary: () => (fc) => fc.webUrl().map((_) => _ as Url)
261
+ }),
254
262
  withDefaultMake
255
263
  )
@@ -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>(), {
@@ -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
  )
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, DateFromString, DateValid, Finite, Literal, 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))