Compare commits

..

30 Commits

Author SHA1 Message Date
Benjamin S. Leveritt
7a3cfabb4c Merge pull request #2481 from garden-co/changeset-release/main
Version Packages
2025-06-11 09:49:08 +01:00
github-actions[bot]
df7101a8ee Version Packages 2025-06-11 08:47:01 +00:00
Benjamin S. Leveritt
f74b46ee2f Merge pull request #2495 from garden-co/2494-applydiff-out-of-bounds-for-strings-with-emoji
Fix: `applyDiff` out of bounds for strings with emoji
2025-06-11 09:43:30 +01:00
Benjamin S. Leveritt
9177579f53 Fixs coText applyDiff out of bounds insertion with emoji 2025-06-11 09:35:56 +01:00
Benjamin S. Leveritt
f1c00903f9 Removes unused navigator code 2025-06-11 09:34:06 +01:00
Benjamin S. Leveritt
1ca9299590 Adds grapheme split helpers for coText 2025-06-11 09:33:45 +01:00
Benjamin S. Leveritt
34082ccaf5 Merge pull request #2486 from garden-co/add-context-to-twoslash-errors
Adds more details to twoslash errors
2025-06-10 17:40:55 +01:00
Benjamin S. Leveritt
a09c417d81 Formats title and code to make is easier to spot 2025-06-10 17:36:41 +01:00
Benjamin S. Leveritt
00a188c22f Merge pull request #2484 from garden-co/2483-encryption-in-jazz-docs
2483 encryption in jazz docs
2025-06-10 16:39:31 +01:00
Benjamin S. Leveritt
dc630b0807 Update homepage/homepage/content/docs/resources/encryption.mdx
Co-authored-by: Anselm Eickhoff <anselm.eickhoff@gmail.com>
2025-06-10 16:39:20 +01:00
Benjamin S. Leveritt
484baabe22 Updates introduction sentence as per feedback 2025-06-10 16:38:16 +01:00
Benjamin S. Leveritt
f529bede7b Adds more details to twoslash errors
To make it easier to track down
2025-06-10 16:37:01 +01:00
Trisha Lim
6f637d21ab Merge pull request #2446 from garden-co/feat/analytics
feat: track create-jazz-app copy action in hero
2025-06-10 15:54:23 +01:00
Benjamin S. Leveritt
aeb96510da Adds a section to the FAQ 2025-06-10 15:31:20 +01:00
Benjamin S. Leveritt
d53cc3676d Adds Encryption reference doc 2025-06-10 15:23:13 +01:00
Guido D'Orsi
81dedb6395 Merge pull request #2448 from garden-co/fix/react-native-db-connection
fix(react-native): close db connections and keep the same instance of storage over JazzProvider renders
2025-06-10 12:24:19 +02:00
Nikos Papadopoulos
17cb04bfb1 Merge pull request #2478 from garden-co/2477-fix-mime-types-for-favicon-in-all-example-apps
fixes favicon mime type for example apps
2025-06-10 12:22:39 +02:00
Nikos Papadopoulos
a98b4e5d81 updates links to public directory 2025-06-09 23:13:47 +01:00
Nikos Papadopoulos
7eb7e2f656 reorders metadata tags for consistency across example apps 2025-06-09 20:22:36 +01:00
Nikos Papadopoulos
80703ea1cc tidy up for metadata tags 2025-06-09 20:15:49 +01:00
Nikos Papadopoulos
d46e0b9d0c fixes favicon mime type, replaces png with ico files where applicable, moves favicon to ./public for consistency 2025-06-09 20:00:45 +01:00
Nikos Papadopoulos
430a9e252a Merge pull request #2475 from garden-co/2474-fix-favicon-for-richtext-prosekit-and-organization-examples
fixes missing favicon for richtext-prosekit and organization examples
2025-06-09 16:26:19 +02:00
Nikos Papadopoulos
6f519462df fixes missing favicon for richtext-prosekit and organization examples 2025-06-09 15:13:41 +01:00
Guido D'Orsi
a0ae2811ce test: wait for Join chat to be visible 2025-06-09 15:51:01 +02:00
Guido D'Orsi
73e5a3548a fix: make chat-rn example private 2025-06-09 13:03:04 +02:00
Guido D'Orsi
c5da3a42a1 fix: remove version field from examples to exclude them from the changelog 2025-06-09 12:49:07 +02:00
Guido D'Orsi
8ef14d4850 fix: do not create a storage adapter on each JazzProvider render 2025-06-05 18:22:00 +02:00
Guido D'Orsi
1a7b7942ad test(expo): restore the chat loading test 2025-06-05 18:22:00 +02:00
Guido D'Orsi
5f42c97184 fix: close the DB connection when the node/context is closed 2025-06-05 18:22:00 +02:00
Trisha Lim
2a2b474aa4 feat: track create-jazz-app copy action in hero 2025-06-05 16:16:06 +01:00
151 changed files with 693 additions and 160 deletions

