Compare commits

..

40 Commits

Author SHA1 Message Date
Anselm
48279ed642 Clearer API 2025-06-23 10:09:27 +01:00
Anselm
88dbfefd17 Initial interface draft 2025-06-12 19:42:27 +01:00
Trisha Lim
2939790335 Merge pull request #2306 from garden-co/feat/svelte-starter
svelte starter app
2025-06-12 16:18:41 +01:00
Trisha Lim
48c29435bc replace Loaded with co.loaded 2025-06-12 15:53:54 +01:00
Trisha Lim
8668906376 refactor helper methods in starters 2025-06-12 15:53:54 +01:00
Trisha Lim
6d84e9e83f remove changelog 2025-06-12 15:53:54 +01:00
Trisha Lim
1fea0ef69c match prettier with monorepo biome as much as possible 2025-06-12 15:53:54 +01:00
Trisha Lim
e4314accb6 upgrade vite 2025-06-12 15:53:54 +01:00
Trisha Lim
ee3a4048ef missing test command 2025-06-12 15:53:54 +01:00
Trisha Lim
9ee1edef3b update lock file 2025-06-12 15:53:53 +01:00
Trisha Lim
8ab5a09a86 fix vite and tailwind config 2025-06-12 15:53:53 +01:00
Trisha Lim
2624442903 add playwright tests 2025-06-12 15:53:52 +01:00
Trisha Lim
2d199089d5 svelte starter 2025-06-12 15:53:14 +01:00
Trisha Lim
683c170b9d svelte starter 2025-06-12 15:53:14 +01:00
Benjamin S. Leveritt
518406e23d Merge pull request #2497 from garden-co/benjamin-gco-546-throw-on-typecheck-failures-with-twoslash
Throw on typecheck failures with twoslash
2025-06-12 12:39:53 +01:00
Nikos Papadopoulos
4dcbafa058 Merge pull request #2508 from garden-co/2503-create-invitation-onclick-rather-than-pre-emptively-in-org-example
organization example app: Invitation creation moved to button click event
2025-06-12 13:06:09 +02:00
Benjamin S. Leveritt
7ae9e01848 Fix. 2025-06-12 11:27:13 +01:00
Benjamin S. Leveritt
dd9ecf660d Fix? 2025-06-12 11:24:22 +01:00
Benjamin S. Leveritt
4f849050dc Fix? 2025-06-12 11:17:33 +01:00
Benjamin S. Leveritt
681600220f Excludes homepage from catalog deps 2025-06-12 10:36:26 +01:00
Benjamin S. Leveritt
384e239ad5 Bumps next to 15 in design system 2025-06-12 10:25:34 +01:00
Benjamin S. Leveritt
54e1a09a46 Bumps next to 15 2025-06-12 10:24:07 +01:00
Benjamin S. Leveritt
392a9c5aac Moves react deps to catalogs 2025-06-12 10:22:28 +01:00
Benjamin S. Leveritt
478334eabf Merge pull request #2482 from garden-co/2480-add-more-context-to-introduction-from-the-homepage
Adds context to the introduction page
2025-06-12 08:49:10 +01:00
Benjamin S. Leveritt
479f9b0113 Adds extends to turbo configs
As they're in a workspace
2025-06-12 08:33:16 +01:00
Benjamin S. Leveritt
812622b161 Adds package manager to package.json 2025-06-12 08:25:59 +01:00
Benjamin S. Leveritt
8b35fae4b6 Adds an error suppression for old code 2025-06-12 08:20:53 +01:00
Benjamin S. Leveritt
9e2ecb0378 Bumps React to 19 2025-06-12 08:11:54 +01:00
Nikos Papadopoulos
6edd061202 removes invite link state 2025-06-11 17:07:07 +01:00
Nikos Papadopoulos
865d5385e9 moves invitation creation to button click event in organization example app 2025-06-11 16:55:18 +01:00
Benjamin S. Leveritt
a998f94789 Includes the recommendation in the error 2025-06-11 12:46:53 +01:00
Benjamin S. Leveritt
d17eecfe16 Removes z.null from docs 2025-06-11 12:19:29 +01:00
Benjamin S. Leveritt
8ebfbc86db Rethrows in production 2025-06-11 11:36:56 +01:00
Benjamin S. Leveritt
abad8e762f Changes config to throw on typecheck error 2025-06-11 11:21:49 +01:00
Benjamin S. Leveritt
037e16392e Removes list of frameworks 2025-06-11 10:55:19 +01:00
Benjamin S. Leveritt
49ac65c123 Reorders list 2025-06-11 10:55:19 +01:00
Benjamin S. Leveritt
3510fb1273 Update homepage/homepage/content/docs/index.mdx
Co-authored-by: Anselm Eickhoff <anselm.eickhoff@gmail.com>
2025-06-11 10:55:19 +01:00
Benjamin S. Leveritt
bc3efe7ca0 Tweaks 2025-06-11 10:55:19 +01:00
Benjamin S. Leveritt
3b06a7809e Adds why and how sections 2025-06-11 10:55:18 +01:00
Benjamin S. Leveritt
58aa04bb10 Adds introduction paragraph 2025-06-11 10:55:18 +01:00
56 changed files with 4056 additions and 1443 deletions

View File

@@ -0,0 +1,5 @@
---
"create-jazz-app": patch
---
use new svelte starter

View File

@@ -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

View File

@@ -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 }}

View File

@@ -25,6 +25,7 @@ jobs:
"examples/organization",
"examples/pets",
"starters/react-passkey-auth",
"starters/svelte-passkey-auth",
"packages/jazz-svelte"
]

View File

@@ -13,6 +13,7 @@
"**/android/**",
"packages/jazz-svelte/**",
"examples/*svelte*/**",
"starters/*svelte*/**",
"examples/jazz-paper-scissors/src/routeTree.gen.ts",
"homepage/homepage/**",
"**/package.json"

View File

@@ -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 (

View File

@@ -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"
}
}

View File

@@ -20,7 +20,8 @@
"paths": {
"@/*": ["./src/*"],
"@components/*": ["./src/components/*"]
}
},
"target": "ES2017"
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]

View File

@@ -12,5 +12,6 @@
"persistent": true,
"dependsOn": ["^build"]
}
}
},
"extends": ["//"]
}

View File

@@ -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();

View File

@@ -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(),
);

View File

@@ -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",

View File

@@ -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.

View File

@@ -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>

View File

@@ -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); }

View File

@@ -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;

View File

@@ -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",

View File

@@ -16,5 +16,6 @@
"persistent": true,
"dependsOn": ["build"]
}
}
},
"extends": ["//"]
}

4169
homepage/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -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"

View File

