Compare commits

..

75 Commits

Author SHA1 Message Date
Guido D'Orsi
597545741b Merge pull request #2515 from garden-co/changeset-release/main
Version Packages
2025-06-13 17:58:33 +02:00
github-actions[bot]
5e42e2a8d8 Version Packages 2025-06-13 15:52:42 +00:00
Guido D'Orsi
19e2a61398 fix: changeset 2025-06-13 17:48:32 +02:00
Trisha Lim
5b06a9d0a7 Merge pull request #2528 from garden-co/chat-svelte
Create Svelte version of chat example
2025-06-13 15:29:58 +01:00
Trisha Lim
41c4fbb8f9 update lock file 2025-06-13 15:14:20 +01:00
Trisha Lim
95febc7777 Revert "update lock file"
This reverts commit 7d80839396a88bfa85d71a79a0fe899b0b6c9fc1.
2025-06-13 15:13:35 +01:00
Trisha Lim
7ddff0a550 update lock file 2025-06-13 15:13:35 +01:00
Joe Innes
cebc58cd3a fix(layout): 🔄 revert to random username generation
* Removed derived username assignment from URL parameters.

https://github.com/garden-co/jazz/pull/2339#discussion_r2139972049
2025-06-13 15:13:22 +01:00
Joe Innes
044626fbc8 test(chat): add test to ensure user is not assigned 'Anonymous user' name
* Implemented a new test case to verify that users do not receive the username 'Anonymous user' upon joining the chat.
* Added a method `expectUserNameNotToBeAnonymousUser` in `ChatPage` to facilitate this check.
2025-06-13 15:13:22 +01:00
Joe Innes
28fdc43c89 fix(chat): 🔄 reload on logout to update profile name
* Ensure the application reloads after logging out to reflect the default profile name.
* Also trim the `user` URL param (in case a user passes an empty param, generate a random username)
2025-06-13 15:13:22 +01:00
Joe Innes
2602a49f12 fix(app.html): 🖼️ add type attribute to icon link
* Ensures the correct MIME type is specified for the icon.
2025-06-13 15:13:22 +01:00
Joe Innes
8505b0a0b6 Minor tweaks:
- Update API Key
- Remove Prettier comment
- Fix README (#2424)
2025-06-13 15:13:22 +01:00
Joe Innes
f2418ff364 Create Svelte version of chat example 2025-06-13 15:13:22 +01:00
Joe Innes
c81a49ebc3 Create Svelte version of chat example 2025-06-13 15:13:20 +01:00
Guido D'Orsi
3cd07dca31 Merge pull request #2518 from garden-co/fix/passkey-login
fix(passkey): increase the challenge to 20 bytes and disable the attestation
2025-06-13 15:05:15 +02:00
Guido D'Orsi
3763a99e14 test: fix passskey tests 2025-06-13 14:01:30 +02:00
Guido D'Orsi
201e2fd015 Merge pull request #2299 from garden-co/emil/group-inheritance-evolution
feat: deprecate extend methods
2025-06-13 13:18:21 +02:00
Guido D'Orsi
3e743d56d3 fix: improve the error UI in PasskeyAuthBasicUI 2025-06-13 11:37:43 +02:00
Guido D'Orsi
b3ff726794 chore: changeset 2025-06-13 11:25:08 +02:00
Guido D'Orsi
f98f88d89b fix: restore the attestation to direct 2025-06-13 11:24:46 +02:00
Trisha Lim
56974412df Merge pull request #2513 from joeinnes/2371-gitignore-dotenv-files
chore: ✏️ update README and .gitignore files
2025-06-12 20:23:11 +01:00
Joe Innes
adaedd2da2 chore: ✏️ update .env.example and fix demo URL
* Added .env.example file with necessary environment variables.
* Corrected demo URL in README.
2025-06-12 21:13:56 +02:00
Guido D'Orsi
a11472a497 chore: show the error cause on the PasskeyAuthBasicUI 2025-06-12 20:02:29 +02:00
Guido D'Orsi
3f269c7483 chore: log the error cause on the music player auth modal 2025-06-12 20:00:55 +02:00
Guido D'Orsi
81b6b8fd04 fix: increase the challenge to 20 bytes and disable the attestation 2025-06-12 19:47:55 +02: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
Joe Innes
ce0e9f0102 chore: ✏️ update README and .gitignore files
* Added instructions for renaming `.env.example` to `.env` in multiple README files.
* Updated `.gitignore` files to include `.env` and `.env.*` while excluding `.env.example` and `.env.test`.
* Removed `.env` file from `examples/multi-cursors`.
* Improved consistency across example projects.
2025-06-12 13:40:19 +02: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
Emil Sayahi
034bd20b39 Merge branch 'main' into emil/group-inheritance-evolution 2025-06-10 09:10:10 -07:00
Emil Sayahi
87f82ac7cd fix(changeset): minor to patch 2025-06-10 08:07:48 -07:00
Emil Sayahi
e8c1a3535a fix(docs): remove parent/child terminology 2025-05-21 09:09:05 -04:00
Emil Sayahi
b05878962b Merge branch 'main' into emil/group-inheritance-evolution 2025-05-21 08:54:41 -04:00
Emil Sayahi
54b4595f23 feat(docs): reduce 'parent', 'child' groups 2025-05-20 18:37:23 -04:00
Emil Sayahi
99a2d9be00 chore: changeset 2025-05-20 17:07:37 -04:00
Emil Sayahi
bf1fbdc8c4 feat(docs): 'Groups as members' 2025-05-20 17:01:33 -04:00
Emil Sayahi
14b3aa29c7 feat(docs): type-checking for inheritance docs 2025-05-20 16:00:26 -04:00
Emil Sayahi
bfaadb9d67 feat(tests): unextend test in jazz-tools 2025-05-20 15:38:25 -04:00
Emil Sayahi
cb770e565d feat: group extend should error with writeOnly
todo:
- add unextend tests in `packages/jazz-tools/src/tests/groupsAndAccounts.test.ts`
- test affected example apps by hand
- rewrite group inheritance documentation in terms of adding & removing groups as members
- changeset
2025-05-20 14:52:32 -04:00
Emil Sayahi
a50a9e6c9b feat: deprecate extend methods
todo:
- add unextend tests in `packages/jazz-tools/src/tests/groupsAndAccounts.test.ts`
  - also add more `addMember` tests
    - `member` is not `Group` but `role` is `undefined` (type error)
    - `member` is not `Group` but `role` is `inherit` (type error)
- changeset
2025-05-20 13:35:43 -04:00
195 changed files with 7049 additions and 1542 deletions

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

@@ -5,10 +5,12 @@ This example demonstrates how to integrate [Better Auth](https://www.better-auth
## Getting started
To run this example, you may either:
* Clone the Jazz monorepo and run this example from within.
* Create a new Jazz project using this example as a template, and run that new project.
- Clone the Jazz monorepo and run this example from within.
- Create a new Jazz project using this example as a template, and run that new project.
### Setting environment variables
- `NEXT_PUBLIC_AUTH_BASE_URL`: A URL to a Better Auth server. If undefined, the example will self-host a Better Auth server.
- `BETTER_AUTH_SECRET`: The encryption secret used by the self-hosted Better Auth server (required only if `NEXT_PUBLIC_AUTH_BASE_URL` is undefined)
- `GITHUB_CLIENT_ID`: The client ID for the GitHub OAuth provider used by the self-hosted Better Auth server (required only if `NEXT_PUBLIC_AUTH_BASE_URL` is undefined)
@@ -17,38 +19,64 @@ To run this example, you may either:
### Using this example as a template
1. Create a new Jazz project, and use this example as a template.
```sh
npx create-jazz-app@latest betterauth-app --example betterauth
```
2. Navigate to the new project and start the development server.
2. Navigate to the new project and install dependencies.
```sh
cd betterauth-app
pnpm install
```
3. Create a .env file (don't forget to set your [BETTER_AUTH_SECRET](https://www.better-auth.com/docs/installation#set-environment-variables)!)
```sh
mv .env.example .env
```
4. Start the development server
```sh
pnpm dev
```
https://www.better-auth.com/docs/installation#set-environment-variables
### Using the monorepo
This requires `pnpm` to be installed, see [https://pnpm.io/installation](https://pnpm.io/installation).
Clone the jazz repository.
```bash
git clone https://github.com/garden-co/jazz.git
```
Install and build dependencies.
```bash
pnpm i && npx turbo build
```
Go to the example directory.
```bash
cd jazz/examples/betterauth/
```
Create a .env file (don't forget to set your [BETTER_AUTH_SECRET](https://www.better-auth.com/docs/installation#set-environment-variables)!)
```sh
mv .env.example .env
```
Start the dev server.
```bash
pnpm dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

View File

@@ -8,9 +8,12 @@ First, install dependencies and build the project:
```bash
pnpm i
mv .env.example .env
pnpm run build
```
Don't forget to update `VITE_CLERK_PUBLISHABLE_KEY` in `.env` with your [Publishable Key](https://clerk.com/docs/deployments/clerk-environment-variables#clerk-publishable-and-secret-keys) from Clerk.
### 2. Inside the `examples/chat-rn-expo-clerk` Directory
Next, navigate to the specific example project and run the following commands:

View File

@@ -36,4 +36,9 @@ yarn-error.*
# typescript
*.tsbuildinfo
android/
ios/
ios/
# env files
.env
.env.*
!.env.example
!.env.test

View File

@@ -73,3 +73,9 @@ yarn-error.log
!.yarn/releases
!.yarn/sdks
!.yarn/versions
# env files
.env
.env.*
!.env.example
!.env.test

21
examples/chat-svelte/.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": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"overrides": [
{
"files": "*.svelte",
"options": {
"parser": "svelte"
}
}
]
}

View File

@@ -0,0 +1,545 @@
# passkey-svelte
## 0.0.85
### Patch Changes
- Updated dependencies [99a2d9b]
- jazz-tools@0.14.25
- jazz-browser-media-images@0.14.25
- jazz-svelte@0.14.25
## 0.0.84
### Patch Changes
- jazz-svelte@0.13.30
- jazz-tools@0.13.30
## 0.0.83
### Patch Changes
- jazz-svelte@0.13.29
- jazz-tools@0.13.29
## 0.0.82
### Patch Changes
- jazz-svelte@0.13.28
- jazz-tools@0.13.28
## 0.0.81
### Patch Changes
- jazz-svelte@0.13.27
- jazz-tools@0.13.27
## 0.0.80
### Patch Changes
- Updated dependencies [ff846d9]
- jazz-tools@0.13.26
- jazz-svelte@0.13.26
## 0.0.79
### Patch Changes
- jazz-svelte@0.13.25
- jazz-tools@0.13.25
## 0.0.78
### Patch Changes
- Updated dependencies [ec546b4]
- jazz-svelte@0.13.24
## 0.0.77
### Patch Changes
- Updated dependencies [3431076]
- Updated dependencies [02a240c]
- jazz-svelte@0.13.23
- jazz-tools@0.13.23
## 0.0.76
### Patch Changes
- jazz-svelte@0.13.21
- jazz-tools@0.13.21
## 0.0.75
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-svelte@0.13.20
## 0.0.74
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-svelte@0.13.19
## 0.0.73
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-svelte@0.13.18
## 0.0.72
### Patch Changes
- jazz-svelte@0.13.17
- jazz-tools@0.13.17
## 0.0.71
### Patch Changes
- jazz-svelte@0.13.16
- jazz-tools@0.13.16
## 0.0.70
### Patch Changes
- jazz-svelte@0.13.15
- jazz-tools@0.13.15
## 0.0.69
### Patch Changes
- jazz-svelte@0.13.14
- jazz-tools@0.13.14
## 0.0.68
### Patch Changes
- jazz-svelte@0.13.13
- jazz-tools@0.13.13
## 0.0.67
### Patch Changes
- Updated dependencies [4547525]
- jazz-tools@0.13.12
- jazz-svelte@0.13.12
## 0.0.66
### Patch Changes
- Updated dependencies [17273a6]
- jazz-tools@0.13.11
- jazz-svelte@0.13.11
## 0.0.65
### Patch Changes
- jazz-svelte@0.13.10
- jazz-tools@0.13.10
## 0.0.64
### Patch Changes
- Updated dependencies [a6cf01f]
- jazz-tools@0.13.9
- jazz-svelte@0.13.9
## 0.0.63
### Patch Changes
- jazz-svelte@0.13.7
## 0.0.62
### Patch Changes
- jazz-svelte@0.13.5
## 0.0.61
### Patch Changes
- jazz-svelte@0.13.4
## 0.0.60
### Patch Changes
- jazz-svelte@0.13.3
## 0.0.59
### Patch Changes
- jazz-svelte@0.13.2
## 0.0.58
### Patch Changes
- jazz-svelte@0.13.0
## 0.0.57
### Patch Changes
- jazz-svelte@0.12.2
## 0.0.56
### Patch Changes
- jazz-svelte@0.12.1
## 0.0.55
### Patch Changes
- jazz-svelte@0.12.0
## 0.0.54
### Patch Changes
- jazz-svelte@0.11.8
## 0.0.53
### Patch Changes
- jazz-svelte@0.11.7
## 0.0.52
### Patch Changes
- 1bfa9bb: Removed when="singedUp" from examples apps' Jazz providers. This is a really niche use-case option and can lead to broken-feeling experiences when anonymous users try to load something.
- jazz-svelte@0.11.6
## 0.0.51
### Patch Changes
- jazz-svelte@0.11.5
## 0.0.50
### Patch Changes
- jazz-svelte@0.11.4
## 0.0.49
### Patch Changes
- jazz-svelte@0.11.3
## 0.0.48
### Patch Changes
- jazz-svelte@0.11.2
## 0.0.47
### Patch Changes
- jazz-svelte@0.11.0
## 0.0.46
### Patch Changes
- jazz-svelte@0.10.15
## 0.0.45
### Patch Changes
- jazz-svelte@0.10.14
## 0.0.44
### Patch Changes
- jazz-svelte@0.10.13
## 0.0.43
### Patch Changes
- Updated dependencies [4612e05]
- jazz-svelte@0.10.12
## 0.0.42
### Patch Changes
- jazz-svelte@0.10.9
## 0.0.41
### Patch Changes
- jazz-svelte@0.10.8
## 0.0.40
### Patch Changes
- Updated dependencies [1136d9b]
- jazz-svelte@0.10.7
## 0.0.39
### Patch Changes
- jazz-svelte@0.10.6
## 0.0.38
### Patch Changes
- jazz-svelte@0.10.5
## 0.0.37
### Patch Changes
- jazz-svelte@0.10.4
## 0.0.36
### Patch Changes
- jazz-svelte@0.10.3
## 0.0.35
### Patch Changes
- jazz-svelte@0.10.2
## 0.0.34
### Patch Changes
- jazz-svelte@0.10.1
## 0.0.33
### Patch Changes
- Updated dependencies [b426342]
- jazz-svelte@0.10.0
## 0.0.32
### Patch Changes
- jazz-svelte@0.9.23
## 0.0.31
### Patch Changes
- jazz-svelte@0.9.22
## 0.0.30
### Patch Changes
- jazz-svelte@0.9.21
## 0.0.29
### Patch Changes
- jazz-svelte@0.9.20
## 0.0.28
### Patch Changes
- jazz-svelte@0.9.19
## 0.0.27
### Patch Changes
- jazz-svelte@0.9.18
## 0.0.26
### Patch Changes
- jazz-svelte@0.9.17
## 0.0.25
### Patch Changes
- jazz-svelte@0.9.16
## 0.0.24
### Patch Changes
- jazz-svelte@0.9.15
## 0.0.23
### Patch Changes
- jazz-svelte@0.9.14
## 0.0.22
### Patch Changes
- jazz-svelte@0.9.13
## 0.0.21
### Patch Changes
- jazz-svelte@0.9.12
## 0.0.20
### Patch Changes
- jazz-svelte@0.9.11
## 0.0.19
### Patch Changes
- jazz-svelte@0.9.10
## 0.0.18
### Patch Changes
- jazz-svelte@0.9.9
## 0.0.17
### Patch Changes
- jazz-svelte@0.9.8
## 0.0.16
### Patch Changes
- jazz-svelte@0.9.1
## 0.0.15
### Patch Changes
- Updated dependencies [9dd8d95]
- jazz-svelte@0.9.0
## 0.0.14
### Patch Changes
- jazz-svelte@0.8.51
## 0.0.13
### Patch Changes
- jazz-svelte@0.8.50
## 0.0.12
### Patch Changes
- jazz-svelte@0.8.49
## 0.0.11
### Patch Changes
- jazz-svelte@0.8.48
## 0.0.10
### Patch Changes
- jazz-svelte@0.8.45
## 0.0.9
### Patch Changes
- jazz-svelte@0.8.44
## 0.0.8
### Patch Changes
- jazz-svelte@0.8.41
## 0.0.7
### Patch Changes
- jazz-svelte@0.8.40
## 0.0.6
### Patch Changes
- Updated dependencies [aa21072]
- jazz-svelte@0.8.39
## 0.0.5
### Patch Changes
- jazz-svelte@0.8.38
## 0.0.4
### Patch Changes
- jazz-svelte@0.0.4
## 0.0.3
### Patch Changes
- jazz-svelte@0.0.3
## 0.0.2
### Patch Changes
- Updated dependencies [0e59e65]
- jazz-svelte@0.0.2

View File

@@ -0,0 +1,68 @@
# Chat example with Jazz and Svelte
## Getting started
You can either
1. Clone the jazz repository, and run the app within the monorepo.
2. Or create a new Jazz project using this example as a template.
### Using the example as a template
Create a new Jazz project, and use this example as a template.
```bash
npx create-jazz-app@latest chat-app --example chat-svelte
```
Go to the new project directory.
```bash
cd chat-app
```
Run the dev server.
```bash
npm run dev
```
### Using the monorepo
This requires `pnpm` to be installed, see [https://pnpm.io/installation](https://pnpm.io/installation).
Clone the jazz repository.
```bash
git clone https://github.com/garden-co/jazz.git
```
Install and build dependencies.
```bash
pnpm i && npx turbo build
```
Go to the example directory.
```bash
cd jazz/examples/chat-svelte/
```
Start the dev server.
```bash
pnpm dev
```
Open [http://localhost:5173](http://localhost:5173) with your browser to see the result.
## 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 example 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 prettier from 'eslint-config-prettier';
import js from '@eslint/js';
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,44 @@
{
"name": "chat-svelte",
"version": "0.0.85",
"type": "module",
"private": true,
"scripts": {
"dev": "vite dev",
"build": "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": "^3.3.1",
"@sveltejs/kit": "^2.21.1",
"@sveltejs/vite-plugin-svelte": "^5.0.3",
"@types/eslint": "^9.6.1",
"eslint": "^9.27.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.46.1",
"globals": "^15.15.0",
"jazz-inspector-element": "workspace:*",
"prettier": "^3.5.3",
"prettier-plugin-svelte": "^3.4.0",
"svelte": "^5.31.1",
"svelte-check": "^4.2.1",
"typescript": "5.6.2",
"typescript-eslint": "^8.32.1",
"vite": "6.0.11"
},
"dependencies": {
"@tailwindcss/vite": "^4.1.7",
"jazz-browser-media-images": "workspace:*",
"jazz-svelte": "workspace:*",
"jazz-tools": "workspace:*",
"tailwindcss": "^4.1.7"
}
}

View File

@@ -0,0 +1,54 @@
import { defineConfig, devices } from '@playwright/test';
import isCI from 'is-ci';
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// import dotenv from 'dotenv';
// dotenv.config({ path: path.resolve(__dirname, '.env') });
/**
* 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 @@
export const apiKey = 'svelte-chat-example@garden.co';

View File

@@ -0,0 +1,21 @@
@import 'tailwindcss';
@theme {
--color-stone-50: oklch(0.988281 0.002 75);
--color-stone-75: oklch(0.980563 0.002 75);
--color-stone-100: oklch(0.964844 0.002 75);
--color-stone-200: oklch(0.917969 0.002 75);
--color-stone-300: oklch(0.853516 0.002 75);
--color-stone-400: oklch(0.789063 0.002 75);
--color-stone-500: oklch(0.726563 0.002 75);
--color-stone-600: oklch(0.613281 0.002 75);
--color-stone-700: oklch(0.523438 0.002 75);
--color-stone-800: oklch(0.412109 0.002 75);
--color-stone-900: oklch(0.302734 0.002 75);
--color-stone-925: oklch(0.22 0.002 75);
--color-stone-950: oklch(0.193359 0.002 75);
--color-blue: oklch(0.57 0.2346 261.2);
}
:focus {
@apply outline-hidden;
}

13
examples/chat-svelte/src/app.d.ts vendored Normal file
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%/jazz-icon.png" type="image/png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View File

@@ -0,0 +1,9 @@
<script lang="ts">
let { children } = $props();
</script>
<div
class="flex flex-col justify-between w-screen h-screen bg-stone-50 dark:bg-stone-925 dark:text-white"
>
{@render children()}
</div>

View File

@@ -0,0 +1,13 @@
<script lang="ts">
import type { Snippet } from 'svelte';
let { children, fromMe }: { children: Snippet; fromMe: boolean | undefined } = $props();
</script>
<div
class="line-clamp-10 text-ellipsis rounded-2xl overflow-hidden max-w-[calc(100%-5rem)] shadow-sm p-1 {fromMe
? 'bg-white dark:bg-stone-900 dark:text-white'
: 'bg-blue text-white'}"
>
{@render children()}
</div>

View File

@@ -0,0 +1,9 @@
<script lang="ts">
import type { Snippet } from 'svelte';
let { children, fromMe }: { children: Snippet; fromMe: boolean | undefined } = $props();
const align = fromMe ? 'items-end' : 'items-start';
</script>
<div class="{align} flex flex-col m-3" role="row">
{@render children()}
</div>

View File

@@ -0,0 +1,12 @@
<script lang="ts">
import { ImageDefinition, type Loaded } from 'jazz-tools';
import { useProgressiveImg } from '$lib/utils/useProgressiveImage.svelte';
let { image }: { image: Loaded<typeof ImageDefinition> } = $props();
const { src } = $derived(
useProgressiveImg({
image
})
);
</script>
<img class="h-auto max-h-[20rem] max-w-full rounded-t-xl mb-1" {src} alt="" />

View File

@@ -0,0 +1,7 @@
<script lang="ts">
let { by, madeAt }: { by: string | undefined; madeAt: Date } = $props();
</script>
<div class="text-xs text-neutral-500 mt-1.5">
{by} · {madeAt.toLocaleTimeString()}
</div>

View File

@@ -0,0 +1,7 @@
<script lang="ts">
import type { CoPlainText } from 'jazz-tools';
let { text, className }: { text: CoPlainText | string | null; className?: string } = $props();
</script>
<div class="whitespace-pre-wrap"><p class="px-2 leading-relaxed {className}">{text}</p></div>

View File

@@ -0,0 +1,7 @@
<script lang="ts">
let { children } = $props();
</script>
<div class="flex flex-col-reverse flex-1 overflow-y-auto" role="application">
{@render children()}
</div>

View File

@@ -0,0 +1,36 @@
<script lang="ts">
import BubbleBody from '$lib/components/BubbleBody.svelte';
import BubbleContainer from '$lib/components/BubbleContainer.svelte';
import BubbleImage from '$lib/components/BubbleImage.svelte';
import BubbleInfo from '$lib/components/BubbleInfo.svelte';
import BubbleText from '$lib/components/BubbleText.svelte';
import type { Message } from '$lib/schema';
import type { Account, Loaded } from 'jazz-tools';
let {
me,
msg
}: {
me: Loaded<typeof Account> | null | undefined;
msg: Loaded<typeof Message>;
} = $props();
const lastEdit = $derived(msg._edits.text);
const fromMe = $derived(lastEdit?.by?.isMe);
const { text, image } = $derived(msg);
</script>
{#if me && (!me.canRead(msg) || !msg.text?.toString())}
<BubbleContainer fromMe={false}>
<BubbleBody fromMe={false}>
<BubbleText text="Message not readable" className="italic text-gray-500"></BubbleText>
</BubbleBody>
</BubbleContainer>
{:else}
<BubbleContainer {fromMe}>
<BubbleBody {fromMe}>
{#if image}
<BubbleImage {image} />{/if}
<BubbleText {text} />
</BubbleBody>
<BubbleInfo by={lastEdit?.by?.profile?.name} madeAt={lastEdit?.madeAt || new Date()} />
</BubbleContainer>
{/if}

View File

@@ -0,0 +1,3 @@
<div class="flex items-center justify-center h-full px-3 text-base text-stone-500 md:text-2xl">
Start a conversation below.
</div>

View File

@@ -0,0 +1,19 @@
<script lang="ts">
let { size = 24, strokeWidth = 2 } = $props();
</script>
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width={strokeWidth}
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-image-icon lucide-image"
><rect width="18" height="18" x="3" y="3" rx="2" ry="2" /><circle cx="9" cy="9" r="2" /><path
d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"
/></svg
>

View File

@@ -0,0 +1,32 @@
<script lang="ts">
import type { ChangeEventHandler } from 'svelte/elements';
import ImageIcon from '$lib/components/ImageIcon.svelte';
let { onImageChange }: { onImageChange?: ChangeEventHandler<HTMLInputElement> } = $props();
let input = $state<HTMLInputElement>();
const onUploadClick = () => {
input?.click();
};
</script>
<button
type="button"
aria-label="Send image"
title="Send image"
onclick={onUploadClick}
class="text-stone-500 p-1.5 rounded-full hover:bg-stone-100 hover:text-stone-800 dark:hover:bg-stone-800 dark:hover:text-stone-200 transition-colors"
>
<ImageIcon size={24} strokeWidth={1.5} />
</button>
<label class="sr-only">
Image
<input
bind:this={input}
type="file"
accept="image/png, image/jpeg, image/gif"
onchange={onImageChange}
/>
</label>

View File

@@ -0,0 +1,9 @@
<script lang="ts">
let { children } = $props();
</script>
<div
class="flex gap-1 p-3 mt-auto bg-white border-t shadow-2xl border-stone-200 dark:bg-transparent dark:border-stone-900"
>
{@render children()}
</div>

View File

@@ -0,0 +1,20 @@
<script lang="ts">
let { onSubmit }: { onSubmit: (text: string) => void } = $props();
const inputId = $props.id();
</script>
<div class="flex-1">
<label class="sr-only" for={inputId}> Type a message and press Enter </label>
<input
id={inputId}
class="block w-full px-3 py-1 border rounded-full border-stone-200 placeholder:text-stone-500 dark:bg-stone-925 dark:text-white dark:border-stone-900"
placeholder="Type a message and press Enter"
maxLength={2048}
type="text"
onkeydown={({ key, currentTarget: input }) => {
if (key !== 'Enter' || !input.value) return;
onSubmit(input.value);
input.value = '';
}}
/>
</div>

View File

@@ -0,0 +1,9 @@
<script lang="ts">
let { children } = $props();
</script>
<div
class="flex justify-between w-full gap-2 p-3 bg-white border-b border-stone-200 dark:bg-transparent dark:border-stone-900"
>
{@render children()}
</div>

View File

@@ -0,0 +1,8 @@
import { co, z } from 'jazz-tools';
export const Message = co.map({
text: co.plainText(),
image: z.optional(co.image())
});
export const Chat = co.list(Message);

View File

@@ -0,0 +1,16 @@
const animals = [
'elephant',
'penguin',
'giraffe',
'octopus',
'kangaroo',
'dolphin',
'cheetah',
'koala',
'platypus',
'pangolin'
];
export function getRandomUsername() {
return `Anonymous ${animals[Math.floor(Math.random() * animals.length)]}`;
}

View File

@@ -0,0 +1,53 @@
import { ImageDefinition, type Loaded } from 'jazz-tools';
import { onDestroy } from 'svelte';
export function useProgressiveImg({
image,
maxWidth,
targetWidth
}: {
image: Loaded<typeof ImageDefinition> | null | undefined;
maxWidth?: number;
targetWidth?: number;
}) {
let current = $state<{
src?: string;
res?: `${number}x${number}` | 'placeholder';
}>();
const originalSize = $state(image?.originalSize);
const unsubscribe = image?.subscribe({}, (update: Loaded<typeof ImageDefinition>) => {
const highestRes = ImageDefinition.highestResAvailable(update, { maxWidth, targetWidth });
if (highestRes) {
if (highestRes.res !== current?.res) {
const blob = highestRes.stream.toBlob();
if (blob) {
const blobURI = URL.createObjectURL(blob);
current = { src: blobURI, res: highestRes.res };
setTimeout(() => URL.revokeObjectURL(blobURI), 200);
}
}
} else {
current = {
src: update?.placeholderDataURL,
res: 'placeholder'
};
}
});
onDestroy(() => () => {
unsubscribe?.();
});
return {
get src() {
return current?.src;
},
get res() {
return current?.res;
},
originalSize
};
}

View File

@@ -0,0 +1,32 @@
<script lang="ts">
import '../app.css';
import { JazzProvider } from 'jazz-svelte';
import 'jazz-inspector-element';
import { page } from '$app/state';
import { apiKey } from '../apiKey';
import { getRandomUsername } from '$lib/utils';
let { children } = $props();
const defaultProfileName = getRandomUsername();
</script>
<svelte:head>
<title>Jazz Chat Example</title>
</svelte:head>
<div class="h-full bg-white text-stone-700 dark:text-stone-400 dark:bg-stone-925">
<JazzProvider
sync={{
peer: `wss://cloud.jazz.tools/?key=${apiKey}`
}}
{defaultProfileName}
>
{@render children?.()}
</JazzProvider>
<jazz-inspector></jazz-inspector>
</div>
<style>
:global(html, body) {
margin: 0;
}
</style>

View File

@@ -0,0 +1,16 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { Chat } from '$lib/schema';
import { AccountCoState } from 'jazz-svelte';
import { Account, Group } from 'jazz-tools';
const account = new AccountCoState(Account);
const me = $derived(account.current);
$effect(() => {
if (!me) return;
const group = Group.create();
group.addMember('everyone', 'writer');
const chat = Chat.create([], group);
goto(`/chat/${chat.id}`);
});
</script>

View File

@@ -0,0 +1,116 @@
<script lang="ts">
import { createImage } from 'jazz-browser-media-images';
import { AccountCoState, CoState } from 'jazz-svelte';
import { Account, CoPlainText, type ID } from 'jazz-tools';
import { page } from '$app/state';
import { Chat, Message } from '$lib/schema';
import AppContainer from '$lib/components/AppContainer.svelte';
import ChatBody from '$lib/components/ChatBody.svelte';
import ChatBubble from '$lib/components/ChatBubble.svelte';
import EmptyChatMessage from '$lib/components/EmptyChatMessage.svelte';
import ImageInput from '$lib/components/ImageInput.svelte';
import InputBar from '$lib/components/InputBar.svelte';
import TopBar from '$lib/components/TopBar.svelte';
import TextInput from '$lib/components/TextInput.svelte';
const chatId = $derived(page.params.id) as ID<typeof Chat>;
const chat = $derived(
new CoState(Chat, chatId, {
resolve: {
$each: {
text: true
}
}
})
);
const account = new AccountCoState(Account, {
resolve: {
profile: true
}
});
const me = $derived(account.current);
let showNLastMessages = $state(30);
const sendImage = (event: Event & { currentTarget: HTMLInputElement }) => {
const file = event.currentTarget.files?.[0];
if (!file || !chat.current) return;
if (file.size > 5000000) {
alert('Please upload an image less than 5MB.');
return;
}
createImage(file, { owner: chat.current._owner }).then((image) => {
if (!chat.current) return;
chat.current.push(
Message.create(
{
text: CoPlainText.create(file.name, chat.current._owner),
image: image
},
chat.current._owner
)
);
});
};
</script>
<AppContainer>
<TopBar>
<input
type="text"
value={me?.profile?.name ?? ''}
class="bg-transparent"
onchange={(e) => {
if (!me?.profile) return;
const target = e.target as HTMLInputElement;
me.profile.name = target.value;
}}
placeholder="Set username"
/>
<button
onclick={() => {
account.logOut();
window.location.reload(); // Otherwise the provider will not update with default profile name
}}>Log out</button
>
</TopBar>
{#if !chat}
<div class="flex items-center justify-center flex-1">Loading...</div>
{:else}
<ChatBody>
{#if chat.current && chat.current.length > 0}
{#each chat.current.slice(-showNLastMessages).reverse() as msg (msg.id)}
<ChatBubble {me} {msg} />
{/each}
{:else}
<EmptyChatMessage />
{/if}
{#if chat.current && chat.current.length > showNLastMessages}
<button
class="block px-4 py-1 mx-auto my-2 border rounded"
onclick={() => (showNLastMessages += 10)}
>
Show more
</button>
{/if}
</ChatBody>
<InputBar>
<ImageInput onImageChange={sendImage} />
<TextInput
onSubmit={(text: string) => {
if (!chat.current) return;
chat.current.push(
Message.create(
{ text: CoPlainText.create(text, chat.current._owner) },
chat.current._owner
)
);
}}
/>
</InputBar>
{/if}
</AppContainer>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 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,40 @@
import { expect } from 'vitest';
import { ChatPage } from './pages/ChatPage';
import { test } from '@playwright/test';
test('chat works between two windows', async ({ page: marioPage, browser }) => {
await marioPage.goto('/');
const context = await browser.newContext();
const luigiPage = await context.newPage();
await marioPage.waitForURL('/chat/**');
const roomUrl = marioPage.url();
await luigiPage.goto(roomUrl);
const marioChat = new ChatPage(marioPage);
const luigiChat = new ChatPage(luigiPage);
await marioChat.setUsername('Mario');
const message1ByMario = 'Hello Luigi, are you ready to save the princess?';
await marioChat.sendMessage(message1ByMario);
await marioChat.expectMessageRow(message1ByMario);
const roomURL = marioPage.url();
await luigiPage.goto(roomURL);
await luigiChat.setUsername('Luigi');
await luigiChat.expectMessageRow(message1ByMario);
const message2ByLuigi = "No, I'm not ready yet. I'm still trying to find the key to the castle.";
await luigiChat.sendMessage(message2ByLuigi);
await luigiChat.expectMessageRow(message2ByLuigi);
await marioChat.expectMessageRow(message1ByMario);
await luigiChat.expectMessageRow(message2ByLuigi);
await context.close();
});

View File

@@ -0,0 +1,41 @@
import { type Locator, type Page, expect } from '@playwright/test';
export class ChatPage {
readonly page: Page;
readonly messageInput: Locator;
readonly logoutButton: Locator;
readonly usernameInput: Locator;
constructor(page: Page) {
this.page = page;
this.messageInput = page.getByRole('textbox', {
name: 'Type a message and press Enter'
});
this.logoutButton = page.getByRole('button', {
name: 'Log out'
});
this.usernameInput = page.getByPlaceholder('Set username');
}
async setUsername(username: string) {
await this.usernameInput.fill(username);
}
async sendMessage(message: string) {
await this.messageInput.fill(message);
await this.messageInput.press('Enter');
}
async expectMessageRow(message: string) {
await expect(this.page.getByText(message)).toBeVisible();
}
async expectUserNameNotToBeAnonymousUser() {
await expect(this.usernameInput).not.toHaveValue(/anonymous user/i);
await expect(this.usernameInput).toHaveValue(/^Anonymous \w+/);
}
async logout() {
await this.logoutButton.click();
await this.page.goto('/');
}
}

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,7 @@
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
import tailwindcss from '@tailwindcss/vite';
export default defineConfig({
plugins: [tailwindcss(), sveltekit()]
});

View File

@@ -1 +1,6 @@
dist
dist
# env files
.env
.env.*
!.env.example
!.env.test

View File

@@ -29,3 +29,9 @@ sync-db/
/playwright-report/
/blob-report/
/playwright/.cache/
# env files
.env
.env.*
!.env.example
!.env.test

View File

@@ -7,23 +7,34 @@ Live version: [https://clerk-demo.jazz.tools](https://clerk-demo.jazz.tools)
## Getting started
You can either
1. Clone the jazz repository, and run the app within the monorepo.
2. Or create a new Jazz project using this example as a template.
### Using the example as a template
Create a new Jazz project, and use this example as a template.
```bash
npx create-jazz-app@latest clerk-app --example clerk
```
Go to the new project directory.
```bash
cd clerk-app
```
Rename .env.example to .env
```bash
mv .env.example .env
```
Update `VITE_CLERK_PUBLISHABLE_KEY` with your [Publishable Key](https://clerk.com/docs/deployments/clerk-environment-variables#clerk-publishable-and-secret-keys) from Clerk.
Run the dev server.
```bash
npm run dev
```
@@ -33,21 +44,33 @@ npm run dev
This requires `pnpm` to be installed, see [https://pnpm.io/installation](https://pnpm.io/installation).
Clone the jazz repository.
```bash
git clone https://github.com/garden-co/jazz.git
```
Install and build dependencies.
```bash
pnpm i && npx turbo build
```
Go to the example directory.
```bash
cd jazz/examples/clerk/
```
Rename .env.example to .env
```bash
mv .env.example .env
```
Update `VITE_CLERK_PUBLISHABLE_KEY` with your [Publishable Key](https://clerk.com/docs/deployments/clerk-environment-variables#clerk-publishable-and-secret-keys) from Clerk.
Start the dev server.
```bash
pnpm dev
```
@@ -57,4 +80,3 @@ Open [http://localhost:5173](http://localhost:5173) with your browser to see the
## 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.

View File

@@ -25,4 +25,10 @@ dist-ssr
*.sln
*.sw?
playwright-report
playwright-report
# env files
.env
.env.*
!.env.example
!.env.test

View File

@@ -25,3 +25,9 @@ dist-ssr
/test-results/
/playwright-report/
# env files
.env
.env.*
!.env.example
!.env.test

View File

@@ -22,3 +22,9 @@ dist-ssr
*.njsproj
*.sln
*.sw?
# env files
.env
.env.*
!.env.example
!.env.test

View File

@@ -23,4 +23,10 @@ dist-ssr
*.sln
*.sw?
sync-db/
sync-db/
# env files
.env
.env.*
!.env.example
!.env.test

View File

@@ -39,3 +39,9 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts
# env files
.env
.env.*
!.env.example
!.env.test

View File

@@ -1,3 +1,7 @@
dist
.env
# env files
.env
.env.*
!.env.example
!.env.test

View File

@@ -1,3 +0,0 @@
VITE_CURSOR_FEED_ID=multi-cursors-250425-1708
VITE_GROUP_ID=co_zXE8C8sd9QxEbxnt3neRvFRPFUc
VITE_OLD_CURSOR_AGE_SECONDS=5

View File

@@ -25,4 +25,9 @@ dist-ssr
*.sln
*.sw?
playwright-report
playwright-report
# env files
.env
.env.*
!.env.example
!.env.test

View File

@@ -6,58 +6,80 @@ Live version: [https://multi-cursors.demo.jazz.tools/](https://multi-cursors.dem
## Getting started
You can either
1. Clone the jazz repository, and run the app within the monorepo.
2. Or create a new Jazz project using this example as a template.
### Using the example as a template
Create a new Jazz project, and use this example as a template.
```bash
npx create-jazz-app@latest multi-cursors-app --example multi-cursors
```
Go to the new project directory.
```bash
cd multi-cursors-app
```
Run the dev server.
```bash
npm run dev
```
If you want to persist the cursors between server restarts, you'll need to update your .env file. Check your console logs for the correct values to add to your .env file.
```
VITE_CURSOR_FEED_ID=
VITE_GROUP_ID=
VITE_OLD_CURSOR_AGE_SECONDS=5
```
### Using the monorepo
This requires `pnpm` to be installed, see [https://pnpm.io/installation](https://pnpm.io/installation).
Clone the jazz repository.
```bash
git clone https://github.com/garden-co/jazz.git
```
Install and build dependencies.
```bash
pnpm i && npx turbo build
```
Go to the example directory.
```bash
cd jazz/examples/multi-cursors/
```
Start the dev server.
```bash
pnpm dev
```
Open [http://localhost:5173](http://localhost:5173) with your browser to see the result.
If you want to persist the cursors between server restarts, you'll need to update your .env file. Check your console logs for the correct values to add to your .env file.
```
VITE_CURSOR_FEED_ID=
VITE_GROUP_ID=
VITE_OLD_CURSOR_AGE_SECONDS=5
```
## 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 example app uses [Jazz Cloud](https://jazz.tools/cloud) (`wss://cloud.jazz.tools`) - so cross-device use, invites and collaboration should just work.

View File

@@ -5,19 +5,35 @@ This example demonstrates using Jazz with multiple authentication methods; in th
## Getting started
To run this example, you may either:
* Clone the Jazz monorepo and run this example from within.
* Create a new Jazz project using this example as a template, and run that new project.
- Clone the Jazz monorepo and run this example from within.
- Create a new Jazz project using this example as a template, and run that new project.
### Using this example as a template
1. Create a new Jazz project, and use this example as a template.
```bash
npx create-jazz-app@latest multiauth-app --example multiauth
```
2. Navigate to the new project and start the development server.
2. Navigate to the new project.
```bash
cd multiauth-app
```
3. Rename .env.example to .env
```bash
mv .env.example .env
```
4. Update `VITE_CLERK_PUBLISHABLE_KEY` with your [Publishable Key](https://clerk.com/docs/deployments/clerk-environment-variables#clerk-publishable-and-secret-keys) from Clerk.
5. Run the development server
```bash
npm run dev
```
@@ -26,17 +42,35 @@ npm run dev
This requires `pnpm` to be installed; see [https://pnpm.io/installation](https://pnpm.io/installation).
1. Clone the `jazz` repository.
```bash
git clone https://github.com/garden-co/jazz.git
```
2. Install dependencies.
```bash
cd jazz
pnpm install
```
3. Navigate to the example and start the development server.
```bash
cd examples/multiauth
```
4. Rename .env.example to .env
```bash
mv .env.example .env
```
5. Update `VITE_CLERK_PUBLISHABLE_KEY` with your [Publishable Key](https://clerk.com/docs/deployments/clerk-environment-variables#clerk-publishable-and-secret-keys) from Clerk.
6. Run the development server
```bash
pnpm dev
```

View File

@@ -28,4 +28,10 @@ dist-ssr
/blob-report/
/playwright/.cache/
sync-db/
sync-db/
# env files
.env
.env.*
!.env.example
!.env.test

View File

@@ -110,7 +110,7 @@ export async function addTrackToPlaylist(
* visible to the Playlist user
*/
const trackGroup = track._owner;
trackGroup.extend(playlist._owner);
trackGroup.addMember(playlist._owner);
playlist.tracks?.push(track);
return;
@@ -129,7 +129,7 @@ export async function removeTrackFromPlaylist(
if (track._owner._type === "Group" && playlist._owner._type === "Group") {
const trackGroup = track._owner;
await trackGroup.revokeExtend(playlist._owner);
await trackGroup.removeMember(playlist._owner);
const index =
playlist.tracks?.findIndex(

View File

@@ -54,7 +54,15 @@ export function AuthModal({ open, onOpenChange }: AuthModalProps) {
}
onOpenChange(false);
} catch (error) {
setError(error instanceof Error ? error.message : "Unknown error");
if (error instanceof Error) {
if (error.cause instanceof Error) {
setError(error.cause.message);
} else {
setError(error.message);
}
} else {
setError("Unknown error");
}
}
};

View File

@@ -23,4 +23,10 @@ dist-ssr
*.sln
*.sw?
playwright-report
playwright-report
# env files
.env
.env.*
!.env.example
!.env.test

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

@@ -22,3 +22,9 @@ dist-ssr
*.njsproj
*.sln
*.sw?
# env files
.env
.env.*
!.env.example
!.env.test

View File

@@ -22,3 +22,9 @@ dist-ssr
*.njsproj
*.sln
*.sw?
# env files
.env
.env.*
!.env.example
!.env.test

View File

@@ -22,3 +22,9 @@ dist-ssr
*.njsproj
*.sln
*.sw?
# env files
.env
.env.*
!.env.example
!.env.test

View File

@@ -27,4 +27,10 @@ dist-ssr
/blob-report/
/playwright/.cache/
sync-db/
sync-db/
# env files
.env
.env.*
!.env.example
!.env.test

View File

@@ -22,3 +22,9 @@ dist-ssr
*.njsproj
*.sln
*.sw?
# env files
.env
.env.*
!.env.example
!.env.test

36
examples/richtext-prosekit/.gitignore vendored Normal file
View File

@@ -0,0 +1,36 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
sync-db/
# env files
.env
.env.*
!.env.example
!.env.test

View File

@@ -0,0 +1,36 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
sync-db/
# env files
.env
.env.*
!.env.example
!.env.test

36
examples/richtext-tiptap/.gitignore vendored Normal file
View File

@@ -0,0 +1,36 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
sync-db/
# env files
.env
.env.*
!.env.example
!.env.test

View File

@@ -1 +1,7 @@
dist
dist
# env files
.env
.env.*
!.env.example
!.env.test

View File

@@ -23,4 +23,10 @@ dist-ssr
*.sln
*.sw?
sync-db/
sync-db/
# env files
.env
.env.*
!.env.example
!.env.test

View File

@@ -22,3 +22,9 @@ dist-ssr
*.njsproj
*.sln
*.sw?
# env files
.env
.env.*
!.env.example
!.env.test

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

@@ -650,6 +650,13 @@ const svelteExamples: Example[] = [
tech: [tech.svelte],
features: [features.fileUpload, features.passkey, features.inviteLink],
illustration: <FileShareIllustration />,
}, {
name: "Chat",
slug: "chat-svelte",
description:
"A simple Svelte app that creates a chat room with a shareable link.",
tech: [tech.svelte],
illustration: <ChatIllustration />,
},
];

View File

@@ -233,7 +233,7 @@ export const docNavigationItems = [
done: 10,
},
{
name: "Group inheritance",
name: "Groups as members",
href: "/docs/groups/inheritance",
done: 100,
},

View File

@@ -1,18 +1,18 @@
import { ContentByFramework, CodeGroup } from '@/components/forMdx'
export const metadata = {
description: "Create Groups that inherit members from other Groups."
export const metadata = {
description: "Add groups as members of other groups."
};
# Group Inheritance
# Groups as members
Groups can inherit members from other groups using the `extend` method.
Groups can be added to other groups using the `addMember` method.
When a group extends another group, members of the parent group will become automatically part of the child group.
When a group is added as a member of another group, members of the added group will become part of the containing group.
## Basic Usage
## Basic usage
Here's how to extend a group:
Here's how to add a group as a member of another group:
<CodeGroup>
```ts twoslash
@@ -21,19 +21,98 @@ import { Group } from "jazz-tools";
const playlistGroup = Group.create();
const trackGroup = Group.create();
// This way track becomes visible to the members of playlist
trackGroup.extend(playlistGroup);
// Tracks are now visible to the members of playlist
trackGroup.addMember(playlistGroup);
```
</CodeGroup>
When you extend a group:
- Members of the parent group get access to the child group
- Their roles are inherited (with some exceptions, see [below](#role-inheritance-rules))
- Removing a member from the parent group also removes their access to child groups
When you add groups as members:
- Members of the added group become members of the container group
- Their roles are inherited (with some exceptions, see [below](#the-rules-of-role-inheritance))
- Revoking access from the member group also removes its access to the container group
## Inheriting members but overriding their role
## Levels of inheritance
In some cases you might want to inherit all members from a parent group but override/flatten their roles to the same specific role in the child group. You can do so by passing an "override role" as a second argument to `extend`:
Adding a group as a member of another is not limited in depth:
<CodeGroup>
```ts twoslash
import { Group } from "jazz-tools";
// ---cut---
const grandParentGroup = Group.create();
const parentGroup = Group.create();
const childGroup = Group.create();
childGroup.addMember(parentGroup);
parentGroup.addMember(grandParentGroup);
```
</CodeGroup>
Members of the grandparent group will get access to all descendant groups based on their roles.
## Roles
### The rules of role inheritance
If the account is already a member of the container group, it will get the more permissive role:
<CodeGroup>
```ts twoslash
import { Group } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const bob = await createJazzTestAccount();
// ---cut---
const addedGroup = Group.create();
addedGroup.addMember(bob, "reader");
const containingGroup = Group.create();
addedGroup.addMember(bob, "writer");
containingGroup.addMember(addedGroup);
// Bob stays a writer because his role is higher
// than the inherited reader role.
```
</CodeGroup>
When adding a group to another group, only admin, writer and reader roles are inherited:
<CodeGroup>
```ts twoslash
import { Group } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const bob = await createJazzTestAccount();
// ---cut---
const addedGroup = Group.create();
addedGroup.addMember(bob, "writeOnly");
const containingGroup = Group.create();
containingGroup.addMember(addedGroup);
// Bob does not become a member of the containing group
```
</CodeGroup>
To add a group to another group:
1. The current account must be an admin in the containing group
2. The current account must be a member of the added group
<CodeGroup>
```ts twoslash
import { co, Group } from "jazz-tools";
const group = Group.create();
const Company = co.map({});
const company = Company.create({ owner: group });
// ---cut---
const companyGroup = company._owner.castAs(Group);
const teamGroup = Group.create();
// Works only if I'm a member of `companyGroup`
teamGroup.addMember(companyGroup);
```
</CodeGroup>
### Overriding the added group's roles
In some cases you might want to inherit all members from an added group but override their roles to the same specific role in the containing group. You can do so by passing an "override role" as a second argument to `addMember`:
<CodeGroup>
```ts twoslash
@@ -46,8 +125,9 @@ organizationGroup.addMember(bob, "admin");
const billingGroup = Group.create();
// This way the members of the organization can only read the billing data
billingGroup.extend(organizationGroup, "reader");
// This way the members of the organization
// can only read the billing data
billingGroup.addMember(organizationGroup, "reader");
```
</CodeGroup>
@@ -57,147 +137,71 @@ The "override role" works in both directions:
```ts twoslash
import { Group } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const bob = await createJazzTestAccount();
const alice = await createJazzTestAccount();
const bob = await createJazzTestAccount();
// ---cut---
const parentGroup = Group.create();
parentGroup.addMember(bob, "reader");
parentGroup.addMember(alice, "admin");
const addedGroup = Group.create();
addedGroup.addMember(bob, "reader");
addedGroup.addMember(alice, "admin");
const childGroup = Group.create();
childGroup.extend(parentGroup, "writer");
const containingGroup = Group.create();
containingGroup.addMember(addedGroup, "writer");
// Bob and Alice are now writers in the child group
// Bob and Alice are now writers in the containing group
```
</CodeGroup>
## Multiple Levels of Inheritance
### Permission changes
Groups can be extended multiple levels deep:
<CodeGroup>
```ts twoslash
import { Group } from "jazz-tools";
// ---cut---
const grandParentGroup = Group.create();
const parentGroup = Group.create();
const childGroup = Group.create();
childGroup.extend(parentGroup);
parentGroup.extend(grandParentGroup);
```
</CodeGroup>
Members of the grandparent group will get access to all descendant groups based on their roles.
## Permission Changes
When you remove a member from a parent group, they automatically lose access to all child groups. We handle key rotation automatically to ensure security.
When you remove a member from an added group, they automatically lose access to all containing groups. We handle key rotation automatically to ensure security.
<CodeGroup>
```ts twoslash
import { Group } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const bob = await createJazzTestAccount();
const parentGroup = Group.create();
const addedGroup = Group.create();
// ---cut---
// Remove member from parent
await parentGroup.removeMember(bob);
// Remove member from added group
await addedGroup.removeMember(bob);
// Bob loses access to both parent and child groups
// Bob loses access to both groups.
// If Bob was also a member of the containing group,
// he wouldn't have lost access.
```
</CodeGroup>
## Role Inheritance Rules
## Removing groups from other groups
If the account is already a member of the child group, it will get the more permissive role:
<CodeGroup>
```ts twoslash
import { Group } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const bob = await createJazzTestAccount();
// ---cut---
const parentGroup = Group.create();
parentGroup.addMember(bob, "reader");
const childGroup = Group.create();
parentGroup.addMember(bob, "writer");
childGroup.extend(parentGroup);
// Bob stays a writer because his role is higher
// than the inherited reader role.
```
</CodeGroup>
When extending groups, only admin, writer and reader roles are inherited:
<CodeGroup>
```ts twoslash
import { Group } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const bob = await createJazzTestAccount();
// ---cut---
const parentGroup = Group.create();
parentGroup.addMember(bob, "writeOnly");
const childGroup = Group.create();
childGroup.extend(parentGroup);
// Bob does not become a member of the child group
```
</CodeGroup>
To extend a group:
1. The current account must be an admin in the child group
2. The current account must be a member of the parent group
<CodeGroup>
```ts twoslash
import { Group, co, z } from "jazz-tools";
const Company = co.map({
name: z.string(),
});
const company = Company.create({ name: "Garden Computing" });
// ---cut---
const companyGroup = company._owner.castAs(Group)
const teamGroup = Group.create();
// Works only if I'm a member of companyGroup
teamGroup.extend(companyGroup);
```
</CodeGroup>
## Revoking a group extension
You can revoke a group extension by using the `revokeExtend` method:
You can remove a group from another group by using the `removeMember` method:
<CodeGroup>
```ts twoslash
import { Group } from "jazz-tools";
// ---cut---
const parentGroup = Group.create();
const childGroup = Group.create();
const addedGroup = Group.create();
const containingGroup = Group.create();
childGroup.extend(parentGroup);
containingGroup.addMember(addedGroup);
// Revoke the extension
await childGroup.revokeExtend(parentGroup);
await containingGroup.removeMember(addedGroup);
```
</CodeGroup>
## Getting all parent groups
## Getting all added groups
You can get all the parent groups of a group by calling the `getParentGroups` method:
You can get all of the groups added to a group by calling the `getParentGroups` method:
<CodeGroup>
```ts twoslash
import { Group } from "jazz-tools";
// ---cut---
const childGroup = Group.create();
const parentGroup = Group.create();
childGroup.extend(parentGroup);
const containingGroup = Group.create();
const addedGroup = Group.create();
containingGroup.addMember(addedGroup);
console.log(childGroup.getParentGroups()); // [parentGroup]
console.log(containingGroup.getParentGroups()); // [addedGroup]
```
</CodeGroup>
@@ -220,13 +224,13 @@ companyGroup.addMember(CEO, "admin");
// Team group with elevated permissions
const teamGroup = Group.create();
teamGroup.extend(companyGroup); // Inherits company-wide access
teamGroup.addMember(companyGroup); // Inherits company-wide access
teamGroup.addMember(teamLead, "admin");
teamGroup.addMember(developer, "writer");
// Project group with specific permissions
const projectGroup = Group.create();
projectGroup.extend(teamGroup); // Inherits team permissions
projectGroup.addMember(teamGroup); // Inherits team permissions
projectGroup.addMember(client, "reader"); // Client can only read project items
```
</CodeGroup>

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

@@ -1,5 +1,12 @@
# cojson-storage-indexeddb
## 0.14.25
### Patch Changes
- cojson@0.14.25
- cojson-storage@0.14.25
## 0.14.24
### Patch Changes

View File

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

View File

@@ -1,5 +1,12 @@
# cojson-storage-sqlite
## 0.14.25
### Patch Changes
- cojson@0.14.25
- cojson-storage@0.14.25
## 0.14.24
### Patch Changes

View File

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

View File

@@ -1,5 +1,11 @@
# cojson-storage
## 0.14.25
### Patch Changes
- cojson@0.14.25
## 0.14.24
### Patch Changes

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