2
bunfig.toml
Normal file
2
bunfig.toml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[install.scopes]
|
||||||
|
"@thilawyn" = "https://git.valverde.cloud/api/packages/thilawyn/npm/"
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
"@tanstack/react-router": "^1.95.3",
|
"@tanstack/react-router": "^1.95.3",
|
||||||
"@tanstack/router-devtools": "^1.95.3",
|
"@tanstack/router-devtools": "^1.95.3",
|
||||||
"@tanstack/router-plugin": "^1.95.3",
|
"@tanstack/router-plugin": "^1.95.3",
|
||||||
|
"@thilawyn/thilaschema": "^0.1.4",
|
||||||
"@types/react": "^19.0.4",
|
"@types/react": "^19.0.4",
|
||||||
"@types/react-dom": "^19.0.2",
|
"@types/react-dom": "^19.0.2",
|
||||||
"@vitejs/plugin-react": "^4.3.4",
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
@@ -30,5 +31,9 @@
|
|||||||
"typescript": "~5.6.2",
|
"typescript": "~5.6.2",
|
||||||
"typescript-eslint": "^8.18.2",
|
"typescript-eslint": "^8.18.2",
|
||||||
"vite": "^6.0.5"
|
"vite": "^6.0.5"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@effect/platform": "^0.73.1",
|
||||||
|
"@effect/platform-browser": "^0.52.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
19
packages/example/src/domain/Todo.ts
Normal file
19
packages/example/src/domain/Todo.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { ThSchema } from "@thilawyn/thilaschema"
|
||||||
|
import { Schema } from "effect"
|
||||||
|
|
||||||
|
|
||||||
|
export class Todo extends Schema.Class<Todo>("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))
|
||||||
@@ -1 +1,2 @@
|
|||||||
export * as Post from "./Post"
|
export * as Post from "./Post"
|
||||||
|
export * as Todo from "./Todo"
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { Reffuse } from "@/reffuse"
|
import { TodosContext } from "@/todos/reffuse"
|
||||||
import { FetchData } from "@/services"
|
import { TodosState } from "@/todos/services"
|
||||||
import { PostsContext } from "@/views/posts/reffuse"
|
import { VTodos } from "@/todos/views/VTodos"
|
||||||
import { PostsState } from "@/views/posts/services"
|
|
||||||
import { VPosts } from "@/views/posts/VPosts"
|
|
||||||
import { createFileRoute } from "@tanstack/react-router"
|
import { createFileRoute } from "@tanstack/react-router"
|
||||||
import { Effect } from "effect"
|
import { Layer } from "effect"
|
||||||
|
import { useMemo } from "react"
|
||||||
|
|
||||||
|
|
||||||
export const Route = createFileRoute("/")({
|
export const Route = createFileRoute("/")({
|
||||||
@@ -13,17 +12,16 @@ export const Route = createFileRoute("/")({
|
|||||||
|
|
||||||
function Index() {
|
function Index() {
|
||||||
|
|
||||||
const postsLayer = Reffuse.useMemo(FetchData.FetchData.pipe(
|
const todosLayer = useMemo(() => Layer.empty.pipe(
|
||||||
Effect.flatMap(({ fetchPosts }) => fetchPosts),
|
Layer.provideMerge(TodosState.make("todos"))
|
||||||
Effect.map(PostsState.make),
|
), [])
|
||||||
))
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto">
|
<div className="container mx-auto">
|
||||||
<PostsContext.Provider layer={postsLayer}>
|
<TodosContext.Provider layer={todosLayer}>
|
||||||
<VPosts />
|
<VTodos />
|
||||||
</PostsContext.Provider>
|
</TodosContext.Provider>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
8
packages/example/src/todos/reffuse.ts
Normal file
8
packages/example/src/todos/reffuse.ts
Normal file
@@ -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<TodosState.TodosState>()
|
||||||
|
export const Reffuse = make(GlobalContext, TodosContext)
|
||||||
64
packages/example/src/todos/services/TodosState.ts
Normal file
64
packages/example/src/todos/services/TodosState.ts
Normal file
@@ -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")<TodosState, {
|
||||||
|
readonly todos: SubscriptionRef.SubscriptionRef<Chunk.Chunk<Todo.Todo>>
|
||||||
|
|
||||||
|
readonly readFromLocalStorage: Effect.Effect<void, Cause.NoSuchElementException | PlatformError | ParseResult.ParseError>
|
||||||
|
readonly saveToLocalStorage: Effect.Effect<void, PlatformError | ParseResult.ParseError>
|
||||||
|
|
||||||
|
readonly prepend: (todo: Todo.Todo) => Effect.Effect<void>
|
||||||
|
readonly remove: (index: number) => Effect.Effect<void>
|
||||||
|
// readonly moveUp: (index: number) => Effect.Effect<void, Cause.NoSuchElementException>
|
||||||
|
// readonly moveDown: (index: number) => Effect.Effect<void, Cause.NoSuchElementException>
|
||||||
|
}>() {}
|
||||||
|
|
||||||
|
|
||||||
|
export const make = (key: string) => Layer.effect(TodosState, Effect.gen(function*() {
|
||||||
|
const todos = yield* SubscriptionRef.make(Chunk.empty<Todo.Todo>())
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}))
|
||||||
1
packages/example/src/todos/services/index.ts
Normal file
1
packages/example/src/todos/services/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * as TodosState from "./TodosState"
|
||||||
21
packages/example/src/todos/views/VTodo.tsx
Normal file
21
packages/example/src/todos/views/VTodo.tsx
Normal file
@@ -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 (
|
||||||
|
<div className="">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
21
packages/example/src/todos/views/VTodos.tsx
Normal file
21
packages/example/src/todos/views/VTodos.tsx
Normal file
@@ -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 <></>
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user