diff --git a/bun.lockb b/bun.lockb index 8da25e4..977532e 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/bunfig.toml b/bunfig.toml new file mode 100644 index 0000000..4fe1e6e --- /dev/null +++ b/bunfig.toml @@ -0,0 +1,2 @@ +[install.scopes] +"@thilawyn" = "https://git.valverde.cloud/api/packages/thilawyn/npm/" diff --git a/packages/example/package.json b/packages/example/package.json index eb4afd7..f9c93b6 100644 --- a/packages/example/package.json +++ b/packages/example/package.json @@ -14,6 +14,7 @@ "@tanstack/react-router": "^1.95.3", "@tanstack/router-devtools": "^1.95.3", "@tanstack/router-plugin": "^1.95.3", + "@thilawyn/thilaschema": "^0.1.4", "@types/react": "^19.0.4", "@types/react-dom": "^19.0.2", "@vitejs/plugin-react": "^4.3.4", @@ -30,5 +31,9 @@ "typescript": "~5.6.2", "typescript-eslint": "^8.18.2", "vite": "^6.0.5" + }, + "dependencies": { + "@effect/platform": "^0.73.1", + "@effect/platform-browser": "^0.52.1" } } diff --git a/packages/example/src/domain/Todo.ts b/packages/example/src/domain/Todo.ts new file mode 100644 index 0000000..5eb8474 --- /dev/null +++ b/packages/example/src/domain/Todo.ts @@ -0,0 +1,19 @@ +import { ThSchema } from "@thilawyn/thilaschema" +import { Schema } from "effect" + + +export class Todo extends Schema.Class("Todo")({ + id: Schema.String, + content: Schema.String, + completedAt: Schema.DateTimeUtcFromSelf, +}) {} + + +export const TodoFromJsonStruct = Schema.Struct({ + ...Todo.fields, + completedAt: Schema.DateTimeUtc, +}).pipe( + ThSchema.assertEncodedJsonifiable +) + +export const TodoFromJson = TodoFromJsonStruct.pipe(Schema.compose(Todo)) diff --git a/packages/example/src/domain/index.ts b/packages/example/src/domain/index.ts index a099a8a..4bf1f83 100644 --- a/packages/example/src/domain/index.ts +++ b/packages/example/src/domain/index.ts @@ -1 +1,2 @@ export * as Post from "./Post" +export * as Todo from "./Todo" diff --git a/packages/example/src/routes/index.tsx b/packages/example/src/routes/index.tsx index c955348..8e2bdad 100644 --- a/packages/example/src/routes/index.tsx +++ b/packages/example/src/routes/index.tsx @@ -1,10 +1,9 @@ -import { Reffuse } from "@/reffuse" -import { FetchData } from "@/services" -import { PostsContext } from "@/views/posts/reffuse" -import { PostsState } from "@/views/posts/services" -import { VPosts } from "@/views/posts/VPosts" +import { TodosContext } from "@/todos/reffuse" +import { TodosState } from "@/todos/services" +import { VTodos } from "@/todos/views/VTodos" import { createFileRoute } from "@tanstack/react-router" -import { Effect } from "effect" +import { Layer } from "effect" +import { useMemo } from "react" export const Route = createFileRoute("/")({ @@ -13,17 +12,16 @@ export const Route = createFileRoute("/")({ function Index() { - const postsLayer = Reffuse.useMemo(FetchData.FetchData.pipe( - Effect.flatMap(({ fetchPosts }) => fetchPosts), - Effect.map(PostsState.make), - )) + const todosLayer = useMemo(() => Layer.empty.pipe( + Layer.provideMerge(TodosState.make("todos")) + ), []) return (
- - - + + +
) diff --git a/packages/example/src/todos/reffuse.ts b/packages/example/src/todos/reffuse.ts new file mode 100644 index 0000000..47b83d1 --- /dev/null +++ b/packages/example/src/todos/reffuse.ts @@ -0,0 +1,8 @@ +import { GlobalContext } from "@/reffuse" +import { ReffuseContext } from "@thilawyn/reffuse" +import { make } from "@thilawyn/reffuse/Reffuse" +import { TodosState } from "./services" + + +export const TodosContext = ReffuseContext.make() +export const Reffuse = make(GlobalContext, TodosContext) diff --git a/packages/example/src/todos/services/TodosState.ts b/packages/example/src/todos/services/TodosState.ts new file mode 100644 index 0000000..e683ddb --- /dev/null +++ b/packages/example/src/todos/services/TodosState.ts @@ -0,0 +1,64 @@ +import { Todo } from "@/domain" +import { KeyValueStore } from "@effect/platform" +import { BrowserKeyValueStore } from "@effect/platform-browser" +import { PlatformError } from "@effect/platform/Error" +import { Cause, Chunk, Context, Effect, identity, Layer, ParseResult, Ref, Schema, SubscriptionRef } from "effect" + + +export class TodosState extends Context.Tag("TodosState")> + + readonly readFromLocalStorage: Effect.Effect + readonly saveToLocalStorage: Effect.Effect + + readonly prepend: (todo: Todo.Todo) => Effect.Effect + readonly remove: (index: number) => Effect.Effect + // readonly moveUp: (index: number) => Effect.Effect + // readonly moveDown: (index: number) => Effect.Effect +}>() {} + + +export const make = (key: string) => Layer.effect(TodosState, Effect.gen(function*() { + const todos = yield* SubscriptionRef.make(Chunk.empty()) + + const readFromLocalStorage = KeyValueStore.KeyValueStore.pipe( + Effect.flatMap(kv => kv.get(key)), + Effect.flatMap(identity), + Effect.flatMap(Schema.parseJson().pipe( + Schema.compose(Schema.Chunk(Todo.TodoFromJson)), + Schema.decode, + )), + Effect.flatMap(v => Ref.set(todos, v)), + + Effect.provide(BrowserKeyValueStore.layerLocalStorage), + ) + + const saveToLocalStorage = Effect.all([KeyValueStore.KeyValueStore, todos]).pipe( + Effect.flatMap(([kv, values]) => values.pipe( + Schema.parseJson().pipe( + Schema.compose(Schema.Chunk(Todo.TodoFromJson)), + Schema.encode, + ), + Effect.flatMap(v => kv.set(key, v)), + )), + + Effect.provide(BrowserKeyValueStore.layerLocalStorage), + ) + + const prepend = (todo: Todo.Todo) => Ref.update(todos, Chunk.prepend(todo)) + const remove = (index: number) => Ref.update(todos, Chunk.remove(index)) + + // const moveUp = (index: number) => Effect.gen(function*() { + + // }) + + yield* readFromLocalStorage + + return { + todos, + readFromLocalStorage, + saveToLocalStorage, + prepend, + remove, + } +})) diff --git a/packages/example/src/todos/services/index.ts b/packages/example/src/todos/services/index.ts new file mode 100644 index 0000000..5d1c39e --- /dev/null +++ b/packages/example/src/todos/services/index.ts @@ -0,0 +1 @@ +export * as TodosState from "./TodosState" diff --git a/packages/example/src/todos/views/VTodo.tsx b/packages/example/src/todos/views/VTodo.tsx new file mode 100644 index 0000000..b34c96a --- /dev/null +++ b/packages/example/src/todos/views/VTodo.tsx @@ -0,0 +1,21 @@ +import { Todo } from "@/domain" +import { Reffuse } from "../reffuse" + + +export interface VTodoProps { + readonly index: number + readonly todo: Todo.Todo +} + +export function VTodo({ index, todo }: VTodoProps) { + + const runSync = Reffuse.useRunSync() + + + return ( +
+ +
+ ) + +} diff --git a/packages/example/src/todos/views/VTodos.tsx b/packages/example/src/todos/views/VTodos.tsx new file mode 100644 index 0000000..b3cb2c6 --- /dev/null +++ b/packages/example/src/todos/views/VTodos.tsx @@ -0,0 +1,21 @@ +import { Effect, Stream } from "effect" +import { Reffuse } from "../reffuse" +import { TodosState } from "../services" + + +export function VTodos() { + + // Sync changes to the todos with the local storage + Reffuse.useFork(TodosState.TodosState.pipe( + Effect.flatMap(state => + Stream.runForEach(state.todos, () => state.saveToLocalStorage) + ) + )) + + const todosRef = Reffuse.useMemo(TodosState.TodosState.pipe(Effect.map(state => state.todos))) + const [todos] = Reffuse.useRefState(todosRef) + + + return <> + +}