Compare commits

..

36 Commits

Author SHA1 Message Date
Guido D'Orsi
4c2e60ab51 Merge pull request #2776 from garden-co/changeset-release/main
Version Packages
2025-08-20 13:25:30 +02:00
github-actions[bot]
1830171930 Version Packages 2025-08-20 10:59:08 +00:00
Guido D'Orsi
6574090402 Merge pull request #2777 from garden-co/fix/load-as-upsertUnique
Explicit loadAs in upsertUnique to use it without loaded context
2025-08-20 12:55:18 +02:00
Guido D'Orsi
a6f65472a7 Merge pull request #2779 from ccssmnn/add-fallback-components
Add Fallback properties to JazzProvider and ClerkProvider
2025-08-20 12:54:16 +02:00
Guido D'Orsi
109952aa6a Merge pull request #2781 from garden-co/gio/setup-codeowners
chore: setup codeowners
2025-08-20 12:50:38 +02:00
Giordano Ricci
556bdf977b more tweaking 2025-08-20 11:41:31 +01:00
Giordano Ricci
0213b4e5b9 add ui 2025-08-20 11:31:54 +01:00
Giordano Ricci
01c195756c add docs 2025-08-20 11:27:17 +01:00
Giordano Ricci
9c69917d24 add @garden-co/framework as owner of all packages 2025-08-20 11:03:18 +01:00
Giordano Ricci
b4d68f0a32 wip: setup codeowners 2025-08-20 10:59:36 +01:00
Guido D'Orsi
fbc3839777 chore: simplify the fallback on the react provider 2025-08-20 11:55:45 +02:00
Guido D'Orsi
296464b282 Merge pull request #2753 from garden-co/GCO-642-clarify-docs-adding-group-members-by-id
Closes #GCO-642 - Clarify docs for adding group members by id
2025-08-20 11:50:03 +02:00
Carl Assmann
c463654970 no need to add fallback to clerk, bc it extends and forwards jazzproviderprops 2025-08-19 23:23:19 +02:00
Carl Assmann
debc052bdc add fallback to clerk example 2025-08-19 23:12:12 +02:00
Carl Assmann
3c7846153d add fallback properties to JazzProvider and ClerkProvider 2025-08-19 23:00:06 +02:00
Meg Culotta
e410fedda7 Merge pull request #2736 from garden-co/#GCO-691-Adds-node-20-troubleshooting-docs
Closes GCO-691, GCO-731 - Add setup troubleshooting page
2025-08-19 14:40:47 -05:00
Matteo Manchi
52ea0c7a9b fix(jazz-tools/tools): explicit loadAs in upsertUnique to use it without loaded context 2025-08-19 18:21:31 +02:00
Guido D'Orsi
b91c93caed Merge pull request #2769 from garden-co/justin-gco-741-skip-session-transaction-verification-in-core-node
Ref GCO-741: Add option for disabling transaction verification to SyncManager
2025-08-19 18:03:02 +02:00
Justin Rosenthal
7586c3bac5 Add changeset 2025-08-19 08:52:44 -07:00
Justin Rosenthal
9b96cd4a65 Craft an invalid transaction in unit test 2025-08-19 08:50:43 -07:00
Justin Rosenthal
2e66ea8e56 Revert signatures to positional args 2025-08-19 08:32:38 -07:00
Guido D'Orsi
336cc1f0fe Merge pull request #2773 from garden-co/changeset-release/main
Version Packages
2025-08-19 16:27:36 +02:00
github-actions[bot]
cc2ca5c23c Version Packages 2025-08-19 14:26:38 +00:00
Guido D'Orsi
3664385113 Merge pull request #2775 from garden-co/fix/comap-coprofile-validate-input
fix: prevent passing CoValue schemas to `co.map` and `co.profile`
2025-08-19 16:22:45 +02:00
Guido D'Orsi
2b2ecdaf3d Merge pull request #2771 from garden-co/feat/rich-text-prosemirror-fix
fix(prosemirror): fix RangeError triggered when creating invalid HTML
2025-08-19 16:21:39 +02:00
Guido D'Orsi
6dbb05320a fix(prosemirror): fix RangeError triggered when creating invalid HTML 2025-08-19 16:15:12 +02:00
Margaret Culotta
0160a188fa Remove strict casting from example 2025-08-19 09:06:58 -05:00
NicoR
ac3e694f4e fix: prevent passing CoValue schemas to co.map and co.profile 2025-08-19 11:05:54 -03:00
Margaret Culotta
a7dca75955 Clean up unnecessary content per PR feedback 2025-08-19 08:29:24 -05:00
Guido D'Orsi
143156cd6a Merge pull request #2772 from garden-co/gio/add-missing-export
fix: add missing export
2025-08-19 14:31:57 +02:00
Giordano Ricci
1a182f07de add changeset 2025-08-19 13:30:30 +01:00
Giordano Ricci
7e7e7ebb51 fix: add missing export 2025-08-19 13:28:45 +01:00
Justin Rosenthal
76f142b70d Add ability to disable verification in SyncManager 2025-08-18 14:52:58 -07:00
Justin Rosenthal
0167153da2 Use named parameters in tryAddTransactions() signatures 2025-08-18 13:19:26 -07:00
Margaret Culotta
d8ae47c4d1 Clarify docs for adding group members by id 2025-08-15 15:01:38 -05:00
Margaret Culotta
92c0048984 add setup troubleshooting page 2025-08-14 10:32:22 -05:00
55 changed files with 650 additions and 90 deletions

