diff --git a/.changeset/blue-tables-invent.md b/.changeset/blue-tables-invent.md new file mode 100644 index 000000000..c69379d89 --- /dev/null +++ b/.changeset/blue-tables-invent.md @@ -0,0 +1,45 @@ +--- +"eslint-config-evolu": major +"@evolu/common-react": major +"@evolu/react-native": major +"@evolu/common-web": major +"@evolu/common": major +"@evolu/server": major +"native": major +"server": major +"web": major +"@evolu/react": minor +--- + +New API + +With the upcoming React 19 `use` Hook, I took a chance to review and improve the Evolu API. I moved as many logic and types as possible to the Evolu interface to make platform variants more lightweight and to allow the use of Evolu directly out of any UI library. + +The most significant change is the split of SQL query declaration and usage. The rest of the API is almost the same except for minor improvements. + +### Example + +```ts +// Create queries. +const allTodos = evolu.createQuery((db) => db.selectFrom("todo").selectAll()); +const todoById = (id: TodoId) => + evolu.createQuery((db) => + db.selectFrom("todo").selectAll().where("id", "=", id), + ); + +// We can load a query or many queries. +const allTodosPromise = evolu.loadQuery(allTodos).then(({ rows }) => { + console.log(rows); +}); +evolu.loadQueries([allTodos, todoById(1)]); + +// useQuery can load once or use a promise. +const { rows } = useQuery(allTodos); +const { rows } = useQuery(allTodos, { once: true }); +const { rows } = useQuery(allTodos, { promise: allTodosPromise }); +const { row } = useQuery(todoById(1)); +``` + +I also refactored (read: simplified a lot) Effect Layer usage across all libraries. + +There is no breaking change in data storage or protocol. diff --git a/.changeset/config.json b/.changeset/config.json index c7a4b13aa..e130f0ddb 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -9,7 +9,9 @@ "updateInternalDependencies": "patch", "ignore": [], "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": { - "useCalculatedVersionForSnapshots": true, "onlyUpdatePeerDependentsWhenOutOfRange": true + }, + "snapshot": { + "useCalculatedVersion": true } } diff --git a/.prettierrc.json b/.prettierrc.json index 0967ef424..bfadbe045 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1 +1,3 @@ -{} +{ + "plugins": ["./node_modules/prettier-plugin-jsdoc/dist/index.js"] +} diff --git a/README.md b/README.md index c49b803cf..b1cdf1aba 100644 --- a/README.md +++ b/README.md @@ -28,20 +28,20 @@ Client-server architecture provides us with easy backup and synchronization, but To start using Evolu, define tables for your database and export React Hooks. ```ts -import * as Schema from "@effect/schema/Schema"; +import * as S from "@effect/schema/Schema"; import * as Evolu from "@evolu/react"; const TodoId = Evolu.id("Todo"); -type TodoId = Schema.Schema.To; +type TodoId = S.Schema.To; -const TodoTable = Schema.struct({ +const TodoTable = S.struct({ id: TodoId, title: Evolu.NonEmptyString1000, isCompleted: Evolu.SqliteBoolean, }); -type TodoTable = Schema.Schema.To; +type TodoTable = S.Schema.To; -const Database = Schema.struct({ +const Database = S.struct({ todo: TodoTable, }); @@ -59,10 +59,10 @@ export const { Learn more about [Schema](https://github.com/effect-ts/schema). ```ts -import * as Schema from "@effect/schema/Schema"; +import * as S from "@effect/schema/Schema"; import * as Evolu from "@evolu/react"; -Schema.parse(Evolu.String1000)(title); +S.parse(Evolu.String1000)(title); ``` ### Mutate Data diff --git a/apps/native/App.tsx b/apps/native/App.tsx index 31ca1435a..0259dc4ed 100644 --- a/apps/native/App.tsx +++ b/apps/native/App.tsx @@ -1,7 +1,7 @@ -import * as Schema from "@effect/schema/Schema"; +import * as S from "@effect/schema/Schema"; import * as TreeFormatter from "@effect/schema/TreeFormatter"; -import { Either, Function } from "effect"; import * as Evolu from "@evolu/react-native"; +import { Effect, Either, Exit, Function } from "effect"; import { FC, Suspense, @@ -21,71 +21,211 @@ import { import RNPickerSelect from "react-native-picker-select"; const TodoId = Evolu.id("Todo"); -type TodoId = Schema.Schema.To; +type TodoId = S.Schema.To; const TodoCategoryId = Evolu.id("TodoCategory"); -type TodoCategoryId = Schema.Schema.To; +type TodoCategoryId = S.Schema.To; const NonEmptyString50 = Evolu.String.pipe( - Schema.minLength(1), - Schema.maxLength(50), - Schema.brand("NonEmptyString50"), + S.minLength(1), + S.maxLength(50), + S.brand("NonEmptyString50"), ); -type NonEmptyString50 = Schema.Schema.To; +type NonEmptyString50 = S.Schema.To; -const TodoTable = Schema.struct({ +const TodoTable = S.struct({ id: TodoId, title: Evolu.NonEmptyString1000, isCompleted: Evolu.SqliteBoolean, - categoryId: Schema.nullable(TodoCategoryId), + categoryId: S.nullable(TodoCategoryId), }); -type TodoTable = Schema.Schema.To; +type TodoTable = S.Schema.To; -const TodoCategoryTable = Schema.struct({ +const SomeJson = S.struct({ foo: S.string, bar: S.boolean }); +type SomeJson = S.Schema.To; + +const TodoCategoryTable = S.struct({ id: TodoCategoryId, name: NonEmptyString50, + json: SomeJson, }); -type TodoCategoryTable = Schema.Schema.To; +type TodoCategoryTable = S.Schema.To; -const Database = Schema.struct({ +const Database = S.struct({ todo: TodoTable, todoCategory: TodoCategoryTable, }); -const { useQuery, useMutation, useEvoluError, useOwner, useOwnerActions } = - Evolu.create(Database, { - ...(process.env.NODE_ENV === "development" && { - syncUrl: "http://localhost:4000", - }), - }); +const evolu = Evolu.create(Database, { + ...(process.env.NODE_ENV === "development" && { + syncUrl: "http://localhost:4000", + }), +}); -interface TodoCategoryForSelect { - readonly id: TodoCategoryTable["id"]; - readonly name: TodoCategoryTable["name"] | null; +// React Hooks +const { useEvolu, useEvoluError, useQuery, useOwner } = evolu; + +export default function App(): JSX.Element { + return ( + + React Native Example + + + ); } -const TodoCategorySelect: FC<{ - categories: ReadonlyArray; - selected: TodoCategoryId | null; - onSelect: (_value: TodoCategoryId | null) => void; -}> = ({ categories, selected, onSelect }) => { - const nothingSelected = ""; - const value = - selected && categories.find((row) => row.id === selected) - ? selected - : nothingSelected; +const NextJsExample: FC = () => { + const [todosShown, setTodosShown] = useState(true); return ( - { - onSelect(value); - }} - items={categories.map((row) => ({ - label: row.name || "", - value: row.id, - }))} - /> + <> + + + + <> + + +

+ {currentTab === "todos" ? "Todos" : "Categories"} +

+ {currentTab === "todos" ? : } +