View File

@@ -1,6 +1,5 @@
{
"name": "betterauth",
"version": "0.1.25",
"private": true,
"type": "module",
"scripts": {

View File

@@ -1,7 +1,6 @@
{
"name": "chat-rn-expo-clerk",
"main": "index.js",
"version": "1.0.145",
"scripts": {
"build": "expo export -p ios",
"start": "expo start",

View File

@@ -1,6 +1,5 @@
{
"name": "chat-rn-expo",
"version": "1.0.13",
"main": "index.ts",
"scripts": {
"build": "expo prebuild",

View File

@@ -128,7 +128,11 @@ export default function ChatScreen() {
}}
testID="chat-id-input"
/>
<TouchableOpacity onPress={joinChat} style={styles.joinChatButton}>
<TouchableOpacity
testID="join-chat-button"
onPress={joinChat}
style={styles.joinChatButton}
>
<Text style={styles.newChatButtonText}>Join chat</Text>
</TouchableOpacity>
</View>

View File

@@ -41,12 +41,17 @@ appId: tools.jazz.chatrnexpo
# logout
- tapOn: "Logout"
- assertVisible: "Username"
- assertVisible: "Anonymous user"
# join chat
- extendedWaitUntil:
visible: "Anonymous user"
timeout: 10000
# join chat
## Commented because it fails on CI
# - tapOn:
# id: "chat-id-input"
# - inputText: "co_zFs6KFyhxPw4xtw83tcEMzeHUNv" # Use a static id because maestro doesn't have access to the system clipboard
# - tapOn: "Join chat"
# - tapOn:
# id: "join-chat-button"
# - assertVisible: "boorad"
# - assertVisible: "bro, low key, it do be like that tho"

View File

@@ -0,0 +1,13 @@
#!/bin/bash
# This script is necessary, because unlike ios, the android emulator action
# accepts a script, runs it as your tests, then terminates.
set -e
# run the e2e tests
export PATH="$PATH":"$HOME/.maestro/bin"
export MAESTRO_DRIVER_STARTUP_TIMEOUT=300000 # setting to 5 mins 👀
export MAESTRO_CLI_NO_ANALYTICS=1
export MAESTRO_CLI_ANALYSIS_NOTIFICATION_DISABLED=true
maestro test test/e2e/flow.yml

View File

@@ -1,6 +1,6 @@
{
"name": "chat-rn",
"version": "1.0.140",
"private": true,
"main": "index.js",
"scripts": {
"android": "react-native run-android",

View File

@@ -1,9 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="./public/favicon.ico" type="image/png">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="UTF-8" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jazz Chat Vue Example</title>
</head>
<body>

View File

@@ -1,6 +1,5 @@
{
"name": "chat-vue",
"version": "0.0.120",
"private": true,
"type": "module",
"scripts": {

View File

@@ -2,7 +2,7 @@
<html lang="en" class="h-full">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/jazz-logo.png" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<link rel="stylesheet" href="/src/index.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jazz Chat Example</title>

View File

@@ -1,7 +1,6 @@
{
"name": "jazz-example-chat",
"private": true,
"version": "0.0.220",
"type": "module",
"scripts": {
"dev": "vite",

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Minimal Auth Clerk Example | Jazz</title>
</head>

View File

@@ -1,7 +1,6 @@
{
"name": "clerk",
"private": true,
"version": "0.0.119",
"type": "module",
"scripts": {
"dev": "vite",

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,6 +1,5 @@
{
"name": "file-share-svelte",
"version": "0.0.104",
"private": true,
"type": "module",
"scripts": {

View File

@@ -1,8 +1,8 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta charset="utf-8" />
<link rel="icon" type="image/png" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>

View File

@@ -2,7 +2,7 @@
<html lang="en" class="h-full">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="./public/favicon.ico" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jazz | React + Demo Auth + Tailwind</title>
</head>

View File

@@ -1,7 +1,6 @@
{
"name": "filestream",
"private": true,
"version": "0.0.59",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -2,9 +2,9 @@
<html lang="en" class="h-full">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jazz | Form example</title>
<link rel="icon" type="image/png" href="./public/favicon.ico">
</head>
<body class="h-full flex flex-col bg-white text-stone-700 dark:text-stone-400 dark:bg-stone-925">
<div id="root" class="align-self-center flex-1"></div>

View File

@@ -1,7 +1,6 @@
{
"name": "form",
"private": true,
"version": "0.1.60",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -2,9 +2,9 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jazz | Image upload example</title>
<link rel="icon" href="./public/favicon.ico" type="image/png">
</head>
<body>
<div id="root"></div>

View File

@@ -1,7 +1,6 @@
{
"name": "image-upload",
"private": true,
"version": "0.0.116",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/jazz-logo.png" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<link rel="stylesheet" href="/src/index.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jazz Inspector</title>

View File

@@ -1,7 +1,6 @@
{
"name": "jazz-inspector-app",
"private": true,
"version": "0.0.169",
"type": "module",
"scripts": {
"dev": "vite",

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,6 +1,5 @@
{
"name": "jazz-nextjs",
"version": "0.1.9",
"private": true,
"scripts": {
"dev": "next dev --turbopack",

View File

@@ -2,8 +2,8 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="/favicon.ico" />
<meta name="theme-color" content="#000000" />
<meta
name="description"

View File

@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/favicon.ico" />
<link rel="icon" type="image/x-icon" href="./favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jazz | Multi-cursors</title>
</head>

View File

@@ -1,7 +1,6 @@
{
"name": "multi-cursors",
"private": true,
"version": "0.0.112",
"type": "module",
"scripts": {
"dev": "vite",

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -2,6 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/x-icon" href="./favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jazz Multi-auth (React)</title>
</head>

View File

@@ -1,7 +1,6 @@
{
"name": "multiauth",
"private": true,
"version": "0.0.60",
"type": "module",
"scripts": {
"dev": "vite",

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/jazz-logo.png" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jazz - Music Player example</title>
</head>

View File

@@ -1,7 +1,6 @@
{
"name": "jazz-example-music-player",
"private": true,
"version": "0.0.141",
"type": "module",
"scripts": {
"dev": "vite",

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -2,8 +2,9 @@
<html lang="en" class="h-full">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jazz | Organization example</title>
<title>Jazz - Organization example</title>
</head>
<body class="h-full flex flex-col bg-gray-50 text-stone-700 dark:text-stone-400 dark:bg-stone-925">
<div id="root" class="align-self-center flex-1"></div>

View File

@@ -1,7 +1,6 @@
{
"name": "organization",
"private": true,
"version": "0.0.112",
"type": "module",
"scripts": {
"dev": "vite",

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,6 +1,5 @@
{
"name": "passkey-svelte",
"version": "0.0.106",
"type": "module",
"private": true,
"scripts": {

View File

@@ -2,10 +2,9 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jazz | Minimal Auth Passkey Example</title>
<link rel="icon" href="./public/favicon.ico" type="image/png">
</head>
<body>
<div id="root"></div>

View File

@@ -1,7 +1,6 @@
{
"name": "passkey",
"private": true,
"version": "0.0.117",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jazz | Minimal Auth Passphrase Example</title>
</head>

View File

@@ -1,7 +1,6 @@
{
"name": "passphrase",
"private": true,
"version": "0.0.114",
"type": "module",
"scripts": {
"dev": "vite",

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -2,9 +2,9 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/x-icon" href="./favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jazz Password Manager Example</title>
<link rel="icon" type="image/png" href="./public/favicon.ico">
</head>
<body>
<div id="root"></div>

View File

@@ -1,7 +1,6 @@
{
"name": "jazz-password-manager",
"private": true,
"version": "0.0.138",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/jazz-logo.png" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jazz Rate My Pet Example</title>
</head>

View File

@@ -1,7 +1,6 @@
{
"name": "jazz-example-pets",
"private": true,
"version": "0.0.236",
"type": "module",
"scripts": {
"dev": "vite",

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -2,6 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jazz | Reactions example</title>
</head>

View File

@@ -1,7 +1,6 @@
{
"name": "reactions",
"private": true,
"version": "0.0.116",
"type": "module",
"scripts": {
"dev": "vite",

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/favicon.ico" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Rich text ProseKit example app | Jazz</title>
</head>

View File

@@ -1,6 +1,5 @@
{
"name": "example-prosekit-jazz",
"version": "0.0.1",
"private": true,
"type": "module",
"scripts": {

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -2,7 +2,7 @@
<html lang="en" class="h-full">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/favicon.ico" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Rich text example app | Jazz</title>
</head>

View File

@@ -1,7 +1,6 @@
{
"name": "richtext",
"private": true,
"version": "0.0.106",
"type": "module",
"scripts": {
"dev": "vite",

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -2,7 +2,7 @@
<html lang="en" class="h-full">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/favicon.ico" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Rich text Tiptap example app | Jazz</title>
</head>

View File

@@ -1,7 +1,6 @@
{
"name": "richtext-tiptap",
"private": true,
"version": "0.1.29",
"type": "module",
"scripts": {
"dev": "vite",

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,10 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="./public/favicon.ico" type="image/png">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
<meta charset="UTF-8" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jazz Todo List Example</title>
</head>
<body>
<div id="app"></div>

View File

@@ -1,6 +1,5 @@
{
"name": "todo-vue",
"version": "0.0.118",
"private": true,
"type": "module",
"scripts": {

View File

@@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/jazz-logo.png" />
<link rel="icon" type="image/x-icon" href="./favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jazz Todo List Example</title>
</head>

View File

@@ -1,7 +1,6 @@
{
"name": "jazz-example-todo",
"private": true,
"version": "0.0.235",
"type": "module",
"scripts": {
"dev": "vite",

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -2,7 +2,7 @@
<html lang="en" class="h-full">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/favicon.ico" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jazz | Version History Example</title>
</head>

View File

@@ -1,7 +1,6 @@
{
"name": "version-history",
"private": true,
"version": "0.0.114",
"type": "module",
"scripts": {
"dev": "vite",

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -8,7 +8,13 @@ export function CopyButton({
code,
size,
className,
}: { code: string; size: "md" | "lg"; className?: string }) {
onCopy,
}: {
code: string;
size: "md" | "lg";
className?: string;
onCopy?: () => void;
}) {
const [copyCount, setCopyCount] = useState(0);
const copied = copyCount > 0;
@@ -38,6 +44,7 @@ export function CopyButton({
window.navigator.clipboard.writeText(code).then(() => {
setCopyCount((count) => count + 1);
});
onCopy?.();
}}
>
<span

View File

@@ -1,3 +1,5 @@
"use client";
import CreateJazzApp from "@/components/home/CreateJazzApp.mdx";
import { marketingCopy } from "@/content/marketingCopy";
import { H1 } from "@garden-co/design-system/src/components/atoms/Headings";
@@ -9,6 +11,7 @@ import { Kicker } from "@garden-co/design-system/src/components/atoms/Kicker";
import { CopyButton } from "@garden-co/design-system/src/components/molecules/CodeGroup";
import { Prose } from "@garden-co/design-system/src/components/molecules/Prose";
import { SectionHeader } from "@garden-co/design-system/src/components/molecules/SectionHeader";
import { track } from "@vercel/analytics";
import Link from "next/link";
const features: Array<{
@@ -107,6 +110,7 @@ export function HeroSection() {
code="npx create-jazz-app@latest"
size="md"
className="mt-0.5 mr-0.5"
onCopy={() => track("create-jazz-app command copied from hero")}
/>
</div>
<div className="p-3">

View File

@@ -306,6 +306,11 @@ export const docNavigationItems = [
{
name: "Resources",
items: [
{
name: "Encryption",
href: "/docs/resources/encryption",
done: 100,
},
{
name: "Jazz under the hood",
href: "/docs/jazz-under-the-hood",

View File

@@ -19,3 +19,16 @@ That's why we've designed Jazz with longevity in mind from the start:
This approach creates a foundation that can continue regardless of any single company's involvement. The local-first architecture means your apps will always work, even offline, and your data remains yours.
## How secure is my data?
Jazz encrypts all your data by default using modern cryptographic standards. Every transaction is cryptographically signed, and data is encrypted using industry-standard algorithms including Blake3 hashing, ed25519 signatures, and xsalsa20 stream ciphers.
Key features of Jazz's security:
- **Privacy by default**: Your data is encrypted even on Jazz Cloud servers
- **Automatic key rotation**: When members are removed from Groups, encryption keys rotate automatically
- **Verifiable authenticity**: Every change is cryptographically signed
- **Zero-trust architecture**: Only people you explicitly grant access can read your data
For technical details, see our [encryption documentation](/docs/resources/encryption).

View File

@@ -0,0 +1,80 @@
export const metadata = {
description: "How Jazz encrypts your data to ensure privacy and security."
};
# Encryption
Jazz uses proven cryptographic primitives in a novel, but simple protocol to implement auditable permissions while allowing real-time collaboration and offline editing.
## How encryption works
Jazz uses proven cryptographic primitives in a novel, but simple protocol to implement auditable permissions while allowing real-time collaboration and offline editing.
### Write permissions: Signing with your keys
When you create or modify CoValues, Jazz cryptographically signs every transaction:
- All transactions are signed with your account's signing keypair
- This proves the transaction came from you
- Whether transactions are valid depends on your permissions in the Group that owns the CoValue
- Groups have internal logic ensuring only admins can change roles or create invites
- You can add yourself to a Group only with a specific role via invites
### Read permissions: Symmetric encryption
Groups use a shared "read key" for encrypting data:
- Admins reveal this symmetric encryption key to accounts with "reader" role or higher
- All transactions in CoValues owned by that Group are encrypted with the current read key
- When someone is removed from a Group, the read key rotates and gets revealed to all remaining members
- CoValues start using the new read key for future transactions
This means removed members can't read new data, but existing data they already had access to remains readable to them.
## Key rotation and security
Jazz automatically handles key management:
- **Member removal triggers rotation**: When you remove someone from a Group, Jazz generates a new read key
- **Seamless transition**: New transactions use the new key immediately
- **No data loss**: Existing members get the new key automatically
## Streaming encryption
Jazz encrypts data efficiently for real-time collaboration:
- **Incremental hashing**: CoValue sessions use [Blake3](https://github.com/BLAKE3-team/BLAKE3) for append-only hashing
- **Session signatures**: Each session is signed with [ed25519](https://ed25519.cr.yp.to/) after each transaction
- **Stream ciphers**: Data is encrypted using [xsalsa20](https://cr.yp.to/salsa20.html) stream cipher
- **Integrity protection**: Hashing and signing ensure data hasn't been tampered with
## Content addressing
CoValue IDs are the [Blake3](https://github.com/BLAKE3-team/BLAKE3) hash of their immutable "header" (containing CoValue type and owning group). This allows CoValues to be "content addressed" while remaining dynamic and changeable.
## What this means for you
**Privacy by default**: Your data is always encrypted, even on Jazz Cloud servers. Only people you explicitly give access to can read your data.
**Flexible permissions**: Use Groups to control exactly who can read, write, or admin your CoValues.
**Automatic security**: Key rotation and encryption happen behind the scenes - you don't need to think about it.
**Verifiable authenticity**: Every change is cryptographically signed, so you always know who made what changes.
## Further reading
- [Blake3](https://github.com/BLAKE3-team/BLAKE3) - append-only hashing
- [ed25519](https://ed25519.cr.yp.to/) - signature scheme
- [xsalsa20](https://cr.yp.to/salsa20.html) - stream cipher for data encryption
### Implementation details
The cryptographic primitives are implemented in the [`cojson/src/crypto`](https://github.com/garden-co/jazz/tree/main/packages/cojson/src/crypto) package.
Key files to explore:
- [`permissions.ts`](https://github.com/garden-co/jazz/blob/main/packages/cojson/src/permissions.ts) - Permission logic
- [`permissions.test.ts`](https://github.com/garden-co/jazz/blob/main/packages/cojson/src/tests/permissions.test.ts) - Permission tests
- [`verifiedState.ts`](https://github.com/garden-co/jazz/blob/main/packages/cojson/src/coValueCore/verifiedState.ts) - State verification
- [`coValueCore.test.ts`](https://github.com/garden-co/jazz/blob/main/packages/cojson/src/tests/coValueCore.test.ts) - Core functionality tests

View File

@@ -52,7 +52,16 @@ const config = {
};
const highlighterPromise = createHighlighter({
langs: ["typescript", "bash", "tsx", "json", "ruby", "groovy", "svelte", "vue"],
langs: [
"typescript",
"bash",
"tsx",
"json",
"ruby",
"groovy",
"svelte",
"vue",
],
themes: [jazzLight, jazzDark],
});
@@ -78,8 +87,12 @@ function highlightPlugin() {
throws: false, //process.env.NODE_ENV === "production",
onTwoslashError:
process.env.NODE_ENV !== "production"
? (e) => {
console.error(e);
? (e, code) => {
const { description, recommendation } = e;
console.error("\nTwoslash error: ");
console.log(description);
console.log(recommendation);
console.log("\nCode: \n```\n" + code + "\n```");
error = e;
}
: undefined,

View File

@@ -1,5 +1,14 @@
# cojson-storage-indexeddb
## 0.14.23
### Patch Changes
- Updated dependencies [1ca9299]
- Updated dependencies [5f42c97]
- cojson@0.14.23
- cojson-storage@0.14.23
## 0.14.22
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "cojson-storage-indexeddb",
"version": "0.14.22",
"version": "0.14.23",
"main": "dist/index.js",
"type": "module",
"types": "dist/index.d.ts",

View File

@@ -1,5 +1,15 @@
# cojson-storage-sqlite
## 0.14.23
### Patch Changes
- 5f42c97: Close the DB connection when the node/context is closed
- Updated dependencies [1ca9299]
- Updated dependencies [5f42c97]
- cojson@0.14.23
- cojson-storage@0.14.23
## 0.14.22
### Patch Changes

View File

@@ -1,13 +1,13 @@
{
"name": "cojson-storage-sqlite",
"type": "module",
"version": "0.14.22",
"version": "0.14.23",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"license": "MIT",
"dependencies": {
"better-sqlite3": "^11.7.0",
"cojson": "workspace:0.14.22",
"cojson": "workspace:0.14.23",
"cojson-storage": "workspace:*"
},
"devDependencies": {

View File

@@ -25,4 +25,8 @@ export class BetterSqliteDriver implements SQLiteDatabaseDriver {
transaction(callback: () => unknown) {
return this.db.transaction(callback)();
}
closeDb() {
this.db.close();
}
}

View File

@@ -3,9 +3,10 @@ import { unlinkSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { LocalNode, cojsonInternals } from "cojson";
import { StorageManagerSync } from "cojson-storage";
import { SQLiteNodeBase, StorageManagerSync } from "cojson-storage";
import { WasmCrypto } from "cojson/crypto/WasmCrypto";
import { expect, onTestFinished, test, vi } from "vitest";
import { BetterSqliteDriver } from "../betterSqliteDriver.js";
import { SQLiteNode } from "../index.js";
import { toSimplifiedMessages } from "./messagesTestUtils.js";
import { trackMessages, waitFor } from "./testUtils.js";
@@ -725,3 +726,39 @@ test("large coValue upload streaming", async () => {
]
`);
});
test("should close the db when the node is closed", async () => {
const agentSecret = Crypto.newRandomAgentSecret();
const node1 = new LocalNode(
agentSecret,
Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)),
Crypto,
);
const dbPath = join(tmpdir(), `test-${randomUUID()}.db`);
const db = new BetterSqliteDriver(dbPath);
const peer = SQLiteNodeBase.create({
db,
localNodeName: "test",
maxBlockingTime: 500,
});
const spy = vi.spyOn(db, "closeDb");
node1.syncManager.addPeer(peer);
await new Promise((resolve) => setTimeout(resolve, 10));
expect(spy).not.toHaveBeenCalled();
node1.gracefulShutdown();
await new Promise((resolve) => setTimeout(resolve, 10));
expect(spy).toHaveBeenCalled();
unlinkSync(dbPath);
});

View File

@@ -1,5 +1,13 @@
# cojson-storage
## 0.14.23
### Patch Changes
- 5f42c97: Close the DB connection when the node/context is closed
- Updated dependencies [1ca9299]
- cojson@0.14.23
## 0.14.22
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "cojson-storage",
"version": "0.14.22",
"version": "0.14.23",
"main": "dist/index.js",
"type": "module",
"types": "dist/index.d.ts",

View File

@@ -59,6 +59,8 @@ export class SQLiteNodeBase {
});
}
}
db.closeDb();
};
processMessages().catch((e) =>

View File

@@ -3,4 +3,5 @@ export interface SQLiteDatabaseDriver {
get<T>(sql: string, params: unknown[]): T | undefined;
query<T>(sql: string, params: unknown[]): T[];
transaction(callback: () => unknown): void;
closeDb(): void;
}

View File

@@ -37,6 +37,10 @@ export class SQLiteNodeBaseAsync {
});
}
}
db.closeDb().catch((e) =>
logger.error("Error closing sqlite", { err: e }),
);
};
processMessages().catch((e) =>

View File

@@ -4,4 +4,5 @@ export interface SQLiteDatabaseDriverAsync {
query<T>(sql: string, params: unknown[]): Promise<T[]>;
get<T>(sql: string, params: unknown[]): Promise<T | undefined>;
transaction(callback: () => unknown): Promise<unknown>;
closeDb(): Promise<unknown>;
}

View File

@@ -48,6 +48,10 @@ class LibSQLSqliteDriver implements SQLiteDatabaseDriverAsync {
await this.run("ROLLBACK", []);
}
}
async closeDb() {
this.db.close();
}
}
async function createSQLiteStorage(defaultDbPath?: string) {
@@ -66,6 +70,7 @@ async function createSQLiteStorage(defaultDbPath?: string) {
db,
}),
dbPath,
db,
};
}
@@ -753,3 +758,29 @@ test("large coValue upload streaming", async () => {
]
`);
});
test("should close the db when the node is closed", async () => {
const agentSecret = Crypto.newRandomAgentSecret();
const node1 = new LocalNode(
agentSecret,
Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)),
Crypto,
);
const { peer, db } = await createSQLiteStorage();
const spy = vi.spyOn(db, "closeDb");
node1.syncManager.addPeer(peer);
await new Promise((resolve) => setTimeout(resolve, 10));
expect(spy).not.toHaveBeenCalled();
node1.gracefulShutdown();
await new Promise((resolve) => setTimeout(resolve, 10));
expect(spy).toHaveBeenCalled();
});

View File

@@ -1,5 +1,12 @@
# cojson-transport-nodejs-ws
## 0.14.23
### Patch Changes
- Updated dependencies [1ca9299]
- cojson@0.14.23
## 0.14.22
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "cojson-transport-ws",
"type": "module",
"version": "0.14.22",
"version": "0.14.23",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"license": "MIT",

View File

@@ -1,5 +1,11 @@
# cojson
## 0.14.23
### Patch Changes
- 1ca9299: Adds grapheme split helpers for coText
## 0.14.22
### Patch Changes

View File

@@ -25,7 +25,7 @@
},
"type": "module",
"license": "MIT",
"version": "0.14.22",
"version": "0.14.23",
"devDependencies": {
"@opentelemetry/sdk-metrics": "^2.0.0",
"typescript": "catalog:"

View File

@@ -3,12 +3,6 @@ import { AvailableCoValueCore } from "../coValueCore/coValueCore.js";
import { JsonObject } from "../jsonValue.js";
import { DeletionOpPayload, OpID, RawCoList } from "./coList.js";
declare const navigator:
| {
language: string;
}
| undefined;
export type StringifiedOpID = string & { __stringifiedOpID: true };
export function stringifyOpID(opID: OpID): StringifiedOpID {
@@ -63,15 +57,6 @@ export class RawCoPlainText<
constructor(core: AvailableCoValueCore) {
super(core);
this._cachedMapping = new WeakMap();
// Use locale from meta if provided, fallback to browser locale, or 'en' as last resort
const effectiveLocale =
(core.verified.header.meta &&
typeof core.verified.header.meta === "object" &&
"locale" in core.verified.header.meta
? (core.verified.header.meta.locale as string)
: undefined) ||
(typeof navigator !== "undefined" ? navigator.language : "en");
}
get mapping() {
@@ -182,4 +167,14 @@ export class RawCoPlainText<
this.core.makeTransaction(ops, privacy);
this.processNewTransactions();
}
/** @internal Helper method to split text into graphemes */
toGraphemes(text: string): string[] {
return [...splitGraphemes(text)];
}
/** @internal Helper method to join graphemes into a string */
fromGraphemes(graphemes: string[]): string {
return graphemes.join("");
}
}

View File

@@ -265,3 +265,24 @@ test("Handle deletion of complex grapheme clusters correctly", () => {
content.deleteRange({ from: 1, to: 2 }, "trusting");
expect(content.toString()).toEqual(" 녕!");
});
test("Splits into and from grapheme string arrays", () => {
const node = nodeWithRandomAgentAndSessionID();
const coValue = node.createCoValue({
type: "coplaintext",
ruleset: { type: "unsafeAllowAll" },
meta: null,
...Crypto.createdNowUnique(),
});
const content = expectPlainText(coValue.getCurrentContent());
content.insertAfter(0, "👋 안녕!", "trusting");
expect(content.toString()).toEqual("👋 안녕!");
const graphemes = content.toGraphemes("👋 안녕!");
expect(graphemes).toEqual(["👋", " ", "안", "녕", "!"]);
const text = content.fromGraphemes(graphemes);
expect(text).toEqual("👋 안녕!");
});

View File

@@ -1,5 +1,16 @@
# jazz-auth-betterauth
## 0.14.23
### Patch Changes
- Updated dependencies [1ca9299]
- Updated dependencies [9177579]
- cojson@0.14.23
- jazz-tools@0.14.23
- jazz-browser@0.14.23
- jazz-betterauth-client-plugin@0.14.23
## 0.14.22
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-auth-betterauth",
"version": "0.14.22",
"version": "0.14.23",
"type": "module",
"main": "dist/index.js",
"types": "src/index.ts",

View File

@@ -1,5 +1,15 @@
# jazz-auth-clerk
## 0.14.23
### Patch Changes
- Updated dependencies [1ca9299]
- Updated dependencies [9177579]
- cojson@0.14.23
- jazz-tools@0.14.23
- jazz-browser@0.14.23
## 0.14.22
### Patch Changes

View File

@@ -1,14 +1,14 @@
{
"name": "jazz-auth-clerk",
"version": "0.14.22",
"version": "0.14.23",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"license": "MIT",
"dependencies": {
"cojson": "workspace:0.14.22",
"jazz-browser": "workspace:0.14.22",
"jazz-tools": "workspace:0.14.22"
"cojson": "workspace:0.14.23",
"jazz-browser": "workspace:0.14.23",
"jazz-tools": "workspace:0.14.23"
},
"scripts": {
"format-and-lint": "biome check .",

Some files were not shown because too many files have changed in this diff Show More