@effect-app/infra 2.16.8 → 2.17.0

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.
@@ -0,0 +1,193 @@
1
+ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
2
+ import { Rpc } from "@effect/rpc"
3
+ import type { Request } from "effect-app"
4
+ import { Context, Effect, FiberRef, Layer, S, Schedule } from "effect-app"
5
+ import { type GetEffectContext, makeRpcClient, type RPCContextMap, UnauthorizedError } from "effect-app/client"
6
+ import { HttpHeaders, HttpServerRequest } from "effect-app/http"
7
+ import type * as EffectRequest from "effect/Request"
8
+ import { makeMiddleware, makeRouter } from "../src/api/routing5.js"
9
+ import type { RequestContext } from "../src/RequestContext.js"
10
+
11
+ const optimisticConcurrencySchedule = Schedule.once
12
+ && Schedule.recurWhile<any>((a) => a?._tag === "OptimisticConcurrencyException")
13
+
14
+ export interface CTX {
15
+ context: RequestContext
16
+ }
17
+
18
+ export type CTXMap = {
19
+ // allowAnonymous: RPCContextMap.Inverted<"userProfile", UserProfile, typeof NotLoggedInError>
20
+ // TODO: not boolean but `string[]`
21
+ requireRoles: RPCContextMap.Custom<"", never, typeof UnauthorizedError, Array<string>>
22
+ }
23
+ const middleware = makeMiddleware({
24
+ contextMap: null as unknown as CTXMap,
25
+ // helper to deal with nested generic lmitations
26
+ context: null as any as HttpServerRequest.HttpServerRequest,
27
+ execute: Effect.gen(function*() {
28
+ return <T extends { config?: { [K in keyof CTXMap]?: any } }, Req extends S.TaggedRequest.All, R>(
29
+ _schema: T & S.Schema<Req, any, never>,
30
+ handler: (request: Req) => Effect.Effect<EffectRequest.Request.Success<Req>, EffectRequest.Request.Error<Req>, R>,
31
+ moduleName?: string
32
+ ) =>
33
+ (
34
+ req: Req
35
+ ): Effect.Effect<
36
+ Request.Request.Success<Req>,
37
+ Request.Request.Error<Req>,
38
+ | HttpServerRequest.HttpServerRequest
39
+ | Exclude<R, GetEffectContext<CTXMap, T["config"]>>
40
+ > =>
41
+ Effect
42
+ .gen(function*() {
43
+ // const headers = yield* Rpc.currentHeaders
44
+ const ctx = Context.empty()
45
+
46
+ // const config = "config" in schema ? schema.config : undefined
47
+
48
+ // Check JWT
49
+ // TODO
50
+ // if (!fakeLogin && !request.allowAnonymous) {
51
+ // yield* Effect.catchAll(
52
+ // checkJWTI({
53
+ // ...authConfig,
54
+ // issuer: authConfig.issuer + "/",
55
+ // jwksUri: `${authConfig.issuer}/.well-known/jwks.json`
56
+ // }),
57
+ // (err) => Effect.fail(new JWTError({ error: err }))
58
+ // )
59
+ // }
60
+
61
+ // const fakeLogin = true
62
+ // const r = (fakeLogin
63
+ // ? makeUserProfileFromUserHeader(headers["x-user"])
64
+ // : makeUserProfileFromAuthorizationHeader(
65
+ // headers["authorization"]
66
+ // ))
67
+ // .pipe(Effect.exit, basicRuntime.runSync)
68
+ // if (!Exit.isSuccess(r)) {
69
+ // yield* Effect.logWarning("Parsing userInfo failed").pipe(Effect.annotateLogs("r", r))
70
+ // }
71
+ // const userProfile = Option.fromNullable(Exit.isSuccess(r) ? r.value : undefined)
72
+ // if (Option.isSome(userProfile)) {
73
+ // // yield* rcc.update((_) => ({ ..._, userPorfile: userProfile.value }))
74
+ // ctx = ctx.pipe(Context.add(UserProfile, userProfile.value))
75
+ // } else if (!config?.allowAnonymous) {
76
+ // return yield* new NotLoggedInError({ message: "no auth" })
77
+ // }
78
+
79
+ // if (config?.requireRoles) {
80
+ // // TODO
81
+ // if (
82
+ // !userProfile.value
83
+ // || !config.requireRoles.every((role: any) => userProfile.value!.roles.includes(role))
84
+ // ) {
85
+ // return yield* new UnauthorizedError()
86
+ // }
87
+ // }
88
+
89
+ return yield* handler(req).pipe(
90
+ Effect.retry(optimisticConcurrencySchedule),
91
+ Effect.provide(ctx as Context.Context<GetEffectContext<CTXMap, T["config"]>>)
92
+ )
93
+ })
94
+ .pipe(
95
+ Effect.provide(
96
+ Effect
97
+ .gen(function*() {
98
+ yield* Effect.annotateCurrentSpan("request.name", moduleName ? `${moduleName}.${req._tag}` : req._tag)
99
+ // yield* RequestContextContainer.update((_) => ({
100
+ // ..._,
101
+ // name: NonEmptyString255(moduleName ? `${moduleName}.${req._tag}` : req._tag)
102
+ // }))
103
+ const httpReq = yield* HttpServerRequest.HttpServerRequest
104
+ // TODO: only pass Authentication etc, or move headers to actual Rpc Headers
105
+ yield* FiberRef.update(
106
+ Rpc.currentHeaders,
107
+ (headers) =>
108
+ HttpHeaders.merge(
109
+ httpReq.headers,
110
+ headers
111
+ )
112
+ )
113
+ })
114
+ .pipe(Layer.effectDiscard)
115
+ )
116
+ )
117
+ // .pipe(Effect.provide(RequestCacheLayers)) as any
118
+ })
119
+ })
120
+
121
+ export const { matchAll, matchFor } = makeRouter(middleware, true)
122
+
123
+ export type RequestConfig = {
124
+ /** Disable authentication requirement */
125
+ allowAnonymous?: true
126
+ /** Control the roles that are required to access the resource */
127
+ allowRoles?: readonly string[]
128
+ }
129
+ export const { TaggedRequest: Req } = makeRpcClient<RequestConfig, CTXMap>({
130
+ // allowAnonymous: NotLoggedInError,
131
+ requireRoles: UnauthorizedError
132
+ })
133
+
134
+ export class GetSomething extends Req<GetSomething>()("GetSomething", {
135
+ id: S.String
136
+ }, { success: S.Void }) {}
137
+
138
+ export class GetSomethingElse extends Req<GetSomethingElse>()("GetSomethingElse", {
139
+ id: S.String
140
+ }, { success: S.String }) {}
141
+
142
+ const Something = { GetSomething, GetSomethingElse, meta: { moduleName: "Something" as const } }
143
+
144
+ export class SomethingService extends Effect.Service<SomethingService>()("SomethingService", {
145
+ dependencies: [],
146
+ effect: Effect.gen(function*() {
147
+ return {}
148
+ })
149
+ }) {}
150
+
151
+ declare const a: {
152
+ (opt: { a: 1 }): void
153
+ (opt: { a: 2 }): void
154
+ (opt: { b: 3 }): void
155
+ (opt: { b: 3 }): void
156
+ }
157
+
158
+ export class SomethingRepo extends Effect.Service<SomethingRepo>()("SomethingRepo", {
159
+ dependencies: [SomethingService.Default],
160
+ effect: Effect.gen(function*() {
161
+ const smth = yield* SomethingService
162
+ console.log({ smth })
163
+ return {}
164
+ })
165
+ }) {}
166
+
167
+ export class SomethingService2 extends Effect.Service<SomethingService2>()("SomethingService2", {
168
+ dependencies: [],
169
+ effect: Effect.gen(function*() {
170
+ return {}
171
+ })
172
+ }) {}
173
+
174
+ export const routes = matchFor(Something)({
175
+ dependencies: [
176
+ SomethingRepo.Default,
177
+ SomethingService.Default,
178
+ SomethingService2.Default
179
+ ],
180
+ effect: Effect.gen(function*() {
181
+ const repo = yield* SomethingRepo
182
+ const smth = yield* SomethingService
183
+ const smth2 = yield* SomethingService2
184
+
185
+ console.log({ repo, smth, smth2 })
186
+
187
+ const { GetSomething, GetSomethingElse } = matchFor(Something)
188
+ return {
189
+ GetSomething: GetSomething(Effect.void),
190
+ GetSomethingElse: GetSomethingElse(Effect.succeed("12"))
191
+ }
192
+ })
193
+ })