forked from effect-app/boilerplate
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathUserRepo.ts
More file actions
113 lines (106 loc) · 3.59 KB
/
UserRepo.ts
File metadata and controls
113 lines (106 loc) · 3.59 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import { RepoConfig } from "#api/config"
import { RepoDefault } from "#api/lib/layers"
import { User } from "#Domain/User"
import type { UserId } from "#Domain/User"
import { Model } from "@effect-app/infra"
import { NotFoundError, NotLoggedInError } from "@effect-app/infra/errors"
import { Q } from "@effect-app/infra/Model"
import { generate } from "@effect-app/infra/test"
import { Array, Effect, Exit, Layer, Option, pipe, Request, RequestResolver, S } from "effect-app"
import { fakerArb } from "effect-app/faker"
import { Email } from "effect-app/Schema"
import fc from "fast-check"
import { UserProfile } from "../UserProfile.js"
export type UserSeed = "sample" | ""
export class UserRepo extends Effect.Service<UserRepo>()("UserRepo", {
dependencies: [RepoDefault],
effect: Effect.gen(function*() {
const cfg = yield* RepoConfig
const makeInitial = yield* Effect.cached(Effect.sync(() => {
const seed = cfg.fakeUsers === "seed" ? "seed" : cfg.fakeUsers === "sample" ? "sample" : ""
const fakeUsers = pipe(
Array
.range(1, 8)
.map((_, i): User => {
const g = generate(S.A.make(User)).value
const emailArb = fakerArb((_) => () =>
_
.internet
.exampleEmail({ firstName: g.name.firstName, lastName: g.name.lastName })
)
return new User({
...g,
email: Email(generate(emailArb(fc)).value),
role: i === 0 || i === 1 ? "manager" : "user"
})
}),
Array.toNonEmptyArray,
Option
.match({
onNone: () => {
throw new Error("must have fake users")
},
onSome: (_) => _
})
)
const items = seed === "sample" ? fakeUsers : []
return items
}))
return yield* Model.makeRepo("User", User, { makeInitial })
})
}) {
get tryGetCurrentUser() {
return Effect.serviceOption(UserProfile).pipe(
Effect.andThen((_) => _.pipe(Effect.mapError(() => new NotLoggedInError()))),
Effect.andThen((_) => this.get(_.sub))
)
}
get getCurrentUser() {
return UserProfile.pipe(
Effect.andThen((_) => this.get(_.sub))
)
}
static readonly getUserByIdResolver = RequestResolver
.makeBatched((requests: GetUserById[]) =>
this.use((_) =>
_
.query(Q.where("id", "in", requests.map((_) => _.id)))
.pipe(Effect.andThen((users) =>
Effect.forEach(requests, (r) =>
Request.complete(
r,
Array
.findFirst(users, (_) => _.id === r.id ? Option.some(Exit.succeed(_)) : Option.none())
.pipe(Option.getOrElse(() => Exit.fail(new NotFoundError({ type: "User", id: r.id }))))
), { discard: true })
))
)
)
.pipe(
RequestResolver.batchN(25),
RequestResolver.contextFromServices(UserRepo)
)
static readonly UserFromIdLayer = User
.resolver
.toLayer(
this.use((userRepo) =>
this
.getUserByIdResolver
.pipe(
Effect.provideService(this, userRepo),
Effect.map((resolver) => ({
get: (id: UserId) =>
Effect
.request(GetUserById({ id }), resolver)
.pipe(Effect.orDie)
}))
)
)
)
.pipe(Layer.provide(this.Default))
}
interface GetUserById extends Request.Request<User, NotFoundError<"User">> {
readonly _tag: "GetUserById"
readonly id: UserId
}
const GetUserById = Request.tagged<GetUserById>("GetUserById")