Compare commits
54 Commits
jazz-react
...
jazz-react
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f66b7c1a5a | ||
|
|
1b57cfc3f6 | ||
|
|
a805e27b0a | ||
|
|
9d6d9fe7a5 | ||
|
|
409758afd0 | ||
|
|
e0bc532345 | ||
|
|
87e02c0516 | ||
|
|
405f9be7b9 | ||
|
|
c82bf737bf | ||
|
|
91cbb2f9d4 | ||
|
|
cfbba59c6d | ||
|
|
b1209e2e09 | ||
|
|
20b3d88135 | ||
|
|
d9ad1f4de5 | ||
|
|
c90153e0c9 | ||
|
|
a4241c0f4b | ||
|
|
e1ff7a65a8 | ||
|
|
011af55446 | ||
|
|
e9dbcf53c8 | ||
|
|
3da21b95ec | ||
|
|
a6d0dd07a1 | ||
|
|
ac474c4afb | ||
|
|
afe8e1f3b2 | ||
|
|
b12b3808fa | ||
|
|
08ec8fe709 | ||
|
|
f70cae6bf6 | ||
|
|
59fe373863 | ||
|
|
be58b4c1d8 | ||
|
|
96c520ae4d | ||
|
|
a9553b4945 | ||
|
|
fa516522e3 | ||
|
|
59e4b5da54 | ||
|
|
2fa3f94b4a | ||
|
|
a2f8461e26 | ||
|
|
cabaf079be | ||
|
|
d8de4a7ada | ||
|
|
e510a544b7 | ||
|
|
b2ee30630d | ||
|
|
ab0d4f364a | ||
|
|
6d9c6ae698 | ||
|
|
914af3deae | ||
|
|
4ad8e9ae78 | ||
|
|
1e0b496555 | ||
|
|
0088aa8b25 | ||
|
|
35a66df1e4 | ||
|
|
3b2fa64a82 | ||
|
|
3d1027f278 | ||
|
|
cc78386163 | ||
|
|
c240eed6a4 | ||
|
|
2a9b7f5d52 | ||
|
|
f2fbd29de5 | ||
|
|
c960176a2a | ||
|
|
fd4bae4cc1 | ||
|
|
ae32b7c19b |
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "betterauth",
|
||||
"version": "0.1.6",
|
||||
"version": "0.1.10",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "chat-rn",
|
||||
"version": "1.0.120",
|
||||
"version": "1.0.124",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"android": "react-native run-android",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "chat-vue",
|
||||
"version": "0.0.103",
|
||||
"version": "0.0.107",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-chat",
|
||||
"private": true,
|
||||
"version": "0.0.201",
|
||||
"version": "0.0.205",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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 }) => (
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "clerk",
|
||||
"private": true,
|
||||
"version": "0.0.100",
|
||||
"version": "0.0.104",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts" module>
|
||||
declare module 'jazz-svelte' {
|
||||
interface Register {
|
||||
Account: FileShareAccount;
|
||||
Account: typeof FileShareAccount;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "filestream",
|
||||
"private": true,
|
||||
"version": "0.0.40",
|
||||
"version": "0.0.44",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "form",
|
||||
"private": true,
|
||||
"version": "0.1.41",
|
||||
"version": "0.1.45",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "image-upload",
|
||||
"private": true,
|
||||
"version": "0.0.97",
|
||||
"version": "0.0.101",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-inspector-app",
|
||||
"private": true,
|
||||
"version": "0.0.150",
|
||||
"version": "0.0.154",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -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: {} },
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "multi-cursors",
|
||||
"private": true,
|
||||
"version": "0.0.93",
|
||||
"version": "0.0.97",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "multiauth",
|
||||
"private": true,
|
||||
"version": "0.0.41",
|
||||
"version": "0.0.45",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-music-player",
|
||||
"private": true,
|
||||
"version": "0.0.122",
|
||||
"version": "0.0.126",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "organization",
|
||||
"private": true,
|
||||
"version": "0.0.93",
|
||||
"version": "0.0.97",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "passkey-svelte",
|
||||
"version": "0.0.88",
|
||||
"version": "0.0.93",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "passkey",
|
||||
"private": true,
|
||||
"version": "0.0.98",
|
||||
"version": "0.0.102",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "passphrase",
|
||||
"private": true,
|
||||
"version": "0.0.95",
|
||||
"version": "0.0.99",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-password-manager",
|
||||
"private": true,
|
||||
"version": "0.0.119",
|
||||
"version": "0.0.123",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-pets",
|
||||
"private": true,
|
||||
"version": "0.0.217",
|
||||
"version": "0.0.221",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "reactions",
|
||||
"private": true,
|
||||
"version": "0.0.97",
|
||||
"version": "0.0.101",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "richtext-tiptap",
|
||||
"private": true,
|
||||
"version": "0.1.10",
|
||||
"version": "0.1.14",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "richtext",
|
||||
"private": true,
|
||||
"version": "0.0.87",
|
||||
"version": "0.0.91",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "todo-vue",
|
||||
"version": "0.0.101",
|
||||
"version": "0.0.105",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-todo",
|
||||
"private": true,
|
||||
"version": "0.0.216",
|
||||
"version": "0.0.220",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "version-history",
|
||||
"private": true,
|
||||
"version": "0.0.95",
|
||||
"version": "0.0.99",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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) {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 .",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user