@@ -55,7 +55,6 @@
"react": "19.0.0",
"react-dom": "19.0.0",
"esbuild": "0.24.0"
},
"patchedDependencies": {
"expo-router": "patches/expo-router.patch"

View File

@@ -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?: {

View File

@@ -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(

View File

@@ -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;
}

View File

@@ -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": {

316
pnpm-lock.yaml generated
View File

@@ -1720,7 +1720,7 @@ importers:
version: link:../../packages/jazz-tools
prosekit:
specifier: ^0.13.3
version: 0.13.4(@shikijs/types@3.5.0)(@types/hast@3.0.4)(preact@10.25.3)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-transform@1.10.4)(prosemirror-view@1.39.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(solid-js@1.9.5)(svelte@5.33.14)(vue@3.5.13(typescript@5.6.2))
version: 0.13.4(@shikijs/types@3.5.0)(@types/hast@3.0.4)(preact@10.25.3)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-transform@1.10.4)(prosemirror-view@1.39.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(solid-js@1.9.5)(svelte@5.33.19)(vue@3.5.13(typescript@5.6.2))
react:
specifier: 19.0.0
version: 19.0.0
@@ -3092,6 +3092,76 @@ importers:
specifier: ^6.3.5
version: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1)
starters/svelte-passkey-auth:
dependencies:
jazz-inspector-element:
specifier: workspace:*
version: link:../../packages/jazz-inspector-element
jazz-svelte:
specifier: workspace:*
version: link:../../packages/jazz-svelte
jazz-tools:
specifier: workspace:*
version: link:../../packages/jazz-tools
devDependencies:
'@playwright/test':
specifier: ^1.50.1
version: 1.50.1
'@sveltejs/adapter-auto':
specifier: ^6.0.1
version: 6.0.1(@sveltejs/kit@2.21.4(@sveltejs/vite-plugin-svelte@5.1.0(svelte@5.33.19)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1)))(svelte@5.33.19)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1)))
'@sveltejs/kit':
specifier: ^2.21.4
version: 2.21.4(@sveltejs/vite-plugin-svelte@5.1.0(svelte@5.33.19)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1)))(svelte@5.33.19)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1))
'@sveltejs/vite-plugin-svelte':
specifier: ^5.1.0
version: 5.1.0(svelte@5.33.19)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1))
'@types/eslint':
specifier: ^9.6.0
version: 9.6.1
eslint:
specifier: ^9.7.0
version: 9.17.0(jiti@2.4.2)
eslint-config-prettier:
specifier: ^9.1.0
version: 9.1.0(eslint@9.17.0(jiti@2.4.2))
eslint-plugin-svelte:
specifier: ^2.36.0
version: 2.46.1(eslint@9.17.0(jiti@2.4.2))(svelte@5.33.19)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@22.15.18)(typescript@5.6.2))
globals:
specifier: ^15.11.0
version: 15.14.0
is-ci:
specifier: ^3.0.1
version: 3.0.1
postcss:
specifier: ^8.4.27
version: 8.5.4
prettier:
specifier: ^3.3.2
version: 3.5.3
prettier-plugin-svelte:
specifier: ^3.2.6
version: 3.4.0(prettier@3.5.3)(svelte@5.33.19)
svelte:
specifier: ^5.33.19
version: 5.33.19
svelte-check:
specifier: ^4.0.0
version: 4.1.1(picomatch@4.0.2)(svelte@5.33.19)(typescript@5.6.2)
tailwindcss:
specifier: ^4.1.7
version: 4.1.8
typescript:
specifier: 5.6.2
version: 5.6.2
typescript-eslint:
specifier: ^8.0.0
version: 8.18.1(eslint@9.17.0(jiti@2.4.2))(typescript@5.6.2)
vite:
specifier: 6.3.5
version: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1)
tests/browser-integration:
dependencies:
cojson:
@@ -6953,6 +7023,11 @@ packages:
peerDependencies:
'@sveltejs/kit': ^2.0.0
'@sveltejs/adapter-auto@6.0.1':
resolution: {integrity: sha512-mcWud3pYGPWM2Pphdj8G9Qiq24nZ8L4LB7coCUckUEy5Y7wOWGJ/enaZ4AtJTcSm5dNK1rIkBRoqt+ae4zlxcQ==}
peerDependencies:
'@sveltejs/kit': ^2.0.0
'@sveltejs/adapter-vercel@5.5.2':
resolution: {integrity: sha512-6NpumKJ3m9Y6mkSFw4Z8YnaJZc42OWLkSFCk5hD6pLrS4f+b9JVBaXpKB/xkxf1Kf7JpzZtNZKbAaoRrTtJ+Hw==}
peerDependencies:
@@ -6967,6 +7042,15 @@ packages:
svelte: ^4.0.0 || ^5.0.0-next.0
vite: ^5.0.3 || ^6.0.0
'@sveltejs/kit@2.21.4':
resolution: {integrity: sha512-683kl4BBnORaYn3vktH01HAHYep8FaiRA21LVY7d6uAX+1D/1gK4WYHRJq90vA01Cz/k6rU3n6vpf4fAn9ipkA==}
engines: {node: '>=18.13'}
hasBin: true
peerDependencies:
'@sveltejs/vite-plugin-svelte': ^3.0.0 || ^4.0.0-next.1 || ^5.0.0
svelte: ^4.0.0 || ^5.0.0-next.0
vite: ^5.0.3 || ^6.0.0
'@sveltejs/package@2.3.7':
resolution: {integrity: sha512-LYgUkde5GUYqOpXbcoCGUpEH4Ctl3Wj4u4CVZBl56dEeLW5fGHE037ZL1qlK0Ky+QD5uUfwONSeGwIOIighFMQ==}
engines: {node: ^16.14 || >=18}
@@ -6989,6 +7073,13 @@ packages:
svelte: ^5.0.0
vite: ^6.0.0
'@sveltejs/vite-plugin-svelte@5.1.0':
resolution: {integrity: sha512-wojIS/7GYnJDYIg1higWj2ROA6sSRWvcR1PO/bqEyFr/5UZah26c8Cz4u0NaqjPeVltzsVpt2Tm8d2io0V+4Tw==}
engines: {node: ^18.0.0 || ^20.0.0 || >=22}
peerDependencies:
svelte: ^5.0.0
vite: ^6.0.0
'@swc/core-darwin-arm64@1.10.1':
resolution: {integrity: sha512-NyELPp8EsVZtxH/mEqvzSyWpfPJ1lugpTQcSlMduZLj1EASLO4sC8wt8hmL1aizRlsbjCX+r0PyL+l0xQ64/6Q==}
engines: {node: '>=10'}
@@ -9876,6 +9967,9 @@ packages:
esrap@1.4.6:
resolution: {integrity: sha512-F/D2mADJ9SHY3IwksD4DAXjTt7qt7GWUf3/8RhCNWmC/67tyb55dpimHmy7EplakFaflV0R/PC+fdSPqrRHAQw==}
esrap@1.4.9:
resolution: {integrity: sha512-3OMlcd0a03UGuZpPeUC1HxR3nA23l+HEyCiZw3b3FumJIN9KphoGzDJKMXI1S72jVS1dsenDyQC0kJlO1U9E1g==}
esrecurse@4.3.0:
resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
engines: {node: '>=4.0'}
@@ -13035,6 +13129,12 @@ packages:
prettier: ^3.0.0
svelte: ^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0
prettier-plugin-svelte@3.4.0:
resolution: {integrity: sha512-pn1ra/0mPObzqoIQn/vUTR3ZZI6UuZ0sHqMK5x2jMLGrs53h0sXhkVuDcrlssHwIMk7FYrMjHBPoUSyyEEDlBQ==}
peerDependencies:
prettier: ^3.0.0
svelte: ^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0
prettier-plugin-tailwindcss@0.6.9:
resolution: {integrity: sha512-r0i3uhaZAXYP0At5xGfJH876W3HHGHDp+LCRUJrs57PBeQ6mYHMwr25KH8NPX44F2yGTvdnH7OqCshlQx183Eg==}
engines: {node: '>=14.21.3'}
@@ -14569,6 +14669,10 @@ packages:
resolution: {integrity: sha512-kRlbhIlMTijbFmVDQFDeKXPLlX1/ovXwV0I162wRqQhRcygaqDIcu1d/Ese3H2uI+yt3uT8E7ndgDthQv5v5BA==}
engines: {node: '>=18'}
svelte@5.33.19:
resolution: {integrity: sha512-udmgc1nnGeAgnfVJjOMfSOAqPAKv8N65MWN2kDuxo6BZthTaUcsLh4vP8bdZC0bMXLGn69smSZXgQOeuZBOn4Q==}
engines: {node: '>=18'}
svg-tags@1.0.0:
resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==}
@@ -15304,6 +15408,14 @@ packages:
vite:
optional: true
vitefu@1.0.6:
resolution: {integrity: sha512-+Rex1GlappUyNN6UfwbVZne/9cYC4+R2XDk9xkNXBKMw6HQagdX9PgZ8V2v1WUSK1wfBLp7qbI1+XSNIlB1xmA==}
peerDependencies:
vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0
peerDependenciesMeta:
vite:
optional: true
vitest@3.1.1:
resolution: {integrity: sha512-kiZc/IYmKICeBAZr9DQ5rT7/6bD9G7uqQEki4fxazi1jdVl2mWGzedtBs5s6llz59yQhVb7FFY2MbHzHCnT79Q==}
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
@@ -15966,7 +16078,7 @@ snapshots:
'@babel/traverse': 7.27.0
'@babel/types': 7.27.0
convert-source-map: 2.0.0
debug: 4.4.0
debug: 4.4.1
gensync: 1.0.0-beta.2
json5: 2.2.3
semver: 6.3.1
@@ -17423,7 +17535,7 @@ snapshots:
'@babel/parser': 7.26.3
'@babel/template': 7.25.9
'@babel/types': 7.26.3
debug: 4.4.0
debug: 4.4.1
globals: 11.12.0
transitivePeerDependencies:
- supports-color
@@ -17435,7 +17547,7 @@ snapshots:
'@babel/parser': 7.27.0
'@babel/template': 7.27.0
'@babel/types': 7.27.0
debug: 4.4.0
debug: 4.4.1
globals: 11.12.0
transitivePeerDependencies:
- supports-color
@@ -18076,7 +18188,7 @@ snapshots:
'@eslint/eslintrc@2.1.4':
dependencies:
ajv: 6.12.6
debug: 4.4.0
debug: 4.4.1
espree: 9.6.1
globals: 13.24.0
ignore: 5.3.2
@@ -18344,7 +18456,7 @@ snapshots:
require-from-string: 2.0.2
resolve-from: 5.0.0
resolve-workspace-root: 2.0.0
semver: 7.6.3
semver: 7.7.2
slugify: 1.6.6
sucrase: 3.35.0
transitivePeerDependencies:
@@ -18380,7 +18492,7 @@ snapshots:
'@expo/env@1.0.5':
dependencies:
chalk: 4.1.2
debug: 4.4.0
debug: 4.4.1
dotenv: 16.4.7
dotenv-expand: 11.0.7
getenv: 1.0.0
@@ -18492,9 +18604,9 @@ snapshots:
'@expo/image-utils': 0.7.4
'@expo/json-file': 9.1.4
'@react-native/normalize-colors': 0.79.2
debug: 4.4.0
debug: 4.4.1
resolve-from: 5.0.0
semver: 7.6.3
semver: 7.7.2
xml2js: 0.6.0
transitivePeerDependencies:
- supports-color
@@ -18504,7 +18616,7 @@ snapshots:
'@expo/server@0.6.2':
dependencies:
abort-controller: 3.0.0
debug: 4.4.0
debug: 4.4.1
source-map-support: 0.5.21
undici: 6.21.0
transitivePeerDependencies:
@@ -18603,7 +18715,7 @@ snapshots:
'@humanwhocodes/config-array@0.13.0':
dependencies:
'@humanwhocodes/object-schema': 2.0.3
debug: 4.4.0
debug: 4.4.1
minimatch: 3.1.2
transitivePeerDependencies:
- supports-color
@@ -19642,15 +19754,15 @@ snapshots:
- y-prosemirror
- yjs
'@prosekit/svelte@0.6.9(@shikijs/types@3.5.0)(@types/hast@3.0.4)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-transform@1.10.4)(prosemirror-view@1.39.2)(svelte@5.33.14)':
'@prosekit/svelte@0.6.9(@shikijs/types@3.5.0)(@types/hast@3.0.4)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-transform@1.10.4)(prosemirror-view@1.39.2)(svelte@5.33.19)':
dependencies:
'@prosekit/core': 0.8.1(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-transform@1.10.4)
'@prosekit/pm': 0.1.10
'@prosekit/web': 0.5.9(@shikijs/types@3.5.0)(@types/hast@3.0.4)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-transform@1.10.4)(prosemirror-view@1.39.2)
'@prosemirror-adapter/core': 0.4.0
'@prosemirror-adapter/svelte': 0.4.1(svelte@5.33.14)
'@prosemirror-adapter/svelte': 0.4.1(svelte@5.33.19)
optionalDependencies:
svelte: 5.33.14
svelte: 5.33.19
transitivePeerDependencies:
- '@shikijs/types'
- '@types/hast'
@@ -19745,13 +19857,13 @@ snapshots:
optionalDependencies:
solid-js: 1.9.5
'@prosemirror-adapter/svelte@0.4.1(svelte@5.33.14)':
'@prosemirror-adapter/svelte@0.4.1(svelte@5.33.19)':
dependencies:
'@prosemirror-adapter/core': 0.4.0
nanoid: 5.0.9
tslib: 2.8.1
optionalDependencies:
svelte: 5.33.14
svelte: 5.33.19
'@prosemirror-adapter/vue@0.4.1(vue@3.5.13(typescript@5.6.2))':
dependencies:
@@ -20234,7 +20346,7 @@ snapshots:
execa: 5.1.1
node-stream-zip: 1.15.0
ora: 5.4.1
semver: 7.6.3
semver: 7.7.2
strip-ansi: 5.2.0
wcwidth: 1.0.1
yaml: 2.6.1
@@ -20255,7 +20367,7 @@ snapshots:
execa: 5.1.1
node-stream-zip: 1.15.0
ora: 5.4.1
semver: 7.6.3
semver: 7.7.2
strip-ansi: 5.2.0
wcwidth: 1.0.1
yaml: 2.6.1
@@ -20310,7 +20422,7 @@ snapshots:
open: 6.4.0
ora: 5.4.1
prompts: 2.4.2
semver: 7.6.3
semver: 7.7.2
shell-quote: 1.8.2
sudo-prompt: 9.1.1
@@ -21179,6 +21291,10 @@ snapshots:
'@sveltejs/kit': 2.17.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.33.14)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1)))(svelte@5.33.14)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1))
import-meta-resolve: 4.1.0
'@sveltejs/adapter-auto@6.0.1(@sveltejs/kit@2.21.4(@sveltejs/vite-plugin-svelte@5.1.0(svelte@5.33.19)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1)))(svelte@5.33.19)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1)))':
dependencies:
'@sveltejs/kit': 2.21.4(@sveltejs/vite-plugin-svelte@5.1.0(svelte@5.33.19)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1)))(svelte@5.33.19)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1))
'@sveltejs/adapter-vercel@5.5.2(@sveltejs/kit@2.17.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.33.14)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1)))(svelte@5.33.14)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1)))(rollup@4.41.1)':
dependencies:
'@sveltejs/kit': 2.17.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.33.14)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1)))(svelte@5.33.14)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1))
@@ -21206,6 +21322,25 @@ snapshots:
svelte: 5.33.14
vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1)
'@sveltejs/kit@2.21.4(@sveltejs/vite-plugin-svelte@5.1.0(svelte@5.33.19)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1)))(svelte@5.33.19)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1))':
dependencies:
'@sveltejs/acorn-typescript': 1.0.5(acorn@8.14.1)
'@sveltejs/vite-plugin-svelte': 5.1.0(svelte@5.33.19)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1))
'@types/cookie': 0.6.0
acorn: 8.14.1
cookie: 0.6.0
devalue: 5.1.1
esm-env: 1.2.2
kleur: 4.1.5
magic-string: 0.30.17
mrmime: 2.0.0
sade: 1.8.1
set-cookie-parser: 2.7.1
sirv: 3.0.1
svelte: 5.33.19
vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1)
vitefu: 1.0.6(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1))
'@sveltejs/package@2.3.7(svelte@5.33.14)(typescript@5.6.2)':
dependencies:
chokidar: 4.0.3
@@ -21220,12 +21355,21 @@ snapshots:
'@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.33.14)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1)))(svelte@5.33.14)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1))':
dependencies:
'@sveltejs/vite-plugin-svelte': 5.0.3(svelte@5.33.14)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1))
debug: 4.4.0
debug: 4.4.1
svelte: 5.33.14
vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1)
transitivePeerDependencies:
- supports-color
'@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.1.0(svelte@5.33.19)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1)))(svelte@5.33.19)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1))':
dependencies:
'@sveltejs/vite-plugin-svelte': 5.1.0(svelte@5.33.19)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1))
debug: 4.4.1
svelte: 5.33.19
vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1)
transitivePeerDependencies:
- supports-color
'@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.33.14)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1))':
dependencies:
'@sveltejs/vite-plugin-svelte-inspector': 4.0.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.33.14)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1)))(svelte@5.33.14)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1))
@@ -21239,6 +21383,19 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@sveltejs/vite-plugin-svelte@5.1.0(svelte@5.33.19)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1))':
dependencies:
'@sveltejs/vite-plugin-svelte-inspector': 4.0.1(@sveltejs/vite-plugin-svelte@5.1.0(svelte@5.33.19)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1)))(svelte@5.33.19)(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1))
debug: 4.4.1
deepmerge: 4.3.1
kleur: 4.1.5
magic-string: 0.30.17
svelte: 5.33.19
vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1)
vitefu: 1.0.6(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1))
transitivePeerDependencies:
- supports-color
'@swc/core-darwin-arm64@1.10.1':
optional: true
@@ -22302,8 +22459,8 @@ snapshots:
dependencies:
'@mapbox/node-pre-gyp': 2.0.0-rc.0
'@rollup/pluginutils': 5.1.4(rollup@4.41.1)
acorn: 8.14.0
acorn-import-attributes: 1.9.5(acorn@8.14.0)
acorn: 8.14.1
acorn-import-attributes: 1.9.5(acorn@8.14.1)
async-sema: 3.1.1
bindings: 1.5.0
estree-walker: 2.0.2
@@ -22829,14 +22986,18 @@ snapshots:
mime-types: 2.1.35
negotiator: 0.6.3
acorn-import-attributes@1.9.5(acorn@8.14.0):
acorn-import-attributes@1.9.5(acorn@8.14.1):
dependencies:
acorn: 8.14.0
acorn: 8.14.1
acorn-jsx@5.3.2(acorn@8.14.0):
dependencies:
acorn: 8.14.0
acorn-jsx@5.3.2(acorn@8.14.1):
dependencies:
acorn: 8.14.1
acorn-walk@8.3.2: {}
acorn-walk@8.3.4:
@@ -24458,7 +24619,7 @@ snapshots:
eslint-compat-utils@0.5.1(eslint@9.17.0(jiti@2.4.2)):
dependencies:
eslint: 9.17.0(jiti@2.4.2)
semver: 7.6.3
semver: 7.7.2
eslint-config-prettier@8.10.0(eslint@8.57.1):
dependencies:
@@ -24544,6 +24705,25 @@ snapshots:
transitivePeerDependencies:
- ts-node
eslint-plugin-svelte@2.46.1(eslint@9.17.0(jiti@2.4.2))(svelte@5.33.19)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@22.15.18)(typescript@5.6.2)):
dependencies:
'@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0(jiti@2.4.2))
'@jridgewell/sourcemap-codec': 1.5.0
eslint: 9.17.0(jiti@2.4.2)
eslint-compat-utils: 0.5.1(eslint@9.17.0(jiti@2.4.2))
esutils: 2.0.3
known-css-properties: 0.35.0
postcss: 8.5.4
postcss-load-config: 3.1.4(postcss@8.5.4)(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@22.15.18)(typescript@5.6.2))
postcss-safe-parser: 6.0.0(postcss@8.5.4)
postcss-selector-parser: 6.1.2
semver: 7.6.3
svelte-eslint-parser: 0.43.0(svelte@5.33.19)
optionalDependencies:
svelte: 5.33.19
transitivePeerDependencies:
- ts-node
eslint-plugin-vue@9.32.0(eslint@9.17.0(jiti@2.4.2)):
dependencies:
'@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0(jiti@2.4.2))
@@ -24673,8 +24853,8 @@ snapshots:
espree@9.6.1:
dependencies:
acorn: 8.14.0
acorn-jsx: 5.3.2(acorn@8.14.0)
acorn: 8.14.1
acorn-jsx: 5.3.2(acorn@8.14.1)
eslint-visitor-keys: 3.4.3
esprima@4.0.1: {}
@@ -24687,6 +24867,10 @@ snapshots:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.0
esrap@1.4.9:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.0
esrecurse@4.3.0:
dependencies:
estraverse: 5.3.0
@@ -25768,7 +25952,7 @@ snapshots:
http-proxy-agent@7.0.2:
dependencies:
agent-base: 7.1.3
debug: 4.4.0
debug: 4.4.1
transitivePeerDependencies:
- supports-color
@@ -25780,7 +25964,7 @@ snapshots:
https-proxy-agent@7.0.6:
dependencies:
agent-base: 7.1.3
debug: 4.4.0
debug: 4.4.1
transitivePeerDependencies:
- supports-color
@@ -28485,6 +28669,11 @@ snapshots:
prettier: 3.4.2
svelte: 5.33.14
prettier-plugin-svelte@3.4.0(prettier@3.5.3)(svelte@5.33.19):
dependencies:
prettier: 3.5.3
svelte: 5.33.19
prettier-plugin-tailwindcss@0.6.9(prettier-plugin-svelte@3.3.2(prettier@3.4.2)(svelte@5.33.14))(prettier@3.4.2):
dependencies:
prettier: 3.4.2
@@ -28553,7 +28742,7 @@ snapshots:
property-information@7.1.0: {}
prosekit@0.13.4(@shikijs/types@3.5.0)(@types/hast@3.0.4)(preact@10.25.3)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-transform@1.10.4)(prosemirror-view@1.39.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(solid-js@1.9.5)(svelte@5.33.14)(vue@3.5.13(typescript@5.6.2)):
prosekit@0.13.4(@shikijs/types@3.5.0)(@types/hast@3.0.4)(preact@10.25.3)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-transform@1.10.4)(prosemirror-view@1.39.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(solid-js@1.9.5)(svelte@5.33.19)(vue@3.5.13(typescript@5.6.2)):
dependencies:
'@prosekit/basic': 0.5.1(@shikijs/types@3.5.0)(@types/hast@3.0.4)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-transform@1.10.4)(prosemirror-view@1.39.2)
'@prosekit/core': 0.8.1(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-transform@1.10.4)
@@ -28563,7 +28752,7 @@ snapshots:
'@prosekit/preact': 0.4.14(@shikijs/types@3.5.0)(@types/hast@3.0.4)(preact@10.25.3)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-transform@1.10.4)(prosemirror-view@1.39.2)
'@prosekit/react': 0.5.0(@shikijs/types@3.5.0)(@types/hast@3.0.4)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-transform@1.10.4)(prosemirror-view@1.39.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@prosekit/solid': 0.4.14(@shikijs/types@3.5.0)(@types/hast@3.0.4)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-transform@1.10.4)(prosemirror-view@1.39.2)(solid-js@1.9.5)
'@prosekit/svelte': 0.6.9(@shikijs/types@3.5.0)(@types/hast@3.0.4)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-transform@1.10.4)(prosemirror-view@1.39.2)(svelte@5.33.14)
'@prosekit/svelte': 0.6.9(@shikijs/types@3.5.0)(@types/hast@3.0.4)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-transform@1.10.4)(prosemirror-view@1.39.2)(svelte@5.33.19)
'@prosekit/vue': 0.4.14(@shikijs/types@3.5.0)(@types/hast@3.0.4)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-transform@1.10.4)(prosemirror-view@1.39.2)(vue@3.5.13(typescript@5.6.2))
'@prosekit/web': 0.5.9(@shikijs/types@3.5.0)(@types/hast@3.0.4)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-transform@1.10.4)(prosemirror-view@1.39.2)
optionalDependencies:
@@ -28571,7 +28760,7 @@ snapshots:
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
solid-js: 1.9.5
svelte: 5.33.14
svelte: 5.33.19
vue: 3.5.13(typescript@5.6.2)
transitivePeerDependencies:
- '@shikijs/types'
@@ -28937,12 +29126,12 @@ snapshots:
'@babel/helper-module-imports': 7.25.9
'@babel/traverse': 7.26.4
'@babel/types': 7.26.3
debug: 4.4.0
debug: 4.4.1
lightningcss: 1.29.1
react: 19.0.0
react-native: 0.79.2(@babel/core@7.26.0)(@react-native-community/cli@15.0.1(typescript@5.8.3))(@types/react@19.0.0)(react@19.0.0)
react-native-reanimated: 3.17.5(@babel/core@7.26.0)(react-native@0.79.2(@babel/core@7.26.0)(@react-native-community/cli@15.0.1(typescript@5.8.3))(@types/react@19.0.0)(react@19.0.0))(react@19.0.0)
semver: 7.6.3
semver: 7.7.2
tailwindcss: 3.3.2(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@22.15.18)(typescript@5.8.3))
optionalDependencies:
react-native-safe-area-context: 5.4.0(react-native@0.79.2(@babel/core@7.26.0)(@react-native-community/cli@15.0.1(typescript@5.8.3))(@types/react@19.0.0)(react@19.0.0))(react@19.0.0)
@@ -28954,12 +29143,12 @@ snapshots:
'@babel/helper-module-imports': 7.25.9
'@babel/traverse': 7.26.4
'@babel/types': 7.26.3
debug: 4.4.0
debug: 4.4.1
lightningcss: 1.29.1
react: 19.0.0
react-native: 0.79.2(@babel/core@7.26.0)(@react-native-community/cli@15.0.1(typescript@5.8.3))(@types/react@19.0.0)(react@19.0.0)
react-native-reanimated: 3.17.5(@babel/core@7.26.0)(react-native@0.79.2(@babel/core@7.26.0)(@react-native-community/cli@15.0.1(typescript@5.8.3))(@types/react@19.0.0)(react@19.0.0))(react@19.0.0)
semver: 7.6.3
semver: 7.7.2
tailwindcss: 3.4.17(ts-node@10.9.2(@swc/core@1.11.29)(@types/node@22.15.18)(typescript@5.8.3))
optionalDependencies:
react-native-safe-area-context: 5.4.0(react-native@0.79.2(@babel/core@7.26.0)(@react-native-community/cli@15.0.1(typescript@5.8.3))(@types/react@19.0.0)(react@19.0.0))(react@19.0.0)
@@ -29874,7 +30063,7 @@ snapshots:
dependencies:
color: 4.2.3
detect-libc: 2.0.3
semver: 7.6.3
semver: 7.7.2
optionalDependencies:
'@img/sharp-darwin-arm64': 0.33.5
'@img/sharp-darwin-x64': 0.33.5
@@ -30406,6 +30595,18 @@ snapshots:
transitivePeerDependencies:
- picomatch
svelte-check@4.1.1(picomatch@4.0.2)(svelte@5.33.19)(typescript@5.6.2):
dependencies:
'@jridgewell/trace-mapping': 0.3.25
chokidar: 4.0.3
fdir: 6.4.2(picomatch@4.0.2)
picocolors: 1.1.1
sade: 1.8.1
svelte: 5.33.19
typescript: 5.6.2
transitivePeerDependencies:
- picomatch
svelte-eslint-parser@0.43.0(svelte@5.33.14):
dependencies:
eslint-scope: 7.2.2
@@ -30416,6 +30617,16 @@ snapshots:
optionalDependencies:
svelte: 5.33.14
svelte-eslint-parser@0.43.0(svelte@5.33.19):
dependencies:
eslint-scope: 7.2.2
eslint-visitor-keys: 3.4.3
espree: 9.6.1
postcss: 8.5.4
postcss-scss: 4.0.9(postcss@8.5.4)
optionalDependencies:
svelte: 5.33.19
svelte-sonner@0.3.28(svelte@5.33.14):
dependencies:
svelte: 5.33.14
@@ -30444,6 +30655,23 @@ snapshots:
magic-string: 0.30.17
zimmerframe: 1.1.2
svelte@5.33.19:
dependencies:
'@ampproject/remapping': 2.3.0
'@jridgewell/sourcemap-codec': 1.5.0
'@sveltejs/acorn-typescript': 1.0.5(acorn@8.14.1)
'@types/estree': 1.0.7
acorn: 8.14.1
aria-query: 5.3.2
axobject-query: 4.1.0
clsx: 2.1.1
esm-env: 1.2.2
esrap: 1.4.9
is-reference: 3.0.3
locate-character: 3.0.0
magic-string: 0.30.17
zimmerframe: 1.1.2
svg-tags@1.0.0: {}
swr@2.2.5(react@19.0.0):
@@ -31223,7 +31451,7 @@ snapshots:
vite-node@3.1.1(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1):
dependencies:
cac: 6.7.14
debug: 4.4.0
debug: 4.4.1
es-module-lexer: 1.6.0
pathe: 2.0.3
vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1)
@@ -31285,13 +31513,13 @@ snapshots:
dependencies:
'@antfu/utils': 0.7.10
'@rollup/pluginutils': 5.1.4(rollup@4.41.1)
debug: 4.4.0
debug: 4.4.1
error-stack-parser-es: 0.1.5
fs-extra: 11.2.0
open: 10.1.0
perfect-debounce: 1.0.0
picocolors: 1.1.1
sirv: 3.0.0
sirv: 3.0.1
vite: 6.3.5(@types/node@22.10.2)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1)
transitivePeerDependencies:
- rollup
@@ -31376,6 +31604,10 @@ snapshots:
optionalDependencies:
vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1)
vitefu@1.0.6(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1)):
optionalDependencies:
vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1)
vitest@3.1.1(@types/node@22.15.18)(@vitest/browser@3.1.1)(@vitest/ui@3.1.1)(happy-dom@17.4.4)(jiti@2.4.2)(jsdom@25.0.1)(lightningcss@1.30.1)(msw@2.7.0(@types/node@22.15.18)(typescript@5.6.2))(terser@5.37.0)(tsx@4.19.3)(yaml@2.6.1):
dependencies:
'@vitest/expect': 3.1.1
@@ -31514,14 +31746,14 @@ snapshots:
vue-eslint-parser@9.4.3(eslint@9.17.0(jiti@2.4.2)):
dependencies:
debug: 4.4.0
debug: 4.4.1
eslint: 9.17.0(jiti@2.4.2)
eslint-scope: 7.2.2
eslint-visitor-keys: 3.4.3
espree: 9.6.1
esquery: 1.6.0
lodash: 4.17.21
semver: 7.6.3
semver: 7.7.2
transitivePeerDependencies:
- supports-color

