Skip to content

Commit

Permalink
update examples to latest effect
Browse files Browse the repository at this point in the history
  • Loading branch information
IMax153 committed Oct 31, 2024
1 parent 7607c1d commit 8350f3e
Show file tree
Hide file tree
Showing 32 changed files with 647 additions and 637 deletions.
90 changes: 90 additions & 0 deletions examples/http-server/flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 18 additions & 19 deletions examples/http-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,40 +14,39 @@
"author": "",
"packageManager": "pnpm@9.10.0",
"devDependencies": {
"@effect/language-service": "^0.1.0",
"@effect/vitest": "^0.9.1",
"@types/node": "^22.5.3",
"@effect/language-service": "^0.2.0",
"@effect/vitest": "^0.13.7",
"@types/node": "^22.8.5",
"@types/uuid": "^10.0.0",
"prettier": "^3.3.3",
"tsup": "^8.2.4",
"tsx": "^4.19.0",
"typescript": "^5.5.4",
"vitest": "^2.0.5"
"tsup": "^8.3.5",
"tsx": "^4.19.2",
"typescript": "^5.6.3",
"vitest": "^2.1.4"
},
"dependencies": {
"@effect/experimental": "^0.24.1",
"@effect/opentelemetry": "^0.36.1",
"@effect/platform": "^0.63.1",
"@effect/platform-node": "^0.58.1",
"@effect/schema": "^0.72.1",
"@effect/sql": "^0.10.1",
"@effect/sql-sqlite-node": "^0.10.1",
"@effect/experimental": "^0.30.13",
"@effect/opentelemetry": "^0.39.7",
"@effect/platform": "^0.69.12",
"@effect/platform-node": "^0.64.13",
"@effect/sql": "^0.18.13",
"@effect/sql-sqlite-node": "^0.19.2",
"@eslint/compat": "1.1.1",
"@eslint/eslintrc": "3.1.0",
"@eslint/js": "9.10.0",
"@opentelemetry/exporter-trace-otlp-http": "^0.53.0",
"@opentelemetry/sdk-trace-base": "^1.26.0",
"@opentelemetry/sdk-trace-node": "^1.26.0",
"@opentelemetry/exporter-trace-otlp-http": "^0.54.0",
"@opentelemetry/sdk-trace-base": "^1.27.0",
"@opentelemetry/sdk-trace-node": "^1.27.0",
"@typescript-eslint/eslint-plugin": "^8.4.0",
"@typescript-eslint/parser": "^8.4.0",
"effect": "^3.7.1",
"effect": "^3.10.7",
"eslint": "^9.10.0",
"eslint-import-resolver-typescript": "^3.6.3",
"eslint-plugin-codegen": "^0.28.0",
"eslint-plugin-deprecation": "^3.0.0",
"eslint-plugin-import": "^2.30.0",
"eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-sort-destructure-keys": "^2.0.0",
"uuid": "^10.0.0"
"uuid": "^11.0.2"
}
}
213 changes: 94 additions & 119 deletions examples/http-server/src/Accounts.ts
Original file line number Diff line number Diff line change
@@ -1,143 +1,118 @@
import { HttpApiBuilder } from "@effect/platform"
import { SqlClient } from "@effect/sql"
import { Effect, Layer, Option, pipe } from "effect"
import { AccountsRepo } from "./Accounts/AccountsRepo.js"
import { UsersRepo } from "./Accounts/UsersRepo.js"
import { security } from "./Api/Security.js"
import type { AccessToken } from "./Domain/AccessToken.js"
import { accessTokenFromRedacted, accessTokenFromString } from "./Domain/AccessToken.js"
import { accessTokenFromString } from "./Domain/AccessToken.js"
import { Account } from "./Domain/Account.js"
import { policyRequire, Unauthorized } from "./Domain/Policy.js"
import { CurrentUser, User, UserId, UserNotFound, UserWithSensitive } from "./Domain/User.js"
import { policyRequire } from "./Domain/Policy.js"
import type { UserId } from "./Domain/User.js"
import { User, UserNotFound, UserWithSensitive } from "./Domain/User.js"
import { SqlLive, SqlTest } from "./Sql.js"
import { Uuid } from "./Uuid.js"

