Skip to content

Commit 4c9dad5

Browse files
committed
new package: db-store
1 parent 40297bd commit 4c9dad5

File tree

10 files changed

+1346
-0
lines changed

10 files changed

+1346
-0
lines changed

.changeset/young-lizards-sing.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@solid-primitives/db-store": major
3+
---
4+
5+
new package: db-store - a store transparently bound to a database

packages/db-store/LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2021 Solid Primitives Working Group
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

packages/db-store/README.md

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<p>
2+
<img width="100%" src="https://assets.solidjs.com/banner?type=Primitives&background=tiles&project=db-store" alt="Solid Primitives db-store">
3+
</p>
4+
5+
# @solid-primitives/db-store
6+
7+
[![size](https://img.shields.io/bundlephobia/minzip/@solid-primitives/db-store?style=for-the-badge&label=size)](https://bundlephobia.com/package/@solid-primitives/db-store)
8+
[![version](https://img.shields.io/npm/v/@solid-primitives/db-store?style=for-the-badge)](https://www.npmjs.com/package/@solid-primitives/db-store)
9+
[![stage](https://img.shields.io/endpoint?style=for-the-badge&url=https%3A%2F%2Fraw.githubusercontent.com%2Fsolidjs-community%2Fsolid-primitives%2Fmain%2Fassets%2Fbadges%2Fstage-0.json)](https://github.com/solidjs-community/solid-primitives#contribution-process)
10+
11+
A primitive that creates a synchronized store from a database:
12+
13+
`createDbStore` - creates a store synchronized to a database.
14+
`supabaseAdapter` - adapter for [supabase](https://supabase.com/) database connections to synchronize stores from.
15+
16+
## Installation
17+
18+
```bash
19+
npm install @solid-primitives/db-store
20+
# or
21+
yarn add @solid-primitives/db-store
22+
# or
23+
pnpm add @solid-primitives/db-store
24+
```
25+
26+
## How to use it
27+
28+
```ts
29+
const [dbStore, setDbStore] = createDbStore({
30+
adapter: supabaseAdapter(client),
31+
table: 'todos',
32+
filter: ({ userid }) => userid === user.id,
33+
onError: handleErrors,
34+
});
35+
```
36+
37+
The store is automatically initialized and optimistically updated both ways. Due to how databases work, the store can only ever contain an array of entries.
38+
39+
> [!WARNING]
40+
> Since the order of items in the database cannot be guaranteed, the same is true for the items in the store.
41+
42+
> [!NOTE]
43+
> It can take some time for the database editor to show updates. They are processed a lot faster.
44+
45+
### Setting preliminary IDs
46+
47+
The `id` field needs to be set by the database, so even if you set it, it needs to be overwritten in any case. It is not required to set it for your fields manually; one can also treat its absence as a sign that an insertion is not yet done in the database.
48+
49+
### Handling errors
50+
51+
If any change could not be successfully committed to the database, the `onError` handler is called with an Error. If the caught error was an error itself, it is used directly, else what was encountered will be set as cause for an Error "unknown error". The error will also be augmented with a "data" property containing the update, an "action" property containing "insert", "update" or "delete" and a "server" flag property that is true if the error happened while sending data to the server.
52+
53+
### Write your own adapter
54+
55+
Your adapter must have the following properties:
56+
57+
```tsx
58+
export type DbAdapterUpdate<Row extends DbRow> =
59+
{ old?: Partial<Row>, new?: Partial<Row> };
60+
61+
export type DbAdapter<Row> = {
62+
insertSignal: () => DbAdapterUpdate<Row> | undefined,
63+
updateSignal: () => DbAdapterUpdate<Row> | undefined,
64+
deleteSignal: () => DbAdapterUpdate<Row> | undefined,
65+
init: () => Promise<Row[]>,
66+
insert: (data: DbAdapterUpdate<Row>) => PromiseLike<any>,
67+
update: (data: DbAdapterUpdate<Row>) => PromiseLike<any>,
68+
delete: (data: DbAdapterUpdate<Row>) => PromiseLike<any>
69+
};
70+
```
71+
72+
## Demo
73+
74+
[Working demonstration](https://primitives.solidjs.community/playground/db-store/) (requires Supabase account)
75+
76+
## Changelog
77+
78+
See [CHANGELOG.md](./CHANGELOG.md)
79+
80+
## Plans
81+
82+
This is an early draft; in the future, more adapters are planned: mongodb, prism, firebase, aws?
83+
84+

packages/db-store/dev/index.tsx

+142
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import { Component, createSignal, For, Show } from "solid-js";
2+
import { createDbStore, supabaseAdapter, DbRow, DbStoreError } from "../src/index.js";
3+
import { createClient, SupabaseClient } from "@supabase/supabase-js";
4+
import { reconcile } from "solid-js/store";
5+
6+
const TodoList = (props: { client: SupabaseClient }) => {
7+
const [error, setError] = createSignal<DbStoreError<DbRow>>();
8+
const [todos, setTodos] = createDbStore({
9+
adapter: supabaseAdapter({ client: props.client, table: "todos" }),
10+
onError: setError,
11+
});
12+
const [edit, setEdit] = createSignal<DbRow>();
13+
const done = (task: DbRow) => setTodos(reconcile(todos.filter(todo => todo !== task)));
14+
const add = (task: string) => setTodos(todos.length, { task });
15+
return (
16+
<>
17+
<Show when={error()} keyed>
18+
{(err) => <p class="text-red-600">{`Error: ${err.message} Cause: ${err.cause} Action: ${err.action} Data: ${JSON.stringify(err.data)}`} ${err.server ? 'server' : 'client'} <button onClick={() => setError()}>ok</button></p>}
19+
</Show>
20+
<ul>
21+
<For
22+
each={Array.from(todos).toSorted((a, b) => Number(a.id) - Number(b.id))}
23+
fallback={<li>No to-dos yet</li>}
24+
>
25+
{item => (
26+
<li class="flex flex-row items-center" data-id={item.id}>
27+
<Show when={item === edit()} fallback={<span onClick={() => setEdit(item)}>{item.task}</span>}>
28+
<input name="update" value={item.task} /><button onClick={() => {
29+
const updated = (document.querySelector('[name="update"]') as HTMLInputElement).value;
30+
const index = todos.indexOf(item);
31+
if (updated && index > -1) setTodos(index, 'task', updated);
32+
setEdit(undefined);
33+
}}>update</button><button onClick={() => setEdit(undefined)}>cancel</button>
34+
</Show>{" "}
35+
<span role="button" class="p-2" onClick={() => done(item)} title="Done">
36+
x
37+
</span>
38+
</li>
39+
)}
40+
</For>
41+
<li class="flex flex-row items-center">
42+
<label>
43+
new task: <input name="task" />
44+
</label>{" "}
45+
<button
46+
onClick={() => {
47+
const task = (document.querySelector('[name="task"]') as HTMLInputElement).value;
48+
if (task) {
49+
add(task);
50+
(document.querySelector('[name="task"]') as HTMLInputElement).value = "";
51+
}
52+
}}
53+
>
54+
do it!
55+
</button>
56+
</li>
57+
</ul>
58+
</>
59+
);
60+
};
61+
62+
const App: Component = () => {
63+
const [client, setClient] = createSignal<SupabaseClient<any, "public", any>>();
64+
const connect = () => {
65+
const url = (document.querySelector('[type="url"]') as HTMLInputElement | null)?.value;
66+
const key = (document.querySelector('[type="password"]') as HTMLInputElement | null)?.value;
67+
url && key && setClient(createClient(url, key));
68+
};
69+
70+
return (
71+
<div class="box-border flex min-h-screen w-full flex-col items-center justify-center space-y-4 bg-gray-800 p-24 text-white">
72+
<div class="wrapper-v">
73+
<h4>db-store-backed to-do list</h4>
74+
<Show
75+
when={client()}
76+
keyed
77+
fallback={
78+
<>
79+
<details>
80+
<summary>To configure your own database,</summary>
81+
<ul class="list-disc">
82+
<li>
83+
Register with <a href="https://supabase.com">Supabase</a>.
84+
</li>
85+
<li>
86+
Create a new database and note down the url and the key (that usually go into an
87+
environment)
88+
</li>
89+
<li>Within the database, create a table and configure it to be public, promote changes in realtime and has no row protection:
90+
91+
<pre><code>{
92+
`-- Create table
93+
create table todos (
94+
id serial primary key,
95+
task text
96+
);
97+
-- Turn off row-level security
98+
alter table "todos"
99+
disable row level security;
100+
-- Allow anonymous access
101+
create policy "Allow anonymous access"
102+
on todos
103+
for select
104+
to anon
105+
using (true);`
106+
}</code></pre>
107+
</li>
108+
<li>Fill in the url and key in the fields below and press "connect".</li>
109+
</ul>
110+
</details>
111+
<p>
112+
<label>
113+
DB
114+
<select>
115+
<option value="supabase">SupaBase</option>
116+
</select>
117+
</label>
118+
</p>
119+
<p>
120+
<label>
121+
Client URL <input type="url" />
122+
</label>
123+
</p>
124+
<p>
125+
<label>
126+
Client Key <input type="password" />
127+
</label>
128+
</p>
129+
<button class="btn" onClick={connect}>
130+
Connect
131+
</button>
132+
</>
133+
}
134+
>
135+
{(client: SupabaseClient) => <TodoList client={client} />}
136+
</Show>
137+
</div>
138+
</div>
139+
);
140+
};
141+
142+
export default App;

packages/db-store/package.json

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
{
2+
"name": "@solid-primitives/db-store",
3+
"version": "0.0.100",
4+
"description": "A template primitive example.",
5+
"author": "Your Name <[email protected]>",
6+
"contributors": [],
7+
"license": "MIT",
8+
"homepage": "https://primitives.solidjs.community/package/db-store",
9+
"repository": {
10+
"type": "git",
11+
"url": "git+https://github.com/solidjs-community/solid-primitives.git"
12+
},
13+
"bugs": {
14+
"url": "https://github.com/solidjs-community/solid-primitives/issues"
15+
},
16+
"primitive": {
17+
"name": "db-store",
18+
"stage": 0,
19+
"list": [
20+
"createDbStore",
21+
"supabaseAdapter"
22+
],
23+
"category": "Reactivity"
24+
},
25+
"keywords": [
26+
"solid",
27+
"primitives"
28+
],
29+
"private": false,
30+
"sideEffects": false,
31+
"files": [
32+
"dist"
33+
],
34+
"type": "module",
35+
"module": "./dist/index.js",
36+
"types": "./dist/index.d.ts",
37+
"browser": {},
38+
"exports": {
39+
"@solid-primitives/source": "./src/index.ts",
40+
"import": {
41+
"types": "./dist/index.d.ts",
42+
"default": "./dist/index.js"
43+
}
44+
},
45+
"typesVersions": {},
46+
"scripts": {
47+
"dev": "tsx ../../scripts/dev.ts",
48+
"build": "tsx ../../scripts/build.ts",
49+
"vitest": "vitest -c ../../configs/vitest.config.ts",
50+
"test": "pnpm run vitest",
51+
"test:ssr": "pnpm run vitest --mode ssr"
52+
},
53+
"dependencies": {
54+
"@solid-primitives/resource": "workspace:^"
55+
},
56+
"peerDependencies": {
57+
"solid-js": "^1.6.12",
58+
"@supabase/supabase-js": "2.*"
59+
},
60+
"devDependencies": {
61+
"@supabase/supabase-js": "^2.48.1"
62+
}
63+
}

0 commit comments

Comments
 (0)