View File

@@ -54,6 +54,8 @@ const packageJsonFiles = findPackageJsonFiles(rootDir);
let hasIssues = false;
for (const file of packageJsonFiles) {
if (file.includes("homepage")) continue;
const issues = checkCatalogDependencies(file);
if (issues.length > 0) {
hasIssues = true;

View File

@@ -41,6 +41,6 @@ If you have feedback, let us know on [Discord](https://discord.gg/utDMjHYg42) or
## Configuration: sync server
By default, the app uses [Jazz Cloud](https://jazz.tools/cloud) (`wss://cloud.jazz.tools`) - so cross-device use, invites and collaboration should just work.
By default, the React starter app uses [Jazz Cloud](https://jazz.tools/cloud) (`wss://cloud.jazz.tools`) - so cross-device use, invites and collaboration should just work.
You can also run a local sync server by running `npx jazz-run sync` and adding the query param `?sync=ws://localhost:4200` to the URL of the example app (for example: `http://localhost:5173/?peer=ws://localhost:4200`), or by setting the `sync` parameter of the `<Jazz.Provider>` provider component in [./src/Main.tsx](./src/Main.tsx).
You can also run a local sync server by running `npx jazz-run sync`, and setting the `sync` parameter of `JazzProvider` in [./src/app.tsx](./src/app.tsx) to `{ peer: "ws://localhost:4200" }`.

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 | React + Tailwind</title>
</head>

View File

@@ -2,7 +2,7 @@ import { useAccount, useIsAuthenticated } from "jazz-react";
import { AuthButton } from "./AuthButton.tsx";
import { Form } from "./Form.tsx";
import { Logo } from "./Logo.tsx";
import { AccountRoot, JazzAccount } from "./schema.ts";
import { JazzAccount, getUserAge } from "./schema.ts";
function App() {
const { me } = useAccount(JazzAccount, {
@@ -32,7 +32,7 @@ function App() {
!
</h1>
{!!me?.root && (
<p>As of today, you are {AccountRoot.age(me.root)} years old.</p>
<p>As of today, you are {getUserAge(me.root)} years old.</p>
)}
</div>

View File

@@ -3,7 +3,7 @@
* https://jazz.tools/docs/react/schemas/covalues
*/
import { Group, Loaded, co, z } from "jazz-tools";
import { Group, co, z } from "jazz-tools";
/** The account profile is an app-specific per-user public `CoMap`
* where you can store top-level objects for that user */
@@ -19,17 +19,14 @@ export const JazzProfile = co.profile({
/** The account root is an app-specific per-user private `CoMap`
* where you can store top-level objects for that user */
export const AccountRoot = co
.map({
dateOfBirth: z.date(),
})
.withHelpers((Self) => ({
// Add helper methods here
age(root: Loaded<typeof Self>) {
if (!root?.dateOfBirth) return null;
return new Date().getFullYear() - root.dateOfBirth.getFullYear();
},
}));
export const AccountRoot = co.map({
dateOfBirth: z.date(),
});
export function getUserAge(root: co.loaded<typeof AccountRoot> | undefined) {
if (!root) return null;
return new Date().getFullYear() - root.dateOfBirth.getFullYear();
}
export const JazzAccount = co
.account({

21
starters/svelte-passkey-auth/.gitignore vendored Normal file
View File

@@ -0,0 +1,21 @@
node_modules
# Output
.output
.vercel
/.svelte-kit
/build
# OS
.DS_Store
Thumbs.db
# Env
.env
.env.*
!.env.example
!.env.test
# Vite
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

View File

@@ -0,0 +1 @@
engine-strict=true

View File

@@ -0,0 +1,4 @@
# Package Managers
package-lock.json
pnpm-lock.yaml
yarn.lock

View File

@@ -0,0 +1,16 @@
{
"useTabs": false,
"tabWidth": 2,
"singleQuote": false,
"trailingComma": "all",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"overrides": [
{
"files": "*.svelte",
"options": {
"parser": "svelte"
}
}
]
}

View File

@@ -0,0 +1,46 @@
# Jazz Svelte starter with Tailwind and Passkey Auth
A minimal starter template for building apps with **[Jazz](https://jazz.tools)**, Svelte, TailwindCSS, and Passkey Auth.
## Creating an app
Create a new Jazz app.
```bash
npx create-jazz-app@latest
```
## Running locally
Install dependencies:
```bash
npm i
# or
yarn
```
Then, run the development server:
```bash
npm run dev
# or
yarn dev
```
Open [http://localhost:5173](http://localhost:5173) with your browser to see the result.
## Learning Jazz
You can start by playing with the form, adding a new field in [./src/lib/schema.ts](./src/lib/schema.ts),
and seeing how easy it is to structure your data, and perform basic operations.
## Questions / problems / feedback
If you have feedback, let us know on [Discord](https://discord.gg/utDMjHYg42) or open an issue or PR to fix something that seems wrong.
## Configuration: sync server
By default, the Svelte starter app uses [Jazz Cloud](https://jazz.tools/cloud) (`wss://cloud.jazz.tools`) - so cross-device use, invites and collaboration should just work.
You can also run a local sync server by running `npx jazz-run sync`, and setting the `sync` parameter of `JazzProvider` in [./src/routes/+layout.svelte](./src/routes/+layout.svelte) to `{ peer: "ws://localhost:4200" }`.

View File

@@ -0,0 +1,33 @@
import js from "@eslint/js";
import prettier from "eslint-config-prettier";
import svelte from "eslint-plugin-svelte";
import globals from "globals";
import ts from "typescript-eslint";
export default ts.config(
js.configs.recommended,
...ts.configs.recommended,
...svelte.configs["flat/recommended"],
prettier,
...svelte.configs["flat/prettier"],
{
languageOptions: {
globals: {
...globals.browser,
...globals.node,
},
},
},
{
files: ["**/*.svelte"],
languageOptions: {
parserOptions: {
parser: ts.parser,
},
},
},
{
ignores: ["build/", ".svelte-kit/", "dist/"],
},
);

View File

@@ -0,0 +1,45 @@
{
"name": "svelte-passkey-auth",
"version": "0.0.89",
"type": "module",
"private": true,
"scripts": {
"dev": "vite dev",
"build": "pnpm run check && vite build",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"format": "prettier --write .",
"lint": "prettier --check . && eslint .",
"format-and-lint": "pnpm run format && pnpm run lint",
"format-and-lint:fix": "pnpm run format --write && pnpm run lint --fix",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^6.0.1",
"@sveltejs/kit": "^2.21.4",
"@sveltejs/vite-plugin-svelte": "^5.1.0",
"@types/eslint": "^9.6.0",
"eslint": "^9.7.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.36.0",
"globals": "^15.11.0",
"is-ci": "^3.0.1",
"@playwright/test": "^1.50.1",
"postcss": "^8.4.27",
"prettier": "^3.3.2",
"prettier-plugin-svelte": "^3.2.6",
"svelte": "^5.33.19",
"svelte-check": "^4.0.0",
"tailwindcss": "^4.1.7",
"typescript": "5.6.2",
"typescript-eslint": "^8.0.0",
"vite": "6.3.5"
},
"dependencies": {
"jazz-inspector-element": "workspace:*",
"jazz-svelte": "workspace:*",
"jazz-tools": "workspace:*"
}
}

View 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,
},
],
});

View File

@@ -0,0 +1,5 @@
export default {
plugins: {
"@tailwindcss/postcss": {},
},
};

View File

@@ -0,0 +1 @@
export const apiKey = "jazz-svelte-starter@garden.co";

View File

@@ -0,0 +1 @@
@import "tailwindcss";

View File

@@ -0,0 +1,13 @@
// See https://svelte.dev/docs/kit/types#app.d.ts
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
}
export {};

View File

@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover" class="p-3 max-w-2xl mx-auto">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View File

@@ -0,0 +1,48 @@
<script lang="ts">
import { JazzAccount } from "$lib/schema";
import { AccountCoState } from "jazz-svelte";
const account = new AccountCoState(JazzAccount, {
resolve: {
profile: true,
root: true,
},
});
const me = $derived(account.current);
const dateOfBirth = $derived(me ? me.root?.dateOfBirth?.toISOString().split("T")[0] || "" : "");
function handleDateOfBirthChange(event: Event & { currentTarget: HTMLInputElement }) {
if (me && event.currentTarget.value) {
me.root.dateOfBirth = new Date(event.currentTarget.value);
}
}
</script>
{#if me}
<div class="grid gap-4 border p-8 border-stone-200">
<div class="flex items-center gap-3">
<label for="firstName" class="sm:w-32"> First name </label>
<input
type="text"
id="firstName"
placeholder="Enter your first name here..."
class="border border-stone-300 rounded shadow-xs py-1 px-2 flex-1"
bind:value={me.profile.firstName}
/>
</div>
<div class="flex items-center gap-3">
<label for="dateOfBirth" class="sm:w-32"> Date of birth </label>
<input
type="date"
id="dateOfBirth"
class="border border-stone-300 rounded shadow-xs py-1 px-2 flex-1"
value={dateOfBirth}
onchange={handleDateOfBirthChange}
/>
</div>
<!--Add more fields here-->
</div>
{/if}

View File

@@ -0,0 +1,50 @@
<script lang="ts">
import { JazzAccount } from "$lib/schema";
import { usePasskeyAuth } from "jazz-svelte";
import { AccountCoState } from "jazz-svelte";
let { appName } = $props();
const { logOut } = new AccountCoState(JazzAccount);
const { current, state } = $derived(
usePasskeyAuth({
appName,
}),
);
const isAuthenticated = $derived(state === "signedIn");
</script>
<header>
<nav class="flex justify-between items-center">
{#if isAuthenticated}
<span>You're logged in.</span>
{:else}
<span>Authenticate to share the data with another device.</span>
{/if}
{#if isAuthenticated}
<button type="button" onclick={logOut} class="bg-stone-100 py-1.5 px-3 text-sm rounded-md">
Log out
</button>
{:else}
<div class="flex gap-2">
<button
type="button"
class="bg-stone-100 py-1.5 px-3 text-sm rounded-md"
onclick={() => current.signUp("")}
>
Sign up
</button>
<button
type="button"
class="bg-stone-100 py-1.5 px-3 text-sm rounded-md"
onclick={() => current.logIn()}
>
Log in
</button>
</div>
{/if}
</nav>
</header>

View File

@@ -0,0 +1 @@
// place files you want to import through the `$lib` alias in this folder.

View File

@@ -0,0 +1,63 @@
/**
* Learn about schemas here:
* https://jazz.tools/docs/svelte/schemas/covalues
*/
import { Group, co, z } from "jazz-tools";
/** The account profile is an app-specific per-user public `CoMap`
* where you can store top-level objects for that user */
export const JazzProfile = co.profile({
/**
* Learn about CoValue field/item types here:
* https://jazz.tools/docs/svelte/schemas/covalues#covalue-fielditem-types
*/
firstName: z.string(),
// Add public fields here
});
/** The account root is an app-specific per-user private `CoMap`
* where you can store top-level objects for that user */
export const AccountRoot = co.map({
dateOfBirth: z.date(),
});
export function getUserAge(root: co.loaded<typeof AccountRoot> | undefined) {
if (!root) return null;
return new Date().getFullYear() - root.dateOfBirth.getFullYear();
}
export const JazzAccount = co
.account({
profile: JazzProfile,
root: AccountRoot,
})
.withMigration(async (account) => {
/** The account migration is run on account creation and on every log-in.
* You can use it to set up the account root and any other initial CoValues you need.
*/
if (account.root === undefined) {
const group = Group.create();
account.root = AccountRoot.create(
{
dateOfBirth: new Date("1/1/1990"),
},
group,
);
}
if (account.profile === undefined) {
const group = Group.create();
group.addMember("everyone", "reader"); // The profile info is visible to everyone
account.profile = JazzProfile.create(
{
name: "",
firstName: "",
},
group,
);
}
});

View File

@@ -0,0 +1,28 @@
<script lang="ts">
import { JazzProvider } from "jazz-svelte";
import { apiKey } from "../apiKey";
import "../app.css";
import Header from "$lib/components/Header.svelte";
import "jazz-inspector-element";
import { JazzAccount } from "$lib/schema";
let { children } = $props();
let appName = "Jazz Svelte starter";
</script>
<svelte:head>
<title>Jazz | Svelte + Tailwind</title>
</svelte:head>
<JazzProvider
sync={{
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
}}
AccountSchema={JazzAccount}
>
<jazz-inspector></jazz-inspector>
<Header {appName} />
<main class="max-w-2xl mx-auto px-3 mt-16 flex flex-col gap-8">
{@render children?.()}
</main>
</JazzProvider>

View File

@@ -0,0 +1,42 @@
<script lang="ts">
import Form from "$lib/components/Form.svelte";
import { getUserAge, JazzAccount } from "$lib/schema";
import { AccountCoState } from "jazz-svelte";
const account = new AccountCoState(JazzAccount, {
resolve: {
profile: true,
root: true,
},
});
const me = $derived(account.current);
</script>
<div class="text-center">
<h1>
Welcome{#if me?.profile?.firstName}, {me?.profile.firstName}{/if}!
</h1>
{#if me?.root}
<p>As of today, you are {getUserAge(me?.root)} years old.</p>
{/if}
</div>
<Form />
<p class="text-center">
Edit the form above,
<button type="button" onclick={() => window.location.reload()} class="font-semibold underline">
refresh
</button>
this page, and see your changes persist.
</p>
<p class="text-center">
Edit <code class="font-semibold">schema.ts</code> to add more fields.
</p>
<p class="text-center my-16">
Go to
<a class="font-semibold underline" href="https://jazz.tools/docs"> jazz.tools/docs </a>
for our docs.
</p>

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,18 @@
import adapter from "@sveltejs/adapter-auto";
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://svelte.dev/docs/kit/integrations
// for more information about preprocessors
preprocess: vitePreprocess(),
kit: {
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
adapter: adapter(),
},
};
export default config;

View File

@@ -0,0 +1,9 @@
import { expect, test } from "@playwright/test";
test("home page loads", async ({ page }) => {
await page.goto("/");
await expect(page.getByText("Welcome!")).toBeVisible();
await page.getByLabel("Name").fill("Bob");
await expect(page.getByText("Welcome, Bob!")).toBeVisible();
});

View File

@@ -0,0 +1,19 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler"
}
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
}

View File

@@ -0,0 +1,6 @@
import { sveltekit } from "@sveltejs/kit/vite";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [sveltekit()],
});