Compare commits

...

34 Commits

Author SHA1 Message Date
Guido D'Orsi
829ab08873 Merge pull request #2165 from garden-co/changeset-release/main
Version Packages
2025-05-08 21:28:50 +02:00
github-actions[bot]
2c9b08a080 Version Packages 2025-05-08 19:27:22 +00:00
Guido D'Orsi
48bda8854f Merge pull request #2163 from garden-co/vanilla-inspector
feat: package jazz-inspector as custom element
2025-05-08 21:24:59 +02:00
Guido D'Orsi
96ed9adf59 Merge pull request #2142 from garden-co/feat/simplify-accept-invite
feat: simplified the acceptInvite method and cleaned the coValue transfer methods
2025-05-08 18:21:03 +02:00
Guido D'Orsi
5e4905ca99 test: add sync invite tests 2025-05-08 18:18:03 +02:00
Guido D'Orsi
1d4949b70c Merge remote-tracking branch 'origin/main' into feat/simplify-accept-invite 2025-05-08 18:08:02 +02:00
Guido D'Orsi
7dacfd03f9 Merge remote-tracking branch 'origin/main' into vanilla-inspector 2025-05-08 18:05:55 +02:00
Guido D'Orsi
bd4191520e Merge pull request #2164 from garden-co/fix/sync-browser-flaky-test
test: fix the flakiness on a browser integration test
2025-05-08 18:05:19 +02:00
Guido D'Orsi
e3dfb1b06e test: fix the flakiness on a browser integration test 2025-05-08 18:05:06 +02:00
Guido D'Orsi
7de210f225 feat: package jazz-inspector as custom element 2025-05-08 17:48:49 +02:00
Guido D'Orsi
1676ff852a fix: remove a broken JSON.stringify from expectCoValueLoaded error 2025-05-08 16:14:24 +02:00
Guido D'Orsi
a8af6efe1a Merge pull request #2162 from garden-co/changeset-release/main
Version Packages
2025-05-08 16:06:25 +02:00
github-actions[bot]
ee11b30d3a Version Packages 2025-05-08 13:38:09 +00:00
Guido D'Orsi
ef78d58729 Merge pull request #2155 from garden-co/feat/missing-profile
fix(auth): removed throw error when the profile is unavailable after a login
2025-05-08 15:36:09 +02:00
Guido D'Orsi
40e2dd0ece Merge pull request #2159 from garden-co/feat/storage-sync
fix: wait for storage sync before resolving the new account creation
2025-05-08 15:35:41 +02:00
Benjamin S. Leveritt
b60c7c1ce0 Merge pull request #2156 from garden-co/play-selection-co-literal-jazz-paper-scissors
Changes to co.literal for playSelection
2025-05-08 14:22:01 +01:00
Benjamin S. Leveritt
69cd362114 Merge pull request #2160 from garden-co/coplaintext-shorthands
CoPlainText shorthands
2025-05-08 14:19:31 +01:00
Benjamin S. Leveritt
526bf0a3cf Adds better output for Number(CoPlainText) 2025-05-08 13:57:59 +01:00
Benjamin S. Leveritt
439f0fe57e Add changeset 2025-05-08 13:30:07 +01:00
Benjamin S. Leveritt
686433f42e Adds reading as string shorthand
Dropping need for `.toString()` in situations where a string is expected
2025-05-08 13:29:54 +01:00
Benjamin S. Leveritt
3ba258e181 Adds owner shorthand to create and constructor 2025-05-08 13:27:55 +01:00
Guido D'Orsi
d6e143e4d5 fix: wait for storage sync before resolving the new account creation 2025-05-08 14:24:11 +02:00
Guido D'Orsi
3e6229da4d feat(waitForSync): skip closed peers and not subscribed client peers 2025-05-08 14:22:55 +02:00
Guido D'Orsi
adfc9a6032 feat: make waitForSync work on storage peers by handling optimistic/known state 2025-05-08 14:19:07 +02:00
Benjamin S. Leveritt
1a6879b1c2 Fix selection in worker 2025-05-08 10:10:45 +01:00
Benjamin S. Leveritt
172fec56f6 Changes to co.literal for playSelection 2025-05-08 10:06:32 +01:00
Guido D'Orsi
13892071f5 fix(auth): removed throw error when the profile is unavailable after a login 2025-05-08 10:45:23 +02:00
Guido D'Orsi
b62d75b847 Merge pull request #2149 from garden-co/changeset-release/main
Version Packages
2025-05-07 18:31:36 +02:00
github-actions[bot]
beafbd3088 Version Packages 2025-05-07 16:29:39 +00:00
Guido D'Orsi
653d8ba69f Merge pull request #2140 from garden-co/guido/rn-e2e-test
test: cover join chat in the rn e2e tests
2025-05-07 18:27:56 +02:00
Guido D'Orsi
6c23dab790 Merge pull request #2148 from garden-co/fix/autoload
fix(subscribe): fix error management and autoload on $each
2025-05-07 18:27:42 +02:00
Guido D'Orsi
80530a4065 fix(subscribe): fix error management and autoload on 2025-05-07 18:24:01 +02:00
Guido D'Orsi
e14e61f7d9 feat: simplified the acceptInvite method and cleaned the coValue transfer methods 2025-05-07 16:23:57 +02:00
Guido D'Orsi
a8466946d3 test: cover join chat in the rn e2e tests 2025-05-07 14:25:56 +02:00
144 changed files with 2702 additions and 474 deletions

View File

@@ -15,6 +15,7 @@
"jazz-browser-media-images",
"jazz-expo",
"jazz-inspector",
"jazz-inspector-element",
"jazz-nodejs",
"jazz-react",
"jazz-react-core",

View File

@@ -1,5 +1,31 @@
# chat-rn-expo-clerk
## 1.0.113
### Patch Changes
- jazz-expo@0.13.21
- jazz-tools@0.13.21
- jazz-react-native-media-images@0.13.21
## 1.0.112
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-expo@0.13.20
- jazz-react-native-media-images@0.13.20
## 1.0.111
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-expo@0.13.19
- jazz-react-native-media-images@0.13.19
## 1.0.110
### Patch Changes

View File

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

View File

@@ -1,5 +1,28 @@
# chat-rn-expo
## 1.0.100
### Patch Changes
- jazz-expo@0.13.21
- jazz-tools@0.13.21
## 1.0.99
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-expo@0.13.20
## 1.0.98
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-expo@0.13.19
## 1.0.97
### Patch Changes

View File

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

View File

