effect-app 4.0.0-beta.12 → 4.0.0-beta.121
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 +466 -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/{ServiceMap.d.ts → Context.d.ts} +14 -18
- 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 +104 -27
- 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 +48 -10
- package/dist/Schema/Class.d.ts.map +1 -1
- package/dist/Schema/Class.js +120 -16
- package/dist/Schema/SpecialJsonSchema.d.ts +33 -0
- package/dist/Schema/SpecialJsonSchema.d.ts.map +1 -0
- package/dist/Schema/SpecialJsonSchema.js +122 -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 +5 -1
- 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 +111 -46
- package/dist/Schema/ext.d.ts.map +1 -1
- package/dist/Schema/ext.js +114 -53
- package/dist/Schema/moreStrings.d.ts +19 -7
- package/dist/Schema/moreStrings.d.ts.map +1 -1
- package/dist/Schema/moreStrings.js +14 -9
- package/dist/Schema/numbers.d.ts +11 -11
- package/dist/Schema/numbers.d.ts.map +1 -1
- package/dist/Schema/numbers.js +10 -9
- 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 +74 -55
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +85 -64
- package/dist/client/apiClientFactory.d.ts +11 -28
- package/dist/client/apiClientFactory.d.ts.map +1 -1
- package/dist/client/apiClientFactory.js +14 -15
- package/dist/client/clientFor.d.ts +6 -5
- package/dist/client/clientFor.d.ts.map +1 -1
- package/dist/client/errors.d.ts +18 -9
- package/dist/client/errors.d.ts.map +1 -1
- package/dist/client/errors.js +35 -10
- package/dist/client/makeClient.d.ts +20 -15
- package/dist/client/makeClient.d.ts.map +1 -1
- package/dist/client/makeClient.js +32 -23
- package/dist/http/Request.d.ts.map +1 -1
- package/dist/http/Request.js +5 -5
- package/dist/ids.d.ts +2 -2
- package/dist/ids.d.ts.map +1 -1
- package/dist/ids.js +3 -2
- 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 +24 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +28 -2
- 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} +51 -60
- package/src/Effect.ts +11 -9
- package/src/Layer.ts +5 -4
- package/src/Pure.ts +17 -18
- package/src/Schema/Class.ts +157 -30
- package/src/Schema/SpecialJsonSchema.ts +137 -0
- package/src/Schema/SpecialOpenApi.ts +130 -0
- package/src/Schema/brand.ts +10 -3
- package/src/Schema/email.ts +10 -2
- package/src/Schema/ext.ts +191 -85
- package/src/Schema/moreStrings.ts +21 -11
- package/src/Schema/numbers.ts +9 -8
- package/src/Schema/phoneNumber.ts +8 -1
- package/src/Schema.ts +195 -104
- package/src/client/apiClientFactory.ts +24 -29
- package/src/client/clientFor.ts +6 -1
- package/src/client/errors.ts +34 -9
- package/src/client/makeClient.ts +121 -61
- package/src/http/Request.ts +7 -4
- package/src/ids.ts +2 -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 +30 -1
- 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 +28 -6
- package/test/schema.test.ts +504 -4
- package/test/secretURL.test.ts +157 -0
- package/test/special.test.ts +862 -0
- package/test/utils.test.ts +2 -2
- package/tsconfig.base.json +0 -1
- package/tsconfig.json +0 -1
- 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/numbers.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { Effect } from "effect"
|
|
1
2
|
import { extendM } from "effect-app/utils"
|
|
2
3
|
import * as S from "effect/Schema"
|
|
3
4
|
import type { Simplify } from "effect/Types"
|
|
4
5
|
import { fromBrand, nominal } from "./brand.js"
|
|
5
|
-
import {
|
|
6
|
+
import { withDefaultMake } from "./ext.js"
|
|
6
7
|
import { type B } from "./schema.js"
|
|
7
8
|
|
|
8
9
|
export interface PositiveIntBrand
|
|
@@ -14,7 +15,7 @@ export const PositiveInt = extendM(
|
|
|
14
15
|
fromBrand(nominal<PositiveInt>(), { identifier: "PositiveInt", title: "PositiveInt", jsonSchema: {} }),
|
|
15
16
|
withDefaultMake
|
|
16
17
|
),
|
|
17
|
-
(s) => ({ withDefault: s.pipe(
|
|
18
|
+
(s) => ({ withDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => s(1)))) })
|
|
18
19
|
)
|
|
19
20
|
export type PositiveInt = number & PositiveIntBrand
|
|
20
21
|
|
|
@@ -29,20 +30,20 @@ export const NonNegativeInt = extendM(
|
|
|
29
30
|
}),
|
|
30
31
|
withDefaultMake
|
|
31
32
|
),
|
|
32
|
-
(s) => ({ withDefault: s.pipe(
|
|
33
|
+
(s) => ({ withDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => s(0)))) })
|
|
33
34
|
)
|
|
34
35
|
export type NonNegativeInt = number & NonNegativeIntBrand
|
|
35
36
|
|
|
36
37
|
export interface IntBrand extends Simplify<B.Brand<"Int">> {}
|
|
37
38
|
export const Int = extendM(
|
|
38
39
|
S.Int.pipe(fromBrand(nominal<Int>(), { identifier: "Int", title: "Int", jsonSchema: {} }), withDefaultMake),
|
|
39
|
-
(s) => ({ withDefault: s.pipe(
|
|
40
|
+
(s) => ({ withDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => s(0)))) })
|
|
40
41
|
)
|
|
41
42
|
export type Int = number & IntBrand
|
|
42
43
|
|
|
43
44
|
export interface PositiveNumberBrand extends Simplify<B.Brand<"PositiveNumber"> & NonNegativeNumberBrand> {}
|
|
44
45
|
export const PositiveNumber = extendM(
|
|
45
|
-
S.
|
|
46
|
+
S.Finite.pipe(
|
|
46
47
|
S.check(S.isGreaterThan(0)),
|
|
47
48
|
fromBrand(nominal<PositiveNumber>(), {
|
|
48
49
|
identifier: "PositiveNumber",
|
|
@@ -51,14 +52,14 @@ export const PositiveNumber = extendM(
|
|
|
51
52
|
}),
|
|
52
53
|
withDefaultMake
|
|
53
54
|
),
|
|
54
|
-
(s) => ({ withDefault: s.pipe(
|
|
55
|
+
(s) => ({ withDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => s(1)))) })
|
|
55
56
|
)
|
|
56
57
|
export type PositiveNumber = number & PositiveNumberBrand
|
|
57
58
|
|
|
58
59
|
export interface NonNegativeNumberBrand extends Simplify<B.Brand<"NonNegativeNumber">> {}
|
|
59
60
|
export const NonNegativeNumber = extendM(
|
|
60
61
|
S
|
|
61
|
-
.
|
|
62
|
+
.Finite
|
|
62
63
|
.pipe(
|
|
63
64
|
S.check(S.isGreaterThanOrEqualTo(0)),
|
|
64
65
|
fromBrand(nominal<NonNegativeNumber>(), {
|
|
@@ -68,7 +69,7 @@ export const NonNegativeNumber = extendM(
|
|
|
68
69
|
}),
|
|
69
70
|
withDefaultMake
|
|
70
71
|
),
|
|
71
|
-
(s) => ({ withDefault: s.pipe(
|
|
72
|
+
(s) => ({ withDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => s(0)))) })
|
|
72
73
|
)
|
|
73
74
|
export type NonNegativeNumber = number & NonNegativeNumberBrand
|
|
74
75
|
|
|
@@ -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"
|
|
6
|
-
import { withDefaultMake } from "./Schema/ext.js"
|
|
7
|
-
import { PhoneNumber as PhoneNumberT } from "./Schema/phoneNumber.js"
|
|
8
|
-
import type { AST } from "./Schema/schema.js"
|
|
5
|
+
import { Email as EmailT, type Email as EmailType } from "./Schema/email.js"
|
|
6
|
+
import { concurrencyUnbounded, withDefaultMake } from "./Schema/ext.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, Literals, 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
|
|
|
@@ -33,6 +32,58 @@ export * as SchemaParser from "effect/SchemaParser"
|
|
|
33
32
|
|
|
34
33
|
export { Void as Void_ } from "effect/Schema"
|
|
35
34
|
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Struct / NonEmptyArray / Record — with concurrency: "unbounded"
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
export function Struct<const Fields extends S.Struct.Fields>(fields: Fields): S.Struct<Fields> {
|
|
40
|
+
const result = S.Struct(fields).annotate(concurrencyUnbounded)
|
|
41
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method, @typescript-eslint/no-unsafe-assignment
|
|
42
|
+
const origMapFields: any = result.mapFields
|
|
43
|
+
;(result as any).mapFields = function(this: any, f: any, options?: any) {
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
45
|
+
return origMapFields.call(this, f, options).annotate(concurrencyUnbounded)
|
|
46
|
+
}
|
|
47
|
+
return result
|
|
48
|
+
}
|
|
49
|
+
export interface Struct<Fields extends S.Struct.Fields> extends S.Struct<Fields> {}
|
|
50
|
+
export declare namespace Struct {
|
|
51
|
+
export type Fields = S.Struct.Fields
|
|
52
|
+
export type Type<F extends S.Struct.Fields> = S.Struct.Type<F>
|
|
53
|
+
export type Encoded<F extends S.Struct.Fields> = S.Struct.Encoded<F>
|
|
54
|
+
export type DecodingServices<F extends S.Struct.Fields> = S.Struct.DecodingServices<F>
|
|
55
|
+
export type EncodingServices<F extends S.Struct.Fields> = S.Struct.EncodingServices<F>
|
|
56
|
+
export type MakeIn<F extends S.Struct.Fields> = S.Struct.MakeIn<F>
|
|
57
|
+
export type Iso<F extends S.Struct.Fields> = S.Struct.Iso<F>
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function NonEmptyArray<Value extends S.Top>(value: Value): S.NonEmptyArray<Value> {
|
|
61
|
+
return S.NonEmptyArray(value).annotate(concurrencyUnbounded)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function TaggedStruct<const Tag extends SchemaAST.LiteralValue, const Fields extends S.Struct.Fields>(
|
|
65
|
+
value: Tag,
|
|
66
|
+
fields: Fields
|
|
67
|
+
): S.TaggedStruct<Tag, Fields> {
|
|
68
|
+
return Struct({ _tag: S.tag(value), ...fields }) as any
|
|
69
|
+
}
|
|
70
|
+
export type TaggedStruct<Tag extends SchemaAST.LiteralValue, Fields extends S.Struct.Fields> = S.TaggedStruct<
|
|
71
|
+
Tag,
|
|
72
|
+
Fields
|
|
73
|
+
>
|
|
74
|
+
|
|
75
|
+
export function Record<Key extends S.Record.Key, Value extends S.Top>(
|
|
76
|
+
key: Key,
|
|
77
|
+
value: Value
|
|
78
|
+
): S.$Record<Key, Value> {
|
|
79
|
+
return S.Record(key, value).annotate(concurrencyUnbounded)
|
|
80
|
+
}
|
|
81
|
+
export declare namespace Record {
|
|
82
|
+
export type Key = S.Record.Key
|
|
83
|
+
export type Type<K extends S.Record.Key, V extends S.Top> = S.Record.Type<K, V>
|
|
84
|
+
export type Encoded<K extends S.Record.Key, V extends S.Top> = S.Record.Encoded<K, V>
|
|
85
|
+
}
|
|
86
|
+
|
|
36
87
|
export const SpanId = Symbol()
|
|
37
88
|
export type SpanId = typeof SpanId
|
|
38
89
|
|
|
@@ -40,127 +91,167 @@ export interface WithOptionalSpan {
|
|
|
40
91
|
[SpanId]?: Tracer.Span
|
|
41
92
|
}
|
|
42
93
|
|
|
94
|
+
const makeEmail = S.decodeSync(EmailT as any) as (value: string) => EmailType
|
|
95
|
+
const makePhoneNumber = S.decodeSync(PhoneNumberT as any) as (value: string) => PhoneNumberType
|
|
96
|
+
|
|
43
97
|
export const Email = EmailT
|
|
44
98
|
.pipe(
|
|
45
99
|
S.annotate({
|
|
46
100
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
47
|
-
|
|
101
|
+
toArbitrary: () => (fc) => fakerArb((faker) => faker.internet.exampleEmail)(fc).map(makeEmail)
|
|
48
102
|
}),
|
|
49
103
|
withDefaultMake
|
|
50
104
|
)
|
|
51
105
|
|
|
52
|
-
export type Email =
|
|
106
|
+
export type Email = EmailType
|
|
53
107
|
|
|
54
108
|
export const PhoneNumber = PhoneNumberT
|
|
55
109
|
.pipe(
|
|
56
110
|
S.annotate({
|
|
57
|
-
|
|
111
|
+
toArbitrary: () => (fc) =>
|
|
58
112
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
59
|
-
fakerArb((faker) => faker.phone.number)(fc).map(
|
|
113
|
+
fakerArb((faker) => faker.phone.number)(fc).map(makePhoneNumber)
|
|
60
114
|
}),
|
|
61
115
|
withDefaultMake
|
|
62
116
|
)
|
|
63
117
|
|
|
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
|
-
}
|
|
118
|
+
export type PhoneNumber = PhoneNumberType
|
|
91
119
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
):
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
throw new Error("Unsupported")
|
|
120
|
+
// Copied from SchemaAST.collectSentinels (marked @internal in effect).
|
|
121
|
+
// Returns all { key, literal } pairs that can discriminate a union member.
|
|
122
|
+
const getTagFromAST = (schema: S.Top): string => {
|
|
123
|
+
const sentinels = collectSentinelsFromAST(schema.ast)
|
|
124
|
+
const sentinel = sentinels.find((s) => s.key === "_tag")
|
|
125
|
+
if (sentinel !== undefined && typeof sentinel.literal === "string") return sentinel.literal
|
|
126
|
+
throw new Error("No _tag literal found on schema member")
|
|
100
127
|
}
|
|
101
128
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
129
|
+
function collectSentinelsFromAST(
|
|
130
|
+
ast: SchemaAST.AST
|
|
131
|
+
): Array<{ key: PropertyKey; literal: SchemaAST.LiteralValue | symbol }> {
|
|
132
|
+
switch (ast._tag) {
|
|
133
|
+
case "Declaration": {
|
|
134
|
+
const s = ast.annotations?.["~sentinels"]
|
|
135
|
+
return Array.isArray(s) ? s : []
|
|
136
|
+
}
|
|
137
|
+
case "Objects":
|
|
138
|
+
return ast.propertySignatures.flatMap(
|
|
139
|
+
(ps): Array<{ key: PropertyKey; literal: SchemaAST.LiteralValue | symbol }> => {
|
|
140
|
+
const type = ps.type
|
|
141
|
+
if (!SchemaAST.isOptional(type)) {
|
|
142
|
+
if (SchemaAST.isLiteral(type)) return [{ key: ps.name, literal: type.literal }]
|
|
143
|
+
if (SchemaAST.isUniqueSymbol(type)) return [{ key: ps.name, literal: type.symbol }]
|
|
144
|
+
}
|
|
145
|
+
return []
|
|
146
|
+
}
|
|
147
|
+
)
|
|
148
|
+
case "Suspend":
|
|
149
|
+
return collectSentinelsFromAST(ast.thunk())
|
|
150
|
+
default:
|
|
151
|
+
return []
|
|
152
|
+
}
|
|
107
153
|
}
|
|
108
154
|
|
|
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[tag] = key as any
|
|
120
|
-
return acc
|
|
121
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
122
|
-
}, {} as any)
|
|
123
|
-
|
|
124
155
|
export const tags = <
|
|
125
|
-
|
|
126
|
-
Members extends NonEmptyReadonlyArray<(S.Top & { fields: { _tag: S.tag<string> } })>
|
|
156
|
+
Members extends NonEmptyReadonlyArray<(S.Top & { readonly Type: { readonly _tag: string } })>
|
|
127
157
|
>(
|
|
128
158
|
self: Members
|
|
129
159
|
) =>
|
|
130
|
-
S.Literals(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
schema: S.Codec<A, I, R>
|
|
140
|
-
) =>
|
|
141
|
-
extendM(
|
|
142
|
-
schema,
|
|
143
|
-
(_) => ({
|
|
144
|
-
is: S.is(schema as any),
|
|
145
|
-
isA: makeIs(_ as any),
|
|
146
|
-
isAnyOf: makeIsAnyOf(_ as any) /*, map: taggedUnionMap(a) */
|
|
147
|
-
})
|
|
148
|
-
)
|
|
160
|
+
S.Literals(
|
|
161
|
+
self.map(getTagFromAST) as {
|
|
162
|
+
[Index in keyof Members]: Members[Index]["Type"]["_tag"]
|
|
163
|
+
}
|
|
164
|
+
) as S.Literals<
|
|
165
|
+
{
|
|
166
|
+
[Index in keyof Members]: Members[Index]["Type"]["_tag"]
|
|
167
|
+
}
|
|
168
|
+
>
|
|
149
169
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
170
|
+
type TaggedUnionMembers = NonEmptyReadonlyArray<
|
|
171
|
+
S.Top & { readonly Type: { readonly _tag: string } }
|
|
172
|
+
>
|
|
173
|
+
|
|
174
|
+
type TaggedUnionTags<Members extends TaggedUnionMembers> = S.Literals<
|
|
175
|
+
{
|
|
176
|
+
[Index in keyof Members]: Members[Index]["Type"]["_tag"]
|
|
177
|
+
}
|
|
178
|
+
>
|
|
179
|
+
|
|
180
|
+
type TaggedPropertyKeys<A, Members extends TaggedUnionMembers> = {
|
|
181
|
+
[K in keyof A & string]: A[K] extends Members[number]["Type"] ? K : never
|
|
182
|
+
}[keyof A & string]
|
|
183
|
+
|
|
184
|
+
type PropertyGuardsFor<
|
|
185
|
+
Members extends TaggedUnionMembers,
|
|
186
|
+
K extends string,
|
|
187
|
+
A
|
|
188
|
+
> =
|
|
189
|
+
& {
|
|
190
|
+
readonly [M in Members[number] as `is${M["Type"]["_tag"]}`]: (
|
|
191
|
+
target: A
|
|
192
|
+
) => target is A & { readonly [P in K]: M["Type"] }
|
|
193
|
+
}
|
|
194
|
+
& {
|
|
195
|
+
readonly isAnyOf: <const Tags extends ReadonlyArray<Members[number]["Type"]["_tag"]>>(
|
|
196
|
+
tags: Tags
|
|
197
|
+
) => (
|
|
198
|
+
target: A
|
|
199
|
+
) => target is A & { readonly [P in K]: Extract<Members[number]["Type"], { readonly _tag: Tags[number] }> }
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
type PropertyGuards<
|
|
203
|
+
Members extends TaggedUnionMembers,
|
|
204
|
+
K extends string
|
|
205
|
+
> =
|
|
206
|
+
& {
|
|
207
|
+
readonly [M in Members[number] as `is${M["Type"]["_tag"]}`]: <
|
|
208
|
+
T extends { readonly [P in K]: Members[number]["Type"] }
|
|
209
|
+
>(target: T) => target is T & { readonly [P in K]: M["Type"] }
|
|
210
|
+
}
|
|
211
|
+
& {
|
|
212
|
+
readonly isAnyOf: <const Tags extends ReadonlyArray<Members[number]["Type"]["_tag"]>>(
|
|
213
|
+
tags: Tags
|
|
214
|
+
) => <T extends { readonly [P in K]: Members[number]["Type"] }>(
|
|
215
|
+
target: T
|
|
216
|
+
) => target is T & { readonly [P in K]: Extract<Members[number]["Type"], { readonly _tag: Tags[number] }> }
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
type TaggedUnionWithTags<Members extends TaggedUnionMembers> = S.toTaggedUnion<"_tag", Members> & {
|
|
220
|
+
readonly tags: TaggedUnionTags<Members>
|
|
221
|
+
readonly generateGuards: <K extends string>(property: K) => PropertyGuards<Members, K>
|
|
222
|
+
readonly generateGuardsFor: <A>() => <K extends TaggedPropertyKeys<A, Members>>(
|
|
223
|
+
property: K
|
|
224
|
+
) => PropertyGuardsFor<Members, K, A>
|
|
225
|
+
}
|
|
165
226
|
|
|
166
|
-
|
|
227
|
+
const extendTaggedUnionWithTags = <Members extends TaggedUnionMembers>(
|
|
228
|
+
schema: S.Union<Members>
|
|
229
|
+
): TaggedUnionWithTags<Members> =>
|
|
230
|
+
extendM(schema.pipe(S.toTaggedUnion("_tag")), (tagged) => {
|
|
231
|
+
const makeGuards = (property: string) => {
|
|
232
|
+
const result: any = {}
|
|
233
|
+
const guards: Record<string, (u: unknown) => boolean> = tagged.guards
|
|
234
|
+
for (const tag of Object.keys(guards)) {
|
|
235
|
+
const guard = guards[tag]!
|
|
236
|
+
result[`is${tag}`] = (target: any) => guard(target[property])
|
|
237
|
+
}
|
|
238
|
+
result.isAnyOf = (memberTags: Array<string>) => {
|
|
239
|
+
const check = tagged.isAnyOf(memberTags)
|
|
240
|
+
return (target: any) => check(target[property])
|
|
241
|
+
}
|
|
242
|
+
return result
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
tags: tags(schema.members),
|
|
246
|
+
generateGuards: makeGuards,
|
|
247
|
+
generateGuardsFor: () => makeGuards
|
|
248
|
+
}
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
export const ExtendTaggedUnion = <Members extends TaggedUnionMembers>(
|
|
252
|
+
schema: S.Union<Members>
|
|
253
|
+
): TaggedUnionWithTags<Members> => extendTaggedUnionWithTags(schema)
|
|
254
|
+
|
|
255
|
+
export const TaggedUnion = <
|
|
256
|
+
Members extends TaggedUnionMembers
|
|
257
|
+
>(...a: Members): TaggedUnionWithTags<Members> => extendTaggedUnionWithTags(S.Union(a))
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import * as Config from "effect/Config"
|
|
3
2
|
import { flow } from "effect/Function"
|
|
4
3
|
import * as Layer from "effect/Layer"
|
|
5
4
|
import * as ManagedRuntime from "effect/ManagedRuntime"
|
|
@@ -7,13 +6,14 @@ import * as Predicate from "effect/Predicate"
|
|
|
7
6
|
import * as Schema from "effect/Schema"
|
|
8
7
|
import * as Struct from "effect/Struct"
|
|
9
8
|
import { Rpc, RpcClient, RpcGroup, RpcSerialization } from "effect/unstable/rpc"
|
|
9
|
+
import * as Config from "../Config.js"
|
|
10
|
+
import * as Context from "../Context.js"
|
|
10
11
|
import * as Effect from "../Effect.js"
|
|
11
12
|
import { HttpClient, HttpClientRequest } from "../http.js"
|
|
12
13
|
import * as Option from "../Option.js"
|
|
13
14
|
import type * as S from "../Schema.js"
|
|
14
|
-
import * as ServiceMap from "../ServiceMap.js"
|
|
15
15
|
import { typedKeysOf, typedValuesOf } from "../utils.js"
|
|
16
|
-
import type { Client, ClientForOptions,
|
|
16
|
+
import type { Client, ClientForOptions, ExtractModuleName, RequestsAny } from "./clientFor.js"
|
|
17
17
|
|
|
18
18
|
export interface ApiConfig {
|
|
19
19
|
url: string
|
|
@@ -37,10 +37,12 @@ export type Req = S.Top & {
|
|
|
37
37
|
success: S.Top
|
|
38
38
|
error: S.Top
|
|
39
39
|
config?: Record<string, any>
|
|
40
|
+
readonly id: string
|
|
41
|
+
readonly moduleName: string
|
|
40
42
|
readonly "~decodingServices"?: unknown
|
|
41
43
|
}
|
|
42
44
|
|
|
43
|
-
class RequestName extends
|
|
45
|
+
class RequestName extends Context.Reference("RequestName", {
|
|
44
46
|
defaultValue: () => ({ requestName: "Unspecified", moduleName: "Error" })
|
|
45
47
|
}) {}
|
|
46
48
|
|
|
@@ -84,7 +86,7 @@ type RpcHandlers<M extends RequestsAny> = {
|
|
|
84
86
|
[K in keyof M]: Rpc.Rpc<M[K]["_tag"], M[K], M[K]["success"], M[K]["error"]>
|
|
85
87
|
}
|
|
86
88
|
|
|
87
|
-
const getFiltered = <M extends
|
|
89
|
+
const getFiltered = <M extends RequestsAny>(resource: M) => {
|
|
88
90
|
type Filtered = {
|
|
89
91
|
[K in keyof M as M[K] extends Req ? K : never]: M[K] extends Req ? M[K] : never
|
|
90
92
|
}
|
|
@@ -102,13 +104,13 @@ const getFiltered = <M extends Requests>(resource: M) => {
|
|
|
102
104
|
return filtered as unknown as Filtered
|
|
103
105
|
}
|
|
104
106
|
|
|
105
|
-
export const getMeta = <M extends
|
|
106
|
-
const
|
|
107
|
-
if (
|
|
108
|
-
|
|
107
|
+
export const getMeta = <M extends RequestsAny>(resource: M): { moduleName: ExtractModuleName<M> } => {
|
|
108
|
+
const first = typedValuesOf(getFiltered(resource))[0]
|
|
109
|
+
if (first && "moduleName" in first) return { moduleName: first.moduleName } as any
|
|
110
|
+
throw new Error("No moduleName on requests!")
|
|
109
111
|
}
|
|
110
112
|
|
|
111
|
-
export const makeRpcGroupFromRequestsAndModuleName = <M extends
|
|
113
|
+
export const makeRpcGroupFromRequestsAndModuleName = <M extends RequestsAny, const ModuleName extends string>(
|
|
112
114
|
resource: M,
|
|
113
115
|
moduleName: ModuleName
|
|
114
116
|
) => {
|
|
@@ -126,20 +128,13 @@ export const makeRpcGroupFromRequestsAndModuleName = <M extends Requests, const
|
|
|
126
128
|
return rpcs
|
|
127
129
|
}
|
|
128
130
|
|
|
129
|
-
|
|
130
|
-
M extends Requests,
|
|
131
|
-
const ModuleName extends string
|
|
132
|
-
>(
|
|
133
|
-
resource: M & { meta: { moduleName: ModuleName } }
|
|
134
|
-
) => makeRpcGroupFromRequestsAndModuleName(resource, resource.meta.moduleName)
|
|
135
|
-
|
|
136
|
-
const makeRpcTag = <M extends Requests>(resource: M) => {
|
|
131
|
+
const makeRpcTag = <M extends RequestsAny>(resource: M) => {
|
|
137
132
|
const meta = getMeta(resource)
|
|
138
133
|
const rpcs = makeRpcGroupFromRequestsAndModuleName(resource, meta.moduleName)
|
|
139
134
|
|
|
140
135
|
// Use Object.assign instead of class extension to avoid TS2509 with complex generic return types.
|
|
141
136
|
// The first type arg is `any` because this is a dynamically created tag — its identity is the string key.
|
|
142
|
-
const TheClient =
|
|
137
|
+
const TheClient = Context.Opaque<
|
|
143
138
|
any,
|
|
144
139
|
RpcClient.RpcClient<RpcGroup.Rpcs<typeof rpcs>>
|
|
145
140
|
>()(`RpcClient.${meta.moduleName}`)
|
|
@@ -153,8 +148,8 @@ const makeRpcTag = <M extends Requests>(resource: M) => {
|
|
|
153
148
|
|
|
154
149
|
const makeApiClientFactory = Effect
|
|
155
150
|
.gen(function*() {
|
|
156
|
-
const ctx = yield* Effect.
|
|
157
|
-
const makeClientFor = <M extends
|
|
151
|
+
const ctx = yield* Effect.context<RpcSerialization.RpcSerialization | HttpClient.HttpClient>()
|
|
152
|
+
const makeClientFor = <M extends RequestsAny>(
|
|
158
153
|
resource: M,
|
|
159
154
|
requestLevelLayers = Layer.empty,
|
|
160
155
|
options?: ClientForOptions
|
|
@@ -176,7 +171,7 @@ const makeApiClientFactory = Effect
|
|
|
176
171
|
url: "" // why not here set meta.moduleName as root?
|
|
177
172
|
})
|
|
178
173
|
.pipe(
|
|
179
|
-
Layer.provideMerge(Layer.
|
|
174
|
+
Layer.provideMerge(Layer.succeedContext(ctx))
|
|
180
175
|
)
|
|
181
176
|
)
|
|
182
177
|
)
|
|
@@ -212,7 +207,7 @@ const makeApiClientFactory = Effect
|
|
|
212
207
|
// @ts-expect-error doc
|
|
213
208
|
prev[cur] = Object.keys(fields).length === 0
|
|
214
209
|
? {
|
|
215
|
-
handler: mr.
|
|
210
|
+
handler: mr.contextEffect.pipe(
|
|
216
211
|
Effect.flatMap((svcs) =>
|
|
217
212
|
TheClient
|
|
218
213
|
.use((client) => (client as any)[requestAttr]!(new Request()) as Effect.Effect<any, any, never>)
|
|
@@ -226,7 +221,7 @@ const makeApiClientFactory = Effect
|
|
|
226
221
|
}
|
|
227
222
|
: {
|
|
228
223
|
handler: (req: any) =>
|
|
229
|
-
mr.
|
|
224
|
+
mr.contextEffect.pipe(
|
|
230
225
|
Effect.flatMap((svcs) =>
|
|
231
226
|
TheClient
|
|
232
227
|
.use((client) =>
|
|
@@ -243,7 +238,7 @@ const makeApiClientFactory = Effect
|
|
|
243
238
|
}
|
|
244
239
|
|
|
245
240
|
return prev
|
|
246
|
-
}, {} as Client<M, M
|
|
241
|
+
}, {} as Client<M, ExtractModuleName<M>>)
|
|
247
242
|
}
|
|
248
243
|
})
|
|
249
244
|
|
|
@@ -259,9 +254,9 @@ const makeApiClientFactory = Effect
|
|
|
259
254
|
cacheL.set(requestLevelLayers, cache)
|
|
260
255
|
}
|
|
261
256
|
|
|
262
|
-
return <M extends
|
|
257
|
+
return <M extends RequestsAny>(
|
|
263
258
|
models: M
|
|
264
|
-
): Effect.Effect<Client<M, M
|
|
259
|
+
): Effect.Effect<Client<M, ExtractModuleName<M>>> =>
|
|
265
260
|
Effect.gen(function*() {
|
|
266
261
|
const found = cache.get(models)
|
|
267
262
|
if (found) {
|
|
@@ -281,7 +276,7 @@ const makeApiClientFactory = Effect
|
|
|
281
276
|
* Used to create clients for resource modules.
|
|
282
277
|
*/
|
|
283
278
|
export class ApiClientFactory
|
|
284
|
-
extends
|
|
279
|
+
extends Context.Opaque<ApiClientFactory, Effect.Success<typeof makeApiClientFactory>>()("ApiClientFactory")
|
|
285
280
|
{
|
|
286
281
|
static readonly layer = (config: ApiConfig) =>
|
|
287
282
|
ApiClientFactory.toLayer(makeApiClientFactory).pipe(Layer.provide(RpcSerializationLayer(config)))
|
|
@@ -294,7 +289,7 @@ export class ApiClientFactory
|
|
|
294
289
|
|
|
295
290
|
static readonly makeFor =
|
|
296
291
|
(requestLevelLayers: Layer.Layer<never, never, never>, options?: ClientForOptions) =>
|
|
297
|
-
<M extends
|
|
292
|
+
<M extends RequestsAny>(
|
|
298
293
|
resource: M
|
|
299
294
|
) =>
|
|
300
295
|
ApiClientFactory.use((apiClientFactory) => {
|
package/src/client/clientFor.ts
CHANGED
|
@@ -48,9 +48,14 @@ export function makePathWithBody(
|
|
|
48
48
|
return path.build(pars, { ignoreSearch: true, ignoreConstraints: true })
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
export type Requests
|
|
51
|
+
export type Requests = RequestsAny
|
|
52
52
|
export type RequestsAny = Record<string, any>
|
|
53
53
|
|
|
54
|
+
export type ExtractModuleName<M extends RequestsAny> =
|
|
55
|
+
{ [K in keyof M]: M[K] extends { moduleName: infer N extends string } ? N : never }[keyof M] extends
|
|
56
|
+
infer R extends string ? R
|
|
57
|
+
: string
|
|
58
|
+
|
|
54
59
|
export type Client<M extends RequestsAny, ModuleName extends string> = RequestHandlers<
|
|
55
60
|
never,
|
|
56
61
|
never,
|