effect-app 4.0.0-beta.2 → 4.0.0-beta.21
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 +108 -0
- package/dist/Operations.d.ts +59 -23
- package/dist/Operations.d.ts.map +1 -1
- package/dist/Pure.js +1 -1
- 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 +26 -25
- package/dist/Schema/ext.d.ts.map +1 -1
- package/dist/Schema/ext.js +13 -20
- package/dist/Schema/moreStrings.d.ts +6 -6
- package/dist/Schema/moreStrings.d.ts.map +1 -1
- package/dist/Schema/moreStrings.js +6 -4
- package/dist/Schema/phoneNumber.d.ts.map +1 -1
- package/dist/Schema/phoneNumber.js +3 -2
- package/dist/Schema.d.ts +18 -49
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +15 -62
- 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.map +1 -1
- package/dist/client/apiClientFactory.js +8 -9
- package/dist/client/errors.d.ts.map +1 -1
- package/dist/client/errors.js +1 -1
- package/dist/client/makeClient.d.ts +6 -6
- package/dist/client/makeClient.d.ts.map +1 -1
- package/dist/client/makeClient.js +3 -14
- package/dist/http/Request.d.ts.map +1 -1
- package/dist/http/Request.js +5 -5
- package/dist/ids.d.ts +6 -6
- package/dist/ids.d.ts.map +1 -1
- package/dist/ids.js +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -2
- package/dist/rpc/MiddlewareMaker.d.ts +1 -1
- package/dist/rpc/MiddlewareMaker.d.ts.map +1 -1
- package/dist/rpc/MiddlewareMaker.js +1 -1
- package/dist/rpc/RpcMiddleware.d.ts +1 -1
- package/dist/rpc/RpcMiddleware.d.ts.map +1 -1
- package/dist/utils.d.ts +18 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +24 -5
- package/package.json +3 -7
- package/src/Pure.ts +2 -2
- package/src/Schema/brand.ts +13 -7
- package/src/Schema/email.ts +4 -2
- package/src/Schema/ext.ts +46 -38
- package/src/Schema/moreStrings.ts +16 -13
- package/src/Schema/phoneNumber.ts +3 -1
- package/src/Schema.ts +48 -99
- package/src/ServiceMap.ts +7 -6
- package/src/client/apiClientFactory.ts +12 -15
- package/src/client/errors.ts +12 -3
- package/src/client/makeClient.ts +6 -19
- package/src/http/Request.ts +7 -4
- package/src/ids.ts +1 -1
- package/src/index.ts +0 -1
- package/src/rpc/MiddlewareMaker.ts +2 -1
- package/src/rpc/RpcMiddleware.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/moreStrings.test.ts +17 -0
- package/test/schema.test.ts +32 -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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "effect-app",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.21",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"dependencies": {
|
|
@@ -22,10 +22,10 @@
|
|
|
22
22
|
"fast-check": "~4.5.3",
|
|
23
23
|
"typescript": "~5.9.3",
|
|
24
24
|
"vitest": "^4.0.18",
|
|
25
|
-
"@effect-app/eslint-shared-config": "0.5.7-beta.
|
|
25
|
+
"@effect-app/eslint-shared-config": "0.5.7-beta.2"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
|
-
"effect": "^4.0.0-beta.
|
|
28
|
+
"effect": "^4.0.0-beta.36"
|
|
29
29
|
},
|
|
30
30
|
"typesVersions": {
|
|
31
31
|
"*": {
|
|
@@ -139,10 +139,6 @@
|
|
|
139
139
|
"types": "./dist/Set.d.ts",
|
|
140
140
|
"default": "./dist/Set.js"
|
|
141
141
|
},
|
|
142
|
-
"./Struct": {
|
|
143
|
-
"types": "./dist/Struct.d.ts",
|
|
144
|
-
"default": "./dist/Struct.js"
|
|
145
|
-
},
|
|
146
142
|
"./TypeTest": {
|
|
147
143
|
"types": "./dist/TypeTest.d.ts",
|
|
148
144
|
"default": "./dist/TypeTest.js"
|
package/src/Pure.ts
CHANGED
|
@@ -202,7 +202,7 @@ export function modify<S2, A, S3>(
|
|
|
202
202
|
const [s, a] = mod(_.env.state)
|
|
203
203
|
_.env.state = s as any
|
|
204
204
|
return a
|
|
205
|
-
})
|
|
205
|
+
})
|
|
206
206
|
}
|
|
207
207
|
|
|
208
208
|
export function modifyM<W, R, E, A, S2, S3>(
|
|
@@ -213,7 +213,7 @@ export function modifyM<W, R, E, A, S2, S3>(
|
|
|
213
213
|
(castTag<W, S3, S2>() as any).use((_: any) => _),
|
|
214
214
|
(_: any) =>
|
|
215
215
|
Effect.map(mod(_.env.state), ([s, a]: any) => {
|
|
216
|
-
_.env.state = s
|
|
216
|
+
_.env.state = s
|
|
217
217
|
return a
|
|
218
218
|
})
|
|
219
219
|
) as any
|
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,18 @@
|
|
|
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
5
|
import { type NonEmptyReadonlyArray } from "../Array.js"
|
|
6
6
|
import { extendM, typedKeysOf } from "../utils.js"
|
|
7
7
|
import { type AST } from "./schema.js"
|
|
8
8
|
|
|
9
|
+
type ProvidedCodec<Self extends S.Top, R> = S.Codec<
|
|
10
|
+
Self["Type"],
|
|
11
|
+
Self["Encoded"],
|
|
12
|
+
Exclude<Self["DecodingServices"], R>,
|
|
13
|
+
Exclude<Self["EncodingServices"], R>
|
|
14
|
+
>
|
|
15
|
+
|
|
9
16
|
// TODO: v4 migration — withConstructorDefault signature changed, propertySignature removed
|
|
10
17
|
// Constraint relaxed from `Self extends S.Top & S.WithoutConstructorDefault` to `Self extends S.Top`
|
|
11
18
|
// because `.pipe()` widens the schema type to `Top` which doesn't satisfy `WithoutConstructorDefault`.
|
|
@@ -69,7 +76,7 @@ export const Literal = <Literals extends NonEmptyReadonlyArray<AST.LiteralValue>
|
|
|
69
76
|
/**
|
|
70
77
|
* Like the default Schema `Array` but with `withDefault` => []
|
|
71
78
|
*/
|
|
72
|
-
export function Array<
|
|
79
|
+
export function Array<ValueSchema extends S.Top>(value: ValueSchema) {
|
|
73
80
|
return pipe(
|
|
74
81
|
S.Array(value),
|
|
75
82
|
(s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => [])) })
|
|
@@ -79,7 +86,7 @@ export function Array<Value extends S.Top>(value: Value) {
|
|
|
79
86
|
/**
|
|
80
87
|
* Like the default Schema `Map` but with `withDefault` => []
|
|
81
88
|
*/
|
|
82
|
-
function Map_<
|
|
89
|
+
function Map_<KeySchema extends S.Top, ValueSchema extends S.Top>(input: { key: KeySchema; value: ValueSchema }) {
|
|
83
90
|
return pipe(
|
|
84
91
|
S.ReadonlyMap(input.key, input.value),
|
|
85
92
|
(s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => new global.Map())) })
|
|
@@ -91,18 +98,19 @@ export { Map_ as Map }
|
|
|
91
98
|
/**
|
|
92
99
|
* Like the default Schema `ReadonlySet` but with `withDefault` => new Set()
|
|
93
100
|
*/
|
|
94
|
-
export const ReadonlySet = <
|
|
101
|
+
export const ReadonlySet = <ValueSchema extends S.Top>(value: ValueSchema) =>
|
|
95
102
|
pipe(
|
|
96
103
|
S.ReadonlySet(value),
|
|
97
|
-
(s) =>
|
|
104
|
+
(s) =>
|
|
105
|
+
Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => new Set<S.Schema.Type<ValueSchema>>())) })
|
|
98
106
|
)
|
|
99
107
|
|
|
100
108
|
/**
|
|
101
109
|
* Like the default Schema `ReadonlyMap` but with `withDefault` => new Map()
|
|
102
110
|
*/
|
|
103
|
-
export const ReadonlyMap = <
|
|
104
|
-
readonly key:
|
|
105
|
-
readonly value:
|
|
111
|
+
export const ReadonlyMap = <KeySchema extends S.Top, ValueSchema extends S.Top>(pair: {
|
|
112
|
+
readonly key: KeySchema
|
|
113
|
+
readonly value: ValueSchema
|
|
106
114
|
}) =>
|
|
107
115
|
pipe(
|
|
108
116
|
S.ReadonlyMap(pair.key, pair.value),
|
|
@@ -112,25 +120,24 @@ export const ReadonlyMap = <K extends S.Top, V extends S.Top>(pair: {
|
|
|
112
120
|
/**
|
|
113
121
|
* Like the default Schema `NullOr` but with `withDefault` => null
|
|
114
122
|
*/
|
|
115
|
-
export const NullOr = <
|
|
123
|
+
export const NullOr = <Schema extends S.Top>(self: Schema) =>
|
|
116
124
|
pipe(
|
|
117
125
|
S.NullOr(self),
|
|
118
126
|
(s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => null)) })
|
|
119
127
|
)
|
|
120
128
|
|
|
121
|
-
export const defaultDate =
|
|
129
|
+
export const defaultDate = <Schema extends S.Top>(schema: Schema) =>
|
|
130
|
+
schema.pipe(withDefaultConstructor(() => new global.Date()))
|
|
122
131
|
|
|
123
|
-
export const defaultBool =
|
|
132
|
+
export const defaultBool = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => false))
|
|
124
133
|
|
|
125
|
-
export const defaultNullable = (
|
|
126
|
-
s: S.Top
|
|
127
|
-
) => s.pipe(withDefaultConstructor(() => null))
|
|
134
|
+
export const defaultNullable = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => null))
|
|
128
135
|
|
|
129
|
-
export const defaultArray =
|
|
136
|
+
export const defaultArray = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => []))
|
|
130
137
|
|
|
131
|
-
export const defaultMap =
|
|
138
|
+
export const defaultMap = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => new Map()))
|
|
132
139
|
|
|
133
|
-
export const defaultSet =
|
|
140
|
+
export const defaultSet = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => new Set()))
|
|
134
141
|
|
|
135
142
|
export const withDefaultMake = <Self extends S.Top>(s: Self) => {
|
|
136
143
|
const a = Object.assign(S.decodeSync(s as any) as WithDefaults<Self>, s)
|
|
@@ -240,29 +247,30 @@ export const transformToOrFail = <To extends S.Top, From extends S.Top, RD>(
|
|
|
240
247
|
)
|
|
241
248
|
)
|
|
242
249
|
|
|
243
|
-
// TODO: v4 migration — S.declare API changed (no [self] + decode/encode pattern)
|
|
244
|
-
// Need to find v4 equivalent for contextual schema wrapping
|
|
245
250
|
export const provide = <Self extends S.Top, R>(
|
|
246
251
|
self: Self,
|
|
247
252
|
context: ServiceMap.ServiceMap<R>
|
|
248
|
-
):
|
|
253
|
+
): ProvidedCodec<Self, R> => {
|
|
249
254
|
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")
|
|
255
|
+
return self.pipe(
|
|
256
|
+
S.middlewareDecoding((effect) => prov(effect)),
|
|
257
|
+
S.middlewareEncoding((effect) => prov(effect))
|
|
258
|
+
) as ProvidedCodec<Self, R>
|
|
268
259
|
}
|
|
260
|
+
export const contextFromServices = <
|
|
261
|
+
Self extends S.Top,
|
|
262
|
+
Tags extends ReadonlyArray<ServiceMap.Key<any, any>>
|
|
263
|
+
>(
|
|
264
|
+
self: Self,
|
|
265
|
+
...services: Tags
|
|
266
|
+
): Effect.Effect<
|
|
267
|
+
ProvidedCodec<Self, ServiceMap.Service.Identifier<Tags[number]>>,
|
|
268
|
+
never,
|
|
269
|
+
ServiceMap.Service.Identifier<Tags[number]>
|
|
270
|
+
> =>
|
|
271
|
+
Effect.gen(function*() {
|
|
272
|
+
const context: ServiceMap.ServiceMap<ServiceMap.Service.Identifier<Tags[number]>> = ServiceMap.pick(...services)(
|
|
273
|
+
yield* Effect.services<ServiceMap.Service.Identifier<Tags[number]>>()
|
|
274
|
+
)
|
|
275
|
+
return provide(self, context)
|
|
276
|
+
})
|
|
@@ -140,11 +140,10 @@ const minLength = 6
|
|
|
140
140
|
const maxLength = 50
|
|
141
141
|
const size = 21
|
|
142
142
|
const length = 10 * size
|
|
143
|
-
const StringIdArb = ():
|
|
143
|
+
const StringIdArb = (): S.LazyArbitrary<StringId> => (fc) =>
|
|
144
144
|
fc
|
|
145
145
|
.uint8Array({ minLength: length, maxLength: length })
|
|
146
|
-
.map((_
|
|
147
|
-
|
|
146
|
+
.map((_) => customRandom(urlAlphabet, size, (size) => _.subarray(0, size))() as StringId)
|
|
148
147
|
/**
|
|
149
148
|
* A string that is at least 6 characters long and a maximum of 50.
|
|
150
149
|
*/
|
|
@@ -155,7 +154,7 @@ export const StringId = extendM(
|
|
|
155
154
|
fromBrand(nominal<StringId>(), {
|
|
156
155
|
identifier: "StringId",
|
|
157
156
|
title: "StringId",
|
|
158
|
-
|
|
157
|
+
toArbitrary: () => (fc) => StringIdArb()(fc),
|
|
159
158
|
jsonSchema: {}
|
|
160
159
|
})
|
|
161
160
|
),
|
|
@@ -178,19 +177,21 @@ export function prefixedStringId<Brand extends StringId>() {
|
|
|
178
177
|
) => {
|
|
179
178
|
type FullPrefix = `${Prefix}${Separator}`
|
|
180
179
|
const pref = `${prefix}${separator ?? "-"}` as FullPrefix
|
|
181
|
-
const arb = ():
|
|
180
|
+
const arb = (): S.LazyArbitrary<string & Brand> => (fc) =>
|
|
182
181
|
StringIdArb()(fc).map(
|
|
183
|
-
(x
|
|
182
|
+
(x) => (pref + x.substring(0, 50 - pref.length)) as Brand
|
|
184
183
|
)
|
|
185
184
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
186
185
|
const s: S.Codec<string & Brand, string> = StringId
|
|
187
186
|
.pipe(
|
|
188
187
|
S.refine((x: string): x is string & Brand => x.startsWith(pref), {
|
|
189
|
-
arbitrary: arb,
|
|
190
188
|
identifier: name,
|
|
191
189
|
title: name
|
|
190
|
+
}),
|
|
191
|
+
S.annotate({
|
|
192
|
+
toArbitrary: () => (fc) => arb()(fc)
|
|
192
193
|
})
|
|
193
|
-
) as
|
|
194
|
+
) as S.Codec<string & Brand, string>
|
|
194
195
|
const schema = s.pipe(withDefaultMake)
|
|
195
196
|
const make = () => (pref + StringId.make().substring(0, 50 - pref.length)) as Brand
|
|
196
197
|
|
|
@@ -217,10 +218,10 @@ export const brandedStringId = <
|
|
|
217
218
|
Brand extends StringIdBrand
|
|
218
219
|
>() =>
|
|
219
220
|
withDefaultMake(
|
|
220
|
-
Object.assign(Object.create(StringId), StringId) as S.Codec<string & Brand> & {
|
|
221
|
+
Object.assign(Object.create(StringId), StringId) as S.Codec<string & Brand, string> & {
|
|
221
222
|
make: () => string & Brand
|
|
222
|
-
withDefault:
|
|
223
|
-
} & WithDefaults<S.Codec<string & Brand>>
|
|
223
|
+
withDefault: S.withConstructorDefault<S.Codec<string & Brand, string> & S.WithoutConstructorDefault>
|
|
224
|
+
} & WithDefaults<S.Codec<string & Brand, string>>
|
|
224
225
|
)
|
|
225
226
|
|
|
226
227
|
export interface PrefixedStringUtils<
|
|
@@ -232,7 +233,7 @@ export interface PrefixedStringUtils<
|
|
|
232
233
|
readonly unsafeFrom: (str: string) => Brand
|
|
233
234
|
prefixSafe: <REST extends string>(str: `${Prefix}${Separator}${REST}`) => Brand
|
|
234
235
|
readonly prefix: Prefix
|
|
235
|
-
readonly withDefault:
|
|
236
|
+
readonly withDefault: S.withConstructorDefault<S.Codec<Brand, string> & S.WithoutConstructorDefault>
|
|
236
237
|
}
|
|
237
238
|
|
|
238
239
|
export interface UrlBrand extends Simplify<B.Brand<"Url"> & NonEmptyStringBrand> {}
|
|
@@ -247,10 +248,12 @@ export const Url = S
|
|
|
247
248
|
.String
|
|
248
249
|
.pipe(
|
|
249
250
|
S.refine(isUrl, {
|
|
250
|
-
arbitrary: (): any => (fc: any) => fc.webUrl().map((_: any) => _ as Url),
|
|
251
251
|
identifier: "Url",
|
|
252
252
|
title: "Url",
|
|
253
253
|
jsonSchema: { format: "uri" }
|
|
254
254
|
}),
|
|
255
|
+
S.annotate({
|
|
256
|
+
toArbitrary: () => (fc) => fc.webUrl().map((_) => _ as Url)
|
|
257
|
+
}),
|
|
255
258
|
withDefaultMake
|
|
256
259
|
)
|
|
@@ -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,11 +1,10 @@
|
|
|
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"
|
|
@@ -40,127 +39,77 @@ 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
|
-
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)
|
|
68
|
+
const getTagLiteral = <Tag extends string>(schema: S.tag<Tag>): Tag => {
|
|
69
|
+
if (!SchemaAST.isLiteral(schema.ast)) {
|
|
70
|
+
throw new Error("Unsupported _tag schema: expected a literal AST")
|
|
98
71
|
}
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
|
|
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>>
|
|
72
|
+
return schema.ast.literal as Tag
|
|
107
73
|
}
|
|
108
74
|
|
|
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
75
|
export const tags = <
|
|
125
76
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
126
77
|
Members extends NonEmptyReadonlyArray<(S.Top & { fields: { _tag: S.tag<string> } })>
|
|
127
78
|
>(
|
|
128
79
|
self: Members
|
|
129
80
|
) =>
|
|
130
|
-
S.Literals(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
81
|
+
S.Literals(
|
|
82
|
+
self.map((key) => getTagLiteral(key.fields._tag)) as {
|
|
83
|
+
[Index in keyof Members]: S.Schema.Type<Members[Index]["fields"]["_tag"]>
|
|
84
|
+
}
|
|
85
|
+
) as S.Literals<
|
|
86
|
+
{
|
|
87
|
+
[Index in keyof Members]: S.Schema.Type<Members[Index]["fields"]["_tag"]>
|
|
88
|
+
}
|
|
89
|
+
>
|
|
90
|
+
|
|
91
|
+
type TaggedUnionMembers = NonEmptyReadonlyArray<
|
|
92
|
+
S.Top & { readonly Type: { readonly _tag: string }; fields: { _tag: S.tag<string> } }
|
|
93
|
+
>
|
|
94
|
+
|
|
95
|
+
type TaggedUnionTags<Members extends TaggedUnionMembers> = S.Literals<
|
|
96
|
+
{
|
|
97
|
+
[Index in keyof Members]: S.Schema.Type<Members[Index]["fields"]["_tag"]>
|
|
98
|
+
}
|
|
99
|
+
>
|
|
149
100
|
|
|
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
|
-
)
|
|
101
|
+
type TaggedUnionWithTags<Members extends TaggedUnionMembers> = S.toTaggedUnion<"_tag", Members> & {
|
|
102
|
+
readonly tags: TaggedUnionTags<Members>
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const extendTaggedUnionWithTags = <Members extends TaggedUnionMembers>(
|
|
106
|
+
schema: S.Union<Members>
|
|
107
|
+
): TaggedUnionWithTags<Members> => extendM(schema.pipe(S.toTaggedUnion("_tag")), () => ({ tags: tags(schema.members) }))
|
|
165
108
|
|
|
166
|
-
export
|
|
109
|
+
export const ExtendTaggedUnion = <Members extends TaggedUnionMembers>(
|
|
110
|
+
schema: S.Union<Members>
|
|
111
|
+
): TaggedUnionWithTags<Members> => extendTaggedUnionWithTags(schema)
|
|
112
|
+
|
|
113
|
+
export const TaggedUnion = <
|
|
114
|
+
Members extends TaggedUnionMembers
|
|
115
|
+
>(...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
|
}
|
|
@@ -50,17 +50,17 @@ export const HttpClientLayer = (config: ApiConfig) =>
|
|
|
50
50
|
Effect
|
|
51
51
|
.gen(function*() {
|
|
52
52
|
const baseClient = yield* HttpClient.HttpClient
|
|
53
|
-
const ctx = yield* RequestName
|
|
54
53
|
const client = baseClient.pipe(
|
|
55
54
|
HttpClient.mapRequest(HttpClientRequest.prependUrl(config.url + "/rpc")),
|
|
56
55
|
HttpClient.mapRequest(
|
|
57
56
|
HttpClientRequest.setHeaders(config.headers.pipe(Option.getOrElse(() => ({}))))
|
|
58
57
|
),
|
|
59
|
-
HttpClient.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
58
|
+
HttpClient.mapRequestEffect((req) =>
|
|
59
|
+
Effect.map(RequestName.asEffect(), (ctx) =>
|
|
60
|
+
flow(
|
|
61
|
+
HttpClientRequest.appendUrlParam("action", ctx.requestName),
|
|
62
|
+
HttpClientRequest.appendUrl("/" + ctx.moduleName)
|
|
63
|
+
)(req))
|
|
64
64
|
)
|
|
65
65
|
)
|
|
66
66
|
return client
|
|
@@ -76,7 +76,7 @@ export const HttpClientFromConfigLayer = Layer.unwrap(
|
|
|
76
76
|
|
|
77
77
|
export const RpcSerializationLayer = (config: ApiConfig) =>
|
|
78
78
|
Layer.mergeAll(
|
|
79
|
-
RpcSerialization.
|
|
79
|
+
RpcSerialization.layerNdjson,
|
|
80
80
|
HttpClientLayer(config)
|
|
81
81
|
)
|
|
82
82
|
|
|
@@ -91,7 +91,7 @@ const getFiltered = <M extends Requests>(resource: M) => {
|
|
|
91
91
|
// TODO: Record.filter
|
|
92
92
|
const filtered = typedKeysOf(resource).reduce((acc, cur) => {
|
|
93
93
|
if (
|
|
94
|
-
Predicate.
|
|
94
|
+
Predicate.isObjectKeyword(resource[cur])
|
|
95
95
|
&& (resource[cur].success)
|
|
96
96
|
) {
|
|
97
97
|
acc[cur as keyof Filtered] = resource[cur] as any
|
|
@@ -146,10 +146,7 @@ const makeRpcTag = <M extends Requests>(resource: M) => {
|
|
|
146
146
|
// Use Layer.effect directly (not TheClient.toLayer) so TypeScript properly excludes Scope
|
|
147
147
|
const layer = Layer.effect(
|
|
148
148
|
TheClient,
|
|
149
|
-
|
|
150
|
-
RpcClient.make(rpcs, { spanPrefix: "RpcClient." + meta.moduleName }),
|
|
151
|
-
(cl) => (cl as any)[meta.moduleName]
|
|
152
|
-
)
|
|
149
|
+
RpcClient.make(rpcs, { spanPrefix: "RpcClient." + meta.moduleName })
|
|
153
150
|
)
|
|
154
151
|
return Object.assign(TheClient, { layer })
|
|
155
152
|
}
|
|
@@ -188,7 +185,7 @@ const makeApiClientFactory = Effect
|
|
|
188
185
|
const filtered = getFiltered(resource)
|
|
189
186
|
return {
|
|
190
187
|
mr,
|
|
191
|
-
client:
|
|
188
|
+
client: typedKeysOf(filtered)
|
|
192
189
|
.reduce((prev, cur) => {
|
|
193
190
|
const h = filtered[cur]!
|
|
194
191
|
|
|
@@ -211,7 +208,7 @@ const makeApiClientFactory = Effect
|
|
|
211
208
|
const layers = requestLevelLayers.pipe(Layer.provideMerge(requestNameLayer))
|
|
212
209
|
|
|
213
210
|
const fields = Struct.omit(Request.fields, ["_tag"] as const)
|
|
214
|
-
const requestAttr = h._tag
|
|
211
|
+
const requestAttr = `${meta.moduleName}.${h._tag}`
|
|
215
212
|
// @ts-expect-error doc
|
|
216
213
|
prev[cur] = Object.keys(fields).length === 0
|
|
217
214
|
? {
|
|
@@ -246,7 +243,7 @@ const makeApiClientFactory = Effect
|
|
|
246
243
|
}
|
|
247
244
|
|
|
248
245
|
return prev
|
|
249
|
-
}, {} as Client<M, M["meta"]["moduleName"]>)
|
|
246
|
+
}, {} as Client<M, M["meta"]["moduleName"]>)
|
|
250
247
|
}
|
|
251
248
|
})
|
|
252
249
|
|