effect-app 4.0.0-beta.5 → 4.0.0-beta.52
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +243 -0
- 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/Effect.d.ts.map +1 -1
- package/dist/Effect.js +3 -2
- package/dist/Operations.d.ts +51 -15
- package/dist/Operations.d.ts.map +1 -1
- package/dist/Pure.d.ts.map +1 -1
- package/dist/Pure.js +11 -11
- 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 +40 -0
- package/dist/Schema/SpecialJsonSchema.d.ts.map +1 -0
- package/dist/Schema/SpecialJsonSchema.js +199 -0
- package/dist/Schema/SpecialOpenApi.d.ts +30 -0
- package/dist/Schema/SpecialOpenApi.d.ts.map +1 -0
- package/dist/Schema/SpecialOpenApi.js +120 -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 +4 -3
- package/dist/Schema/ext.d.ts +177 -44
- package/dist/Schema/ext.d.ts.map +1 -1
- package/dist/Schema/ext.js +144 -35
- package/dist/Schema/moreStrings.d.ts.map +1 -1
- package/dist/Schema/moreStrings.js +6 -4
- 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 +3 -2
- package/dist/Schema.d.ts +21 -54
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +43 -64
- package/dist/ServiceMap.d.ts +3 -3
- package/dist/ServiceMap.d.ts.map +1 -1
- package/dist/ServiceMap.js +1 -1
- package/dist/client/apiClientFactory.d.ts +1 -1
- package/dist/client/apiClientFactory.d.ts.map +1 -1
- package/dist/client/apiClientFactory.js +8 -9
- package/dist/client/errors.d.ts +8 -0
- package/dist/client/errors.d.ts.map +1 -1
- package/dist/client/errors.js +34 -10
- package/dist/client/makeClient.d.ts +13 -12
- package/dist/client/makeClient.d.ts.map +1 -1
- package/dist/client/makeClient.js +7 -15
- 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 +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/utils.d.ts +18 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +24 -5
- package/package.json +24 -12
- package/src/Config.ts +14 -0
- package/src/ConfigProvider.ts +48 -0
- package/src/Effect.ts +3 -2
- package/src/Pure.ts +12 -13
- package/src/Schema/Class.ts +114 -16
- package/src/Schema/SpecialJsonSchema.ts +216 -0
- package/src/Schema/SpecialOpenApi.ts +126 -0
- package/src/Schema/brand.ts +13 -7
- package/src/Schema/email.ts +4 -2
- package/src/Schema/ext.ts +213 -56
- package/src/Schema/moreStrings.ts +10 -6
- package/src/Schema/numbers.ts +2 -2
- package/src/Schema/phoneNumber.ts +3 -1
- package/src/Schema.ts +79 -103
- package/src/ServiceMap.ts +7 -6
- package/src/client/apiClientFactory.ts +12 -15
- package/src/client/errors.ts +45 -12
- package/src/client/makeClient.ts +33 -26
- package/src/http/Request.ts +7 -4
- package/src/ids.ts +1 -1
- package/src/index.ts +2 -1
- 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/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 +292 -1
- package/test/special.test.ts +525 -0
- package/test/utils.test.ts +1 -1
- package/tsconfig.base.json +0 -1
- package/tsconfig.json +0 -1
- 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/brand.ts
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
|
3
3
|
import type { Option } from "effect"
|
|
4
4
|
import * as B from "effect/Brand"
|
|
5
|
-
import type * as Brand from "effect/Brand"
|
|
6
5
|
import type * as Result from "effect/Result"
|
|
7
6
|
import * as S from "effect/Schema"
|
|
8
7
|
|
|
@@ -21,7 +20,7 @@ export interface Constructor<in out A extends B.Brand<any>> {
|
|
|
21
20
|
* Constructs a branded type from a value of type `A`, returning `Result.succeed`
|
|
22
21
|
* if the provided `A` is valid, `Result.fail` otherwise.
|
|
23
22
|
*/
|
|
24
|
-
result(args: Unbranded<A>): Result.Result<A,
|
|
23
|
+
result(args: Unbranded<A>): Result.Result<A, B.BrandError>
|
|
25
24
|
/**
|
|
26
25
|
* Attempts to refine the provided value of type `A`, returning `true` if
|
|
27
26
|
* the provided `A` is valid, `false` otherwise.
|
|
@@ -29,19 +28,26 @@ export interface Constructor<in out A extends B.Brand<any>> {
|
|
|
29
28
|
is(a: Unbranded<A>): a is Unbranded<A> & A
|
|
30
29
|
}
|
|
31
30
|
|
|
32
|
-
|
|
31
|
+
type BrandAnnotations<C extends B.Brand<any>> =
|
|
32
|
+
& S.Annotations.Filter
|
|
33
|
+
& (
|
|
34
|
+
C extends string ? { readonly toArbitrary?: S.Annotations.ToArbitrary.Declaration<C, readonly []> }
|
|
35
|
+
: {}
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
export const fromBrand = <C extends B.Brand<any>>(
|
|
33
39
|
constructor: Constructor<C>,
|
|
34
|
-
options?:
|
|
40
|
+
options?: BrandAnnotations<C>
|
|
35
41
|
) =>
|
|
36
|
-
<Self extends S.Top>(self: Self): S.brand<Self["~rebuild.out"],
|
|
42
|
+
<Self extends S.Top>(self: Self): S.brand<Self["~rebuild.out"], B.Brand.Keys<C>> => {
|
|
37
43
|
const branded = S.fromBrand(options?.identifier ?? "Brand", constructor as any)(self as any)
|
|
38
44
|
return options ? (branded as any).pipe(S.annotate(options)) : branded as any
|
|
39
45
|
}
|
|
40
46
|
|
|
41
|
-
export type Brands<P> = P extends B.Brand<any> ?
|
|
47
|
+
export type Brands<P> = P extends B.Brand<any> ? B.Brand.Brands<P>
|
|
42
48
|
: never
|
|
43
49
|
|
|
44
|
-
export type Unbranded<P> = P extends B.Brand<any> ?
|
|
50
|
+
export type Unbranded<P> = P extends B.Brand<any> ? B.Brand.Unbranded<P> : P
|
|
45
51
|
|
|
46
52
|
export const nominal: <A extends B.Brand<any>>() => Constructor<A> = <
|
|
47
53
|
A extends B.Brand<any>
|
package/src/Schema/email.ts
CHANGED
|
@@ -16,7 +16,9 @@ export const Email = S
|
|
|
16
16
|
identifier: "Email",
|
|
17
17
|
title: "Email",
|
|
18
18
|
description: "an email according to RFC 5322",
|
|
19
|
-
jsonSchema: { format: "email", minLength: 3, /* a@b */ maxLength: 998 }
|
|
20
|
-
|
|
19
|
+
jsonSchema: { format: "email", minLength: 3, /* a@b */ maxLength: 998 }
|
|
20
|
+
}),
|
|
21
|
+
S.annotate({
|
|
22
|
+
toArbitrary: () => (fc) => fc.emailAddress().map((_) => _ as Email)
|
|
21
23
|
})
|
|
22
24
|
)
|
package/src/Schema/ext.ts
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
2
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
|
3
|
-
import { Effect, Option, pipe,
|
|
3
|
+
import { Effect, Option, pipe, type SchemaAST, SchemaGetter, SchemaIssue, SchemaTransformation, ServiceMap } from "effect"
|
|
4
4
|
import * as S from "effect/Schema"
|
|
5
|
+
import { isDateValid } from "effect/Schema"
|
|
5
6
|
import { type NonEmptyReadonlyArray } from "../Array.js"
|
|
6
7
|
import { extendM, typedKeysOf } from "../utils.js"
|
|
7
8
|
import { type AST } from "./schema.js"
|
|
8
9
|
|
|
10
|
+
type ProvidedCodec<Self extends S.Top, R> = S.Codec<
|
|
11
|
+
Self["Type"],
|
|
12
|
+
Self["Encoded"],
|
|
13
|
+
Exclude<Self["DecodingServices"], R>,
|
|
14
|
+
Exclude<Self["EncodingServices"], R>
|
|
15
|
+
>
|
|
16
|
+
|
|
9
17
|
// TODO: v4 migration — withConstructorDefault signature changed, propertySignature removed
|
|
10
18
|
// Constraint relaxed from `Self extends S.Top & S.WithoutConstructorDefault` to `Self extends S.Top`
|
|
11
19
|
// because `.pipe()` widens the schema type to `Top` which doesn't satisfy `WithoutConstructorDefault`.
|
|
@@ -21,13 +29,108 @@ export const withDefaultConstructor = <A>(
|
|
|
21
29
|
}
|
|
22
30
|
|
|
23
31
|
// TODO: v4 migration - Date is no longer by default encoded to string.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
32
|
+
/*
|
|
33
|
+
in v4, there's the notion of `toCodecJson`, as a declaration and as a schema transformer.
|
|
34
|
+
this means that Date, Map/Set, etc, remain the same type Encoded as Decoded, but when transformed to and from JSON, will go through
|
|
35
|
+
the toCodecJson transformation, which for e.g Date will be the dateFromString transformation.
|
|
36
|
+
|
|
37
|
+
While this is a cool feature, our stack (especially the Store/Repository api) is based on having an Encoded shape representing the JSON shape, so we revert back to that for now.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Formats a `Date` as an ISO 8601 string, returning `"Invalid Date"` for
|
|
42
|
+
* invalid dates instead of throwing.
|
|
43
|
+
*
|
|
44
|
+
* When to use:
|
|
45
|
+
* - You want a safe `toISOString()` that never throws.
|
|
46
|
+
*
|
|
47
|
+
* Behavior:
|
|
48
|
+
* - Returns `date.toISOString()` on success.
|
|
49
|
+
* - Returns `"Invalid Date"` if `toISOString()` throws (e.g. for
|
|
50
|
+
* `new Date(NaN)`).
|
|
51
|
+
* - Pure function; does not mutate input.
|
|
52
|
+
*
|
|
53
|
+
* **Example** (Safe date formatting)
|
|
54
|
+
*
|
|
55
|
+
* ```ts
|
|
56
|
+
* import { Formatter } from "effect"
|
|
57
|
+
*
|
|
58
|
+
* console.log(Formatter.formatDate(new Date("2024-01-15T10:30:00Z")))
|
|
59
|
+
* // 2024-01-15T10:30:00.000Z
|
|
60
|
+
*
|
|
61
|
+
* console.log(Formatter.formatDate(new Date("invalid")))
|
|
62
|
+
* // Invalid Date
|
|
63
|
+
* ```
|
|
64
|
+
*
|
|
65
|
+
* See also: {@link format}
|
|
66
|
+
*
|
|
67
|
+
* @internal
|
|
68
|
+
*/
|
|
69
|
+
export function formatDate(date: Date): string {
|
|
70
|
+
try {
|
|
71
|
+
return date.toISOString()
|
|
72
|
+
} catch {
|
|
73
|
+
return "Invalid Date"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Decodes a `string` into a `Date` and encodes a `Date` back to a `string`.
|
|
79
|
+
*
|
|
80
|
+
* When to use this:
|
|
81
|
+
* - Parsing ISO 8601 date strings from APIs or user input.
|
|
82
|
+
*
|
|
83
|
+
* Behavior:
|
|
84
|
+
* - Decode: creates a `Date` from the string (like `new Date(s)`).
|
|
85
|
+
* - Encode: converts the `Date` to an ISO string (like `date.toISOString()`),
|
|
86
|
+
* returning `"Invalid Date"` for invalid dates.
|
|
87
|
+
*
|
|
88
|
+
* **Example** (Date from string)
|
|
89
|
+
*
|
|
90
|
+
* ```ts
|
|
91
|
+
* import { Schema, SchemaTransformation } from "effect"
|
|
92
|
+
*
|
|
93
|
+
* const schema = Schema.String.pipe(
|
|
94
|
+
* Schema.decodeTo(Schema.Date, SchemaTransformation.dateFromString)
|
|
95
|
+
* )
|
|
96
|
+
* ```
|
|
97
|
+
*
|
|
98
|
+
* See also:
|
|
99
|
+
* - {@link numberFromString}
|
|
100
|
+
* - {@link dateTimeUtcFromString}
|
|
101
|
+
*
|
|
102
|
+
* @category Coercions
|
|
103
|
+
* @since 4.0.0
|
|
104
|
+
*/
|
|
105
|
+
export const dateFromString: SchemaTransformation.Transformation<globalThis.Date, string> = new SchemaTransformation
|
|
106
|
+
.Transformation(
|
|
107
|
+
SchemaGetter.Date(),
|
|
108
|
+
SchemaGetter.transform(formatDate)
|
|
29
109
|
)
|
|
30
110
|
|
|
111
|
+
const DateString = S.String.annotate({ expected: "a string in ISO 8601 format that will be decoded as a Date" })
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Schema type for {@link DateFromString}.
|
|
115
|
+
*
|
|
116
|
+
* @category Schemas
|
|
117
|
+
* @since 4.0.0
|
|
118
|
+
*/
|
|
119
|
+
export interface DateFromString extends S.decodeTo<S.Date, S.String> {}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* A transformation schema that parses an ISO 8601 string into a `Date`.
|
|
123
|
+
*
|
|
124
|
+
* Decoding:
|
|
125
|
+
* - A `string` is decoded as a `Date`.
|
|
126
|
+
*
|
|
127
|
+
* Encoding:
|
|
128
|
+
* - A `Date` is encoded as a `string`.
|
|
129
|
+
*
|
|
130
|
+
* @since 4.0.0
|
|
131
|
+
*/
|
|
132
|
+
export const DateFromString: DateFromString = DateString.pipe(S.decodeTo(S.Date, dateFromString))
|
|
133
|
+
|
|
31
134
|
/**
|
|
32
135
|
* Like the default Schema `Date` but from String with `withDefault` => now
|
|
33
136
|
*/
|
|
@@ -35,6 +138,13 @@ export const Date = Object.assign(DateFromString, {
|
|
|
35
138
|
withDefault: DateFromString.pipe(withDefaultConstructor(() => new global.Date()))
|
|
36
139
|
})
|
|
37
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Like the default Schema `DateValid` but from String with `withDefault` => now
|
|
143
|
+
*/
|
|
144
|
+
export const DateValid = Object.assign(Date.check(isDateValid()), {
|
|
145
|
+
withDefault: DateFromString.pipe(withDefaultConstructor(() => new global.Date()))
|
|
146
|
+
})
|
|
147
|
+
|
|
38
148
|
/**
|
|
39
149
|
* Like the default Schema `Boolean` but with `withDefault` => false
|
|
40
150
|
*/
|
|
@@ -43,10 +153,16 @@ export const Boolean = Object.assign(S.Boolean, {
|
|
|
43
153
|
})
|
|
44
154
|
|
|
45
155
|
/**
|
|
156
|
+
* You probably want to use `Finite` instead of this.
|
|
46
157
|
* Like the default Schema `Number` but with `withDefault` => 0
|
|
47
158
|
*/
|
|
48
159
|
export const Number = Object.assign(S.Number, { withDefault: S.Number.pipe(withDefaultConstructor(() => 0)) })
|
|
49
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Like the default Schema `Finite` but with `withDefault` => 0
|
|
163
|
+
*/
|
|
164
|
+
export const Finite = Object.assign(S.Finite, { withDefault: S.Finite.pipe(withDefaultConstructor(() => 0)) })
|
|
165
|
+
|
|
50
166
|
/**
|
|
51
167
|
* Like the default Schema `Literal` but with `withDefault` => literals[0]
|
|
52
168
|
*/
|
|
@@ -69,7 +185,7 @@ export const Literal = <Literals extends NonEmptyReadonlyArray<AST.LiteralValue>
|
|
|
69
185
|
/**
|
|
70
186
|
* Like the default Schema `Array` but with `withDefault` => []
|
|
71
187
|
*/
|
|
72
|
-
export function Array<
|
|
188
|
+
export function Array<ValueSchema extends S.Top>(value: ValueSchema) {
|
|
73
189
|
return pipe(
|
|
74
190
|
S.Array(value),
|
|
75
191
|
(s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => [])) })
|
|
@@ -77,60 +193,100 @@ export function Array<Value extends S.Top>(value: Value) {
|
|
|
77
193
|
}
|
|
78
194
|
|
|
79
195
|
/**
|
|
80
|
-
*
|
|
196
|
+
* An annotated `S.Array` of unique items that decodes to a `ReadonlySet`.
|
|
81
197
|
*/
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
198
|
+
export const ReadonlySetFromArray = <ValueSchema extends S.Top>(value: ValueSchema) => {
|
|
199
|
+
const from = S
|
|
200
|
+
.Array(value)
|
|
201
|
+
.annotate({ expected: "an array of unique items that will be decoded as a ReadonlySet" })
|
|
202
|
+
const to = S.instanceOf(Set) as S.instanceOf<ReadonlySet<S.Schema.Type<ValueSchema>>>
|
|
203
|
+
const schema = from.pipe(
|
|
204
|
+
S.decodeTo(
|
|
205
|
+
to,
|
|
206
|
+
SchemaTransformation.transform({
|
|
207
|
+
decode: (arr: globalThis.Array<S.Schema.Type<ValueSchema>>) => new Set<S.Schema.Type<ValueSchema>>(arr),
|
|
208
|
+
encode: (set: Set<S.Schema.Type<ValueSchema>>) => [...set] as globalThis.Array<S.Schema.Type<ValueSchema>>
|
|
209
|
+
}) as any
|
|
210
|
+
)
|
|
86
211
|
)
|
|
212
|
+
return S.revealCodec(schema)
|
|
87
213
|
}
|
|
88
214
|
|
|
89
|
-
|
|
215
|
+
/**
|
|
216
|
+
* An annotated `S.Array` of key-value tuples that decodes to a `ReadonlyMap`.
|
|
217
|
+
*/
|
|
218
|
+
export const ReadonlyMapFromArray = <KeySchema extends S.Top, ValueSchema extends S.Top>(pair: {
|
|
219
|
+
readonly key: KeySchema
|
|
220
|
+
readonly value: ValueSchema
|
|
221
|
+
}) => {
|
|
222
|
+
const from = S
|
|
223
|
+
.Array(S.Tuple([pair.key, pair.value]))
|
|
224
|
+
.annotate({ expected: "an array of key-value tuples that will be decoded as a ReadonlyMap" })
|
|
225
|
+
const to = S.instanceOf(Map) as S.instanceOf<
|
|
226
|
+
ReadonlyMap<S.Schema.Type<KeySchema>, S.Schema.Type<ValueSchema>>
|
|
227
|
+
>
|
|
228
|
+
const schema = from.pipe(
|
|
229
|
+
S.decodeTo(
|
|
230
|
+
to,
|
|
231
|
+
SchemaTransformation.transform({
|
|
232
|
+
decode: (
|
|
233
|
+
arr: globalThis.Array<readonly [S.Schema.Type<KeySchema>, S.Schema.Type<ValueSchema>]>
|
|
234
|
+
) => new Map<S.Schema.Type<KeySchema>, S.Schema.Type<ValueSchema>>(arr),
|
|
235
|
+
encode: (
|
|
236
|
+
map: Map<S.Schema.Type<KeySchema>, S.Schema.Type<ValueSchema>>
|
|
237
|
+
) =>
|
|
238
|
+
[...map.entries()] as globalThis.Array<
|
|
239
|
+
readonly [S.Schema.Type<KeySchema>, S.Schema.Type<ValueSchema>]
|
|
240
|
+
>
|
|
241
|
+
}) as any
|
|
242
|
+
)
|
|
243
|
+
)
|
|
244
|
+
return S.revealCodec(schema)
|
|
245
|
+
}
|
|
90
246
|
|
|
91
247
|
/**
|
|
92
|
-
* Like the default Schema `ReadonlySet` but with `withDefault` => new Set()
|
|
248
|
+
* Like the default Schema `ReadonlySet` but from Array with `withDefault` => new Set()
|
|
93
249
|
*/
|
|
94
|
-
export const ReadonlySet = <
|
|
250
|
+
export const ReadonlySet = <ValueSchema extends S.Top>(value: ValueSchema) =>
|
|
95
251
|
pipe(
|
|
96
|
-
|
|
97
|
-
(s) =>
|
|
252
|
+
ReadonlySetFromArray(value),
|
|
253
|
+
(s) =>
|
|
254
|
+
Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => new Set<S.Schema.Type<ValueSchema>>())) })
|
|
98
255
|
)
|
|
99
256
|
|
|
100
257
|
/**
|
|
101
|
-
* Like the default Schema `ReadonlyMap` but with `withDefault` => new Map()
|
|
258
|
+
* Like the default Schema `ReadonlyMap` but from Array with `withDefault` => new Map()
|
|
102
259
|
*/
|
|
103
|
-
export const ReadonlyMap = <
|
|
104
|
-
readonly key:
|
|
105
|
-
readonly value:
|
|
260
|
+
export const ReadonlyMap = <KeySchema extends S.Top, ValueSchema extends S.Top>(pair: {
|
|
261
|
+
readonly key: KeySchema
|
|
262
|
+
readonly value: ValueSchema
|
|
106
263
|
}) =>
|
|
107
264
|
pipe(
|
|
108
|
-
|
|
265
|
+
ReadonlyMapFromArray(pair),
|
|
109
266
|
(s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => new Map())) })
|
|
110
267
|
)
|
|
111
268
|
|
|
112
269
|
/**
|
|
113
270
|
* Like the default Schema `NullOr` but with `withDefault` => null
|
|
114
271
|
*/
|
|
115
|
-
export const NullOr = <
|
|
272
|
+
export const NullOr = <Schema extends S.Top>(self: Schema) =>
|
|
116
273
|
pipe(
|
|
117
274
|
S.NullOr(self),
|
|
118
275
|
(s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => null)) })
|
|
119
276
|
)
|
|
120
277
|
|
|
121
|
-
export const defaultDate =
|
|
278
|
+
export const defaultDate = <Schema extends S.Top>(schema: Schema) =>
|
|
279
|
+
schema.pipe(withDefaultConstructor(() => new global.Date()))
|
|
122
280
|
|
|
123
|
-
export const defaultBool =
|
|
281
|
+
export const defaultBool = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => false))
|
|
124
282
|
|
|
125
|
-
export const defaultNullable = (
|
|
126
|
-
s: S.Top
|
|
127
|
-
) => s.pipe(withDefaultConstructor(() => null))
|
|
283
|
+
export const defaultNullable = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => null))
|
|
128
284
|
|
|
129
|
-
export const defaultArray =
|
|
285
|
+
export const defaultArray = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => []))
|
|
130
286
|
|
|
131
|
-
export const defaultMap =
|
|
287
|
+
export const defaultMap = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => new Map()))
|
|
132
288
|
|
|
133
|
-
export const defaultSet =
|
|
289
|
+
export const defaultSet = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => new Set()))
|
|
134
290
|
|
|
135
291
|
export const withDefaultMake = <Self extends S.Top>(s: Self) => {
|
|
136
292
|
const a = Object.assign(S.decodeSync(s as any) as WithDefaults<Self>, s)
|
|
@@ -160,7 +316,7 @@ export type WithDefaults<Self extends S.Top> = (
|
|
|
160
316
|
// : never
|
|
161
317
|
|
|
162
318
|
export const inputDate = extendM(
|
|
163
|
-
S.Union([S.DateValid,
|
|
319
|
+
S.Union([S.DateValid, Date]).pipe(S.revealCodec),
|
|
164
320
|
(s) => ({ withDefault: s.pipe(withDefaultConstructor(() => new globalThis.Date())) })
|
|
165
321
|
)
|
|
166
322
|
|
|
@@ -211,7 +367,7 @@ export const transformTo = <To extends S.Top, From extends S.Top>(
|
|
|
211
367
|
{ message: "One way schema transformation, encoding is not allowed" }
|
|
212
368
|
)
|
|
213
369
|
)
|
|
214
|
-
})
|
|
370
|
+
})
|
|
215
371
|
)
|
|
216
372
|
)
|
|
217
373
|
|
|
@@ -228,7 +384,7 @@ export const transformToOrFail = <To extends S.Top, From extends S.Top, RD>(
|
|
|
228
384
|
S.decodeTo(
|
|
229
385
|
to,
|
|
230
386
|
SchemaTransformation.transformOrFail({
|
|
231
|
-
decode
|
|
387
|
+
decode,
|
|
232
388
|
encode: (i: any) =>
|
|
233
389
|
Effect.fail(
|
|
234
390
|
new SchemaIssue.Forbidden(
|
|
@@ -236,33 +392,34 @@ export const transformToOrFail = <To extends S.Top, From extends S.Top, RD>(
|
|
|
236
392
|
{ message: "One way schema transformation, encoding is not allowed" }
|
|
237
393
|
)
|
|
238
394
|
)
|
|
239
|
-
})
|
|
395
|
+
})
|
|
240
396
|
)
|
|
241
397
|
)
|
|
242
398
|
|
|
243
|
-
// TODO: v4 migration — S.declare API changed (no [self] + decode/encode pattern)
|
|
244
|
-
// Need to find v4 equivalent for contextual schema wrapping
|
|
245
399
|
export const provide = <Self extends S.Top, R>(
|
|
246
400
|
self: Self,
|
|
247
401
|
context: ServiceMap.ServiceMap<R>
|
|
248
|
-
):
|
|
402
|
+
): ProvidedCodec<Self, R> => {
|
|
249
403
|
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")
|
|
404
|
+
return self.pipe(
|
|
405
|
+
S.middlewareDecoding((effect) => prov(effect)),
|
|
406
|
+
S.middlewareEncoding((effect) => prov(effect))
|
|
407
|
+
) as ProvidedCodec<Self, R>
|
|
268
408
|
}
|
|
409
|
+
export const contextFromServices = <
|
|
410
|
+
Self extends S.Top,
|
|
411
|
+
Tags extends ReadonlyArray<ServiceMap.Key<any, any>>
|
|
412
|
+
>(
|
|
413
|
+
self: Self,
|
|
414
|
+
...services: Tags
|
|
415
|
+
): Effect.Effect<
|
|
416
|
+
ProvidedCodec<Self, ServiceMap.Service.Identifier<Tags[number]>>,
|
|
417
|
+
never,
|
|
418
|
+
ServiceMap.Service.Identifier<Tags[number]>
|
|
419
|
+
> =>
|
|
420
|
+
Effect.gen(function*() {
|
|
421
|
+
const context: ServiceMap.ServiceMap<ServiceMap.Service.Identifier<Tags[number]>> = ServiceMap.pick(...services)(
|
|
422
|
+
yield* Effect.services<ServiceMap.Service.Identifier<Tags[number]>>()
|
|
423
|
+
)
|
|
424
|
+
return provide(self, context)
|
|
425
|
+
})
|
|
@@ -140,10 +140,10 @@ const minLength = 6
|
|
|
140
140
|
const maxLength = 50
|
|
141
141
|
const size = 21
|
|
142
142
|
const length = 10 * size
|
|
143
|
-
const StringIdArb = (): S.LazyArbitrary<
|
|
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
|
|
|
@@ -246,10 +248,12 @@ export const Url = S
|
|
|
246
248
|
.String
|
|
247
249
|
.pipe(
|
|
248
250
|
S.refine(isUrl, {
|
|
249
|
-
arbitrary: (): S.LazyArbitrary<Url> => (fc) => fc.webUrl().map((_) => _ as Url),
|
|
250
251
|
identifier: "Url",
|
|
251
252
|
title: "Url",
|
|
252
253
|
jsonSchema: { format: "uri" }
|
|
253
254
|
}),
|
|
255
|
+
S.annotate({
|
|
256
|
+
toArbitrary: () => (fc) => fc.webUrl().map((_) => _ as Url)
|
|
257
|
+
}),
|
|
254
258
|
withDefaultMake
|
|
255
259
|
)
|
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>(), {
|
|
@@ -17,8 +17,10 @@ export const PhoneNumber = S
|
|
|
17
17
|
identifier: "PhoneNumber",
|
|
18
18
|
title: "PhoneNumber",
|
|
19
19
|
description: "a phone number with at least 7 digits",
|
|
20
|
-
arbitrary: () => (fc: any) => Numbers(7, 10)(fc).map((_: any) => _ as PhoneNumber),
|
|
21
20
|
jsonSchema: { format: "phone" }
|
|
22
21
|
}),
|
|
22
|
+
S.annotate({
|
|
23
|
+
toArbitrary: () => (fc) => Numbers(7, 10)(fc).map((_) => _ as PhoneNumber)
|
|
24
|
+
}),
|
|
23
25
|
withDefaultMake
|
|
24
26
|
)
|