Compare commits

...

20 Commits

Author SHA1 Message Date
Guido D'Orsi
be492a8184 chore(music-player): add the react-compiler linting rule 2024-10-14 22:12:50 +02:00
Guido D'Orsi
90149ac411 test: add subscribe tests 2024-10-14 20:40:08 +02:00
Guido D'Orsi
7bae6e43b7 fix(password-manager): fix infinite loop on the shared folder loading 2024-10-14 20:40:08 +02:00
Guido D'Orsi
32c2435cb1 fix(subscription): renew root value identity at each update 2024-10-14 20:40:08 +02:00
Guido D'Orsi
64feacd2c1 feat(react): migrate to useSyncExternalStore 2024-10-14 20:40:08 +02:00
Anselm
62bbe9a09b Release 2024-10-14 17:32:12 +01:00
Anselm Eickhoff
4dd80a65da Merge pull request #559 from gardencmp/expo-secure-store-for-jazz-react-native
Expo secure store for jazz react native
2024-10-14 17:30:15 +01:00
pax-k
52dd1d37ab fix(react-native): set ExpoSecureStoreAdapter values accessible AFTER_FIRST_UNLOCK 2024-10-14 19:26:05 +03:00
pax-k
b75df6fa5c chore: revert jazz-react-native-media-images bump 2024-10-14 19:03:37 +03:00
pax-k
b7639cffd3 chore: changeset 2024-10-14 19:02:52 +03:00
pax-k
21575f2e79 feat(react-native): replaced react-native-mmkv with expo-secure-store and initialize it by default as kvStore in createJazzRNApp() (BREAKING) 2024-10-14 19:00:36 +03:00
pax-k
35587aaacc chore: bump jazz-react-native-media-images 2024-10-14 17:34:26 +03:00
Anselm
2398dedbae FIx lockfile 2024-10-14 15:30:42 +01:00
Anselm
d545a3b8d6 Release 2024-10-14 15:27:53 +01:00
Anselm Eickhoff
dce673ff7a Merge pull request #546 from gardencmp/feat/client-batching
feat: enable batching on client peers
2024-10-14 15:27:20 +01:00
pax
d18f6bf371 Merge pull request #557 from gardencmp/fix-native-peer-dependencies
Weaker native peer dependencies
2024-10-14 17:25:42 +03:00
pax-k
32b05b69a3 chore: changeset 2024-10-14 17:25:01 +03:00
pax-k
c1a5c3cad7 fix(react-native): weaker peer dependencies 2024-10-14 17:23:43 +03:00
Guido D'Orsi
e82cf3dd71 chore: add changeset 2024-10-14 14:56:34 +02:00
Guido D'Orsi
c715587a54 feat: enable batching on client peers 2024-10-14 14:31:54 +02:00
72 changed files with 1028 additions and 351 deletions

View File

