Compare commits

...

128 Commits

Author SHA1 Message Date
Guido D'Orsi
f3f56b9be0 docs: clarifications 2025-03-06 14:42:30 +01:00
Guido D'Orsi
4cae6bad34 Merge 2025-03-06 14:38:01 +01:00
Guido D'Orsi
17f2ef57de docs: bump the upgrade version 2025-03-06 14:37:19 +01:00
Guido D'Orsi
3a4d111a37 Merge remote-tracking branch 'origin/main' into jazz-581-rfc-new-deep-loading-api 2025-03-06 14:33:18 +01:00
Guido D'Orsi
6c94c2cd3d Merge pull request #1605 from garden-co/changeset-release/main
Version Packages
2025-03-06 13:54:08 +01:00
github-actions[bot]
98c4221340 Version Packages 2025-03-06 12:46:19 +00:00
Guido D'Orsi
7b00a8155f fix: align packages versions to 0.11 2025-03-06 13:44:06 +01:00
Guido D'Orsi
cc5aea708a fix: remove the prepublish hook 2025-03-06 13:35:35 +01:00
Guido D'Orsi
87c7306632 fix(release): build packages with turbo before publishing 2025-03-06 13:30:59 +01:00
Guido D'Orsi
a65219ba52 Merge pull request #1578 from garden-co/changeset-release/main
Version Packages
2025-03-06 13:23:02 +01:00
github-actions[bot]
689595fd0a Version Packages 2025-03-06 12:21:42 +00:00
Guido D'Orsi
efbf2b65a8 Merge pull request #1492 from garden-co/0-11-0
Jazz 0.11.0 - Better permissions checks and members API
2025-03-06 13:19:38 +01:00
Guido D'Orsi
912232192f docs: release notes final touches 2025-03-06 13:18:24 +01:00
Guido D'Orsi
11b5d77064 fix: check auth state when accepting an invite 2025-03-06 13:04:07 +01:00
Guido D'Orsi
63b88b591d test(music-player): uncomment remove track test 2025-03-06 13:02:21 +01:00
Guido D'Orsi
87d0544d98 chore: fix type errors 2025-03-05 18:55:37 +01:00
Guido D'Orsi
d43dcc2c94 Merge pull request #1592 from garden-co/revert-1557-1556-update-homepage-typedoc-to-027
Revert "1556-update-homepage-typedoc-to-027"
2025-03-05 18:55:09 +01:00
Guido D'Orsi
093a166db1 Revert "1556-update-homepage-typedoc-to-027" 2025-03-05 18:54:57 +01:00
Guido D'Orsi
593f3ed83e Revert "Fix typing"
This reverts commit 6a2a176361.
2025-03-05 18:48:55 +01:00
Guido D'Orsi
6c6c89e068 Revert "Bumps typedoc and typescript versions"
This reverts commit 824bbc8bab.
2025-03-05 18:45:30 +01:00
Guido D'Orsi
c1646f6bbb Merge pull request #1570 from garden-co/trisha/1224-add-status-to-footer
Add status to footer, remove releases
2025-03-05 18:03:51 +01:00
Guido D'Orsi
b7deb3c937 chore: changeset 2025-03-05 17:59:56 +01:00
Guido D'Orsi
9b425701e4 Merge pull request #1589 from 3f765d5e-ac23-4170-97a5-68b8feee155b/3f765d5e-ac23-4170-97a5-68b8feee155b-patch-3
Add React 19 to peer deps
2025-03-05 17:58:51 +01:00
Guido D'Orsi
07dcf54aca chore: fix type errors 2025-03-05 17:52:50 +01:00
Guido D'Orsi
8ed33c18ca Merge remote-tracking branch 'origin/main' into 0-11-0 2025-03-05 17:48:33 +01:00
Guido D'Orsi
6a96d8b53b feat: add getParentGroups API to Group and Account 2025-03-05 17:27:28 +01:00
Nikos Papadopoulos
a1bcbda72e Merge pull request #1522 from garden-co/jazz-758-allow-optional-fields-in-types-passed-to-cojson-see
Fix and test cases for reported issue with optional fields
2025-03-05 16:03:25 +00:00
Nikos Papadopoulos
0f67e0a988 adds changeset 2025-03-05 15:54:29 +00:00
3f765d5e-ac23-4170-97a5-68b8feee155b
6c7a3ebf82 Add React 19 to peer deps
Hope that's enough to make the peer dep warning go away. Been running inspector with react 19 and no problems so far
2025-03-05 16:46:31 +01:00
Guido D'Orsi
27ce186152 fix(co.json): accept null values and improve the function check 2025-03-05 15:51:17 +01:00
Nikos Papadopoulos
77ae2cc186 Revert "fixes CoJsonValue type"
This reverts commit d6d9218b3e.
2025-03-05 10:53:56 +00:00
Guido D'Orsi
66600c0c27 Merge pull request #1584 from garden-co/clarification-profile-changes
docs: clarify better the situation on profile migrations
2025-03-05 11:06:42 +01:00
Guido D'Orsi
81d5261f4d docs: clarify better the situation on profile migrations 2025-03-05 11:01:02 +01:00
Guido D'Orsi
d748dea90f test: cover invalid actions on revokeExtend 2025-03-05 10:24:03 +01:00
Guido D'Orsi
707e544b83 docs: add the 0.11.0 group APIs 2025-03-05 10:17:00 +01:00
Guido D'Orsi
f3cbaee890 Merge pull request #1500 from garden-co/jazz-760-jazz-0110-upgrade-guide
jazz 0.11.0 upgrade guide
2025-03-05 10:03:04 +01:00
Trisha Lim
60ce483db7 add changeset 2025-03-05 15:51:33 +07:00
Trisha Lim
c08a41398d Make initial commit on create-jazz-app apps 2025-03-05 15:51:33 +07:00
Nikos Papadopoulos
d6d9218b3e fixes CoJsonValue type 2025-03-04 19:20:08 +00:00
Anselm
667e301f12 Simplify JSON related types and test for more cases 2025-03-04 17:33:02 +00:00
Guido D'Orsi
b15f904347 chore: fix a type error 2025-03-04 18:02:48 +01:00
Nikos Papadopoulos
0c150a4c74 fix attempt 0 2025-03-04 16:50:01 +00:00
Guido D'Orsi
66de2dacb6 docs: whats new 2025-03-04 17:13:57 +01:00
Guido D'Orsi
ae1425db8a Merge remote-tracking branch 'origin/main' into 0-11-0 2025-03-04 17:08:49 +01:00
Guido D'Orsi
a164b102f5 docs: add revokeExtend to the migration docs 2025-03-04 16:49:25 +01:00
Guido D'Orsi
a6db9a438e Merge remote-tracking branch 'origin/0-11-0' into jazz-760-jazz-0110-upgrade-guide 2025-03-04 16:38:49 +01:00
Nikita
a35249a831 Fix CoMap.toJSON() with encoded fields (#1577)
* encode -> encoded

* add test and changeset

* patch
2025-03-04 15:56:08 +01:00
Guido D'Orsi
36e246be79 docs: add the this type to the migration examples 2025-03-04 15:37:29 +01:00
Trisha Lim
b894455c9e Remove unnecessary owner argument in react examples 2025-03-04 19:28:16 +07:00
Trisha Lim
a462254199 Add status to footer, remove releases 2025-03-04 17:21:22 +07:00
Emil Sayahi
34cbdc3e4c feat!(Groups): API to unextend group (#1512)
* feat!(Groups): API to unextend group

See: #1504

* feat!(Groups): API to unextend group

See: #1504

* test(groupsAndAccounts): revoking group extensions

* test(cojson): group, permissions test for unextend

* chore: add changeset

* test(permissions): unextend reading & grandparents

* feat!(Groups): API to unextend group

See: #1504

* test(groupsAndAccounts): revoking group extensions

* test(cojson): group, permissions test for unextend

* chore: add changeset

* test(permissions): unextend reading & grandparents

* feat(music-player): removing tracks from playlists

* fix(music-player): remove unused import
2025-03-03 14:13:35 -05:00
Guido D'Orsi
029c32d86c chore: fix type errors 2025-02-28 18:44:16 +01:00
Guido D'Orsi
1e18c7f5fc Merge remote-tracking branch 'origin/0-11-0' into jazz-581-rfc-new-deep-loading-api 2025-02-28 14:58:32 +01:00
Guido D'Orsi
45bed3ea80 Merge remote-tracking branch 'origin/main' into 0-11-0 2025-02-28 14:12:02 +01:00
Guido D'Orsi
43fa1ecba4 chore: correctly rename the interface 2025-02-28 11:20:47 +01:00
Nikos Papadopoulos
141ea11bf1 Rename interface 2025-02-27 19:08:52 +00:00
Guido D'Orsi
273b478381 Merge pull request #1535 from garden-co/test/group-members-sync
test(groupAndAccounts): enable sync and add members test
2025-02-27 17:24:01 +01:00
Guido D'Orsi
1d29f74df6 test(groupAndAccounts): enable sync and add members test 2025-02-27 17:16:27 +01:00
Guido D'Orsi
393d066b25 docs: add the Group.members schema and everyone changes 2025-02-27 11:45:51 +01:00
Guido D'Orsi
45bff625c4 Merge branch '0-11-0' into jazz-760-jazz-0110-upgrade-guide 2025-02-27 11:40:42 +01:00
Guido D'Orsi
a8fca7b5f7 Merge pull request #1459 from garden-co/declaration-files-migration
feat: migrate to declaration files
2025-02-27 11:28:21 +01:00
Guido D'Orsi
658a0602ea Merge pull request #1528 from garden-co/fix/group-members-type
feat(Group.members): use the global AccountSchema and remove everyone from the returned values
2025-02-27 11:28:04 +01:00
Guido D'Orsi
f039e8f097 feat(Group.members): use the global AccountSchema and remove everyone from the returned values 2025-02-27 11:20:57 +01:00
Guido D'Orsi
dc9da28bf9 Merge remote-tracking branch 'origin/0-11-0' into declaration-files-migration 2025-02-27 09:55:15 +01:00
Guido D'Orsi
e6db8f2a02 test(permissions): rewrite flaky tests 2025-02-26 18:53:32 +01:00
Guido D'Orsi
8c7a6b27ed docs: refine the examples and align the feature descripted with the implementation 2025-02-26 18:23:02 +01:00
Guido D'Orsi
91f96e1188 Merge branch 'jazz-581-rfc-new-deep-loading-api' into jazz-581-add-docs 2025-02-26 18:12:56 +01:00
Guido D'Orsi
f0c8f7b3eb Merge pull request #1526 from garden-co/upgrade-guide-permissions-docs
docs: update the 0.11 upgrade guide with the new permissions APIs
2025-02-26 17:46:09 +01:00
Guido D'Orsi
945163b7bc docs: fix typos in the upgrade guide 2025-02-26 15:34:20 +01:00
Guido D'Orsi
3142e503ae docs: update the 0.11 upgrade guide with the new permissions APIs 2025-02-26 15:33:00 +01:00
Guido D'Orsi
28dac10723 Merge pull request #1517 from garden-co/fix/optional-fields-acl
feat(deepLoading): return undefined when an optional field is not accessible
2025-02-26 14:58:27 +01:00
Guido D'Orsi
932a21e62a Merge pull request #1521 from garden-co/permission-checks-simplified
feat: add shortcut methods to account to check permissions on covalues
2025-02-26 14:45:33 +01:00
Guido D'Orsi
628a33eb12 feat: add shortcut methods to account to check permissions on covalues 2025-02-26 14:45:22 +01:00
Nikos Papadopoulos
20eef64b47 Added a new test case to validate a reported issue with optional fields 2025-02-26 13:16:05 +00:00
Giordano Ricci
c64f38b6c9 docs for usePassKeyAuth changes 2025-02-26 12:02:39 +00:00
Giordano Ricci
e5e047660b nicer alert 2025-02-26 11:57:20 +00:00
Guido D'Orsi
9cb11e38dd feat(deepLoading): return undefined when an optional field is not accessible 2025-02-26 12:56:06 +01:00
Giordano Ricci
a65d6bed73 reorganize sections 2025-02-26 11:17:48 +00:00
Guido D'Orsi
f3e4bacb33 Merge remote-tracking branch 'origin/main' into jazz-581-rfc-new-deep-loading-api 2025-02-25 17:46:22 +01:00
Giordano Ricci
d4f7891890 add more docs 2025-02-25 16:39:37 +00:00
Giordano Ricci
6eef92b602 jazz 0.11.0 upgrade guide 2025-02-25 13:01:16 +00:00
Guido D'Orsi
dfbb4b8c69 Merge pull request #1478 from garden-co/jazz-749-migrations-make-migrations-work-with-profiles
fix: allow profile migrations, enforce profile ownership
2025-02-25 13:58:59 +01:00
Giordano Ricci
5f2d8e143c remove profile.name forced write 2025-02-25 12:35:39 +00:00
Guido D'Orsi
240f071951 Merge pull request #1502 from garden-co/jazz-730-passkeyauth-update-the-profile-name-only-if-the-value-on
Jazz 730 passkeyauth update the profile name only if the value on
2025-02-25 12:30:43 +01:00
Giordano Ricci
d560b4ddfb fixpermission resolution chain 2025-02-25 11:19:34 +00:00
Guido D'Orsi
73d6fd1a85 tests: add more permissions tests on inheritance 2025-02-25 11:42:58 +01:00
Guido D'Orsi
46f5133510 test: revoking access on a child group doesn't block access to that group if a more permissive role is inheritable 2025-02-24 20:17:58 +01:00
Giordano Ricci
b3f2e67d9d restore correct behavior in test 2025-02-24 19:17:30 +00:00
Giordano Ricci
f689cd20fc add tests 2025-02-24 19:05:34 +00:00
Giordano Ricci
e22de9ff4c add changeset 2025-02-24 18:45:37 +00:00
Giordano Ricci
735bd41242 fix permsissions checks 2025-02-24 18:41:23 +00:00
Giordano Ricci
71998bde62 throw a meaningful error when trying to access an inbox with inusfficient permissions 2025-02-24 18:40:44 +00:00
Giordano Ricci
f01b714e29 remove profile visibility requirements 2025-02-24 18:40:44 +00:00
Giordano Ricci
028eca9f81 throw error is profile has an account owner 2025-02-24 18:40:44 +00:00
Giordano Ricci
d3d4118b86 fix tests 2025-02-24 18:40:43 +00:00
Giordano Ricci
45f1fe19ac fix: allow profile migrations, enforce profile ownership 2025-02-24 18:40:43 +00:00
Emil Sayahi
ad2db5e6cb docs(changeset): mention PasskeyAuth
Co-authored-by: Guido D'Orsi <gu.dorsi@gmail.com>
2025-02-24 12:57:16 -05:00
Guido D'Orsi
480890d2e9 Merge pull request #1467 from garden-co/feat/return-inherited-members
feat: return inherited members and add getRoleOf and hasPermissions methods to Group
2025-02-24 17:49:18 +01:00
Emil Sayahi
18428eaaa1 chore: changeset 2025-02-24 11:47:41 -05:00
Emil Sayahi
5ed31f2678 fix(PasskeyAuth): set name iff username given
If an empty username is passed to `signUp`, the `profile.name` of the user is not updated.

See: #1433
2025-02-24 11:11:57 -05:00
Guido D'Orsi
b9d194a80e feat: return inherited members and add getRoleOf and hasPermissions methods to Group 2025-02-24 12:55:14 +01:00
Guido D'Orsi
2359e85ebd feat: enable the declarationMap option 2025-02-21 18:55:20 +01:00
Guido D'Orsi
626d43f07b Merge remote-tracking branch 'origin/main' into jazz-581-rfc-new-deep-loading-api 2025-02-21 18:27:14 +01:00
Guido D'Orsi
a4713df30c feat: migrate to declaration files 2025-02-21 16:07:51 +01:00
Benjamin S. Leveritt
1f5d073035 Update homepage/homepage/app/(docs)/docs/[framework]/[...slug]/upgrade/0-11-0.mdx
Co-authored-by: Guido D'Orsi <guido@float.com>
2025-02-13 17:34:20 +00:00
Benjamin S. Leveritt
a3b607e799 Update homepage/homepage/app/(docs)/docs/[framework]/[...slug]/upgrade/0-11-0.mdx
Co-authored-by: Guido D'Orsi <guido@float.com>
2025-02-13 17:34:06 +00:00
Benjamin S. Leveritt
8fb93502af Update homepage/homepage/app/(docs)/docs/[framework]/[...slug]/upgrade/0-11-0.mdx
Co-authored-by: Guido D'Orsi <guido@float.com>
2025-02-13 17:33:49 +00:00
Benjamin S. Leveritt
36774122e0 Updates react guide for new resolve API
Signed-off-by: Benjamin S. Leveritt <benjamin@leveritt.co.uk>
2025-02-13 16:08:52 +00:00
Benjamin S. Leveritt
a6923128c1 Adds upgrade guide
Signed-off-by: Benjamin S. Leveritt <benjamin@leveritt.co.uk>
2025-02-13 15:56:11 +00:00
Guido D'Orsi
706ca62feb Merge pull request #1363 from garden-co/unauthorized-deep-loading
fix: check CoValue permissions when loading/subscribing
2025-02-13 09:55:16 +01:00
Guido D'Orsi
01523dcca3 fix: check CoValue permissions when loading/subscribing 2025-02-13 09:19:19 +01:00
Guido D'Orsi
77f039b561 Merge remote-tracking branch 'origin/main' into jazz-581-rfc-new-deep-loading-api 2025-02-13 09:18:47 +01:00
Guido D'Orsi
d661ba77be chore: improve pre-release comment output 2025-02-12 11:32:05 +01:00
Guido D'Orsi
f8fbc59b6f Merge remote-tracking branch 'origin/main' into jazz-581-rfc-new-deep-loading-api 2025-02-11 16:26:12 +01:00
Guido D'Orsi
cce0d22007 fix: update resolve property 2025-02-11 15:15:44 +01:00
Guido D'Orsi
e3ff76e9cb Merge remote-tracking branch 'origin/authv2' into jazz-581-rfc-new-deep-loading-api 2025-02-11 15:12:51 +01:00
Guido D'Orsi
4cbf71bff7 Merge pull request #1338 from garden-co/new-deep-loading-extra-props-fix
fix: disallow extra props in the resolve type
2025-02-11 14:38:43 +01:00
Guido D'Orsi
ceb060243a fix: disallow extra props in the resolve type 2025-02-10 20:04:32 +01:00
Anselm
a70bebb96a Clean up new deep-loading API 2025-01-29 14:39:16 +00:00
Anselm
b3b2507c35 Merge branch 'main' into jazz-581-rfc-new-deep-loading-api 2025-01-27 11:36:08 +00:00
Anselm
6a8fa16b49 Fix form and chat-rn-clerk examples 2024-12-16 15:56:48 +00:00
Anselm
1f08807701 Merge branch 'main' into jazz-581-rfc-new-deep-loading-api 2024-12-16 15:37:39 +00:00
Anselm
ba4a7f6170 Remove temp vite config 2024-12-16 15:35:06 +00:00
Anselm
a2854e3602 Upgrade to minor version change 2024-12-16 11:25:00 +00:00
Anselm
4ea87dc494 Add changeset 2024-12-16 10:55:51 +00:00
Anselm
d8c87c5314 Use $each instead of each 2024-12-16 10:54:25 +00:00
Anselm
46f624a12e Replace 'items' with 'each' 2024-12-13 16:28:40 +00:00
Anselm
86ce770f38 Implement clearer syntax for deep loading 2024-12-13 15:12:42 +00:00
243 changed files with 5250 additions and 2013 deletions

View File

@@ -0,0 +1,6 @@
---
"jazz-tools": minor
"cojson": minor
---
Check CoValue access permissions when loading

View File

@@ -9,12 +9,15 @@
"cojson-storage",
"cojson-storage-indexeddb",
"cojson-storage-sqlite",
"cojson-storage-rn-sqlite",
"cojson-transport-ws",
"jazz-browser",
"jazz-auth-clerk",
"jazz-browser-media-images",
"jazz-inspector",
"jazz-nodejs",
"jazz-react",
"jazz-react-core",
"jazz-react-auth-clerk",
"jazz-react-native",
"jazz-react-native-auth-clerk",

View File

@@ -0,0 +1,5 @@
---
"jazz-tools": minor
---
Implement new API for deep loading

View File

@@ -0,0 +1,5 @@
---
"cojson": minor
---
Return the EVERYONE role if the account is not direct a member of the group

View File

@@ -25,6 +25,9 @@ jobs:
- name: Setup Source Code
uses: ./.github/actions/source-code/
- name: Build packages
run: pnpm exec turbo run build --filter='./packages/*'
- name: Create Release Pull Request or Publish to npm
id: changesets
uses: changesets/action@v1

View File

@@ -1,5 +1,28 @@
# chat-rn-clerk
## 1.0.82
### Patch Changes
- jazz-react-native@0.11.1
- jazz-react-native-auth-clerk@0.11.1
## 1.0.81
### Patch Changes
- Updated dependencies [6a96d8b]
- Updated dependencies [a35249a]
- Updated dependencies [b9d194a]
- Updated dependencies [a4713df]
- Updated dependencies [34cbdc3]
- Updated dependencies [f039e8f]
- Updated dependencies [e22de9f]
- jazz-tools@0.11.0
- jazz-react-native-media-images@0.11.0
- jazz-react-native-auth-clerk@0.11.0
- jazz-react-native@0.11.0
## 1.0.80
### Patch Changes

View File

@@ -28,7 +28,7 @@ export default function Conversation() {
const { me } = useAccount();
const [chat, setChat] = useState<Chat>();
const [message, setMessage] = useState("");
const loadedChat = useCoState(Chat, chat?.id, [{}]);
const loadedChat = useCoState(Chat, chat?.id, { resolve: { $each: true } });
const navigation = useNavigation();
const [isUploading, setIsUploading] = useState(false);
@@ -71,7 +71,7 @@ export default function Conversation() {
const loadChat = async (chatId: ID<Chat>) => {
try {
const chat = await Chat.load(chatId, me, []);
const chat = await Chat.load(chatId, me);
setChat(chat);
} catch (error) {
console.log("Error loading chat", error);

View File

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

View File

@@ -1,5 +1,25 @@
# chat-rn
## 1.0.78
### Patch Changes
- jazz-react-native@0.11.1
## 1.0.77
### Patch Changes
- Updated dependencies [6a96d8b]
- Updated dependencies [a35249a]
- Updated dependencies [b9d194a]
- Updated dependencies [a4713df]
- Updated dependencies [34cbdc3]
- Updated dependencies [f039e8f]
- Updated dependencies [e22de9f]
- jazz-tools@0.11.0
- jazz-react-native@0.11.0
## 1.0.76
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "chat-rn",
"version": "1.0.76",
"version": "1.0.78",
"main": "index.js",
"scripts": {
"build": "expo export -p ios",

View File

@@ -20,7 +20,7 @@ import { Chat, Message } from "./schema";
export default function ChatScreen({ navigation }: { navigation: any }) {
const { me, logOut } = useAccount();
const [chatId, setChatId] = useState<ID<Chat>>();
const loadedChat = useCoState(Chat, chatId, [{}]);
const loadedChat = useCoState(Chat, chatId, { resolve: { $each: true } });
const [message, setMessage] = useState("");
const profile = useCoState(Profile, me._refs.profile?.id, {});

View File

@@ -1,5 +1,21 @@
# chat-vue
## 0.0.63
### Patch Changes
- Updated dependencies [6a96d8b]
- Updated dependencies [a35249a]
- Updated dependencies [b9d194a]
- Updated dependencies [a4713df]
- Updated dependencies [34cbdc3]
- Updated dependencies [18428ea]
- Updated dependencies [f039e8f]
- Updated dependencies [e22de9f]
- jazz-tools@0.11.0
- jazz-browser@0.11.0
- jazz-vue@0.11.0
## 0.0.62
### Patch Changes

View File

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

View File

@@ -49,7 +49,7 @@ export default defineComponent({
},
},
setup(props) {
const chat = useCoState(Chat, props.chatId, [{}]);
const chat = useCoState(Chat, props.chatId, { resolve: { $each: true } });
const showNLastMessages = ref(30);
const displayedMessages = computed(() => {

View File

@@ -1,5 +1,26 @@
# jazz-example-chat
## 0.0.160
### Patch Changes
- jazz-react@0.11.1
## 0.0.159
### Patch Changes
- Updated dependencies [6a96d8b]
- Updated dependencies [a35249a]
- Updated dependencies [b9d194a]
- Updated dependencies [a4713df]
- Updated dependencies [34cbdc3]
- Updated dependencies [f039e8f]
- Updated dependencies [e22de9f]
- jazz-tools@0.11.0
- jazz-browser-media-images@0.11.0
- jazz-react@0.11.0
## 0.0.158
### Patch Changes

View File

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

View File

@@ -17,7 +17,7 @@ import {
} from "./ui.tsx";
export function ChatScreen(props: { chatID: ID<Chat> }) {
const chat = useCoState(Chat, props.chatID, [{}]);
const chat = useCoState(Chat, props.chatID, { resolve: { $each: true } });
const [showNLastMessages, setShowNLastMessages] = useState(30);
if (!chat)

View File

@@ -1,5 +1,27 @@
# minimal-auth-clerk
## 0.0.59
### Patch Changes
- jazz-react@0.11.1
- jazz-react-auth-clerk@0.11.1
## 0.0.58
### Patch Changes
- Updated dependencies [6a96d8b]
- Updated dependencies [a35249a]
- Updated dependencies [b9d194a]
- Updated dependencies [a4713df]
- Updated dependencies [34cbdc3]
- Updated dependencies [f039e8f]
- Updated dependencies [e22de9f]
- jazz-tools@0.11.0
- jazz-react-auth-clerk@0.11.0
- jazz-react@0.11.0
## 0.0.57
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "clerk",
"private": true,
"version": "0.0.57",
"version": "0.0.59",
"type": "module",
"scripts": {
"dev": "vite",
@@ -13,7 +13,7 @@
"dependencies": {
"@clerk/clerk-react": "^5.4.1",
"jazz-react": "workspace:*",
"jazz-react-auth-clerk": "workspace:0.10.15",
"jazz-react-auth-clerk": "workspace:0.11.1",
"jazz-tools": "workspace:*",
"react": "^18.3.1",
"react-dom": "^18.3.1"

View File

@@ -1,5 +1,19 @@
# file-share-svelte
## 0.0.43
### Patch Changes
- Updated dependencies [6a96d8b]
- Updated dependencies [a35249a]
- Updated dependencies [b9d194a]
- Updated dependencies [a4713df]
- Updated dependencies [34cbdc3]
- Updated dependencies [f039e8f]
- Updated dependencies [e22de9f]
- jazz-tools@0.11.0
- jazz-svelte@0.11.0
## 0.0.42
### Patch Changes

View File

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

View File

@@ -1,5 +1,30 @@
# form
## 0.1.1
### Patch Changes
- jazz-react@0.11.1
## 0.1.0
### Minor Changes
- 18428ea: PasskeyAuth: Sets `profile.name` only if a non-empty username is passed to `signUp`
### Patch Changes
- Updated dependencies [6a96d8b]
- Updated dependencies [a35249a]
- Updated dependencies [b9d194a]
- Updated dependencies [a4713df]
- Updated dependencies [34cbdc3]
- Updated dependencies [f039e8f]
- Updated dependencies [e22de9f]
- jazz-tools@0.11.0
- jazz-browser-media-images@0.11.0
- jazz-react@0.11.0
## 0.0.53
### Patch Changes

View File

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

View File

@@ -12,7 +12,9 @@ import {
} from "./schema.ts";
export function CreateOrder() {
const { me } = useAccount({ root: { draft: {}, orders: [] } });
const { me } = useAccount({
resolve: { root: { draft: true, orders: true } },
});
const router = useIframeHashRouter();
const [errors, setErrors] = useState<string[]>([]);
@@ -60,7 +62,7 @@ function CreateOrderForm({
onSave: (draft: DraftBubbleTeaOrder) => void;
}) {
const draft = useCoState(DraftBubbleTeaOrder, id, {
addOns: [],
resolve: { addOns: true },
});
if (!draft) return;

View File

@@ -2,7 +2,7 @@ import { useAccount } from "jazz-react";
export function DraftIndicator() {
const { me } = useAccount({
root: { draft: {} },
resolve: { root: { draft: true } },
});
if (me?.root.draft?.hasChanges) {

View File

@@ -6,7 +6,7 @@ import { OrderThumbnail } from "./OrderThumbnail.tsx";
import { BubbleTeaOrder } from "./schema.ts";
export function EditOrder(props: { id: ID<BubbleTeaOrder> }) {
const order = useCoState(BubbleTeaOrder, props.id, []);
const order = useCoState(BubbleTeaOrder, props.id);
if (!order) return;

View File

@@ -4,7 +4,7 @@ import { OrderThumbnail } from "./OrderThumbnail.tsx";
export function Orders() {
const { me } = useAccount({
root: { orders: [] },
resolve: { root: { orders: true } },
});
return (

View File

@@ -1,5 +1,26 @@
# image-upload
## 0.0.57
### Patch Changes
- jazz-react@0.11.1
## 0.0.56
### Patch Changes
- Updated dependencies [6a96d8b]
- Updated dependencies [a35249a]
- Updated dependencies [b9d194a]
- Updated dependencies [a4713df]
- Updated dependencies [34cbdc3]
- Updated dependencies [f039e8f]
- Updated dependencies [e22de9f]
- jazz-tools@0.11.0
- jazz-browser-media-images@0.11.0
- jazz-react@0.11.0
## 0.0.55
### Patch Changes

View File

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

View File

@@ -1,5 +1,17 @@
# jazz-example-inspector
## 0.0.111
### Patch Changes
- Updated dependencies [b9d194a]
- Updated dependencies [a4713df]
- Updated dependencies [e22de9f]
- Updated dependencies [34cbdc3]
- Updated dependencies [0f67e0a]
- cojson@0.11.0
- cojson-transport-ws@0.11.0
## 0.0.110
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-inspector-app",
"private": true,
"version": "0.0.110",
"version": "0.0.111",
"type": "module",
"scripts": {
"dev": "vite",
@@ -16,8 +16,8 @@
"@radix-ui/react-toast": "^1.1.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"cojson": "workspace:0.10.15",
"cojson-transport-ws": "workspace:0.10.15",
"cojson": "workspace:0.11.0",
"cojson-transport-ws": "workspace:0.11.0",
"hash-slash": "workspace:0.2.2",
"lucide-react": "^0.274.0",
"qrcode": "^1.5.3",

View File

@@ -1,5 +1,29 @@
# jazz-example-musicplayer
## 0.0.81
### Patch Changes
- Updated dependencies [7b00a81]
- jazz-inspector@0.11.1
- jazz-react@0.11.1
## 0.0.80
### Patch Changes
- Updated dependencies [6a96d8b]
- Updated dependencies [a35249a]
- Updated dependencies [b9d194a]
- Updated dependencies [a4713df]
- Updated dependencies [34cbdc3]
- Updated dependencies [b7deb3c]
- Updated dependencies [f039e8f]
- Updated dependencies [e22de9f]
- jazz-tools@0.11.0
- jazz-react@0.11.0
- jazz-inspector@0.10.13
## 0.0.79
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-music-player",
"private": true,
"version": "0.0.79",
"version": "0.0.81",
"type": "module",
"scripts": {
"dev": "vite",
@@ -22,8 +22,8 @@
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"jazz-inspector": "workspace:*",
"jazz-react": "workspace:0.10.15",
"jazz-tools": "workspace:0.10.15",
"jazz-react": "workspace:0.11.1",
"jazz-tools": "workspace:0.11.0",
"lucide-react": "^0.274.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",

View File

@@ -24,10 +24,7 @@ export function HomePage({ mediaPlayer }: { mediaPlayer: MediaPlayer }) {
* access rights to CoValues. We get it from the top-level provider `<WithJazz/>`.
*/
const { me } = useAccount({
root: {
rootPlaylist: {},
playlists: [],
},
resolve: { root: { rootPlaylist: true, playlists: true } },
});
const navigate = useNavigate();
@@ -51,8 +48,9 @@ export function HomePage({ mediaPlayer }: { mediaPlayer: MediaPlayer }) {
const params = useParams<{ playlistId: ID<Playlist> }>();
const playlistId = params.playlistId ?? me?.root._refs.rootPlaylist.id;
const playlist = useCoState(Playlist, playlistId, {
tracks: [],
resolve: { tracks: true },
});
const isRootPlaylist = !params.playlistId;

View File

@@ -27,9 +27,11 @@ export async function uploadMusicTracks(
isExampleTrack: boolean = false,
) {
const { root } = await MusicaAccount.getMe().ensureLoaded({
root: {
rootPlaylist: {
tracks: [],
resolve: {
root: {
rootPlaylist: {
tracks: true,
},
},
},
});
@@ -65,8 +67,10 @@ export async function uploadMusicTracks(
export async function createNewPlaylist() {
const { root } = await MusicaAccount.getMe().ensureLoaded({
root: {
playlists: [],
resolve: {
root: {
playlists: true,
},
},
});
@@ -112,34 +116,31 @@ export async function addTrackToPlaylist(
playlist.tracks?.push(track);
return;
}
}
/**
* Since musicTracks are created as private values (see uploadMusicTracks)
* to make them shareable as part of the playlist we are cloning them
* and setting the playlist group as owner of the clone
*
* Doing this for backwards compatibility for when the Group inheritance wasn't possible
*/
const blob = await FileStream.loadAsBlob(track._refs.file.id);
const waveform = await MusicTrackWaveform.load(track._refs.waveform.id, {});
if (!blob || !waveform) return;
const trackClone = MusicTrack.create(
{
file: await FileStream.createFromBlob(blob, playlist._owner),
duration: track.duration,
waveform: MusicTrackWaveform.create(
{ data: waveform.data },
playlist._owner,
),
title: track.title,
sourceTrack: track,
},
playlist._owner,
export async function removeTrackFromPlaylist(
playlist: Playlist,
track: MusicTrack,
) {
const notAdded = !playlist.tracks?.some(
(t) => t?.id === track.id || t?._refs.sourceTrack?.id === track.id,
);
playlist.tracks?.push(trackClone);
if (notAdded) return;
if (track._owner._type === "Group" && playlist._owner._type === "Group") {
const trackGroup = track._owner;
await trackGroup.revokeExtend(playlist._owner);
const index =
playlist.tracks?.findIndex(
(t) => t?.id === track.id || t?._refs.sourceTrack?.id === track.id,
) ?? -1;
if (index > -1) {
playlist.tracks?.splice(index, 1);
}
return;
}
}
export async function updatePlaylistTitle(playlist: Playlist, title: string) {
@@ -152,9 +153,11 @@ export async function updateMusicTrackTitle(track: MusicTrack, title: string) {
export async function updateActivePlaylist(playlist?: Playlist) {
const { root } = await MusicaAccount.getMe().ensureLoaded({
root: {
activePlaylist: {},
rootPlaylist: {},
resolve: {
root: {
activePlaylist: true,
rootPlaylist: true,
},
},
});
@@ -163,7 +166,9 @@ export async function updateActivePlaylist(playlist?: Playlist) {
export async function updateActiveTrack(track: MusicTrack) {
const { root } = await MusicaAccount.getMe().ensureLoaded({
root: {},
resolve: {
root: {},
},
});
root.activeTrack = track;
@@ -173,17 +178,23 @@ export async function onAnonymousAccountDiscarded(
anonymousAccount: MusicaAccount,
) {
const { root: anonymousAccountRoot } = await anonymousAccount.ensureLoaded({
root: {
rootPlaylist: {
tracks: [{}],
resolve: {
root: {
rootPlaylist: {
tracks: {
$each: true,
},
},
},
},
});
const me = await MusicaAccount.getMe().ensureLoaded({
root: {
rootPlaylist: {
tracks: [],
resolve: {
root: {
rootPlaylist: {
tracks: true,
},
},
},
});

View File

@@ -9,7 +9,7 @@ import { getNextTrack, getPrevTrack } from "./lib/getters";
export function useMediaPlayer() {
const { me } = useAccount({
root: {},
resolve: { root: true },
});
const playState = usePlayState();

View File

@@ -1,4 +1,4 @@
import { useAcceptInvite } from "jazz-react";
import { useAcceptInvite, useIsAuthenticated } from "jazz-react";
import { ID } from "jazz-tools";
import { useCallback } from "react";
import { useNavigate } from "react-router-dom";
@@ -7,6 +7,8 @@ import { MusicaAccount, Playlist } from "./1_schema";
export function InvitePage() {
const navigate = useNavigate();
const isAuthenticated = useIsAuthenticated();
useAcceptInvite({
invitedObjectSchema: Playlist,
onAccept: useCallback(
@@ -14,8 +16,10 @@ export function InvitePage() {
const playlist = await Playlist.load(playlistId, {});
const me = await MusicaAccount.getMe().ensureLoaded({
root: {
playlists: [],
resolve: {
root: {
playlists: true,
},
},
});
@@ -32,5 +36,9 @@ export function InvitePage() {
),
});
return <p>Accepting invite....</p>;
return isAuthenticated ? (
<p>Accepting invite....</p>
) : (
<p>Please sign in to accept the invite.</p>
);
}

View File

@@ -22,9 +22,13 @@ export function AuthModal({ open, onOpenChange }: AuthModalProps) {
const [error, setError] = useState<string | null>(null);
const { me } = useAccount({
root: {
rootPlaylist: {
tracks: [{}],
resolve: {
root: {
rootPlaylist: {
tracks: {
$each: true,
},
},
},
},
});

View File

@@ -1,5 +1,5 @@
import { MusicTrack, Playlist } from "@/1_schema";
import { addTrackToPlaylist } from "@/4_actions";
import { addTrackToPlaylist, removeTrackFromPlaylist } from "@/4_actions";
import {
DropdownMenu,
DropdownMenuContent,
@@ -10,6 +10,7 @@ import { cn } from "@/lib/utils";
import { useAccount, useCoState } from "jazz-react";
import { ID } from "jazz-tools";
import { MoreHorizontal } from "lucide-react";
import { Fragment } from "react/jsx-runtime";
import { MusicTrackTitleInput } from "./MusicTrackTitleInput";
import { Button } from "./ui/button";
@@ -29,9 +30,7 @@ export function MusicTrackRow({
const track = useCoState(MusicTrack, trackId);
const { me } = useAccount({
root: {
playlists: [{}],
},
resolve: { root: { playlists: { $each: true } } },
});
const playlists = me?.root.playlists ?? [];
@@ -46,6 +45,11 @@ export function MusicTrackRow({
addTrackToPlaylist(playlist, track);
}
function handleRemoveFromPlaylist(playlist: Playlist) {
if (!track) return;
removeTrackFromPlaylist(playlist, track);
}
return (
<li
className={
@@ -85,12 +89,20 @@ export function MusicTrackRow({
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{playlists.map((playlist, index) => (
<DropdownMenuItem
key={index}
onSelect={() => handleAddToPlaylist(playlist)}
>
Add to {playlist.title}
</DropdownMenuItem>
<Fragment key={index}>
<DropdownMenuItem
key={`add-${index}`}
onSelect={() => handleAddToPlaylist(playlist)}
>
Add to {playlist.title}
</DropdownMenuItem>
<DropdownMenuItem
key={`remove-${index}`}
onSelect={() => handleRemoveFromPlaylist(playlist)}
>
Remove from {playlist.title}
</DropdownMenuItem>
</Fragment>
))}
</DropdownMenuContent>
</DropdownMenu>

View File

@@ -12,9 +12,7 @@ export function PlayerControls({ mediaPlayer }: { mediaPlayer: MediaPlayer }) {
const isPlaying = playState.value === "play";
const activePlaylist = useAccount({
root: {
activePlaylist: {},
},
resolve: { root: { activePlaylist: true } },
}).me?.root.activePlaylist;
useMediaEndListener(mediaPlayer.playNextTrack);
@@ -25,7 +23,7 @@ export function PlayerControls({ mediaPlayer }: { mediaPlayer: MediaPlayer }) {
});
const activeTrack = useCoState(MusicTrack, mediaPlayer.activeTrackId, {
waveform: {},
resolve: { waveform: true },
});
if (!activeTrack) return null;

View File

@@ -6,9 +6,7 @@ export function SidePanel() {
const { playlistId } = useParams();
const navigate = useNavigate();
const { me } = useAccount({
root: {
playlists: [{}],
},
resolve: { root: { playlists: { $each: true } } },
});
function handleAllTracksClick(evt: React.MouseEvent<HTMLAnchorElement>) {

View File

@@ -8,7 +8,6 @@ export function Waveform(props: { track: MusicTrack; height: number }) {
const waveformData = useCoState(
MusicTrackWaveform,
track._refs.waveform.id,
{},
)?.data;
const duration = track.duration;

View File

@@ -2,9 +2,11 @@ import { MusicaAccount } from "../1_schema";
export async function getNextTrack() {
const me = await MusicaAccount.getMe().ensureLoaded({
root: {
activePlaylist: {
tracks: [],
resolve: {
root: {
activePlaylist: {
tracks: true,
},
},
},
});
@@ -21,9 +23,11 @@ export async function getNextTrack() {
export async function getPrevTrack() {
const me = await MusicaAccount.getMe().ensureLoaded({
root: {
activePlaylist: {
tracks: [],
resolve: {
root: {
activePlaylist: {
tracks: true,
},
},
},
});

View File

@@ -13,7 +13,7 @@ export function useUploadExampleData() {
async function uploadOnboardingData() {
const me = await MusicaAccount.getMe().ensureLoaded({
root: {},
resolve: { root: true },
});
if (me.root.exampleDataLoaded) return;

View File

@@ -82,3 +82,47 @@ test("create a new playlist and share", async ({
await luigiHome.playMusicTrack("Super Mario World");
await luigiHome.expectActiveTrackPlaying();
});
test("create a new playlist, share, then remove track", async ({
page: marioPage,
browser,
}) => {
// Create playlist with a song and share
await marioPage.goto("/");
const marioHome = new HomePage(marioPage);
await marioHome.expectMusicTrack("Example song");
await marioHome.editTrackTitle("Example song", "Super Mario World");
await marioHome.createPlaylist();
await marioHome.editPlaylistTitle("Save the princess");
await marioHome.navigateToPlaylist("All tracks");
await marioHome.addTrackToPlaylist("Super Mario World", "Save the princess");
await marioHome.navigateToPlaylist("Save the princess");
await marioHome.expectMusicTrack("Super Mario World");
await marioHome.signUp("Mario");
const url = await marioHome.getShareLink();
await sleep(4000); // Wait for the sync to complete
// Retrieve shared playlist
const luigiContext = await browser.newContext();
await mockAuthenticator(luigiContext);
const luigiPage = await luigiContext.newPage();
await luigiPage.goto("/");
const luigiHome = new HomePage(luigiPage);
await luigiHome.signUp("Luigi");
await luigiPage.goto(url);
await luigiHome.expectMusicTrack("Super Mario World");
// Remove track from playlist
await marioHome.navigateToHome();
await marioHome.removeTrackFromPlaylist(
"Super Mario World",
"Save the princess",
);
await sleep(4000); // Wait for the sync to complete
// Expect that the track is removed from the playlist
await marioHome.navigateToPlaylist("Save the princess");
await marioHome.notExpectMusicTrack("Super Mario World");
await luigiHome.notExpectMusicTrack("Super Mario World");
});

View File

@@ -33,6 +33,14 @@ export class HomePage {
).toBeVisible();
}
async notExpectMusicTrack(trackName: string) {
await expect(
this.page.getByRole("button", {
name: `Play ${trackName}`,
}),
).not.toBeVisible();
}
async playMusicTrack(trackName: string) {
await this.page
.getByRole("button", {
@@ -65,6 +73,14 @@ export class HomePage {
.click();
}
async navigateToHome() {
await this.page
.getByRole("link", {
name: "All tracks",
})
.click();
}
async getShareLink() {
await this.page
.getByRole("button", {
@@ -95,6 +111,20 @@ export class HomePage {
.click();
}
async removeTrackFromPlaylist(trackTitle: string, playlistTitle: string) {
await this.page
.getByRole("button", {
name: `Open ${trackTitle} menu`,
})
.click();
await this.page
.getByRole("menuitem", {
name: `Remove from ${playlistTitle}`,
})
.click();
}
async signUp(name: string) {
await this.page.getByRole("button", { name: "Sign up" }).click();
await this.page.getByRole("textbox", { name: "Username" }).fill(name);

View File

@@ -1,5 +1,25 @@
# organization
## 0.0.53
### Patch Changes
- jazz-react@0.11.1
## 0.0.52
### Patch Changes
- Updated dependencies [6a96d8b]
- Updated dependencies [a35249a]
- Updated dependencies [b9d194a]
- Updated dependencies [a4713df]
- Updated dependencies [34cbdc3]
- Updated dependencies [f039e8f]
- Updated dependencies [e22de9f]
- jazz-tools@0.11.0
- jazz-react@0.11.0
## 0.0.51
### Patch Changes

View File

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

View File

@@ -5,11 +5,11 @@ import { Organization } from "./schema.ts";
export function AcceptInvitePage() {
const navigate = useNavigate();
const { me } = useAccount({ root: { organizations: [] } });
const { me } = useAccount({ resolve: { root: { organizations: true } } });
const onAccept = (organizationId: ID<Organization>) => {
if (me?.root?.organizations) {
Organization.load(organizationId, me, []).then((organization) => {
Organization.load(organizationId).then((organization) => {
if (organization) {
// avoid duplicates
const ids = me.root.organizations.map(

View File

@@ -5,7 +5,7 @@ import { Heading } from "./components/Heading.tsx";
export function HomePage() {
const { me } = useAccount({
root: { organizations: [{}] },
resolve: { root: { organizations: true } },
});
if (!me?.root.organizations) return;

View File

@@ -3,7 +3,7 @@ import { UserIcon } from "lucide-react";
export function Layout({ children }: { children: React.ReactNode }) {
const { me, logOut } = useAccount({
root: { draftOrganization: {} },
resolve: { root: { draftOrganization: true } },
});
return (

View File

@@ -13,7 +13,7 @@ export function OrganizationPage() {
.organizationId;
const organization = useCoState(Organization, paramOrganizationId, {
projects: [],
resolve: { projects: true },
});
if (!organization) return <p>Loading organization...</p>;

View File

@@ -8,7 +8,7 @@ import { OrganizationForm } from "./OrganizationForm.tsx";
export function CreateOrganization() {
const { me } = useAccount({
root: { draftOrganization: {}, organizations: [] },
resolve: { root: { draftOrganization: true, organizations: true } },
});
const [errors, setErrors] = useState<string[]>([]);
const navigate = useNavigate();
@@ -23,7 +23,7 @@ export function CreateOrganization() {
return;
}
const group = Group.create({ owner: me });
const group = Group.create();
me.root.organizations.push(draft as Organization);

View File

@@ -1,5 +1,4 @@
import { useCoState } from "jazz-react";
import { Account, Group, ID } from "jazz-tools";
import { Group } from "jazz-tools";
import { Organization } from "../schema.ts";
export function OrganizationMembers({
@@ -10,26 +9,13 @@ export function OrganizationMembers({
return (
<>
{group.members.map((member) => (
<Member
key={member.id}
accountId={member.id as ID<Account>}
role={member.role}
/>
<div key={member.id} className="px-4 py-5 sm:px-6">
<strong className="font-medium">
{member.account.profile?.name}
</strong>{" "}
({member.role})
</div>
))}
</>
);
}
function Member({
accountId,
role,
}: { accountId: ID<Account>; role?: string }) {
const account = useCoState(Account, accountId, { profile: {} });
if (!account?.profile) return;
return (
<div className="px-4 py-5 sm:px-6">
<strong className="font-medium">{account.profile.name}</strong> ({role})
</div>
);
}

View File

@@ -7,7 +7,7 @@ import { Organization } from "../schema.ts";
export function OrganizationSelector({ className }: { className?: string }) {
const { me } = useAccount({
root: { organizations: [{}] },
resolve: { root: { organizations: { $each: true } } },
});
const navigate = useNavigate();

View File

@@ -1,5 +1,11 @@
# passkey-svelte
## 0.0.47
### Patch Changes
- jazz-svelte@0.11.0
## 0.0.46
### Patch Changes

View File

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

View File

@@ -1,5 +1,25 @@
# minimal-auth-passkey
## 0.0.58
### Patch Changes
- jazz-react@0.11.1
## 0.0.57
### Patch Changes
- Updated dependencies [6a96d8b]
- Updated dependencies [a35249a]
- Updated dependencies [b9d194a]
- Updated dependencies [a4713df]
- Updated dependencies [34cbdc3]
- Updated dependencies [f039e8f]
- Updated dependencies [e22de9f]
- jazz-tools@0.11.0
- jazz-react@0.11.0
## 0.0.56
### Patch Changes

View File

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

View File

@@ -1,5 +1,25 @@
# passphrase
## 0.0.55
### Patch Changes
- jazz-react@0.11.1
## 0.0.54
### Patch Changes
- Updated dependencies [6a96d8b]
- Updated dependencies [a35249a]
- Updated dependencies [b9d194a]
- Updated dependencies [a4713df]
- Updated dependencies [34cbdc3]
- Updated dependencies [f039e8f]
- Updated dependencies [e22de9f]
- jazz-tools@0.11.0
- jazz-react@0.11.0
## 0.0.53
### Patch Changes

View File

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

View File

@@ -1,5 +1,25 @@
# jazz-password-manager
## 0.0.79
### Patch Changes
- jazz-react@0.11.1
## 0.0.78
### Patch Changes
- Updated dependencies [6a96d8b]
- Updated dependencies [a35249a]
- Updated dependencies [b9d194a]
- Updated dependencies [a4713df]
- Updated dependencies [34cbdc3]
- Updated dependencies [f039e8f]
- Updated dependencies [e22de9f]
- jazz-tools@0.11.0
- jazz-react@0.11.0
## 0.0.77
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-password-manager",
"private": true,
"version": "0.0.77",
"version": "0.0.79",
"type": "module",
"scripts": {
"dev": "vite",
@@ -12,8 +12,8 @@
"clean-install": "rm -rf node_modules pnpm-lock.yaml && pnpm install"
},
"dependencies": {
"jazz-react": "workspace:0.10.15",
"jazz-tools": "workspace:0.10.15",
"jazz-react": "workspace:0.11.1",
"jazz-tools": "workspace:0.11.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.41.5",

View File

@@ -6,7 +6,7 @@ import NewItemModal from "./components/new-item-modal";
import Table from "./components/table";
import { useAccount, useCoState } from "jazz-react";
import { CoMapInit, Group, ID } from "jazz-tools";
import { CoMapInit, ID } from "jazz-tools";
import { useNavigate, useParams } from "react-router-dom";
import { Folder, FolderList, PasswordItem } from "./1_schema";
import {
@@ -43,9 +43,11 @@ const VaultPage: React.FC = () => {
(item): item is Exclude<typeof item, null> => !!item,
) || [],
);
const folders = useCoState(FolderList, me.root?._refs.folders?.id, [
{ items: [{}] },
]);
const folders = useCoState(FolderList, me.root?._refs.folders?.id, {
resolve: {
$each: { items: { $each: true } },
},
});
const [selectedFolder, setSelectedFolder] = useState<Folder | undefined>();
const [isNewItemModalOpen, setIsNewItemModalOpen] = useState(false);
@@ -136,20 +138,14 @@ const VaultPage: React.FC = () => {
</Button>
<Button
onClick={() => setEditingItem(item)}
disabled={
item._owner.castAs(Group).myRole() !== "admin" &&
item._owner.castAs(Group).myRole() !== "writer"
}
disabled={!me.canWrite(item)}
>
Edit
</Button>
<Button
onClick={() => handleDeleteItem(item)}
variant="danger"
disabled={
item._owner.castAs(Group).myRole() !== "admin" &&
item._owner.castAs(Group).myRole() !== "writer"
}
disabled={!me.canWrite(item)}
>
Delete
</Button>
@@ -210,21 +206,13 @@ const VaultPage: React.FC = () => {
<div className="flex gap-2">
<Button
onClick={() => setIsNewItemModalOpen(true)}
disabled={
!selectedFolder ||
(selectedFolder._owner.castAs(Group).myRole() !== "admin" &&
selectedFolder._owner.castAs(Group).myRole() !== "writer")
}
disabled={!selectedFolder || !me.canWrite(selectedFolder)}
>
New Item
</Button>
<Button
onClick={() => setIsInviteModalOpen(true)}
disabled={
!selectedFolder ||
(selectedFolder._owner.castAs(Group).myRole() !== "admin" &&
selectedFolder._owner.castAs(Group).myRole() !== "writer")
}
disabled={!selectedFolder || !me.canWrite(selectedFolder)}
>
Share Folder
</Button>

View File

@@ -60,11 +60,9 @@ export async function addSharedFolder(
me: PasswordManagerAccount,
) {
const [sharedFolder, account] = await Promise.all([
Folder.load(sharedFolderId, me, {}),
PasswordManagerAccount.load(me.id, me, {
root: {
folders: [],
},
Folder.load(sharedFolderId),
PasswordManagerAccount.load(me.id, {
resolve: { root: { folders: true } },
}),
]);

View File

@@ -21,11 +21,9 @@ const InviteModal: React.FC<InviteModalProps> = ({
>("reader");
const [inviteLink, setInviteLink] = useState("");
const members = selectedFolder?._owner.castAs(Group).members;
const members = selectedFolder?._owner.members;
const invitedMembers = members
? members
.filter((m) => !m.account?.isMe && m.role !== "revoked")
.map((m) => m.account)
? members.filter((m) => !m.account?.isMe).map((m) => m.account)
: [];
const handleCreateInviteLink = () => {

View File

@@ -1,5 +1,26 @@
# jazz-example-pets
## 0.0.177
### Patch Changes
- jazz-react@0.11.1
## 0.0.176
### Patch Changes
- Updated dependencies [6a96d8b]
- Updated dependencies [a35249a]
- Updated dependencies [b9d194a]
- Updated dependencies [a4713df]
- Updated dependencies [34cbdc3]
- Updated dependencies [f039e8f]
- Updated dependencies [e22de9f]
- jazz-tools@0.11.0
- jazz-browser-media-images@0.11.0
- jazz-react@0.11.0
## 0.0.175
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-pets",
"private": true,
"version": "0.0.175",
"version": "0.0.177",
"type": "module",
"scripts": {
"dev": "vite",
@@ -19,9 +19,9 @@
"@radix-ui/react-toast": "^1.1.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"jazz-browser-media-images": "workspace:0.10.15",
"jazz-react": "workspace:0.10.15",
"jazz-tools": "workspace:0.10.15",
"jazz-browser-media-images": "workspace:0.11.0",
"jazz-react": "workspace:0.11.1",
"jazz-tools": "workspace:0.11.0",
"lucide-react": "^0.274.0",
"qrcode": "^1.5.3",
"react": "^18.3.1",
@@ -41,7 +41,7 @@
"@vitejs/plugin-react-swc": "^3.3.2",
"autoprefixer": "^10.4.20",
"is-ci": "^3.0.1",
"jazz-run": "workspace:0.10.15",
"jazz-run": "workspace:0.11.0",
"postcss": "^8.4.27",
"tailwindcss": "^3.4.17",
"typescript": "~5.6.2",

View File

@@ -34,7 +34,7 @@ export function NewPetPostForm() {
if (newPetPost) {
newPetPost.name = name;
} else {
const petPostGroup = Group.create({ owner: me });
const petPostGroup = Group.create();
const petPost = PartialPetPost.create(
{
name,

View File

@@ -2,7 +2,7 @@ import { useParams } from "react-router";
import { PetPost, PetReactions, ReactionTypes } from "./1_schema";
import { ProgressiveImg } from "jazz-react";
import { ProgressiveImg, useAccount } from "jazz-react";
import { useCoState } from "jazz-react";
import { ID } from "jazz-tools";
import uniqolor from "uniqolor";
@@ -26,6 +26,7 @@ const reactionEmojiMap: {
export function RatePetPostUI() {
const petPostID = useParams<{ petPostId: ID<PetPost> }>().petPostId;
const { me } = useAccount();
const petPost = useCoState(PetPost, petPostID);
return (
@@ -60,7 +61,7 @@ export function RatePetPostUI() {
))}
</div>
{petPost?._owner.myRole() === "admin" && petPost.reactions && (
{petPost && me.canAdmin(petPost) && petPost.reactions && (
<ReactionOverview petReactions={petPost.reactions} />
)}
</div>

View File

@@ -2,7 +2,7 @@ import { useState } from "react";
import { PetPost } from "../1_schema";
import { createInviteLink } from "jazz-react";
import { createInviteLink, useAccount } from "jazz-react";
import QRCode from "qrcode";
import { Button, useToast } from "../basicComponents";
@@ -10,9 +10,11 @@ import { Button, useToast } from "../basicComponents";
export function ShareButton({ petPost }: { petPost?: PetPost | null }) {
const [existingInviteLink, setExistingInviteLink] = useState<string>();
const { toast } = useToast();
const { me } = useAccount();
return (
petPost?._owner.myRole() === "admin" && (
petPost &&
me.canAdmin(petPost) && (
<Button
size="sm"
className="py-0"

View File

@@ -1,5 +1,26 @@
# reactions
## 0.0.57
### Patch Changes
- jazz-react@0.11.1
## 0.0.56
### Patch Changes
- Updated dependencies [6a96d8b]
- Updated dependencies [a35249a]
- Updated dependencies [b9d194a]
- Updated dependencies [a4713df]
- Updated dependencies [34cbdc3]
- Updated dependencies [f039e8f]
- Updated dependencies [e22de9f]
- jazz-tools@0.11.0
- jazz-browser-media-images@0.11.0
- jazz-react@0.11.0
## 0.0.55
### Patch Changes

View File

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

View File

@@ -9,8 +9,7 @@ function App() {
const router = useIframeHashRouter();
const createReactions = () => {
if (!me) return;
const group = Group.create({ owner: me });
const group = Group.create();
group.addMember("everyone", "writer");
const chat = Reactions.create([], { owner: group });
router.navigate("/#/reactions/" + chat.id);

View File

@@ -14,7 +14,7 @@ const reactionEmojiMap: {
};
export function ReactionsScreen(props: { id: ID<Reactions> }) {
const reactions = useCoState(Reactions, props.id, []);
const reactions = useCoState(Reactions, props.id);
if (!reactions) return;

View File

@@ -1,5 +1,21 @@
# todo-vue
## 0.0.61
### Patch Changes
- Updated dependencies [6a96d8b]
- Updated dependencies [a35249a]
- Updated dependencies [b9d194a]
- Updated dependencies [a4713df]
- Updated dependencies [34cbdc3]
- Updated dependencies [18428ea]
- Updated dependencies [f039e8f]
- Updated dependencies [e22de9f]
- jazz-tools@0.11.0
- jazz-browser@0.11.0
- jazz-vue@0.11.0
## 0.0.60
### Patch Changes

View File

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

View File

@@ -4,15 +4,15 @@
<div class="section-header">
<h2>Folders</h2>
<div class="new-folder">
<input
v-model="newFolderName"
placeholder="New folder name"
<input
v-model="newFolderName"
placeholder="New folder name"
class="input"
/>
<button class="btn btn-primary" @click="createFolder">Create</button>
</div>
</div>
<div class="folder-list">
<div
v-for="folder in folders"
@@ -32,9 +32,9 @@
<div class="section-header">
<h2>{{ selectedFolder?.name }}</h2>
<div class="new-todo">
<input
v-model="newTodoTitle"
placeholder="Add a new task"
<input
v-model="newTodoTitle"
placeholder="Add a new task"
class="input"
/>
<button class="btn btn-primary" @click="createTodo">Add</button>
@@ -72,7 +72,9 @@ import { Folder, FolderList, ToDoItem, ToDoList } from "../schema";
const { me } = useAccount();
const computedFoldersId = computed(() => me.value?.root?.folders?.id);
const folders = useCoState(FolderList, computedFoldersId, [{ items: [{}] }]);
const folders = useCoState(FolderList, computedFoldersId, {
resolve: { $each: { items: true } },
});
const selectedFolder = ref<Folder>();
const newFolderName = ref("");

View File

@@ -1,5 +1,25 @@
# jazz-example-todo
## 0.0.176
### Patch Changes
- jazz-react@0.11.1
## 0.0.175
### Patch Changes
- Updated dependencies [6a96d8b]
- Updated dependencies [a35249a]
- Updated dependencies [b9d194a]
- Updated dependencies [a4713df]
- Updated dependencies [34cbdc3]
- Updated dependencies [f039e8f]
- Updated dependencies [e22de9f]
- jazz-tools@0.11.0
- jazz-react@0.11.0
## 0.0.174
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-todo",
"private": true,
"version": "0.0.174",
"version": "0.0.176",
"type": "module",
"scripts": {
"dev": "vite",
@@ -16,8 +16,8 @@
"@radix-ui/react-toast": "^1.1.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"jazz-react": "workspace:0.10.15",
"jazz-tools": "workspace:0.10.15",
"jazz-react": "workspace:0.11.1",
"jazz-tools": "workspace:0.11.0",
"lucide-react": "^0.274.0",
"qrcode": "^1.5.3",
"react": "^18.3.1",

View File

@@ -122,7 +122,7 @@ export default function App() {
function HomeScreen() {
const { me } = useAccount({
root: { projects: [{}] },
resolve: { root: { projects: { $each: true } } },
});
const navigate = useNavigate();

View File

@@ -21,7 +21,7 @@ export function NewProjectForm() {
// To create a new todo project, we first create a `Group`,
// which is a scope for defining access rights (reader/writer/admin)
// of its members, which will apply to all CoValues owned by that group.
const projectGroup = Group.create({ owner: me });
const projectGroup = Group.create();
// Then we create an empty todo project within that group
const project = TodoProject.create(

View File

@@ -2,7 +2,7 @@ import { useState } from "react";
import QRCode from "qrcode";
import { createInviteLink } from "jazz-react";
import { createInviteLink, useAccount } from "jazz-react";
import { CoValue } from "jazz-tools";
import { Button, useToast } from "../basicComponents";
@@ -15,9 +15,11 @@ export function InviteButton<T extends CoValue>({
}) {
const [existingInviteLink, setExistingInviteLink] = useState<string>();
const { toast } = useToast();
const { me } = useAccount();
return (
value?._owner?.myRole() === "admin" && (
value &&
me.canAdmin(value) && (
<Button
size="sm"
className="py-0"

View File

@@ -1,5 +1,25 @@
# version-history
## 0.0.54
### Patch Changes
- jazz-react@0.11.1
## 0.0.53
### Patch Changes
- Updated dependencies [6a96d8b]
- Updated dependencies [a35249a]
- Updated dependencies [b9d194a]
- Updated dependencies [a4713df]
- Updated dependencies [34cbdc3]
- Updated dependencies [f039e8f]
- Updated dependencies [e22de9f]
- jazz-tools@0.11.0
- jazz-react@0.11.0
## 0.0.52
### Patch Changes

View File

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

View File

@@ -15,7 +15,7 @@ function App() {
const issue = useCoState(Issue, issueID);
const createIssue = () => {
const group = Group.create({ owner: me });
const group = Group.create();
group.addMember("everyone", "writer");
const newIssue = Issue.create(

View File

@@ -1,10 +1,12 @@
import { createInviteLink } from "jazz-react";
import { createInviteLink, useAccount } from "jazz-react";
import { useCoState } from "jazz-react";
import { ID } from "jazz-tools";
import { IssueComponent } from "./Issue.tsx";
import { Issue, Project } from "./schema.ts";
export function ProjectComponent({ projectID }: { projectID: ID<Project> }) {
const project = useCoState(Project, projectID, { issues: [{}] });
const project = useCoState(Project, projectID, {
resolve: { issues: { $each: true } },
});
if (!project) return;
@@ -21,14 +23,15 @@ export function ProjectComponent({ projectID }: { projectID: ID<Project> }) {
estimate: 0,
status: "backlog",
},
{ owner: project._owner },
project._owner,
),
);
};
const { me } = useAccount();
return project ? (
<div>
<h1>{project.name}</h1>
{project._owner?.myRole() === "admin" && (
{me.canAdmin(project) && (
<>
<button onClick={() => invite("reader")}>Invite Guest</button>
<button onClick={() => invite("writer")}>Invite Member</button>

View File

@@ -0,0 +1,49 @@
import clsx from "clsx";
import type { ReactNode } from "react";
import { Icon } from "./Icon";
interface Props {
children: ReactNode;
variant?: "warning" | "info";
title: string;
className?: string;
}
export function Alert({
children,
variant = "warning",
title,
className,
}: Props) {
return (
<div
className={clsx(
"border-l-4 p-4 pl-6 dark:bg-red-200/5 overflow-hidden relative rounded",
{
"border-yellow-400 bg-yellow-50 dark:border-yellow-500 dark:bg-yellow-200/5":
variant === "warning",
"border-blue-400 bg-blue-50 dark:border-blue-500 dark:bg-blue-200/5":
variant === "info",
},
className,
)}
>
<span
className={clsx(
"text-sm font-bold flex items-center gap-1",
variant === "warning" && "text-yellow-700 dark:text-yellow-400",
variant === "info" && "text-blue-700 dark:text-blue-400",
)}
>
<Icon
name={variant}
size="7xl"
className="absolute -z-10 right-0 opacity-5 top-0 rotate-12 pointer-events-none"
/>
<Icon name={variant} size="xs" />
{title}
</span>
<span className={clsx("text-sm")}>{children}</span>
</div>
);
}

View File

@@ -1,4 +1,6 @@
import {
AlertCircleIcon,
AlertTriangleIcon,
ArrowDownIcon,
ArrowRightIcon,
BookTextIcon,
@@ -16,9 +18,10 @@ import {
GlobeIcon,
HashIcon,
ImageIcon,
InfoIcon,
LinkIcon,
LockKeyholeIcon,
LucideIcon,
type LucideIcon,
MailIcon,
MenuIcon,
MessageCircleQuestionIcon,
@@ -71,6 +74,8 @@ const icons = {
touchId: FingerprintIcon,
upload: UploadCloudIcon,
zip: FolderArchiveIcon,
warning: AlertTriangleIcon,
info: InfoIcon,
};
// copied from tailwind line height https://tailwindcss.com/docs/font-size

View File

@@ -133,6 +133,36 @@ teamGroup.extend(companyGroup);
```
</CodeGroup>
## Revoking a group extension
You can revoke a group extension by using the `revokeExtend` method:
<CodeGroup>
```typescript
const parentGroup = Group.create();
const childGroup = Group.create();
childGroup.extend(parentGroup);
// Revoke the extension
await childGroup.revokeExtend(parentGroup);
```
</CodeGroup>
## Getting all parent groups
You can get all the parent groups of a group by calling the `getParentGroups` method:
<CodeGroup>
```typescript
const childGroup = Group.create();
const parentGroup = Group.create();
childGroup.extend(parentGroup);
console.log(childGroup.getParentGroups()); // [parentGroup]
```
</CodeGroup>
## Example: Team Hierarchy
Here's a practical example of using group inheritance for team permissions:

View File

@@ -9,8 +9,6 @@ Every CoValue has an owner, which can be a `Group` or an `Account`.
You can use a `Group` to grant access to a CoValue to multiple users. These users can
have different roles, such as "writer", "reader" or "admin".
...more docs coming soon
## Creating a Group
Here's how you can create a `Group`.
@@ -19,7 +17,7 @@ Here's how you can create a `Group`.
```tsx
import { Group } from "jazz-tools";
const group = Group.create({ owner: me });
const group = Group.create();
```
</CodeGroup>
@@ -53,7 +51,6 @@ const bob = await Account.load(bobsID as ID<Account>, []);
group.addMember(bob, "writer");
```
</CodeGroup>
...more docs coming soon
## Getting the Group of an existing CoValue
@@ -77,7 +74,47 @@ import { Group } from "jazz-tools";
const group = existingCoValue._owner.castAs(Group);
group.addMember(bob, "writer");
group.myRole();
const role = group.getRoleOf(bob);
```
</CodeGroup>
...more docs coming soon
## Checking the permissions
You can check the permissions of an account on a CoValue by using the `canRead`, `canWrite` and `canAdmin` methods.
<CodeGroup>
```tsx
const value = await MyCoMap.load(valueID, {});
const me = Account.getMe();
if (me.canAdmin(value)) {
console.log("I can share value with others");
} else if (me.canWrite(value)) {
console.log("I can edit value");
} else if (me.canRead(value)) {
console.log("I can view value");
} else {
console.log("I cannot access value");
}
```
</CodeGroup>
To check the permissions of another account, you need to load it first:
<CodeGroup>
```tsx
const value = await MyCoMap.load(valueID, {});
const bob = await Account.load(accountID, []);
if (bob.canAdmin(value)) {
console.log("Bob can share value with others");
} else if (bob.canWrite(value)) {
console.log("Bob can edit value");
} else if (bob.canRead(value)) {
console.log("Bob can view value");
} else {
console.log("Bob cannot access value");
}
```
</CodeGroup>

View File

@@ -331,7 +331,7 @@ This works because CoValues
<CodeGroup>
```ts
const unsub = issue.subscribe([], (updatedIssue) => console.log(updatedIssue));
const unsub = issue.subscribe({ resolve: true }, (updatedIssue) => console.log(updatedIssue));
```
</CodeGroup>
@@ -339,7 +339,7 @@ This works because CoValues
<CodeGroup>
```ts
const unsub = Issue.subscribe(issueID, me, [], (updatedIssue) => {
const unsub = Issue.subscribe(issueID, me, { resolve: true }, (updatedIssue) => {
console.log(updatedIssue);
});
```
@@ -358,7 +358,7 @@ This works because CoValues
function useCoState<V extends CoValue>(Schema: CoValueClass<V>, id?: ID<V>): V | undefined {
const [value, setValue] = useState<V>();
useEffect(() => Schema.subscribe(id, [], setValue), [id]);
useEffect(() => Schema.subscribe(id, { resolve: true }, setValue), [id]);
return value;
}
@@ -454,7 +454,7 @@ function App() { // old
const issue = useCoState(Issue, issueID); // old
// old
const createIssue = () => { // old
const group = Group.create({ owner: me });
const group = Group.create();
group.addMember("everyone", "writer");
// old
const newIssue = Issue.create( // old
@@ -605,6 +605,8 @@ export function ProjectComponent({ projectID }: { projectID: ID<Project> }) {
<button onClick={createAndAddIssue}>Create Issue</button>
</div>
</div>
) : project === null ? (
<div>Project not found or access denied</div>
) : (
<div>Loading project...</div>
);
@@ -635,7 +637,7 @@ import { IssueComponent } from "./Issue.tsx"; // old
import { useCoState } from "jazz-react"; // old
// old
export function ProjectComponent({ projectID }: { projectID: ID<Project> }) {// old
const project = useCoState(Project, projectID, { issues: [{}] });
const project = useCoState(Project, projectID, { resolve: { issues: { $each: true } } });
const createAndAddIssue = () => {// old
project?.issues.push(Issue.create({
@@ -663,7 +665,7 @@ export function ProjectComponent({ projectID }: { projectID: ID<Project> }) {//
```
</CodeGroup>
The loading-depth spec `{ issues: [{}] }` means "in `Project`, load `issues` and load each item in `issues` shallowly". (Since an `Issue` doesn't have any further references, "shallowly" actually means all its properties will be available).
The loading-depth spec `{ resolve: { issues: { $each: true } } }` means "in `Project`, load `issues` and load each item in `issues` deeply". (Since an `Issue` doesn't have any further references, "deeply" actually means all its properties will be available).
- Now, we can get rid of a lot of conditional accesses because we know that once `project` is loaded, `project.issues` and each `Issue` in it will be loaded as well.
- This also results in only one rerender and visual update when everything is loaded, which is faster (especially for long lists) and gives you more control over the loading UX.
@@ -747,7 +749,9 @@ import { createInviteLink } from "jazz-react";
// old
export function ProjectComponent({ projectID }: { projectID: ID<Project> }) {// old
const project = useCoState(Project, projectID, { issues: [{}] }); // old
const project = useCoState(Project, projectID, { resolve: { issues: { $each: true } } }); // old
const { me } = useAccount();
const invite = (role: "reader" | "writer") => {
const link = createInviteLink(project, role, { valueHint: "project" });
@@ -766,7 +770,7 @@ export function ProjectComponent({ projectID }: { projectID: ID<Project> }) {//
return project ? (// old
<div>// old
<h1>{project.name}</h1>// old
{project._owner?.myRole() === "admin" && (
{me.canAdmin(project) && (
<>
<button onClick={() => invite("reader")}>Invite Guest</button>
<button onClick={() => invite("writer")}>Invite Member</button>

View File

@@ -146,8 +146,9 @@ Jazz waits for the migration to finish before passing the account to your app's
```ts
export class MyAppAccount extends Account {
root = co.ref(MyAppRoot);
profile = co.ref(MyAppProfile);
async migrate() {
async migrate(this: MyAppAccount, creationProps?: { name: string }) {
// we specifically need to check for undefined,
// because the root might simply be not loaded (`null`) yet
if (this.root === undefined) {
@@ -159,6 +160,17 @@ export class MyAppAccount extends Account {
myContacts: ListOfAccounts.create([], Group.create())
});
}
if (this.profile === undefined) {
const profileGroup = Group.create();
// Unlike the root, we want the profile to be publicly readable.
profileGroup.addMember("everyone", "reader");
this.profile = MyAppProfile.create({
name: creationProps?.name,
bookmarks: ListOfBookmarks.create([], profileGroup),
}, profileGroup);
}
}
}
```
@@ -179,7 +191,7 @@ Now let's say we want to add a `myBookmarks` field to the `root` schema:
export class MyAppAccount extends Account {
root = co.ref(MyAppRoot);// old
async migrate() { // old
async migrate(this: MyAppAccount) {
if (this.root === undefined) { // old
this.root = MyAppRoot.create({ // old
myChats: ListOfChats.create([], Group.create()), // old
@@ -188,12 +200,10 @@ export class MyAppAccount extends Account {
} // old
// We need to load the root field to check for the myContacts field
const result = await this.ensureLoaded({
const { root } = await this.ensureLoaded({
root: {},
});
const { root } = result;
// we specifically need to check for undefined,
// because myBookmarks might simply be not loaded (`null`) yet
if (root.myBookmarks === undefined) {

View File

@@ -0,0 +1,247 @@
import { ContentByFramework, CodeGroup } from '@/components/forMdx'
import { Alert } from "gcmp-design-system/src/app/components/atoms/Alert";
export const metadata = { title: "Upgrade to Jazz 0.11.0" };
# Jazz 0.11.0 is out!
Jazz 0.11.0 brings several improvements to member handling, roles, and permissions management. This guide will help you upgrade your application to the latest version.
## What's new?
Here is what's changed in this release:
- [New permissions check APIs](#new-permissions-check-apis): New methods like `canRead`, `canWrite`, `canAdmin`, and `getRoleOf` to simplify permission checks.
- [Group.revokeExtend](#grouprevokeextend): New method to revoke group extension permissions.
- [Group.getParentGroups](#accountgetparentgroups): New method to get all the parent groups of an account.
- [Account Profile & Migrations](#account-profile--migrations): Fixed issues with custom account profile migrations for a more consistent experience
- [Dropped support for Accounts owning Profiles](#dropped-support-for-accounts-owning-profiles): Profiles can now only be owned by Groups.
- [Group.members now includes inherited members](#member-inheritance-changes): Updated behavior for the `members` getter method to include inherited members and have a more intuitive type definition.
## New Features
### New permissions check APIs
New methods have been added to both `Account` and `Group` classes to improve permission handling:
#### Permission checks
The new `canRead`, `canWrite` and `canAdmin` methods on `Account` allow you to easily check if the account has specific permissions on a CoValue:
<CodeGroup>
{/* prettier-ignore */}
```typescript
const me = Account.getMe();
if (me.canAdmin(value)) {
console.log("I can share value with others");
} else if (me.canWrite(value)) {
console.log("I can edit value");
} else if (me.canRead(value)) {
console.log("I can view value");
} else {
console.log("I cannot access value");
}
```
</CodeGroup>
#### Getting the role of an account
The `getRoleOf` method has been added to query the role of specific entities:
<CodeGroup>
```typescript
const group = Group.create();
group.getRoleOf(me); // admin
group.getRoleOf(Eve); // undefined
group.addMember(Eve, "writer");
group.getRoleOf(Eve); // writer
```
</CodeGroup>
#### Group.revokeExtend
We added a new method to revoke the extend of a Group:
<CodeGroup>
```typescript
function addTrackToPlaylist(playlist: Playlist, track: MusicTrack) {
const trackGroup = track._owner.castAs(Group);
trackGroup.extend(playlist._owner, "reader"); // Grant read access to the track to the playlist accounts
playlist.tracks.push(track);
}
function removeTrackFromPlaylist(playlist: Playlist, track: MusicTrack) {
const trackGroup = track._owner.castAs(Group);
trackGroup.revokeExtend(playlist._owner); // Revoke access to the track to the playlist accounts
const index = playlist.tracks.findIndex(t => t.id === track.id);
if (index !== -1) {
playlist.tracks.splice(index, 1);
}
}
```
</CodeGroup>
### Group.getParentGroups
The `getParentGroups` method has been added to `Group` to get all the parent groups of a group.
<CodeGroup>
```ts
const childGroup = Group.create();
const parentGroup = Group.create();
childGroup.extend(parentGroup);
console.log(childGroup.getParentGroups()); // [parentGroup]
```
</CodeGroup>
## Breaking Changes
### Account Profile & Migrations
The previous way of making the `Profile` migration work was to assume that the profile was always already there:
<CodeGroup>
```ts
export class MyAppAccount extends Account {
profile = co.ref(MyAppProfile);
async migrate(this: MyAppAccount, creationProps: { name: string, lastName: string }) {
if (creationProps) {
const { profile } = await this.ensureLoaded({ profile: {} });
profile.name = creationProps.name;
profile.bookmarks = ListOfBookmarks.create([], profileGroup);
}
}
}
```
</CodeGroup>
This was kind-of tricky to picture, and having different migration strategies for different CoValues was confusing.
We changed the logic so the default profile is created only if you didn't provide one in your migration.
This way you can use the same pattern for both `root` and `profile` migrations:
<CodeGroup>
```ts
export class MyAppAccount extends Account {
profile = co.ref(MyAppProfile);
async migrate(this: MyAppAccount, creationProps?: { name: string }) {
if (this.profile === undefined) {
const profileGroup = Group.create();
profileGroup.addMember("everyone", "reader");
this.profile = MyAppProfile.create({
name: creationProps?.name,
bookmarks: ListOfBookmarks.create([], profileGroup),
}, profileGroup);
}
}
}
```
</CodeGroup>
<Alert variant="warning" title="Warning" className="mt-4">
If you provide a custom `Profile` in your `Account` schema and migration for a Worker account,
make sure to also add `everyone` as member with `reader` role to the owning group.
Failing to do so will prevent any account from sending messages to the Worker's Inbox.
</Alert>
### Dropped support for Accounts owning Profiles
Starting from `0.11.0` `Profile`s can only be owned by `Group`s.
<Alert variant="info" title="Note" className="mt-4">
Existing profiles owned by `Account`s will still work, but you will get incorrect types when accessing a `Profile`'s `_owner`.
</Alert>
### Member Inheritance Changes
The behavior of groups' `members` getter method has been updated to return both direct members and inherited ones from ancestor groups.
This might affect your application if you were relying on only direct members being returned.
<CodeGroup>
```ts
/**
* The following pseudocode only illustrates the inheritance logic,
* the actual implementation is different.
*/
const parentGroup = Group.create();
parentGroup.addMember(John, "admin");
const childGroup = Group.create();
childGroup.addMember(Eve, "admin");
childGroup.extend(parentGroup);
console.log(childGroup.members);
// Before 0.11.0
// [Eve]
// After 0.11.0
// [Eve, John]
```
</CodeGroup>
Additionally:
- now `Group.members` doesn't include the `everyone` member anymore
- the account type in `Group.members` is now the globally registered Account schema and we have removed the `co.members` way to define an AccountSchema for members
If you need to explicitly check if "everyone" is a member of a group, you can use the `getRoleOf` method instead:
<CodeGroup>
```ts
if (group.getRoleOf("everyone")) {
console.log("Everyone has access to the group");
}
```
</CodeGroup>
#### Migration Steps
1. Review your member querying logic to account for inherited members.
2. Update your permission checking code to utilize the new [`hasPermissions` and `getRoleOf`](#enhanced-permission-management) methods.
3. Consider implementing `"everyone"` role checks where appropriate.
### Removed auto-update of `profile.name` in `usePasskeyAuth`
The `usePasskeyAuth` hook now doesn't update the `profile.name` if the provided username is empty.
## Troubleshooting
> I'm getting the following error: `Error: Profile must be owned by a Group`
If you previously forced a migration of your `Account` schema to include a custom `Profile`,
and assigned its ownership to an `Account`, you need to recreate your profile code and assign it to a `Group` instead.
<CodeGroup>
```ts
export class MyAppAccount extends Account {
profile = co.ref(MyAppProfile);
override async migrate() {
// ...
const me = await this.ensureLoaded({
profile: {},
});
if ((me.profile._owner as Group | Account)._type === "Account") {
const profileGroup = Group.create();
profileGroup.addMember("everyone", "reader");
// recreate your profile here...
me.profile = Profile.create(
{
name: me.profile.name,
},
profileGroup,
);
}
}
}
```
</CodeGroup>

View File

@@ -0,0 +1,110 @@
import { CodeGroup } from '@/components/forMdx'
export const metadata = { title: "Jazz 0.11.0 - Deeply resolved data" };
# Jazz 0.11.0 - Deeply resolved data
<h2 className="not-prose text-sm text-stone-600 dark:text-stone-400 mb-5 pb-2 border-b">
15 March 2025
</h2>
<div>
Jazz 0.11.0 makes it easier and safer to load nested data. You can now specify exactly which nested data you want to load, and Jazz will check permissions and handle missing data gracefully. This helps catch errors earlier during development and makes your code more reliable.
<h3>What's new?</h3>
- New resolve API for a more type-safe deep loading
- Improved permission checks on deep loading
- Easier type safety with the Resolved type
</div>
<h3>New Resolve API</h3>
<div>
We're introducing a new resolve API for deep loading, more friendly to TypeScript, IDE autocompletion and LLMs.
To get started with the new resolve API, replace empty array/object parameters with structured resolve configs:
<CodeGroup>
{/* prettier-ignore */}
```tsx
// Before
const playlist = useCoState(Playlist, id, [{}]);
const { me } = useAccount({ root: { playlists: [] } });
// After
const playlist = useCoState(Playlist, id, {
resolve: { $each: true }
});
const { me } = useAccount({
resolve: { root: { playlists: true } }
});
```
</CodeGroup>
The new API works across all loading methods:
<CodeGroup>
{/* prettier-ignore */}
```ts
// Before
Playlist.load(id, account, {
tracks: [],
});
// After
Playlist.load(id, {
loadAs: account,
resolve: { tracks: true }
});
```
</CodeGroup>
</div>
<h3>Improved permission checks on deep loading</h3>
<div>
Now `useCoState` will return `null` when the current user lacks permissions to load the requested data.
Previously, `useCoState` would return `undefined` if the current user lacked permissions, making it hard to tell if the value is loading or if it's missing.
Now `undefined` means that the value is definitely loading, and `null` means that the value is temporarily missing.
We also have implemented a more granular permission checking, where if an optional coValue cannot be accessed, useCoState will return the data stripped of that coValue.
<CodeGroup>
{/* prettier-ignore */}
```tsx
class ListOfTracks extends CoList.Of(co.optional.ref(Track)) {}
// Before (ambiguous states)
const value = useCoState(ListOfTracks, id, [{}]);
if (value === undefined) return <div>Loading or access denied</div>;
if (value === null) return <div>Not found</div>;
// After
const value = useCoState(ListOfTracks, id, { resolve: { $each: true } });
if (value === undefined) return <div>Loading...</div>;
if (value === null) return <div>Not found or access denied</div>;
// If the current user lacks permissions to load a Track, the ListOfTracks will be returned without that Track.
return tracks.map(track => track && <MusicTrack track={track} />);
```
</CodeGroup>
</div>
<h3>Type Safety Improvements</h3>
<div>
The new `Resolved` type can be used to define what kind of deeply loaded data you expect in your parameters:
<CodeGroup>
{/* prettier-ignore */}
```ts
// Before
type PlaylistResolved = Resolved<Playlist, {
tracks: { $each: true }
}>;
// After
function TrackList({ playlist }: { playlist: PlaylistResolved }) {
// Safe access to resolved tracks
return playlist.tracks.map(track => /* ... */);
}
```
</CodeGroup>
</div>

View File

@@ -18,20 +18,6 @@ import {
PropDecl,
} from "./tags";
function isDeclarationReflection(child: any): child is DeclarationReflection {
return (
'kind' in child &&
'name' in child &&
'flags' in child &&
(child.kind === ReflectionKind.Class ||
child.kind === ReflectionKind.Interface ||
child.kind === ReflectionKind.TypeAlias ||
child.kind === ReflectionKind.Function ||
child.kind === ReflectionKind.Property ||
child.kind === ReflectionKind.Method)
);
}
export async function PackageDocs({
package: packageName,
}: {
@@ -50,7 +36,7 @@ export async function PackageDocs({
return (
<section key={category.title}>
<h2>{category.title}</h2>
{category.children.filter(isDeclarationReflection).map((child) => (
{category.children.map((child) => (
<RenderPackageChild
child={child}
key={child.id}
@@ -203,7 +189,7 @@ function RenderClassOrInterface({
),
)}
/>
{category.children.filter(isDeclarationReflection).map((prop) => (
{category.children.map((prop) => (
<RenderProp prop={prop} klass={classOrInterface} key={prop.id} />
))}
</div>

View File

@@ -1,4 +1,4 @@
import { Deserializer, FileRegistry, JSONOutput, ProjectReflection } from "typedoc";
import { Deserializer, JSONOutput, ProjectReflection } from "typedoc";
import JazzBrowserMediaImagesDocs from "../../typedoc/jazz-browser-media-images.json";
import JazzBrowserDocs from "../../typedoc/jazz-browser.json";
@@ -7,19 +7,17 @@ import JazzReactDocs from "../../typedoc/jazz-react.json";
import JazzToolsDocs from "../../typedoc/jazz-tools.json";
const docs = {
"jazz-tools": JazzToolsDocs,
"jazz-react": JazzReactDocs,
"jazz-browser": JazzBrowserDocs,
"jazz-browser-media-images": JazzBrowserMediaImagesDocs,
"jazz-nodejs": JazzNodejsDocs,
} as const;
"jazz-tools": JazzToolsDocs as JSONOutput.ProjectReflection,
"jazz-react": JazzReactDocs as JSONOutput.ProjectReflection,
"jazz-browser": JazzBrowserDocs as JSONOutput.ProjectReflection,
"jazz-browser-media-images":
JazzBrowserMediaImagesDocs as JSONOutput.ProjectReflection,
"jazz-nodejs": JazzNodejsDocs as JSONOutput.ProjectReflection,
};
export async function requestProject(
packageName: keyof typeof docs,
): Promise<ProjectReflection> {
const deserializer = new Deserializer({} as any);
return deserializer.reviveProject(packageName, docs[packageName] as unknown as JSONOutput.ProjectReflection, {
projectRoot: ".",
registry: new FileRegistry(),
});
return deserializer.reviveProject(docs[packageName], packageName);
}

View File

@@ -13,6 +13,10 @@ export function JazzFooter() {
{
title: "About",
links: [
{
href: "/status",
label: "Status",
},
{
href: "https://garden.co/team",
label: "Team",
@@ -23,11 +27,6 @@ export function JazzFooter() {
label: "Blog",
newTab: true,
},
{
href: "https://github.com/garden-co/jazz/releases",
label: "Releases",
newTab: true,
},
],
},
{

View File

@@ -38,13 +38,8 @@ export function JazzNav({ sections }: { sections?: NavSection[] }) {
newTab: true,
},
{
title: "Releases",
href: "https://github.com/garden-co/jazz/releases",
newTab: true,
},
{
title: "Status",
href: "/status",
title: "Status",
},
]}
socials={socials}

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