Compare commits

..

54 Commits

Author SHA1 Message Date
Guido D'Orsi
f66b7c1a5a Merge pull request #2326 from garden-co/changeset-release/main
Version Packages
2025-05-22 11:49:26 +02:00
github-actions[bot]
1b57cfc3f6 Version Packages 2025-05-22 09:45:36 +00:00
Guido D'Orsi
a805e27b0a Merge pull request #2322 from garden-co/fix-z-create
fix: fixes create types for CoList, CoRecord, CoFeed and CoFileStream
2025-05-22 11:42:24 +02:00
Guido D'Orsi
9d6d9fe7a5 fix: fixes create types for CoList, CoRecord, CoFeed and CoFileStream 2025-05-22 11:33:03 +02:00
Benjamin S. Leveritt
409758afd0 Adds tests for co.fileStream()'s this 2025-05-22 10:53:46 +02:00
Guido D'Orsi
e0bc532345 Merge pull request #2321 from garden-co/changeset-release/main
Version Packages
2025-05-22 10:14:43 +02:00
github-actions[bot]
87e02c0516 Version Packages 2025-05-22 08:14:27 +00:00
Guido D'Orsi
405f9be7b9 Merge pull request #2320 from garden-co/fix-z-create
fix: fix coMap.create with optional references
2025-05-22 10:10:01 +02:00
Guido D'Orsi
c82bf737bf Merge pull request #2319 from garden-co/fix-z-enum
feat: export the co.loaded alias
2025-05-22 10:08:49 +02:00
Guido D'Orsi
91cbb2f9d4 fix: fix coMap.create with optional types 2025-05-22 09:58:44 +02:00
Guido D'Orsi
cfbba59c6d test: reproduce the create type errors 2025-05-21 20:44:04 +02:00
Guido D'Orsi
b1209e2e09 docs: bring co.loaded in the upgrade guide 2025-05-21 19:38:06 +02:00
Guido D'Orsi
20b3d88135 feat: export the co.loaded alias 2025-05-21 19:25:47 +02:00
Guido D'Orsi
d9ad1f4de5 Merge pull request #2317 from garden-co/changeset-release/main
Version Packages
2025-05-21 18:16:49 +02:00
github-actions[bot]
c90153e0c9 Version Packages 2025-05-21 15:57:01 +00:00
Guido D'Orsi
a4241c0f4b Merge pull request #2315 from garden-co/fix-z-enum
fix: support z.enum
2025-05-21 17:53:00 +02:00
Benjamin S. Leveritt
e1ff7a65a8 Merge pull request #2316 from garden-co/fix-file-share-svelte
fix-file-share-svelte
2025-05-21 16:51:08 +01:00
Guido D'Orsi
011af55446 fix: support z.enum 2025-05-21 17:48:38 +02:00
Benjamin S. Leveritt
e9dbcf53c8 Fix formatting 2025-05-21 16:30:59 +01:00
Benjamin S. Leveritt
3da21b95ec Fix type issues 2025-05-21 16:30:49 +01:00
Benjamin S. Leveritt
a6d0dd07a1 Add type checking to build process
Can’t fathom why it’s not in there by default.
2025-05-21 15:51:25 +01:00
Benjamin S. Leveritt
ac474c4afb Merge pull request #2313 from garden-co/1888-type-check-every-react-example
Final mop-up for React docs type checks
2025-05-21 13:54:36 +01:00
Benjamin S. Leveritt
afe8e1f3b2 Merge pull request #2312 from garden-co/2311-fix-type-check-on-organization-pattern
Fixes org pattern doc
2025-05-21 13:45:40 +01:00
Benjamin S. Leveritt
b12b3808fa Merge pull request #2310 from garden-co/2212-remove-skiperrorchecking-workaround-in-jazz-react-typedoc-options
Remove typedoc check skip
2025-05-21 13:37:14 +01:00
Benjamin S. Leveritt
08ec8fe709 Fixes org pattern doc 2025-05-21 13:27:52 +01:00
Benjamin S. Leveritt
f70cae6bf6 Final mop-up for React docs type checks
Closes #1888
2025-05-21 13:27:12 +01:00
Benjamin S. Leveritt
59fe373863 Remove typedoc check skip 2025-05-21 13:23:35 +01:00
Guido D'Orsi
be58b4c1d8 Merge pull request #2308 from garden-co/changeset-release/main
Version Packages
2025-05-21 13:47:28 +02:00
github-actions[bot]
96c520ae4d Version Packages 2025-05-21 11:46:24 +00:00
Benjamin S. Leveritt
a9553b4945 Merge pull request #2307 from garden-co/2220-type-check-ImageDefinition
2220-type-check-ImageDefinition
2025-05-21 12:43:43 +01:00
Guido D'Orsi
fa516522e3 Merge pull request #2301 from garden-co/fix/svelte-zod
feat: make jazz-svelte compatible with the 0.14 changes
2025-05-21 13:16:18 +02:00
Benjamin S. Leveritt
59e4b5da54 Add typechecked vanillaJS docs 2025-05-21 11:51:24 +01:00
Benjamin S. Leveritt
2fa3f94b4a Add typechecked React docs 2025-05-21 11:51:07 +01:00
Guido D'Orsi
a2f8461e26 docs: add vanilla and expo 2025-05-21 12:47:50 +02:00
Guido D'Orsi
cabaf079be docs(0-14): svelte upgrade guide, fill todo sections 2025-05-21 12:40:08 +02:00
Trisha Lim
d8de4a7ada Merge pull request #2294 from garden-co/docs/groups-type-check
docs: type check Groups
2025-05-21 10:37:53 +01:00
Benjamin S. Leveritt
e510a544b7 Merge pull request #2300 from garden-co/2220-type-check-filestreams
Fix typechecking for FileStreams
2025-05-21 10:35:38 +01:00
Guido D'Orsi
b2ee30630d feat: make jazz-svelte compatible with the 0.14 changes 2025-05-21 11:11:35 +02:00
Benjamin S. Leveritt
ab0d4f364a Fix typechecking for FileStreams 2025-05-21 09:45:42 +01:00
Trisha Lim
6d9c6ae698 docs: add twoslash type checks to Group Inheritance 2025-05-20 16:01:41 +01:00
Trisha Lim
914af3deae docs: add twoslash type checks to Public Sharing 2025-05-20 15:52:56 +01:00
Trisha Lim
4ad8e9ae78 docs: add twoslash type checks to Groups 2025-05-20 15:29:08 +01:00
Trisha Lim
1e0b496555 docs: add twoslash type check to CoLists, CoFeeds (#2281)
* docs: add twoslash type check to CoLists

* revert to not using a list type

* twoslash type check for CoFeed
2025-05-20 15:04:24 +01:00
Guido D'Orsi
0088aa8b25 Merge pull request #2292 from garden-co/changeset-release/main
Version Packages
2025-05-20 15:48:40 +02:00
github-actions[bot]
35a66df1e4 Version Packages 2025-05-20 12:52:45 +00:00
Guido D'Orsi
3b2fa64a82 Merge pull request #2291 from garden-co/fix/test
fix: restore custom AccountSchema support in testing utils
2025-05-20 14:46:22 +02:00
Guido D'Orsi
3d1027f278 chore: changeset 2025-05-20 14:46:04 +02:00
Guido D'Orsi
cc78386163 Merge pull request #2290 from garden-co/feat/extand-parent
feat: make possible to extend a group without having access to it
2025-05-20 14:17:56 +02:00
Guido D'Orsi
c240eed6a4 fix: Fix custom AccountSchema support in testing utils 2025-05-20 13:19:59 +02:00
Guido D'Orsi
2a9b7f5d52 test: more tests on group extend 2025-05-20 12:44:31 +02:00
Guido D'Orsi
f2fbd29de5 feat: make possible to extend a group without having access to it 2025-05-20 11:54:11 +02:00
Benjamin S. Leveritt
c960176a2a Merge pull request #2285 from garden-co/update-subscriptions-and-loading-for-zod
Update subscriptions and loading docs for 0.14
2025-05-20 06:43:46 +01:00
Benjamin S. Leveritt
fd4bae4cc1 Replace Resolved with Loaded 2025-05-19 22:16:41 +01:00
Benjamin S. Leveritt
ae32b7c19b Updates code expamples 2025-05-19 22:07:55 +01:00
179 changed files with 5433 additions and 1809 deletions

View File

@@ -1,5 +1,56 @@
# betterauth
## 0.1.10
### Patch Changes
- Updated dependencies [9d6d9fe]
- Updated dependencies [9d6d9fe]
- jazz-tools@0.14.6
- jazz-betterauth-server-plugin@0.14.6
- jazz-inspector@0.14.6
- jazz-react@0.14.6
- jazz-react-auth-betterauth@0.14.6
- jazz-betterauth-client-plugin@0.14.6
## 0.1.9
### Patch Changes
- Updated dependencies [91cbb2f]
- Updated dependencies [20b3d88]
- jazz-tools@0.14.5
- jazz-betterauth-server-plugin@0.14.5
- jazz-inspector@0.14.5
- jazz-react@0.14.5
- jazz-react-auth-betterauth@0.14.5
- jazz-betterauth-client-plugin@0.14.5
## 0.1.8
### Patch Changes
- Updated dependencies [011af55]
- jazz-tools@0.14.4
- jazz-betterauth-server-plugin@0.14.4
- jazz-inspector@0.14.4
- jazz-react@0.14.4
- jazz-react-auth-betterauth@0.14.4
- jazz-betterauth-client-plugin@0.14.4
## 0.1.7
### Patch Changes
- Updated dependencies [3d1027f]
- Updated dependencies [c240eed]
- jazz-tools@0.14.2
- jazz-betterauth-server-plugin@0.14.2
- jazz-inspector@0.14.2
- jazz-react@0.14.2
- jazz-react-auth-betterauth@0.14.2
- jazz-betterauth-client-plugin@0.14.2
## 0.1.6
### Patch Changes

View File

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

View File

@@ -1,5 +1,44 @@
# chat-rn-expo-clerk
## 1.0.129
### Patch Changes
- Updated dependencies [9d6d9fe]
- Updated dependencies [9d6d9fe]
- jazz-tools@0.14.6
- jazz-expo@0.14.6
- jazz-react-native-media-images@0.14.6
## 1.0.128
### Patch Changes
- Updated dependencies [91cbb2f]
- Updated dependencies [20b3d88]
- jazz-tools@0.14.5
- jazz-expo@0.14.5
- jazz-react-native-media-images@0.14.5
## 1.0.127
### Patch Changes
- Updated dependencies [011af55]
- jazz-tools@0.14.4
- jazz-expo@0.14.4
- jazz-react-native-media-images@0.14.4
## 1.0.126
### Patch Changes
- Updated dependencies [3d1027f]
- Updated dependencies [c240eed]
- jazz-tools@0.14.2
- jazz-expo@0.14.2
- jazz-react-native-media-images@0.14.2
## 1.0.125
### Patch Changes

View File

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

View File

@@ -1,5 +1,40 @@
# chat-rn-expo
## 1.0.116
### Patch Changes
- Updated dependencies [9d6d9fe]
- Updated dependencies [9d6d9fe]
- jazz-tools@0.14.6
- jazz-expo@0.14.6
## 1.0.115
### Patch Changes
- Updated dependencies [91cbb2f]
- Updated dependencies [20b3d88]
- jazz-tools@0.14.5
- jazz-expo@0.14.5
## 1.0.114
### Patch Changes
- Updated dependencies [011af55]
- jazz-tools@0.14.4
- jazz-expo@0.14.4
## 1.0.113
### Patch Changes
- Updated dependencies [3d1027f]
- Updated dependencies [c240eed]
- jazz-tools@0.14.2
- jazz-expo@0.14.2
## 1.0.112
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "chat-rn-expo",
"version": "1.0.112",
"version": "1.0.116",
"main": "index.js",
"scripts": {
"build": "tsc --noEmit && expo export -p ios",

View File

@@ -1,5 +1,40 @@
# chat-rn
## 1.0.124
### Patch Changes
- Updated dependencies [9d6d9fe]
- Updated dependencies [9d6d9fe]
- jazz-tools@0.14.6
- jazz-react-native@0.14.6
## 1.0.123
### Patch Changes
- Updated dependencies [91cbb2f]
- Updated dependencies [20b3d88]
- jazz-tools@0.14.5
- jazz-react-native@0.14.5
## 1.0.122
### Patch Changes
- Updated dependencies [011af55]
- jazz-tools@0.14.4
- jazz-react-native@0.14.4
## 1.0.121
### Patch Changes
- Updated dependencies [3d1027f]
- Updated dependencies [c240eed]
- jazz-tools@0.14.2
- jazz-react-native@0.14.2
## 1.0.120
### Patch Changes

View File

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

View File

@@ -1,5 +1,44 @@
# chat-vue
## 0.0.107
### Patch Changes
- Updated dependencies [9d6d9fe]
- Updated dependencies [9d6d9fe]
- jazz-tools@0.14.6
- jazz-browser@0.14.6
- jazz-vue@0.14.6
## 0.0.106
### Patch Changes
- Updated dependencies [91cbb2f]
- Updated dependencies [20b3d88]
- jazz-tools@0.14.5
- jazz-browser@0.14.5
- jazz-vue@0.14.5
## 0.0.105
### Patch Changes
- Updated dependencies [011af55]
- jazz-tools@0.14.4
- jazz-browser@0.14.4
- jazz-vue@0.14.4
## 0.0.104
### Patch Changes
- Updated dependencies [3d1027f]
- Updated dependencies [c240eed]
- jazz-tools@0.14.2
- jazz-browser@0.14.2
- jazz-vue@0.14.2
## 0.0.103
### Patch Changes

View File

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

View File

@@ -1,5 +1,44 @@
# jazz-example-chat
## 0.0.205
### Patch Changes
- Updated dependencies [9d6d9fe]
- Updated dependencies [9d6d9fe]
- jazz-tools@0.14.6
- jazz-inspector@0.14.6
- jazz-react@0.14.6
## 0.0.204
### Patch Changes
- Updated dependencies [91cbb2f]
- Updated dependencies [20b3d88]
- jazz-tools@0.14.5
- jazz-inspector@0.14.5
- jazz-react@0.14.5
## 0.0.203
### Patch Changes
- Updated dependencies [011af55]
- jazz-tools@0.14.4
- jazz-inspector@0.14.4
- jazz-react@0.14.4
## 0.0.202
### Patch Changes
- Updated dependencies [3d1027f]
- Updated dependencies [c240eed]
- jazz-tools@0.14.2
- jazz-inspector@0.14.2
- jazz-react@0.14.2
## 0.0.201
### Patch Changes

View File

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

View File

@@ -1,5 +1,5 @@
import { createImage, useAccount, useCoState } from "jazz-react";
import { Account, Loaded, co } from "jazz-tools";
import { Account, co } from "jazz-tools";
import { useState } from "react";
import { Chat, Message } from "./schema.ts";
import {
@@ -91,7 +91,7 @@ export function ChatScreen(props: { chatID: string }) {
function ChatBubble(props: {
me: Account;
msg: Loaded<typeof Message, { text: true }>;
msg: co.loaded<typeof Message, { text: true }>;
}) {
if (!props.me.canRead(props.msg) || !props.msg.text?.toString()) {
return (

View File

@@ -4,5 +4,7 @@ export const Message = co.map({
text: co.plainText(),
image: z.optional(co.image()),
});
export type Message = co.loaded<typeof Message>;
export const Chat = co.list(Message);
export type Chat = co.loaded<typeof Chat>;

View File

@@ -1,6 +1,6 @@
import clsx from "clsx";
import { ProgressiveImg } from "jazz-react";
import { CoPlainText, ImageDefinition, Loaded } from "jazz-tools";
import { CoPlainText, ImageDefinition } from "jazz-tools";
import { ImageIcon } from "lucide-react";
import { useId, useRef } from "react";
@@ -81,7 +81,7 @@ export function BubbleText(props: {
);
}
export function BubbleImage(props: { image: Loaded<typeof ImageDefinition> }) {
export function BubbleImage(props: { image: ImageDefinition }) {
return (
<ProgressiveImg image={props.image}>
{({ src }) => (

View File

@@ -2,8 +2,8 @@
// This is NOT needed to make the chat work
import { Chat } from "@/schema.ts";
import { Loaded } from "jazz-tools";
export function onChatLoad(chat: Loaded<typeof Chat>) {
export function onChatLoad(chat: Chat) {
if (window.parent) {
chat.waitForSync().then(() => {
window.parent.postMessage(

View File

@@ -1,5 +1,44 @@
# minimal-auth-clerk
## 0.0.104
### Patch Changes
- Updated dependencies [9d6d9fe]
- Updated dependencies [9d6d9fe]
- jazz-tools@0.14.6
- jazz-react@0.14.6
- jazz-react-auth-clerk@0.14.6
## 0.0.103
### Patch Changes
- Updated dependencies [91cbb2f]
- Updated dependencies [20b3d88]
- jazz-tools@0.14.5
- jazz-react@0.14.5
- jazz-react-auth-clerk@0.14.5
## 0.0.102
### Patch Changes
- Updated dependencies [011af55]
- jazz-tools@0.14.4
- jazz-react@0.14.4
- jazz-react-auth-clerk@0.14.4
## 0.0.101
### Patch Changes
- Updated dependencies [3d1027f]
- Updated dependencies [c240eed]
- jazz-tools@0.14.2
- jazz-react@0.14.2
- jazz-react-auth-clerk@0.14.2
## 0.0.100
### Patch Changes

View File

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

View File

@@ -1,5 +1,51 @@
# file-share-svelte
## 0.0.89
### Patch Changes
- Updated dependencies [9d6d9fe]
- Updated dependencies [9d6d9fe]
- jazz-tools@0.14.6
- jazz-inspector-element@0.14.6
- jazz-svelte@0.14.6
## 0.0.88
### Patch Changes
- Updated dependencies [91cbb2f]
- Updated dependencies [20b3d88]
- jazz-tools@0.14.5
- jazz-inspector-element@0.14.5
- jazz-svelte@0.14.5
## 0.0.87
### Patch Changes
- Updated dependencies [011af55]
- jazz-tools@0.14.4
- jazz-inspector-element@0.14.4
- jazz-svelte@0.14.4
## 0.0.86
### Patch Changes
- Updated dependencies [b2ee306]
- jazz-svelte@0.14.3
## 0.0.85
### Patch Changes
- Updated dependencies [3d1027f]
- Updated dependencies [c240eed]
- jazz-tools@0.14.2
- jazz-inspector-element@0.14.2
- jazz-svelte@0.14.2
## 0.0.84
### Patch Changes

View File

@@ -1,11 +1,11 @@
{
"name": "file-share-svelte",
"version": "0.0.84",
"version": "0.0.89",
"private": true,
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build",
"build": "pnpm run check && vite build",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import { slide } from 'svelte/transition';
import { SharedFile } from '$lib/schema';
import { FileStream } from 'jazz-tools';
import { type SharedFile } from '$lib/schema';
import { FileStream, type Loaded } from 'jazz-tools';
import { File, FileDown, Trash2, Link2 } from 'lucide-svelte';
import { toast } from 'svelte-sonner';
import { downloadFileBlob, formatFileSize } from '$lib/utils';
@@ -11,9 +11,9 @@
loading = false,
onDelete
}: {
file: SharedFile;
file: Loaded<SharedFile>;
loading?: boolean;
onDelete: (file: SharedFile) => void;
onDelete: (file: Loaded<SharedFile>) => void;
} = $props();
const isAdmin = $derived(file._owner?.myRole() === 'admin');
@@ -55,7 +55,7 @@
class="flex items-center justify-between rounded-lg border border-gray-200 bg-white p-4"
transition:slide={{ duration: 200 }}
>
<div class="flex items-center space-x-4 flex-grow">
<div class="flex flex-grow items-center space-x-4">
<div class="flex h-10 w-10 items-center justify-center rounded-lg bg-blue-100 text-blue-600">
<File class="h-6 w-6" />
</div>
@@ -64,7 +64,12 @@
<label class="sr-only" for={`file-name-${file.id}`}>File name</label>
<!-- Jazz values are reactive, but they are not recognized as reactive by Svelte -->
<!-- svelte-ignore binding_property_non_reactive -->
<input class="font-medium text-gray-900 w-full py-1" type="text" bind:value={file.name} id={`file-name-${file.id}`} />
<input
class="w-full py-1 font-medium text-gray-900"
type="text"
bind:value={file.name}
id={`file-name-${file.id}`}
/>
{:else}
<h3 class="font-medium text-gray-900">{file.name}</h3>
{/if}

View File

@@ -1,46 +1,31 @@
import { Account, CoList, CoMap, FileStream, Profile, coField, Group } from 'jazz-tools';
import { Group, co, z } from 'jazz-tools';
export class SharedFile extends CoMap {
name = coField.string;
file = coField.ref(FileStream);
createdAt = coField.Date;
uploadedAt = coField.Date;
size = coField.number;
}
export const SharedFile = co.map({
name: z.string(),
file: co.fileStream(),
createdAt: z.date(),
uploadedAt: z.date(),
size: z.number(),
});
export class FileShareProfile extends Profile {
name = coField.string;
}
export type SharedFile = typeof SharedFile;
export class ListOfSharedFiles extends CoList.Of(coField.ref(SharedFile)) {}
export const FileShareAccountRoot = co.map({
type: z.literal('file-share-account'),
sharedFiles: co.list(SharedFile),
})
export class FileShareAccountRoot extends CoMap {
type = coField.literal('file-share-account');
sharedFiles = coField.ref(ListOfSharedFiles);
}
export const FileShareAccount = co.account({
profile: co.profile(),
root: FileShareAccountRoot,
}).withMigration((account) => {
if (account.root === undefined) {
const publicGroup = Group.create({ owner: account });
publicGroup.addMember('everyone', 'reader');
export class FileShareAccount extends Account {
profile = coField.ref(FileShareProfile);
root = coField.ref(FileShareAccountRoot);
/** The account migration is run on account creation and on every log-in.
* You can use it to set up the account root and any other initial CoValues you need.
*/
async migrate() {
await this._refs.root?.load();
// Initialize root if it doesn't exist
if (this.root === undefined || this.root?.type !== 'file-share-account') {
// Create a group that will own all shared files
const publicGroup = Group.create({ owner: this });
publicGroup.addMember('everyone', 'reader');
this.root = FileShareAccountRoot.create(
{
type: 'file-share-account',
sharedFiles: ListOfSharedFiles.create([], { owner: publicGroup }),
},
);
}
account.root = FileShareAccountRoot.create({
type: 'file-share-account',
sharedFiles: co.list(SharedFile).create([], publicGroup),
}, publicGroup);
}
}
});

View File

@@ -1,7 +1,7 @@
<script lang="ts" module>
declare module 'jazz-svelte' {
interface Register {
Account: FileShareAccount;
Account: typeof FileShareAccount;
}
}
</script>

View File

@@ -1,21 +1,23 @@
<script lang="ts">
import { AccountCoState } from 'jazz-svelte';
import { SharedFile } from '$lib/schema';
import { FileStream } from 'jazz-tools';
import { SharedFile, FileShareAccount } from '$lib/schema';
import { FileStream, type Loaded } from 'jazz-tools';
import FileItem from '$lib/components/FileItem.svelte';
import { CloudUpload } from 'lucide-svelte';
const me = new AccountCoState({
const me = new AccountCoState(FileShareAccount, {
resolve: {
profile: true,
root: {
sharedFiles: {
$each: true
},
}
}
}
});
$inspect(me);
const sharedFiles = $derived(me.current?.root.sharedFiles);
let fileInput: HTMLInputElement;
@@ -53,7 +55,7 @@
}
}
async function deleteFile(file: SharedFile) {
async function deleteFile(file: Loaded<typeof SharedFile>) {
if (!sharedFiles) return;
const index = sharedFiles.indexOf(file);
@@ -107,10 +109,7 @@
{#if sharedFiles.length}
{#each sharedFiles as file}
{#if file}
<FileItem
{file}
onDelete={deleteFile}
/>
<FileItem {file} onDelete={deleteFile} />
{/if}
{/each}
{:else}

View File

@@ -1,19 +1,18 @@
<script lang="ts">
import { page } from '$app/stores';
import { CoState } from 'jazz-svelte';
import { CoState } from 'jazz-svelte';
import { SharedFile } from '$lib/schema';
import { File, FileDown, Link2 } from 'lucide-svelte';
import type { ID } from 'jazz-tools';
import { FileStream } from 'jazz-tools';
import { toast } from 'svelte-sonner';
import { downloadFileBlob } from '$lib/utils';
const fileId = $page.params.fileId;
const file = $derived(new CoState(SharedFile, fileId as ID<SharedFile>));
const file = $derived(new CoState(SharedFile, fileId));
const isAdmin = $derived(file.current?._owner?.myRole() === 'admin');
const fileStreamId = $derived(file.current?._refs.file?.id);
const fileStreamId = $derived(file.current?._refs?.file?.id);
async function downloadFile() {
if (!fileStreamId || !file.current) {

View File

@@ -1,5 +1,44 @@
# jazz-tailwind-demo-auth-starter
## 0.0.44
### Patch Changes
- Updated dependencies [9d6d9fe]
- Updated dependencies [9d6d9fe]
- jazz-tools@0.14.6
- jazz-inspector@0.14.6
- jazz-react@0.14.6
## 0.0.43
### Patch Changes
- Updated dependencies [91cbb2f]
- Updated dependencies [20b3d88]
- jazz-tools@0.14.5
- jazz-inspector@0.14.5
- jazz-react@0.14.5
## 0.0.42
### Patch Changes
- Updated dependencies [011af55]
- jazz-tools@0.14.4
- jazz-inspector@0.14.4
- jazz-react@0.14.4
## 0.0.41
### Patch Changes
- Updated dependencies [3d1027f]
- Updated dependencies [c240eed]
- jazz-tools@0.14.2
- jazz-inspector@0.14.2
- jazz-react@0.14.2
## 0.0.40
### Patch Changes

View File

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

View File

@@ -1,6 +1,6 @@
"use client";
import { useAccount } from "jazz-react";
import { FileStream, co } from "jazz-tools";
import { co } from "jazz-tools";
import { useRef, useState } from "react";
import { JazzAccount } from "./schema";
@@ -125,7 +125,7 @@ export function FileWidget() {
try {
setIsUploading(true);
me.profile.file = await FileStream.createFromBlob(file, {
me.profile.file = await co.fileStream().createFromBlob(file, {
onProgress: (p) => setProgress(Math.round(p * 100)),
});
} catch (error) {

View File

@@ -1,5 +1,40 @@
# form
## 0.1.45
### Patch Changes
- Updated dependencies [9d6d9fe]
- Updated dependencies [9d6d9fe]
- jazz-tools@0.14.6
- jazz-react@0.14.6
## 0.1.44
### Patch Changes
- Updated dependencies [91cbb2f]
- Updated dependencies [20b3d88]
- jazz-tools@0.14.5
- jazz-react@0.14.5
## 0.1.43
### Patch Changes
- Updated dependencies [011af55]
- jazz-tools@0.14.4
- jazz-react@0.14.4
## 0.1.42
### Patch Changes
- Updated dependencies [3d1027f]
- Updated dependencies [c240eed]
- jazz-tools@0.14.2
- jazz-react@0.14.2
## 0.1.41
### Patch Changes

View File

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

View File

@@ -1,5 +1,40 @@
# image-upload
## 0.0.101
### Patch Changes
- Updated dependencies [9d6d9fe]
- Updated dependencies [9d6d9fe]
- jazz-tools@0.14.6
- jazz-react@0.14.6
## 0.0.100
### Patch Changes
- Updated dependencies [91cbb2f]
- Updated dependencies [20b3d88]
- jazz-tools@0.14.5
- jazz-react@0.14.5
## 0.0.99
### Patch Changes
- Updated dependencies [011af55]
- jazz-tools@0.14.4
- jazz-react@0.14.4
## 0.0.98
### Patch Changes
- Updated dependencies [3d1027f]
- Updated dependencies [c240eed]
- jazz-tools@0.14.2
- jazz-react@0.14.2
## 0.0.97
### Patch Changes

View File

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

View File

@@ -1,5 +1,29 @@
# jazz-example-inspector
## 0.0.154
### Patch Changes
- jazz-inspector@0.14.6
## 0.0.153
### Patch Changes
- jazz-inspector@0.14.5
## 0.0.152
### Patch Changes
- jazz-inspector@0.14.4
## 0.0.151
### Patch Changes
- jazz-inspector@0.14.2
## 0.0.150
### Patch Changes

View File

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

View File

@@ -10,7 +10,7 @@ import { Input } from "@/components/ui/input";
import { WORKER_ID } from "@/constants";
import { JoinGameRequest, WaitingRoom } from "@/schema";
import { createFileRoute, redirect } from "@tanstack/react-router";
import { Group, InboxSender, type Loaded } from "jazz-tools";
import { Group, InboxSender } from "jazz-tools";
import { ClipboardCopyIcon, Loader2Icon } from "lucide-react";
import { useEffect, useState } from "react";
@@ -29,10 +29,7 @@ export const Route = createFileRoute(
throw redirect({ to: "/" });
}
if (!waitingRoom?.account1?.isMe) {
const sender = await InboxSender.load<
Loaded<typeof JoinGameRequest>,
Loaded<typeof WaitingRoom>
>(
const sender = await InboxSender.load<JoinGameRequest, WaitingRoom>(
WORKER_ID,
me,
// { account1: {}, account2: {}, me, game: {} },

View File

@@ -4,6 +4,7 @@ export const Player = co.map({
account: co.account(),
playSelection: z.optional(z.literal(["rock", "paper", "scissors"])),
});
export type Player = co.loaded<typeof Player>;
export const Game = co.map({
player1: Player,
@@ -12,12 +13,14 @@ export const Game = co.map({
player1Score: z.number(),
player2Score: z.number(),
});
export type Game = co.loaded<typeof Game>;
export const WaitingRoom = co.map({
account1: co.account(),
account2: z.optional(co.account()),
game: z.optional(Game),
});
export type WaitingRoom = co.loaded<typeof WaitingRoom>;
export const PlayIntent = co.map({
type: z.literal("play"),
@@ -25,20 +28,24 @@ export const PlayIntent = co.map({
player: z.literal(["player1", "player2"]),
playSelection: z.literal(["rock", "paper", "scissors"]),
});
export type PlayIntent = co.loaded<typeof PlayIntent>;
export const NewGameIntent = co.map({
type: z.literal("newGame"),
gameId: z.string(),
});
export type NewGameIntent = co.loaded<typeof NewGameIntent>;
export const CreateGameRequest = co.map({
type: z.literal("createGame"),
});
export type CreateGameRequest = co.loaded<typeof CreateGameRequest>;
export const JoinGameRequest = co.map({
type: z.literal("joinGame"),
waitingRoom: WaitingRoom,
});
export type JoinGameRequest = co.loaded<typeof JoinGameRequest>;
export const InboxMessage = z.discriminatedUnion([
PlayIntent,
@@ -46,3 +53,4 @@ export const InboxMessage = z.discriminatedUnion([
CreateGameRequest,
JoinGameRequest,
]);
export type InboxMessage = co.loaded<typeof InboxMessage>;

View File

@@ -7,7 +7,7 @@ import {
WaitingRoom,
} from "@/schema";
import { startWorker } from "jazz-nodejs";
import { Account, Group, type Loaded, co } from "jazz-tools";
import { Account, Group, co } from "jazz-tools";
import { determineWinner } from "./lib/utils";
if (!process.env.VITE_JAZZ_WORKER_ACCOUNT || !process.env.JAZZ_WORKER_SECRET) {
@@ -124,10 +124,7 @@ function createPlayer({ account }: CreatePlayerParams) {
return player;
}
async function handleNewGameIntent(
_: string,
message: Loaded<typeof NewGameIntent>,
) {
async function handleNewGameIntent(_: string, message: NewGameIntent) {
const gameId = message.gameId;
const game = await Game.load(gameId, {
@@ -152,7 +149,7 @@ async function handleNewGameIntent(
}
}
async function handlePlayIntent(_: string, message: Loaded<typeof PlayIntent>) {
async function handlePlayIntent(_: string, message: PlayIntent) {
// determine current player, update game with outcome
const gameId = message.gameId;
if (!gameId) {

View File

@@ -1,5 +1,40 @@
# multi-cursors
## 0.0.97
### Patch Changes
- Updated dependencies [9d6d9fe]
- Updated dependencies [9d6d9fe]
- jazz-tools@0.14.6
- jazz-react@0.14.6
## 0.0.96
### Patch Changes
- Updated dependencies [91cbb2f]
- Updated dependencies [20b3d88]
- jazz-tools@0.14.5
- jazz-react@0.14.5
## 0.0.95
### Patch Changes
- Updated dependencies [011af55]
- jazz-tools@0.14.4
- jazz-react@0.14.4
## 0.0.94
### Patch Changes
- Updated dependencies [3d1027f]
- Updated dependencies [c240eed]
- jazz-tools@0.14.2
- jazz-react@0.14.2
## 0.0.93
### Patch Changes

View File

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

View File

@@ -1,5 +1,44 @@
# multiauth
## 0.0.45
### Patch Changes
- Updated dependencies [9d6d9fe]
- Updated dependencies [9d6d9fe]
- jazz-tools@0.14.6
- jazz-react@0.14.6
- jazz-react-auth-clerk@0.14.6
## 0.0.44
### Patch Changes
- Updated dependencies [91cbb2f]
- Updated dependencies [20b3d88]
- jazz-tools@0.14.5
- jazz-react@0.14.5
- jazz-react-auth-clerk@0.14.5
## 0.0.43
### Patch Changes
- Updated dependencies [011af55]
- jazz-tools@0.14.4
- jazz-react@0.14.4
- jazz-react-auth-clerk@0.14.4
## 0.0.42
### Patch Changes
- Updated dependencies [3d1027f]
- Updated dependencies [c240eed]
- jazz-tools@0.14.2
- jazz-react@0.14.2
- jazz-react-auth-clerk@0.14.2
## 0.0.41
### Patch Changes

View File

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

View File

@@ -1,5 +1,44 @@
# jazz-example-musicplayer
## 0.0.126
### Patch Changes
- Updated dependencies [9d6d9fe]
- Updated dependencies [9d6d9fe]
- jazz-tools@0.14.6
- jazz-inspector@0.14.6
- jazz-react@0.14.6
## 0.0.125
### Patch Changes
- Updated dependencies [91cbb2f]
- Updated dependencies [20b3d88]
- jazz-tools@0.14.5
- jazz-inspector@0.14.5
- jazz-react@0.14.5
## 0.0.124
### Patch Changes
- Updated dependencies [011af55]
- jazz-tools@0.14.4
- jazz-inspector@0.14.4
- jazz-react@0.14.4
## 0.0.123
### Patch Changes
- Updated dependencies [3d1027f]
- Updated dependencies [c240eed]
- jazz-tools@0.14.2
- jazz-inspector@0.14.2
- jazz-react@0.14.2
## 0.0.122
### Patch Changes

View File

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

View File

@@ -13,6 +13,7 @@ import { co, z } from "jazz-tools";
export const MusicTrackWaveform = co.map({
data: z.array(z.number()),
});
export type MusicTrackWaveform = co.loaded<typeof MusicTrackWaveform>;
export const MusicTrack = co.map({
/**
@@ -44,12 +45,13 @@ export const MusicTrack = co.map({
return z.optional(MusicTrack);
},
});
export type MusicTrack = co.loaded<typeof MusicTrack>;
export const Playlist = co.map({
title: z.string(),
tracks: co.list(MusicTrack), // CoList is the collaborative version of Array
});
export type Playlist = co.loaded<typeof Playlist>;
/** The account root is an app-specific per-user private `CoMap`
* where you can store top-level objects for that user */
export const MusicaAccountRoot = co.map({
@@ -69,7 +71,7 @@ export const MusicaAccountRoot = co.map({
exampleDataLoaded: z.optional(z.boolean()),
});
export type MusicaAccountRoot = co.loaded<typeof MusicaAccountRoot>;
export const MusicaAccount = co
.account({
/** the default user profile with a name */
@@ -97,5 +99,6 @@ export const MusicaAccount = co
});
}
});
export type MusicaAccount = co.loaded<typeof MusicaAccount>;
/** Walkthrough: Continue with ./2_main.tsx */

View File

@@ -1,5 +1,5 @@
import { getAudioFileData } from "@/lib/audio/getAudioFileData";
import { FileStream, Group, Loaded, co } from "jazz-tools";
import { FileStream, Group, co } from "jazz-tools";
import {
MusicTrack,
MusicTrackWaveform,
@@ -94,8 +94,8 @@ export async function createNewPlaylist() {
}
export async function addTrackToPlaylist(
playlist: Loaded<typeof Playlist>,
track: Loaded<typeof MusicTrack>,
playlist: Playlist,
track: MusicTrack,
) {
const alreadyAdded = playlist.tracks?.some(
(t) => t?.id === track.id || t?._refs.sourceTrack?.id === track.id,
@@ -118,8 +118,8 @@ export async function addTrackToPlaylist(
}
export async function removeTrackFromPlaylist(
playlist: Loaded<typeof Playlist>,
track: Loaded<typeof MusicTrack>,
playlist: Playlist,
track: MusicTrack,
) {
const notAdded = !playlist.tracks?.some(
(t) => t?.id === track.id || t?._refs.sourceTrack?.id === track.id,
@@ -142,21 +142,15 @@ export async function removeTrackFromPlaylist(
}
}
export async function updatePlaylistTitle(
playlist: Loaded<typeof Playlist>,
title: string,
) {
export async function updatePlaylistTitle(playlist: Playlist, title: string) {
playlist.title = title;
}
export async function updateMusicTrackTitle(
track: Loaded<typeof MusicTrack>,
title: string,
) {
export async function updateMusicTrackTitle(track: MusicTrack, title: string) {
track.title = title;
}
export async function updateActivePlaylist(playlist?: Loaded<typeof Playlist>) {
export async function updateActivePlaylist(playlist?: Playlist) {
const { root } = await MusicaAccount.getMe().ensureLoaded({
resolve: {
root: {
@@ -169,7 +163,7 @@ export async function updateActivePlaylist(playlist?: Loaded<typeof Playlist>) {
root.activePlaylist = playlist ?? root.rootPlaylist;
}
export async function updateActiveTrack(track: Loaded<typeof MusicTrack>) {
export async function updateActiveTrack(track: MusicTrack) {
const { root } = await MusicaAccount.getMe().ensureLoaded({
resolve: {
root: {},
@@ -180,7 +174,7 @@ export async function updateActiveTrack(track: Loaded<typeof MusicTrack>) {
}
export async function onAnonymousAccountDiscarded(
anonymousAccount: Loaded<typeof MusicaAccount>,
anonymousAccount: MusicaAccount,
) {
const { root: anonymousAccountRoot } = await anonymousAccount.ensureLoaded({
resolve: {

View File

@@ -2,7 +2,7 @@ import { MusicTrack, MusicaAccount, Playlist } from "@/1_schema";
import { usePlayMedia } from "@/lib/audio/usePlayMedia";
import { usePlayState } from "@/lib/audio/usePlayState";
import { useAccount } from "jazz-react";
import { FileStream, Loaded } from "jazz-tools";
import { FileStream } from "jazz-tools";
import { useRef, useState } from "react";
import { updateActivePlaylist, updateActiveTrack } from "./4_actions";
import { getNextTrack, getPrevTrack } from "./lib/getters";
@@ -22,7 +22,7 @@ export function useMediaPlayer() {
// Reference used to avoid out-of-order track loads
const lastLoadedTrackId = useRef<string | null>(null);
async function loadTrack(track: Loaded<typeof MusicTrack>) {
async function loadTrack(track: MusicTrack) {
lastLoadedTrackId.current = track.id;
setLoading(track.id);
@@ -64,10 +64,7 @@ export function useMediaPlayer() {
}
}
async function setActiveTrack(
track: Loaded<typeof MusicTrack>,
playlist?: Loaded<typeof Playlist>,
) {
async function setActiveTrack(track: MusicTrack, playlist?: Playlist) {
if (activeTrackId === track.id && lastLoadedTrackId.current !== null) {
playState.toggle();
return;

View File

@@ -1,5 +1,40 @@
# organization
## 0.0.97
### Patch Changes
- Updated dependencies [9d6d9fe]
- Updated dependencies [9d6d9fe]
- jazz-tools@0.14.6
- jazz-react@0.14.6
## 0.0.96
### Patch Changes
- Updated dependencies [91cbb2f]
- Updated dependencies [20b3d88]
- jazz-tools@0.14.5
- jazz-react@0.14.5
## 0.0.95
### Patch Changes
- Updated dependencies [011af55]
- jazz-tools@0.14.4
- jazz-react@0.14.4
## 0.0.94
### Patch Changes
- Updated dependencies [3d1027f]
- Updated dependencies [c240eed]
- jazz-tools@0.14.2
- jazz-react@0.14.2
## 0.0.93
### Patch Changes

View File

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

View File

@@ -1,5 +1,47 @@
# passkey-svelte
## 0.0.93
### Patch Changes
- Updated dependencies [9d6d9fe]
- Updated dependencies [9d6d9fe]
- jazz-tools@0.14.6
- jazz-svelte@0.14.6
## 0.0.92
### Patch Changes
- Updated dependencies [91cbb2f]
- Updated dependencies [20b3d88]
- jazz-tools@0.14.5
- jazz-svelte@0.14.5
## 0.0.91
### Patch Changes
- Updated dependencies [011af55]
- jazz-tools@0.14.4
- jazz-svelte@0.14.4
## 0.0.90
### Patch Changes
- Updated dependencies [b2ee306]
- jazz-svelte@0.14.3
## 0.0.89
### Patch Changes
- Updated dependencies [3d1027f]
- Updated dependencies [c240eed]
- jazz-tools@0.14.2
- jazz-svelte@0.14.2
## 0.0.88
### Patch Changes

View File

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

View File

@@ -1,5 +1,40 @@
# minimal-auth-passkey
## 0.0.102
### Patch Changes
- Updated dependencies [9d6d9fe]
- Updated dependencies [9d6d9fe]
- jazz-tools@0.14.6
- jazz-react@0.14.6
## 0.0.101
### Patch Changes
- Updated dependencies [91cbb2f]
- Updated dependencies [20b3d88]
- jazz-tools@0.14.5
- jazz-react@0.14.5
## 0.0.100
### Patch Changes
- Updated dependencies [011af55]
- jazz-tools@0.14.4
- jazz-react@0.14.4
## 0.0.99
### Patch Changes
- Updated dependencies [3d1027f]
- Updated dependencies [c240eed]
- jazz-tools@0.14.2
- jazz-react@0.14.2
## 0.0.98
### Patch Changes

View File

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

View File

@@ -1,5 +1,40 @@
# passphrase
## 0.0.99
### Patch Changes
- Updated dependencies [9d6d9fe]
- Updated dependencies [9d6d9fe]
- jazz-tools@0.14.6
- jazz-react@0.14.6
## 0.0.98
### Patch Changes
- Updated dependencies [91cbb2f]
- Updated dependencies [20b3d88]
- jazz-tools@0.14.5
- jazz-react@0.14.5
## 0.0.97
### Patch Changes
- Updated dependencies [011af55]
- jazz-tools@0.14.4
- jazz-react@0.14.4
## 0.0.96
### Patch Changes
- Updated dependencies [3d1027f]
- Updated dependencies [c240eed]
- jazz-tools@0.14.2
- jazz-react@0.14.2
## 0.0.95
### Patch Changes

View File

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

View File

@@ -1,5 +1,40 @@
# jazz-password-manager
## 0.0.123
### Patch Changes
- Updated dependencies [9d6d9fe]
- Updated dependencies [9d6d9fe]
- jazz-tools@0.14.6
- jazz-react@0.14.6
## 0.0.122
### Patch Changes
- Updated dependencies [91cbb2f]
- Updated dependencies [20b3d88]
- jazz-tools@0.14.5
- jazz-react@0.14.5
## 0.0.121
### Patch Changes
- Updated dependencies [011af55]
- jazz-tools@0.14.4
- jazz-react@0.14.4
## 0.0.120
### Patch Changes
- Updated dependencies [3d1027f]
- Updated dependencies [c240eed]
- jazz-tools@0.14.2
- jazz-react@0.14.2
## 0.0.119
### Patch Changes

View File

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

View File

@@ -1,5 +1,40 @@
# jazz-example-pets
## 0.0.221
### Patch Changes
- Updated dependencies [9d6d9fe]
- Updated dependencies [9d6d9fe]
- jazz-tools@0.14.6
- jazz-react@0.14.6
## 0.0.220
### Patch Changes
- Updated dependencies [91cbb2f]
- Updated dependencies [20b3d88]
- jazz-tools@0.14.5
- jazz-react@0.14.5
## 0.0.219
### Patch Changes
- Updated dependencies [011af55]
- jazz-tools@0.14.4
- jazz-react@0.14.4
## 0.0.218
### Patch Changes
- Updated dependencies [3d1027f]
- Updated dependencies [c240eed]
- jazz-tools@0.14.2
- jazz-react@0.14.2
## 0.0.217
### Patch Changes

View File

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

View File

@@ -1,5 +1,40 @@
# reactions
## 0.0.101
### Patch Changes
- Updated dependencies [9d6d9fe]
- Updated dependencies [9d6d9fe]
- jazz-tools@0.14.6
- jazz-react@0.14.6
## 0.0.100
### Patch Changes
- Updated dependencies [91cbb2f]
- Updated dependencies [20b3d88]
- jazz-tools@0.14.5
- jazz-react@0.14.5
## 0.0.99
### Patch Changes
- Updated dependencies [011af55]
- jazz-tools@0.14.4
- jazz-react@0.14.4
## 0.0.98
### Patch Changes
- Updated dependencies [3d1027f]
- Updated dependencies [c240eed]
- jazz-tools@0.14.2
- jazz-react@0.14.2
## 0.0.97
### Patch Changes

View File

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

View File

@@ -1,5 +1,44 @@
# richtext-tiptap
## 0.1.14
### Patch Changes
- Updated dependencies [9d6d9fe]
- Updated dependencies [9d6d9fe]
- jazz-tools@0.14.6
- jazz-react@0.14.6
- jazz-richtext-tiptap@0.1.14
## 0.1.13
### Patch Changes
- Updated dependencies [91cbb2f]
- Updated dependencies [20b3d88]
- jazz-tools@0.14.5
- jazz-react@0.14.5
- jazz-richtext-tiptap@0.1.13
## 0.1.12
### Patch Changes
- Updated dependencies [011af55]
- jazz-tools@0.14.4
- jazz-react@0.14.4
- jazz-richtext-tiptap@0.1.12
## 0.1.11
### Patch Changes
- Updated dependencies [3d1027f]
- Updated dependencies [c240eed]
- jazz-tools@0.14.2
- jazz-react@0.14.2
- jazz-richtext-tiptap@0.1.11
## 0.1.10
### Patch Changes

View File

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

View File

@@ -1,5 +1,44 @@
# richtext
## 0.0.91
### Patch Changes
- Updated dependencies [9d6d9fe]
- Updated dependencies [9d6d9fe]
- jazz-tools@0.14.6
- jazz-react@0.14.6
- jazz-richtext-prosemirror@0.1.25
## 0.0.90
### Patch Changes
- Updated dependencies [91cbb2f]
- Updated dependencies [20b3d88]
- jazz-tools@0.14.5
- jazz-react@0.14.5
- jazz-richtext-prosemirror@0.1.24
## 0.0.89
### Patch Changes
- Updated dependencies [011af55]
- jazz-tools@0.14.4
- jazz-react@0.14.4
- jazz-richtext-prosemirror@0.1.23
## 0.0.88
### Patch Changes
- Updated dependencies [3d1027f]
- Updated dependencies [c240eed]
- jazz-tools@0.14.2
- jazz-react@0.14.2
- jazz-richtext-prosemirror@0.1.22
## 0.0.87
### Patch Changes

View File

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

View File

@@ -1,5 +1,44 @@
# todo-vue
## 0.0.105
### Patch Changes
- Updated dependencies [9d6d9fe]
- Updated dependencies [9d6d9fe]
- jazz-tools@0.14.6
- jazz-browser@0.14.6
- jazz-vue@0.14.6
## 0.0.104
### Patch Changes
- Updated dependencies [91cbb2f]
- Updated dependencies [20b3d88]
- jazz-tools@0.14.5
- jazz-browser@0.14.5
- jazz-vue@0.14.5
## 0.0.103
### Patch Changes
- Updated dependencies [011af55]
- jazz-tools@0.14.4
- jazz-browser@0.14.4
- jazz-vue@0.14.4
## 0.0.102
### Patch Changes
- Updated dependencies [3d1027f]
- Updated dependencies [c240eed]
- jazz-tools@0.14.2
- jazz-browser@0.14.2
- jazz-vue@0.14.2
## 0.0.101
### Patch Changes

View File

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

View File

@@ -1,5 +1,40 @@
# jazz-example-todo
## 0.0.220
### Patch Changes
- Updated dependencies [9d6d9fe]
- Updated dependencies [9d6d9fe]
- jazz-tools@0.14.6
- jazz-react@0.14.6
## 0.0.219
### Patch Changes
- Updated dependencies [91cbb2f]
- Updated dependencies [20b3d88]
- jazz-tools@0.14.5
- jazz-react@0.14.5
## 0.0.218
### Patch Changes
- Updated dependencies [011af55]
- jazz-tools@0.14.4
- jazz-react@0.14.4
## 0.0.217
### Patch Changes
- Updated dependencies [3d1027f]
- Updated dependencies [c240eed]
- jazz-tools@0.14.2
- jazz-react@0.14.2
## 0.0.216
### Patch Changes

View File

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

View File

@@ -1,5 +1,44 @@
# version-history
## 0.0.99
### Patch Changes
- Updated dependencies [9d6d9fe]
- Updated dependencies [9d6d9fe]
- jazz-tools@0.14.6
- jazz-inspector@0.14.6
- jazz-react@0.14.6
## 0.0.98
### Patch Changes
- Updated dependencies [91cbb2f]
- Updated dependencies [20b3d88]
- jazz-tools@0.14.5
- jazz-inspector@0.14.5
- jazz-react@0.14.5
## 0.0.97
### Patch Changes
- Updated dependencies [011af55]
- jazz-tools@0.14.4
- jazz-inspector@0.14.4
- jazz-react@0.14.4
## 0.0.96
### Patch Changes
- Updated dependencies [3d1027f]
- Updated dependencies [c240eed]
- jazz-tools@0.14.2
- jazz-inspector@0.14.2
- jazz-react@0.14.2
## 0.0.95
### Patch Changes

View File

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

View File

@@ -0,0 +1,79 @@
# Jazz Documentation: Actionable Task List
## Immediate Priority (Next 1-2 Weeks)
1. **Complete Link Audit**
- [x] Verify all links in Building with Jazz section
- [ ] Replace generic links with section-specific links
- [ ] Add backlinks from reference docs to concept docs
2. **Standardize Documentation Structure**
- [x] Create consistent format for "Further Reading" sections in all docs
- [x] Standardize intro paragraphs to explain document purpose
- [x] Ensure all Next Steps sections correctly link to next document
3. **Resolve Authentication Content Duplication**
- [x] Decide approach (remove conceptual sections, cross-reference, or reorganize)
- [x] Implement chosen approach
- [x] Add appropriate links between documents
## High Priority (Next 3-4 Weeks)
4. **Create New Technical Reference Docs**
- [ ] Sync Server Configuration Guide
- [ ] Group Patterns Cookbook
- [ ] Data Loading Patterns Guide
5. **Develop Documentation Style Guide**
- [ ] Define standards for explanation vs reference content
- [ ] Create terminology guide for consistent language
- [ ] Establish code example formatting standards
6. **Technical Validation**
- [ ] Review all code examples for correctness
- [ ] Verify technical accuracy of explanations
- [ ] Test code examples in real applications
## Medium Priority (Next 1-2 Months)
7. **Enhance Visual Elements**
- [ ] Create component relationship diagrams
- [ ] Add flow charts for authentication and data processes
- [ ] Design visual navigation aids
8. **Improve Discoverability**
- [ ] Review information architecture
- [ ] Optimize section organization
- [ ] Add cross-linking between related concepts
9. **Create Quick Reference Materials**
- [ ] Develop downloadable cheat sheets
- [ ] Create concept summaries
- [ ] Build a glossary of terms
## Future Enhancements (2+ Months)
10. **Implement Analytics and Feedback**
- [ ] Set up documentation usage tracking
- [ ] Add feedback collection mechanism
- [ ] Create process for incorporating feedback
11. **Develop Interactive Elements**
- [ ] Create interactive code examples
- [ ] Build mini-tutorials within documentation
- [ ] Add "try it yourself" sections
12. **Add Real-World Context**
- [ ] Develop case studies
- [ ] Include real application examples
- [ ] Add decision guides for architecture choices
Each task should be assigned an owner and delivery date when work begins.

View File

@@ -0,0 +1,136 @@
# Technical Content for Reference Documentation
This document tracks content that was removed from the explanation-focused "Building with Jazz" section but should be preserved for technical reference documentation.
## Schemas
### Removed Content
- Detailed information about specific CoValue types
- In-depth explanation of field types and references
- Code examples showing how to create and manipulate schema instances
- Information about SchemaUnions and their usage
- Examples of computed fields and methods
- Specific details about optional fields and refs
### Source Location
Original content from: `/Users/benjamin/projects/gcmp/jazz/homepage/homepage/content/docs/schemas/covalues.mdx`
## Providers
### Removed Content
- Detailed provider configuration options:
```tsx
// Configure how your app connects to the Jazz network
const syncConfig = {
// Connect to Jazz Cloud (or your own sync server)
peer: "wss://cloud.jazz.tools/?key=your-api-key",
// When to sync: "always" (default), "never", or "signedUp"
when: "always"
}
```
- Account Schema specific configuration details
- Section on provider options including:
- Guest mode configuration
- Default profile name setting
- Logout handling
- Anonymous account data handling
- References to authentication states and how they affect synchronization
### Source Location
Original content from: `/Users/benjamin/projects/gcmp/jazz/homepage/homepage/content/docs/project-setup/providers/react.mdx`
## Authentication
### Removed Content
- Detailed code examples showing implementation of authentication state detection
- Specific code for migrating data from anonymous to authenticated accounts
- Configuration code examples for providers related to authentication
- Implementation details for controlling sync in different authentication states
- Code examples for guest mode configuration
### Source Location
Original content from: `/Users/benjamin/projects/gcmp/jazz/homepage/homepage/content/docs/authentication/overview.mdx` and `/Users/benjamin/projects/gcmp/jazz/homepage/homepage/content/docs/authentication/authentication-states.mdx`
Most of the conceptual content about authentication states was borrowed from authentication-states.mdx. Options for handling this duplication:
1. **Remove the conceptual sections**: Remove overlapping conceptual content from authentication-states.mdx, leaving only implementation details and code examples. Add a prominent link at the top directing users to the explanation document for conceptual understanding.
2. **Keep both but cross-reference**: Keep the original content but add clear cross-references between the documents, with explanation document for concepts and the original for implementation details.
3. **Complete reorganization**: Completely reorganize authentication-states.mdx to focus solely on implementation, moving all conceptual content to the explanation document.
For now, we're maintaining both documents but should decide on an approach when finalizing the documentation structure.
## Groups and Ownership
### Removed Content
- Detailed implementation code examples for adding members to groups
- Code examples for public sharing and invite links
- Common Group Patterns section with examples for:
- Organization Structure
- Project Collaboration
- Public Community
Note: The Common Group Patterns section contained valuable examples showing real-world usage patterns. This content should be preserved and potentially added to a patterns guide, cookbook, or examples section in the reference documentation.
### Source Location
Original content from: `/Users/benjamin/projects/gcmp/jazz/homepage/homepage/content/docs/groups/intro.mdx`, `/Users/benjamin/projects/gcmp/jazz/homepage/homepage/content/docs/groups/sharing.mdx`, and `/Users/benjamin/projects/gcmp/jazz/homepage/homepage/content/docs/groups/inheritance.mdx`
## Sync and Storage
### Removed Content
- Detailed instructions for using Jazz Cloud with API key examples
- Command line instructions for running a self-hosted sync server
- Command line options and parameters for configuring a sync server
- References to the source code and GitHub repositories
- Configuring Sync in Your Application section with code examples
- Offline-First Approach section explaining offline capabilities
- Sync and Authentication section explaining how sync relates to authentication states
### Source Location
Original content from: `/Users/benjamin/projects/gcmp/jazz/homepage/homepage/content/docs/sync-and-storage.mdx`
### Technical Documentation Needed
We should create a detailed technical reference document for sync server configuration that covers:
- Complete configuration options for self-hosted sync servers
- Performance tuning parameters
- Security considerations
- Deployment scenarios and best practices
- Monitoring and maintenance
This would be valuable for users who need to self-host their sync server with specific configuration requirements.
## Server Workers
### Removed Content
- Code example for generating Server Worker credentials
- Instructions for storing credentials as environment variables
- Code example for starting a Server Worker
- Implementation details for loading/subscribing to CoValues
- Specific implementation patterns with code examples
### Source Location
Original content from: `/Users/benjamin/projects/gcmp/jazz/homepage/homepage/content/docs/project-setup/server-side.mdx` and `/Users/benjamin/projects/gcmp/jazz/homepage/homepage/content/docs/building-with-jazz/server-workers.mdx`
## Next Steps
### Missing Topics to Address
- ~~**Data Loading and Subscriptions**~~: Rather than creating a separate document, we've incorporated this topic into existing documents:
- Added a substantial section about subscriptions and deep loading to the **Providers** document
- Added an explanation of accessing shared data to the **Groups** document
- Added links to the comprehensive [Subscription and Loading](/docs/react/using-covalues/subscription-and-loading) documentation in both documents
This approach ensures that data loading concepts are covered in relevant contexts rather than isolated in a separate document.
We've updated all Next Steps sections to reflect the current document flow:
1. Installation → Schemas
2. Schemas → Providers
3. Providers → Accounts
4. Accounts → Authentication
5. Authentication → Groups
6. Groups → Sync
7. Sync → Server Workers
8. Server Workers → (end of journey)
As we continue creating explanation-focused content for the "Building with Jazz" section, we should add to this document to ensure all technical reference material is preserved for the appropriate documentation.

View File

@@ -20,23 +20,21 @@ For this example, users within an `Organization` will be sharing `Project`s.
<CodeGroup>
```ts twoslash
import { co, CoMap, CoList } from "jazz-tools";
import { co, z } from "jazz-tools";
// ---cut---
// schema.ts
export class Project extends CoMap {
name = coField.string;
}
export const Project = co.map({
name: z.string(),
});
export class ListOfProjects extends CoList.Of(coField.ref(Project)) {}
export class Organization extends CoMap {
name = coField.string;
export const Organization = co.map({
name: z.string(),
// shared data between users of each organization
projects = coField.ref(ListOfProjects);
}
projects: co.list(Project),
});
export class ListOfOrganizations extends CoList.Of(coField.ref(Organization)) {}
export const ListOfOrganizations = co.list(Organization);
```
</CodeGroup>
@@ -48,54 +46,47 @@ Let's add the list of `Organization`s to the user's Account `root` so they can a
<CodeGroup>
```ts twoslash
import { co, CoMap, CoList, Account, Group } from "jazz-tools";
export class Project extends CoMap {
name = co.string;
}
import { Group, co, z } from "jazz-tools";
export const Project = co.map({
name: z.string(),
});
export class ListOfProjects extends CoList.Of(co.ref(Project)) {}
export const Organization = co.map({
name: z.string(),
export class Organization extends CoMap {
name = co.string;
// shared data between users of each organization
projects: co.list(Project),
});
// shared data between users of each organization
projects = co.ref(ListOfProjects);
}
export class ListOfOrganizations extends CoList.Of(co.ref(Organization)) {}
// ---cut---
// schema.ts
export class JazzAccountRoot extends CoMap {
organizations = coField.ref(ListOfOrganizations);
}
export const JazzAccountRoot = co.map({
organizations: co.list(Organization),
});
export class JazzAccount extends Account {
root = coField.ref(JazzAccountRoot);
async migrate() {
if (this.root === undefined) {
export const JazzAccount = co
.account({
root: JazzAccountRoot,
profile: co.profile({}),
})
.withMigration((account) => {
if (account.root === undefined) {
// Using a Group as an owner allows you to give access to other users
const organizationGroup = Group.create();
const organizations = ListOfOrganizations.create(
[
// Create the first Organization so users can start right away
Organization.create(
{
name: "My organization",
projects: ListOfProjects.create([], organizationGroup),
},
organizationGroup,
),
],
);
this.root = JazzAccountRoot.create(
{ organizations },
);
const organizations = co.list(Organization).create([
// Create the first Organization so users can start right away
Organization.create(
{
name: "My organization",
projects: co.list(Project).create([], organizationGroup),
},
organizationGroup,
),
]);
account.root = JazzAccountRoot.create({ organizations });
}
}
}
});
```
</CodeGroup>
@@ -120,44 +111,35 @@ When the user accepts the invite, add the `Organization` to the user's `organiza
<CodeGroup>
```tsx twoslash
import { useAcceptInvite, useAccount } from "jazz-react";
import { ID } from "jazz-tools";
import { Account, CoList, CoMap, Group, co } from "jazz-tools";
import * as React from "react";
import { useAcceptInvite, useAccount } from "jazz-react";
import { co, z } from "jazz-tools";
export class Project extends CoMap {
name = co.string;
}
const Project = z.object({
name: z.string(),
});
export class ListOfProjects extends CoList.Of(co.ref(Project)) {}
const Organization = co.map({
name: z.string(),
projects: co.list(Project),
});
export class Organization extends CoMap {
name = co.string;
projects = co.ref(ListOfProjects);
}
const JazzAccountRoot = co.map({
organizations: co.list(Organization),
});
export class ListOfOrganizations extends CoList.Of(co.ref(Organization)) {}
const JazzAccount = co.account({
root: JazzAccountRoot,
profile: co.profile({}),
});
export class JazzAccountRoot extends CoMap {
organizations = co.ref(ListOfOrganizations);
}
export class JazzAccount extends Account {
root = co.ref(JazzAccountRoot);
}
declare module "jazz-react" {
interface Register {
Account: JazzAccount;
}
}
// ---cut---
export function AcceptInvitePage() {
const { me } = useAccount({
const { me } = useAccount(JazzAccount, {
resolve: { root: { organizations: { $each: { $onError: null } } } },
});
const onAccept = (organizationId: ID<Organization>) => {
const onAccept = (organizationId: string) => {
if (me) {
Organization.load(organizationId).then((organization) => {
if (organization) {

View File

@@ -133,14 +133,14 @@ export const docNavigationItems = [
// done: 100,
// framework: "react-native",
// },
{
// upgrade guides
name: "0.9.2 - Local persistence on React Native Expo",
href: "/docs/upgrade/react-native-local-persistence",
done: 100,
framework: "react-native-expo",
excludeFromNavigation: true,
},
// {
// // upgrade guides
// name: "0.9.2 - Local persistence on React Native Expo",
// href: "/docs/upgrade/react-native-local-persistence",
// done: 100,
// framework: "react-native-expo",
// excludeFromNavigation: true,
// },
// {
// // upgrade guides
// name: "0.9.0 - Top level imports",

View File

@@ -15,7 +15,9 @@ When a group extends another group, members of the parent group will become auto
Here's how to extend a group:
<CodeGroup>
```typescript
```ts twoslash
import { Group } from "jazz-tools";
// ---cut---
const playlistGroup = Group.create();
const trackGroup = Group.create();
@@ -34,7 +36,11 @@ When you extend a group:
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`:
<CodeGroup>
```typescript
```ts twoslash
import { Group } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const bob = await createJazzTestAccount();
// ---cut---
const organizationGroup = Group.create();
organizationGroup.addMember(bob, "admin");
@@ -48,7 +54,12 @@ billingGroup.extend(organizationGroup, "reader");
The "override role" works in both directions:
<CodeGroup>
```typescript
```ts twoslash
import { Group } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const bob = await createJazzTestAccount();
const alice = await createJazzTestAccount();
// ---cut---
const parentGroup = Group.create();
parentGroup.addMember(bob, "reader");
parentGroup.addMember(alice, "admin");
@@ -65,7 +76,9 @@ childGroup.extend(parentGroup, "writer");
Groups can be extended multiple levels deep:
<CodeGroup>
```typescript
```ts twoslash
import { Group } from "jazz-tools";
// ---cut---
const grandParentGroup = Group.create();
const parentGroup = Group.create();
const childGroup = Group.create();
@@ -82,7 +95,12 @@ Members of the grandparent group will get access to all descendant groups based
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.
<CodeGroup>
```typescript
```ts twoslash
import { Group } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const bob = await createJazzTestAccount();
const parentGroup = Group.create();
// ---cut---
// Remove member from parent
await parentGroup.removeMember(bob);
@@ -94,7 +112,11 @@ await parentGroup.removeMember(bob);
If the account is already a member of the child group, it will get the more permissive role:
<CodeGroup>
```typescript
```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");
@@ -109,7 +131,11 @@ childGroup.extend(parentGroup);
When extending groups, only admin, writer and reader roles are inherited:
<CodeGroup>
```typescript
```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");
@@ -126,7 +152,13 @@ To extend a group:
2. The current account must be a member of the parent group
<CodeGroup>
```typescript
```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();
@@ -140,7 +172,9 @@ teamGroup.extend(companyGroup);
You can revoke a group extension by using the `revokeExtend` method:
<CodeGroup>
```typescript
```ts twoslash
import { Group } from "jazz-tools";
// ---cut---
const parentGroup = Group.create();
const childGroup = Group.create();
@@ -156,7 +190,9 @@ await childGroup.revokeExtend(parentGroup);
You can get all the parent groups of a group by calling the `getParentGroups` method:
<CodeGroup>
```typescript
```ts twoslash
import { Group } from "jazz-tools";
// ---cut---
const childGroup = Group.create();
const parentGroup = Group.create();
childGroup.extend(parentGroup);
@@ -170,7 +206,14 @@ console.log(childGroup.getParentGroups()); // [parentGroup]
Here's a practical example of using group inheritance for team permissions:
<CodeGroup>
```typescript
```ts twoslash
import { Group } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const CEO = await createJazzTestAccount();
const teamLead = await createJazzTestAccount();
const developer = await createJazzTestAccount();
const client = await createJazzTestAccount();
// ---cut---
// Company-wide group
const companyGroup = Group.create();
companyGroup.addMember(CEO, "admin");

View File

@@ -16,7 +16,7 @@ have different roles, such as "writer", "reader" or "admin".
Here's how you can create a `Group`.
<CodeGroup>
```tsx
```tsx twoslash
import { Group } from "jazz-tools";
const group = Group.create();
@@ -33,33 +33,54 @@ But if you already know their ID, you can add them directly (see below).
You can add group members by ID by using `Account.load` and `Group.addMember`.
<CodeGroup>
```tsx
```tsx twoslash
import { ID } from "jazz-tools";
const bobsID = "co_z123" as ID<Account>;
// ---cut---
import { Group, Account } from "jazz-tools";
const group = Group.create();
const bob = await Account.load(bobsID, []);
group.addMember(bob, "writer");
const bob = await Account.load(bobsID);
if (bob) {
group.addMember(bob, "writer");
}
```
</CodeGroup>
**Note:** if the account ID is of type `string`, because it comes from a URL parameter or something similar, you need to cast it to `ID<Account>` first:
<CodeGroup>
```tsx
```tsx twoslash
const bobsID = "co_z123" as ID<Account>;
const group = Group.create();
// ---cut---
import { Group, Account, ID } from "jazz-tools";
const bob = await Account.load(bobsID as ID<Account>, []);
group.addMember(bob, "writer");
const bob = await Account.load(bobsID as ID<Account>);
if (bob) {
group.addMember(bob, "writer");
}
```
</CodeGroup>
## Change a member's role
## Changing a member's role
To change a member's role, use the `addMember` method.
<CodeGroup>
```ts
```ts twoslash
import { Group, Account, ID } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const bob = await createJazzTestAccount();
const group = Group.create();
// ---cut---
group.addMember(bob, "reader");
```
</CodeGroup>
@@ -73,7 +94,12 @@ Bob just went from a writer to a reader.
To remove a member, use the `removeMember` method.
<CodeGroup>
```ts
```ts twoslash
import { Group, Account, ID } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const bob = await createJazzTestAccount();
const group = Group.create();
// ---cut---
group.removeMember(bob);
```
</CodeGroup>
@@ -89,7 +115,16 @@ Rules:
You can get the group of an existing CoValue by using `coValue._owner`.
<CodeGroup>
```tsx
```ts twoslash
import { createJazzTestAccount } from 'jazz-tools/testing';
import { co, z } from "jazz-tools";
const existingCoValue = await createJazzTestAccount();
const MyCoMap = co.map({
color: z.string(),
});
// ---cut---
const group = existingCoValue._owner;
const newValue = MyCoMap.create(
{ color: "red"},
@@ -101,13 +136,27 @@ const newValue = MyCoMap.create(
Because `._owner` can be an `Account` or a `Group`, in cases where you specifically need to use `Group` methods (such as for adding members or getting your own role), you can cast it to assert it to be a Group:
<CodeGroup>
```tsx
```ts twoslash
import { createJazzTestAccount } from 'jazz-tools/testing';
import { co, z } from "jazz-tools";
const bob = await createJazzTestAccount();
const MyCoMap = co.map({
color: z.string(),
});
const existingCoValue = MyCoMap.create(
{ color: "red"},
{ owner: bob }
);
// ---cut---
import { Group } from "jazz-tools";
const group = existingCoValue._owner.castAs(Group);
group.addMember(bob, "writer");
const role = group.getRoleOf(bob);
const role = group.getRoleOf(bob.id);
```
</CodeGroup>
@@ -116,8 +165,14 @@ const role = group.getRoleOf(bob);
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, {});
```ts twoslash
import { co, z, Account } from "jazz-tools";
const MyCoMap = co.map({
color: z.string(),
});
// ---cut---
const value = await MyCoMap.create({ color: "red"})
const me = Account.getMe();
if (me.canAdmin(value)) {
@@ -135,18 +190,29 @@ if (me.canAdmin(value)) {
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, []);
```ts twoslash
import { co, z, Account } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
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");
const MyCoMap = co.map({
color: z.string(),
});
const account = await createJazzTestAccount();
const accountID = account.id;
// ---cut---
const value = await MyCoMap.create({ color: "red"})
const bob = await Account.load(accountID);
if (bob) {
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

@@ -12,10 +12,12 @@ You can share CoValues publicly by setting the `owner` to a `Group`, and grantin
access to "everyone".
<CodeGroup>
```ts
const group = Group.create();
group.addMember("everyone", "writer"); // *highlight*
```
```ts twoslash
import { Group } from "jazz-tools";
// ---cut---
const group = Group.create();
group.addMember("everyone", "writer");
```
</CodeGroup>
This is done in the [chat example](https://github.com/garden-co/jazz/tree/main/examples/chat) where anyone can join the chat, and send messages.
@@ -29,52 +31,19 @@ You can grant users access to a CoValue by sending them an invite link.
This is used in the [pet example](https://github.com/garden-co/jazz/tree/main/examples/pets)
and the [todo example](https://github.com/garden-co/jazz/tree/main/examples/todo).
<ContentByFramework framework="react">
<ContentByFramework framework={["react", "react-native", "react-native-expo", "vue", "svelte"]}>
<CodeGroup>
```ts
```ts twoslash
import { co, z } from "jazz-tools";
const Organization = co.map({
name: z.string(),
});
const organization = Organization.create({ name: "Garden Computing" });
// ---cut---
import { createInviteLink } from "jazz-react";
createInviteLink(organization, "writer"); // or reader, or admin
```
</CodeGroup>
</ContentByFramework>
<ContentByFramework framework="react-native">
<CodeGroup>
```ts
import { createInviteLink } from "jazz-react-native";
createInviteLink(organization, "writer"); // or reader, or admin
```
</CodeGroup>
</ContentByFramework>
<ContentByFramework framework="react-native-expo">
<CodeGroup>
```ts
import { createInviteLink } from "jazz-expo";
createInviteLink(organization, "writer"); // or reader, or admin
```
</CodeGroup>
</ContentByFramework>
<ContentByFramework framework="vue">
<CodeGroup>
```ts
import { createInviteLink } from "jazz-vue";
createInviteLink(organization, "writer"); // or reader, or admin
```
</CodeGroup>
</ContentByFramework>
<ContentByFramework framework="svelte">
<CodeGroup>
```ts
import { createInviteLink } from "jazz-browser";
createInviteLink(organization, "writer"); // or reader, or admin
createInviteLink(organization, "writer"); // or reader, admin, writeOnly
```
</CodeGroup>
</ContentByFramework>
@@ -85,10 +54,23 @@ In your app, you need to handle this route, and let the user accept the invitati
as done [here](https://github.com/garden-co/jazz/tree/main/examples/pets/src/2_main.tsx).
<CodeGroup>
```ts
```ts twoslash
import { co, z } from "jazz-tools";
const Organization = co.map({
name: z.string(),
});
const organization = Organization.create({ name: "Garden Computing" });
const organizationID = organization.id;
// ---cut---
import { useAcceptInvite } from "jazz-react";
useAcceptInvite({
invitedObjectSchema: PetPost,
onAccept: (petPostID) => navigate("/pet/" + petPostID),
invitedObjectSchema: Organization,
onAccept: (organizationID) => {
console.log("Accepted invite!")
// navigate to the organization page
},
});
```
</CodeGroup>
@@ -104,14 +86,14 @@ Create the data models.
<CodeGroup>
```ts twoslash
import { CoMap, co, CoValue, Account, CoList } from "jazz-tools";
// ---cut-before---
class JoinRequest extends CoMap {
account = co.ref(Account);
status = co.literal("pending", "approved", "rejected");
}
import { co, z, Account } from "jazz-tools";
// ---cut---
const JoinRequest = co.map({
account: Account,
status: z.literal(["pending", "approved", "rejected"]),
});
class RequestsList extends CoList.Of(co.ref(JoinRequest)) {};
const RequestsList = co.list(JoinRequest);
```
</CodeGroup>
@@ -119,15 +101,15 @@ Set up the request system with appropriate access controls.
<CodeGroup>
```ts twoslash
import { Group, co, CoList, CoMap, Account } from "jazz-tools";
import { co, z, Account, Group, Loaded } from "jazz-tools";
import { createInviteLink } from "jazz-react";
export class JoinRequest extends CoMap {
account = co.ref(Account);
status = co.literal("pending", "approved", "rejected");
}
const JoinRequest = co.map({
account: Account,
status: z.literal(["pending", "approved", "rejected"]),
});
export class RequestsList extends CoList.Of(co.ref(JoinRequest)) {};
const RequestsList = co.list(JoinRequest);
// ---cut-before---
function createRequestsToJoin() {
@@ -138,7 +120,7 @@ function createRequestsToJoin() {
}
async function sendJoinRequest(
requestsList: RequestsList,
requestsList: Loaded<typeof RequestsList>,
account: Account,
) {
const request = JoinRequest.create(
@@ -160,23 +142,23 @@ Using the write-only access users can submit requests that only administrators c
<CodeGroup>
```ts twoslash
import { co, CoMap, CoList, ID, Account, Group, Resolved } from "jazz-tools";
import { co, z, ID, Account, Group, Loaded } from "jazz-tools";
class JoinRequest extends CoMap {
account = co.ref(Account);
status = co.string; // Can be "pending", "approved", "rejected"
}
const JoinRequest = co.map({
account: Account,
status: z.literal(["pending", "approved", "rejected"]),
});
export class RequestsList extends CoList.Of(co.ref(JoinRequest)) {};
const RequestsList = co.list(JoinRequest);
export class RequestsToJoin extends CoMap {
writeOnlyInvite = co.string;
requests = co.ref(RequestsList);
}
const RequestsToJoin = co.map({
writeOnlyInvite: z.string(),
requests: RequestsList,
});
// ---cut-before---
async function approveJoinRequest(
joinRequest: JoinRequest,
joinRequest: Loaded<typeof JoinRequest, { account: true }>,
targetGroup: Group,
) {
const account = await Account.load(joinRequest._refs.account.id);

View File

@@ -19,7 +19,6 @@ Server Workers typically have static credentials, consisting of a public Account
To generate new credentials for a Server Worker, you can run:
<CodeGroup>
{/* prettier-ignore */}
```sh
npx jazz-run account create --name "My Server Worker"
```
@@ -43,13 +42,15 @@ You can use `startWorker` from `jazz-nodejs` to start a Server Worker. Similarly
`startWorker` expects credentials in the `JAZZ_WORKER_ACCOUNT` and `JAZZ_WORKER_SECRET` environment variables by default (as printed by `npx account create ...`), but you can also pass them manually as `accountID` and `accountSecret` parameters if you get them from elsewhere.
<CodeGroup>
{/* prettier-ignore */}
```ts
```ts twoslash
import { Account } from "jazz-tools";
class MyWorkerAccount extends Account {}
// ---cut---
import { startWorker } from 'jazz-nodejs';
const { worker } = await startWorker({
AccountSchema: MyWorkerAccount,
syncServer: 'wss://cloud.jazz.tools/?key=you@example.com',
AccountSchema: MyWorkerAccount,
syncServer: 'wss://cloud.jazz.tools/?key=you@example.com',
});
```
</CodeGroup>

View File

@@ -87,6 +87,7 @@ Similar to Zod v4's new object syntax, recursive and mutually recursive types ar
### How to pass loaded CoValues
<ContentByFramework framework={["react", "react-native", "vue", "vanilla", "react-native-expo"]}>
Calls to `useCoState()` work just the same, but they return a slightly different type than before.
And while you can still read from the type just as before...
@@ -102,41 +103,14 @@ const Pet = co.map({
name: z.string(),
age: z.number(),
});
type Pet = co.loaded<typeof Pet>;
const Person = co.map({
name: z.string(),
age: z.number(),
pets: co.list(Pet),
});
function MyComponent({ id }: { id: string }) {
const person = useCoState(Person, id);
return person && <div>{person.name}</div>;
}
```
</CodeGroup>
...you now need to specify the type differently **when passing CoValues as a parameter** or
**whenever you need to refer to the type of a loaded CoValue instance:**
<CodeGroup>
```tsx twoslash
import React from "react";
// ---cut---
import { z, co, Loaded } from "jazz-tools"; // [!code ++]
import { useCoState } from "jazz-react";
const Pet = co.map({
name: z.string(),
age: z.number(),
});
const Person = co.map({
name: z.string(),
age: z.number(),
pets: co.list(Pet),
});
type Person = co.loaded<typeof Person>;
function MyComponent({ id }: { id: string }) {
const person = useCoState(Person, id);
@@ -144,31 +118,35 @@ function MyComponent({ id }: { id: string }) {
return person && <PersonName person={person} />;
}
function PersonName({ person }: { person: Loaded<typeof Person> }) { // [!code ++]
function PersonName({ person }: {
person: Person
}) {
return <div>{person.name}</div>;
}
```
</CodeGroup>
`Loaded` can also take a second argument to specify the loading depth of the expected CoValue, mirroring the `resolve` options for `useCoState`, `load`, `subscribe`, etc.
`co.loaded` can also take a second argument to specify the loading depth of the expected CoValue, mirroring the `resolve` options for `useCoState`, `load`, `subscribe`, etc.
<CodeGroup>
```tsx twoslash
import React from "react";
// ---cut---
import { z, co, Loaded } from "jazz-tools";
import { z, co } from "jazz-tools";
import { useCoState } from "jazz-react";
const Pet = co.map({
name: z.string(),
age: z.number(),
});
type Pet = co.loaded<typeof Pet>;
const Person = co.map({
name: z.string(),
age: z.number(),
pets: co.list(Pet),
});
type Person = co.loaded<typeof Person>;
function MyComponent({ id }: { id: string }) {
const personWithPets = useCoState(Person, id, {
@@ -179,29 +157,277 @@ function MyComponent({ id }: { id: string }) {
}
function PersonAndFirstPetName({ person }: {
person: Loaded<typeof Person, { pets: { $each: true } }> // [!code ++]
person: co.loaded<typeof Person, { pets: { $each: true } }> // [!code ++]
}) {
return <div>{person.name} & {person.pets[0].name}</div>;
}
```
</CodeGroup>
</ContentByFramework>
<ContentByFramework framework="svelte">
We've removed the `useCoState`, `useAccount` and `useAccountOrGuest` hooks.
You should now use the `CoState` and `AccountCoState` reactive classes instead. These provide greater stability and are significantly easier to work with.
Calls to `new CoState()` work just the same, but they return a slightly different type than before.
And while you can still read from the type just as before...
<CodeGroup>
```ts twoslash filename="schema.ts"
// @filename: schema.ts
import { z, co } from "jazz-tools";
const Pet = co.map({
name: z.string(),
age: z.number(),
});
type Pet = co.loaded<typeof Pet>;
const Person = co.map({
name: z.string(),
age: z.number(),
pets: co.list(Pet),
});
type Person = co.loaded<typeof Person>;
```
```svelte twoslash filename="app.svelte"
// @filename: app.svelte
<script lang="ts">
import { CoState } from "jazz-svelte";
import { Person } from "./schema";
const person = new CoState(Person, id);
</script>
<div>
{person.current?.name}
</div>
```
</CodeGroup>
`co.loaded` can also take a second argument to specify the loading depth of the expected CoValue, mirroring the `resolve` options for `CoState`, `load`, `subscribe`, etc.
<CodeGroup>
```svelte twoslash
<script lang="ts" module>
export type Props = {
person: co.loaded<typeof Person, { pets: { $each: true } }>; // [!code ++]
};
</script>
<script lang="ts">
import { Person } from './schema';
let props: Props = $props();
</script>
<div>
{props.person.name}
</div>
<ul>
{#each props.person.pets as pet}
<li>{pet.name}</li>
{/each}
</ul>
```
</CodeGroup>
</ContentByFramework>
### Removing AccountSchema registration
We have removed the Typescript AccountSchema registration.
It was causing some deal of confusion to new adopters so we have decided to replace the magic inference with a more explicit approach.
<ContentByFramework framework={["react", "react-native", "vue", "vanilla", "react-native-expo"]}>
When using `useAccount` you should now pass the `Account` schema directly:
<CodeGroup>
```tsx twoslash
// @filename: schema.ts
import { co, z } from "jazz-tools";
export const MyAccount = co.account({
profile: co.profile(),
root: co.map({})
});
// @filename: app.tsx
import React from "react";
// ---cut---
import { useAccount } from "jazz-react";
import { MyAccount } from "./schema";
function MyComponent() {
const { me } = useAccount(MyAccount, {
resolve: {
profile: true,
},
});
return <div>{me?.profile.name}</div>;
}
```
</CodeGroup>
</ContentByFramework>
<ContentByFramework framework="svelte">
When using `AccountCoState` you should now pass the `Account` schema directly:
<CodeGroup>
```svelte twoslash filename="app.svelte"
<script lang="ts">
import { AccountCoState } from "jazz-svelte";
import { MyAccount } from "./schema";
const account = new AccountCoState(MyAccount, {
resolve: {
profile: true,
},
});
</script>
<div>
{account.current?.profile.name}
</div>
```
</CodeGroup>
</ContentByFramework>
### Defining migrations
TODO
Now account schemas need to be defined with `co.account()` and migrations can be declared using `withMigration()`:
<CodeGroup>
```ts twoslash
import { co, z, Group } from "jazz-tools";
const Pet = co.map({
name: z.string(),
age: z.number(),
});
const MyAppRoot = co.map({
pets: co.list(Pet),
});
const MyAppProfile = co.profile({
name: z.string(),
age: z.number().optional(),
});
export const MyAppAccount = co.account({
root: MyAppRoot,
profile: MyAppProfile,
}).withMigration((account, creationProps?: { name: string }) => {
if (account.root === undefined) {
account.root = MyAppRoot.create({
pets: co.list(Pet).create([]),
});
}
if (account.profile === undefined) {
const profileGroup = Group.create();
profileGroup.addMember("everyone", "reader");
account.profile = MyAppProfile.create({
name: creationProps?.name ?? "New user",
}, profileGroup);
}
});
```
</CodeGroup>
### Defining Schema helper methods
TODO
### Removing AccountSchema registration
TODO
## Minor breaking changes
### `_refs` and `_edits` are now potentially null
TODO
The type of `_refs` and `_edits` is now nullable.
### `members` and `by` now return basic `Account`s
<CodeGroup>
```ts twoslash
import { z, co } from "jazz-tools";
// ---cut---
const Person = co.map({
name: z.string(),
age: z.number(),
});
const person = Person.create({ name: "John", age: 30 });
person._refs; // now nullable
person._edits; // now nullable
```
</CodeGroup>
### `members` and `by` now return basic `Account`
We have removed the Account schema registration, so now `members` and `by` methods now always return basic `Account`.
This means that you now need to rely on `useCoState` on them to load their using your account schema.
<CodeGroup>
```tsx twoslash
import React from "react";
import { co, z, Group } from "jazz-tools";
import { useCoState } from "jazz-react";
const Pet = co.map({
name: z.string(),
age: z.number(),
});
const MyAppRoot = co.map({
pets: co.list(Pet),
});
const MyAppProfile = co.profile({
name: z.string(),
age: z.number().optional(),
});
export const MyAppAccount = co.account({
root: MyAppRoot,
profile: MyAppProfile,
});
// ---cut---
function GroupMembers({ group }: { group: Group }) {
const members = group.members;
return (
<div>
{members.map((member) => (
<GroupMemberDetails
accountId={member.account.id}
key={member.account.id}
/>
))}
</div>
);
}
function GroupMemberDetails({ accountId }: { accountId: string }) {
const account = useCoState(MyAppAccount, accountId, {
resolve: {
profile: true,
root: {
pets: { $each: true },
},
},
});
return (
<div>
<div>{account?.profile.name}</div>
<ul>{account?.root.pets.map((pet) => <li>{pet.name}</li>)}</ul>
</div>
);
}
```
</CodeGroup>

View File

@@ -19,7 +19,9 @@ The following examples demonstrate a practical use of CoFeeds:
CoFeeds are defined by specifying the type of items they'll contain, similar to how you define CoLists:
<CodeGroup>
```ts
```ts twoslash
import { co, z } from "jazz-tools";
// ---cut---
// Define a schema for feed items
const Activity = co.map({
timestamp: z.date(),
@@ -73,24 +75,54 @@ Since CoFeeds are made of entries from users over multiple sessions, you can acc
To retrieve entries from a session:
<CodeGroup>
```ts
```ts twoslash
import { Group, co, z, ID, Account, SessionID } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const me = await createJazzTestAccount();
const Activity = co.map({
timestamp: z.date(),
action: z.literal(["watering", "planting", "harvesting", "maintenance"]),
notes: z.optional(z.string()),
});
const ActivityFeed = co.feed(Activity);
const activityFeed = ActivityFeed.create([]);
const sessionId = `${me.id}_session_z1` as SessionID;
// ---cut---
// Get the feed for a specific session
const sessionFeed = activityFeed.perSession[sessionId];
// Latest entry from a session
console.log(sessionFeed.value.action); // "watering"
console.log(sessionFeed?.value?.action); // "watering"
```
</CodeGroup>
For convenience, you can also access the latest entry from the current session with `inCurrentSession`:
<CodeGroup>
```ts
```ts twoslash
import { Group, co, z, ID, Account, SessionID } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const me = await createJazzTestAccount();
const Activity = co.map({
timestamp: z.date(),
action: z.literal(["watering", "planting", "harvesting", "maintenance"]),
notes: z.optional(z.string()),
});
const ActivityFeed = co.feed(Activity);
const activityFeed = ActivityFeed.create([]);
const sessionId = `${me.id}_session_z1` as SessionID;
// ---cut---
// Get the feed for the current session
const currentSessionFeed = activityFeed.inCurrentSession;
// Latest entry from the current session
console.log(currentSessionFeed.value.action); // "harvesting"
console.log(currentSessionFeed?.value?.action); // "harvesting"
```
</CodeGroup>
@@ -99,24 +131,54 @@ console.log(currentSessionFeed.value.action); // "harvesting"
To retrieve entries from a specific account (with entries from all sessions combined) use `perAccount`:
<CodeGroup>
```ts
```ts twoslash
import { Group, co, z, ID, Account, SessionID } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const me = await createJazzTestAccount();
const Activity = co.map({
timestamp: z.date(),
action: z.literal(["watering", "planting", "harvesting", "maintenance"]),
notes: z.optional(z.string()),
});
const ActivityFeed = co.feed(Activity);
const activityFeed = ActivityFeed.create([]);
const accountId = me.id;
// ---cut---
// Get the feed for a specific account
const accountFeed = activityFeed.perAccount[accountId];
// Latest entry from the account
console.log(accountFeed.value.action); // "watering"
console.log(accountFeed.value?.action); // "watering"
```
</CodeGroup>
For convenience, you can also access the latest entry from the current account with `byMe`:
<CodeGroup>
```ts
```ts twoslash
import { Group, co, z, ID, Account, SessionID } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const me = await createJazzTestAccount();
const Activity = co.map({
timestamp: z.date(),
action: z.literal(["watering", "planting", "harvesting", "maintenance"]),
notes: z.optional(z.string()),
});
const ActivityFeed = co.feed(Activity);
const activityFeed = ActivityFeed.create([]);
const accountId = me.id;
// ---cut---
// Get the feed for the current account
const myLatestEntry = activityFeed.byMe;
// Latest entry from the current account
console.log(myLatestEntry.value.action); // "harvesting"
console.log(myLatestEntry?.value?.action); // "harvesting"
```
</CodeGroup>
@@ -127,7 +189,23 @@ console.log(myLatestEntry.value.action); // "harvesting"
To retrieve all entries from a CoFeed:
<CodeGroup>
```ts
```ts twoslash
import { Group, co, z, ID, Account, SessionID } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const me = await createJazzTestAccount();
const Activity = co.map({
timestamp: z.date(),
action: z.literal(["watering", "planting", "harvesting", "maintenance"]),
notes: z.optional(z.string()),
});
const ActivityFeed = co.feed(Activity);
const activityFeed = ActivityFeed.create([]);
const accountId = me.id;
const sessionId = `${me.id}_session_z1` as SessionID;
// ---cut---
// Get the feeds for a specific account and session
const accountFeed = activityFeed.perAccount[accountId];
const sessionFeed = activityFeed.perSession[sessionId];
@@ -149,11 +227,25 @@ for (const entry of sessionFeed.all) {
To retrieve the latest entry from a CoFeed, ie. the last update:
<CodeGroup>
```ts
```ts twoslash
import { Group, co, z, ID, Account, SessionID } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const me = await createJazzTestAccount();
const Activity = co.map({
timestamp: z.date(),
action: z.literal(["watering", "planting", "harvesting", "maintenance"]),
notes: z.optional(z.string()),
});
const ActivityFeed = co.feed(Activity);
const activityFeed = ActivityFeed.create([]);
// ---cut---
// Get the latest entry from the current account
const latestEntry = activityFeed.byMe;
console.log(`My last action was ${latestEntry.value.action}`);
console.log(`My last action was ${latestEntry?.value?.action}`);
// "My last action was harvesting"
// Get the latest entry from each account
@@ -171,7 +263,21 @@ CoFeeds are append-only; you can add new items, but not modify existing ones. Th
### Adding Items
<CodeGroup>
```ts
```ts twoslash
import { Group, co, z, ID, Account, SessionID } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const me = await createJazzTestAccount();
const Activity = co.map({
timestamp: z.date(),
action: z.literal(["watering", "planting", "harvesting", "maintenance"]),
notes: z.optional(z.string()),
});
const ActivityFeed = co.feed(Activity);
const activityFeed = ActivityFeed.create([]);
// ---cut---
// Log a new activity
activityFeed.push(Activity.create({
timestamp: new Date(),
@@ -188,23 +294,37 @@ Each item is automatically associated with the current user's session. You don't
Each entry is automatically added to the current session's feed. When a user has multiple open sessions (like both a mobile app and web browser), each session creates its own separate entries:
<CodeGroup>
```ts
```ts twoslash
import { Group, co, z, ID, Account, SessionID } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const me = await createJazzTestAccount();
const Activity = co.map({
timestamp: z.date(),
action: z.literal(["watering", "planting", "harvesting", "maintenance"]),
notes: z.optional(z.string()),
});
const ActivityFeed = co.feed(Activity);
const fromMobileFeed = ActivityFeed.create([]);
const fromBrowserFeed = ActivityFeed.create([]);
// ---cut---
// On mobile device:
fromMobileFeed.push(Activity.create({
timestamp: new Date(),
action: "harvesting",
location: "Vegetable patch"
notes: "Vegetable patch"
}));
// On web browser (same user):
fromBrowserFeed.push(Activity.create({
timestamp: new Date(),
action: "planting",
location: "Flower bed"
notes: "Flower bed"
}));
// These are separate entries in the same feed, from the same account
```
</CodeGroup>
@@ -217,7 +337,22 @@ CoFeeds support metadata, which is useful for tracking information about the fee
The `by` property is the account that made the entry.
<CodeGroup>
```ts
```ts twoslash
import { Group, co, z, ID, Account, SessionID } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const me = await createJazzTestAccount();
const Activity = co.map({
timestamp: z.date(),
action: z.literal(["watering", "planting", "harvesting", "maintenance"]),
notes: z.optional(z.string()),
});
const ActivityFeed = co.feed(Activity);
const activityFeed = ActivityFeed.create([]);
const accountId = me.id;
// ---cut---
const accountFeed = activityFeed.perAccount[accountId];
// Get the account that made the last entry
@@ -230,7 +365,22 @@ console.log(accountFeed?.by);
The `madeAt` property is a timestamp of when the entry was added to the feed.
<CodeGroup>
```ts
```ts twoslash
import { Group, co, z, ID, Account, SessionID } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const me = await createJazzTestAccount();
const Activity = co.map({
timestamp: z.date(),
action: z.literal(["watering", "planting", "harvesting", "maintenance"]),
notes: z.optional(z.string()),
});
const ActivityFeed = co.feed(Activity);
const activityFeed = ActivityFeed.create([]);
const accountId = me.id;
// ---cut---
const accountFeed = activityFeed.perAccount[accountId];
// Get the timestamp of the last update

View File

@@ -13,7 +13,13 @@ CoLists are ordered collections that work like JavaScript arrays. They provide i
CoLists are defined by specifying the type of items they contain:
<CodeGroup>
```ts
```ts twoslash
const Task = co.map({
title: z.string(),
status: z.literal(["todo", "in-progress", "complete"]),
});
// ---cut---
import { co, z } from "jazz-tools";
const ListOfResources = co.list(z.string());
@@ -25,7 +31,14 @@ const ListOfTasks = co.list(Task);
To create a `CoList`:
<CodeGroup>
```ts
```ts twoslash
import { co, z } from "jazz-tools";
const Task = co.map({
title: z.string(),
status: z.literal(["todo", "in-progress", "complete"]),
});
// ---cut---
// Create an empty list
const resources = co.list(z.string()).create([]);
@@ -68,7 +81,22 @@ See [Groups as permission scopes](/docs/groups/intro) for more information on ho
CoLists support standard array access patterns:
<CodeGroup>
```ts
```ts twoslash
import { co, z } from "jazz-tools";
const Task = co.map({
title: z.string(),
status: z.literal(["todo", "in-progress", "complete"]),
});
const ListOfTasks = co.list(Task);
const tasks = ListOfTasks.create([
Task.create({ title: "Prepare soil beds", status: "todo" }),
Task.create({ title: "Order compost", status: "todo" }),
]);
// ---cut---
// Access by index
const firstTask = tasks[0];
console.log(firstTask.title); // "Prepare soil beds"
@@ -94,7 +122,22 @@ console.log(todoTasks.length); // 1
Update `CoList`s just like you would JavaScript arrays:
<CodeGroup>
```ts
```ts twoslash
import { co, z } from "jazz-tools";
const Task = co.map({
title: z.string(),
status: z.literal(["todo", "in-progress", "complete"]),
});
const ListOfTasks = co.list(Task);
const ListOfResources = co.list(z.string());
const resources = ListOfResources.create([]);
const tasks = ListOfTasks.create([]);
// ---cut---
// Add items
resources.push("Tomatoes"); // Add to end
resources.unshift("Lettuce"); // Add to beginning
@@ -116,7 +159,18 @@ tasks[0].status = "complete"; // Update properties of references
Remove specific items by index with `splice`, or remove the first or last item with `pop` or `shift`:
<CodeGroup>
```ts
```ts twoslash
import { co, z } from "jazz-tools";
const ListOfResources = co.list(z.string());
const resources = ListOfResources.create([
"Tomatoes",
"Cucumber",
"Peppers",
]);
// ---cut---
// Remove 2 items starting at index 1
resources.splice(1, 2);
console.log(resources); // ["Cucumber", "Peppers"]
@@ -136,7 +190,14 @@ resources.shift(); // Remove first item
`CoList`s support the standard JavaScript array methods you already know:
<CodeGroup>
```ts
```ts twoslash
import { co, z } from "jazz-tools";
const ListOfResources = co.list(z.string());
const resources = ListOfResources.create([]);
// ---cut---
// Add multiple items at once
resources.push("Tomatoes", "Basil", "Peppers");
@@ -158,9 +219,23 @@ console.log(resources); // ["Basil", "Peppers", "Tomatoes"]
CoLists maintain type safety for their items:
<CodeGroup>
```ts
```ts twoslash
import { co, z } from "jazz-tools";
const Task = co.map({
title: z.string(),
status: z.literal(["todo", "in-progress", "complete"]),
});
const ListOfTasks = co.list(Task);
const ListOfResources = co.list(z.string());
const resources = ListOfResources.create([]);
const tasks = ListOfTasks.create([]);
// ---cut---
// TypeScript catches type errors
resources.push("Carrots"); // ✓ Valid string
// @errors: 2345
resources.push(42); // ✗ Type error: expected string
// For lists of references
@@ -178,20 +253,30 @@ tasks.forEach(task => {
CoLists work well with UI rendering libraries:
<CodeGroup>
```tsx
```tsx twoslash
import * as React from "react";
const Task = co.map({
title: z.string(),
status: z.literal(["todo", "in-progress", "complete"]),
});
// ---cut---
import { co, z, Loaded } from "jazz-tools";
const ListOfTasks = co.list(Task);
// React example
function TaskList({ tasks: Loaded<typeof ListOfTasks> }) {
return (
<ul>
{tasks.map(task => (
function TaskList({ tasks }: { tasks: Loaded<typeof ListOfTasks> }) {
return (
<ul>
{tasks.map(task => (
task ? (
<li key={task.id}>
{task.title} - {task.status}
</li>
))}
</ul>
): null
))}
</ul>
);
}
```
@@ -202,15 +287,34 @@ function TaskList({ tasks: Loaded<typeof ListOfTasks> }) {
CoLists can be used to create one-to-many relationships:
<CodeGroup>
```ts
import { co, z } from "jazz-tools";
```ts twoslash
import { co, z, CoListSchema } from "jazz-tools";
const Task = co.map({
title: z.string(),
status: z.literal(["todo", "in-progress", "complete"]),
get project(): z.ZodOptional<typeof Project> {
return z.optional(Project);
}
});
const ListOfTasks = co.list(Task);
const Project = co.map({
name: z.string(),
tasks: co.list(Task),
get tasks(): CoListSchema<typeof Task> {
return ListOfTasks;
}
});
// ...
const project = Project.create(
{
name: "Garden Project",
tasks: ListOfTasks.create([]),
},
);
const task = Task.create({
title: "Plant seedlings",

View File

@@ -76,11 +76,11 @@ Create an empty FileStream when you want to manually [add binary data in chunks]
<CodeGroup>
```ts twoslash
import { Group, co } from "jazz-tools";
import { Group, FileStream } from "jazz-tools";
const myGroup = Group.create();
// ---cut---
// Create a new empty FileStream
const fileStream = co.fileStream().create({ owner: myGroup } );
const fileStream = FileStream.create({ owner: myGroup } );
```
</CodeGroup>
@@ -90,7 +90,7 @@ Like other CoValues, you can specify ownership when creating FileStreams.
<CodeGroup>
```ts twoslash
import { Group, co } from "jazz-tools";
import { Group, FileStream } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const me = await createJazzTestAccount();
const colleagueAccount = await createJazzTestAccount();
@@ -101,7 +101,7 @@ const teamGroup = Group.create();
teamGroup.addMember(colleagueAccount, "writer");
// Create a FileStream with shared ownership
const teamFileStream = co.fileStream().create({ owner: teamGroup });
const teamFileStream = FileStream.create({ owner: teamGroup });
```
</CodeGroup>
@@ -117,8 +117,8 @@ To access the raw binary data and metadata:
<CodeGroup>
```ts twoslash
import { co } from "jazz-tools";
const fileStream = co.fileStream().create();
import { FileStream } from "jazz-tools";
const fileStream = FileStream.create();
// ---cut---
// Get all chunks and metadata
const fileData = fileStream.getChunks();
@@ -142,8 +142,8 @@ By default, `getChunks()` only returns data for completely synced `FileStream`s.
<CodeGroup>
```ts twoslash
import { co } from "jazz-tools";
const fileStream = co.fileStream().create();
import { FileStream } from "jazz-tools";
const fileStream = FileStream.create();
// ---cut---
// Get data even if the stream isn't complete
const partialData = fileStream.getChunks({ allowUnfinished: true });
@@ -156,8 +156,8 @@ For easier integration with web APIs, convert to a `Blob`:
<CodeGroup>
```ts twoslash
import { co } from "jazz-tools";
const fileStream = co.fileStream().create();
import { FileStream } from "jazz-tools";
const fileStream = FileStream.create();
// ---cut---
// Convert to a Blob
const blob = fileStream.toBlob();
@@ -187,16 +187,16 @@ You can directly load a `FileStream` as a `Blob` when you only have its ID:
<CodeGroup>
```ts twoslash
import { co, type ID } from "jazz-tools";
import { FileStream, type ID } from "jazz-tools";
const fileStreamId = "co_z123" as ID<FileStream>;
// ---cut---
// Load directly as a Blob when you have an ID
const blob = await co.fileStream().loadAsBlob(fileStreamId);
const blob = await FileStream.loadAsBlob(fileStreamId);
// By default, waits for complete uploads
// For in-progress uploads:
const partialBlob = await co.fileStream().loadAsBlob(fileStreamId, {
allowUnfinished: true
const partialBlob = await FileStream.loadAsBlob(fileStreamId, {
allowUnfinished: true,
});
```
</CodeGroup>
@@ -207,8 +207,8 @@ Check if a `FileStream` is fully synced:
<CodeGroup>
```ts twoslash
import { co } from "jazz-tools";
const fileStream = co.fileStream().create();
import { FileStream } from "jazz-tools";
const fileStream = FileStream.create();
// ---cut---
if (fileStream.isBinaryStreamEnded()) {
console.log('File is completely synced');
@@ -236,11 +236,11 @@ Begin by providing metadata about the file:
<CodeGroup>
```ts twoslash
import { co, Group } from "jazz-tools";
import { FileStream, Group } from "jazz-tools";
const myGroup = Group.create();
// ---cut---
// Create an empty FileStream
const fileStream = co.fileStream().create({ owner: myGroup });
const fileStream = FileStream.create({ owner: myGroup });
// Initialize with metadata
fileStream.start({
@@ -257,8 +257,8 @@ Add binary data in chunks - this helps with large files and progress tracking:
<CodeGroup>
```ts twoslash
import { co, Group } from "jazz-tools";
const fileStream = co.fileStream().create();
import { FileStream, Group } from "jazz-tools";
const fileStream = FileStream.create();
const file = [0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x57, 0x6F, 0x72, 0x6C, 0x64]; // "Hello World" in ASCII
const bytes = new Uint8Array(file);
const arrayBuffer = bytes.buffer;
@@ -288,8 +288,8 @@ Once all chunks are pushed, mark the `FileStream` as complete:
<CodeGroup>
```ts twoslash
import { co } from "jazz-tools";
const fileStream = co.fileStream().create();
import { FileStream } from "jazz-tools";
const fileStream = FileStream.create();
// ---cut---
// Finalize the upload
fileStream.end();
@@ -308,11 +308,11 @@ Load a `FileStream` when you have its ID:
<CodeGroup>
```ts twoslash
import { co, type ID } from "jazz-tools";
const fileStreamId = "co_z123" as ID<FileStream>;
import { FileStream } from "jazz-tools";
const fileStreamId = "co_z123";
// ---cut---
// Load a FileStream by ID
const fileStream = await co.fileStream().load(fileStreamId);
const fileStream = await FileStream.load(fileStreamId);
if (fileStream) {
console.log('FileStream loaded successfully');
@@ -332,12 +332,12 @@ Subscribe to a `FileStream` to be notified when chunks are added or when the upl
<CodeGroup>
```ts twoslash
import { co, type ID } from "jazz-tools";
import { FileStream } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const fileStreamId = "co_z123" as ID<FileStream>;
const fileStreamId = "co_z123";
// ---cut---
// Subscribe to a FileStream by ID
const unsubscribe = co.fileStream().subscribe(fileStreamId, (fileStream: FileStream) => {
const unsubscribe = FileStream.subscribe(fileStreamId, (fileStream: FileStream) => {
// Called whenever the FileStream changes
console.log('FileStream updated');
@@ -369,8 +369,8 @@ If you need to wait for a `FileStream` to be fully synchronized across devices:
<CodeGroup>
```ts twoslash
import { co } from "jazz-tools";
const fileStream = co.fileStream().create();
import { FileStream } from "jazz-tools";
const fileStream = FileStream.create();
// ---cut---
// Wait for the FileStream to be fully synced
await fileStream.waitForSync({

View File

@@ -1,6 +1,6 @@
import { CodeGroup } from "@/components/forMdx";
export const metadata = {
export const metadata = {
description: "ImageDefinition is a CoValue for managing images, storage of multiple resolutions, and progressive loading."
};
@@ -19,26 +19,55 @@ The [Image Upload example](https://github.com/gardencmp/jazz/tree/main/examples/
The easiest way to create and use images in your Jazz application is with the `createImage()` function:
<CodeGroup>
```ts
```ts twoslash
import { Group, co, z } from "jazz-tools";
const MyProfile = co.profile({
name: z.string(),
image: z.optional(co.image()),
});
const MyAccount = co.account({
root: co.map({}),
profile: MyProfile,
});
MyAccount.withMigration((account, creationProps) => {
if (account.profile === undefined) {
const profileGroup = Group.create();
profileGroup.addMember("everyone", "reader");
account.profile = MyProfile.create(
{
name: creationProps?.name ?? "New user",
},
profileGroup,
);
}
});
const me = await MyAccount.create({ creationProps: { name: "John Doe" } });
const myGroup = Group.create();
// ---cut---
import { createImage } from "jazz-browser-media-images";
// Create an image from a file input
async function handleFileUpload(event) {
const file = event.target.files[0];
async function handleFileUpload(event: Event) {
const file = (event.target as HTMLInputElement).files?.[0];
if (file) {
// Creates ImageDefinition with multiple resolutions automatically
const image = await createImage(file, {
owner: me.profile._owner,
});
const image = await createImage(file, { owner: myGroup });
// Store the image in your application data
me.profile.image = image;
}
}
```
</CodeGroup>
> Note: `createImage()` requires a browser environment as it uses browser APIs to process images.
**Note:** `createImage()` requires a browser environment as it uses browser APIs to process images.
The `createImage()` function:
- Creates an `ImageDefinition` with the right properties
@@ -51,11 +80,16 @@ The `createImage()` function:
You can configure `createImage()` with additional options:
<CodeGroup>
```tsx
```tsx twoslash
import { createImage } from "jazz-browser-media-images";
import { createJazzTestAccount } from 'jazz-tools/testing';
const me = await createJazzTestAccount();
const file = new File([], "test.jpg", { type: "image/jpeg" });
// ---cut---
// Configuration options
const options = {
owner: me, // Owner for access control
maxSize: 1024 // Maximum resolution to generate
maxSize: 1024 as 1024 // Maximum resolution to generate
};
// Setting maxSize controls which resolutions are generated:
@@ -98,7 +132,7 @@ See [Groups as permission scopes](/docs/groups/intro) for more information on ho
Create an `ImageDefinition` by specifying the original dimensions and an optional placeholder:
<CodeGroup>
```ts
```ts twoslash
import { ImageDefinition } from "jazz-tools";
// Create with original dimensions
@@ -109,7 +143,7 @@ const image = ImageDefinition.create({
// With a placeholder for immediate display
const imageWithPlaceholder = ImageDefinition.create({
originalSize: [1920, 1080],
placeholderDataURL: "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAP...",
placeholderDataURL: "data:image/jpeg;base64,/9j/4AAQSkZJ...",
});
```
</CodeGroup>
@@ -124,15 +158,13 @@ const imageWithPlaceholder = ImageDefinition.create({
Each resolution is stored with a key in the format `"widthxheight"` (e.g., `"1920x1080"`, `"800x450"`).
<CodeGroup>
```ts
import { CoMap, CoList, ImageDefinition, coField } from "jazz-tools";
```ts twoslash
import { ImageDefinition, co, z } from "jazz-tools";
class ListOfImages extends CoList.Of(coField.ref(ImageDefinition)) {}
class Gallery extends CoMap {
title = coField.string;
images = coField.ref(ListOfImages);
}
const Gallery = co.map({
title: z.string(),
images: co.list(co.image()),
});
```
</CodeGroup>
@@ -141,7 +173,19 @@ class Gallery extends CoMap {
Add multiple resolutions to an `ImageDefinition` by creating `FileStream`s for each size:
<CodeGroup>
```ts
```ts twoslash
import { FileStream, ImageDefinition } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const me = await createJazzTestAccount();
const fullSizeBlob = new Blob([], { type: "image/jpeg" });
const mediumSizeBlob = new Blob([], { type: "image/jpeg" });
const thumbnailBlob = new Blob([], { type: "image/jpeg" });
const image = ImageDefinition.create({
originalSize: [1920, 1080],
}, { owner: me });
// ---cut---
// Create FileStreams for different resolutions
const fullRes = await FileStream.createFromBlob(fullSizeBlob);
const mediumRes = await FileStream.createFromBlob(mediumSizeBlob);
@@ -159,28 +203,46 @@ image["320x180"] = thumbnailRes;
The `highestResAvailable` method helps select the best image resolution for the current context:
<CodeGroup>
```ts
// Get the highest resolution available
const highestRes = image.highestResAvailable();
if (highestRes) {
console.log(`Found resolution: ${highestRes.res}`);
```ts twoslash
import { ImageDefinition, FileStream } from "jazz-tools";
import { createJazzTestAccount } from "jazz-tools/testing";
// Convert to a usable blob
// Simple document environment
global.document = {
createElement: () =>
({ src: "", onload: null }) as unknown as HTMLImageElement,
} as unknown as Document;
global.window = { innerWidth: 1000 } as unknown as Window & typeof globalThis;
// Setup
const fakeBlob = new Blob(["fake image data"], { type: "image/jpeg" });
const me = await createJazzTestAccount();
const image = ImageDefinition.create(
{ originalSize: [1920, 1080] },
{ owner: me },
);
image["1920x1080"] = await FileStream.createFromBlob(fakeBlob, { owner: me });
const imageElement = document.createElement("img");
// ---cut---
// Get highest resolution available (unconstrained)
const highestRes = ImageDefinition.highestResAvailable(image);
console.log(highestRes);
if (highestRes) {
const blob = highestRes.stream.toBlob();
if (blob) {
// Create a URL for the blob
const url = URL.createObjectURL(blob);
imageElement.src = url;
// Remember to revoke the URL when no longer needed
imageElement.onload = () => {
URL.revokeObjectURL(url);
};
// Revoke the URL when the image is loaded
imageElement.onload = () => URL.revokeObjectURL(url);
}
}
// Or constrain by maximum width
const targetWidth = window.innerWidth;
const appropriateRes = image.highestResAvailable({ targetWidth });
// Get appropriate resolution for specific width
const appropriateRes = ImageDefinition.highestResAvailable(image, {
targetWidth: window.innerWidth,
});
```
</CodeGroup>
@@ -189,7 +251,11 @@ const appropriateRes = image.highestResAvailable({ targetWidth });
`highestResAvailable` returns the largest resolution that fits your constraints. If a resolution has incomplete data, it falls back to the next available lower resolution.
<CodeGroup>
```ts
```ts twoslash
import { ImageDefinition, FileStream } from "jazz-tools";
const mediumSizeBlob = new Blob([], { type: "image/jpeg" });
// ---cut---
const image = ImageDefinition.create({
originalSize: [1920, 1080],
});
@@ -197,8 +263,8 @@ const image = ImageDefinition.create({
image["1920x1080"] = FileStream.create(); // Empty image upload
image["800x450"] = await FileStream.createFromBlob(mediumSizeBlob);
const highestRes = image.highestResAvailable();
console.log(highestRes.res); // 800x450
const highestRes = ImageDefinition.highestResAvailable(image);
console.log(highestRes?.res); // 800x450
```
</CodeGroup>
@@ -207,7 +273,31 @@ console.log(highestRes.res); // 800x450
`ImageDefinition` supports simple progressive loading with placeholders and resolution selection:
<CodeGroup>
```ts
```ts twoslash
import { FileStream, ImageDefinition } from "jazz-tools";
import { createJazzTestAccount } from "jazz-tools/testing";
// Simple document environment
global.document = {
createElement: () =>
({ src: "", onload: null }) as unknown as HTMLImageElement,
} as unknown as Document;
global.window = { innerWidth: 1000 } as unknown as Window & typeof globalThis;
const me = await createJazzTestAccount();
const mediumSizeBlob = new Blob([], { type: "image/jpeg" });
const image = ImageDefinition.create(
{
originalSize: [1920, 1080],
},
{ owner: me },
);
image["1920x1080"] = await FileStream.createFromBlob(mediumSizeBlob, {
owner: me,
});
const imageElement = document.createElement("img");
// ---cut---
// Start with placeholder for immediate display
if (image.placeholderDataURL) {
imageElement.src = image.placeholderDataURL;
@@ -215,7 +305,9 @@ if (image.placeholderDataURL) {
// Then load the best resolution for the current display
const screenWidth = window.innerWidth;
const bestRes = image.highestResAvailable({ targetWidth: screenWidth });
const bestRes = ImageDefinition.highestResAvailable(image, {
targetWidth: screenWidth,
});
if (bestRes) {
const blob = bestRes.stream.toBlob();
@@ -229,6 +321,7 @@ if (bestRes) {
};
}
}
```
</CodeGroup>
## Best Practices

View File

@@ -1,6 +1,6 @@
import { CodeGroup } from "@/components/forMdx";
export const metadata = {
export const metadata = {
description: "ImageDefinition is a CoValue for managing images, storage of multiple resolutions, and progressive loading."
};
@@ -20,26 +20,55 @@ The [Image Upload example](https://github.com/gardencmp/jazz/tree/main/examples/
The easiest way to create and use images in your Jazz application is with the `createImage()` function:
<CodeGroup>
```ts
```ts twoslash
import { Group, co, z } from "jazz-tools";
const MyProfile = co.profile({
name: z.string(),
image: z.optional(co.image()),
});
const MyAccount = co.account({
root: co.map({}),
profile: MyProfile,
});
MyAccount.withMigration((account, creationProps) => {
if (account.profile === undefined) {
const profileGroup = Group.create();
profileGroup.addMember("everyone", "reader");
account.profile = MyProfile.create(
{
name: creationProps?.name ?? "New user",
},
profileGroup,
);
}
});
const me = await MyAccount.create({});
const myGroup = Group.create();
// ---cut---
import { createImage } from "jazz-browser-media-images";
// Create an image from a file input
async function handleFileUpload(event) {
const file = event.target.files[0];
async function handleFileUpload(event: React.ChangeEvent<HTMLInputElement>) {
const file = event.target.files?.[0];
if (file) {
// Creates ImageDefinition with multiple resolutions automatically
const image = await createImage(file, {
owner: me.profile._owner,
});
const image = await createImage(file, { owner: myGroup });
// Store the image in your application data
me.profile.image = image;
}
}
```
</CodeGroup>
> Note: `createImage()` requires a browser environment as it uses browser APIs to process images.
**Note:** `createImage()` requires a browser environment as it uses browser APIs to process images.
The `createImage()` function:
- Creates an `ImageDefinition` with the right properties
@@ -52,11 +81,16 @@ The `createImage()` function:
You can configure `createImage()` with additional options:
<CodeGroup>
```tsx
```ts twoslash
import { createImage } from "jazz-browser-media-images";
import { createJazzTestAccount } from 'jazz-tools/testing';
const me = await createJazzTestAccount();
const file = new File([], "test.jpg", { type: "image/jpeg" });
// ---cut---
// Configuration options
const options = {
owner: me, // Owner for access control
maxSize: 1024 // Maximum resolution to generate
maxSize: 1024 as 1024 // Maximum resolution to generate
};
// Setting maxSize controls which resolutions are generated:
@@ -99,10 +133,14 @@ See [Groups as permission scopes](/docs/groups/intro) for more information on ho
For a complete progressive loading experience, use the `ProgressiveImg` component:
<CodeGroup>
```tsx
```tsx twoslash
import * as React from "react";
// ---cut---
import { ProgressiveImg } from "jazz-react";
import { Loaded, co } from "jazz-tools";
const Image = co.image();
function GalleryView({ image }) {
function GalleryView({ image }: { image: Loaded<typeof Image> }) {
return (
<div className="image-container">
<ProgressiveImg
@@ -134,10 +172,14 @@ The `ProgressiveImg` component handles:
For more control over image loading, you can implement your own progressive image component:
<CodeGroup>
```tsx
```tsx twoslash
import * as React from "react";
import { Loaded, co } from "jazz-tools";
const Image = co.image();
// ---cut---
import { useProgressiveImg } from "jazz-react";
function CustomImageComponent({ image }) {
function CustomImageComponent({ image }: { image: Loaded<typeof Image> }) {
const {
src, // Data URI containing the image data as a base64 string,
// or a placeholder image URI
@@ -186,7 +228,9 @@ Behind the scenes, `ImageDefinition` is a specialized CoValue that stores:
Each resolution is stored with a key in the format `"widthxheight"` (e.g., `"1920x1080"`, `"800x450"`).
<CodeGroup>
```ts
```ts twoslash
import { ImageDefinition } from "jazz-tools";
// ---cut---
// Structure of an ImageDefinition
const image = ImageDefinition.create({
originalSize: [1920, 1080],
@@ -194,7 +238,7 @@ const image = ImageDefinition.create({
});
// Accessing the highest available resolution
const highestRes = image.highestResAvailable();
const highestRes = ImageDefinition.highestResAvailable(image);
if (highestRes) {
console.log(`Found resolution: ${highestRes.res}`);
console.log(`Stream: ${highestRes.stream}`);
@@ -209,7 +253,11 @@ For more details on using `ImageDefinition` directly, see the [VanillaJS docs](/
`highestResAvailable` returns the largest resolution that fits your constraints. If a resolution has incomplete data, it falls back to the next available lower resolution.
<CodeGroup>
```ts
```ts twoslash
import { ImageDefinition, FileStream } from "jazz-tools";
const mediumSizeBlob = new Blob([], { type: "image/jpeg" });
// ---cut---
const image = ImageDefinition.create({
originalSize: [1920, 1080],
});
@@ -217,7 +265,7 @@ const image = ImageDefinition.create({
image["1920x1080"] = FileStream.create(); // Empty image upload
image["800x450"] = await FileStream.createFromBlob(mediumSizeBlob);
const highestRes = image.highestResAvailable();
console.log(highestRes.res); // 800x450
const highestRes = ImageDefinition.highestResAvailable(image);
console.log(highestRes?.res); // 800x450
```
</CodeGroup>

View File

@@ -1,6 +1,6 @@
import { CodeGroup, ContentByFramework } from "@/components/forMdx";
export const metadata = {
export const metadata = {
description: "Learn how to subscribe to CoValues, specify loading depths, and handle loading states and inaccessible data."
};
@@ -26,27 +26,26 @@ If you're using React in your project, check out our [React hooks](/docs/react/u
<CodeGroup>
```ts twoslash
import { ID, CoMap, coField } from "jazz-tools";
const taskId = "co_123" as ID<Task>;
import { co, z } from "jazz-tools";
const taskId = "co_123";
// ---cut-before---
class Task extends CoMap {
title = coField.string;
description = coField.string;
status = coField.literal("todo", "in-progress", "completed");
assignedTo = coField.optional.string;
}
const Task = co.map({
title: z.string(),
description: z.string(),
status: z.literal(["todo", "in-progress", "completed"]),
assignedTo: z.optional(z.string()),
});
// ...
// Subscribe to a Task by ID
const unsubscribe = Task.subscribe(taskId, (updatedTask) => {
const unsubscribe = Task.subscribe(taskId, {}, (updatedTask) => {
console.log("Task updated:", updatedTask.title);
console.log("New status:", updatedTask.status);
});
// Clean up when you're done
unsubscribe();
```
</CodeGroup>
@@ -54,14 +53,14 @@ If you already have a CoValue instance, you can subscribe to it by calling its `
<CodeGroup>
```ts twoslash
import { ID, CoMap, coField } from "jazz-tools";
import { co, z } from "jazz-tools";
class Task extends CoMap {
title = coField.string;
description = coField.string;
status = coField.literal("todo", "in-progress", "completed");
assignedTo = coField.optional.string;
}
const Task = co.map({
title: z.string(),
description: z.string(),
status: z.literal(["todo", "in-progress", "completed"]),
assignedTo: z.optional(z.string()),
});
const otherProps = {} as any;
// ---cut-before---
const task = Task.create({
@@ -88,22 +87,20 @@ Jazz provides a `useCoState` hook that provides a convenient way to subscribe to
<CodeGroup>
```tsx twoslash
import React from "react";
import { ID, CoMap, coField, CoList } from "jazz-tools";
class Task extends CoMap {
title = coField.string;
status = coField.literal("todo", "in-progress", "completed");
}
class Project extends CoMap {
name = coField.string;
tasks = coField.ref(ListOfTasks);
}
class ListOfTasks extends CoList.Of(coField.ref(Task)) {}
import { co, z, ID, Loaded } from "jazz-tools";
const Task = co.map({
title: z.string(),
status: z.literal(["todo", "in-progress", "completed"]),
});
const Project = co.map({
name: z.string(),
tasks: co.list(Task),
});
// ---cut-before---
import { useCoState } from "jazz-react";
function GardenPlanner({ projectId }: { projectId: ID<Project> }) {
function GardenPlanner({ projectId }: { projectId: string }) {
// Subscribe to a project and its tasks
const project = useCoState(Project, projectId, {
resolve: {
@@ -125,7 +122,7 @@ function GardenPlanner({ projectId }: { projectId: ID<Project> }) {
);
}
function TaskList({ tasks }: { tasks: Task[] }) {
function TaskList({ tasks }: { tasks: Loaded<typeof Task>[] }) {
return (
<ul>
{tasks.map((task) => (
@@ -152,37 +149,38 @@ Like `useCoState`, you can specify a resolve query to also subscribe to CoValues
<CodeGroup>
```tsx twoslash
import React from "react";
import { ID, CoMap, coField, CoList, Account } from "jazz-tools";
class Task extends CoMap {
title = coField.string;
}
class ListOfTasks extends CoList.Of(coField.ref(Task)) {}
class Project extends CoMap {
name = coField.string;
tasks = coField.ref(ListOfTasks);
}
class ListOfProjects extends CoList.Of(coField.ref(Project)) {}
import { co, z } from "jazz-tools";
const Task = co.map({
title: z.string(),
});
const Project = co.map({
name: z.string(),
tasks: co.list(Task),
});
const AccountRoot = co.map({
myProjects: co.list(Project),
});
const MyAppAccount = co.account({
root: AccountRoot,
profile: co.profile(),
});
class AccountRoot extends CoMap {
myProjects = coField.ref(ListOfProjects);
}
class MyAppAccount extends Account {
root = coField.ref(AccountRoot);
}
declare module "jazz-react" { interface Register { Account: MyAppAccount; } }
// ---cut-before---
import { useAccount } from "jazz-react";
function ProjectList() {
const { me } = useAccount({
const { me } = useAccount(MyAppAccount, {
resolve: {
profile: true,
root: {
myProjects: {
$each: {
tasks: true
}
}
tasks: true,
},
},
},
},
});
@@ -191,17 +189,20 @@ function ProjectList() {
return <div>Loading...</div>;
}
return <div>
<h1>{me.profile.name}'s projects</h1>
<ul>
{me.root.myProjects.map(project => (
<li key={project.id}>
{project.name} ({project.tasks.length} tasks)
</li>
))}
</ul>
</div>
return (
<div>
<h1>{me.profile.name}'s projects</h1>
<ul>
{me.root.myProjects.map((project) => (
<li key={project.id}>
{project.name} ({project.tasks.length} tasks)
</li>
))}
</ul>
</div>
);
}
```
</CodeGroup>
@@ -219,14 +220,14 @@ This allows you to handle loading, error, and success states in your application
<CodeGroup>
```ts twoslash
import { ID, CoMap, coField } from "jazz-tools";
class Task extends CoMap {
title = coField.string;
}
import { co, z, Loaded } from "jazz-tools";
const Task = co.map({
title: z.string(),
});
const taskId = "co_123" as ID<Task>;
const taskId = "co_123";
// ---cut-before---
Task.subscribe(taskId, (task) => {
Task.subscribe(taskId, {}, (task: Loaded<typeof Task>) => {
if (task === undefined) {
console.log("Task is loading...");
} else if (task === null) {
@@ -248,27 +249,25 @@ Resolve queries let you declare exactly which references to load and how deep to
<CodeGroup>
```ts twoslash
import { ID, CoMap, coField, CoList } from "jazz-tools";
const projectId = "co_123" as ID<Project>;
import { co, z, CoListSchema } from "jazz-tools";
const projectId = "co_123";
// ---cut-before---
class Project extends CoMap {
name = coField.string;
tasks = coField.ref(ListOfTasks);
owner = coField.ref(TeamMember);
}
const TeamMember = co.map({
name: z.string(),
});
class Task extends CoMap {
title = coField.string;
subtasks = coField.ref(ListOfTasks);
assignee = coField.optional.ref(TeamMember);
}
const Task = co.map({
title: z.string(),
assignee: z.optional(TeamMember),
get subtasks(): CoListSchema<typeof Task> { return co.list(Task) },
});
class TeamMember extends CoMap {
name = coField.string;
}
class ListOfTasks extends CoList.Of(coField.ref(Task)) {}
const Project = co.map({
name: z.string(),
tasks: co.list(Task),
owner: TeamMember,
});
// Load just the project, not its references
const project = await Project.load(projectId);
@@ -349,28 +348,26 @@ When loading data with references, the load operation will fail if one of the re
When a user tries to load a reference they don't have access to:
<CodeGroup>
```typescript twoslash
import { ID, CoMap, coField, CoList } from "jazz-tools";
```ts twoslash
import { co, z, CoListSchema } from "jazz-tools";
class Project extends CoMap {
name = coField.string;
tasks = coField.ref(ListOfTasks);
owner = coField.ref(TeamMember);
}
const TeamMember = co.map({
name: z.string(),
});
class Task extends CoMap {
title = coField.string;
subtasks = coField.ref(ListOfTasks);
assignee = coField.optional.ref(TeamMember);
}
const Task = co.map({
title: z.string(),
assignee: z.optional(TeamMember),
get subtasks(): CoListSchema<typeof Task> { return co.list(Task) },
});
class TeamMember extends CoMap {
name = coField.string;
}
const Project = co.map({
name: z.string(),
tasks: co.list(Task),
owner: TeamMember,
});
class ListOfTasks extends CoList.Of(coField.ref(Task)) {}
const taskId = "co_123" as ID<Task>;
const taskId = "co_123";
// ---cut-before---
// If assignee is not accessible to the user:
@@ -390,28 +387,26 @@ The behavior is the same for optional and required references.
When a list contains references to items the user can't access:
<CodeGroup>
```typescript twoslash
import { ID, CoMap, coField, CoList } from "jazz-tools";
```ts twoslash
import { co, z, CoListSchema } from "jazz-tools";
class Project extends CoMap {
name = coField.string;
tasks = coField.ref(ListOfTasks);
owner = coField.ref(TeamMember);
}
const TeamMember = co.map({
name: z.string(),
});
class Task extends CoMap {
title = coField.string;
subtasks = coField.ref(ListOfTasks);
assignee = coField.optional.ref(TeamMember);
}
const Task = co.map({
title: z.string(),
assignee: z.optional(TeamMember),
get subtasks(): CoListSchema<typeof Task> { return co.list(Task) },
});
class TeamMember extends CoMap {
name = coField.string;
}
const Project = co.map({
name: z.string(),
tasks: co.list(Task),
owner: TeamMember,
});
class ListOfTasks extends CoList.Of(coField.ref(Task)) {}
const projectId = "co_123" as ID<Project>;
const projectId = "co_123";
// ---cut-before---
// If any item in the list is not accessible:
const project = await Project.load(projectId, {
@@ -428,28 +423,26 @@ If any item in a list is inaccessible to the user, the entire load operation wil
When trying to load an object with an inaccessible reference without directly resolving it:
<CodeGroup>
```typescript twoslash
import { ID, CoMap, coField, CoList } from "jazz-tools";
```ts twoslash
import { co, z, CoListSchema } from "jazz-tools";
class Project extends CoMap {
name = coField.string;
tasks = coField.ref(ListOfTasks);
owner = coField.ref(TeamMember);
}
const TeamMember = co.map({
name: z.string(),
});
class Task extends CoMap {
title = coField.string;
subtasks = coField.ref(ListOfTasks);
assignee = coField.optional.ref(TeamMember);
}
const Task = co.map({
title: z.string(),
assignee: z.optional(TeamMember),
get subtasks(): CoListSchema<typeof Task> { return co.list(Task) },
});
class TeamMember extends CoMap {
name = coField.string;
}
const Project = co.map({
name: z.string(),
tasks: co.list(Task),
owner: TeamMember,
});
class ListOfTasks extends CoList.Of(coField.ref(Task)) {}
const projectId = "co_123" as ID<Project>;
const projectId = "co_123";
// ---cut-before---
const project = await Project.load(projectId, {
resolve: true
@@ -474,21 +467,24 @@ This is especially useful when in your app access to these items might be revoke
This way the inaccessible items are replaced with `null` in the returned list.
<CodeGroup>
```typescript twoslash
import { ID, CoMap, CoList, co, Group, Account } from "jazz-tools";
import { assert } from "vitest";
```ts twoslash
import { co, z, CoListSchema, Group } from "jazz-tools";
import { createJazzTestAccount } from "jazz-tools/testing";
class Person extends CoMap {
name = co.string;
}
class Friends extends CoList.Of(co.ref(Person)) {}
const me = await createJazzTestAccount();
const account2 = await createJazzTestAccount();
const privateGroup = Group.create();
const publicGroup = Group.create();
const me = {} as unknown as Account;
const Person = co.map({
name: z.string(),
});
const Friends = co.list(Person);
const privateGroup = Group.create({ owner: account2 });
const publicGroup = Group.create({ owner: me });
// ---cut-before---
const source = Friends.create(
const source = co.list(Person).create(
[
Person.create(
{
@@ -506,7 +502,7 @@ const source = Friends.create(
publicGroup,
);
const friends = await Friends.load(source.id, {
const friends = await co.list(Person).load(source.id, {
resolve: {
$each: { $onError: null }
},
@@ -515,47 +511,47 @@ const friends = await Friends.load(source.id, {
// Thanks to $onError catching the errors, the list is loaded
// because we have access to friends
friends // => Friends
assert(friends);
console.log(friends); // Person[]
// Jane is null because we lack access rights
// and we have used $onError to catch the error on the list items
friends[0] // => null
console.log(friends?.[0]); // null
// Alice is not null because we have access
// the type is nullable because we have used $onError
friends[1] // => Person
console.log(friends?.[1]); // Person
```
</CodeGroup>
The `$onError` works as a "catch" clause option to block any error in the resolved childs.
The `$onError` works as a "catch" clause option to block any error in the resolved children.
<CodeGroup>
```typescript twoslash
import { ID, CoMap, CoList, co, Group, Account } from "jazz-tools";
import { assert } from "vitest";
```ts twoslash
import { createJazzTestAccount } from "jazz-tools/testing";
const me = await createJazzTestAccount();
const account2 = await createJazzTestAccount();
class Person extends CoMap {
name = co.string;
dog = co.ref(Dog);
}
class Dog extends CoMap {
name = co.string;
}
class Friends extends CoList.Of(co.ref(Person)) {}
import { Group, co, z } from "jazz-tools";
class User extends CoMap {
name = co.string;
friends = co.ref(Friends);
}
const Dog = co.map({
name: z.string(),
});
const privateGroup = Group.create();
const publicGroup = Group.create();
const me = {} as unknown as Account;
const Person = co.map({
name: z.string(),
dog: Dog,
});
const User = co.map({
name: z.string(),
friends: co.list(Person),
});
const privateGroup = Group.create({ owner: account2 });
const publicGroup = Group.create({ owner: me });
// ---cut-before---
const source = Friends.create(
const source = co.list(Person).create(
[
Person.create(
{
@@ -571,47 +567,47 @@ const source = Friends.create(
publicGroup,
);
const friends = await Friends.load(source.id, {
const friends = await co.list(Person).load(source.id, {
resolve: {
$each: { dog: true, $onError: null }
},
loadAs: me,
});
assert(friends);
// Jane is null because we don't have access to Rex
// and we have used $onError to catch the error on the list items
friends[0] // => null
console.log(friends?.[0]); // null
```
</CodeGroup>
We can actually use `$onError` everywhere in the resolve query, so we can use it to catch the error on dog:
<CodeGroup>
```typescript twoslash
import { ID, CoMap, CoList, co, Group, Account } from "jazz-tools";
import { assert } from "vitest";
```ts twoslash
import { createJazzTestAccount } from "jazz-tools/testing";
const me = await createJazzTestAccount();
const account2 = await createJazzTestAccount();
class Person extends CoMap {
name = co.string;
dog = co.ref(Dog);
}
class Dog extends CoMap {
name = co.string;
}
class Friends extends CoList.Of(co.ref(Person)) {}
import { co, z, Group } from "jazz-tools";
class User extends CoMap {
name = co.string;
friends = co.ref(Friends);
}
const Dog = co.map({
name: z.string(),
});
const privateGroup = Group.create();
const publicGroup = Group.create();
const me = {} as unknown as Account;
const Person = co.map({
name: z.string(),
dog: Dog,
});
const source = Friends.create(
const User = co.map({
name: z.string(),
friends: co.list(Person),
});
const privateGroup = Group.create({ owner: account2 });
const publicGroup = Group.create({ owner: me });
const source = co.list(Person).create(
[
Person.create(
{
@@ -628,71 +624,71 @@ const source = Friends.create(
);
// ---cut-before---
const friends = await Friends.load(source.id, {
const friends = await co.list(Person).load(source.id, {
resolve: {
$each: { dog: { $onError: null } }
},
loadAs: me,
});
assert(friends);
// Jane now is not-nullable at type level because
// we have moved $onError down to the dog field
//
// This also means that if we don't have access to Jane
// the entire friends list will be null
friends[0] // => Person
console.log(friends?.[0]); // => Person
// Jane's dog is null because we don't have access to Rex
// and we have used $onError to catch the error
friends[0].dog // => null
console.log(friends?.[0]?.dog); // => null
```
</CodeGroup>
## Type Safety with Resolved Type
## Type Safety with Loaded Type
Jazz provides the `Resolved` type to help you define and enforce the structure of deeply loaded data in your application. This makes it easier to ensure that components receive the data they expect with proper TypeScript validation.
Jazz provides the `Loaded` type to help you define and enforce the structure of deeply loaded data in your application. This makes it easier to ensure that components receive the data they expect with proper TypeScript validation.
The `Resolved` type is especially useful when passing data between components, as it guarantees that all necessary nested data has been loaded:
The `Loaded` type is especially useful when passing data between components, as it guarantees that all necessary nested data has been loaded:
<ContentByFramework framework="react">
<CodeGroup>
```tsx twoslash
import { CoListSchema, Loaded, co, z } from "jazz-tools";
import React from "react";
import { ID, CoMap, coField, CoList, Resolved } from "jazz-tools";
class Project extends CoMap {
name = coField.string;
tasks = coField.ref(ListOfTasks);
owner = coField.ref(TeamMember);
}
const TeamMember = co.map({
name: z.string(),
});
class Task extends CoMap {
title = coField.string;
subtasks = coField.ref(ListOfTasks);
assignee = coField.optional.ref(TeamMember);
}
class TeamMember extends CoMap {
name = coField.string;
}
class ListOfTasks extends CoList.Of(coField.ref(Task)) {}
const Task = co.map({
title: z.string(),
assignee: z.optional(TeamMember),
get subtasks(): CoListSchema<typeof Task> {
return co.list(Task);
},
});
const Project = co.map({
name: z.string(),
tasks: co.list(Task),
owner: TeamMember,
});
// ---cut-before---
// Define a type that includes resolved nested data
type ProjectWithTasks = Resolved<Project, {
tasks: { $each: true }
}>;
// Define a type that includes loaded nested data
type ProjectWithTasks = Loaded<
typeof Project,
{
tasks: { $each: true };
}
>;
// Component that expects a fully resolved project
// Component that expects a fully loaded project
function TaskList({ project }: { project: ProjectWithTasks }) {
// TypeScript knows tasks are loaded, so this is type-safe
return (
<ul>
{project.tasks.map(task => (
{project.tasks.map((task) => (
<li key={task.id}>{task.title}</li>
))}
</ul>
@@ -700,26 +696,30 @@ function TaskList({ project }: { project: ProjectWithTasks }) {
}
// For more complex resolutions
type FullyLoadedProject = Resolved<Project, {
tasks: {
$each: {
subtasks: true,
assignee: true
}
},
owner: true
}>;
type FullyLoadedProject = Loaded<
typeof Project,
{
tasks: {
$each: {
subtasks: true;
assignee: true;
};
};
owner: true;
}
>;
// Function that requires deeply resolved data
// Function that requires deeply loaded data
function processProject(project: FullyLoadedProject) {
// Safe access to all resolved properties
// Safe access to all loaded properties
console.log(`Project ${project.name} owned by ${project.owner.name}`);
project.tasks.forEach(task => {
project.tasks.forEach((task) => {
console.log(`Task: ${task.title}, Assigned to: ${task.assignee?.name}`);
console.log(`Subtasks: ${task.subtasks.length}`);
});
}
```
</CodeGroup>
</ContentByFramework>
@@ -727,59 +727,62 @@ function processProject(project: FullyLoadedProject) {
<ContentByFramework framework="vanilla">
<CodeGroup>
```ts twoslash
import { ID, CoMap, coField, CoList, Resolved } from "jazz-tools";
import { CoListSchema, Loaded, co, z } from "jazz-tools";
class Project extends CoMap {
name = coField.string;
tasks = coField.ref(ListOfTasks);
owner = coField.ref(TeamMember);
}
const TeamMember = co.map({
name: z.string(),
});
class Task extends CoMap {
title = coField.string;
subtasks = coField.ref(ListOfTasks);
assignee = coField.optional.ref(TeamMember);
}
class TeamMember extends CoMap {
name = coField.string;
}
class ListOfTasks extends CoList.Of(coField.ref(Task)) {}
const Task = co.map({
title: z.string(),
assignee: z.optional(TeamMember),
get subtasks(): CoListSchema<typeof Task> {
return co.list(Task);
},
});
const Project = co.map({
name: z.string(),
tasks: co.list(Task),
owner: TeamMember,
});
// ---cut-before---
// Define a type that includes resolved nested data
type ProjectWithTasks = Resolved<Project, {
tasks: { $each: true }
}>;
// Define a type that includes loaded nested data
type ProjectWithTasks = Loaded<
typeof Project,
{
tasks: { $each: true };
}
>;
// Function that expects resolved data
async function taskList({project}: {project: ProjectWithTasks}) {
// Function that expects loaded data
async function taskList({ project }: { project: ProjectWithTasks }) {
// TypeScript knows tasks are loaded, so this is type-safe
return project.tasks
.map(task => task.title)
.join(`\n - `);
return project.tasks.map((task) => task.title).join(`\n - `);
}
// For more complex resolutions
type FullyLoadedProject = Resolved<Project, {
tasks: {
$each: {
title: true,
subtasks: true,
assignee: true
}
},
owner: true
}>;
type FullyLoadedProject = Loaded<
typeof Project,
{
tasks: {
$each: {
title: true;
subtasks: true;
assignee: true;
};
};
owner: true;
}
>;
// Function that requires deeply resolved data
// Function that requires deeply loaded data
function processProject(project: FullyLoadedProject) {
// Safe access to all resolved properties
// Safe access to all loaded properties
console.log(`Project ${project.name} owned by ${project.owner.name}`);
project.tasks.forEach(task => {
project.tasks.forEach((task) => {
console.log(`Task: ${task.title}, Assigned to: ${task.assignee?.name}`);
console.log(`Subtasks: ${task.subtasks.length}`);
});
@@ -788,7 +791,7 @@ function processProject(project: FullyLoadedProject) {
</CodeGroup>
</ContentByFramework>
Using the `Resolved` type helps catch errors at compile time rather than runtime, ensuring that your components and functions receive data with the proper resolution depth. This is especially useful for larger applications where data is passed between many components.
Using the `Loaded` type helps catch errors at compile time rather than runtime, ensuring that your components and functions receive data with the proper resolution depth. This is especially useful for larger applications where data is passed between many components.
## Ensuring Data is Loaded
@@ -796,29 +799,29 @@ Sometimes you need to make sure data is loaded before proceeding with an operati
<CodeGroup>
```ts twoslash
import { ID, CoMap, coField, CoList, Resolved } from "jazz-tools";
import { CoListSchema, Loaded, co, z } from "jazz-tools";
class Project extends CoMap {
name = coField.string;
tasks = coField.ref(ListOfTasks);
owner = coField.ref(TeamMember);
}
const TeamMember = co.map({
name: z.string(),
});
class Task extends CoMap {
title = coField.string;
status = coField.literal("todo", "in-progress", "completed");
subtasks = coField.ref(ListOfTasks);
assignee = coField.optional.ref(TeamMember);
}
const Task = co.map({
title: z.string(),
status: z.literal(["todo", "in-progress", "completed"]),
assignee: z.string().optional(),
get subtasks(): CoListSchema<typeof Task> {
return co.list(Task);
},
});
class TeamMember extends CoMap {
name = coField.string;
}
class ListOfTasks extends CoList.Of(coField.ref(Task)) {}
const Project = co.map({
name: z.string(),
tasks: co.list(Task),
owner: TeamMember,
});
// ---cut-before---
async function completeAllTasks(projectId: ID<Project>) {
async function completeAllTasks(projectId: string) {
// Ensure the project is loaded
const project = await Project.load(projectId, { resolve: true });
if (!project) return;
@@ -827,13 +830,13 @@ async function completeAllTasks(projectId: ID<Project>) {
const loadedProject = await project.ensureLoaded({
resolve: {
tasks: {
$each: true
}
}
$each: true,
},
},
});
// Now we can safely access and modify tasks
loadedProject.tasks.forEach(task => {
loadedProject.tasks.forEach((task) => {
task.status = "completed";
});
}
@@ -848,4 +851,4 @@ async function completeAllTasks(projectId: ID<Project>) {
2. **Use framework integrations**: They handle subscription lifecycle automatically
3. **Clean up subscriptions**: Always store and call the unsubscribe function when you're done
4. **Handle all loading states**: Check for undefined (loading), null (not found), and success states
5. **Use the Resolved type**: Add compile-time type safety for components that require specific resolution patterns
5. **Use the Loaded type**: Add compile-time type safety for components that require specific resolution patterns

View File

@@ -28,9 +28,6 @@ export const PACKAGES = [
packageName: "jazz-react",
entryPoint: "index.ts",
description: "React bindings for Jazz, a framework for distributed state.",
typedocOptions: {
skipErrorChecking: true, // TODO: remove this. Temporary workaround
},
},
{
packageName: "jazz-browser",

View File

@@ -451,7 +451,9 @@ export class RawGroup<
}
getCurrentReadKeyId() {
if (this.myRole() === "writeOnly") {
const myRole = this.myRole();
if (myRole === "writeOnly") {
const accountId = this.core.node.getCurrentAgent().id;
const key = this.get(`writeKeyFor_${accountId}`) as KeyID;
@@ -469,6 +471,16 @@ export class RawGroup<
return key;
}
if (!myRole) {
const accountId = this.core.node.getCurrentAgent().id;
const key = this.get(`writeKeyFor_${accountId}`) as KeyID;
if (key) {
return key;
}
}
return this.get("readKey");
}
@@ -670,22 +682,24 @@ export class RawGroup<
);
}
const value = role === "inherit" ? "extend" : role;
this.set(`parent_${parent.id}`, value, "trusting");
parent.set(`child_${this.id}`, "extend", "trusting");
if (
parent.myRole() !== "admin" &&
parent.myRole() !== "writer" &&
parent.myRole() !== "reader" &&
parent.myRole() !== "writeOnly"
) {
throw new Error(
"To extend a group, the current account must be a member of the parent group",
// Create a writeOnly key in the parent group to be able to reveal the current child key to the parent group
parent.internalCreateWriteOnlyKeyForMember(
this.core.node.getCurrentAgent().id,
this.core.node.getCurrentAgent().currentAgentID(),
);
}
const value = role === "inherit" ? "extend" : role;
this.set(`parent_${parent.id}`, value, "trusting");
parent.set(`child_${this.id}`, "extend", "trusting");
const { id: parentReadKeyID, secret: parentReadKeySecret } =
parent.core.getCurrentReadKey();
if (!parentReadKeySecret) {

View File

@@ -345,18 +345,6 @@ function determineValidTransactionsForGroup(
validTransactions.push({ txID: { sessionID, txIndex }, tx });
continue;
} else if (isChildExtension(change.key)) {
if (
memberState[transactor] !== "admin" &&
memberState[transactor] !== "writer" &&
memberState[transactor] !== "reader" &&
memberState[transactor] !== "writeOnly"
) {
logPermissionError(
"Only admins, writers, readers and writeOnly can set child extensions",
);
continue;
}
validTransactions.push({ txID: { sessionID, txIndex }, tx });
continue;
} else if (isWriteKeyForMember(change.key)) {

View File

@@ -157,6 +157,29 @@ describe("extend", () => {
expect(childGroup.roleOf(node2.accountID)).toEqual(undefined);
});
test("should be possible to extend a group without having membership in the parent group", async () => {
const { node1, node2, node3 } = await createThreeConnectedNodes(
"server",
"server",
"server",
);
const parentGroup = node1.node.createGroup();
const childGroup = node2.node.createGroup();
const alice = await loadCoValueOrFail(node1.node, node3.accountID);
parentGroup.addMember(alice, "writer");
const parentGroupOnNode2 = await loadCoValueOrFail(
node2.node,
parentGroup.id,
);
childGroup.extend(parentGroupOnNode2);
expect(childGroup.roleOf(alice.id)).toBe("writer");
});
});
describe("unextend", () => {
@@ -191,6 +214,78 @@ describe("unextend", () => {
expect(childGroup.roleOf(alice.id)).toBe(undefined);
});
test("should work when the account has no access to the parent group but owns the writeKey", async () => {
const { node1, node2, node3 } = await createThreeConnectedNodes(
"server",
"server",
"server",
);
const parentGroup = node1.node.createGroup();
const childGroup = node2.node.createGroup();
const alice = await loadCoValueOrFail(node1.node, node3.accountID);
parentGroup.addMember(alice, "writer");
const parentGroupOnNode2 = await loadCoValueOrFail(
node2.node,
parentGroup.id,
);
childGroup.extend(parentGroupOnNode2);
expect(childGroup.roleOf(alice.id)).toBe("writer");
// `childGroup` no longer has `parentGroup`'s members
await childGroup.revokeExtend(parentGroup);
expect(childGroup.roleOf(alice.id)).toBe(undefined);
const map = childGroup.createMap();
map.set("test", "Hello!");
const mapOnAlice = await loadCoValueOrFail(node3.node, map.id);
expect(mapOnAlice.get("test")).toEqual(undefined);
});
test("should work when the account has no access to the parent group and not owns the writeKey", async () => {
const {
node1: bobNode,
node2: johnNode,
node3: aliceNode,
} = await createThreeConnectedNodes("server", "server", "server");
const parentGroup = bobNode.node.createGroup();
const childGroup = johnNode.node.createGroup();
const parentGroupOnJohn = await loadCoValueOrFail(
johnNode.node,
parentGroup.id,
);
childGroup.extend(parentGroupOnJohn);
const bob = await loadCoValueOrFail(johnNode.node, bobNode.accountID);
const alice = await loadCoValueOrFail(johnNode.node, aliceNode.accountID);
childGroup.addMember(alice, "admin");
const childGroupOnAlice = await loadCoValueOrFail(
aliceNode.node,
childGroup.id,
);
// `childGroup` no longer has `parentGroup`'s members
await childGroupOnAlice.revokeExtend(parentGroup);
expect(childGroupOnAlice.roleOf(bob.id)).toBe(undefined);
const map = childGroupOnAlice.createMap();
map.set("test", "Hello!");
const mapOnBob = await loadCoValueOrFail(bobNode.node, map.id);
expect(mapOnBob.get("test")).toEqual(undefined);
});
test("should do nothing if applied to a group that is not extended", async () => {
const { node1, node2, node3 } = await createThreeConnectedNodes(
"server",

View File

@@ -1982,40 +1982,6 @@ test("Writers, readers and writeOnly can set child extensions", () => {
expect(groupAsReader.get(`child_${childGroup.id}`)).toEqual("extend");
});
test("Invitees can not set child extensions", () => {
const { group, node } = newGroupHighLevel();
const childGroup = node.createGroup();
const adminInvite = createAccountInNode(node);
const writerInvite = createAccountInNode(node);
const readerInvite = createAccountInNode(node);
group.addMember(adminInvite, "adminInvite");
group.addMember(writerInvite, "writerInvite");
group.addMember(readerInvite, "readerInvite");
const groupAsAdminInvite = expectGroup(
group.core.contentInClonedNodeWithDifferentAccount(adminInvite),
);
groupAsAdminInvite.set(`child_${childGroup.id}`, "extend", "trusting");
expect(groupAsAdminInvite.get(`child_${childGroup.id}`)).toBeUndefined();
const groupAsWriterInvite = expectGroup(
group.core.contentInClonedNodeWithDifferentAccount(writerInvite),
);
groupAsWriterInvite.set(`child_${childGroup.id}`, "extend", "trusting");
expect(groupAsWriterInvite.get(`child_${childGroup.id}`)).toBeUndefined();
const groupAsReaderInvite = expectGroup(
group.core.contentInClonedNodeWithDifferentAccount(readerInvite),
);
groupAsReaderInvite.set(`child_${childGroup.id}`, "extend", "trusting");
expect(groupAsReaderInvite.get(`child_${childGroup.id}`)).toBeUndefined();
});
test("Member roles are inherited by child groups (except invites)", () => {
const { group, node, admin } = newGroupHighLevel();
const parentGroup = node.createGroup();

View File

@@ -1,5 +1,44 @@
# jazz-auth-betterauth
## 0.14.6
### Patch Changes
- Updated dependencies [9d6d9fe]
- Updated dependencies [9d6d9fe]
- jazz-tools@0.14.6
- jazz-browser@0.14.6
- jazz-betterauth-client-plugin@0.14.6
## 0.14.5
### Patch Changes
- Updated dependencies [91cbb2f]
- Updated dependencies [20b3d88]
- jazz-tools@0.14.5
- jazz-browser@0.14.5
- jazz-betterauth-client-plugin@0.14.5
## 0.14.4
### Patch Changes
- Updated dependencies [011af55]
- jazz-tools@0.14.4
- jazz-browser@0.14.4
- jazz-betterauth-client-plugin@0.14.4
## 0.14.2
### Patch Changes
- Updated dependencies [3d1027f]
- Updated dependencies [c240eed]
- jazz-tools@0.14.2
- jazz-browser@0.14.2
- jazz-betterauth-client-plugin@0.14.2
## 0.14.1
### Patch Changes

View File

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

View File

@@ -1,5 +1,40 @@
# jazz-auth-clerk
## 0.14.6
### Patch Changes
- Updated dependencies [9d6d9fe]
- Updated dependencies [9d6d9fe]
- jazz-tools@0.14.6
- jazz-browser@0.14.6
## 0.14.5
### Patch Changes
- Updated dependencies [91cbb2f]
- Updated dependencies [20b3d88]
- jazz-tools@0.14.5
- jazz-browser@0.14.5
## 0.14.4
### Patch Changes
- Updated dependencies [011af55]
- jazz-tools@0.14.4
- jazz-browser@0.14.4
## 0.14.2
### Patch Changes
- Updated dependencies [3d1027f]
- Updated dependencies [c240eed]
- jazz-tools@0.14.2
- jazz-browser@0.14.2
## 0.14.1
### Patch Changes

View File

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

View File

@@ -1,5 +1,29 @@
# jazz-betterauth-client-plugin
## 0.14.6
### Patch Changes
- jazz-betterauth-server-plugin@0.14.6
## 0.14.5
### Patch Changes
- jazz-betterauth-server-plugin@0.14.5
## 0.14.4
### Patch Changes
- jazz-betterauth-server-plugin@0.14.4
## 0.14.2
### Patch Changes
- jazz-betterauth-server-plugin@0.14.2
## 0.14.1
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-betterauth-client-plugin",
"version": "0.14.1",
"version": "0.14.6",
"type": "module",
"main": "dist/index.js",
"types": "src/index.ts",

View File

@@ -1,5 +1,40 @@
# jazz-betterauth-server-plugin
## 0.14.6
### Patch Changes
- Updated dependencies [9d6d9fe]
- Updated dependencies [9d6d9fe]
- jazz-tools@0.14.6
- jazz-browser@0.14.6
## 0.14.5
### Patch Changes
- Updated dependencies [91cbb2f]
- Updated dependencies [20b3d88]
- jazz-tools@0.14.5
- jazz-browser@0.14.5
## 0.14.4
### Patch Changes
- Updated dependencies [011af55]
- jazz-tools@0.14.4
- jazz-browser@0.14.4
## 0.14.2
### Patch Changes
- Updated dependencies [3d1027f]
- Updated dependencies [c240eed]
- jazz-tools@0.14.2
- jazz-browser@0.14.2
## 0.14.1
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-betterauth-server-plugin",
"version": "0.14.1",
"version": "0.14.6",
"type": "module",
"main": "dist/index.js",
"types": "src/index.ts",

View File

@@ -1,5 +1,40 @@
# jazz-browser-media-images
## 0.14.6
### Patch Changes
- Updated dependencies [9d6d9fe]
- Updated dependencies [9d6d9fe]
- jazz-tools@0.14.6
- jazz-browser@0.14.6
## 0.14.5
### Patch Changes
- Updated dependencies [91cbb2f]
- Updated dependencies [20b3d88]
- jazz-tools@0.14.5
- jazz-browser@0.14.5
## 0.14.4
### Patch Changes
- Updated dependencies [011af55]
- jazz-tools@0.14.4
- jazz-browser@0.14.4
## 0.14.2
### Patch Changes
- Updated dependencies [3d1027f]
- Updated dependencies [c240eed]
- jazz-tools@0.14.2
- jazz-browser@0.14.2
## 0.14.1
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-browser-media-images",
"version": "0.14.1",
"version": "0.14.6",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
@@ -8,8 +8,8 @@
"dependencies": {
"@types/image-blob-reduce": "^4.1.1",
"image-blob-reduce": "^4.1.0",
"jazz-browser": "workspace:0.14.1",
"jazz-tools": "workspace:0.14.1",
"jazz-browser": "workspace:0.14.6",
"jazz-tools": "workspace:0.14.6",
"pica": "^9.0.1"
},
"scripts": {

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