Compare commits

...

28 Commits

Author SHA1 Message Date
Guido D'Orsi
88ea30a6f8 Merge pull request #2013 from garden-co/changeset-release/main
Version Packages
2025-04-28 14:12:53 +02:00
github-actions[bot]
f4cbe395d5 Version Packages 2025-04-28 12:09:21 +00:00
Guido D'Orsi
c59fb5dc1f fix: complete the incremental view revert 2025-04-28 14:07:15 +02:00
Guido D'Orsi
c712ef28e8 fix(coList): revert incremental processing 2025-04-28 13:20:09 +02:00
Guido D'Orsi
c62a4a1c69 Revert "perf(colist): process the content incrementally"
This reverts commit e05dff9c32.
2025-04-28 13:15:56 +02:00
Guido D'Orsi
d28ce598e2 Merge pull request #2003 from garden-co/changeset-release/main
Version Packages
2025-04-26 10:24:45 +02:00
github-actions[bot]
14475991c8 Version Packages 2025-04-25 12:39:39 +00:00
Guido D'Orsi
15d9ec4b38 chore: small cleanup on startPeerReconciliation 2025-04-25 14:37:34 +02:00
Guido D'Orsi
f911545ae3 Merge pull request #2006 from garden-co/chore/simplify-coValue-sync
chore: clean up syncCoValue code and remove the peer role
2025-04-25 13:55:00 +02:00
Guido D'Orsi
ad71530cc0 Merge pull request #2005 from garden-co/feat/no-sync-promises
feat(sync): make the incoming messages handling in the sync manager syncronous
2025-04-25 13:54:35 +02:00
Guido D'Orsi
c33c02691f chore: clean up syncCoValue code and remove the peer role 2025-04-25 13:22:02 +02:00
Guido D'Orsi
51c19770a8 feat: remove unused promises in PeerState outgoing queue 2025-04-25 13:08:20 +02:00
Guido D'Orsi
5c2c7d4188 feat(sync): make the incoming messages handling in the sync manager syncronous 2025-04-25 12:36:31 +02:00
Benjamin S. Leveritt
334d27d53d Merge pull request #2002 from garden-co/2001-fix-unused-vars-in-jazz-paper-scissors
Removes unused vars
2025-04-25 08:19:17 +01:00
Benjamin S. Leveritt
a5396a42ce Merge pull request #1989 from garden-co/fix-rn-metro-docs
docs(metro): fix RN docs for metro config
2025-04-24 15:31:12 +01:00
Benjamin S. Leveritt
5cfe38d547 Removes unused vars 2025-04-24 13:43:25 +01:00
Benjamin S. Leveritt
3f7aa34726 Merge pull request #1996 from garden-co/1995-add-ownership-sections-to-all-the-covalue-docs
Add Ownership sections to CoValue docs
2025-04-24 13:11:52 +01:00
Trisha Lim
008750d401 Merge pull request #1997 from garden-co/docs/framework-component
add Framework component to show framework name
2025-04-24 12:27:54 +01:00
Trisha Lim
72708f82ea add Framework component to show framework name 2025-04-24 12:18:24 +01:00
Benjamin S. Leveritt
30f65f1c91 Add Ownership sections to CoValue docs 2025-04-24 12:06:52 +01:00
Meg Culotta
67d55ce0ee update styles to handle mobile devices (#1944)
* update styles to handle mobile devices

* handle twoslash overflow

---------

Co-authored-by: Margaret Culotta <mculotta@Margarets-MacBook-Air.local>
Co-authored-by: Trisha Lim <hello@trishalim.com>
2025-04-24 09:11:36 +01:00
Trisha Lim
e887f37713 Merge pull request #1994 from garden-co/fix/code-copy
fix(docs): exclude twoslash popovers from getting copied
2025-04-24 09:08:55 +01:00
Trisha Lim
82a515d493 fix(docs): exclude twoslash popovers from getting copied 2025-04-24 08:57:00 +01:00
pax-k
bd94012507 chore: changeset 2025-04-23 23:00:22 +03:00
Guido D'Orsi
b675249960 Merge pull request #1990 from garden-co/changeset-release/main
Version Packages
2025-04-23 21:49:44 +02:00
github-actions[bot]
05198e4181 Version Packages 2025-04-23 19:48:47 +00:00
Guido D'Orsi
ec9cb40fa4 fix: remove .every() call on iterator to fix compat issues with React Native 2025-04-23 21:46:27 +02:00
pax-k
dafea6039b docs(metro): fix RN docs for metro config 2025-04-23 22:31:50 +03:00
134 changed files with 1766 additions and 549 deletions

View File

@@ -1,5 +1,30 @@
# chat-rn-expo-clerk
## 1.0.107
### Patch Changes
- jazz-expo@0.13.15
- jazz-tools@0.13.15
- jazz-react-native-media-images@0.13.15
## 1.0.106
### Patch Changes
- Updated dependencies [bd94012]
- jazz-expo@0.13.14
- jazz-tools@0.13.14
- jazz-react-native-media-images@0.13.14
## 1.0.105
### Patch Changes
- jazz-expo@0.13.13
- jazz-tools@0.13.13
- jazz-react-native-media-images@0.13.13
## 1.0.104
### Patch Changes

View File

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

View File

@@ -1,5 +1,27 @@
# chat-rn-expo
## 1.0.94
### Patch Changes
- jazz-expo@0.13.15
- jazz-tools@0.13.15
## 1.0.93
### Patch Changes
- Updated dependencies [bd94012]
- jazz-expo@0.13.14
- jazz-tools@0.13.14
## 1.0.92
### Patch Changes
- jazz-expo@0.13.13
- jazz-tools@0.13.13
## 1.0.91
### Patch Changes

View File

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

View File

@@ -1,5 +1,35 @@
# chat-rn
## 1.0.102
### Patch Changes
- Updated dependencies [c712ef2]
- cojson@0.13.15
- cojson-transport-ws@0.13.15
- jazz-react-native@0.13.15
- jazz-tools@0.13.15
## 1.0.101
### Patch Changes
- Updated dependencies [5c2c7d4]
- cojson@0.13.14
- cojson-transport-ws@0.13.14
- jazz-react-native@0.13.14
- jazz-tools@0.13.14
## 1.0.100
### Patch Changes
- Updated dependencies [ec9cb40]
- cojson@0.13.13
- cojson-transport-ws@0.13.13
- jazz-react-native@0.13.13
- jazz-tools@0.13.13
## 1.0.99
### Patch Changes

View File

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

View File

@@ -1,5 +1,29 @@
# chat-vue
## 0.0.86
### Patch Changes
- jazz-browser@0.13.15
- jazz-tools@0.13.15
- jazz-vue@0.13.15
## 0.0.85
### Patch Changes
- jazz-browser@0.13.14
- jazz-tools@0.13.14
- jazz-vue@0.13.14
## 0.0.84
### Patch Changes
- jazz-browser@0.13.13
- jazz-tools@0.13.13
- jazz-vue@0.13.13
## 0.0.83
### Patch Changes

View File

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

View File

@@ -1,5 +1,29 @@
# jazz-example-chat
## 0.0.184
### Patch Changes
- jazz-inspector@0.13.15
- jazz-react@0.13.15
- jazz-tools@0.13.15
## 0.0.183
### Patch Changes
- jazz-inspector@0.13.14
- jazz-react@0.13.14
- jazz-tools@0.13.14
## 0.0.182
### Patch Changes
- jazz-inspector@0.13.13
- jazz-react@0.13.13
- jazz-tools@0.13.13
## 0.0.181
### Patch Changes

View File

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

View File

@@ -1,5 +1,29 @@
# minimal-auth-clerk
## 0.0.83
### Patch Changes
- jazz-react@0.13.15
- jazz-react-auth-clerk@0.13.15
- jazz-tools@0.13.15
## 0.0.82
### Patch Changes
- jazz-react@0.13.14
- jazz-react-auth-clerk@0.13.14
- jazz-tools@0.13.14
## 0.0.81
### Patch Changes
- jazz-react@0.13.13
- jazz-react-auth-clerk@0.13.13
- jazz-tools@0.13.13
## 0.0.80
### Patch Changes

View File

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

View File

@@ -1,5 +1,26 @@
# file-share-svelte
## 0.0.66
### Patch Changes
- jazz-svelte@0.13.15
- jazz-tools@0.13.15
## 0.0.65
### Patch Changes
- jazz-svelte@0.13.14
- jazz-tools@0.13.14
## 0.0.64
### Patch Changes
- jazz-svelte@0.13.13
- jazz-tools@0.13.13
## 0.0.63
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "file-share-svelte",
"version": "0.0.63",
"version": "0.0.66",
"private": true,
"type": "module",
"scripts": {

View File

@@ -1,5 +1,29 @@
# jazz-tailwind-demo-auth-starter
## 0.0.23
### Patch Changes
- jazz-inspector@0.13.15
- jazz-react@0.13.15
- jazz-tools@0.13.15
## 0.0.22
### Patch Changes
- jazz-inspector@0.13.14
- jazz-react@0.13.14
- jazz-tools@0.13.14
## 0.0.21
### Patch Changes
- jazz-inspector@0.13.13
- jazz-react@0.13.13
- jazz-tools@0.13.13
## 0.0.20
### Patch Changes

View File

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

View File

@@ -1,5 +1,26 @@
# form
## 0.1.24
### Patch Changes
- jazz-react@0.13.15
- jazz-tools@0.13.15
## 0.1.23
### Patch Changes
- jazz-react@0.13.14
- jazz-tools@0.13.14
## 0.1.22
### Patch Changes
- jazz-react@0.13.13
- jazz-tools@0.13.13
## 0.1.21
### Patch Changes

View File

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

View File

@@ -1,5 +1,26 @@
# image-upload
## 0.0.80
### Patch Changes
- jazz-react@0.13.15
- jazz-tools@0.13.15
## 0.0.79
### Patch Changes
- jazz-react@0.13.14
- jazz-tools@0.13.14
## 0.0.78
### Patch Changes
- jazz-react@0.13.13
- jazz-tools@0.13.13
## 0.0.77
### Patch Changes

View File

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

View File

@@ -1,5 +1,32 @@
# jazz-example-inspector
## 0.0.134
### Patch Changes
- Updated dependencies [c712ef2]
- cojson@0.13.15
- cojson-transport-ws@0.13.15
- jazz-inspector@0.13.15
## 0.0.133
### Patch Changes
- Updated dependencies [5c2c7d4]
- cojson@0.13.14
- cojson-transport-ws@0.13.14
- jazz-inspector@0.13.14
## 0.0.132
### Patch Changes
- Updated dependencies [ec9cb40]
- cojson@0.13.13
- cojson-transport-ws@0.13.13
- jazz-inspector@0.13.13
## 0.0.131
### Patch Changes

View File

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

View File

@@ -45,7 +45,7 @@ export const Route = createFileRoute("/_authenticated/game/$gameId")({
});
function RouteComponent() {
const { gameId, me, loaderGame } = Route.useLoaderData();
const { gameId, loaderGame } = Route.useLoaderData();
const isPlayer1 = loaderGame.player1?.account?.isMe;
const player = isPlayer1 ? "player1" : "player2";

View File

@@ -1,4 +1,4 @@
import { Account, CoMap, SchemaUnion, co } from "jazz-tools";
import { Account, CoMap, co } from "jazz-tools";
export class Game extends CoMap {
player1 = co.ref(Player);

View File

@@ -1,5 +1,26 @@
# multi-cursors
## 0.0.76
### Patch Changes
- jazz-react@0.13.15
- jazz-tools@0.13.15
## 0.0.75
### Patch Changes
- jazz-react@0.13.14
- jazz-tools@0.13.14
## 0.0.74
### Patch Changes
- jazz-react@0.13.13
- jazz-tools@0.13.13
## 0.0.73
### Patch Changes

View File

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

View File

@@ -1,5 +1,29 @@
# multiauth
## 0.0.24
### Patch Changes
- jazz-react@0.13.15
- jazz-react-auth-clerk@0.13.15
- jazz-tools@0.13.15
## 0.0.23
### Patch Changes
- jazz-react@0.13.14
- jazz-react-auth-clerk@0.13.14
- jazz-tools@0.13.14
## 0.0.22
### Patch Changes
- jazz-react@0.13.13
- jazz-react-auth-clerk@0.13.13
- jazz-tools@0.13.13
## 0.0.21
### Patch Changes

View File

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

View File

@@ -1,5 +1,29 @@
# jazz-example-musicplayer
## 0.0.105
### Patch Changes
- jazz-inspector@0.13.15
- jazz-react@0.13.15
- jazz-tools@0.13.15
## 0.0.104
### Patch Changes
- jazz-inspector@0.13.14
- jazz-react@0.13.14
- jazz-tools@0.13.14
## 0.0.103
### Patch Changes
- jazz-inspector@0.13.13
- jazz-react@0.13.13
- jazz-tools@0.13.13
## 0.0.102
### Patch Changes

View File

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

View File

@@ -1,5 +1,26 @@
# organization
## 0.0.76
### Patch Changes
- jazz-react@0.13.15
- jazz-tools@0.13.15
## 0.0.75
### Patch Changes
- jazz-react@0.13.14
- jazz-tools@0.13.14
## 0.0.74
### Patch Changes
- jazz-react@0.13.13
- jazz-tools@0.13.13
## 0.0.73
### Patch Changes

View File

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

View File

@@ -1,5 +1,26 @@
# passkey-svelte
## 0.0.70
### Patch Changes
- jazz-svelte@0.13.15
- jazz-tools@0.13.15
## 0.0.69
### Patch Changes
- jazz-svelte@0.13.14
- jazz-tools@0.13.14
## 0.0.68
### Patch Changes
- jazz-svelte@0.13.13
- jazz-tools@0.13.13
## 0.0.67
### Patch Changes

View File

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

View File

@@ -1,5 +1,26 @@
# minimal-auth-passkey
## 0.0.81
### Patch Changes
- jazz-react@0.13.15
- jazz-tools@0.13.15
## 0.0.80
### Patch Changes
- jazz-react@0.13.14
- jazz-tools@0.13.14
## 0.0.79
### Patch Changes
- jazz-react@0.13.13
- jazz-tools@0.13.13
## 0.0.78
### Patch Changes

View File

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

View File

@@ -1,5 +1,26 @@
# passphrase
## 0.0.78
### Patch Changes
- jazz-react@0.13.15
- jazz-tools@0.13.15
## 0.0.77
### Patch Changes
- jazz-react@0.13.14
- jazz-tools@0.13.14
## 0.0.76
### Patch Changes
- jazz-react@0.13.13
- jazz-tools@0.13.13
## 0.0.75
### Patch Changes

View File

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

View File

@@ -1,5 +1,26 @@
# jazz-password-manager
## 0.0.102
### Patch Changes
- jazz-react@0.13.15
- jazz-tools@0.13.15
## 0.0.101
### Patch Changes
- jazz-react@0.13.14
- jazz-tools@0.13.14
## 0.0.100
### Patch Changes
- jazz-react@0.13.13
- jazz-tools@0.13.13
## 0.0.99
### Patch Changes

View File

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

View File

@@ -1,5 +1,26 @@
# jazz-example-pets
## 0.0.200
### Patch Changes
- jazz-react@0.13.15
- jazz-tools@0.13.15
## 0.0.199
### Patch Changes
- jazz-react@0.13.14
- jazz-tools@0.13.14
## 0.0.198
### Patch Changes
- jazz-react@0.13.13
- jazz-tools@0.13.13
## 0.0.197
### Patch Changes

View File

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

View File

@@ -1,5 +1,26 @@
# reactions
## 0.0.80
### Patch Changes
- jazz-react@0.13.15
- jazz-tools@0.13.15
## 0.0.79
### Patch Changes
- jazz-react@0.13.14
- jazz-tools@0.13.14
## 0.0.78
### Patch Changes
- jazz-react@0.13.13
- jazz-tools@0.13.13
## 0.0.77
### Patch Changes

View File

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

View File

@@ -1,5 +1,29 @@
# richtext
## 0.0.70
### Patch Changes
- jazz-react@0.13.15
- jazz-tools@0.13.15
- jazz-richtext-prosemirror@0.1.4
## 0.0.69
### Patch Changes
- jazz-react@0.13.14
- jazz-tools@0.13.14
- jazz-richtext-prosemirror@0.1.3
## 0.0.68
### Patch Changes
- jazz-react@0.13.13
- jazz-tools@0.13.13
- jazz-richtext-prosemirror@0.1.2
## 0.0.67
### Patch Changes

View File

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

View File

@@ -1,5 +1,29 @@
# todo-vue
## 0.0.84
### Patch Changes
- jazz-browser@0.13.15
- jazz-tools@0.13.15
- jazz-vue@0.13.15
## 0.0.83
### Patch Changes
- jazz-browser@0.13.14
- jazz-tools@0.13.14
- jazz-vue@0.13.14
## 0.0.82
### Patch Changes
- jazz-browser@0.13.13
- jazz-tools@0.13.13
- jazz-vue@0.13.13
## 0.0.81
### Patch Changes

View File

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

View File

@@ -1,5 +1,26 @@
# jazz-example-todo
## 0.0.199
### Patch Changes
- jazz-react@0.13.15
- jazz-tools@0.13.15
## 0.0.198
### Patch Changes
- jazz-react@0.13.14
- jazz-tools@0.13.14
## 0.0.197
### Patch Changes
- jazz-react@0.13.13
- jazz-tools@0.13.13
## 0.0.196
### Patch Changes

View File

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

View File

@@ -1,5 +1,29 @@
# version-history
## 0.0.78
### Patch Changes
- jazz-inspector@0.13.15
- jazz-react@0.13.15
- jazz-tools@0.13.15
## 0.0.77
### Patch Changes
- jazz-inspector@0.13.14
- jazz-react@0.13.14
- jazz-tools@0.13.14
## 0.0.76
### Patch Changes
- jazz-inspector@0.13.13
- jazz-react@0.13.13
- jazz-tools@0.13.13
## 0.0.75
### Patch Changes

View File

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

View File

@@ -1,7 +1,7 @@
"use client";
import { clsx } from "clsx";
import { useEffect, useId, useRef, useState } from "react";
import { useEffect, useRef, useState } from "react";
import { Icon } from "../atoms/Icon";
// TODO: add tabs feature, and remove CodeExampleTabs
@@ -84,9 +84,25 @@ export function CodeGroup({
}) {
const textRef = useRef<HTMLPreElement | null>(null);
const [code, setCode] = useState<string>();
const filterText = (node: Node): string => {
if (
node instanceof Element &&
(node.classList.contains("twoslash-popup-container") ||
node.classList.contains("twoslash-completion-cursor"))
) {
return "";
}
if (node.nodeType === Node.TEXT_NODE) {
return node.textContent ?? "";
}
return Array.from(node.childNodes).map(filterText).join("");
};
useEffect(() => {
if (textRef.current) {
setCode(textRef.current.innerText);
setCode(filterText(textRef.current));
}
}, [children]);

View File

@@ -0,0 +1,10 @@
"use client";
import { frameworkNames } from "@/content/framework";
import { useFramework } from "@/lib/use-framework";
export function Framework() {
const framework = useFramework();
return <>{frameworkNames[framework].label}</>;
}

View File

@@ -1,6 +1,6 @@
"use client";
import { Framework } from "@/content/framework";
import { Framework, frameworkNames } from "@/content/framework";
import { useFramework } from "@/lib/use-framework";
import { Button } from "@garden-co/design-system/src/components/atoms/Button";
import { Icon } from "@garden-co/design-system/src/components/atoms/Icon";
@@ -13,39 +13,6 @@ import {
import { usePathname, useRouter } from "next/navigation";
import { useState } from "react";
const frameworks: Record<
Framework,
{
label: string;
experimental: boolean;
}
> = {
[Framework.React]: {
label: "React",
experimental: false,
},
[Framework.ReactNative]: {
label: "React Native",
experimental: false,
},
[Framework.ReactNativeExpo]: {
label: "React Native (Expo)",
experimental: false,
},
[Framework.Vanilla]: {
label: "VanillaJS",
experimental: false,
},
[Framework.Svelte]: {
label: "Svelte",
experimental: true,
},
[Framework.Vue]: {
label: "Vue",
experimental: true,
},
};
export function FrameworkSelect() {
const router = useRouter();
const defaultFramework = useFramework();
@@ -66,11 +33,11 @@ export function FrameworkSelect() {
as={Button}
variant="secondary"
>
{frameworks[selectedFramework].label}
{frameworkNames[selectedFramework].label}
<Icon name="chevronDown" size="sm" className="text-muted" />
</DropdownButton>
<DropdownMenu className="w-[--button-width] z-50" anchor="bottom start">
{Object.entries(frameworks).map(([key, framework]) => (
{Object.entries(frameworkNames).map(([key, framework]) => (
<DropdownItem
className="items-baseline"
key={key}

View File

@@ -12,6 +12,7 @@ import { CodeGroup as CodeGroupClient } from "@garden-co/design-system/src/compo
import { AnchorHTMLAttributes, DetailedHTMLProps } from "react";
import { FileDownloadLink as FileDownloadLinkClient } from "./FileDownloadLink";
import { ComingSoon as ComingSoonClient } from "./docs/ComingSoon";
import { Framework as FrameworkClient } from "./docs/Framework";
import { IssueTrackerPreview as IssueTrackerPreviewClient } from "./docs/IssueTrackerPreview";
export function CodeExampleTabs(props: CodeExampleTabsProps) {
@@ -46,3 +47,7 @@ export function FileDownloadLink(
) {
return <FileDownloadLinkClient {...props} />;
}
export function Framework() {
return <FrameworkClient />;
}

View File

@@ -70,7 +70,7 @@ If you are not working within a monorepo, create a new file `metro.config.js` in
// @noErrors: 2304
// metro.config.js
const { getDefaultConfig } = require("expo/metro-config");
const config = getDefaultConfig(projectRoot);
const config = getDefaultConfig(__dirname);
config.resolver.sourceExts = ["mjs", "js", "json", "ts", "tsx"];
config.resolver.requireCycleIgnorePatterns = [/(^|\/|\\)node_modules($|\/|\\)/];

View File

@@ -33,7 +33,34 @@ const activityFeed = ActivityFeed.create([]);
```
</CodeGroup>
Like other CoValues, you can specify [ownership](/docs/using-covalues/ownership) when creating CoFeeds.
### Ownership
Like other CoValues, you can specify ownership when creating CoFeeds.
<CodeGroup>
```ts twoslash
import { Group, co, CoMap, CoFeed } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const me = await createJazzTestAccount();
const colleagueAccount = await createJazzTestAccount();
class Activity extends CoMap {
timestamp = co.Date;
action = co.literal("watering", "planting", "harvesting", "maintenance");
notes = co.optional.string;
}
class ActivityFeed extends CoFeed.Of(co.ref(Activity)) {}
// ---cut---
const teamGroup = Group.create();
teamGroup.addMember(colleagueAccount, "writer");
const teamFeed = ActivityFeed.create([], { owner: teamGroup });
```
</CodeGroup>
See [Groups as permission scopes](/docs/groups/intro) for more information on how to use groups to control access to CoFeeds.
## Reading from CoFeeds

View File

@@ -33,7 +33,32 @@ const tasks = ListOfTasks.create([
```
</CodeGroup>
Like other CoValues, you can specify [ownership](/docs/using-covalues/ownership) when creating CoLists.
### Ownership
Like other CoValues, you can specify ownership when creating CoLists.
<CodeGroup>
```ts twoslash
import { Group, co, CoMap, CoList } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const me = await createJazzTestAccount();
const colleagueAccount = await createJazzTestAccount();
class Task extends CoMap {
title = co.string;
status = co.string;
}
class ListOfTasks extends CoList.Of(co.ref(Task)) {}
// ---cut---
// Create with shared ownership
const teamGroup = Group.create();
teamGroup.addMember(colleagueAccount, "writer");
const teamList = ListOfTasks.create([], { owner: teamGroup });
```
</CodeGroup>
See [Groups as permission scopes](/docs/groups/intro) for more information on how to use groups to control access to CoLists.
## Reading from CoLists

View File

@@ -52,7 +52,24 @@ const inventory = Inventory.create({
When creating CoMaps, you can specify ownership to control access:
<CodeGroup>
```ts
```ts twoslash
import { Group, co, CoMap } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const me = await createJazzTestAccount();
const memberAccount = await createJazzTestAccount();
class Member extends CoMap {
name = co.string;
}
class Project extends CoMap {
name = co.string;
startDate = co.Date;
status = co.literal("planning", "active", "completed");
coordinator = co.optional.ref(Member);
}
// ---cut---
// Create with default owner (current user)
const privateProject = Project.create({
name: "My Herb Garden",
@@ -75,6 +92,8 @@ const communityProject = Project.create(
```
</CodeGroup>
See [Groups as permission scopes](/docs/groups/intro) for more information on how to use groups to control access to CoMaps.
## Reading from CoMaps
CoMaps can be accessed using familiar JavaScript object notation:

View File

@@ -73,6 +73,29 @@ const fileStream = FileStream.create();
```
</CodeGroup>
### Ownership
Like other CoValues, you can specify ownership when creating FileStreams.
<CodeGroup>
```ts twoslash
import { Group, FileStream } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const me = await createJazzTestAccount();
const colleagueAccount = await createJazzTestAccount();
// ---cut---
// Create a team group
const teamGroup = Group.create();
teamGroup.addMember(colleagueAccount, "writer");
// Create a FileStream with shared ownership
const teamFileStream = FileStream.create({ owner: teamGroup });
```
</CodeGroup>
See [Groups as permission scopes](/docs/groups/intro) for more information on how to use groups to control access to FileStreams.
## Reading from FileStreams
`FileStream`s provide several ways to access their binary content, from raw chunks to convenient Blob objects.

View File

@@ -66,6 +66,31 @@ const image = await createImage(file, options);
```
</CodeGroup>
### Ownership
Like other CoValues, you can specify ownership when creating image definitions.
<CodeGroup>
```ts twoslash
import { Group } from "jazz-tools";
import { createImage } from "jazz-browser-media-images";
import { createJazzTestAccount } from 'jazz-tools/testing';
const me = await createJazzTestAccount();
const colleagueAccount = await createJazzTestAccount();
const file = new File([], "test.jpg", { type: "image/jpeg" });
// ---cut---
const teamGroup = Group.create();
teamGroup.addMember(colleagueAccount, "writer");
// Create an image with shared ownership
const teamImage = await createImage(file, { owner: teamGroup });
```
</CodeGroup>
See [Groups as permission scopes](/docs/groups/intro) for more information on how to use groups to control access to images.
## Creating ImageDefinitions
Create an `ImageDefinition` by specifying the original dimensions and an optional placeholder:

View File

@@ -67,6 +67,31 @@ const image = await createImage(file, options);
```
</CodeGroup>
### Ownership
Like other CoValues, you can specify ownership when creating image definitions.
<CodeGroup>
```ts twoslash
import { Group } from "jazz-tools";
import { createImage } from "jazz-browser-media-images";
import { createJazzTestAccount } from 'jazz-tools/testing';
const me = await createJazzTestAccount();
const colleagueAccount = await createJazzTestAccount();
const file = new File([], "test.jpg", { type: "image/jpeg" });
// ---cut---
const teamGroup = Group.create();
teamGroup.addMember(colleagueAccount, "writer");
// Create an image with shared ownership
const teamImage = await createImage(file, { owner: teamGroup });
```
</CodeGroup>
See [Groups as permission scopes](/docs/groups/intro) for more information on how to use groups to control access to images.
## Displaying Images with `ProgressiveImg`
For a complete progressive loading experience, use the `ProgressiveImg` component:

View File

@@ -8,6 +8,38 @@ export enum Framework {
}
export const frameworks = Object.values(Framework);
export const frameworkNames: Record<
Framework,
{
label: string;
experimental: boolean;
}
> = {
[Framework.React]: {
label: "React",
experimental: false,
},
[Framework.ReactNative]: {
label: "React Native",
experimental: false,
},
[Framework.ReactNativeExpo]: {
label: "React Native (Expo)",
experimental: false,
},
[Framework.Vanilla]: {
label: "VanillaJS",
experimental: false,
},
[Framework.Svelte]: {
label: "Svelte",
experimental: true,
},
[Framework.Vue]: {
label: "Vue",
experimental: true,
},
};
export const DEFAULT_FRAMEWORK = Framework.React;

View File

@@ -42,7 +42,7 @@ export async function getDocMetadata(framework: string, slug?: string[]) {
function DocProse({ children }: { children: React.ReactNode }) {
return (
<Prose className="overflow-x-visible lg:flex-1 pb-8 pt-[calc(61px+2rem)] md:pt-8 md:max-w-3xl mx-auto">
<Prose className="overflow-x-hidden lg:overflow-x-visible lg:flex-1 pb-8 pt-[calc(61px+2rem)] md:pt-8 md:max-w-3xl mx-auto">
{children}
</Prose>
);

View File

@@ -1,5 +1,29 @@
# cojson-storage-indexeddb
## 0.13.15
### Patch Changes
- Updated dependencies [c712ef2]
- cojson@0.13.15
- cojson-storage@0.13.15
## 0.13.14
### Patch Changes
- Updated dependencies [5c2c7d4]
- cojson@0.13.14
- cojson-storage@0.13.14
## 0.13.13
### Patch Changes
- Updated dependencies [ec9cb40]
- cojson@0.13.13
- cojson-storage@0.13.13
## 0.13.12
### Patch Changes

View File

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

View File

@@ -1,5 +1,29 @@
# cojson-storage-sqlite
## 0.13.15
### Patch Changes
- Updated dependencies [c712ef2]
- cojson@0.13.15
- cojson-storage@0.13.15
## 0.13.14
### Patch Changes
- Updated dependencies [5c2c7d4]
- cojson@0.13.14
- cojson-storage@0.13.14
## 0.13.13
### Patch Changes
- Updated dependencies [ec9cb40]
- cojson@0.13.13
- cojson-storage@0.13.13
## 0.13.12
### Patch Changes

View File

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

View File

@@ -118,9 +118,9 @@ test("should sync and load data from storage", async () => {
"client -> LOAD Map sessions: empty",
"storage -> KNOWN Group sessions: header/3",
"storage -> CONTENT Group header: true new: After: 0 New: 3",
"client -> KNOWN Group sessions: header/3",
"storage -> KNOWN Map sessions: header/1",
"storage -> CONTENT Map header: true new: After: 0 New: 1",
"client -> KNOWN Group sessions: header/3",
"client -> KNOWN Map sessions: header/1",
]
`);
@@ -205,12 +205,12 @@ test("should load dependencies correctly (group inheritance)", async () => {
"client -> LOAD Map sessions: empty",
"storage -> KNOWN ParentGroup sessions: header/4",
"storage -> CONTENT ParentGroup header: true new: After: 0 New: 4",
"client -> KNOWN ParentGroup sessions: header/4",
"storage -> KNOWN Group sessions: header/5",
"storage -> CONTENT Group header: true new: After: 0 New: 5",
"client -> KNOWN Group sessions: header/5",
"client -> KNOWN ParentGroup sessions: header/4",
"storage -> KNOWN Map sessions: header/1",
"storage -> CONTENT Map header: true new: After: 0 New: 1",
"client -> KNOWN Group sessions: header/5",
"client -> KNOWN Map sessions: header/1",
]
`);
@@ -312,9 +312,9 @@ test("should recover from data loss", async () => {
"client -> LOAD Map sessions: empty",
"storage -> KNOWN Group sessions: header/3",
"storage -> CONTENT Group header: true new: After: 0 New: 3",
"client -> KNOWN Group sessions: header/3",
"storage -> KNOWN Map sessions: header/4",
"storage -> CONTENT Map header: true new: After: 0 New: 4",
"client -> KNOWN Group sessions: header/3",
"client -> KNOWN Map sessions: header/4",
]
`);

View File

@@ -1,5 +1,26 @@
# cojson-storage
## 0.13.15
### Patch Changes
- Updated dependencies [c712ef2]
- cojson@0.13.15
## 0.13.14
### Patch Changes
- Updated dependencies [5c2c7d4]
- cojson@0.13.14
## 0.13.13
### Patch Changes
- Updated dependencies [ec9cb40]
- cojson@0.13.13
## 0.13.12
### Patch Changes

View File

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

View File

@@ -1,5 +1,26 @@
# cojson-transport-nodejs-ws
## 0.13.15
### Patch Changes
- Updated dependencies [c712ef2]
- cojson@0.13.15
## 0.13.14
### Patch Changes
- Updated dependencies [5c2c7d4]
- cojson@0.13.14
## 0.13.13
### Patch Changes
- Updated dependencies [ec9cb40]
- cojson@0.13.13
## 0.13.12
### Patch Changes

View File

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

View File

@@ -1,5 +1,23 @@
# cojson
## 0.13.15
### Patch Changes
- c712ef2: Revert the RawCoList incremental processing
## 0.13.14
### Patch Changes
- 5c2c7d4: Make the incoming messages handling in the sync manager syncronous
## 0.13.13
### Patch Changes
- ec9cb40: Remove .every() call on iterator to fix compat issues with React Native
## 0.13.12
### Patch Changes

View File

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

View File

@@ -1,9 +1,5 @@
import { PeerKnownStates, ReadonlyPeerKnownStates } from "./PeerKnownStates.js";
import {
PriorityBasedMessageQueue,
QueueEntry,
} from "./PriorityBasedMessageQueue.js";
import { TryAddTransactionsError } from "./coValueCore.js";
import { PriorityBasedMessageQueue } from "./PriorityBasedMessageQueue.js";
import { RawCoID, SessionID } from "./ids.js";
import { logger } from "./logger.js";
import { CO_VALUE_PRIORITY } from "./priority.js";
@@ -12,9 +8,6 @@ import { CoValueKnownState, Peer, SyncMessage } from "./sync.js";
export class PeerState {
private queue: PriorityBasedMessageQueue;
incomingMessagesProcessingPromise: Promise<void> | undefined;
nextPeer: Peer | undefined;
constructor(
private peer: Peer,
knownStates: ReadonlyPeerKnownStates | undefined,
@@ -156,15 +149,26 @@ export class PeerState {
this.processing = true;
let entry: QueueEntry | undefined;
while ((entry = this.queue.pull())) {
let msg: SyncMessage | undefined;
while ((msg = this.queue.pull())) {
if (this.closed) {
break;
}
// Awaiting the push to send one message at a time
// This way when the peer is "under pressure" we can enqueue all
// the coming messages and organize them by priority
await this.peer.outgoing
.push(entry.msg)
.then(entry.resolve)
.catch(entry.reject);
try {
await this.peer.outgoing.push(msg);
} catch (e) {
logger.error("Error sending message", {
err: e,
action: msg.action,
id: msg.id,
peerId: this.id,
peerRole: this.role,
});
}
}
this.processing = false;
@@ -172,14 +176,16 @@ export class PeerState {
pushOutgoingMessage(msg: SyncMessage) {
if (this.closed) {
return Promise.resolve();
return;
}
const promise = this.queue.push(msg);
this.queue.push(msg);
void this.processQueue();
}
return promise;
isProcessing() {
return this.processing;
}
get incoming() {
@@ -192,14 +198,6 @@ export class PeerState {
return this.peer.incoming;
}
private closeQueue() {
let entry: QueueEntry | undefined;
while ((entry = this.queue.pull())) {
// Using resolve here to avoid unnecessary noise in the logs
entry.resolve();
}
}
closeListeners = new Set<() => void>();
addCloseListener(listener: () => void) {
@@ -228,40 +226,38 @@ export class PeerState {
peerId: this.id,
peerRole: this.role,
});
this.closeQueue();
this.peer.outgoing.close();
this.closed = true;
this.emitClose();
}
async processIncomingMessages(callback: (msg: SyncMessage) => Promise<void>) {
async processIncomingMessages(callback: (msg: SyncMessage) => void) {
if (this.closed) {
throw new Error("Peer is closed");
}
if (this.incomingMessagesProcessingPromise) {
throw new Error("Incoming messages processing already in progress");
}
const processIncomingMessages = async () => {
for await (const msg of this.incoming) {
if (msg === "Disconnected") {
break;
if (this.closed) {
return;
}
if (msg === "Disconnected") {
return;
}
if (msg === "PingTimeout") {
logger.error("Ping timeout from peer", {
peerId: this.id,
peerRole: this.role,
});
break;
return;
}
await callback(msg);
callback(msg);
}
};
this.incomingMessagesProcessingPromise = processIncomingMessages();
return this.incomingMessagesProcessingPromise;
return processIncomingMessages();
}
}

View File

@@ -2,29 +2,6 @@ import { Counter, ValueType, metrics } from "@opentelemetry/api";
import { CO_VALUE_PRIORITY, type CoValuePriority } from "./priority.js";
import type { SyncMessage } from "./sync.js";
function promiseWithResolvers<R>() {
let resolve = (_: R) => {};
let reject = (_: unknown) => {};
const promise = new Promise<R>((_resolve, _reject) => {
resolve = _resolve;
reject = _reject;
});
return {
promise,
resolve,
reject,
};
}
export type QueueEntry = {
msg: SyncMessage;
promise: Promise<void>;
resolve: () => void;
reject: (_: unknown) => void;
};
/**
* Since we have a fixed range of priority values (0-7) we can create a fixed array of queues.
*/
@@ -34,7 +11,7 @@ type Tuple<T, N extends number, A extends unknown[] = []> = A extends {
? A
: Tuple<T, N, [...A, T]>;
type QueueTuple = Tuple<LinkedList<QueueEntry>, 3>;
type QueueTuple = Tuple<LinkedList<SyncMessage>, 3>;
type LinkedListNode<T> = {
value: T;
@@ -164,14 +141,9 @@ export class PriorityBasedMessageQueue {
}
public push(msg: SyncMessage) {
const { promise, resolve, reject } = promiseWithResolvers<void>();
const entry: QueueEntry = { msg, promise, resolve, reject };
const priority = "priority" in msg ? msg.priority : this.defaultPriority;
this.getQueue(priority).push(entry);
return promise;
this.getQueue(priority).push(msg);
}
public pull() {

View File

@@ -488,7 +488,7 @@ export class CoValueCore {
if (success) {
this.node.syncManager.recordTransactionsSize([transaction], "local");
void this.node.syncManager.syncCoValue(this);
void this.node.syncManager.requestCoValueSync(this);
}
return success;

View File

@@ -44,21 +44,19 @@ export class CoValueState {
get highLevelState() {
if (this.core) {
return "available";
} else {
if (this.peers.size === 0) {
return "unknown";
} else if (
this.peers
.values()
.every((p) => p.type === "unavailable" || p.type === "errored")
) {
return "unavailable";
} else if (this.peers.values().some((p) => p.type === "pending")) {
} else if (this.peers.size === 0) {
return "unknown";
}
for (const peer of this.peers.values()) {
if (peer.type === "pending") {
return "loading";
} else {
} else if (peer.type === "unknown") {
return "unknown";
}
}
return "unavailable";
}
isErroredInPeer(peerId: PeerID) {
@@ -143,16 +141,10 @@ export class CoValueState {
continue;
}
peer
.pushOutgoingMessage({
action: "load",
...(this.core ? this.core.knownState() : emptyKnownState(this.id)),
})
.catch((err) => {
logger.warn(`Failed to push load message to peer ${peer.id}`, {
err,
});
});
peer.pushOutgoingMessage({
action: "load",
...(this.core ? this.core.knownState() : emptyKnownState(this.id)),
});
/**
* Use a very long timeout for storage peers, because under pressure

View File

@@ -2,7 +2,6 @@ import { CoID, RawCoValue } from "../coValue.js";
import { CoValueCore } from "../coValueCore.js";
import { AgentID, SessionID, TransactionID } from "../ids.js";
import { JsonObject, JsonValue } from "../jsonValue.js";
import { CoValueKnownState } from "../sync.js";
import { accountOrAgentIDfromSessionID } from "../typeUtils/accountOrAgentIDfromSessionID.js";
import { isCoValue } from "../typeUtils/isCoValue.js";
import { RawAccountID } from "./account.js";
@@ -82,8 +81,6 @@ export class RawCoListView<
madeAt: number;
opID: OpID;
}[];
/** @internal */
knownTransactions: CoValueKnownState["sessions"];
/** @internal */
constructor(core: CoValueCore) {
@@ -98,24 +95,12 @@ export class RawCoListView<
this.deletionsByInsertion = {};
this.afterStart = [];
this.beforeEnd = [];
this.knownTransactions = {};
this.processNewTransactions();
}
processNewTransactions() {
const newTransactions = this.core.getValidTransactions({
ignorePrivateTransactions: false,
knownTransactions: this.knownTransactions,
});
if (newTransactions.length === 0) {
return;
}
this._cachedEntries = undefined;
for (const { txID, changes, madeAt } of newTransactions) {
for (const {
txID,
changes,
madeAt,
} of this.core.getValidSortedTransactions()) {
for (const [changeIdx, changeUntyped] of changes.entries()) {
const change = changeUntyped as ListOpPayload<Item>;
@@ -207,11 +192,6 @@ export class RawCoListView<
);
}
}
this.knownTransactions[txID.sessionID] = Math.max(
this.knownTransactions[txID.sessionID] ?? 0,
txID.txIndex,
);
}
}
@@ -501,7 +481,7 @@ export class RawCoList<
this.core.makeTransaction(changes, privacy);
this.processNewTransactions();
this.rebuildFromCore();
}
/**
@@ -548,7 +528,7 @@ export class RawCoList<
privacy,
);
this.processNewTransactions();
this.rebuildFromCore();
}
/** Deletes the item at index `at`.
@@ -575,7 +555,7 @@ export class RawCoList<
privacy,
);
this.processNewTransactions();
this.rebuildFromCore();
}
replace(
@@ -603,6 +583,17 @@ export class RawCoList<
],
privacy,
);
this.processNewTransactions();
this.rebuildFromCore();
}
/** @internal */
rebuildFromCore() {
const listAfter = new RawCoList(this.core) as this;
this.afterStart = listAfter.afterStart;
this.beforeEnd = listAfter.beforeEnd;
this.insertions = listAfter.insertions;
this.deletionsByInsertion = listAfter.deletionsByInsertion;
this._cachedEntries = undefined;
}
}

View File

@@ -187,6 +187,6 @@ export class RawCoPlainText<
}
this.core.makeTransaction(ops, privacy);
this.processNewTransactions();
this.rebuildFromCore();
}
}

View File

@@ -140,7 +140,9 @@ export class LocalNode {
function syncAllCoValuesAfterCreateAccount() {
for (const coValueEntry of nodeWithAccount.coValuesStore.getValues()) {
if (coValueEntry.isAvailable()) {
void nodeWithAccount.syncManager.syncCoValue(coValueEntry.core);
void nodeWithAccount.syncManager.requestCoValueSync(
coValueEntry.core,
);
}
}
}
@@ -248,7 +250,7 @@ export class LocalNode {
const coValue = new CoValueCore(header, this);
this.coValuesStore.internalMarkMagicallyAvailable(coValue.id, coValue);
void this.syncManager.syncCoValue(coValue);
void this.syncManager.requestCoValueSync(coValue);
return coValue;
}

View File

@@ -7,8 +7,8 @@ export function connectedPeers(
peer2id: PeerID,
{
trace = false,
peer1role = "peer",
peer2role = "peer",
peer1role = "client",
peer2role = "client",
crashOnClose = false,
}: {
trace?: boolean;

View File

@@ -78,7 +78,7 @@ export interface Peer {
id: PeerID;
incoming: IncomingSyncStream;
outgoing: OutgoingSyncQueue;
role: "peer" | "server" | "client" | "storage";
role: "server" | "client" | "storage";
priority?: number;
crashOnClose: boolean;
deletePeerStateOnClose?: boolean;
@@ -112,11 +112,6 @@ export function combinedKnownStates(
export class SyncManager {
peers: { [key: PeerID]: PeerState } = {};
local: LocalNode;
requestedSyncs: {
[id: RawCoID]:
| { done: Promise<void>; nRequestsThisTick: number }
| undefined;
} = {};
peersCounter = metrics.getMeter("cojson").createUpDownCounter("jazz.peers", {
description: "Amount of connected peers",
@@ -162,7 +157,7 @@ export class SyncManager {
);
}
async handleSyncMessage(msg: SyncMessage, peer: PeerState) {
handleSyncMessage(msg: SyncMessage, peer: PeerState) {
if (this.local.coValuesStore.get(msg.id).isErroredInPeer(peer.id)) {
logger.warn(
`Skipping message ${msg.action} on errored coValue ${msg.id} from peer ${peer.id}`,
@@ -183,18 +178,17 @@ export class SyncManager {
// TODO: validate
switch (msg.action) {
case "load":
return await this.handleLoad(msg, peer);
return this.handleLoad(msg, peer);
case "known":
if (msg.isCorrection) {
return await this.handleCorrection(msg, peer);
return this.handleCorrection(msg, peer);
} else {
return await this.handleKnownState(msg, peer);
return this.handleKnownState(msg, peer);
}
case "content":
// await new Promise<void>((resolve) => setTimeout(resolve, 0));
return await this.handleNewContent(msg, peer);
return this.handleNewContent(msg, peer);
case "done":
return await this.handleUnsubscribe(msg);
return this.handleUnsubscribe(msg);
default:
throw new Error(
`Unknown message type ${(msg as { action: "string" }).action}`,
@@ -202,14 +196,12 @@ export class SyncManager {
}
}
async sendNewContentIncludingDependencies(id: RawCoID, peer: PeerState) {
sendNewContentIncludingDependencies(id: RawCoID, peer: PeerState) {
const coValue = this.local.expectCoValueLoaded(id);
await Promise.all(
coValue
.getDependedOnCoValues()
.map((id) => this.sendNewContentIncludingDependencies(id, peer)),
);
coValue
.getDependedOnCoValues()
.map((id) => this.sendNewContentIncludingDependencies(id, peer));
const newContentPieces = coValue.newContentSince(
peer.optimisticKnownStates.get(id),
@@ -217,9 +209,7 @@ export class SyncManager {
if (newContentPieces) {
for (const piece of newContentPieces) {
this.trySendToPeer(peer, piece).catch((e: unknown) => {
logger.error("Error sending content piece", { err: e });
});
this.trySendToPeer(peer, piece);
}
peer.toldKnownState.add(id);
@@ -228,15 +218,13 @@ export class SyncManager {
this.trySendToPeer(peer, {
action: "known",
...coValue.knownState(),
}).catch((e: unknown) => {
logger.error("Error sending known state", { err: e });
});
peer.toldKnownState.add(id);
}
}
async startPeerReconciliation(peer: PeerState) {
startPeerReconciliation(peer: PeerState) {
const coValuesOrderedByDependency: CoValueCore[] = [];
const gathered = new Set<string>();
@@ -264,8 +252,12 @@ export class SyncManager {
// If the coValue is unavailable and we never tried this peer
// we try to load it from the peer
if (!peer.toldKnownState.has(entry.id)) {
await entry.loadFromPeers([peer]).catch((e: unknown) => {
logger.error("Error sending load", { err: e });
peer.toldKnownState.add(entry.id);
this.trySendToPeer(peer, {
action: "load",
header: false,
id: entry.id,
sessions: {},
});
}
} else {
@@ -293,33 +285,15 @@ export class SyncManager {
this.trySendToPeer(peer, {
action: "load",
...coValue.knownState(),
}).catch((e: unknown) => {
logger.error("Error sending load", { err: e });
});
}
}
nextPeer: Map<PeerID, Peer> = new Map();
async addPeer(peer: Peer) {
const prevPeer = this.peers[peer.id];
if (prevPeer) {
// Assign to nextPeer to check against race conditions
prevPeer.nextPeer = peer;
if (!prevPeer.closed) {
prevPeer.gracefulShutdown();
}
// Wait for the previous peer to finish processing the incoming messages
await prevPeer.incomingMessagesProcessingPromise?.catch((e) => {});
// If another peer was added in the meantime, we close this peer
if (prevPeer.nextPeer !== peer) {
peer.outgoing.close();
return;
}
if (prevPeer && !prevPeer.closed) {
prevPeer.gracefulShutdown();
}
const peerState = new PeerState(peer, prevPeer?.knownStates);
@@ -338,8 +312,8 @@ export class SyncManager {
}
peerState
.processIncomingMessages(async (msg) => {
await this.handleSyncMessage(msg, peerState);
.processIncomingMessages((msg) => {
this.handleSyncMessage(msg, peerState);
})
.then(() => {
if (peer.crashOnClose) {
@@ -386,7 +360,7 @@ export class SyncManager {
* - The peer known state is stored as-is instead of being merged
* - The load message always replies with a known state message
*/
async handleLoad(msg: LoadMessage, peer: PeerState) {
handleLoad(msg: LoadMessage, peer: PeerState) {
/**
* We use the msg sessions as source of truth for the known states
*
@@ -413,8 +387,6 @@ export class SyncManager {
id: msg.id,
header: false,
sessions: {},
}).catch((e) => {
logger.error("Error sending known state back", { err: e });
});
return;
@@ -442,14 +414,12 @@ export class SyncManager {
id: msg.id,
header: false,
sessions: {},
}).catch((e) => {
logger.error("Error sending known state back", { err: e });
});
return;
}
await this.sendNewContentIncludingDependencies(msg.id, peer);
this.sendNewContentIncludingDependencies(msg.id, peer);
})
.catch((e) => {
logger.error("Error loading coValue in handleLoad loading state", {
@@ -457,7 +427,7 @@ export class SyncManager {
});
});
} else if (entry.isAvailable()) {
await this.sendNewContentIncludingDependencies(msg.id, peer);
this.sendNewContentIncludingDependencies(msg.id, peer);
} else {
this.trySendToPeer(peer, {
action: "known",
@@ -468,7 +438,7 @@ export class SyncManager {
}
}
async handleKnownState(msg: KnownStateMessage, peer: PeerState) {
handleKnownState(msg: KnownStateMessage, peer: PeerState) {
const entry = this.local.coValuesStore.get(msg.id);
peer.combineWith(msg.id, knownStateIn(msg));
@@ -482,7 +452,7 @@ export class SyncManager {
}
if (entry.isAvailable()) {
await this.sendNewContentIncludingDependencies(msg.id, peer);
this.sendNewContentIncludingDependencies(msg.id, peer);
}
}
@@ -499,7 +469,7 @@ export class SyncManager {
}
}
async handleNewContent(msg: NewContentMessage, peer: PeerState) {
handleNewContent(msg: NewContentMessage, peer: PeerState) {
const entry = this.local.coValuesStore.get(msg.id);
let coValue: CoValueCore;
@@ -512,12 +482,6 @@ export class SyncManager {
id: msg.id,
header: false,
sessions: {},
}).catch((e) => {
logger.error("Error sending known state correction", {
peerId: peer.id,
peerRole: peer.role,
err: e,
});
});
return;
}
@@ -590,12 +554,6 @@ export class SyncManager {
action: "known",
isCorrection: true,
...coValue.knownState(),
}).catch((e) => {
logger.error("Error sending known state correction", {
peerId: peer.id,
peerRole: peer.role,
err: e,
});
});
peer.toldKnownState.add(msg.id);
} else {
@@ -609,12 +567,6 @@ export class SyncManager {
this.trySendToPeer(peer, {
action: "known",
...coValue.knownState(),
}).catch((e: unknown) => {
logger.error("Error sending known state", {
peerId: peer.id,
peerRole: peer.role,
err: e,
});
});
peer.toldKnownState.add(msg.id);
}
@@ -624,51 +576,54 @@ export class SyncManager {
* response to the peers that are waiting for confirmation that a coValue is
* fully synced
*/
this.syncCoValue(coValue);
this.requestCoValueSync(coValue);
}
async handleCorrection(msg: KnownStateMessage, peer: PeerState) {
handleCorrection(msg: KnownStateMessage, peer: PeerState) {
peer.setKnownState(msg.id, knownStateIn(msg));
return this.sendNewContentIncludingDependencies(msg.id, peer);
}
handleUnsubscribe(_msg: DoneMessage) {
throw new Error("Method not implemented.");
}
handleUnsubscribe(_msg: DoneMessage) {}
async syncCoValue(coValue: CoValueCore) {
if (this.requestedSyncs[coValue.id]) {
this.requestedSyncs[coValue.id]!.nRequestsThisTick++;
return this.requestedSyncs[coValue.id]!.done;
requestedSyncs = new Map<RawCoID, Promise<void>>();
async requestCoValueSync(coValue: CoValueCore) {
const promise = this.requestedSyncs.get(coValue.id);
if (promise) {
return promise;
} else {
const done = new Promise<void>((resolve) => {
queueMicrotask(async () => {
delete this.requestedSyncs[coValue.id];
await this.actuallySyncCoValue(coValue);
const promise = new Promise<void>((resolve) => {
queueMicrotask(() => {
this.requestedSyncs.delete(coValue.id);
this.syncCoValue(coValue);
resolve();
});
});
const entry = {
done,
nRequestsThisTick: 1,
};
this.requestedSyncs[coValue.id] = entry;
return done;
this.requestedSyncs.set(coValue.id, promise);
return promise;
}
}
async actuallySyncCoValue(coValue: CoValueCore) {
async syncCoValue(coValue: CoValueCore) {
const entry = this.local.coValuesStore.get(coValue.id);
for (const peer of this.peersInPriorityOrder()) {
if (peer.closed) continue;
if (this.local.coValuesStore.get(coValue.id).isErroredInPeer(peer.id))
continue;
if (entry.isErroredInPeer(peer.id)) continue;
if (peer.optimisticKnownStates.has(coValue.id)) {
await this.sendNewContentIncludingDependencies(coValue.id, peer);
} else if (peer.isServerOrStoragePeer()) {
await this.sendNewContentIncludingDependencies(coValue.id, peer);
// Only subscribed CoValues are synced to clients
if (
peer.role === "client" &&
!peer.optimisticKnownStates.has(coValue.id)
) {
continue;
}
this.sendNewContentIncludingDependencies(coValue.id, peer);
}
for (const peer of this.getPeers()) {

View File

@@ -2,11 +2,12 @@ import { describe, expect, test, vi } from "vitest";
import { PeerState } from "../PeerState.js";
import { CO_VALUE_PRIORITY } from "../priority.js";
import { CoValueKnownState, Peer, SyncMessage } from "../sync.js";
import { waitFor } from "./testUtils.js";
function setup() {
const mockPeer: Peer = {
id: "test-peer",
role: "peer",
role: "client",
priority: 1,
crashOnClose: false,
incoming: (async function* () {})(),
@@ -62,13 +63,19 @@ describe("PeerState", () => {
});
});
const message1 = peerState.pushOutgoingMessage({
peerState.pushOutgoingMessage({
action: "content",
id: "co_z1",
new: {},
priority: CO_VALUE_PRIORITY.HIGH,
});
const message2 = peerState.pushOutgoingMessage({
peerState.pushOutgoingMessage({
action: "content",
id: "co_z1",
new: {},
priority: CO_VALUE_PRIORITY.HIGH,
});
peerState.pushOutgoingMessage({
action: "content",
id: "co_z1",
new: {},
@@ -77,14 +84,21 @@ describe("PeerState", () => {
peerState.gracefulShutdown();
await Promise.allSettled([message1, message2]);
await waitFor(() => {
expect(peerState.isProcessing()).toBe(false);
});
await expect(message1).resolves.toBe(undefined);
await expect(message2).resolves.toBe(undefined);
expect(mockPeer.outgoing.push).toHaveBeenCalledTimes(1);
});
test("should schedule outgoing messages based on their priority", async () => {
const { peerState } = setup();
const { peerState, mockPeer } = setup();
mockPeer.outgoing.push = vi.fn().mockImplementation((message) => {
return new Promise<void>((resolve) => {
setTimeout(resolve, 0);
});
});
const loadMessage: SyncMessage = {
action: "load",
@@ -111,14 +125,14 @@ describe("PeerState", () => {
priority: CO_VALUE_PRIORITY.LOW,
};
const promises = [
peerState.pushOutgoingMessage(contentMessageLow),
peerState.pushOutgoingMessage(contentMessageMid),
peerState.pushOutgoingMessage(contentMessageHigh),
peerState.pushOutgoingMessage(loadMessage),
];
peerState.pushOutgoingMessage(contentMessageLow);
peerState.pushOutgoingMessage(contentMessageMid);
peerState.pushOutgoingMessage(contentMessageHigh);
peerState.pushOutgoingMessage(loadMessage);
await Promise.all(promises);
await waitFor(() => {
expect(peerState.isProcessing()).toBe(false);
});
// The first message is pushed directly, the other three are queued because are waiting
// for the first push to be completed.

View File

@@ -157,8 +157,7 @@ describe("PriorityBasedMessageQueue", () => {
sessions: {},
};
void queue.push(message);
const pulledEntry = queue.pull();
expect(pulledEntry?.msg).toEqual(message);
expect(queue.pull()).toEqual(message);
});
test("should push message with specified priority", async () => {
@@ -170,8 +169,7 @@ describe("PriorityBasedMessageQueue", () => {
priority: CO_VALUE_PRIORITY.HIGH,
};
void queue.push(message);
const pulledEntry = queue.pull();
expect(pulledEntry?.msg).toEqual(message);
expect(queue.pull()).toEqual(message);
});
test("should pull messages in priority order", async () => {
@@ -199,45 +197,13 @@ describe("PriorityBasedMessageQueue", () => {
void queue.push(mediumPriorityMsg);
void queue.push(highPriorityMsg);
expect(queue.pull()?.msg).toEqual(highPriorityMsg);
expect(queue.pull()?.msg).toEqual(mediumPriorityMsg);
expect(queue.pull()?.msg).toEqual(lowPriorityMsg);
expect(queue.pull()).toEqual(highPriorityMsg);
expect(queue.pull()).toEqual(mediumPriorityMsg);
expect(queue.pull()).toEqual(lowPriorityMsg);
});
test("should return undefined when pulling from empty queue", () => {
const { queue } = setup();
expect(queue.pull()).toBeUndefined();
});
test("should resolve promise when message is pulled", async () => {
const { queue } = setup();
const message: SyncMessage = {
action: "load",
id: "co_ztest-id",
header: false,
sessions: {},
};
const pushPromise = queue.push(message);
const pulledEntry = queue.pull();
pulledEntry?.resolve();
await expect(pushPromise).resolves.toBeUndefined();
});
test("should reject promise when message is rejected", async () => {
const { queue } = setup();
const message: SyncMessage = {
action: "load",
id: "co_ztest-id",
header: false,
sessions: {},
};
const pushPromise = queue.push(message);
const pulledEntry = queue.pull();
pulledEntry?.reject(new Error("Test error"));
await expect(pushPromise).rejects.toThrow("Test error");
});
});

View File

@@ -7,8 +7,6 @@ import { connectedPeers } from "../streamUtils.js";
import { emptyKnownState } from "../sync.js";
import {
SyncMessagesLog,
blockMessageTypeOnOutgoingPeer,
createTestNode,
loadCoValueOrFail,
setupTestNode,
waitFor,
@@ -37,7 +35,7 @@ describe("SyncStateManager", () => {
const updateSpy: GlobalSyncStateListenerCallback = vi.fn();
const unsubscribe = subscriptionManager.subscribeToUpdates(updateSpy);
await client.node.syncManager.actuallySyncCoValue(map.core);
await client.node.syncManager.syncCoValue(map.core);
expect(updateSpy).toHaveBeenCalledWith(
peerState.id,
@@ -97,7 +95,7 @@ describe("SyncStateManager", () => {
unsubscribe2();
});
await client.node.syncManager.actuallySyncCoValue(map.core);
await client.node.syncManager.syncCoValue(map.core);
expect(updateToJazzCloudSpy).toHaveBeenCalledWith(
emptyKnownState(map.core.id),
@@ -132,7 +130,7 @@ describe("SyncStateManager", () => {
const map = group.createMap();
map.set("key1", "value1", "trusting");
await client.node.syncManager.actuallySyncCoValue(map.core);
await client.node.syncManager.syncCoValue(map.core);
const subscriptionManager = client.node.syncManager.syncState;
@@ -173,7 +171,7 @@ describe("SyncStateManager", () => {
unsubscribe1();
unsubscribe2();
await client.node.syncManager.actuallySyncCoValue(map.core);
await client.node.syncManager.syncCoValue(map.core);
anyUpdateSpy.mockClear();
@@ -217,9 +215,6 @@ describe("SyncStateManager", () => {
const mapOnServer = await loadCoValueOrFail(serverNode, map.id);
// Block the content messages so the client won't fully sync immediately
const outgoing = blockMessageTypeOnOutgoingPeer(peerOnServer, "content");
mapOnServer.set("key2", "value2", "trusting");
expect(
@@ -236,9 +231,6 @@ describe("SyncStateManager", () => {
),
).toEqual({ uploaded: false });
await outgoing.sendBlockedMessages();
outgoing.unblock();
await mapOnServer.core.waitForSync();
expect(

View File

@@ -221,16 +221,3 @@ test("Items prepended to start appear with latest first", () => {
expect(content.toJSON()).toEqual(["third", "second", "first"]);
});
test("should handle large lists", () => {
const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
const group = node.createGroup();
const coValue = group.createList();
for (let i = 0; i < 8_000; i++) {
coValue.append(`item ${i}`, undefined, "trusting");
}
expect(coValue.toJSON().length).toEqual(8_000);
});

View File

@@ -74,8 +74,8 @@ describe("loading coValues from server", () => {
"server -> client | CONTENT ParentGroup header: true new: After: 0 New: 6",
"client -> server | KNOWN ParentGroup sessions: header/6",
"server -> client | CONTENT Group header: true new: After: 0 New: 5",
"server -> client | CONTENT Map header: true new: After: 0 New: 1",
"client -> server | KNOWN Group sessions: header/5",
"server -> client | CONTENT Map header: true new: After: 0 New: 1",
"client -> server | KNOWN Map sessions: header/1",
]
`);
@@ -118,6 +118,8 @@ describe("loading coValues from server", () => {
"client -> server | LOAD Map sessions: header/1",
"server -> client | CONTENT Map header: false new: After: 1 New: 1",
"client -> server | KNOWN Map sessions: header/2",
"server -> client | CONTENT Map header: false new: After: 1 New: 1",
"client -> server | KNOWN Map sessions: header/2",
]
`);
});
@@ -161,9 +163,11 @@ describe("loading coValues from server", () => {
"server -> client | KNOWN Group sessions: header/5",
"client -> server | LOAD Map sessions: header/2",
"server -> client | CONTENT Map header: false new: After: 1 New: 1",
"client -> server | KNOWN Map sessions: header/3",
"client -> server | CONTENT Map header: false new: After: 0 New: 1",
"server -> client | CONTENT Map header: false new: After: 1 New: 1",
"client -> server | KNOWN Map sessions: header/3",
"server -> client | KNOWN Map sessions: header/3",
"client -> server | KNOWN Map sessions: header/3",
]
`);
});
@@ -288,36 +292,36 @@ describe("loading coValues from server", () => {
"server -> client | CONTENT Group header: true new: After: 0 New: 5",
"client -> server | KNOWN Group sessions: header/5",
"server -> client | CONTENT Map header: true new: ",
"server -> client | CONTENT Map header: false new: After: 0 New: 73",
"client -> server | KNOWN Map sessions: header/0",
"server -> client | CONTENT Map header: false new: After: 73 New: 73",
"server -> client | CONTENT Map header: false new: After: 146 New: 73",
"server -> client | CONTENT Map header: false new: After: 0 New: 73",
"client -> server | KNOWN Map sessions: header/73",
"server -> client | CONTENT Map header: false new: After: 219 New: 73",
"server -> client | CONTENT Map header: false new: After: 292 New: 73",
"server -> client | CONTENT Map header: false new: After: 73 New: 73",
"client -> server | KNOWN Map sessions: header/146",
"server -> client | CONTENT Map header: false new: After: 365 New: 73",
"server -> client | CONTENT Map header: false new: After: 438 New: 73",
"server -> client | CONTENT Map header: false new: After: 146 New: 73",
"client -> server | KNOWN Map sessions: header/219",
"server -> client | CONTENT Map header: false new: After: 511 New: 73",
"server -> client | CONTENT Map header: false new: After: 584 New: 73",
"server -> client | CONTENT Map header: false new: After: 219 New: 73",
"client -> server | KNOWN Map sessions: header/292",
"server -> client | CONTENT Map header: false new: After: 657 New: 73",
"server -> client | CONTENT Map header: false new: After: 730 New: 73",
"server -> client | CONTENT Map header: false new: After: 292 New: 73",
"client -> server | KNOWN Map sessions: header/365",
"server -> client | CONTENT Map header: false new: After: 803 New: 73",
"server -> client | CONTENT Map header: false new: After: 876 New: 73",
"server -> client | CONTENT Map header: false new: After: 365 New: 73",
"client -> server | KNOWN Map sessions: header/438",
"server -> client | CONTENT Map header: false new: After: 949 New: 73",
"server -> client | CONTENT Map header: false new: After: 1022 New: 2",
"server -> client | CONTENT Map header: false new: After: 438 New: 73",
"client -> server | KNOWN Map sessions: header/511",
"server -> client | CONTENT Map header: false new: After: 511 New: 73",
"client -> server | KNOWN Map sessions: header/584",
"server -> client | CONTENT Map header: false new: After: 584 New: 73",
"client -> server | KNOWN Map sessions: header/657",
"server -> client | CONTENT Map header: false new: After: 657 New: 73",
"client -> server | KNOWN Map sessions: header/730",
"server -> client | CONTENT Map header: false new: After: 730 New: 73",
"client -> server | KNOWN Map sessions: header/803",
"server -> client | CONTENT Map header: false new: After: 803 New: 73",
"client -> server | KNOWN Map sessions: header/876",
"server -> client | CONTENT Map header: false new: After: 876 New: 73",
"client -> server | KNOWN Map sessions: header/949",
"server -> client | CONTENT Map header: false new: After: 949 New: 73",
"client -> server | KNOWN Map sessions: header/1022",
"server -> client | CONTENT Map header: false new: After: 1022 New: 2",
"client -> server | KNOWN Map sessions: header/1024",
]
`);

View File

@@ -66,10 +66,10 @@ describe("multiple clients syncing with the a cloud-like server mesh", () => {
[
"edge-france -> core | CONTENT Group header: true new: After: 0 New: 3",
"core -> edge-france | KNOWN Group sessions: header/3",
"core -> storage | CONTENT Group header: true new: After: 0 New: 3",
"edge-france -> core | CONTENT Map header: true new: After: 0 New: 1",
"storage -> core | KNOWN Group sessions: header/3",
"core -> storage | CONTENT Group header: true new: After: 0 New: 3",
"core -> edge-france | KNOWN Map sessions: header/1",
"storage -> core | KNOWN Group sessions: header/3",
"core -> storage | CONTENT Map header: true new: After: 0 New: 1",
"storage -> core | KNOWN Map sessions: header/1",
"client -> edge-italy | LOAD Map sessions: empty",
@@ -77,17 +77,16 @@ describe("multiple clients syncing with the a cloud-like server mesh", () => {
"core -> edge-italy | CONTENT Group header: true new: After: 0 New: 3",
"edge-italy -> core | KNOWN Group sessions: header/3",
"core -> edge-italy | CONTENT Map header: true new: After: 0 New: 1",
"edge-italy -> core | KNOWN Map sessions: header/1",
"edge-italy -> client | CONTENT Group header: true new: After: 0 New: 3",
"client -> edge-italy | KNOWN Group sessions: header/3",
"edge-italy -> core | KNOWN Map sessions: header/1",
"edge-italy -> client | CONTENT Map header: true new: After: 0 New: 1",
"client -> edge-italy | KNOWN Map sessions: header/1",
]
`);
});
// FIXME: Expected parent group to be loaded: CoValue co_zEKiodKQprnfsi2qfDtsHGCGDSo not yet loaded
test.skip("coValue created on a different edge with parent groups loading", async () => {
test("coValue created on a different edge with parent groups loading", async () => {
const client = setupTestNode();
client.connectToSyncServer({
@@ -96,7 +95,7 @@ describe("multiple clients syncing with the a cloud-like server mesh", () => {
});
const group = mesh.edgeFrance.node.createGroup();
const parentGroup = mesh.edgeItaly.node.createGroup();
const parentGroup = mesh.edgeFrance.node.createGroup();
parentGroup.addMember("everyone", "reader");
group.extend(parentGroup);
@@ -115,7 +114,36 @@ describe("multiple clients syncing with the a cloud-like server mesh", () => {
Group: group.core,
Map: map.core,
}),
).toMatchInlineSnapshot();
).toMatchInlineSnapshot(`
[
"edge-france -> core | CONTENT ParentGroup header: true new: After: 0 New: 6",
"core -> edge-france | KNOWN ParentGroup sessions: header/6",
"edge-france -> core | CONTENT Group header: true new: After: 0 New: 5",
"core -> storage | CONTENT ParentGroup header: true new: After: 0 New: 6",
"core -> edge-france | KNOWN Group sessions: header/5",
"edge-france -> core | CONTENT Map header: true new: After: 0 New: 1",
"storage -> core | KNOWN ParentGroup sessions: header/6",
"core -> storage | CONTENT Group header: true new: After: 0 New: 5",
"core -> edge-france | KNOWN Map sessions: header/1",
"storage -> core | KNOWN Group sessions: header/5",
"core -> storage | CONTENT Map header: true new: After: 0 New: 1",
"storage -> core | KNOWN Map sessions: header/1",
"client -> edge-italy | LOAD Map sessions: empty",
"edge-italy -> core | LOAD Map sessions: empty",
"core -> edge-italy | CONTENT ParentGroup header: true new: After: 0 New: 6",
"edge-italy -> core | KNOWN ParentGroup sessions: header/6",
"core -> edge-italy | CONTENT Group header: true new: After: 0 New: 5",
"edge-italy -> core | KNOWN Group sessions: header/5",
"core -> edge-italy | CONTENT Map header: true new: After: 0 New: 1",
"edge-italy -> core | KNOWN Map sessions: header/1",
"edge-italy -> client | CONTENT ParentGroup header: true new: After: 0 New: 6",
"client -> edge-italy | KNOWN ParentGroup sessions: header/6",
"edge-italy -> client | CONTENT Group header: true new: After: 0 New: 5",
"client -> edge-italy | KNOWN Group sessions: header/5",
"edge-italy -> client | CONTENT Map header: true new: After: 0 New: 1",
"client -> edge-italy | KNOWN Map sessions: header/1",
]
`);
});
test("updating a coValue coming from a different edge", async () => {
@@ -155,8 +183,8 @@ describe("multiple clients syncing with the a cloud-like server mesh", () => {
"edge-italy -> core | CONTENT Map header: false new: After: 0 New: 1",
"core -> edge-italy | KNOWN Map sessions: header/2",
"core -> storage | CONTENT Map header: false new: After: 0 New: 1",
"storage -> core | KNOWN Map sessions: header/2",
"core -> edge-france | CONTENT Map header: false new: After: 0 New: 1",
"storage -> core | KNOWN Map sessions: header/2",
"edge-france -> core | KNOWN Map sessions: header/2",
]
`);
@@ -210,29 +238,29 @@ describe("multiple clients syncing with the a cloud-like server mesh", () => {
"edge-italy -> core | CONTENT Group header: true new: After: 0 New: 5",
"edge-italy -> client | CONTENT Group header: true new: After: 0 New: 5",
"core -> edge-italy | KNOWN Group sessions: header/5",
"client -> edge-italy | KNOWN Group sessions: header/5",
"core -> storage | CONTENT Group header: true new: After: 0 New: 5",
"edge-italy -> core | CONTENT Map header: true new: After: 0 New: 1",
"client -> edge-italy | KNOWN Group sessions: header/5",
"edge-italy -> client | CONTENT Map header: true new: After: 0 New: 1",
"storage -> core | KNOWN Group sessions: header/5",
"core -> storage | CONTENT Group header: true new: After: 0 New: 5",
"core -> edge-italy | KNOWN Map sessions: header/1",
"core -> storage | CONTENT Map header: true new: After: 0 New: 1",
"client -> edge-italy | KNOWN Map sessions: header/1",
"storage -> core | KNOWN Group sessions: header/5",
"core -> storage | CONTENT Map header: true new: After: 0 New: 1",
"storage -> core | KNOWN Map sessions: header/1",
"client -> edge-italy | CONTENT Map header: false new: After: 0 New: 1",
"core -> storage | CONTENT Map header: false new: After: 0 New: 1",
"core -> edge-italy | CONTENT Map header: false new: After: 0 New: 1",
"edge-italy -> client | KNOWN CORRECTION Map sessions: empty",
"storage -> core | KNOWN Map sessions: header/2",
"core -> edge-italy | CONTENT Map header: false new: After: 0 New: 1",
"client -> edge-italy | CONTENT Map header: true new: After: 0 New: 1 | After: 0 New: 1",
"edge-italy -> core | KNOWN CORRECTION Map sessions: empty",
"edge-italy -> client | KNOWN Map sessions: header/2",
"client -> edge-italy | CONTENT Map header: true new: After: 0 New: 1 | After: 0 New: 1",
"core -> edge-italy | CONTENT Map header: true new: After: 0 New: 1 | After: 0 New: 1",
"edge-italy -> core | CONTENT Map header: false new: After: 0 New: 1",
"edge-italy -> client | CONTENT Map header: false new: After: 0 New: 1",
"core -> edge-italy | KNOWN Map sessions: header/3",
"edge-italy -> client | KNOWN Map sessions: header/2",
"edge-italy -> core | KNOWN Map sessions: header/3",
"edge-italy -> client | CONTENT Map header: false new: After: 0 New: 1",
"edge-italy -> core | CONTENT Map header: false new: After: 0 New: 1",
"client -> edge-italy | KNOWN Map sessions: header/3",
"core -> edge-italy | KNOWN Map sessions: header/3",
"core -> storage | CONTENT Map header: false new: After: 0 New: 1",
"storage -> core | KNOWN Map sessions: header/3",
]

View File

@@ -1,18 +1,7 @@
import { assert, beforeEach, describe, expect, test } from "vitest";
import { expectMap } from "../coValue";
import { WasmCrypto } from "../crypto/WasmCrypto";
import { CoValueCore, RawCoMap } from "../exports";
import { LocalNode } from "../localNode";
import { toSimplifiedMessages } from "./messagesTestUtils";
import {
SyncMessagesLog,
createTestNode,
randomAnonymousAccountAndSessionID,
setupTestNode,
waitFor,
} from "./testUtils";
const Crypto = await WasmCrypto.create();
import { SyncMessagesLog, setupTestNode, waitFor } from "./testUtils";
let jazzCloud = setupTestNode({ isSyncServer: true });
@@ -32,8 +21,6 @@ describe("peer reconciliation", () => {
client.connectToSyncServer();
await new Promise((resolve) => setTimeout(resolve, 100));
await map.core.waitForSync();
expect(
@@ -44,12 +31,12 @@ describe("peer reconciliation", () => {
).toMatchInlineSnapshot(`
[
"client -> server | LOAD Group sessions: header/3",
"server -> client | KNOWN Group sessions: empty",
"client -> server | LOAD Map sessions: header/1",
"server -> client | KNOWN Map sessions: empty",
"server -> client | KNOWN Group sessions: empty",
"client -> server | CONTENT Group header: true new: After: 0 New: 3",
"server -> client | KNOWN Group sessions: header/3",
"server -> client | KNOWN Map sessions: empty",
"client -> server | CONTENT Map header: true new: After: 0 New: 1",
"server -> client | KNOWN Group sessions: header/3",
"server -> client | KNOWN Map sessions: header/1",
]
`);
@@ -93,10 +80,10 @@ describe("peer reconciliation", () => {
).toMatchInlineSnapshot(`
[
"client -> server | LOAD Group sessions: header/3",
"server -> client | KNOWN Group sessions: header/3",
"client -> server | LOAD Map sessions: header/2",
"server -> client | KNOWN Map sessions: header/1",
"server -> client | KNOWN Group sessions: header/3",
"client -> server | CONTENT Map header: false new: After: 1 New: 1",
"server -> client | KNOWN Map sessions: header/1",
"server -> client | KNOWN Map sessions: header/2",
]
`);
@@ -148,10 +135,11 @@ describe("peer reconciliation", () => {
).toMatchInlineSnapshot(`
[
"client -> server | LOAD Group sessions: header/3",
"server -> client | KNOWN Group sessions: header/3",
"client -> server | LOAD Group sessions: header/3",
"client -> server | LOAD Map sessions: header/2",
"server -> client | KNOWN Map sessions: header/1",
"server -> client | KNOWN Group sessions: header/3",
"client -> server | CONTENT Map header: false new: After: 1 New: 1",
"server -> client | KNOWN Map sessions: header/1",
"server -> client | KNOWN Map sessions: header/2",
]
`);
@@ -191,16 +179,16 @@ describe("peer reconciliation", () => {
).toMatchInlineSnapshot(`
[
"client -> server | LOAD Group sessions: header/3",
"server -> client | KNOWN Group sessions: empty",
"client -> server | LOAD Map sessions: header/2",
"server -> client | KNOWN Map sessions: empty",
"server -> client | KNOWN Group sessions: empty",
"client -> server | CONTENT Group header: true new: After: 0 New: 3",
"server -> client | KNOWN Group sessions: header/3",
"server -> client | KNOWN Map sessions: empty",
"client -> server | CONTENT Map header: true new: After: 0 New: 2",
"server -> client | KNOWN Group sessions: header/3",
"server -> client | KNOWN Map sessions: header/2",
"client -> server | LOAD Group sessions: header/3",
"server -> client | KNOWN Group sessions: header/3",
"client -> server | LOAD Map sessions: header/2",
"server -> client | KNOWN Group sessions: header/3",
"server -> client | KNOWN Map sessions: header/2",
]
`);

View File

@@ -39,11 +39,11 @@ describe("client with storage syncs with server", () => {
"client -> server | LOAD Map sessions: empty",
"server -> client | CONTENT Group header: true new: After: 0 New: 3",
"client -> server | KNOWN Group sessions: header/3",
"client -> storage | CONTENT Group header: true new: After: 0 New: 3",
"server -> client | CONTENT Map header: true new: After: 0 New: 1",
"client -> storage | CONTENT Group header: true new: After: 0 New: 3",
"client -> server | KNOWN Map sessions: header/1",
"storage -> client | KNOWN Group sessions: header/3",
"client -> storage | CONTENT Map header: true new: After: 0 New: 1",
"client -> server | KNOWN Map sessions: header/1",
"storage -> client | KNOWN Map sessions: header/1",
]
`);
@@ -76,10 +76,12 @@ describe("client with storage syncs with server", () => {
"client -> storage | KNOWN Group sessions: header/3",
"storage -> client | CONTENT Map header: true new: After: 0 New: 1",
"client -> server | CONTENT Group header: true new: After: 0 New: 3",
"client -> storage | KNOWN Map sessions: header/1",
"server -> client | KNOWN Group sessions: header/3",
"client -> server | LOAD Map sessions: header/1",
"client -> storage | KNOWN Map sessions: header/1",
"server -> client | CONTENT Group header: true new: After: 0 New: 3",
"client -> server | CONTENT Map header: true new: After: 0 New: 1",
"server -> client | KNOWN Map sessions: header/1",
"client -> server | KNOWN Group sessions: header/3",
"server -> client | KNOWN Map sessions: header/1",
]
@@ -117,16 +119,16 @@ describe("client with storage syncs with server", () => {
"client -> server | LOAD Map sessions: empty",
"server -> client | CONTENT ParentGroup header: true new: After: 0 New: 6",
"client -> server | KNOWN ParentGroup sessions: header/6",
"client -> storage | CONTENT ParentGroup header: true new: After: 0 New: 6",
"server -> client | CONTENT Group header: true new: After: 0 New: 5",
"storage -> client | KNOWN ParentGroup sessions: header/6",
"server -> client | CONTENT Map header: true new: After: 0 New: 1",
"client -> storage | CONTENT Group header: true new: After: 0 New: 5",
"storage -> client | KNOWN Group sessions: header/5",
"client -> storage | CONTENT ParentGroup header: true new: After: 0 New: 6",
"client -> server | KNOWN Group sessions: header/5",
"server -> client | CONTENT Map header: true new: After: 0 New: 1",
"storage -> client | KNOWN ParentGroup sessions: header/6",
"client -> storage | CONTENT Group header: true new: After: 0 New: 5",
"client -> server | KNOWN Map sessions: header/1",
"storage -> client | KNOWN Group sessions: header/5",
"client -> storage | CONTENT Map header: true new: After: 0 New: 1",
"storage -> client | KNOWN Map sessions: header/1",
"client -> server | KNOWN Map sessions: header/1",
]
`);
});
@@ -167,7 +169,9 @@ describe("client with storage syncs with server", () => {
"client -> server | LOAD Map sessions: header/1",
"server -> client | CONTENT Map header: false new: After: 1 New: 1",
"client -> server | KNOWN Map sessions: header/2",
"server -> client | CONTENT Map header: false new: After: 1 New: 1",
"client -> storage | CONTENT Map header: false new: After: 1 New: 1",
"client -> server | KNOWN Map sessions: header/2",
"storage -> client | KNOWN Map sessions: header/2",
]
`);
@@ -212,10 +216,10 @@ describe("client syncs with a server with storage", () => {
[
"client -> server | CONTENT Group header: true new: After: 0 New: 3",
"server -> client | KNOWN Group sessions: header/3",
"server -> storage | CONTENT Group header: true new: After: 0 New: 3",
"client -> server | CONTENT Map header: true new: After: 0 New: 1",
"storage -> server | KNOWN Group sessions: header/3",
"server -> storage | CONTENT Group header: true new: After: 0 New: 3",
"server -> client | KNOWN Map sessions: header/1",
"storage -> server | KNOWN Group sessions: header/3",
"server -> storage | CONTENT Map header: true new: After: 0 New: 1",
"storage -> server | KNOWN Map sessions: header/1",
]

View File

@@ -31,7 +31,7 @@ beforeEach(async () => {
});
});
test("If we add a peer, but it never subscribes to a coValue, it won't get any messages", async () => {
test("If we add a client peer, but it never subscribes to a coValue, it won't get any messages", async () => {
const [admin, session] = randomAnonymousAccountAndSessionID();
const node = new LocalNode(admin, session, Crypto);
@@ -47,7 +47,7 @@ test("If we add a peer, but it never subscribes to a coValue, it won't get any m
id: "test",
incoming: inRx,
outgoing: outTx,
role: "peer",
role: "client",
crashOnClose: true,
});
@@ -561,7 +561,7 @@ describe("SyncManager - knownStates vs optimisticKnownStates", () => {
const mapOnClient = group.createMap();
mapOnClient.set("key1", "value1", "trusting");
await client.syncManager.actuallySyncCoValue(mapOnClient.core);
await client.syncManager.syncCoValue(mapOnClient.core);
// Wait for the full sync to complete
await mapOnClient.core.waitForSync();
@@ -592,7 +592,7 @@ describe("SyncManager - knownStates vs optimisticKnownStates", () => {
const map = group.createMap();
map.set("key1", "value1", "trusting");
await client.node.syncManager.actuallySyncCoValue(map.core);
await client.node.syncManager.syncCoValue(map.core);
await map.core.waitForSync();
// Block the content messages
@@ -604,7 +604,7 @@ describe("SyncManager - knownStates vs optimisticKnownStates", () => {
map.set("key2", "value2", "trusting");
await client.node.syncManager.actuallySyncCoValue(map.core);
await client.node.syncManager.syncCoValue(map.core);
expect(peerState.optimisticKnownStates.get(map.core.id)).not.toEqual(
peerState.knownStates.get(map.core.id),
@@ -636,7 +636,7 @@ describe("SyncManager.addPeer", () => {
const map = group.createMap();
map.set("key1", "value1", "trusting");
await client.node.syncManager.actuallySyncCoValue(map.core);
await client.node.syncManager.syncCoValue(map.core);
// Wait for initial sync
await map.core.waitForSync();
@@ -669,7 +669,7 @@ describe("SyncManager.addPeer", () => {
const map = group.createMap();
map.set("key1", "value1", "trusting");
await client.node.syncManager.actuallySyncCoValue(map.core);
await client.node.syncManager.syncCoValue(map.core);
// Wait for initial sync
await map.core.waitForSync();
@@ -841,7 +841,7 @@ describe("waitForSyncWithPeer", () => {
const map = group.createMap();
map.set("key1", "value1", "trusting");
await client.node.syncManager.actuallySyncCoValue(map.core);
await client.node.syncManager.syncCoValue(map.core);
await expect(
client.node.syncManager.waitForSyncWithPeer(
@@ -866,7 +866,7 @@ describe("waitForSyncWithPeer", () => {
return Promise.resolve();
});
await client.node.syncManager.actuallySyncCoValue(map.core);
await client.node.syncManager.syncCoValue(map.core);
await expect(
client.node.syncManager.waitForSyncWithPeer(
@@ -914,7 +914,7 @@ describe("metrics", () => {
const node = new LocalNode(admin, session, Crypto);
let connectedPeers = await metricReader.getMetricValue("jazz.peers", {
role: "peer",
role: "client",
});
expect(connectedPeers).toBeUndefined();
let connectedServerPeers = await metricReader.getMetricValue("jazz.peers", {
@@ -928,12 +928,12 @@ describe("metrics", () => {
id: "peer-1",
incoming: inPeer1,
outgoing: outPeer1,
role: "peer",
role: "client",
crashOnClose: false,
});
connectedPeers = await metricReader.getMetricValue("jazz.peers", {
role: "peer",
role: "client",
});
expect(connectedPeers).toBe(1);
@@ -943,12 +943,12 @@ describe("metrics", () => {
id: "peer-2",
incoming: inPeer2,
outgoing: outPeer2,
role: "peer",
role: "client",
crashOnClose: false,
});
connectedPeers = await metricReader.getMetricValue("jazz.peers", {
role: "peer",
role: "client",
});
expect(connectedPeers).toBe(2);
@@ -966,7 +966,7 @@ describe("metrics", () => {
});
expect(connectedServerPeers).toBe(1);
connectedPeers = await metricReader.getMetricValue("jazz.peers", {
role: "peer",
role: "client",
});
expect(connectedPeers).toBe(2);
@@ -975,7 +975,7 @@ describe("metrics", () => {
await waitFor(() => node.syncManager.peers["peer-1"]?.closed);
connectedPeers = await metricReader.getMetricValue("jazz.peers", {
role: "peer",
role: "client",
});
expect(connectedPeers).toBe(1);
@@ -991,20 +991,6 @@ describe("metrics", () => {
});
});
function groupContentEx(group: RawGroup) {
return {
action: "content",
id: group.core.id,
};
}
function groupStateEx(group: RawGroup) {
return {
action: "known",
id: group.core.id,
};
}
describe("LocalNode.load", () => {
test("should throw error when trying to load with undefined ID", async () => {
const client = await setupTestAccount();

View File

@@ -169,32 +169,32 @@ describe("client to server upload", () => {
"client -> server | CONTENT Map header: false new: After: 0 New: 73",
"server -> client | KNOWN Map sessions: header/73",
"client -> server | CONTENT Map header: false new: After: 73 New: 73",
"client -> server | CONTENT Map header: false new: After: 146 New: 73",
"server -> client | KNOWN Map sessions: header/146",
"client -> server | CONTENT Map header: false new: After: 219 New: 73",
"client -> server | CONTENT Map header: false new: After: 292 New: 73",
"client -> server | CONTENT Map header: false new: After: 146 New: 73",
"server -> client | KNOWN Map sessions: header/219",
"client -> server | CONTENT Map header: false new: After: 365 New: 73",
"client -> server | CONTENT Map header: false new: After: 438 New: 73",
"client -> server | CONTENT Map header: false new: After: 219 New: 73",
"server -> client | KNOWN Map sessions: header/292",
"client -> server | CONTENT Map header: false new: After: 511 New: 73",
"client -> server | CONTENT Map header: false new: After: 584 New: 73",
"client -> server | CONTENT Map header: false new: After: 292 New: 73",
"server -> client | KNOWN Map sessions: header/365",
"client -> server | CONTENT Map header: false new: After: 657 New: 73",
"client -> server | CONTENT Map header: false new: After: 730 New: 73",
"client -> server | CONTENT Map header: false new: After: 365 New: 73",
"server -> client | KNOWN Map sessions: header/438",
"client -> server | CONTENT Map header: false new: After: 803 New: 73",
"client -> server | CONTENT Map header: false new: After: 876 New: 73",
"client -> server | CONTENT Map header: false new: After: 438 New: 73",
"server -> client | KNOWN Map sessions: header/511",
"client -> server | CONTENT Map header: false new: After: 949 New: 73",
"client -> server | CONTENT Map header: false new: After: 1022 New: 2",
"client -> server | CONTENT Map header: false new: After: 511 New: 73",
"server -> client | KNOWN Map sessions: header/584",
"client -> server | CONTENT Map header: false new: After: 584 New: 73",
"server -> client | KNOWN Map sessions: header/657",
"client -> server | CONTENT Map header: false new: After: 657 New: 73",
"server -> client | KNOWN Map sessions: header/730",
"client -> server | CONTENT Map header: false new: After: 730 New: 73",
"server -> client | KNOWN Map sessions: header/803",
"client -> server | CONTENT Map header: false new: After: 803 New: 73",
"server -> client | KNOWN Map sessions: header/876",
"client -> server | CONTENT Map header: false new: After: 876 New: 73",
"server -> client | KNOWN Map sessions: header/949",
"client -> server | CONTENT Map header: false new: After: 949 New: 73",
"server -> client | KNOWN Map sessions: header/1022",
"client -> server | CONTENT Map header: false new: After: 1022 New: 2",
"server -> client | KNOWN Map sessions: header/1024",
]
`);

View File

@@ -295,6 +295,7 @@ export function blockMessageTypeOnOutgoingPeer(
});
return {
blockedMessages,
sendBlockedMessages: async () => {
for (const msg of blockedMessages) {
await push.call(peer.outgoing, msg);

View File

@@ -1,5 +1,32 @@
# jazz-auth-clerk
## 0.13.15
### Patch Changes
- Updated dependencies [c712ef2]
- cojson@0.13.15
- jazz-browser@0.13.15
- jazz-tools@0.13.15
## 0.13.14
### Patch Changes
- Updated dependencies [5c2c7d4]
- cojson@0.13.14
- jazz-browser@0.13.14
- jazz-tools@0.13.14
## 0.13.13
### Patch Changes
- Updated dependencies [ec9cb40]
- cojson@0.13.13
- jazz-browser@0.13.13
- jazz-tools@0.13.13
## 0.13.12
### Patch Changes

View File

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

View File

@@ -1,5 +1,26 @@
# jazz-browser-media-images
## 0.13.15
### Patch Changes
- jazz-browser@0.13.15
- jazz-tools@0.13.15
## 0.13.14
### Patch Changes
- jazz-browser@0.13.14
- jazz-tools@0.13.14
## 0.13.13
### Patch Changes
- jazz-browser@0.13.13
- jazz-tools@0.13.13
## 0.13.12
### Patch Changes

View File

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

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