Compare commits
8 Commits
cojson-sto
...
chat-rn@1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31614c0a4f | ||
|
|
57b69eb8da | ||
|
|
066676c243 | ||
|
|
e141024656 | ||
|
|
2c48ae0434 | ||
|
|
2bf974390d | ||
|
|
e123715819 | ||
|
|
0d087f3d4c |
@@ -1,5 +1,14 @@
|
||||
# betterauth
|
||||
|
||||
## 0.1.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-betterauth-server-plugin@0.13.32
|
||||
- jazz-react@0.13.32
|
||||
- jazz-react-auth-betterauth@0.13.32
|
||||
- jazz-betterauth-client-plugin@0.13.32
|
||||
|
||||
## 0.1.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "betterauth",
|
||||
"version": "0.1.3",
|
||||
"version": "0.1.4",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# chat-rn-expo-clerk
|
||||
|
||||
## 1.0.123
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-expo@0.13.32
|
||||
|
||||
## 1.0.122
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "chat-rn-expo-clerk",
|
||||
"main": "index.js",
|
||||
"version": "1.0.122",
|
||||
"version": "1.0.123",
|
||||
"scripts": {
|
||||
"build": "expo export -p ios",
|
||||
"start": "expo start",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# chat-rn-expo
|
||||
|
||||
## 1.0.110
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-expo@0.13.32
|
||||
|
||||
## 1.0.109
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "chat-rn-expo",
|
||||
"version": "1.0.109",
|
||||
"version": "1.0.110",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "expo export -p ios",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# chat-rn
|
||||
|
||||
## 1.0.118
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-native@0.13.32
|
||||
|
||||
## 1.0.117
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "chat-rn",
|
||||
"version": "1.0.117",
|
||||
"version": "1.0.118",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"android": "react-native run-android",
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# chat-vue
|
||||
|
||||
## 0.0.101
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-browser@0.13.32
|
||||
- jazz-vue@0.13.32
|
||||
|
||||
## 0.0.100
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "chat-vue",
|
||||
"version": "0.0.100",
|
||||
"version": "0.0.101",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# jazz-example-chat
|
||||
|
||||
## 0.0.199
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.13.32
|
||||
|
||||
## 0.0.198
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-chat",
|
||||
"private": true,
|
||||
"version": "0.0.198",
|
||||
"version": "0.0.199",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# minimal-auth-clerk
|
||||
|
||||
## 0.0.98
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.13.32
|
||||
- jazz-react-auth-clerk@0.13.32
|
||||
|
||||
## 0.0.97
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "clerk",
|
||||
"private": true,
|
||||
"version": "0.0.97",
|
||||
"version": "0.0.98",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# file-share-svelte
|
||||
|
||||
## 0.0.82
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-svelte@0.13.32
|
||||
|
||||
## 0.0.81
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "file-share-svelte",
|
||||
"version": "0.0.81",
|
||||
"version": "0.0.82",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# jazz-tailwind-demo-auth-starter
|
||||
|
||||
## 0.0.38
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.13.32
|
||||
|
||||
## 0.0.37
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "filestream",
|
||||
"private": true,
|
||||
"version": "0.0.37",
|
||||
"version": "0.0.38",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# form
|
||||
|
||||
## 0.1.39
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.13.32
|
||||
|
||||
## 0.1.38
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "form",
|
||||
"private": true,
|
||||
"version": "0.1.38",
|
||||
"version": "0.1.39",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# image-upload
|
||||
|
||||
## 0.0.95
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.13.32
|
||||
|
||||
## 0.0.94
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "image-upload",
|
||||
"private": true,
|
||||
"version": "0.0.94",
|
||||
"version": "0.0.95",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# multi-cursors
|
||||
|
||||
## 0.0.91
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.13.32
|
||||
|
||||
## 0.0.90
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "multi-cursors",
|
||||
"private": true,
|
||||
"version": "0.0.90",
|
||||
"version": "0.0.91",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# multiauth
|
||||
|
||||
## 0.0.39
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.13.32
|
||||
- jazz-react-auth-clerk@0.13.32
|
||||
|
||||
## 0.0.38
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "multiauth",
|
||||
"private": true,
|
||||
"version": "0.0.38",
|
||||
"version": "0.0.39",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# jazz-example-musicplayer
|
||||
|
||||
## 0.0.120
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.13.32
|
||||
|
||||
## 0.0.119
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-music-player",
|
||||
"private": true,
|
||||
"version": "0.0.119",
|
||||
"version": "0.0.120",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# organization
|
||||
|
||||
## 0.0.91
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.13.32
|
||||
|
||||
## 0.0.90
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "organization",
|
||||
"private": true,
|
||||
"version": "0.0.90",
|
||||
"version": "0.0.91",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# passkey-svelte
|
||||
|
||||
## 0.0.86
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-svelte@0.13.32
|
||||
|
||||
## 0.0.85
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "passkey-svelte",
|
||||
"version": "0.0.85",
|
||||
"version": "0.0.86",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
<JazzProvider
|
||||
sync={{
|
||||
peer: `wss://cloud.jazz.tools/?key={apiKey}`,
|
||||
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
|
||||
}}
|
||||
>
|
||||
<PasskeyAuthBasicUI appName="minimal-svelte-auth-passkey">
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# minimal-auth-passkey
|
||||
|
||||
## 0.0.96
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.13.32
|
||||
|
||||
## 0.0.95
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "passkey",
|
||||
"private": true,
|
||||
"version": "0.0.95",
|
||||
"version": "0.0.96",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# passphrase
|
||||
|
||||
## 0.0.93
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.13.32
|
||||
|
||||
## 0.0.92
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "passphrase",
|
||||
"private": true,
|
||||
"version": "0.0.92",
|
||||
"version": "0.0.93",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# jazz-password-manager
|
||||
|
||||
## 0.0.117
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.13.32
|
||||
|
||||
## 0.0.116
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-password-manager",
|
||||
"private": true,
|
||||
"version": "0.0.116",
|
||||
"version": "0.0.117",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# jazz-example-pets
|
||||
|
||||
## 0.0.215
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.13.32
|
||||
|
||||
## 0.0.214
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-pets",
|
||||
"private": true,
|
||||
"version": "0.0.214",
|
||||
"version": "0.0.215",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# reactions
|
||||
|
||||
## 0.0.95
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.13.32
|
||||
|
||||
## 0.0.94
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "reactions",
|
||||
"private": true,
|
||||
"version": "0.0.94",
|
||||
"version": "0.0.95",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# richtext-tiptap
|
||||
|
||||
## 0.1.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.13.32
|
||||
- jazz-richtext-tiptap@0.1.8
|
||||
|
||||
## 0.1.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "richtext-tiptap",
|
||||
"private": true,
|
||||
"version": "0.1.7",
|
||||
"version": "0.1.8",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# richtext
|
||||
|
||||
## 0.0.85
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.13.32
|
||||
- jazz-richtext-prosemirror@0.1.19
|
||||
|
||||
## 0.0.84
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "richtext",
|
||||
"private": true,
|
||||
"version": "0.0.84",
|
||||
"version": "0.0.85",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# todo-vue
|
||||
|
||||
## 0.0.99
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-browser@0.13.32
|
||||
- jazz-vue@0.13.32
|
||||
|
||||
## 0.0.98
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "todo-vue",
|
||||
"version": "0.0.98",
|
||||
"version": "0.0.99",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# jazz-example-todo
|
||||
|
||||
## 0.0.214
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.13.32
|
||||
|
||||
## 0.0.213
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-todo",
|
||||
"private": true,
|
||||
"version": "0.0.213",
|
||||
"version": "0.0.214",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# version-history
|
||||
|
||||
## 0.0.93
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.13.32
|
||||
|
||||
## 0.0.92
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "version-history",
|
||||
"private": true,
|
||||
"version": "0.0.92",
|
||||
"version": "0.0.93",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -8,7 +8,7 @@ import { CodeGroup } from "@/components/forMdx";
|
||||
|
||||
Every CoValue has an owner, which can be a `Group` or an `Account`.
|
||||
|
||||
You can use a `Group` to grant access to a CoValue to multiple users. These users can
|
||||
You can use a `Group` to grant access to a CoValue to **multiple users**. These users can
|
||||
have different roles, such as "writer", "reader" or "admin".
|
||||
|
||||
## Creating a Group
|
||||
@@ -43,7 +43,7 @@ group.addMember(bob, "writer");
|
||||
```
|
||||
</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:** 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:
|
||||
|
||||
<CodeGroup>
|
||||
```tsx
|
||||
@@ -66,7 +66,7 @@ group.addMember(bob, "reader");
|
||||
|
||||
Bob just went from a writer to a reader.
|
||||
|
||||
Note: only admins can change a member's role.
|
||||
**Note:** only admins can change a member's role.
|
||||
|
||||
## Removing a member
|
||||
|
||||
@@ -78,9 +78,11 @@ group.removeMember(bob);
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
This only works if you are an admin, and Bob is not an admin.
|
||||
|
||||
Admins cannot remove other admins, but they can remove themselves, as long as there is another admin present.
|
||||
Rules:
|
||||
- All roles can remove themselves.
|
||||
- Only admins can remove other users.
|
||||
- An admin cannot remove other admins.
|
||||
- As an admin, you cannot remove yourself if you are the only admin in the Group, because there has to be at least one admin present.
|
||||
|
||||
## Getting the Group of an existing CoValue
|
||||
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# cojson-storage-indexeddb
|
||||
|
||||
## 0.13.32
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [2bf9743]
|
||||
- cojson-storage@0.13.32
|
||||
|
||||
## 0.13.31
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cojson-storage-indexeddb",
|
||||
"version": "0.13.31",
|
||||
"version": "0.13.32",
|
||||
"main": "dist/index.js",
|
||||
"type": "module",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -67,16 +67,14 @@ export class IDBClient implements DBClientInterfaceAsync {
|
||||
|
||||
async getNewTransactionInSession(
|
||||
sessionRowId: number,
|
||||
firstNewTxIdx: number,
|
||||
fromIdx: number,
|
||||
toIdx: number,
|
||||
): Promise<TransactionRow[]> {
|
||||
return this.makeRequest<TransactionRow[]>((tx) =>
|
||||
tx
|
||||
.getObjectStore("transactions")
|
||||
.getAll(
|
||||
IDBKeyRange.bound(
|
||||
[sessionRowId, firstNewTxIdx],
|
||||
[sessionRowId, Number.POSITIVE_INFINITY],
|
||||
),
|
||||
IDBKeyRange.bound([sessionRowId, fromIdx], [sessionRowId, toIdx]),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -161,7 +159,7 @@ export class IDBClient implements DBClientInterfaceAsync {
|
||||
ses: sessionRowID,
|
||||
idx,
|
||||
signature,
|
||||
} satisfies SignatureAfterRow),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { WasmCrypto } from "cojson/crypto/WasmCrypto";
|
||||
import { expect, test, vi } from "vitest";
|
||||
import { IDBStorage } from "../index.js";
|
||||
import { toSimplifiedMessages } from "./messagesTestUtils.js";
|
||||
import { trackMessages } from "./testUtils.js";
|
||||
import { trackMessages, waitFor } from "./testUtils.js";
|
||||
|
||||
const Crypto = await WasmCrypto.create();
|
||||
|
||||
@@ -96,11 +96,84 @@ test("should sync and load data from storage", async () => {
|
||||
).toMatchInlineSnapshot(`
|
||||
[
|
||||
"client -> LOAD Map sessions: empty",
|
||||
"storage -> KNOWN Group sessions: header/3",
|
||||
"storage -> CONTENT Group header: true new: After: 0 New: 3",
|
||||
"storage -> KNOWN Map sessions: header/1",
|
||||
"storage -> CONTENT Map header: true new: After: 0 New: 1",
|
||||
"client -> KNOWN Group sessions: header/3",
|
||||
]
|
||||
`);
|
||||
|
||||
node2Sync.restore();
|
||||
});
|
||||
|
||||
test("should send an empty content message if there is no content", async () => {
|
||||
const agentSecret = Crypto.newRandomAgentSecret();
|
||||
|
||||
const node1 = new LocalNode(
|
||||
agentSecret,
|
||||
Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)),
|
||||
Crypto,
|
||||
);
|
||||
|
||||
const node1Sync = trackMessages(node1);
|
||||
|
||||
const peer = await IDBStorage.asPeer();
|
||||
|
||||
node1.syncManager.addPeer(peer);
|
||||
|
||||
const group = node1.createGroup();
|
||||
|
||||
const map = group.createMap();
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
|
||||
expect(
|
||||
toSimplifiedMessages(
|
||||
{
|
||||
Map: map.core,
|
||||
Group: group.core,
|
||||
},
|
||||
node1Sync.messages,
|
||||
),
|
||||
).toMatchInlineSnapshot(`
|
||||
[
|
||||
"client -> CONTENT Group header: true new: After: 0 New: 3",
|
||||
"storage -> KNOWN Group sessions: header/3",
|
||||
"client -> CONTENT Map header: true new: ",
|
||||
"storage -> KNOWN Map sessions: header/0",
|
||||
]
|
||||
`);
|
||||
|
||||
node1Sync.restore();
|
||||
|
||||
const node2 = new LocalNode(
|
||||
agentSecret,
|
||||
Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)),
|
||||
Crypto,
|
||||
);
|
||||
|
||||
const node2Sync = trackMessages(node2);
|
||||
|
||||
const peer2 = await IDBStorage.asPeer();
|
||||
|
||||
node2.syncManager.addPeer(peer2);
|
||||
|
||||
const map2 = await node2.load(map.id);
|
||||
if (map2 === "unavailable") {
|
||||
throw new Error("Map is unavailable");
|
||||
}
|
||||
|
||||
expect(
|
||||
toSimplifiedMessages(
|
||||
{
|
||||
Map: map.core,
|
||||
Group: group.core,
|
||||
},
|
||||
node2Sync.messages,
|
||||
),
|
||||
).toMatchInlineSnapshot(`
|
||||
[
|
||||
"client -> LOAD Map sessions: empty",
|
||||
"storage -> CONTENT Group header: true new: After: 0 New: 3",
|
||||
"storage -> CONTENT Map header: true new: ",
|
||||
]
|
||||
`);
|
||||
|
||||
@@ -185,14 +258,9 @@ test("should load dependencies correctly (group inheritance)", async () => {
|
||||
).toMatchInlineSnapshot(`
|
||||
[
|
||||
"client -> LOAD Map sessions: empty",
|
||||
"storage -> KNOWN ParentGroup sessions: header/4",
|
||||
"storage -> CONTENT ParentGroup header: true new: After: 0 New: 4",
|
||||
"storage -> KNOWN Group sessions: header/5",
|
||||
"storage -> CONTENT Group header: true new: After: 0 New: 5",
|
||||
"client -> KNOWN ParentGroup sessions: header/4",
|
||||
"storage -> KNOWN Map sessions: header/1",
|
||||
"storage -> CONTENT Map header: true new: After: 0 New: 1",
|
||||
"client -> KNOWN Group sessions: header/5",
|
||||
]
|
||||
`);
|
||||
});
|
||||
@@ -260,19 +328,14 @@ test("should not send the same dependency value twice", async () => {
|
||||
).toMatchInlineSnapshot(`
|
||||
[
|
||||
"client -> LOAD Map sessions: empty",
|
||||
"storage -> KNOWN ParentGroup sessions: header/4",
|
||||
"storage -> CONTENT ParentGroup header: true new: After: 0 New: 4",
|
||||
"storage -> KNOWN Group sessions: header/5",
|
||||
"storage -> CONTENT Group header: true new: After: 0 New: 5",
|
||||
"client -> KNOWN ParentGroup sessions: header/4",
|
||||
"storage -> KNOWN Map sessions: header/1",
|
||||
"storage -> CONTENT Map header: true new: After: 0 New: 1",
|
||||
"client -> KNOWN ParentGroup sessions: header/4",
|
||||
"client -> KNOWN Group sessions: header/5",
|
||||
"client -> KNOWN Map sessions: header/1",
|
||||
"client -> LOAD MapFromParent sessions: empty",
|
||||
"storage -> KNOWN MapFromParent sessions: header/1",
|
||||
"storage -> CONTENT MapFromParent header: true new: After: 0 New: 1",
|
||||
"client -> KNOWN MapFromParent sessions: header/1",
|
||||
]
|
||||
`);
|
||||
});
|
||||
@@ -374,11 +437,164 @@ test("should recover from data loss", async () => {
|
||||
).toMatchInlineSnapshot(`
|
||||
[
|
||||
"client -> LOAD Map sessions: empty",
|
||||
"storage -> KNOWN Group sessions: header/3",
|
||||
"storage -> CONTENT Group header: true new: After: 0 New: 3",
|
||||
"storage -> KNOWN Map sessions: header/4",
|
||||
"storage -> CONTENT Map header: true new: After: 0 New: 4",
|
||||
"client -> KNOWN Group sessions: header/3",
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test("should sync multiple sessions in a single content message", async () => {
|
||||
const agentSecret = Crypto.newRandomAgentSecret();
|
||||
|
||||
const node1 = new LocalNode(
|
||||
agentSecret,
|
||||
Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)),
|
||||
Crypto,
|
||||
);
|
||||
|
||||
node1.syncManager.addPeer(await IDBStorage.asPeer());
|
||||
|
||||
const group = node1.createGroup();
|
||||
|
||||
const map = group.createMap();
|
||||
|
||||
map.set("hello", "world");
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
|
||||
node1.gracefulShutdown();
|
||||
|
||||
const node2 = new LocalNode(
|
||||
agentSecret,
|
||||
Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)),
|
||||
Crypto,
|
||||
);
|
||||
|
||||
node2.syncManager.addPeer(await IDBStorage.asPeer());
|
||||
|
||||
const map2 = await node2.load(map.id);
|
||||
if (map2 === "unavailable") {
|
||||
throw new Error("Map is unavailable");
|
||||
}
|
||||
|
||||
expect(map2.get("hello")).toBe("world");
|
||||
|
||||
map2.set("hello", "world2");
|
||||
|
||||
await map2.core.waitForSync();
|
||||
|
||||
node2.gracefulShutdown();
|
||||
|
||||
const node3 = new LocalNode(
|
||||
agentSecret,
|
||||
Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)),
|
||||
Crypto,
|
||||
);
|
||||
|
||||
const node3Sync = trackMessages(node3);
|
||||
|
||||
node3.syncManager.addPeer(await IDBStorage.asPeer());
|
||||
|
||||
const map3 = await node3.load(map.id);
|
||||
if (map3 === "unavailable") {
|
||||
throw new Error("Map is unavailable");
|
||||
}
|
||||
|
||||
expect(map3.get("hello")).toBe("world2");
|
||||
|
||||
expect(
|
||||
toSimplifiedMessages(
|
||||
{
|
||||
Map: map.core,
|
||||
Group: group.core,
|
||||
},
|
||||
node3Sync.messages,
|
||||
),
|
||||
).toMatchInlineSnapshot(`
|
||||
[
|
||||
"client -> LOAD Map sessions: empty",
|
||||
"storage -> CONTENT Group header: true new: After: 0 New: 3",
|
||||
"storage -> CONTENT Map header: true new: After: 0 New: 1 | After: 0 New: 1",
|
||||
]
|
||||
`);
|
||||
|
||||
node3Sync.restore();
|
||||
});
|
||||
|
||||
test("large coValue upload streaming", async () => {
|
||||
const agentSecret = Crypto.newRandomAgentSecret();
|
||||
|
||||
const node1 = new LocalNode(
|
||||
agentSecret,
|
||||
Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)),
|
||||
Crypto,
|
||||
);
|
||||
|
||||
node1.syncManager.addPeer(await IDBStorage.asPeer());
|
||||
|
||||
const group = node1.createGroup();
|
||||
const largeMap = group.createMap();
|
||||
|
||||
// Generate a large amount of data (about 100MB)
|
||||
const dataSize = 1 * 1024 * 200;
|
||||
const chunkSize = 1024; // 1KB chunks
|
||||
const chunks = dataSize / chunkSize;
|
||||
|
||||
const value = "a".repeat(chunkSize);
|
||||
|
||||
for (let i = 0; i < chunks; i++) {
|
||||
const key = `key${i}`;
|
||||
largeMap.set(key, value, "trusting");
|
||||
}
|
||||
|
||||
await largeMap.core.waitForSync();
|
||||
|
||||
const knownState = largeMap.core.knownState();
|
||||
|
||||
node1.gracefulShutdown();
|
||||
|
||||
const node2 = new LocalNode(
|
||||
agentSecret,
|
||||
Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)),
|
||||
Crypto,
|
||||
);
|
||||
|
||||
const node2Sync = trackMessages(node2);
|
||||
|
||||
node2.syncManager.addPeer(await IDBStorage.asPeer());
|
||||
|
||||
const largeMapOnNode2 = await node2.load(largeMap.id);
|
||||
|
||||
if (largeMapOnNode2 === "unavailable") {
|
||||
throw new Error("Map is unavailable");
|
||||
}
|
||||
|
||||
await waitFor(() => {
|
||||
expect(largeMapOnNode2.core.knownState()).toEqual(knownState);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
expect(
|
||||
toSimplifiedMessages(
|
||||
{
|
||||
Map: largeMap.core,
|
||||
Group: group.core,
|
||||
},
|
||||
node2Sync.messages,
|
||||
),
|
||||
).toMatchInlineSnapshot(`
|
||||
[
|
||||
"client -> LOAD Map sessions: empty",
|
||||
"storage -> KNOWN Map sessions: header/200",
|
||||
"storage -> CONTENT Group header: true new: After: 0 New: 3",
|
||||
"storage -> CONTENT Map header: true new: After: 0 New: 97",
|
||||
"storage -> CONTENT Map header: true new: After: 97 New: 97",
|
||||
"storage -> CONTENT Map header: true new: After: 194 New: 6",
|
||||
"client -> KNOWN Group sessions: header/3",
|
||||
"client -> KNOWN Map sessions: header/97",
|
||||
"client -> KNOWN Map sessions: header/194",
|
||||
"client -> KNOWN Map sessions: header/200",
|
||||
]
|
||||
`);
|
||||
});
|
||||
@@ -42,3 +42,33 @@ export function trackMessages(node: LocalNode) {
|
||||
restore,
|
||||
};
|
||||
}
|
||||
|
||||
export function waitFor(
|
||||
callback: () => boolean | undefined | Promise<boolean | undefined>,
|
||||
) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const checkPassed = async () => {
|
||||
try {
|
||||
return { ok: await callback(), error: null };
|
||||
} catch (error) {
|
||||
return { ok: false, error };
|
||||
}
|
||||
};
|
||||
|
||||
let retries = 0;
|
||||
|
||||
const interval = setInterval(async () => {
|
||||
const { ok, error } = await checkPassed();
|
||||
|
||||
if (ok !== false) {
|
||||
clearInterval(interval);
|
||||
resolve();
|
||||
}
|
||||
|
||||
if (++retries > 10) {
|
||||
clearInterval(interval);
|
||||
reject(error);
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# cojson-storage-sqlite
|
||||
|
||||
## 0.13.32
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [2bf9743]
|
||||
- cojson-storage@0.13.32
|
||||
|
||||
## 0.13.31
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "cojson-storage-sqlite",
|
||||
"type": "module",
|
||||
"version": "0.13.31",
|
||||
"version": "0.13.32",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -86,13 +86,14 @@ export class SQLiteClient implements DBClientInterfaceSync {
|
||||
|
||||
getNewTransactionInSession(
|
||||
sessionRowId: number,
|
||||
firstNewTxIdx: number,
|
||||
fromIdx: number,
|
||||
toIdx: number,
|
||||
): TransactionRow[] {
|
||||
const txs = this.db
|
||||
.prepare<[number, number]>(
|
||||
"SELECT * FROM transactions WHERE ses = ? AND idx >= ?",
|
||||
.prepare<[number, number, number]>(
|
||||
"SELECT * FROM transactions WHERE ses = ? AND idx >= ? AND idx <= ?",
|
||||
)
|
||||
.all(sessionRowId, firstNewTxIdx) as RawTransactionRow[];
|
||||
.all(sessionRowId, fromIdx, toIdx) as RawTransactionRow[];
|
||||
|
||||
try {
|
||||
return txs.map((transactionRow) => ({
|
||||
|
||||
@@ -8,7 +8,7 @@ import { WasmCrypto } from "cojson/crypto/WasmCrypto";
|
||||
import { expect, onTestFinished, test, vi } from "vitest";
|
||||
import { SQLiteNode } from "../index.js";
|
||||
import { toSimplifiedMessages } from "./messagesTestUtils.js";
|
||||
import { trackMessages } from "./testUtils.js";
|
||||
import { trackMessages, waitFor } from "./testUtils.js";
|
||||
|
||||
const Crypto = await WasmCrypto.create();
|
||||
|
||||
@@ -117,10 +117,8 @@ test("should sync and load data from storage", async () => {
|
||||
).toMatchInlineSnapshot(`
|
||||
[
|
||||
"client -> LOAD Map sessions: empty",
|
||||
"storage -> KNOWN Group sessions: header/3",
|
||||
"storage -> CONTENT Group header: true new: After: 0 New: 3",
|
||||
"client -> KNOWN Group sessions: header/3",
|
||||
"storage -> KNOWN Map sessions: header/1",
|
||||
"storage -> CONTENT Map header: true new: After: 0 New: 1",
|
||||
"client -> KNOWN Map sessions: header/1",
|
||||
]
|
||||
@@ -129,6 +127,84 @@ test("should sync and load data from storage", async () => {
|
||||
node2Sync.restore();
|
||||
});
|
||||
|
||||
test("should send an empty content message if there is no content", async () => {
|
||||
const agentSecret = Crypto.newRandomAgentSecret();
|
||||
|
||||
const node1 = new LocalNode(
|
||||
agentSecret,
|
||||
Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)),
|
||||
Crypto,
|
||||
);
|
||||
|
||||
const node1Sync = trackMessages(node1);
|
||||
|
||||
const { peer, dbPath } = await createSQLiteStorage();
|
||||
|
||||
node1.syncManager.addPeer(peer);
|
||||
|
||||
const group = node1.createGroup();
|
||||
|
||||
const map = group.createMap();
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
|
||||
expect(
|
||||
toSimplifiedMessages(
|
||||
{
|
||||
Map: map.core,
|
||||
Group: group.core,
|
||||
},
|
||||
node1Sync.messages,
|
||||
),
|
||||
).toMatchInlineSnapshot(`
|
||||
[
|
||||
"client -> CONTENT Group header: true new: After: 0 New: 3",
|
||||
"storage -> KNOWN Group sessions: header/3",
|
||||
"client -> CONTENT Map header: true new: ",
|
||||
"storage -> KNOWN Map sessions: header/0",
|
||||
]
|
||||
`);
|
||||
|
||||
node1Sync.restore();
|
||||
|
||||
const node2 = new LocalNode(
|
||||
agentSecret,
|
||||
Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)),
|
||||
Crypto,
|
||||
);
|
||||
|
||||
const node2Sync = trackMessages(node2);
|
||||
|
||||
const { peer: peer2 } = await createSQLiteStorage(dbPath);
|
||||
|
||||
node2.syncManager.addPeer(peer2);
|
||||
|
||||
const map2 = await node2.load(map.id);
|
||||
if (map2 === "unavailable") {
|
||||
throw new Error("Map is unavailable");
|
||||
}
|
||||
|
||||
expect(
|
||||
toSimplifiedMessages(
|
||||
{
|
||||
Map: map.core,
|
||||
Group: group.core,
|
||||
},
|
||||
node2Sync.messages,
|
||||
),
|
||||
).toMatchInlineSnapshot(`
|
||||
[
|
||||
"client -> LOAD Map sessions: empty",
|
||||
"storage -> CONTENT Group header: true new: After: 0 New: 3",
|
||||
"client -> KNOWN Group sessions: header/3",
|
||||
"storage -> CONTENT Map header: true new: ",
|
||||
"client -> KNOWN Map sessions: header/0",
|
||||
]
|
||||
`);
|
||||
|
||||
node2Sync.restore();
|
||||
});
|
||||
|
||||
test("should load dependencies correctly (group inheritance)", async () => {
|
||||
const agentSecret = Crypto.newRandomAgentSecret();
|
||||
|
||||
@@ -207,13 +283,10 @@ test("should load dependencies correctly (group inheritance)", async () => {
|
||||
).toMatchInlineSnapshot(`
|
||||
[
|
||||
"client -> LOAD Map sessions: empty",
|
||||
"storage -> KNOWN ParentGroup sessions: header/4",
|
||||
"storage -> CONTENT ParentGroup header: true new: After: 0 New: 4",
|
||||
"client -> KNOWN ParentGroup sessions: header/4",
|
||||
"storage -> KNOWN Group sessions: header/5",
|
||||
"storage -> CONTENT Group header: true new: After: 0 New: 5",
|
||||
"client -> KNOWN Group sessions: header/5",
|
||||
"storage -> KNOWN Map sessions: header/1",
|
||||
"storage -> CONTENT Map header: true new: After: 0 New: 1",
|
||||
"client -> KNOWN Map sessions: header/1",
|
||||
]
|
||||
@@ -283,17 +356,13 @@ test("should not send the same dependency value twice", async () => {
|
||||
).toMatchInlineSnapshot(`
|
||||
[
|
||||
"client -> LOAD Map sessions: empty",
|
||||
"storage -> KNOWN ParentGroup sessions: header/4",
|
||||
"storage -> CONTENT ParentGroup header: true new: After: 0 New: 4",
|
||||
"client -> KNOWN ParentGroup sessions: header/4",
|
||||
"storage -> KNOWN Group sessions: header/5",
|
||||
"storage -> CONTENT Group header: true new: After: 0 New: 5",
|
||||
"client -> KNOWN Group sessions: header/5",
|
||||
"storage -> KNOWN Map sessions: header/1",
|
||||
"storage -> CONTENT Map header: true new: After: 0 New: 1",
|
||||
"client -> KNOWN Map sessions: header/1",
|
||||
"client -> LOAD MapFromParent sessions: empty",
|
||||
"storage -> KNOWN MapFromParent sessions: header/1",
|
||||
"storage -> CONTENT MapFromParent header: true new: After: 0 New: 1",
|
||||
"client -> KNOWN MapFromParent sessions: header/1",
|
||||
]
|
||||
@@ -397,10 +466,8 @@ test("should recover from data loss", async () => {
|
||||
).toMatchInlineSnapshot(`
|
||||
[
|
||||
"client -> LOAD Map sessions: empty",
|
||||
"storage -> KNOWN Group sessions: header/3",
|
||||
"storage -> CONTENT Group header: true new: After: 0 New: 3",
|
||||
"client -> KNOWN Group sessions: header/3",
|
||||
"storage -> KNOWN Map sessions: header/4",
|
||||
"storage -> CONTENT Map header: true new: After: 0 New: 4",
|
||||
"client -> KNOWN Map sessions: header/4",
|
||||
]
|
||||
@@ -495,3 +562,166 @@ test("should recover missing dependencies from storage", async () => {
|
||||
"0": 0,
|
||||
});
|
||||
});
|
||||
|
||||
test("should sync multiple sessions in a single content message", async () => {
|
||||
const agentSecret = Crypto.newRandomAgentSecret();
|
||||
|
||||
const node1 = new LocalNode(
|
||||
agentSecret,
|
||||
Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)),
|
||||
Crypto,
|
||||
);
|
||||
|
||||
const { peer, dbPath } = await createSQLiteStorage();
|
||||
|
||||
node1.syncManager.addPeer(peer);
|
||||
|
||||
const group = node1.createGroup();
|
||||
|
||||
const map = group.createMap();
|
||||
|
||||
map.set("hello", "world");
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
|
||||
node1.gracefulShutdown();
|
||||
|
||||
const node2 = new LocalNode(
|
||||
agentSecret,
|
||||
Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)),
|
||||
Crypto,
|
||||
);
|
||||
|
||||
node2.syncManager.addPeer((await createSQLiteStorage(dbPath)).peer);
|
||||
|
||||
const map2 = await node2.load(map.id);
|
||||
if (map2 === "unavailable") {
|
||||
throw new Error("Map is unavailable");
|
||||
}
|
||||
|
||||
expect(map2.get("hello")).toBe("world");
|
||||
|
||||
map2.set("hello", "world2");
|
||||
|
||||
await map2.core.waitForSync();
|
||||
|
||||
node2.gracefulShutdown();
|
||||
|
||||
const node3 = new LocalNode(
|
||||
agentSecret,
|
||||
Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)),
|
||||
Crypto,
|
||||
);
|
||||
|
||||
const node3Sync = trackMessages(node3);
|
||||
|
||||
node3.syncManager.addPeer((await createSQLiteStorage(dbPath)).peer);
|
||||
|
||||
const map3 = await node3.load(map.id);
|
||||
if (map3 === "unavailable") {
|
||||
throw new Error("Map is unavailable");
|
||||
}
|
||||
|
||||
expect(map3.get("hello")).toBe("world2");
|
||||
|
||||
expect(
|
||||
toSimplifiedMessages(
|
||||
{
|
||||
Map: map.core,
|
||||
Group: group.core,
|
||||
},
|
||||
node3Sync.messages,
|
||||
),
|
||||
).toMatchInlineSnapshot(`
|
||||
[
|
||||
"client -> LOAD Map sessions: empty",
|
||||
"storage -> CONTENT Group header: true new: After: 0 New: 3",
|
||||
"client -> KNOWN Group sessions: header/3",
|
||||
"storage -> CONTENT Map header: true new: After: 0 New: 1 | After: 0 New: 1",
|
||||
"client -> KNOWN Map sessions: header/2",
|
||||
]
|
||||
`);
|
||||
|
||||
node3Sync.restore();
|
||||
});
|
||||
|
||||
test("large coValue upload streaming", async () => {
|
||||
const agentSecret = Crypto.newRandomAgentSecret();
|
||||
|
||||
const node1 = new LocalNode(
|
||||
agentSecret,
|
||||
Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)),
|
||||
Crypto,
|
||||
);
|
||||
|
||||
const { peer, dbPath } = await createSQLiteStorage();
|
||||
|
||||
node1.syncManager.addPeer(peer);
|
||||
|
||||
const group = node1.createGroup();
|
||||
const largeMap = group.createMap();
|
||||
|
||||
const dataSize = 1 * 1024 * 200;
|
||||
const chunkSize = 1024; // 1KB chunks
|
||||
const chunks = dataSize / chunkSize;
|
||||
|
||||
const value = "a".repeat(chunkSize);
|
||||
|
||||
for (let i = 0; i < chunks; i++) {
|
||||
const key = `key${i}`;
|
||||
largeMap.set(key, value, "trusting");
|
||||
}
|
||||
|
||||
await largeMap.core.waitForSync();
|
||||
|
||||
node1.gracefulShutdown();
|
||||
|
||||
const node2 = new LocalNode(
|
||||
agentSecret,
|
||||
Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)),
|
||||
Crypto,
|
||||
);
|
||||
|
||||
const node2Sync = trackMessages(node2);
|
||||
|
||||
const { peer: peer2 } = await createSQLiteStorage(dbPath);
|
||||
|
||||
node2.syncManager.addPeer(peer2);
|
||||
|
||||
const largeMapOnNode2 = await node2.load(largeMap.id);
|
||||
|
||||
if (largeMapOnNode2 === "unavailable") {
|
||||
throw new Error("Map is unavailable");
|
||||
}
|
||||
|
||||
await waitFor(() => {
|
||||
expect(largeMapOnNode2.core.knownState()).toEqual(
|
||||
largeMap.core.knownState(),
|
||||
);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
expect(
|
||||
toSimplifiedMessages(
|
||||
{
|
||||
Map: largeMap.core,
|
||||
Group: group.core,
|
||||
},
|
||||
node2Sync.messages,
|
||||
),
|
||||
).toMatchInlineSnapshot(`
|
||||
[
|
||||
"client -> LOAD Map sessions: empty",
|
||||
"storage -> KNOWN Map sessions: header/200",
|
||||
"storage -> CONTENT Group header: true new: After: 0 New: 3",
|
||||
"client -> KNOWN Group sessions: header/3",
|
||||
"storage -> CONTENT Map header: true new: After: 0 New: 97",
|
||||
"client -> KNOWN Map sessions: header/97",
|
||||
"storage -> CONTENT Map header: true new: After: 97 New: 97",
|
||||
"client -> KNOWN Map sessions: header/194",
|
||||
"storage -> CONTENT Map header: true new: After: 194 New: 6",
|
||||
"client -> KNOWN Map sessions: header/200",
|
||||
]
|
||||
`);
|
||||
});
|
||||
@@ -42,3 +42,32 @@ export function trackMessages(node: LocalNode) {
|
||||
restore,
|
||||
};
|
||||
}
|
||||
export function waitFor(
|
||||
callback: () => boolean | undefined | Promise<boolean | undefined>,
|
||||
) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const checkPassed = async () => {
|
||||
try {
|
||||
return { ok: await callback(), error: null };
|
||||
} catch (error) {
|
||||
return { ok: false, error };
|
||||
}
|
||||
};
|
||||
|
||||
let retries = 0;
|
||||
|
||||
const interval = setInterval(async () => {
|
||||
const { ok, error } = await checkPassed();
|
||||
|
||||
if (ok !== false) {
|
||||
clearInterval(interval);
|
||||
resolve();
|
||||
}
|
||||
|
||||
if (++retries > 10) {
|
||||
clearInterval(interval);
|
||||
reject(error);
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# cojson-storage
|
||||
|
||||
## 0.13.32
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 2bf9743: Implement content streaming for large CoValues on storage
|
||||
|
||||
## 0.13.31
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cojson-storage",
|
||||
"version": "0.13.31",
|
||||
"version": "0.13.32",
|
||||
"main": "dist/index.js",
|
||||
"type": "module",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -9,7 +9,12 @@ import {
|
||||
logger,
|
||||
} from "cojson";
|
||||
import { collectNewTxs, getDependedOnCoValues } from "./syncUtils.js";
|
||||
import type { DBClientInterfaceAsync, StoredSessionRow } from "./types.js";
|
||||
import type {
|
||||
DBClientInterfaceAsync,
|
||||
SignatureAfterRow,
|
||||
StoredCoValueRow,
|
||||
StoredSessionRow,
|
||||
} from "./types.js";
|
||||
import NewContentMessage = CojsonInternalTypes.NewContentMessage;
|
||||
import KnownStateMessage = CojsonInternalTypes.KnownStateMessage;
|
||||
import RawCoID = CojsonInternalTypes.RawCoID;
|
||||
@@ -42,156 +47,156 @@ export class StorageManagerAsync {
|
||||
await this.handleContent(msg);
|
||||
break;
|
||||
case "known":
|
||||
await this.handleKnown(msg);
|
||||
this.handleKnown(msg);
|
||||
break;
|
||||
case "done":
|
||||
await this.handleDone(msg);
|
||||
this.handleDone(msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
async handleSessionUpdate({
|
||||
sessionRow,
|
||||
peerKnownState,
|
||||
newContentMessages,
|
||||
}: {
|
||||
sessionRow: StoredSessionRow;
|
||||
peerKnownState: CojsonInternalTypes.CoValueKnownState;
|
||||
newContentMessages: CojsonInternalTypes.NewContentMessage[];
|
||||
}) {
|
||||
if (
|
||||
sessionRow.lastIdx <= (peerKnownState.sessions[sessionRow.sessionID] || 0)
|
||||
)
|
||||
return;
|
||||
|
||||
const firstNewTxIdx = peerKnownState.sessions[sessionRow.sessionID] || 0;
|
||||
|
||||
const newTxsInSession = await this.dbClient.getNewTransactionInSession(
|
||||
sessionRow.rowID,
|
||||
firstNewTxIdx,
|
||||
);
|
||||
|
||||
collectNewTxs({
|
||||
newTxsInSession,
|
||||
newContentMessages,
|
||||
sessionRow,
|
||||
firstNewTxIdx,
|
||||
});
|
||||
}
|
||||
|
||||
async sendNewContent(
|
||||
coValueKnownState: CojsonInternalTypes.CoValueKnownState,
|
||||
): Promise<void> {
|
||||
const outputMessages: OutputMessageMap =
|
||||
await this.collectCoValueData(coValueKnownState);
|
||||
|
||||
// reverse it to send the top level id the last in the order
|
||||
const collectedMessages = Object.values(outputMessages).reverse();
|
||||
for (const { knownMessage, contentMessages } of collectedMessages) {
|
||||
this.sendStateMessage(knownMessage);
|
||||
|
||||
if (contentMessages?.length) {
|
||||
for (const msg of contentMessages) {
|
||||
this.sendStateMessage(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async collectCoValueData(
|
||||
peerKnownState: CojsonInternalTypes.CoValueKnownState,
|
||||
messageMap: OutputMessageMap = {},
|
||||
asDependencyOf?: CojsonInternalTypes.RawCoID,
|
||||
) {
|
||||
if (messageMap[peerKnownState.id]) {
|
||||
return messageMap;
|
||||
}
|
||||
|
||||
const coValueRow = await this.dbClient.getCoValue(peerKnownState.id);
|
||||
const coValueRow = await this.dbClient.getCoValue(coValueKnownState.id);
|
||||
|
||||
if (!coValueRow) {
|
||||
const emptyKnownMessage: KnownStateMessage = {
|
||||
action: "known",
|
||||
...emptyKnownState(peerKnownState.id),
|
||||
...emptyKnownState(coValueKnownState.id),
|
||||
};
|
||||
if (asDependencyOf) {
|
||||
emptyKnownMessage.asDependencyOf = asDependencyOf;
|
||||
}
|
||||
messageMap[peerKnownState.id] = { knownMessage: emptyKnownMessage };
|
||||
return messageMap;
|
||||
|
||||
this.sendStateMessage(emptyKnownMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
const allCoValueSessions = await this.dbClient.getCoValueSessions(
|
||||
coValueRow.rowID,
|
||||
);
|
||||
|
||||
const newCoValueKnownState: CojsonInternalTypes.CoValueKnownState = {
|
||||
id: coValueRow.id,
|
||||
header: true,
|
||||
sessions: {},
|
||||
};
|
||||
const signaturesBySession = new Map<
|
||||
SessionID,
|
||||
Pick<SignatureAfterRow, "idx" | "signature">[]
|
||||
>();
|
||||
|
||||
const newContentMessages: CojsonInternalTypes.NewContentMessage[] = [
|
||||
{
|
||||
action: "content",
|
||||
let contentStreaming = false;
|
||||
for (const sessionRow of allCoValueSessions) {
|
||||
const signatures = await this.dbClient.getSignatures(sessionRow.rowID, 0);
|
||||
|
||||
if (signatures.length > 0) {
|
||||
contentStreaming = true;
|
||||
signaturesBySession.set(sessionRow.sessionID, signatures);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If we are going to send the content in streaming, we send before a known state message
|
||||
* to let the peer know how many transactions we are going to send.
|
||||
*/
|
||||
if (contentStreaming) {
|
||||
const newCoValueKnownState: CojsonInternalTypes.CoValueKnownState = {
|
||||
id: coValueRow.id,
|
||||
header: coValueRow.header,
|
||||
new: {},
|
||||
priority: cojsonInternals.getPriorityFromHeader(coValueRow.header),
|
||||
},
|
||||
];
|
||||
header: true,
|
||||
sessions: {},
|
||||
};
|
||||
|
||||
await Promise.all(
|
||||
allCoValueSessions.map((sessionRow) => {
|
||||
for (const sessionRow of allCoValueSessions) {
|
||||
newCoValueKnownState.sessions[sessionRow.sessionID] =
|
||||
sessionRow.lastIdx;
|
||||
// Collect new sessions data into newContentMessages
|
||||
return this.handleSessionUpdate({
|
||||
sessionRow,
|
||||
peerKnownState,
|
||||
newContentMessages,
|
||||
});
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
this.sendStateMessage({
|
||||
action: "known",
|
||||
...newCoValueKnownState,
|
||||
});
|
||||
}
|
||||
|
||||
this.loadedCoValues.add(coValueRow.id);
|
||||
|
||||
let contentMessage = {
|
||||
action: "content",
|
||||
id: coValueRow.id,
|
||||
header: coValueRow.header,
|
||||
new: {},
|
||||
priority: cojsonInternals.getPriorityFromHeader(coValueRow.header),
|
||||
} satisfies CojsonInternalTypes.NewContentMessage;
|
||||
|
||||
for (const sessionRow of allCoValueSessions) {
|
||||
if (
|
||||
sessionRow.lastIdx <=
|
||||
(coValueKnownState.sessions[sessionRow.sessionID] || 0)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const signatures = signaturesBySession.get(sessionRow.sessionID) || [];
|
||||
|
||||
let idx = 0;
|
||||
|
||||
signatures.push({
|
||||
idx: sessionRow.lastIdx,
|
||||
signature: sessionRow.lastSignature,
|
||||
});
|
||||
|
||||
for (const signature of signatures) {
|
||||
const newTxsInSession = await this.dbClient.getNewTransactionInSession(
|
||||
sessionRow.rowID,
|
||||
idx,
|
||||
signature.idx,
|
||||
);
|
||||
|
||||
collectNewTxs({
|
||||
newTxsInSession,
|
||||
contentMessage,
|
||||
sessionRow,
|
||||
firstNewTxIdx: idx,
|
||||
signature: signature.signature,
|
||||
});
|
||||
|
||||
idx = signature.idx + 1;
|
||||
|
||||
if (signatures.length > 1) {
|
||||
await this.sendContentMessage(coValueRow, contentMessage);
|
||||
contentMessage = {
|
||||
action: "content",
|
||||
id: coValueRow.id,
|
||||
header: coValueRow.header,
|
||||
new: {},
|
||||
priority: cojsonInternals.getPriorityFromHeader(coValueRow.header),
|
||||
} satisfies CojsonInternalTypes.NewContentMessage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(contentMessage.new).length === 0 && contentStreaming) {
|
||||
return;
|
||||
}
|
||||
|
||||
return this.sendContentMessage(coValueRow, contentMessage);
|
||||
}
|
||||
|
||||
async sendContentMessage(
|
||||
coValueRow: StoredCoValueRow,
|
||||
contentMessage: CojsonInternalTypes.NewContentMessage,
|
||||
) {
|
||||
const dependedOnCoValuesList = getDependedOnCoValues({
|
||||
coValueRow,
|
||||
newContentMessages,
|
||||
newContentMessages: [contentMessage],
|
||||
});
|
||||
|
||||
const knownMessage: KnownStateMessage = {
|
||||
action: "known",
|
||||
...newCoValueKnownState,
|
||||
};
|
||||
if (asDependencyOf) {
|
||||
knownMessage.asDependencyOf = asDependencyOf;
|
||||
for (const dependedOnCoValue of dependedOnCoValuesList) {
|
||||
if (this.loadedCoValues.has(dependedOnCoValue)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await this.sendNewContent({
|
||||
id: dependedOnCoValue,
|
||||
header: false,
|
||||
sessions: {},
|
||||
});
|
||||
}
|
||||
messageMap[newCoValueKnownState.id] = {
|
||||
knownMessage: knownMessage,
|
||||
contentMessages: newContentMessages,
|
||||
};
|
||||
|
||||
await Promise.all(
|
||||
dependedOnCoValuesList.map((dependedOnCoValue) => {
|
||||
if (this.loadedCoValues.has(dependedOnCoValue)) {
|
||||
return;
|
||||
}
|
||||
|
||||
return this.collectCoValueData(
|
||||
{
|
||||
id: dependedOnCoValue,
|
||||
header: false,
|
||||
sessions: {},
|
||||
},
|
||||
messageMap,
|
||||
asDependencyOf || coValueRow.id,
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
return messageMap;
|
||||
this.sendStateMessage(contentMessage);
|
||||
}
|
||||
|
||||
handleLoad(msg: CojsonInternalTypes.LoadMessage) {
|
||||
|
||||
@@ -9,7 +9,12 @@ import {
|
||||
logger,
|
||||
} from "cojson";
|
||||
import { collectNewTxs, getDependedOnCoValues } from "./syncUtils.js";
|
||||
import type { DBClientInterfaceSync, StoredSessionRow } from "./types.js";
|
||||
import type {
|
||||
DBClientInterfaceSync,
|
||||
SignatureAfterRow,
|
||||
StoredCoValueRow,
|
||||
StoredSessionRow,
|
||||
} from "./types.js";
|
||||
import NewContentMessage = CojsonInternalTypes.NewContentMessage;
|
||||
import KnownStateMessage = CojsonInternalTypes.KnownStateMessage;
|
||||
import RawCoID = CojsonInternalTypes.RawCoID;
|
||||
@@ -47,145 +52,161 @@ export class StorageManagerSync {
|
||||
}
|
||||
}
|
||||
|
||||
async handleSessionUpdate({
|
||||
sessionRow,
|
||||
peerKnownState,
|
||||
newContentMessages,
|
||||
}: {
|
||||
sessionRow: StoredSessionRow;
|
||||
peerKnownState: CojsonInternalTypes.CoValueKnownState;
|
||||
newContentMessages: CojsonInternalTypes.NewContentMessage[];
|
||||
}) {
|
||||
if (
|
||||
sessionRow.lastIdx <= (peerKnownState.sessions[sessionRow.sessionID] || 0)
|
||||
)
|
||||
return;
|
||||
|
||||
const firstNewTxIdx = peerKnownState.sessions[sessionRow.sessionID] || 0;
|
||||
|
||||
const newTxsInSession = this.dbClient.getNewTransactionInSession(
|
||||
sessionRow.rowID,
|
||||
firstNewTxIdx,
|
||||
);
|
||||
|
||||
collectNewTxs({
|
||||
newTxsInSession,
|
||||
newContentMessages,
|
||||
sessionRow,
|
||||
firstNewTxIdx,
|
||||
});
|
||||
}
|
||||
|
||||
sendNewContent(coValueKnownState: CojsonInternalTypes.CoValueKnownState) {
|
||||
const outputMessages: OutputMessageMap =
|
||||
this.collectCoValueData(coValueKnownState);
|
||||
|
||||
// reverse it to send the top level id the last in the order
|
||||
const collectedMessages = Object.values(outputMessages).reverse();
|
||||
for (const { knownMessage, contentMessages } of collectedMessages) {
|
||||
this.sendStateMessage(knownMessage);
|
||||
|
||||
if (contentMessages?.length) {
|
||||
for (const msg of contentMessages) {
|
||||
this.sendStateMessage(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private collectCoValueData(
|
||||
peerKnownState: CojsonInternalTypes.CoValueKnownState,
|
||||
messageMap: OutputMessageMap = {},
|
||||
asDependencyOf?: CojsonInternalTypes.RawCoID,
|
||||
async sendNewContent(
|
||||
coValueKnownState: CojsonInternalTypes.CoValueKnownState,
|
||||
) {
|
||||
if (messageMap[peerKnownState.id]) {
|
||||
return messageMap;
|
||||
}
|
||||
|
||||
const coValueRow = this.dbClient.getCoValue(peerKnownState.id);
|
||||
const coValueRow = this.dbClient.getCoValue(coValueKnownState.id);
|
||||
|
||||
if (!coValueRow) {
|
||||
const emptyKnownMessage: KnownStateMessage = {
|
||||
action: "known",
|
||||
...emptyKnownState(peerKnownState.id),
|
||||
...emptyKnownState(coValueKnownState.id),
|
||||
};
|
||||
if (asDependencyOf) {
|
||||
emptyKnownMessage.asDependencyOf = asDependencyOf;
|
||||
}
|
||||
messageMap[peerKnownState.id] = { knownMessage: emptyKnownMessage };
|
||||
return messageMap;
|
||||
|
||||
this.sendStateMessage(emptyKnownMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
const allCoValueSessions = this.dbClient.getCoValueSessions(
|
||||
coValueRow.rowID,
|
||||
);
|
||||
|
||||
const newCoValueKnownState: CojsonInternalTypes.CoValueKnownState = {
|
||||
id: coValueRow.id,
|
||||
header: true,
|
||||
sessions: {},
|
||||
};
|
||||
const signaturesBySession = new Map<
|
||||
SessionID,
|
||||
Pick<SignatureAfterRow, "idx" | "signature">[]
|
||||
>();
|
||||
|
||||
const newContentMessages: CojsonInternalTypes.NewContentMessage[] = [
|
||||
{
|
||||
action: "content",
|
||||
let contentStreaming = false;
|
||||
for (const sessionRow of allCoValueSessions) {
|
||||
const signatures = this.dbClient.getSignatures(sessionRow.rowID, 0);
|
||||
|
||||
if (signatures.length > 0) {
|
||||
contentStreaming = true;
|
||||
signaturesBySession.set(sessionRow.sessionID, signatures);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If we are going to send the content in streaming, we send before a known state message
|
||||
* to let the peer know how many transactions we are going to send.
|
||||
*/
|
||||
if (contentStreaming) {
|
||||
const newCoValueKnownState: CojsonInternalTypes.CoValueKnownState = {
|
||||
id: coValueRow.id,
|
||||
header: coValueRow.header,
|
||||
new: {},
|
||||
priority: cojsonInternals.getPriorityFromHeader(coValueRow.header),
|
||||
},
|
||||
];
|
||||
header: true,
|
||||
sessions: {},
|
||||
};
|
||||
|
||||
allCoValueSessions.map((sessionRow) => {
|
||||
newCoValueKnownState.sessions[sessionRow.sessionID] = sessionRow.lastIdx;
|
||||
// Collect new sessions data into newContentMessages
|
||||
this.handleSessionUpdate({
|
||||
sessionRow,
|
||||
peerKnownState,
|
||||
newContentMessages,
|
||||
for (const sessionRow of allCoValueSessions) {
|
||||
newCoValueKnownState.sessions[sessionRow.sessionID] =
|
||||
sessionRow.lastIdx;
|
||||
}
|
||||
|
||||
this.sendStateMessage({
|
||||
action: "known",
|
||||
...newCoValueKnownState,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
this.loadedCoValues.add(coValueRow.id);
|
||||
|
||||
const dependedOnCoValuesList = getDependedOnCoValues({
|
||||
coValueRow,
|
||||
newContentMessages,
|
||||
});
|
||||
let contentMessage = {
|
||||
action: "content",
|
||||
id: coValueRow.id,
|
||||
header: coValueRow.header,
|
||||
new: {},
|
||||
priority: cojsonInternals.getPriorityFromHeader(coValueRow.header),
|
||||
} satisfies CojsonInternalTypes.NewContentMessage;
|
||||
|
||||
const knownMessage: KnownStateMessage = {
|
||||
action: "known",
|
||||
...newCoValueKnownState,
|
||||
};
|
||||
if (asDependencyOf) {
|
||||
knownMessage.asDependencyOf = asDependencyOf;
|
||||
}
|
||||
messageMap[newCoValueKnownState.id] = {
|
||||
knownMessage: knownMessage,
|
||||
contentMessages: newContentMessages,
|
||||
};
|
||||
|
||||
dependedOnCoValuesList.map((dependedOnCoValue) => {
|
||||
if (this.loadedCoValues.has(dependedOnCoValue)) {
|
||||
return;
|
||||
for (const sessionRow of allCoValueSessions) {
|
||||
if (
|
||||
sessionRow.lastIdx <=
|
||||
(coValueKnownState.sessions[sessionRow.sessionID] || 0)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return this.collectCoValueData(
|
||||
{
|
||||
id: dependedOnCoValue,
|
||||
header: false,
|
||||
sessions: {},
|
||||
},
|
||||
messageMap,
|
||||
asDependencyOf || coValueRow.id,
|
||||
);
|
||||
const signatures = signaturesBySession.get(sessionRow.sessionID) || [];
|
||||
|
||||
let idx = 0;
|
||||
|
||||
signatures.push({
|
||||
idx: sessionRow.lastIdx,
|
||||
signature: sessionRow.lastSignature,
|
||||
});
|
||||
|
||||
for (const signature of signatures) {
|
||||
const newTxsInSession = this.dbClient.getNewTransactionInSession(
|
||||
sessionRow.rowID,
|
||||
idx,
|
||||
signature.idx,
|
||||
);
|
||||
|
||||
collectNewTxs({
|
||||
newTxsInSession,
|
||||
contentMessage,
|
||||
sessionRow,
|
||||
firstNewTxIdx: idx,
|
||||
signature: signature.signature,
|
||||
});
|
||||
|
||||
idx = signature.idx + 1;
|
||||
|
||||
if (signatures.length > 1) {
|
||||
await this.sendContentMessage(coValueRow, contentMessage);
|
||||
contentMessage = {
|
||||
action: "content",
|
||||
id: coValueRow.id,
|
||||
header: coValueRow.header,
|
||||
new: {},
|
||||
priority: cojsonInternals.getPriorityFromHeader(coValueRow.header),
|
||||
} satisfies CojsonInternalTypes.NewContentMessage;
|
||||
|
||||
// Introduce a delay to not block the main thread
|
||||
// for the entire content processing
|
||||
await new Promise((resolve) => setTimeout(resolve));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(contentMessage.new).length === 0 && contentStreaming) {
|
||||
return;
|
||||
}
|
||||
|
||||
return this.sendContentMessage(coValueRow, contentMessage);
|
||||
}
|
||||
|
||||
async sendContentMessage(
|
||||
coValueRow: StoredCoValueRow,
|
||||
contentMessage: CojsonInternalTypes.NewContentMessage,
|
||||
) {
|
||||
const dependedOnCoValuesList = getDependedOnCoValues({
|
||||
coValueRow,
|
||||
newContentMessages: [contentMessage],
|
||||
});
|
||||
|
||||
return messageMap;
|
||||
for (const dependedOnCoValue of dependedOnCoValuesList) {
|
||||
if (this.loadedCoValues.has(dependedOnCoValue)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await this.sendNewContent({
|
||||
id: dependedOnCoValue,
|
||||
header: false,
|
||||
sessions: {},
|
||||
});
|
||||
}
|
||||
|
||||
this.sendStateMessage(contentMessage);
|
||||
}
|
||||
|
||||
handleLoad(msg: CojsonInternalTypes.LoadMessage) {
|
||||
return this.sendNewContent(msg);
|
||||
this.sendNewContent(msg).catch((e) =>
|
||||
logger.error("Error sending new content", {
|
||||
id: msg.id,
|
||||
err: e,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
handleContent(msg: CojsonInternalTypes.NewContentMessage) {
|
||||
|
||||
@@ -13,32 +13,33 @@ import type {
|
||||
|
||||
export function collectNewTxs({
|
||||
newTxsInSession,
|
||||
newContentMessages,
|
||||
contentMessage,
|
||||
sessionRow,
|
||||
firstNewTxIdx,
|
||||
signature,
|
||||
}: {
|
||||
newTxsInSession: TransactionRow[];
|
||||
newContentMessages: CojsonInternalTypes.NewContentMessage[];
|
||||
contentMessage: CojsonInternalTypes.NewContentMessage;
|
||||
sessionRow: StoredSessionRow;
|
||||
signature: CojsonInternalTypes.Signature;
|
||||
firstNewTxIdx: number;
|
||||
}) {
|
||||
for (const tx of newTxsInSession) {
|
||||
const lastMessage = newContentMessages[newContentMessages.length - 1];
|
||||
if (!lastMessage) return;
|
||||
let sessionEntry = contentMessage.new[sessionRow.sessionID];
|
||||
|
||||
let sessionEntry = lastMessage.new[sessionRow.sessionID];
|
||||
if (!sessionEntry) {
|
||||
sessionEntry = {
|
||||
after: firstNewTxIdx,
|
||||
lastSignature: "WILL_BE_REPLACED" as CojsonInternalTypes.Signature,
|
||||
newTransactions: [],
|
||||
};
|
||||
lastMessage.new[sessionRow.sessionID] = sessionEntry;
|
||||
}
|
||||
|
||||
sessionEntry.newTransactions.push(tx.tx);
|
||||
sessionEntry.lastSignature = sessionRow.lastSignature;
|
||||
if (!sessionEntry) {
|
||||
sessionEntry = {
|
||||
after: firstNewTxIdx,
|
||||
lastSignature: "WILL_BE_REPLACED" as CojsonInternalTypes.Signature,
|
||||
newTransactions: [],
|
||||
};
|
||||
contentMessage.new[sessionRow.sessionID] = sessionEntry;
|
||||
}
|
||||
|
||||
for (const tx of newTxsInSession) {
|
||||
sessionEntry.newTransactions.push(tx.tx);
|
||||
}
|
||||
|
||||
sessionEntry.lastSignature = signature;
|
||||
}
|
||||
|
||||
export function getDependedOnCoValues({
|
||||
|
||||
@@ -98,13 +98,7 @@ describe("DB sync manager", () => {
|
||||
|
||||
await syncManager.handleSyncMessage(loadMsg);
|
||||
|
||||
expect(syncManager.sendStateMessage).toBeCalledTimes(2);
|
||||
expect(syncManager.sendStateMessage).toBeCalledWith({
|
||||
action: "known",
|
||||
header: true,
|
||||
id: coValueIdToLoad,
|
||||
sessions: {},
|
||||
});
|
||||
expect(syncManager.sendStateMessage).toBeCalledTimes(1);
|
||||
expect(syncManager.sendStateMessage).toBeCalledWith({
|
||||
action: "content",
|
||||
header: expect.objectContaining({
|
||||
@@ -117,78 +111,6 @@ describe("DB sync manager", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("Sends both known and content messages when we have new sessions info for the requested coValue ", async () => {
|
||||
const loadMsg = createEmptyLoadMsg(coValueIdToLoad);
|
||||
|
||||
DBClient.prototype.getCoValue.mockResolvedValueOnce({
|
||||
id: coValueIdToLoad,
|
||||
header: coValueHeader,
|
||||
rowID: 3,
|
||||
});
|
||||
DBClient.prototype.getCoValueSessions.mockResolvedValueOnce(sessionsData);
|
||||
|
||||
const newTxData = {
|
||||
newTransactions: [
|
||||
{
|
||||
privacy: "trusting",
|
||||
madeAt: 1732368535089,
|
||||
changes: "",
|
||||
} as CojsonInternalTypes.Transaction,
|
||||
],
|
||||
after: 0,
|
||||
lastSignature: "signature_z111",
|
||||
} satisfies CojsonInternalTypes.SessionNewContent;
|
||||
|
||||
// mock content data combined with session updates
|
||||
syncManager.handleSessionUpdate = vi.fn(
|
||||
async ({ sessionRow, newContentMessages }) => {
|
||||
newContentMessages[0]!.new[sessionRow.sessionID] = newTxData;
|
||||
},
|
||||
);
|
||||
|
||||
await syncManager.handleSyncMessage(loadMsg);
|
||||
|
||||
expect(syncManager.sendStateMessage).toBeCalledTimes(2);
|
||||
|
||||
expect(syncManager.sendStateMessage).toHaveBeenNthCalledWith(1, {
|
||||
action: "known",
|
||||
header: true,
|
||||
id: coValueIdToLoad,
|
||||
sessions: sessionsData.reduce(
|
||||
(acc, sessionRow) => {
|
||||
acc[sessionRow.sessionID] = sessionRow.lastIdx;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, number>,
|
||||
),
|
||||
});
|
||||
|
||||
expect(syncManager.sendStateMessage).toHaveBeenNthCalledWith(2, {
|
||||
action: "content",
|
||||
header: coValueHeader,
|
||||
id: coValueIdToLoad,
|
||||
new: sessionsData.reduce(
|
||||
(acc, sessionRow) => {
|
||||
acc[sessionRow.sessionID] = {
|
||||
after: expect.any(Number),
|
||||
lastSignature: expect.any(String),
|
||||
newTransactions: expect.any(Array),
|
||||
};
|
||||
return acc;
|
||||
},
|
||||
{} as Record<
|
||||
string,
|
||||
{
|
||||
after: number;
|
||||
lastSignature: string;
|
||||
newTransactions: Transaction[];
|
||||
}
|
||||
>,
|
||||
),
|
||||
priority: 0,
|
||||
});
|
||||
});
|
||||
|
||||
test("Sends messages for unique coValue dependencies only, leaving out circular dependencies", async () => {
|
||||
const loadMsg = createEmptyLoadMsg(coValueIdToLoad);
|
||||
const dependency1 = "co_zMKhQJs5rAeGjta3JX2qEdBS6hS";
|
||||
@@ -220,13 +142,7 @@ describe("DB sync manager", () => {
|
||||
|
||||
// We send out pairs (known + content) messages only FOUR times - as many as the coValues number
|
||||
// and less than amount of interconnected dependencies to loop through in dependenciesTreeWithLoop
|
||||
expect(syncManager.sendStateMessage).toBeCalledTimes(4 * 2);
|
||||
|
||||
const knownExpected = {
|
||||
action: "known",
|
||||
header: true,
|
||||
sessions: {},
|
||||
};
|
||||
expect(syncManager.sendStateMessage).toBeCalledTimes(4);
|
||||
|
||||
const contentExpected = {
|
||||
action: "content",
|
||||
@@ -236,37 +152,18 @@ describe("DB sync manager", () => {
|
||||
};
|
||||
|
||||
expect(syncManager.sendStateMessage).toHaveBeenNthCalledWith(1, {
|
||||
...knownExpected,
|
||||
id: dependency3,
|
||||
asDependencyOf: coValueIdToLoad,
|
||||
...contentExpected,
|
||||
id: dependency1,
|
||||
});
|
||||
expect(syncManager.sendStateMessage).toHaveBeenNthCalledWith(2, {
|
||||
...contentExpected,
|
||||
id: dependency3,
|
||||
});
|
||||
expect(syncManager.sendStateMessage).toHaveBeenNthCalledWith(3, {
|
||||
...knownExpected,
|
||||
...contentExpected,
|
||||
id: dependency2,
|
||||
asDependencyOf: coValueIdToLoad,
|
||||
});
|
||||
expect(syncManager.sendStateMessage).toHaveBeenNthCalledWith(4, {
|
||||
...contentExpected,
|
||||
id: dependency2,
|
||||
});
|
||||
expect(syncManager.sendStateMessage).toHaveBeenNthCalledWith(5, {
|
||||
...knownExpected,
|
||||
id: dependency1,
|
||||
asDependencyOf: coValueIdToLoad,
|
||||
});
|
||||
expect(syncManager.sendStateMessage).toHaveBeenNthCalledWith(6, {
|
||||
...contentExpected,
|
||||
id: dependency1,
|
||||
});
|
||||
expect(syncManager.sendStateMessage).toHaveBeenNthCalledWith(7, {
|
||||
...knownExpected,
|
||||
id: coValueIdToLoad,
|
||||
});
|
||||
expect(syncManager.sendStateMessage).toHaveBeenNthCalledWith(8, {
|
||||
...contentExpected,
|
||||
id: coValueIdToLoad,
|
||||
});
|
||||
|
||||
@@ -47,7 +47,8 @@ export interface DBClientInterfaceAsync {
|
||||
|
||||
getNewTransactionInSession(
|
||||
sessionRowId: number,
|
||||
firstNewTxIdx: number,
|
||||
fromIdx: number,
|
||||
toIdx: number,
|
||||
): Promise<TransactionRow[]>;
|
||||
|
||||
getSignatures(
|
||||
@@ -96,13 +97,14 @@ export interface DBClientInterfaceSync {
|
||||
|
||||
getNewTransactionInSession(
|
||||
sessionRowId: number,
|
||||
firstNewTxIdx: number,
|
||||
fromIdx: number,
|
||||
toIdx: number,
|
||||
): TransactionRow[];
|
||||
|
||||
getSignatures(
|
||||
sessionRowId: number,
|
||||
firstNewTxIdx: number,
|
||||
): SignatureAfterRow[];
|
||||
): Pick<SignatureAfterRow, "idx" | "signature">[];
|
||||
|
||||
addCoValue(msg: CojsonInternalTypes.NewContentMessage): number;
|
||||
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# jazz-auth-betterauth
|
||||
|
||||
## 0.13.32
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-browser@0.13.32
|
||||
- jazz-betterauth-client-plugin@0.13.32
|
||||
|
||||
## 0.13.31
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jazz-auth-betterauth",
|
||||
"version": "0.13.31",
|
||||
"version": "0.13.32",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"types": "src/index.ts",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# jazz-auth-clerk
|
||||
|
||||
## 0.13.32
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-browser@0.13.32
|
||||
|
||||
## 0.13.31
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "jazz-auth-clerk",
|
||||
"version": "0.13.31",
|
||||
"version": "0.13.32",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cojson": "workspace:0.13.31",
|
||||
"jazz-browser": "workspace:0.13.31",
|
||||
"jazz-browser": "workspace:0.13.32",
|
||||
"jazz-tools": "workspace:0.13.31"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# jazz-betterauth-client-plugin
|
||||
|
||||
## 0.13.32
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-betterauth-server-plugin@0.13.32
|
||||
|
||||
## 0.13.31
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jazz-betterauth-client-plugin",
|
||||
"version": "0.13.31",
|
||||
"version": "0.13.32",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"types": "src/index.ts",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# jazz-betterauth-server-plugin
|
||||
|
||||
## 0.13.32
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-browser@0.13.32
|
||||
|
||||
## 0.13.31
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jazz-betterauth-server-plugin",
|
||||
"version": "0.13.31",
|
||||
"version": "0.13.32",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"types": "src/index.ts",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# jazz-browser-media-images
|
||||
|
||||
## 0.13.32
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-browser@0.13.32
|
||||
|
||||
## 0.13.31
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jazz-browser-media-images",
|
||||
"version": "0.13.31",
|
||||
"version": "0.13.32",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
@@ -8,7 +8,7 @@
|
||||
"dependencies": {
|
||||
"@types/image-blob-reduce": "^4.1.1",
|
||||
"image-blob-reduce": "^4.1.0",
|
||||
"jazz-browser": "workspace:0.13.31",
|
||||
"jazz-browser": "workspace:0.13.32",
|
||||
"jazz-tools": "workspace:0.13.31",
|
||||
"pica": "^9.0.1"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# jazz-browser
|
||||
|
||||
## 0.13.32
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- cojson-storage-indexeddb@0.13.32
|
||||
|
||||
## 0.13.31
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jazz-browser",
|
||||
"version": "0.13.31",
|
||||
"version": "0.13.32",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# jazz-browser
|
||||
|
||||
## 0.13.32
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-native-core@0.13.32
|
||||
- jazz-auth-clerk@0.13.32
|
||||
|
||||
## 0.13.31
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jazz-expo",
|
||||
"version": "0.13.31",
|
||||
"version": "0.13.32",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
# jazz-react-auth-betterauth
|
||||
|
||||
## 0.13.32
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-browser@0.13.32
|
||||
- jazz-auth-betterauth@0.13.32
|
||||
- jazz-react@0.13.32
|
||||
- jazz-betterauth-client-plugin@0.13.32
|
||||
|
||||
## 0.13.31
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jazz-react-auth-betterauth",
|
||||
"version": "0.13.31",
|
||||
"version": "0.13.32",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"types": "src/index.tsx",
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# jazz-browser-media-images
|
||||
|
||||
## 0.13.32
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-browser@0.13.32
|
||||
- jazz-auth-clerk@0.13.32
|
||||
- jazz-react@0.13.32
|
||||
|
||||
## 0.13.31
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jazz-react-auth-clerk",
|
||||
"version": "0.13.31",
|
||||
"version": "0.13.32",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# jazz-browser
|
||||
|
||||
## 0.13.32
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [2bf9743]
|
||||
- cojson-storage@0.13.32
|
||||
|
||||
## 0.13.31
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-react-native-core",
|
||||
"type": "module",
|
||||
"version": "0.13.31",
|
||||
"version": "0.13.32",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# jazz-browser
|
||||
|
||||
## 0.13.32
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [2bf9743]
|
||||
- cojson-storage@0.13.32
|
||||
- jazz-react-native-core@0.13.32
|
||||
|
||||
## 0.13.31
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jazz-react-native",
|
||||
"version": "0.13.31",
|
||||
"version": "0.13.32",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# jazz-react
|
||||
|
||||
## 0.13.32
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-browser@0.13.32
|
||||
- jazz-browser-media-images@0.13.32
|
||||
|
||||
## 0.13.31
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jazz-react",
|
||||
"version": "0.13.31",
|
||||
"version": "0.13.32",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
@@ -18,8 +18,8 @@
|
||||
"dependencies": {
|
||||
"@scure/bip39": "^1.3.0",
|
||||
"cojson": "workspace:0.13.31",
|
||||
"jazz-browser-media-images": "workspace:0.13.31",
|
||||
"jazz-browser": "workspace:0.13.31",
|
||||
"jazz-browser-media-images": "workspace:0.13.32",
|
||||
"jazz-browser": "workspace:0.13.32",
|
||||
"jazz-react-core": "workspace:0.13.31",
|
||||
"jazz-tools": "workspace:0.13.31"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# jazz-richtext-prosemirror
|
||||
|
||||
## 0.1.19
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-browser@0.13.32
|
||||
|
||||
## 0.1.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jazz-richtext-prosemirror",
|
||||
"version": "0.1.18",
|
||||
"version": "0.1.19",
|
||||
"description": "ProseMirror integration for Jazz rich text editing",
|
||||
"main": "dist/index.js",
|
||||
"types": "src/index.ts",
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# jazz-richtext-tiptap
|
||||
|
||||
## 0.1.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-browser@0.13.32
|
||||
- jazz-richtext-prosemirror@0.1.19
|
||||
|
||||
## 0.1.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jazz-richtext-tiptap",
|
||||
"version": "0.1.7",
|
||||
"version": "0.1.8",
|
||||
"description": "Tiptap integration for Jazz rich text editing",
|
||||
"main": "dist/index.js",
|
||||
"types": "src/index.ts",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# jazz-run
|
||||
|
||||
## 0.13.32
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- cojson-storage-sqlite@0.13.32
|
||||
|
||||
## 0.13.31
|
||||
|
||||
### Patch Changes
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user