@@ -1,5 +1,11 @@
# @jazz-e2e/binarycostream
## 0.0.88
### Patch Changes
- jazz-react@0.8.7
## 0.0.87
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "@jazz-e2e/binarycostream",
"private": true,
"version": "0.0.87",
"version": "0.0.88",
"type": "module",
"scripts": {
"dev": "vite",
@@ -17,7 +17,7 @@
"cojson": "workspace:0.8.5",
"hash-slash": "workspace:0.2.1",
"is-ci": "^3.0.1",
"jazz-react": "workspace:0.8.6",
"jazz-react": "workspace:0.8.7",
"jazz-tools": "workspace:0.8.5",
"react": "^18.2.0",
"react-dom": "^18.2.0"

View File

@@ -1,5 +1,11 @@
# @jazz-e2e/covalues
## 0.0.87
### Patch Changes
- jazz-react@0.8.7
## 0.0.86
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "@jazz-e2e/covalues",
"private": true,
"version": "0.0.86",
"version": "0.0.87",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,12 @@
# jazz-example-book-shelf
## 0.1.3
### Patch Changes
- jazz-browser-media-images@0.8.7
- jazz-react@0.8.7
## 0.1.2
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-example-book-shelf",
"version": "0.1.2",
"version": "0.1.3",
"private": true,
"scripts": {
"dev": "next dev",
@@ -11,8 +11,8 @@
},
"dependencies": {
"clsx": "^2.0.0",
"jazz-browser-media-images": "workspace:0.8.6",
"jazz-react": "workspace:0.8.6",
"jazz-browser-media-images": "workspace:0.8.7",
"jazz-react": "workspace:0.8.7",
"jazz-tools": "workspace:0.8.5",
"next": "14.2.5",
"react": "^18.2.0",

View File

@@ -1,5 +1,12 @@
# jazz-example-chat
## 0.0.87
### Patch Changes
- jazz-react@0.8.7
- jazz-react-auth-clerk@0.8.7
## 0.0.86
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-chat-clerk",
"private": true,
"version": "0.0.86",
"version": "0.0.87",
"type": "module",
"scripts": {
"dev": "vite",
@@ -23,8 +23,8 @@
"clsx": "^2.0.0",
"cojson": "workspace:0.8.5",
"hash-slash": "workspace:0.2.1",
"jazz-react": "workspace:0.8.6",
"jazz-react-auth-clerk": "workspace:0.8.6",
"jazz-react": "workspace:0.8.7",
"jazz-react-auth-clerk": "workspace:0.8.7",
"jazz-tools": "workspace:0.8.5",
"lucide-react": "^0.274.0",
"qrcode": "^1.5.3",

View File

@@ -1,5 +1,22 @@
# chat-rn-clerk
## 1.0.3
### Patch Changes
- b7639cf: feat(react-native): replaced react-native-mmkv with expo-secure-store and initialize it by default as kvStore in createJazzRNApp() (BREAKING)
- Updated dependencies [b7639cf]
- jazz-react-native@0.8.8
## 1.0.2
### Patch Changes
- Updated dependencies [32b05b6]
- jazz-react-native-media-images@0.8.6
- jazz-react-native@0.8.7
- jazz-react-auth-clerk@0.8.7
## 1.0.1
### Patch Changes

View File

@@ -1,45 +1,46 @@
{
"expo": {
"name": "jazz-chat-rn-clerk",
"scheme": "jazz-chat-rn-clerk",
"slug": "jazz-chat-rn-clerk",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/images/icon.png",
"userInterfaceStyle": "light",
"splash": {
"image": "./assets/images/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.jazz.chatrnclerk"
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/images/adaptive-icon.png",
"backgroundColor": "#ffffff"
},
"package": "com.jazz.chatrnclerk"
},
"plugins": [
[
"expo-build-properties",
{
"ios": {
"newArchEnabled": true
},
"android": {
"newArchEnabled": true
}
}
]
],
"extra": {
"eas": {
"projectId": "ca3d46e5-a10a-47ec-9d77-3b841e1c62d4"
}
"expo": {
"name": "jazz-chat-rn-clerk",
"scheme": "jazz-chat-rn-clerk",
"slug": "jazz-chat-rn-clerk",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/images/icon.png",
"userInterfaceStyle": "light",
"splash": {
"image": "./assets/images/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.jazz.chatrnclerk"
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/images/adaptive-icon.png",
"backgroundColor": "#ffffff"
},
"package": "com.jazz.chatrnclerk"
},
"plugins": [
[
"expo-build-properties",
{
"ios": {
"newArchEnabled": true
},
"android": {
"newArchEnabled": true
}
}
],
"expo-secure-store"
],
"extra": {
"eas": {
"projectId": "ca3d46e5-a10a-47ec-9d77-3b841e1c62d4"
}
}
}
}

View File

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

View File

@@ -1,7 +1,4 @@
import { createJazzRNApp } from "jazz-react-native";
import { MMKVStorage } from "./mmkv-storage";
const nativeStorage = new MMKVStorage();
export const Jazz = createJazzRNApp({ nativeStorage });
export const Jazz = createJazzRNApp();
export const { useAccount, useCoState, useAcceptInvite } = Jazz;

View File

@@ -1,25 +0,0 @@
import { MMKV } from "react-native-mmkv";
import type { NativeStorage } from "jazz-react-native";
const storage = new MMKV();
export class MMKVStorage implements NativeStorage {
get(key: string): Promise<string | undefined> {
return Promise.resolve(storage.getString(key));
}
set(key: string, value: string): Promise<void> {
storage.set(key, value);
return Promise.resolve();
}
delete(key: string): Promise<void> {
storage.delete(key);
return Promise.resolve();
}
clearAll(): Promise<void> {
storage.clearAll();
return Promise.resolve();
}
}

View File

@@ -1 +0,0 @@
EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_ZXZpZGVudC1kYW5lLTg5LmNsZXJrLmFjY291bnRzLmRldiQ

View File

@@ -1,5 +1,20 @@
# chat-rn
## 1.0.5
### Patch Changes
- b7639cf: feat(react-native): replaced react-native-mmkv with expo-secure-store and initialize it by default as kvStore in createJazzRNApp() (BREAKING)
- Updated dependencies [b7639cf]
- jazz-react-native@0.8.8
## 1.0.4
### Patch Changes
- Updated dependencies [32b05b6]
- jazz-react-native@0.8.7
## 1.0.3
### Patch Changes

View File

@@ -1,46 +1,47 @@
{
"expo": {
"name": "jazz-chat-rn",
"scheme": "jazz-chat-rn",
"slug": "jazz-chat-rn",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "light",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.jazz.chatrn"
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
},
"package": "com.jazz.chatrn"
},
"plugins": [
[
"expo-build-properties",
{
"ios": {
"newArchEnabled": true
},
"android": {
"newArchEnabled": true
}
}
]
],
"extra": {
"eas": {
"projectId": "e0e61872-1906-4c84-b9d8-9be77355cad0"
}
},
"owner": "paxx"
}
"expo": {
"name": "jazz-chat-rn",
"scheme": "jazz-chat-rn",
"slug": "jazz-chat-rn",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "light",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.jazz.chatrn"
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
},
"package": "com.jazz.chatrn"
},
"plugins": [
[
"expo-build-properties",
{
"ios": {
"newArchEnabled": true
},
"android": {
"newArchEnabled": true
}
}
],
"expo-secure-store"
],
"extra": {
"eas": {
"projectId": "e0e61872-1906-4c84-b9d8-9be77355cad0"
}
},
"owner": "paxx"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "chat-rn",
"version": "1.0.3",
"version": "1.0.5",
"main": "index.js",
"scripts": {
"build": "expo export -p ios",
@@ -23,6 +23,7 @@
"expo-crypto": "~13.0.2",
"expo-dev-client": "~4.0.28",
"expo-linking": "~6.3.1",
"expo-secure-store": "~13.0.2",
"expo-status-bar": "~1.12.1",
"expo-web-browser": "~13.0.3",
"jazz-react-native": "workspace:*",

View File

@@ -1,7 +1,4 @@
import { createJazzRNApp } from "jazz-react-native";
import { MMKVStorage } from "./mmkv-storage";
const nativeStorage = new MMKVStorage();
export const Jazz = createJazzRNApp({ nativeStorage });
export const Jazz = createJazzRNApp();
export const { useAccount, useCoState, useAcceptInvite } = Jazz;

View File

@@ -1,25 +0,0 @@
import { MMKV } from "react-native-mmkv";
import type { NativeStorage } from "jazz-react-native";
const storage = new MMKV();
export class MMKVStorage implements NativeStorage {
get(key: string): Promise<string | undefined> {
return Promise.resolve(storage.getString(key));
}
set(key: string, value: string): Promise<void> {
storage.set(key, value);
return Promise.resolve();
}
delete(key: string): Promise<void> {
storage.delete(key);
return Promise.resolve();
}
clearAll(): Promise<void> {
storage.clearAll();
return Promise.resolve();
}
}

View File

@@ -1,5 +1,11 @@
# jazz-example-chat
## 0.0.89
### Patch Changes
- jazz-react@0.8.7
## 0.0.88
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-chat",
"private": true,
"version": "0.0.88",
"version": "0.0.89",
"type": "module",
"scripts": {
"dev": "vite",
@@ -24,7 +24,7 @@
"clsx": "^2.0.0",
"cojson": "workspace:0.8.5",
"hash-slash": "workspace:0.2.1",
"jazz-react": "workspace:0.8.6",
"jazz-react": "workspace:0.8.7",
"jazz-tools": "workspace:0.8.5",
"lucide-react": "^0.274.0",
"qrcode": "^1.5.3",

View File

@@ -1,5 +1,12 @@
# jazz-example-inspector
## 0.0.65
### Patch Changes
- Updated dependencies [e82cf3d]
- cojson-transport-ws@0.8.7
## 0.0.64
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-inspector",
"private": true,
"version": "0.0.64",
"version": "0.0.65",
"type": "module",
"scripts": {
"dev": "vite",
@@ -16,7 +16,7 @@
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"cojson": "workspace:0.8.5",
"cojson-transport-ws": "workspace:0.8.6",
"cojson-transport-ws": "workspace:0.8.7",
"hash-slash": "workspace:0.2.1",
"lucide-react": "^0.274.0",
"qrcode": "^1.5.3",

View File

@@ -9,11 +9,12 @@ module.exports = {
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
plugins: ['react-refresh', "eslint-plugin-react-compiler"],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
"react-compiler/react-compiler": "error"
},
}

View File

@@ -1,5 +1,11 @@
# jazz-example-musicplayer
## 0.0.9
### Patch Changes
- jazz-react@0.8.7
## 0.0.8
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-music-player",
"private": true,
"version": "0.0.8",
"version": "0.0.9",
"type": "module",
"scripts": {
"dev": "vite",
@@ -20,7 +20,7 @@
"@radix-ui/react-toast": "^1.1.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"jazz-react": "workspace:0.8.6",
"jazz-react": "workspace:0.8.7",
"jazz-tools": "workspace:0.8.5",
"lucide-react": "^0.274.0",
"react": "^18.2.0",
@@ -38,6 +38,7 @@
"@vitejs/plugin-react-swc": "^3.3.2",
"autoprefixer": "^10.4.14",
"eslint": "^8.46.0",
"eslint-plugin-react-compiler": "0.0.0-experimental-fa06e2c-20241014",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3",
"postcss": "^8.4.27",

View File

@@ -5,7 +5,7 @@ import { usePlayState } from "./lib/audio/usePlayState";
import { SidePanel } from "./components/SidePanel";
import { FileUploadButton } from "./components/FileUploadButton";
import { Button } from "./components/ui/button";
import { createNewPlaylist, uploadMusicTracks } from "./4_actions";
import { createNewPlaylist, updatePlaylistTitle, uploadMusicTracks } from "./4_actions";
import { useNavigate, useParams } from "react-router";
import { ID } from "jazz-tools";
import { Playlist } from "./1_schema";
@@ -62,7 +62,7 @@ export function HomePage({ mediaPlayer }: { mediaPlayer: MediaPlayer }) {
) => {
if (!playlist) return;
playlist.title = evt.target.value;
updatePlaylistTitle(playlist, evt.target.value);
};
const handlePlaylistShareClick = async () => {

View File

@@ -128,3 +128,19 @@ export async function addTrackToPlaylist(
playlist.tracks?.push(trackClone);
}
export async function updatePlaylistTitle(playlist: Playlist, title: string) {
playlist.title = title;
}
export async function updateMusicTrackTitle(track: MusicTrack, title: string) {
track.title = title;
}
export async function updateActivePlaylist(playlist: Playlist, me: MusicaAccount) {
me.root!.activePlaylist = playlist ?? me.root!.rootPlaylist;
}
export async function updateActiveTrack(track: MusicTrack, me: MusicaAccount) {
me.root!.activeTrack = track;
}

View File

@@ -5,6 +5,7 @@ import { MusicTrack, Playlist } from "@/1_schema";
import { useRef, useState } from "react";
import { getNextTrack, getPrevTrack } from "./lib/getters";
import { BinaryCoStream, ID } from "jazz-tools";
import { updateActivePlaylist, updateActiveTrack } from "./4_actions";
export function useMediaPlayer() {
const { me } = useAccount();
@@ -39,7 +40,7 @@ export function useMediaPlayer() {
return;
}
me.root.activeTrack = track;
updateActiveTrack(track, me);
await playMedia(file);
@@ -52,7 +53,7 @@ export function useMediaPlayer() {
const track = await getNextTrack(me);
if (track) {
me.root.activeTrack = track;
updateActiveTrack(track, me);
await loadTrack(track);
}
}
@@ -78,7 +79,7 @@ export function useMediaPlayer() {
return;
}
me.root.activePlaylist = playlist ?? me.root.rootPlaylist;
updateActivePlaylist(playlist!, me);
await loadTrack(track);

View File

@@ -10,7 +10,7 @@ import { MoreHorizontal } from "lucide-react";
import { ChangeEvent } from "react";
import { Button } from "./ui/button";
import { useAccount, useCoState } from "@/2_main";
import { addTrackToPlaylist } from "@/4_actions";
import { addTrackToPlaylist, updateMusicTrackTitle } from "@/4_actions";
import { ID } from "jazz-tools";
export function MusicTrackRow({
@@ -30,7 +30,8 @@ export function MusicTrackRow({
function handleTrackTitleChange(evt: ChangeEvent<HTMLInputElement>) {
if (!track) return;
track.title = evt.target.value;
updateMusicTrackTitle(track, evt.target.value);
}
const { me } = useAccount({

View File

@@ -1,5 +1,11 @@
# jazz-password-manager
## 0.0.8
### Patch Changes
- jazz-react@0.8.7
## 0.0.7
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-password-manager",
"private": true,
"version": "0.0.7",
"version": "0.0.8",
"type": "module",
"scripts": {
"dev": "vite",
@@ -11,7 +11,7 @@
"clean-install": "rm -rf node_modules pnpm-lock.yaml && pnpm install"
},
"dependencies": {
"jazz-react": "workspace:0.8.6",
"jazz-react": "workspace:0.8.7",
"jazz-tools": "workspace:0.8.5",
"react": "^18.2.0",
"react-dom": "^18.2.0",

View File

@@ -5,9 +5,13 @@ import Table from "./components/table";
import NewItemModal from "./components/new-item-modal";
import InviteModal from "./components/invite-modal";
import { saveItem, deleteItem, createFolder, updateItem } from "./4_actions";
import { saveItem, deleteItem, createFolder, updateItem, addSharedFolder } from "./4_actions";
import { Alert, AlertDescription } from "./components/alert";
import { Folder, FolderList, PasswordItem } from "./1_schema";
import {
Folder,
FolderList,
PasswordItem,
} from "./1_schema";
import { useAccount, useCoState } from "./2_main";
import { CoMapInit, Group, ID } from "jazz-tools";
import { useNavigate, useParams } from "react-router-dom";
@@ -17,20 +21,19 @@ const VaultPage: React.FC = () => {
const { me, logOut } = useAccount();
const sharedFolderId = useParams<{ sharedFolderId: ID<Folder> }>()
.sharedFolderId;
const sharedFolder = useCoState(Folder, sharedFolderId);
const navigate = useNavigate();
useEffect(() => {
if (!sharedFolderId || !sharedFolder || !me.root?.folders) return;
const existsIndex = me.root?.folders.findIndex(
(f) => f?.id === sharedFolder.id
);
if (existsIndex > -1) {
me.root?.folders?.splice(existsIndex, 1);
}
me.root?.folders?.push(sharedFolder);
navigate("/vault");
}, [sharedFolder, me.root?.folders, sharedFolderId, navigate]);
if (!sharedFolderId) return;
addSharedFolder(sharedFolderId, me).then(() => {
navigate("/vault");
});
// We want to trigger this only on mount
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const items = me.root?.folders?.flatMap(
(folder) =>

View File

@@ -1,4 +1,4 @@
import { Group } from "jazz-tools";
import { Group, ID } from "jazz-tools";
import {
Folder,
PasswordItem,
@@ -8,6 +8,7 @@ import {
import { CoMapInit } from "jazz-tools";
import { createInviteLink } from "jazz-react";
import { PasswordItemFormValues } from "./types";
import { waitForCoValue } from "./lib/waitForCoValue";
export const saveItem = (item: CoMapInit<PasswordItem>): PasswordItem => {
const passwordItem = PasswordItem.create(item, {
@@ -54,3 +55,24 @@ export const shareFolder = (
}
return undefined;
};
export async function addSharedFolder(
sharedFolderId: ID<Folder>,
me: PasswordManagerAccount) {
const [sharedFolder, account] = await Promise.all([
await waitForCoValue(Folder, sharedFolderId, me, Boolean, {}),
await waitForCoValue(PasswordManagerAccount, me.id, me, Boolean, {
root: {
folders: [],
},
}),
]);
if (!account.root?.folders) return;
const found = account.root.folders.some((f) => f?.id === sharedFolder.id);
if (!found) {
account.root.folders.push(sharedFolder);
}
}

View File

@@ -0,0 +1,39 @@
import {
Account,
CoValue,
CoValueClass,
DepthsIn,
ID,
subscribeToCoValue,
} from "jazz-tools";
export function waitForCoValue<T extends CoValue>(
coMap: CoValueClass<T>,
valueId: ID<T>,
account: Account,
predicate: (value: T) => boolean,
depth: DepthsIn<T>
) {
return new Promise<T>((resolve) => {
function subscribe() {
const unsubscribe = subscribeToCoValue(
coMap,
valueId,
account,
depth,
(value) => {
if (predicate(value)) {
resolve(value);
unsubscribe();
}
},
() => {
unsubscribe();
setTimeout(subscribe, 100);
}
);
}
subscribe();
});
}

View File

@@ -1,5 +1,12 @@
# jazz-example-pets
## 0.0.106
### Patch Changes
- jazz-browser-media-images@0.8.7
- jazz-react@0.8.7
## 0.0.105
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-pets",
"private": true,
"version": "0.0.105",
"version": "0.0.106",
"type": "module",
"scripts": {
"dev": "vite",
@@ -23,8 +23,8 @@
"@radix-ui/react-toast": "^1.1.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"jazz-browser-media-images": "workspace:0.8.6",
"jazz-react": "workspace:0.8.6",
"jazz-browser-media-images": "workspace:0.8.7",
"jazz-react": "workspace:0.8.7",
"jazz-tools": "workspace:0.8.5",
"lucide-react": "^0.274.0",
"qrcode": "^1.5.3",
@@ -50,7 +50,7 @@
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3",
"is-ci": "^3.0.1",
"jazz-run": "workspace:0.8.6",
"jazz-run": "workspace:0.8.7",
"postcss": "^8.4.27",
"tailwindcss": "3.3.2",
"typescript": "^5.3.3",

View File

@@ -1,5 +1,11 @@
# jazz-example-todo
## 0.0.105
### Patch Changes
- jazz-react@0.8.7
## 0.0.104
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-todo",
"private": true,
"version": "0.0.104",
"version": "0.0.105",
"type": "module",
"scripts": {
"dev": "vite",
@@ -20,7 +20,7 @@
"@radix-ui/react-toast": "^1.1.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"jazz-react": "workspace:0.8.6",
"jazz-react": "workspace:0.8.7",
"jazz-tools": "workspace:0.8.5",
"lucide-react": "^0.274.0",
"qrcode": "^1.5.3",

View File

@@ -1,5 +1,11 @@
# cojson-transport-nodejs-ws
## 0.8.7
### Patch Changes
- e82cf3d: Enable WebSocket batching on client peers
## 0.8.6
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "cojson-transport-ws",
"type": "module",
"version": "0.8.6",
"version": "0.8.7",
"main": "dist/index.js",
"types": "src/index.ts",
"license": "MIT",

View File

@@ -25,7 +25,7 @@ export function createWebSocketPeer({
websocket,
role,
expectPings = true,
batchingByDefault = false, // Keeping this false until we release batching upgrade to the mesh
batchingByDefault = true,
}: CreateWebSocketPeerOpts): Peer {
const incoming = new cojsonInternals.Channel<
SyncMessage | DisconnectedError | PingTimeoutError

View File

@@ -1,5 +1,11 @@
# jazz-browser-media-images
## 0.8.7
### Patch Changes
- jazz-browser@0.8.7
## 0.8.6
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-browser-auth-clerk",
"version": "0.8.6",
"version": "0.8.7",
"type": "module",
"main": "dist/index.js",
"types": "src/index.ts",
@@ -11,7 +11,7 @@
},
"dependencies": {
"cojson": "workspace:0.8.5",
"jazz-browser": "workspace:0.8.6",
"jazz-browser": "workspace:0.8.7",
"jazz-tools": "workspace:0.8.5"
},
"scripts": {

View File

@@ -1,5 +1,11 @@
# jazz-browser-media-images
## 0.8.7
### Patch Changes
- jazz-browser@0.8.7
## 0.8.6
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-browser-media-images",
"version": "0.8.6",
"version": "0.8.7",
"type": "module",
"main": "dist/index.js",
"types": "src/index.ts",
@@ -8,7 +8,7 @@
"dependencies": {
"@types/image-blob-reduce": "^4.1.1",
"image-blob-reduce": "^4.1.0",
"jazz-browser": "workspace:0.8.6",
"jazz-browser": "workspace:0.8.7",
"jazz-tools": "workspace:0.8.5",
"pica": "^9.0.1",
"typescript": "^5.3.3"

View File

@@ -1,5 +1,12 @@
# jazz-browser
## 0.8.7
### Patch Changes
- Updated dependencies [e82cf3d]
- cojson-transport-ws@0.8.7
## 0.8.6
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-browser",
"version": "0.8.6",
"version": "0.8.7",
"type": "module",
"main": "dist/index.js",
"types": "src/index.ts",
@@ -9,7 +9,7 @@
"@scure/bip39": "^1.3.0",
"cojson": "workspace:0.8.5",
"cojson-storage-indexeddb": "workspace:0.8.5",
"cojson-transport-ws": "workspace:0.8.6",
"cojson-transport-ws": "workspace:0.8.7",
"jazz-tools": "workspace:0.8.5",
"typescript": "^5.3.3"
},

View File

@@ -1,5 +1,12 @@
# jazz-autosub
## 0.8.7
### Patch Changes
- Updated dependencies [e82cf3d]
- cojson-transport-ws@0.8.7
## 0.8.6
### Patch Changes

View File

@@ -5,10 +5,10 @@
"types": "src/index.ts",
"type": "module",
"license": "MIT",
"version": "0.8.6",
"version": "0.8.7",
"dependencies": {
"cojson": "workspace:0.8.5",
"cojson-transport-ws": "workspace:0.8.6",
"cojson-transport-ws": "workspace:0.8.7",
"jazz-tools": "workspace:0.8.5",
"ws": "^8.14.2"
},

View File

@@ -1,5 +1,12 @@
# jazz-browser-media-images
## 0.8.7
### Patch Changes
- jazz-browser-auth-clerk@0.8.7
- jazz-react@0.8.7
## 0.8.6
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-react-auth-clerk",
"version": "0.8.6",
"version": "0.8.7",
"type": "module",
"main": "dist/index.js",
"types": "src/index.tsx",
@@ -11,8 +11,8 @@
},
"dependencies": {
"cojson": "workspace:0.8.5",
"jazz-browser-auth-clerk": "workspace:0.8.6",
"jazz-react": "workspace:0.8.6",
"jazz-browser-auth-clerk": "workspace:0.8.7",
"jazz-react": "workspace:0.8.7",
"jazz-tools": "workspace:0.8.5"
},
"peerDependencies": {

View File

@@ -1,5 +1,11 @@
# jazz-browser-media-images
## 0.8.6
### Patch Changes
- 32b05b6: weaker peer dependencies
## 0.8.5
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-react-native-media-images",
"version": "0.8.5",
"version": "0.8.6",
"type": "module",
"main": "dist/index.js",
"types": "src/index.ts",
@@ -15,9 +15,9 @@
"react-native": "~0.74.5"
},
"peerDependencies": {
"@bam.tech/react-native-image-resizer": "^3.0.10",
"expo-file-system": "^17.0.1",
"react-native": "~0.74.5"
"@bam.tech/react-native-image-resizer": "*",
"expo-file-system": "*",
"react-native": "*"
},
"scripts": {
"lint": "eslint . --ext ts,tsx",

View File

@@ -1,5 +1,19 @@
# jazz-browser
## 0.8.8
### Patch Changes
- b7639cf: feat(react-native): replaced react-native-mmkv with expo-secure-store and initialize it by default as kvStore in createJazzRNApp() (BREAKING)
## 0.8.7
### Patch Changes
- 32b05b6: weaker peer dependencies
- Updated dependencies [e82cf3d]
- cojson-transport-ws@0.8.7
## 0.8.6
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-react-native",
"version": "0.8.6",
"version": "0.8.8",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -22,16 +22,16 @@
"typescript": "^5.3.3"
},
"peerDependencies": {
"@react-native-community/netinfo": "^11.4.1",
"expo-linking": "~6.3.1",
"react-native": "0.74.2",
"react-native-mmkv": "3.0.1"
"@react-native-community/netinfo": "*",
"expo-linking": "*",
"expo-secure-store": "*",
"react-native": "*"
},
"devDependencies": {
"@react-native-community/netinfo": "^11.3.1",
"expo-linking": "~6.3.1",
"react-native": "~0.74.5",
"react-native-mmkv": "3.0.1"
"expo-secure-store": "~13.0.2",
"react-native": "~0.74.5"
},
"scripts": {
"dev": "tsc --watch --sourceMap --outDir dist",

View File

@@ -1,6 +1,6 @@
import { AgentSecret } from "cojson";
import { Account, AuthMethod, AuthResult, ID } from "jazz-tools";
import NativeStorageContext, { NativeStorage } from "../native-storage.js";
import { KvStore, KvStoreContext } from "../storage/kv-store-context.js";
type StorageData = {
accountID: ID<Account>;
@@ -26,7 +26,7 @@ const localStorageKey = "demo-auth-logged-in-secret";
export class RNDemoAuth implements AuthMethod {
private constructor(
private driver: RNDemoAuth.Driver,
private storage: NativeStorage,
private kvStore: KvStore,
) {}
public static async init(
@@ -38,40 +38,40 @@ export class RNDemoAuth implements AuthMethod {
};
},
) {
const storage = NativeStorageContext.getInstance().getStorage();
const kvStore = KvStoreContext.getInstance().getStorage();
for (const [name, credentials] of Object.entries(seedAccounts || {})) {
const storageData = JSON.stringify(
credentials satisfies StorageData,
);
if (
!(
(await storage.get("demo-auth-existing-users"))?.split(
(await kvStore.get("demo-auth-existing-users"))?.split(
",",
) as string[] | undefined
)?.includes(name)
) {
const existingUsers = await storage.get(
const existingUsers = await kvStore.get(
"demo-auth-existing-users",
);
if (existingUsers) {
await storage.set(
await kvStore.set(
"demo-auth-existing-users",
existingUsers + "," + name,
);
} else {
await storage.set("demo-auth-existing-users", name);
await kvStore.set("demo-auth-existing-users", name);
}
}
await storage.set("demo-auth-existing-users-" + name, storageData);
await kvStore.set("demo-auth-existing-users-" + name, storageData);
}
return new RNDemoAuth(driver, storage);
return new RNDemoAuth(driver, kvStore);
}
async start() {
try {
if (await this.storage.get(localStorageKey)) {
if (await this.kvStore.get(localStorageKey)) {
const localStorageData = JSON.parse(
(await this.storage.get(localStorageKey)) ?? "{}",
(await this.kvStore.get(localStorageKey)) ?? "{}",
) as StorageData;
const accountID = localStorageData.accountID as ID<Account>;
@@ -87,7 +87,7 @@ export class RNDemoAuth implements AuthMethod {
this.driver.onError(error);
},
logOut: async () => {
void (await this.storage.delete(localStorageKey));
void (await this.kvStore.delete(localStorageKey));
},
} satisfies AuthResult;
} else {
@@ -109,7 +109,7 @@ export class RNDemoAuth implements AuthMethod {
// Retrieve the list of existing users
const existingUsers =
await this.storage.get(
await this.kvStore.get(
"demo-auth-existing-users",
);
const existingUsernames = existingUsers
@@ -129,11 +129,11 @@ export class RNDemoAuth implements AuthMethod {
}
// Save credentials using the unique username
await this.storage.set(
await this.kvStore.set(
localStorageKey,
storageData,
);
await this.storage.set(
await this.kvStore.set(
"demo-auth-existing-users-" +
uniqueUsername,
storageData,
@@ -143,7 +143,7 @@ export class RNDemoAuth implements AuthMethod {
const updatedUsers = existingUsers
? `${existingUsers},${uniqueUsername}`
: uniqueUsername;
await this.storage.set(
await this.kvStore.set(
"demo-auth-existing-users",
updatedUsers,
);
@@ -157,7 +157,7 @@ export class RNDemoAuth implements AuthMethod {
this.driver.onError(error);
},
logOut: async () => {
void (await this.storage.delete(
void (await this.kvStore.delete(
localStorageKey,
));
},
@@ -166,7 +166,7 @@ export class RNDemoAuth implements AuthMethod {
getExistingUsers: async () => {
return (
(
await this.storage.get(
await this.kvStore.get(
"demo-auth-existing-users",
)
)?.split(",") ?? []
@@ -174,12 +174,12 @@ export class RNDemoAuth implements AuthMethod {
},
logInAs: async (existingUser) => {
const storageData = JSON.parse(
(await this.storage.get(
(await this.kvStore.get(
"demo-auth-existing-users-" + existingUser,
)) ?? "{}",
) as StorageData;
await this.storage.set(
await this.kvStore.set(
localStorageKey,
JSON.stringify(storageData),
);
@@ -197,7 +197,7 @@ export class RNDemoAuth implements AuthMethod {
this.driver.onError(error);
},
logOut: async () => {
void (await this.storage.delete(
void (await this.kvStore.delete(
localStorageKey,
));
},
@@ -214,6 +214,6 @@ export class RNDemoAuth implements AuthMethod {
}
async function logOut() {
const storage = NativeStorageContext.getInstance().getStorage();
void (await storage.delete(localStorageKey));
const kvStore = KvStoreContext.getInstance().getStorage();
void (await kvStore.delete(localStorageKey));
}

View File

@@ -18,13 +18,12 @@ import {
import { PureJSCrypto } from "jazz-tools/native";
import { RawAccountID } from "cojson";
import { createWebSocketPeer } from "cojson-transport-ws";
import { MMKV } from "react-native-mmkv";
import NetInfo from "@react-native-community/netinfo";
import * as Linking from "expo-linking";
export { RNDemoAuth } from "./auth/DemoAuthMethod.js";
import { NativeStorageContext } from "./native-storage.js";
import { KvStoreContext } from "./storage/kv-store-context.js";
/** @category Context Creation */
export type BrowserContext<Acc extends Account> = {
@@ -182,12 +181,12 @@ export async function provideLockSession(
) {
const sessionDone = () => {};
const storage = NativeStorageContext.getInstance().getStorage();
const kvStore = KvStoreContext.getInstance().getStorage();
const sessionID =
((await storage.get(accountID)) as SessionID) ||
((await kvStore.get(accountID)) as SessionID) ||
crypto.newRandomSessionID(accountID as RawAccountID | AgentID);
await storage.set(accountID, sessionID);
await kvStore.set(accountID, sessionID);
return Promise.resolve({
sessionID,
@@ -272,4 +271,4 @@ export function parseInviteLink<C extends CoValue>(
export * from "./provider.js";
export * from "./auth/auth.js";
export * from "./native-storage.js";
export * from "./storage/kv-store-context.js";

View File

@@ -1,35 +0,0 @@
export interface NativeStorage {
get(key: string): Promise<string | undefined>;
set(key: string, value: string): Promise<void>;
delete(key: string): Promise<void>;
clearAll(): Promise<void>;
}
export class NativeStorageContext {
private static instance: NativeStorageContext;
private storageInstance: NativeStorage | null = null;
private constructor() {}
public static getInstance(): NativeStorageContext {
if (!NativeStorageContext.instance) {
NativeStorageContext.instance = new NativeStorageContext();
}
return NativeStorageContext.instance;
}
public initialize(db: NativeStorage): void {
if (!this.storageInstance) {
this.storageInstance = db;
}
}
public getStorage(): NativeStorage {
if (!this.storageInstance) {
throw new Error("Storage instance is not initialized.");
}
return this.storageInstance;
}
}
export default NativeStorageContext;

View File

@@ -16,25 +16,26 @@ import {
BrowserContext,
BrowserGuestContext,
createJazzRNContext,
NativeStorageContext,
NativeStorage,
KvStoreContext,
parseInviteLink,
} from "./index.js";
import { Linking } from "react-native";
import { ExpoSecureStoreAdapter } from "./storage/expo-secure-store-adapter.js";
/** @category Context & Hooks */
export function createJazzRNApp<Acc extends Account>({
nativeStorage,
kvStore = new ExpoSecureStoreAdapter(),
AccountSchema = Account as unknown as AccountClass<Acc>,
}: {
nativeStorage: NativeStorage;
AccountSchema?: AccountClass<Acc>;
}): JazzReactApp<Acc> {
} = {}): JazzReactApp<Acc> {
const JazzContext = React.createContext<
BrowserContext<Acc> | BrowserGuestContext | undefined
>(undefined);
NativeStorageContext.getInstance().initialize(nativeStorage);
if (!kvStore) {
throw new Error("kvStore is required");
}
KvStoreContext.getInstance().initialize(kvStore);
function Provider({
children,

View File

@@ -0,0 +1,29 @@
import * as SecureStore from "expo-secure-store";
import type { KvStore } from "./kv-store-context.js";
export class ExpoSecureStoreAdapter implements KvStore {
get(key: string): Promise<string | null> {
return SecureStore.getItemAsync(key, {
requireAuthentication: SecureStore.canUseBiometricAuthentication(),
keychainAccessible: SecureStore.AFTER_FIRST_UNLOCK,
});
}
async set(key: string, value: string): Promise<void> {
return SecureStore.setItemAsync(key, value, {
requireAuthentication: SecureStore.canUseBiometricAuthentication(),
keychainAccessible: SecureStore.AFTER_FIRST_UNLOCK,
});
}
async delete(key: string): Promise<void> {
return SecureStore.deleteItemAsync(key, {
requireAuthentication: SecureStore.canUseBiometricAuthentication(),
keychainAccessible: SecureStore.AFTER_FIRST_UNLOCK,
});
}
async clearAll(): Promise<void> {
throw new Error("Not implemented");
}
}

View File

@@ -0,0 +1,35 @@
export interface KvStore {
get(key: string): Promise<string | null>;
set(key: string, value: string): Promise<void>;
delete(key: string): Promise<void>;
clearAll(): Promise<void>;
}
export class KvStoreContext {
private static instance: KvStoreContext;
private storageInstance: KvStore | null = null;
private constructor() {}
public static getInstance(): KvStoreContext {
if (!KvStoreContext.instance) {
KvStoreContext.instance = new KvStoreContext();
}
return KvStoreContext.instance;
}
public initialize(store: KvStore): void {
if (!this.storageInstance) {
this.storageInstance = store;
}
}
public getStorage(): KvStore {
if (!this.storageInstance) {
throw new Error("Storage instance is not initialized.");
}
return this.storageInstance;
}
}
export default KvStoreContext;

View File

@@ -1,5 +1,11 @@
# jazz-react
## 0.8.7
### Patch Changes
- jazz-browser@0.8.7
## 0.8.6
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-react",
"version": "0.8.6",
"version": "0.8.7",
"type": "module",
"main": "dist/index.js",
"types": "src/index.ts",
@@ -8,7 +8,7 @@
"dependencies": {
"@scure/bip39": "^1.3.0",
"cojson": "workspace:0.8.5",
"jazz-browser": "workspace:0.8.6",
"jazz-browser": "workspace:0.8.7",
"jazz-tools": "workspace:0.8.5",
"typescript": "^5.3.3"
},

View File

@@ -16,7 +16,7 @@ import {
DeeplyLoaded,
DepthsIn,
ID,
subscribeToCoValue,
createCoValueObservable,
} from "jazz-tools";
/** @category Context & Hooks */
@@ -152,30 +152,38 @@ export function createJazzReactApp<Acc extends Account>({
id: ID<V> | undefined,
depth: D & DepthsIn<V> = [] as D & DepthsIn<V>,
): DeeplyLoaded<V, D> | undefined {
const [state, setState] = useState<{
value: DeeplyLoaded<V, D> | undefined;
}>({ value: undefined });
const context = React.useContext(JazzContext);
if (!context) {
throw new Error("useCoState must be used within a JazzProvider");
}
useEffect(() => {
if (!id) return;
const [observable] = React.useState(() => createCoValueObservable());
return subscribeToCoValue(
Schema,
id,
"me" in context ? context.me : context.guest,
depth,
(value) => {
setState({ value });
const value = React.useSyncExternalStore<
DeeplyLoaded<V, D> | undefined
>(
React.useCallback(
(callback) => {
if (!id) return () => {};
const agent = "me" in context ? context.me : context.guest;
return observable.subscribe(
Schema,
id,
agent,
depth,
callback,
);
},
);
}, [Schema, id, context]);
[Schema, id, context],
),
() => observable.getCurrentValue(),
() => observable.getCurrentValue(),
);
return state.value;
return value;
}
function useAcceptInvite<V extends CoValue>({

View File

@@ -1,5 +1,12 @@
# jazz-run
## 0.8.7
### Patch Changes
- Updated dependencies [e82cf3d]
- cojson-transport-ws@0.8.7
## 0.8.6
### Patch Changes

View File

@@ -3,7 +3,7 @@
"bin": "./dist/index.js",
"type": "module",
"license": "MIT",
"version": "0.8.6",
"version": "0.8.7",
"scripts": {
"lint": "eslint . --ext ts,tsx",
"format": "prettier --write './src/**/*.{ts,tsx}'",
@@ -19,7 +19,7 @@
"@effect/typeclass": "^0.25.5",
"cojson": "workspace:0.8.5",
"cojson-storage-sqlite": "workspace:0.8.5",
"cojson-transport-ws": "workspace:0.8.6",
"cojson-transport-ws": "workspace:0.8.7",
"effect": "^3.6.5",
"jazz-tools": "workspace:0.8.5",
"ws": "^8.14.2"

View File

@@ -213,6 +213,40 @@ export function subscribeToCoValue<V extends CoValue, Depth>(
};
}
export function createCoValueObservable<V extends CoValue, Depth>() {
let currentValue: DeeplyLoaded<V, Depth> | undefined = undefined;
function subscribe(
cls: CoValueClass<V>,
id: ID<V>,
as: Account | AnonymousJazzAgent,
depth: Depth & DepthsIn<V>,
listener: () => void,
onUnavailable?: () => void,
) {
const unsubscribe = subscribeToCoValue(
cls,
id,
as,
depth,
(value) => {
currentValue = value;
listener();
},
onUnavailable,
);
return unsubscribe
}
const observable = {
getCurrentValue: () => currentValue,
subscribe,
};
return observable;
}
export function subscribeToExistingCoValue<V extends CoValue, Depth>(
existing: V,
depth: Depth & DepthsIn<V>,

View File

@@ -21,7 +21,7 @@ export { ImageDefinition } from "./internal.js";
export { CoValueBase, type CoValueClass } from "./internal.js";
export type { DepthsIn, DeeplyLoaded } from "./internal.js";
export { loadCoValue, subscribeToCoValue } from "./internal.js";
export { loadCoValue, subscribeToCoValue, createCoValueObservable } from "./internal.js";
export {
type AuthMethod,

View File

@@ -26,10 +26,10 @@ export class SubscriptionScope<Root extends CoValue> {
>();
rootEntry: {
state: "loaded";
value: Root;
value: RawCoValue;
rawUnsub: () => void;
};
onUpdate: (newRoot: Root) => void;
scheduleUpdate: () => void;
scheduledUpdate: boolean = false;
cachedValues: { [id: ID<CoValue>]: CoValue } = {};
parents: { [id: ID<CoValue>]: Set<ID<CoValue>> } = {};
@@ -41,7 +41,7 @@ export class SubscriptionScope<Root extends CoValue> {
) {
this.rootEntry = {
state: "loaded" as const,
value: root,
value: root._raw,
rawUnsub: () => {}, // placeholder
};
this.entries.set(root.id, this.rootEntry);
@@ -49,22 +49,21 @@ export class SubscriptionScope<Root extends CoValue> {
subscriptionsScopes.set(root, this);
this.subscriber = root._loadedAs;
this.onUpdate = onUpdate;
this.scheduleUpdate = () => {
const value = rootSchema.fromRaw(this.rootEntry.value) as Root;
subscriptionsScopes.set(value, this);
onUpdate(value);
};
this.rootEntry.rawUnsub = root._raw.core.subscribe(
(rawUpdate: RawCoValue | undefined) => {
if (!rawUpdate) return;
this.rootEntry.value = rootSchema.fromRaw(rawUpdate) as Root;
// console.log("root update", this.rootEntry.value.toJSON());
subscriptionsScopes.set(this.rootEntry.value, this);
this.rootEntry.value = rawUpdate;
this.scheduleUpdate();
},
);
}
scheduleUpdate() {
this.onUpdate(this.rootEntry.value);
}
onRefAccessedOrSet(
fromId: ID<CoValue>,
accessedOrSetId: ID<CoValue> | undefined,

View File

@@ -0,0 +1,348 @@
const Crypto = await WasmCrypto.create();
import { expect, describe, it, vi, onTestFinished } from "vitest";
import { connectedPeers } from "cojson/src/streamUtils.js";
import {
Account,
CoList,
CoMap,
CoStream,
WasmCrypto,
co,
isControlledAccount,
createJazzContext,
fixedCredentialsAuth,
} from "../index.web.js";
import {
BinaryCoStream,
Group,
randomSessionProvider,
subscribeToCoValue,
} from "../internal.js";
class ChatRoom extends CoMap {
messages = co.ref(MessagesList);
name = co.string;
}
class Message extends CoMap {
text = co.string;
reactions = co.ref(ReactionsStream);
attachment = co.optional.ref(BinaryCoStream);
}
class MessagesList extends CoList.Of(co.ref(Message)) {}
class ReactionsStream extends CoStream.Of(co.string) {}
async function setupAccount() {
const me = await Account.create({
creationProps: { name: "Hermes Puggington" },
crypto: Crypto,
});
const [initialAsPeer, secondPeer] = connectedPeers("initial", "second", {
peer1role: "server",
peer2role: "client",
});
if (!isControlledAccount(me)) {
throw "me is not a controlled account";
}
me._raw.core.node.syncManager.addPeer(secondPeer);
const { account: meOnSecondPeer } = await createJazzContext({
auth: fixedCredentialsAuth({
accountID: me.id,
secret: me._raw.agentSecret,
}),
sessionProvider: randomSessionProvider,
peersToLoadFrom: [initialAsPeer],
crypto: Crypto,
});
return { me, meOnSecondPeer };
}
function createChatRoom(me: Account | Group, name: string) {
return ChatRoom.create(
{ messages: MessagesList.create([], { owner: me }), name },
{ owner: me },
);
}
function createMessage(me: Account | Group, text: string) {
return Message.create(
{ text, reactions: ReactionsStream.create([], { owner: me }) },
{ owner: me },
);
}
describe("subscribeToCoValue", () => {
it("subscribes to a CoMap", async () => {
const { me, meOnSecondPeer } = await setupAccount();
const chatRoom = createChatRoom(me, "General");
const updateFn = vi.fn();
const unsubscribe = subscribeToCoValue(
ChatRoom,
chatRoom.id,
meOnSecondPeer,
{},
updateFn,
);
onTestFinished(unsubscribe);
await waitFor(() => {
expect(updateFn).toHaveBeenCalled();
});
expect(updateFn).toHaveBeenCalledWith(
expect.objectContaining({
id: chatRoom.id,
messages: null,
name: "General",
}),
);
updateFn.mockClear();
await waitFor(() => {
expect(updateFn).toHaveBeenCalled();
});
expect(updateFn).toHaveBeenCalledWith(
expect.objectContaining({
id: chatRoom.id,
name: "General",
messages: expect.any(Array),
}),
);
updateFn.mockClear();
chatRoom.name = "Lounge";
await waitFor(() => {
expect(updateFn).toHaveBeenCalled();
});
expect(updateFn).toHaveBeenCalledWith(
expect.objectContaining({
id: chatRoom.id,
name: "Lounge",
messages: expect.any(Array),
}),
);
});
it("shouldn't fire updates until the declared load depth isn't reached", async () => {
const { me, meOnSecondPeer } = await setupAccount();
const chatRoom = createChatRoom(me, "General");
const updateFn = vi.fn();
const unsubscribe = subscribeToCoValue(
ChatRoom,
chatRoom.id,
meOnSecondPeer,
{
messages: [],
},
updateFn,
);
onTestFinished(unsubscribe);
await waitFor(() => {
expect(updateFn).toHaveBeenCalled();
});
expect(updateFn).toHaveBeenCalledTimes(1);
expect(updateFn).toHaveBeenCalledWith(
expect.objectContaining({
id: chatRoom.id,
name: "General",
messages: expect.any(Array),
}),
);
});
it("should fire updates when a ref entity is updates", async () => {
const { me, meOnSecondPeer } = await setupAccount();
const chatRoom = createChatRoom(me, "General");
const message = createMessage(me, "Hello Luigi, are you ready to save the princess?");
chatRoom.messages?.push(message);
const updateFn = vi.fn()
const unsubscribe = subscribeToCoValue(
ChatRoom,
chatRoom.id,
meOnSecondPeer,
{
messages: [{
}],
},
updateFn,
);
onTestFinished(unsubscribe);
await waitFor(() => {
const lastValue = updateFn.mock.lastCall[0];
expect(lastValue?.messages?.[0]?.text).toBe(message.text);
});
message.text = "Nevermind, she was gone to the supermarket";
updateFn.mockClear();
await waitFor(() => {
expect(updateFn).toHaveBeenCalled();
});
const lastValue = updateFn.mock.lastCall[0];
expect(lastValue?.messages?.[0]?.text).toBe("Nevermind, she was gone to the supermarket");
});
it("should handle the updates as immutable changes", async () => {
const { me, meOnSecondPeer } = await setupAccount();
const chatRoom = createChatRoom(me, "General");
const message = createMessage(me, "Hello Luigi, are you ready to save the princess?");
const message2 = createMessage(me, "Let's go!");
chatRoom.messages?.push(message);
chatRoom.messages?.push(message2);
const updateFn = vi.fn()
const unsubscribe = subscribeToCoValue(
ChatRoom,
chatRoom.id,
meOnSecondPeer,
{
messages: [{
reactions: [],
}],
},
updateFn,
);
onTestFinished(unsubscribe);
await waitFor(() => {
const lastValue = updateFn.mock.lastCall[0];
expect(lastValue?.messages?.[0]?.text).toBe(message.text);
});
const initialValue = updateFn.mock.lastCall[0];
const initialMessagesList = initialValue?.messages;
const initialMessage1 = initialValue?.messages[0];
const initialMessage2 = initialValue?.messages[1];
const initialMessageReactions = initialValue?.messages[0].reactions;
message.reactions?.push("👍");
updateFn.mockClear();
await waitFor(() => {
expect(updateFn).toHaveBeenCalled();
});
const lastValue = updateFn.mock.lastCall[0];
expect(lastValue).not.toBe(initialValue);
expect(lastValue.messages).not.toBe(initialMessagesList);
expect(lastValue.messages[0]).not.toBe(initialMessage1);
expect(lastValue.messages[0].reactions).not.toBe(initialMessageReactions);
// This shouldn't change
expect(lastValue.messages[1]).toBe(initialMessage2);
// TODO: The initial should point at that snapshot in time
// expect(lastValue.messages).not.toBe(initialValue.messages);
// expect(lastValue.messages[0]).not.toBe(initialValue.messages[0]);
// expect(lastValue.messages[1]).toBe(initialValue.messages[1]);
// expect(lastValue.messages[0].reactions).not.toBe(initialValue.messages[0].reactions);
});
it("should keep the same identity on the ref entities when a property is updated", async () => {
const { me, meOnSecondPeer } = await setupAccount();
const chatRoom = createChatRoom(me, "General");
const message = createMessage(me, "Hello Luigi, are you ready to save the princess?");
const message2 = createMessage(me, "Let's go!");
chatRoom.messages?.push(message);
chatRoom.messages?.push(message2);
const updateFn = vi.fn()
const unsubscribe = subscribeToCoValue(
ChatRoom,
chatRoom.id,
meOnSecondPeer,
{
messages: [{
reactions: [],
}],
},
updateFn,
);
onTestFinished(unsubscribe);
await waitFor(() => {
const lastValue = updateFn.mock.lastCall[0];
expect(lastValue?.messages?.[0]?.text).toBe(message.text);
expect(lastValue?.messages?.[1]?.text).toBe(message2.text);
});
const initialValue = updateFn.mock.lastCall[0];
chatRoom.name = "Me and Luigi";
updateFn.mockClear();
await waitFor(() => {
expect(updateFn).toHaveBeenCalled();
});
const lastValue = updateFn.mock.lastCall[0];
expect(lastValue).not.toBe(initialValue);
expect(lastValue.name).toBe("Me and Luigi");
expect(initialValue.name).toBe("General");
expect(lastValue.messages).toBe(initialValue.messages);
expect(lastValue.messages[0]).toBe(initialValue.messages[0]);
expect(lastValue.messages[1]).toBe(initialValue.messages[1]);
});
});
function waitFor(callback: () => boolean | void) {
return new Promise<void>((resolve, reject) => {
const checkPassed = () => {
try {
return { ok: callback(), error: null };
} catch (error) {
return { ok: false, error };
}
};
let retries = 0;
const interval = setInterval(() => {
const { ok, error } = checkPassed();
if (ok !== false) {
clearInterval(interval);
resolve();
}
if (++retries > 10) {
clearInterval(interval);
reject(error);
}
}, 100);
});
}

125
pnpm-lock.yaml generated
View File

@@ -49,7 +49,7 @@ importers:
specifier: ^3.0.1
version: 3.0.1
jazz-react:
specifier: workspace:0.8.5
specifier: workspace:0.8.7
version: link:../../packages/jazz-react
jazz-tools:
specifier: workspace:0.8.5
@@ -144,10 +144,10 @@ importers:
specifier: ^2.0.0
version: 2.0.0
jazz-browser-media-images:
specifier: workspace:0.8.5
specifier: workspace:0.8.7
version: link:../../packages/jazz-browser-media-images
jazz-react:
specifier: workspace:0.8.5
specifier: workspace:0.8.7
version: link:../../packages/jazz-react
jazz-tools:
specifier: workspace:0.8.5
@@ -223,7 +223,7 @@ importers:
specifier: workspace:0.2.1
version: link:../../packages/hash-slash
jazz-react:
specifier: workspace:0.8.5
specifier: workspace:0.8.7
version: link:../../packages/jazz-react
jazz-tools:
specifier: workspace:0.8.5
@@ -335,10 +335,10 @@ importers:
specifier: workspace:0.2.1
version: link:../../packages/hash-slash
jazz-react:
specifier: workspace:0.8.5
specifier: workspace:0.8.7
version: link:../../packages/jazz-react
jazz-react-auth-clerk:
specifier: workspace:0.8.5
specifier: workspace:0.8.7
version: link:../../packages/jazz-react-auth-clerk
jazz-tools:
specifier: workspace:0.8.5
@@ -458,6 +458,9 @@ importers:
expo-linking:
specifier: ~6.3.1
version: 6.3.1(expo@51.0.37(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2)))
expo-secure-store:
specifier: ~13.0.2
version: 13.0.2(expo@51.0.37(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2)))
expo-status-bar:
specifier: ~1.12.1
version: 1.12.1
@@ -722,7 +725,7 @@ importers:
specifier: workspace:0.8.5
version: link:../../packages/cojson
cojson-transport-ws:
specifier: workspace:0.8.5
specifier: workspace:0.8.7
version: link:../../packages/cojson-transport-ws
hash-slash:
specifier: workspace:0.2.1
@@ -819,7 +822,7 @@ importers:
specifier: ^2.0.0
version: 2.0.0
jazz-react:
specifier: workspace:0.8.5
specifier: workspace:0.8.7
version: link:../../packages/jazz-react
jazz-tools:
specifier: workspace:0.8.5
@@ -867,6 +870,9 @@ importers:
eslint:
specifier: ^8.46.0
version: 8.56.0
eslint-plugin-react-compiler:
specifier: 0.0.0-experimental-fa06e2c-20241014
version: 0.0.0-experimental-fa06e2c-20241014(eslint@8.56.0)
eslint-plugin-react-hooks:
specifier: ^4.6.0
version: 4.6.0(eslint@8.56.0)
@@ -889,7 +895,7 @@ importers:
examples/password-manager:
dependencies:
jazz-react:
specifier: workspace:0.8.5
specifier: workspace:0.8.7
version: link:../../packages/jazz-react
jazz-tools:
specifier: workspace:0.8.5
@@ -968,10 +974,10 @@ importers:
specifier: ^2.0.0
version: 2.0.0
jazz-browser-media-images:
specifier: workspace:0.8.5
specifier: workspace:0.8.7
version: link:../../packages/jazz-browser-media-images
jazz-react:
specifier: workspace:0.8.5
specifier: workspace:0.8.7
version: link:../../packages/jazz-react
jazz-tools:
specifier: workspace:0.8.5
@@ -1044,7 +1050,7 @@ importers:
specifier: ^3.0.1
version: 3.0.1
jazz-run:
specifier: workspace:0.8.5
specifier: workspace:0.8.7
version: link:../../packages/jazz-run
postcss:
specifier: ^8.4.27
@@ -1082,7 +1088,7 @@ importers:
specifier: ^2.0.0
version: 2.0.0
jazz-react:
specifier: workspace:0.8.5
specifier: workspace:0.8.7
version: link:../../packages/jazz-react
jazz-tools:
specifier: workspace:0.8.5
@@ -1291,7 +1297,7 @@ importers:
specifier: workspace:0.8.5
version: link:../cojson-storage-indexeddb
cojson-transport-ws:
specifier: workspace:0.8.5
specifier: workspace:0.8.7
version: link:../cojson-transport-ws
jazz-tools:
specifier: workspace:0.8.5
@@ -1306,7 +1312,7 @@ importers:
specifier: workspace:0.8.5
version: link:../cojson
jazz-browser:
specifier: workspace:0.8.5
specifier: workspace:0.8.7
version: link:../jazz-browser
jazz-tools:
specifier: workspace:0.8.5
@@ -1325,7 +1331,7 @@ importers:
specifier: ^4.1.0
version: 4.1.0
jazz-browser:
specifier: workspace:0.8.5
specifier: workspace:0.8.7
version: link:../jazz-browser
jazz-tools:
specifier: workspace:0.8.5
@@ -1347,7 +1353,7 @@ importers:
specifier: workspace:0.8.5
version: link:../cojson
cojson-transport-ws:
specifier: workspace:0.8.5
specifier: workspace:0.8.7
version: link:../cojson-transport-ws
jazz-tools:
specifier: workspace:0.8.5
@@ -1372,7 +1378,7 @@ importers:
specifier: workspace:0.8.5
version: link:../cojson
jazz-browser:
specifier: workspace:0.8.5
specifier: workspace:0.8.7
version: link:../jazz-browser
jazz-tools:
specifier: workspace:0.8.5
@@ -1397,10 +1403,10 @@ importers:
specifier: workspace:0.8.5
version: link:../cojson
jazz-browser-auth-clerk:
specifier: workspace:0.8.5
specifier: workspace:0.8.7
version: link:../jazz-browser-auth-clerk
jazz-react:
specifier: workspace:0.8.5
specifier: workspace:0.8.7
version: link:../jazz-react
jazz-tools:
specifier: workspace:0.8.5
@@ -1440,12 +1446,12 @@ importers:
expo-linking:
specifier: ~6.3.1
version: 6.3.1(expo@51.0.37(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2)))
expo-secure-store:
specifier: ~13.0.2
version: 13.0.2(expo@51.0.37(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2)))
react-native:
specifier: ~0.74.5
version: 0.74.5(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2))(@types/react@18.2.79)(react@18.3.1)
react-native-mmkv:
specifier: 3.0.1
version: 3.0.1(react-native@0.74.5(@babel/core@7.25.2)(@babel/preset-env@7.25.4(@babel/core@7.25.2))(@types/react@18.2.79)(react@18.3.1))(react@18.3.1)
packages/jazz-react-native-media-images:
dependencies:
@@ -1493,7 +1499,7 @@ importers:
specifier: workspace:0.8.5
version: link:../cojson-storage-sqlite
cojson-transport-ws:
specifier: workspace:0.8.5
specifier: workspace:0.8.7
version: link:../cojson-transport-ws
effect:
specifier: ^3.6.5
@@ -1774,6 +1780,13 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
'@babel/plugin-proposal-private-methods@7.18.6':
resolution: {integrity: sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==}
engines: {node: '>=6.9.0'}
deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead.
peerDependencies:
'@babel/core': ^7.0.0-0
'@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2':
resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==}
engines: {node: '>=6.9.0'}
@@ -3291,7 +3304,7 @@ packages:
'@radix-ui/react-compose-refs@1.1.0':
resolution: {integrity: sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==}
peerDependencies:
'@types/react': '*'
'@types/react': ^18.2.32
react: 18.3.1
peerDependenciesMeta:
'@types/react':
@@ -3375,8 +3388,8 @@ packages:
'@radix-ui/react-focus-scope@1.1.0':
resolution: {integrity: sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
'@types/react': ^18.2.32
'@types/react-dom': ^18.2.14
react: 18.3.1
react-dom: 18.3.1
peerDependenciesMeta:
@@ -3388,7 +3401,7 @@ packages:
'@radix-ui/react-id@1.1.0':
resolution: {integrity: sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==}
peerDependencies:
'@types/react': '*'
'@types/react': ^18.2.32
react: 18.3.1
peerDependenciesMeta:
'@types/react':
@@ -3410,8 +3423,8 @@ packages:
'@radix-ui/react-popper@1.2.0':
resolution: {integrity: sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
'@types/react': ^18.2.32
'@types/react-dom': ^18.2.14
react: 18.3.1
react-dom: 18.3.1
peerDependenciesMeta:
@@ -3488,8 +3501,8 @@ packages:
'@radix-ui/react-primitive@2.0.0':
resolution: {integrity: sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
'@types/react': ^18.2.32
'@types/react-dom': ^18.2.14
react: 18.3.1
react-dom: 18.3.1
peerDependenciesMeta:
@@ -5884,6 +5897,12 @@ packages:
eslint-config-prettier:
optional: true
eslint-plugin-react-compiler@0.0.0-experimental-fa06e2c-20241014:
resolution: {integrity: sha512-tHntZz8Kx/6RgCLn7aDGfBQizqTUUfHEDaBcrvJi1GhKzgDxmAbdn85Y6z8eGSh4s0gufNWyO9WRCYLf0hP0ow==}
engines: {node: ^14.17.0 || ^16.0.0 || >= 18.0.0}
peerDependencies:
eslint: '>=7'
eslint-plugin-react-hooks@4.6.0:
resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==}
engines: {node: '>=10'}
@@ -6596,12 +6615,18 @@ packages:
hermes-estree@0.19.1:
resolution: {integrity: sha512-daLGV3Q2MKk8w4evNMKwS8zBE/rcpA800nu1Q5kM08IKijoSnPe9Uo1iIxzPKRkn95IxxsgBMPeYHt3VG4ej2g==}
hermes-estree@0.20.1:
resolution: {integrity: sha512-SQpZK4BzR48kuOg0v4pb3EAGNclzIlqMj3Opu/mu7bbAoFw6oig6cEt/RAi0zTFW/iW6Iz9X9ggGuZTAZ/yZHg==}
hermes-estree@0.23.1:
resolution: {integrity: sha512-eT5MU3f5aVhTqsfIReZ6n41X5sYn4IdQL0nvz6yO+MMlPxw49aSARHLg/MSehQftyjnrE8X6bYregzSumqc6cg==}
hermes-parser@0.19.1:
resolution: {integrity: sha512-Vp+bXzxYJWrpEuJ/vXxUsLnt0+y4q9zyi4zUlkLqD8FKv4LjIfOvP69R/9Lty3dCyKh0E2BU7Eypqr63/rKT/A==}
hermes-parser@0.20.1:
resolution: {integrity: sha512-BL5P83cwCogI8D7rrDCgsFY0tdYUtmFP9XaXtl2IQjC+2Xo+4okjfXintlTxcIwl4qeGddEl28Z11kbVIw0aNA==}
hermes-parser@0.23.1:
resolution: {integrity: sha512-oxl5h2DkFW83hT4DAUJorpah8ou4yvmweUzLJmmr6YV2cezduCdlil1AvU/a/xSsAFo4WUcNA4GoV5Bvq6JffA==}
@@ -10331,6 +10356,12 @@ packages:
peerDependencies:
zod: ^3.18.0
zod-validation-error@3.4.0:
resolution: {integrity: sha512-ZOPR9SVY6Pb2qqO5XHt+MkkTRxGXb4EVtnjc9JpXUOtUB1T9Ru7mZOT361AN3MsetVe7R0a1KZshJDZdgp9miQ==}
engines: {node: '>=18.0.0'}
peerDependencies:
zod: ^3.18.0
zod@3.23.8:
resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==}
@@ -10653,6 +10684,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@babel/plugin-proposal-private-methods@7.18.6(@babel/core@7.25.2)':
dependencies:
'@babel/core': 7.25.2
'@babel/helper-create-class-features-plugin': 7.25.4(@babel/core@7.25.2)
'@babel/helper-plugin-utils': 7.24.8
transitivePeerDependencies:
- supports-color
'@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.25.2)':
dependencies:
'@babel/core': 7.25.2
@@ -15891,6 +15930,18 @@ snapshots:
'@types/eslint': 9.6.0
eslint-config-prettier: 9.1.0(eslint@8.57.1)
eslint-plugin-react-compiler@0.0.0-experimental-fa06e2c-20241014(eslint@8.56.0):
dependencies:
'@babel/core': 7.25.2
'@babel/parser': 7.25.6
'@babel/plugin-proposal-private-methods': 7.18.6(@babel/core@7.25.2)
eslint: 8.56.0
hermes-parser: 0.20.1
zod: 3.23.8
zod-validation-error: 3.4.0(zod@3.23.8)
transitivePeerDependencies:
- supports-color
eslint-plugin-react-hooks@4.6.0(eslint@8.56.0):
dependencies:
eslint: 8.56.0
@@ -16807,12 +16858,18 @@ snapshots:
hermes-estree@0.19.1: {}
hermes-estree@0.20.1: {}
hermes-estree@0.23.1: {}
hermes-parser@0.19.1:
dependencies:
hermes-estree: 0.19.1
hermes-parser@0.20.1:
dependencies:
hermes-estree: 0.20.1
hermes-parser@0.23.1:
dependencies:
hermes-estree: 0.23.1
@@ -21015,4 +21072,8 @@ snapshots:
dependencies:
zod: 3.23.8
zod-validation-error@3.4.0(zod@3.23.8):
dependencies:
zod: 3.23.8
zod@3.23.8: {}