effect-app 4.0.0-beta.5 → 4.0.0-beta.51
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 +237 -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 +48 -12
- 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 +43 -27
- package/dist/Schema/ext.d.ts.map +1 -1
- package/dist/Schema/ext.js +26 -21
- 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 +61 -39
- 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 +178 -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, Schema, type SchemaAST, SchemaGetter, SchemaIssue,
|
|
3
|
+
import { Effect, Option, pipe, Schema, 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`.
|
|
@@ -35,6 +43,13 @@ export const Date = Object.assign(DateFromString, {
|
|
|
35
43
|
withDefault: DateFromString.pipe(withDefaultConstructor(() => new global.Date()))
|
|
36
44
|
})
|
|
37
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Like the default Schema `DateValid` but from String with `withDefault` => now
|
|
48
|
+
*/
|
|
49
|
+
export const DateValid = Object.assign(Date.check(isDateValid()), {
|
|
50
|
+
withDefault: DateFromString.pipe(withDefaultConstructor(() => new global.Date()))
|
|
51
|
+
})
|
|
52
|
+
|
|
38
53
|
/**
|
|
39
54
|
* Like the default Schema `Boolean` but with `withDefault` => false
|
|
40
55
|
*/
|
|
@@ -43,10 +58,16 @@ export const Boolean = Object.assign(S.Boolean, {
|
|
|
43
58
|
})
|
|
44
59
|
|
|
45
60
|
/**
|
|
61
|
+
* You probably want to use `Finite` instead of this.
|
|
46
62
|
* Like the default Schema `Number` but with `withDefault` => 0
|
|
47
63
|
*/
|
|
48
64
|
export const Number = Object.assign(S.Number, { withDefault: S.Number.pipe(withDefaultConstructor(() => 0)) })
|
|
49
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Like the default Schema `Finite` but with `withDefault` => 0
|
|
68
|
+
*/
|
|
69
|
+
export const Finite = Object.assign(S.Finite, { withDefault: S.Finite.pipe(withDefaultConstructor(() => 0)) })
|
|
70
|
+
|
|
50
71
|
/**
|
|
51
72
|
* Like the default Schema `Literal` but with `withDefault` => literals[0]
|
|
52
73
|
*/
|
|
@@ -69,7 +90,7 @@ export const Literal = <Literals extends NonEmptyReadonlyArray<AST.LiteralValue>
|
|
|
69
90
|
/**
|
|
70
91
|
* Like the default Schema `Array` but with `withDefault` => []
|
|
71
92
|
*/
|
|
72
|
-
export function Array<
|
|
93
|
+
export function Array<ValueSchema extends S.Top>(value: ValueSchema) {
|
|
73
94
|
return pipe(
|
|
74
95
|
S.Array(value),
|
|
75
96
|
(s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => [])) })
|
|
@@ -79,7 +100,7 @@ export function Array<Value extends S.Top>(value: Value) {
|
|
|
79
100
|
/**
|
|
80
101
|
* Like the default Schema `Map` but with `withDefault` => []
|
|
81
102
|
*/
|
|
82
|
-
function Map_<
|
|
103
|
+
function Map_<KeySchema extends S.Top, ValueSchema extends S.Top>(input: { key: KeySchema; value: ValueSchema }) {
|
|
83
104
|
return pipe(
|
|
84
105
|
S.ReadonlyMap(input.key, input.value),
|
|
85
106
|
(s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => new global.Map())) })
|
|
@@ -91,18 +112,19 @@ export { Map_ as Map }
|
|
|
91
112
|
/**
|
|
92
113
|
* Like the default Schema `ReadonlySet` but with `withDefault` => new Set()
|
|
93
114
|
*/
|
|
94
|
-
export const ReadonlySet = <
|
|
115
|
+
export const ReadonlySet = <ValueSchema extends S.Top>(value: ValueSchema) =>
|
|
95
116
|
pipe(
|
|
96
117
|
S.ReadonlySet(value),
|
|
97
|
-
(s) =>
|
|
118
|
+
(s) =>
|
|
119
|
+
Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => new Set<S.Schema.Type<ValueSchema>>())) })
|
|
98
120
|
)
|
|
99
121
|
|
|
100
122
|
/**
|
|
101
123
|
* Like the default Schema `ReadonlyMap` but with `withDefault` => new Map()
|
|
102
124
|
*/
|
|
103
|
-
export const ReadonlyMap = <
|
|
104
|
-
readonly key:
|
|
105
|
-
readonly value:
|
|
125
|
+
export const ReadonlyMap = <KeySchema extends S.Top, ValueSchema extends S.Top>(pair: {
|
|
126
|
+
readonly key: KeySchema
|
|
127
|
+
readonly value: ValueSchema
|
|
106
128
|
}) =>
|
|
107
129
|
pipe(
|
|
108
130
|
S.ReadonlyMap(pair.key, pair.value),
|
|
@@ -112,25 +134,24 @@ export const ReadonlyMap = <K extends S.Top, V extends S.Top>(pair: {
|
|
|
112
134
|
/**
|
|
113
135
|
* Like the default Schema `NullOr` but with `withDefault` => null
|
|
114
136
|
*/
|
|
115
|
-
export const NullOr = <
|
|
137
|
+
export const NullOr = <Schema extends S.Top>(self: Schema) =>
|
|
116
138
|
pipe(
|
|
117
139
|
S.NullOr(self),
|
|
118
140
|
(s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => null)) })
|
|
119
141
|
)
|
|
120
142
|
|
|
121
|
-
export const defaultDate =
|
|
143
|
+
export const defaultDate = <Schema extends S.Top>(schema: Schema) =>
|
|
144
|
+
schema.pipe(withDefaultConstructor(() => new global.Date()))
|
|
122
145
|
|
|
123
|
-
export const defaultBool =
|
|
146
|
+
export const defaultBool = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => false))
|
|
124
147
|
|
|
125
|
-
export const defaultNullable = (
|
|
126
|
-
s: S.Top
|
|
127
|
-
) => s.pipe(withDefaultConstructor(() => null))
|
|
148
|
+
export const defaultNullable = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => null))
|
|
128
149
|
|
|
129
|
-
export const defaultArray =
|
|
150
|
+
export const defaultArray = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => []))
|
|
130
151
|
|
|
131
|
-
export const defaultMap =
|
|
152
|
+
export const defaultMap = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => new Map()))
|
|
132
153
|
|
|
133
|
-
export const defaultSet =
|
|
154
|
+
export const defaultSet = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => new Set()))
|
|
134
155
|
|
|
135
156
|
export const withDefaultMake = <Self extends S.Top>(s: Self) => {
|
|
136
157
|
const a = Object.assign(S.decodeSync(s as any) as WithDefaults<Self>, s)
|
|
@@ -160,7 +181,7 @@ export type WithDefaults<Self extends S.Top> = (
|
|
|
160
181
|
// : never
|
|
161
182
|
|
|
162
183
|
export const inputDate = extendM(
|
|
163
|
-
S.Union([S.DateValid,
|
|
184
|
+
S.Union([S.DateValid, Date]).pipe(S.revealCodec),
|
|
164
185
|
(s) => ({ withDefault: s.pipe(withDefaultConstructor(() => new globalThis.Date())) })
|
|
165
186
|
)
|
|
166
187
|
|
|
@@ -240,29 +261,30 @@ export const transformToOrFail = <To extends S.Top, From extends S.Top, RD>(
|
|
|
240
261
|
)
|
|
241
262
|
)
|
|
242
263
|
|
|
243
|
-
// TODO: v4 migration — S.declare API changed (no [self] + decode/encode pattern)
|
|
244
|
-
// Need to find v4 equivalent for contextual schema wrapping
|
|
245
264
|
export const provide = <Self extends S.Top, R>(
|
|
246
265
|
self: Self,
|
|
247
266
|
context: ServiceMap.ServiceMap<R>
|
|
248
|
-
):
|
|
267
|
+
): ProvidedCodec<Self, R> => {
|
|
249
268
|
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")
|
|
269
|
+
return self.pipe(
|
|
270
|
+
S.middlewareDecoding((effect) => prov(effect)),
|
|
271
|
+
S.middlewareEncoding((effect) => prov(effect))
|
|
272
|
+
) as ProvidedCodec<Self, R>
|
|
268
273
|
}
|
|
274
|
+
export const contextFromServices = <
|
|
275
|
+
Self extends S.Top,
|
|
276
|
+
Tags extends ReadonlyArray<ServiceMap.Key<any, any>>
|
|
277
|
+
>(
|
|
278
|
+
self: Self,
|
|
279
|
+
...services: Tags
|
|
280
|
+
): Effect.Effect<
|
|
281
|
+
ProvidedCodec<Self, ServiceMap.Service.Identifier<Tags[number]>>,
|
|
282
|
+
never,
|
|
283
|
+
ServiceMap.Service.Identifier<Tags[number]>
|
|
284
|
+
> =>
|
|
285
|
+
Effect.gen(function*() {
|
|
286
|
+
const context: ServiceMap.ServiceMap<ServiceMap.Service.Identifier<Tags[number]>> = ServiceMap.pick(...services)(
|
|
287
|
+
yield* Effect.services<ServiceMap.Service.Identifier<Tags[number]>>()
|
|
288
|
+
)
|
|
289
|
+
return provide(self, context)
|
|
290
|
+
})
|
|
@@ -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
|
)
|
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, Literal, Map, NullOr, Number, ReadonlyMap, ReadonlySet } from "./Schema/ext.js"
|
|
16
|
+
export { Array, Boolean, Date, DateValid, Finite, Literal, Map, 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))
|
package/src/ServiceMap.ts
CHANGED
|
@@ -7,13 +7,14 @@
|
|
|
7
7
|
|
|
8
8
|
import { type Effect, Layer, type Scope, type Types } from "effect"
|
|
9
9
|
import * as ServiceMap from "effect/ServiceMap"
|
|
10
|
-
import { Yieldable } from "./Effect.js"
|
|
10
|
+
import { type Yieldable } from "./Effect.js"
|
|
11
11
|
|
|
12
12
|
export * from "effect/ServiceMap"
|
|
13
13
|
|
|
14
|
-
export interface Opaque<Self extends object, in out Shape extends object>
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
export interface Opaque<Self extends object, in out Shape extends object>
|
|
15
|
+
extends ServiceMap.Key<Self, Self>, Yieldable<Opaque<Self, Shape>, Self, never, Self>
|
|
16
|
+
{
|
|
17
|
+
of(this: void, self: Shape): Self
|
|
17
18
|
serviceMap(self: Shape): ServiceMap.ServiceMap<Self>
|
|
18
19
|
// a version that leverages the Shape -> Self conversion
|
|
19
20
|
toLayer: <E, R>(
|
|
@@ -151,7 +152,7 @@ export const Opaque: {
|
|
|
151
152
|
id: Identifier,
|
|
152
153
|
options?: {
|
|
153
154
|
readonly make: ((...args: Args) => Effect.Effect<Shape, E, R>) | Effect.Effect<Shape, E, R> | undefined
|
|
154
|
-
}
|
|
155
|
+
}
|
|
155
156
|
) =>
|
|
156
157
|
& OpaqueClass<Self, Identifier, Shape>
|
|
157
158
|
& ([Types.unassigned] extends [R] ? unknown
|
|
@@ -181,7 +182,7 @@ export const Opaque: {
|
|
|
181
182
|
const svc = ServiceMap.Service()(id, options) as any
|
|
182
183
|
return Object.assign(svc, {
|
|
183
184
|
toLayer: (eff: Effect.Effect<any, any, any>) => {
|
|
184
|
-
return Layer.effect(svc
|
|
185
|
+
return Layer.effect(svc, eff)
|
|
185
186
|
}
|
|
186
187
|
})
|
|
187
188
|
}
|