Compare commits

..

5 Commits

Author SHA1 Message Date
Anselm Eickhoff
6921e621d7 Merge pull request #749 from gardencmp/changeset-release/main
Version Packages
2024-11-14 09:41:28 +00:00
github-actions[bot]
1b0ef401fb Version Packages 2024-11-14 09:29:38 +00:00
pax
1833983b8d Merge pull request #754 from gardencmp/jazz-tools-ts-target
Change jazz-tools TS target to ES2021
2024-11-14 11:28:21 +02:00
pax-k
149ca97c48 chore: changeset 2024-11-14 11:15:25 +02:00
pax-k
f01a7621b0 fix: change jazz-tools TS target to ES2021 2024-11-14 11:13:13 +02:00
81 changed files with 642 additions and 1020 deletions

View File

@@ -1,5 +0,0 @@
---
"cojson": patch
---
Improved the known state tracking within the PeerState.knownState property

View File

@@ -1,5 +1,15 @@
# @jazz-e2e/binarycostream
## 0.0.99
### Patch Changes
- Updated dependencies [0f30eea]
- Updated dependencies [149ca97]
- cojson@0.8.21
- jazz-tools@0.8.21
- jazz-react@0.8.21
## 0.0.98
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "@jazz-e2e/binarycostream",
"private": true,
"version": "0.0.98",
"version": "0.0.99",
"type": "module",
"scripts": {
"dev": "vite",
@@ -13,11 +13,11 @@
"test:ui": "playwright test --ui"
},
"dependencies": {
"cojson": "workspace:0.8.19",
"cojson": "workspace:0.8.21",
"hash-slash": "workspace:0.2.1",
"is-ci": "^3.0.1",
"jazz-react": "workspace:0.8.20",
"jazz-tools": "workspace:0.8.19",
"jazz-react": "workspace:0.8.21",
"jazz-tools": "workspace:0.8.21",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},

View File

