Skip to content

Commit

Permalink
Add example content to monorepo template (#34)
Browse files Browse the repository at this point in the history
  • Loading branch information
IMax153 authored Sep 11, 2024
1 parent 243077d commit 5aadbbd
Show file tree
Hide file tree
Showing 17 changed files with 277 additions and 22 deletions.
7 changes: 7 additions & 0 deletions templates/monorepo/packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@
"test": "vitest",
"coverage": "vitest --coverage"
},
"dependencies": {
"@effect/cli": "latest",
"@effect/platform": "latest",
"@effect/platform-node": "latest",
"@template/domain": "workspace:^",
"effect": "latest"
},
"effect": {
"generateExports": {
"include": [
Expand Down
39 changes: 39 additions & 0 deletions templates/monorepo/packages/cli/src/Cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Args, Command, Options } from "@effect/cli"
import { TodosClient } from "./TodosClient.js"

const todoArg = Args.text({ name: "todo" }).pipe(
Args.withDescription("The message associated with a todo")
)

const todoId = Options.integer("id").pipe(
Options.withDescription("The identifier of the todo")
)

const add = Command.make("add", { todo: todoArg }).pipe(
Command.withDescription("Add a new todo"),
Command.withHandler(({ todo }) => TodosClient.create(todo))
)

const done = Command.make("done", { id: todoId }).pipe(
Command.withDescription("Mark a todo as done"),
Command.withHandler(({ id }) => TodosClient.complete(id))
)

const list = Command.make("list").pipe(
Command.withDescription("List all todos"),
Command.withHandler(() => TodosClient.list)
)

const remove = Command.make("remove", { id: todoId }).pipe(
Command.withDescription("Remove a todo"),
Command.withHandler(({ id }) => TodosClient.remove(id))
)

const command = Command.make("todo").pipe(
Command.withSubcommands([add, done, list, remove])
)

export const cli = Command.run(command, {
name: "Todo CLI",
version: "0.0.0"
})
47 changes: 47 additions & 0 deletions templates/monorepo/packages/cli/src/TodosClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { HttpApiClient } from "@effect/platform"
import { TodosApi } from "@template/domain/TodosApi"
import { Effect, Layer } from "effect"

export const make = Effect.gen(function*() {
const client = yield* HttpApiClient.make(TodosApi, {
baseUrl: "http://localhost:3000"
})

function create(text: string) {
return client.todos.createTodo({ payload: { text } }).pipe(
Effect.flatMap((todo) => Effect.logInfo("Created todo: ", todo))
)
}

const list = client.todos.getAllTodos().pipe(
Effect.flatMap((todos) => Effect.logInfo(todos))
)

function complete(id: number) {
return client.todos.completeTodo({ path: { id } }).pipe(
Effect.flatMap((todo) => Effect.logInfo("Marked todo completed: ", todo)),
Effect.catchTag("TodoNotFound", () => Effect.logError(`Failed to find todo with id: ${id}`))
)
}

function remove(id: number) {
return client.todos.removeTodo({ path: { id } }).pipe(
Effect.flatMap(() => Effect.logInfo(`Deleted todo with id: ${id}`)),
Effect.catchTag("TodoNotFound", () => Effect.logError(`Failed to find todo with id: ${id}`))
)
}

return {
create,
list,
complete,
remove
} as const
})

export class TodosClient extends Effect.Tag("cli/TodosClient")<
TodosClient,
Effect.Effect.Success<typeof make>
>() {
static Live = Layer.effect(this, make)
}
16 changes: 16 additions & 0 deletions templates/monorepo/packages/cli/src/bin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env node

import { NodeContext, NodeHttpClient, NodeRuntime } from "@effect/platform-node"
import { Effect, Layer } from "effect"
import { cli } from "./Cli.js"
import { TodosClient } from "./TodosClient.js"

const MainLive = TodosClient.Live.pipe(
Layer.provide(NodeHttpClient.layerUndici),
Layer.merge(NodeContext.layer)
)

cli(process.argv).pipe(
Effect.provide(MainLive),
NodeRuntime.runMain
)
3 changes: 3 additions & 0 deletions templates/monorepo/packages/cli/tsconfig.build.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{
"extends": "./tsconfig.src.json",
"references": [
{ "path": "../domain/tsconfig.build.json" }
],
"compilerOptions": {
"types": ["node"],
"tsBuildInfoFile": ".tsbuildinfo/build.tsbuildinfo",
Expand Down
3 changes: 3 additions & 0 deletions templates/monorepo/packages/cli/tsconfig.src.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
{
"extends": "../../tsconfig.base.json",
"include": ["src"],
"references": [
{ "path": "../domain" }
],
"compilerOptions": {
"types": ["node"],
"outDir": "build/src",
Expand Down
3 changes: 2 additions & 1 deletion templates/monorepo/packages/cli/tsconfig.test.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"extends": "../../tsconfig.base.json",
"include": ["test"],
"references": [
{ "path": "tsconfig.src.json" }
{ "path": "tsconfig.src.json" },
{ "path": "../domain" }
],
"compilerOptions": {
"types": ["node"],
Expand Down
1 change: 1 addition & 0 deletions templates/monorepo/packages/domain/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"coverage": "vitest --coverage"
},
"dependencies": {
"@effect/platform": "^0.64.0",
"@effect/schema": "latest",
"@effect/sql": "latest",
"effect": "latest"
Expand Down
20 changes: 0 additions & 20 deletions templates/monorepo/packages/domain/src/Todo.ts

This file was deleted.

56 changes: 56 additions & 0 deletions templates/monorepo/packages/domain/src/TodosApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { HttpApi, HttpApiEndpoint, HttpApiGroup } from "@effect/platform"
import { Schema } from "@effect/schema"

export const TodoId = Schema.Number.pipe(Schema.brand("TodoId"))
export type TodoId = typeof TodoId.Type

export const TodoIdFromString = Schema.NumberFromString.pipe(
Schema.compose(TodoId)
)

export class Todo extends Schema.Class<Todo>("Todo")({
id: TodoId,
text: Schema.NonEmptyTrimmedString,
done: Schema.Boolean
}) {}

export class TodoNotFound extends Schema.TaggedError<TodoNotFound>()("TodoNotFound", {
id: Schema.Number
}) {}

export class TodosApiGroup extends HttpApiGroup.make("todos").pipe(
HttpApiGroup.add(
HttpApiEndpoint.get("getAllTodos", "/todos").pipe(
HttpApiEndpoint.setSuccess(Schema.Array(Todo))
)
),
HttpApiGroup.add(
HttpApiEndpoint.get("getTodoById", "/todos/:id").pipe(
HttpApiEndpoint.setSuccess(Todo),
HttpApiEndpoint.addError(TodoNotFound, { status: 404 }),
HttpApiEndpoint.setPath(Schema.Struct({ id: Schema.NumberFromString }))
)
),
HttpApiGroup.add(
HttpApiEndpoint.post("createTodo", "/todos").pipe(
HttpApiEndpoint.setSuccess(Todo),
HttpApiEndpoint.setPayload(Schema.Struct({ text: Schema.NonEmptyTrimmedString }))
)
),
HttpApiGroup.add(
HttpApiEndpoint.patch("completeTodo", "/todos/:id").pipe(
HttpApiEndpoint.setSuccess(Todo),
HttpApiEndpoint.addError(TodoNotFound, { status: 404 }),
HttpApiEndpoint.setPath(Schema.Struct({ id: Schema.NumberFromString }))
)
),
HttpApiGroup.add(
HttpApiEndpoint.del("removeTodo", "/todos/:id").pipe(
HttpApiEndpoint.setSuccess(Schema.Void),
HttpApiEndpoint.addError(TodoNotFound, { status: 404 }),
HttpApiEndpoint.setPath(Schema.Struct({ id: Schema.NumberFromString }))
)
)
) {}

export class TodosApi extends HttpApi.empty.pipe(HttpApi.addGroup(TodosApiGroup)) {}
6 changes: 6 additions & 0 deletions templates/monorepo/packages/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@
"test": "vitest",
"coverage": "vitest --coverage"
},
"dependencies": {
"@effect/platform": "latest",
"@effect/platform-node": "latest",
"@template/domain": "workspace:^",
"effect": "latest"
},
"effect": {
"generateExports": {
"include": [
Expand Down
20 changes: 20 additions & 0 deletions templates/monorepo/packages/server/src/Api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { HttpApiBuilder } from "@effect/platform"
import { TodosApi } from "@template/domain/TodosApi"
import { Effect, Layer } from "effect"
import { TodosRepository } from "./TodosRepository.js"

const TodosApiLive = HttpApiBuilder.group(TodosApi, "todos", (handlers) =>
Effect.gen(function*() {
const todos = yield* TodosRepository
return handlers.pipe(
HttpApiBuilder.handle("getAllTodos", () => todos.getAll),
HttpApiBuilder.handle("getTodoById", ({ path: { id } }) => todos.getById(id)),
HttpApiBuilder.handle("createTodo", ({ payload: { text } }) => todos.create(text)),
HttpApiBuilder.handle("completeTodo", ({ path: { id } }) => todos.complete(id)),
HttpApiBuilder.handle("removeTodo", ({ path: { id } }) => todos.remove(id))
)
}))

export const ApiLive = HttpApiBuilder.api(TodosApi).pipe(
Layer.provide(TodosApiLive)
)
53 changes: 53 additions & 0 deletions templates/monorepo/packages/server/src/TodosRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Todo, TodoId, TodoNotFound } from "@template/domain/TodosApi"
import { Context, Effect, HashMap, Layer, Ref } from "effect"

const make = Effect.gen(function*() {
const todos = yield* Ref.make(HashMap.empty<TodoId, Todo>())

const getAll = Ref.get(todos).pipe(
Effect.map((todos) => Array.from(HashMap.values(todos)))
)

function getById(id: number): Effect.Effect<Todo, TodoNotFound> {
return Ref.get(todos).pipe(
Effect.flatMap(HashMap.get(id)),
Effect.catchTag("NoSuchElementException", () => new TodoNotFound({ id }))
)
}

function create(text: string): Effect.Effect<Todo> {
return Ref.modify(todos, (map) => {
const id = TodoId.make(HashMap.reduce(map, 0, (max, todo) => todo.id > max ? todo.id : max))
const todo = new Todo({ id, text, done: false })
return [todo, HashMap.set(map, id, todo)]
})
}

function complete(id: number): Effect.Effect<Todo, TodoNotFound> {
return getById(id).pipe(
Effect.map((todo) => new Todo({ ...todo, done: true })),
Effect.tap((todo) => Ref.update(todos, HashMap.set(todo.id, todo)))
)
}

function remove(id: number): Effect.Effect<void, TodoNotFound> {
return getById(id).pipe(
Effect.flatMap((todo) => Ref.update(todos, HashMap.remove(todo.id)))
)
}

return {
getAll,
getById,
create,
complete,
remove
} as const
})

export class TodosRepository extends Context.Tag("api/TodosRepository")<
TodosRepository,
Effect.Effect.Success<typeof make>
>() {
static Live = Layer.effect(this, make)
}
16 changes: 16 additions & 0 deletions templates/monorepo/packages/server/src/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { HttpApiBuilder, HttpMiddleware } from "@effect/platform"
import { NodeHttpServer, NodeRuntime } from "@effect/platform-node"
import { Layer } from "effect"
import { createServer } from "node:http"
import { ApiLive } from "./Api.js"
import { TodosRepository } from "./TodosRepository.js"

const HttpLive = HttpApiBuilder.serve(HttpMiddleware.logger).pipe(
Layer.provide(ApiLive),
Layer.provide(TodosRepository.Live),
Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 }))
)

Layer.launch(HttpLive).pipe(
NodeRuntime.runMain
)
3 changes: 3 additions & 0 deletions templates/monorepo/packages/server/tsconfig.build.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{
"extends": "./tsconfig.src.json",
"references": [
{ "path": "../domain/tsconfig.build.json" }
],
"compilerOptions": {
"types": ["node"],
"tsBuildInfoFile": ".tsbuildinfo/build.tsbuildinfo",
Expand Down
3 changes: 3 additions & 0 deletions templates/monorepo/packages/server/tsconfig.src.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
{
"extends": "../../tsconfig.base.json",
"include": ["src"],
"references": [
{ "path": "../domain" }
],
"compilerOptions": {
"types": ["node"],
"outDir": "build/src",
Expand Down
3 changes: 2 additions & 1 deletion templates/monorepo/packages/server/tsconfig.test.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"extends": "../../tsconfig.base.json",
"include": ["test"],
"references": [
{ "path": "tsconfig.src.json" }
{ "path": "tsconfig.src.json" },
{ "path": "../domain" }
],
"compilerOptions": {
"types": ["node"],
Expand Down

0 comments on commit 5aadbbd

Please sign in to comment.