@@ -20,6 +20,7 @@ import { Chat, Message } from "./schema";
export default function ChatScreen({ navigation }: { navigation: any }) {
const { me, logOut } = useAccount();
const [chatId, setChatId] = useState<ID<Chat>>();
const [chatIdInput, setChatIdInput] = useState<string>();
const loadedChat = useCoState(Chat, chatId, { resolve: { $each: true } });
const [message, setMessage] = useState("");
const profile = useCoState(Profile, me._refs.profile?.id, {});
@@ -57,27 +58,11 @@ export default function ChatScreen({ navigation }: { navigation: any }) {
};
const joinChat = () => {
Alert.prompt(
"Join Chat",
"Enter the Chat ID (example: co_zBGEHYvRfGuT2YSBraY3njGjnde)",
[
{
text: "Cancel",
style: "cancel",
},
{
text: "Join",
onPress: (chatId) => {
if (chatId) {
setChatId(chatId as ID<Chat>);
} else {
Alert.alert("Error", "Chat ID cannot be empty.");
}
},
},
],
"plain-text",
);
if (chatIdInput) {
setChatId(chatIdInput as ID<Chat>);
} else {
Alert.alert("Error", "Chat ID cannot be empty.");
}
};
const sendMessage = () => {
@@ -160,9 +145,25 @@ export default function ChatScreen({ navigation }: { navigation: any }) {
>
<Text className="text-white font-semibold">Start new chat</Text>
</TouchableOpacity>
<Text className="text-m font-bold mt-6">Join existing chat</Text>
<TextInput
className="rounded h-12 p-2 m-2 mt-4 w-80 border border-gray-200 block"
placeholder="Chat ID"
value={chatIdInput ?? ""}
onChangeText={(value) => {
setChatIdInput(value);
}}
textAlignVertical="center"
onSubmitEditing={() => {
if (chatIdInput) {
setChatId(chatIdInput as ID<Chat>);
}
}}
testID="chat-id-input"
/>
<TouchableOpacity
onPress={joinChat}
className="bg-green-500 p-4 rounded-md mt-4"
className="bg-green-500 p-4 rounded-md"
>
<Text className="text-white font-semibold">Join chat</Text>
</TouchableOpacity>
@@ -172,7 +173,6 @@ export default function ChatScreen({ navigation }: { navigation: any }) {
<FlatList
contentContainerStyle={{
flexGrow: 1,
flex: 1,
gap: 6,
padding: 8,
}}

View File

@@ -44,9 +44,11 @@ appId: com.jazz.chatrn
# logout
- tapOn: "Logout"
- assertVisible: "Anonymous user"
# This doesn't work on CI, maybe because Android has a different alert dialog
# - tapOn: "Join chat"
# - inputText: "co_zFs6KFyhxPw4xtw83tcEMzeHUNv" # Use a static id because maestro doesn't have access to the system clipboard
# - pressKey: "enter"
# - assertVisible: "boorad"
# - assertVisible: "bro, low key, it do be like that tho"
# join chat
- tapOn:
id: "chat-id-input"
- inputText: "co_zFs6KFyhxPw4xtw83tcEMzeHUNv" # Use a static id because maestro doesn't have access to the system clipboard
- tapOn: "Join chat"
- assertVisible: "boorad"
- assertVisible: "bro, low key, it do be like that tho"

View File

@@ -1,5 +1,37 @@
# chat-rn
## 1.0.108
### Patch Changes
- Updated dependencies [e14e61f]
- cojson@0.13.21
- cojson-transport-ws@0.13.21
- jazz-react-native@0.13.21
- jazz-tools@0.13.21
## 1.0.107
### Patch Changes
- Updated dependencies [adfc9a6]
- Updated dependencies [1389207]
- Updated dependencies [d6e143e]
- Updated dependencies [439f0fe]
- Updated dependencies [3e6229d]
- cojson@0.13.20
- jazz-tools@0.13.20
- jazz-react-native@0.13.20
- cojson-transport-ws@0.13.20
## 1.0.106
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-react-native@0.13.19
## 1.0.105
### Patch Changes

View File

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

View File

@@ -1,5 +1,31 @@
# chat-vue
## 0.0.92
### Patch Changes
- jazz-browser@0.13.21
- jazz-tools@0.13.21
- jazz-vue@0.13.21
## 0.0.91
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-browser@0.13.20
- jazz-vue@0.13.20
## 0.0.90
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-browser@0.13.19
- jazz-vue@0.13.19
## 0.0.89
### Patch Changes

View File

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

View File

@@ -1,5 +1,32 @@
# jazz-example-chat
## 0.0.190
### Patch Changes
- Updated dependencies [7de210f]
- jazz-inspector@0.13.21
- jazz-react@0.13.21
- jazz-tools@0.13.21
## 0.0.189
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-inspector@0.13.20
- jazz-react@0.13.20
## 0.0.188
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-inspector@0.13.19
- jazz-react@0.13.19
## 0.0.187
### Patch Changes

View File

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

View File

@@ -1,5 +1,31 @@
# minimal-auth-clerk
## 0.0.89
### Patch Changes
- jazz-react@0.13.21
- jazz-react-auth-clerk@0.13.21
- jazz-tools@0.13.21
## 0.0.88
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-react@0.13.20
- jazz-react-auth-clerk@0.13.20
## 0.0.87
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-react@0.13.19
- jazz-react-auth-clerk@0.13.19
## 0.0.86
### Patch Changes

View File

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

View File

@@ -1,5 +1,29 @@
# file-share-svelte
## 0.0.72
### Patch Changes
- jazz-inspector-element@0.13.21
- jazz-svelte@0.13.21
- jazz-tools@0.13.21
## 0.0.71
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-svelte@0.13.20
## 0.0.70
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-svelte@0.13.19
## 0.0.69
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "file-share-svelte",
"version": "0.0.69",
"version": "0.0.72",
"private": true,
"type": "module",
"scripts": {
@@ -39,6 +39,7 @@
},
"dependencies": {
"@tailwindcss/typography": "^0.5.15",
"jazz-inspector-element": "workspace:*",
"jazz-svelte": "workspace:*",
"jazz-tools": "workspace:*",
"lucide-svelte": "^0.463.0",

View File

@@ -8,7 +8,8 @@
<script lang="ts">
import { JazzProvider } from 'jazz-svelte';
import { PasskeyAuthBasicUI, usePasskeyAuth } from 'jazz-svelte';
import "jazz-inspector-element"
import { PasskeyAuthBasicUI } from 'jazz-svelte';
import { Toaster } from 'svelte-sonner';
import '../app.css';
import { FileShareAccount } from '$lib/schema';
@@ -29,6 +30,7 @@
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
}}
>
<jazz-inspector></jazz-inspector>
<PasskeyAuthBasicUI appName="File Share">
<div class="min-h-screen bg-gray-100">
{@render children()}

View File

@@ -1,5 +1,32 @@
# jazz-tailwind-demo-auth-starter
## 0.0.29
### Patch Changes
- Updated dependencies [7de210f]
- jazz-inspector@0.13.21
- jazz-react@0.13.21
- jazz-tools@0.13.21
## 0.0.28
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-inspector@0.13.20
- jazz-react@0.13.20
## 0.0.27
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-inspector@0.13.19
- jazz-react@0.13.19
## 0.0.26
### Patch Changes

View File

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

View File

@@ -1,5 +1,28 @@
# form
## 0.1.30
### Patch Changes
- jazz-react@0.13.21
- jazz-tools@0.13.21
## 0.1.29
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-react@0.13.20
## 0.1.28
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-react@0.13.19
## 0.1.27
### Patch Changes

View File

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

View File

@@ -1,5 +1,28 @@
# image-upload
## 0.0.86
### Patch Changes
- jazz-react@0.13.21
- jazz-tools@0.13.21
## 0.0.85
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-react@0.13.20
## 0.0.84
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-react@0.13.19
## 0.0.83
### Patch Changes

View File

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

View File

@@ -1,5 +1,33 @@
# jazz-example-inspector
## 0.0.140
### Patch Changes
- Updated dependencies [7de210f]
- Updated dependencies [e14e61f]
- jazz-inspector@0.13.21
- cojson@0.13.21
- cojson-transport-ws@0.13.21
## 0.0.139
### Patch Changes
- Updated dependencies [adfc9a6]
- Updated dependencies [1389207]
- Updated dependencies [d6e143e]
- Updated dependencies [3e6229d]
- cojson@0.13.20
- cojson-transport-ws@0.13.20
- jazz-inspector@0.13.20
## 0.0.138
### Patch Changes
- jazz-inspector@0.13.19
## 0.0.137
### Patch Changes

View File

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

View File

@@ -8,7 +8,10 @@ export function cn(...inputs: ClassValue[]) {
/**
* Given a player selections, returns the winner of the current game.
*/
export function determineWinner(player1Choice: string, player2Choice: string) {
export function determineWinner(
player1Choice: "rock" | "paper" | "scissors",
player2Choice: "rock" | "paper" | "scissors",
) {
if (player1Choice === player2Choice) {
return "draw";
} else if (

View File

@@ -14,7 +14,7 @@ import { type ID } from "jazz-tools";
import { Badge, CircleHelp, Scissors, ScrollText } from "lucide-react";
import { useEffect, useState } from "react";
const playIcon = (selection: string | undefined) => {
const playIcon = (selection: "rock" | "paper" | "scissors" | undefined) => {
switch (selection) {
case "rock":
return <Badge className="w-5 h-5" />;
@@ -50,9 +50,9 @@ function RouteComponent() {
const isPlayer1 = loaderGame.player1?.account?.isMe;
const player = isPlayer1 ? "player1" : "player2";
const [playSelection, setPlaySelection] = useState(
loaderGame[player]?.playSelection ?? "",
);
const [playSelection, setPlaySelection] = useState<
"rock" | "paper" | "scissors" | undefined
>(loaderGame[player]?.playSelection);
const sendInboxMessage = experimental_useInboxSender(WORKER_ID);
const game = useCoState(Game, gameId as ID<Game>);
@@ -62,7 +62,7 @@ function RouteComponent() {
return loaderGame.subscribe((game) => {
if (gameCompleted && !game.outcome) {
setPlaySelection(""); // Reset play selection when one player clicks on "Start a new game"
setPlaySelection(undefined); // Reset play selection when one player clicks on "Start a new game"
}
gameCompleted = Boolean(game.outcome);
@@ -82,7 +82,10 @@ function RouteComponent() {
const opponentSelection = opponentPlayer?.playSelection;
const onSubmit = async (playSelection: string) => {
const onSubmit = async (
playSelection: "rock" | "paper" | "scissors" | undefined,
) => {
if (!playSelection) return;
sendInboxMessage(
PlayIntent.create({ type: "play", gameId, player, playSelection }),
);
@@ -117,7 +120,9 @@ function RouteComponent() {
) : null}
<CardContent>
<div>
{playSelection === "" ? "Make Your Selection" : "Your Selection: "}
{playSelection === undefined
? "Make Your Selection"
: "Your Selection: "}
</div>
<CardSmall>{playIcon(playSelection)}</CardSmall>
{gameComplete ? null : (
@@ -148,7 +153,7 @@ function RouteComponent() {
<div className="m-4">
<Button
disabled={
playSelection === "" ||
playSelection === undefined ||
Boolean(currentPlayer?.playSelection)
}
onClick={() => onSubmit(playSelection)}

View File

@@ -30,7 +30,7 @@ export class Game extends CoMap {
export class Player extends CoMap {
account = co.ref(Account);
playSelection? = co.string;
playSelection? = co.literal("rock", "paper", "scissors");
}
export class WaitingRoom extends CoMap {
@@ -47,7 +47,7 @@ export class PlayIntent extends InboxMessage {
type = co.literal("play");
gameId = co.string;
player = co.literal("player1", "player2");
playSelection = co.string;
playSelection = co.literal("rock", "paper", "scissors");
}
export class NewGameIntent extends InboxMessage {

View File

@@ -186,9 +186,9 @@ async function handlePlayIntent(_: ID<Account>, message: PlayIntent) {
// once both players have a selection, determine the winner
if (
!!player1Selection &&
player1Selection !== "" &&
player1Selection !== undefined &&
!!player2Selection &&
player2Selection !== ""
player2Selection !== undefined
) {
const outcome = determineWinner(player1Selection, player2Selection);
game.outcome = outcome;

View File

@@ -1,5 +1,28 @@
# multi-cursors
## 0.0.82
### Patch Changes
- jazz-react@0.13.21
- jazz-tools@0.13.21
## 0.0.81
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-react@0.13.20
## 0.0.80
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-react@0.13.19
## 0.0.79
### Patch Changes

View File

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

View File

@@ -1,5 +1,31 @@
# multiauth
## 0.0.30
### Patch Changes
- jazz-react@0.13.21
- jazz-react-auth-clerk@0.13.21
- jazz-tools@0.13.21
## 0.0.29
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-react@0.13.20
- jazz-react-auth-clerk@0.13.20
## 0.0.28
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-react@0.13.19
- jazz-react-auth-clerk@0.13.19
## 0.0.27
### Patch Changes

View File

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

View File

@@ -1,5 +1,32 @@
# jazz-example-musicplayer
## 0.0.111
### Patch Changes
- Updated dependencies [7de210f]
- jazz-inspector@0.13.21
- jazz-react@0.13.21
- jazz-tools@0.13.21
## 0.0.110
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-inspector@0.13.20
- jazz-react@0.13.20
## 0.0.109
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-inspector@0.13.19
- jazz-react@0.13.19
## 0.0.108
### Patch Changes

View File

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

View File

@@ -1,5 +1,28 @@
# organization
## 0.0.82
### Patch Changes
- jazz-react@0.13.21
- jazz-tools@0.13.21
## 0.0.81
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-react@0.13.20
## 0.0.80
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-react@0.13.19
## 0.0.79
### Patch Changes

View File

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

View File

@@ -1,5 +1,28 @@
# passkey-svelte
## 0.0.76
### Patch Changes
- jazz-svelte@0.13.21
- jazz-tools@0.13.21
## 0.0.75
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-svelte@0.13.20
## 0.0.74
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-svelte@0.13.19
## 0.0.73
### Patch Changes

View File

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

View File

@@ -1,5 +1,28 @@
# minimal-auth-passkey
## 0.0.87
### Patch Changes
- jazz-react@0.13.21
- jazz-tools@0.13.21
## 0.0.86
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-react@0.13.20
## 0.0.85
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-react@0.13.19
## 0.0.84
### Patch Changes

View File

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

View File

@@ -1,5 +1,28 @@
# passphrase
## 0.0.84
### Patch Changes
- jazz-react@0.13.21
- jazz-tools@0.13.21
## 0.0.83
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-react@0.13.20
## 0.0.82
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-react@0.13.19
## 0.0.81
### Patch Changes

View File

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

View File

@@ -1,5 +1,28 @@
# jazz-password-manager
## 0.0.108
### Patch Changes
- jazz-react@0.13.21
- jazz-tools@0.13.21
## 0.0.107
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-react@0.13.20
## 0.0.106
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-react@0.13.19
## 0.0.105
### Patch Changes

View File

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

View File

@@ -1,5 +1,28 @@
# jazz-example-pets
## 0.0.206
### Patch Changes
- jazz-react@0.13.21
- jazz-tools@0.13.21
## 0.0.205
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-react@0.13.20
## 0.0.204
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-react@0.13.19
## 0.0.203
### Patch Changes

View File

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

View File

@@ -1,5 +1,28 @@
# reactions
## 0.0.86
### Patch Changes
- jazz-react@0.13.21
- jazz-tools@0.13.21
## 0.0.85
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-react@0.13.20
## 0.0.84
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-react@0.13.19
## 0.0.83
### Patch Changes

View File

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

View File

@@ -1,5 +1,31 @@
# richtext
## 0.0.76
### Patch Changes
- jazz-react@0.13.21
- jazz-tools@0.13.21
- jazz-richtext-prosemirror@0.1.10
## 0.0.75
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-react@0.13.20
- jazz-richtext-prosemirror@0.1.9
## 0.0.74
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-react@0.13.19
- jazz-richtext-prosemirror@0.1.8
## 0.0.73
### Patch Changes

View File

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

View File

@@ -1,5 +1,31 @@
# todo-vue
## 0.0.90
### Patch Changes
- jazz-browser@0.13.21
- jazz-tools@0.13.21
- jazz-vue@0.13.21
## 0.0.89
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-browser@0.13.20
- jazz-vue@0.13.20
## 0.0.88
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-browser@0.13.19
- jazz-vue@0.13.19
## 0.0.87
### Patch Changes

View File

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

View File

@@ -1,5 +1,28 @@
# jazz-example-todo
## 0.0.205
### Patch Changes
- jazz-react@0.13.21
- jazz-tools@0.13.21
## 0.0.204
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-react@0.13.20
## 0.0.203
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-react@0.13.19
## 0.0.202
### Patch Changes

View File

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

View File

@@ -1,5 +1,32 @@
# version-history
## 0.0.84
### Patch Changes
- Updated dependencies [7de210f]
- jazz-inspector@0.13.21
- jazz-react@0.13.21
- jazz-tools@0.13.21
## 0.0.83
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-inspector@0.13.20
- jazz-react@0.13.20
## 0.0.82
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-inspector@0.13.19
- jazz-react@0.13.19
## 0.0.81
### Patch Changes

View File

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

View File

@@ -1,5 +1,24 @@
# cojson-storage-indexeddb
## 0.13.21
### Patch Changes
- Updated dependencies [e14e61f]
- cojson@0.13.21
- cojson-storage@0.13.21
## 0.13.20
### Patch Changes
- Updated dependencies [adfc9a6]
- Updated dependencies [1389207]
- Updated dependencies [d6e143e]
- Updated dependencies [3e6229d]
- cojson-storage@0.13.20
- cojson@0.13.20
## 0.13.18
### Patch Changes

View File

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

View File

@@ -44,10 +44,7 @@ export class IDBNode {
}
static async asPeer(
{
trace,
localNodeName = "local",
}: { trace?: boolean; localNodeName?: string } | undefined = {
{ localNodeName = "local" }: { localNodeName?: string } | undefined = {
localNodeName: "local",
},
): Promise<Peer> {
@@ -57,7 +54,6 @@ export class IDBNode {
{
peer1role: "client",
peer2role: "storage",
trace,
crashOnClose: true,
},
);

View File

@@ -1,5 +1,24 @@
# cojson-storage-sqlite
## 0.13.21
### Patch Changes
- Updated dependencies [e14e61f]
- cojson@0.13.21
- cojson-storage@0.13.21
## 0.13.20
### Patch Changes
- Updated dependencies [adfc9a6]
- Updated dependencies [1389207]
- Updated dependencies [d6e143e]
- Updated dependencies [3e6229d]
- cojson-storage@0.13.20
- cojson@0.13.20
## 0.13.18
### Patch Changes

View File

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

View File

@@ -56,17 +56,15 @@ export class SQLiteNode {
static async asPeer({
filename,
trace,
localNodeName = "local",
}: {
filename: string;
trace?: boolean;
localNodeName?: string;
}): Promise<Peer> {
const [localNodeAsPeer, storageAsPeer] = cojsonInternals.connectedPeers(
localNodeName,
"storage",
{ peer1role: "client", peer2role: "storage", trace, crashOnClose: true },
{ peer1role: "client", peer2role: "storage", crashOnClose: true },
);
await SQLiteNode.open(

View File

@@ -79,7 +79,9 @@ test("should sync and load data from storage", async () => {
).toMatchInlineSnapshot(`
[
"client -> CONTENT Group header: true new: After: 0 New: 3",
"storage -> KNOWN Group sessions: header/3",
"client -> CONTENT Map header: true new: After: 0 New: 1",
"storage -> KNOWN Map sessions: header/1",
]
`);
@@ -164,8 +166,11 @@ test("should load dependencies correctly (group inheritance)", async () => {
).toMatchInlineSnapshot(`
[
"client -> CONTENT ParentGroup header: true new: After: 0 New: 4",
"storage -> KNOWN ParentGroup sessions: header/4",
"client -> CONTENT Group header: true new: After: 0 New: 5",
"storage -> KNOWN Group sessions: header/5",
"client -> CONTENT Map header: true new: After: 0 New: 1",
"storage -> KNOWN Map sessions: header/1",
]
`);
@@ -342,10 +347,13 @@ test("should recover from data loss", async () => {
).toMatchInlineSnapshot(`
[
"client -> CONTENT Group header: true new: After: 0 New: 3",
"storage -> KNOWN Group sessions: header/3",
"client -> CONTENT Map header: true new: After: 0 New: 1",
"storage -> KNOWN Map sessions: header/1",
"client -> CONTENT Map header: false new: After: 3 New: 1",
"storage -> KNOWN CORRECTION Map sessions: header/1",
"client -> CONTENT Map header: false new: After: 1 New: 3",
"storage -> KNOWN Map sessions: header/4",
]
`);

View File

@@ -1,5 +1,23 @@
# cojson-storage
## 0.13.21
### Patch Changes
- Updated dependencies [e14e61f]
- cojson@0.13.21
## 0.13.20
### Patch Changes
- adfc9a6: Make waitForSync work on storage peers by handling optimistic/known states
- Updated dependencies [adfc9a6]
- Updated dependencies [1389207]
- Updated dependencies [d6e143e]
- Updated dependencies [3e6229d]
- cojson@0.13.20
## 0.13.18
### Patch Changes

View File

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

View File

@@ -237,7 +237,13 @@ export class SyncManager {
if ((sessionRow?.lastIdx || 0) < (msg.new[sessionID]?.after || 0)) {
invalidAssumptions = true;
} else {
return this.putNewTxs(msg, sessionID, sessionRow, storedCoValueRowID);
const newLastIdx = await this.putNewTxs(
msg,
sessionID,
sessionRow,
storedCoValueRowID,
);
ourKnown.sessions[sessionID] = newLastIdx;
}
});
}
@@ -248,6 +254,11 @@ export class SyncManager {
...ourKnown,
isCorrection: invalidAssumptions,
});
} else {
this.sendStateMessage({
action: "known",
...ourKnown,
});
}
}
@@ -310,11 +321,13 @@ export class SyncManager {
});
}
return Promise.all(
await Promise.all(
actuallyNewTransactions.map((newTransaction, i) =>
this.dbClient.addTransaction(sessionRowID, nextIdx + i, newTransaction),
),
);
return newLastIdx;
}
handleKnown(_msg: CojsonInternalTypes.KnownStateMessage) {

View File

@@ -291,7 +291,7 @@ describe("DB sync manager", () => {
});
});
test("Saves new transaction without sending message when IDB has fewer transactions", async () => {
test("Saves new transaction and sends an ack message as response", async () => {
DBClient.prototype.getCoValue.mockResolvedValueOnce({
id: coValueIdToLoad,
header: coValueHeader,
@@ -314,7 +314,12 @@ describe("DB sync manager", () => {
incomingTxCount,
);
expect(syncManager.sendStateMessage).not.toBeCalled();
expect(syncManager.sendStateMessage).toBeCalledWith({
action: "known",
header: true,
id: coValueIdToLoad,
sessions: expect.any(Object),
});
});
test("Sends correction message when peer sends a message far ahead of our state due to invalid assumption", async () => {

View File

@@ -1,5 +1,22 @@
# cojson-transport-nodejs-ws
## 0.13.21
### Patch Changes
- Updated dependencies [e14e61f]
- cojson@0.13.21
## 0.13.20
### Patch Changes
- Updated dependencies [adfc9a6]
- Updated dependencies [1389207]
- Updated dependencies [d6e143e]
- Updated dependencies [3e6229d]
- cojson@0.13.20
## 0.13.18
### Patch Changes

View File

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

View File

@@ -1,5 +1,20 @@
# cojson
## 0.13.21
### Patch Changes
- e14e61f: Optimized the acceptInvite flow
## 0.13.20
### Patch Changes
- adfc9a6: Make waitForSync work on storage peers by handling optimistic/known states
- 1389207: Removed throw error when the profile is unavailable after a login
- d6e143e: Wait for storage sync before resolving new account creation
- 3e6229d: Skip closed and unsubscribed peers when calling waitForSync
## 0.13.18
### Patch Changes

View File

@@ -25,7 +25,7 @@
},
"type": "module",
"license": "MIT",
"version": "0.13.18",
"version": "0.13.21",
"devDependencies": {
"@opentelemetry/sdk-metrics": "^2.0.0",
"typescript": "catalog:"

View File

@@ -23,15 +23,7 @@ export class PeerState {
});
this._knownStates = knownStates?.clone() ?? new PeerKnownStates();
// We assume that exchanges with storage peers are always successful
// hence we don't need to differentiate between knownStates and optimisticKnownStates
if (peer.role === "storage") {
this._optimisticKnownStates = "assumeInfallible";
} else {
this._optimisticKnownStates =
knownStates?.clone() ?? new PeerKnownStates();
}
this._optimisticKnownStates = knownStates?.clone() ?? new PeerKnownStates();
}
/**
@@ -52,13 +44,9 @@ export class PeerState {
* The main difference with knownState is that this is updated when the content is sent to the peer without
* waiting for any acknowledgement from the peer.
*/
readonly _optimisticKnownStates: PeerKnownStates | "assumeInfallible";
readonly _optimisticKnownStates: PeerKnownStates;
get optimisticKnownStates(): ReadonlyPeerKnownStates {
if (this._optimisticKnownStates === "assumeInfallible") {
return this.knownStates;
}
return this._optimisticKnownStates;
}
@@ -76,53 +64,33 @@ export class PeerState {
updateHeader(id: RawCoID, header: boolean) {
this._knownStates.updateHeader(id, header);
if (this._optimisticKnownStates !== "assumeInfallible") {
this._optimisticKnownStates.updateHeader(id, header);
}
this._optimisticKnownStates.updateHeader(id, header);
}
combineWith(id: RawCoID, value: CoValueKnownState) {
this._knownStates.combineWith(id, value);
if (this._optimisticKnownStates !== "assumeInfallible") {
this._optimisticKnownStates.combineWith(id, value);
}
this._optimisticKnownStates.combineWith(id, value);
}
combineOptimisticWith(id: RawCoID, value: CoValueKnownState) {
if (this._optimisticKnownStates === "assumeInfallible") {
this._knownStates.combineWith(id, value);
} else {
this._optimisticKnownStates.combineWith(id, value);
}
this._optimisticKnownStates.combineWith(id, value);
}
updateSessionCounter(id: RawCoID, sessionId: SessionID, value: number) {
this._knownStates.updateSessionCounter(id, sessionId, value);
if (this._optimisticKnownStates !== "assumeInfallible") {
this._optimisticKnownStates.updateSessionCounter(id, sessionId, value);
}
this._optimisticKnownStates.updateSessionCounter(id, sessionId, value);
}
setKnownState(id: RawCoID, knownState: CoValueKnownState | "empty") {
this._knownStates.set(id, knownState);
if (this._optimisticKnownStates !== "assumeInfallible") {
this._optimisticKnownStates.set(id, knownState);
}
this._optimisticKnownStates.set(id, knownState);
}
setOptimisticKnownState(
id: RawCoID,
knownState: CoValueKnownState | "empty",
) {
if (this._optimisticKnownStates === "assumeInfallible") {
this._knownStates.set(id, knownState);
} else {
this._optimisticKnownStates.set(id, knownState);
}
this._optimisticKnownStates.set(id, knownState);
}
get id() {

View File

@@ -2,11 +2,7 @@ import { UpDownCounter, ValueType, metrics } from "@opentelemetry/api";
import { Result, err } from "neverthrow";
import { PeerState } from "../PeerState.js";
import { RawCoValue } from "../coValue.js";
import {
ControlledAccount,
ControlledAccountOrAgent,
RawAccountID,
} from "../coValues/account.js";
import { ControlledAccountOrAgent, RawAccountID } from "../coValues/account.js";
import { RawGroup } from "../coValues/group.js";
import { coreToCoValue } from "../coreToCoValue.js";
import {
@@ -315,14 +311,10 @@ export class CoValueCore {
}
}
contentInClonedNodeWithDifferentAccount(
controlledAccountOrAgent: ControlledAccountOrAgent,
): RawCoValue {
const newNode = this.node.cloneWithDifferentAccount(
controlledAccountOrAgent,
);
return newNode.expectCoValueLoaded(this.id).getCurrentContent();
contentInClonedNodeWithDifferentAccount(account: ControlledAccountOrAgent) {
return this.node
.loadCoValueAsDifferentAgent(this.id, account.agentSecret, account.id)
.getCurrentContent();
}
knownState(): CoValueKnownState {
@@ -656,15 +648,15 @@ export class CoValueCore {
a: Pick<DecryptedTransaction, "madeAt" | "txID">,
b: Pick<DecryptedTransaction, "madeAt" | "txID">,
) {
return (
a.madeAt - b.madeAt ||
(a.txID.sessionID === b.txID.sessionID
? 0
: a.txID.sessionID < b.txID.sessionID
? -1
: 1) ||
a.txID.txIndex - b.txID.txIndex
);
if (a.madeAt !== b.madeAt) {
return a.madeAt - b.madeAt;
}
if (a.txID.sessionID === b.txID.sessionID) {
return a.txID.txIndex - b.txID.txIndex;
}
return 0;
}
getCurrentReadKey(): {
@@ -1006,6 +998,10 @@ export class CoValueCore {
const waitingForPeer = new Promise<void>((resolve) => {
const markNotFound = () => {
if (this.peers.get(peer.id)?.type === "pending") {
logger.warn("Timeout waiting for peer to load coValue", {
id: this.id,
peerID: peer.id,
});
this.markNotFoundInPeer(peer.id);
}
};

View File

@@ -1,4 +1,4 @@
import { Result, ResultAsync, err, ok, okAsync } from "neverthrow";
import { Result, err, ok } from "neverthrow";
import { CoID } from "./coValue.js";
import { RawCoValue } from "./coValue.js";
import {
@@ -223,10 +223,19 @@ export class LocalNode {
account.set("profile", profile.id, "trusting");
}
if (!account.get("profile")) {
const profileId = account.get("profile");
if (!profileId) {
throw new Error("Must set account profile in initial migration");
}
if (node.syncManager.hasStoragePeers()) {
await Promise.all([
node.syncManager.waitForStorageSync(account.id),
node.syncManager.waitForStorageSync(profileId),
]);
}
return {
node,
accountID: account.id,
@@ -272,11 +281,9 @@ export class LocalNode {
if (!profileID) {
throw new Error("Account has no profile");
}
const profile = await node.load(profileID);
if (profile === "unavailable") {
throw new Error("Profile unavailable from all peers");
}
// Preload the profile
await node.load(profileID);
if (migration) {
await migration(account, node);
@@ -426,32 +433,44 @@ export class LocalNode {
groupOrOwnedValueID: CoID<T>,
inviteSecret: InviteSecret,
): Promise<void> {
const groupOrOwnedValue = await this.load(groupOrOwnedValueID);
const value = await this.load(groupOrOwnedValueID);
if (groupOrOwnedValue === "unavailable") {
if (value === "unavailable") {
throw new Error(
"Trying to accept invite: Group/owned value unavailable from all peers",
);
}
if (
groupOrOwnedValue.core.verified.header.ruleset.type === "ownedByGroup"
) {
return this.acceptInvite(
groupOrOwnedValue.core.verified.header.ruleset.group as CoID<RawGroup>,
inviteSecret,
);
} else if (
groupOrOwnedValue.core.verified.header.ruleset.type !== "group"
) {
throw new Error("Can only accept invites to groups");
const ruleset = value.core.verified.header.ruleset;
let group: RawGroup;
if (ruleset.type === "unsafeAllowAll") {
throw new Error("Can only accept invites to values owned by groups");
}
const group = expectGroup(groupOrOwnedValue);
if (ruleset.type === "ownedByGroup") {
const owner = await this.load(ruleset.group as CoID<RawGroup>);
if (owner === "unavailable") {
throw new Error(
"Trying to accept invite: CoValue owner unavailable from all peers",
);
}
group = expectGroup(owner);
} else {
group = expectGroup(value);
}
if (group.core.verified.header.meta?.type === "account") {
throw new Error("Can't accept invites to values owned by accounts");
}
const inviteAgentSecret = this.crypto.agentSecretFromSecretSeed(
secretSeedFromInviteSecret(inviteSecret),
);
const inviteAgentID = this.crypto.getAgentID(inviteAgentSecret);
const inviteRole = await new Promise((resolve, reject) => {
@@ -486,9 +505,10 @@ export class LocalNode {
}
const groupAsInvite = expectGroup(
group.core.contentInClonedNodeWithDifferentAccount(
new ControlledAgent(inviteAgentSecret, this.crypto),
),
this.loadCoValueAsDifferentAgent(
group.id,
inviteAgentSecret,
).getCurrentContent(),
);
groupAsInvite.addMemberInternal(
@@ -517,7 +537,7 @@ export class LocalNode {
if (!coValue.isAvailable()) {
throw new Error(
`${expectation ? expectation + ": " : ""}CoValue ${id} not yet loaded. Current state: ${JSON.stringify(coValue)}`,
`${expectation ? expectation + ": " : ""}CoValue ${id} not yet loaded.`,
);
}
return coValue;
@@ -614,46 +634,56 @@ export class LocalNode {
return group;
}
/** @internal */
cloneWithDifferentAccount(
controlledAccountOrAgent: ControlledAccountOrAgent,
): LocalNode {
loadCoValueAsDifferentAgent(
id: RawCoID,
secret: AgentSecret,
accountId?: RawAccountID | AgentID,
) {
const agent = new ControlledAgent(secret, this.crypto);
const newNode = new LocalNode(
controlledAccountOrAgent.agentSecret,
this.crypto.newRandomSessionID(controlledAccountOrAgent.id),
secret,
this.crypto.newRandomSessionID(accountId || agent.id),
this.crypto,
);
newNode.cloneVerifiedStateFrom(this);
newNode.cloneVerifiedStateFrom(this, id);
return newNode;
return newNode.expectCoValueLoaded(id);
}
/** @internal */
cloneVerifiedStateFrom(otherNode: LocalNode) {
const coValuesToCopy = Array.from(otherNode.coValues.entries());
cloneVerifiedStateFrom(otherNode: LocalNode, id: RawCoID) {
const coValuesIdsToCopy = [id];
while (coValuesToCopy.length > 0) {
const [coValueID, coValue] = coValuesToCopy[coValuesToCopy.length - 1]!;
// Scan all the dependencies and add them to the list
for (let i = 0; i < coValuesIdsToCopy.length; i++) {
const coValueID = coValuesIdsToCopy[i]!;
const coValue = otherNode.getCoValue(coValueID);
if (!coValue.isAvailable()) {
coValuesToCopy.pop();
continue;
} else {
const allDepsCopied = coValue
.getDependedOnCoValues()
.every((dep) => this.coValues.get(dep)?.isAvailable());
if (!allDepsCopied) {
// move to end of queue
coValuesToCopy.unshift(coValuesToCopy.pop()!);
continue;
}
this.putCoValue(coValueID, coValue.verified);
coValuesToCopy.pop();
}
for (const dep of coValue.getDependedOnCoValues()) {
coValuesIdsToCopy.push(dep);
}
}
// Copy the coValue all the dependencies by following the dependency order
while (coValuesIdsToCopy.length > 0) {
const coValueID = coValuesIdsToCopy.pop()!;
const coValue = otherNode.getCoValue(coValueID);
if (!coValue.isAvailable()) {
continue;
}
if (this.coValues.get(coValueID)?.isAvailable()) {
continue;
}
this.putCoValue(coValueID, coValue.verified);
}
}

View File

@@ -6,23 +6,17 @@ export function connectedPeers(
peer1id: PeerID,
peer2id: PeerID,
{
trace = false,
peer1role = "client",
peer2role = "client",
crashOnClose = false,
}: {
trace?: boolean;
peer1role?: Peer["role"];
peer2role?: Peer["role"];
crashOnClose?: boolean;
} = {},
): [Peer, Peer] {
const [from1to2Rx, from1to2Tx] = newQueuePair(
trace ? { traceAs: `${peer1id} -> ${peer2id}` } : undefined,
);
const [from2to1Rx, from2to1Tx] = newQueuePair(
trace ? { traceAs: `${peer2id} -> ${peer1id}` } : undefined,
);
const [from1to2Rx, from1to2Tx] = newQueuePair();
const [from2to1Rx, from2to1Tx] = newQueuePair();
const peer2AsPeer: Peer = {
id: peer2id,
@@ -43,32 +37,11 @@ export function connectedPeers(
return [peer1AsPeer, peer2AsPeer];
}
export function newQueuePair(
options: { traceAs?: string } = {},
): [AsyncIterable<SyncMessage>, Channel<SyncMessage>] {
export function newQueuePair(): [
AsyncIterable<SyncMessage>,
Channel<SyncMessage>,
] {
const channel = new Channel<SyncMessage>();
if (options.traceAs) {
return [
(async function* () {
for await (const msg of channel) {
console.debug(
options.traceAs,
JSON.stringify(
msg,
(k, v) =>
k === "changes" || k === "encryptedChanges"
? v.slice(0, 20) + "..."
: v,
2,
),
);
yield msg;
}
})(),
channel,
];
} else {
return [channel.wrap(), channel];
}
return [channel.wrap(), channel];
}

View File

@@ -159,6 +159,12 @@ export class SyncManager {
);
}
hasStoragePeers(): boolean {
return this.getPeers().some(
(peer) => peer.role === "storage" && !peer.closed,
);
}
handleSyncMessage(msg: SyncMessage, peer: PeerState) {
if (this.local.getCoValue(msg.id).isErroredInPeer(peer.id)) {
logger.warn(
@@ -393,10 +399,12 @@ export class SyncManager {
return;
} else {
// Should move the state to loading
this.local.loadCoValueCore(msg.id, peer.id).catch((e) => {
logger.error("Error loading coValue in handleLoad", { err: e });
});
// Syncronously updates the state loading is possible
coValue
.loadFromPeers(this.getServerAndStoragePeers(peer.id))
.catch((e) => {
logger.error("Error loading coValue in handleLoad", { err: e });
});
}
}
@@ -622,28 +630,24 @@ export class SyncManager {
handleUnsubscribe(_msg: DoneMessage) {}
requestedSyncs = new Map<RawCoID, Promise<void>>();
async requestCoValueSync(coValue: CoValueCore) {
const promise = this.requestedSyncs.get(coValue.id);
if (promise) {
return promise;
} else {
const promise = new Promise<void>((resolve) => {
queueMicrotask(() => {
this.requestedSyncs.delete(coValue.id);
this.syncCoValue(coValue);
resolve();
});
});
this.requestedSyncs.set(coValue.id, promise);
return promise;
requestedSyncs = new Set<RawCoID>();
requestCoValueSync(coValue: CoValueCore) {
if (this.requestedSyncs.has(coValue.id)) {
return;
}
queueMicrotask(() => {
if (this.requestedSyncs.has(coValue.id)) {
this.syncCoValue(coValue);
}
});
this.requestedSyncs.add(coValue.id);
}
async syncCoValue(coValue: CoValueCore) {
this.requestedSyncs.delete(coValue.id);
for (const peer of this.peersInPriorityOrder()) {
if (peer.closed) continue;
if (coValue.isErroredInPeer(peer.id)) continue;
@@ -674,6 +678,21 @@ export class SyncManager {
return true;
}
const peerState = this.peers[peerId];
// The peer has been closed, so it isn't possible to sync
if (!peerState || peerState.closed) {
return true;
}
// The client isn't subscribed to the coValue, so we won't sync it
if (
peerState.role === "client" &&
!peerState.optimisticKnownStates.has(id)
) {
return true;
}
return new Promise((resolve, reject) => {
const unsubscribe = this.syncState.subscribeToPeerUpdates(
peerId,
@@ -693,10 +712,20 @@ export class SyncManager {
});
}
async waitForStorageSync(id: RawCoID, timeout = 30_000) {
const peers = this.getPeers();
await Promise.all(
peers
.filter((peer) => peer.role === "storage")
.map((peer) => this.waitForSyncWithPeer(peer.id, id, timeout)),
);
}
async waitForSync(id: RawCoID, timeout = 30_000) {
const peers = this.getPeers();
return Promise.all(
await Promise.all(
peers.map((peer) => this.waitForSyncWithPeer(peer.id, id, timeout)),
);
}

View File

@@ -174,9 +174,6 @@ describe("PeerState", () => {
test("should dispatch to both states", () => {
const { peerState } = setup();
const knownStatesSpy = vi.spyOn(peerState._knownStates, "set");
if (peerState._optimisticKnownStates === "assumeInfallible") {
throw new Error("Expected normal optimisticKnownStates");
}
const optimisticKnownStatesSpy = vi.spyOn(
peerState._optimisticKnownStates,
@@ -195,40 +192,6 @@ describe("PeerState", () => {
expect(optimisticKnownStatesSpy).toHaveBeenCalledWith("co_z1", state);
});
test("should use same reference for knownStates and optimisticKnownStates for storage peers", () => {
const mockStoragePeer: Peer = {
id: "test-storage-peer",
role: "storage",
priority: 1,
crashOnClose: false,
incoming: (async function* () {})(),
outgoing: {
push: vi.fn().mockResolvedValue(undefined),
close: vi.fn(),
},
};
const peerState = new PeerState(mockStoragePeer, undefined);
// Verify they are the same reference
expect(peerState.knownStates).toBe(peerState.optimisticKnownStates);
// Verify that dispatching only updates one state
const knownStatesSpy = vi.spyOn(peerState._knownStates, "set");
expect(peerState._optimisticKnownStates).toBe("assumeInfallible");
const state: CoValueKnownState = {
id: "co_z1",
header: false,
sessions: {},
};
peerState.setKnownState("co_z1", state);
// Only one dispatch should happen since they're the same reference
expect(knownStatesSpy).toHaveBeenCalledTimes(1);
expect(knownStatesSpy).toHaveBeenCalledWith("co_z1", state);
});
test("should use separate references for knownStates and optimisticKnownStates for non-storage peers", () => {
const { peerState } = setup(); // Uses a regular peer

View File

@@ -70,12 +70,12 @@ describe("SyncStateManager", () => {
const map = group.createMap();
map.set("key1", "value1", "trusting");
const [clientStoragePeer] = connectedPeers("clientStorage", "unusedPeer", {
peer1role: "client",
peer2role: "server",
const [serverPeer] = connectedPeers("serverPeer", "unusedPeer", {
peer1role: "server",
peer2role: "client",
});
client.node.syncManager.addPeer(clientStoragePeer);
client.node.syncManager.addPeer(serverPeer);
const subscriptionManager = client.node.syncManager.syncState;
@@ -86,7 +86,7 @@ describe("SyncStateManager", () => {
updateToJazzCloudSpy,
);
const unsubscribe2 = subscriptionManager.subscribeToPeerUpdates(
clientStoragePeer.id,
serverPeer.id,
updateToStorageSpy,
);
@@ -115,7 +115,7 @@ describe("SyncStateManager", () => {
);
expect(updateToStorageSpy).toHaveBeenLastCalledWith(
emptyKnownState(map.core.id),
emptyKnownState(group.core.id),
{ uploaded: false },
);
});
@@ -247,4 +247,54 @@ describe("SyncStateManager", () => {
),
).toEqual({ uploaded: true });
});
test("should skip closed peers", async () => {
const client = setupTestNode();
const { peerState } = client.connectToSyncServer();
peerState.gracefulShutdown();
const group = client.node.createGroup();
const map = group.createMap();
await expect(map.core.waitForSync()).resolves.toBeUndefined();
});
test("should skip client peers that are not subscribed to the coValue", async () => {
const server = setupTestNode({ isSyncServer: true });
const client = setupTestNode();
client.connectToSyncServer({
syncServer: server.node,
});
const group = server.node.createGroup();
const map = group.createMap();
await map.core.waitForSync();
expect(client.node.getCoValue(map.id).isAvailable()).toBe(false);
});
test("should wait for client peers that are subscribed to the coValue", async () => {
const server = setupTestNode({ isSyncServer: true });
const client = setupTestNode();
const { peerStateOnServer } = client.connectToSyncServer();
const group = server.node.createGroup();
const map = group.createMap();
map.set("key1", "value1", "trusting");
// Simulate the subscription to the coValue
peerStateOnServer.setKnownState(map.core.id, {
id: map.core.id,
header: true,
sessions: {},
});
await map.core.waitForSync();
expect(client.node.getCoValue(map.id).isAvailable()).toBe(true);
});
});

View File

@@ -1,7 +1,9 @@
import { expect, test } from "vitest";
import { expectAccount } from "../coValues/account.js";
import { WasmCrypto } from "../crypto/WasmCrypto.js";
import { LocalNode } from "../localNode.js";
import { connectedPeers } from "../streamUtils.js";
import { createMockStoragePeer } from "./testUtils.js";
const Crypto = await WasmCrypto.create();
@@ -85,3 +87,25 @@ test("throws an error if the user tried to create an invite from an account", as
"Cannot create invite from an account",
);
});
test("wait for storage sync before resolving withNewlyCreatedAccount", async () => {
const { storage, peer } = createMockStoragePeer({
peerId: "account-node",
});
const { node, accountID } = await LocalNode.withNewlyCreatedAccount({
creationProps: { name: "Hermes Puggington" },
crypto: Crypto,
peersToLoadFrom: [peer],
});
const account = storage.getCoValue(accountID);
expect(account.isAvailable()).toBe(true);
const profile = storage.getCoValue(
expectAccount(account.getCurrentContent()).get("profile")!,
);
expect(profile.isAvailable()).toBe(true);
});

View File

@@ -750,7 +750,6 @@ describe("extend with role mapping", () => {
const mapOnNode2 = await loadCoValueOrFail(node2.node, map.id);
expect(mapOnNode2.get("test")).toEqual("Written from the admin");
mapOnNode2.set("test", "Written from the inherited role");
expect(mapOnNode2.get("test")).toEqual("Written from the inherited role");

View File

@@ -512,7 +512,6 @@ test("Admins can set group read key and then use it to create private transactio
const { node, groupCore, admin } = newGroup();
const reader1 = createAccountInNode(node);
const reader2 = createAccountInNode(node);
const { secret: readKey, id: readKeyID } = Crypto.newRandomKeySecret();
@@ -583,6 +582,9 @@ test("Admins can set group read key and then use it to create private transactio
childObject.contentInClonedNodeWithDifferentAccount(reader2),
);
// Need to copy the account coValue to the new node to be able to read the readKey
childObjectAsReader2.core.node.cloneVerifiedStateFrom(node, reader2.id);
expect(childObjectAsReader2.core.getCurrentReadKey().secret).toEqual(readKey);
expect(childObjectAsReader2.get("foo")).toEqual("bar");
});
@@ -1113,9 +1115,10 @@ test("Admins can create an adminInvite, which can add an admin (high-level)", as
const invitedAdminSecret = Crypto.newRandomAgentSecret();
const invitedAdminID = Crypto.getAgentID(invitedAdminSecret);
const nodeAsInvitedAdmin = node.cloneWithDifferentAccount(
new ControlledAgent(invitedAdminSecret, Crypto),
);
const nodeAsInvitedAdmin = node.loadCoValueAsDifferentAgent(
group.id,
invitedAdminSecret,
).node;
await nodeAsInvitedAdmin.acceptInvite(group.id, inviteSecret);
@@ -1219,9 +1222,10 @@ test("Admins can create a writerInvite, which can add a writer (high-level)", as
const invitedWriterSecret = Crypto.newRandomAgentSecret();
const invitedWriterID = Crypto.getAgentID(invitedWriterSecret);
const nodeAsInvitedWriter = node.cloneWithDifferentAccount(
new ControlledAgent(invitedWriterSecret, Crypto),
);
const nodeAsInvitedWriter = node.loadCoValueAsDifferentAgent(
group.id,
invitedWriterSecret,
).node;
await nodeAsInvitedWriter.acceptInvite(group.id, inviteSecret);
@@ -1308,9 +1312,10 @@ test("Admins can create a readerInvite, which can add a reader (high-level)", as
const invitedReaderSecret = Crypto.newRandomAgentSecret();
const invitedReaderID = Crypto.getAgentID(invitedReaderSecret);
const nodeAsInvitedReader = node.cloneWithDifferentAccount(
new ControlledAgent(invitedReaderSecret, Crypto),
);
const nodeAsInvitedReader = node.loadCoValueAsDifferentAgent(
group.id,
invitedReaderSecret,
).node;
await nodeAsInvitedReader.acceptInvite(group.id, inviteSecret);

View File

@@ -1,16 +1,11 @@
import { assert, beforeEach, describe, expect, test } from "vitest";
import { expectMap } from "../coValue";
import { expectAccount } from "../coValues/account";
import { WasmCrypto } from "../crypto/WasmCrypto";
import { LocalNode } from "../localNode";
import { toSimplifiedMessages } from "./messagesTestUtils";
import {
SyncMessagesLog,
getSyncServerConnectedPeer,
loadCoValueOrFail,
setupTestNode,
waitFor,
} from "./testUtils";
const Crypto = await WasmCrypto.create();
@@ -27,7 +22,7 @@ describe("LocalNode auth sync", () => {
peerId: "new-account",
});
const { accountID, node } = await LocalNode.withNewlyCreatedAccount({
const { node } = await LocalNode.withNewlyCreatedAccount({
creationProps: {
name: "new-account",
},
@@ -128,6 +123,7 @@ describe("LocalNode auth sync", () => {
test("authenticate to an existing account", async () => {
const { peer: newAccountPeer } = getSyncServerConnectedPeer({
peerId: "new-account",
ourName: "creation-node",
});
const { accountID, accountSecret } =
@@ -141,10 +137,9 @@ describe("LocalNode auth sync", () => {
const { peer: existingAccountPeer } = getSyncServerConnectedPeer({
peerId: "existing-account",
ourName: "auth-node",
});
SyncMessagesLog.clear();
const node = await LocalNode.withLoadedAccount({
accountID,
accountSecret,
@@ -169,19 +164,82 @@ describe("LocalNode auth sync", () => {
}),
).toMatchInlineSnapshot(`
[
"client -> server | LOAD Account sessions: empty",
"server -> client | KNOWN Account sessions: header/4",
"client -> server | CONTENT ProfileGroup header: true new: After: 0 New: 5",
"server -> client | CONTENT Account header: true new: After: 0 New: 4",
"server -> client | KNOWN ProfileGroup sessions: header/5",
"client -> server | CONTENT Profile header: true new: After: 0 New: 1",
"client -> server | KNOWN Account sessions: header/4",
"server -> client | KNOWN Profile sessions: header/1",
"client -> server | LOAD Profile sessions: empty",
"server -> client | CONTENT ProfileGroup header: true new: After: 0 New: 5",
"client -> server | KNOWN ProfileGroup sessions: header/5",
"server -> client | CONTENT Profile header: true new: After: 0 New: 1",
"client -> server | KNOWN Profile sessions: header/1",
"creation-node -> server | CONTENT Account header: true new: After: 0 New: 4",
"auth-node -> server | LOAD Account sessions: empty",
"server -> creation-node | KNOWN Account sessions: header/4",
"creation-node -> server | CONTENT ProfileGroup header: true new: After: 0 New: 5",
"server -> auth-node | CONTENT Account header: true new: After: 0 New: 4",
"server -> creation-node | KNOWN ProfileGroup sessions: header/5",
"creation-node -> server | CONTENT Profile header: true new: After: 0 New: 1",
"auth-node -> server | KNOWN Account sessions: header/4",
"server -> creation-node | KNOWN Profile sessions: header/1",
"auth-node -> server | LOAD Profile sessions: empty",
"server -> auth-node | CONTENT ProfileGroup header: true new: After: 0 New: 5",
"auth-node -> server | KNOWN ProfileGroup sessions: header/5",
"server -> auth-node | CONTENT Profile header: true new: After: 0 New: 1",
"auth-node -> server | KNOWN Profile sessions: header/1",
]
`);
});
test("authenticate to an existing account after immediately close the creation node", async () => {
const { peer: newAccountPeer } = getSyncServerConnectedPeer({
peerId: "new-account",
ourName: "creation-node",
});
const {
accountID,
accountSecret,
node: creationNode,
} = await LocalNode.withNewlyCreatedAccount({
creationProps: {
name: "new-account",
},
peersToLoadFrom: [newAccountPeer],
crypto: Crypto,
});
creationNode.gracefulShutdown();
const { peer: existingAccountPeer } = getSyncServerConnectedPeer({
peerId: "existing-account",
ourName: "auth-node",
});
const node = await LocalNode.withLoadedAccount({
accountID,
accountSecret,
peersToLoadFrom: [existingAccountPeer],
sessionID: undefined,
crypto: Crypto,
});
const account = node.expectCurrentAccount("after login");
const profile = creationNode.getCoValue(account.get("profile")!);
assert(profile.isAvailable());
expect(account.id).toBe(accountID);
expect(node.agentSecret).toBe(accountSecret);
expect(
SyncMessagesLog.getMessages({
Account: account.core,
Profile: profile,
ProfileGroup: profile.getGroup().core,
}),
).toMatchInlineSnapshot(`
[
"creation-node -> server | CONTENT Account header: true new: After: 0 New: 4",
"auth-node -> server | LOAD Account sessions: empty",
"server -> creation-node | KNOWN Account sessions: header/4",
"server -> auth-node | CONTENT Account header: true new: After: 0 New: 4",
"auth-node -> server | KNOWN Account sessions: header/4",
"auth-node -> server | LOAD Profile sessions: empty",
"server -> auth-node | KNOWN Profile sessions: empty",
"auth-node -> server | LOAD Profile sessions: empty",
"server -> auth-node | KNOWN Profile sessions: empty",
]
`);
});

View File

@@ -0,0 +1,163 @@
import { beforeEach, describe, expect, test } from "vitest";
import { expectList, expectMap } from "../coValue";
import { WasmCrypto } from "../crypto/WasmCrypto";
import {
SyncMessagesLog,
loadCoValueOrFail,
setupTestNode,
waitFor,
} from "./testUtils";
const Crypto = await WasmCrypto.create();
let jazzCloud = setupTestNode({ isSyncServer: true });
beforeEach(async () => {
SyncMessagesLog.clear();
jazzCloud = setupTestNode({ isSyncServer: true });
});
describe("invitations sync", () => {
test("invite a client to a group", async () => {
const client = setupTestNode();
client.connectToSyncServer({
ourName: "invite-provider",
});
const client2 = setupTestNode();
client2.connectToSyncServer({
ourName: "invite-consumer",
});
const group = client.node.createGroup();
const map = group.createMap();
map.set("hello", "world");
await map.core.waitForSync();
const invite = group.createInvite("reader");
await group.core.waitForSync();
SyncMessagesLog.clear();
await client2.node.acceptInvite(map.id, invite);
const mapOnClient2 = await loadCoValueOrFail(client2.node, map.id);
expect(mapOnClient2.get("hello")).toEqual("world");
expect(
SyncMessagesLog.getMessages({
Group: group.core,
Map: map.core,
}),
).toMatchInlineSnapshot(`
[
"invite-consumer -> server | LOAD Map sessions: empty",
"server -> invite-consumer | CONTENT Group header: true new: After: 0 New: 5",
"invite-consumer -> server | KNOWN Group sessions: header/5",
"server -> invite-consumer | CONTENT Map header: true new: After: 0 New: 1",
"invite-consumer -> server | KNOWN Map sessions: header/1",
]
`);
});
test("invite a client to a group with parents", async () => {
const client = setupTestNode();
client.connectToSyncServer({
ourName: "invite-provider",
});
const client2 = setupTestNode();
client2.connectToSyncServer({
ourName: "invite-consumer",
});
const parentGroup = client.node.createGroup();
const group = client.node.createGroup();
group.extend(parentGroup);
const map = group.createMap();
map.set("hello", "world");
await map.core.waitForSync();
const invite = group.createInvite("reader");
await group.core.waitForSync();
SyncMessagesLog.clear();
await client2.node.acceptInvite(map.id, invite);
const mapOnClient2 = await loadCoValueOrFail(client2.node, map.id);
expect(mapOnClient2.get("hello")).toEqual("world");
expect(
SyncMessagesLog.getMessages({
Group: group.core,
ParentGroup: parentGroup.core,
Map: map.core,
}),
).toMatchInlineSnapshot(`
[
"invite-consumer -> server | LOAD Map sessions: empty",
"server -> invite-consumer | CONTENT ParentGroup header: true new: After: 0 New: 4",
"invite-consumer -> server | KNOWN ParentGroup sessions: header/4",
"server -> invite-consumer | CONTENT Group header: true new: After: 0 New: 7",
"invite-consumer -> server | KNOWN Group sessions: header/7",
"server -> invite-consumer | CONTENT Map header: true new: After: 0 New: 1",
"invite-consumer -> server | KNOWN Map sessions: header/1",
]
`);
});
test("invite a client to a parent group", async () => {
const client = setupTestNode();
client.connectToSyncServer({
ourName: "invite-provider",
});
const client2 = setupTestNode();
client2.connectToSyncServer({
ourName: "invite-consumer",
});
const parentGroup = client.node.createGroup();
const group = client.node.createGroup();
group.extend(parentGroup);
const map = group.createMap();
map.set("hello", "world");
await map.core.waitForSync();
const invite = parentGroup.createInvite("reader");
await parentGroup.core.waitForSync();
SyncMessagesLog.clear();
await client2.node.acceptInvite(parentGroup.id, invite);
const mapOnClient2 = await loadCoValueOrFail(client2.node, map.id);
expect(mapOnClient2.get("hello")).toEqual("world");
expect(
SyncMessagesLog.getMessages({
Group: group.core,
ParentGroup: parentGroup.core,
Map: map.core,
}),
).toMatchInlineSnapshot(`
[
"invite-consumer -> server | LOAD ParentGroup sessions: empty",
"server -> invite-consumer | CONTENT ParentGroup header: true new: After: 0 New: 6",
"invite-consumer -> server | KNOWN ParentGroup sessions: header/6",
"invite-consumer -> server | LOAD Map sessions: empty",
"server -> invite-consumer | CONTENT Group header: true new: After: 0 New: 5",
"invite-consumer -> server | KNOWN Group sessions: header/5",
"server -> invite-consumer | CONTENT Map header: true new: After: 0 New: 1",
"invite-consumer -> server | CONTENT ParentGroup header: false new: After: 0 New: 2",
"server -> invite-consumer | KNOWN ParentGroup sessions: header/8",
"server -> invite-provider | CONTENT ParentGroup header: false new: After: 0 New: 2",
"invite-consumer -> server | KNOWN Map sessions: header/1",
"invite-provider -> server | KNOWN ParentGroup sessions: header/8",
]
`);
});
});

View File

@@ -197,8 +197,6 @@ describe("loading coValues from server", () => {
await map.core.waitForSync();
await mapOnClient.core.waitForSync();
expect(mapOnClient.get("fromServer")).toEqual("updated");
expect(mapOnClient.get("fromClient")).toEqual("updated");
expect(
SyncMessagesLog.getMessages({
Group: group.core,
@@ -217,6 +215,9 @@ describe("loading coValues from server", () => {
"client -> server | KNOWN Map sessions: header/3",
]
`);
expect(mapOnClient.get("fromServer")).toEqual("updated");
expect(mapOnClient.get("fromClient")).toEqual("updated");
});
test("wrong optimistic known state should be corrected", async () => {

View File

@@ -448,6 +448,31 @@ export function getSyncServerConnectedPeer(opts: {
};
}
export function createMockStoragePeer(opts: {
ourName?: string;
peerId: string;
}) {
const storage = createTestNode();
const { peer1, peer2 } = connectedPeersWithMessagesTracking({
peer1: { id: storage.getCurrentAgent().id, role: "storage" },
peer2: {
id: opts.peerId,
role: "client",
name: opts.ourName,
},
});
peer1.priority = 100;
storage.syncManager.addPeer(peer2);
return {
storage,
peer: peer1,
};
}
export function setupTestNode(
opts: {
isSyncServer?: boolean;
@@ -485,26 +510,14 @@ export function setupTestNode(
}
function addStoragePeer(opts: { ourName?: string } = {}) {
const storage = createTestNode();
const { peer1, peer2 } = connectedPeersWithMessagesTracking({
peer1: { id: storage.getCurrentAgent().id, role: "storage" },
peer2: {
id: node.getCurrentAgent().id,
role: "client",
name: opts.ourName,
},
const { peer, storage } = createMockStoragePeer({
peerId: node.getCurrentAgent().id,
ourName: opts.ourName,
});
peer1.priority = 100;
node.syncManager.addPeer(peer);
node.syncManager.addPeer(peer1);
storage.syncManager.addPeer(peer2);
return {
storage,
peer: node.syncManager.peers[peer1.id]!,
};
return { peer, peerState: node.syncManager.peers[peer.id]!, storage };
}
if (opts.connected) {
@@ -575,26 +588,14 @@ export async function setupTestAccount(
}
function addStoragePeer(opts: { ourName?: string } = {}) {
const storage = createTestNode();
const { peer1, peer2 } = connectedPeersWithMessagesTracking({
peer1: { id: storage.getCurrentAgent().id, role: "storage" },
peer2: {
id: ctx.node.getCurrentAgent().id,
role: "client",
name: opts.ourName,
},
const { peer, storage } = createMockStoragePeer({
peerId: ctx.node.getCurrentAgent().id,
ourName: opts.ourName,
});
peer1.priority = 100;
ctx.node.syncManager.addPeer(peer);
ctx.node.syncManager.addPeer(peer1);
storage.syncManager.addPeer(peer2);
return {
storage,
peer: ctx.node.syncManager.peers[peer1.id]!,
};
return { peer, peerState: ctx.node.syncManager.peers[peer.id]!, storage };
}
if (opts.connected) {

View File

@@ -1,9 +0,0 @@
export function parseError(e: unknown): {
message: string | null;
stack: string | null;
} {
return {
message: e instanceof Error ? e.message : null,
stack: e instanceof Error ? (e.stack ?? null) : null,
};
}

View File

@@ -1,5 +1,35 @@
# jazz-auth-clerk
## 0.13.21
### Patch Changes
- Updated dependencies [e14e61f]
- cojson@0.13.21
- jazz-browser@0.13.21
- jazz-tools@0.13.21
## 0.13.20
### Patch Changes
- Updated dependencies [adfc9a6]
- Updated dependencies [1389207]
- Updated dependencies [d6e143e]
- Updated dependencies [439f0fe]
- Updated dependencies [3e6229d]
- cojson@0.13.20
- jazz-tools@0.13.20
- jazz-browser@0.13.20
## 0.13.19
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-browser@0.13.19
## 0.13.18
### Patch Changes

View File

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

View File

@@ -1,5 +1,28 @@
# jazz-browser-media-images
## 0.13.21
### Patch Changes
- jazz-browser@0.13.21
- jazz-tools@0.13.21
## 0.13.20
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-browser@0.13.20
## 0.13.19
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-browser@0.13.19
## 0.13.18
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-browser-media-images",
"version": "0.13.18",
"version": "0.13.21",
"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.13.18",
"jazz-tools": "workspace:0.13.18",
"jazz-browser": "workspace:0.13.21",
"jazz-tools": "workspace:0.13.21",
"pica": "^9.0.1"
},
"scripts": {

View File

@@ -1,5 +1,36 @@
# jazz-browser
## 0.13.21
### Patch Changes
- Updated dependencies [e14e61f]
- cojson@0.13.21
- cojson-storage-indexeddb@0.13.21
- cojson-transport-ws@0.13.21
- jazz-tools@0.13.21
## 0.13.20
### Patch Changes
- Updated dependencies [adfc9a6]
- Updated dependencies [1389207]
- Updated dependencies [d6e143e]
- Updated dependencies [439f0fe]
- Updated dependencies [3e6229d]
- cojson@0.13.20
- jazz-tools@0.13.20
- cojson-storage-indexeddb@0.13.20
- cojson-transport-ws@0.13.20
## 0.13.19
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
## 0.13.18
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-browser",
"version": "0.13.18",
"version": "0.13.21",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",

View File

@@ -1,5 +1,43 @@
# jazz-browser
## 0.13.21
### Patch Changes
- Updated dependencies [e14e61f]
- cojson@0.13.21
- cojson-transport-ws@0.13.21
- jazz-auth-clerk@0.13.21
- jazz-react-core@0.13.21
- jazz-react-native-core@0.13.21
- jazz-tools@0.13.21
## 0.13.20
### Patch Changes
- Updated dependencies [adfc9a6]
- Updated dependencies [1389207]
- Updated dependencies [d6e143e]
- Updated dependencies [439f0fe]
- Updated dependencies [3e6229d]
- cojson@0.13.20
- jazz-tools@0.13.20
- jazz-react-native-core@0.13.20
- cojson-transport-ws@0.13.20
- jazz-auth-clerk@0.13.20
- jazz-react-core@0.13.20
## 0.13.19
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-auth-clerk@0.13.19
- jazz-react-core@0.13.19
- jazz-react-native-core@0.13.19
## 0.13.18
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-expo",
"version": "0.13.18",
"version": "0.13.21",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",

View File

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

View File

@@ -0,0 +1,9 @@
# jazz-inspector-element
## 0.13.21
### Patch Changes
- Updated dependencies [7de210f]
- jazz-inspector@0.13.21
- jazz-tools@0.13.21

View File

@@ -0,0 +1,31 @@
# Jazz Inspector Element
A custom element that renders the Jazz Inspector component for inspecting Jazz accounts and CoValues.
## Installation
```bash
npm install jazz-inspector-element
```
## Usage
By default jazz-inspector-element relies on `Account.getMe()` to link itself to the Jazz context, so it's enough to mount the html element in a page with a loaded Jazz app:
```ts
import "jazz-inspector-element"
document.body.appendChild(document.createElement("jazz-inspector"))
```
OR
```ts
import "jazz-inspector-element" // somewhere in your app modules
```
and
```html
<jazz-inspector />
```

View File

@@ -0,0 +1,32 @@
{
"name": "jazz-inspector-element",
"version": "0.13.21",
"type": "module",
"main": "./dist/main.js",
"types": "./dist/main.d.ts",
"files": [
"dist/**",
"src"
],
"scripts": {
"dev": "rm -rf ./dist && tsc --emitDeclarationOnly --watch & vite build --watch",
"build": "rm -rf ./dist && tsc --sourceMap --declaration --outDir dist",
"format-and-lint": "biome check .",
"format-and-lint:fix": "biome check . --write",
"preview": "vite preview"
},
"dependencies": {
"jazz-inspector": "workspace:*",
"jazz-tools": "workspace:*",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react-swc": "^3.3.2",
"rollup-plugin-node-externals": "^8.0.0",
"typescript": "catalog:",
"vite": "catalog:"
}
}

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