effect-app 4.0.0-beta.7 → 4.0.0-beta.71
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 +313 -0
- package/dist/Config/SecretURL.js +2 -2
- package/dist/Config.d.ts +7 -0
- package/dist/Config.d.ts.map +1 -0
- package/dist/Config.js +6 -0
- package/dist/ConfigProvider.d.ts +39 -0
- package/dist/ConfigProvider.d.ts.map +1 -0
- package/dist/ConfigProvider.js +42 -0
- package/dist/Context.d.ts +40 -0
- package/dist/Context.d.ts.map +1 -0
- package/dist/Context.js +66 -0
- package/dist/Effect.d.ts +8 -7
- package/dist/Effect.d.ts.map +1 -1
- package/dist/Effect.js +3 -2
- package/dist/Layer.d.ts +5 -4
- package/dist/Layer.d.ts.map +1 -1
- package/dist/Layer.js +1 -1
- package/dist/Operations.d.ts +61 -25
- package/dist/Operations.d.ts.map +1 -1
- package/dist/Pure.d.ts +2 -2
- package/dist/Pure.d.ts.map +1 -1
- package/dist/Pure.js +13 -13
- package/dist/Schema/Class.d.ts +39 -1
- package/dist/Schema/Class.d.ts.map +1 -1
- package/dist/Schema/Class.js +89 -12
- package/dist/Schema/SpecialJsonSchema.d.ts +21 -0
- package/dist/Schema/SpecialJsonSchema.d.ts.map +1 -0
- package/dist/Schema/SpecialJsonSchema.js +59 -0
- package/dist/Schema/SpecialOpenApi.d.ts +32 -0
- package/dist/Schema/SpecialOpenApi.d.ts.map +1 -0
- package/dist/Schema/SpecialOpenApi.js +123 -0
- package/dist/Schema/brand.d.ts +8 -5
- package/dist/Schema/brand.d.ts.map +1 -1
- package/dist/Schema/brand.js +1 -1
- package/dist/Schema/email.d.ts.map +1 -1
- package/dist/Schema/email.js +9 -4
- package/dist/Schema/ext.d.ts +81 -45
- package/dist/Schema/ext.d.ts.map +1 -1
- package/dist/Schema/ext.js +94 -49
- package/dist/Schema/moreStrings.d.ts +6 -6
- 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 +22 -55
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +43 -64
- package/dist/client/apiClientFactory.d.ts +3 -3
- package/dist/client/apiClientFactory.d.ts.map +1 -1
- package/dist/client/apiClientFactory.js +14 -15
- package/dist/client/errors.d.ts +17 -9
- package/dist/client/errors.d.ts.map +1 -1
- package/dist/client/errors.js +35 -10
- package/dist/client/makeClient.d.ts +13 -12
- package/dist/client/makeClient.d.ts.map +1 -1
- package/dist/client/makeClient.js +5 -2
- package/dist/http/Request.d.ts.map +1 -1
- package/dist/http/Request.js +5 -5
- package/dist/ids.d.ts +3 -3
- 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 +18 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +24 -5
- package/package.json +29 -17
- package/src/Config/SecretURL.ts +1 -1
- package/src/Config.ts +14 -0
- package/src/ConfigProvider.ts +48 -0
- package/src/{ServiceMap.ts → Context.ts} +57 -64
- package/src/Effect.ts +11 -9
- package/src/Layer.ts +5 -4
- package/src/Pure.ts +17 -18
- package/src/Schema/Class.ts +114 -16
- package/src/Schema/SpecialJsonSchema.ts +69 -0
- package/src/Schema/SpecialOpenApi.ts +130 -0
- package/src/Schema/brand.ts +13 -7
- package/src/Schema/email.ts +10 -2
- package/src/Schema/ext.ts +182 -80
- package/src/Schema/moreStrings.ts +20 -10
- package/src/Schema/numbers.ts +9 -8
- package/src/Schema/phoneNumber.ts +8 -1
- package/src/Schema.ts +79 -103
- package/src/client/apiClientFactory.ts +18 -18
- package/src/client/errors.ts +46 -12
- package/src/client/makeClient.ts +32 -12
- package/src/http/Request.ts +7 -4
- package/src/ids.ts +3 -2
- package/src/index.ts +3 -11
- package/src/middleware.ts +2 -2
- package/src/rpc/MiddlewareMaker.ts +8 -7
- package/src/rpc/RpcContextMap.ts +6 -5
- package/src/rpc/RpcMiddleware.ts +5 -4
- package/src/utils/gen.ts +1 -1
- package/src/utils/logger.ts +2 -2
- package/src/utils.ts +26 -4
- package/test/dist/moreStrings.test.d.ts.map +1 -0
- package/test/dist/rpc.test.d.ts.map +1 -1
- package/test/dist/secretURL.test.d.ts.map +1 -0
- package/test/dist/special.test.d.ts.map +1 -0
- package/test/moreStrings.test.ts +17 -0
- package/test/rpc.test.ts +26 -5
- package/test/schema.test.ts +396 -3
- package/test/secretURL.test.ts +157 -0
- package/test/special.test.ts +732 -0
- package/test/utils.test.ts +1 -1
- package/tsconfig.base.json +0 -1
- package/tsconfig.json +0 -1
- package/dist/ServiceMap.d.ts +0 -44
- package/dist/ServiceMap.d.ts.map +0 -1
- package/dist/ServiceMap.js +0 -91
- package/dist/Struct.d.ts +0 -44
- package/dist/Struct.d.ts.map +0 -1
- package/dist/Struct.js +0 -29
- package/src/Struct.ts +0 -54
package/src/Schema.ts
CHANGED
|
@@ -1,22 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SchemaAST, type Tracer } from "effect"
|
|
2
2
|
import * as S from "effect/Schema"
|
|
3
3
|
import type { NonEmptyReadonlyArray } from "./Array.js"
|
|
4
4
|
import { fakerArb } from "./faker.js"
|
|
5
|
-
import { Email as EmailT } from "./Schema/email.js"
|
|
5
|
+
import { Email as EmailT, type Email as EmailType } from "./Schema/email.js"
|
|
6
6
|
import { withDefaultMake } from "./Schema/ext.js"
|
|
7
|
-
import { PhoneNumber as PhoneNumberT } from "./Schema/phoneNumber.js"
|
|
8
|
-
import type { AST } from "./Schema/schema.js"
|
|
7
|
+
import { PhoneNumber as PhoneNumberT, type PhoneNumber as PhoneNumberType } from "./Schema/phoneNumber.js"
|
|
9
8
|
import { extendM } from "./utils.js"
|
|
10
9
|
|
|
11
10
|
export * from "effect/Schema"
|
|
12
|
-
// v4: TaggedError renamed to TaggedErrorClass
|
|
13
|
-
export { TaggedErrorClass as TaggedError } from "effect/Schema"
|
|
14
11
|
|
|
15
12
|
export * from "./Schema/Class.js"
|
|
16
13
|
export { Class, TaggedClass } from "./Schema/Class.js"
|
|
17
14
|
|
|
18
15
|
export { fromBrand, nominal } from "./Schema/brand.js"
|
|
19
|
-
export { Array, Boolean, Date,
|
|
16
|
+
export { Array, Boolean, Date, DateFromString, DateValid, Finite, Literal, NullOr, Number, ReadonlyMap, ReadonlySet } from "./Schema/ext.js"
|
|
20
17
|
export { Int, NonNegativeInt } from "./Schema/numbers.js"
|
|
21
18
|
|
|
22
19
|
export * from "./Schema/email.js"
|
|
@@ -25,6 +22,8 @@ export * from "./Schema/moreStrings.js"
|
|
|
25
22
|
export * from "./Schema/numbers.js"
|
|
26
23
|
export * from "./Schema/phoneNumber.js"
|
|
27
24
|
export * from "./Schema/schema.js"
|
|
25
|
+
export * from "./Schema/SpecialJsonSchema.js"
|
|
26
|
+
export * from "./Schema/SpecialOpenApi.js"
|
|
28
27
|
export * from "./Schema/strings.js"
|
|
29
28
|
export { NonEmptyString } from "./Schema/strings.js"
|
|
30
29
|
|
|
@@ -40,127 +39,104 @@ export interface WithOptionalSpan {
|
|
|
40
39
|
[SpanId]?: Tracer.Span
|
|
41
40
|
}
|
|
42
41
|
|
|
42
|
+
const makeEmail = S.decodeSync(EmailT as any) as (value: string) => EmailType
|
|
43
|
+
const makePhoneNumber = S.decodeSync(PhoneNumberT as any) as (value: string) => PhoneNumberType
|
|
44
|
+
|
|
43
45
|
export const Email = EmailT
|
|
44
46
|
.pipe(
|
|
45
47
|
S.annotate({
|
|
46
48
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
47
|
-
|
|
49
|
+
toArbitrary: () => (fc) => fakerArb((faker) => faker.internet.exampleEmail)(fc).map(makeEmail)
|
|
48
50
|
}),
|
|
49
51
|
withDefaultMake
|
|
50
52
|
)
|
|
51
53
|
|
|
52
|
-
export type Email =
|
|
54
|
+
export type Email = EmailType
|
|
53
55
|
|
|
54
56
|
export const PhoneNumber = PhoneNumberT
|
|
55
57
|
.pipe(
|
|
56
58
|
S.annotate({
|
|
57
|
-
|
|
59
|
+
toArbitrary: () => (fc) =>
|
|
58
60
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
59
|
-
fakerArb((faker) => faker.phone.number)(fc).map(
|
|
61
|
+
fakerArb((faker) => faker.phone.number)(fc).map(makePhoneNumber)
|
|
60
62
|
}),
|
|
61
63
|
withDefaultMake
|
|
62
64
|
)
|
|
63
65
|
|
|
64
|
-
export
|
|
65
|
-
schema: S.Codec<A, I, R>
|
|
66
|
-
) => {
|
|
67
|
-
// In v4, transformations are stored as encoding on nodes, not as wrapper nodes.
|
|
68
|
-
// Union member ASTs are directly Objects (TypeLiteral equivalent).
|
|
69
|
-
if (SchemaAST.isUnion(schema.ast)) {
|
|
70
|
-
return schema.ast.types.reduce((acc: any, t: AST.AST) => {
|
|
71
|
-
if (!SchemaAST.isObjects(t)) return acc
|
|
72
|
-
const tag = Array.findFirst(t.propertySignatures, (_: any) => {
|
|
73
|
-
if (_.name === "_tag" && SchemaAST.isLiteral(_.type)) {
|
|
74
|
-
return Option.some(_.type)
|
|
75
|
-
}
|
|
76
|
-
return Option.none()
|
|
77
|
-
})
|
|
78
|
-
const ast = Option.getOrUndefined(tag)
|
|
79
|
-
if (!ast) {
|
|
80
|
-
return acc
|
|
81
|
-
}
|
|
82
|
-
return {
|
|
83
|
-
...acc,
|
|
84
|
-
[String((ast as SchemaAST.Literal).literal)]: (x: { _tag: string }) =>
|
|
85
|
-
x._tag === (ast as SchemaAST.Literal).literal
|
|
86
|
-
}
|
|
87
|
-
}, {} as Is<A>)
|
|
88
|
-
}
|
|
89
|
-
throw new Error("Unsupported")
|
|
90
|
-
}
|
|
66
|
+
export type PhoneNumber = PhoneNumberType
|
|
91
67
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
):
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
throw new Error("Unsupported")
|
|
68
|
+
// Copied from SchemaAST.collectSentinels (marked @internal in effect).
|
|
69
|
+
// Returns all { key, literal } pairs that can discriminate a union member.
|
|
70
|
+
const getTagFromAST = (schema: S.Top): string => {
|
|
71
|
+
const sentinels = collectSentinelsFromAST(schema.ast)
|
|
72
|
+
const sentinel = sentinels.find((s) => s.key === "_tag")
|
|
73
|
+
if (sentinel !== undefined && typeof sentinel.literal === "string") return sentinel.literal
|
|
74
|
+
throw new Error("No _tag literal found on schema member")
|
|
100
75
|
}
|
|
101
76
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
77
|
+
function collectSentinelsFromAST(
|
|
78
|
+
ast: SchemaAST.AST
|
|
79
|
+
): Array<{ key: PropertyKey; literal: SchemaAST.LiteralValue | symbol }> {
|
|
80
|
+
switch (ast._tag) {
|
|
81
|
+
case "Declaration": {
|
|
82
|
+
const s = ast.annotations?.["~sentinels"]
|
|
83
|
+
return Array.isArray(s) ? s : []
|
|
84
|
+
}
|
|
85
|
+
case "Objects":
|
|
86
|
+
return ast.propertySignatures.flatMap(
|
|
87
|
+
(ps): Array<{ key: PropertyKey; literal: SchemaAST.LiteralValue | symbol }> => {
|
|
88
|
+
const type = ps.type
|
|
89
|
+
if (!SchemaAST.isOptional(type)) {
|
|
90
|
+
if (SchemaAST.isLiteral(type)) return [{ key: ps.name, literal: type.literal }]
|
|
91
|
+
if (SchemaAST.isUniqueSymbol(type)) return [{ key: ps.name, literal: type.symbol }]
|
|
92
|
+
}
|
|
93
|
+
return []
|
|
94
|
+
}
|
|
95
|
+
)
|
|
96
|
+
case "Suspend":
|
|
97
|
+
return collectSentinelsFromAST(ast.thunk())
|
|
98
|
+
default:
|
|
99
|
+
return []
|
|
100
|
+
}
|
|
107
101
|
}
|
|
108
102
|
|
|
109
|
-
export const taggedUnionMap = <
|
|
110
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
111
|
-
Members extends readonly (S.Top & { fields: { _tag: S.tag<string> } })[]
|
|
112
|
-
>(
|
|
113
|
-
self: Members
|
|
114
|
-
) =>
|
|
115
|
-
self.reduce((acc, key) => {
|
|
116
|
-
// TODO: v4 migration — PropertySignatureDeclaration removed, need v4 AST traversal
|
|
117
|
-
const ast = key.fields._tag.ast as any
|
|
118
|
-
const tag = ((ast.type ?? ast) as SchemaAST.Literal).literal as string // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
|
|
119
|
-
;(acc as any)[tag] = key as any
|
|
120
|
-
return acc
|
|
121
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
122
|
-
}, {} as any)
|
|
123
|
-
|
|
124
103
|
export const tags = <
|
|
125
|
-
|
|
126
|
-
Members extends NonEmptyReadonlyArray<(S.Top & { fields: { _tag: S.tag<string> } })>
|
|
104
|
+
Members extends NonEmptyReadonlyArray<(S.Top & { readonly Type: { readonly _tag: string } })>
|
|
127
105
|
>(
|
|
128
106
|
self: Members
|
|
129
107
|
) =>
|
|
130
|
-
S.Literals(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
108
|
+
S.Literals(
|
|
109
|
+
self.map(getTagFromAST) as {
|
|
110
|
+
[Index in keyof Members]: Members[Index]["Type"]["_tag"]
|
|
111
|
+
}
|
|
112
|
+
) as S.Literals<
|
|
113
|
+
{
|
|
114
|
+
[Index in keyof Members]: Members[Index]["Type"]["_tag"]
|
|
115
|
+
}
|
|
116
|
+
>
|
|
117
|
+
|
|
118
|
+
type TaggedUnionMembers = NonEmptyReadonlyArray<
|
|
119
|
+
S.Top & { readonly Type: { readonly _tag: string } }
|
|
120
|
+
>
|
|
121
|
+
|
|
122
|
+
type TaggedUnionTags<Members extends TaggedUnionMembers> = S.Literals<
|
|
123
|
+
{
|
|
124
|
+
[Index in keyof Members]: Members[Index]["Type"]["_tag"]
|
|
125
|
+
}
|
|
126
|
+
>
|
|
149
127
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
extendM(_, (_) => ({
|
|
158
|
-
is: S.is(_ as any),
|
|
159
|
-
isA: makeIs(_ as any),
|
|
160
|
-
isAnyOf: makeIsAnyOf(_ as any),
|
|
161
|
-
tagMap: taggedUnionMap(a),
|
|
162
|
-
tags: tags(a as any)
|
|
163
|
-
}))
|
|
164
|
-
)
|
|
128
|
+
type TaggedUnionWithTags<Members extends TaggedUnionMembers> = S.toTaggedUnion<"_tag", Members> & {
|
|
129
|
+
readonly tags: TaggedUnionTags<Members>
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const extendTaggedUnionWithTags = <Members extends TaggedUnionMembers>(
|
|
133
|
+
schema: S.Union<Members>
|
|
134
|
+
): TaggedUnionWithTags<Members> => extendM(schema.pipe(S.toTaggedUnion("_tag")), () => ({ tags: tags(schema.members) }))
|
|
165
135
|
|
|
166
|
-
export
|
|
136
|
+
export const ExtendTaggedUnion = <Members extends TaggedUnionMembers>(
|
|
137
|
+
schema: S.Union<Members>
|
|
138
|
+
): TaggedUnionWithTags<Members> => extendTaggedUnionWithTags(schema)
|
|
139
|
+
|
|
140
|
+
export const TaggedUnion = <
|
|
141
|
+
Members extends TaggedUnionMembers
|
|
142
|
+
>(...a: Members): TaggedUnionWithTags<Members> => extendTaggedUnionWithTags(S.Union(a))
|
|
@@ -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,11 +6,12 @@ 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
16
|
import type { Client, ClientForOptions, Requests, RequestsAny } from "./clientFor.js"
|
|
17
17
|
|
|
@@ -40,7 +40,7 @@ export type Req = S.Top & {
|
|
|
40
40
|
readonly "~decodingServices"?: unknown
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
class RequestName extends
|
|
43
|
+
class RequestName extends Context.Reference("RequestName", {
|
|
44
44
|
defaultValue: () => ({ requestName: "Unspecified", moduleName: "Error" })
|
|
45
45
|
}) {}
|
|
46
46
|
|
|
@@ -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
|
|
|
@@ -139,7 +139,7 @@ const makeRpcTag = <M extends Requests>(resource: M) => {
|
|
|
139
139
|
|
|
140
140
|
// Use Object.assign instead of class extension to avoid TS2509 with complex generic return types.
|
|
141
141
|
// The first type arg is `any` because this is a dynamically created tag — its identity is the string key.
|
|
142
|
-
const TheClient =
|
|
142
|
+
const TheClient = Context.Opaque<
|
|
143
143
|
any,
|
|
144
144
|
RpcClient.RpcClient<RpcGroup.Rpcs<typeof rpcs>>
|
|
145
145
|
>()(`RpcClient.${meta.moduleName}`)
|
|
@@ -153,7 +153,7 @@ const makeRpcTag = <M extends Requests>(resource: M) => {
|
|
|
153
153
|
|
|
154
154
|
const makeApiClientFactory = Effect
|
|
155
155
|
.gen(function*() {
|
|
156
|
-
const ctx = yield* Effect.
|
|
156
|
+
const ctx = yield* Effect.context<RpcSerialization.RpcSerialization | HttpClient.HttpClient>()
|
|
157
157
|
const makeClientFor = <M extends Requests>(
|
|
158
158
|
resource: M,
|
|
159
159
|
requestLevelLayers = Layer.empty,
|
|
@@ -176,7 +176,7 @@ const makeApiClientFactory = Effect
|
|
|
176
176
|
url: "" // why not here set meta.moduleName as root?
|
|
177
177
|
})
|
|
178
178
|
.pipe(
|
|
179
|
-
Layer.provideMerge(Layer.
|
|
179
|
+
Layer.provideMerge(Layer.succeedContext(ctx))
|
|
180
180
|
)
|
|
181
181
|
)
|
|
182
182
|
)
|
|
@@ -185,7 +185,7 @@ const makeApiClientFactory = Effect
|
|
|
185
185
|
const filtered = getFiltered(resource)
|
|
186
186
|
return {
|
|
187
187
|
mr,
|
|
188
|
-
client:
|
|
188
|
+
client: typedKeysOf(filtered)
|
|
189
189
|
.reduce((prev, cur) => {
|
|
190
190
|
const h = filtered[cur]!
|
|
191
191
|
|
|
@@ -212,7 +212,7 @@ const makeApiClientFactory = Effect
|
|
|
212
212
|
// @ts-expect-error doc
|
|
213
213
|
prev[cur] = Object.keys(fields).length === 0
|
|
214
214
|
? {
|
|
215
|
-
handler: mr.
|
|
215
|
+
handler: mr.contextEffect.pipe(
|
|
216
216
|
Effect.flatMap((svcs) =>
|
|
217
217
|
TheClient
|
|
218
218
|
.use((client) => (client as any)[requestAttr]!(new Request()) as Effect.Effect<any, any, never>)
|
|
@@ -226,7 +226,7 @@ const makeApiClientFactory = Effect
|
|
|
226
226
|
}
|
|
227
227
|
: {
|
|
228
228
|
handler: (req: any) =>
|
|
229
|
-
mr.
|
|
229
|
+
mr.contextEffect.pipe(
|
|
230
230
|
Effect.flatMap((svcs) =>
|
|
231
231
|
TheClient
|
|
232
232
|
.use((client) =>
|
|
@@ -243,7 +243,7 @@ const makeApiClientFactory = Effect
|
|
|
243
243
|
}
|
|
244
244
|
|
|
245
245
|
return prev
|
|
246
|
-
}, {} as Client<M, M["meta"]["moduleName"]>)
|
|
246
|
+
}, {} as Client<M, M["meta"]["moduleName"]>)
|
|
247
247
|
}
|
|
248
248
|
})
|
|
249
249
|
|
|
@@ -281,7 +281,7 @@ const makeApiClientFactory = Effect
|
|
|
281
281
|
* Used to create clients for resource modules.
|
|
282
282
|
*/
|
|
283
283
|
export class ApiClientFactory
|
|
284
|
-
extends
|
|
284
|
+
extends Context.Opaque<ApiClientFactory, Effect.Success<typeof makeApiClientFactory>>()("ApiClientFactory")
|
|
285
285
|
{
|
|
286
286
|
static readonly layer = (config: ApiConfig) =>
|
|
287
287
|
ApiClientFactory.toLayer(makeApiClientFactory).pipe(Layer.provide(RpcSerializationLayer(config)))
|
package/src/client/errors.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** @effect-diagnostics overriddenSchemaConstructor:skip-file */
|
|
2
|
-
import {
|
|
2
|
+
import { TaggedErrorClass } from "effect-app/Schema"
|
|
3
3
|
import * as Cause from "effect/Cause"
|
|
4
4
|
import * as S from "../Schema.js"
|
|
5
5
|
|
|
@@ -21,7 +21,7 @@ export const tryToJson = (error: { toJSON(): unknown; toString(): string }) => {
|
|
|
21
21
|
|
|
22
22
|
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
23
23
|
// @ts-expect-error type not used
|
|
24
|
-
export class NotFoundError<ItemType = string> extends
|
|
24
|
+
export class NotFoundError<ItemType = string> extends TaggedErrorClass<NotFoundError<ItemType>>()("NotFoundError", {
|
|
25
25
|
type: S.String,
|
|
26
26
|
id: S.Unknown
|
|
27
27
|
}) {
|
|
@@ -34,28 +34,43 @@ export class NotFoundError<ItemType = string> extends TaggedError<NotFoundError<
|
|
|
34
34
|
override get message() {
|
|
35
35
|
return `Didn't find ${(this as any).type}#${JSON.stringify((this as any).id)}`
|
|
36
36
|
}
|
|
37
|
+
override toString() {
|
|
38
|
+
return `NotFoundError: ${this.message}`
|
|
39
|
+
}
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
const messageFallback = (messageOrObject?: string | { message: string }) =>
|
|
40
43
|
typeof messageOrObject === "object" ? messageOrObject : { message: messageOrObject ?? "" }
|
|
41
44
|
|
|
42
|
-
export class InvalidStateError extends
|
|
45
|
+
export class InvalidStateError extends TaggedErrorClass<InvalidStateError>()("InvalidStateError", {
|
|
43
46
|
message: S.String
|
|
44
47
|
}) {
|
|
45
48
|
constructor(messageOrObject: string | { message: string; cause?: unknown }, disableValidation?: boolean) {
|
|
46
|
-
super(
|
|
49
|
+
super(
|
|
50
|
+
typeof messageOrObject === "object" ? messageOrObject : { message: messageOrObject } as any,
|
|
51
|
+
disableValidation as any
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
override toString() {
|
|
55
|
+
return `InvalidStateError: ${this.message}`
|
|
47
56
|
}
|
|
48
57
|
}
|
|
49
58
|
|
|
50
|
-
export class ServiceUnavailableError extends
|
|
59
|
+
export class ServiceUnavailableError extends TaggedErrorClass<ServiceUnavailableError>()("ServiceUnavailableError", {
|
|
51
60
|
message: S.String
|
|
52
61
|
}) {
|
|
53
62
|
constructor(messageOrObject: string | { message: string; cause?: unknown }, disableValidation?: boolean) {
|
|
54
|
-
super(
|
|
63
|
+
super(
|
|
64
|
+
typeof messageOrObject === "object" ? messageOrObject : { message: messageOrObject } as any,
|
|
65
|
+
disableValidation as any
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
override toString() {
|
|
69
|
+
return `ServiceUnavailableError: ${this.message}`
|
|
55
70
|
}
|
|
56
71
|
}
|
|
57
72
|
|
|
58
|
-
export class ValidationError extends
|
|
73
|
+
export class ValidationError extends TaggedErrorClass<ValidationError>()("ValidationError", {
|
|
59
74
|
errors: S.Array(S.Unknown)
|
|
60
75
|
}) {
|
|
61
76
|
constructor(
|
|
@@ -67,33 +82,45 @@ export class ValidationError extends TaggedError<ValidationError>()("ValidationE
|
|
|
67
82
|
override get message() {
|
|
68
83
|
return `Validation failed: ${(this as any).errors.map((e: any) => JSON.stringify(e, undefined, 2)).join(",\n")}`
|
|
69
84
|
}
|
|
85
|
+
override toString() {
|
|
86
|
+
return `ValidationError: ${this.message}`
|
|
87
|
+
}
|
|
70
88
|
}
|
|
71
89
|
|
|
72
|
-
export class NotLoggedInError extends
|
|
90
|
+
export class NotLoggedInError extends TaggedErrorClass<NotLoggedInError>()("NotLoggedInError", {
|
|
73
91
|
message: S.String
|
|
74
92
|
}) {
|
|
75
93
|
constructor(messageOrObject?: string | { message: string; cause?: unknown }, disableValidation?: boolean) {
|
|
76
94
|
super(messageFallback(messageOrObject) as any, disableValidation as any)
|
|
77
95
|
}
|
|
96
|
+
override toString() {
|
|
97
|
+
return `NotLoggedInError: ${this.message}`
|
|
98
|
+
}
|
|
78
99
|
}
|
|
79
100
|
|
|
80
101
|
/**
|
|
81
102
|
* The user carries a valid Userprofile, but there is a problem with the login none the less.
|
|
82
103
|
*/
|
|
83
|
-
export class LoginError extends
|
|
104
|
+
export class LoginError extends TaggedErrorClass<LoginError>()("NotLoggedInError", {
|
|
84
105
|
message: S.String
|
|
85
106
|
}) {
|
|
86
107
|
constructor(messageOrObject?: string | { message: string; cause?: unknown }, disableValidation?: boolean) {
|
|
87
108
|
super(messageFallback(messageOrObject) as any, disableValidation as any)
|
|
88
109
|
}
|
|
110
|
+
override toString() {
|
|
111
|
+
return `LoginError: ${this.message}`
|
|
112
|
+
}
|
|
89
113
|
}
|
|
90
114
|
|
|
91
|
-
export class UnauthorizedError extends
|
|
115
|
+
export class UnauthorizedError extends TaggedErrorClass<UnauthorizedError>()("UnauthorizedError", {
|
|
92
116
|
message: S.String
|
|
93
117
|
}) {
|
|
94
118
|
constructor(messageOrObject?: string | { message: string; cause?: unknown }, disableValidation?: boolean) {
|
|
95
119
|
super(messageFallback(messageOrObject) as any, disableValidation as any)
|
|
96
120
|
}
|
|
121
|
+
override toString() {
|
|
122
|
+
return `UnauthorizedError: ${this.message}`
|
|
123
|
+
}
|
|
97
124
|
}
|
|
98
125
|
|
|
99
126
|
type OptimisticConcurrencyDetails = {
|
|
@@ -104,7 +131,7 @@ type OptimisticConcurrencyDetails = {
|
|
|
104
131
|
readonly found?: string | undefined
|
|
105
132
|
}
|
|
106
133
|
|
|
107
|
-
export class OptimisticConcurrencyException extends
|
|
134
|
+
export class OptimisticConcurrencyException extends TaggedErrorClass<OptimisticConcurrencyException>()(
|
|
108
135
|
"OptimisticConcurrencyException",
|
|
109
136
|
{ message: S.String }
|
|
110
137
|
) {
|
|
@@ -116,11 +143,17 @@ export class OptimisticConcurrencyException extends TaggedError<OptimisticConcur
|
|
|
116
143
|
| ({ message: string; cause?: unknown; raw?: unknown }),
|
|
117
144
|
disableValidation?: boolean
|
|
118
145
|
) {
|
|
119
|
-
super(
|
|
146
|
+
super(
|
|
147
|
+
"message" in args ? args : { message: `Existing ${args.type} ${args.id} record changed` } as any,
|
|
148
|
+
disableValidation as any
|
|
149
|
+
)
|
|
120
150
|
if (!("message" in args)) {
|
|
121
151
|
this.details = args
|
|
122
152
|
}
|
|
123
153
|
}
|
|
154
|
+
override toString() {
|
|
155
|
+
return `OptimisticConcurrencyException: ${this.message}`
|
|
156
|
+
}
|
|
124
157
|
}
|
|
125
158
|
|
|
126
159
|
const MutationOnlyErrors = [
|
|
@@ -174,6 +207,7 @@ export class CauseException<E> extends Error {
|
|
|
174
207
|
Error.stackTraceLimit = 0
|
|
175
208
|
super()
|
|
176
209
|
Error.stackTraceLimit = limit
|
|
210
|
+
this.cause = Cause.squash(originalCause)
|
|
177
211
|
// v4: makeFiberFailure removed — use Cause.prettyErrors instead
|
|
178
212
|
const errors = Cause.prettyErrors(originalCause)
|
|
179
213
|
const first = errors[0]
|
package/src/client/makeClient.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SchemaTransformation } from "effect"
|
|
2
|
+
import { type GetContextConfig, type GetEffectError, type RequestContextMapTagAny } from "../rpc/RpcContextMap.js"
|
|
2
3
|
import * as S from "../Schema.js"
|
|
3
4
|
import { AST } from "../Schema.js"
|
|
4
5
|
|
|
@@ -8,33 +9,38 @@ const merge = (a: any, b: Array<any>) =>
|
|
|
8
9
|
/**
|
|
9
10
|
* Whatever the input, we will only decode or encode to void
|
|
10
11
|
*/
|
|
11
|
-
const ForceVoid
|
|
12
|
+
export const ForceVoid = S
|
|
13
|
+
.declare((_: unknown): _ is unknown => true)
|
|
14
|
+
.pipe(
|
|
15
|
+
S.decodeTo(S.Any, SchemaTransformation.transform<unknown, unknown>({ decode: () => void 0, encode: () => void 0 }))
|
|
16
|
+
)
|
|
12
17
|
|
|
13
18
|
type SchemaOrFields<T> = T extends S.Top ? T : T extends S.Struct.Fields ? S.Struct<T> : S.Void
|
|
14
19
|
|
|
15
20
|
type TaggedRequestResult<
|
|
21
|
+
Self,
|
|
16
22
|
Tag extends string,
|
|
17
23
|
Payload extends S.Struct.Fields,
|
|
18
24
|
Success extends S.Top,
|
|
19
25
|
Error extends S.Top,
|
|
20
26
|
Config = Record<string, never>
|
|
21
27
|
> =
|
|
22
|
-
& S.TaggedStruct<Tag, Payload>
|
|
28
|
+
& S.EnhancedClass<Self, S.TaggedStruct<Tag, Payload>, {}>
|
|
23
29
|
& {
|
|
24
|
-
new(...args: any[]): any
|
|
25
30
|
readonly _tag: Tag
|
|
26
|
-
readonly fields: { readonly _tag: S.tag<Tag> } & Payload
|
|
27
31
|
readonly success: Success
|
|
28
32
|
readonly error: Error
|
|
29
33
|
readonly config: Config
|
|
34
|
+
// TODO: these two are wrong. Anything using this request's success/error, should however derive the Decoding/Encoding services from them..
|
|
30
35
|
readonly "~decodingServices": S.Codec.DecodingServices<Success> | S.Codec.DecodingServices<Error>
|
|
36
|
+
readonly "~encodingServices": S.Codec.EncodingServices<Success> | S.Codec.EncodingServices<Error>
|
|
31
37
|
}
|
|
32
38
|
|
|
33
39
|
export const makeRpcClient = <
|
|
34
40
|
RequestContextMap extends RequestContextMapTagAny,
|
|
35
41
|
GeneralErrors extends S.Top = never
|
|
36
42
|
>(rcs: RequestContextMap, generalErrors?: GeneralErrors) => {
|
|
37
|
-
// Long way around
|
|
43
|
+
// Long way around Context/C extends etc to support actual jsdoc from passed in RequestConfig etc... (??)
|
|
38
44
|
type ServiceMap = {
|
|
39
45
|
success: S.Top | S.Struct.Fields // SchemaOrFields will make a Schema type out of Struct.Fields
|
|
40
46
|
error: S.Top | S.Struct.Fields // SchemaOrFields will make a Schema type out of Struct.Fields
|
|
@@ -47,31 +53,45 @@ export const makeRpcClient = <
|
|
|
47
53
|
: [GeneralErrors] extends [never] ? GetEffectError<RequestContextMap["config"], C>
|
|
48
54
|
: MergeError<GetEffectError<RequestContextMap["config"], C>>
|
|
49
55
|
|
|
50
|
-
function TaggedRequest<
|
|
56
|
+
function TaggedRequest<Self>(): {
|
|
51
57
|
<Tag extends string, Payload extends S.Struct.Fields, C extends ServiceMap>(
|
|
52
58
|
tag: Tag,
|
|
53
59
|
fields: Payload,
|
|
54
60
|
config: RequestConfig & C
|
|
55
|
-
): TaggedRequestResult<
|
|
61
|
+
): TaggedRequestResult<
|
|
62
|
+
Self,
|
|
63
|
+
Tag,
|
|
64
|
+
Payload,
|
|
65
|
+
SchemaOrFields<C["success"]>,
|
|
66
|
+
ErrorResult<C>,
|
|
67
|
+
Omit<C, "success" | "error">
|
|
68
|
+
>
|
|
56
69
|
<Tag extends string, Payload extends S.Struct.Fields, C extends Pick<ServiceMap, "success">>(
|
|
57
70
|
tag: Tag,
|
|
58
71
|
fields: Payload,
|
|
59
72
|
config: RequestConfig & C
|
|
60
|
-
): TaggedRequestResult<
|
|
73
|
+
): TaggedRequestResult<
|
|
74
|
+
Self,
|
|
75
|
+
Tag,
|
|
76
|
+
Payload,
|
|
77
|
+
SchemaOrFields<C["success"]>,
|
|
78
|
+
ErrorResult<C>,
|
|
79
|
+
Omit<C, "success" | "error">
|
|
80
|
+
>
|
|
61
81
|
<Tag extends string, Payload extends S.Struct.Fields, C extends Pick<ServiceMap, "error">>(
|
|
62
82
|
tag: Tag,
|
|
63
83
|
fields: Payload,
|
|
64
84
|
config: RequestConfig & C
|
|
65
|
-
): TaggedRequestResult<Tag, Payload,
|
|
85
|
+
): TaggedRequestResult<Self, Tag, Payload, typeof ForceVoid, ErrorResult<C>, Omit<C, "success" | "error">>
|
|
66
86
|
<Tag extends string, Payload extends S.Struct.Fields, C extends Record<string, any>>(
|
|
67
87
|
tag: Tag,
|
|
68
88
|
fields: Payload,
|
|
69
89
|
config: C & RequestConfig
|
|
70
|
-
): TaggedRequestResult<Tag, Payload,
|
|
90
|
+
): TaggedRequestResult<Self, Tag, Payload, typeof ForceVoid, ErrorResult<C>, Omit<C, "success" | "error">>
|
|
71
91
|
<Tag extends string, Payload extends S.Struct.Fields>(
|
|
72
92
|
tag: Tag,
|
|
73
93
|
fields: Payload
|
|
74
|
-
): TaggedRequestResult<Tag, Payload,
|
|
94
|
+
): TaggedRequestResult<Self, Tag, Payload, typeof ForceVoid, ErrorResult<{}>, Record<string, never>>
|
|
75
95
|
} {
|
|
76
96
|
// TODO: filter errors based on config + take care of inversion
|
|
77
97
|
const errorSchemas = Object.values(rcs.config).map((_) => _.error)
|
package/src/http/Request.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Option } from "effect"
|
|
1
2
|
import type { HttpClientResponse } from "effect/unstable/http/HttpClientResponse"
|
|
2
3
|
import * as Effect from "../Effect.js"
|
|
3
4
|
import { HttpClient, HttpClientError, HttpClientRequest, HttpHeaders } from "./internal/lib.js"
|
|
@@ -24,16 +25,18 @@ export const demandJson = (client: HttpClient.HttpClient) =>
|
|
|
24
25
|
.mapRequest(client, (_) => HttpClientRequest.acceptJson(_))
|
|
25
26
|
.pipe(HttpClient.transform((r, request) =>
|
|
26
27
|
Effect.tap(r, (response) =>
|
|
27
|
-
|
|
28
|
-
.
|
|
29
|
-
|
|
28
|
+
Option
|
|
29
|
+
.exists(
|
|
30
|
+
HttpHeaders.get(response.headers, "Content-Type"),
|
|
31
|
+
(_) => _.startsWith("application/json")
|
|
32
|
+
)
|
|
30
33
|
? Effect.void
|
|
31
34
|
: Effect.fail(
|
|
32
35
|
new HttpClientError.DecodeError({
|
|
33
36
|
request,
|
|
34
37
|
response,
|
|
35
38
|
description: "not json response: "
|
|
36
|
-
+ HttpHeaders.get(response.headers, "Content-Type")
|
|
39
|
+
+ Option.getOrElse(HttpHeaders.get(response.headers, "Content-Type"), () => "<missing>")
|
|
37
40
|
})
|
|
38
41
|
))
|
|
39
42
|
))
|
package/src/ids.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Effect } from "effect"
|
|
2
|
+
import { brandedStringId, type Codec, NonEmptyString255, StringId, type StringIdBrand, withDefaultMake } from "effect-app/Schema"
|
|
2
3
|
import type { B } from "effect-app/Schema/schema"
|
|
3
4
|
import type { Simplify } from "effect/Types"
|
|
4
5
|
import { S } from "./index.js"
|
|
@@ -17,7 +18,7 @@ export const RequestId = extendM(
|
|
|
17
18
|
const make = StringId.make as () => NonEmptyString255
|
|
18
19
|
return ({
|
|
19
20
|
make,
|
|
20
|
-
withDefault:
|
|
21
|
+
withDefault: S.withConstructorDefault(Effect.sync(make))(s as typeof s & S.WithoutConstructorDefault)
|
|
21
22
|
})
|
|
22
23
|
}
|
|
23
24
|
)
|