effect-app 4.0.0-beta.1 → 4.0.0-beta.100
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 +444 -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 +62 -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 +43 -10
- package/dist/Schema/Class.d.ts.map +1 -1
- package/dist/Schema/Class.js +89 -16
- 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 +103 -46
- package/dist/Schema/ext.d.ts.map +1 -1
- package/dist/Schema/ext.js +110 -51
- package/dist/Schema/moreStrings.d.ts +25 -13
- 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 +11 -28
- package/dist/client/apiClientFactory.d.ts.map +1 -1
- package/dist/client/apiClientFactory.js +21 -23
- package/dist/client/clientFor.d.ts +8 -7
- 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 +21 -16
- package/dist/client/makeClient.d.ts.map +1 -1
- package/dist/client/makeClient.js +32 -34
- package/dist/http/Request.d.ts.map +1 -1
- package/dist/http/Request.js +5 -5
- package/dist/ids.d.ts +10 -10
- 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 +6 -5
- 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 +6 -5
- 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 +118 -30
- 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 +185 -82
- package/src/Schema/moreStrings.ts +27 -18
- 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 +35 -43
- package/src/client/clientFor.ts +8 -3
- package/src/client/errors.ts +46 -12
- package/src/client/makeClient.ts +122 -75
- package/src/http/Request.ts +7 -4
- package/src/ids.ts +4 -3
- package/src/index.ts +3 -11
- package/src/middleware.ts +2 -2
- package/src/rpc/MiddlewareMaker.ts +12 -10
- package/src/rpc/RpcContextMap.ts +6 -5
- package/src/rpc/RpcMiddleware.ts +7 -6
- 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 +28 -6
- package/test/schema.test.ts +397 -4
- package/test/secretURL.test.ts +157 -0
- package/test/special.test.ts +729 -0
- package/test/utils.test.ts +2 -2
- 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
|
@@ -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
|
|
|
@@ -50,17 +52,17 @@ export const HttpClientLayer = (config: ApiConfig) =>
|
|
|
50
52
|
Effect
|
|
51
53
|
.gen(function*() {
|
|
52
54
|
const baseClient = yield* HttpClient.HttpClient
|
|
53
|
-
const ctx = yield* RequestName
|
|
54
55
|
const client = baseClient.pipe(
|
|
55
56
|
HttpClient.mapRequest(HttpClientRequest.prependUrl(config.url + "/rpc")),
|
|
56
57
|
HttpClient.mapRequest(
|
|
57
58
|
HttpClientRequest.setHeaders(config.headers.pipe(Option.getOrElse(() => ({}))))
|
|
58
59
|
),
|
|
59
|
-
HttpClient.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
HttpClient.mapRequestEffect((req) =>
|
|
61
|
+
Effect.map(RequestName.asEffect(), (ctx) =>
|
|
62
|
+
flow(
|
|
63
|
+
HttpClientRequest.appendUrlParam("action", ctx.requestName),
|
|
64
|
+
HttpClientRequest.appendUrl("/" + ctx.moduleName)
|
|
65
|
+
)(req))
|
|
64
66
|
)
|
|
65
67
|
)
|
|
66
68
|
return client
|
|
@@ -76,7 +78,7 @@ export const HttpClientFromConfigLayer = Layer.unwrap(
|
|
|
76
78
|
|
|
77
79
|
export const RpcSerializationLayer = (config: ApiConfig) =>
|
|
78
80
|
Layer.mergeAll(
|
|
79
|
-
RpcSerialization.
|
|
81
|
+
RpcSerialization.layerNdjson,
|
|
80
82
|
HttpClientLayer(config)
|
|
81
83
|
)
|
|
82
84
|
|
|
@@ -84,14 +86,14 @@ 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
|
}
|
|
91
93
|
// TODO: Record.filter
|
|
92
94
|
const filtered = typedKeysOf(resource).reduce((acc, cur) => {
|
|
93
95
|
if (
|
|
94
|
-
Predicate.
|
|
96
|
+
Predicate.isObjectKeyword(resource[cur])
|
|
95
97
|
&& (resource[cur].success)
|
|
96
98
|
) {
|
|
97
99
|
acc[cur as keyof Filtered] = resource[cur] as any
|
|
@@ -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,38 +128,28 @@ 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}`)
|
|
146
141
|
// Use Layer.effect directly (not TheClient.toLayer) so TypeScript properly excludes Scope
|
|
147
142
|
const layer = Layer.effect(
|
|
148
143
|
TheClient,
|
|
149
|
-
|
|
150
|
-
RpcClient.make(rpcs, { spanPrefix: "RpcClient." + meta.moduleName }),
|
|
151
|
-
(cl) => (cl as any)[meta.moduleName]
|
|
152
|
-
)
|
|
144
|
+
RpcClient.make(rpcs, { spanPrefix: "RpcClient." + meta.moduleName })
|
|
153
145
|
)
|
|
154
146
|
return Object.assign(TheClient, { layer })
|
|
155
147
|
}
|
|
156
148
|
|
|
157
149
|
const makeApiClientFactory = Effect
|
|
158
150
|
.gen(function*() {
|
|
159
|
-
const ctx = yield* Effect.
|
|
160
|
-
const makeClientFor = <M extends
|
|
151
|
+
const ctx = yield* Effect.context<RpcSerialization.RpcSerialization | HttpClient.HttpClient>()
|
|
152
|
+
const makeClientFor = <M extends RequestsAny>(
|
|
161
153
|
resource: M,
|
|
162
154
|
requestLevelLayers = Layer.empty,
|
|
163
155
|
options?: ClientForOptions
|
|
@@ -179,7 +171,7 @@ const makeApiClientFactory = Effect
|
|
|
179
171
|
url: "" // why not here set meta.moduleName as root?
|
|
180
172
|
})
|
|
181
173
|
.pipe(
|
|
182
|
-
Layer.provideMerge(Layer.
|
|
174
|
+
Layer.provideMerge(Layer.succeedContext(ctx))
|
|
183
175
|
)
|
|
184
176
|
)
|
|
185
177
|
)
|
|
@@ -188,7 +180,7 @@ const makeApiClientFactory = Effect
|
|
|
188
180
|
const filtered = getFiltered(resource)
|
|
189
181
|
return {
|
|
190
182
|
mr,
|
|
191
|
-
client:
|
|
183
|
+
client: typedKeysOf(filtered)
|
|
192
184
|
.reduce((prev, cur) => {
|
|
193
185
|
const h = filtered[cur]!
|
|
194
186
|
|
|
@@ -211,11 +203,11 @@ const makeApiClientFactory = Effect
|
|
|
211
203
|
const layers = requestLevelLayers.pipe(Layer.provideMerge(requestNameLayer))
|
|
212
204
|
|
|
213
205
|
const fields = Struct.omit(Request.fields, ["_tag"] as const)
|
|
214
|
-
const requestAttr = h._tag
|
|
206
|
+
const requestAttr = `${meta.moduleName}.${h._tag}`
|
|
215
207
|
// @ts-expect-error doc
|
|
216
208
|
prev[cur] = Object.keys(fields).length === 0
|
|
217
209
|
? {
|
|
218
|
-
handler: mr.
|
|
210
|
+
handler: mr.contextEffect.pipe(
|
|
219
211
|
Effect.flatMap((svcs) =>
|
|
220
212
|
TheClient
|
|
221
213
|
.use((client) => (client as any)[requestAttr]!(new Request()) as Effect.Effect<any, any, never>)
|
|
@@ -229,7 +221,7 @@ const makeApiClientFactory = Effect
|
|
|
229
221
|
}
|
|
230
222
|
: {
|
|
231
223
|
handler: (req: any) =>
|
|
232
|
-
mr.
|
|
224
|
+
mr.contextEffect.pipe(
|
|
233
225
|
Effect.flatMap((svcs) =>
|
|
234
226
|
TheClient
|
|
235
227
|
.use((client) =>
|
|
@@ -246,7 +238,7 @@ const makeApiClientFactory = Effect
|
|
|
246
238
|
}
|
|
247
239
|
|
|
248
240
|
return prev
|
|
249
|
-
}, {} as Client<M, M
|
|
241
|
+
}, {} as Client<M, ExtractModuleName<M>>)
|
|
250
242
|
}
|
|
251
243
|
})
|
|
252
244
|
|
|
@@ -262,9 +254,9 @@ const makeApiClientFactory = Effect
|
|
|
262
254
|
cacheL.set(requestLevelLayers, cache)
|
|
263
255
|
}
|
|
264
256
|
|
|
265
|
-
return <M extends
|
|
257
|
+
return <M extends RequestsAny>(
|
|
266
258
|
models: M
|
|
267
|
-
): Effect.Effect<Client<M, M
|
|
259
|
+
): Effect.Effect<Client<M, ExtractModuleName<M>>> =>
|
|
268
260
|
Effect.gen(function*() {
|
|
269
261
|
const found = cache.get(models)
|
|
270
262
|
if (found) {
|
|
@@ -284,7 +276,7 @@ const makeApiClientFactory = Effect
|
|
|
284
276
|
* Used to create clients for resource modules.
|
|
285
277
|
*/
|
|
286
278
|
export class ApiClientFactory
|
|
287
|
-
extends
|
|
279
|
+
extends Context.Opaque<ApiClientFactory, Effect.Success<typeof makeApiClientFactory>>()("ApiClientFactory")
|
|
288
280
|
{
|
|
289
281
|
static readonly layer = (config: ApiConfig) =>
|
|
290
282
|
ApiClientFactory.toLayer(makeApiClientFactory).pipe(Layer.provide(RpcSerializationLayer(config)))
|
|
@@ -297,7 +289,7 @@ export class ApiClientFactory
|
|
|
297
289
|
|
|
298
290
|
static readonly makeFor =
|
|
299
291
|
(requestLevelLayers: Layer.Layer<never, never, never>, options?: ClientForOptions) =>
|
|
300
|
-
<M extends
|
|
292
|
+
<M extends RequestsAny>(
|
|
301
293
|
resource: M
|
|
302
294
|
) =>
|
|
303
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,
|
|
@@ -58,11 +63,11 @@ export type Client<M extends RequestsAny, ModuleName extends string> = RequestHa
|
|
|
58
63
|
ModuleName
|
|
59
64
|
>
|
|
60
65
|
|
|
61
|
-
export type ExtractResponse<T> = T extends S.
|
|
66
|
+
export type ExtractResponse<T> = T extends S.Codec<any> ? S.Schema.Type<T>
|
|
62
67
|
: T extends unknown ? void
|
|
63
68
|
: never
|
|
64
69
|
|
|
65
|
-
export type ExtractEResponse<T> = T extends S.
|
|
70
|
+
export type ExtractEResponse<T> = T extends S.Codec<any> ? S.Codec.Encoded<T>
|
|
66
71
|
: T extends unknown ? void
|
|
67
72
|
: never
|
|
68
73
|
|
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,40 @@ 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
|
-
type
|
|
20
|
+
type TaggedRequestForResult<
|
|
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
|
-
Config
|
|
26
|
+
Config,
|
|
27
|
+
ModuleName extends string
|
|
21
28
|
> =
|
|
22
|
-
& S.TaggedStruct<Tag, Payload>
|
|
29
|
+
& S.EnhancedClass<Self, S.TaggedStruct<Tag, Payload>, {}>
|
|
23
30
|
& {
|
|
24
|
-
new(...args: any[]): any
|
|
25
31
|
readonly _tag: Tag
|
|
26
|
-
readonly fields: { readonly _tag: S.tag<Tag> } & Payload
|
|
27
32
|
readonly success: Success
|
|
28
33
|
readonly error: Error
|
|
29
34
|
readonly config: Config
|
|
30
35
|
readonly "~decodingServices": S.Codec.DecodingServices<Success> | S.Codec.DecodingServices<Error>
|
|
36
|
+
readonly "~encodingServices": S.Codec.EncodingServices<Success> | S.Codec.EncodingServices<Error>
|
|
37
|
+
readonly id: `${ModuleName}.${Tag}`
|
|
38
|
+
readonly moduleName: ModuleName
|
|
31
39
|
}
|
|
32
40
|
|
|
33
41
|
export const makeRpcClient = <
|
|
34
42
|
RequestContextMap extends RequestContextMapTagAny,
|
|
35
43
|
GeneralErrors extends S.Top = never
|
|
36
44
|
>(rcs: RequestContextMap, generalErrors?: GeneralErrors) => {
|
|
37
|
-
// Long way around
|
|
45
|
+
// Long way around Context/C extends etc to support actual jsdoc from passed in RequestConfig etc... (??)
|
|
38
46
|
type ServiceMap = {
|
|
39
47
|
success: S.Top | S.Struct.Fields // SchemaOrFields will make a Schema type out of Struct.Fields
|
|
40
48
|
error: S.Top | S.Struct.Fields // SchemaOrFields will make a Schema type out of Struct.Fields
|
|
@@ -44,80 +52,119 @@ export const makeRpcClient = <
|
|
|
44
52
|
|
|
45
53
|
type MergeError<E> = [GeneralErrors] extends [never] ? SchemaOrFields<E> : S.Union<[SchemaOrFields<E>, GeneralErrors]>
|
|
46
54
|
type ErrorResult<C> = C extends { error: infer E } ? MergeError<E>
|
|
47
|
-
: [GeneralErrors] extends [never] ?
|
|
48
|
-
:
|
|
55
|
+
: [GeneralErrors] extends [never] ? GetEffectError<RequestContextMap["config"], C>
|
|
56
|
+
: MergeError<GetEffectError<RequestContextMap["config"], C>>
|
|
49
57
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
tag: Tag,
|
|
53
|
-
fields: Payload,
|
|
54
|
-
config: RequestConfig & C
|
|
55
|
-
): TaggedRequestResult<Tag, Payload, SchemaOrFields<C["success"]>, ErrorResult<C>, Omit<C, "success" | "error">>
|
|
56
|
-
<Tag extends string, Payload extends S.Struct.Fields, C extends Pick<ServiceMap, "success">>(
|
|
57
|
-
tag: Tag,
|
|
58
|
-
fields: Payload,
|
|
59
|
-
config: RequestConfig & C
|
|
60
|
-
): TaggedRequestResult<Tag, Payload, SchemaOrFields<C["success"]>, ErrorResult<C>, Omit<C, "success" | "error">>
|
|
61
|
-
<Tag extends string, Payload extends S.Struct.Fields, C extends Pick<ServiceMap, "error">>(
|
|
62
|
-
tag: Tag,
|
|
63
|
-
fields: Payload,
|
|
64
|
-
config: RequestConfig & C
|
|
65
|
-
): TaggedRequestResult<Tag, Payload, S.Schema<void>, ErrorResult<C>, Omit<C, "success" | "error">>
|
|
66
|
-
<Tag extends string, Payload extends S.Struct.Fields, C extends Record<string, any>>(
|
|
67
|
-
tag: Tag,
|
|
68
|
-
fields: Payload,
|
|
69
|
-
config: C & RequestConfig
|
|
70
|
-
): TaggedRequestResult<Tag, Payload, S.Schema<void>, ErrorResult<C>, Omit<C, "success" | "error">>
|
|
71
|
-
<Tag extends string, Payload extends S.Struct.Fields>(
|
|
72
|
-
tag: Tag,
|
|
73
|
-
fields: Payload
|
|
74
|
-
): TaggedRequestResult<Tag, Payload, S.Schema<void>, ErrorResult<never>, Record<string, never>>
|
|
75
|
-
} {
|
|
76
|
-
// TODO: filter errors based on config + take care of inversion
|
|
77
|
-
const errorSchemas = Object.values(rcs.config).map((_) => _.error)
|
|
78
|
-
return (<Tag extends string, Fields extends S.Struct.Fields, C extends ServiceMap>(
|
|
79
|
-
tag: Tag,
|
|
80
|
-
fields: Fields,
|
|
81
|
-
config?: C
|
|
82
|
-
) => {
|
|
83
|
-
// TODO: S.TaggedRequest removed in v4 — needs rework to use Rpc.make or Request.TaggedClass
|
|
84
|
-
// For now, creating a simple tagged struct class with success/failure properties
|
|
85
|
-
const failureSchema = merge(
|
|
86
|
-
config?.error ? S.isSchema(config.error) ? config.error : S.Struct(config.error) : undefined,
|
|
87
|
-
[...errorSchemas, generalErrors].filter(Boolean)
|
|
88
|
-
)
|
|
89
|
-
const successSchema = config?.success
|
|
90
|
-
? S.isSchema(config.success)
|
|
91
|
-
? AST.isVoid(config.success.ast) ? ForceVoid : config.success
|
|
92
|
-
: S.Struct(config.success)
|
|
93
|
-
: ForceVoid
|
|
58
|
+
// TODO: filter errors based on config + take care of inversion
|
|
59
|
+
const errorSchemas = Object.values(rcs.config).map((_) => _.error)
|
|
94
60
|
|
|
95
|
-
|
|
61
|
+
function makeRequestClass<Tag extends string, Fields extends S.Struct.Fields, C extends ServiceMap>(
|
|
62
|
+
tag: Tag,
|
|
63
|
+
fields: Fields,
|
|
64
|
+
config?: C
|
|
65
|
+
) {
|
|
66
|
+
const failureSchema = merge(
|
|
67
|
+
config?.error ? S.isSchema(config.error) ? config.error : S.Struct(config.error) : undefined,
|
|
68
|
+
[...errorSchemas, generalErrors].filter(Boolean)
|
|
69
|
+
)
|
|
70
|
+
const successSchema = config?.success
|
|
71
|
+
? S.isSchema(config.success)
|
|
72
|
+
? AST.isVoid(config.success.ast) ? ForceVoid : config.success
|
|
73
|
+
: S.Struct(config.success)
|
|
74
|
+
: ForceVoid
|
|
96
75
|
|
|
97
|
-
|
|
76
|
+
const RequestClass = S.TaggedClass<any>()(tag, fields)
|
|
77
|
+
Object.assign(RequestClass, {
|
|
78
|
+
_tag: tag,
|
|
79
|
+
success: successSchema,
|
|
80
|
+
error: failureSchema,
|
|
81
|
+
config
|
|
82
|
+
})
|
|
98
83
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if (payload) {
|
|
102
|
-
Object.assign(this, payload)
|
|
103
|
-
}
|
|
104
|
-
;(this as any)._tag = tag
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
Object.assign(RequestClass, payloadSchema, {
|
|
109
|
-
_tag: tag,
|
|
110
|
-
fields: taggedFields,
|
|
111
|
-
success: successSchema,
|
|
112
|
-
error: failureSchema,
|
|
113
|
-
config
|
|
114
|
-
})
|
|
84
|
+
return RequestClass
|
|
85
|
+
}
|
|
115
86
|
|
|
116
|
-
|
|
117
|
-
|
|
87
|
+
function TaggedRequestFor<ModuleName extends string>(moduleName: ModuleName) {
|
|
88
|
+
function TaggedRequestWithMeta<Self>(): {
|
|
89
|
+
<Tag extends string, Payload extends S.Struct.Fields, C extends ServiceMap>(
|
|
90
|
+
tag: Tag,
|
|
91
|
+
fields: Payload,
|
|
92
|
+
config: RequestConfig & C
|
|
93
|
+
): TaggedRequestForResult<
|
|
94
|
+
Self,
|
|
95
|
+
Tag,
|
|
96
|
+
Payload,
|
|
97
|
+
SchemaOrFields<C["success"]>,
|
|
98
|
+
ErrorResult<C>,
|
|
99
|
+
Omit<C, "success" | "error">,
|
|
100
|
+
ModuleName
|
|
101
|
+
>
|
|
102
|
+
<Tag extends string, Payload extends S.Struct.Fields, C extends Pick<ServiceMap, "success">>(
|
|
103
|
+
tag: Tag,
|
|
104
|
+
fields: Payload,
|
|
105
|
+
config: RequestConfig & C
|
|
106
|
+
): TaggedRequestForResult<
|
|
107
|
+
Self,
|
|
108
|
+
Tag,
|
|
109
|
+
Payload,
|
|
110
|
+
SchemaOrFields<C["success"]>,
|
|
111
|
+
ErrorResult<C>,
|
|
112
|
+
Omit<C, "success" | "error">,
|
|
113
|
+
ModuleName
|
|
114
|
+
>
|
|
115
|
+
<Tag extends string, Payload extends S.Struct.Fields, C extends Pick<ServiceMap, "error">>(
|
|
116
|
+
tag: Tag,
|
|
117
|
+
fields: Payload,
|
|
118
|
+
config: RequestConfig & C
|
|
119
|
+
): TaggedRequestForResult<
|
|
120
|
+
Self,
|
|
121
|
+
Tag,
|
|
122
|
+
Payload,
|
|
123
|
+
typeof ForceVoid,
|
|
124
|
+
ErrorResult<C>,
|
|
125
|
+
Omit<C, "success" | "error">,
|
|
126
|
+
ModuleName
|
|
127
|
+
>
|
|
128
|
+
<Tag extends string, Payload extends S.Struct.Fields, C extends Record<string, any>>(
|
|
129
|
+
tag: Tag,
|
|
130
|
+
fields: Payload,
|
|
131
|
+
config: C & RequestConfig
|
|
132
|
+
): TaggedRequestForResult<
|
|
133
|
+
Self,
|
|
134
|
+
Tag,
|
|
135
|
+
Payload,
|
|
136
|
+
typeof ForceVoid,
|
|
137
|
+
ErrorResult<C>,
|
|
138
|
+
Omit<C, "success" | "error">,
|
|
139
|
+
ModuleName
|
|
140
|
+
>
|
|
141
|
+
<Tag extends string, Payload extends S.Struct.Fields>(
|
|
142
|
+
tag: Tag,
|
|
143
|
+
fields: Payload
|
|
144
|
+
): TaggedRequestForResult<
|
|
145
|
+
Self,
|
|
146
|
+
Tag,
|
|
147
|
+
Payload,
|
|
148
|
+
typeof ForceVoid,
|
|
149
|
+
ErrorResult<{}>,
|
|
150
|
+
Record<string, never>,
|
|
151
|
+
ModuleName
|
|
152
|
+
>
|
|
153
|
+
} {
|
|
154
|
+
return (<Tag extends string, Fields extends S.Struct.Fields, C extends ServiceMap>(
|
|
155
|
+
tag: Tag,
|
|
156
|
+
fields: Fields,
|
|
157
|
+
config?: C
|
|
158
|
+
) => {
|
|
159
|
+
const cls = makeRequestClass(tag, fields, config)
|
|
160
|
+
Object.assign(cls, { id: `${moduleName}.${tag}`, moduleName })
|
|
161
|
+
return cls
|
|
162
|
+
}) as any
|
|
163
|
+
}
|
|
164
|
+
return Object.assign(TaggedRequestWithMeta, { moduleName } as const)
|
|
118
165
|
}
|
|
119
166
|
|
|
120
167
|
return {
|
|
121
|
-
|
|
168
|
+
TaggedRequestFor
|
|
122
169
|
}
|
|
123
170
|
}
|
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
|
))
|