Compare commits
77 Commits
jazz-react
...
feat/rust-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48279ed642 | ||
|
|
88dbfefd17 | ||
|
|
2939790335 | ||
|
|
48c29435bc | ||
|
|
8668906376 | ||
|
|
6d84e9e83f | ||
|
|
1fea0ef69c | ||
|
|
e4314accb6 | ||
|
|
ee3a4048ef | ||
|
|
9ee1edef3b | ||
|
|
8ab5a09a86 | ||
|
|
2624442903 | ||
|
|
2d199089d5 | ||
|
|
683c170b9d | ||
|
|
518406e23d | ||
|
|
4dcbafa058 | ||
|
|
7ae9e01848 | ||
|
|
dd9ecf660d | ||
|
|
4f849050dc | ||
|
|
681600220f | ||
|
|
384e239ad5 | ||
|
|
54e1a09a46 | ||
|
|
392a9c5aac | ||
|
|
478334eabf | ||
|
|
479f9b0113 | ||
|
|
812622b161 | ||
|
|
8b35fae4b6 | ||
|
|
9e2ecb0378 | ||
|
|
fe908b5300 | ||
|
|
1f99807971 | ||
|
|
47f54c4d81 | ||
|
|
814f65acaf | ||
|
|
78fd4c86a1 | ||
|
|
678f326bc1 | ||
|
|
ea33ad4864 | ||
|
|
57b6d5efb4 | ||
|
|
15929b121d | ||
|
|
6edd061202 | ||
|
|
eb4cef196c | ||
|
|
85703f9241 | ||
|
|
865d5385e9 | ||
|
|
5bb12523ee | ||
|
|
190cb1efb2 | ||
|
|
46ab1f6db2 | ||
|
|
a44fc6fc6d | ||
|
|
2376a8d3b2 | ||
|
|
2c3ec2fea6 | ||
|
|
da6f6ec4d5 | ||
|
|
8c78b37bfb | ||
|
|
5f382309de | ||
|
|
aa7eb3cf2c | ||
|
|
9b41762e96 | ||
|
|
28be460286 | ||
|
|
df8af06814 | ||
|
|
2ef460fccf | ||
|
|
9660e2c03c | ||
|
|
908645e4b7 | ||
|
|
f3ca37ed5e | ||
|
|
a9d0fd14c4 | ||
|
|
c496f49bb0 | ||
|
|
b26666ab4c | ||
|
|
9088a349a0 | ||
|
|
54b12dcb7a | ||
|
|
a998f94789 | ||
|
|
d17eecfe16 | ||
|
|
71b9a5ce25 | ||
|
|
8ebfbc86db | ||
|
|
abad8e762f | ||
|
|
037e16392e | ||
|
|
49ac65c123 | ||
|
|
3510fb1273 | ||
|
|
bc3efe7ca0 | ||
|
|
3b06a7809e | ||
|
|
58aa04bb10 | ||
|
|
afb94ef043 | ||
|
|
7554dd9f88 | ||
|
|
4f0fb6c27f |
5
.changeset/shaggy-cycles-carry.md
Normal file
5
.changeset/shaggy-cycles-carry.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"create-jazz-app": patch
|
||||
---
|
||||
|
||||
use new svelte starter
|
||||
2
.github/workflows/build-starters.yaml
vendored
2
.github/workflows/build-starters.yaml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2204
|
||||
strategy:
|
||||
matrix:
|
||||
starter: ["react-passkey-auth"]
|
||||
starter: ["react-passkey-auth", "svelte-passkey-auth"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
3
.github/workflows/playwright-homepage.yml
vendored
3
.github/workflows/playwright-homepage.yml
vendored
@@ -22,6 +22,9 @@ jobs:
|
||||
- name: Setup Source Code
|
||||
uses: ./.github/actions/source-code/
|
||||
|
||||
- name: Install root dependencies
|
||||
run: pnpm install && pnpm turbo build
|
||||
|
||||
- name: Install project dependencies
|
||||
run: pnpm install
|
||||
working-directory: ./${{ matrix.project }}
|
||||
|
||||
22
.github/workflows/playwright.yml
vendored
22
.github/workflows/playwright.yml
vendored
@@ -13,7 +13,21 @@ jobs:
|
||||
continue-on-error: true
|
||||
strategy:
|
||||
matrix:
|
||||
project: ["tests/e2e", "examples/chat", "examples/clerk", "examples/betterauth", "examples/file-share-svelte", "examples/form", "examples/music-player", "examples/organization", "examples/pets", "starters/react-passkey-auth"]
|
||||
project: [
|
||||
"tests/e2e",
|
||||
"examples/chat",
|
||||
"examples/clerk",
|
||||
"examples/betterauth",
|
||||
"examples/file-share-svelte",
|
||||
"examples/form",
|
||||
"examples/inspector",
|
||||
"examples/music-player",
|
||||
"examples/organization",
|
||||
"examples/pets",
|
||||
"starters/react-passkey-auth",
|
||||
"starters/svelte-passkey-auth",
|
||||
"packages/jazz-svelte"
|
||||
]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -24,7 +38,11 @@ jobs:
|
||||
uses: ./.github/actions/source-code/
|
||||
|
||||
- name: Pnpm Build
|
||||
run: pnpm turbo build
|
||||
run: |
|
||||
if [ -f .env.test ]; then
|
||||
cp .env.test .env
|
||||
fi
|
||||
pnpm turbo build
|
||||
working-directory: ./${{ matrix.project }}
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"**/android/**",
|
||||
"packages/jazz-svelte/**",
|
||||
"examples/*svelte*/**",
|
||||
"starters/*svelte*/**",
|
||||
"examples/jazz-paper-scissors/src/routeTree.gen.ts",
|
||||
"homepage/homepage/**",
|
||||
"**/package.json"
|
||||
|
||||
6
examples/betterauth/.gitignore
vendored
6
examples/betterauth/.gitignore
vendored
@@ -36,8 +36,10 @@ yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env*
|
||||
!.env
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
!.env.test
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_ZXZpZGVudC1kYW5lLTg5LmNsZXJrLmFjY291bnRzLmRldiQ
|
||||
1
examples/chat-rn-expo-clerk/.env.example
Normal file
1
examples/chat-rn-expo-clerk/.env.example
Normal file
@@ -0,0 +1 @@
|
||||
EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=
|
||||
6
examples/chat-rn-expo-clerk/.gitignore
vendored
6
examples/chat-rn-expo-clerk/.gitignore
vendored
@@ -15,3 +15,9 @@ web-build/
|
||||
|
||||
ios
|
||||
android
|
||||
|
||||
# Env
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
!.env.test
|
||||
|
||||
1
examples/clerk/.env.example
Normal file
1
examples/clerk/.env.example
Normal file
@@ -0,0 +1 @@
|
||||
VITE_CLERK_PUBLISHABLE_KEY=
|
||||
@@ -1 +1 @@
|
||||
VITE_CLERK_PUBLISHABLE_KEY=pk_test_ZXZpZGVudC1kYW5lLTg5LmNsZXJrLmFjY291bnRzLmRldiQ
|
||||
VITE_CLERK_PUBLISHABLE_KEY=pk_test_ZXZpZGVudC1kYW5lLTg5LmNsZXJrLmFjY291bnRzLmRldiQ
|
||||
8
examples/clerk/.gitignore
vendored
8
examples/clerk/.gitignore
vendored
@@ -23,4 +23,10 @@ dist-ssr
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
playwright-report
|
||||
playwright-report
|
||||
|
||||
# Env
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
!.env.test
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@clerk/clerk-react": "^5.4.1",
|
||||
"jazz-inspector": "workspace:*",
|
||||
"jazz-react": "workspace:*",
|
||||
"jazz-react-auth-clerk": "workspace:*",
|
||||
"jazz-tools": "workspace:*",
|
||||
|
||||
@@ -3,6 +3,7 @@ import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import App from "./App.tsx";
|
||||
import "./index.css";
|
||||
import { JazzInspector } from "jazz-inspector";
|
||||
import { JazzProviderWithClerk } from "jazz-react-auth-clerk";
|
||||
import { ReactNode } from "react";
|
||||
import { apiKey } from "./apiKey";
|
||||
@@ -44,6 +45,7 @@ if (location.search.includes("expirationTest")) {
|
||||
<ClerkProvider publishableKey={PUBLISHABLE_KEY} afterSignOutUrl="/">
|
||||
<JazzProvider>
|
||||
<App />
|
||||
<JazzInspector />
|
||||
</JazzProvider>
|
||||
</ClerkProvider>
|
||||
</StrictMode>,
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
<script lang="ts" module>
|
||||
declare module 'jazz-svelte' {
|
||||
interface Register {
|
||||
Account: typeof FileShareAccount;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { JazzProvider } from 'jazz-svelte';
|
||||
import "jazz-inspector-element"
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"hash-slash": "workspace:*",
|
||||
"jazz-inspector": "workspace:*",
|
||||
"jazz-react": "workspace:*",
|
||||
"jazz-tools": "workspace:*",
|
||||
"react": "19.0.0",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { JazzInspector } from "jazz-inspector";
|
||||
import { JazzProvider } from "jazz-react";
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
@@ -15,6 +16,7 @@ createRoot(document.getElementById("root")!).render(
|
||||
AccountSchema={JazzAccount}
|
||||
>
|
||||
<App />
|
||||
<JazzInspector />
|
||||
</JazzProvider>
|
||||
</StrictMode>,
|
||||
);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"format-and-lint:fix": "biome check . --write"
|
||||
},
|
||||
"dependencies": {
|
||||
"jazz-inspector": "workspace:*",
|
||||
"jazz-react": "workspace:*",
|
||||
"jazz-tools": "workspace:*",
|
||||
"react": "19.0.0",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { JazzInspector } from "jazz-inspector";
|
||||
import { JazzProvider } from "jazz-react";
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
@@ -15,6 +16,7 @@ createRoot(document.getElementById("root")!).render(
|
||||
AccountSchema={JazzAccount}
|
||||
>
|
||||
<App />
|
||||
<JazzInspector />
|
||||
</JazzProvider>
|
||||
</StrictMode>,
|
||||
);
|
||||
|
||||
@@ -5,24 +5,25 @@
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"test": "playwright test",
|
||||
"test:headed": "playwright test --headed",
|
||||
"format-and-lint": "biome check .",
|
||||
"format-and-lint:fix": "biome check . --write",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"jazz-inspector": "workspace:*",
|
||||
"jazz-tools": "workspace:*",
|
||||
"clsx": "^2.0.0",
|
||||
"cojson": "workspace:*",
|
||||
"cojson-transport-ws": "workspace:*",
|
||||
"hash-slash": "workspace:*",
|
||||
"lucide-react": "^0.274.0",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
"react-router": "^6.16.0",
|
||||
"react-router-dom": "^6.16.0",
|
||||
"react-use": "^17.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.50.1",
|
||||
"@types/react": "19.0.0",
|
||||
"@types/react-dom": "19.0.0",
|
||||
"@vitejs/plugin-react-swc": "^3.10.1",
|
||||
|
||||
46
examples/inspector/playwright.config.ts
Normal file
46
examples/inspector/playwright.config.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
import isCI from "is-ci";
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
testDir: "./tests",
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: isCI,
|
||||
/* Retry on CI only */
|
||||
retries: isCI ? 2 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: isCI ? 1 : undefined,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: "html",
|
||||
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
baseURL: "http://localhost:5173/",
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: "on-first-retry",
|
||||
permissions: ["clipboard-read", "clipboard-write"],
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: "chromium",
|
||||
use: { ...devices["Desktop Chrome"] },
|
||||
},
|
||||
],
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
webServer: [
|
||||
{
|
||||
command: "pnpm preview --port 5173",
|
||||
url: "http://localhost:5173/",
|
||||
reuseExistingServer: !isCI,
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -35,6 +35,7 @@ interface JazzLoggedInSecret {
|
||||
}
|
||||
|
||||
export default function CoJsonViewerApp() {
|
||||
const [errors, setErrors] = useState<string | null>(null);
|
||||
const [accounts, setAccounts] = useState<Account[]>(() => {
|
||||
const storedAccounts = localStorage.getItem("inspectorAccounts");
|
||||
return storedAccounts ? JSON.parse(storedAccounts) : [];
|
||||
@@ -80,23 +81,46 @@ export default function CoJsonViewerApp() {
|
||||
websocket: new WebSocket("wss://cloud.jazz.tools"),
|
||||
role: "server",
|
||||
});
|
||||
const node = await LocalNode.withLoadedAccount({
|
||||
accountID: currentAccount.id,
|
||||
accountSecret: currentAccount.secret,
|
||||
sessionID: crypto.newRandomSessionID(currentAccount.id),
|
||||
peersToLoadFrom: [wsPeer],
|
||||
crypto,
|
||||
migration: async () => {
|
||||
console.log("Not running any migration in inspector");
|
||||
},
|
||||
});
|
||||
let node;
|
||||
try {
|
||||
node = await LocalNode.withLoadedAccount({
|
||||
accountID: currentAccount.id,
|
||||
accountSecret: currentAccount.secret,
|
||||
sessionID: crypto.newRandomSessionID(currentAccount.id),
|
||||
peersToLoadFrom: [wsPeer],
|
||||
crypto,
|
||||
migration: async () => {
|
||||
console.log("Not running any migration in inspector");
|
||||
},
|
||||
});
|
||||
} catch (err: any) {
|
||||
if (err.toString().includes("invalid id")) {
|
||||
setAccounts(accounts.filter((acc) => acc.id !== currentAccount.id));
|
||||
//remove from localStorage
|
||||
localStorage.removeItem("lastSelectedAccountId");
|
||||
localStorage.setItem(
|
||||
"inspectorAccounts",
|
||||
JSON.parse(localStorage.inspectorAccounts).filter(
|
||||
(acc: Account) => acc.id != currentAccount.id,
|
||||
),
|
||||
);
|
||||
setCurrentAccount(null);
|
||||
setErrors("Trying to load covalue with invalid id");
|
||||
} else {
|
||||
setErrors("The account could not be loaded");
|
||||
}
|
||||
setLocalNode(null);
|
||||
goToIndex(-1);
|
||||
return;
|
||||
}
|
||||
setLocalNode(node);
|
||||
});
|
||||
}, [currentAccount, goToIndex, path]);
|
||||
}, [currentAccount, accounts, goToIndex, path]);
|
||||
|
||||
const addAccount = (id: RawAccountID, secret: AgentSecret) => {
|
||||
const newAccount = { id, secret };
|
||||
const accountExists = accounts.some((account) => account.id === id);
|
||||
//todo: ideally there would be some validation here so we don't have to manually remove a non existent account from localStorage
|
||||
if (!accountExists) {
|
||||
setAccounts([...accounts, newAccount]);
|
||||
}
|
||||
@@ -174,7 +198,9 @@ export default function CoJsonViewerApp() {
|
||||
goBack={goBack}
|
||||
addPages={addPages}
|
||||
>
|
||||
{!currentAccount && <AddAccountForm addAccount={addAccount} />}
|
||||
{!currentAccount && (
|
||||
<AddAccountForm addAccount={addAccount} errors={errors} />
|
||||
)}
|
||||
|
||||
{currentAccount && path.length <= 0 && (
|
||||
<form
|
||||
@@ -270,8 +296,10 @@ function AccountSwitcher({
|
||||
|
||||
function AddAccountForm({
|
||||
addAccount,
|
||||
errors,
|
||||
}: {
|
||||
addAccount: (id: RawAccountID, secret: AgentSecret) => void;
|
||||
errors: string | null;
|
||||
}) {
|
||||
const [id, setId] = useState("");
|
||||
const [secret, setSecret] = useState("");
|
||||
@@ -305,8 +333,17 @@ function AddAccountForm({
|
||||
return (
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className="flex flex-col gap-3 max-w-md mx-auto h-full justify-center"
|
||||
className={`flex flex-col max-w-[30rem] mx-auto justify-center ${errors == null ? "h-full" : ""}`}
|
||||
>
|
||||
{errors != null && (
|
||||
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mt-4 font-mono whitespace-pre-wrap break-words mb-8">
|
||||
<h3>Error</h3>
|
||||
<pre className="whitespace-pre-wrap break-words overflow-hidden">
|
||||
{errors}
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<h2 className="text-2xl font-medium text-gray-900 dark:text-white">
|
||||
Add an account to inspect
|
||||
</h2>
|
||||
|
||||
91
examples/inspector/tests/data.ts
Normal file
91
examples/inspector/tests/data.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import { co, z } from "jazz-tools";
|
||||
|
||||
const projectsData: {
|
||||
name: string;
|
||||
description: string;
|
||||
issues: {
|
||||
title: string;
|
||||
status: "open" | "closed";
|
||||
labels: string[];
|
||||
}[];
|
||||
}[] = [
|
||||
{
|
||||
name: "Jazz",
|
||||
description: "Jazz is a framework for building collaborative apps.",
|
||||
issues: [
|
||||
{
|
||||
title: "Issue 1",
|
||||
status: "open",
|
||||
labels: [
|
||||
"bug",
|
||||
"feature",
|
||||
"enhancement",
|
||||
"documentation",
|
||||
"homepage",
|
||||
"help needed",
|
||||
"requested",
|
||||
"blocked",
|
||||
"high priority",
|
||||
"urgent",
|
||||
],
|
||||
},
|
||||
{ title: "Issue 2", status: "closed", labels: ["bug"] },
|
||||
{ title: "Issue 3", status: "open", labels: ["feature", "enhancement"] },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Waffle",
|
||||
description: "Start waffling",
|
||||
issues: [],
|
||||
},
|
||||
{
|
||||
name: "Garden",
|
||||
description: "Grow your garden",
|
||||
issues: [],
|
||||
},
|
||||
{
|
||||
name: "Tilescape",
|
||||
description: "",
|
||||
issues: [],
|
||||
},
|
||||
];
|
||||
|
||||
const Issue = co.map({
|
||||
title: z.string(),
|
||||
status: z.enum(["open", "closed"]),
|
||||
labels: co.list(z.string()),
|
||||
});
|
||||
|
||||
const Project = co.map({
|
||||
name: z.string(),
|
||||
description: z.string(),
|
||||
issues: co.list(Issue),
|
||||
});
|
||||
|
||||
const Organization = co.map({
|
||||
name: z.string(),
|
||||
projects: co.list(Project),
|
||||
});
|
||||
|
||||
export const createOrganization = () => {
|
||||
return Organization.create({
|
||||
name: "Garden Computing",
|
||||
projects: co.list(Project).create(
|
||||
projectsData.map((project) =>
|
||||
Project.create({
|
||||
name: project.name,
|
||||
description: project.description,
|
||||
issues: co.list(Issue).create(
|
||||
project.issues.map((issue) =>
|
||||
Issue.create({
|
||||
title: issue.title,
|
||||
status: issue.status,
|
||||
labels: co.list(z.string()).create(issue.labels),
|
||||
}),
|
||||
),
|
||||
),
|
||||
}),
|
||||
),
|
||||
),
|
||||
});
|
||||
};
|
||||
82
examples/inspector/tests/inspector.spec.ts
Normal file
82
examples/inspector/tests/inspector.spec.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { createOrganization } from "./data";
|
||||
import { createAccount, initializeKvStore } from "./lib";
|
||||
|
||||
initializeKvStore();
|
||||
const { account, accountID, accountSecret } = await createAccount();
|
||||
|
||||
test("should add and delete account in dropdown", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
await page.getByLabel("Account ID").fill(accountID);
|
||||
await page.getByLabel("Account secret").fill(accountSecret);
|
||||
await page.getByRole("button", { name: "Add account" }).click();
|
||||
|
||||
await expect(page.getByText("Jazz CoValue Inspector")).toBeVisible();
|
||||
await page
|
||||
.getByLabel("Account to inspect")
|
||||
.selectOption(`Inspector test account <${accountID}>`);
|
||||
|
||||
await page.getByRole("button", { name: "Remove account" }).click();
|
||||
await expect(page.getByText("Jazz CoValue Inspector")).not.toBeVisible();
|
||||
await expect(page.getByText("Add an account to inspect")).toBeVisible();
|
||||
await expect(
|
||||
page.getByText(`Inspector test account <${accountID}>`),
|
||||
).not.toBeVisible();
|
||||
});
|
||||
|
||||
test("should inspect account", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
await page.getByLabel("Account ID").fill(accountID);
|
||||
await page.getByLabel("Account secret").fill(accountSecret);
|
||||
await page.getByRole("button", { name: "Add account" }).click();
|
||||
await page.getByRole("button", { name: "Inspect my account" }).click();
|
||||
|
||||
await expect(page.getByRole("heading", { name: accountID })).toBeVisible();
|
||||
await expect(page.getByText("👤 Account")).toBeVisible();
|
||||
|
||||
await page.getByRole("button", { name: "profile {} CoMap name:" }).click();
|
||||
await expect(page.getByText("Role: admin")).toBeVisible();
|
||||
});
|
||||
|
||||
test("should inspect CoValue", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
await page.getByLabel("Account ID").fill(accountID);
|
||||
await page.getByLabel("Account secret").fill(accountSecret);
|
||||
await page.getByRole("button", { name: "Add account" }).click();
|
||||
|
||||
const organization = createOrganization();
|
||||
|
||||
await account.waitForAllCoValuesSync(); // Ensures that the organization is uploaded
|
||||
|
||||
await page.getByLabel("CoValue ID").fill(organization.id);
|
||||
await page.getByRole("button", { name: "Inspect CoValue" }).click();
|
||||
|
||||
await expect(page.getByText(/Garden Computing/)).toHaveCount(2);
|
||||
await expect(
|
||||
page.getByRole("heading", { name: organization.id }),
|
||||
).toBeVisible();
|
||||
await expect(page.getByText("Role: admin")).toBeVisible();
|
||||
|
||||
await page.getByRole("button", { name: /projects/ }).click();
|
||||
await expect(page.getByText("Showing 4 of 4")).toBeVisible();
|
||||
|
||||
await page.getByRole("button", { name: "View" }).first().click();
|
||||
await expect(
|
||||
page.getByText("Jazz is a framework for building collaborative apps."),
|
||||
).toBeVisible();
|
||||
|
||||
await page.getByRole("button", { name: /issues/ }).click();
|
||||
await expect(page.getByText("Showing 3 of 3")).toBeVisible();
|
||||
await page.getByRole("button", { name: "View" }).first().click();
|
||||
|
||||
await page.getByRole("button", { name: /labels/ }).click();
|
||||
// currently broken:
|
||||
// await expect(page.getByText("Showing 10 of 10")).toBeVisible();
|
||||
await expect(page.getByRole("table").getByRole("row")).toHaveCount(11);
|
||||
|
||||
await page.getByRole("button", { name: "issues" }).click();
|
||||
await expect(page.getByRole("table").getByRole("row")).toHaveCount(4);
|
||||
|
||||
await page.getByRole("button", { name: "projects" }).click();
|
||||
await expect(page.getByRole("table").getByRole("row")).toHaveCount(5);
|
||||
});
|
||||
43
examples/inspector/tests/lib.ts
Normal file
43
examples/inspector/tests/lib.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { createWebSocketPeer } from "cojson-transport-ws";
|
||||
import { WasmCrypto } from "cojson/crypto/WasmCrypto";
|
||||
import {
|
||||
AuthSecretStorage,
|
||||
InMemoryKVStore,
|
||||
KvStoreContext,
|
||||
co,
|
||||
createJazzContext,
|
||||
randomSessionProvider,
|
||||
z,
|
||||
} from "jazz-tools";
|
||||
|
||||
export const initializeKvStore = () => {
|
||||
const kvStore = new InMemoryKVStore();
|
||||
KvStoreContext.getInstance().initialize(kvStore);
|
||||
};
|
||||
|
||||
export async function createAccount() {
|
||||
const { account, authSecretStorage } = await createJazzContext({
|
||||
defaultProfileName: "Inspector test account",
|
||||
crypto: await WasmCrypto.create(),
|
||||
sessionProvider: randomSessionProvider,
|
||||
authSecretStorage: new AuthSecretStorage(),
|
||||
peersToLoadFrom: [
|
||||
createWebSocketPeer({
|
||||
id: "upstream",
|
||||
role: "server",
|
||||
websocket: new WebSocket(
|
||||
"wss://cloud.jazz.tools/?key=inspector-test@jazz.tools",
|
||||
),
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
await account.waitForAllCoValuesSync();
|
||||
|
||||
const credentials = await authSecretStorage.get();
|
||||
if (!credentials) {
|
||||
throw new Error("No credentials found");
|
||||
}
|
||||
|
||||
return { account, ...credentials };
|
||||
}
|
||||
@@ -11,6 +11,7 @@
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"next": "15.3.2",
|
||||
"jazz-inspector": "workspace:*",
|
||||
"jazz-react": "workspace:*",
|
||||
"jazz-tools": "workspace:*"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { JazzInspector } from "jazz-inspector";
|
||||
import { JazzProvider } from "jazz-react";
|
||||
|
||||
export function Jazz({ children }: { children: React.ReactNode }) {
|
||||
@@ -11,6 +12,7 @@ export function Jazz({ children }: { children: React.ReactNode }) {
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
<JazzInspector />
|
||||
</JazzProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-spring/web": "^9.7.5",
|
||||
"jazz-inspector": "workspace:*",
|
||||
"jazz-react": "workspace:*",
|
||||
"jazz-tools": "workspace:*",
|
||||
"react": "19.0.0",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { JazzInspector } from "jazz-inspector";
|
||||
import { JazzProvider } from "jazz-react";
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
@@ -16,6 +17,7 @@ createRoot(document.getElementById("root")!).render(
|
||||
AccountSchema={CursorAccount}
|
||||
>
|
||||
<App />
|
||||
<JazzInspector />
|
||||
</JazzProvider>
|
||||
</StrictMode>,
|
||||
);
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
VITE_CLERK_PUBLISHABLE_KEY=pk_test_ZXZpZGVudC1kYW5lLTg5LmNsZXJrLmFjY291bnRzLmRldiQ
|
||||
1
examples/multiauth/.env.example
Normal file
1
examples/multiauth/.env.example
Normal file
@@ -0,0 +1 @@
|
||||
VITE_CLERK_PUBLISHABLE_KEY=
|
||||
6
examples/multiauth/.gitignore
vendored
6
examples/multiauth/.gitignore
vendored
@@ -22,3 +22,9 @@ dist-ssr
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
# Env
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
!.env.test
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"dependencies": {
|
||||
"@clerk/clerk-react": "^5.4.1",
|
||||
"jazz-react": "workspace:*",
|
||||
"jazz-inspector": "workspace:*",
|
||||
"jazz-react-auth-clerk": "workspace:*",
|
||||
"jazz-tools": "workspace:*",
|
||||
"react": "19.0.0",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ClerkProvider } from "@clerk/clerk-react";
|
||||
import { JazzInspector } from "jazz-inspector";
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import App from "./App";
|
||||
@@ -22,6 +23,7 @@ createRoot(document.getElementById("root")!).render(
|
||||
}}
|
||||
>
|
||||
<App />
|
||||
<JazzInspector />
|
||||
</OmniAuth>
|
||||
</ClerkProvider>
|
||||
</StrictMode>,
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"test:e2e:ui": "playwright test --ui"
|
||||
},
|
||||
"dependencies": {
|
||||
"jazz-inspector": "workspace:*",
|
||||
"jazz-react": "workspace:*",
|
||||
"jazz-tools": "workspace:*",
|
||||
"lucide-react": "^0.274.0",
|
||||
@@ -21,8 +22,8 @@
|
||||
"react-router-dom": "^6.16.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.50.1",
|
||||
"@biomejs/biome": "1.9.4",
|
||||
"@playwright/test": "^1.50.1",
|
||||
"@tailwindcss/forms": "^0.5.9",
|
||||
"@types/react": "19.0.0",
|
||||
"@types/react-dom": "19.0.0",
|
||||
|
||||
@@ -7,16 +7,9 @@ import { Organization } from "../schema.ts";
|
||||
export function InviteLink({
|
||||
organization,
|
||||
}: { organization: Loaded<typeof Organization> }) {
|
||||
const [inviteLink, setInviteLink] = useState<string>();
|
||||
let [copyCount, setCopyCount] = useState(0);
|
||||
let copied = copyCount > 0;
|
||||
|
||||
useEffect(() => {
|
||||
if (organization) {
|
||||
setInviteLink(createInviteLink(organization, "writer"));
|
||||
}
|
||||
}, [organization.id]);
|
||||
|
||||
useEffect(() => {
|
||||
if (copyCount > 0) {
|
||||
let timeout = setTimeout(() => setCopyCount(0), 1000);
|
||||
@@ -27,11 +20,10 @@ export function InviteLink({
|
||||
}, [copyCount]);
|
||||
|
||||
const copyUrl = () => {
|
||||
if (inviteLink) {
|
||||
navigator.clipboard.writeText(inviteLink).then(() => {
|
||||
setCopyCount((count) => count + 1);
|
||||
});
|
||||
}
|
||||
const inviteLink = createInviteLink(organization, "writer");
|
||||
navigator.clipboard.writeText(inviteLink).then(() => {
|
||||
setCopyCount((count) => count + 1);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { JazzInspector } from "jazz-inspector";
|
||||
import { JazzProvider } from "jazz-react";
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
@@ -37,6 +38,7 @@ createRoot(document.getElementById("root")!).render(
|
||||
}}
|
||||
>
|
||||
<Router />
|
||||
<JazzInspector />
|
||||
</JazzProvider>
|
||||
</StrictMode>,
|
||||
);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"format-and-lint:fix": "biome check . --write"
|
||||
},
|
||||
"dependencies": {
|
||||
"jazz-inspector": "workspace:*",
|
||||
"jazz-react": "workspace:*",
|
||||
"jazz-tools": "workspace:*",
|
||||
"react": "19.0.0",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { JazzInspector } from "jazz-inspector";
|
||||
import { JazzProvider, PasskeyAuthBasicUI } from "jazz-react";
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
@@ -15,6 +16,7 @@ function JazzAndAuth({ children }: { children: React.ReactNode }) {
|
||||
<PasskeyAuthBasicUI appName="Jazz Minimal Auth Passkey Example">
|
||||
{children}
|
||||
</PasskeyAuthBasicUI>
|
||||
<JazzInspector />
|
||||
</JazzProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"format-and-lint:fix": "biome check . --write"
|
||||
},
|
||||
"dependencies": {
|
||||
"jazz-inspector": "workspace:*",
|
||||
"jazz-react": "workspace:*",
|
||||
"jazz-tools": "workspace:*",
|
||||
"react": "19.0.0",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { JazzInspector } from "jazz-inspector";
|
||||
import { JazzProvider, usePassphraseAuth } from "jazz-react";
|
||||
import { StrictMode, useState } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
@@ -150,6 +151,7 @@ function JazzAndAuth({ children }: { children: React.ReactNode }) {
|
||||
>
|
||||
{children}
|
||||
</PassphraseAuthBasicUI>
|
||||
<JazzInspector />
|
||||
</JazzProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"clean-install": "rm -rf node_modules pnpm-lock.yaml && pnpm install"
|
||||
},
|
||||
"dependencies": {
|
||||
"jazz-inspector": "workspace:*",
|
||||
"jazz-react": "workspace:*",
|
||||
"jazz-tools": "workspace:*",
|
||||
"react": "19.0.0",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { JazzInspector } from "jazz-inspector";
|
||||
import { JazzProvider, PasskeyAuthBasicUI } from "jazz-react";
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
@@ -25,6 +26,7 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
<JazzAndAuth>
|
||||
<App />
|
||||
<JazzInspector />
|
||||
</JazzAndAuth>
|
||||
</React.StrictMode>,
|
||||
);
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"@radix-ui/react-toast": "^1.1.4",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"jazz-inspector": "workspace:*",
|
||||
"jazz-react": "workspace:*",
|
||||
"jazz-tools": "workspace:*",
|
||||
"lucide-react": "^0.274.0",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { JazzInspector } from "jazz-inspector";
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import {
|
||||
@@ -47,6 +48,7 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<PasskeyAuthBasicUI appName={appName}>
|
||||
<App />
|
||||
</PasskeyAuthBasicUI>
|
||||
<JazzInspector />
|
||||
</JazzProvider>
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"hash-slash": "workspace:*",
|
||||
"jazz-inspector": "workspace:*",
|
||||
"jazz-react": "workspace:*",
|
||||
"jazz-tools": "workspace:*",
|
||||
"react": "19.0.0",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { JazzInspector } from "jazz-inspector";
|
||||
import { JazzProvider, PasskeyAuthBasicUI } from "jazz-react";
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
@@ -15,6 +16,7 @@ createRoot(document.getElementById("root")!).render(
|
||||
<PasskeyAuthBasicUI appName="Jazz Reactions Example">
|
||||
<App />
|
||||
</PasskeyAuthBasicUI>
|
||||
<JazzInspector />
|
||||
</JazzProvider>
|
||||
</StrictMode>,
|
||||
);
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"jazz-inspector": "workspace:*",
|
||||
"jazz-react": "workspace:*",
|
||||
"jazz-richtext-prosemirror": "workspace:*",
|
||||
"jazz-tools": "workspace:*",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import "./app.css";
|
||||
import { JazzInspector } from "jazz-inspector";
|
||||
import { JazzProvider } from "jazz-react";
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
@@ -15,6 +16,7 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
AccountSchema={JazzAccount}
|
||||
>
|
||||
<App />
|
||||
<JazzInspector />
|
||||
</JazzProvider>
|
||||
</React.StrictMode>,
|
||||
);
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"format-and-lint:fix": "biome check . --write"
|
||||
},
|
||||
"dependencies": {
|
||||
"jazz-inspector": "workspace:*",
|
||||
"jazz-react": "workspace:*",
|
||||
"jazz-richtext-prosemirror": "workspace:*",
|
||||
"jazz-tools": "workspace:*",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { JazzInspector } from "jazz-inspector";
|
||||
import { JazzProvider } from "jazz-react";
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
@@ -21,6 +22,7 @@ createRoot(document.getElementById("root")!).render(
|
||||
AccountSchema={JazzAccount}
|
||||
>
|
||||
<App />
|
||||
<JazzInspector />
|
||||
</JazzProvider>
|
||||
</StrictMode>,
|
||||
);
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"@tiptap/react": "^2.12.0",
|
||||
"@tiptap/starter-kit": "^2.12.0",
|
||||
"clsx": "^2.1.1",
|
||||
"jazz-inspector": "workspace:*",
|
||||
"jazz-react": "workspace:*",
|
||||
"jazz-richtext-tiptap": "workspace:*",
|
||||
"jazz-tools": "workspace:*",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { JazzInspector } from "jazz-inspector";
|
||||
import { JazzProvider } from "jazz-react";
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
@@ -21,6 +22,7 @@ createRoot(document.getElementById("root")!).render(
|
||||
AccountSchema={JazzAccount}
|
||||
>
|
||||
<App />
|
||||
<JazzInspector />
|
||||
</JazzProvider>
|
||||
</StrictMode>,
|
||||
);
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"format-and-lint:fix": "biome check . --write"
|
||||
},
|
||||
"dependencies": {
|
||||
"jazz-inspector-element": "workspace:*",
|
||||
"jazz-browser": "workspace:*",
|
||||
"jazz-tools": "workspace:*",
|
||||
"jazz-vue": "workspace:*",
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"@radix-ui/react-toast": "^1.1.4",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"jazz-inspector": "workspace:*",
|
||||
"jazz-react": "workspace:*",
|
||||
"jazz-tools": "workspace:*",
|
||||
"lucide-react": "^0.274.0",
|
||||
|
||||
@@ -6,13 +6,13 @@ import {
|
||||
} from "react-router-dom";
|
||||
import "./index.css";
|
||||
|
||||
import { JazzInspector } from "jazz-inspector";
|
||||
import {
|
||||
JazzProvider,
|
||||
PassphraseAuthBasicUI,
|
||||
useAcceptInvite,
|
||||
useAccount,
|
||||
} from "jazz-react";
|
||||
|
||||
import React from "react";
|
||||
import { TodoAccount, TodoProject } from "./1_schema.ts";
|
||||
import { NewProjectForm } from "./3_NewProjectForm.tsx";
|
||||
@@ -62,6 +62,7 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<App />
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
<JazzInspector />
|
||||
</JazzAndAuth>
|
||||
</React.StrictMode>,
|
||||
);
|
||||
|
||||
@@ -19,12 +19,12 @@
|
||||
"jazz-react": "link:../../packages/jazz-react",
|
||||
"jazz-tools": "link:../../packages/jazz-tools",
|
||||
"lucide-react": "^0.436.0",
|
||||
"next": "14.2.7",
|
||||
"next": "15.2.1",
|
||||
"next-themes": "^0.2.1",
|
||||
"postcss": "^8",
|
||||
"radix-ui": "^1.4.2",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"react": "catalog:",
|
||||
"react-dom": "catalog:",
|
||||
"resend": "^4.0.0",
|
||||
"tailwind-merge": "^1.14.0",
|
||||
"tailwindcss": "^3.4.17",
|
||||
@@ -34,8 +34,8 @@
|
||||
"@biomejs/biome": "1.9.4",
|
||||
"@csstools/postcss-oklab-function": "^3.0.6",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"@types/react": "catalog:",
|
||||
"@types/react-dom": "catalog:",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,8 @@
|
||||
"paths": {
|
||||
"@/*": ["./src/*"],
|
||||
"@components/*": ["./src/components/*"]
|
||||
}
|
||||
},
|
||||
"target": "ES2017"
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
|
||||
@@ -12,5 +12,6 @@
|
||||
"persistent": true,
|
||||
"dependsOn": ["^build"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"extends": ["//"]
|
||||
}
|
||||
|
||||
@@ -10,8 +10,15 @@ import { Metadata } from "next";
|
||||
import Image from "next/image";
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
type Params = {
|
||||
params: Promise<{
|
||||
slug: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
export default async function Post({ params }: Params) {
|
||||
const post = getPostBySlug(params.slug);
|
||||
const { slug } = await params;
|
||||
const post = getPostBySlug(slug);
|
||||
|
||||
if (!post) {
|
||||
return notFound();
|
||||
@@ -65,14 +72,9 @@ export default async function Post({ params }: Params) {
|
||||
);
|
||||
}
|
||||
|
||||
type Params = {
|
||||
params: {
|
||||
slug: string;
|
||||
};
|
||||
};
|
||||
|
||||
export function generateMetadata({ params }: Params): Metadata {
|
||||
const post = getPostBySlug(params.slug);
|
||||
export async function generateMetadata({ params }: Params): Promise<Metadata> {
|
||||
const { slug } = await params;
|
||||
const post = getPostBySlug(slug);
|
||||
|
||||
if (!post) {
|
||||
return notFound();
|
||||
|
||||
@@ -18,15 +18,17 @@ const membersNameToInfoMap = {
|
||||
brad: "Bradley Kowalski",
|
||||
};
|
||||
|
||||
export default function TeamMemberPage({
|
||||
export default async function TeamMemberPage({
|
||||
params,
|
||||
}: { params: { member: string } }) {
|
||||
if (!(params.member in membersNameToInfoMap)) {
|
||||
}: { params: Promise<{ member: string }> }) {
|
||||
const { member } = await params;
|
||||
|
||||
if (!(member in membersNameToInfoMap)) {
|
||||
Router.push("/team");
|
||||
}
|
||||
|
||||
const memberName =
|
||||
membersNameToInfoMap[params.member as keyof typeof membersNameToInfoMap];
|
||||
membersNameToInfoMap[member as keyof typeof membersNameToInfoMap];
|
||||
const memberInfo = team.find(
|
||||
(m: { name: string }) => m.name.toLowerCase() === memberName.toLowerCase(),
|
||||
);
|
||||
|
||||
@@ -26,10 +26,10 @@
|
||||
"mdast-util-from-markdown": "^2.0.0",
|
||||
"mdast-util-mdx": "^3.0.0",
|
||||
"micromark-extension-mdxjs": "^3.0.0",
|
||||
"next": "14.2.15",
|
||||
"next": "15.2.1",
|
||||
"next-themes": "^0.2.1",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"react": "catalog:",
|
||||
"react-dom": "catalog:",
|
||||
"shiki": "^0.14.6",
|
||||
"shiki-twoslash": "^3.1.2",
|
||||
"tailwind-merge": "^1.14.0",
|
||||
@@ -39,8 +39,8 @@
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "1.9.4",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"@types/react": "catalog:",
|
||||
"@types/react-dom": "catalog:",
|
||||
"autoprefixer": "^10",
|
||||
"postcss": "^8",
|
||||
"tailwindcss": "^3",
|
||||
|
||||
@@ -7,85 +7,54 @@ import { ReactLogo } from "@/components/icons/ReactLogo";
|
||||
import { ReactNativeLogo } from "@/components/icons/ReactNativeLogo";
|
||||
import { SvelteLogo } from "@/components/icons/SvelteLogo";
|
||||
import { GappedGrid } from "@garden-co/design-system/src/components/molecules/GappedGrid";
|
||||
import Link from "next/link";
|
||||
import React from "react";
|
||||
|
||||
const frameworks = [
|
||||
{
|
||||
name: "JavaScript",
|
||||
icon: JavascriptLogo,
|
||||
href: "/docs/vanilla",
|
||||
},
|
||||
{
|
||||
name: "React",
|
||||
icon: ReactLogo,
|
||||
href: "/docs/react",
|
||||
},
|
||||
{
|
||||
name: "React Native",
|
||||
icon: ReactNativeLogo,
|
||||
href: "/docs/react-native",
|
||||
},
|
||||
{
|
||||
name: "Expo",
|
||||
icon: ExpoLogo,
|
||||
href: "/docs/react-native-expo",
|
||||
},
|
||||
{
|
||||
name: "Svelte",
|
||||
icon: SvelteLogo,
|
||||
href: "/docs/svelte",
|
||||
},
|
||||
];
|
||||
|
||||
const serverWorkers = [
|
||||
{
|
||||
name: "Node.js",
|
||||
icon: NodejsLogo,
|
||||
href: "/docs/react/server-workers",
|
||||
},
|
||||
{
|
||||
name: "Cloudflare Workers",
|
||||
icon: CloudflareWorkerLogo,
|
||||
},
|
||||
{
|
||||
name: "Bun",
|
||||
icon: BunLogo,
|
||||
},
|
||||
];
|
||||
|
||||
export function SupportedEnvironmentsSection() {
|
||||
const frameworks = [
|
||||
{
|
||||
name: "JavaScript",
|
||||
icon: JavascriptLogo,
|
||||
},
|
||||
{
|
||||
name: "React",
|
||||
icon: ReactLogo,
|
||||
},
|
||||
{
|
||||
name: "React Native",
|
||||
icon: ReactNativeLogo,
|
||||
},
|
||||
{
|
||||
name: "Expo",
|
||||
icon: ExpoLogo,
|
||||
},
|
||||
{
|
||||
name: "Svelte",
|
||||
icon: SvelteLogo,
|
||||
},
|
||||
];
|
||||
|
||||
const serverWorkers = [
|
||||
{
|
||||
name: "Node.js",
|
||||
icon: NodejsLogo,
|
||||
},
|
||||
{
|
||||
name: "Cloudflare Workers",
|
||||
icon: CloudflareWorkerLogo,
|
||||
},
|
||||
{
|
||||
name: "Bun",
|
||||
icon: BunLogo,
|
||||
},
|
||||
];
|
||||
|
||||
const first = [
|
||||
{
|
||||
name: "JavaScript",
|
||||
icon: JavascriptLogo,
|
||||
},
|
||||
{
|
||||
name: "React",
|
||||
icon: ReactLogo,
|
||||
},
|
||||
{
|
||||
name: "Svelte",
|
||||
icon: SvelteLogo,
|
||||
},
|
||||
{
|
||||
name: "Expo",
|
||||
icon: ExpoLogo,
|
||||
},
|
||||
{
|
||||
name: "React Native",
|
||||
icon: ReactNativeLogo,
|
||||
},
|
||||
];
|
||||
|
||||
const second = [
|
||||
{
|
||||
name: "Node.js",
|
||||
icon: NodejsLogo,
|
||||
},
|
||||
{
|
||||
name: "Cloudflare Workers",
|
||||
icon: CloudflareWorkerLogo,
|
||||
},
|
||||
{
|
||||
name: "Bun",
|
||||
icon: BunLogo,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2 className="sr-only">Supported environments</h2>
|
||||
@@ -102,13 +71,27 @@ export function SupportedEnvironmentsSection() {
|
||||
].map(({ label, items }) => (
|
||||
<div className="col-span-2 lg:col-span-3" key={label}>
|
||||
<h3 className="mb-4 text-highlight font-medium">{label}</h3>
|
||||
<div className="flex gap-x-6 gap-y-3 grayscale flex-col lg:flex-row">
|
||||
{items.map(({ name, icon: Icon }) => (
|
||||
<div key={name} className="flex items-center gap-2">
|
||||
<Icon className="size-6" />
|
||||
{name}
|
||||
</div>
|
||||
))}
|
||||
<div className="flex gap-x-6 gap-y-3 flex-col lg:flex-row">
|
||||
{items.map(({ name, icon: Icon, href }) => {
|
||||
if (href) {
|
||||
return (
|
||||
<Link
|
||||
href={href}
|
||||
key={name}
|
||||
className="flex items-center gap-2 grayscale hover:grayscale-0"
|
||||
>
|
||||
<Icon className="size-6" />
|
||||
{name}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div key={name} className="flex items-center gap-2 grayscale">
|
||||
<Icon className="size-6" />
|
||||
{name}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -6,7 +6,11 @@ export const metadata = {
|
||||
|
||||
# Learn some <span className="sr-only">Jazz</span> <JazzLogo className="h-[41px] -ml-0.5 -mt-[3px] inline" />
|
||||
|
||||
Welcome to the Jazz documentation!
|
||||
**Jazz is a toolkit for building backendless apps**. You get data without needing a database — plus auth, permissions, files and multiplayer without needing a backend. Jazz lets you do everything right from the frontend and you'll ship better apps, faster.
|
||||
|
||||
Instead of wrestling with databases, APIs, and server infrastructure, you work with **CoValues** ("collaborative values") — your new cloud-synced building blocks that feel like local state but automatically sync across all devices and users in real-time.
|
||||
|
||||
---
|
||||
|
||||
**Note:** We just released [Jazz 0.14.0](/docs/upgrade/0-14-0) with a bunch of breaking changes and are still cleaning the docs up - see the [upgrade guide](/docs/upgrade/0-14-0) for details.
|
||||
|
||||
@@ -20,37 +24,44 @@ npx create-jazz-app@latest --api-key you@example.com
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
Or set up Jazz yourself, using the following instructions for your framework of choice:
|
||||
|
||||
- [React](/docs/react/project-setup)
|
||||
- [Next.js](/docs/react/project-setup#nextjs)
|
||||
- [React Native](/docs/react-native/project-setup)
|
||||
- [React Native Expo](/docs/react-native-expo/project-setup)
|
||||
- [Vue](/docs/vue/project-setup)
|
||||
- [Svelte](/docs/svelte/project-setup)
|
||||
|
||||
{/* <ContentByFramework framework="react">
|
||||
Or you can follow this [React step-by-step guide](/docs/react/guide) where we walk you through building an issue tracker app.
|
||||
</ContentByFramework> */}
|
||||
|
||||
## Example apps
|
||||
## Why Jazz is different
|
||||
|
||||
You can also find [example apps](/examples) with code most similar to what you want to build. These apps
|
||||
make use of different features such as auth, file upload, and more.
|
||||
Most apps rebuild the same thing: shared state that syncs between users and devices. Jazz starts from that shared state, giving you:
|
||||
|
||||
- **No backend required** — Focus on building features, not infrastructure
|
||||
- **Real-time sync** — Changes appear everywhere immediately
|
||||
- **Multiplayer by default** — Collaboration just works
|
||||
- **Local-first** — Your app works offline and feels instant
|
||||
|
||||
Think Figma, Notion, or Linear — but you don't need years to build a custom stack.
|
||||
|
||||
## How it works
|
||||
|
||||
1. **Define your data** with CoValues schemas
|
||||
2. **Connect to sync infrastructure** (Jazz Cloud or self-hosted)
|
||||
3. **Create and edit CoValues** like normal objects
|
||||
4. **Get automatic sync and persistence** across all devices and users
|
||||
|
||||
Your UI updates instantly on every change, everywhere. It's like having reactive local state that happens to be shared with the world.
|
||||
|
||||
## Ready to see Jazz in action?
|
||||
|
||||
Have a look at our [example apps](/examples) for inspiration and to see what's possible with Jazz. From real-time chat and collaborative editors to file sharing and social features — these are just the beginning of what you can build.
|
||||
|
||||
## Core concepts
|
||||
|
||||
Learn how to structure your data using [collaborative values](/docs/schemas/covalues) — the building blocks that make Jazz apps work.
|
||||
|
||||
## Sync and storage
|
||||
|
||||
Sync and persist your data by setting up a [sync and storage infrastructure](/docs/sync-and-storage) using Jazz Cloud, or do it yourself.
|
||||
Sync and persist your data by setting up [sync and storage infrastructure](/docs/sync-and-storage) using Jazz Cloud, or host it yourself.
|
||||
|
||||
## Collaborative values
|
||||
|
||||
Learn how to structure your data using [collaborative values](/docs/schemas/covalues).
|
||||
|
||||
## LLM Docs
|
||||
## Going deeper
|
||||
|
||||
Get better results with AI by [importing the Jazz docs](/docs/ai-tools) into your context window.
|
||||
|
||||
## Get support
|
||||
|
||||
If you have any questions or need assistance, please don't hesitate to reach out to us on [Discord](https://discord.gg/utDMjHYg42).
|
||||
We would love to help you get started.
|
||||
If you have any questions or need assistance, please don't hesitate to reach out to us on [Discord](https://discord.gg/utDMjHYg42). We'd love to help you get started.
|
||||
|
||||
@@ -253,14 +253,13 @@ Here's a quick overview of the primitive types you can use:
|
||||
|
||||
<CodeGroup>
|
||||
```ts twoslash
|
||||
import {z} from "jazz-tools";
|
||||
// ---cut---
|
||||
z.string(); // For simple strings
|
||||
z.number(); // For numbers
|
||||
import { z } from "jazz-tools";
|
||||
// ---cut---
|
||||
z.string(); // For simple strings
|
||||
z.number(); // For numbers
|
||||
z.boolean(); // For booleans
|
||||
z.null(); // For null
|
||||
z.date(); // For dates
|
||||
z.literal(["waiting", "ready"]); // For enums
|
||||
z.date(); // For dates
|
||||
z.literal(["waiting", "ready"]); // For enums
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ We're introducing a new resolve API for deep loading, more friendly to TypeScrip
|
||||
|
||||
<CodeGroup>
|
||||
```tsx twoslash
|
||||
// @noErrors: 2451
|
||||
// @noErrors: 2451 2769
|
||||
import { CoMap, CoList, coField, Account } from "jazz-tools";
|
||||
import { useAccount } from "jazz-react";
|
||||
class AccountRoot extends CoMap { friends = coField.ref(ListOfAccounts); }
|
||||
|
||||
@@ -84,18 +84,22 @@ function highlightPlugin() {
|
||||
transformers: [
|
||||
transformerTwoslash({
|
||||
explicitTrigger: true,
|
||||
throws: false, //process.env.NODE_ENV === "production",
|
||||
onTwoslashError:
|
||||
process.env.NODE_ENV !== "production"
|
||||
? (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,
|
||||
throws: process.env.NODE_ENV === "production",
|
||||
onTwoslashError: (e, code) => {
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
// Re-throw to actually fail the build in production
|
||||
throw e;
|
||||
}
|
||||
|
||||
const { description, recommendation } = e;
|
||||
console.error("\nTwoslash error: ");
|
||||
console.log(description);
|
||||
console.log(recommendation);
|
||||
console.log("\nCode: \n```\n" + code + "\n```");
|
||||
|
||||
// In development, store the error to show inline
|
||||
error = e;
|
||||
},
|
||||
}),
|
||||
transformerNotationDiff(),
|
||||
],
|
||||
@@ -103,7 +107,7 @@ function highlightPlugin() {
|
||||
|
||||
node.type = "html";
|
||||
node.value = error
|
||||
? `<div style="color: red;">${error}</div>` + html
|
||||
? `<div style="color: red; background: #fee; padding: 8px; border: 1px solid #fcc; margin: 8px 0;"><strong>Twoslash Error:</strong> ${error.description || error.message} ${error.recommendation}</div>` + html
|
||||
: html;
|
||||
node.children = [];
|
||||
return SKIP;
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
"@stefanprobst/rehype-extract-toc": "^2.2.0",
|
||||
"@turf/turf": "^7.1.0",
|
||||
"@types/mdx": "^2.0.8",
|
||||
"@types/react-native": "^0.73.0",
|
||||
"@types/topojson-client": "^3.1.5",
|
||||
"@vercel/analytics": "^1.3.1",
|
||||
"@vercel/speed-insights": "^1.0.12",
|
||||
@@ -57,8 +58,8 @@
|
||||
"next": "15.2.1",
|
||||
"next-themes": "^0.2.1",
|
||||
"qrcode": "^1.5.4",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"react": "catalog:",
|
||||
"react-dom": "catalog:",
|
||||
"react-singleton-hook": "^4.0.1",
|
||||
"shiki": "^3.2.1",
|
||||
"tailwind-merge": "^1.14.0",
|
||||
@@ -70,8 +71,8 @@
|
||||
"@playwright/test": "^1.52.0",
|
||||
"@types/geojson": "^7946.0.14",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"@types/react": "catalog:",
|
||||
"@types/react-dom": "catalog:",
|
||||
"autoprefixer": "^10",
|
||||
"pagefind": "^1.3.0",
|
||||
"postcss": "^8",
|
||||
|
||||
@@ -16,5 +16,6 @@
|
||||
"persistent": true,
|
||||
"dependsOn": ["build"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"extends": ["//"]
|
||||
}
|
||||
|
||||
4169
homepage/pnpm-lock.yaml
generated
4169
homepage/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,10 @@
|
||||
packages:
|
||||
- "homepage"
|
||||
- "design-system"
|
||||
- "gcmp"
|
||||
- "gcmp"
|
||||
|
||||
catalog:
|
||||
"react": "19.0.0"
|
||||
"react-dom": "19.0.0"
|
||||
"@types/react": "19.0.0"
|
||||
"@types/react-dom": "19.0.0"
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
"esbuild": "0.24.0"
|
||||
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"expo-router": "patches/expo-router.patch"
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# cojson-storage-indexeddb
|
||||
|
||||
## 0.14.24
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- cojson@0.14.24
|
||||
- cojson-storage@0.14.24
|
||||
|
||||
## 0.14.23
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cojson-storage-indexeddb",
|
||||
"version": "0.14.23",
|
||||
"version": "0.14.24",
|
||||
"main": "dist/index.js",
|
||||
"type": "module",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# cojson-storage-sqlite
|
||||
|
||||
## 0.14.24
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- cojson@0.14.24
|
||||
- cojson-storage@0.14.24
|
||||
|
||||
## 0.14.23
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "cojson-storage-sqlite",
|
||||
"type": "module",
|
||||
"version": "0.14.23",
|
||||
"version": "0.14.24",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"better-sqlite3": "^11.7.0",
|
||||
"cojson": "workspace:0.14.23",
|
||||
"cojson": "workspace:0.14.24",
|
||||
"cojson-storage": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# cojson-storage
|
||||
|
||||
## 0.14.24
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- cojson@0.14.24
|
||||
|
||||
## 0.14.23
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cojson-storage",
|
||||
"version": "0.14.23",
|
||||
"version": "0.14.24",
|
||||
"main": "dist/index.js",
|
||||
"type": "module",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# cojson-transport-nodejs-ws
|
||||
|
||||
## 0.14.24
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- cojson@0.14.24
|
||||
|
||||
## 0.14.23
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "cojson-transport-ws",
|
||||
"type": "module",
|
||||
"version": "0.14.23",
|
||||
"version": "0.14.24",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# cojson
|
||||
|
||||
## 0.14.24
|
||||
|
||||
## 0.14.23
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
},
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"version": "0.14.23",
|
||||
"version": "0.14.24",
|
||||
"devDependencies": {
|
||||
"@opentelemetry/sdk-metrics": "^2.0.0",
|
||||
"typescript": "catalog:"
|
||||
|
||||
@@ -369,11 +369,9 @@ export class CoValueCore {
|
||||
tryAddTransactions(
|
||||
sessionID: SessionID,
|
||||
newTransactions: Transaction[],
|
||||
givenExpectedNewHash: Hash | undefined,
|
||||
newSignature: Signature,
|
||||
notifyMode: "immediate" | "deferred",
|
||||
skipVerify: boolean = false,
|
||||
givenNewStreamingHash?: StreamingHash,
|
||||
): Result<true, TryAddTransactionsError> {
|
||||
return this.node
|
||||
.resolveAccountAgent(
|
||||
@@ -390,14 +388,12 @@ export class CoValueCore {
|
||||
|
||||
const signerID = this.crypto.getAgentSignerID(agent);
|
||||
|
||||
const result = this.verified.tryAddTransactions(
|
||||
const result = this.verified.tryAdd(
|
||||
sessionID,
|
||||
signerID,
|
||||
newTransactions,
|
||||
givenExpectedNewHash,
|
||||
newSignature,
|
||||
skipVerify,
|
||||
givenNewStreamingHash,
|
||||
);
|
||||
|
||||
if (result.isOk()) {
|
||||
@@ -531,30 +527,33 @@ export class CoValueCore {
|
||||
) as SessionID)
|
||||
: this.node.currentSessionID;
|
||||
|
||||
const { expectedNewHash, newStreamingHash } =
|
||||
this.verified.expectedNewHashAfter(sessionID, [transaction]);
|
||||
const agent = this.node.getCurrentAgent();
|
||||
|
||||
const signature = this.crypto.sign(
|
||||
this.node.getCurrentAgent().currentSignerSecret(),
|
||||
expectedNewHash,
|
||||
this.verified.addNew(
|
||||
sessionID,
|
||||
agent.currentSignerID(),
|
||||
[transaction],
|
||||
agent.currentSignerSecret(),
|
||||
);
|
||||
|
||||
const success = this.tryAddTransactions(
|
||||
sessionID,
|
||||
[transaction],
|
||||
expectedNewHash,
|
||||
signature,
|
||||
"immediate",
|
||||
true,
|
||||
newStreamingHash,
|
||||
)._unsafeUnwrap({ withStackTrace: true });
|
||||
this.node.syncManager.recordTransactionsSize([transaction], "local");
|
||||
void this.node.syncManager.requestCoValueSync(this);
|
||||
|
||||
if (success) {
|
||||
this.node.syncManager.recordTransactionsSize([transaction], "local");
|
||||
void this.node.syncManager.requestCoValueSync(this);
|
||||
if (
|
||||
this._cachedContent &&
|
||||
"processNewTransactions" in this._cachedContent &&
|
||||
typeof this._cachedContent.processNewTransactions === "function"
|
||||
) {
|
||||
this._cachedContent.processNewTransactions();
|
||||
} else {
|
||||
this._cachedContent = undefined;
|
||||
}
|
||||
|
||||
return success;
|
||||
this._cachedDependentOn = undefined;
|
||||
|
||||
this.notifyUpdate("immediate");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
getCurrentContent(options?: {
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import { Result, err, ok } from "neverthrow";
|
||||
import { AnyRawCoValue } from "../coValue.js";
|
||||
import {
|
||||
APPEND_INVALID_SIGNATURE,
|
||||
APPEND_OK,
|
||||
AppendOnlyVerifiedLog,
|
||||
CryptoProvider,
|
||||
Encrypted,
|
||||
Hash,
|
||||
KeyID,
|
||||
Signature,
|
||||
SignerID,
|
||||
SignerSecret,
|
||||
StreamingHash,
|
||||
} from "../crypto/crypto.js";
|
||||
import { RawCoID, SessionID, TransactionID } from "../ids.js";
|
||||
@@ -48,13 +52,15 @@ export type TrustingTransaction = {
|
||||
|
||||
export type Transaction = PrivateTransaction | TrustingTransaction;
|
||||
|
||||
type SessionLog = {
|
||||
readonly transactions: Transaction[];
|
||||
lastHash?: Hash;
|
||||
streamingHash: StreamingHash;
|
||||
readonly signatureAfter: { [txIdx: number]: Signature | undefined };
|
||||
lastSignature: Signature;
|
||||
};
|
||||
// type SessionLog = {
|
||||
// readonly transactions: Transaction[];
|
||||
// lastHash?: Hash;
|
||||
// streamingHash: StreamingHash;
|
||||
// readonly signatureAfter: { [txIdx: number]: Signature | undefined };
|
||||
// lastSignature: Signature;
|
||||
// };
|
||||
|
||||
type SessionLog = AppendOnlyVerifiedLog<Transaction>;
|
||||
|
||||
export type ValidatedSessions = Map<SessionID, SessionLog>;
|
||||
|
||||
@@ -82,134 +88,47 @@ export class VerifiedState {
|
||||
// do a deep clone, including the sessions
|
||||
const clonedSessions = new Map();
|
||||
for (let [sessionID, sessionLog] of this.sessions) {
|
||||
clonedSessions.set(sessionID, {
|
||||
lastSignature: sessionLog.lastSignature,
|
||||
lastHash: sessionLog.lastHash,
|
||||
streamingHash: sessionLog.streamingHash.clone(),
|
||||
signatureAfter: { ...sessionLog.signatureAfter },
|
||||
transactions: sessionLog.transactions.slice(),
|
||||
} satisfies SessionLog);
|
||||
clonedSessions.set(sessionID, sessionLog.clone());
|
||||
}
|
||||
return new VerifiedState(this.id, this.crypto, this.header, clonedSessions);
|
||||
}
|
||||
|
||||
tryAddTransactions(
|
||||
addNew(
|
||||
sessionID: SessionID,
|
||||
signerID: SignerID,
|
||||
newTransactions: Transaction[],
|
||||
signerSecret: SignerSecret,
|
||||
) {
|
||||
const sessionLog =
|
||||
this.sessions.get(sessionID) ||
|
||||
this.crypto.emptyAppendOnlyVerifiedLog(signerID);
|
||||
sessionLog.addNew(newTransactions, signerSecret);
|
||||
this.sessions.set(sessionID, sessionLog);
|
||||
}
|
||||
|
||||
tryAdd(
|
||||
sessionID: SessionID,
|
||||
signerID: SignerID,
|
||||
newTransactions: Transaction[],
|
||||
givenExpectedNewHash: Hash | undefined,
|
||||
newSignature: Signature,
|
||||
skipVerify: boolean = false,
|
||||
givenNewStreamingHash?: StreamingHash,
|
||||
): Result<true, TryAddTransactionsError> {
|
||||
if (skipVerify === true && givenNewStreamingHash && givenExpectedNewHash) {
|
||||
this.doAddTransactions(
|
||||
sessionID,
|
||||
newTransactions,
|
||||
newSignature,
|
||||
givenExpectedNewHash,
|
||||
givenNewStreamingHash,
|
||||
);
|
||||
const sessionLog =
|
||||
this.sessions.get(sessionID) ||
|
||||
this.crypto.emptyAppendOnlyVerifiedLog(signerID);
|
||||
const result = sessionLog.tryAdd(newTransactions, newSignature, skipVerify);
|
||||
if (result === APPEND_OK) {
|
||||
this.sessions.set(sessionID, sessionLog);
|
||||
return ok(true as const);
|
||||
} else {
|
||||
const { expectedNewHash, newStreamingHash } = this.expectedNewHashAfter(
|
||||
sessionID,
|
||||
newTransactions,
|
||||
);
|
||||
|
||||
if (givenExpectedNewHash && givenExpectedNewHash !== expectedNewHash) {
|
||||
return err({
|
||||
type: "InvalidHash",
|
||||
id: this.id,
|
||||
expectedNewHash,
|
||||
givenExpectedNewHash,
|
||||
} satisfies InvalidHashError);
|
||||
}
|
||||
|
||||
if (!this.crypto.verify(newSignature, expectedNewHash, signerID)) {
|
||||
return err({
|
||||
type: "InvalidSignature",
|
||||
id: this.id,
|
||||
newSignature,
|
||||
sessionID,
|
||||
signerID,
|
||||
} satisfies InvalidSignatureError);
|
||||
}
|
||||
|
||||
this.doAddTransactions(
|
||||
sessionID,
|
||||
newTransactions,
|
||||
return err({
|
||||
type: "InvalidSignature",
|
||||
id: this.id,
|
||||
newSignature,
|
||||
expectedNewHash,
|
||||
newStreamingHash,
|
||||
);
|
||||
sessionID,
|
||||
signerID: sessionLog.signerID,
|
||||
} satisfies InvalidSignatureError);
|
||||
}
|
||||
|
||||
return ok(true as const);
|
||||
}
|
||||
|
||||
private doAddTransactions(
|
||||
sessionID: SessionID,
|
||||
newTransactions: Transaction[],
|
||||
newSignature: Signature,
|
||||
expectedNewHash: Hash,
|
||||
newStreamingHash: StreamingHash,
|
||||
) {
|
||||
const transactions = this.sessions.get(sessionID)?.transactions ?? [];
|
||||
|
||||
for (const tx of newTransactions) {
|
||||
transactions.push(tx);
|
||||
}
|
||||
|
||||
const signatureAfter = this.sessions.get(sessionID)?.signatureAfter ?? {};
|
||||
|
||||
const lastInbetweenSignatureIdx = Object.keys(signatureAfter).reduce(
|
||||
(max, idx) => (parseInt(idx) > max ? parseInt(idx) : max),
|
||||
-1,
|
||||
);
|
||||
|
||||
const sizeOfTxsSinceLastInbetweenSignature = transactions
|
||||
.slice(lastInbetweenSignatureIdx + 1)
|
||||
.reduce(
|
||||
(sum, tx) =>
|
||||
sum +
|
||||
(tx.privacy === "private"
|
||||
? tx.encryptedChanges.length
|
||||
: tx.changes.length),
|
||||
0,
|
||||
);
|
||||
|
||||
if (sizeOfTxsSinceLastInbetweenSignature > MAX_RECOMMENDED_TX_SIZE) {
|
||||
signatureAfter[transactions.length - 1] = newSignature;
|
||||
}
|
||||
|
||||
this.sessions.set(sessionID, {
|
||||
transactions,
|
||||
lastHash: expectedNewHash,
|
||||
streamingHash: newStreamingHash,
|
||||
lastSignature: newSignature,
|
||||
signatureAfter: signatureAfter,
|
||||
});
|
||||
|
||||
this._cachedNewContentSinceEmpty = undefined;
|
||||
this._cachedKnownState = undefined;
|
||||
}
|
||||
|
||||
expectedNewHashAfter(
|
||||
sessionID: SessionID,
|
||||
newTransactions: Transaction[],
|
||||
): { expectedNewHash: Hash; newStreamingHash: StreamingHash } {
|
||||
const streamingHash =
|
||||
this.sessions.get(sessionID)?.streamingHash.clone() ??
|
||||
new StreamingHash(this.crypto);
|
||||
|
||||
for (const transaction of newTransactions) {
|
||||
streamingHash.update(transaction);
|
||||
}
|
||||
|
||||
return {
|
||||
expectedNewHash: streamingHash.digest(),
|
||||
newStreamingHash: streamingHash,
|
||||
};
|
||||
}
|
||||
|
||||
newContentSince(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { base58 } from "@scure/base";
|
||||
import { Transaction } from "../coValueCore/verifiedState.js";
|
||||
import { RawAccountID } from "../coValues/account.js";
|
||||
import { AgentID, RawCoID, TransactionID } from "../ids.js";
|
||||
import { SessionID } from "../ids.js";
|
||||
@@ -297,6 +298,10 @@ export abstract class CryptoProvider<Blake3State = any> {
|
||||
newRandomSessionID(accountID: RawAccountID | AgentID): SessionID {
|
||||
return `${accountID}_session_z${base58.encode(this.randomBytes(8))}`;
|
||||
}
|
||||
|
||||
abstract emptyAppendOnlyVerifiedLog<T>(
|
||||
signedID: SignerID,
|
||||
): AppendOnlyVerifiedLog<T>;
|
||||
}
|
||||
|
||||
export type Hash = `hash_z${string}`;
|
||||
@@ -341,3 +346,34 @@ export type KeySecret = `keySecret_z${string}`;
|
||||
export type KeyID = `key_z${string}`;
|
||||
|
||||
export const secretSeedLength = 32;
|
||||
|
||||
export const APPEND_OK = 0;
|
||||
export const APPEND_INVALID_SIGNATURE = 1;
|
||||
|
||||
export type AppendResult = typeof APPEND_OK | typeof APPEND_INVALID_SIGNATURE;
|
||||
|
||||
export interface SessionLog {
|
||||
// these only have to be maintained in JS, anything lower level
|
||||
// only needs to store the encoded items and last streaming hash
|
||||
// and return the new signature on addNew
|
||||
transactions: readonly Transaction[];
|
||||
signerID: SignerID;
|
||||
lastSignature: Signature;
|
||||
signatureAfter: { [txIdx: number]: Signature | undefined };
|
||||
|
||||
clone(): SessionLog;
|
||||
tryAdd(
|
||||
transactions: Transaction[],
|
||||
newSignature: Signature,
|
||||
skipVerify: boolean,
|
||||
): AppendResult;
|
||||
addNewTransaction(changes: JsonValue[], signerSecret: SignerSecret): void;
|
||||
|
||||
// note: this may need to decrypt all transactions since the last decrypted one, even if some of these are invalid
|
||||
// in case we use compression etc.
|
||||
// invariant: we must call this with strictly monotonically increasing txIndex
|
||||
decryptNextTransactionChanges(
|
||||
txIndex: number,
|
||||
keySecret: KeySecret,
|
||||
): JsonValue[] | undefined;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# create-jazz-app
|
||||
|
||||
## 0.1.22
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 5bb1252: delete .env.test file
|
||||
|
||||
## 0.1.21
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"types": "src/index.ts",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"version": "0.1.21",
|
||||
"version": "0.1.22",
|
||||
"bin": {
|
||||
"create-jazz-app": "./dist/index.js"
|
||||
},
|
||||
|
||||
@@ -105,7 +105,7 @@ export const frameworkToAuthExamples: Partial<
|
||||
},
|
||||
"svelte-passkey-auth": {
|
||||
name: "Passkey auth",
|
||||
repo: "garden-co/jazz/examples/passkey-svelte",
|
||||
repo: "garden-co/jazz/starters/svelte-passkey-auth",
|
||||
platform: PLATFORM.WEB,
|
||||
},
|
||||
"rn-minimal-auth": {
|
||||
|
||||
@@ -147,6 +147,13 @@ async function scaffoldProject({
|
||||
verbose: true,
|
||||
});
|
||||
await emitter.clone(projectName);
|
||||
|
||||
// Remove .env.test file if it exists
|
||||
const envTestFilePath = `${projectName}/.env.test`;
|
||||
if (fs.existsSync(envTestFilePath)) {
|
||||
fs.unlinkSync(envTestFilePath);
|
||||
}
|
||||
|
||||
cloneSpinner.succeed(chalk.green("Template cloned successfully"));
|
||||
} catch (error) {
|
||||
cloneSpinner.fail(chalk.red("Failed to clone template"));
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
# jazz-auth-betterauth
|
||||
|
||||
## 0.14.24
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- cojson@0.14.24
|
||||
- jazz-browser@0.14.24
|
||||
- jazz-betterauth-client-plugin@0.14.24
|
||||
- jazz-tools@0.14.24
|
||||
|
||||
## 0.14.23
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jazz-auth-betterauth",
|
||||
"version": "0.14.23",
|
||||
"version": "0.14.24",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"types": "src/index.ts",
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# jazz-auth-clerk
|
||||
|
||||
## 0.14.24
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- cojson@0.14.24
|
||||
- jazz-browser@0.14.24
|
||||
- jazz-tools@0.14.24
|
||||
|
||||
## 0.14.23
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"name": "jazz-auth-clerk",
|
||||
"version": "0.14.23",
|
||||
"version": "0.14.24",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cojson": "workspace:0.14.23",
|
||||
"jazz-browser": "workspace:0.14.23",
|
||||
"jazz-tools": "workspace:0.14.23"
|
||||
"cojson": "workspace:0.14.24",
|
||||
"jazz-browser": "workspace:0.14.24",
|
||||
"jazz-tools": "workspace:0.14.24"
|
||||
},
|
||||
"scripts": {
|
||||
"format-and-lint": "biome check .",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# jazz-betterauth-client-plugin
|
||||
|
||||
## 0.14.24
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-betterauth-server-plugin@0.14.24
|
||||
|
||||
## 0.14.23
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jazz-betterauth-client-plugin",
|
||||
"version": "0.14.23",
|
||||
"version": "0.14.24",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"types": "src/index.ts",
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# jazz-betterauth-server-plugin
|
||||
|
||||
## 0.14.24
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- cojson@0.14.24
|
||||
- jazz-browser@0.14.24
|
||||
- jazz-tools@0.14.24
|
||||
|
||||
## 0.14.23
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jazz-betterauth-server-plugin",
|
||||
"version": "0.14.23",
|
||||
"version": "0.14.24",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"types": "src/index.ts",
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# jazz-browser-media-images
|
||||
|
||||
## 0.14.24
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-browser@0.14.24
|
||||
- jazz-tools@0.14.24
|
||||
|
||||
## 0.14.23
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jazz-browser-media-images",
|
||||
"version": "0.14.23",
|
||||
"version": "0.14.24",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
@@ -8,8 +8,8 @@
|
||||
"dependencies": {
|
||||
"@types/image-blob-reduce": "^4.1.1",
|
||||
"image-blob-reduce": "^4.1.0",
|
||||
"jazz-browser": "workspace:0.14.23",
|
||||
"jazz-tools": "workspace:0.14.23",
|
||||
"jazz-browser": "workspace:0.14.24",
|
||||
"jazz-tools": "workspace:0.14.24",
|
||||
"pica": "^9.0.1"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user