Compare commits
43 Commits
jazz-react
...
jazz-bette
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c2e60ab51 | ||
|
|
1830171930 | ||
|
|
6574090402 | ||
|
|
a6f65472a7 | ||
|
|
109952aa6a | ||
|
|
556bdf977b | ||
|
|
0213b4e5b9 | ||
|
|
01c195756c | ||
|
|
9c69917d24 | ||
|
|
b4d68f0a32 | ||
|
|
fbc3839777 | ||
|
|
296464b282 | ||
|
|
c463654970 | ||
|
|
debc052bdc | ||
|
|
3c7846153d | ||
|
|
e410fedda7 | ||
|
|
52ea0c7a9b | ||
|
|
b91c93caed | ||
|
|
7586c3bac5 | ||
|
|
9b96cd4a65 | ||
|
|
2e66ea8e56 | ||
|
|
336cc1f0fe | ||
|
|
cc2ca5c23c | ||
|
|
3664385113 | ||
|
|
2b2ecdaf3d | ||
|
|
6dbb05320a | ||
|
|
0160a188fa | ||
|
|
ac3e694f4e | ||
|
|
a7dca75955 | ||
|
|
143156cd6a | ||
|
|
1a182f07de | ||
|
|
7e7e7ebb51 | ||
|
|
0966a90f3d | ||
|
|
76f142b70d | ||
|
|
cd2f0846db | ||
|
|
c2e411d056 | ||
|
|
0167153da2 | ||
|
|
feaa69ebdd | ||
|
|
d5fa172b17 | ||
|
|
96de15593b | ||
|
|
5ba03ebc70 | ||
|
|
d8ae47c4d1 | ||
|
|
92c0048984 |
8
.github/CODEOWNERS
vendored
Normal file
8
.github/CODEOWNERS
vendored
Normal 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
|
||||||
@@ -1,5 +1,27 @@
|
|||||||
# passkey-svelte
|
# 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
|
||||||
|
|
||||||
|
- jazz-tools@0.17.7
|
||||||
|
|
||||||
## 0.0.119
|
## 0.0.119
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "chat-svelte",
|
"name": "chat-svelte",
|
||||||
"version": "0.0.119",
|
"version": "0.0.122",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -9,8 +9,10 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root">
|
||||||
|
<p>Loading...</p>
|
||||||
|
</div>
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ function JazzProvider({ children }: { children: ReactNode }) {
|
|||||||
sync={{
|
sync={{
|
||||||
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
|
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
|
||||||
}}
|
}}
|
||||||
|
fallback={<p>Loading...</p>}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</JazzReactProviderWithClerk>
|
</JazzReactProviderWithClerk>
|
||||||
|
|||||||
@@ -25,6 +25,11 @@ export const docNavigationItems = [
|
|||||||
excludeFromNavigation: true,
|
excludeFromNavigation: true,
|
||||||
},
|
},
|
||||||
{ name: "FAQs", href: "/docs/faq", done: 100 },
|
{ name: "FAQs", href: "/docs/faq", done: 100 },
|
||||||
|
{
|
||||||
|
name: "Troubleshooting",
|
||||||
|
href: "/docs/troubleshooting",
|
||||||
|
done: 100,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -49,18 +49,20 @@ if (bob) {
|
|||||||
```
|
```
|
||||||
</CodeGroup>
|
</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>
|
<CodeGroup>
|
||||||
```tsx twoslash
|
```tsx twoslash
|
||||||
const bobsID = "co_z123";
|
const bobsID = "co_z123";
|
||||||
|
import { Group } from "jazz-tools";
|
||||||
const group = Group.create();
|
const group = Group.create();
|
||||||
|
|
||||||
// ---cut---
|
// ---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) {
|
if (bob) {
|
||||||
group.addMember(bob, "writer");
|
group.addMember(bob, "writer");
|
||||||
|
|||||||
@@ -25,7 +25,10 @@ You can use [`create-jazz-app`](/docs/tools/create-jazz-app) to create a new Jaz
|
|||||||
```
|
```
|
||||||
</CodeGroup>
|
</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">
|
{/* <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.
|
Or you can follow this [React step-by-step guide](/docs/react/guide) where we walk you through building an issue tracker app.
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ npm i -S jazz-tools
|
|||||||
<Alert variant="info" className="mt-4" title="Note">
|
<Alert variant="info" className="mt-4" title="Note">
|
||||||
- Requires at least Node.js v20.
|
- 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`.
|
- 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>
|
</Alert>
|
||||||
|
|
||||||
#### Fix incompatible dependencies
|
#### Fix incompatible dependencies
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ npm i -S jazz-tools
|
|||||||
</CodeGroup>
|
</CodeGroup>
|
||||||
|
|
||||||
<Alert variant="info" className="mt-4" title="Note">
|
<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`.
|
- 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>
|
</Alert>
|
||||||
|
|
||||||
|
|||||||
@@ -27,8 +27,6 @@ pnpm install jazz-tools
|
|||||||
```
|
```
|
||||||
</CodeGroup>
|
</CodeGroup>
|
||||||
|
|
||||||
<Alert variant="info" className="mt-4 flex gap-2 items-center">Requires at least Node.js v20.</Alert>
|
|
||||||
|
|
||||||
## Write your schema
|
## Write your schema
|
||||||
|
|
||||||
Define your data schema using [CoValues](/docs/schemas/covalues) from `jazz-tools`.
|
Define your data schema using [CoValues](/docs/schemas/covalues) from `jazz-tools`.
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ pnpm install jazz-tools
|
|||||||
```
|
```
|
||||||
</CodeGroup>
|
</CodeGroup>
|
||||||
|
|
||||||
<Alert variant="info" className="mt-4 flex gap-2 items-center">Requires at least Node.js v20.</Alert>
|
|
||||||
|
|
||||||
## Write your schema
|
## Write your schema
|
||||||
|
|
||||||
See the [schema docs](/docs/schemas/covalues) for more information.
|
See the [schema docs](/docs/schemas/covalues) for more information.
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { ContentByFramework, CodeGroup } from '@/components/forMdx'
|
import { ContentByFramework, CodeGroup } from '@/components/forMdx'
|
||||||
|
import { Alert } from "@garden-co/design-system/src/components/atoms/Alert";
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
description: "Learn how to sync and persist your data using Jazz Cloud, or run your own sync server."
|
description: "Learn how to sync and persist your data using Jazz Cloud, or run your own sync server."
|
||||||
@@ -44,8 +45,14 @@ 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.
|
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.
|
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:
|
### Command line options:
|
||||||
|
|
||||||
|
- `--host` / `-h` - the host to run the sync server on. Defaults to 127.0.0.1.
|
||||||
- `--port` / `-p` - the port to run the sync server on. Defaults to 4200.
|
- `--port` / `-p` - the port to run the sync server on. Defaults to 4200.
|
||||||
- `--in-memory` - keep CoValues in-memory only and do sync only, no persistence. Persistence is enabled by default.
|
- `--in-memory` - keep CoValues in-memory only and do sync only, no persistence. Persistence is enabled by default.
|
||||||
- `--db` - the path to the file where to store the data (SQLite). Defaults to `sync-db/storage.db`.
|
- `--db` - the path to the file where to store the data (SQLite). Defaults to `sync-db/storage.db`.
|
||||||
|
|||||||
133
homepage/homepage/content/docs/troubleshooting.mdx
Normal file
133
homepage/homepage/content/docs/troubleshooting.mdx
Normal 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 you’re 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 can’t 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)
|
||||||
@@ -26,3 +26,4 @@ export const navigationItems: NavItemProps[] = [
|
|||||||
title: "Status",
|
title: "Status",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,24 @@
|
|||||||
# cojson-storage-indexeddb
|
# 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
|
||||||
|
|
||||||
|
- cojson@0.17.7
|
||||||
|
|
||||||
## 0.17.6
|
## 0.17.6
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "cojson-storage-indexeddb",
|
"name": "cojson-storage-indexeddb",
|
||||||
"version": "0.17.6",
|
"version": "0.17.9",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
|||||||
@@ -1,5 +1,24 @@
|
|||||||
# cojson-storage-sqlite
|
# 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
|
||||||
|
|
||||||
|
- cojson@0.17.7
|
||||||
|
|
||||||
## 0.17.6
|
## 0.17.6
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "cojson-storage-sqlite",
|
"name": "cojson-storage-sqlite",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.17.6",
|
"version": "0.17.9",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,5 +1,24 @@
|
|||||||
# cojson-transport-nodejs-ws
|
# 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
|
||||||
|
|
||||||
|
- cojson@0.17.7
|
||||||
|
|
||||||
## 0.17.6
|
## 0.17.6
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "cojson-transport-ws",
|
"name": "cojson-transport-ws",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.17.6",
|
"version": "0.17.9",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
# cojson
|
# cojson
|
||||||
|
|
||||||
|
## 0.17.9
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 7586c3b: Adds disableTransactionVerification() method to SyncManager
|
||||||
|
|
||||||
|
## 0.17.8
|
||||||
|
|
||||||
|
## 0.17.7
|
||||||
|
|
||||||
## 0.17.6
|
## 0.17.6
|
||||||
|
|
||||||
## 0.17.5
|
## 0.17.5
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"version": "0.17.6",
|
"version": "0.17.9",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@opentelemetry/sdk-metrics": "^2.0.0",
|
"@opentelemetry/sdk-metrics": "^2.0.0",
|
||||||
"libsql": "^0.5.13",
|
"libsql": "^0.5.13",
|
||||||
|
|||||||
@@ -132,6 +132,11 @@ export class SyncManager {
|
|||||||
peers: { [key: PeerID]: PeerState } = {};
|
peers: { [key: PeerID]: PeerState } = {};
|
||||||
local: LocalNode;
|
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", {
|
peersCounter = metrics.getMeter("cojson").createUpDownCounter("jazz.peers", {
|
||||||
description: "Amount of connected peers",
|
description: "Amount of connected peers",
|
||||||
valueType: ValueType.INT,
|
valueType: ValueType.INT,
|
||||||
@@ -154,6 +159,10 @@ export class SyncManager {
|
|||||||
|
|
||||||
syncState: SyncStateManager;
|
syncState: SyncStateManager;
|
||||||
|
|
||||||
|
disableTransactionVerification() {
|
||||||
|
this.skipVerify = true;
|
||||||
|
}
|
||||||
|
|
||||||
peersInPriorityOrder(): PeerState[] {
|
peersInPriorityOrder(): PeerState[] {
|
||||||
return Object.values(this.peers).sort((a, b) => {
|
return Object.values(this.peers).sort((a, b) => {
|
||||||
const aPriority = a.priority || 0;
|
const aPriority = a.priority || 0;
|
||||||
@@ -637,6 +646,7 @@ export class SyncManager {
|
|||||||
undefined,
|
undefined,
|
||||||
newContentForSession.lastSignature,
|
newContentForSession.lastSignature,
|
||||||
"immediate",
|
"immediate",
|
||||||
|
this.skipVerify,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
|
|||||||
@@ -20,11 +20,13 @@ import {
|
|||||||
createTestNode,
|
createTestNode,
|
||||||
loadCoValueOrFail,
|
loadCoValueOrFail,
|
||||||
nodeWithRandomAgentAndSessionID,
|
nodeWithRandomAgentAndSessionID,
|
||||||
|
randomAgentAndSessionID,
|
||||||
setupTestAccount,
|
setupTestAccount,
|
||||||
setupTestNode,
|
setupTestNode,
|
||||||
tearDownTestMetricReader,
|
tearDownTestMetricReader,
|
||||||
waitFor,
|
waitFor,
|
||||||
} from "./testUtils.js";
|
} from "./testUtils.js";
|
||||||
|
import { stableStringify } from "../jsonStringify.js";
|
||||||
|
|
||||||
// We want to simulate a real world communication that happens asynchronously
|
// We want to simulate a real world communication that happens asynchronously
|
||||||
TEST_NODE_CONFIG.withAsyncPeers = true;
|
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();
|
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", () => {
|
describe("sync - extra tests", () => {
|
||||||
test("Node handles disconnection and reconnection of a peer gracefully", async () => {
|
test("Node handles disconnection and reconnection of a peer gracefully", async () => {
|
||||||
// Create two nodes
|
// Create two nodes
|
||||||
|
|||||||
@@ -1,5 +1,31 @@
|
|||||||
# jazz-react
|
# 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
|
||||||
|
|
||||||
|
- cojson@0.17.7
|
||||||
|
- jazz-tools@0.17.7
|
||||||
|
|
||||||
## 0.17.6
|
## 0.17.6
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "community-jazz-vue",
|
"name": "community-jazz-vue",
|
||||||
"version": "0.17.6",
|
"version": "0.17.9",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "src/index.ts",
|
"types": "src/index.ts",
|
||||||
|
|||||||
@@ -1,5 +1,34 @@
|
|||||||
# jazz-auth-betterauth
|
# 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
|
||||||
|
|
||||||
|
- cojson@0.17.7
|
||||||
|
- jazz-betterauth-client-plugin@0.17.7
|
||||||
|
- jazz-tools@0.17.7
|
||||||
|
|
||||||
## 0.17.6
|
## 0.17.6
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "jazz-auth-betterauth",
|
"name": "jazz-auth-betterauth",
|
||||||
"version": "0.17.6",
|
"version": "0.17.9",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "src/index.ts",
|
"types": "src/index.ts",
|
||||||
|
|||||||
@@ -1,5 +1,23 @@
|
|||||||
# jazz-betterauth-client-plugin
|
# 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
|
||||||
|
|
||||||
|
- jazz-betterauth-server-plugin@0.17.7
|
||||||
|
|
||||||
## 0.17.6
|
## 0.17.6
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "jazz-betterauth-client-plugin",
|
"name": "jazz-betterauth-client-plugin",
|
||||||
"version": "0.17.6",
|
"version": "0.17.9",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "src/index.ts",
|
"types": "src/index.ts",
|
||||||
|
|||||||
@@ -1,5 +1,31 @@
|
|||||||
# jazz-betterauth-server-plugin
|
# 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
|
||||||
|
|
||||||
|
- cojson@0.17.7
|
||||||
|
- jazz-tools@0.17.7
|
||||||
|
|
||||||
## 0.17.6
|
## 0.17.6
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "jazz-betterauth-server-plugin",
|
"name": "jazz-betterauth-server-plugin",
|
||||||
"version": "0.17.6",
|
"version": "0.17.9",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "src/index.ts",
|
"types": "src/index.ts",
|
||||||
|
|||||||
@@ -1,5 +1,37 @@
|
|||||||
# jazz-react-auth-betterauth
|
# 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
|
||||||
|
|
||||||
|
- cojson@0.17.7
|
||||||
|
- jazz-auth-betterauth@0.17.7
|
||||||
|
- jazz-betterauth-client-plugin@0.17.7
|
||||||
|
- jazz-tools@0.17.7
|
||||||
|
|
||||||
## 0.17.6
|
## 0.17.6
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "jazz-react-auth-betterauth",
|
"name": "jazz-react-auth-betterauth",
|
||||||
"version": "0.17.6",
|
"version": "0.17.9",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "src/index.tsx",
|
"types": "src/index.tsx",
|
||||||
|
|||||||
@@ -1,5 +1,38 @@
|
|||||||
# jazz-run
|
# 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
|
||||||
|
|
||||||
|
- feaa69e: Add host option to the jazz-run sync command
|
||||||
|
- cojson@0.17.7
|
||||||
|
- cojson-storage-sqlite@0.17.7
|
||||||
|
- cojson-transport-ws@0.17.7
|
||||||
|
- jazz-tools@0.17.7
|
||||||
|
|
||||||
## 0.17.6
|
## 0.17.6
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"bin": "./dist/index.js",
|
"bin": "./dist/index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"version": "0.17.6",
|
"version": "0.17.9",
|
||||||
"exports": {
|
"exports": {
|
||||||
"./startSyncServer": {
|
"./startSyncServer": {
|
||||||
"types": "./dist/startSyncServer.d.ts",
|
"types": "./dist/startSyncServer.d.ts",
|
||||||
@@ -28,11 +28,11 @@
|
|||||||
"@effect/printer-ansi": "^0.34.5",
|
"@effect/printer-ansi": "^0.34.5",
|
||||||
"@effect/schema": "^0.71.1",
|
"@effect/schema": "^0.71.1",
|
||||||
"@effect/typeclass": "^0.25.5",
|
"@effect/typeclass": "^0.25.5",
|
||||||
"cojson": "workspace:0.17.6",
|
"cojson": "workspace:0.17.9",
|
||||||
"cojson-storage-sqlite": "workspace:0.17.6",
|
"cojson-storage-sqlite": "workspace:0.17.9",
|
||||||
"cojson-transport-ws": "workspace:0.17.6",
|
"cojson-transport-ws": "workspace:0.17.9",
|
||||||
"effect": "^3.6.5",
|
"effect": "^3.6.5",
|
||||||
"jazz-tools": "workspace:0.17.6",
|
"jazz-tools": "workspace:0.17.9",
|
||||||
"ws": "^8.14.2"
|
"ws": "^8.14.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
6
packages/jazz-run/src/config.ts
Normal file
6
packages/jazz-run/src/config.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export const serverDefaults = {
|
||||||
|
host: "127.0.0.1",
|
||||||
|
port: 4200,
|
||||||
|
inMemory: false,
|
||||||
|
db: "sync-db/storage.db",
|
||||||
|
};
|
||||||
@@ -5,6 +5,7 @@ import { NodeContext, NodeRuntime } from "@effect/platform-node";
|
|||||||
import { Console, Effect } from "effect";
|
import { Console, Effect } from "effect";
|
||||||
import { createWorkerAccount } from "./createWorkerAccount.js";
|
import { createWorkerAccount } from "./createWorkerAccount.js";
|
||||||
import { startSyncServer } from "./startSyncServer.js";
|
import { startSyncServer } from "./startSyncServer.js";
|
||||||
|
import { serverDefaults } from "./config.js";
|
||||||
|
|
||||||
const jazzTools = Command.make("jazz-tools");
|
const jazzTools = Command.make("jazz-tools");
|
||||||
|
|
||||||
@@ -39,36 +40,63 @@ const accountCommand = Command.make("account").pipe(
|
|||||||
Command.withSubcommands([createAccountCommand]),
|
Command.withSubcommands([createAccountCommand]),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const hostOption = Options.text("host")
|
||||||
|
.pipe(Options.withAlias("h"))
|
||||||
|
.pipe(
|
||||||
|
Options.withDescription(
|
||||||
|
`The host to listen on. Default is ${serverDefaults.host}`,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.pipe(Options.withDefault(serverDefaults.host));
|
||||||
|
|
||||||
const portOption = Options.text("port")
|
const portOption = Options.text("port")
|
||||||
.pipe(Options.withAlias("p"))
|
.pipe(Options.withAlias("p"))
|
||||||
.pipe(
|
.pipe(
|
||||||
Options.withDescription(
|
Options.withDescription(
|
||||||
"Select a different port for the WebSocket server. Default is 4200",
|
`Select a different port for the WebSocket server. Default is ${serverDefaults.port}`,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.pipe(Options.withDefault("4200"));
|
.pipe(Options.withDefault(serverDefaults.port.toString()));
|
||||||
|
|
||||||
const inMemoryOption = Options.boolean("in-memory").pipe(
|
const inMemoryOption = Options.boolean("in-memory").pipe(
|
||||||
Options.withDescription("Use an in-memory storage instead of file-based"),
|
Options.withDescription("Use an in-memory storage instead of file-based."),
|
||||||
);
|
);
|
||||||
|
|
||||||
const dbOption = Options.file("db")
|
const dbOption = Options.file("db")
|
||||||
.pipe(
|
.pipe(
|
||||||
Options.withDescription(
|
Options.withDescription(
|
||||||
"The path to the file where to store the data. Default is 'sync-db/storage.db'",
|
`The path to the file where to store the data. Default is '${serverDefaults.db}'`,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.pipe(Options.withDefault("sync-db/storage.db"));
|
.pipe(Options.withDefault(serverDefaults.db));
|
||||||
|
|
||||||
const startSyncServerCommand = Command.make(
|
const startSyncServerCommand = Command.make(
|
||||||
"sync",
|
"sync",
|
||||||
{ port: portOption, inMemory: inMemoryOption, db: dbOption },
|
{
|
||||||
({ port, inMemory, db }) => {
|
host: hostOption,
|
||||||
|
port: portOption,
|
||||||
|
inMemory: inMemoryOption,
|
||||||
|
db: dbOption,
|
||||||
|
},
|
||||||
|
({ host, port, inMemory, db }) => {
|
||||||
return Effect.gen(function* () {
|
return Effect.gen(function* () {
|
||||||
yield* Effect.promise(() => startSyncServer({ port, inMemory, db }));
|
const server = yield* Effect.promise(() =>
|
||||||
|
startSyncServer({ host, port, inMemory, db }),
|
||||||
|
);
|
||||||
|
|
||||||
|
const serverAddress = server.address();
|
||||||
|
|
||||||
|
if (!serverAddress) {
|
||||||
|
return yield* Effect.fail(new Error("Failed to start sync server."));
|
||||||
|
}
|
||||||
|
|
||||||
|
const socketAddress =
|
||||||
|
typeof serverAddress === "object"
|
||||||
|
? `${serverAddress.address}:${serverAddress.port}`
|
||||||
|
: serverAddress;
|
||||||
|
|
||||||
yield* Console.log(
|
yield* Console.log(
|
||||||
`COJSON sync server listening on ws://127.0.0.1:${port}`,
|
`COJSON sync server listening on ws://${socketAddress}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Keep the server up
|
// Keep the server up
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createServer, type Server } from "node:http";
|
import { createServer } from "node:http";
|
||||||
import { mkdir } from "node:fs/promises";
|
import { mkdir } from "node:fs/promises";
|
||||||
import { dirname } from "node:path";
|
import { dirname } from "node:path";
|
||||||
import { LocalNode } from "cojson";
|
import { LocalNode } from "cojson";
|
||||||
@@ -6,16 +6,19 @@ import { getBetterSqliteStorage } from "cojson-storage-sqlite";
|
|||||||
import { createWebSocketPeer } from "cojson-transport-ws";
|
import { createWebSocketPeer } from "cojson-transport-ws";
|
||||||
import { WasmCrypto } from "cojson/crypto/WasmCrypto";
|
import { WasmCrypto } from "cojson/crypto/WasmCrypto";
|
||||||
import { WebSocketServer } from "ws";
|
import { WebSocketServer } from "ws";
|
||||||
|
import { type SyncServer } from "./types.js";
|
||||||
|
|
||||||
export const startSyncServer = async ({
|
export const startSyncServer = async ({
|
||||||
|
host,
|
||||||
port,
|
port,
|
||||||
inMemory,
|
inMemory,
|
||||||
db,
|
db,
|
||||||
}: {
|
}: {
|
||||||
|
host: string | undefined;
|
||||||
port: string | undefined;
|
port: string | undefined;
|
||||||
inMemory: boolean;
|
inMemory: boolean;
|
||||||
db: string;
|
db: string;
|
||||||
}) => {
|
}): Promise<SyncServer> => {
|
||||||
const crypto = await WasmCrypto.create();
|
const crypto = await WasmCrypto.create();
|
||||||
|
|
||||||
const server = createServer((req, res) => {
|
const server = createServer((req, res) => {
|
||||||
@@ -94,8 +97,6 @@ export const startSyncServer = async ({
|
|||||||
localNode.gracefulShutdown();
|
localNode.gracefulShutdown();
|
||||||
});
|
});
|
||||||
|
|
||||||
server.listen(port ? parseInt(port) : undefined);
|
|
||||||
|
|
||||||
const _close = server.close;
|
const _close = server.close;
|
||||||
|
|
||||||
server.close = () => {
|
server.close = () => {
|
||||||
@@ -106,5 +107,11 @@ export const startSyncServer = async ({
|
|||||||
|
|
||||||
Object.defineProperty(server, "localNode", { value: localNode });
|
Object.defineProperty(server, "localNode", { value: localNode });
|
||||||
|
|
||||||
return server as Server & { localNode: LocalNode };
|
server.listen(port ? parseInt(port) : undefined, host);
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
server.once("listening", () => {
|
||||||
|
resolve(server as SyncServer);
|
||||||
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,11 +5,13 @@ import { describe, expect, it, onTestFinished } from "vitest";
|
|||||||
import { WebSocket } from "ws";
|
import { WebSocket } from "ws";
|
||||||
import { createWorkerAccount } from "../createWorkerAccount.js";
|
import { createWorkerAccount } from "../createWorkerAccount.js";
|
||||||
import { startSyncServer } from "../startSyncServer.js";
|
import { startSyncServer } from "../startSyncServer.js";
|
||||||
|
import { serverDefaults } from "../config.js";
|
||||||
|
|
||||||
describe("createWorkerAccount - integration tests", () => {
|
describe("createWorkerAccount - integration tests", () => {
|
||||||
it("should create a worker account using the local sync server", async () => {
|
it("should create a worker account using the local sync server", async () => {
|
||||||
// Pass port: undefined to let the server choose a random port
|
// Pass port: undefined to let the server choose a random port
|
||||||
const server = await startSyncServer({
|
const server = await startSyncServer({
|
||||||
|
host: serverDefaults.host,
|
||||||
port: undefined,
|
port: undefined,
|
||||||
inMemory: true,
|
inMemory: true,
|
||||||
db: "",
|
db: "",
|
||||||
|
|||||||
@@ -1,24 +1,29 @@
|
|||||||
import { randomUUID } from "crypto";
|
import { randomUUID } from "crypto";
|
||||||
import { tmpdir } from "os";
|
import { tmpdir } from "os";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import { LocalNode } from "cojson";
|
|
||||||
import { co, z } from "jazz-tools";
|
import { co, z } from "jazz-tools";
|
||||||
import { startWorker } from "jazz-tools/worker";
|
import { startWorker } from "jazz-tools/worker";
|
||||||
import { describe, expect, test } from "vitest";
|
import { describe, expect, test, afterAll } from "vitest";
|
||||||
import { createWorkerAccount } from "../createWorkerAccount.js";
|
import { createWorkerAccount } from "../createWorkerAccount.js";
|
||||||
import { startSyncServer } from "../startSyncServer.js";
|
import { startSyncServer } from "../startSyncServer.js";
|
||||||
|
import { serverDefaults } from "../config.js";
|
||||||
|
import { unlinkSync } from "node:fs";
|
||||||
|
|
||||||
const TestMap = co.map({
|
const TestMap = co.map({
|
||||||
value: z.string(),
|
value: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const dbPath = join(tmpdir(), `test-${randomUUID()}.db`);
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
unlinkSync(dbPath);
|
||||||
|
});
|
||||||
|
|
||||||
describe("startSyncServer", () => {
|
describe("startSyncServer", () => {
|
||||||
test("persists values in storage and loads them after restart", async () => {
|
test("persists values in storage and loads them after restart", async () => {
|
||||||
// Create a temporary database file
|
|
||||||
const dbPath = join(tmpdir(), `test-${randomUUID()}.db`);
|
|
||||||
|
|
||||||
// Start first server instance
|
// Start first server instance
|
||||||
const server1 = await startSyncServer({
|
const server1 = await startSyncServer({
|
||||||
|
host: serverDefaults.host,
|
||||||
port: "0", // Random available port
|
port: "0", // Random available port
|
||||||
inMemory: false,
|
inMemory: false,
|
||||||
db: dbPath,
|
db: dbPath,
|
||||||
@@ -48,6 +53,7 @@ describe("startSyncServer", () => {
|
|||||||
|
|
||||||
// Start second server instance with same DB
|
// Start second server instance with same DB
|
||||||
const server2 = await startSyncServer({
|
const server2 = await startSyncServer({
|
||||||
|
host: serverDefaults.host,
|
||||||
port: "0",
|
port: "0",
|
||||||
inMemory: false,
|
inMemory: false,
|
||||||
db: dbPath,
|
db: dbPath,
|
||||||
@@ -74,4 +80,21 @@ describe("startSyncServer", () => {
|
|||||||
await worker2.done();
|
await worker2.done();
|
||||||
server2.close();
|
server2.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("starts a sync server with a specific host and port", async () => {
|
||||||
|
const server = await startSyncServer({
|
||||||
|
host: "0.0.0.0",
|
||||||
|
port: "4900",
|
||||||
|
inMemory: false,
|
||||||
|
db: dbPath,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(server.address()).toEqual({
|
||||||
|
address: "0.0.0.0",
|
||||||
|
port: 4900,
|
||||||
|
family: "IPv4",
|
||||||
|
});
|
||||||
|
|
||||||
|
server.close();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { afterAll, describe, expect, onTestFinished, test } from "vitest";
|
|||||||
import { createWorkerAccount } from "../createWorkerAccount.js";
|
import { createWorkerAccount } from "../createWorkerAccount.js";
|
||||||
import { startSyncServer } from "../startSyncServer.js";
|
import { startSyncServer } from "../startSyncServer.js";
|
||||||
import { waitFor } from "./utils.js";
|
import { waitFor } from "./utils.js";
|
||||||
|
import { serverDefaults } from "../config.js";
|
||||||
|
|
||||||
const dbPath = join(tmpdir(), `test-${randomUUID()}.db`);
|
const dbPath = join(tmpdir(), `test-${randomUUID()}.db`);
|
||||||
|
|
||||||
@@ -30,9 +31,9 @@ async function setup<
|
|||||||
| (AccountClass<Account> & CoValueFromRaw<Account>)
|
| (AccountClass<Account> & CoValueFromRaw<Account>)
|
||||||
| AnyAccountSchema,
|
| AnyAccountSchema,
|
||||||
>(AccountSchema?: S) {
|
>(AccountSchema?: S) {
|
||||||
const { server, port } = await setupSyncServer();
|
const { server, port, host } = await setupSyncServer();
|
||||||
|
|
||||||
const syncServer = `ws://localhost:${port}`;
|
const syncServer = `ws://${host}:${port}`;
|
||||||
|
|
||||||
const { worker, done, waitForConnection, subscribeToConnectionChange } =
|
const { worker, done, waitForConnection, subscribeToConnectionChange } =
|
||||||
await setupWorker(syncServer, AccountSchema);
|
await setupWorker(syncServer, AccountSchema);
|
||||||
@@ -43,13 +44,18 @@ async function setup<
|
|||||||
syncServer,
|
syncServer,
|
||||||
server,
|
server,
|
||||||
port,
|
port,
|
||||||
|
host,
|
||||||
waitForConnection,
|
waitForConnection,
|
||||||
subscribeToConnectionChange,
|
subscribeToConnectionChange,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setupSyncServer(defaultPort = "0") {
|
async function setupSyncServer(
|
||||||
|
defaultHost = serverDefaults.host,
|
||||||
|
defaultPort = "0",
|
||||||
|
) {
|
||||||
const server = await startSyncServer({
|
const server = await startSyncServer({
|
||||||
|
host: defaultHost,
|
||||||
port: defaultPort,
|
port: defaultPort,
|
||||||
inMemory: false,
|
inMemory: false,
|
||||||
db: dbPath,
|
db: dbPath,
|
||||||
@@ -61,7 +67,7 @@ async function setupSyncServer(defaultPort = "0") {
|
|||||||
server.close();
|
server.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
return { server, port };
|
return { server, port, host: defaultHost };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setupWorker<
|
async function setupWorker<
|
||||||
@@ -258,6 +264,7 @@ describe("startWorker integration", () => {
|
|||||||
|
|
||||||
// Start a new sync server on the same port
|
// Start a new sync server on the same port
|
||||||
const newServer = await startSyncServer({
|
const newServer = await startSyncServer({
|
||||||
|
host: worker1.host,
|
||||||
port: worker1.port,
|
port: worker1.port,
|
||||||
inMemory: true,
|
inMemory: true,
|
||||||
db: "",
|
db: "",
|
||||||
@@ -290,6 +297,7 @@ describe("startWorker integration", () => {
|
|||||||
|
|
||||||
// Start a new sync server on the same port
|
// Start a new sync server on the same port
|
||||||
const newServer = await startSyncServer({
|
const newServer = await startSyncServer({
|
||||||
|
host: worker1.host,
|
||||||
port: worker1.port,
|
port: worker1.port,
|
||||||
inMemory: true,
|
inMemory: true,
|
||||||
db: "",
|
db: "",
|
||||||
@@ -326,6 +334,7 @@ describe("startWorker integration", () => {
|
|||||||
|
|
||||||
// Start a new sync server on the same port
|
// Start a new sync server on the same port
|
||||||
const newServer = await startSyncServer({
|
const newServer = await startSyncServer({
|
||||||
|
host: worker1.host,
|
||||||
port: worker1.port,
|
port: worker1.port,
|
||||||
inMemory: true,
|
inMemory: true,
|
||||||
db: "",
|
db: "",
|
||||||
|
|||||||
4
packages/jazz-run/src/types.ts
Normal file
4
packages/jazz-run/src/types.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { type Server } from "node:http";
|
||||||
|
import { type LocalNode } from "cojson";
|
||||||
|
|
||||||
|
export type SyncServer = Server & { localNode: LocalNode };
|
||||||
@@ -1,5 +1,34 @@
|
|||||||
# jazz-tools
|
# 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
|
||||||
|
|
||||||
|
- cojson@0.17.7
|
||||||
|
- cojson-storage-indexeddb@0.17.7
|
||||||
|
- cojson-transport-ws@0.17.7
|
||||||
|
|
||||||
## 0.17.6
|
## 0.17.6
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -140,7 +140,7 @@
|
|||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"version": "0.17.6",
|
"version": "0.17.9",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@manuscripts/prosemirror-recreate-steps": "^0.1.4",
|
"@manuscripts/prosemirror-recreate-steps": "^0.1.4",
|
||||||
"@scure/base": "1.2.1",
|
"@scure/base": "1.2.1",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { recreateTransform } from "@manuscripts/prosemirror-recreate-steps";
|
import { recreateTransform } from "@manuscripts/prosemirror-recreate-steps";
|
||||||
import { CoRichText } from "jazz-tools";
|
import { CoRichText } from "jazz-tools";
|
||||||
import { Transaction } from "prosemirror-state";
|
import { EditorState, Transaction } from "prosemirror-state";
|
||||||
import { EditorView } from "prosemirror-view";
|
import { EditorView } from "prosemirror-view";
|
||||||
import { htmlToProseMirror, proseMirrorToHtml } from "./converter.js";
|
import { htmlToProseMirror, proseMirrorToHtml } from "./converter.js";
|
||||||
|
|
||||||
@@ -34,6 +34,8 @@ export const META_KEY = "fromJazz";
|
|||||||
export function createSyncHandlers(coRichText: CoRichText | undefined) {
|
export function createSyncHandlers(coRichText: CoRichText | undefined) {
|
||||||
// Store the editor view in a closure
|
// Store the editor view in a closure
|
||||||
let view: EditorView | undefined;
|
let view: EditorView | undefined;
|
||||||
|
let localChange = false;
|
||||||
|
let remoteChange = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles changes from CoRichText by updating the ProseMirror editor.
|
* 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
|
* @param newText - The updated CoRichText instance
|
||||||
*/
|
*/
|
||||||
function handleCoRichTextChange(newText: CoRichText) {
|
function handleCoRichTextChange(newText: CoRichText) {
|
||||||
if (!view || !newText) return;
|
if (!view || !newText || localChange || remoteChange) return;
|
||||||
|
|
||||||
const pmDoc = htmlToProseMirror(
|
const currentView = view;
|
||||||
newText.toString(),
|
remoteChange = true;
|
||||||
view.state.doc.type.schema,
|
|
||||||
);
|
|
||||||
const transform = recreateTransform(view.state.doc, pmDoc);
|
|
||||||
|
|
||||||
// Create a new transaction
|
// Changes on CoPlainText are emitted word by word, which means that it creates
|
||||||
const tr = view.state.tr;
|
// 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
|
try {
|
||||||
transform.steps.forEach((step) => {
|
const transform = recreateTransform(currentView.state.doc, pmDoc);
|
||||||
tr.step(step);
|
|
||||||
|
// 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) {
|
if (tr.docChanged) {
|
||||||
const str = proseMirrorToHtml(tr.doc);
|
const str = proseMirrorToHtml(tr.doc);
|
||||||
coRichText.applyDiff(str);
|
localChange = true;
|
||||||
|
try {
|
||||||
|
coRichText.applyDiff(str);
|
||||||
|
} finally {
|
||||||
|
localChange = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,29 +1,33 @@
|
|||||||
// @vitest-environment jsdom
|
// @vitest-environment jsdom
|
||||||
|
|
||||||
import { Account, CoRichText } from "jazz-tools";
|
import { CoRichText } from "jazz-tools";
|
||||||
import { createJazzTestAccount, setupJazzTestSync } from "jazz-tools/testing";
|
import { createJazzTestAccount, setupJazzTestSync } from "jazz-tools/testing";
|
||||||
import { schema } from "prosemirror-schema-basic";
|
|
||||||
import { EditorState, TextSelection } from "prosemirror-state";
|
import { EditorState, TextSelection } from "prosemirror-state";
|
||||||
import { Plugin } from "prosemirror-state";
|
|
||||||
import { EditorView } from "prosemirror-view";
|
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 { 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;
|
const schema = new Schema({
|
||||||
let coRichText: CoRichText;
|
nodes: addListNodes(basicSchema.spec.nodes, "paragraph block*", "block"),
|
||||||
let plugin: Plugin;
|
marks: basicSchema.spec.marks,
|
||||||
let state: EditorState;
|
});
|
||||||
let view: EditorView;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await setupJazzTestSync();
|
|
||||||
account = await createJazzTestAccount({ isCurrentActiveAccount: true });
|
|
||||||
|
|
||||||
|
async function setupTest(initialContent = "<p>Hello</p>") {
|
||||||
// Create a real CoRichText with the test account as owner
|
// 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);
|
const plugin = createJazzPlugin(coRichText);
|
||||||
state = EditorState.create({
|
const state = EditorState.create({
|
||||||
schema,
|
schema,
|
||||||
plugins: [plugin],
|
plugins: [plugin],
|
||||||
});
|
});
|
||||||
@@ -33,25 +37,32 @@ beforeEach(async () => {
|
|||||||
document.body.appendChild(editorElement);
|
document.body.appendChild(editorElement);
|
||||||
|
|
||||||
// Initialize the editor view
|
// Initialize the editor view
|
||||||
view = new EditorView(editorElement, {
|
const view = new EditorView(editorElement, {
|
||||||
state,
|
state,
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
onTestFinished(() => {
|
||||||
// Clean up the editor view
|
|
||||||
if (view) {
|
|
||||||
view.destroy();
|
view.destroy();
|
||||||
view.dom.remove();
|
editorElement.remove();
|
||||||
}
|
});
|
||||||
|
|
||||||
|
return { coRichText, plugin, state, view, editorElement };
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await setupJazzTestSync();
|
||||||
|
await createJazzTestAccount({ isCurrentActiveAccount: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("createJazzPlugin", () => {
|
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");
|
expect(state.doc.textContent).toContain("Hello");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("updates editor when CoRichText changes", async () => {
|
it("updates editor when CoRichText changes", async () => {
|
||||||
|
const { coRichText, view } = await setupTest();
|
||||||
|
|
||||||
// Update CoRichText content
|
// Update CoRichText content
|
||||||
coRichText.applyDiff("<p>Updated content</p>");
|
coRichText.applyDiff("<p>Updated content</p>");
|
||||||
|
|
||||||
@@ -61,7 +72,9 @@ describe("createJazzPlugin", () => {
|
|||||||
expect(view.state.doc.textContent).toContain("Updated content");
|
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
|
// Create a transaction to update the editor content
|
||||||
const tr = view.state.tr.insertText(" World", 6);
|
const tr = view.state.tr.insertText(" World", 6);
|
||||||
view.dispatch(tr);
|
view.dispatch(tr);
|
||||||
@@ -70,8 +83,8 @@ describe("createJazzPlugin", () => {
|
|||||||
expect(coRichText.toString()).toContain("Hello World");
|
expect(coRichText.toString()).toContain("Hello World");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("handles empty CoRichText initialization", () => {
|
it("handles empty CoRichText initialization", async () => {
|
||||||
const emptyCoRichText = CoRichText.create("", account);
|
const emptyCoRichText = CoRichText.create("");
|
||||||
const emptyPlugin = createJazzPlugin(emptyCoRichText);
|
const emptyPlugin = createJazzPlugin(emptyCoRichText);
|
||||||
const emptyState = EditorState.create({
|
const emptyState = EditorState.create({
|
||||||
schema,
|
schema,
|
||||||
@@ -81,7 +94,7 @@ describe("createJazzPlugin", () => {
|
|||||||
expect(emptyState.doc.textContent).toBe("");
|
expect(emptyState.doc.textContent).toBe("");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("handles undefined CoRichText", () => {
|
it("handles undefined CoRichText", async () => {
|
||||||
const undefinedPlugin = createJazzPlugin(undefined);
|
const undefinedPlugin = createJazzPlugin(undefined);
|
||||||
const undefinedState = EditorState.create({
|
const undefinedState = EditorState.create({
|
||||||
schema,
|
schema,
|
||||||
@@ -91,7 +104,9 @@ describe("createJazzPlugin", () => {
|
|||||||
expect(undefinedState.doc.textContent).toBe("");
|
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
|
// Create a transaction that would normally trigger a CoRichText update
|
||||||
const tr = view.state.tr.insertText(" Loop", 6);
|
const tr = view.state.tr.insertText(" Loop", 6);
|
||||||
|
|
||||||
@@ -106,7 +121,9 @@ describe("createJazzPlugin", () => {
|
|||||||
expect(coRichText.toString()).not.toContain("Loop");
|
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
|
// Set a selection in the editor
|
||||||
const tr = view.state.tr.setSelection(
|
const tr = view.state.tr.setSelection(
|
||||||
TextSelection.create(view.state.doc, 2, 5),
|
TextSelection.create(view.state.doc, 2, 5),
|
||||||
@@ -118,10 +135,49 @@ describe("createJazzPlugin", () => {
|
|||||||
expect(view.state.selection.to).toBe(5);
|
expect(view.state.selection.to).toBe(5);
|
||||||
|
|
||||||
// Update CoRichText content
|
// 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
|
// Verify selection is preserved after content update
|
||||||
expect(view.state.selection.from).toBe(2);
|
expect(view.state.selection.from).toBe(2);
|
||||||
expect(view.state.selection.to).toBe(5);
|
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>",
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
} from "jazz-tools";
|
} from "jazz-tools";
|
||||||
import { LocalStorageKVStore } from "jazz-tools/browser";
|
import { LocalStorageKVStore } from "jazz-tools/browser";
|
||||||
import { useAuthSecretStorage, useJazzContext } from "jazz-tools/react-core";
|
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";
|
import { JazzProviderProps, JazzReactProvider } from "../provider.js";
|
||||||
|
|
||||||
function useJazzClerkAuth(clerk: MinimalClerkClient) {
|
function useJazzClerkAuth(clerk: MinimalClerkClient) {
|
||||||
@@ -43,7 +43,9 @@ export const JazzReactProviderWithClerk = <
|
|||||||
| (AccountClass<Account> & CoValueFromRaw<Account>)
|
| (AccountClass<Account> & CoValueFromRaw<Account>)
|
||||||
| AnyAccountSchema,
|
| AnyAccountSchema,
|
||||||
>(
|
>(
|
||||||
props: { clerk: MinimalClerkClient } & JazzProviderProps<S>,
|
props: {
|
||||||
|
clerk: MinimalClerkClient;
|
||||||
|
} & JazzProviderProps<S>,
|
||||||
) => {
|
) => {
|
||||||
const [isLoaded, setIsLoaded] = useState(false);
|
const [isLoaded, setIsLoaded] = useState(false);
|
||||||
|
|
||||||
@@ -61,7 +63,7 @@ export const JazzReactProviderWithClerk = <
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (!isLoaded) {
|
if (!isLoaded) {
|
||||||
return null;
|
return props.fallback ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export type JazzProviderProps<
|
|||||||
> = {
|
> = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
enableSSR?: boolean;
|
enableSSR?: boolean;
|
||||||
|
fallback?: React.ReactNode | null;
|
||||||
} & JazzContextManagerProps<S>;
|
} & JazzContextManagerProps<S>;
|
||||||
|
|
||||||
/** @category Context & Hooks */
|
/** @category Context & Hooks */
|
||||||
@@ -38,6 +39,7 @@ export function JazzReactProvider<
|
|||||||
logOutReplacement,
|
logOutReplacement,
|
||||||
onAnonymousAccountDiscarded,
|
onAnonymousAccountDiscarded,
|
||||||
enableSSR,
|
enableSSR,
|
||||||
|
fallback = null,
|
||||||
}: JazzProviderProps<S>) {
|
}: JazzProviderProps<S>) {
|
||||||
const [contextManager] = React.useState(
|
const [contextManager] = React.useState(
|
||||||
() =>
|
() =>
|
||||||
@@ -100,7 +102,7 @@ export function JazzReactProvider<
|
|||||||
return (
|
return (
|
||||||
<JazzContext.Provider value={value}>
|
<JazzContext.Provider value={value}>
|
||||||
<JazzContextManagerContext.Provider value={contextManager}>
|
<JazzContextManagerContext.Provider value={contextManager}>
|
||||||
{value && children}
|
{value ? children : fallback}
|
||||||
</JazzContextManagerContext.Provider>
|
</JazzContextManagerContext.Provider>
|
||||||
</JazzContext.Provider>
|
</JazzContext.Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -628,7 +628,11 @@ export class CoMap extends CoValueBase implements CoValue {
|
|||||||
resolve?: RefsToResolveStrict<M, R>;
|
resolve?: RefsToResolveStrict<M, R>;
|
||||||
},
|
},
|
||||||
): Promise<Resolved<M, R> | null> {
|
): 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, {
|
let map: Resolved<M, R> | null = await loadCoValueWithoutMe(this, mapId, {
|
||||||
...options,
|
...options,
|
||||||
loadAs: options.owner._loadedAs,
|
loadAs: options.owner._loadedAs,
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export type {
|
|||||||
TextPos,
|
TextPos,
|
||||||
AccountClass,
|
AccountClass,
|
||||||
AccountCreationProps,
|
AccountCreationProps,
|
||||||
|
BaseProfileShape,
|
||||||
} from "./internal.js";
|
} from "./internal.js";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|||||||
@@ -38,9 +38,14 @@ import {
|
|||||||
// Note: if you're editing this function, edit the `isAnyCoValueSchema`
|
// Note: if you're editing this function, edit the `isAnyCoValueSchema`
|
||||||
// function in `zodReExport.ts` as well
|
// function in `zodReExport.ts` as well
|
||||||
export function isAnyCoValueSchema(
|
export function isAnyCoValueSchema(
|
||||||
schema: AnyZodOrCoValueSchema | CoValueClass,
|
schema: unknown,
|
||||||
): schema is AnyCoreCoValueSchema {
|
): 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(
|
export function isCoValueSchema(
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
createCoreCoPlainTextSchema,
|
createCoreCoPlainTextSchema,
|
||||||
createCoreFileStreamSchema,
|
createCoreFileStreamSchema,
|
||||||
hydrateCoreCoValueSchema,
|
hydrateCoreCoValueSchema,
|
||||||
|
isAnyCoValueSchema,
|
||||||
} from "../../internal.js";
|
} from "../../internal.js";
|
||||||
import {
|
import {
|
||||||
CoDiscriminatedUnionSchema,
|
CoDiscriminatedUnionSchema,
|
||||||
@@ -36,6 +37,11 @@ import { z } from "./zodReExport.js";
|
|||||||
export const coMapDefiner = <Shape extends z.core.$ZodLooseShape>(
|
export const coMapDefiner = <Shape extends z.core.$ZodLooseShape>(
|
||||||
shape: Shape,
|
shape: Shape,
|
||||||
): CoMapSchema<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);
|
const coreSchema = createCoreCoMapSchema(shape);
|
||||||
return hydrateCoreCoValueSchema(coreSchema);
|
return hydrateCoreCoValueSchema(coreSchema);
|
||||||
};
|
};
|
||||||
@@ -116,6 +122,11 @@ export const coProfileDefiner = <
|
|||||||
>(
|
>(
|
||||||
shape: Shape & Partial<DefaultProfileShape> = {} as any,
|
shape: Shape & Partial<DefaultProfileShape> = {} as any,
|
||||||
): CoProfileSchema<Shape> => {
|
): 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, {
|
const ehnancedShape = Object.assign(shape, {
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
inbox: z.optional(z.string()),
|
inbox: z.optional(z.string()),
|
||||||
|
|||||||
@@ -88,6 +88,11 @@ function containsCoValueSchema(shape?: core.$ZodLooseShape): boolean {
|
|||||||
|
|
||||||
// Note: if you're editing this function, edit the `isAnyCoValueSchema`
|
// Note: if you're editing this function, edit the `isAnyCoValueSchema`
|
||||||
// function in `zodSchemaToCoSchema.ts` as well
|
// function in `zodSchemaToCoSchema.ts` as well
|
||||||
function isAnyCoValueSchema(schema: any): boolean {
|
function isAnyCoValueSchema(schema: unknown): boolean {
|
||||||
return "collaborative" in schema && schema.collaborative === true;
|
return (
|
||||||
|
typeof schema === "object" &&
|
||||||
|
schema !== null &&
|
||||||
|
"collaborative" in schema &&
|
||||||
|
schema.collaborative === true
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,6 +137,12 @@ test("loading raw accounts should work", async () => {
|
|||||||
expect(loadedAccount.profile!.name).toBe("test 1");
|
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 () => {
|
test("should support recursive props on co.profile", async () => {
|
||||||
const User = co.profile({
|
const User = co.profile({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
|
|||||||
@@ -2325,6 +2325,12 @@ describe("co.map schema", () => {
|
|||||||
expect(draftPerson.extraField).toEqual("extra");
|
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", () => {
|
describe("Updating a nested reference", () => {
|
||||||
|
|||||||
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
@@ -2038,19 +2038,19 @@ importers:
|
|||||||
specifier: ^0.25.5
|
specifier: ^0.25.5
|
||||||
version: 0.25.8(effect@3.11.9)
|
version: 0.25.8(effect@3.11.9)
|
||||||
cojson:
|
cojson:
|
||||||
specifier: workspace:0.17.6
|
specifier: workspace:0.17.9
|
||||||
version: link:../cojson
|
version: link:../cojson
|
||||||
cojson-storage-sqlite:
|
cojson-storage-sqlite:
|
||||||
specifier: workspace:0.17.6
|
specifier: workspace:0.17.9
|
||||||
version: link:../cojson-storage-sqlite
|
version: link:../cojson-storage-sqlite
|
||||||
cojson-transport-ws:
|
cojson-transport-ws:
|
||||||
specifier: workspace:0.17.6
|
specifier: workspace:0.17.9
|
||||||
version: link:../cojson-transport-ws
|
version: link:../cojson-transport-ws
|
||||||
effect:
|
effect:
|
||||||
specifier: ^3.6.5
|
specifier: ^3.6.5
|
||||||
version: 3.11.9
|
version: 3.11.9
|
||||||
jazz-tools:
|
jazz-tools:
|
||||||
specifier: workspace:0.17.6
|
specifier: workspace:0.17.9
|
||||||
version: link:../jazz-tools
|
version: link:../jazz-tools
|
||||||
ws:
|
ws:
|
||||||
specifier: ^8.14.2
|
specifier: ^8.14.2
|
||||||
|
|||||||
@@ -1,5 +1,27 @@
|
|||||||
# jazz-react-tailwind-starter
|
# 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
|
||||||
|
|
||||||
|
- jazz-tools@0.17.7
|
||||||
|
|
||||||
## 0.0.150
|
## 0.0.150
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "jazz-react-passkey-auth-starter",
|
"name": "jazz-react-passkey-auth-starter",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.150",
|
"version": "0.0.153",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -1,5 +1,27 @@
|
|||||||
# svelte-passkey-auth
|
# 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
|
||||||
|
|
||||||
|
- jazz-tools@0.17.7
|
||||||
|
|
||||||
## 0.0.124
|
## 0.0.124
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "svelte-passkey-auth",
|
"name": "svelte-passkey-auth",
|
||||||
"version": "0.0.124",
|
"version": "0.0.127",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
Reference in New Issue
Block a user