diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 68d44a16..cbf458a1 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -8,7 +8,8 @@
- [ ] no tests needed
### Docs
-- [ ] added relevant docs
+- [ ] added relevant docs
+ - preview them at https://homebase.io/docs/homebase-react/{BRANCH_NAME}/overview
- [ ] updated relevant sections in the README.md
- [ ] updated relevant docstrings in index.d.ts
- [ ] no docs needed
@@ -16,3 +17,10 @@
### Typescript
- [ ] added or edited relevant Typescript type declarations
- [ ] no type declaration updates needed
+
+## Merging
+For maintainers.
+
+To merge, select "Squash and Merge". Then:
+ 1. Make sure the top commit message follows [Angular Git Commit Guidelines](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#-git-commit-guidelines),
+ 2. Delete all other commit messages in the description, but keep any lines designating [co-authors](https://docs.github.com/en/free-pro-team@latest/github/committing-changes-to-your-project/creating-a-commit-with-multiple-authors) so contributors will retain credit for their contributions.
diff --git a/.gitignore b/.gitignore
index eef28bac..cf6f1beb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,3 +28,4 @@ pom.xml.asc
.hgignore
.hg/
+.vscode
diff --git a/.npmignore b/.npmignore
index ff5a7b79..a5d298a4 100644
--- a/.npmignore
+++ b/.npmignore
@@ -31,8 +31,8 @@ pom.xml.asc
.hg/
examples/
-
- types/
- src/
- public/
- js/
+types/
+src/
+public/
+js/
+docs/
diff --git a/README.md b/README.md
index 9d5f4556..3c84dfa4 100644
--- a/README.md
+++ b/README.md
@@ -22,6 +22,10 @@ npm install homebase-react --save
# Yarn
yarn add homebase-react
```
+
+## Docs
+https://homebase.io/docs/homebase-react
+
## Features
- The simplest and most declarative state management solution
- The power of a backend relational graph database, but without having to wait on the network
@@ -103,7 +107,7 @@ const RootComponent = () => (
### `useEntity` and `entity.get`
-Entities are the building blocks of the Homebase data model. They are like JSON objects with bonus features. In particular **you can traverse arbitrarily deep relationship without actually denormalizing and nesting your data**.
+Entities are the building blocks of the Homebase data model. They are like JSON objects with bonus features. In particular **you can traverse arbitrarily deep relationships without actually denormalizing and nesting your data**.
```js
// You can get an entity by its id and get attributes off of it.
@@ -185,107 +189,6 @@ This hook returns the current database client with some helpful functions for sy
Check out the [Firebase example](https://homebaseio.github.io/homebase-react/#!/example.todo_firebase) for a demonstration of how you might integrate a backend.
-### Arrays & Nested JSON
-
-Arrays and arbitrary JSON are partially supported for convenience. However in most cases its better to avoid arrays. Using a query and then sorting by an attribute is simpler and more flexible. This is because arrays add extra overhead to keep track of order.
-
-```js
-const config = {
- schema: {
- company: {
- numbers: { type: 'ref', cardinality: 'many' },
- projects: { type: 'ref', cardinality: 'many' },
- }
- }
-}
-
-transact([
- { project: { id: -1, name: 'a' } },
- {
- company: {
- numbers: [1, 2, 3],
- projects: [
- { project: { id: -1 } },
- { project: { name: 'b' } },
- ]
- }
- }
-])
-
-// Index into arrays
-company.get('numbers', 1, 'value') // => 2
-company.get('projects', 0, 'ref', 'name') // => 'a'
-// Get the automatically assigned order
-// Order starts at 1 and increments by 1
-company.get('numbers', 0, 'order') // => 1
-company.get('projects', 0, 'order') // => 1
-company.get('projects', 1, 'order') // => 2
-// Map over individual attributes
-company.get('numbers', 'value') // => [1, 2, 3]
-company.get('projects', 'ref', 'name') // => ['a', 'b']
-```
-
-The `entity.get` API is flexible and supports indexing into arrays as well as automatically mapping over individual attributes.
-
-Array items are automatically assigned an `order` and either a `value` or a `ref` depending on if item in the array is an entity or not. To reorder an array item change its `order`.
-
-```js
-transact([
- {
- id: company.get('numbers', 2, 'id'),
- order: (company.get('numbers', 0, 'order')
- + company.get('numbers', 1, 'order')) / 2
- }
-])
-
-company.get('numbers', 'value') // => [1 3 2]
-```
-
-If you need to transact complex JSON like arrays of arrays then you're better off serializing it to a string first.
-
-```js
-// NOT supported
-transact([{ company: { matrix: [[1, 2, 3], [4, 5, 6]] } }])
-
-// Better
-transact([{ company: { matrix: JSON.stringify([[1, 2, 3], [4, 5, 6]]) } }])
-JSON.parse(company.get('matrix'))
-```
-
-## Performance
-
-Homebase React tracks the attributes consumed in each component via the `entity.get` function and scopes those attributes to their respective `useEntity` or `useQuery` hook. Re-renders are only triggered when an attribute changes.
-
-The default caching reduces unnecessary re-renders and virtual DOM thrashing a lot. That said, it is still possible to trigger more re-renders than you might want.
-
-One top level `useQuery` + prop drilling the entities it returns will cause all children to re-render on any change to the parent or their siblings.
-
-To fix this we recommend passing ids to children, not whole entities. Instead get the entity in the child with `useEntity(id)`. This creates a new scope for each child so they are not affected by changes in the state of the parent or sibling components.
-
-```js
-const TodoList = () => {
- const [todos] = useQuery({
- $find: 'todo',
- $where: { todo: { name: '$any' } }
- })
- return (todos.map(t => ))
-}
-
-// Good
-const Todo = React.memo(({ id }) => {
- const [todo] = useEntity(id)
- // ...
-})
-
-// Bad
-const Todo = React.memo(({ todo }) => {
- // ...
-})
-```
-
-
-## Docs
-https://www.notion.so/Homebase-Alpha-Docs-0f0e22f3adcd4e9d87a13440ab0c7a0b
## Development
```bash
diff --git a/docs/0100|Overview.md b/docs/0100|Overview.md
new file mode 100644
index 00000000..aae05f46
--- /dev/null
+++ b/docs/0100|Overview.md
@@ -0,0 +1,40 @@
+## Homebase React
+
+[](https://github.com/homebaseio/homebase-react/actions?query=workflow%3ACI)
+[](https://github.com/homebaseio/homebase-react/actions?query=workflow%3ACD)
+[](https://www.npmjs.com/package/homebase-react)
+[](https://www.npmjs.com/package/homebase-react)
+[](LICENSE)
+[](https://github.com/homebaseio/homebase-react)
+[](https://twitter.com/homebase__io)
+
+*The graph database for delightful React state management*
+
+
+Homebase React makes state management painless by enabling you to plug a relational graph database into your React application with just 3 lines of code. This is the same database that powers Roam Research and many other ClojureScript applications, but with an API that's familiar to React and JS developers.
+
+## Install
+
+```bash
+# NPM
+npm install homebase-react --save
+
+# Yarn
+yarn add homebase-react
+```
+## Features
+- The simplest and most declarative state management solution
+- The power of a backend relational graph database, but without having to wait on the network
+- Convenient JSON query syntax
+- Powerful Clojure style [Datalog](https://docs.datomic.com/on-prem/query.html) query syntax if you need it
+- Traverse your data graph like it's a big JSON object
+- Backup your data to the cloud
+
+## Roadmap
+
+1. Document integration with more backends
+1. Swap [Datascript](https://github.com/tonsky/datascript) out for [Datahike](https://github.com/replikativ/datahike)
+ 1. Immutability
+ 1. History / Change Tracking
+2. Persist to IndexedDB
+3. [Local-first](https://www.inkandswitch.com/local-first.html) conflict resolution for offline caching and sync between multiple devices
\ No newline at end of file
diff --git a/docs/0200|Quick_Start.md b/docs/0200|Quick_Start.md
new file mode 100644
index 00000000..5883e40a
--- /dev/null
+++ b/docs/0200|Quick_Start.md
@@ -0,0 +1,36 @@
+Homebase React creates a local relational database for your React app.
+
+```js
+import { HomebaseProvider } from 'homebase-react'
+
+const RootComponent = () => (
+
+
+
+)
+```
+
+Read from and write to that database via hooks.
+
+```js
+import { useCallback } from 'react'
+import { useEntity, useTransact } from 'homebase-react'
+
+const App = () => {
+ const [counter] = useEntity(1)
+ const [transact] = useTransact()
+
+ const handleClick = useCallback(() => {
+ transact([{ counter: {
+ id: 1, count: counter.get('count') + 1
+ } }])
+ }, [counter, transact])
+
+ return (
+
+ )
+}
+```
\ No newline at end of file
diff --git a/docs/0300|Tutorial.md b/docs/0300|Tutorial.md
new file mode 100644
index 00000000..2eb467ec
--- /dev/null
+++ b/docs/0300|Tutorial.md
@@ -0,0 +1,593 @@
+This tutorial takes you through our [Todo Example](https://homebaseio.github.io/homebase-react/#!/example.todo).
+
+## HomebaseProvider
+
+Let's get started.
+
+`HomebaseProvider` is a component that wraps your React app and creates a local relational database. This database is then accessible to any child components via React Hooks.
+
+```jsx
+import React from 'react'
+import { HomebaseProvider, useTransact, useQuery, useEntity } from 'homebase-react'
+
+export const App = () => {
+ return (
+
+
+
+ )
+}
+```
+
+## Schema
+
+Unlike other state managers, Homebase does not try to create yet another design pattern for state. Instead, we store state in a way we already know and love: as a relational graph database.
+
+Like any good database we support schema on read.
+
+At the moment schema is only for relationships and uniqueness constraints. It does not support typing of attributes, e.g. strings, integers, dates. We're working on adding the option to opt into schema on write support. This will provide basic type checking like you see in SQL.
+
+```jsx
+const schema = {
+ project: {
+ name: {
+ unique: 'identity'
+ }
+ },
+ todo: {
+ // refs are relationships
+ project: {
+ type: 'ref'
+ },
+ owner: {
+ type: 'ref'
+ }
+ }
+}
+```
+
+## Initial Data
+
+Hydrate your application with initial data. Here we add an initial user, project, and todo as well as the todoFilter that will filter the initial state to show todos that have been completed.
+
+This data is a transaction that runs on database creation to seed your DB.
+
+```jsx
+const initialData = [
+ {
+ user: {
+ // negative numbers can be used as temporary ids in a transaction
+ id: -1,
+ name: 'Stella'
+ }
+ }, {
+ project: {
+ id: -3,
+ name: 'To the stars'
+ }
+ }, {
+ todo: {
+ name: 'Fix ship',
+ owner: -1,
+ project: -3,
+ isCompleted: true,
+ createdAt: new Date('2003/11/10')
+ }
+ }, {
+ todoFilter: {
+ // identity is a special attribute for user generated ids
+ // E.g. this is a setting that should be easy to lookup by name
+ identity: 'todoFilters',
+ showCompleted: true,
+ project: 0
+ }
+ }
+]
+```
+
+## Config
+
+And now we're ready to go. 馃殌
+
+```jsx
+const config = {
+ schema,
+ initialData
+}
+```
+
+## Reading and Writing Data
+
+Use the todoFilters we added earlier to filter the view. With Homebase everything is just data. It's similar to a reducer in React Hooks or Redux, but without the need to write bespoke mutation functions. We also introduce our `useEntity` and `useTransact` React Hooks here.
+
+`useEntity` enables you to grab the todoFilters Entity directly from Homebase by its `identity`(a unique developer given name, like a custom id). Entities are the building blocks of the Homebase data model. They are like JSON objects with bonus features: you can traverse arbitrarily deep relationship without actually denormalizing and nesting your data.
+
+`useTransact` lets you `transact` state from any component. Transactions let you create, update and delete multiple entities simultaneously and atomically. All changes will reactively update any components that depend on the changed data.
+
+```jsx
+const TodoFilters = () => {
+ const [filters] = useEntity({ identity: 'todoFilters' })
+ const [transact] = useTransact()
+ return (
+
+ )
+}
+```
+
+## Entity and Transact Examples
+
+In the following example `todo` is a Homebase database entity being passed in as a prop.
+
+As you probably noticed entities have a convenient function `entity.get('attribute')`. It's like `jsObj['attribute']` but with a lot of bonus benefits:
+
+1. You can chain attributes to traverse your relational graph
+ - `todo.get('project', 'owners', 0, 'name') => 'Stella'`
+1. Chaining attributes that return undefined will return null instead of an error
+ - `jsObj.nullAttr.childAttr => Error`
+ - `entity.get('nullAttr', 'childAttr') => null`
+1. Caching is built in. Homebase tracks the attributes used by every component and only triggers re-renders when that specific data changes. This caching is scoped to our hooks, so while we're passing a `todo` entity in the following example it can be better to pass the `id` as a prop and then `const [todo] = useEntity(id)` in the component to create a new caching scope.
+
+```jsx
+const Todo = ({ todo }) => (
+
+
+
+
+
+
+
+ 路
+
+ 路
+
+
+
+ {todo.get('createdAt').toLocaleString()}
+
+
+)
+```
+
+Notice how `id: todo.get('id')` is used in the following example to update an existing todo entity.
+
+```jsx
+const TodoCheck = ({ todo }) => {
+ const [transact] = useTransact()
+ return (
+ transact([{
+ todo: {
+ id: todo.get('id'),
+ isCompleted: e.target.checked
+ }
+ }])}
+ />
+ )
+}
+
+```
+
+`useTransact` is incredibly convenient. It lets you create, update, and delete any state from any component.
+
+```jsx
+const TodoName = ({ todo }) => {
+ const [transact] = useTransact()
+ return (
+ transact([{ todo: { id: todo.get('id'), name: e.target.value }}])}
+ />
+ )
+}
+
+const TodoProject = ({ todo }) => {
+ const [transact] = useTransact()
+ return (
+ transact([{ todo: { id: todo.get('id'), 'project': projectId || null }}])}
+ />
+ )
+}
+```
+
+## Queries
+
+`TodoOwner` introduces our first instance of the `useQuery` React Hook. Query the relational database directly in your component using Javascript friendly syntax. Queries return an array of unique entities instead of an individual entity like `useEntity`.
+
+```jsx
+const TodoOwner = ({ todo }) => {
+ const [transact] = useTransact()
+ const [users] = useQuery({
+ $find: 'user',
+ $where: { user: { name: '$any' } }
+ })
+...
+```
+
+Here's the full `TodoOwner` component:
+
+```jsx
+const TodoOwner = ({ todo }) => {
+ const [transact] = useTransact()
+ const [users] = useQuery({
+ $find: 'user',
+ $where: { user: { name: '$any' } }
+ })
+ return (
+ <>
+
+
+
+ >
+ ).
+```
+
+Our query API is powered by Datalog, and exposed as JSON similar to a JS SQL driver or MongoDB. Datalog is a subset of Prolog, a logic programming language, but with a few features restricted so it is guaranteed to terminate. This is not a perfect metaphor, but you can think of it as a more powerful version of SQL.
+
+We don't go into Datalog queries here, instead easing you into our JSON query API for simplicity, but you can directly query `Homebase` with Datalog as well. If you're interested in writing more sophisticated queries you can pass a datalog string instead of a JSON query as the first argument to useQuery.
+
+**E.g.**
+```jsx
+const [users] = useQuery(`
+ [:find ?todo
+ :where [?todo :todo/owner ?user]
+ [?user :user/name "Stella"]]
+`)
+```
+
+This will return all todos owned by users named Stella. As you can see joins are implicit by using the `?user` variable in multiple `:where` clauses.
+
+We won't get into more detail about Datalog here since it's essentially a programming language. The syntax we use has it's roots in [Clojure](https://clojure.org/) and [Datomic](https://www.datomic.com/on-prem.html). If you'd like us to priortize documention for these advanced queries please [let us know](https://github.com/homebaseio/homebase-react/issues?q=is%3Aissue+datalog). In the meantime we recommend [Learn Datalog Today](http://www.learndatalogtoday.org/) as a good place to start.
+
+### Create Data
+
+It's all just transactions. Yes it's repetitive, but the goal of Homebase it to make data declarative and composable on the client and the server. This means providing a powerful core library and letting you combine the pieces to declare what you want, without needing to say how to do achieve it.
+
+```jsx
+const NewTodo = () => {
+ const [transact] = useTransact()
+ return (
+
+ )
+}
+```
+
+### Delete Data
+
+The object style transactions support deletion of individual attributes by setting them to null.
+```jsx
+transact({ todo: { id: 123, name: null } })
+```
+
+To delete entire entities we provide a database function `retractEntity`. Database functions are atomic functions that run inside of transactions, basically they're reducers. Database functions are invoked by passing an array with the function name first followed by arguments `['retractEntity', 123]`.
+
+```jsx
+const TodoDelete = ({ todo }) => {
+ const [transact] = useTransact()
+ return (
+
+ )
+}
+```
+
+## The Full Example
+
+Here's everything we covered. You can try the app for yourself [here](https://homebaseio.github.io/homebase-react/#!/example.todo).
+
+If you're interested in integrating a backend you can check out our Firebase example [here](https://homebaseio.github.io/homebase-react/#!/example.todo_firebase) for inspiration or ping us at hi@homebase.io or in our [message board](https://github.com/homebaseio/homebase-react/discussions) to pair on integrating a custom backend.
+
+```jsx
+import React from 'react'
+import { HomebaseProvider, useTransact, useQuery, useEntity } from 'homebase-react'
+
+export const App = () => {
+ return (
+
+
+
+ )
+}
+
+const config = {
+ // Schema is only used to enforce
+ // unique constraints and relationships.
+ // It is not a type system, yet.
+ schema: {
+ project: { name: { unique: 'identity' } },
+ todo: {
+ // refs are relationships
+ project: { type: 'ref' },
+ owner: { type: 'ref' }
+ }
+ },
+ // Initial data let's you conveniently transact some
+ // starting data on DB creation to hydrate your components.
+ initialData: [
+ {
+ user: {
+ // negative numbers can be used as temporary ids in a transaction
+ id: -1,
+ name: 'Stella'
+ }
+ }, {
+ project: {
+ id: -3,
+ name: 'To the stars'
+ }
+ }, {
+ todo: {
+ name: 'Fix ship',
+ owner: -1,
+ project: -3,
+ isCompleted: true,
+ createdAt: new Date('2003/11/10')
+ }
+ }, {
+ todoFilter: {
+ // identity is a special attribute for user generated ids
+ // E.g. this is a setting that should be easy to lookup by name
+ identity: 'todoFilters',
+ showCompleted: true,
+ project: 0
+ }
+ }
+ ]
+}
+
+const Todos = () => {
+ return (
+