effect-app 4.0.0-beta.2 → 4.0.0-beta.200
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 +960 -0
- package/dist/Array.d.ts +1 -1
- package/dist/Chunk.d.ts +1 -1
- package/dist/Chunk.d.ts.map +1 -1
- package/dist/Config/SecretURL.d.ts +1 -1
- package/dist/Config/SecretURL.d.ts.map +1 -1
- package/dist/Config/SecretURL.js +2 -2
- package/dist/Config/internal/configSecretURL.d.ts +1 -1
- package/dist/Config/internal/configSecretURL.d.ts.map +1 -1
- 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 +67 -0
- package/dist/Effect.d.ts +9 -10
- package/dist/Effect.d.ts.map +1 -1
- package/dist/Effect.js +3 -6
- package/dist/Function.d.ts +1 -1
- package/dist/Function.d.ts.map +1 -1
- package/dist/Inputify.type.d.ts +1 -1
- package/dist/Layer.d.ts +7 -6
- package/dist/Layer.d.ts.map +1 -1
- package/dist/Layer.js +1 -1
- package/dist/NonEmptySet.d.ts +1 -1
- package/dist/NonEmptySet.d.ts.map +1 -1
- package/dist/Option.d.ts +1 -1
- package/dist/Option.d.ts.map +1 -1
- package/dist/Pure.d.ts +5 -5
- package/dist/Pure.d.ts.map +1 -1
- package/dist/Pure.js +13 -13
- package/dist/Schema/Class.d.ts +66 -20
- package/dist/Schema/Class.d.ts.map +1 -1
- package/dist/Schema/Class.js +189 -22
- package/dist/Schema/FastCheck.d.ts +1 -1
- package/dist/Schema/FastCheck.d.ts.map +1 -1
- package/dist/Schema/Methods.d.ts +1 -1
- package/dist/Schema/SchemaParser.d.ts +5 -0
- package/dist/Schema/SchemaParser.d.ts.map +1 -0
- package/dist/Schema/SchemaParser.js +6 -0
- 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 +14 -6
- package/dist/Schema/brand.d.ts.map +1 -1
- package/dist/Schema/brand.js +1 -1
- package/dist/Schema/email.d.ts +1 -1
- package/dist/Schema/email.d.ts.map +1 -1
- package/dist/Schema/email.js +9 -5
- package/dist/Schema/ext.d.ts +121 -48
- package/dist/Schema/ext.d.ts.map +1 -1
- package/dist/Schema/ext.js +134 -52
- package/dist/Schema/moreStrings.d.ts +117 -17
- package/dist/Schema/moreStrings.d.ts.map +1 -1
- package/dist/Schema/moreStrings.js +19 -18
- package/dist/Schema/numbers.d.ts +127 -15
- package/dist/Schema/numbers.d.ts.map +1 -1
- package/dist/Schema/numbers.js +10 -12
- package/dist/Schema/phoneNumber.d.ts +1 -1
- package/dist/Schema/phoneNumber.d.ts.map +1 -1
- package/dist/Schema/phoneNumber.js +8 -4
- package/dist/Schema/schema.d.ts +1 -1
- package/dist/Schema/strings.d.ts +37 -5
- package/dist/Schema/strings.d.ts.map +1 -1
- package/dist/Schema/strings.js +1 -5
- package/dist/Schema.d.ts +159 -58
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +136 -68
- package/dist/Set.d.ts +1 -1
- package/dist/Set.d.ts.map +1 -1
- package/dist/TypeTest.d.ts +1 -1
- package/dist/Types.d.ts +1 -1
- package/dist/Widen.type.d.ts +1 -1
- package/dist/_ext/Array.d.ts +1 -1
- package/dist/_ext/Array.d.ts.map +1 -1
- package/dist/_ext/date.d.ts +1 -1
- package/dist/_ext/misc.d.ts +1 -1
- package/dist/_ext/ord.ext.d.ts +1 -1
- package/dist/_ext/ord.ext.d.ts.map +1 -1
- package/dist/builtin.d.ts +1 -1
- package/dist/builtin.d.ts.map +1 -1
- package/dist/client/InvalidationKeys.d.ts +29 -0
- package/dist/client/InvalidationKeys.d.ts.map +1 -0
- package/dist/client/InvalidationKeys.js +33 -0
- package/dist/client/apiClientFactory.d.ts +18 -32
- package/dist/client/apiClientFactory.d.ts.map +1 -1
- package/dist/client/apiClientFactory.js +98 -36
- package/dist/client/clientFor.d.ts +51 -17
- package/dist/client/clientFor.d.ts.map +1 -1
- package/dist/client/clientFor.js +9 -1
- package/dist/client/errors.d.ts +49 -25
- package/dist/client/errors.d.ts.map +1 -1
- package/dist/client/errors.js +43 -17
- package/dist/client/makeClient.d.ts +468 -32
- package/dist/client/makeClient.d.ts.map +1 -1
- package/dist/client/makeClient.js +61 -34
- package/dist/client.d.ts +2 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +2 -1
- package/dist/faker.d.ts +1 -1
- package/dist/faker.d.ts.map +1 -1
- package/dist/http/Request.d.ts +2 -2
- package/dist/http/Request.d.ts.map +1 -1
- package/dist/http/Request.js +5 -5
- package/dist/http/internal/lib.d.ts +1 -1
- package/dist/http.d.ts +1 -1
- package/dist/ids.d.ts +9 -9
- package/dist/ids.d.ts.map +1 -1
- package/dist/ids.js +3 -2
- package/dist/index.d.ts +5 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -9
- package/dist/logger.d.ts +1 -1
- package/dist/middleware.d.ts +16 -9
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +13 -9
- package/dist/rpc/Invalidation.d.ts +397 -0
- package/dist/rpc/Invalidation.d.ts.map +1 -0
- package/dist/rpc/Invalidation.js +150 -0
- package/dist/rpc/MiddlewareMaker.d.ts +6 -5
- package/dist/rpc/MiddlewareMaker.d.ts.map +1 -1
- package/dist/rpc/MiddlewareMaker.js +51 -28
- package/dist/rpc/RpcContextMap.d.ts +3 -3
- 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/rpc.d.ts +2 -2
- package/dist/rpc.d.ts.map +1 -1
- package/dist/rpc.js +2 -2
- package/dist/transform.d.ts +1 -1
- package/dist/transform.d.ts.map +1 -1
- package/dist/transform.js +3 -3
- package/dist/utils/effectify.d.ts +1 -1
- package/dist/utils/extend.d.ts +1 -1
- package/dist/utils/extend.d.ts.map +1 -1
- package/dist/utils/gen.d.ts +2 -2
- package/dist/utils/gen.d.ts.map +1 -1
- package/dist/utils/logLevel.d.ts +2 -2
- package/dist/utils/logLevel.d.ts.map +1 -1
- package/dist/utils/logger.d.ts +3 -3
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +3 -3
- package/dist/utils.d.ts +48 -10
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +33 -8
- package/dist/validation/validators.d.ts +1 -1
- package/dist/validation/validators.d.ts.map +1 -1
- package/dist/validation.d.ts +1 -1
- package/dist/validation.d.ts.map +1 -1
- package/package.json +46 -28
- package/src/Config/SecretURL.ts +2 -1
- package/src/Config.ts +14 -0
- package/src/ConfigProvider.ts +48 -0
- package/src/{ServiceMap.ts → Context.ts} +58 -64
- package/src/Effect.ts +12 -14
- package/src/Layer.ts +6 -5
- package/src/Pure.ts +17 -18
- package/src/Schema/Class.ts +268 -62
- package/src/Schema/SchemaParser.ts +12 -0
- package/src/Schema/SpecialJsonSchema.ts +137 -0
- package/src/Schema/SpecialOpenApi.ts +130 -0
- package/src/Schema/brand.ts +21 -7
- package/src/Schema/email.ts +10 -3
- package/src/Schema/ext.ts +226 -86
- package/src/Schema/moreStrings.ts +37 -32
- package/src/Schema/numbers.ts +14 -16
- package/src/Schema/phoneNumber.ts +8 -2
- package/src/Schema/strings.ts +4 -8
- package/src/Schema.ts +350 -107
- package/src/client/InvalidationKeys.ts +50 -0
- package/src/client/apiClientFactory.ts +227 -136
- package/src/client/clientFor.ts +86 -29
- package/src/client/errors.ts +61 -26
- package/src/client/makeClient.ts +530 -80
- package/src/client.ts +1 -0
- package/src/http/Request.ts +7 -4
- package/src/ids.ts +3 -2
- package/src/index.ts +5 -11
- package/src/middleware.ts +12 -10
- package/src/rpc/Invalidation.ts +221 -0
- package/src/rpc/MiddlewareMaker.ts +61 -51
- package/src/rpc/README.md +2 -2
- package/src/rpc/RpcContextMap.ts +6 -5
- package/src/rpc/RpcMiddleware.ts +6 -5
- package/src/rpc.ts +1 -1
- package/src/transform.ts +2 -2
- package/src/utils/gen.ts +1 -1
- package/src/utils/logger.ts +2 -2
- package/src/utils.ts +73 -15
- 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 +38 -6
- package/test/schema.test.ts +609 -4
- package/test/secretURL.test.ts +157 -0
- package/test/special.test.ts +1023 -0
- package/test/utils.test.ts +6 -6
- package/tsconfig.base.json +3 -4
- package/tsconfig.json +0 -1
- package/tsconfig.json.bak +2 -2
- package/tsconfig.src.json +29 -29
- package/tsconfig.test.json +2 -2
- package/dist/Operations.d.ts +0 -87
- package/dist/Operations.d.ts.map +0 -1
- package/dist/Operations.js +0 -29
- 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/eslint.config.mjs +0 -26
- package/src/Operations.ts +0 -55
- package/src/Struct.ts +0 -54
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import
|
|
3
|
-
import { flow } from "effect/Function"
|
|
2
|
+
import { constant, flow } from "effect/Function"
|
|
4
3
|
import * as Layer from "effect/Layer"
|
|
5
4
|
import * as ManagedRuntime from "effect/ManagedRuntime"
|
|
5
|
+
import * as Option from "effect/Option"
|
|
6
6
|
import * as Predicate from "effect/Predicate"
|
|
7
7
|
import * as Schema from "effect/Schema"
|
|
8
|
+
import * as Stream from "effect/Stream"
|
|
8
9
|
import * as Struct from "effect/Struct"
|
|
9
|
-
import { Rpc, RpcClient, RpcGroup, RpcSerialization } from "effect/unstable/rpc"
|
|
10
|
+
import { Rpc, RpcClient, RpcGroup, RpcMiddleware, RpcSerialization } from "effect/unstable/rpc"
|
|
11
|
+
import * as Config from "../Config.js"
|
|
12
|
+
import * as Context from "../Context.js"
|
|
10
13
|
import * as Effect from "../Effect.js"
|
|
11
14
|
import { HttpClient, HttpClientRequest } from "../http.js"
|
|
12
|
-
import
|
|
15
|
+
import { Invalidation } from "../rpc.js"
|
|
13
16
|
import type * as S from "../Schema.js"
|
|
14
|
-
import * as ServiceMap from "../ServiceMap.js"
|
|
15
17
|
import { typedKeysOf, typedValuesOf } from "../utils.js"
|
|
16
|
-
import type { Client, ClientForOptions,
|
|
18
|
+
import type { Client, ClientForOptions, ExtractModuleName, RequestsAny } from "./clientFor.js"
|
|
19
|
+
import { InvalidationKeysFromServer } from "./InvalidationKeys.js"
|
|
17
20
|
|
|
18
21
|
export interface ApiConfig {
|
|
19
22
|
url: string
|
|
@@ -31,16 +34,21 @@ export const DefaultApiConfig = Config.all({
|
|
|
31
34
|
})
|
|
32
35
|
|
|
33
36
|
export type Req = S.Top & {
|
|
34
|
-
|
|
37
|
+
readonly make: (...args: any[]) => any
|
|
35
38
|
_tag: string
|
|
36
39
|
fields: S.Struct.Fields
|
|
37
40
|
success: S.Top
|
|
38
41
|
error: S.Top
|
|
42
|
+
/** Optional final-value schema for stream requests. When set, the execute effect resolves with the last stream value decoded to this type. */
|
|
43
|
+
final?: S.Top
|
|
39
44
|
config?: Record<string, any>
|
|
40
|
-
readonly
|
|
45
|
+
readonly id: string
|
|
46
|
+
readonly moduleName: string
|
|
47
|
+
readonly type: "command" | "query"
|
|
48
|
+
readonly stream: boolean
|
|
41
49
|
}
|
|
42
50
|
|
|
43
|
-
class RequestName extends
|
|
51
|
+
class RequestName extends Context.Reference("RequestName", {
|
|
44
52
|
defaultValue: () => ({ requestName: "Unspecified", moduleName: "Error" })
|
|
45
53
|
}) {}
|
|
46
54
|
|
|
@@ -50,17 +58,17 @@ export const HttpClientLayer = (config: ApiConfig) =>
|
|
|
50
58
|
Effect
|
|
51
59
|
.gen(function*() {
|
|
52
60
|
const baseClient = yield* HttpClient.HttpClient
|
|
53
|
-
const ctx = yield* RequestName
|
|
54
61
|
const client = baseClient.pipe(
|
|
55
62
|
HttpClient.mapRequest(HttpClientRequest.prependUrl(config.url + "/rpc")),
|
|
56
63
|
HttpClient.mapRequest(
|
|
57
64
|
HttpClientRequest.setHeaders(config.headers.pipe(Option.getOrElse(() => ({}))))
|
|
58
65
|
),
|
|
59
|
-
HttpClient.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
66
|
+
HttpClient.mapRequestEffect((req) =>
|
|
67
|
+
Effect.map(RequestName.asEffect(), (ctx) =>
|
|
68
|
+
flow(
|
|
69
|
+
HttpClientRequest.appendUrlParam("action", ctx.requestName),
|
|
70
|
+
HttpClientRequest.appendUrl("/" + ctx.moduleName)
|
|
71
|
+
)(req))
|
|
64
72
|
)
|
|
65
73
|
)
|
|
66
74
|
return client
|
|
@@ -76,7 +84,7 @@ export const HttpClientFromConfigLayer = Layer.unwrap(
|
|
|
76
84
|
|
|
77
85
|
export const RpcSerializationLayer = (config: ApiConfig) =>
|
|
78
86
|
Layer.mergeAll(
|
|
79
|
-
RpcSerialization.
|
|
87
|
+
RpcSerialization.layerNdjson,
|
|
80
88
|
HttpClientLayer(config)
|
|
81
89
|
)
|
|
82
90
|
|
|
@@ -84,14 +92,14 @@ type RpcHandlers<M extends RequestsAny> = {
|
|
|
84
92
|
[K in keyof M]: Rpc.Rpc<M[K]["_tag"], M[K], M[K]["success"], M[K]["error"]>
|
|
85
93
|
}
|
|
86
94
|
|
|
87
|
-
const getFiltered = <M extends
|
|
95
|
+
const getFiltered = <M extends RequestsAny>(resource: M) => {
|
|
88
96
|
type Filtered = {
|
|
89
97
|
[K in keyof M as M[K] extends Req ? K : never]: M[K] extends Req ? M[K] : never
|
|
90
98
|
}
|
|
91
99
|
// TODO: Record.filter
|
|
92
100
|
const filtered = typedKeysOf(resource).reduce((acc, cur) => {
|
|
93
101
|
if (
|
|
94
|
-
Predicate.
|
|
102
|
+
Predicate.isObjectKeyword(resource[cur])
|
|
95
103
|
&& (resource[cur].success)
|
|
96
104
|
) {
|
|
97
105
|
acc[cur as keyof Filtered] = resource[cur] as any
|
|
@@ -102,13 +110,13 @@ const getFiltered = <M extends Requests>(resource: M) => {
|
|
|
102
110
|
return filtered as unknown as Filtered
|
|
103
111
|
}
|
|
104
112
|
|
|
105
|
-
export const getMeta = <M extends
|
|
106
|
-
const
|
|
107
|
-
if (
|
|
108
|
-
|
|
113
|
+
export const getMeta = <M extends RequestsAny>(resource: M): { moduleName: ExtractModuleName<M> } => {
|
|
114
|
+
const first = typedValuesOf(getFiltered(resource))[0]
|
|
115
|
+
if (first && "moduleName" in first) return { moduleName: first.moduleName }
|
|
116
|
+
throw new Error("No moduleName on requests!")
|
|
109
117
|
}
|
|
110
118
|
|
|
111
|
-
export const makeRpcGroupFromRequestsAndModuleName = <M extends
|
|
119
|
+
export const makeRpcGroupFromRequestsAndModuleName = <M extends RequestsAny, const ModuleName extends string>(
|
|
112
120
|
resource: M,
|
|
113
121
|
moduleName: ModuleName
|
|
114
122
|
) => {
|
|
@@ -117,7 +125,34 @@ export const makeRpcGroupFromRequestsAndModuleName = <M extends Requests, const
|
|
|
117
125
|
const rpcs = RpcGroup
|
|
118
126
|
.make(
|
|
119
127
|
...typedValuesOf(filtered).map((_) => {
|
|
120
|
-
|
|
128
|
+
const r = _ as any
|
|
129
|
+
const isStream = r.stream
|
|
130
|
+
const isCommand = r.type === "command"
|
|
131
|
+
const rpc = (isCommand
|
|
132
|
+
? isStream
|
|
133
|
+
? Invalidation.makeStreamRpc(r._tag, {
|
|
134
|
+
payload: r,
|
|
135
|
+
success: r.success,
|
|
136
|
+
error: r.error,
|
|
137
|
+
stream: true as const
|
|
138
|
+
})
|
|
139
|
+
: Invalidation.makeCommandRpc(r._tag, { payload: r, success: r.success, error: r.error })
|
|
140
|
+
: Rpc.make(r._tag, { payload: r, success: r.success, error: r.error, stream: isStream })) as any
|
|
141
|
+
|
|
142
|
+
// Stream rpcs force `errorSchema = Never` in effect-rpc and bury the
|
|
143
|
+
// resource error union inside `StreamFailureChunk`. Middleware-thrown
|
|
144
|
+
// errors (e.g. `NotLoggedInError` from auth) reach the Cause un-wrapped
|
|
145
|
+
// and would fail client decode. Attach a synthetic schema-only middleware
|
|
146
|
+
// tag carrying the resource error union so `Rpc.exitSchema` includes it
|
|
147
|
+
// in the failure union via the `rpc.middlewares[*].error` channel.
|
|
148
|
+
if (isStream && r.error) {
|
|
149
|
+
const ErrorBag = RpcMiddleware.Service<any>()(
|
|
150
|
+
`${moduleName}.${r._tag}.ClientErrorBag`,
|
|
151
|
+
{ error: r.error }
|
|
152
|
+
)
|
|
153
|
+
return rpc.middleware(ErrorBag as any)
|
|
154
|
+
}
|
|
155
|
+
return rpc
|
|
121
156
|
})
|
|
122
157
|
)
|
|
123
158
|
.prefix(`${moduleName}.`) as unknown as RpcGroup.RpcGroup<
|
|
@@ -126,155 +161,211 @@ export const makeRpcGroupFromRequestsAndModuleName = <M extends Requests, const
|
|
|
126
161
|
return rpcs
|
|
127
162
|
}
|
|
128
163
|
|
|
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) => {
|
|
164
|
+
const makeRpcTag = <M extends RequestsAny>(resource: M) => {
|
|
137
165
|
const meta = getMeta(resource)
|
|
138
166
|
const rpcs = makeRpcGroupFromRequestsAndModuleName(resource, meta.moduleName)
|
|
139
167
|
|
|
140
168
|
// Use Object.assign instead of class extension to avoid TS2509 with complex generic return types.
|
|
141
169
|
// The first type arg is `any` because this is a dynamically created tag — its identity is the string key.
|
|
142
|
-
const TheClient =
|
|
170
|
+
const TheClient = Context.Opaque<
|
|
143
171
|
any,
|
|
144
172
|
RpcClient.RpcClient<RpcGroup.Rpcs<typeof rpcs>>
|
|
145
173
|
>()(`RpcClient.${meta.moduleName}`)
|
|
146
174
|
// Use Layer.effect directly (not TheClient.toLayer) so TypeScript properly excludes Scope
|
|
147
175
|
const layer = Layer.effect(
|
|
148
176
|
TheClient,
|
|
149
|
-
|
|
150
|
-
RpcClient.make(rpcs, { spanPrefix: "RpcClient." + meta.moduleName }),
|
|
151
|
-
(cl) => (cl as any)[meta.moduleName]
|
|
152
|
-
)
|
|
177
|
+
RpcClient.make(rpcs, { spanPrefix: "RpcClient." + meta.moduleName })
|
|
153
178
|
)
|
|
154
179
|
return Object.assign(TheClient, { layer })
|
|
155
180
|
}
|
|
156
181
|
|
|
157
182
|
const makeApiClientFactory = Effect
|
|
158
183
|
.gen(function*() {
|
|
159
|
-
const ctx = yield* Effect.
|
|
160
|
-
const makeClientFor =
|
|
184
|
+
const ctx = yield* Effect.context<RpcSerialization.RpcSerialization | HttpClient.HttpClient>()
|
|
185
|
+
const makeClientFor = Effect.fnUntraced(function*<M extends RequestsAny>(
|
|
161
186
|
resource: M,
|
|
162
187
|
requestLevelLayers = Layer.empty,
|
|
163
188
|
options?: ClientForOptions
|
|
164
|
-
)
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
189
|
+
) {
|
|
190
|
+
const TheClient = makeRpcTag(resource)
|
|
191
|
+
|
|
192
|
+
const meta = getMeta(resource)
|
|
193
|
+
|
|
194
|
+
// TODO: somehow we need a protocol per REQUEST kind of it seems ...
|
|
195
|
+
// otherwise it locks up on the client, navigation remains empty...
|
|
196
|
+
const clientLayer = TheClient.layer.pipe(
|
|
197
|
+
// add ApiClientFactory for nested schemas
|
|
198
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
199
|
+
Layer.provide(Layer.succeed(ApiClientFactory, makeClientForCached as any)),
|
|
200
|
+
Layer.provide(
|
|
201
|
+
RpcClient
|
|
202
|
+
.layerProtocolHttp({ url: "" }) // why not here set meta.moduleName as root?
|
|
203
|
+
.pipe(
|
|
204
|
+
Layer.provideMerge(Layer.succeedContext(ctx))
|
|
205
|
+
)
|
|
206
|
+
)
|
|
207
|
+
)
|
|
208
|
+
const mr = ManagedRuntime.make(clientLayer)
|
|
209
|
+
|
|
210
|
+
const filtered = getFiltered(resource)
|
|
211
|
+
|
|
212
|
+
const unwrapCommand = (eff: Effect.Effect<any, any, any>): Effect.Effect<any, any, any> =>
|
|
213
|
+
eff.pipe(
|
|
214
|
+
Effect.flatMap((result: any) =>
|
|
215
|
+
Effect.gen(function*() {
|
|
216
|
+
const keys: ReadonlyArray<Invalidation.InvalidationKey> = result?.metadata?.invalidateQueries ?? []
|
|
217
|
+
const invalidationKeys = yield* InvalidationKeysFromServer
|
|
218
|
+
yield* Effect.forEach(keys, (key) => invalidationKeys.add(key), { discard: true })
|
|
219
|
+
return result.payload
|
|
220
|
+
})
|
|
221
|
+
),
|
|
222
|
+
// V2: unwrap CommandFailureWithMetaData failures — forward keys, re-fail with the
|
|
223
|
+
// original error so callers see the unmodified error type.
|
|
224
|
+
Effect.catch((result: any) =>
|
|
225
|
+
result?._tag === "CommandFailureWithMetaData"
|
|
226
|
+
? Effect.gen(function*() {
|
|
227
|
+
const keys: ReadonlyArray<Invalidation.InvalidationKey> = result.metadata?.invalidateQueries ?? []
|
|
228
|
+
const invalidationKeys = yield* InvalidationKeysFromServer
|
|
229
|
+
yield* Effect.forEach(keys, (key) => invalidationKeys.add(key), { discard: true })
|
|
230
|
+
return yield* Effect.fail(result.error)
|
|
180
231
|
})
|
|
181
|
-
.
|
|
182
|
-
Layer.provideMerge(Layer.succeedServices(ctx))
|
|
183
|
-
)
|
|
232
|
+
: Effect.fail(result)
|
|
184
233
|
)
|
|
185
234
|
)
|
|
186
|
-
const mr = ManagedRuntime.make(clientLayer)
|
|
187
|
-
|
|
188
|
-
const filtered = getFiltered(resource)
|
|
189
|
-
return {
|
|
190
|
-
mr,
|
|
191
|
-
client: (typedKeysOf(filtered)
|
|
192
|
-
.reduce((prev, cur) => {
|
|
193
|
-
const h = filtered[cur]!
|
|
194
235
|
|
|
195
|
-
|
|
236
|
+
return {
|
|
237
|
+
mr,
|
|
238
|
+
client: typedKeysOf(filtered)
|
|
239
|
+
.reduce((prev, cur) => {
|
|
240
|
+
const h = filtered[cur]!
|
|
241
|
+
|
|
242
|
+
const Request = h
|
|
243
|
+
|
|
244
|
+
const id = `${meta.moduleName}.${cur as string}`
|
|
245
|
+
.replaceAll(".js", "")
|
|
246
|
+
|
|
247
|
+
const requestMeta = {
|
|
248
|
+
Request,
|
|
249
|
+
id,
|
|
250
|
+
options
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const requestNameLayer = Layer.succeed(RequestName, {
|
|
254
|
+
requestName: cur as string,
|
|
255
|
+
moduleName: meta.moduleName
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
const layers = requestLevelLayers.pipe(Layer.provideMerge(requestNameLayer))
|
|
259
|
+
|
|
260
|
+
const fields = Struct.omit(Request.fields, ["_tag"] as const)
|
|
261
|
+
const requestAttr = `${meta.moduleName}.${h._tag}`
|
|
262
|
+
const isCommand = h.type === "command"
|
|
263
|
+
const isStream = h.stream
|
|
264
|
+
|
|
265
|
+
const buildEffect = (input: any) =>
|
|
266
|
+
mr.contextEffect.pipe(
|
|
267
|
+
Effect.flatMap((svcs) => {
|
|
268
|
+
const rpcEffect = TheClient
|
|
269
|
+
.use((client) => (client as any)[requestAttr]!(Request.make(input)) as Effect.Effect<any, any>)
|
|
270
|
+
.pipe(
|
|
271
|
+
Effect.provide(layers),
|
|
272
|
+
Effect.provide(svcs)
|
|
273
|
+
)
|
|
274
|
+
return isCommand ? unwrapCommand(rpcEffect) : rpcEffect
|
|
275
|
+
})
|
|
276
|
+
)
|
|
196
277
|
|
|
197
|
-
|
|
198
|
-
|
|
278
|
+
const buildStream = (input: any) =>
|
|
279
|
+
Stream.unwrap(
|
|
280
|
+
mr.contextEffect.pipe(
|
|
281
|
+
Effect.flatMap((svcs) =>
|
|
282
|
+
TheClient
|
|
283
|
+
.useSync((client) => {
|
|
284
|
+
const rpcStream = (client as any)[requestAttr]!(
|
|
285
|
+
Request.make(input)
|
|
286
|
+
) as Stream.Stream<any, any, any>
|
|
287
|
+
return rpcStream.pipe(
|
|
288
|
+
// Collect server invalidation keys from the "done" chunk, then discard it.
|
|
289
|
+
Stream.tap((item: any) =>
|
|
290
|
+
item._tag === "done" || item._tag === "metadata"
|
|
291
|
+
? InvalidationKeysFromServer.use((svc) =>
|
|
292
|
+
Effect.forEach(
|
|
293
|
+
(item.metadata as Invalidation.CommandMetaData).invalidateQueries,
|
|
294
|
+
svc.add,
|
|
295
|
+
{ discard: true }
|
|
296
|
+
)
|
|
297
|
+
)
|
|
298
|
+
: Effect.void
|
|
299
|
+
),
|
|
300
|
+
Stream.filter((item: any) => item._tag === "value"),
|
|
301
|
+
Stream.map((item: any) => item.value),
|
|
302
|
+
// V2: unwrap StreamFailureChunk — forward keys from failures too,
|
|
303
|
+
// then re-fail with the original error so callers see the unmodified
|
|
304
|
+
// error type.
|
|
305
|
+
Stream.catch((err: any) =>
|
|
306
|
+
err?._tag === "error" && err?.metadata
|
|
307
|
+
? Stream.fromEffect(
|
|
308
|
+
InvalidationKeysFromServer.use((svc) =>
|
|
309
|
+
Effect
|
|
310
|
+
.forEach(
|
|
311
|
+
(err.metadata as Invalidation.CommandMetaData).invalidateQueries,
|
|
312
|
+
svc.add,
|
|
313
|
+
{ discard: true }
|
|
314
|
+
)
|
|
315
|
+
.pipe(Effect.flatMap(() => Effect.fail(err.error)))
|
|
316
|
+
)
|
|
317
|
+
)
|
|
318
|
+
: Stream.fail(err)
|
|
319
|
+
),
|
|
320
|
+
Stream.provide(layers),
|
|
321
|
+
Stream.provide(svcs)
|
|
322
|
+
)
|
|
323
|
+
})
|
|
324
|
+
.pipe(Effect.provide(svcs))
|
|
325
|
+
)
|
|
326
|
+
)
|
|
327
|
+
)
|
|
199
328
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
329
|
+
// @ts-expect-error doc
|
|
330
|
+
prev[cur] = Object.keys(fields).length === 0
|
|
331
|
+
? {
|
|
332
|
+
handler: isStream ? constant(buildStream({})) : constant(buildEffect({})),
|
|
333
|
+
...requestMeta
|
|
334
|
+
}
|
|
335
|
+
: {
|
|
336
|
+
handler: isStream
|
|
337
|
+
? (req: any) => buildStream(req)
|
|
338
|
+
: (req: any) => buildEffect(req),
|
|
339
|
+
...requestMeta
|
|
204
340
|
}
|
|
205
341
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
const layers = requestLevelLayers.pipe(Layer.provideMerge(requestNameLayer))
|
|
212
|
-
|
|
213
|
-
const fields = Struct.omit(Request.fields, ["_tag"] as const)
|
|
214
|
-
const requestAttr = h._tag
|
|
215
|
-
// @ts-expect-error doc
|
|
216
|
-
prev[cur] = Object.keys(fields).length === 0
|
|
217
|
-
? {
|
|
218
|
-
handler: mr.servicesEffect.pipe(
|
|
219
|
-
Effect.flatMap((svcs) =>
|
|
220
|
-
TheClient
|
|
221
|
-
.use((client) => (client as any)[requestAttr]!(new Request()) as Effect.Effect<any, any, never>)
|
|
222
|
-
.pipe(
|
|
223
|
-
Effect.provide(layers),
|
|
224
|
-
Effect.provide(svcs)
|
|
225
|
-
)
|
|
226
|
-
)
|
|
227
|
-
),
|
|
228
|
-
...requestMeta
|
|
229
|
-
}
|
|
230
|
-
: {
|
|
231
|
-
handler: (req: any) =>
|
|
232
|
-
mr.servicesEffect.pipe(
|
|
233
|
-
Effect.flatMap((svcs) =>
|
|
234
|
-
TheClient
|
|
235
|
-
.use((client) =>
|
|
236
|
-
(client as any)[requestAttr]!(new Request(req)) as Effect.Effect<any, any, never>
|
|
237
|
-
)
|
|
238
|
-
.pipe(
|
|
239
|
-
Effect.provide(layers),
|
|
240
|
-
Effect.provide(svcs)
|
|
241
|
-
)
|
|
242
|
-
)
|
|
243
|
-
),
|
|
244
|
-
|
|
245
|
-
...requestMeta
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
return prev
|
|
249
|
-
}, {} as Client<M, M["meta"]["moduleName"]>))
|
|
250
|
-
}
|
|
251
|
-
})
|
|
342
|
+
return prev
|
|
343
|
+
}, {} as Client<M, ExtractModuleName<M>>)
|
|
344
|
+
}
|
|
345
|
+
})
|
|
252
346
|
|
|
253
347
|
const register: ManagedRuntime.ManagedRuntime<any, any>[] = []
|
|
254
348
|
yield* Effect.addFinalizer(() => Effect.forEach(register, (mr) => mr.disposeEffect))
|
|
255
349
|
|
|
256
350
|
const cacheL = new Map<any, Map<any, Client<any, any>>>()
|
|
257
351
|
|
|
258
|
-
function makeClientForCached(requestLevelLayers: Layer.Layer<never
|
|
352
|
+
function makeClientForCached(requestLevelLayers: Layer.Layer<never>, options?: ClientForOptions) {
|
|
259
353
|
let cache = cacheL.get(requestLevelLayers)
|
|
260
354
|
if (!cache) {
|
|
261
355
|
cache = new Map<any, Client<any, any>>()
|
|
262
356
|
cacheL.set(requestLevelLayers, cache)
|
|
263
357
|
}
|
|
264
358
|
|
|
265
|
-
return
|
|
266
|
-
models
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
register.push(m.mr)
|
|
276
|
-
return m.client
|
|
277
|
-
})
|
|
359
|
+
return Effect.fnUntraced(function*<M extends RequestsAny>(models: M) {
|
|
360
|
+
const found = cache.get(models) as Client<M, ExtractModuleName<M>> | undefined
|
|
361
|
+
if (found) {
|
|
362
|
+
return found
|
|
363
|
+
}
|
|
364
|
+
const m = yield* makeClientFor(models, requestLevelLayers, options)
|
|
365
|
+
cache.set(models, m.client)
|
|
366
|
+
register.push(m.mr)
|
|
367
|
+
return m.client
|
|
368
|
+
})
|
|
278
369
|
}
|
|
279
370
|
|
|
280
371
|
return makeClientForCached
|
|
@@ -284,7 +375,7 @@ const makeApiClientFactory = Effect
|
|
|
284
375
|
* Used to create clients for resource modules.
|
|
285
376
|
*/
|
|
286
377
|
export class ApiClientFactory
|
|
287
|
-
extends
|
|
378
|
+
extends Context.Opaque<ApiClientFactory, Effect.Success<typeof makeApiClientFactory>>()("ApiClientFactory")
|
|
288
379
|
{
|
|
289
380
|
static readonly layer = (config: ApiConfig) =>
|
|
290
381
|
ApiClientFactory.toLayer(makeApiClientFactory).pipe(Layer.provide(RpcSerializationLayer(config)))
|
|
@@ -296,8 +387,8 @@ export class ApiClientFactory
|
|
|
296
387
|
)
|
|
297
388
|
|
|
298
389
|
static readonly makeFor =
|
|
299
|
-
(requestLevelLayers: Layer.Layer<never
|
|
300
|
-
<M extends
|
|
390
|
+
(requestLevelLayers: Layer.Layer<never>, options?: ClientForOptions) =>
|
|
391
|
+
<M extends RequestsAny>(
|
|
301
392
|
resource: M
|
|
302
393
|
) =>
|
|
303
394
|
ApiClientFactory.use((apiClientFactory) => {
|
package/src/client/clientFor.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
3
|
|
|
4
4
|
import * as Record from "effect/Record"
|
|
5
|
-
import type * as
|
|
5
|
+
import type * as Stream from "effect/Stream"
|
|
6
6
|
import type { Path } from "path-parser"
|
|
7
7
|
import qs from "query-string"
|
|
8
8
|
import type * as Effect from "../Effect.js"
|
|
@@ -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,
|
|
@@ -66,48 +71,100 @@ export type ExtractEResponse<T> = T extends S.Codec<any> ? S.Codec.Encoded<T>
|
|
|
66
71
|
: T extends unknown ? void
|
|
67
72
|
: never
|
|
68
73
|
|
|
69
|
-
type IsEmpty<T> = keyof T extends never ? true
|
|
70
|
-
: false
|
|
71
|
-
|
|
72
|
-
// v4: Request.RequestTypeId, S.symbolSerializable, S.symbolWithResult removed — use keyof Request to filter internal props
|
|
73
|
-
type Cruft = "_tag" | keyof Request.Request<any, any, any>
|
|
74
|
-
|
|
75
74
|
export interface ClientForOptions {
|
|
76
75
|
readonly skipQueryKey?: readonly string[]
|
|
77
76
|
}
|
|
78
77
|
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
// $Project/$Configuration.Index
|
|
79
|
+
// -> "$Project", "$Configuration", "Index"
|
|
80
|
+
export const makeQueryKey = ({ id, options }: { id: string; options?: ClientForOptions }) =>
|
|
81
|
+
id
|
|
82
|
+
.split("/")
|
|
83
|
+
.filter((segment: string) => !options || !options.skipQueryKey?.includes(segment))
|
|
84
|
+
.map((segment: string) => "$" + segment)
|
|
85
|
+
.join(".")
|
|
86
|
+
.split(".")
|
|
87
|
+
|
|
88
|
+
export interface RequestHandlerWithInput<I, A, E, R, Request extends Req, Id extends string> {
|
|
89
|
+
handler: (i: I) => Effect.Effect<A, E, R>
|
|
81
90
|
id: Id
|
|
82
91
|
options?: ClientForOptions
|
|
83
92
|
Request: Request
|
|
84
93
|
}
|
|
85
94
|
|
|
86
|
-
|
|
87
|
-
|
|
95
|
+
/** Type alias: a no-input handler is simply `RequestHandlerWithInput<void, …>`. */
|
|
96
|
+
export type RequestHandler<A, E, R, Request extends Req, Id extends string> = RequestHandlerWithInput<
|
|
97
|
+
void,
|
|
98
|
+
A,
|
|
99
|
+
E,
|
|
100
|
+
R,
|
|
101
|
+
Request,
|
|
102
|
+
Id
|
|
103
|
+
>
|
|
104
|
+
|
|
105
|
+
export interface RequestStreamHandlerWithInput<I, A, E, R, Request extends Req, Id extends string, Final = A> {
|
|
106
|
+
handler: (i: I) => Stream.Stream<A, E, R>
|
|
88
107
|
id: Id
|
|
89
108
|
options?: ClientForOptions
|
|
90
109
|
Request: Request
|
|
110
|
+
/**
|
|
111
|
+
* Phantom type property (never set at runtime) that carries the `Final` type to
|
|
112
|
+
* `StreamMutationWithExtensions`. The tilde prefix follows the Effect convention for
|
|
113
|
+
* phantom/virtual properties and prevents accidental runtime access.
|
|
114
|
+
* Stream failures bubble through the execute effect's typed error channel `E`;
|
|
115
|
+
* the reactive `AsyncResult` ref also mirrors the failure for live progress UI.
|
|
116
|
+
*/
|
|
117
|
+
readonly "~final"?: Final
|
|
91
118
|
}
|
|
92
119
|
|
|
120
|
+
/** Type alias: a no-input stream handler is simply `RequestStreamHandlerWithInput<void, …>`. */
|
|
121
|
+
export type RequestStreamHandler<A, E, R, Request extends Req, Id extends string, Final = A> =
|
|
122
|
+
RequestStreamHandlerWithInput<void, A, E, R, Request, Id, Final>
|
|
123
|
+
|
|
93
124
|
// make sure this is exported or d.ts of apiClientFactory breaks?!
|
|
94
|
-
type
|
|
125
|
+
export type RequestInputFromMake<I extends { readonly make: (...args: any[]) => any }> = Parameters<I["make"]> extends
|
|
126
|
+
[] ? void : Parameters<I["make"]>[0]
|
|
127
|
+
|
|
128
|
+
// Has no input only when the request schema declares no payload fields (the auto-added
|
|
129
|
+
// `_tag` field is ignored). Any payload fields (even all-optional) produce a function handler.
|
|
130
|
+
type HasNoFields<I> = I extends { readonly fields: infer F extends S.Struct.Fields }
|
|
131
|
+
? [Exclude<keyof F, "_tag">] extends [never] ? true : false
|
|
132
|
+
: false
|
|
133
|
+
|
|
134
|
+
type RequestInput<I extends { readonly make: (...args: any[]) => any }> = Parameters<I["make"]>[0]
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Caller-facing input type for a request. `void` when the request schema has no fields;
|
|
138
|
+
* otherwise `make`'s first param type.
|
|
139
|
+
*/
|
|
140
|
+
export type HandlerInput<I extends { readonly make: (...args: any[]) => any }> = HasNoFields<I> extends true ? void
|
|
141
|
+
: RequestInput<I>
|
|
142
|
+
|
|
143
|
+
/** Extracts the final-value type from a stream request. Defaults to the success type when no `final` schema is set. */
|
|
144
|
+
type FinalTypeOf<T extends Req> = T extends { readonly final: infer F extends S.Top } ? S.Schema.Type<F>
|
|
145
|
+
: S.Schema.Type<T["success"]>
|
|
146
|
+
|
|
147
|
+
type RequestHandlerFor<R, E, T extends Req, Id extends string> = T["stream"] extends true
|
|
148
|
+
? RequestStreamHandlerWithInput<
|
|
149
|
+
HandlerInput<T>,
|
|
150
|
+
S.Schema.Type<T["success"]>,
|
|
151
|
+
S.Schema.Type<T["error"]> | E,
|
|
152
|
+
R | S.Codec.DecodingServices<T["success"]> | S.Codec.DecodingServices<T["error"]>,
|
|
153
|
+
T,
|
|
154
|
+
Id,
|
|
155
|
+
FinalTypeOf<T>
|
|
156
|
+
>
|
|
157
|
+
: RequestHandlerWithInput<
|
|
158
|
+
HandlerInput<T>,
|
|
159
|
+
S.Schema.Type<T["success"]>,
|
|
160
|
+
S.Schema.Type<T["error"]> | E,
|
|
161
|
+
R | S.Codec.DecodingServices<T["success"]> | S.Codec.DecodingServices<T["error"]>,
|
|
162
|
+
T,
|
|
163
|
+
Id
|
|
164
|
+
>
|
|
95
165
|
|
|
96
166
|
export type RequestHandlers<R, E, M extends RequestsAny, ModuleName extends string> = {
|
|
97
|
-
[K in keyof M as M[K] extends Req ? K : never]:
|
|
98
|
-
?
|
|
99
|
-
|
|
100
|
-
S.Schema.Type<M[K]["error"]> | E,
|
|
101
|
-
R | ReqDecodingServices<M[K]>,
|
|
102
|
-
M[K],
|
|
103
|
-
`${ModuleName}.${K & string}`
|
|
104
|
-
>
|
|
105
|
-
: RequestHandlerWithInput<
|
|
106
|
-
Omit<S.Schema.Type<M[K]>, Cruft>,
|
|
107
|
-
S.Schema.Type<M[K]["success"]>,
|
|
108
|
-
S.Schema.Type<M[K]["error"]> | E,
|
|
109
|
-
R | ReqDecodingServices<M[K]>,
|
|
110
|
-
M[K],
|
|
111
|
-
`${ModuleName}.${K & string}`
|
|
112
|
-
>
|
|
167
|
+
[K in keyof M as M[K] extends Req ? K : never]: Extract<M[K], Req> extends infer T extends Req
|
|
168
|
+
? RequestHandlerFor<R, E, T, `${ModuleName}.${K & string}`>
|
|
169
|
+
: never
|
|
113
170
|
}
|