diff --git a/.changeset/blue-tables-invent.md b/.changeset/blue-tables-invent.md deleted file mode 100644 index 42645697d..000000000 --- a/.changeset/blue-tables-invent.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -"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 identical except for minor improvements and one removal: filterMap helper is gone. - -It was a good idea with a nice DX, but such ad-hoc migrations belong in the database, not the JavaScript code. Filtering already loaded data pulls excessive data that should stay in the database. The good news is we can do that and even better with Kysely. - -To refresh what we are talking about for Evolu newcomers. Because database schema is evolving, and we can't do classical migrations in local-first apps (because we don't delete and other CRDT stuff), Evolu adopted GraphQL schema-less everything-is-nullable pattern. - -Having nullable everywhere in code is not ideal DX, so it would be nice to filter, ensure non-nullability, and even map rows directly in the database. Surprisingly, SQL is capable of that. Expect Evolu DSL for that soon. Meanwhile, we can do that manually: - -```ts -const todosWithout = evolu.createQuery((db) => - db - .selectFrom("todo") - .select(["id", "title", "isCompleted", "categoryId"]) - .where("isDeleted", "is not", Evolu.cast(true)) - // Filter null value and ensure non-null type. Evolu will provide a helper. - .where("title", "is not", null) - .$narrowType<{ title: Evolu.NonEmptyString1000 }>() - .orderBy("createdAt"), -); -``` - -And now to the new API. Behold: - -```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) the usage of Effect Layers across all libraries. And the last thing: There is no breaking change in data storage or protocol. diff --git a/apps/native/CHANGELOG.md b/apps/native/CHANGELOG.md index dca22b647..a2fbdc3d4 100644 --- a/apps/native/CHANGELOG.md +++ b/apps/native/CHANGELOG.md @@ -1,5 +1,64 @@ # native +## 1.0.0 + +### Major Changes + +- 7e80483: 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 identical except for minor improvements and one removal: filterMap helper is gone. + + It was a good idea with a nice DX, but such ad-hoc migrations belong in the database, not the JavaScript code. Filtering already loaded data pulls excessive data that should stay in the database. The good news is we can do that and even better with Kysely. + + To refresh what we are talking about for Evolu newcomers. Because database schema is evolving, and we can't do classical migrations in local-first apps (because we don't delete and other CRDT stuff), Evolu adopted GraphQL schema-less everything-is-nullable pattern. + + Having nullable everywhere in code is not ideal DX, so it would be nice to filter, ensure non-nullability, and even map rows directly in the database. Surprisingly, SQL is capable of that. Expect Evolu DSL for that soon. Meanwhile, we can do that manually: + + ```ts + const todosWithout = evolu.createQuery((db) => + db + .selectFrom("todo") + .select(["id", "title", "isCompleted", "categoryId"]) + .where("isDeleted", "is not", Evolu.cast(true)) + // Filter null value and ensure non-null type. Evolu will provide a helper. + .where("title", "is not", null) + .$narrowType<{ title: Evolu.NonEmptyString1000 }>() + .orderBy("createdAt"), + ); + ``` + + And now to the new API. Behold: + + ```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) the usage of Effect Layers across all libraries. And the last thing: There is no breaking change in data storage or protocol. + +### Patch Changes + +- Updated dependencies [7e80483] + - @evolu/react-native@2.0.0 + ## 0.0.97 ### Patch Changes diff --git a/apps/native/package.json b/apps/native/package.json index 389d6fb88..514d24cb0 100644 --- a/apps/native/package.json +++ b/apps/native/package.json @@ -1,6 +1,6 @@ { "name": "native", - "version": "0.0.97", + "version": "1.0.0", "private": true, "main": "index.js", "scripts": { diff --git a/apps/server/CHANGELOG.md b/apps/server/CHANGELOG.md index ce2b7475b..2a7b184d0 100644 --- a/apps/server/CHANGELOG.md +++ b/apps/server/CHANGELOG.md @@ -1,5 +1,64 @@ # server +## 1.0.0 + +### Major Changes + +- 7e80483: 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 identical except for minor improvements and one removal: filterMap helper is gone. + + It was a good idea with a nice DX, but such ad-hoc migrations belong in the database, not the JavaScript code. Filtering already loaded data pulls excessive data that should stay in the database. The good news is we can do that and even better with Kysely. + + To refresh what we are talking about for Evolu newcomers. Because database schema is evolving, and we can't do classical migrations in local-first apps (because we don't delete and other CRDT stuff), Evolu adopted GraphQL schema-less everything-is-nullable pattern. + + Having nullable everywhere in code is not ideal DX, so it would be nice to filter, ensure non-nullability, and even map rows directly in the database. Surprisingly, SQL is capable of that. Expect Evolu DSL for that soon. Meanwhile, we can do that manually: + + ```ts + const todosWithout = evolu.createQuery((db) => + db + .selectFrom("todo") + .select(["id", "title", "isCompleted", "categoryId"]) + .where("isDeleted", "is not", Evolu.cast(true)) + // Filter null value and ensure non-null type. Evolu will provide a helper. + .where("title", "is not", null) + .$narrowType<{ title: Evolu.NonEmptyString1000 }>() + .orderBy("createdAt"), + ); + ``` + + And now to the new API. Behold: + + ```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) the usage of Effect Layers across all libraries. And the last thing: There is no breaking change in data storage or protocol. + +### Patch Changes + +- Updated dependencies [7e80483] + - @evolu/server@2.0.0 + ## 0.0.105 ### Patch Changes diff --git a/apps/server/package.json b/apps/server/package.json index abe2a3f5d..6c4633795 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -1,6 +1,6 @@ { "name": "server", - "version": "0.0.105", + "version": "1.0.0", "private": true, "type": "module", "scripts": { diff --git a/apps/web/CHANGELOG.md b/apps/web/CHANGELOG.md index ff4fdd0a0..2abe0a024 100644 --- a/apps/web/CHANGELOG.md +++ b/apps/web/CHANGELOG.md @@ -1,5 +1,65 @@ # web +## 1.0.0 + +### Major Changes + +- 7e80483: 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 identical except for minor improvements and one removal: filterMap helper is gone. + + It was a good idea with a nice DX, but such ad-hoc migrations belong in the database, not the JavaScript code. Filtering already loaded data pulls excessive data that should stay in the database. The good news is we can do that and even better with Kysely. + + To refresh what we are talking about for Evolu newcomers. Because database schema is evolving, and we can't do classical migrations in local-first apps (because we don't delete and other CRDT stuff), Evolu adopted GraphQL schema-less everything-is-nullable pattern. + + Having nullable everywhere in code is not ideal DX, so it would be nice to filter, ensure non-nullability, and even map rows directly in the database. Surprisingly, SQL is capable of that. Expect Evolu DSL for that soon. Meanwhile, we can do that manually: + + ```ts + const todosWithout = evolu.createQuery((db) => + db + .selectFrom("todo") + .select(["id", "title", "isCompleted", "categoryId"]) + .where("isDeleted", "is not", Evolu.cast(true)) + // Filter null value and ensure non-null type. Evolu will provide a helper. + .where("title", "is not", null) + .$narrowType<{ title: Evolu.NonEmptyString1000 }>() + .orderBy("createdAt"), + ); + ``` + + And now to the new API. Behold: + + ```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) the usage of Effect Layers across all libraries. And the last thing: There is no breaking change in data storage or protocol. + +### Patch Changes + +- Updated dependencies [7e80483] + - @evolu/common@2.0.0 + - @evolu/react@3.0.0 + ## 0.0.107 ### Patch Changes diff --git a/apps/web/package.json b/apps/web/package.json index 527c01331..8c7a465df 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "web", - "version": "0.0.107", + "version": "1.0.0", "private": true, "scripts": { "dev": "next dev", diff --git a/packages/eslint-config-evolu/CHANGELOG.md b/packages/eslint-config-evolu/CHANGELOG.md new file mode 100644 index 000000000..ec7fe67d5 --- /dev/null +++ b/packages/eslint-config-evolu/CHANGELOG.md @@ -0,0 +1,55 @@ +# eslint-config-evolu + +## 1.0.0 + +### Major Changes + +- 7e80483: 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 identical except for minor improvements and one removal: filterMap helper is gone. + + It was a good idea with a nice DX, but such ad-hoc migrations belong in the database, not the JavaScript code. Filtering already loaded data pulls excessive data that should stay in the database. The good news is we can do that and even better with Kysely. + + To refresh what we are talking about for Evolu newcomers. Because database schema is evolving, and we can't do classical migrations in local-first apps (because we don't delete and other CRDT stuff), Evolu adopted GraphQL schema-less everything-is-nullable pattern. + + Having nullable everywhere in code is not ideal DX, so it would be nice to filter, ensure non-nullability, and even map rows directly in the database. Surprisingly, SQL is capable of that. Expect Evolu DSL for that soon. Meanwhile, we can do that manually: + + ```ts + const todosWithout = evolu.createQuery((db) => + db + .selectFrom("todo") + .select(["id", "title", "isCompleted", "categoryId"]) + .where("isDeleted", "is not", Evolu.cast(true)) + // Filter null value and ensure non-null type. Evolu will provide a helper. + .where("title", "is not", null) + .$narrowType<{ title: Evolu.NonEmptyString1000 }>() + .orderBy("createdAt"), + ); + ``` + + And now to the new API. Behold: + + ```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) the usage of Effect Layers across all libraries. And the last thing: There is no breaking change in data storage or protocol. diff --git a/packages/eslint-config-evolu/package.json b/packages/eslint-config-evolu/package.json index 5dea4a74e..d57b7178e 100644 --- a/packages/eslint-config-evolu/package.json +++ b/packages/eslint-config-evolu/package.json @@ -1,6 +1,6 @@ { "name": "eslint-config-evolu", - "version": "0.0.2", + "version": "1.0.0", "private": true, "main": "index.js", "scripts": { diff --git a/packages/evolu-common-react/CHANGELOG.md b/packages/evolu-common-react/CHANGELOG.md index 389861a4f..445d2c27d 100644 --- a/packages/evolu-common-react/CHANGELOG.md +++ b/packages/evolu-common-react/CHANGELOG.md @@ -1,5 +1,64 @@ # @evolu/common-react +## 2.0.0 + +### Major Changes + +- 7e80483: 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 identical except for minor improvements and one removal: filterMap helper is gone. + + It was a good idea with a nice DX, but such ad-hoc migrations belong in the database, not the JavaScript code. Filtering already loaded data pulls excessive data that should stay in the database. The good news is we can do that and even better with Kysely. + + To refresh what we are talking about for Evolu newcomers. Because database schema is evolving, and we can't do classical migrations in local-first apps (because we don't delete and other CRDT stuff), Evolu adopted GraphQL schema-less everything-is-nullable pattern. + + Having nullable everywhere in code is not ideal DX, so it would be nice to filter, ensure non-nullability, and even map rows directly in the database. Surprisingly, SQL is capable of that. Expect Evolu DSL for that soon. Meanwhile, we can do that manually: + + ```ts + const todosWithout = evolu.createQuery((db) => + db + .selectFrom("todo") + .select(["id", "title", "isCompleted", "categoryId"]) + .where("isDeleted", "is not", Evolu.cast(true)) + // Filter null value and ensure non-null type. Evolu will provide a helper. + .where("title", "is not", null) + .$narrowType<{ title: Evolu.NonEmptyString1000 }>() + .orderBy("createdAt"), + ); + ``` + + And now to the new API. Behold: + + ```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) the usage of Effect Layers across all libraries. And the last thing: There is no breaking change in data storage or protocol. + +### Patch Changes + +- Updated dependencies [7e80483] + - @evolu/common@2.0.0 + ## 1.0.9 ### Patch Changes diff --git a/packages/evolu-common-react/package.json b/packages/evolu-common-react/package.json index f1a2dd3ab..ebb2addb5 100644 --- a/packages/evolu-common-react/package.json +++ b/packages/evolu-common-react/package.json @@ -1,6 +1,6 @@ { "name": "@evolu/common-react", - "version": "1.0.9", + "version": "2.0.0", "description": "Common code for Evolu React libraries", "keywords": [ "evolu", @@ -49,7 +49,7 @@ "vitest": "^0.34.6" }, "peerDependencies": { - "@evolu/common": "^1.0.16", + "@evolu/common": "^2.0.0", "@types/react": "^18.2.22", "react": "^18.2.0" }, diff --git a/packages/evolu-common-web/CHANGELOG.md b/packages/evolu-common-web/CHANGELOG.md index 6e2b61d58..726f05bdc 100644 --- a/packages/evolu-common-web/CHANGELOG.md +++ b/packages/evolu-common-web/CHANGELOG.md @@ -1,5 +1,64 @@ # @evolu/common-web +## 2.0.0 + +### Major Changes + +- 7e80483: 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 identical except for minor improvements and one removal: filterMap helper is gone. + + It was a good idea with a nice DX, but such ad-hoc migrations belong in the database, not the JavaScript code. Filtering already loaded data pulls excessive data that should stay in the database. The good news is we can do that and even better with Kysely. + + To refresh what we are talking about for Evolu newcomers. Because database schema is evolving, and we can't do classical migrations in local-first apps (because we don't delete and other CRDT stuff), Evolu adopted GraphQL schema-less everything-is-nullable pattern. + + Having nullable everywhere in code is not ideal DX, so it would be nice to filter, ensure non-nullability, and even map rows directly in the database. Surprisingly, SQL is capable of that. Expect Evolu DSL for that soon. Meanwhile, we can do that manually: + + ```ts + const todosWithout = evolu.createQuery((db) => + db + .selectFrom("todo") + .select(["id", "title", "isCompleted", "categoryId"]) + .where("isDeleted", "is not", Evolu.cast(true)) + // Filter null value and ensure non-null type. Evolu will provide a helper. + .where("title", "is not", null) + .$narrowType<{ title: Evolu.NonEmptyString1000 }>() + .orderBy("createdAt"), + ); + ``` + + And now to the new API. Behold: + + ```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) the usage of Effect Layers across all libraries. And the last thing: There is no breaking change in data storage or protocol. + +### Patch Changes + +- Updated dependencies [7e80483] + - @evolu/common@2.0.0 + ## 1.1.5 ### Patch Changes diff --git a/packages/evolu-common-web/package.json b/packages/evolu-common-web/package.json index d2abbe387..9e166389e 100644 --- a/packages/evolu-common-web/package.json +++ b/packages/evolu-common-web/package.json @@ -1,6 +1,6 @@ { "name": "@evolu/common-web", - "version": "1.1.5", + "version": "2.0.0", "description": "Common code for Evolu libraries targeting web", "keywords": [ "evolu", @@ -46,7 +46,7 @@ "vitest": "^0.34.6" }, "peerDependencies": { - "@evolu/common": "^1.0.16", + "@evolu/common": "^2.0.0", "@sqlite.org/sqlite-wasm": "3.43.2-build1" }, "publishConfig": { diff --git a/packages/evolu-common/CHANGELOG.md b/packages/evolu-common/CHANGELOG.md index 52b213540..de9107aaf 100644 --- a/packages/evolu-common/CHANGELOG.md +++ b/packages/evolu-common/CHANGELOG.md @@ -1,5 +1,59 @@ # @evolu/common +## 2.0.0 + +### Major Changes + +- 7e80483: 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 identical except for minor improvements and one removal: filterMap helper is gone. + + It was a good idea with a nice DX, but such ad-hoc migrations belong in the database, not the JavaScript code. Filtering already loaded data pulls excessive data that should stay in the database. The good news is we can do that and even better with Kysely. + + To refresh what we are talking about for Evolu newcomers. Because database schema is evolving, and we can't do classical migrations in local-first apps (because we don't delete and other CRDT stuff), Evolu adopted GraphQL schema-less everything-is-nullable pattern. + + Having nullable everywhere in code is not ideal DX, so it would be nice to filter, ensure non-nullability, and even map rows directly in the database. Surprisingly, SQL is capable of that. Expect Evolu DSL for that soon. Meanwhile, we can do that manually: + + ```ts + const todosWithout = evolu.createQuery((db) => + db + .selectFrom("todo") + .select(["id", "title", "isCompleted", "categoryId"]) + .where("isDeleted", "is not", Evolu.cast(true)) + // Filter null value and ensure non-null type. Evolu will provide a helper. + .where("title", "is not", null) + .$narrowType<{ title: Evolu.NonEmptyString1000 }>() + .orderBy("createdAt"), + ); + ``` + + And now to the new API. Behold: + + ```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) the usage of Effect Layers across all libraries. And the last thing: There is no breaking change in data storage or protocol. + ## 1.0.17 ### Patch Changes diff --git a/packages/evolu-common/package.json b/packages/evolu-common/package.json index 932128d84..8ccd0cd89 100644 --- a/packages/evolu-common/package.json +++ b/packages/evolu-common/package.json @@ -1,6 +1,6 @@ { "name": "@evolu/common", - "version": "1.0.17", + "version": "2.0.0", "description": "Local-first platform designed for privacy, ease of use, and no vendor lock-in to sync and backup people's lifetime data", "keywords": [ "evolu", diff --git a/packages/evolu-common/src/Protobuf.ts b/packages/evolu-common/src/Protobuf.ts index a02ddd0d3..2aac0b469 100644 --- a/packages/evolu-common/src/Protobuf.ts +++ b/packages/evolu-common/src/Protobuf.ts @@ -7,99 +7,59 @@ import { MerkleTreeString, TimestampString } from "./Crdt.js"; import { NodeId } from "./Crypto.js"; import { OwnerId } from "./Owner.js"; import { Id } from "./Model.js"; -/** - * @generated from protobuf message SyncRequest - */ +/** @generated from protobuf message SyncRequest */ export interface SyncRequest { - /** - * @generated from protobuf field: repeated EncryptedMessage messages = 1; - */ + /** @generated from protobuf field: repeated EncryptedMessage messages = 1; */ messages: ReadonlyArray; - /** - * @generated from protobuf field: string userId = 2; - */ + /** @generated from protobuf field: string userId = 2; */ userId: OwnerId; - /** - * @generated from protobuf field: string nodeId = 3; - */ + /** @generated from protobuf field: string nodeId = 3; */ nodeId: NodeId; - /** - * @generated from protobuf field: string merkleTree = 4; - */ + /** @generated from protobuf field: string merkleTree = 4; */ merkleTree: MerkleTreeString; } -/** - * @generated from protobuf message SyncResponse - */ +/** @generated from protobuf message SyncResponse */ export interface SyncResponse { - /** - * @generated from protobuf field: repeated EncryptedMessage messages = 1; - */ + /** @generated from protobuf field: repeated EncryptedMessage messages = 1; */ messages: ReadonlyArray; - /** - * @generated from protobuf field: string merkleTree = 2; - */ + /** @generated from protobuf field: string merkleTree = 2; */ merkleTree: MerkleTreeString; } -/** - * @generated from protobuf message EncryptedMessage - */ +/** @generated from protobuf message EncryptedMessage */ export interface EncryptedMessage { - /** - * @generated from protobuf field: string timestamp = 1; - */ + /** @generated from protobuf field: string timestamp = 1; */ timestamp: TimestampString; - /** - * @generated from protobuf field: bytes content = 2; - */ + /** @generated from protobuf field: bytes content = 2; */ content: Uint8Array; } -/** - * @generated from protobuf message MessageContent - */ +/** @generated from protobuf message MessageContent */ export interface MessageContent { - /** - * @generated from protobuf field: string table = 1; - */ + /** @generated from protobuf field: string table = 1; */ table: string; - /** - * @generated from protobuf field: string row = 2; - */ + /** @generated from protobuf field: string row = 2; */ row: Id; - /** - * @generated from protobuf field: string column = 3; - */ + /** @generated from protobuf field: string column = 3; */ column: string; - /** - * @generated from protobuf oneof: value - */ + /** @generated from protobuf oneof: value */ value: | { oneofKind: "stringValue"; - /** - * @generated from protobuf field: string stringValue = 4; - */ + /** @generated from protobuf field: string stringValue = 4; */ stringValue: string; } | { oneofKind: "numberValue"; - /** - * @generated from protobuf field: int32 numberValue = 5; - */ + /** @generated from protobuf field: int32 numberValue = 5; */ numberValue: number; } | { oneofKind: "bytesValue"; - /** - * @generated from protobuf field: bytes bytesValue = 6; - */ + /** @generated from protobuf field: bytes bytesValue = 6; */ bytesValue: Uint8Array; } | { oneofKind: "jsonValue"; - /** - * @generated from protobuf field: string jsonValue = 7; - */ + /** @generated from protobuf field: string jsonValue = 7; */ jsonValue: string; } | { @@ -123,9 +83,7 @@ class SyncRequest$Type extends MessageType { ]); } } -/** - * @generated MessageType for protobuf message SyncRequest - */ +/** @generated MessageType for protobuf message SyncRequest */ export const SyncRequest = new SyncRequest$Type(); // @generated message type with reflection information, may provide speed optimized methods class SyncResponse$Type extends MessageType { @@ -142,9 +100,7 @@ class SyncResponse$Type extends MessageType { ]); } } -/** - * @generated MessageType for protobuf message SyncResponse - */ +/** @generated MessageType for protobuf message SyncResponse */ export const SyncResponse = new SyncResponse$Type(); // @generated message type with reflection information, may provide speed optimized methods class EncryptedMessage$Type extends MessageType { @@ -155,9 +111,7 @@ class EncryptedMessage$Type extends MessageType { ]); } } -/** - * @generated MessageType for protobuf message EncryptedMessage - */ +/** @generated MessageType for protobuf message EncryptedMessage */ export const EncryptedMessage = new EncryptedMessage$Type(); // @generated message type with reflection information, may provide speed optimized methods class MessageContent$Type extends MessageType { @@ -197,7 +151,5 @@ class MessageContent$Type extends MessageType { ]); } } -/** - * @generated MessageType for protobuf message MessageContent - */ +/** @generated MessageType for protobuf message MessageContent */ export const MessageContent = new MessageContent$Type(); diff --git a/packages/evolu-react-native/CHANGELOG.md b/packages/evolu-react-native/CHANGELOG.md index a842e33a3..81808d5e5 100644 --- a/packages/evolu-react-native/CHANGELOG.md +++ b/packages/evolu-react-native/CHANGELOG.md @@ -1,5 +1,64 @@ # @evolu/react-native +## 2.0.0 + +### Major Changes + +- 7e80483: 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 identical except for minor improvements and one removal: filterMap helper is gone. + + It was a good idea with a nice DX, but such ad-hoc migrations belong in the database, not the JavaScript code. Filtering already loaded data pulls excessive data that should stay in the database. The good news is we can do that and even better with Kysely. + + To refresh what we are talking about for Evolu newcomers. Because database schema is evolving, and we can't do classical migrations in local-first apps (because we don't delete and other CRDT stuff), Evolu adopted GraphQL schema-less everything-is-nullable pattern. + + Having nullable everywhere in code is not ideal DX, so it would be nice to filter, ensure non-nullability, and even map rows directly in the database. Surprisingly, SQL is capable of that. Expect Evolu DSL for that soon. Meanwhile, we can do that manually: + + ```ts + const todosWithout = evolu.createQuery((db) => + db + .selectFrom("todo") + .select(["id", "title", "isCompleted", "categoryId"]) + .where("isDeleted", "is not", Evolu.cast(true)) + // Filter null value and ensure non-null type. Evolu will provide a helper. + .where("title", "is not", null) + .$narrowType<{ title: Evolu.NonEmptyString1000 }>() + .orderBy("createdAt"), + ); + ``` + + And now to the new API. Behold: + + ```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) the usage of Effect Layers across all libraries. And the last thing: There is no breaking change in data storage or protocol. + +### Patch Changes + +- Updated dependencies [7e80483] + - @evolu/common-react@2.0.0 + ## 1.0.8 ### Patch Changes diff --git a/packages/evolu-react-native/package.json b/packages/evolu-react-native/package.json index cd6b07141..006d11464 100644 --- a/packages/evolu-react-native/package.json +++ b/packages/evolu-react-native/package.json @@ -1,6 +1,6 @@ { "name": "@evolu/react-native", - "version": "1.0.8", + "version": "2.0.0", "description": "Evolu for React Native", "keywords": [ "evolu", @@ -53,7 +53,7 @@ "vitest": "^0.34.6" }, "peerDependencies": { - "@evolu/common-react": "^1.0.9", + "@evolu/common-react": "^2.0.0", "expo": "^49.0.20", "expo-sqlite": "~11.3.3", "react-native": "^0.72.4" diff --git a/packages/evolu-react/CHANGELOG.md b/packages/evolu-react/CHANGELOG.md index 34921633b..a770ebce3 100644 --- a/packages/evolu-react/CHANGELOG.md +++ b/packages/evolu-react/CHANGELOG.md @@ -1,5 +1,66 @@ # @evolu/react +## 3.0.0 + +### Minor Changes + +- 7e80483: 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 identical except for minor improvements and one removal: filterMap helper is gone. + + It was a good idea with a nice DX, but such ad-hoc migrations belong in the database, not the JavaScript code. Filtering already loaded data pulls excessive data that should stay in the database. The good news is we can do that and even better with Kysely. + + To refresh what we are talking about for Evolu newcomers. Because database schema is evolving, and we can't do classical migrations in local-first apps (because we don't delete and other CRDT stuff), Evolu adopted GraphQL schema-less everything-is-nullable pattern. + + Having nullable everywhere in code is not ideal DX, so it would be nice to filter, ensure non-nullability, and even map rows directly in the database. Surprisingly, SQL is capable of that. Expect Evolu DSL for that soon. Meanwhile, we can do that manually: + + ```ts + const todosWithout = evolu.createQuery((db) => + db + .selectFrom("todo") + .select(["id", "title", "isCompleted", "categoryId"]) + .where("isDeleted", "is not", Evolu.cast(true)) + // Filter null value and ensure non-null type. Evolu will provide a helper. + .where("title", "is not", null) + .$narrowType<{ title: Evolu.NonEmptyString1000 }>() + .orderBy("createdAt"), + ); + ``` + + And now to the new API. Behold: + + ```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) the usage of Effect Layers across all libraries. And the last thing: There is no breaking change in data storage or protocol. + +### Patch Changes + +- Updated dependencies [7e80483] + - @evolu/common-react@2.0.0 + - @evolu/common-web@2.0.0 + - @evolu/common@2.0.0 + ## 2.0.6 ### Patch Changes diff --git a/packages/evolu-react/package.json b/packages/evolu-react/package.json index 0b0a7d39d..3b19a8260 100644 --- a/packages/evolu-react/package.json +++ b/packages/evolu-react/package.json @@ -1,6 +1,6 @@ { "name": "@evolu/react", - "version": "2.0.6", + "version": "2.0.7", "description": "Evolu for React", "keywords": [ "evolu", @@ -48,9 +48,9 @@ "vitest": "^0.34.6" }, "peerDependencies": { - "@evolu/common": "^1.0.16", - "@evolu/common-react": "^1.0.9", - "@evolu/common-web": "^1.1.5", + "@evolu/common": "^2.0.0", + "@evolu/common-react": "^2.0.0", + "@evolu/common-web": "^2.0.0", "@types/react-dom": "^18.2.7", "react-dom": "^18.2.0" }, diff --git a/packages/evolu-server/CHANGELOG.md b/packages/evolu-server/CHANGELOG.md index a011f5590..22c521b90 100644 --- a/packages/evolu-server/CHANGELOG.md +++ b/packages/evolu-server/CHANGELOG.md @@ -1,5 +1,64 @@ # @evolu/server +## 2.0.0 + +### Major Changes + +- 7e80483: 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 identical except for minor improvements and one removal: filterMap helper is gone. + + It was a good idea with a nice DX, but such ad-hoc migrations belong in the database, not the JavaScript code. Filtering already loaded data pulls excessive data that should stay in the database. The good news is we can do that and even better with Kysely. + + To refresh what we are talking about for Evolu newcomers. Because database schema is evolving, and we can't do classical migrations in local-first apps (because we don't delete and other CRDT stuff), Evolu adopted GraphQL schema-less everything-is-nullable pattern. + + Having nullable everywhere in code is not ideal DX, so it would be nice to filter, ensure non-nullability, and even map rows directly in the database. Surprisingly, SQL is capable of that. Expect Evolu DSL for that soon. Meanwhile, we can do that manually: + + ```ts + const todosWithout = evolu.createQuery((db) => + db + .selectFrom("todo") + .select(["id", "title", "isCompleted", "categoryId"]) + .where("isDeleted", "is not", Evolu.cast(true)) + // Filter null value and ensure non-null type. Evolu will provide a helper. + .where("title", "is not", null) + .$narrowType<{ title: Evolu.NonEmptyString1000 }>() + .orderBy("createdAt"), + ); + ``` + + And now to the new API. Behold: + + ```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) the usage of Effect Layers across all libraries. And the last thing: There is no breaking change in data storage or protocol. + +### Patch Changes + +- Updated dependencies [7e80483] + - @evolu/common@2.0.0 + ## 1.0.17 ### Patch Changes diff --git a/packages/evolu-server/package.json b/packages/evolu-server/package.json index f967d1948..c7602fa3d 100644 --- a/packages/evolu-server/package.json +++ b/packages/evolu-server/package.json @@ -1,6 +1,6 @@ { "name": "@evolu/server", - "version": "1.0.17", + "version": "2.0.0", "description": "Node.js server for Evolu", "author": "Daniel Steigerwald ", "license": "MIT",