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.
- package/CHANGELOG.md +307 -0
- package/dist/Config/SecretURL.js +2 -2
- package/dist/Config.d.ts +7 -0
- package/dist/Config.d.ts.map +1 -0
- package/dist/Config.js +6 -0
- package/dist/ConfigProvider.d.ts +39 -0
- package/dist/ConfigProvider.d.ts.map +1 -0
- package/dist/ConfigProvider.js +42 -0
- package/dist/Context.d.ts +40 -0
- package/dist/Context.d.ts.map +1 -0
- package/dist/Context.js +66 -0
- package/dist/Effect.d.ts +8 -7
- package/dist/Effect.d.ts.map +1 -1
- package/dist/Effect.js +3 -2
- package/dist/Layer.d.ts +5 -4
- package/dist/Layer.d.ts.map +1 -1
- package/dist/Layer.js +1 -1
- package/dist/Operations.d.ts +54 -18
- package/dist/Operations.d.ts.map +1 -1
- package/dist/Pure.d.ts +2 -2
- package/dist/Pure.d.ts.map +1 -1
- package/dist/Pure.js +13 -13
- package/dist/Schema/Class.d.ts +39 -1
- package/dist/Schema/Class.d.ts.map +1 -1
- package/dist/Schema/Class.js +89 -12
- package/dist/Schema/SpecialJsonSchema.d.ts +21 -0
- package/dist/Schema/SpecialJsonSchema.d.ts.map +1 -0
- package/dist/Schema/SpecialJsonSchema.js +59 -0
- package/dist/Schema/SpecialOpenApi.d.ts +32 -0
- package/dist/Schema/SpecialOpenApi.d.ts.map +1 -0
- package/dist/Schema/SpecialOpenApi.js +123 -0
- package/dist/Schema/brand.d.ts +8 -5
- package/dist/Schema/brand.d.ts.map +1 -1
- package/dist/Schema/brand.js +1 -1
- package/dist/Schema/email.d.ts.map +1 -1
- package/dist/Schema/email.js +9 -4
- package/dist/Schema/ext.d.ts +81 -44
- package/dist/Schema/ext.d.ts.map +1 -1
- package/dist/Schema/ext.js +76 -36
- package/dist/Schema/moreStrings.d.ts +4 -4
- package/dist/Schema/moreStrings.d.ts.map +1 -1
- package/dist/Schema/moreStrings.js +10 -5
- package/dist/Schema/numbers.d.ts +8 -8
- package/dist/Schema/numbers.js +2 -2
- package/dist/Schema/phoneNumber.d.ts.map +1 -1
- package/dist/Schema/phoneNumber.js +8 -3
- package/dist/Schema/strings.d.ts +4 -4
- package/dist/Schema/strings.d.ts.map +1 -1
- package/dist/Schema.d.ts +22 -55
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +43 -64
- package/dist/client/apiClientFactory.d.ts +3 -3
- package/dist/client/apiClientFactory.d.ts.map +1 -1
- package/dist/client/apiClientFactory.js +14 -15
- package/dist/client/errors.d.ts +16 -8
- package/dist/client/errors.d.ts.map +1 -1
- package/dist/client/errors.js +35 -10
- package/dist/client/makeClient.d.ts +13 -12
- package/dist/client/makeClient.d.ts.map +1 -1
- package/dist/client/makeClient.js +5 -2
- package/dist/http/Request.d.ts.map +1 -1
- package/dist/http/Request.js +5 -5
- package/dist/ids.d.ts +1 -1
- package/dist/ids.d.ts.map +1 -1
- package/dist/ids.js +1 -1
- package/dist/index.d.ts +3 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -9
- package/dist/middleware.d.ts +2 -2
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +3 -3
- package/dist/rpc/MiddlewareMaker.d.ts +4 -3
- package/dist/rpc/MiddlewareMaker.d.ts.map +1 -1
- package/dist/rpc/MiddlewareMaker.js +7 -6
- package/dist/rpc/RpcContextMap.d.ts +2 -2
- package/dist/rpc/RpcContextMap.d.ts.map +1 -1
- package/dist/rpc/RpcContextMap.js +4 -4
- package/dist/rpc/RpcMiddleware.d.ts +4 -3
- package/dist/rpc/RpcMiddleware.d.ts.map +1 -1
- package/dist/rpc/RpcMiddleware.js +1 -1
- package/dist/utils/gen.d.ts +1 -1
- package/dist/utils/gen.d.ts.map +1 -1
- package/dist/utils/logger.d.ts +2 -2
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +3 -3
- package/dist/utils.d.ts +18 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +24 -5
- package/package.json +29 -17
- package/src/Config/SecretURL.ts +1 -1
- package/src/Config.ts +14 -0
- package/src/ConfigProvider.ts +48 -0
- package/src/{ServiceMap.ts → Context.ts} +57 -64
- package/src/Effect.ts +11 -9
- package/src/Layer.ts +5 -4
- package/src/Pure.ts +17 -18
- package/src/Schema/Class.ts +114 -16
- package/src/Schema/SpecialJsonSchema.ts +69 -0
- package/src/Schema/SpecialOpenApi.ts +130 -0
- package/src/Schema/brand.ts +13 -7
- package/src/Schema/email.ts +10 -2
- package/src/Schema/ext.ts +150 -59
- package/src/Schema/moreStrings.ts +14 -6
- package/src/Schema/numbers.ts +2 -2
- package/src/Schema/phoneNumber.ts +8 -1
- package/src/Schema.ts +79 -103
- package/src/client/apiClientFactory.ts +18 -18
- package/src/client/errors.ts +46 -12
- package/src/client/makeClient.ts +32 -12
- package/src/http/Request.ts +7 -4
- package/src/ids.ts +1 -1
- package/src/index.ts +3 -11
- package/src/middleware.ts +2 -2
- package/src/rpc/MiddlewareMaker.ts +8 -7
- package/src/rpc/RpcContextMap.ts +6 -5
- package/src/rpc/RpcMiddleware.ts +5 -4
- package/src/utils/gen.ts +1 -1
- package/src/utils/logger.ts +2 -2
- package/src/utils.ts +26 -4
- package/test/dist/moreStrings.test.d.ts.map +1 -0
- package/test/dist/rpc.test.d.ts.map +1 -1
- package/test/dist/secretURL.test.d.ts.map +1 -0
- package/test/dist/special.test.d.ts.map +1 -0
- package/test/moreStrings.test.ts +17 -0
- package/test/rpc.test.ts +26 -5
- package/test/schema.test.ts +396 -3
- package/test/secretURL.test.ts +157 -0
- package/test/special.test.ts +732 -0
- package/test/utils.test.ts +1 -1
- package/tsconfig.base.json +0 -1
- package/tsconfig.json +0 -1
- package/dist/ServiceMap.d.ts +0 -44
- package/dist/ServiceMap.d.ts.map +0 -1
- package/dist/ServiceMap.js +0 -91
- package/dist/Struct.d.ts +0 -44
- package/dist/Struct.d.ts.map +0 -1
- package/dist/Struct.js +0 -29
- 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,
|
|
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
|
-
() =>
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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<
|
|
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
|
-
*
|
|
123
|
+
* An annotated `S.Array` of unique items that decodes to a `ReadonlySet`.
|
|
81
124
|
*/
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
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 = <
|
|
184
|
+
export const ReadonlySet = <ValueSchema extends S.Top>(value: ValueSchema) =>
|
|
95
185
|
pipe(
|
|
96
|
-
|
|
97
|
-
(s) =>
|
|
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 = <
|
|
104
|
-
readonly key:
|
|
105
|
-
readonly value:
|
|
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
|
-
|
|
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 = <
|
|
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 =
|
|
212
|
+
export const defaultDate = <Schema extends S.Top>(schema: Schema) =>
|
|
213
|
+
schema.pipe(withDefaultConstructor(() => new global.Date()))
|
|
122
214
|
|
|
123
|
-
export const defaultBool =
|
|
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 =
|
|
219
|
+
export const defaultArray = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => []))
|
|
130
220
|
|
|
131
|
-
export const defaultMap =
|
|
221
|
+
export const defaultMap = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => new Map()))
|
|
132
222
|
|
|
133
|
-
export const defaultSet =
|
|
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,
|
|
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
|
-
})
|
|
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
|
|
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
|
-
})
|
|
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:
|
|
248
|
-
):
|
|
335
|
+
context: Context.Context<R>
|
|
336
|
+
): ProvidedCodec<Self, R> => {
|
|
249
337
|
const prov = Effect.provide(context)
|
|
250
|
-
return
|
|
251
|
-
.
|
|
252
|
-
.
|
|
253
|
-
|
|
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<
|
|
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
|
-
|
|
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
|
|
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
|
)
|
package/src/Schema/numbers.ts
CHANGED
|
@@ -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.
|
|
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
|
-
.
|
|
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 {
|
|
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,
|
|
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
|
-
|
|
49
|
+
toArbitrary: () => (fc) => fakerArb((faker) => faker.internet.exampleEmail)(fc).map(makeEmail)
|
|
48
50
|
}),
|
|
49
51
|
withDefaultMake
|
|
50
52
|
)
|
|
51
53
|
|
|
52
|
-
export type Email =
|
|
54
|
+
export type Email = EmailType
|
|
53
55
|
|
|
54
56
|
export const PhoneNumber = PhoneNumberT
|
|
55
57
|
.pipe(
|
|
56
58
|
S.annotate({
|
|
57
|
-
|
|
59
|
+
toArbitrary: () => (fc) =>
|
|
58
60
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
59
|
-
fakerArb((faker) => faker.phone.number)(fc).map(
|
|
61
|
+
fakerArb((faker) => faker.phone.number)(fc).map(makePhoneNumber)
|
|
60
62
|
}),
|
|
61
63
|
withDefaultMake
|
|
62
64
|
)
|
|
63
65
|
|
|
64
|
-
export
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
):
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
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(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
|
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))
|