Compare commits
100 Commits
docs/llms
...
cursor-doc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a3928a637 | ||
|
|
3fa276c18d | ||
|
|
1928519d39 | ||
|
|
f4fa80b782 | ||
|
|
782df5d4b8 | ||
|
|
9db20ad630 | ||
|
|
3405d8f275 | ||
|
|
0a64dca0cd | ||
|
|
4d0b9b1bf1 | ||
|
|
403d61c8e8 | ||
|
|
0685c1cd5f | ||
|
|
834203f270 | ||
|
|
f01bc19257 | ||
|
|
6405a77aa2 | ||
|
|
ace6486075 | ||
|
|
23c3e5a125 | ||
|
|
e3d75e5c97 | ||
|
|
7838075bd6 | ||
|
|
dd792bf0ca | ||
|
|
233aae1deb | ||
|
|
153dc996a5 | ||
|
|
2b548c1758 | ||
|
|
167b588553 | ||
|
|
424930d06d | ||
|
|
d624a676d8 | ||
|
|
06f2af465d | ||
|
|
c7332f84b9 | ||
|
|
68fdbfbe94 | ||
|
|
e98d0e0c7f | ||
|
|
7efe89df31 | ||
|
|
2fb6428ea1 | ||
|
|
5a8f5d8bc2 | ||
|
|
2cc9daab37 | ||
|
|
e0276f42ee | ||
|
|
363be52022 | ||
|
|
1e87fc7772 | ||
|
|
03ec5d3ec8 | ||
|
|
5f272ff6ba | ||
|
|
12392424dd | ||
|
|
58eb3c0a98 | ||
|
|
adf965d53d | ||
|
|
3baa951bb9 | ||
|
|
b726e31669 | ||
|
|
63bb31e0ad | ||
|
|
d059460abd | ||
|
|
ba81951331 | ||
|
|
bd132bb9ad | ||
|
|
1ee435ad95 | ||
|
|
07273f7ab8 | ||
|
|
646ad330ae | ||
|
|
af8e6e3f82 | ||
|
|
f247525dfe | ||
|
|
012022db2b | ||
|
|
da92891498 | ||
|
|
b0c55720f8 | ||
|
|
a3d825fc6f | ||
|
|
29ca2e6f65 | ||
|
|
07a683c13d | ||
|
|
e53f02d6d7 | ||
|
|
4915bfa26d | ||
|
|
af45aac5f2 | ||
|
|
1136d9b744 | ||
|
|
92e78dc262 | ||
|
|
bf76d798c4 | ||
|
|
627f8c4c28 | ||
|
|
a1e0410863 | ||
|
|
03897a2689 | ||
|
|
823f546028 | ||
|
|
9bc54d1939 | ||
|
|
30780c05f0 | ||
|
|
118b6294ac | ||
|
|
6dc9b9d2ec | ||
|
|
ccbcee5102 | ||
|
|
0ae2067c3c | ||
|
|
2137938ead | ||
|
|
d14bb57ff5 | ||
|
|
3f42a4ddf9 | ||
|
|
0eed228170 | ||
|
|
a519537701 | ||
|
|
43c79cac2a | ||
|
|
44dbaa00d4 | ||
|
|
a0df32e81a | ||
|
|
236d8226d8 | ||
|
|
1220fa5d97 | ||
|
|
f3a5f83f25 | ||
|
|
a1bd6fc79b | ||
|
|
0f83320222 | ||
|
|
a3c4067de3 | ||
|
|
3042627748 | ||
|
|
8cea1e96cf | ||
|
|
5cc58c8e02 | ||
|
|
9df644c578 | ||
|
|
1e625f3c12 | ||
|
|
8b3686c7ce | ||
|
|
bce04ee06d | ||
|
|
f2e9115f4c | ||
|
|
6854f9930c | ||
|
|
ee0897d9a8 | ||
|
|
243ab074eb | ||
|
|
938f9256db |
5
.changeset/thin-months-explode.md
Normal file
5
.changeset/thin-months-explode.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"cursor-docs": patch
|
||||
---
|
||||
|
||||
Added Cursor docs
|
||||
@@ -1,5 +1,40 @@
|
||||
# chat-rn-clerk
|
||||
|
||||
## 1.0.75
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3405d8f]
|
||||
- jazz-react-native@0.10.10
|
||||
- jazz-react-native-auth-clerk@0.10.10
|
||||
|
||||
## 1.0.74
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-native-auth-clerk@0.10.9
|
||||
|
||||
## 1.0.73
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [2fb6428]
|
||||
- jazz-tools@0.10.8
|
||||
- jazz-react-native@0.10.8
|
||||
- jazz-react-native-auth-clerk@0.10.8
|
||||
- jazz-react-native-media-images@0.10.8
|
||||
|
||||
## 1.0.72
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [1136d9b]
|
||||
- Updated dependencies [0eed228]
|
||||
- jazz-react-native@0.10.7
|
||||
- jazz-tools@0.10.7
|
||||
- jazz-react-native-auth-clerk@0.10.7
|
||||
- jazz-react-native-media-images@0.10.7
|
||||
|
||||
## 1.0.71
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "chat-rn-clerk",
|
||||
"main": "index.js",
|
||||
"version": "1.0.71",
|
||||
"version": "1.0.75",
|
||||
"scripts": {
|
||||
"build": "expo export -p ios",
|
||||
"start": "expo start",
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
# chat-rn
|
||||
|
||||
## 1.0.71
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3405d8f]
|
||||
- jazz-react-native@0.10.10
|
||||
|
||||
## 1.0.70
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [2fb6428]
|
||||
- jazz-tools@0.10.8
|
||||
- jazz-react-native@0.10.8
|
||||
|
||||
## 1.0.69
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [1136d9b]
|
||||
- Updated dependencies [0eed228]
|
||||
- jazz-react-native@0.10.7
|
||||
- jazz-tools@0.10.7
|
||||
|
||||
## 1.0.68
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "chat-rn",
|
||||
"version": "1.0.68",
|
||||
"version": "1.0.71",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "expo export -p ios",
|
||||
|
||||
@@ -44,4 +44,5 @@ appId: com.jazz.chatrn
|
||||
|
||||
# logout
|
||||
- tapOn: "Logout"
|
||||
- assertVisible: "Anonymous user"
|
||||
- assertVisible: "boorad"
|
||||
- assertVisible: "bro, low key, it do be like that tho"
|
||||
|
||||
@@ -1,5 +1,34 @@
|
||||
# chat-vue
|
||||
|
||||
## 0.0.58
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [834203f]
|
||||
- jazz-browser@0.10.9
|
||||
- jazz-vue@0.10.9
|
||||
|
||||
## 0.0.57
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [1e87fc7]
|
||||
- Updated dependencies [2fb6428]
|
||||
- jazz-browser@0.10.8
|
||||
- jazz-tools@0.10.8
|
||||
- jazz-vue@0.10.8
|
||||
|
||||
## 0.0.56
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [1136d9b]
|
||||
- Updated dependencies [bf76d79]
|
||||
- Updated dependencies [0eed228]
|
||||
- jazz-browser@0.10.7
|
||||
- jazz-tools@0.10.7
|
||||
- jazz-vue@0.10.7
|
||||
|
||||
## 0.0.55
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "chat-vue",
|
||||
"version": "0.0.55",
|
||||
"version": "0.0.58",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,31 @@
|
||||
# jazz-example-chat
|
||||
|
||||
## 0.0.154
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-browser-media-images@0.10.9
|
||||
- jazz-react@0.10.9
|
||||
|
||||
## 0.0.153
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [2fb6428]
|
||||
- jazz-tools@0.10.8
|
||||
- jazz-react@0.10.8
|
||||
- jazz-browser-media-images@0.10.8
|
||||
|
||||
## 0.0.152
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [1136d9b]
|
||||
- Updated dependencies [0eed228]
|
||||
- jazz-react@0.10.7
|
||||
- jazz-tools@0.10.7
|
||||
- jazz-browser-media-images@0.10.7
|
||||
|
||||
## 0.0.151
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-chat",
|
||||
"private": true,
|
||||
"version": "0.0.151",
|
||||
"version": "0.0.154",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,31 @@
|
||||
# minimal-auth-clerk
|
||||
|
||||
## 0.0.53
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.10.9
|
||||
- jazz-react-auth-clerk@0.10.9
|
||||
|
||||
## 0.0.52
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [2fb6428]
|
||||
- jazz-tools@0.10.8
|
||||
- jazz-react@0.10.8
|
||||
- jazz-react-auth-clerk@0.10.8
|
||||
|
||||
## 0.0.51
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [1136d9b]
|
||||
- Updated dependencies [0eed228]
|
||||
- jazz-react-auth-clerk@0.10.7
|
||||
- jazz-react@0.10.7
|
||||
- jazz-tools@0.10.7
|
||||
|
||||
## 0.0.50
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "clerk",
|
||||
"private": true,
|
||||
"version": "0.0.50",
|
||||
"version": "0.0.53",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -13,7 +13,7 @@
|
||||
"dependencies": {
|
||||
"@clerk/clerk-react": "^5.4.1",
|
||||
"jazz-react": "workspace:*",
|
||||
"jazz-react-auth-clerk": "workspace:0.10.6",
|
||||
"jazz-react-auth-clerk": "workspace:0.10.9",
|
||||
"jazz-tools": "workspace:*",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
|
||||
@@ -1,5 +1,28 @@
|
||||
# file-share-svelte
|
||||
|
||||
## 0.0.38
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-svelte@0.10.9
|
||||
|
||||
## 0.0.37
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [2fb6428]
|
||||
- jazz-tools@0.10.8
|
||||
- jazz-svelte@0.10.8
|
||||
|
||||
## 0.0.36
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [1136d9b]
|
||||
- Updated dependencies [0eed228]
|
||||
- jazz-svelte@0.10.7
|
||||
- jazz-tools@0.10.7
|
||||
|
||||
## 0.0.35
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "file-share-svelte",
|
||||
"version": "0.0.35",
|
||||
"version": "0.0.38",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,31 @@
|
||||
# form
|
||||
|
||||
## 0.0.49
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-browser-media-images@0.10.9
|
||||
- jazz-react@0.10.9
|
||||
|
||||
## 0.0.48
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [2fb6428]
|
||||
- jazz-tools@0.10.8
|
||||
- jazz-react@0.10.8
|
||||
- jazz-browser-media-images@0.10.8
|
||||
|
||||
## 0.0.47
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [1136d9b]
|
||||
- Updated dependencies [0eed228]
|
||||
- jazz-react@0.10.7
|
||||
- jazz-tools@0.10.7
|
||||
- jazz-browser-media-images@0.10.7
|
||||
|
||||
## 0.0.46
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "form",
|
||||
"private": true,
|
||||
"version": "0.0.46",
|
||||
"version": "0.0.49",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useIframeHashRouter } from "hash-slash";
|
||||
import { useAccount } from "jazz-react";
|
||||
import { ID } from "jazz-tools";
|
||||
import { CreateOrder } from "./CreateOrder.tsx";
|
||||
import { EditOrder } from "./EditOrder.tsx";
|
||||
@@ -7,25 +6,10 @@ import { Orders } from "./Orders.tsx";
|
||||
import { BubbleTeaOrder } from "./schema.ts";
|
||||
|
||||
function App() {
|
||||
const { me, logOut } = useAccount();
|
||||
const router = useIframeHashRouter();
|
||||
|
||||
return (
|
||||
<>
|
||||
<header>
|
||||
<nav className="container py-2 border-b flex items-center justify-between">
|
||||
<span>
|
||||
You're logged in as <strong>{me?.profile?.name}</strong>
|
||||
</span>
|
||||
<button
|
||||
className="bg-stone-100 py-1.5 px-3 text-sm rounded-md dark:bg-stone-900 dark:text-white"
|
||||
onClick={() => logOut()}
|
||||
>
|
||||
Log out
|
||||
</button>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main className="container py-8 space-y-8">
|
||||
{router.route({
|
||||
"/": () => <Orders />,
|
||||
|
||||
@@ -1,5 +1,31 @@
|
||||
# image-upload
|
||||
|
||||
## 0.0.51
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-browser-media-images@0.10.9
|
||||
- jazz-react@0.10.9
|
||||
|
||||
## 0.0.50
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [2fb6428]
|
||||
- jazz-tools@0.10.8
|
||||
- jazz-react@0.10.8
|
||||
- jazz-browser-media-images@0.10.8
|
||||
|
||||
## 0.0.49
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [1136d9b]
|
||||
- Updated dependencies [0eed228]
|
||||
- jazz-react@0.10.7
|
||||
- jazz-tools@0.10.7
|
||||
- jazz-browser-media-images@0.10.7
|
||||
|
||||
## 0.0.48
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "image-upload",
|
||||
"private": true,
|
||||
"version": "0.0.48",
|
||||
"version": "0.0.51",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,19 +1,8 @@
|
||||
import { useAccount } from "jazz-react";
|
||||
import ImageUpload from "./ImageUpload.tsx";
|
||||
|
||||
function App() {
|
||||
const { me, logOut } = useAccount();
|
||||
|
||||
return (
|
||||
<>
|
||||
<header>
|
||||
<nav className="container">
|
||||
<span>
|
||||
You're logged in as <strong>{me?.profile?.name}</strong>
|
||||
</span>
|
||||
<button onClick={() => logOut()}>Log out</button>
|
||||
</nav>
|
||||
</header>
|
||||
<main className="container">
|
||||
<ImageUpload />
|
||||
</main>
|
||||
|
||||
@@ -46,7 +46,12 @@ export default function ImageUpload() {
|
||||
) : (
|
||||
<div>
|
||||
<label>Upload image</label>
|
||||
<input ref={inputRef} type="file" onChange={onImageChange} />
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="file"
|
||||
accept="image/png, image/jpeg, image/gif"
|
||||
onChange={onImageChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -72,8 +72,7 @@ nav {
|
||||
.container {
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
padding-right: 0.75rem;
|
||||
padding-left: 0.75rem;
|
||||
padding: 2rem 0.75rem;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
# jazz-example-inspector
|
||||
|
||||
## 0.0.109
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [153dc99]
|
||||
- cojson@0.10.8
|
||||
- cojson-transport-ws@0.10.8
|
||||
|
||||
## 0.0.108
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [0f83320]
|
||||
- Updated dependencies [012022d]
|
||||
- cojson@0.10.7
|
||||
- cojson-transport-ws@0.10.7
|
||||
|
||||
## 0.0.107
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-inspector-app",
|
||||
"private": true,
|
||||
"version": "0.0.107",
|
||||
"version": "0.0.109",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -16,8 +16,8 @@
|
||||
"@radix-ui/react-toast": "^1.1.4",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"cojson": "workspace:0.10.6",
|
||||
"cojson-transport-ws": "workspace:0.10.6",
|
||||
"cojson": "workspace:0.10.8",
|
||||
"cojson-transport-ws": "workspace:0.10.8",
|
||||
"hash-slash": "workspace:0.2.2",
|
||||
"lucide-react": "^0.274.0",
|
||||
"qrcode": "^1.5.3",
|
||||
|
||||
@@ -1,5 +1,30 @@
|
||||
# jazz-example-musicplayer
|
||||
|
||||
## 0.0.75
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.10.9
|
||||
|
||||
## 0.0.74
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [2fb6428]
|
||||
- jazz-tools@0.10.8
|
||||
- jazz-inspector@0.10.8
|
||||
- jazz-react@0.10.8
|
||||
|
||||
## 0.0.73
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [1136d9b]
|
||||
- Updated dependencies [0eed228]
|
||||
- jazz-react@0.10.7
|
||||
- jazz-tools@0.10.7
|
||||
- jazz-inspector@0.10.7
|
||||
|
||||
## 0.0.72
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-music-player",
|
||||
"private": true,
|
||||
"version": "0.0.72",
|
||||
"version": "0.0.75",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -22,8 +22,8 @@
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"jazz-inspector": "workspace:*",
|
||||
"jazz-react": "workspace:0.10.6",
|
||||
"jazz-tools": "workspace:0.10.6",
|
||||
"jazz-react": "workspace:0.10.9",
|
||||
"jazz-tools": "workspace:0.10.8",
|
||||
"lucide-react": "^0.274.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
|
||||
@@ -1,5 +1,28 @@
|
||||
# organization
|
||||
|
||||
## 0.0.47
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.10.9
|
||||
|
||||
## 0.0.46
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [2fb6428]
|
||||
- jazz-tools@0.10.8
|
||||
- jazz-react@0.10.8
|
||||
|
||||
## 0.0.45
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [1136d9b]
|
||||
- Updated dependencies [0eed228]
|
||||
- jazz-react@0.10.7
|
||||
- jazz-tools@0.10.7
|
||||
|
||||
## 0.0.44
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "organization",
|
||||
"private": true,
|
||||
"version": "0.0.44",
|
||||
"version": "0.0.47",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,24 @@
|
||||
# passkey-svelte
|
||||
|
||||
## 0.0.42
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-svelte@0.10.9
|
||||
|
||||
## 0.0.41
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-svelte@0.10.8
|
||||
|
||||
## 0.0.40
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [1136d9b]
|
||||
- jazz-svelte@0.10.7
|
||||
|
||||
## 0.0.39
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "passkey-svelte",
|
||||
"version": "0.0.39",
|
||||
"version": "0.0.42",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,28 @@
|
||||
# minimal-auth-passkey
|
||||
|
||||
## 0.0.52
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.10.9
|
||||
|
||||
## 0.0.51
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [2fb6428]
|
||||
- jazz-tools@0.10.8
|
||||
- jazz-react@0.10.8
|
||||
|
||||
## 0.0.50
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [1136d9b]
|
||||
- Updated dependencies [0eed228]
|
||||
- jazz-react@0.10.7
|
||||
- jazz-tools@0.10.7
|
||||
|
||||
## 0.0.49
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "passkey",
|
||||
"private": true,
|
||||
"version": "0.0.49",
|
||||
"version": "0.0.52",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,28 @@
|
||||
# passphrase
|
||||
|
||||
## 0.0.49
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.10.9
|
||||
|
||||
## 0.0.48
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [2fb6428]
|
||||
- jazz-tools@0.10.8
|
||||
- jazz-react@0.10.8
|
||||
|
||||
## 0.0.47
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [1136d9b]
|
||||
- Updated dependencies [0eed228]
|
||||
- jazz-react@0.10.7
|
||||
- jazz-tools@0.10.7
|
||||
|
||||
## 0.0.46
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "passphrase",
|
||||
"private": true,
|
||||
"version": "0.0.46",
|
||||
"version": "0.0.49",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,28 @@
|
||||
# jazz-password-manager
|
||||
|
||||
## 0.0.73
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.10.9
|
||||
|
||||
## 0.0.72
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [2fb6428]
|
||||
- jazz-tools@0.10.8
|
||||
- jazz-react@0.10.8
|
||||
|
||||
## 0.0.71
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [1136d9b]
|
||||
- Updated dependencies [0eed228]
|
||||
- jazz-react@0.10.7
|
||||
- jazz-tools@0.10.7
|
||||
|
||||
## 0.0.70
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-password-manager",
|
||||
"private": true,
|
||||
"version": "0.0.70",
|
||||
"version": "0.0.73",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -12,8 +12,8 @@
|
||||
"clean-install": "rm -rf node_modules pnpm-lock.yaml && pnpm install"
|
||||
},
|
||||
"dependencies": {
|
||||
"jazz-react": "workspace:0.10.6",
|
||||
"jazz-tools": "workspace:0.10.6",
|
||||
"jazz-react": "workspace:0.10.9",
|
||||
"jazz-tools": "workspace:0.10.8",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-hook-form": "^7.41.5",
|
||||
|
||||
@@ -1,5 +1,31 @@
|
||||
# jazz-example-pets
|
||||
|
||||
## 0.0.171
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-browser-media-images@0.10.9
|
||||
- jazz-react@0.10.9
|
||||
|
||||
## 0.0.170
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [2fb6428]
|
||||
- jazz-tools@0.10.8
|
||||
- jazz-react@0.10.8
|
||||
- jazz-browser-media-images@0.10.8
|
||||
|
||||
## 0.0.169
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [1136d9b]
|
||||
- Updated dependencies [0eed228]
|
||||
- jazz-react@0.10.7
|
||||
- jazz-tools@0.10.7
|
||||
- jazz-browser-media-images@0.10.7
|
||||
|
||||
## 0.0.168
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-pets",
|
||||
"private": true,
|
||||
"version": "0.0.168",
|
||||
"version": "0.0.171",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -19,9 +19,9 @@
|
||||
"@radix-ui/react-toast": "^1.1.4",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"jazz-browser-media-images": "workspace:0.10.6",
|
||||
"jazz-react": "workspace:0.10.6",
|
||||
"jazz-tools": "workspace:0.10.6",
|
||||
"jazz-browser-media-images": "workspace:0.10.9",
|
||||
"jazz-react": "workspace:0.10.9",
|
||||
"jazz-tools": "workspace:0.10.8",
|
||||
"lucide-react": "^0.274.0",
|
||||
"qrcode": "^1.5.3",
|
||||
"react": "^18.3.1",
|
||||
@@ -41,7 +41,7 @@
|
||||
"@vitejs/plugin-react-swc": "^3.3.2",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"is-ci": "^3.0.1",
|
||||
"jazz-run": "workspace:0.10.6",
|
||||
"jazz-run": "workspace:0.10.8",
|
||||
"postcss": "^8.4.27",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "~5.6.2",
|
||||
|
||||
@@ -1,5 +1,31 @@
|
||||
# reactions
|
||||
|
||||
## 0.0.51
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-browser-media-images@0.10.9
|
||||
- jazz-react@0.10.9
|
||||
|
||||
## 0.0.50
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [2fb6428]
|
||||
- jazz-tools@0.10.8
|
||||
- jazz-react@0.10.8
|
||||
- jazz-browser-media-images@0.10.8
|
||||
|
||||
## 0.0.49
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [1136d9b]
|
||||
- Updated dependencies [0eed228]
|
||||
- jazz-react@0.10.7
|
||||
- jazz-tools@0.10.7
|
||||
- jazz-browser-media-images@0.10.7
|
||||
|
||||
## 0.0.48
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "reactions",
|
||||
"private": true,
|
||||
"version": "0.0.48",
|
||||
"version": "0.0.51",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,34 @@
|
||||
# todo-vue
|
||||
|
||||
## 0.0.56
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [834203f]
|
||||
- jazz-browser@0.10.9
|
||||
- jazz-vue@0.10.9
|
||||
|
||||
## 0.0.55
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [1e87fc7]
|
||||
- Updated dependencies [2fb6428]
|
||||
- jazz-browser@0.10.8
|
||||
- jazz-tools@0.10.8
|
||||
- jazz-vue@0.10.8
|
||||
|
||||
## 0.0.54
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [1136d9b]
|
||||
- Updated dependencies [bf76d79]
|
||||
- Updated dependencies [0eed228]
|
||||
- jazz-browser@0.10.7
|
||||
- jazz-tools@0.10.7
|
||||
- jazz-vue@0.10.7
|
||||
|
||||
## 0.0.53
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "todo-vue",
|
||||
"version": "0.0.53",
|
||||
"version": "0.0.56",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,28 @@
|
||||
# jazz-example-todo
|
||||
|
||||
## 0.0.170
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.10.9
|
||||
|
||||
## 0.0.169
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [2fb6428]
|
||||
- jazz-tools@0.10.8
|
||||
- jazz-react@0.10.8
|
||||
|
||||
## 0.0.168
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [1136d9b]
|
||||
- Updated dependencies [0eed228]
|
||||
- jazz-react@0.10.7
|
||||
- jazz-tools@0.10.7
|
||||
|
||||
## 0.0.167
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-todo",
|
||||
"private": true,
|
||||
"version": "0.0.167",
|
||||
"version": "0.0.170",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -16,8 +16,8 @@
|
||||
"@radix-ui/react-toast": "^1.1.4",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"jazz-react": "workspace:0.10.6",
|
||||
"jazz-tools": "workspace:0.10.6",
|
||||
"jazz-react": "workspace:0.10.9",
|
||||
"jazz-tools": "workspace:0.10.8",
|
||||
"lucide-react": "^0.274.0",
|
||||
"qrcode": "^1.5.3",
|
||||
"react": "^18.3.1",
|
||||
|
||||
@@ -1,5 +1,28 @@
|
||||
# version-history
|
||||
|
||||
## 0.0.48
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.10.9
|
||||
|
||||
## 0.0.47
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [2fb6428]
|
||||
- jazz-tools@0.10.8
|
||||
- jazz-react@0.10.8
|
||||
|
||||
## 0.0.46
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [1136d9b]
|
||||
- Updated dependencies [0eed228]
|
||||
- jazz-react@0.10.7
|
||||
- jazz-tools@0.10.7
|
||||
|
||||
## 0.0.45
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "version-history",
|
||||
"private": true,
|
||||
"version": "0.0.45",
|
||||
"version": "0.0.48",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
FolderArchiveIcon,
|
||||
GaugeIcon,
|
||||
GlobeIcon,
|
||||
HashIcon,
|
||||
ImageIcon,
|
||||
LinkIcon,
|
||||
LockKeyholeIcon,
|
||||
@@ -53,6 +54,7 @@ const icons = {
|
||||
encryption: LockKeyholeIcon,
|
||||
faceId: ScanFace,
|
||||
file: FileTextIcon,
|
||||
hash: HashIcon,
|
||||
help: MessageCircleQuestionIcon,
|
||||
image: ImageIcon,
|
||||
instant: GaugeIcon,
|
||||
|
||||
@@ -136,6 +136,7 @@ const config = {
|
||||
padding: "0.15rem 0.25rem",
|
||||
borderRadius: "2px",
|
||||
whiteSpace: "nowrap",
|
||||
fontWeight: 400,
|
||||
},
|
||||
p: {
|
||||
marginBottom: theme("spacing.3"),
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
import { ContentByFramework, CodeGroup } from '@/components/forMdx'
|
||||
import { ContentByFramework, FileDownloadLink, CodeGroup } from '@/components/forMdx'
|
||||
|
||||
# Using AI tools to build Jazz apps
|
||||
# Using AI to build Jazz apps
|
||||
|
||||
AI tools, particularly large language models (LLMs), can enhance the speed at which you build apps with Jazz.
|
||||
However, for these tools to work effectively and provide better results, they need access to the relevant documentation.
|
||||
AI tools, particularly large language models (LLMs), can accelerate your development with Jazz. Searching docs, responding to questions and even helping you write code are all things that LLMs are starting to get good at.
|
||||
|
||||
## LLM docs
|
||||
However, Jazz is a rapidly evolving framework, so sometimes AI might get things a little wrong.
|
||||
|
||||
To provide AI tools, like Cursor, with access to Jazz docs, we've compressed the docs into a single txt file that is
|
||||
more LLM-readable than a full website, which you can easily upload to your tool of choice.
|
||||
To help the LLMs, we provide the Jazz documentation in a txt file that is optimized for use with AI tools, like Cursor.
|
||||
|
||||
## Adding docs to ChatGPT and v0
|
||||
<FileDownloadLink href="/llms-full.txt">llms-full.txt</FileDownloadLink>
|
||||
|
||||
<a href="/llms-full.txt" download>Download llms-full.txt</a>
|
||||
## Setting up AI tools
|
||||
|
||||
Every tool is different, but generally, you'll need to either paste the contents of the [llms-full.txt](https://jazz.tools/llms-full.txt) file directly in your prompt, or attach the file to the tool.
|
||||
|
||||
### ChatGPT and v0
|
||||
|
||||
Upload the txt file in your prompt.
|
||||
|
||||

|
||||
|
||||
## Adding docs to Cursor
|
||||
### Cursor
|
||||
|
||||
1. Go to Settings > Cursor Settings > Features > Docs
|
||||
2. Click "Add new doc"
|
||||
@@ -30,3 +32,12 @@ https://jazz.tools/llms-full.txt
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## llms.txt convention
|
||||
|
||||
We follow the llms.txt [proposed standard](https://llmstxt.org/) for providing documentation to AI tools at inference time that helps them understand the context of the code you're writing.
|
||||
|
||||
## Limitations and considerations
|
||||
|
||||
AI is amazing, but it's not perfect. What works well this week could break next week (or be twice as good).
|
||||
|
||||
We're keen to keep up with changes in tooling to help support you building the best apps, but if you need help from humans (or you have issues getting set up), please let us know on [Discord](https://discord.gg/utDMjHYg42).
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { CodeGroup } from '@/components/forMdx'
|
||||
|
||||
# Jazz Inspector
|
||||
|
||||
[Jazz Inspector](https://inspector.jazz.tools) is a tool to visually inspect a Jazz account or other CoValues.
|
||||
|
||||
For now, you can get your account credentials from the `jazz-logged-in-secret` local storage key from within your Jazz app.
|
||||
|
||||
[https://inspector.jazz.tools](https://inspector.jazz.tools)
|
||||
|
||||
## Exporting current account to Inspector from your app
|
||||
|
||||
In development mode, you can launch the Inspector from your Jazz app to inspect your account by pressing `Cmd+J`.
|
||||
|
||||
## Embedding the Inspector widget into your app
|
||||
|
||||
Alternatively, you can embed the Inspector directly into your app, so you don't need to open a separate window.
|
||||
|
||||
Install the package.
|
||||
|
||||
<CodeGroup>
|
||||
```sh
|
||||
npm install jazz-inspector
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
Render the component within your `JazzProvider`.
|
||||
|
||||
<CodeGroup>
|
||||
```sh
|
||||
import { JazzInspector } from "jazz-inspector";
|
||||
|
||||
<JazzProvider> // old
|
||||
<JazzInspector />
|
||||
</JazzProvider> // old
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
Check out the [music player app](https://github.com/garden-co/jazz/blob/main/examples/music-player/src/2_main.tsx) for a full example.
|
||||
@@ -4,31 +4,24 @@ export const metadata = { title: "Jazz 0.10.0 is out!" };
|
||||
|
||||
# Jazz 0.10.0 is out!
|
||||
|
||||
<h2 className="not-prose text-sm text-stone-600 dark:text-stone-400 mb-5 pb-2 border-b">
|
||||
11 February 2025
|
||||
</h2>
|
||||
For Jazz 0.10.0 we have been focusing on enhancing authentication to make it optional, more flexible and easier to use.
|
||||
|
||||
<div>
|
||||
For Jazz 0.10.0 we have been focusing on enhancing authentication to make it optional, more flexible and easier to use.
|
||||
The default is now anonymous auth, which means that you can build the functionality of your app first and figure out auth later. For users this means that they can start using your app right away on one device -- and once you integrate an auth method, users can sign up and their anonymous accounts are transparently upgraded to authenticated accounts that work across devices.
|
||||
|
||||
The default is now anonymous auth, which means that you can build the functionality of your app first and figure out auth later. For users this means that they can start using your app right away on one device -- and once you integrate an auth method, users can sign up and their anonymous accounts are transparently upgraded to authenticated accounts that work across devices.
|
||||
|
||||
There are also some other minor improvements that will make your Jazz experience even better!
|
||||
There are also some other minor improvements that will make your Jazz experience even better!
|
||||
|
||||
<h3>What's new?</h3>
|
||||
Here is what's changed in this release:
|
||||
- [New authentication flow](#new-authentication-flow): Now with anonymous auth, redesigned to make Jazz easier to start with and be more flexible.
|
||||
- [Local-only mode](#local-only-mode): Users can now explore your app in local-only mode before signing up.
|
||||
- [Improvements on the loading APIs](#improved-loading-api); `ensureLoaded` now always returns a value and `useCoState` now returns `null` if the value is not found.
|
||||
- [Jazz Workers on native WebSockets](#native-websocket-for-jazz-workers): Improves compatibility with a wider set of Javascript runtimes.
|
||||
- [Group inheritance with role mapping](#group-inheritance): Groups can now inherit members from other groups with a fixed role.
|
||||
- Support for Node 14 dropped on cojson.
|
||||
- Bugfix: `Group.removeMember` now returns a promise.
|
||||
- Now `cojson` and `jazz-tools` don't export directly the crypto providers anymore. Replace the import with `cojson/crypto/WasmCrypto` or `cojson/crypto/PureJSCrypto` depending on your use case.
|
||||
</div>
|
||||
## What's new?
|
||||
Here is what's changed in this release:
|
||||
- [New authentication flow](#new-authentication-flow): Now with anonymous auth, redesigned to make Jazz easier to start with and be more flexible.
|
||||
- [Local-only mode](#local-only-mode): Users can now explore your app in local-only mode before signing up.
|
||||
- [Improvements on the loading APIs](#improved-loading-api); `ensureLoaded` now always returns a value and `useCoState` now returns `null` if the value is not found.
|
||||
- [Jazz Workers on native WebSockets](#native-websocket-for-jazz-workers): Improves compatibility with a wider set of Javascript runtimes.
|
||||
- [Group inheritance with role mapping](#group-inheritance): Groups can now inherit members from other groups with a fixed role.
|
||||
- Support for Node 14 dropped on cojson.
|
||||
- Bugfix: `Group.removeMember` now returns a promise.
|
||||
- Now `cojson` and `jazz-tools` don't export directly the crypto providers anymore. Replace the import with `cojson/crypto/WasmCrypto` or `cojson/crypto/PureJSCrypto` depending on your use case.
|
||||
|
||||
<h3 id="new-authentication-flow">New authentication flow</h3>
|
||||
<div>
|
||||
## New authentication flow
|
||||
Up until now authentication has been the first part to figure out when building a Jazz app, and this was a stumbling block for many.
|
||||
|
||||
Now it is no longer required and setting up a Jazz app is as easy as writing this:
|
||||
@@ -191,8 +184,8 @@ export function AuthButton() {
|
||||
|
||||
if (isAuthenticated) {
|
||||
return (
|
||||
<Button
|
||||
variant="outline"
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={logOut}
|
||||
>
|
||||
Sign out
|
||||
@@ -224,8 +217,8 @@ export function AuthButton() {
|
||||
|
||||
if (isAuthenticated) {
|
||||
return (
|
||||
<Button
|
||||
variant="outline"
|
||||
<Button
|
||||
variant="outline"
|
||||
onPress={logOut}
|
||||
>
|
||||
Sign out
|
||||
@@ -267,16 +260,16 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
For the changes related to the specific auth providers see the updated [authentication docs](/docs/authentication/overview).
|
||||
</div>
|
||||
|
||||
<h3 id="local-only-mode">Local-only mode</h3>
|
||||
<div>
|
||||
For the changes related to the specific auth providers see the updated [authentication docs](/docs/authentication/overview).
|
||||
|
||||
## Local-only mode
|
||||
|
||||
If you are ok with data not being persisted on the sync server for anonymous users, you can now set your app to local-only depending on the user's authentication state.
|
||||
|
||||
With `sync.when` set to `"signedUp"` the app will work in local-only mode when the user is anonymous and unlock the multiplayer/multi-device features and cloud persistence when they sign up:
|
||||
<CodeGroup>
|
||||
```ts
|
||||
```tsx
|
||||
<JazzProvider
|
||||
sync={{
|
||||
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
|
||||
@@ -290,10 +283,9 @@ With `sync.when` set to `"signedUp"` the app will work in local-only mode when t
|
||||
</CodeGroup>
|
||||
|
||||
You can control when Jazz will sync by switching the `when` config to `"always"` or `"never"`.
|
||||
</div>
|
||||
|
||||
<h3 id="improved-loading-api">Improvements on the loading APIs</h3>
|
||||
<div>
|
||||
## Improvements on the loading APIs
|
||||
|
||||
Before 0.10.0 `ensureLoaded` was returning a nullable value forcing the Typescript code to always include null checks:
|
||||
<CodeGroup>
|
||||
```ts
|
||||
@@ -336,17 +328,17 @@ if (value === null) {
|
||||
return <div>Track not found</div>;
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</div>
|
||||
</CodeGroup>
|
||||
|
||||
<h3 id="native-websocket-for-jazz-workers">Jazz Workers on native WebSockets</h3>
|
||||
<div>
|
||||
We have removed the dependency on `ws` and switched to the native WebSocket API for Jazz Workers.
|
||||
## Jazz Workers on native WebSockets
|
||||
|
||||
This improves the compatibility with a wider set of Javascript runtimes adding drop-in support for Deno, Bun, Browsers and Cloudflare Durable Objects.
|
||||
We have removed the dependency on `ws` and switched to the native WebSocket API for Jazz Workers.
|
||||
|
||||
If you are using a Node.js version lower than 22 you will need to install the `ws` package and provide the WebSocket constructor:
|
||||
<CodeGroup>
|
||||
This improves the compatibility with a wider set of Javascript runtimes adding drop-in support for Deno, Bun, Browsers and Cloudflare Durable Objects.
|
||||
|
||||
If you are using a Node.js version lower than 22 you will need to install the `ws` package and provide the WebSocket constructor:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
import { WebSocket } from "ws";
|
||||
import { startWorker } from "jazz-nodejs";
|
||||
@@ -355,24 +347,21 @@ const { worker } = await startWorker({
|
||||
WebSocket,
|
||||
});
|
||||
```
|
||||
</CodeGroup>
|
||||
</div>
|
||||
</CodeGroup>
|
||||
|
||||
<h3 id="group-inheritance">Group inheritance with role mapping</h3>
|
||||
<div>
|
||||
You can override the inherited role by passing a second argument to `extend`.
|
||||
## Group inheritance with role mapping
|
||||
You can override the inherited role by passing a second argument to `extend`.
|
||||
|
||||
This can be used to give users limited access to a child group:
|
||||
<CodeGroup>
|
||||
```ts
|
||||
const organization = Group.create();
|
||||
const billing = Group.create();
|
||||
This can be used to give users limited access to a child group:
|
||||
<CodeGroup>
|
||||
```ts
|
||||
const organization = Group.create();
|
||||
const billing = Group.create();
|
||||
|
||||
billing.extend(organization, "reader");
|
||||
```
|
||||
</CodeGroup>
|
||||
billing.extend(organization, "reader");
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
This way the members of the organization can only read the billing data, even if they are admins in the organization group.
|
||||
This way the members of the organization can only read the billing data, even if they are admins in the organization group.
|
||||
|
||||
More about the group inheritance can be found in the [dedicated docs page](/docs/groups/inheritance).
|
||||
</div>
|
||||
More about the group inheritance can be found in the [dedicated docs page](/docs/groups/inheritance).
|
||||
|
||||
@@ -1,545 +0,0 @@
|
||||
import { ContentByFramework, CodeGroup } from '@/components/forMdx'
|
||||
|
||||
export const metadata = { title: "Upgrade to Jazz 0.9.0" };
|
||||
|
||||
# Upgrade to Jazz 0.9.0
|
||||
|
||||
<h2 className="not-prose text-sm text-stone-600 dark:text-stone-400 mb-5 pb-2 border-b">
|
||||
08 January 2025
|
||||
</h2>
|
||||
|
||||
<ContentByFramework framework="react">
|
||||
<div>
|
||||
Version 0.9.0 simplifies the application setup and makes Jazz more intellisense friendly by
|
||||
replacing the `createJazzReactApp` API with top-level imports.
|
||||
|
||||
We have also introduced some new API to make testing Jazz components a breeze. 🌬️
|
||||
</div>
|
||||
|
||||
<h3>New provider setup</h3>
|
||||
|
||||
<div>
|
||||
The `JazzProvider` is now imported from `jazz-react` instead of `createJazzReactApp`.
|
||||
|
||||
While `createJazzReactApp` was originally designed to setup strong typing for custom Account schemas in `useAccount`,
|
||||
we found that this approach made the Jazz setup awkward and confusing for some users.
|
||||
|
||||
So we decided to remove `createJazzReactApp` step and to provide the types through namespace declarations:
|
||||
</div>
|
||||
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
import { JazzProvider, usePasskeyAuth, PasskeyAuthBasicUI } from "jazz-react";
|
||||
import { MyAppAccount } from "./schema";
|
||||
|
||||
// Remove these lines // *bin*
|
||||
const Jazz = createJazzReactApp({ AccountSchema: MyAppAccount }); // *bin*
|
||||
export const { useAccount, useCoState } = Jazz; // *bin*
|
||||
|
||||
export function JazzAndAuth({ children }: { children: React.ReactNode }) { // old
|
||||
const [passkeyAuth, passKeyState] = usePasskeyAuth({ appName }); // old
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Replace Jazz.Provider with provider from jazz-react */}
|
||||
<JazzProvider
|
||||
auth={passkeyAuth} // old
|
||||
peer="wss://cloud.jazz.tools/?key=you@example.com" // old
|
||||
AccountSchema={MyAppAccount} {/* The custom Account schema is passed here */} // *add*
|
||||
>
|
||||
{children} // old
|
||||
</JazzProvider>
|
||||
<PasskeyAuthBasicUI state={passKeyState} /> // old
|
||||
</> // old
|
||||
);
|
||||
}
|
||||
|
||||
// Register the Account schema so `useAccount` returns our custom `MyAppAccount`
|
||||
declare module "jazz-react" { // *add*
|
||||
interface Register { // *add*
|
||||
Account: MyAppAccount; // *add*
|
||||
} // *add*
|
||||
} // *add*
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
<h3>Top level imports for hooks</h3>
|
||||
|
||||
<div>
|
||||
All Jazz hooks are now available as top-level imports from the `jazz-react` package.
|
||||
|
||||
This change improves IDE intellisense support and simplifies imports:
|
||||
</div>
|
||||
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
// Replace local imports with "jazz-react" imports
|
||||
import { useAccount } from "./main"; // *bin*
|
||||
import { useAccount } from "jazz-react"; // *add*
|
||||
|
||||
export function Hello() {
|
||||
const { me } = useAccount();
|
||||
|
||||
return (
|
||||
<>
|
||||
Hello {me.profile?.name}
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
<h3>New testing utilities</h3>
|
||||
|
||||
<div>
|
||||
Removing `createJazzReactApp` also makes testing way easier!
|
||||
|
||||
We can now use `createJazzTestAccount` to setup accounts and testing data and pass it to
|
||||
your components and hooks using `JazzTestProvider`:
|
||||
</div>
|
||||
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
import { createJazzTestAccount, JazzTestProvider } from "jazz-react/testing";
|
||||
import { renderHook } from "@testing-library/react"; // old
|
||||
import { usePlaylist } from "./usePlaylist"; // old
|
||||
import { Playlist, MusicAccount } from "./schema"; // old
|
||||
|
||||
test("should load the playlist", async () => {
|
||||
// ✅ Create a test account with your schema
|
||||
const account = await createJazzTestAccount({ AccountSchema: MusicAccount });
|
||||
|
||||
// ✅ Set up test data
|
||||
const playlist = Playlist.create({
|
||||
name: "My playlist",
|
||||
}, account);
|
||||
|
||||
// ✅ Use JazzTestProvider in your tests
|
||||
const { result } = renderHook(() => usePlaylist(playlist.id), {
|
||||
wrapper: ({ children }) => (
|
||||
<JazzTestProvider account={account}>
|
||||
{children}
|
||||
</JazzTestProvider>
|
||||
),
|
||||
});
|
||||
|
||||
// The result is resolved synchronously, so you can assert the value immediately
|
||||
expect(result.current?.name).toBe("My playlist");
|
||||
});
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
<ContentByFramework framework="react-native">
|
||||
<div>
|
||||
Version 0.9.0 simplifies the application setup and makes Jazz more intellisense friendly by
|
||||
replacing the `createJazzRNApp` API with top-level imports.
|
||||
|
||||
We have also introduced some new API to make testing Jazz components a breeze. 🌬️
|
||||
</div>
|
||||
|
||||
<h3>New provider setup</h3>
|
||||
|
||||
<div>
|
||||
The `JazzProvider` is now imported from `jazz-react-native` instead of `createJazzRNApp`.
|
||||
|
||||
While `createJazzRNApp` was originally designed to setup strong typing for custom Account schemas in `useAccount`,
|
||||
we found that this approach made the Jazz setup awkward and confusing for some users.
|
||||
|
||||
So we decided to remove `createJazzRNApp` step and to provide the types through namespace declarations:
|
||||
</div>
|
||||
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
import { JazzProvider, useDemoAuth, DemoAuthBasicUI } from "jazz-react-native";
|
||||
import { MyAppAccount } from "./schema";
|
||||
|
||||
// Remove these lines // *bin*
|
||||
const Jazz = createJazzRNApp({ AccountSchema: MyAppAccount }); // *bin*
|
||||
export const { useAccount, useCoState } = Jazz; // *bin*
|
||||
|
||||
export function JazzAndAuth({ children }: { children: React.ReactNode }) { // old
|
||||
const [auth, state] = useDemoAuth(); // old
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Replace Jazz.Provider with provider from jazz-react */}
|
||||
<JazzProvider
|
||||
auth={auth} // old
|
||||
peer="wss://cloud.jazz.tools/?key=you@example.com" // old
|
||||
AccountSchema={MyAppAccount} {/* The custom Account schema is passed here */}
|
||||
>
|
||||
{children} // old
|
||||
</JazzProvider>
|
||||
<DemoAuthBasicUI appName="My App" state={state} /> // old
|
||||
</> // old
|
||||
);
|
||||
}
|
||||
|
||||
// Register the Account schema so `useAccount` returns our custom `MyAppAccount`
|
||||
declare module "jazz-react-native" {
|
||||
interface Register {
|
||||
Account: MyAppAccount;
|
||||
}
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
<h3>Top level imports for hooks</h3>
|
||||
|
||||
<div>
|
||||
All Jazz hooks are now available as top-level imports from the `jazz-react-native` package.
|
||||
|
||||
This change improves IDE intellisense support and simplifies imports:
|
||||
</div>
|
||||
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
// Replace local imports with "jazz-react-native" imports
|
||||
import { useAccount } from "./main"; // *bin*
|
||||
import { useAccount } from "jazz-react-native"; // *add*
|
||||
|
||||
export function Hello() {
|
||||
const { me } = useAccount();
|
||||
|
||||
return (
|
||||
<>
|
||||
Hello {me.profile?.name}
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
<h3>New testing utilities</h3>
|
||||
|
||||
<div>
|
||||
Removing `createJazzRNApp` also makes testing way easier!
|
||||
|
||||
We can now use `createJazzTestAccount` to setup accounts and testing data and pass it to
|
||||
your components and hooks using `JazzTestProvider`:
|
||||
</div>
|
||||
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
import { createJazzTestAccount, JazzTestProvider } from "jazz-react-native/testing";
|
||||
import { renderHook } from "@testing-library/react-native"; // old
|
||||
import { usePlaylist } from "./usePlaylist"; // old
|
||||
import { Playlist, MusicAccount } from "./schema"; // old
|
||||
|
||||
test("should load the playlist", async () => {
|
||||
// ✅ Create a test account with your schema
|
||||
const account = await createJazzTestAccount({ AccountSchema: MusicAccount });
|
||||
|
||||
// ✅ Set up test data
|
||||
const playlist = Playlist.create({
|
||||
name: "My playlist",
|
||||
}, account);
|
||||
|
||||
// ✅ Use JazzTestProvider in your tests
|
||||
const { result } = renderHook(() => usePlaylist(playlist.id), {
|
||||
wrapper: ({ children }) => (
|
||||
<JazzTestProvider account={account}>
|
||||
{children}
|
||||
</JazzTestProvider>
|
||||
),
|
||||
});
|
||||
|
||||
// The result is resolved synchronously, so you can assert the value immediately
|
||||
expect(result.current?.name).toBe("My playlist");
|
||||
});
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
<ContentByFramework framework="svelte">
|
||||
<div>
|
||||
Version 0.9.0 simplifies the application setup and makes Jazz more intellisense friendly by
|
||||
replacing the `createJazzApp` API with top-level imports.
|
||||
|
||||
We have also introduced some new API to make testing Jazz components a breeze. 🌬️
|
||||
</div>
|
||||
|
||||
<h3>New provider setup</h3>
|
||||
|
||||
<div>
|
||||
The `JazzProvider` is now imported from `jazz-svelte` instead of `createJazzApp`.
|
||||
|
||||
While `createJazzApp` was originally designed to setup strong typing for custom Account schemas in `useAccount`,
|
||||
we found that this approach made the Jazz setup awkward and confusing for some users.
|
||||
|
||||
So we decided to remove `createJazzApp` step and to provide the types through namespace declarations:
|
||||
</div>
|
||||
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```svelte
|
||||
<!-- src/routes/+layout.svelte -->
|
||||
<script lang="ts" module>
|
||||
// Register the Account schema so `useAccount` returns our custom `MyAppAccount`
|
||||
declare module 'jazz-svelte' {
|
||||
interface Register {
|
||||
Account: MyAccount;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { Provider } from '$lib/jazz'; // *bin*
|
||||
import { JazzProvider } from 'jazz-svelte';
|
||||
|
||||
// Example configuration for authentication and peer connection
|
||||
let auth = null; // Replace with your auth implementation
|
||||
let peer = "wss://your-peer-endpoint";
|
||||
|
||||
// The custom Account schema is passed now as a prop
|
||||
let AccountSchema = MyAccount;
|
||||
</script>
|
||||
|
||||
<JazzProvider {auth} {peer} {AccountSchema}>
|
||||
<App />
|
||||
</JazzProvider>
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
<h3>Top level imports for hooks</h3>
|
||||
|
||||
<div>
|
||||
All Jazz hooks are now available as top-level imports from the `jazz-svelte` package.
|
||||
|
||||
This change improves IDE intellisense support and simplifies imports:
|
||||
</div>
|
||||
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```svelte
|
||||
|
||||
<script lang="ts">
|
||||
import { useAccount } from '$lib/jazz'; // *bin*
|
||||
import { useAccount } from 'jazz-svelte'; // *add*
|
||||
|
||||
const { me } = useAccount();
|
||||
</script>
|
||||
|
||||
<div>
|
||||
Hello {me.profile?.name}
|
||||
</div>
|
||||
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
<h3>New testing utilities</h3>
|
||||
|
||||
<div>
|
||||
Removing `createJazzApp` also makes testing way easier!
|
||||
|
||||
We can now use `createJazzTestAccount` to setup accounts and testing data and pass it to
|
||||
your components and hooks using `JazzTestProvider`:
|
||||
</div>
|
||||
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```ts
|
||||
import { useCoState } from "jazz-svelte";
|
||||
import { createJazzTestAccount, JazzTestProvider } from "jazz-svelte/testing";
|
||||
import { render } from "@testing-library/svelte"; // old
|
||||
import { Playlist, MusicAccount } from "./schema"; // old
|
||||
|
||||
test("should load the playlist", async () => {
|
||||
// ✅ Create a test account with your schema
|
||||
const account = await createJazzTestAccount({ AccountSchema: MusicAccount });
|
||||
|
||||
// ✅ Set up test data
|
||||
const playlist = Playlist.create({
|
||||
name: "My playlist",
|
||||
}, account);
|
||||
|
||||
// ✅ Use createJazzTestContext in your tests
|
||||
render(PlaylistComponent, {
|
||||
context: createJazzTestContext({ account: options.account }),
|
||||
props: {
|
||||
id: playlist.id,
|
||||
},
|
||||
});
|
||||
|
||||
expect(await screen.findByRole("heading", { name: "My playlist" })).toBeInTheDocument();
|
||||
});
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
<ContentByFramework framework="vue">
|
||||
<div>
|
||||
Version 0.9.0 simplifies the application setup and makes Jazz more intellisense friendly by
|
||||
replacing the `createJazzVueApp` API with top-level imports.
|
||||
|
||||
We have also introduced some new API to make testing Jazz components a breeze. 🌬️
|
||||
</div>
|
||||
|
||||
<h3>New provider setup</h3>
|
||||
|
||||
<div>
|
||||
The `JazzProvider` is now imported from `jazz-vue` instead of `createJazzVueApp`.
|
||||
|
||||
While `createJazzReactApp` was originally designed to setup strong typing for custom Account schemas in `useAccount`,
|
||||
we found that this approach made the Jazz setup awkward and confusing for some users.
|
||||
|
||||
So we decided to remove `createJazzReactApp` step and to provide the types through namespace declarations:
|
||||
</div>
|
||||
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```typescript
|
||||
import "./assets/main.css"; // old
|
||||
import { DemoAuthBasicUI, useDemoAuth, JazzProvider } from "jazz-vue";
|
||||
import { createApp, defineComponent, h } from "vue"; // old
|
||||
import App from "./App.vue"; // old
|
||||
import router from "./router"; // old
|
||||
import { ToDoAccount } from "./schema"; // old
|
||||
|
||||
// Remove these lines // *bin*
|
||||
const Jazz = createJazzVueApp<ToDoAccount>({ AccountSchema: ToDoAccount }); // *bin*
|
||||
export const { useAccount, useCoState } = Jazz; // *bin*
|
||||
const { JazzProvider } = Jazz; // *bin*
|
||||
|
||||
const RootComponent = defineComponent({ // old
|
||||
name: "RootComponent", // old
|
||||
setup() { // old
|
||||
const { authMethod, state } = useDemoAuth(); // old
|
||||
return () => [ // old
|
||||
h( // old
|
||||
JazzProvider, // old
|
||||
{ // old
|
||||
AccountSchema: ToDoAccount, // The custom Account schema is passed here now
|
||||
auth: authMethod.value, // old
|
||||
peer: "wss://cloud.jazz.tools/?key=vue-todo-example-jazz@garden.co", // old
|
||||
}, // old
|
||||
{ // old
|
||||
default: () => h(App), // old
|
||||
}, // old
|
||||
), // old
|
||||
state.state !== "signedIn" && // old
|
||||
h(DemoAuthBasicUI, { // old
|
||||
appName: "Jazz Vue Todo", // old
|
||||
state, // old
|
||||
}), // old
|
||||
]; // old
|
||||
}, // old
|
||||
}); // old
|
||||
|
||||
// Register the Account schema so `useAccount` returns our custom `MyAppAccount`
|
||||
declare module "jazz-vue" {
|
||||
interface Register {
|
||||
Account: ToDoAccount;
|
||||
}
|
||||
}
|
||||
|
||||
const app = createApp(RootComponent); // old
|
||||
app.use(router); // old
|
||||
app.mount("#app"); // old
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
<h3>Top level imports for hooks</h3>
|
||||
|
||||
<div>
|
||||
All Jazz hooks are now available as top-level imports from the `jazz-vue` package.
|
||||
|
||||
This change improves IDE intellisense support and simplifies imports:
|
||||
</div>
|
||||
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```typescript
|
||||
<template>
|
||||
Hello {{ me.profile?.name }}
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// Replace local imports with "jazz-vue" imports
|
||||
import { useAccount } from "./main"; // *bin*
|
||||
import { useAccount } from "jazz-vue"; // *add*
|
||||
|
||||
const { me, logOut } = useAccount();
|
||||
</script>
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
<h3>New testing utilities</h3>
|
||||
|
||||
<div>
|
||||
Removing `createJazzTestApp` also makes testing way easier!
|
||||
|
||||
We can now use `createJazzTestAccount` to setup accounts and testing data and pass it to
|
||||
your components and hooks using `JazzTestProvider`:
|
||||
</div>
|
||||
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
import { createJazzTestAccount, JazzTestProvider } from "jazz-vue/testing";
|
||||
import { createApp, defineComponent, h } from "vue";
|
||||
import { usePlaylist } from "./usePlaylist";
|
||||
import { Playlist, MusicAccount } from "./schema"; // old
|
||||
|
||||
// This can be reused on other tests!
|
||||
export const renderComposableWithJazz = <C extends (...args: any[]) => any>(
|
||||
composable: C,
|
||||
{ account }: { account: Account | { guest: AnonymousJazzAgent } },
|
||||
) => {
|
||||
let result;
|
||||
|
||||
const wrapper = defineComponent({
|
||||
setup() {
|
||||
result = composable();
|
||||
// suppress missing template warning
|
||||
return () => {};
|
||||
},
|
||||
});
|
||||
|
||||
// ✅ Use JazzTestProvider in your tests
|
||||
const app = createApp({
|
||||
setup() {
|
||||
return () =>
|
||||
h(
|
||||
JazzTestProvider,
|
||||
{
|
||||
account,
|
||||
},
|
||||
{
|
||||
default: () => h(wrapper),
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
app.mount(document.createElement("div"));
|
||||
return [result, app] as [ReturnType<C>, ReturnType<typeof createApp>];
|
||||
};
|
||||
|
||||
test("should load the playlist", async () => {
|
||||
// ✅ Create a test account with your schema
|
||||
const account = await createJazzTestAccount({ AccountSchema: MusicAccount });
|
||||
|
||||
// ✅ Set up test data
|
||||
const playlist = Playlist.create({
|
||||
name: "My playlist",
|
||||
}, account);
|
||||
|
||||
// ✅ Set up test data
|
||||
const { result } = renderComposableWithJazz(() => usePlaylist(playlist.id), {
|
||||
account,
|
||||
});
|
||||
|
||||
// The result is resolved synchronously, so you can assert the value immediately
|
||||
expect(result?.name).toBe("My playlist");
|
||||
});
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
@@ -0,0 +1,123 @@
|
||||
import { ContentByFramework, CodeGroup } from '@/components/forMdx'
|
||||
|
||||
export const metadata = { title: "Upgrade to Jazz 0.9.0" };
|
||||
|
||||
# Upgrade to Jazz 0.9.0
|
||||
|
||||
|
||||
Version 0.9.0 simplifies the application setup and makes Jazz more intellisense friendly by
|
||||
replacing the `createJazzRNApp` API with top-level imports.
|
||||
|
||||
We have also introduced some new API to make testing Jazz components a breeze. 🌬️
|
||||
|
||||
## New provider setup
|
||||
|
||||
The `JazzProvider` is now imported from `jazz-react-native` instead of `createJazzRNApp`.
|
||||
|
||||
While `createJazzRNApp` was originally designed to setup strong typing for custom Account schemas in `useAccount`,
|
||||
we found that this approach made the Jazz setup awkward and confusing for some users.
|
||||
|
||||
So we decided to remove `createJazzRNApp` step and to provide the types through namespace declarations:
|
||||
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
import { JazzProvider, useDemoAuth, DemoAuthBasicUI } from "jazz-react-native";
|
||||
import { MyAppAccount } from "./schema";
|
||||
|
||||
// Remove these lines // *bin*
|
||||
const Jazz = createJazzRNApp({ AccountSchema: MyAppAccount }); // *bin*
|
||||
export const { useAccount, useCoState } = Jazz; // *bin*
|
||||
|
||||
export function JazzAndAuth({ children }: { children: React.ReactNode }) { // old
|
||||
const [auth, state] = useDemoAuth(); // old
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Replace Jazz.Provider with provider from jazz-react */}
|
||||
<JazzProvider
|
||||
auth={auth} // old
|
||||
peer="wss://cloud.jazz.tools/?key=you@example.com" // old
|
||||
AccountSchema={MyAppAccount} {/* The custom Account schema is passed here */}
|
||||
>
|
||||
{children} // old
|
||||
</JazzProvider>
|
||||
<DemoAuthBasicUI appName="My App" state={state} /> // old
|
||||
</> // old
|
||||
);
|
||||
}
|
||||
|
||||
// Register the Account schema so `useAccount` returns our custom `MyAppAccount`
|
||||
declare module "jazz-react-native" {
|
||||
interface Register {
|
||||
Account: MyAppAccount;
|
||||
}
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Top level imports for hooks
|
||||
|
||||
All Jazz hooks are now available as top-level imports from the `jazz-react-native` package.
|
||||
|
||||
This change improves IDE intellisense support and simplifies imports:
|
||||
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
// Replace local imports with "jazz-react-native" imports
|
||||
import { useAccount } from "./main"; // *bin*
|
||||
import { useAccount } from "jazz-react-native"; // *add*
|
||||
|
||||
export function Hello() {
|
||||
const { me } = useAccount();
|
||||
|
||||
return (
|
||||
<>
|
||||
Hello {me.profile?.name}
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## New testing utilities
|
||||
|
||||
<div>
|
||||
Removing `createJazzRNApp` also makes testing way easier!
|
||||
|
||||
We can now use `createJazzTestAccount` to setup accounts and testing data and pass it to
|
||||
your components and hooks using `JazzTestProvider`:
|
||||
</div>
|
||||
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
import { createJazzTestAccount, JazzTestProvider } from "jazz-react-native/testing";
|
||||
import { renderHook } from "@testing-library/react-native"; // old
|
||||
import { usePlaylist } from "./usePlaylist"; // old
|
||||
import { Playlist, MusicAccount } from "./schema"; // old
|
||||
|
||||
test("should load the playlist", async () => {
|
||||
// ✅ Create a test account with your schema
|
||||
const account = await createJazzTestAccount({ AccountSchema: MusicAccount });
|
||||
|
||||
// ✅ Set up test data
|
||||
const playlist = Playlist.create({
|
||||
name: "My playlist",
|
||||
}, account);
|
||||
|
||||
// ✅ Use JazzTestProvider in your tests
|
||||
const { result } = renderHook(() => usePlaylist(playlist.id), {
|
||||
wrapper: ({ children }) => (
|
||||
<JazzTestProvider account={account}>
|
||||
{children}
|
||||
</JazzTestProvider>
|
||||
),
|
||||
});
|
||||
|
||||
// The result is resolved synchronously, so you can assert the value immediately
|
||||
expect(result.current?.name).toBe("My playlist");
|
||||
});
|
||||
```
|
||||
</CodeGroup>
|
||||
@@ -0,0 +1,120 @@
|
||||
import { ContentByFramework, CodeGroup } from '@/components/forMdx'
|
||||
|
||||
export const metadata = { title: "Upgrade to Jazz 0.9.0" };
|
||||
|
||||
# Upgrade to Jazz 0.9.0
|
||||
|
||||
Version 0.9.0 simplifies the application setup and makes Jazz more intellisense friendly by
|
||||
replacing the `createJazzReactApp` API with top-level imports.
|
||||
|
||||
We have also introduced some new API to make testing Jazz components a breeze. 🌬️
|
||||
|
||||
## New provider setup
|
||||
|
||||
The `JazzProvider` is now imported from `jazz-react` instead of `createJazzReactApp`.
|
||||
|
||||
While `createJazzReactApp` was originally designed to setup strong typing for custom Account schemas in `useAccount`,
|
||||
we found that this approach made the Jazz setup awkward and confusing for some users.
|
||||
|
||||
So we decided to remove `createJazzReactApp` step and to provide the types through namespace declarations:
|
||||
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
import { JazzProvider, usePasskeyAuth, PasskeyAuthBasicUI } from "jazz-react";
|
||||
import { MyAppAccount } from "./schema";
|
||||
|
||||
// Remove these lines // *bin*
|
||||
const Jazz = createJazzReactApp({ AccountSchema: MyAppAccount }); // *bin*
|
||||
export const { useAccount, useCoState } = Jazz; // *bin*
|
||||
|
||||
export function JazzAndAuth({ children }: { children: React.ReactNode }) { // old
|
||||
const [passkeyAuth, passKeyState] = usePasskeyAuth({ appName }); // old
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Replace Jazz.Provider with provider from jazz-react */}
|
||||
<JazzProvider
|
||||
auth={passkeyAuth} // old
|
||||
peer="wss://cloud.jazz.tools/?key=you@example.com" // old
|
||||
AccountSchema={MyAppAccount} {/* The custom Account schema is passed here */} // *add*
|
||||
>
|
||||
{children} // old
|
||||
</JazzProvider>
|
||||
<PasskeyAuthBasicUI state={passKeyState} /> // old
|
||||
</> // old
|
||||
);
|
||||
}
|
||||
|
||||
// Register the Account schema so `useAccount` returns our custom `MyAppAccount`
|
||||
declare module "jazz-react" { // *add*
|
||||
interface Register { // *add*
|
||||
Account: MyAppAccount; // *add*
|
||||
} // *add*
|
||||
} // *add*
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Top level imports for hooks
|
||||
|
||||
All Jazz hooks are now available as top-level imports from the `jazz-react` package.
|
||||
|
||||
This change improves IDE intellisense support and simplifies imports:
|
||||
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
// Replace local imports with "jazz-react" imports
|
||||
import { useAccount } from "./main"; // *bin*
|
||||
import { useAccount } from "jazz-react"; // *add*
|
||||
|
||||
export function Hello() {
|
||||
const { me } = useAccount();
|
||||
|
||||
return (
|
||||
<>
|
||||
Hello {me.profile?.name}
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## New testing utilities
|
||||
|
||||
Removing `createJazzReactApp` also makes testing way easier!
|
||||
|
||||
We can now use `createJazzTestAccount` to setup accounts and testing data and pass it to
|
||||
your components and hooks using `JazzTestProvider`:
|
||||
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
import { createJazzTestAccount, JazzTestProvider } from "jazz-react/testing";
|
||||
import { renderHook } from "@testing-library/react"; // old
|
||||
import { usePlaylist } from "./usePlaylist"; // old
|
||||
import { Playlist, MusicAccount } from "./schema"; // old
|
||||
|
||||
test("should load the playlist", async () => {
|
||||
// ✅ Create a test account with your schema
|
||||
const account = await createJazzTestAccount({ AccountSchema: MusicAccount });
|
||||
|
||||
// ✅ Set up test data
|
||||
const playlist = Playlist.create({
|
||||
name: "My playlist",
|
||||
}, account);
|
||||
|
||||
// ✅ Use JazzTestProvider in your tests
|
||||
const { result } = renderHook(() => usePlaylist(playlist.id), {
|
||||
wrapper: ({ children }) => (
|
||||
<JazzTestProvider account={account}>
|
||||
{children}
|
||||
</JazzTestProvider>
|
||||
),
|
||||
});
|
||||
|
||||
// The result is resolved synchronously, so you can assert the value immediately
|
||||
expect(result.current?.name).toBe("My playlist");
|
||||
});
|
||||
```
|
||||
</CodeGroup>
|
||||
@@ -0,0 +1,113 @@
|
||||
import { ContentByFramework, CodeGroup } from '@/components/forMdx'
|
||||
|
||||
export const metadata = { title: "Upgrade to Jazz 0.9.0" };
|
||||
|
||||
# Upgrade to Jazz 0.9.0
|
||||
|
||||
Version 0.9.0 simplifies the application setup and makes Jazz more intellisense friendly by
|
||||
replacing the `createJazzApp` API with top-level imports.
|
||||
|
||||
We have also introduced some new API to make testing Jazz components a breeze. 🌬️
|
||||
|
||||
## New provider setup
|
||||
|
||||
The `JazzProvider` is now imported from `jazz-svelte` instead of `createJazzApp`.
|
||||
|
||||
While `createJazzApp` was originally designed to setup strong typing for custom Account schemas in `useAccount`,
|
||||
we found that this approach made the Jazz setup awkward and confusing for some users.
|
||||
|
||||
So we decided to remove `createJazzApp` step and to provide the types through namespace declarations:
|
||||
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```svelte
|
||||
<!-- src/routes/+layout.svelte -->
|
||||
<script lang="ts" module>
|
||||
// Register the Account schema so `useAccount` returns our custom `MyAppAccount`
|
||||
declare module 'jazz-svelte' {
|
||||
interface Register {
|
||||
Account: MyAccount;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { Provider } from '$lib/jazz'; // *bin*
|
||||
import { JazzProvider } from 'jazz-svelte';
|
||||
|
||||
// Example configuration for authentication and peer connection
|
||||
let auth = null; // Replace with your auth implementation
|
||||
let peer = "wss://your-peer-endpoint";
|
||||
|
||||
// The custom Account schema is passed now as a prop
|
||||
let AccountSchema = MyAccount;
|
||||
</script>
|
||||
|
||||
<JazzProvider {auth} {peer} {AccountSchema}>
|
||||
<App />
|
||||
</JazzProvider>
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Top level imports for hooks
|
||||
|
||||
<div>
|
||||
All Jazz hooks are now available as top-level imports from the `jazz-svelte` package.
|
||||
|
||||
This change improves IDE intellisense support and simplifies imports:
|
||||
</div>
|
||||
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```svelte
|
||||
|
||||
<script lang="ts">
|
||||
import { useAccount } from '$lib/jazz'; // *bin*
|
||||
import { useAccount } from 'jazz-svelte'; // *add*
|
||||
|
||||
const { me } = useAccount();
|
||||
</script>
|
||||
|
||||
<div>
|
||||
Hello {me.profile?.name}
|
||||
</div>
|
||||
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## New testing utilities
|
||||
|
||||
Removing `createJazzApp` also makes testing way easier!
|
||||
|
||||
We can now use `createJazzTestAccount` to setup accounts and testing data and pass it to
|
||||
your components and hooks using `JazzTestProvider`:
|
||||
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```ts
|
||||
import { useCoState } from "jazz-svelte";
|
||||
import { createJazzTestAccount, JazzTestProvider } from "jazz-svelte/testing";
|
||||
import { render } from "@testing-library/svelte"; // old
|
||||
import { Playlist, MusicAccount } from "./schema"; // old
|
||||
|
||||
test("should load the playlist", async () => {
|
||||
// ✅ Create a test account with your schema
|
||||
const account = await createJazzTestAccount({ AccountSchema: MusicAccount });
|
||||
|
||||
// ✅ Set up test data
|
||||
const playlist = Playlist.create({
|
||||
name: "My playlist",
|
||||
}, account);
|
||||
|
||||
// ✅ Use createJazzTestContext in your tests
|
||||
render(PlaylistComponent, {
|
||||
context: createJazzTestContext({ account: options.account }),
|
||||
props: {
|
||||
id: playlist.id,
|
||||
},
|
||||
});
|
||||
|
||||
expect(await screen.findByRole("heading", { name: "My playlist" })).toBeInTheDocument();
|
||||
});
|
||||
```
|
||||
</CodeGroup>
|
||||
@@ -0,0 +1,165 @@
|
||||
import { ContentByFramework, CodeGroup } from '@/components/forMdx'
|
||||
|
||||
export const metadata = { title: "Upgrade to Jazz 0.9.0" };
|
||||
|
||||
# Upgrade to Jazz 0.9.0
|
||||
|
||||
Version 0.9.0 simplifies the application setup and makes Jazz more intellisense friendly by
|
||||
replacing the `createJazzVueApp` API with top-level imports.
|
||||
|
||||
We have also introduced some new API to make testing Jazz components a breeze. 🌬️
|
||||
|
||||
## New provider setup
|
||||
|
||||
The `JazzProvider` is now imported from `jazz-vue` instead of `createJazzVueApp`.
|
||||
|
||||
While `createJazzReactApp` was originally designed to setup strong typing for custom Account schemas in `useAccount`,
|
||||
we found that this approach made the Jazz setup awkward and confusing for some users.
|
||||
|
||||
So we decided to remove `createJazzReactApp` step and to provide the types through namespace declarations:
|
||||
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```typescript
|
||||
import "./assets/main.css"; // old
|
||||
import { DemoAuthBasicUI, useDemoAuth, JazzProvider } from "jazz-vue";
|
||||
import { createApp, defineComponent, h } from "vue"; // old
|
||||
import App from "./App.vue"; // old
|
||||
import router from "./router"; // old
|
||||
import { ToDoAccount } from "./schema"; // old
|
||||
|
||||
// Remove these lines // *bin*
|
||||
const Jazz = createJazzVueApp<ToDoAccount>({ AccountSchema: ToDoAccount }); // *bin*
|
||||
export const { useAccount, useCoState } = Jazz; // *bin*
|
||||
const { JazzProvider } = Jazz; // *bin*
|
||||
|
||||
const RootComponent = defineComponent({ // old
|
||||
name: "RootComponent", // old
|
||||
setup() { // old
|
||||
const { authMethod, state } = useDemoAuth(); // old
|
||||
return () => [ // old
|
||||
h( // old
|
||||
JazzProvider, // old
|
||||
{ // old
|
||||
AccountSchema: ToDoAccount, // The custom Account schema is passed here now
|
||||
auth: authMethod.value, // old
|
||||
peer: "wss://cloud.jazz.tools/?key=vue-todo-example-jazz@garden.co", // old
|
||||
}, // old
|
||||
{ // old
|
||||
default: () => h(App), // old
|
||||
}, // old
|
||||
), // old
|
||||
state.state !== "signedIn" && // old
|
||||
h(DemoAuthBasicUI, { // old
|
||||
appName: "Jazz Vue Todo", // old
|
||||
state, // old
|
||||
}), // old
|
||||
]; // old
|
||||
}, // old
|
||||
}); // old
|
||||
|
||||
// Register the Account schema so `useAccount` returns our custom `MyAppAccount`
|
||||
declare module "jazz-vue" {
|
||||
interface Register {
|
||||
Account: ToDoAccount;
|
||||
}
|
||||
}
|
||||
|
||||
const app = createApp(RootComponent); // old
|
||||
app.use(router); // old
|
||||
app.mount("#app"); // old
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Top level imports for hooks
|
||||
|
||||
All Jazz hooks are now available as top-level imports from the `jazz-vue` package.
|
||||
|
||||
This change improves IDE intellisense support and simplifies imports:
|
||||
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```typescript
|
||||
<template>
|
||||
Hello {{ me.profile?.name }}
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// Replace local imports with "jazz-vue" imports
|
||||
import { useAccount } from "./main"; // *bin*
|
||||
import { useAccount } from "jazz-vue"; // *add*
|
||||
|
||||
const { me, logOut } = useAccount();
|
||||
</script>
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## New testing utilities
|
||||
|
||||
Removing `createJazzTestApp` also makes testing way easier!
|
||||
|
||||
We can now use `createJazzTestAccount` to setup accounts and testing data and pass it to
|
||||
your components and hooks using `JazzTestProvider`:
|
||||
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
import { createJazzTestAccount, JazzTestProvider } from "jazz-vue/testing";
|
||||
import { createApp, defineComponent, h } from "vue";
|
||||
import { usePlaylist } from "./usePlaylist";
|
||||
import { Playlist, MusicAccount } from "./schema"; // old
|
||||
|
||||
// This can be reused on other tests!
|
||||
export const renderComposableWithJazz = <C extends (...args: any[]) => any>(
|
||||
composable: C,
|
||||
{ account }: { account: Account | { guest: AnonymousJazzAgent } },
|
||||
) => {
|
||||
let result;
|
||||
|
||||
const wrapper = defineComponent({
|
||||
setup() {
|
||||
result = composable();
|
||||
// suppress missing template warning
|
||||
return () => {};
|
||||
},
|
||||
});
|
||||
|
||||
// ✅ Use JazzTestProvider in your tests
|
||||
const app = createApp({
|
||||
setup() {
|
||||
return () =>
|
||||
h(
|
||||
JazzTestProvider,
|
||||
{
|
||||
account,
|
||||
},
|
||||
{
|
||||
default: () => h(wrapper),
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
app.mount(document.createElement("div"));
|
||||
return [result, app] as [ReturnType<C>, ReturnType<typeof createApp>];
|
||||
};
|
||||
|
||||
test("should load the playlist", async () => {
|
||||
// ✅ Create a test account with your schema
|
||||
const account = await createJazzTestAccount({ AccountSchema: MusicAccount });
|
||||
|
||||
// ✅ Set up test data
|
||||
const playlist = Playlist.create({
|
||||
name: "My playlist",
|
||||
}, account);
|
||||
|
||||
// ✅ Set up test data
|
||||
const { result } = renderComposableWithJazz(() => usePlaylist(playlist.id), {
|
||||
account,
|
||||
});
|
||||
|
||||
// The result is resolved synchronously, so you can assert the value immediately
|
||||
expect(result?.name).toBe("My playlist");
|
||||
});
|
||||
```
|
||||
</CodeGroup>
|
||||
@@ -4,15 +4,9 @@ export const metadata = { title: "Upgrade to Jazz 0.9.0" };
|
||||
|
||||
# Jazz 0.9.8 - Without me!
|
||||
|
||||
<h2 className="not-prose text-sm text-stone-600 dark:text-stone-400 mb-5 pb-2 border-b">
|
||||
14 January 2025
|
||||
</h2>
|
||||
We have simplified the API to make the "me" value always optional!
|
||||
|
||||
<div>
|
||||
We have simplified the API to make the "me" value always optional!
|
||||
|
||||
This removes the need of using `useAccount` like the 90% of the time!
|
||||
</div>
|
||||
This removes the need of using `useAccount` like the 90% of the time!
|
||||
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
@@ -20,6 +14,7 @@ export const metadata = { title: "Upgrade to Jazz 0.9.0" };
|
||||
import { useState } from "react";
|
||||
import { Issue } from "./schema";
|
||||
import { IssueComponent } from "./components/Issue.tsx";
|
||||
|
||||
function App() {
|
||||
const [issue, setIssue] = useState<Issue>();
|
||||
|
||||
@@ -43,9 +38,7 @@ export const metadata = { title: "Upgrade to Jazz 0.9.0" };
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
<div>
|
||||
This also applies to the load API:
|
||||
</div>
|
||||
This also applies to the load API:
|
||||
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
@@ -54,9 +47,7 @@ export const metadata = { title: "Upgrade to Jazz 0.9.0" };
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
<div>
|
||||
And `Group.create`:
|
||||
</div>
|
||||
And `Group.create`:
|
||||
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
@@ -67,8 +58,6 @@ export const metadata = { title: "Upgrade to Jazz 0.9.0" };
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
<div>
|
||||
Everything is backward compatible, so no upgrade steps are required.
|
||||
|
||||
With this Jazz API becomes way more lean and more is coming!
|
||||
</div>
|
||||
|
||||
@@ -4,46 +4,34 @@ export const metadata = { title: "Enable local persistence" };
|
||||
|
||||
# Enable local persistence
|
||||
|
||||
<h2 className="not-prose text-sm text-stone-600 dark:text-stone-400 mb-5 pb-2 border-b">
|
||||
10 January 2025
|
||||
</h2>
|
||||
Version 0.9.2 introduces local persistence for React Native apps using SQLite.
|
||||
|
||||
<ContentByFramework framework="react-native">
|
||||
<div>
|
||||
Version 0.9.2 introduces local persistence for React Native apps using SQLite.
|
||||
If you are upgrading from a version before 0.9.2, you need to enable local persistence by following the steps below.
|
||||
|
||||
If you are upgrading from a version before 0.9.2, you need to enable local persistence by following the steps below.
|
||||
Local persistence will become the default in 0.10.0.
|
||||
|
||||
Local persistence will become the default in 0.10.0.
|
||||
</div>
|
||||
## Add the required dependencies
|
||||
|
||||
<h3>Add the required dependencies</h3>
|
||||
As SQLite package we now use `@op-engineering/op-sqlite`.
|
||||
|
||||
<div>
|
||||
As SQLite package we now use `@op-engineering/op-sqlite`.
|
||||
To get local persistence working, you need to add `@op-engineering/op-sqlite` as a direct dependency to your project and run `npx pod-install`.
|
||||
|
||||
To get local persistence working, you need to add `@op-engineering/op-sqlite` as a direct dependency to your project and run `npx pod-install`.
|
||||
</div>
|
||||
## Update your JazzProvider to enable local persistence
|
||||
|
||||
<h3>Update your JazzProvider to enable local persistence</h3>
|
||||
Local persistence is now disabled by default.
|
||||
|
||||
<div>
|
||||
Local persistence is now disabled by default.
|
||||
To enable it, you need to pass the `storage` option to the `JazzProvider` component:
|
||||
|
||||
To enable it, you need to pass the `storage` option to the `JazzProvider` component:
|
||||
</div>
|
||||
|
||||
<CodeGroup>
|
||||
```tsx
|
||||
<JazzProvider
|
||||
auth={auto}
|
||||
storage="sqlite"
|
||||
peer="wss://cloud.jazz.tools/?key=you@example.com"
|
||||
AccountSchema={MyAppAccount}
|
||||
>
|
||||
<App />
|
||||
</JazzProvider>
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
<CodeGroup>
|
||||
```tsx
|
||||
<JazzProvider
|
||||
auth={auto}
|
||||
storage="sqlite"
|
||||
peer="wss://cloud.jazz.tools/?key=you@example.com"
|
||||
AccountSchema={MyAppAccount}
|
||||
>
|
||||
<App />
|
||||
</JazzProvider>
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
31
homepage/homepage/components/FileDownloadLink.tsx
Normal file
31
homepage/homepage/components/FileDownloadLink.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { AnchorHTMLAttributes, DetailedHTMLProps } from "react";
|
||||
|
||||
import { Button } from "gcmp-design-system/src/app/components/atoms/Button";
|
||||
import { Icon } from "gcmp-design-system/src/app/components/atoms/Icon";
|
||||
|
||||
export function FileDownloadLink(
|
||||
props: DetailedHTMLProps<
|
||||
AnchorHTMLAttributes<HTMLAnchorElement>,
|
||||
HTMLAnchorElement
|
||||
>,
|
||||
) {
|
||||
if (!props.href) {
|
||||
return props.children;
|
||||
}
|
||||
|
||||
const { children, href } = props;
|
||||
|
||||
return (
|
||||
<div className="inline-flex items-center font-medium text-stone-900 rounded-md border p-3 shadow-sm dark:text-white dark:bg-stone-925 flex py-2 rounded-lg ">
|
||||
<Icon name="file" size="sm" className="mr-2" />
|
||||
{children}
|
||||
|
||||
<a href={href} download className="ml-12">
|
||||
Download
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
28
homepage/homepage/components/docs/DocHeading.tsx
Normal file
28
homepage/homepage/components/docs/DocHeading.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
"use client";
|
||||
|
||||
import { type ReactNode, useRef } from "react";
|
||||
|
||||
interface HeadingProps extends React.HTMLAttributes<HTMLHeadingElement> {
|
||||
tag: "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
|
||||
children?: ReactNode;
|
||||
}
|
||||
export const Heading = ({ children, tag: Tag, ...props }: HeadingProps) => {
|
||||
const linkRef = useRef<HTMLAnchorElement>(null);
|
||||
|
||||
return (
|
||||
<Tag {...props} className="group">
|
||||
<a
|
||||
href={`#${props.id}`}
|
||||
className="no-underline float-left absolute -ml-[1.25em] hidden sm:block opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
aria-label="Navigate to header"
|
||||
ref={linkRef}
|
||||
>
|
||||
<span aria-hidden="true">#</span>
|
||||
</a>
|
||||
|
||||
<span className="cursor-pointer" onClick={() => linkRef.current?.click()}>
|
||||
{children}
|
||||
</span>
|
||||
</Tag>
|
||||
);
|
||||
};
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
} from "@/components/docs/ContentByFramework";
|
||||
import { JazzLogo as JazzLogoClient } from "gcmp-design-system/src/app/components/atoms/logos/JazzLogo";
|
||||
import { CodeGroup as CodeGroupClient } from "gcmp-design-system/src/app/components/molecules/CodeGroup";
|
||||
import { AnchorHTMLAttributes, DetailedHTMLProps } from "react";
|
||||
import { FileDownloadLink as FileDownloadLinkClient } from "./FileDownloadLink";
|
||||
import { ComingSoon as ComingSoonClient } from "./docs/ComingSoon";
|
||||
import { IssueTrackerPreview as IssueTrackerPreviewClient } from "./docs/IssueTrackerPreview";
|
||||
|
||||
@@ -35,3 +37,12 @@ export function IssueTrackerPreview() {
|
||||
export function JazzLogo(props: { className?: string }) {
|
||||
return <JazzLogoClient {...props} />;
|
||||
}
|
||||
|
||||
export function FileDownloadLink(
|
||||
props: DetailedHTMLProps<
|
||||
AnchorHTMLAttributes<HTMLAnchorElement>,
|
||||
HTMLAnchorElement
|
||||
>,
|
||||
) {
|
||||
return <FileDownloadLinkClient {...props} />;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { promises as fs } from "fs";
|
||||
import path from "path";
|
||||
import { Deserializer, ReflectionKind } from "typedoc";
|
||||
import { DOC_SECTIONS, PACKAGES } from "./utils/config.mjs";
|
||||
import {
|
||||
getPackageDescription,
|
||||
loadTypedocFiles,
|
||||
writeDocsFile,
|
||||
} from "./utils/index.mjs";
|
||||
import { join } from "path";
|
||||
import { readFile, readdir } from "fs/promises";
|
||||
import { Deserializer } from "typedoc";
|
||||
import { DOC_SECTIONS } from "./utils/config.mjs";
|
||||
import { loadTypedocFiles, writeDocsFile } from "./utils/index.mjs";
|
||||
|
||||
function formatType(type) {
|
||||
if (!type) return "unknown";
|
||||
@@ -500,7 +498,90 @@ async function generateDetailedDocs(docs) {
|
||||
"- [Examples](https://jazz.tools/examples): Code examples and tutorials\n",
|
||||
);
|
||||
|
||||
await writeDocsFile("llms-full.txt", output.join("\n"));
|
||||
const outputWithExamples = [...output];
|
||||
await readMusicExample(outputWithExamples);
|
||||
|
||||
await writeDocsFile("llms.txt", output.join("\n"));
|
||||
await writeDocsFile("llms-full.txt", outputWithExamples.join("\n"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} FileContent
|
||||
* @property {string} filepath - The relative path to the file
|
||||
* @property {string} content - The content of the file
|
||||
*/
|
||||
|
||||
/**
|
||||
* Recursively loads all files from a directory and its subdirectories
|
||||
* @param {string} directoryPath - The path to the directory to load
|
||||
* @param {Object} options - Optional configuration
|
||||
* @param {string[]} options.exclude - File patterns to exclude (e.g., ['*.md', '*.git'])
|
||||
* @param {string} options.encoding - File encoding (default: 'utf-8')
|
||||
* @returns {Promise<FileContent[]>} Array of filepath/content pairs
|
||||
*/
|
||||
async function loadDirectoryContent(directoryPath, options = {}) {
|
||||
const { exclude = [], encoding = "utf-8" } = options;
|
||||
|
||||
async function processDirectory(currentPath) {
|
||||
const results = [];
|
||||
|
||||
try {
|
||||
const entries = await readdir(currentPath, { withFileTypes: true });
|
||||
|
||||
for (const entry of entries) {
|
||||
const fullPath = join(currentPath, entry.name);
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
// Recursively process subdirectories
|
||||
const subDirResults = await processDirectory(fullPath);
|
||||
results.push(...subDirResults);
|
||||
} else if (entry.isFile()) {
|
||||
// Check if file should be excluded
|
||||
const shouldExclude = exclude.some((pattern) => {
|
||||
if (pattern.startsWith("*.")) {
|
||||
const extension = pattern.slice(1);
|
||||
return entry.name.endsWith(extension);
|
||||
}
|
||||
return entry.name === pattern;
|
||||
});
|
||||
|
||||
if (!shouldExclude) {
|
||||
try {
|
||||
const content = await readFile(fullPath, { encoding });
|
||||
results.push({
|
||||
filepath: fullPath,
|
||||
content: content,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Error reading file ${fullPath}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error reading directory ${currentPath}:`, error);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
try {
|
||||
return await processDirectory(directoryPath);
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to load directory content: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function readMusicExample(output) {
|
||||
const files = await loadDirectoryContent(
|
||||
path.join(process.cwd(), "../../examples/music-player/src"),
|
||||
);
|
||||
|
||||
output.push("## Music Example\n\n");
|
||||
for (const file of files) {
|
||||
output.push(`### ${file.filepath}\n`);
|
||||
output.push(`\`\`\`ts\n${file.content}\n\`\`\`\n`);
|
||||
}
|
||||
}
|
||||
|
||||
// Main execution
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
import { Deserializer } from "typedoc";
|
||||
import { DOC_SECTIONS, PACKAGES } from "./utils/config.mjs";
|
||||
import {
|
||||
cleanDescription,
|
||||
loadTypedocFiles,
|
||||
writeDocsFile,
|
||||
} from "./utils/index.mjs";
|
||||
|
||||
async function generateConciseDocs(docs) {
|
||||
const output = [];
|
||||
const deserializer = new Deserializer();
|
||||
|
||||
// Project title
|
||||
output.push("# Jazz\n");
|
||||
|
||||
// Documentation sections
|
||||
output.push("## Documentation\n");
|
||||
DOC_SECTIONS.forEach((section) => {
|
||||
output.push(`### ${section.title}\n`);
|
||||
section.pages.forEach((page) => {
|
||||
output.push(`- [${page.title}](https://jazz.tools${page.url})\n`);
|
||||
});
|
||||
output.push("\n");
|
||||
});
|
||||
|
||||
// API Reference by package
|
||||
for (const [packageName, packageDocs] of Object.entries(docs)) {
|
||||
const project = deserializer.reviveProject(packageDocs, packageName);
|
||||
|
||||
// Add package heading
|
||||
output.push(`## ${packageName}\n`);
|
||||
|
||||
// Process each category and its exports with direct links
|
||||
if (project.categories) {
|
||||
const seen = new Set(); // Track seen names to avoid duplicates
|
||||
project.categories.forEach((category) => {
|
||||
category.children.forEach((child) => {
|
||||
if (seen.has(child.name)) return;
|
||||
seen.add(child.name);
|
||||
|
||||
// Get and clean up description
|
||||
let description = child.comment?.summary
|
||||
? cleanDescription(child.comment.summary)
|
||||
: "";
|
||||
|
||||
// Truncate description if it's too long
|
||||
if (description && description.length > 150) {
|
||||
description = description.substring(0, 147) + "...";
|
||||
}
|
||||
|
||||
// Create the line without wrapping
|
||||
output.push(
|
||||
`- [${child.name}](https://jazz.tools/api-reference/${packageName}#${child.name})${description ? `: ${description}` : ""}\n`,
|
||||
);
|
||||
});
|
||||
});
|
||||
output.push("\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Optional section for additional resources
|
||||
output.push("## Optional\n");
|
||||
output.push(
|
||||
"- [Documentation](https://jazz.tools/docs): Detailed documentation about Jazz\n",
|
||||
);
|
||||
output.push(
|
||||
"- [Examples](https://jazz.tools/examples): Code examples and tutorials\n",
|
||||
);
|
||||
|
||||
await writeDocsFile("llms.txt", output.join(""));
|
||||
}
|
||||
|
||||
// Main execution
|
||||
async function main() {
|
||||
console.log("Generating concise LLM docs...");
|
||||
const docs = await loadTypedocFiles();
|
||||
await generateConciseDocs(docs);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -29,6 +29,11 @@ export const docNavigationItems = [
|
||||
href: "/docs/ai-tools",
|
||||
done: 100,
|
||||
},
|
||||
{
|
||||
name: "Inspector",
|
||||
href: "/docs/inspector",
|
||||
done: 100,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
import { DocsLink } from "@/components/docs/DocsLink";
|
||||
import type { MDXComponents } from "mdx/types";
|
||||
import { Heading } from "./components/docs/DocHeading";
|
||||
|
||||
export function useMDXComponents(components: MDXComponents): MDXComponents {
|
||||
return {
|
||||
a: (props) => <DocsLink {...props} />,
|
||||
h2: (props) => <Heading tag="h2" {...props} />,
|
||||
h3: (props) => <Heading tag="h3" {...props} />,
|
||||
h4: (props) => <Heading tag="h4" {...props} />,
|
||||
h5: (props) => <Heading tag="h5" {...props} />,
|
||||
h6: (props) => <Heading tag="h6" {...props} />,
|
||||
...components,
|
||||
CodeWithInterpolation: ({
|
||||
highlightedCode,
|
||||
}: { highlightedCode: string }) => {
|
||||
return <div dangerouslySetInnerHTML={{ __html: highlightedCode }} />;
|
||||
},
|
||||
};
|
||||
} satisfies MDXComponents;
|
||||
}
|
||||
|
||||
export function InterpolateInCode(replace: { [key: string]: string }) {
|
||||
|
||||
@@ -5,15 +5,13 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "NODE_OPTIONS=--max-old-space-size=8192 next dev",
|
||||
"build:generate-docs": "pnpm run generate:docs && pnpm run generate:llm-docs:all",
|
||||
"build:generate-docs": "pnpm run generate:docs && pnpm run generate:llm-docs",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"format-and-lint": "biome check .",
|
||||
"format-and-lint:fix": "biome check . --write",
|
||||
"generate:docs": "node generate-docs/typedocs.mjs --build",
|
||||
"generate:llm-docs:all": "pnpm run generate:llm-docs:concise && pnpm run generate:llm-docs:full",
|
||||
"generate:llm-docs:concise": "node generate-docs/llms.mjs",
|
||||
"generate:llm-docs:full": "node generate-docs/llms-full.mjs",
|
||||
"generate:llm-docs": "node generate-docs/llms-full.mjs",
|
||||
"test": "pnpm run test:llm-docs",
|
||||
"test:llm-docs": "node generate-docs/llms-full.test.mjs"
|
||||
},
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
"lefthook": "^1.8.2",
|
||||
"pkg-pr-new": "^0.0.39",
|
||||
"playwright": "^1.50.1",
|
||||
"ts-node": "^10.9.1",
|
||||
"turbo": "^2.3.1",
|
||||
"typedoc": "^0.25.13",
|
||||
"vitest": "3.0.5"
|
||||
|
||||
@@ -1,5 +1,24 @@
|
||||
# cojson-storage-indexeddb
|
||||
|
||||
## 0.10.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [153dc99]
|
||||
- cojson@0.10.8
|
||||
- cojson-storage@0.10.8
|
||||
|
||||
## 0.10.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 1e625f3: Improve rollback on error when failing to add new content
|
||||
- Updated dependencies [0f83320]
|
||||
- Updated dependencies [012022d]
|
||||
- Updated dependencies [1e625f3]
|
||||
- cojson@0.10.7
|
||||
- cojson-storage@0.10.7
|
||||
|
||||
## 0.10.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cojson-storage-indexeddb",
|
||||
"version": "0.10.6",
|
||||
"version": "0.10.8",
|
||||
"main": "dist/index.js",
|
||||
"type": "module",
|
||||
"types": "src/index.ts",
|
||||
|
||||
111
packages/cojson-storage-indexeddb/src/CoJsonIDBTransaction.ts
Normal file
111
packages/cojson-storage-indexeddb/src/CoJsonIDBTransaction.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
export type StoreName =
|
||||
| "coValues"
|
||||
| "sessions"
|
||||
| "transactions"
|
||||
| "signatureAfter";
|
||||
|
||||
// A access unit for the IndexedDB Jazz database
|
||||
// It's a wrapper around the IDBTransaction object that helps on batching multiple operations
|
||||
// in a single transaction.
|
||||
export class CoJsonIDBTransaction {
|
||||
db: IDBDatabase;
|
||||
tx: IDBTransaction;
|
||||
|
||||
pendingRequests: ((txEntry: this) => void)[] = [];
|
||||
rejectHandlers: (() => void)[] = [];
|
||||
|
||||
id = Math.random();
|
||||
|
||||
running = false;
|
||||
failed = false;
|
||||
done = false;
|
||||
|
||||
constructor(db: IDBDatabase) {
|
||||
this.db = db;
|
||||
|
||||
this.tx = this.db.transaction(
|
||||
["coValues", "sessions", "transactions", "signatureAfter"],
|
||||
"readwrite",
|
||||
);
|
||||
|
||||
this.tx.oncomplete = () => {
|
||||
this.done = true;
|
||||
};
|
||||
this.tx.onabort = () => {
|
||||
this.done = true;
|
||||
};
|
||||
}
|
||||
|
||||
startedAt = performance.now();
|
||||
isReusable() {
|
||||
const delta = performance.now() - this.startedAt;
|
||||
return !this.done && delta <= 20;
|
||||
}
|
||||
|
||||
getObjectStore(name: StoreName) {
|
||||
return this.tx.objectStore(name);
|
||||
}
|
||||
|
||||
private pushRequest<T>(
|
||||
handler: (txEntry: this, next: () => void) => Promise<T>,
|
||||
) {
|
||||
const next = () => {
|
||||
const next = this.pendingRequests.shift();
|
||||
|
||||
if (next) {
|
||||
next(this);
|
||||
} else {
|
||||
this.running = false;
|
||||
this.done = true;
|
||||
}
|
||||
};
|
||||
|
||||
if (this.running) {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
this.rejectHandlers.push(reject);
|
||||
this.pendingRequests.push(async () => {
|
||||
try {
|
||||
const result = await handler(this, next);
|
||||
resolve(result);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
this.running = true;
|
||||
return handler(this, next);
|
||||
}
|
||||
|
||||
handleRequest<T>(handler: (txEntry: this) => IDBRequest<T>) {
|
||||
return this.pushRequest<T>((txEntry, next) => {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
const request = handler(txEntry);
|
||||
|
||||
request.onerror = () => {
|
||||
this.failed = true;
|
||||
this.tx.abort();
|
||||
console.error(request.error);
|
||||
reject(request.error);
|
||||
|
||||
// Don't leave any pending promise
|
||||
for (const handler of this.rejectHandlers) {
|
||||
handler();
|
||||
}
|
||||
};
|
||||
|
||||
request.onsuccess = () => {
|
||||
resolve(request.result as T);
|
||||
next();
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
commit() {
|
||||
if (!this.done) {
|
||||
this.tx.commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { CojsonInternalTypes, RawCoID } from "cojson";
|
||||
import type { CojsonInternalTypes, RawCoID, SessionID } from "cojson";
|
||||
import type {
|
||||
CoValueRow,
|
||||
DBClientInterface,
|
||||
@@ -8,119 +8,60 @@ import type {
|
||||
StoredSessionRow,
|
||||
TransactionRow,
|
||||
} from "cojson-storage";
|
||||
import { SyncPromise } from "./syncPromises.js";
|
||||
import { CoJsonIDBTransaction } from "./CoJsonIDBTransaction.js";
|
||||
|
||||
export class IDBClient implements DBClientInterface {
|
||||
private db;
|
||||
|
||||
currentTx:
|
||||
| {
|
||||
id: number;
|
||||
tx: IDBTransaction;
|
||||
stores: {
|
||||
coValues: IDBObjectStore;
|
||||
sessions: IDBObjectStore;
|
||||
transactions: IDBObjectStore;
|
||||
signatureAfter: IDBObjectStore;
|
||||
};
|
||||
startedAt: number;
|
||||
pendingRequests: ((txEntry: {
|
||||
stores: {
|
||||
coValues: IDBObjectStore;
|
||||
sessions: IDBObjectStore;
|
||||
transactions: IDBObjectStore;
|
||||
signatureAfter: IDBObjectStore;
|
||||
};
|
||||
}) => void)[];
|
||||
}
|
||||
| undefined;
|
||||
|
||||
currentTxID = 0;
|
||||
activeTransaction: CoJsonIDBTransaction | undefined;
|
||||
autoBatchingTransaction: CoJsonIDBTransaction | undefined;
|
||||
|
||||
constructor(db: IDBDatabase) {
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
makeRequest<T>(
|
||||
handler: (stores: {
|
||||
coValues: IDBObjectStore;
|
||||
sessions: IDBObjectStore;
|
||||
transactions: IDBObjectStore;
|
||||
signatureAfter: IDBObjectStore;
|
||||
}) => IDBRequest,
|
||||
): SyncPromise<T> {
|
||||
return new SyncPromise((resolve, reject) => {
|
||||
let txEntry = this.currentTx;
|
||||
handler: (txEntry: CoJsonIDBTransaction) => IDBRequest<T>,
|
||||
): Promise<T> {
|
||||
if (this.activeTransaction) {
|
||||
return this.activeTransaction.handleRequest<T>(handler);
|
||||
}
|
||||
|
||||
const requestEntry = ({
|
||||
stores,
|
||||
}: {
|
||||
stores: {
|
||||
coValues: IDBObjectStore;
|
||||
sessions: IDBObjectStore;
|
||||
transactions: IDBObjectStore;
|
||||
signatureAfter: IDBObjectStore;
|
||||
};
|
||||
}) => {
|
||||
const request = handler(stores);
|
||||
request.onerror = () => {
|
||||
console.error("Error in request", request.error);
|
||||
this.currentTx = undefined;
|
||||
reject(request.error);
|
||||
};
|
||||
request.onsuccess = () => {
|
||||
const value = request.result as T;
|
||||
resolve(value);
|
||||
if (this.autoBatchingTransaction?.isReusable()) {
|
||||
return this.autoBatchingTransaction.handleRequest<T>(handler);
|
||||
}
|
||||
|
||||
const next = txEntry?.pendingRequests.shift();
|
||||
const tx = new CoJsonIDBTransaction(this.db);
|
||||
|
||||
if (next) {
|
||||
next({ stores });
|
||||
} else {
|
||||
if (this.currentTx === txEntry) {
|
||||
this.currentTx = undefined;
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
this.autoBatchingTransaction = tx;
|
||||
|
||||
// Transaction batching
|
||||
if (!txEntry || performance.now() - txEntry.startedAt > 20) {
|
||||
const tx = this.db.transaction(
|
||||
["coValues", "sessions", "transactions", "signatureAfter"],
|
||||
"readwrite",
|
||||
);
|
||||
txEntry = {
|
||||
id: this.currentTxID++,
|
||||
tx,
|
||||
stores: {
|
||||
coValues: tx.objectStore("coValues"),
|
||||
sessions: tx.objectStore("sessions"),
|
||||
transactions: tx.objectStore("transactions"),
|
||||
signatureAfter: tx.objectStore("signatureAfter"),
|
||||
},
|
||||
startedAt: performance.now(),
|
||||
pendingRequests: [],
|
||||
};
|
||||
|
||||
this.currentTx = txEntry;
|
||||
|
||||
requestEntry(txEntry);
|
||||
} else {
|
||||
txEntry.pendingRequests.push(requestEntry);
|
||||
}
|
||||
});
|
||||
return tx.handleRequest<T>(handler);
|
||||
}
|
||||
|
||||
async getCoValue(coValueId: RawCoID): Promise<StoredCoValueRow | undefined> {
|
||||
return this.makeRequest<StoredCoValueRow | undefined>(({ coValues }) =>
|
||||
coValues.index("coValuesById").get(coValueId),
|
||||
return this.makeRequest<StoredCoValueRow | undefined>((tx) =>
|
||||
tx.getObjectStore("coValues").index("coValuesById").get(coValueId),
|
||||
);
|
||||
}
|
||||
|
||||
async getCoValueSessions(coValueRowId: number): Promise<StoredSessionRow[]> {
|
||||
return this.makeRequest<StoredSessionRow[]>(({ sessions }) =>
|
||||
sessions.index("sessionsByCoValue").getAll(coValueRowId),
|
||||
return this.makeRequest<StoredSessionRow[]>((tx) =>
|
||||
tx
|
||||
.getObjectStore("sessions")
|
||||
.index("sessionsByCoValue")
|
||||
.getAll(coValueRowId),
|
||||
);
|
||||
}
|
||||
|
||||
async getSingleCoValueSession(
|
||||
coValueRowId: number,
|
||||
sessionID: SessionID,
|
||||
): Promise<StoredSessionRow | undefined> {
|
||||
return this.makeRequest<StoredSessionRow>((tx) =>
|
||||
tx
|
||||
.getObjectStore("sessions")
|
||||
.index("uniqueSessions")
|
||||
.get([coValueRowId, sessionID]),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -128,13 +69,15 @@ export class IDBClient implements DBClientInterface {
|
||||
sessionRowId: number,
|
||||
firstNewTxIdx: number,
|
||||
): Promise<TransactionRow[]> {
|
||||
return this.makeRequest<TransactionRow[]>(({ transactions }) =>
|
||||
transactions.getAll(
|
||||
IDBKeyRange.bound(
|
||||
[sessionRowId, firstNewTxIdx],
|
||||
[sessionRowId, Number.POSITIVE_INFINITY],
|
||||
return this.makeRequest<TransactionRow[]>((tx) =>
|
||||
tx
|
||||
.getObjectStore("transactions")
|
||||
.getAll(
|
||||
IDBKeyRange.bound(
|
||||
[sessionRowId, firstNewTxIdx],
|
||||
[sessionRowId, Number.POSITIVE_INFINITY],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -142,9 +85,10 @@ export class IDBClient implements DBClientInterface {
|
||||
sessionRowId: number,
|
||||
firstNewTxIdx: number,
|
||||
): Promise<SignatureAfterRow[]> {
|
||||
return this.makeRequest<SignatureAfterRow[]>(
|
||||
({ signatureAfter }: { signatureAfter: IDBObjectStore }) =>
|
||||
signatureAfter.getAll(
|
||||
return this.makeRequest<SignatureAfterRow[]>((tx) =>
|
||||
tx
|
||||
.getObjectStore("signatureAfter")
|
||||
.getAll(
|
||||
IDBKeyRange.bound(
|
||||
[sessionRowId, firstNewTxIdx],
|
||||
[sessionRowId, Number.POSITIVE_INFINITY],
|
||||
@@ -160,8 +104,8 @@ export class IDBClient implements DBClientInterface {
|
||||
throw new Error(`Header is required, coId: ${msg.id}`);
|
||||
}
|
||||
|
||||
return (await this.makeRequest<IDBValidKey>(({ coValues }) =>
|
||||
coValues.put({
|
||||
return (await this.makeRequest<IDBValidKey>((tx) =>
|
||||
tx.getObjectStore("coValues").put({
|
||||
id: msg.id,
|
||||
// biome-ignore lint/style/noNonNullAssertion: TODO(JAZZ-561): Review
|
||||
header: msg.header!,
|
||||
@@ -176,25 +120,26 @@ export class IDBClient implements DBClientInterface {
|
||||
sessionUpdate: SessionRow;
|
||||
sessionRow?: StoredSessionRow;
|
||||
}): Promise<number> {
|
||||
return this.makeRequest<number>(({ sessions }) =>
|
||||
sessions.put(
|
||||
sessionRow?.rowID
|
||||
? {
|
||||
rowID: sessionRow.rowID,
|
||||
...sessionUpdate,
|
||||
}
|
||||
: sessionUpdate,
|
||||
),
|
||||
return this.makeRequest<number>(
|
||||
(tx) =>
|
||||
tx.getObjectStore("sessions").put(
|
||||
sessionRow?.rowID
|
||||
? {
|
||||
rowID: sessionRow.rowID,
|
||||
...sessionUpdate,
|
||||
}
|
||||
: sessionUpdate,
|
||||
) as IDBRequest<number>,
|
||||
);
|
||||
}
|
||||
|
||||
addTransaction(
|
||||
async addTransaction(
|
||||
sessionRowID: number,
|
||||
idx: number,
|
||||
newTransaction: CojsonInternalTypes.Transaction,
|
||||
) {
|
||||
return this.makeRequest(({ transactions }) =>
|
||||
transactions.add({
|
||||
await this.makeRequest((tx) =>
|
||||
tx.getObjectStore("transactions").add({
|
||||
ses: sessionRowID,
|
||||
idx,
|
||||
tx: newTransaction,
|
||||
@@ -211,8 +156,8 @@ export class IDBClient implements DBClientInterface {
|
||||
idx: number;
|
||||
signature: CojsonInternalTypes.Signature;
|
||||
}) {
|
||||
return this.makeRequest(({ signatureAfter }) =>
|
||||
signatureAfter.put({
|
||||
return this.makeRequest((tx) =>
|
||||
tx.getObjectStore("signatureAfter").put({
|
||||
ses: sessionRowID,
|
||||
idx,
|
||||
signature,
|
||||
@@ -220,7 +165,24 @@ export class IDBClient implements DBClientInterface {
|
||||
);
|
||||
}
|
||||
|
||||
async unitOfWork(operationsCallback: () => unknown[]) {
|
||||
return Promise.all(operationsCallback());
|
||||
closeTransaction(tx: CoJsonIDBTransaction) {
|
||||
tx.commit();
|
||||
|
||||
if (tx === this.activeTransaction) {
|
||||
this.activeTransaction = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async transaction(operationsCallback: () => unknown) {
|
||||
const tx = new CoJsonIDBTransaction(this.db);
|
||||
|
||||
this.activeTransaction = tx;
|
||||
|
||||
try {
|
||||
await operationsCallback();
|
||||
tx.commit(); // Tells the browser to not wait for another possible request and commit the transaction immediately
|
||||
} finally {
|
||||
this.activeTransaction = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,18 +33,7 @@ export class IDBNode {
|
||||
}
|
||||
await this.syncManager.handleSyncMessage(msg);
|
||||
} catch (e) {
|
||||
console.error(
|
||||
new Error(
|
||||
`Error reading from localNode, handling msg\n\n${JSON.stringify(
|
||||
msg,
|
||||
(k, v) =>
|
||||
k === "changes" || k === "encryptedChanges"
|
||||
? `${v.slice(0, 20)}...`
|
||||
: v,
|
||||
)}`,
|
||||
{ cause: e },
|
||||
),
|
||||
);
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,224 +0,0 @@
|
||||
const isFunction = (func: any) => typeof func === "function";
|
||||
|
||||
const isObject = (supposedObject: any) =>
|
||||
typeof supposedObject === "object" &&
|
||||
supposedObject !== null &&
|
||||
!Array.isArray(supposedObject);
|
||||
|
||||
const isThenable = (obj: any) => isObject(obj) && isFunction(obj.then);
|
||||
|
||||
const identity = (co: any) => co;
|
||||
|
||||
export { identity, isFunction, isObject, isThenable };
|
||||
|
||||
enum States {
|
||||
PENDING = "PENDING",
|
||||
RESOLVED = "RESOLVED",
|
||||
REJECTED = "REJECTED",
|
||||
}
|
||||
|
||||
interface Handler<T, U> {
|
||||
onSuccess: HandlerOnSuccess<T, U>;
|
||||
onFail: HandlerOnFail<U>;
|
||||
}
|
||||
|
||||
type HandlerOnSuccess<T, U = any> = (value: T) => U | Thenable<U>;
|
||||
type HandlerOnFail<U = any> = (reason: any) => U | Thenable<U>;
|
||||
type Finally<U> = () => U | Thenable<U>;
|
||||
|
||||
interface Thenable<T> {
|
||||
then<U>(
|
||||
onSuccess?: HandlerOnSuccess<T, U>,
|
||||
onFail?: HandlerOnFail<U>,
|
||||
): Thenable<U>;
|
||||
then<U>(
|
||||
onSuccess?: HandlerOnSuccess<T, U>,
|
||||
onFail?: (reason: any) => void,
|
||||
): Thenable<U>;
|
||||
}
|
||||
|
||||
type Resolve<R> = (value?: R | Thenable<R>) => void;
|
||||
type Reject = (value?: any) => void;
|
||||
|
||||
export class SyncPromise<T> {
|
||||
private state: States = States.PENDING;
|
||||
private handlers: Handler<T, any>[] = [];
|
||||
private value: T | any;
|
||||
|
||||
public constructor(callback: (resolve: Resolve<T>, reject: Reject) => void) {
|
||||
try {
|
||||
callback(this.resolve as Resolve<T>, this.reject);
|
||||
} catch (e) {
|
||||
this.reject(e);
|
||||
}
|
||||
}
|
||||
|
||||
private resolve = (value: T) => {
|
||||
return this.setResult(value, States.RESOLVED);
|
||||
};
|
||||
|
||||
private reject = (reason: any) => {
|
||||
return this.setResult(reason, States.REJECTED);
|
||||
};
|
||||
|
||||
private setResult = (value: T | any, state: States) => {
|
||||
const set = () => {
|
||||
if (this.state !== States.PENDING) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isThenable(value)) {
|
||||
return (value as Thenable<T>).then(this.resolve, this.reject);
|
||||
}
|
||||
|
||||
this.value = value;
|
||||
this.state = state;
|
||||
|
||||
return this.executeHandlers();
|
||||
};
|
||||
|
||||
void set();
|
||||
};
|
||||
|
||||
private executeHandlers = () => {
|
||||
if (this.state === States.PENDING) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (const handler of this.handlers) {
|
||||
if (this.state === States.REJECTED) {
|
||||
handler.onFail(this.value);
|
||||
} else {
|
||||
handler.onSuccess(this.value);
|
||||
}
|
||||
}
|
||||
|
||||
this.handlers = [];
|
||||
};
|
||||
|
||||
private attachHandler = (handler: Handler<T, any>) => {
|
||||
this.handlers = [...this.handlers, handler];
|
||||
|
||||
this.executeHandlers();
|
||||
};
|
||||
|
||||
// biome-ignore lint/suspicious/noThenProperty: TODO(JAZZ-561): Review
|
||||
public then<U>(onSuccess: HandlerOnSuccess<T, U>, onFail?: HandlerOnFail<U>) {
|
||||
return new SyncPromise<U>((resolve, reject) => {
|
||||
return this.attachHandler({
|
||||
onSuccess: (result) => {
|
||||
try {
|
||||
return resolve(onSuccess(result));
|
||||
} catch (e) {
|
||||
return reject(e);
|
||||
}
|
||||
},
|
||||
onFail: (reason) => {
|
||||
if (!onFail) {
|
||||
return reject(reason);
|
||||
}
|
||||
|
||||
try {
|
||||
return resolve(onFail(reason));
|
||||
} catch (e) {
|
||||
return reject(e);
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public catch<U>(onFail: HandlerOnFail<U>) {
|
||||
return this.then<U>(identity, onFail);
|
||||
}
|
||||
|
||||
// methods
|
||||
|
||||
public toString() {
|
||||
return "[object SyncPromise]";
|
||||
}
|
||||
|
||||
public finally<U>(cb: Finally<U>) {
|
||||
return new SyncPromise<U>((resolve, reject) => {
|
||||
let co: U | any;
|
||||
let isRejected: boolean;
|
||||
|
||||
return this.then(
|
||||
(value) => {
|
||||
isRejected = false;
|
||||
co = value;
|
||||
return cb();
|
||||
},
|
||||
(reason) => {
|
||||
isRejected = true;
|
||||
co = reason;
|
||||
return cb();
|
||||
},
|
||||
).then(() => {
|
||||
if (isRejected) {
|
||||
return reject(co);
|
||||
}
|
||||
|
||||
return resolve(co);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public spread<U>(handler: (...args: any[]) => U) {
|
||||
return this.then<U>((collection) => {
|
||||
if (Array.isArray(collection)) {
|
||||
return handler(...collection);
|
||||
}
|
||||
|
||||
return handler(collection);
|
||||
});
|
||||
}
|
||||
|
||||
// static
|
||||
|
||||
public static resolve<U = any>(value?: U | Thenable<U>) {
|
||||
return new SyncPromise<U>((resolve) => {
|
||||
return resolve(value);
|
||||
});
|
||||
}
|
||||
|
||||
public static reject<U>(reason?: any) {
|
||||
return new SyncPromise<U>((_resolve, reject) => {
|
||||
return reject(reason);
|
||||
});
|
||||
}
|
||||
|
||||
public static all<U = any>(collection: (U | Thenable<U>)[]) {
|
||||
return new SyncPromise<U[]>((resolve, reject) => {
|
||||
if (!Array.isArray(collection)) {
|
||||
return reject(new TypeError("An array must be provided."));
|
||||
}
|
||||
|
||||
if (collection.length === 0) {
|
||||
return resolve([]);
|
||||
}
|
||||
|
||||
let counter = collection.length;
|
||||
const resolvedCollection: U[] = [];
|
||||
|
||||
const tryResolve = (value: U, index: number) => {
|
||||
counter -= 1;
|
||||
resolvedCollection[index] = value;
|
||||
|
||||
if (counter !== 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return resolve(resolvedCollection);
|
||||
};
|
||||
|
||||
return collection.forEach((item, index) => {
|
||||
return SyncPromise.resolve(item)
|
||||
.then((value) => {
|
||||
return tryResolve(value, index);
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
||||
import { CoJsonIDBTransaction } from "../CoJsonIDBTransaction";
|
||||
|
||||
const TEST_DB_NAME = "test-cojson-idb-transaction";
|
||||
|
||||
describe("CoJsonIDBTransaction", () => {
|
||||
let db: IDBDatabase;
|
||||
|
||||
beforeEach(async () => {
|
||||
// Create test database
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const request = indexedDB.open(TEST_DB_NAME, 1);
|
||||
|
||||
request.onerror = () => reject(request.error);
|
||||
|
||||
request.onupgradeneeded = (event) => {
|
||||
const db = request.result;
|
||||
// Create test stores
|
||||
db.createObjectStore("coValues", { keyPath: "id" });
|
||||
const sessions = db.createObjectStore("sessions", { keyPath: "id" });
|
||||
sessions.createIndex("uniqueSessions", ["coValue", "sessionID"], {
|
||||
unique: true,
|
||||
});
|
||||
db.createObjectStore("transactions", { keyPath: "id" });
|
||||
db.createObjectStore("signatureAfter", { keyPath: "id" });
|
||||
};
|
||||
|
||||
request.onsuccess = () => {
|
||||
db = request.result;
|
||||
resolve();
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
// Close and delete test database
|
||||
db.close();
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const request = indexedDB.deleteDatabase(TEST_DB_NAME);
|
||||
request.onerror = () => reject(request.error);
|
||||
request.onsuccess = () => resolve();
|
||||
});
|
||||
});
|
||||
|
||||
test("handles successful write and read operations", async () => {
|
||||
const tx = new CoJsonIDBTransaction(db);
|
||||
|
||||
// Write test
|
||||
await tx.handleRequest((tx) =>
|
||||
tx.getObjectStore("coValues").put({
|
||||
id: "test1",
|
||||
value: "hello",
|
||||
}),
|
||||
);
|
||||
|
||||
// Read test
|
||||
const readTx = new CoJsonIDBTransaction(db);
|
||||
const result = await readTx.handleRequest((tx) =>
|
||||
tx.getObjectStore("coValues").get("test1"),
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
id: "test1",
|
||||
value: "hello",
|
||||
});
|
||||
});
|
||||
|
||||
test("handles multiple operations in single transaction", async () => {
|
||||
const tx = new CoJsonIDBTransaction(db);
|
||||
|
||||
// Multiple writes
|
||||
await Promise.all([
|
||||
tx.handleRequest((tx) =>
|
||||
tx.getObjectStore("coValues").put({
|
||||
id: "test1",
|
||||
value: "hello",
|
||||
}),
|
||||
),
|
||||
tx.handleRequest((tx) =>
|
||||
tx.getObjectStore("coValues").put({
|
||||
id: "test2",
|
||||
value: "world",
|
||||
}),
|
||||
),
|
||||
]);
|
||||
|
||||
// Read results
|
||||
const readTx = new CoJsonIDBTransaction(db);
|
||||
const [result1, result2] = await Promise.all([
|
||||
readTx.handleRequest((tx) => tx.getObjectStore("coValues").get("test1")),
|
||||
readTx.handleRequest((tx) => tx.getObjectStore("coValues").get("test2")),
|
||||
]);
|
||||
|
||||
expect(result1).toEqual({
|
||||
id: "test1",
|
||||
value: "hello",
|
||||
});
|
||||
expect(result2).toEqual({
|
||||
id: "test2",
|
||||
value: "world",
|
||||
});
|
||||
});
|
||||
|
||||
test("handles transaction across multiple stores", async () => {
|
||||
const tx = new CoJsonIDBTransaction(db);
|
||||
|
||||
await Promise.all([
|
||||
tx.handleRequest((tx) =>
|
||||
tx.getObjectStore("coValues").put({
|
||||
id: "value1",
|
||||
data: "value data",
|
||||
}),
|
||||
),
|
||||
tx.handleRequest((tx) =>
|
||||
tx.getObjectStore("sessions").put({
|
||||
id: "session1",
|
||||
data: "session data",
|
||||
}),
|
||||
),
|
||||
]);
|
||||
|
||||
const readTx = new CoJsonIDBTransaction(db);
|
||||
const [valueResult, sessionResult] = await Promise.all([
|
||||
readTx.handleRequest((tx) => tx.getObjectStore("coValues").get("value1")),
|
||||
readTx.handleRequest((tx) =>
|
||||
tx.getObjectStore("sessions").get("session1"),
|
||||
),
|
||||
]);
|
||||
|
||||
expect(valueResult).toEqual({
|
||||
id: "value1",
|
||||
data: "value data",
|
||||
});
|
||||
expect(sessionResult).toEqual({
|
||||
id: "session1",
|
||||
data: "session data",
|
||||
});
|
||||
});
|
||||
|
||||
test("handles failed transactions", async () => {
|
||||
const tx = new CoJsonIDBTransaction(db);
|
||||
|
||||
await expect(
|
||||
tx.handleRequest((tx) =>
|
||||
tx.getObjectStore("sessions").put({
|
||||
id: 1,
|
||||
coValue: "value1",
|
||||
sessionID: "session1",
|
||||
data: "session data",
|
||||
}),
|
||||
),
|
||||
).resolves.toBe(1);
|
||||
|
||||
expect(tx.failed).toBe(false);
|
||||
|
||||
const badTx = new CoJsonIDBTransaction(db);
|
||||
await expect(
|
||||
badTx.handleRequest((tx) =>
|
||||
tx.getObjectStore("sessions").put({
|
||||
id: 2,
|
||||
coValue: "value1",
|
||||
sessionID: "session1",
|
||||
data: "session data",
|
||||
}),
|
||||
),
|
||||
).rejects.toThrow();
|
||||
|
||||
expect(badTx.failed).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,24 @@
|
||||
# cojson-storage-sqlite
|
||||
|
||||
## 0.8.66
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [153dc99]
|
||||
- cojson@0.10.8
|
||||
- cojson-storage@0.10.8
|
||||
|
||||
## 0.8.65
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 1e625f3: Improve rollback on error when failing to add new content
|
||||
- Updated dependencies [0f83320]
|
||||
- Updated dependencies [012022d]
|
||||
- Updated dependencies [1e625f3]
|
||||
- cojson@0.10.7
|
||||
- cojson-storage@0.10.7
|
||||
|
||||
## 0.8.64
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "cojson-storage-rn-sqlite",
|
||||
"type": "module",
|
||||
"version": "0.8.64",
|
||||
"version": "0.8.66",
|
||||
"main": "dist/index.js",
|
||||
"types": "src/index.ts",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { type DB as DatabaseT } from "@op-engineering/op-sqlite";
|
||||
import { CojsonInternalTypes, type OutgoingSyncQueue, RawCoID } from "cojson";
|
||||
import {
|
||||
CojsonInternalTypes,
|
||||
type OutgoingSyncQueue,
|
||||
RawCoID,
|
||||
SessionID,
|
||||
} from "cojson";
|
||||
import type {
|
||||
DBClientInterface,
|
||||
SessionRow,
|
||||
@@ -48,6 +53,17 @@ export class SQLiteClient implements DBClientInterface {
|
||||
return rows as StoredSessionRow[];
|
||||
}
|
||||
|
||||
async getSingleCoValueSession(
|
||||
coValueRowId: number,
|
||||
sessionID: SessionID,
|
||||
): Promise<StoredSessionRow | undefined> {
|
||||
const { rows } = await this.db.execute(
|
||||
"SELECT * FROM sessions WHERE coValue = ? AND sessionID = ?",
|
||||
[coValueRowId, sessionID],
|
||||
);
|
||||
return rows[0] as StoredSessionRow | undefined;
|
||||
}
|
||||
|
||||
async getNewTransactionInSession(
|
||||
sessionRowId: number,
|
||||
firstNewTxIdx: number,
|
||||
@@ -142,12 +158,10 @@ export class SQLiteClient implements DBClientInterface {
|
||||
);
|
||||
}
|
||||
|
||||
async unitOfWork(
|
||||
operationsCallback: () => Promise<unknown>[],
|
||||
): Promise<void> {
|
||||
async transaction(operationsCallback: () => unknown) {
|
||||
try {
|
||||
await this.db.transaction(async () => {
|
||||
await Promise.all(operationsCallback());
|
||||
await operationsCallback();
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("Transaction failed:", e);
|
||||
|
||||
@@ -42,18 +42,6 @@ export class SQLiteReactNative {
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(
|
||||
new Error(
|
||||
`Error reading from localNode, handling msg\n\n${JSON.stringify(
|
||||
msg,
|
||||
(k, v) =>
|
||||
k === "changes" || k === "encryptedChanges"
|
||||
? `${v.slice(0, 20)}...`
|
||||
: v,
|
||||
)}`,
|
||||
{ cause: e },
|
||||
),
|
||||
);
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,24 @@
|
||||
# cojson-storage-sqlite
|
||||
|
||||
## 0.10.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [153dc99]
|
||||
- cojson@0.10.8
|
||||
- cojson-storage@0.10.8
|
||||
|
||||
## 0.10.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 1e625f3: Improve rollback on error when failing to add new content
|
||||
- Updated dependencies [0f83320]
|
||||
- Updated dependencies [012022d]
|
||||
- Updated dependencies [1e625f3]
|
||||
- cojson@0.10.7
|
||||
- cojson-storage@0.10.7
|
||||
|
||||
## 0.10.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "cojson-storage-sqlite",
|
||||
"type": "module",
|
||||
"version": "0.10.6",
|
||||
"version": "0.10.8",
|
||||
"main": "dist/index.js",
|
||||
"types": "src/index.ts",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"better-sqlite3": "^11.7.0",
|
||||
"cojson": "workspace:0.10.6",
|
||||
"cojson": "workspace:0.10.8",
|
||||
"cojson-storage": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -71,6 +71,17 @@ export class SQLiteClient implements DBClientInterface {
|
||||
.all(coValueRowId) as StoredSessionRow[];
|
||||
}
|
||||
|
||||
getSingleCoValueSession(
|
||||
coValueRowId: number,
|
||||
sessionID: SessionID,
|
||||
): StoredSessionRow | undefined {
|
||||
return this.db
|
||||
.prepare<[number, string]>(
|
||||
`SELECT * FROM sessions WHERE coValue = ? AND sessionID = ?`,
|
||||
)
|
||||
.get(coValueRowId, sessionID) as StoredSessionRow | undefined;
|
||||
}
|
||||
|
||||
getNewTransactionInSession(
|
||||
sessionRowId: number,
|
||||
firstNewTxIdx: number,
|
||||
@@ -159,7 +170,7 @@ export class SQLiteClient implements DBClientInterface {
|
||||
.run(sessionRowID, idx, signature);
|
||||
}
|
||||
|
||||
unitOfWork(operationsCallback: () => any[]) {
|
||||
transaction(operationsCallback: () => unknown) {
|
||||
this.db.transaction(operationsCallback)();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# cojson-storage
|
||||
|
||||
## 0.10.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [153dc99]
|
||||
- cojson@0.10.8
|
||||
|
||||
## 0.10.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 1e625f3: Improve rollback on error when failing to add new content
|
||||
- Updated dependencies [0f83320]
|
||||
- Updated dependencies [012022d]
|
||||
- cojson@0.10.7
|
||||
|
||||
## 0.10.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cojson-storage",
|
||||
"version": "0.10.6",
|
||||
"version": "0.10.8",
|
||||
"main": "dist/index.js",
|
||||
"type": "module",
|
||||
"types": "src/index.ts",
|
||||
|
||||
@@ -200,15 +200,6 @@ export class SyncManager {
|
||||
? coValueRow.rowID
|
||||
: await this.dbClient.addCoValue(msg);
|
||||
|
||||
const allOurSessionsEntries =
|
||||
await this.dbClient.getCoValueSessions(storedCoValueRowID);
|
||||
|
||||
const allOurSessions: {
|
||||
[sessionID: SessionID]: StoredSessionRow;
|
||||
} = Object.fromEntries(
|
||||
allOurSessionsEntries.map((row) => [row.sessionID, row]),
|
||||
);
|
||||
|
||||
const ourKnown: CojsonInternalTypes.CoValueKnownState = {
|
||||
id: msg.id,
|
||||
header: true,
|
||||
@@ -217,9 +208,13 @@ export class SyncManager {
|
||||
|
||||
let invalidAssumptions = false;
|
||||
|
||||
await this.dbClient.unitOfWork(() =>
|
||||
(Object.keys(msg.new) as SessionID[]).map((sessionID) => {
|
||||
const sessionRow = allOurSessions[sessionID];
|
||||
for (const sessionID of Object.keys(msg.new) as SessionID[]) {
|
||||
await this.dbClient.transaction(async () => {
|
||||
const sessionRow = await this.dbClient.getSingleCoValueSession(
|
||||
storedCoValueRowID,
|
||||
sessionID,
|
||||
);
|
||||
|
||||
if (sessionRow) {
|
||||
ourKnown.sessions[sessionRow.sessionID] = sessionRow.lastIdx;
|
||||
}
|
||||
@@ -229,8 +224,8 @@ export class SyncManager {
|
||||
} else {
|
||||
return this.putNewTxs(msg, sessionID, sessionRow, storedCoValueRowID);
|
||||
}
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (invalidAssumptions) {
|
||||
this.sendStateMessage({
|
||||
|
||||
@@ -45,9 +45,11 @@ describe("DB sync manager", () => {
|
||||
const DBClient = vi.fn();
|
||||
DBClient.prototype.getCoValue = vi.fn();
|
||||
DBClient.prototype.getCoValueSessions = vi.fn();
|
||||
DBClient.prototype.getSingleCoValueSession = vi.fn();
|
||||
DBClient.prototype.getNewTransactionInSession = vi.fn();
|
||||
DBClient.prototype.addSessionUpdate = vi.fn();
|
||||
DBClient.prototype.addTransaction = vi.fn();
|
||||
DBClient.prototype.unitOfWork = vi.fn((callback) => Promise.all(callback()));
|
||||
DBClient.prototype.transaction = vi.fn((callback) => callback());
|
||||
|
||||
beforeEach(async () => {
|
||||
const idbClient = new DBClient() as unknown as Mocked<DBClientInterface>;
|
||||
|
||||
@@ -41,6 +41,11 @@ export interface DBClientInterface {
|
||||
coValueRowId: number,
|
||||
): Promise<StoredSessionRow[]> | StoredSessionRow[];
|
||||
|
||||
getSingleCoValueSession(
|
||||
coValueRowId: number,
|
||||
sessionID: SessionID,
|
||||
): Promise<StoredSessionRow | undefined> | StoredSessionRow | undefined;
|
||||
|
||||
getNewTransactionInSession(
|
||||
sessionRowId: number,
|
||||
firstNewTxIdx: number,
|
||||
@@ -79,5 +84,5 @@ export interface DBClientInterface {
|
||||
signature: Signature;
|
||||
}): Promise<number> | void | unknown;
|
||||
|
||||
unitOfWork(operationsCallback: () => unknown[]): Promise<unknown> | void;
|
||||
transaction(callback: () => unknown): Promise<unknown> | void;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
# cojson-transport-nodejs-ws
|
||||
|
||||
## 0.10.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [153dc99]
|
||||
- cojson@0.10.8
|
||||
|
||||
## 0.10.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [0f83320]
|
||||
- Updated dependencies [012022d]
|
||||
- cojson@0.10.7
|
||||
|
||||
## 0.10.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "cojson-transport-ws",
|
||||
"type": "module",
|
||||
"version": "0.10.6",
|
||||
"version": "0.10.8",
|
||||
"main": "dist/index.js",
|
||||
"types": "src/index.ts",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
# cojson
|
||||
|
||||
## 0.10.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 153dc99: Catch errors on CoValueCore subscribers to avoid effects on the sync
|
||||
|
||||
## 0.10.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 0f83320: Use jazz-crypto-rs isomorphic bundle
|
||||
- 012022d: Improve error logging on sync errors
|
||||
|
||||
## 0.10.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
},
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"version": "0.10.6",
|
||||
"version": "0.10.8",
|
||||
"devDependencies": {
|
||||
"@opentelemetry/sdk-metrics": "^1.29.0",
|
||||
"typescript": "~5.6.2",
|
||||
@@ -37,7 +37,7 @@
|
||||
"@noble/hashes": "^1.4.0",
|
||||
"@opentelemetry/api": "^1.0.0",
|
||||
"@scure/base": "1.2.1",
|
||||
"jazz-crypto-rs": "0.0.3",
|
||||
"jazz-crypto-rs": "0.0.6",
|
||||
"neverthrow": "^7.0.1",
|
||||
"queueueue": "^4.1.2"
|
||||
},
|
||||
|
||||
@@ -35,6 +35,7 @@ import { CoValueKnownState, NewContentMessage } from "./sync.js";
|
||||
import { accountOrAgentIDfromSessionID } from "./typeUtils/accountOrAgentIDfromSessionID.js";
|
||||
import { expectGroup } from "./typeUtils/expectGroup.js";
|
||||
import { isAccountID } from "./typeUtils/isAccountID.js";
|
||||
import { parseError } from "./utils.js";
|
||||
|
||||
/**
|
||||
In order to not block other concurrently syncing CoValues we introduce a maximum size of transactions,
|
||||
@@ -326,7 +327,14 @@ export class CoValueCore {
|
||||
if (notifyMode === "immediate") {
|
||||
const content = this.getCurrentContent();
|
||||
for (const listener of this.listeners) {
|
||||
listener(content);
|
||||
try {
|
||||
listener(content);
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
"Error in listener for coValue " + this.id,
|
||||
parseError(e),
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!this.nextDeferredNotify) {
|
||||
@@ -336,7 +344,14 @@ export class CoValueCore {
|
||||
this.deferredUpdates = 0;
|
||||
const content = this.getCurrentContent();
|
||||
for (const listener of this.listeners) {
|
||||
listener(content);
|
||||
try {
|
||||
listener(content);
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
"Error in listener for coValue " + this.id,
|
||||
parseError(e),
|
||||
);
|
||||
}
|
||||
}
|
||||
resolve();
|
||||
}, 0);
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
encrypt,
|
||||
get_sealer_id,
|
||||
get_signer_id,
|
||||
initialize,
|
||||
new_ed25519_signing_key,
|
||||
new_x25519_private_key,
|
||||
seal,
|
||||
@@ -49,6 +50,7 @@ export class WasmCrypto extends CryptoProvider<Blake3State> {
|
||||
}
|
||||
|
||||
static async create(): Promise<WasmCrypto> {
|
||||
await initialize();
|
||||
return new WasmCrypto();
|
||||
}
|
||||
|
||||
|
||||
@@ -341,20 +341,8 @@ export class SyncManager {
|
||||
});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await this.handleSyncMessage(msg, peerState);
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`Error reading from peer ${
|
||||
peer.id
|
||||
}, handling msg\n\n${JSON.stringify(msg, (k, v) =>
|
||||
k === "changes" || k === "encryptedChanges"
|
||||
? v.slice(0, 20) + "..."
|
||||
: v,
|
||||
)}`,
|
||||
{ cause: e },
|
||||
);
|
||||
}
|
||||
|
||||
await this.handleSyncMessage(msg, peerState);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ import { LocalNode } from "../localNode.js";
|
||||
import { Role } from "../permissions.js";
|
||||
import {
|
||||
createTestNode,
|
||||
createTwoConnectedNodes,
|
||||
loadCoValueOrFail,
|
||||
randomAnonymousAccountAndSessionID,
|
||||
} from "./testUtils.js";
|
||||
|
||||
@@ -221,3 +223,38 @@ test("creating a coValue with a group should't trigger automatically a content c
|
||||
|
||||
getCurrentContentSpy.mockRestore();
|
||||
});
|
||||
|
||||
test("listeners are notified even if the previous listener threw an error", async () => {
|
||||
const { node1, node2 } = await createTwoConnectedNodes("server", "server");
|
||||
|
||||
const group = node1.node.createGroup();
|
||||
group.addMember("everyone", "writer");
|
||||
|
||||
const coMap = group.createMap();
|
||||
|
||||
const spy1 = vi.fn();
|
||||
const spy2 = vi.fn();
|
||||
|
||||
coMap.subscribe(spy1);
|
||||
coMap.subscribe(spy2);
|
||||
|
||||
spy1.mockImplementation(() => {
|
||||
throw new Error("test");
|
||||
});
|
||||
|
||||
const errorLog = vi.spyOn(console, "error").mockImplementation(() => {});
|
||||
|
||||
coMap.set("hello", "world");
|
||||
|
||||
expect(spy1).toHaveBeenCalledTimes(2);
|
||||
expect(spy2).toHaveBeenCalledTimes(2);
|
||||
expect(errorLog).toHaveBeenCalledTimes(1);
|
||||
|
||||
await coMap.core.waitForSync();
|
||||
|
||||
const mapOnNode2 = await loadCoValueOrFail(node2.node, coMap.id);
|
||||
|
||||
expect(mapOnNode2.get("hello")).toBe("world");
|
||||
|
||||
errorLog.mockRestore();
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import { ControlledAgent } from "../coValues/account.js";
|
||||
import { WasmCrypto } from "../crypto/WasmCrypto.js";
|
||||
import { expectGroup } from "../typeUtils/expectGroup.js";
|
||||
import {
|
||||
connectTwoPeers,
|
||||
createTwoConnectedNodes,
|
||||
groupWithTwoAdmins,
|
||||
groupWithTwoAdminsHighLevel,
|
||||
@@ -2029,9 +2030,13 @@ test("Can give write permissions to 'everyone' (high-level)", async () => {
|
||||
.getCurrentContent(),
|
||||
);
|
||||
|
||||
connectTwoPeers(group.core.node, childContent2.core.node, "server", "server");
|
||||
|
||||
// Ensure that the group is available to newAccount
|
||||
await group.core.waitForSync();
|
||||
|
||||
expect(childContent2.get("foo")).toEqual("bar");
|
||||
|
||||
console.log("Before anon set");
|
||||
childContent2.set("foo", "bar2", "private");
|
||||
expect(childContent2.get("foo")).toEqual("bar2");
|
||||
});
|
||||
|
||||
9
packages/cojson/src/utils.ts
Normal file
9
packages/cojson/src/utils.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export function parseError(e: unknown): {
|
||||
message: string | null;
|
||||
stack: string | null;
|
||||
} {
|
||||
return {
|
||||
message: e instanceof Error ? e.message : null,
|
||||
stack: e instanceof Error ? (e.stack ?? null) : null,
|
||||
};
|
||||
}
|
||||
@@ -238,6 +238,36 @@ module.exports = withNativeWind(config, { input: "./src/global.css" });
|
||||
}
|
||||
}
|
||||
|
||||
// Step 5: Clone cursor-docs
|
||||
const docsSpinner = ora({
|
||||
text: chalk.blue(`Adding cursor-docs...`),
|
||||
spinner: "dots",
|
||||
}).start();
|
||||
|
||||
try {
|
||||
// Create a temporary directory for cursor-docs
|
||||
const tempDocsDir = `${projectName}-cursor-docs-temp`;
|
||||
const emitter = degit("garden-co/jazz/packages/cursor-docs", {
|
||||
cache: false,
|
||||
force: true,
|
||||
verbose: true,
|
||||
});
|
||||
|
||||
// Clone cursor-docs to temp directory
|
||||
await emitter.clone(tempDocsDir);
|
||||
|
||||
// Copy contents to project root
|
||||
fs.cpSync(tempDocsDir, projectName, { recursive: true });
|
||||
|
||||
// Clean up temp directory
|
||||
fs.rmSync(tempDocsDir, { recursive: true, force: true });
|
||||
|
||||
docsSpinner.succeed(chalk.green("Cursor docs added successfully"));
|
||||
} catch (error) {
|
||||
docsSpinner.fail(chalk.red("Failed to add cursor docs"));
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Final success message
|
||||
console.log("\n" + chalk.green.bold("✨ Project setup completed! ✨\n"));
|
||||
console.log(chalk.cyan("To get started:"));
|
||||
|
||||
28
packages/cursor-docs/.cursor/rules/jazz-rule.mdc
Normal file
28
packages/cursor-docs/.cursor/rules/jazz-rule.mdc
Normal file
@@ -0,0 +1,28 @@
|
||||
---
|
||||
description: Creating Jazz Schema Rule
|
||||
globs: *.ts, *.tsx
|
||||
---
|
||||
|
||||
# Creating Jazz Schema Rule
|
||||
You are a helpful AI assistant specialized in software engineering, TypeScript, Jazz.
|
||||
Jazz is a TypeScript framework for building local-first apps.
|
||||
Users can ask your help with anything related to Jazz, including creating Jazz schemas.
|
||||
|
||||
If the user needs help with creating or refining a Jazz Schema, follow this agentic protocol:
|
||||
1) read the Jazz Docs [1_jazz_docs.md](mdc:packages/cursor-docs/docs/1_jazz_docs.md)
|
||||
2) read each example one by one:
|
||||
- [4_1_example_without_specs.md](mdc:docs/4_1_example_without_specs.md) for example app 1: A secure and organized password manager app that allows users to store, manage, and categorize their credentials in folders
|
||||
- [4_2_example_without_specs.md](mdc:docs/4_2_example_without_specs.md) for example app 2: A feature-rich music player app that allows users to manage playlists, store tracks, and visualize audio waveforms
|
||||
- @4_3_example_without_specs.md for example app 3: A social pet app where users can share pet photos, react with fun emojis, and organize posts in a collaborative feed
|
||||
- [4_4_example_without_specs.md](mdc:docs/4_4_example_without_specs.md) for example app 4: A bubble tea ordering app that lets users customize drinks with different tea bases, add-ons, and delivery preferences
|
||||
- [4_5_example_without_specs.md](mdc:docs/4_5_example_without_specs.md) for example app 5: An employee onboarding app that streamlines the hiring process through structured steps, including initial data collection, document uploads, and final approvals
|
||||
- [4_6_example_without_specs.md](mdc:docs/4_6_example_without_specs.md) for example app 6: A task management app that helps users organize their to-dos with categories, tags, due dates, and priority levels
|
||||
3) read the Jazz Schema template you have to follow [2_jazz_schema_template.md](mdc:docs/2_jazz_schema_template.md)
|
||||
4) read the rules for creating Jazz Schema [3_jazz_rules.md](mdc:docs/3_jazz_rules.md)
|
||||
|
||||
When processing files:
|
||||
1. MUST validate each file was read
|
||||
2. MUST process files in sequence
|
||||
3. MUST confirm completion before continuing
|
||||
|
||||
After you followed all four steps, continue with correctly creating the schema based on what you learned.
|
||||
171
packages/cursor-docs/.gitignore
vendored
Normal file
171
packages/cursor-docs/.gitignore
vendored
Normal file
@@ -0,0 +1,171 @@
|
||||
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
|
||||
|
||||
# Logs
|
||||
|
||||
logs
|
||||
_.log
|
||||
npm-debug.log_
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
|
||||
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||
|
||||
# Runtime data
|
||||
|
||||
pids
|
||||
_.pid
|
||||
_.seed
|
||||
\*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
|
||||
coverage
|
||||
\*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
|
||||
\*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
|
||||
\*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
|
||||
.cache/
|
||||
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
|
||||
.temp
|
||||
.cache
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.\*
|
||||
|
||||
.DS_Store
|
||||
2
packages/cursor-docs/.npmignore
Normal file
2
packages/cursor-docs/.npmignore
Normal file
@@ -0,0 +1,2 @@
|
||||
coverage
|
||||
node_modules
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user