@@ -1,14 +1,17 @@
import { Account, BinaryCoStream, ID } from "jazz-tools";
import { useEffect } from "react";
import { useAccount, useCoState } from "./jazz";
import { waitForCoValue } from "./lib/waitForCoValue";
import { UploadedFile } from "./schema";
async function getUploadedFile(me: Account, uploadedFileId: ID<UploadedFile>) {
const uploadedFile = await UploadedFile.load(uploadedFileId, me, {});
if (!uploadedFile) {
throw new Error("Uploaded file not found");
}
const uploadedFile = await waitForCoValue(
UploadedFile,
uploadedFileId,
me,
Boolean,
{},
);
uploadedFile.coMapDownloaded = true;

View File

@@ -1,5 +1,15 @@
# @jazz-e2e/covalues
## 0.0.98
### Patch Changes
- Updated dependencies [0f30eea]
- Updated dependencies [149ca97]
- cojson@0.8.21
- jazz-tools@0.8.21
- jazz-react@0.8.21
## 0.0.97
### Patch Changes

View File

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

View File

@@ -3,7 +3,6 @@ import ReactDOM from "react-dom/client";
import { RouterProvider, createBrowserRouter } from "react-router-dom";
import { AuthAndJazz } from "./jazz";
import { ResumeSyncState } from "./pages/ResumeSyncState";
import { RetryUnavailable } from "./pages/RetryUnavailable";
import { TestInput } from "./pages/TestInput";
const router = createBrowserRouter([
@@ -15,10 +14,6 @@ const router = createBrowserRouter([
path: "/resume-sync",
element: <ResumeSyncState />,
},
{
path: "/retry-unavailable",
element: <RetryUnavailable />,
},
{
path: "/",
element: <TestInput />,

View File

@@ -1,46 +0,0 @@
import { CoMap, Group, ID, co } from "jazz-tools";
import { useEffect, useState } from "react";
import { useAccount, useCoState } from "../jazz";
export class RetryUnavailableCoMap extends CoMap {
value = co.string;
}
function getIdParam() {
const url = new URL(window.location.href);
return (url.searchParams.get("id") as ID<RetryUnavailableCoMap>) ?? undefined;
}
export function RetryUnavailable() {
const [id, setId] = useState(getIdParam);
const coMap = useCoState(RetryUnavailableCoMap, id);
const { me } = useAccount();
useEffect(() => {
if (id) {
const url = new URL(window.location.href);
url.searchParams.set("id", id);
history.pushState({}, "", url.toString());
}
}, [id]);
const createCoMap = () => {
if (!me || id) return;
const group = Group.create({ owner: me });
group.addMember("everyone", "writer");
setId(
RetryUnavailableCoMap.create({ value: "Hello!" }, { owner: group }).id,
);
};
return (
<div>
<h1>Retry Unavailable</h1>
<p data-testid="id">{coMap?.id}</p>
<button onClick={createCoMap}>Create a new value!</button>
</div>
);
}

View File

@@ -1,34 +0,0 @@
import { setTimeout } from "node:timers/promises";
import { expect, test } from "@playwright/test";
test.describe("Retry unavailable states", () => {
test("should retry unavailable values", async ({ page, browser }) => {
const context = page.context();
await page.goto("/retry-unavailable?userName=SuperMario");
await context.setOffline(true);
await page.getByRole("button", { name: "Create a new value!" }).click();
const id = await page.getByTestId("id").textContent();
// Create a new incognito instance and try to load the coValue
const newUserPage = await (await browser.newContext()).newPage();
await newUserPage.goto(`/retry-unavailable?userName=Luigi&id=${id}`);
await expect(newUserPage.getByTestId("id")).toBeInViewport({
timeout: 20_000,
});
// Make the load fail at least twice
await setTimeout(1000);
// Go back online, the value should be uploaded
await context.setOffline(false);
await expect(newUserPage.getByTestId("id")).toHaveText(id ?? "", {
timeout: 20_000,
});
});
});

View File

@@ -1,5 +1,14 @@
# jazz-example-book-shelf
## 0.1.14
### Patch Changes
- Updated dependencies [149ca97]
- jazz-tools@0.8.21
- jazz-react@0.8.21
- jazz-browser-media-images@0.8.21
## 0.1.13
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-example-book-shelf",
"version": "0.1.13",
"version": "0.1.14",
"private": true,
"scripts": {
"dev": "next dev",
@@ -11,9 +11,9 @@
},
"dependencies": {
"clsx": "^2.0.0",
"jazz-browser-media-images": "workspace:0.8.20",
"jazz-react": "workspace:0.8.20",
"jazz-tools": "workspace:0.8.19",
"jazz-browser-media-images": "workspace:0.8.21",
"jazz-react": "workspace:0.8.21",
"jazz-tools": "workspace:0.8.21",
"next": "14.2.5",
"react": "^18.2.0",
"react-dom": "^18.2.0"

View File

@@ -1,5 +1,16 @@
# jazz-example-chat
## 0.0.98
### Patch Changes
- Updated dependencies [0f30eea]
- Updated dependencies [149ca97]
- cojson@0.8.21
- jazz-tools@0.8.21
- jazz-react@0.8.21
- jazz-react-auth-clerk@0.8.21
## 0.0.97
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-chat-clerk",
"private": true,
"version": "0.0.97",
"version": "0.0.98",
"type": "module",
"scripts": {
"dev": "vite",
@@ -17,11 +17,11 @@
"@radix-ui/react-toast": "^1.1.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"cojson": "workspace:0.8.19",
"cojson": "workspace:0.8.21",
"hash-slash": "workspace:0.2.1",
"jazz-react": "workspace:0.8.20",
"jazz-react-auth-clerk": "workspace:0.8.20",
"jazz-tools": "workspace:0.8.19",
"jazz-react": "workspace:0.8.21",
"jazz-react-auth-clerk": "workspace:0.8.21",
"jazz-tools": "workspace:0.8.21",
"lucide-react": "^0.274.0",
"qrcode": "^1.5.3",
"react": "^18.2.0",

View File

@@ -1,5 +1,15 @@
# chat-rn-clerk
## 1.0.14
### Patch Changes
- Updated dependencies [149ca97]
- jazz-tools@0.8.21
- jazz-react-auth-clerk@0.8.21
- jazz-react-native@0.8.21
- jazz-react-native-media-images@0.8.17
## 1.0.13
### Patch Changes

View File

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

View File

@@ -1,5 +1,13 @@
# chat-rn
## 1.0.16
### Patch Changes
- Updated dependencies [149ca97]
- jazz-tools@0.8.21
- jazz-react-native@0.8.21
## 1.0.15
### Patch Changes

View File

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

View File

@@ -1,5 +1,14 @@
# chat-vue
## 0.0.6
### Patch Changes
- Updated dependencies [149ca97]
- jazz-tools@0.8.21
- jazz-browser@0.8.21
- jazz-vue@0.8.11
## 0.0.5
### Patch Changes

View File

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

View File

@@ -1,5 +1,15 @@
# jazz-example-chat
## 0.0.100
### Patch Changes
- Updated dependencies [0f30eea]
- Updated dependencies [149ca97]
- cojson@0.8.21
- jazz-tools@0.8.21
- jazz-react@0.8.21
## 0.0.99
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-chat",
"private": true,
"version": "0.0.99",
"version": "0.0.100",
"type": "module",
"scripts": {
"dev": "vite",
@@ -18,10 +18,10 @@
"@radix-ui/react-toast": "^1.1.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"cojson": "workspace:0.8.19",
"cojson": "workspace:0.8.21",
"hash-slash": "workspace:0.2.1",
"jazz-react": "workspace:0.8.20",
"jazz-tools": "workspace:0.8.19",
"jazz-react": "workspace:0.8.21",
"jazz-tools": "workspace:0.8.21",
"lucide-react": "^0.274.0",
"qrcode": "^1.5.3",
"react": "^18.2.0",

View File

@@ -1,5 +1,13 @@
# jazz-example-inspector
## 0.0.73
### Patch Changes
- Updated dependencies [0f30eea]
- cojson@0.8.21
- cojson-transport-ws@0.8.21
## 0.0.72
### Patch Changes

View File

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

View File

@@ -64,7 +64,6 @@ export default function CoJsonViewerApp() {
id: "cloud",
websocket: new WebSocket("wss://cloud.jazz.tools"),
role: "server",
retryUnavailableCoValues: true,
});
const node = await LocalNode.withLoadedAccount({
accountID: currentAccount.id,

View File

@@ -1,5 +1,13 @@
# jazz-example-musicplayer
## 0.0.20
### Patch Changes
- Updated dependencies [149ca97]
- jazz-tools@0.8.21
- jazz-react@0.8.21
## 0.0.19
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-music-player",
"private": true,
"version": "0.0.19",
"version": "0.0.20",
"type": "module",
"scripts": {
"dev": "vite",
@@ -18,8 +18,8 @@
"@radix-ui/react-toast": "^1.1.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"jazz-react": "workspace:0.8.20",
"jazz-tools": "workspace:0.8.19",
"jazz-react": "workspace:0.8.21",
"jazz-tools": "workspace:0.8.21",
"lucide-react": "^0.274.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",

View File

@@ -1,5 +1,13 @@
# jazz-password-manager
## 0.0.19
### Patch Changes
- Updated dependencies [149ca97]
- jazz-tools@0.8.21
- jazz-react@0.8.21
## 0.0.18
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-password-manager",
"private": true,
"version": "0.0.18",
"version": "0.0.19",
"type": "module",
"scripts": {
"dev": "vite",
@@ -12,8 +12,8 @@
"clean-install": "rm -rf node_modules pnpm-lock.yaml && pnpm install"
},
"dependencies": {
"jazz-react": "workspace:0.8.20",
"jazz-tools": "workspace:0.8.19",
"jazz-react": "workspace:0.8.21",
"jazz-tools": "workspace:0.8.21",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.41.5",

View File

@@ -7,6 +7,7 @@ import {
PasswordList,
PasswordManagerAccount,
} from "./1_schema";
import { waitForCoValue } from "./lib/waitForCoValue";
import { PasswordItemFormValues } from "./types";
export const saveItem = (item: CoMapInit<PasswordItem>): PasswordItem => {
@@ -60,16 +61,14 @@ export async function addSharedFolder(
me: PasswordManagerAccount,
) {
const [sharedFolder, account] = await Promise.all([
Folder.load(sharedFolderId, me, {}),
PasswordManagerAccount.load(me.id, me, {
await waitForCoValue(Folder, sharedFolderId, me, Boolean, {}),
await waitForCoValue(PasswordManagerAccount, me.id, me, Boolean, {
root: {
folders: [],
},
}),
]);
if (!sharedFolder || !account) return;
if (!account.root?.folders) return;
const found = account.root.folders.some((f) => f?.id === sharedFolder.id);

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,14 @@
# jazz-example-pets
## 0.0.117
### Patch Changes
- Updated dependencies [149ca97]
- jazz-tools@0.8.21
- jazz-react@0.8.21
- jazz-browser-media-images@0.8.21
## 0.0.116
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-pets",
"private": true,
"version": "0.0.116",
"version": "0.0.117",
"type": "module",
"scripts": {
"dev": "vite",
@@ -19,9 +19,9 @@
"@radix-ui/react-toast": "^1.1.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"jazz-browser-media-images": "workspace:0.8.20",
"jazz-react": "workspace:0.8.20",
"jazz-tools": "workspace:0.8.19",
"jazz-browser-media-images": "workspace:0.8.21",
"jazz-react": "workspace:0.8.21",
"jazz-tools": "workspace:0.8.21",
"lucide-react": "^0.274.0",
"qrcode": "^1.5.3",
"react": "^18.2.0",
@@ -41,7 +41,7 @@
"@vitejs/plugin-react-swc": "^3.3.2",
"autoprefixer": "^10.4.14",
"is-ci": "^3.0.1",
"jazz-run": "workspace:0.8.19",
"jazz-run": "workspace:0.8.21",
"postcss": "^8.4.27",
"tailwindcss": "3.3.2",
"typescript": "^5.3.3",

View File

@@ -1,5 +1,13 @@
# jazz-example-todo
## 0.0.116
### Patch Changes
- Updated dependencies [149ca97]
- jazz-tools@0.8.21
- jazz-react@0.8.21
## 0.0.115
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-todo",
"private": true,
"version": "0.0.115",
"version": "0.0.116",
"type": "module",
"scripts": {
"dev": "vite",
@@ -16,8 +16,8 @@
"@radix-ui/react-toast": "^1.1.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"jazz-react": "workspace:0.8.20",
"jazz-tools": "workspace:0.8.19",
"jazz-react": "workspace:0.8.21",
"jazz-tools": "workspace:0.8.21",
"lucide-react": "^0.274.0",
"qrcode": "^1.5.3",
"react": "^18.2.0",

View File

@@ -1,5 +1,12 @@
# cojson-storage-indexeddb
## 0.8.21
### Patch Changes
- Updated dependencies [0f30eea]
- cojson@0.8.21
## 0.8.20
### Patch Changes

View File

@@ -1,12 +1,12 @@
{
"name": "cojson-storage-indexeddb",
"version": "0.8.20",
"version": "0.8.21",
"main": "dist/index.js",
"type": "module",
"types": "src/index.ts",
"license": "MIT",
"dependencies": {
"cojson": "workspace:0.8.19"
"cojson": "workspace:0.8.21"
},
"devDependencies": {
"@vitest/browser": "^0.34.1",

View File

@@ -1,5 +1,12 @@
# cojson-storage-sqlite
## 0.8.21
### Patch Changes
- Updated dependencies [0f30eea]
- cojson@0.8.21
## 0.8.19
### Patch Changes

View File

@@ -1,13 +1,13 @@
{
"name": "cojson-storage-sqlite",
"type": "module",
"version": "0.8.19",
"version": "0.8.21",
"main": "dist/index.js",
"types": "src/index.ts",
"license": "MIT",
"dependencies": {
"better-sqlite3": "^8.5.2",
"cojson": "workspace:0.8.19",
"cojson": "workspace:0.8.21",
"typescript": "^5.3.3"
},
"devDependencies": {

View File

@@ -1,5 +1,12 @@
# cojson-transport-nodejs-ws
## 0.8.21
### Patch Changes
- Updated dependencies [0f30eea]
- cojson@0.8.21
## 0.8.19
### Patch Changes

View File

@@ -1,12 +1,12 @@
{
"name": "cojson-transport-ws",
"type": "module",
"version": "0.8.19",
"version": "0.8.21",
"main": "dist/index.js",
"types": "src/index.ts",
"license": "MIT",
"dependencies": {
"cojson": "workspace:0.8.19",
"cojson": "workspace:0.8.21",
"typescript": "^5.3.3"
},
"scripts": {

View File

@@ -18,7 +18,6 @@ export type CreateWebSocketPeerOpts = {
role: Peer["role"];
expectPings?: boolean;
batchingByDefault?: boolean;
retryUnavailableCoValues: boolean;
onClose?: () => void;
};
@@ -119,7 +118,6 @@ export function createWebSocketPeer({
role,
expectPings = true,
batchingByDefault = true,
retryUnavailableCoValues,
onClose,
}: CreateWebSocketPeerOpts): Peer {
const incoming = new cojsonInternals.Channel<
@@ -214,6 +212,5 @@ export function createWebSocketPeer({
},
role,
crashOnClose: false,
retryUnavailableCoValues,
};
}

View File

@@ -30,7 +30,6 @@ function setup(opts: Partial<CreateWebSocketPeerOpts> = {}) {
websocket: mockWebSocket,
role: "client",
batchingByDefault: true,
retryUnavailableCoValues: false,
...opts,
});

View File

@@ -1,5 +1,11 @@
# cojson
## 0.8.21
### Patch Changes
- 0f30eea: Improved the known state tracking within the PeerState.knownState property
## 0.8.19
### Patch Changes

View File

@@ -19,7 +19,7 @@
},
"type": "module",
"license": "MIT",
"version": "0.8.19",
"version": "0.8.21",
"devDependencies": {
"@types/jest": "^29.5.3",
"typescript": "^5.3.3",

View File

@@ -1,38 +0,0 @@
import { CoValueCore } from "./coValueCore.js";
import { CoValueState } from "./coValueState.js";
import { RawCoID } from "./ids.js";
export class CoValuesStore {
coValues = new Map<RawCoID, CoValueState>();
get(id: RawCoID) {
let entry = this.coValues.get(id);
if (!entry) {
entry = CoValueState.Unknown(id);
this.coValues.set(id, entry);
}
return entry;
}
setAsAvailable(id: RawCoID, coValue: CoValueCore) {
const entry = this.get(id);
entry.dispatch({
type: "available",
coValue,
});
}
getEntries() {
return this.coValues.entries();
}
getValues() {
return this.coValues.values();
}
getKeys() {
return this.coValues.keys();
}
}

View File

@@ -57,10 +57,6 @@ export class PeerState {
return this.peer.crashOnClose;
}
get retryUnavailableCoValues() {
return this.peer.retryUnavailableCoValues;
}
isServerOrStoragePeer() {
return this.peer.role === "server" || this.peer.role === "storage";
}

View File

@@ -86,9 +86,9 @@ export class SyncStateSubscriptionManager {
getIsCoValueFullyUploadedIntoPeer(peerId: PeerID, id: RawCoID) {
const peer = this.syncManager.peers[peerId];
const entry = this.syncManager.local.coValuesStore.get(id);
const entry = this.syncManager.local.coValues[id];
if (!peer) {
if (!peer || !entry) {
return false;
}

View File

@@ -1,44 +1,54 @@
import { PeerState } from "./PeerState.js";
import { CoValueCore } from "./coValueCore.js";
import { RawCoID } from "./ids.js";
import { PeerID } from "./sync.js";
export const CO_VALUE_LOADING_MAX_RETRIES = 5;
function createResolvablePromise<T>() {
let resolve!: (value: T) => void;
export class CoValueUnknownState {
type = "unknown" as const;
const promise = new Promise<T>((res) => {
resolve = res;
});
return { promise, resolve };
}
export class CoValueLoadingState {
type = "loading" as const;
private peers = new Map<
class CoValueUnknownState {
type = "unknown" as const;
private peers: Map<
PeerID,
ReturnType<typeof createResolvablePromise<void>>
>();
private resolveResult: (value: CoValueCore | "unavailable") => void;
ReturnType<typeof createResolvablePromise<"available" | "unavailable">>
>;
private resolve: (value: "available" | "unavailable") => void;
result: Promise<CoValueCore | "unavailable">;
ready: Promise<"available" | "unavailable">;
constructor(peersIds: Iterable<PeerID>) {
this.peers = new Map();
for (const peerId of peersIds) {
this.peers.set(peerId, createResolvablePromise<void>());
this.peers.set(
peerId,
createResolvablePromise<"available" | "unavailable">(),
);
}
const { resolve, promise } = createResolvablePromise<
CoValueCore | "unavailable"
"available" | "unavailable"
>();
this.result = promise;
this.resolveResult = resolve;
this.ready = promise;
this.resolve = resolve;
}
markAsUnavailable(peerId: PeerID) {
update(peerId: PeerID, value: "available" | "unavailable") {
const entry = this.peers.get(peerId);
if (entry) {
entry.resolve();
entry.resolve(value);
}
if (value === "available") {
this.resolve("available");
return;
}
this.peers.delete(peerId);
@@ -49,14 +59,6 @@ export class CoValueLoadingState {
}
}
resolve(value: CoValueCore | "unavailable") {
this.resolveResult(value);
for (const entry of this.peers.values()) {
entry.resolve();
}
this.peers.clear();
}
// Wait for a specific peer to have a known state
waitForPeer(peerId: PeerID) {
const entry = this.peers.get(peerId);
@@ -69,261 +71,47 @@ export class CoValueLoadingState {
}
}
export class CoValueAvailableState {
class CoValueAvailableState {
type = "available" as const;
constructor(public coValue: CoValueCore) {}
}
export class CoValueUnavailableState {
type = "unavailable" as const;
}
type CoValueStateAction =
| {
type: "load-requested";
peersIds: PeerID[];
}
| {
type: "not-found-in-peer";
type: "not-found";
peerId: PeerID;
}
| {
type: "available";
type: "found";
peerId: PeerID;
coValue: CoValueCore;
};
type CoValueStateType =
| CoValueUnknownState
| CoValueLoadingState
| CoValueAvailableState
| CoValueUnavailableState;
export class CoValueState {
promise?: Promise<CoValueCore | "unavailable">;
private resolve?: (value: CoValueCore | "unavailable") => void;
constructor(public state: CoValueUnknownState | CoValueAvailableState) {}
constructor(
public id: RawCoID,
public state: CoValueStateType,
) {}
static Unknown(id: RawCoID) {
return new CoValueState(id, new CoValueUnknownState());
}
static Loading(id: RawCoID, peersIds: Iterable<PeerID>) {
return new CoValueState(id, new CoValueLoadingState(peersIds));
static Unknown(peersToWaitFor: Set<PeerID>) {
return new CoValueState(new CoValueUnknownState(peersToWaitFor));
}
static Available(coValue: CoValueCore) {
return new CoValueState(coValue.id, new CoValueAvailableState(coValue));
}
static Unavailable(id: RawCoID) {
return new CoValueState(id, new CoValueUnavailableState());
}
async getCoValue() {
if (this.state.type === "available") {
return this.state.coValue;
}
if (this.state.type === "unavailable") {
return "unavailable";
}
// If we don't have a resolved state we return a new promise
// that will be resolved when the state will move to available or unavailable
if (!this.promise) {
const { promise, resolve } = createResolvablePromise<
CoValueCore | "unavailable"
>();
this.promise = promise;
this.resolve = resolve;
}
return this.promise;
}
private moveToState(value: CoValueStateType) {
this.state = value;
if (!this.resolve) {
return;
}
// If the state is available we resolve the promise
// and clear it to handle the possible transition from unavailable to available
if (value.type === "available") {
this.resolve(value.coValue);
this.clearPromise();
} else if (value.type === "unavailable") {
this.resolve("unavailable");
this.clearPromise();
}
}
private clearPromise() {
this.promise = undefined;
this.resolve = undefined;
}
async loadFromPeers(peers: PeerState[]) {
const state = this.state;
if (state.type !== "unknown" && state.type !== "unavailable") {
return;
}
if (peers.length === 0) {
return;
}
const doLoad = async (peersToLoadFrom: PeerState[]) => {
const peersWithoutErrors = getPeersWithoutErrors(
peersToLoadFrom,
this.id,
);
// Set the state to loading and reset all the loading promises
const currentState = this.dispatch({
type: "load-requested",
peersIds: peersWithoutErrors.map((p) => p.id),
});
// If we entered successfully the loading state, we load the coValue from the peers
//
// We may not enter the loading state if the coValue has become available in between
// of the retries
if (currentState.type === "loading") {
await loadCoValueFromPeers(this, peersWithoutErrors);
const result = await currentState.result;
return result !== "unavailable";
}
return currentState.type === "available";
};
await doLoad(peers);
// Retry loading from peers that have the retry flag enabled
const peersWithRetry = peers.filter((p) => p.retryUnavailableCoValues);
if (peersWithRetry.length > 0) {
// We want to exit early if the coValue becomes available in between the retries
await Promise.race([
this.getCoValue(),
runWithRetry(
() => doLoad(peersWithRetry),
CO_VALUE_LOADING_MAX_RETRIES,
),
]);
}
// If after the retries the coValue is still loading, we consider the load failed
if (this.state.type === "loading") {
this.moveToState(new CoValueUnavailableState());
}
return new CoValueState(new CoValueAvailableState(coValue));
}
dispatch(action: CoValueStateAction) {
const prevState = this.state;
switch (action.type) {
case "load-requested":
// We use this action to reset the loading state
if (prevState.type === "loading" || prevState.type === "unknown") {
this.moveToState(new CoValueLoadingState(action.peersIds));
}
break;
case "available":
if (prevState.type === "loading") {
prevState.resolve(action.coValue);
}
// It should be always possible to move to the available state
this.moveToState(new CoValueAvailableState(action.coValue));
break;
case "not-found-in-peer":
if (prevState.type === "loading") {
prevState.markAsUnavailable(action.peerId);
}
break;
}
return this.state;
}
}
async function loadCoValueFromPeers(
coValueEntry: CoValueState,
peers: PeerState[],
) {
for (const peer of peers) {
await peer.pushOutgoingMessage({
action: "load",
id: coValueEntry.id,
header: false,
sessions: {},
});
if (coValueEntry.state.type === "loading") {
await coValueEntry.state.waitForPeer(peer.id);
}
}
}
async function runWithRetry<T>(fn: () => Promise<T>, maxRetries: number) {
let retries = 1;
while (retries < maxRetries) {
/**
* With maxRetries of 5 we should wait:
* 300ms
* 900ms
* 2700ms
* 8100ms
*/
await sleep(3 ** retries * 100);
const result = await fn();
if (result === true) {
if (this.state.type === "available") {
return;
}
retries++;
switch (action.type) {
case "not-found":
this.state.update(action.peerId, "unavailable");
break;
case "found":
this.state.update(action.peerId, "available");
this.state = new CoValueAvailableState(action.coValue);
break;
}
}
}
function createResolvablePromise<T>() {
let resolve!: (value: T) => void;
const promise = new Promise<T>((res) => {
resolve = res;
});
return { promise, resolve };
}
function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function getPeersWithoutErrors(peers: PeerState[], coValueId: RawCoID) {
return peers.filter((p) => {
if (p.erroredCoValues.has(coValueId)) {
console.error(
`Skipping load on errored coValue ${coValueId} from peer ${p.id}`,
);
return false;
}
return true;
});
}

View File

@@ -1,5 +1,4 @@
import { Result, ResultAsync, err, ok, okAsync } from "neverthrow";
import { CoValuesStore } from "./CoValuesStore.js";
import { CoID } from "./coValue.js";
import { RawCoValue } from "./coValue.js";
import {
@@ -7,6 +6,7 @@ import {
CoValueHeader,
CoValueUniqueness,
} from "./coValueCore.js";
import { CoValueState } from "./coValueState.js";
import {
AccountMeta,
ControlledAccountOrAgent,
@@ -45,7 +45,7 @@ export class LocalNode {
/** @internal */
crypto: CryptoProvider;
/** @internal */
coValuesStore = new CoValuesStore();
coValues: { [key: RawCoID]: CoValueState } = {};
/** @category 3. Low-level */
account: ControlledAccountOrAgent;
/** @category 3. Low-level */
@@ -125,8 +125,7 @@ export class LocalNode {
);
nodeWithAccount.account = controlledAccount;
nodeWithAccount.coValuesStore.setAsAvailable(
controlledAccount.id,
nodeWithAccount.coValues[controlledAccount.id] = CoValueState.Available(
controlledAccount.core,
);
controlledAccount.core._cachedContent = undefined;
@@ -137,7 +136,7 @@ export class LocalNode {
// we shouldn't need this, but it fixes account data not syncing for new accounts
function syncAllCoValuesAfterCreateAccount() {
for (const coValueEntry of nodeWithAccount.coValuesStore.getValues()) {
for (const coValueEntry of Object.values(nodeWithAccount.coValues)) {
if (coValueEntry.state.type === "available") {
void nodeWithAccount.syncManager.syncCoValue(
coValueEntry.state.coValue,
@@ -207,7 +206,7 @@ export class LocalNode {
node.syncManager.local = node;
controlledAccount.core.node = node;
node.coValuesStore.setAsAvailable(accountID, controlledAccount.core);
node.coValues[accountID] = CoValueState.Available(controlledAccount.core);
controlledAccount.core._cachedContent = undefined;
const profileID = account.get("profile");
@@ -244,7 +243,7 @@ export class LocalNode {
}
const coValue = new CoValueCore(header, this);
this.coValuesStore.setAsAvailable(coValue.id, coValue);
this.coValues[coValue.id] = CoValueState.Available(coValue);
void this.syncManager.syncCoValue(coValue);
@@ -254,7 +253,10 @@ export class LocalNode {
/** @internal */
async loadCoValueCore(
id: RawCoID,
skipLoadingFromPeer?: PeerID,
options: {
dontLoadFrom?: PeerID;
dontWaitFor?: PeerID;
} = {},
): Promise<CoValueCore | "unavailable"> {
if (this.crashed) {
throw new Error("Trying to load CoValue after node has crashed", {
@@ -262,18 +264,40 @@ export class LocalNode {
});
}
const entry = this.coValuesStore.get(id);
let entry = this.coValues[id];
if (!entry) {
const peersToWaitFor = new Set(
Object.values(this.syncManager.peers)
.filter((peer) => peer.isServerOrStoragePeer())
.map((peer) => peer.id),
);
if (options.dontWaitFor) peersToWaitFor.delete(options.dontWaitFor);
entry = CoValueState.Unknown(peersToWaitFor);
if (entry.state.type === "unknown" || entry.state.type === "unavailable") {
const peers =
this.syncManager.getServerAndStoragePeers(skipLoadingFromPeer);
this.coValues[id] = entry;
await entry.loadFromPeers(peers).catch((e) => {
console.error("Error loading from peers", id, e);
this.syncManager.loadFromPeers(id, options.dontLoadFrom).catch((e) => {
console.error(
"Error loading from peers",
id,
e,
);
});
}
if (entry.state.type === "available") {
return Promise.resolve(entry.state.coValue);
}
return entry.getCoValue();
await entry.state.ready;
const updatedEntry = this.coValues[id];
if (updatedEntry?.state.type === "available") {
return Promise.resolve(updatedEntry.state.coValue);
}
return "unavailable";
}
/**
@@ -294,12 +318,13 @@ export class LocalNode {
}
getLoaded<T extends RawCoValue>(id: CoID<T>): T | undefined {
const entry = this.coValuesStore.get(id);
const entry = this.coValues[id];
if (!entry) {
return undefined;
}
if (entry.state.type === "available") {
return entry.state.coValue.getCurrentContent() as T;
}
return undefined;
}
@@ -423,11 +448,15 @@ export class LocalNode {
/** @internal */
expectCoValueLoaded(id: RawCoID, expectation?: string): CoValueCore {
const entry = this.coValuesStore.get(id);
if (entry.state.type !== "available") {
const entry = this.coValues[id];
if (!entry) {
throw new Error(
`${expectation ? expectation + ": " : ""}CoValue ${id} not yet loaded. Current state: ${entry.state.type}`,
`${expectation ? expectation + ": " : ""}Unknown CoValue ${id}`,
);
}
if (entry.state.type === "unknown") {
throw new Error(
`${expectation ? expectation + ": " : ""}CoValue ${id} not yet loaded`,
);
}
return entry.state.coValue;
@@ -621,20 +650,18 @@ export class LocalNode {
): LocalNode {
const newNode = new LocalNode(account, currentSessionID, this.crypto);
const coValuesToCopy = Array.from(this.coValuesStore.getEntries());
const coValuesToCopy = Object.entries(this.coValues);
while (coValuesToCopy.length > 0) {
const [coValueID, entry] = coValuesToCopy[coValuesToCopy.length - 1]!;
if (entry.state.type !== "available") {
if (entry.state.type === "unknown") {
coValuesToCopy.pop();
continue;
} else {
const allDepsCopied = entry.state.coValue
.getDependedOnCoValues()
.every(
(dep) => newNode.coValuesStore.get(dep).state.type === "available",
);
.every((dep) => newNode.coValues[dep]?.state.type === "available");
if (!allDepsCopied) {
// move to end of queue
@@ -648,7 +675,8 @@ export class LocalNode {
new Map(entry.state.coValue.sessionLogs),
);
newNode.coValuesStore.setAsAvailable(coValueID, newCoValue);
newNode.coValues[coValueID as RawCoID] =
CoValueState.Available(newCoValue);
coValuesToCopy.pop();
}

View File

@@ -2,6 +2,7 @@ import { PeerState } from "./PeerState.js";
import { SyncStateSubscriptionManager } from "./SyncStateSubscriptionManager.js";
import { CoValueHeader, Transaction } from "./coValueCore.js";
import { CoValueCore } from "./coValueCore.js";
import { CoValueState } from "./coValueState.js";
import { Signature } from "./crypto/crypto.js";
import { RawCoID, SessionID } from "./ids.js";
import { LocalNode } from "./localNode.js";
@@ -78,7 +79,6 @@ export interface Peer {
role: "peer" | "server" | "client" | "storage";
priority?: number;
crashOnClose: boolean;
retryUnavailableCoValues?: boolean;
}
export function combinedKnownStates(
@@ -135,10 +135,31 @@ export class SyncManager {
return Object.values(this.peers);
}
getServerAndStoragePeers(excludePeerId?: PeerID): PeerState[] {
return this.peersInPriorityOrder().filter(
(peer) => peer.isServerOrStoragePeer() && peer.id !== excludePeerId,
async loadFromPeers(id: RawCoID, forPeer?: PeerID) {
const eligiblePeers = this.peersInPriorityOrder().filter(
(peer) => peer.id !== forPeer && peer.isServerOrStoragePeer(),
);
const coValueEntry = this.local.coValues[id];
for (const peer of eligiblePeers) {
if (peer.erroredCoValues.has(id)) {
console.error(
`Skipping load on errored coValue ${id} from peer ${peer.id}`,
);
continue;
}
await peer.pushOutgoingMessage({
action: "load",
id: id,
header: false,
sessions: {},
});
if (coValueEntry?.state.type === "unknown") {
await coValueEntry.state.waitForPeer(peer.id);
}
}
}
async handleSyncMessage(msg: SyncMessage, peer: PeerState) {
@@ -171,10 +192,19 @@ export class SyncManager {
}
async subscribeToIncludingDependencies(id: RawCoID, peer: PeerState) {
let entry = this.local.coValuesStore.get(id);
const entry = this.local.coValues[id];
if (entry.state.type !== "available") {
entry.loadFromPeers([peer]).catch((e: unknown) => {
if (!entry) {
throw new Error("Expected coValue entry on subscribe");
}
if (entry.state.type === "unknown") {
this.trySendToPeer(peer, {
action: "load",
id,
header: false,
sessions: {},
}).catch((e: unknown) => {
console.error("Error sending load", e);
});
return;
@@ -304,7 +334,7 @@ export class SyncManager {
if (peerState.isServerOrStoragePeer()) {
const initialSync = async () => {
for (const id of this.local.coValuesStore.getKeys()) {
for (const id of Object.keys(this.local.coValues) as RawCoID[]) {
// console.log("subscribing to after peer added", id, peer.id)
await this.subscribeToIncludingDependencies(id, peerState);
@@ -375,31 +405,55 @@ export class SyncManager {
id: msg.id,
value: knownStateIn(msg),
});
const entry = this.local.coValuesStore.get(msg.id);
let entry = this.local.coValues[msg.id];
if (entry.state.type === "unknown" || entry.state.type === "unavailable") {
const eligiblePeers = this.getServerAndStoragePeers(peer.id);
if (!entry) {
// console.log(`Loading ${msg.id} from all peers except ${peer.id}`);
// special case: we should be able to solve this much more neatly
// with an explicit state machine in the future
const eligiblePeers = this.peersInPriorityOrder().filter(
(other) => other.id !== peer.id && other.isServerOrStoragePeer(),
);
if (eligiblePeers.length === 0) {
// If we know less then the "load" sender and we don't have any eligible
// peers to load the coValue from, we try to load it from the sender
if (msg.header || Object.keys(msg.sessions).length > 0) {
entry.loadFromPeers([peer]).catch((e) => {
console.error("Error loading coValue in handleLoad", e);
this.local.coValues[msg.id] = CoValueState.Unknown(
new Set([peer.id]),
);
this.trySendToPeer(peer, {
action: "known",
id: msg.id,
header: false,
sessions: {},
}).catch((e) => {
console.error("Error sending known state", e);
});
}
return;
} else {
this.local.loadCoValueCore(msg.id, peer.id).catch((e) => {
console.error("Error loading coValue in handleLoad", e);
});
this.local
.loadCoValueCore(msg.id, {
dontLoadFrom: peer.id,
dontWaitFor: peer.id,
})
.catch((e) => {
console.error("Error loading coValue in handleLoad", e);
});
}
entry = this.local.coValues[msg.id]!;
}
if (entry.state.type === "loading") {
const value = await entry.getCoValue();
if (entry.state.type === "unknown") {
// console.debug(
// "Waiting for loaded",
// msg.id,
// "after message from",
// peer.id,
// );
const loaded = await entry.state.ready;
if (value === "unavailable") {
if (loaded === "unavailable") {
peer.dispatchToKnownStates({
type: "SET",
id: msg.id,
@@ -420,14 +474,12 @@ export class SyncManager {
}
}
if (entry.state.type === "available") {
await this.tellUntoldKnownStateIncludingDependencies(msg.id, peer);
await this.sendNewContentIncludingDependencies(msg.id, peer);
}
await this.tellUntoldKnownStateIncludingDependencies(msg.id, peer);
await this.sendNewContentIncludingDependencies(msg.id, peer);
}
async handleKnownState(msg: KnownStateMessage, peer: PeerState) {
const entry = this.local.coValuesStore.get(msg.id);
let entry = this.local.coValues[msg.id];
peer.dispatchToKnownStates({
type: "COMBINE_WITH",
@@ -435,22 +487,18 @@ export class SyncManager {
value: knownStateIn(msg),
});
if (entry.state.type === "unknown" || entry.state.type === "unavailable") {
if (!entry) {
if (msg.asDependencyOf) {
const dependencyEntry = this.local.coValuesStore.get(
msg.asDependencyOf,
);
if (
dependencyEntry.state.type === "available" ||
dependencyEntry.state.type === "loading"
) {
this.local.loadCoValueCore(msg.id, peer.id).catch((e) => {
console.error(
`Error loading coValue ${msg.id} to create loading state, as dependency of ${msg.asDependencyOf}`,
e,
);
});
if (this.local.coValues[msg.asDependencyOf]) {
this.local
.loadCoValueCore(msg.id, { dontLoadFrom: peer.id })
.catch((e) => {
console.error(
`Error loading coValue ${msg.id} to create loading state, as dependency of ${msg.asDependencyOf}`,
e,
);
});
entry = this.local.coValues[msg.id]!; // must exist after loadCoValueCore
} else {
throw new Error(
"Expected coValue dependency entry to be created, missing subscribe?",
@@ -463,15 +511,12 @@ export class SyncManager {
}
}
// When we receive a known state from a peer and the header is "false"
// it means that the peer doesn't know anything about this coValue
// So we can say that the coValue has been not found on this peer
if (entry.state.type !== "available") {
if (entry.state.type === "unknown") {
const availableOnPeer = peer.optimisticKnownStates.get(msg.id)?.header;
if (!availableOnPeer) {
entry.dispatch({
type: "not-found-in-peer",
type: "not-found",
peerId: peer.id,
});
}
@@ -479,18 +524,23 @@ export class SyncManager {
return;
}
if (entry.state.type === "available") {
await this.tellUntoldKnownStateIncludingDependencies(msg.id, peer);
await this.sendNewContentIncludingDependencies(msg.id, peer);
}
await this.tellUntoldKnownStateIncludingDependencies(msg.id, peer);
await this.sendNewContentIncludingDependencies(msg.id, peer);
}
async handleNewContent(msg: NewContentMessage, peer: PeerState) {
const entry = this.local.coValuesStore.get(msg.id);
const entry = this.local.coValues[msg.id];
if (!entry) {
console.error(
`Expected coValue entry for ${msg.id} to be created on new content, missing subscribe?`,
);
return;
}
let coValue: CoValueCore;
if (entry.state.type !== "available") {
if (entry.state.type === "unknown") {
if (!msg.header) {
console.error("Expected header to be sent in first message");
return;
@@ -505,8 +555,9 @@ export class SyncManager {
coValue = new CoValueCore(msg.header, this.local);
entry.dispatch({
type: "available",
type: "found",
coValue,
peerId: peer.id,
});
} else {
coValue = entry.state.coValue;

View File

@@ -1,330 +0,0 @@
import { describe, expect, test, vi } from "vitest";
import { PeerState } from "../PeerState";
import { CoValueCore } from "../coValueCore";
import { CO_VALUE_LOADING_MAX_RETRIES, CoValueState } from "../coValueState";
import { RawCoID } from "../ids";
describe("CoValueState", () => {
const mockCoValueId = "co_test123" as RawCoID;
test("should create unknown state", () => {
const state = CoValueState.Unknown(mockCoValueId);
expect(state.id).toBe(mockCoValueId);
expect(state.state.type).toBe("unknown");
});
test("should create loading state", () => {
const peerIds = ["peer1", "peer2"];
const state = CoValueState.Loading(mockCoValueId, peerIds);
expect(state.id).toBe(mockCoValueId);
expect(state.state.type).toBe("loading");
});
test("should create available state", async () => {
const mockCoValue = { id: mockCoValueId } as CoValueCore;
const state = CoValueState.Available(mockCoValue);
expect(state.id).toBe(mockCoValueId);
expect(state.state.type).toBe("available");
expect((state.state as any).coValue).toBe(mockCoValue);
await expect(state.getCoValue()).resolves.toEqual(mockCoValue);
});
test("should handle found action", async () => {
const mockCoValue = { id: mockCoValueId } as CoValueCore;
const state = CoValueState.Loading(mockCoValueId, ["peer1", "peer2"]);
const stateValuePromise = state.getCoValue();
state.dispatch({
type: "available",
coValue: mockCoValue,
});
const result = await state.getCoValue();
expect(result).toBe(mockCoValue);
await expect(stateValuePromise).resolves.toBe(mockCoValue);
});
test("should ignore actions when not in loading state", () => {
const state = CoValueState.Unknown(mockCoValueId);
state.dispatch({
type: "not-found-in-peer",
peerId: "peer1",
});
expect(state.state.type).toBe("unknown");
});
test("should retry loading from peers when unsuccessful", async () => {
vi.useFakeTimers();
const peer1 = {
id: "peer1",
erroredCoValues: new Set(),
retryUnavailableCoValues: true,
pushOutgoingMessage: vi.fn().mockImplementation(async () => {
state.dispatch({
type: "not-found-in-peer",
peerId: "peer1",
});
}),
};
const peer2 = {
id: "peer2",
erroredCoValues: new Set(),
retryUnavailableCoValues: true,
pushOutgoingMessage: vi.fn().mockImplementation(async () => {
state.dispatch({
type: "not-found-in-peer",
peerId: "peer2",
});
}),
};
const mockPeers = [peer1, peer2] as unknown as PeerState[];
const state = CoValueState.Unknown(mockCoValueId);
const loadPromise = state.loadFromPeers(mockPeers);
// Should attempt CO_VALUE_LOADING_MAX_RETRIES retries
for (let i = 0; i < CO_VALUE_LOADING_MAX_RETRIES; i++) {
await vi.runAllTimersAsync();
}
await loadPromise;
expect(peer1.pushOutgoingMessage).toHaveBeenCalledTimes(
CO_VALUE_LOADING_MAX_RETRIES,
);
expect(peer2.pushOutgoingMessage).toHaveBeenCalledTimes(
CO_VALUE_LOADING_MAX_RETRIES,
);
expect(state.state.type).toBe("unavailable");
await expect(state.getCoValue()).resolves.toBe("unavailable");
vi.useRealTimers();
});
test("should skip errored coValues when loading from peers", async () => {
vi.useFakeTimers();
const peer1 = {
id: "peer1",
erroredCoValues: new Set<RawCoID>(),
retryUnavailableCoValues: true,
pushOutgoingMessage: vi.fn().mockImplementation(async () => {
peer1.erroredCoValues.add(mockCoValueId);
state.dispatch({
type: "not-found-in-peer",
peerId: "peer1",
});
}),
};
const peer2 = {
id: "peer2",
erroredCoValues: new Set(),
retryUnavailableCoValues: true,
pushOutgoingMessage: vi.fn().mockImplementation(async () => {
console.log("pushing not-found from peer2");
state.dispatch({
type: "not-found-in-peer",
peerId: "peer2",
});
}),
};
const mockPeers = [peer1, peer2] as unknown as PeerState[];
const state = CoValueState.Unknown(mockCoValueId);
const loadPromise = state.loadFromPeers(mockPeers);
// Should attempt CO_VALUE_LOADING_MAX_RETRIES retries
for (let i = 0; i < CO_VALUE_LOADING_MAX_RETRIES; i++) {
await vi.runAllTimersAsync();
}
await loadPromise;
expect(peer1.pushOutgoingMessage).toHaveBeenCalledTimes(1);
expect(peer2.pushOutgoingMessage).toHaveBeenCalledTimes(
CO_VALUE_LOADING_MAX_RETRIES,
);
expect(state.state.type).toBe("unavailable");
await expect(state.getCoValue()).resolves.toBe("unavailable");
vi.useRealTimers();
});
test("should retry only from peers that have the retry flag enabled", async () => {
vi.useFakeTimers();
const peer1 = {
id: "peer1",
erroredCoValues: new Set([]),
retryUnavailableCoValues: false,
pushOutgoingMessage: vi.fn().mockImplementation(async () => {
state.dispatch({
type: "not-found-in-peer",
peerId: "peer1",
});
}),
};
const peer2 = {
id: "peer2",
erroredCoValues: new Set(),
retryUnavailableCoValues: true,
pushOutgoingMessage: vi.fn().mockImplementation(async () => {
state.dispatch({
type: "not-found-in-peer",
peerId: "peer2",
});
}),
};
const mockPeers = [peer1, peer2] as unknown as PeerState[];
const state = CoValueState.Unknown(mockCoValueId);
const loadPromise = state.loadFromPeers(mockPeers);
// Should attempt CO_VALUE_LOADING_MAX_RETRIES retries
for (let i = 0; i < CO_VALUE_LOADING_MAX_RETRIES; i++) {
await vi.runAllTimersAsync();
}
await loadPromise;
expect(peer1.pushOutgoingMessage).toHaveBeenCalledTimes(1);
expect(peer2.pushOutgoingMessage).toHaveBeenCalledTimes(
CO_VALUE_LOADING_MAX_RETRIES,
);
expect(state.state.type).toBe("unavailable");
await expect(state.getCoValue()).resolves.toEqual("unavailable");
vi.useRealTimers();
});
test("should handle the coValues that become available in between of the retries", async () => {
vi.useFakeTimers();
let retries = 0;
const peer1 = {
id: "peer1",
erroredCoValues: new Set([]),
retryUnavailableCoValues: true,
pushOutgoingMessage: vi.fn().mockImplementation(async () => {
retries++;
state.dispatch({
type: "not-found-in-peer",
peerId: "peer1",
});
if (retries === 2) {
setTimeout(() => {
state.dispatch({
type: "available",
coValue: { id: mockCoValueId } as CoValueCore,
});
}, 100);
}
}),
};
const mockPeers = [peer1] as unknown as PeerState[];
const state = CoValueState.Unknown(mockCoValueId);
const loadPromise = state.loadFromPeers(mockPeers);
// Should attempt CO_VALUE_LOADING_MAX_RETRIES retries
for (let i = 0; i < CO_VALUE_LOADING_MAX_RETRIES + 1; i++) {
await vi.runAllTimersAsync();
}
await loadPromise;
expect(peer1.pushOutgoingMessage).toHaveBeenCalledTimes(2);
expect(state.state.type).toBe("available");
await expect(state.getCoValue()).resolves.toEqual({ id: mockCoValueId });
vi.useRealTimers();
});
test("should have a coValue as value property when becomes available after that have been marked as unavailable", async () => {
vi.useFakeTimers();
const peer1 = {
id: "peer1",
erroredCoValues: new Set([]),
retryUnavailableCoValues: true,
pushOutgoingMessage: vi.fn().mockImplementation(async () => {
state.dispatch({
type: "not-found-in-peer",
peerId: "peer1",
});
}),
};
const mockPeers = [peer1] as unknown as PeerState[];
const state = CoValueState.Unknown(mockCoValueId);
const loadPromise = state.loadFromPeers(mockPeers);
// Should attempt CO_VALUE_LOADING_MAX_RETRIES retries
for (let i = 0; i < CO_VALUE_LOADING_MAX_RETRIES; i++) {
await vi.runAllTimersAsync();
}
state.dispatch({
type: "available",
coValue: { id: mockCoValueId } as CoValueCore,
});
await loadPromise;
expect(peer1.pushOutgoingMessage).toHaveBeenCalledTimes(5);
expect(state.state.type).toBe("available");
await expect(state.getCoValue()).resolves.toEqual({ id: mockCoValueId });
vi.useRealTimers();
});
test("should stop retrying when value becomes available", async () => {
vi.useFakeTimers();
let run = 1;
const peer1 = {
id: "peer1",
erroredCoValues: new Set(),
retryUnavailableCoValues: true,
pushOutgoingMessage: vi.fn().mockImplementation(async () => {
if (run > 2) {
state.dispatch({
type: "available",
coValue: { id: mockCoValueId } as CoValueCore,
});
}
state.dispatch({
type: "not-found-in-peer",
peerId: "peer1",
});
run++;
}),
};
const mockPeers = [peer1] as unknown as PeerState[];
const state = CoValueState.Unknown(mockCoValueId);
const loadPromise = state.loadFromPeers(mockPeers);
for (let i = 0; i < CO_VALUE_LOADING_MAX_RETRIES; i++) {
await vi.runAllTimersAsync();
}
await loadPromise;
expect(peer1.pushOutgoingMessage).toHaveBeenCalledTimes(3);
expect(state.state.type).toBe("available");
await expect(state.getCoValue()).resolves.toEqual({ id: mockCoValueId });
vi.useRealTimers();
});
});

View File

@@ -807,7 +807,7 @@ test.skip("When replaying creation and transactions of a coValue as new content,
sessions: {},
} satisfies SyncMessage);
expect(node2.coValuesStore.get(map.core.id).state.type).toEqual("loading");
expect(node2.coValues[map.core.id]?.state).toEqual("loading");
await inTx2.push(mapNewContentMsg);
@@ -1102,7 +1102,7 @@ test("If we start loading a coValue before connecting to a peer that has it, it
const mapOnNode2Promise = node2.loadCoValueCore(map.core.id);
expect(node2.coValuesStore.get(map.core.id).state.type).toEqual("unknown");
expect(node2.coValues[map.core.id]?.state.type).toEqual("unknown");
node2.syncManager.addPeer(node1asPeer);
@@ -1821,63 +1821,6 @@ describe("SyncManager.addPeer", () => {
});
});
describe("loadCoValueCore with retry", () => {
test("should load the value if available on the server", async () => {
const { client, jazzCloud } = createTwoConnectedNodes();
const anotherClient = createTestNode();
const [
connectionWithAnotherClientAsPeer,
jazzCloudConnectionAsPeerForAnotherClient,
] = connectedPeers("connectionWithAnotherClient", "jazzCloudConnection", {
peer1role: "client",
peer2role: "server",
});
jazzCloud.syncManager.addPeer(connectionWithAnotherClientAsPeer);
const group = anotherClient.createGroup();
const map = group.createMap();
map.set("key1", "value1", "trusting");
const promise = client.loadCoValueCore(map.id);
anotherClient.syncManager.addPeer(
jazzCloudConnectionAsPeerForAnotherClient,
);
await expect(promise).resolves.not.toBe("unavailable");
});
test("should handle correctly two subsequent loads", async () => {
const { client, jazzCloud } = createTwoConnectedNodes();
const anotherClient = createTestNode();
const [
connectionWithAnotherClientAsPeer,
jazzCloudConnectionAsPeerForAnotherClient,
] = connectedPeers("connectionWithAnotherClient", "jazzCloudConnection", {
peer1role: "client",
peer2role: "server",
});
jazzCloud.syncManager.addPeer(connectionWithAnotherClientAsPeer);
const group = anotherClient.createGroup();
const map = group.createMap();
map.set("key1", "value1", "trusting");
const promise1 = client.loadCoValueCore(map.id);
const promise2 = client.loadCoValueCore(map.id);
anotherClient.syncManager.addPeer(
jazzCloudConnectionAsPeerForAnotherClient,
);
await expect(promise1).resolves.not.toBe("unavailable");
await expect(promise2).resolves.not.toBe("unavailable");
});
});
describe("waitForUploadIntoPeer", () => {
test("should resolve when the coValue is fully uploaded into the peer", async () => {
const { client, jazzCloudConnectionAsPeer: peer } =
@@ -1953,7 +1896,3 @@ function _admStateEx(adminID: RawAccountID) {
id: adminID,
};
}
function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}

View File

@@ -1,5 +1,15 @@
# jazz-browser-media-images
## 0.8.21
### Patch Changes
- Updated dependencies [0f30eea]
- Updated dependencies [149ca97]
- cojson@0.8.21
- jazz-tools@0.8.21
- jazz-browser@0.8.21
## 0.8.20
### Patch Changes

View File

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

View File

@@ -1,5 +1,13 @@
# jazz-browser-media-images
## 0.8.21
### Patch Changes
- Updated dependencies [149ca97]
- jazz-tools@0.8.21
- jazz-browser@0.8.21
## 0.8.20
### Patch Changes

View File

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

View File

@@ -1,5 +1,16 @@
# jazz-browser
## 0.8.21
### Patch Changes
- Updated dependencies [0f30eea]
- Updated dependencies [149ca97]
- cojson@0.8.21
- jazz-tools@0.8.21
- cojson-storage-indexeddb@0.8.21
- cojson-transport-ws@0.8.21
## 0.8.20
### Patch Changes

View File

@@ -1,16 +1,16 @@
{
"name": "jazz-browser",
"version": "0.8.20",
"version": "0.8.21",
"type": "module",
"main": "dist/index.js",
"types": "src/index.ts",
"license": "MIT",
"dependencies": {
"@scure/bip39": "^1.3.0",
"cojson": "workspace:0.8.19",
"cojson-storage-indexeddb": "workspace:0.8.20",
"cojson-transport-ws": "workspace:0.8.19",
"jazz-tools": "workspace:0.8.19",
"cojson": "workspace:0.8.21",
"cojson-storage-indexeddb": "workspace:0.8.21",
"cojson-transport-ws": "workspace:0.8.21",
"jazz-tools": "workspace:0.8.21",
"typescript": "^5.3.3"
},
"scripts": {

View File

@@ -11,7 +11,6 @@ export function createWebSocketPeerWithReconnection(
id: peer,
role: "server",
onClose: reconnectWebSocket,
retryUnavailableCoValues: true,
});
let shouldTryToReconnect = true;
@@ -47,7 +46,6 @@ export function createWebSocketPeerWithReconnection(
id: peer,
role: "server",
onClose: reconnectWebSocket,
retryUnavailableCoValues: true,
}),
);
}

View File

@@ -1,5 +1,15 @@
# jazz-autosub
## 0.8.21
### Patch Changes
- Updated dependencies [0f30eea]
- Updated dependencies [149ca97]
- cojson@0.8.21
- jazz-tools@0.8.21
- cojson-transport-ws@0.8.21
## 0.8.19
### Patch Changes

View File

@@ -5,11 +5,11 @@
"types": "src/index.ts",
"type": "module",
"license": "MIT",
"version": "0.8.19",
"version": "0.8.21",
"dependencies": {
"cojson": "workspace:0.8.19",
"cojson-transport-ws": "workspace:0.8.19",
"jazz-tools": "workspace:0.8.19",
"cojson": "workspace:0.8.21",
"cojson-transport-ws": "workspace:0.8.21",
"jazz-tools": "workspace:0.8.21",
"ws": "^8.14.2"
},
"devDependencies": {

View File

@@ -26,7 +26,6 @@ export async function startWorker<Acc extends Account>({
id: "upstream",
websocket: new WebSocket(peer),
role: "server",
retryUnavailableCoValues: true,
});
if (!accountID) {
@@ -62,7 +61,6 @@ export async function startWorker<Acc extends Account>({
id: "upstream",
websocket: new WebSocket(peer),
role: "server",
retryUnavailableCoValues: true,
});
worker._raw.core.node.syncManager.addPeer(wsPeer);

View File

@@ -1,5 +1,16 @@
# jazz-browser-media-images
## 0.8.21
### Patch Changes
- Updated dependencies [0f30eea]
- Updated dependencies [149ca97]
- cojson@0.8.21
- jazz-tools@0.8.21
- jazz-browser-auth-clerk@0.8.21
- jazz-react@0.8.21
## 0.8.20
### Patch Changes

View File

@@ -1,15 +1,15 @@
{
"name": "jazz-react-auth-clerk",
"version": "0.8.20",
"version": "0.8.21",
"type": "module",
"main": "dist/index.js",
"types": "src/index.tsx",
"license": "MIT",
"dependencies": {
"cojson": "workspace:0.8.19",
"jazz-browser-auth-clerk": "workspace:0.8.20",
"jazz-react": "workspace:0.8.20",
"jazz-tools": "workspace:0.8.19"
"cojson": "workspace:0.8.21",
"jazz-browser-auth-clerk": "workspace:0.8.21",
"jazz-react": "workspace:0.8.21",
"jazz-tools": "workspace:0.8.21"
},
"peerDependencies": {
"react": "^18.2.0"

View File

@@ -1,5 +1,12 @@
# jazz-browser-media-images
## 0.8.17
### Patch Changes
- Updated dependencies [149ca97]
- jazz-tools@0.8.21
## 0.8.16
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-react-native-media-images",
"version": "0.8.16",
"version": "0.8.17",
"type": "module",
"main": "dist/index.js",
"types": "src/index.ts",

View File

@@ -1,5 +1,15 @@
# jazz-browser
## 0.8.21
### Patch Changes
- Updated dependencies [0f30eea]
- Updated dependencies [149ca97]
- cojson@0.8.21
- jazz-tools@0.8.21
- cojson-transport-ws@0.8.21
## 0.8.20
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-react-native",
"version": "0.8.20",
"version": "0.8.21",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",

View File

@@ -71,7 +71,6 @@ export async function createJazzRNContext<Acc extends Account>(
id: options.peer + "@" + new Date().toISOString(),
role: "server",
expectPings: true,
retryUnavailableCoValues: true,
});
let shouldTryToReconnect = true;
@@ -136,7 +135,6 @@ export async function createJazzRNContext<Acc extends Account>(
websocket: new WebSocket(options.peer),
id: options.peer + "@" + new Date().toISOString(),
role: "server",
retryUnavailableCoValues: true,
}),
);
}

View File

@@ -1,5 +1,15 @@
# jazz-react
## 0.8.21
### Patch Changes
- Updated dependencies [0f30eea]
- Updated dependencies [149ca97]
- cojson@0.8.21
- jazz-tools@0.8.21
- jazz-browser@0.8.21
## 0.8.20
### Patch Changes

View File

@@ -1,15 +1,15 @@
{
"name": "jazz-react",
"version": "0.8.20",
"version": "0.8.21",
"type": "module",
"main": "dist/index.js",
"types": "src/index.ts",
"license": "MIT",
"dependencies": {
"@scure/bip39": "^1.3.0",
"cojson": "workspace:0.8.19",
"jazz-browser": "workspace:0.8.20",
"jazz-tools": "workspace:0.8.19"
"cojson": "workspace:0.8.21",
"jazz-browser": "workspace:0.8.21",
"jazz-tools": "workspace:0.8.21"
},
"devDependencies": {
"@types/react": "^18.2.19",

View File

@@ -1,5 +1,16 @@
# jazz-run
## 0.8.21
### Patch Changes
- Updated dependencies [0f30eea]
- Updated dependencies [149ca97]
- cojson@0.8.21
- jazz-tools@0.8.21
- cojson-storage-sqlite@0.8.21
- cojson-transport-ws@0.8.21
## 0.8.19
### Patch Changes

View File

@@ -3,7 +3,7 @@
"bin": "./dist/index.js",
"type": "module",
"license": "MIT",
"version": "0.8.19",
"version": "0.8.21",
"scripts": {
"format-and-lint": "biome check .",
"format-and-lint:fix": "biome check . --write",
@@ -18,11 +18,11 @@
"@effect/printer-ansi": "^0.34.5",
"@effect/schema": "^0.71.1",
"@effect/typeclass": "^0.25.5",
"cojson": "workspace:0.8.19",
"cojson-storage-sqlite": "workspace:0.8.19",
"cojson-transport-ws": "workspace:0.8.19",
"cojson": "workspace:0.8.21",
"cojson-storage-sqlite": "workspace:0.8.21",
"cojson-transport-ws": "workspace:0.8.21",
"effect": "^3.6.5",
"jazz-tools": "workspace:0.8.19",
"jazz-tools": "workspace:0.8.21",
"ws": "^8.14.2"
},
"devDependencies": {

View File

@@ -24,7 +24,6 @@ export const createWorkerAccount = async ({
id: "upstream",
websocket: new WebSocket(peerAddr),
role: "server",
retryUnavailableCoValues: true,
});
const account = await Account.create({
@@ -62,7 +61,6 @@ export const createWorkerAccount = async ({
id: "verifyingPeer",
websocket: new WebSocket(peerAddr),
role: "server",
retryUnavailableCoValues: true,
});
await Promise.race([

View File

@@ -73,7 +73,6 @@ export const startSyncServer = async ({
websocket: ws,
expectPings: false,
batchingByDefault: false,
retryUnavailableCoValues: false,
}),
);

View File

@@ -1,5 +1,13 @@
# jazz-tools
## 0.8.21
### Patch Changes
- 149ca97: changed jazz-tools TS target to ES2021
- Updated dependencies [0f30eea]
- cojson@0.8.21
## 0.8.19
### Patch Changes

View File

@@ -19,7 +19,7 @@
},
"type": "module",
"license": "MIT",
"version": "0.8.19",
"version": "0.8.21",
"dependencies": {
"cojson": "workspace:*",
"fast-check": "^3.17.2"

View File

@@ -2,7 +2,7 @@
"compilerOptions": {
"lib": ["ESNext"],
"module": "esnext",
"target": "es2022",
"target": "ES2021",
"moduleResolution": "bundler",
"moduleDetection": "force",
"strict": true,

View File

@@ -1,5 +1,15 @@
# jazz-react
## 0.8.11
### Patch Changes
- Updated dependencies [0f30eea]
- Updated dependencies [149ca97]
- cojson@0.8.21
- jazz-tools@0.8.21
- jazz-browser@0.8.21
## 0.8.10
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-vue",
"version": "0.8.10",
"version": "0.8.11",
"type": "module",
"main": "dist/index.js",
"types": "src/index.ts",

102
pnpm-lock.yaml generated
View File

@@ -46,7 +46,7 @@ importers:
e2e/BinaryCoStream:
dependencies:
cojson:
specifier: workspace:0.8.19
specifier: workspace:0.8.21
version: link:../../packages/cojson
hash-slash:
specifier: workspace:0.2.1
@@ -55,10 +55,10 @@ importers:
specifier: ^3.0.1
version: 3.0.1
jazz-react:
specifier: workspace:0.8.20
specifier: workspace:0.8.21
version: link:../../packages/jazz-react
jazz-tools:
specifier: workspace:0.8.19
specifier: workspace:0.8.21
version: link:../../packages/jazz-tools
react:
specifier: 18.3.1
@@ -150,13 +150,13 @@ importers:
specifier: ^2.0.0
version: 2.0.0
jazz-browser-media-images:
specifier: workspace:0.8.20
specifier: workspace:0.8.21
version: link:../../packages/jazz-browser-media-images
jazz-react:
specifier: workspace:0.8.20
specifier: workspace:0.8.21
version: link:../../packages/jazz-react
jazz-tools:
specifier: workspace:0.8.19
specifier: workspace:0.8.21
version: link:../../packages/jazz-tools
next:
specifier: 14.2.5
@@ -205,16 +205,16 @@ importers:
specifier: ^2.0.0
version: 2.0.0
cojson:
specifier: workspace:0.8.19
specifier: workspace:0.8.21
version: link:../../packages/cojson
hash-slash:
specifier: workspace:0.2.1
version: link:../../packages/hash-slash
jazz-react:
specifier: workspace:0.8.20
specifier: workspace:0.8.21
version: link:../../packages/jazz-react
jazz-tools:
specifier: workspace:0.8.19
specifier: workspace:0.8.21
version: link:../../packages/jazz-tools
lucide-react:
specifier: ^0.274.0
@@ -302,19 +302,19 @@ importers:
specifier: ^2.0.0
version: 2.0.0
cojson:
specifier: workspace:0.8.19
specifier: workspace:0.8.21
version: link:../../packages/cojson
hash-slash:
specifier: workspace:0.2.1
version: link:../../packages/hash-slash
jazz-react:
specifier: workspace:0.8.20
specifier: workspace:0.8.21
version: link:../../packages/jazz-react
jazz-react-auth-clerk:
specifier: workspace:0.8.20
specifier: workspace:0.8.21
version: link:../../packages/jazz-react-auth-clerk
jazz-tools:
specifier: workspace:0.8.19
specifier: workspace:0.8.21
version: link:../../packages/jazz-tools
lucide-react:
specifier: ^0.274.0
@@ -738,10 +738,10 @@ importers:
specifier: ^2.0.0
version: 2.0.0
cojson:
specifier: workspace:0.8.19
specifier: workspace:0.8.21
version: link:../../packages/cojson
cojson-transport-ws:
specifier: workspace:0.8.19
specifier: workspace:0.8.21
version: link:../../packages/cojson-transport-ws
hash-slash:
specifier: workspace:0.2.1
@@ -823,10 +823,10 @@ importers:
specifier: ^2.0.0
version: 2.0.0
jazz-react:
specifier: workspace:0.8.20
specifier: workspace:0.8.21
version: link:../../packages/jazz-react
jazz-tools:
specifier: workspace:0.8.19
specifier: workspace:0.8.21
version: link:../../packages/jazz-tools
lucide-react:
specifier: ^0.274.0
@@ -881,10 +881,10 @@ importers:
examples/password-manager:
dependencies:
jazz-react:
specifier: workspace:0.8.20
specifier: workspace:0.8.21
version: link:../../packages/jazz-react
jazz-tools:
specifier: workspace:0.8.19
specifier: workspace:0.8.21
version: link:../../packages/jazz-tools
react:
specifier: 18.3.1
@@ -945,13 +945,13 @@ importers:
specifier: ^2.0.0
version: 2.0.0
jazz-browser-media-images:
specifier: workspace:0.8.20
specifier: workspace:0.8.21
version: link:../../packages/jazz-browser-media-images
jazz-react:
specifier: workspace:0.8.20
specifier: workspace:0.8.21
version: link:../../packages/jazz-react
jazz-tools:
specifier: workspace:0.8.19
specifier: workspace:0.8.21
version: link:../../packages/jazz-tools
lucide-react:
specifier: ^0.274.0
@@ -1006,7 +1006,7 @@ importers:
specifier: ^3.0.1
version: 3.0.1
jazz-run:
specifier: workspace:0.8.19
specifier: workspace:0.8.21
version: link:../../packages/jazz-run
postcss:
specifier: ^8.4.27
@@ -1044,10 +1044,10 @@ importers:
specifier: ^2.0.0
version: 2.0.0
jazz-react:
specifier: workspace:0.8.20
specifier: workspace:0.8.21
version: link:../../packages/jazz-react
jazz-tools:
specifier: workspace:0.8.19
specifier: workspace:0.8.21
version: link:../../packages/jazz-tools
lucide-react:
specifier: ^0.274.0
@@ -1145,7 +1145,7 @@ importers:
packages/cojson-storage-indexeddb:
dependencies:
cojson:
specifier: workspace:0.8.19
specifier: workspace:0.8.21
version: link:../cojson
devDependencies:
'@vitest/browser':
@@ -1170,7 +1170,7 @@ importers:
specifier: ^8.5.2
version: 8.7.0
cojson:
specifier: workspace:0.8.19
specifier: workspace:0.8.21
version: link:../cojson
typescript:
specifier: ^5.3.3
@@ -1183,7 +1183,7 @@ importers:
packages/cojson-transport-ws:
dependencies:
cojson:
specifier: workspace:0.8.19
specifier: workspace:0.8.21
version: link:../cojson
typescript:
specifier: ^5.3.3
@@ -1211,16 +1211,16 @@ importers:
specifier: ^1.3.0
version: 1.3.0
cojson:
specifier: workspace:0.8.19
specifier: workspace:0.8.21
version: link:../cojson
cojson-storage-indexeddb:
specifier: workspace:0.8.20
specifier: workspace:0.8.21
version: link:../cojson-storage-indexeddb
cojson-transport-ws:
specifier: workspace:0.8.19
specifier: workspace:0.8.21
version: link:../cojson-transport-ws
jazz-tools:
specifier: workspace:0.8.19
specifier: workspace:0.8.21
version: link:../jazz-tools
typescript:
specifier: ^5.3.3
@@ -1229,13 +1229,13 @@ importers:
packages/jazz-browser-auth-clerk:
dependencies:
cojson:
specifier: workspace:0.8.19
specifier: workspace:0.8.21
version: link:../cojson
jazz-browser:
specifier: workspace:0.8.20
specifier: workspace:0.8.21
version: link:../jazz-browser
jazz-tools:
specifier: workspace:0.8.19
specifier: workspace:0.8.21
version: link:../jazz-tools
devDependencies:
typescript:
@@ -1251,10 +1251,10 @@ importers:
specifier: ^4.1.0
version: 4.1.0
jazz-browser:
specifier: workspace:0.8.20
specifier: workspace:0.8.21
version: link:../jazz-browser
jazz-tools:
specifier: workspace:0.8.19
specifier: workspace:0.8.21
version: link:../jazz-tools
pica:
specifier: ^9.0.1
@@ -1270,13 +1270,13 @@ importers:
packages/jazz-nodejs:
dependencies:
cojson:
specifier: workspace:0.8.19
specifier: workspace:0.8.21
version: link:../cojson
cojson-transport-ws:
specifier: workspace:0.8.19
specifier: workspace:0.8.21
version: link:../cojson-transport-ws
jazz-tools:
specifier: workspace:0.8.19
specifier: workspace:0.8.21
version: link:../jazz-tools
ws:
specifier: ^8.14.2
@@ -1295,13 +1295,13 @@ importers:
specifier: ^1.3.0
version: 1.3.0
cojson:
specifier: workspace:0.8.19
specifier: workspace:0.8.21
version: link:../cojson
jazz-browser:
specifier: workspace:0.8.20
specifier: workspace:0.8.21
version: link:../jazz-browser
jazz-tools:
specifier: workspace:0.8.19
specifier: workspace:0.8.21
version: link:../jazz-tools
devDependencies:
'@types/react':
@@ -1320,16 +1320,16 @@ importers:
packages/jazz-react-auth-clerk:
dependencies:
cojson:
specifier: workspace:0.8.19
specifier: workspace:0.8.21
version: link:../cojson
jazz-browser-auth-clerk:
specifier: workspace:0.8.20
specifier: workspace:0.8.21
version: link:../jazz-browser-auth-clerk
jazz-react:
specifier: workspace:0.8.20
specifier: workspace:0.8.21
version: link:../jazz-react
jazz-tools:
specifier: workspace:0.8.19
specifier: workspace:0.8.21
version: link:../jazz-tools
react:
specifier: 18.3.1
@@ -1413,19 +1413,19 @@ importers:
specifier: ^0.25.5
version: 0.25.5(effect@3.6.5)
cojson:
specifier: workspace:0.8.19
specifier: workspace:0.8.21
version: link:../cojson
cojson-storage-sqlite:
specifier: workspace:0.8.19
specifier: workspace:0.8.21
version: link:../cojson-storage-sqlite
cojson-transport-ws:
specifier: workspace:0.8.19
specifier: workspace:0.8.21
version: link:../cojson-transport-ws
effect:
specifier: ^3.6.5
version: 3.6.5
jazz-tools:
specifier: workspace:0.8.19
specifier: workspace:0.8.21
version: link:../jazz-tools
ws:
specifier: ^8.14.2