const make = Effect.gen(function*() {
const sql = yield* SqlClient.SqlClient
const accountRepo = yield* AccountsRepo
const userRepo = yield* UsersRepo
const uuid = yield* Uuid
export class Accounts extends Effect.Service<Accounts>()("Accounts", {
effect: Effect.gen(function*() {
const sql = yield* SqlClient.SqlClient
const accountRepo = yield* AccountsRepo
const userRepo = yield* UsersRepo
const uuid = yield* Uuid

const createUser = (user: typeof User.jsonCreate.Type) =>
accountRepo.insert(Account.insert.make({})).pipe(
Effect.tap((account) => Effect.annotateCurrentSpan("account", account)),
Effect.bindTo("account"),
Effect.bind("accessToken", () => uuid.generate.pipe(Effect.map(accessTokenFromString))),
Effect.bind("user", ({ accessToken, account }) =>
userRepo.insert(
User.insert.make({
...user,
accountId: account.id,
accessToken
})
)),
Effect.map(
({ account, user }) =>
new UserWithSensitive({
...user,
account
})
),
sql.withTransaction,
Effect.orDie,
Effect.withSpan("Accounts.createUser", { attributes: { user } }),
policyRequire("User", "create")
)

const updateUser = (id: UserId, user: Partial<typeof User.jsonUpdate.Type>) =>
userRepo.findById(id).pipe(
Effect.flatMap(
Option.match({
onNone: () => new UserNotFound({ id }),
onSome: Effect.succeed
})
),
Effect.andThen((previous) =>
userRepo.update({
...previous,
...user,
id,
updatedAt: undefined
})
),
sql.withTransaction,
Effect.catchTag("SqlError", (err) => Effect.die(err)),
Effect.withSpan("Accounts.updateUser", { attributes: { id, user } }),
policyRequire("User", "update")
)

const findUserByAccessToken = (apiKey: AccessToken) =>
pipe(
userRepo.findByAccessToken(apiKey),
Effect.withSpan("Accounts.findUserByAccessToken"),
policyRequire("User", "read")
)

const findUserById = (id: UserId) =>
pipe(
userRepo.findById(id),
Effect.withSpan("Accounts.findUserById", {
attributes: { id }
}),
policyRequire("User", "read")
)

const embellishUser = (user: User) =>
pipe(
accountRepo.findById(user.accountId),
Effect.flatten,
Effect.map((account) => new UserWithSensitive({ ...user, account })),
Effect.orDie,
Effect.withSpan("Accounts.embellishUser", {
attributes: { id: user.id }
}),
policyRequire("User", "readSensitive")
)
const createUser = (user: typeof User.jsonCreate.Type) =>
accountRepo.insert(Account.insert.make({})).pipe(
Effect.tap((account) => Effect.annotateCurrentSpan("account", account)),
Effect.bindTo("account"),
Effect.bind("accessToken", () => uuid.generate.pipe(Effect.map(accessTokenFromString))),
Effect.bind("user", ({ accessToken, account }) =>
userRepo.insert(
User.insert.make({
...user,
accountId: account.id,
accessToken
})
)),
Effect.map(
({ account, user }) =>
new UserWithSensitive({
...user,
account
})
),
sql.withTransaction,
Effect.orDie,
Effect.withSpan("Accounts.createUser", { attributes: { user } }),
policyRequire("User", "create")
)

const httpSecurity = HttpApiBuilder.middlewareSecurity(
security,
CurrentUser,
(token) =>
userRepo.findByAccessToken(accessTokenFromRedacted(token)).pipe(
const updateUser = (
id: UserId,
user: Partial<typeof User.jsonUpdate.Type>
) =>
userRepo.findById(id).pipe(
Effect.flatMap(
Option.match({
onNone: () =>
new Unauthorized({
actorId: UserId.make(-1),
entity: "User",
action: "read"
}),
onNone: () => new UserNotFound({ id }),
onSome: Effect.succeed
})
),
Effect.withSpan("Accounts.httpSecurity")
Effect.andThen((previous) =>
userRepo.update({
...previous,
...user,
id,
updatedAt: undefined
})
),
sql.withTransaction,
Effect.catchTag("SqlError", (err) => Effect.die(err)),
Effect.withSpan("Accounts.updateUser", { attributes: { id, user } }),
policyRequire("User", "update")
)
)

return {
createUser,
updateUser,
findUserByAccessToken,
findUserById,
embellishUser,
httpSecurity
} as const
})
const findUserByAccessToken = (apiKey: AccessToken) =>
pipe(
userRepo.findByAccessToken(apiKey),
Effect.withSpan("Accounts.findUserByAccessToken"),
policyRequire("User", "read")
)

export class Accounts extends Effect.Tag("Accounts")<
Accounts,
Effect.Effect.Success<typeof make>
>() {
static layer = Layer.effect(Accounts, make)
const findUserById = (id: UserId) =>
pipe(
userRepo.findById(id),
Effect.withSpan("Accounts.findUserById", {
attributes: { id }
}),
policyRequire("User", "read")
)

static Live = this.layer.pipe(
Layer.provide(SqlLive),
Layer.provide(AccountsRepo.Live),
Layer.provide(UsersRepo.Live),
Layer.provide(Uuid.Live)
)
const embellishUser = (user: User) =>
pipe(
accountRepo.findById(user.accountId),
Effect.flatten,
Effect.map((account) => new UserWithSensitive({ ...user, account })),
Effect.orDie,
Effect.withSpan("Accounts.embellishUser", {
attributes: { id: user.id }
}),
policyRequire("User", "readSensitive")
)

static Test = this.layer.pipe(
return {
createUser,
updateUser,
findUserByAccessToken,
findUserById,
embellishUser
} as const
}),
dependencies: [
SqlLive,
AccountsRepo.Default,
UsersRepo.Default,
Uuid.Default
]
}) {
static Test = this.DefaultWithoutDependencies.pipe(
Layer.provideMerge(SqlTest),
Layer.provideMerge(Uuid.Test)
)
Expand Down
Loading

0 comments on commit 8350f3e

Please sign in to comment.