8
.github/CODEOWNERS vendored Normal file
View File

@@ -0,0 +1,8 @@
./packages @garden-co/framework
./tests @garden-co/framework
./packages/quint-ui @garden-co/ui
./homepage @garden-co/ui
./homepage/homepage/content/docs @garden-co/docs
./starters @garden-co/docs
./examples @garden-co/docs @garden-co/ui

View File

@@ -1,5 +1,21 @@
# passkey-svelte
## 0.0.122
### Patch Changes
- Updated dependencies [52ea0c7]
- jazz-tools@0.17.9
## 0.0.121
### Patch Changes
- Updated dependencies [ac3e694]
- Updated dependencies [6dbb053]
- Updated dependencies [1a182f0]
- jazz-tools@0.17.8
## 0.0.120
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "chat-svelte",
"version": "0.0.120",
"version": "0.0.122",
"type": "module",
"private": true,
"scripts": {

View File

@@ -9,8 +9,10 @@
</head>
<body>
<div id="root"></div>
<div id="root">
<p>Loading...</p>
</div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
</html>

View File

@@ -24,6 +24,7 @@ function JazzProvider({ children }: { children: ReactNode }) {
sync={{
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
}}
fallback={<p>Loading...</p>}
>
{children}
</JazzReactProviderWithClerk>

View File

@@ -25,6 +25,11 @@ export const docNavigationItems = [
excludeFromNavigation: true,
},
{ name: "FAQs", href: "/docs/faq", done: 100 },
{
name: "Troubleshooting",
href: "/docs/troubleshooting",
done: 100,
},
],
},
{

View File

@@ -49,18 +49,20 @@ if (bob) {
```
</CodeGroup>
**Note:** if the account ID is of type `string`, because it comes from a URL parameter or something similar, you need to cast it to `ID<Account>` first:
**Note:**
- Both `Account.load(id)` and `co.account().load(id)` do the same thing — they load an account from its ID.
<CodeGroup>
```tsx twoslash
const bobsID = "co_z123";
import { Group } from "jazz-tools";
const group = Group.create();
// ---cut---
import { co, Group } from "jazz-tools";
import { ID, Account, co } from "jazz-tools";
const bob = await co.account().load(bobsID);
const bob = await Account.load(bobsID);
// Or: const bob = await co.account().load(bobsID);
if (bob) {
group.addMember(bob, "writer");

View File

@@ -25,7 +25,10 @@ You can use [`create-jazz-app`](/docs/tools/create-jazz-app) to create a new Jaz
```
</CodeGroup>
<Alert variant="info" className="mt-4 flex gap-2 items-center">Requires at least Node.js v20.</Alert>
<Alert variant="info" className="mt-4 flex gap-2 items-center">
Requires at least Node.js v20.
See our [Troubleshooting Guide](https://jazz.tools/docs/troubleshooting) for quick fixes.
</Alert>
{/* <ContentByFramework framework="react">
Or you can follow this [React step-by-step guide](/docs/react/guide) where we walk you through building an issue tracker app.

View File

@@ -53,6 +53,7 @@ npm i -S jazz-tools
<Alert variant="info" className="mt-4" title="Note">
- Requires at least Node.js v20.
- Hermes has added support for `atob` and `btoa` in React Native 0.74. If you are using earlier versions, you may also need to polyfill `atob` and `btoa` in your `package.json`. Packages to try include `text-encoding` and `base-64`, and you can drop `@bacons/text-decoder`.
See our [Troubleshooting Guide](https://jazz.tools/docs/troubleshooting) for quick fixes.
</Alert>
#### Fix incompatible dependencies

View File

@@ -51,7 +51,6 @@ npm i -S jazz-tools
</CodeGroup>
<Alert variant="info" className="mt-4" title="Note">
- Requires at least Node.js v20.
- Hermes has added support for `atob` and `btoa` in React Native 0.74. If you are using earlier versions, you may also need to polyfill `atob` and `btoa` in your `package.json`. Packages to try include `text-encoding` and `base-64`, and you can drop `@bacons/text-decoder`.
</Alert>

View File

@@ -27,8 +27,6 @@ pnpm install jazz-tools
```
</CodeGroup>
<Alert variant="info" className="mt-4 flex gap-2 items-center">Requires at least Node.js v20.</Alert>
## Write your schema
Define your data schema using [CoValues](/docs/schemas/covalues) from `jazz-tools`.

View File

@@ -19,8 +19,6 @@ pnpm install jazz-tools
```
</CodeGroup>
<Alert variant="info" className="mt-4 flex gap-2 items-center">Requires at least Node.js v20.</Alert>
## Write your schema
See the [schema docs](/docs/schemas/covalues) for more information.

View File

@@ -1,4 +1,5 @@
import { ContentByFramework, CodeGroup } from '@/components/forMdx'
import { Alert } from "@garden-co/design-system/src/components/atoms/Alert";
export const metadata = {
description: "Learn how to sync and persist your data using Jazz Cloud, or run your own sync server."
@@ -44,6 +45,11 @@ And then use `ws://localhost:4200` as the sync server URL.
You can also run this simple sync server behind a proxy that supports WebSockets, for example to provide TLS.
In this case, provide the WebSocket endpoint your proxy exposes as the sync server URL.
<Alert variant="info" className="mt-4 flex gap-2 items-center">
Requires at least Node.js v20.
See our [Troubleshooting Guide](https://jazz.tools/docs/troubleshooting) for quick fixes.
</Alert>
### Command line options:
- `--host` / `-h` - the host to run the sync server on. Defaults to 127.0.0.1.

View File

@@ -0,0 +1,133 @@
import { ContentByFramework, CodeGroup } from '@/components/forMdx'
import { Alert } from "@garden-co/design-system/src/components/atoms/Alert";
export const metadata = {
title: "Setup troubleshooting",
description: "A few reported setup hiccups and how to fix them."
};
# Setup troubleshooting
A few reported setup hiccups and how to fix them.
---
## Node.js version requirements
Jazz requires **Node.js v20 or later** due to native module dependencies.
Check your version:
<CodeGroup>
```sh
node -v
```
</CodeGroup>
If youre on Node 18 or earlier, upgrade via nvm:
<CodeGroup>
```sh
nvm install 20
nvm use 20
```
</CodeGroup>
---
## npx jazz-run: command not found
If, when running:
<CodeGroup>
```sh
npx jazz-run sync
```
</CodeGroup>
you encounter:
<CodeGroup>
```sh
sh: jazz-run: command not found
```
</CodeGroup>
This is often due to an npx cache quirk. (For most apps using Jazz)
1. Clear your npx cache:
<CodeGroup>
```sh
npx clear-npx-cache
```
</CodeGroup>
2. Rerun the command:
<CodeGroup>
```sh
npx jazz-run sync
```
</CodeGroup>
---
### Node 18 workaround (rebuilding the native module)
If you cant upgrade to Node 20+, you can rebuild the native `better-sqlite3` module for your architecture.
1. Install `jazz-run` locally in your project:
<CodeGroup>
```sh
pnpm add -D jazz-run
```
</CodeGroup>
2. Find the installed version of better-sqlite3 inside node_modules.
It should look like this:
<CodeGroup>
```sh
./node_modules/.pnpm/better-sqlite3{version}/node_modules/better-sqlite3
```
</CodeGroup>
Replace `{version}` with your installed version and run:
<CodeGroup>
```sh
# Navigate to the installed module and rebuild
pushd ./node_modules/.pnpm/better-sqlite3{version}/node_modules/better-sqlite3
&& pnpm install
&& popd
```
</CodeGroup>
If you get ModuleNotFoundError: No module named 'distutils':
Linux:
<CodeGroup>
```sh
pip install --upgrade setuptools
```
</CodeGroup>
macOS:
<CodeGroup>
```sh
brew install python-setuptools
```
</CodeGroup>
<p><i>Workaround originally shared by @aheissenberger on Jun 24, 2025.</i></p>
---
### Still having trouble?
If none of the above fixes work:
Make sure dependencies installed without errors (`pnpm install`).
Double-check your `node -v` output matches the required version.
Open an issue on GitHub with:
- Your OS and version
- Node.js version
- Steps you ran and full error output
We're always happy to help! If you're stuck, reachout via [Discord](https://discord.gg/utDMjHYg42)

View File

@@ -26,3 +26,4 @@ export const navigationItems: NavItemProps[] = [
title: "Status",
},
];

View File

@@ -1,5 +1,18 @@
# cojson-storage-indexeddb
## 0.17.9
### Patch Changes
- Updated dependencies [7586c3b]
- cojson@0.17.9
## 0.17.8
### Patch Changes
- cojson@0.17.8
## 0.17.7
### Patch Changes

View File

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

View File

@@ -1,5 +1,18 @@
# cojson-storage-sqlite
## 0.17.9
### Patch Changes
- Updated dependencies [7586c3b]
- cojson@0.17.9
## 0.17.8
### Patch Changes
- cojson@0.17.8
## 0.17.7
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "cojson-storage-sqlite",
"type": "module",
"version": "0.17.7",
"version": "0.17.9",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"license": "MIT",

View File

@@ -1,5 +1,18 @@
# cojson-transport-nodejs-ws
## 0.17.9
### Patch Changes
- Updated dependencies [7586c3b]
- cojson@0.17.9
## 0.17.8
### Patch Changes
- cojson@0.17.8
## 0.17.7
### Patch Changes

View File

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

View File

@@ -1,5 +1,13 @@
# cojson
## 0.17.9
### Patch Changes
- 7586c3b: Adds disableTransactionVerification() method to SyncManager
## 0.17.8
## 0.17.7
## 0.17.6

View File

@@ -25,7 +25,7 @@
},
"type": "module",
"license": "MIT",
"version": "0.17.7",
"version": "0.17.9",
"devDependencies": {
"@opentelemetry/sdk-metrics": "^2.0.0",
"libsql": "^0.5.13",

View File

@@ -132,6 +132,11 @@ export class SyncManager {
peers: { [key: PeerID]: PeerState } = {};
local: LocalNode;
// When true, transactions will not be verified.
// This is useful when syncing only for storage purposes, with the expectation that
// the transactions have already been verified by the [trusted] peer that sent them.
private skipVerify: boolean = false;
peersCounter = metrics.getMeter("cojson").createUpDownCounter("jazz.peers", {
description: "Amount of connected peers",
valueType: ValueType.INT,
@@ -154,6 +159,10 @@ export class SyncManager {
syncState: SyncStateManager;
disableTransactionVerification() {
this.skipVerify = true;
}
peersInPriorityOrder(): PeerState[] {
return Object.values(this.peers).sort((a, b) => {
const aPriority = a.priority || 0;
@@ -637,6 +646,7 @@ export class SyncManager {
undefined,
newContentForSession.lastSignature,
"immediate",
this.skipVerify,
);
if (result.isErr()) {

View File

@@ -20,11 +20,13 @@ import {
createTestNode,
loadCoValueOrFail,
nodeWithRandomAgentAndSessionID,
randomAgentAndSessionID,
setupTestAccount,
setupTestNode,
tearDownTestMetricReader,
waitFor,
} from "./testUtils.js";
import { stableStringify } from "../jsonStringify.js";
// We want to simulate a real world communication that happens asynchronously
TEST_NODE_CONFIG.withAsyncPeers = true;
@@ -160,6 +162,36 @@ test("should delete the peer state when the peer closes if persistent is false",
expect(syncManager.peers[peer.id]).toBeUndefined();
});
test("should not verify transactions when SyncManager has verification disabled", async () => {
jazzCloud.node.syncManager.disableTransactionVerification();
const [agent] = randomAgentAndSessionID();
const client = await setupTestAccount({ connected: true });
const group = client.node.createGroup();
const map = group.createMap();
map.core.tryAddTransactions(
client.node.currentSessionID,
[
{
privacy: "trusting",
changes: stableStringify([{ op: "set", key: "hello", value: "world" }]),
madeAt: Date.now(),
},
],
undefined,
Crypto.sign(agent.currentSignerSecret(), "hash_z12345678"),
"immediate",
true,
);
await map.core.waitForSync();
const loadedMap = await loadCoValueOrFail(jazzCloud.node, map.id);
expect(loadedMap.get("hello")).toEqual("world");
});
describe("sync - extra tests", () => {
test("Node handles disconnection and reconnection of a peer gracefully", async () => {
// Create two nodes

View File

@@ -1,5 +1,24 @@
# jazz-react
## 0.17.9
### Patch Changes
- Updated dependencies [52ea0c7]
- Updated dependencies [7586c3b]
- jazz-tools@0.17.9
- cojson@0.17.9
## 0.17.8
### Patch Changes
- Updated dependencies [ac3e694]
- Updated dependencies [6dbb053]
- Updated dependencies [1a182f0]
- jazz-tools@0.17.8
- cojson@0.17.8
## 0.17.7
### Patch Changes

View File

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

View File

@@ -1,5 +1,26 @@
# jazz-auth-betterauth
## 0.17.9
### Patch Changes
- Updated dependencies [52ea0c7]
- Updated dependencies [7586c3b]
- jazz-tools@0.17.9
- cojson@0.17.9
- jazz-betterauth-client-plugin@0.17.9
## 0.17.8
### Patch Changes
- Updated dependencies [ac3e694]
- Updated dependencies [6dbb053]
- Updated dependencies [1a182f0]
- jazz-tools@0.17.8
- jazz-betterauth-client-plugin@0.17.8
- cojson@0.17.8
## 0.17.7
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-auth-betterauth",
"version": "0.17.7",
"version": "0.17.9",
"type": "module",
"main": "dist/index.js",
"types": "src/index.ts",

View File

@@ -1,5 +1,17 @@
# jazz-betterauth-client-plugin
## 0.17.9
### Patch Changes
- jazz-betterauth-server-plugin@0.17.9
## 0.17.8
### Patch Changes
- jazz-betterauth-server-plugin@0.17.8
## 0.17.7
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-betterauth-client-plugin",
"version": "0.17.7",
"version": "0.17.9",
"type": "module",
"main": "dist/index.js",
"types": "src/index.ts",

View File

@@ -1,5 +1,24 @@
# jazz-betterauth-server-plugin
## 0.17.9
### Patch Changes
- Updated dependencies [52ea0c7]
- Updated dependencies [7586c3b]
- jazz-tools@0.17.9
- cojson@0.17.9
## 0.17.8
### Patch Changes
- Updated dependencies [ac3e694]
- Updated dependencies [6dbb053]
- Updated dependencies [1a182f0]
- jazz-tools@0.17.8
- cojson@0.17.8
## 0.17.7
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-betterauth-server-plugin",
"version": "0.17.7",
"version": "0.17.9",
"type": "module",
"main": "dist/index.js",
"types": "src/index.ts",

View File

@@ -1,5 +1,28 @@
# jazz-react-auth-betterauth
## 0.17.9
### Patch Changes
- Updated dependencies [52ea0c7]
- Updated dependencies [7586c3b]
- jazz-tools@0.17.9
- cojson@0.17.9
- jazz-auth-betterauth@0.17.9
- jazz-betterauth-client-plugin@0.17.9
## 0.17.8
### Patch Changes
- Updated dependencies [ac3e694]
- Updated dependencies [6dbb053]
- Updated dependencies [1a182f0]
- jazz-tools@0.17.8
- jazz-auth-betterauth@0.17.8
- jazz-betterauth-client-plugin@0.17.8
- cojson@0.17.8
## 0.17.7
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-react-auth-betterauth",
"version": "0.17.7",
"version": "0.17.9",
"type": "module",
"main": "dist/index.js",
"types": "src/index.tsx",

View File

@@ -1,5 +1,28 @@
# jazz-run
## 0.17.9
### Patch Changes
- Updated dependencies [52ea0c7]
- Updated dependencies [7586c3b]
- jazz-tools@0.17.9
- cojson@0.17.9
- cojson-storage-sqlite@0.17.9
- cojson-transport-ws@0.17.9
## 0.17.8
### Patch Changes
- Updated dependencies [ac3e694]
- Updated dependencies [6dbb053]
- Updated dependencies [1a182f0]
- jazz-tools@0.17.8
- cojson@0.17.8
- cojson-storage-sqlite@0.17.8
- cojson-transport-ws@0.17.8
## 0.17.7
### Patch Changes

View File

@@ -3,7 +3,7 @@
"bin": "./dist/index.js",
"type": "module",
"license": "MIT",
"version": "0.17.7",
"version": "0.17.9",
"exports": {
"./startSyncServer": {
"types": "./dist/startSyncServer.d.ts",
@@ -28,11 +28,11 @@
"@effect/printer-ansi": "^0.34.5",
"@effect/schema": "^0.71.1",
"@effect/typeclass": "^0.25.5",
"cojson": "workspace:0.17.7",
"cojson-storage-sqlite": "workspace:0.17.7",
"cojson-transport-ws": "workspace:0.17.7",
"cojson": "workspace:0.17.9",
"cojson-storage-sqlite": "workspace:0.17.9",
"cojson-transport-ws": "workspace:0.17.9",
"effect": "^3.6.5",
"jazz-tools": "workspace:0.17.7",
"jazz-tools": "workspace:0.17.9",
"ws": "^8.14.2"
},
"devDependencies": {

View File

@@ -1,5 +1,26 @@
# jazz-tools
## 0.17.9
### Patch Changes
- 52ea0c7: Explicit loadAs in upsertUnique to use it without loaded context
- Updated dependencies [7586c3b]
- cojson@0.17.9
- cojson-storage-indexeddb@0.17.9
- cojson-transport-ws@0.17.9
## 0.17.8
### Patch Changes
- ac3e694: Fixed an issue where CoValue schemas could be incorrectly passed to `co.map` and `co.profile` schema definers.
- 6dbb053: Prosemirror: fix RangeError triggered when creating invalid HTML
- 1a182f0: Add missing BaseProfileShape export
- cojson@0.17.8
- cojson-storage-indexeddb@0.17.8
- cojson-transport-ws@0.17.8
## 0.17.7
### Patch Changes

View File

@@ -140,7 +140,7 @@
},
"type": "module",
"license": "MIT",
"version": "0.17.7",
"version": "0.17.9",
"dependencies": {
"@manuscripts/prosemirror-recreate-steps": "^0.1.4",
"@scure/base": "1.2.1",

View File

@@ -1,6 +1,6 @@
import { recreateTransform } from "@manuscripts/prosemirror-recreate-steps";
import { CoRichText } from "jazz-tools";
import { Transaction } from "prosemirror-state";
import { EditorState, Transaction } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { htmlToProseMirror, proseMirrorToHtml } from "./converter.js";
@@ -34,6 +34,8 @@ export const META_KEY = "fromJazz";
export function createSyncHandlers(coRichText: CoRichText | undefined) {
// Store the editor view in a closure
let view: EditorView | undefined;
let localChange = false;
let remoteChange = false;
/**
* Handles changes from CoRichText by updating the ProseMirror editor.
@@ -47,24 +49,47 @@ export function createSyncHandlers(coRichText: CoRichText | undefined) {
* @param newText - The updated CoRichText instance
*/
function handleCoRichTextChange(newText: CoRichText) {
if (!view || !newText) return;
if (!view || !newText || localChange || remoteChange) return;
const pmDoc = htmlToProseMirror(
newText.toString(),
view.state.doc.type.schema,
);
const transform = recreateTransform(view.state.doc, pmDoc);
const currentView = view;
remoteChange = true;
// Create a new transaction
const tr = view.state.tr;
// Changes on CoPlainText are emitted word by word, which means that it creates
// invalid intermediate states when wrapping a document with HTML tags
// To fix the issue, we throttle the changes to the next microtask
queueMicrotask(() => {
const pmDoc = htmlToProseMirror(
newText.toString(),
currentView.state.doc.type.schema,
);
// Apply all steps from the transform to the transaction
transform.steps.forEach((step) => {
tr.step(step);
try {
const transform = recreateTransform(currentView.state.doc, pmDoc);
// Create a new transaction
const tr = currentView.state.tr;
// Apply all steps from the transform to the transaction
transform.steps.forEach((step) => {
tr.step(step);
});
tr.setMeta(META_KEY, true);
currentView.dispatch(tr);
} catch (err) {
// Sometimes recreateTransform fails, so we just rebuild the doc from scratch
const newState = EditorState.create({
schema: currentView.state.schema,
doc: pmDoc,
plugins: currentView.state.plugins,
selection: currentView.state.selection,
});
currentView.updateState(newState);
} finally {
remoteChange = false;
}
});
tr.setMeta(META_KEY, true);
view.dispatch(tr);
}
/**
@@ -82,7 +107,12 @@ export function createSyncHandlers(coRichText: CoRichText | undefined) {
if (tr.docChanged) {
const str = proseMirrorToHtml(tr.doc);
coRichText.applyDiff(str);
localChange = true;
try {
coRichText.applyDiff(str);
} finally {
localChange = false;
}
}
}

View File

@@ -1,29 +1,33 @@
// @vitest-environment jsdom
import { Account, CoRichText } from "jazz-tools";
import { CoRichText } from "jazz-tools";
import { createJazzTestAccount, setupJazzTestSync } from "jazz-tools/testing";
import { schema } from "prosemirror-schema-basic";
import { EditorState, TextSelection } from "prosemirror-state";
import { Plugin } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import {
afterEach,
beforeEach,
describe,
expect,
it,
onTestFinished,
} from "vitest";
import { createJazzPlugin } from "../lib/plugin";
import { Schema } from "prosemirror-model";
import { schema as basicSchema } from "prosemirror-schema-basic";
import { addListNodes } from "prosemirror-schema-list";
let account: Account;
let coRichText: CoRichText;
let plugin: Plugin;
let state: EditorState;
let view: EditorView;
beforeEach(async () => {
await setupJazzTestSync();
account = await createJazzTestAccount({ isCurrentActiveAccount: true });
const schema = new Schema({
nodes: addListNodes(basicSchema.spec.nodes, "paragraph block*", "block"),
marks: basicSchema.spec.marks,
});
async function setupTest(initialContent = "<p>Hello</p>") {
// Create a real CoRichText with the test account as owner
coRichText = CoRichText.create("<p>Hello</p>", account);
const coRichText = CoRichText.create(initialContent);
plugin = createJazzPlugin(coRichText);
state = EditorState.create({
const plugin = createJazzPlugin(coRichText);
const state = EditorState.create({
schema,
plugins: [plugin],
});
@@ -33,25 +37,32 @@ beforeEach(async () => {
document.body.appendChild(editorElement);
// Initialize the editor view
view = new EditorView(editorElement, {
const view = new EditorView(editorElement, {
state,
});
});
afterEach(() => {
// Clean up the editor view
if (view) {
onTestFinished(() => {
view.destroy();
view.dom.remove();
}
editorElement.remove();
});
return { coRichText, plugin, state, view, editorElement };
}
beforeEach(async () => {
await setupJazzTestSync();
await createJazzTestAccount({ isCurrentActiveAccount: true });
});
describe("createJazzPlugin", () => {
it("initializes editor with CoRichText content", () => {
it("initializes editor with CoRichText content", async () => {
const { state } = await setupTest();
expect(state.doc.textContent).toContain("Hello");
});
it("updates editor when CoRichText changes", async () => {
const { coRichText, view } = await setupTest();
// Update CoRichText content
coRichText.applyDiff("<p>Updated content</p>");
@@ -61,7 +72,9 @@ describe("createJazzPlugin", () => {
expect(view.state.doc.textContent).toContain("Updated content");
});
it("updates CoRichText when editor content changes", () => {
it("updates CoRichText when editor content changes", async () => {
const { coRichText, view } = await setupTest();
// Create a transaction to update the editor content
const tr = view.state.tr.insertText(" World", 6);
view.dispatch(tr);
@@ -70,8 +83,8 @@ describe("createJazzPlugin", () => {
expect(coRichText.toString()).toContain("Hello World");
});
it("handles empty CoRichText initialization", () => {
const emptyCoRichText = CoRichText.create("", account);
it("handles empty CoRichText initialization", async () => {
const emptyCoRichText = CoRichText.create("");
const emptyPlugin = createJazzPlugin(emptyCoRichText);
const emptyState = EditorState.create({
schema,
@@ -81,7 +94,7 @@ describe("createJazzPlugin", () => {
expect(emptyState.doc.textContent).toBe("");
});
it("handles undefined CoRichText", () => {
it("handles undefined CoRichText", async () => {
const undefinedPlugin = createJazzPlugin(undefined);
const undefinedState = EditorState.create({
schema,
@@ -91,7 +104,9 @@ describe("createJazzPlugin", () => {
expect(undefinedState.doc.textContent).toBe("");
});
it("prevents infinite update loops", () => {
it("prevents infinite update loops", async () => {
const { coRichText, view } = await setupTest();
// Create a transaction that would normally trigger a CoRichText update
const tr = view.state.tr.insertText(" Loop", 6);
@@ -106,7 +121,9 @@ describe("createJazzPlugin", () => {
expect(coRichText.toString()).not.toContain("Loop");
});
it.skip("preserves selection when CoRichText changes", () => {
it("preserves selection when CoRichText changes", async () => {
const { coRichText, view } = await setupTest();
// Set a selection in the editor
const tr = view.state.tr.setSelection(
TextSelection.create(view.state.doc, 2, 5),
@@ -118,10 +135,49 @@ describe("createJazzPlugin", () => {
expect(view.state.selection.to).toBe(5);
// Update CoRichText content
coRichText.applyDiff("<p>Updated content</p>");
coRichText.applyDiff("<p>Hello world</p>");
await new Promise((resolve) => setTimeout(resolve, 0));
// Verify selection is preserved after content update
expect(view.state.selection.from).toBe(2);
expect(view.state.selection.to).toBe(5);
});
it("falls back to creating a new EditorState when the transform fails", async () => {
const { coRichText, editorElement } = await setupTest(
"<p>A <strong>hu<em>man</strong></em>.</p>",
);
// Wait for the next tick to allow the update to propagate
await new Promise((resolve) => setTimeout(resolve, 0));
// Update CoRichText content
coRichText.applyDiff(
"<ol><li><p>A <strong>hu</strong><em><strong>man</strong></em>.</p></li></ol>",
);
// Wait for the next tick to allow the update to propagate
await new Promise((resolve) => setTimeout(resolve, 0));
expect(editorElement.querySelector(".ProseMirror")?.innerHTML).toBe(
"<ol><li><p>A <strong>hu</strong><em><strong>man</strong></em>.</p></li></ol>",
);
});
it("handles updates with emojis", async () => {
const { coRichText, editorElement } = await setupTest(
"<p>A <strong>hu</strong><em><strong>man</strong></em>.</p>",
);
// Update CoRichText content
coRichText.applyDiff("<p>A human💪</p>");
// Wait for the next tick to allow the update to propagate
await new Promise((resolve) => setTimeout(resolve, 0));
expect(editorElement.querySelector(".ProseMirror")?.innerHTML).toBe(
"<p>A human💪</p>",
);
});
});

View File

@@ -9,7 +9,7 @@ import {
} from "jazz-tools";
import { LocalStorageKVStore } from "jazz-tools/browser";
import { useAuthSecretStorage, useJazzContext } from "jazz-tools/react-core";
import { useEffect, useMemo, useState } from "react";
import { ReactNode, useEffect, useMemo, useState } from "react";
import { JazzProviderProps, JazzReactProvider } from "../provider.js";
function useJazzClerkAuth(clerk: MinimalClerkClient) {
@@ -43,7 +43,9 @@ export const JazzReactProviderWithClerk = <
| (AccountClass<Account> & CoValueFromRaw<Account>)
| AnyAccountSchema,
>(
props: { clerk: MinimalClerkClient } & JazzProviderProps<S>,
props: {
clerk: MinimalClerkClient;
} & JazzProviderProps<S>,
) => {
const [isLoaded, setIsLoaded] = useState(false);
@@ -61,7 +63,7 @@ export const JazzReactProviderWithClerk = <
}, []);
if (!isLoaded) {
return null;
return props.fallback ?? null;
}
return (

View File

@@ -20,6 +20,7 @@ export type JazzProviderProps<
> = {
children: React.ReactNode;
enableSSR?: boolean;
fallback?: React.ReactNode | null;
} & JazzContextManagerProps<S>;
/** @category Context & Hooks */
@@ -38,6 +39,7 @@ export function JazzReactProvider<
logOutReplacement,
onAnonymousAccountDiscarded,
enableSSR,
fallback = null,
}: JazzProviderProps<S>) {
const [contextManager] = React.useState(
() =>
@@ -100,7 +102,7 @@ export function JazzReactProvider<
return (
<JazzContext.Provider value={value}>
<JazzContextManagerContext.Provider value={contextManager}>
{value && children}
{value ? children : fallback}
</JazzContextManagerContext.Provider>
</JazzContext.Provider>
);

View File

@@ -628,7 +628,11 @@ export class CoMap extends CoValueBase implements CoValue {
resolve?: RefsToResolveStrict<M, R>;
},
): Promise<Resolved<M, R> | null> {
let mapId = CoMap._findUnique(options.unique, options.owner.id);
const mapId = CoMap._findUnique(
options.unique,
options.owner.id,
options.owner._loadedAs,
);
let map: Resolved<M, R> | null = await loadCoValueWithoutMe(this, mapId, {
...options,
loadAs: options.owner._loadedAs,

View File

@@ -35,6 +35,7 @@ export type {
TextPos,
AccountClass,
AccountCreationProps,
BaseProfileShape,
} from "./internal.js";
export {

View File

@@ -38,9 +38,14 @@ import {
// Note: if you're editing this function, edit the `isAnyCoValueSchema`
// function in `zodReExport.ts` as well
export function isAnyCoValueSchema(
schema: AnyZodOrCoValueSchema | CoValueClass,
schema: unknown,
): schema is AnyCoreCoValueSchema {
return "collaborative" in schema && schema.collaborative === true;
return (
typeof schema === "object" &&
schema !== null &&
"collaborative" in schema &&
schema.collaborative === true
);
}
export function isCoValueSchema(

View File

@@ -19,6 +19,7 @@ import {
createCoreCoPlainTextSchema,
createCoreFileStreamSchema,
hydrateCoreCoValueSchema,
isAnyCoValueSchema,
} from "../../internal.js";
import {
CoDiscriminatedUnionSchema,
@@ -36,6 +37,11 @@ import { z } from "./zodReExport.js";
export const coMapDefiner = <Shape extends z.core.$ZodLooseShape>(
shape: Shape,
): CoMapSchema<Shape> => {
if (isAnyCoValueSchema(shape as any)) {
throw new Error(
"co.map() expects an object as its argument, not a CoValue schema",
);
}
const coreSchema = createCoreCoMapSchema(shape);
return hydrateCoreCoValueSchema(coreSchema);
};
@@ -116,6 +122,11 @@ export const coProfileDefiner = <
>(
shape: Shape & Partial<DefaultProfileShape> = {} as any,
): CoProfileSchema<Shape> => {
if (isAnyCoValueSchema(shape as any)) {
throw new Error(
"co.profile() expects an object as its argument, not a CoValue schema",
);
}
const ehnancedShape = Object.assign(shape, {
name: z.string(),
inbox: z.optional(z.string()),

View File

@@ -88,6 +88,11 @@ function containsCoValueSchema(shape?: core.$ZodLooseShape): boolean {
// Note: if you're editing this function, edit the `isAnyCoValueSchema`
// function in `zodSchemaToCoSchema.ts` as well
function isAnyCoValueSchema(schema: any): boolean {
return "collaborative" in schema && schema.collaborative === true;
function isAnyCoValueSchema(schema: unknown): boolean {
return (
typeof schema === "object" &&
schema !== null &&
"collaborative" in schema &&
schema.collaborative === true
);
}

View File

@@ -137,6 +137,12 @@ test("loading raw accounts should work", async () => {
expect(loadedAccount.profile!.name).toBe("test 1");
});
test("co.profile() should throw an error if passed a CoValue schema", async () => {
expect(() => co.profile(co.map({}))).toThrow(
"co.profile() expects an object as its argument, not a CoValue schema",
);
});
test("should support recursive props on co.profile", async () => {
const User = co.profile({
name: z.string(),

View File

@@ -2325,6 +2325,12 @@ describe("co.map schema", () => {
expect(draftPerson.extraField).toEqual("extra");
});
});
test("co.map() should throw an error if passed a CoValue schema", () => {
expect(() => co.map(co.map({}))).toThrow(
"co.map() expects an object as its argument, not a CoValue schema",
);
});
});
describe("Updating a nested reference", () => {

8
pnpm-lock.yaml generated
View File

@@ -2038,19 +2038,19 @@ importers:
specifier: ^0.25.5
version: 0.25.8(effect@3.11.9)
cojson:
specifier: workspace:0.17.7
specifier: workspace:0.17.9
version: link:../cojson
cojson-storage-sqlite:
specifier: workspace:0.17.7
specifier: workspace:0.17.9
version: link:../cojson-storage-sqlite
cojson-transport-ws:
specifier: workspace:0.17.7
specifier: workspace:0.17.9
version: link:../cojson-transport-ws
effect:
specifier: ^3.6.5
version: 3.11.9
jazz-tools:
specifier: workspace:0.17.7
specifier: workspace:0.17.9
version: link:../jazz-tools
ws:
specifier: ^8.14.2

View File

@@ -1,5 +1,21 @@
# jazz-react-tailwind-starter
## 0.0.153
### Patch Changes
- Updated dependencies [52ea0c7]
- jazz-tools@0.17.9
## 0.0.152
### Patch Changes
- Updated dependencies [ac3e694]
- Updated dependencies [6dbb053]
- Updated dependencies [1a182f0]
- jazz-tools@0.17.8
## 0.0.151
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-react-passkey-auth-starter",
"private": true,
"version": "0.0.151",
"version": "0.0.153",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,21 @@
# svelte-passkey-auth
## 0.0.127
### Patch Changes
- Updated dependencies [52ea0c7]
- jazz-tools@0.17.9
## 0.0.126
### Patch Changes
- Updated dependencies [ac3e694]
- Updated dependencies [6dbb053]
- Updated dependencies [1a182f0]
- jazz-tools@0.17.8
## 0.0.125
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "svelte-passkey-auth",
"version": "0.0.125",
"version": "0.0.127",
"type": "module",
"private": true,
"scripts": {