Compare commits

...

74 Commits

Author SHA1 Message Date
Anselm
4e16575f97 Hotifx release 2024-08-22 14:29:31 +01:00
Anselm
12ab20ecd9 Use queueable fork 2024-08-22 14:29:03 +01:00
Anselm
2c3a40c94b Hotfix release 2024-08-22 14:06:35 +01:00
Anselm
0d8175ba1c Use fork of queueable 2024-08-22 14:06:08 +01:00
Anselm
032f69f692 Fix jazz-run deps even more 2024-08-22 12:49:34 +01:00
Anselm
6dc52b2a6d Fix jazz-run dependencies 2024-08-22 12:47:35 +01:00
Anselm
1232c0240a Hotifx release 2024-08-22 12:03:33 +01:00
Anselm
55fa74f44a Hack to make jazz-run create account work 2024-08-22 12:02:50 +01:00
Anselm
5d91f9f8dc Hotifx release 2024-08-21 16:20:55 +01:00
Anselm
08f1f77834 Stop using tryAddTransactionsAsync 2024-08-21 16:20:35 +01:00
Anselm
ea882aba63 Hotfix release 2024-08-21 15:59:16 +01:00
Anselm
513a78ab9b Better logging for failed transactions 2024-08-21 15:58:38 +01:00
Anselm
406ab9b0da Hotfix release 2024-08-21 15:24:27 +01:00
Anselm
140f6616cb Remove WS buffer filling log message 2024-08-21 15:23:41 +01:00
Anselm
b09589b15e Hotfix release 2024-08-21 11:39:18 +01:00
Anselm
00638897f4 only one async transaction per covalue again 2024-08-21 11:36:47 +01:00
Anselm
5094e6d536 Hotfix release 2024-08-20 18:03:01 +01:00
Anselm
39242e7f68 Start introducing neverthrow, make tryAddNewTransactionsAsync and handleNewContent less throwy 2024-08-20 18:02:26 +01:00
Anselm Eickhoff
be7e208b1c Merge pull request #333 from gardencmp/anselm-jazz-265
Make sure errors in storage peer completely crash jazz context instead of failing silently
2024-08-20 14:09:19 +01:00
Anselm
c3bffbf4de Release 2024-08-20 14:02:53 +01:00
Anselm
d46467f318 Hotfix release 2024-08-20 12:58:09 +01:00
Anselm
6d21400803 Get rid of simulated errors 2024-08-20 12:57:50 +01:00
Anselm
db53161296 Hotfix release 2024-08-20 12:46:15 +01:00
Anselm
cb4a116cec Make simulated errors even more likely 2024-08-20 12:45:34 +01:00
Anselm
013199b9b2 Increase chance of simulated storage bug 2024-08-20 11:43:28 +01:00
Anselm
a8b74ff703 Hotfix release 2024-08-20 11:43:20 +01:00
Anselm
b1985a9161 Throw properly on peer that should crash on close 2024-08-20 11:42:53 +01:00
Anselm
3bf512719f Hotfix release 2024-08-20 11:22:44 +01:00
Anselm
d83ed69d41 Allow crashing whole local node on peer errors 2024-08-20 11:20:20 +01:00
Anselm
fdde8db664 Hotfix release 2024-08-19 17:19:40 +01:00
Anselm
dd5581ba2d Handle ws closing while buffering 2024-08-19 17:19:01 +01:00
Anselm
07fe2b9dcf Release 2024-08-19 16:45:35 +01:00
Anselm
b297c93b80 Release fixes 2024-08-19 15:08:08 +01:00
Anselm
d2e62e5b44 Reduce log level on loading message 2024-08-19 14:50:26 +01:00
Anselm
fe73ce7514 Make failed transaction log message leaner 2024-08-19 14:48:57 +01:00
Anselm Eickhoff
0fed16cea4 Merge pull request #323 from gardencmp/callumflack-jazz-240
fix: chat example URL
2024-08-19 11:15:39 +01:00
Callum Flack
08804ad435 fix: chat example URL 2024-08-19 11:03:33 +10:00
Anselm
79fa7724ad Release 2024-08-15 19:35:11 +01:00
Anselm
4604c2184a Adapt type of applyDiff to make CoMaps fully subclassable again 2024-08-15 19:34:47 +01:00
Anselm
11bac697fb Release 2024-08-15 19:23:19 +01:00
Anselm
96cec27f89 Close both ends of the peer on gracefulShutdown 2024-08-15 19:22:59 +01:00
Anselm
bcd412b8f9 Properly close connecting websockets 2024-08-15 19:22:19 +01:00
Anselm
6b456e2841 Properly close and delete peer on incoming disconnect/timeout 2024-08-15 19:10:22 +01:00
Anselm
1df72b3dc8 Reintroduce nomad example deploy, but only on main 2024-08-15 19:08:42 +01:00
Anselm
402d692739 Don't deploy examples to Nomad anymore 2024-08-15 17:02:40 +01:00
Anselm
fad46b2fb5 Release 2024-08-15 16:37:47 +01:00
Anselm
0153c80cf2 Immediately resolve an already-open websocket 2024-08-15 16:37:23 +01:00
Anselm
4f75dc8d97 Release 2024-08-15 16:17:31 +01:00
Anselm
e2c79cccb5 Remove noisy log 2024-08-15 16:17:03 +01:00
Anselm
c14a0e05be Release 2024-08-15 15:30:22 +01:00
Anselm
016dd3a5dd Fix ignoring server peers 2024-08-15 15:30:00 +01:00
Anselm
5c4ca9103c Release 2024-08-15 13:36:33 +01:00
Anselm
b4aad92907 Option to not expect pings 2024-08-15 13:36:08 +01:00
Anselm
56d1e095a1 Release 2024-08-15 13:02:39 +01:00
Anselm Eickhoff
6dee9aae49 Merge pull request #281 from gardencmp/anselm-jazz-190
Remove effect from jazz
2024-08-15 11:51:53 +01:00
Anselm Eickhoff
a10bff981e Merge pull request #284 from gardencmp/anselm-jazz-227
Remove effect from peer communication
2024-08-15 11:51:10 +01:00
Anselm Eickhoff
e333f7884a Merge pull request #313 from gardencmp/anselm-jazz-246
Remove effect from jazz-tools and dependent packages
2024-08-15 11:50:44 +01:00
Anselm
8ea7bf237b Remove effect from storage implementation 2024-08-15 11:13:10 +01:00
Anselm
5e8409fa08 Remove rest of effect use 2024-08-14 17:51:10 +01:00
Anselm
23354c1767 Progress on removing effect 2024-08-14 15:24:20 +01:00
Anselm Eickhoff
0efb69d0db Merge pull request #312 from pax-k/JAZZ-252/make-sure-castas-preserves-subscriptionscope
fix: preserve subscriptionScope for castAs in CoList and CoMap
2024-08-14 14:51:54 +01:00
pax-k
0462c4e41b fix: preserve subscriptionScope for castAs in CoList and CoMap 2024-08-14 16:50:12 +03:00
Anselm
70a5673197 More progress 2024-08-13 12:25:15 +01:00
Anselm Eickhoff
9ec3203485 Merge pull request #285 from gdorsi/main
docs: fix the get started links position
2024-08-12 17:46:41 +01:00
Guido D'Orsi
1a46f9b2e1 docs: fix the get started links position 2024-08-10 16:17:18 +02:00
Anselm
77bb26a8d7 Only use queable in cojson, rework tests 2024-08-09 16:44:50 +01:00
Anselm
2a36dcf592 WIP switch to queueable 2024-08-09 13:59:26 +01:00
Anselm
fc2bcadbe2 Remove effect schema from jazz schema 2024-08-08 18:18:17 +01:00
Anselm
46b0cc1adb Release 2024-08-08 14:44:00 +01:00
Anselm Eickhoff
d75d1c6a3f Merge pull request #279 from pax-k/JAZZ-219/implement-applydiff-on-comap-to-only-update-changed-fields
feat: Implement applyDiff on CoMap to only update changed fields
2024-08-08 13:50:17 +01:00
pax-k
13b236aeed feat: Implement applyDiff on CoMap to only update changed fields 2024-08-08 11:03:08 +03:00
Anselm Eickhoff
1c0a61b0b2 Merge pull request #271 from pax-k/document-max-recommended-tx-size
chore: document MAX_RECOMMENDED_TX_SIZE
2024-08-07 16:32:07 +01:00
pax-k
3f5ef7e799 chore: formatting 2024-08-06 19:35:14 +03:00
pax-k
e7a573fa94 chore: document MAX_RECOMMENDED_TX_SIZE 2024-08-06 19:22:41 +03:00
78 changed files with 5155 additions and 2637 deletions

View File

@@ -0,0 +1,5 @@
---
"cojson": patch
---
Stop using tryAddTransactionsAsync

View File

@@ -0,0 +1,5 @@
---
"jazz-run": patch
---
Hack to make jazz-run account create work

View File

@@ -0,0 +1,5 @@
---
"cojson": patch
---
Start introducing neverthrow, make tryAddNewTransactionsAsync and handleNewContent less throwy

View File

@@ -0,0 +1,5 @@
---
"cojson": patch
---
Only one async transaction per CoValue at a time again

View File

@@ -0,0 +1,6 @@
---
"jazz-run": patch
"cojson": patch
---
Use fork of queueable

33
.changeset/pre.json Normal file
View File

@@ -0,0 +1,33 @@
{
"mode": "pre",
"tag": "neverthrow",
"initialVersions": {
"jazz-example-chat": "0.0.80",
"jazz-inspector": "0.0.58",
"jazz-example-pets": "0.0.98",
"jazz-example-todo": "0.0.97",
"cojson": "0.7.33",
"cojson-storage-indexeddb": "0.7.33",
"cojson-storage-sqlite": "0.7.33",
"cojson-transport-ws": "0.7.33",
"hash-slash": "0.2.0",
"jazz-browser": "0.7.33",
"jazz-browser-media-images": "0.7.33",
"jazz-nodejs": "0.7.33",
"jazz-react": "0.7.33",
"jazz-run": "0.7.33",
"jazz-tools": "0.7.33"
},
"changesets": [
"bright-dingos-boil",
"cyan-panthers-fry",
"early-files-lick",
"mighty-cameras-tickle",
"neat-tools-call",
"real-ravens-refuse",
"rich-tigers-cross",
"rotten-pens-drive",
"rude-cheetahs-ring",
"tidy-rice-speak"
]
}

View File

@@ -0,0 +1,5 @@
---
"jazz-run": patch
---
Fix deps even more

View File

@@ -0,0 +1,5 @@
---
"cojson-transport-ws": patch
---
Remove WS buffer filling log message

View File

@@ -0,0 +1,5 @@
---
"jazz-run": patch
---
Fix peer deps

View File

@@ -0,0 +1,6 @@
---
"jazz-run": patch
"cojson": patch
---
Use queueable fork

View File

@@ -0,0 +1,5 @@
---
"cojson": patch
---
Better log message for failed transactions

View File

@@ -3,8 +3,6 @@ name: Build and Deploy
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build-examples:

View File

@@ -1,5 +1,189 @@
# jazz-example-chat
## 0.0.81-neverthrow.6
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.8
- jazz-react@0.7.34-neverthrow.8
- jazz-tools@0.7.34-neverthrow.8
## 0.0.81-neverthrow.5
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.7
- jazz-react@0.7.34-neverthrow.7
- jazz-tools@0.7.34-neverthrow.7
## 0.0.81-neverthrow.4
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.4
- jazz-react@0.7.34-neverthrow.4
- jazz-tools@0.7.34-neverthrow.4
## 0.0.81-neverthrow.3
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.3
- jazz-react@0.7.34-neverthrow.3
- jazz-tools@0.7.34-neverthrow.3
## 0.0.81-neverthrow.2
### Patch Changes
- jazz-react@0.7.34-neverthrow.2
## 0.0.81-neverthrow.1
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.1
- jazz-react@0.7.34-neverthrow.1
- jazz-tools@0.7.34-neverthrow.1
## 0.0.81-neverthrow.0
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.0
- jazz-react@0.7.34-neverthrow.0
- jazz-tools@0.7.34-neverthrow.0
## 0.0.80
### Patch Changes
- Updated dependencies [b297c93]
- Updated dependencies [3bf5127]
- Updated dependencies [a8b74ff]
- Updated dependencies [db53161]
- cojson@0.7.33
- jazz-react@0.7.33
- jazz-tools@0.7.33
## 0.0.80-hotfixes.5
### Patch Changes
- Updated dependencies
- cojson@0.7.33-hotfixes.5
- jazz-react@0.7.33-hotfixes.5
- jazz-tools@0.7.33-hotfixes.5
## 0.0.80-hotfixes.4
### Patch Changes
- Updated dependencies
- cojson@0.7.33-hotfixes.4
- jazz-react@0.7.33-hotfixes.4
- jazz-tools@0.7.33-hotfixes.4
## 0.0.80-hotfixes.3
### Patch Changes
- Updated dependencies
- cojson@0.7.33-hotfixes.3
- jazz-react@0.7.33-hotfixes.3
- jazz-tools@0.7.33-hotfixes.3
## 0.0.80-hotfixes.2
### Patch Changes
- jazz-react@0.7.33-hotfixes.2
## 0.0.80-hotfixes.1
### Patch Changes
- jazz-react@0.7.33-hotfixes.1
## 0.0.80-hotfixes.0
### Patch Changes
- Updated dependencies
- cojson@0.7.33-hotfixes.0
- jazz-react@0.7.33-hotfixes.0
- jazz-tools@0.7.33-hotfixes.0
## 0.0.79
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.32
- jazz-react@0.7.32
## 0.0.78
### Patch Changes
- Updated dependencies
- cojson@0.7.31
- jazz-react@0.7.31
- jazz-tools@0.7.31
## 0.0.77
### Patch Changes
- jazz-react@0.7.30
## 0.0.76
### Patch Changes
- Updated dependencies
- cojson@0.7.29
- jazz-react@0.7.29
- jazz-tools@0.7.29
## 0.0.75
### Patch Changes
- Updated dependencies
- cojson@0.7.28
- jazz-react@0.7.28
- jazz-tools@0.7.28
## 0.0.74
### Patch Changes
- jazz-react@0.7.27
## 0.0.73
### Patch Changes
- Updated dependencies
- cojson@0.7.26
- jazz-react@0.7.26
- jazz-tools@0.7.26
## 0.0.72
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.25
- jazz-react@0.7.25
## 0.0.71
### Patch Changes

View File

@@ -1,12 +1,13 @@
# Jazz Chat Example
Live version: https://example-chat.jazz.tools
Live version: [https://chat.jazz.tools](https://chat.jazz.tools)
## Installing & running the example locally
(this requires `pnpm` to be installed, see [https://pnpm.io/installation](https://pnpm.io/installation))
Start by checking out `jazz`
```bash
git clone https://github.com/gardencmp/jazz.git
cd jazz/examples/chat
@@ -34,7 +35,6 @@ pnpm dev
If you have feedback, let us know on [Discord](https://discord.gg/utDMjHYg42) or open an issue or PR to fix something that seems wrong.
## Configuration: sync server
By default, the example app uses [Jazz Global Mesh](https://jazz.tools/mesh) (`wss://sync.jazz.tools`) - so cross-device use, invites and collaboration should just work.

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-chat",
"private": true,
"version": "0.0.71",
"version": "0.0.81-neverthrow.6",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -3,6 +3,7 @@ import { createJazzReactContext, DemoAuth } from "jazz-react";
import { createRoot } from "react-dom/client";
import { useIframeHashRouter } from "hash-slash";
import { ChatScreen } from "./chatScreen.tsx";
import { StrictMode } from "react";
export class Message extends CoMap {
text = co.string;
@@ -39,4 +40,4 @@ function App() {
}
createRoot(document.getElementById("root")!)
.render(<Jazz.Provider><App/></Jazz.Provider>);
.render(<StrictMode><Jazz.Provider><App/></Jazz.Provider></StrictMode>);

View File

@@ -1,5 +1,166 @@
# jazz-example-chat
## 0.0.59-neverthrow.6
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.8
- cojson-transport-ws@0.7.34-neverthrow.8
## 0.0.59-neverthrow.5
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.7
- cojson-transport-ws@0.7.34-neverthrow.7
## 0.0.59-neverthrow.4
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.4
- cojson-transport-ws@0.7.34-neverthrow.4
## 0.0.59-neverthrow.3
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.3
- cojson-transport-ws@0.7.34-neverthrow.3
## 0.0.59-neverthrow.2
### Patch Changes
- Updated dependencies
- cojson-transport-ws@0.7.34-neverthrow.2
## 0.0.59-neverthrow.1
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.1
- cojson-transport-ws@0.7.34-neverthrow.1
## 0.0.59-neverthrow.0
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.0
- cojson-transport-ws@0.7.34-neverthrow.0
## 0.0.58
### Patch Changes
- Updated dependencies [fdde8db]
- Updated dependencies [b297c93]
- Updated dependencies [07fe2b9]
- Updated dependencies [3bf5127]
- Updated dependencies [a8b74ff]
- Updated dependencies [db53161]
- cojson-transport-ws@0.7.33
- cojson@0.7.33
## 0.0.58-hotfixes.5
### Patch Changes
- Updated dependencies
- cojson@0.7.33-hotfixes.5
- cojson-transport-ws@0.7.33-hotfixes.5
## 0.0.58-hotfixes.4
### Patch Changes
- Updated dependencies
- cojson@0.7.33-hotfixes.4
- cojson-transport-ws@0.7.33-hotfixes.4
## 0.0.58-hotfixes.3
### Patch Changes
- Updated dependencies
- cojson-transport-ws@0.7.33-hotfixes.3
- cojson@0.7.33-hotfixes.3
## 0.0.58-hotfixes.2
### Patch Changes
- Updated dependencies
- cojson-transport-ws@0.7.33-hotfixes.2
## 0.0.58-hotfixes.1
### Patch Changes
- Updated dependencies
- cojson-transport-ws@0.7.33-hotfixes.1
## 0.0.58-hotfixes.0
### Patch Changes
- Updated dependencies
- cojson@0.7.33-hotfixes.0
- cojson-transport-ws@0.7.33-hotfixes.0
## 0.0.57
### Patch Changes
- Updated dependencies
- Updated dependencies
- cojson-transport-ws@0.7.31
- cojson@0.7.31
## 0.0.56
### Patch Changes
- Updated dependencies
- cojson-transport-ws@0.7.30
## 0.0.55
### Patch Changes
- Updated dependencies
- cojson@0.7.29
- cojson-transport-ws@0.7.29
## 0.0.54
### Patch Changes
- Updated dependencies
- cojson@0.7.28
- cojson-transport-ws@0.7.28
## 0.0.53
### Patch Changes
- Updated dependencies
- cojson-transport-ws@0.7.27
## 0.0.52
### Patch Changes
- Updated dependencies
- cojson@0.7.26
- cojson-transport-ws@0.7.26
## 0.0.51
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-inspector",
"private": true,
"version": "0.0.51",
"version": "0.0.59-neverthrow.6",
"type": "module",
"scripts": {
"dev": "vite",
@@ -19,7 +19,6 @@
"hash-slash": "workspace:*",
"cojson": "workspace:*",
"cojson-transport-ws": "workspace:*",
"effect": "^3.5.2",
"lucide-react": "^0.274.0",
"qrcode": "^1.5.3",
"react": "^18.2.0",

View File

@@ -10,7 +10,6 @@ import {
WasmCrypto,
} from "cojson";
import { createWebSocketPeer } from "cojson-transport-ws";
import { Effect } from "effect";
import { Trash2 } from "lucide-react";
import { Breadcrumbs } from "./breadcrumbs";
import { usePagePath } from "./use-page-path";
@@ -62,13 +61,11 @@ export default function CoJsonViewerApp() {
}
WasmCrypto.create().then(async (crypto) => {
const wsPeer = await Effect.runPromise(
createWebSocketPeer({
id: "mesh",
websocket: new WebSocket("wss://mesh.jazz.tools"),
role: "server",
}),
);
const wsPeer = createWebSocketPeer({
id: "mesh",
websocket: new WebSocket("wss://mesh.jazz.tools"),
role: "server",
});
const node = await LocalNode.withLoadedAccount({
accountID: currentAccount.id,
accountSecret: currentAccount.secret,

View File

@@ -1,5 +1,179 @@
# jazz-example-pets
## 0.0.99-neverthrow.6
### Patch Changes
- jazz-react@0.7.34-neverthrow.8
- jazz-tools@0.7.34-neverthrow.8
- jazz-browser-media-images@0.7.34-neverthrow.8
## 0.0.99-neverthrow.5
### Patch Changes
- jazz-react@0.7.34-neverthrow.7
- jazz-tools@0.7.34-neverthrow.7
- jazz-browser-media-images@0.7.34-neverthrow.7
## 0.0.99-neverthrow.4
### Patch Changes
- jazz-react@0.7.34-neverthrow.4
- jazz-tools@0.7.34-neverthrow.4
- jazz-browser-media-images@0.7.34-neverthrow.4
## 0.0.99-neverthrow.3
### Patch Changes
- jazz-react@0.7.34-neverthrow.3
- jazz-tools@0.7.34-neverthrow.3
- jazz-browser-media-images@0.7.34-neverthrow.3
## 0.0.99-neverthrow.2
### Patch Changes
- jazz-browser-media-images@0.7.34-neverthrow.2
- jazz-react@0.7.34-neverthrow.2
## 0.0.99-neverthrow.1
### Patch Changes
- jazz-react@0.7.34-neverthrow.1
- jazz-tools@0.7.34-neverthrow.1
- jazz-browser-media-images@0.7.34-neverthrow.1
## 0.0.99-neverthrow.0
### Patch Changes
- jazz-react@0.7.34-neverthrow.0
- jazz-tools@0.7.34-neverthrow.0
- jazz-browser-media-images@0.7.34-neverthrow.0
## 0.0.98
### Patch Changes
- jazz-react@0.7.33
- jazz-tools@0.7.33
- jazz-browser-media-images@0.7.33
## 0.0.98-hotfixes.5
### Patch Changes
- jazz-react@0.7.33-hotfixes.5
- jazz-tools@0.7.33-hotfixes.5
- jazz-browser-media-images@0.7.33-hotfixes.5
## 0.0.98-hotfixes.4
### Patch Changes
- jazz-react@0.7.33-hotfixes.4
- jazz-tools@0.7.33-hotfixes.4
- jazz-browser-media-images@0.7.33-hotfixes.4
## 0.0.98-hotfixes.3
### Patch Changes
- jazz-react@0.7.33-hotfixes.3
- jazz-tools@0.7.33-hotfixes.3
- jazz-browser-media-images@0.7.33-hotfixes.3
## 0.0.98-hotfixes.2
### Patch Changes
- jazz-browser-media-images@0.7.33-hotfixes.2
- jazz-react@0.7.33-hotfixes.2
## 0.0.98-hotfixes.1
### Patch Changes
- jazz-browser-media-images@0.7.33-hotfixes.1
- jazz-react@0.7.33-hotfixes.1
## 0.0.98-hotfixes.0
### Patch Changes
- jazz-react@0.7.33-hotfixes.0
- jazz-tools@0.7.33-hotfixes.0
- jazz-browser-media-images@0.7.33-hotfixes.0
## 0.0.97
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.32
- jazz-browser-media-images@0.7.32
- jazz-react@0.7.32
## 0.0.96
### Patch Changes
- jazz-react@0.7.31
- jazz-tools@0.7.31
- jazz-browser-media-images@0.7.31
## 0.0.95
### Patch Changes
- jazz-browser-media-images@0.7.30
- jazz-react@0.7.30
## 0.0.94
### Patch Changes
- jazz-react@0.7.29
- jazz-tools@0.7.29
- jazz-browser-media-images@0.7.29
## 0.0.93
### Patch Changes
- jazz-react@0.7.28
- jazz-tools@0.7.28
- jazz-browser-media-images@0.7.28
## 0.0.92
### Patch Changes
- jazz-browser-media-images@0.7.27
- jazz-react@0.7.27
## 0.0.91
### Patch Changes
- Updated dependencies
- jazz-react@0.7.26
- jazz-tools@0.7.26
- jazz-browser-media-images@0.7.26
## 0.0.90
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.25
- jazz-browser-media-images@0.7.25
- jazz-react@0.7.25
## 0.0.89
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-pets",
"private": true,
"version": "0.0.89",
"version": "0.0.99-neverthrow.6",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,157 @@
# jazz-example-todo
## 0.0.98-neverthrow.6
### Patch Changes
- jazz-react@0.7.34-neverthrow.8
- jazz-tools@0.7.34-neverthrow.8
## 0.0.98-neverthrow.5
### Patch Changes
- jazz-react@0.7.34-neverthrow.7
- jazz-tools@0.7.34-neverthrow.7
## 0.0.98-neverthrow.4
### Patch Changes
- jazz-react@0.7.34-neverthrow.4
- jazz-tools@0.7.34-neverthrow.4
## 0.0.98-neverthrow.3
### Patch Changes
- jazz-react@0.7.34-neverthrow.3
- jazz-tools@0.7.34-neverthrow.3
## 0.0.98-neverthrow.2
### Patch Changes
- jazz-react@0.7.34-neverthrow.2
## 0.0.98-neverthrow.1
### Patch Changes
- jazz-react@0.7.34-neverthrow.1
- jazz-tools@0.7.34-neverthrow.1
## 0.0.98-neverthrow.0
### Patch Changes
- jazz-react@0.7.34-neverthrow.0
- jazz-tools@0.7.34-neverthrow.0
## 0.0.97
### Patch Changes
- jazz-react@0.7.33
- jazz-tools@0.7.33
## 0.0.97-hotfixes.5
### Patch Changes
- jazz-react@0.7.33-hotfixes.5
- jazz-tools@0.7.33-hotfixes.5
## 0.0.97-hotfixes.4
### Patch Changes
- jazz-react@0.7.33-hotfixes.4
- jazz-tools@0.7.33-hotfixes.4
## 0.0.97-hotfixes.3
### Patch Changes
- jazz-react@0.7.33-hotfixes.3
- jazz-tools@0.7.33-hotfixes.3
## 0.0.97-hotfixes.2
### Patch Changes
- jazz-react@0.7.33-hotfixes.2
## 0.0.97-hotfixes.1
### Patch Changes
- jazz-react@0.7.33-hotfixes.1
## 0.0.97-hotfixes.0
### Patch Changes
- jazz-react@0.7.33-hotfixes.0
- jazz-tools@0.7.33-hotfixes.0
## 0.0.96
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.32
- jazz-react@0.7.32
## 0.0.95
### Patch Changes
- jazz-react@0.7.31
- jazz-tools@0.7.31
## 0.0.94
### Patch Changes
- jazz-react@0.7.30
## 0.0.93
### Patch Changes
- jazz-react@0.7.29
- jazz-tools@0.7.29
## 0.0.92
### Patch Changes
- jazz-react@0.7.28
- jazz-tools@0.7.28
## 0.0.91
### Patch Changes
- jazz-react@0.7.27
## 0.0.90
### Patch Changes
- Updated dependencies
- jazz-react@0.7.26
- jazz-tools@0.7.26
## 0.0.89
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.25
- jazz-react@0.7.25
## 0.0.88
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-todo",
"private": true,
"version": "0.0.88",
"version": "0.0.98-neverthrow.6",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -268,11 +268,7 @@ Jazz Mesh is currently free &mdash; and it's set up as the default sync & storag
## Get Started
- <Link href="/docs" target="_blank">
Read the docs
</Link>
- <Link href="https://discord.gg/utDMjHYg42" target="_blank">
Join our Discord
</Link>
- <Link href="/docs" target="_blank">Read the docs</Link>
- <Link href="https://discord.gg/utDMjHYg42" target="_blank">Join our Discord</Link>
</Prose>

View File

@@ -1,5 +1,116 @@
# cojson-storage-indexeddb
## 0.7.34-neverthrow.8
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.8
## 0.7.34-neverthrow.7
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.7
## 0.7.34-neverthrow.4
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.4
## 0.7.34-neverthrow.3
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.3
## 0.7.34-neverthrow.1
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.1
## 0.7.34-neverthrow.0
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.0
## 0.7.33
### Patch Changes
- 3bf5127: Allow crashing whole node on peer errors
- Updated dependencies [b297c93]
- Updated dependencies [3bf5127]
- Updated dependencies [a8b74ff]
- Updated dependencies [db53161]
- cojson@0.7.33
## 0.7.33-hotfixes.5
### Patch Changes
- Updated dependencies
- cojson@0.7.33-hotfixes.5
## 0.7.33-hotfixes.4
### Patch Changes
- Updated dependencies
- cojson@0.7.33-hotfixes.4
## 0.7.33-hotfixes.3
### Patch Changes
- Allow crashing whole node on peer errors
- Updated dependencies
- cojson@0.7.33-hotfixes.3
## 0.7.33-hotfixes.0
### Patch Changes
- Updated dependencies
- cojson@0.7.33-hotfixes.0
## 0.7.31
### Patch Changes
- Updated dependencies
- cojson@0.7.31
## 0.7.29
### Patch Changes
- Updated dependencies
- cojson@0.7.29
## 0.7.28
### Patch Changes
- Updated dependencies
- cojson@0.7.28
## 0.7.26
### Patch Changes
- Remove Effect from jazz/cojson internals
- Updated dependencies
- cojson@0.7.26
## 0.7.23
### Patch Changes

View File

@@ -1,13 +1,12 @@
{
"name": "cojson-storage-indexeddb",
"version": "0.7.23",
"version": "0.7.34-neverthrow.8",
"main": "dist/index.js",
"type": "module",
"types": "src/index.ts",
"license": "MIT",
"dependencies": {
"cojson": "workspace:*",
"effect": "^3.5.2",
"typescript": "^5.1.6"
},
"devDependencies": {

View File

@@ -10,7 +10,6 @@ import {
OutgoingSyncQueue,
} from "cojson";
import { SyncPromise } from "./syncPromises.js";
import { Effect, Queue, Stream } from "effect";
type CoValueRow = {
id: CojsonInternalTypes.RawCoID;
@@ -53,11 +52,15 @@ export class IDBStorage {
this.db = db;
this.toLocalNode = toLocalNode;
void fromLocalNode.pipe(
Stream.runForEach((msg) =>
Effect.tryPromise({
try: () => this.handleSyncMessage(msg),
catch: (e) =>
const processMessages = async () => {
for await (const msg of fromLocalNode) {
try {
if (msg === "Disconnected" || msg === "PingTimeout") {
throw new Error("Unexpected Disconnected message");
}
await this.handleSyncMessage(msg);
} catch (e) {
console.error(
new Error(
`Error reading from localNode, handling msg\n\n${JSON.stringify(
msg,
@@ -68,9 +71,13 @@ export class IDBStorage {
)}`,
{ cause: e },
),
}),
),
Effect.runPromise,
);
}
}
};
processMessages().catch((e) =>
console.error("Error in processMessages in IndexedDB", e),
);
}
@@ -82,25 +89,18 @@ export class IDBStorage {
localNodeName: "local",
},
): Promise<Peer> {
return Effect.runPromise(
Effect.gen(function* () {
const [localNodeAsPeer, storageAsPeer] =
yield* cojsonInternals.connectedPeers(
localNodeName,
"storage",
{ peer1role: "client", peer2role: "server", trace },
);
yield* Effect.promise(() =>
IDBStorage.open(
localNodeAsPeer.incoming,
localNodeAsPeer.outgoing,
),
);
return { ...storageAsPeer, priority: 100 };
}),
const [localNodeAsPeer, storageAsPeer] = cojsonInternals.connectedPeers(
localNodeName,
"storage",
{ peer1role: "client", peer2role: "server", trace, crashOnClose: true },
);
await IDBStorage.open(
localNodeAsPeer.incoming,
localNodeAsPeer.outgoing,
);
return { ...storageAsPeer, priority: 100 };
}
static async open(
@@ -392,35 +392,40 @@ export class IDBStorage {
),
).then(() => {
// we're done with IndexedDB stuff here so can use native Promises again
setTimeout(() =>
Effect.runPromise(
Effect.gen(this, function* () {
yield* Queue.offer(this.toLocalNode, {
action: "known",
...ourKnown,
asDependencyOf,
});
setTimeout(() => {
this.toLocalNode
.push({
action: "known",
...ourKnown,
asDependencyOf,
})
.catch((e) =>
console.error(
"Error sending known state",
e,
),
);
const nonEmptyNewContentPieces =
newContentPieces.filter(
(piece) =>
piece.header ||
Object.keys(piece.new)
.length > 0,
);
const nonEmptyNewContentPieces =
newContentPieces.filter(
(piece) =>
piece.header ||
Object.keys(piece.new).length > 0,
);
// console.log(theirKnown.id, nonEmptyNewContentPieces);
// console.log(theirKnown.id, nonEmptyNewContentPieces);
for (const piece of nonEmptyNewContentPieces) {
yield* Queue.offer(
this.toLocalNode,
piece,
);
yield* Effect.yieldNow();
}
}),
),
);
for (const piece of nonEmptyNewContentPieces) {
this.toLocalNode
.push(piece)
.catch((e) =>
console.error(
"Error sending new content piece",
e,
),
);
}
});
return Promise.resolve();
});
@@ -445,16 +450,18 @@ export class IDBStorage {
const header = msg.header;
if (!header) {
console.error("Expected to be sent header first");
void Effect.runPromise(
Queue.offer(this.toLocalNode, {
this.toLocalNode
.push({
action: "known",
id: msg.id,
header: false,
sessions: {},
isCorrection: true,
}),
);
throw new Error("Expected to be sent header first");
})
.catch((e) =>
console.error("Error sending known state", e),
);
return SyncPromise.resolve();
}
return this.makeRequest<IDBValidKey>(({ coValues }) =>
@@ -515,13 +522,18 @@ export class IDBStorage {
),
).then(() => {
if (invalidAssumptions) {
void Effect.runPromise(
Queue.offer(this.toLocalNode, {
this.toLocalNode
.push({
action: "known",
...ourKnown,
isCorrection: invalidAssumptions,
}),
);
})
.catch((e) =>
console.error(
"Error sending known state",
e,
),
);
}
});
});

View File

@@ -1,5 +1,123 @@
# cojson-storage-sqlite
## 0.7.34-neverthrow.8
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.8
## 0.7.34-neverthrow.7
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.7
## 0.7.34-neverthrow.4
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.4
## 0.7.34-neverthrow.3
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.3
## 0.7.34-neverthrow.1
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.1
## 0.7.34-neverthrow.0
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.0
## 0.7.33
### Patch Changes
- 3bf5127: Allow crashing whole node on peer errors
- Updated dependencies [b297c93]
- Updated dependencies [3bf5127]
- Updated dependencies [a8b74ff]
- Updated dependencies [db53161]
- cojson@0.7.33
## 0.7.33-hotfixes.6
### Patch Changes
- Get rid of simulated errors
## 0.7.33-hotfixes.5
### Patch Changes
- Make simulated errors even more likely
- Updated dependencies
- cojson@0.7.33-hotfixes.5
## 0.7.33-hotfixes.4
### Patch Changes
- Updated dependencies
- cojson@0.7.33-hotfixes.4
## 0.7.33-hotfixes.3
### Patch Changes
- Allow crashing whole node on peer errors
- Updated dependencies
- cojson@0.7.33-hotfixes.3
## 0.7.33-hotfixes.0
### Patch Changes
- Updated dependencies
- cojson@0.7.33-hotfixes.0
## 0.7.31
### Patch Changes
- Updated dependencies
- cojson@0.7.31
## 0.7.29
### Patch Changes
- Updated dependencies
- cojson@0.7.29
## 0.7.28
### Patch Changes
- Updated dependencies
- cojson@0.7.28
## 0.7.26
### Patch Changes
- Remove Effect from jazz/cojson internals
- Updated dependencies
- cojson@0.7.26
## 0.7.23
### Patch Changes

View File

@@ -1,14 +1,13 @@
{
"name": "cojson-storage-sqlite",
"type": "module",
"version": "0.7.23",
"version": "0.7.34-neverthrow.8",
"main": "dist/index.js",
"types": "src/index.ts",
"license": "MIT",
"dependencies": {
"better-sqlite3": "^8.5.2",
"cojson": "workspace:*",
"effect": "^3.5.2",
"typescript": "^5.1.6"
},
"devDependencies": {

View File

@@ -11,7 +11,6 @@ import {
} from "cojson";
import Database, { Database as DatabaseT } from "better-sqlite3";
import { Effect, Queue, Stream } from "effect";
type CoValueRow = {
id: CojsonInternalTypes.RawCoID;
@@ -54,11 +53,15 @@ export class SQLiteStorage {
this.db = db;
this.toLocalNode = toLocalNode;
void fromLocalNode.pipe(
Stream.runForEach((msg) =>
Effect.tryPromise({
try: () => this.handleSyncMessage(msg),
catch: (e) =>
const processMessages = async () => {
for await (const msg of fromLocalNode) {
try {
if (msg === "Disconnected" || msg === "PingTimeout") {
throw new Error("Unexpected Disconnected message");
}
await this.handleSyncMessage(msg);
} catch (e) {
console.error(
new Error(
`Error reading from localNode, handling msg\n\n${JSON.stringify(
msg,
@@ -69,9 +72,13 @@ export class SQLiteStorage {
)}`,
{ cause: e },
),
}),
),
Effect.runPromise,
);
}
}
};
processMessages().catch((e) =>
console.error("Error in processMessages in sqlite", e),
);
}
@@ -84,26 +91,19 @@ export class SQLiteStorage {
trace?: boolean;
localNodeName?: string;
}): Promise<Peer> {
return Effect.runPromise(
Effect.gen(function* () {
const [localNodeAsPeer, storageAsPeer] =
yield* cojsonInternals.connectedPeers(
localNodeName,
"storage",
{ peer1role: "client", peer2role: "server", trace },
);
yield* Effect.promise(() =>
SQLiteStorage.open(
filename,
localNodeAsPeer.incoming,
localNodeAsPeer.outgoing,
),
);
return { ...storageAsPeer, priority: 100 };
}),
const [localNodeAsPeer, storageAsPeer] = cojsonInternals.connectedPeers(
localNodeName,
"storage",
{ peer1role: "client", peer2role: "server", trace, crashOnClose: true },
);
await SQLiteStorage.open(
filename,
localNodeAsPeer.incoming,
localNodeAsPeer.outgoing,
);
return { ...storageAsPeer, priority: 100 };
}
static async open(
@@ -441,13 +441,13 @@ export class SQLiteStorage {
);
}
await Effect.runPromise(
Queue.offer(this.toLocalNode, {
this.toLocalNode
.push({
action: "known",
...ourKnown,
asDependencyOf,
}),
);
})
.catch((e) => console.error("Error while pushing known", e));
const nonEmptyNewContentPieces = newContentPieces.filter(
(piece) => piece.header || Object.keys(piece.new).length > 0,
@@ -456,7 +456,11 @@ export class SQLiteStorage {
// console.log(theirKnown.id, nonEmptyNewContentPieces);
for (const piece of nonEmptyNewContentPieces) {
await Effect.runPromise(Queue.offer(this.toLocalNode, piece));
this.toLocalNode
.push(piece)
.catch((e) =>
console.error("Error while pushing content piece", e),
);
await new Promise((resolve) => setTimeout(resolve, 0));
}
}
@@ -478,15 +482,17 @@ export class SQLiteStorage {
const header = msg.header;
if (!header) {
console.error("Expected to be sent header first");
await Effect.runPromise(
Queue.offer(this.toLocalNode, {
this.toLocalNode
.push({
action: "known",
id: msg.id,
header: false,
sessions: {},
isCorrection: true,
}),
);
})
.catch((e) =>
console.error("Error while pushing known", e),
);
return;
}
@@ -618,13 +624,13 @@ export class SQLiteStorage {
})();
if (invalidAssumptions) {
await Effect.runPromise(
Queue.offer(this.toLocalNode, {
this.toLocalNode
.push({
action: "known",
...ourKnown,
isCorrection: invalidAssumptions,
}),
);
})
.catch((e) => console.error("Error while pushing known", e));
}
}

View File

@@ -1,5 +1,149 @@
# cojson-transport-nodejs-ws
## 0.7.34-neverthrow.8
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.8
## 0.7.34-neverthrow.7
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.7
## 0.7.34-neverthrow.4
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.4
## 0.7.34-neverthrow.3
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.3
## 0.7.34-neverthrow.2
### Patch Changes
- Remove WS buffer filling log message
## 0.7.34-neverthrow.1
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.1
## 0.7.34-neverthrow.0
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.0
## 0.7.33
### Patch Changes
- fdde8db: Handle ws closing while buffering
- 07fe2b9: Wait if WS buffer is full
- 3bf5127: Allow crashing whole node on peer errors
- Updated dependencies [b297c93]
- Updated dependencies [3bf5127]
- Updated dependencies [a8b74ff]
- Updated dependencies [db53161]
- cojson@0.7.33
## 0.7.33-hotfixes.5
### Patch Changes
- Updated dependencies
- cojson@0.7.33-hotfixes.5
## 0.7.33-hotfixes.4
### Patch Changes
- Updated dependencies
- cojson@0.7.33-hotfixes.4
## 0.7.33-hotfixes.3
### Patch Changes
- Allow crashing whole node on peer errors
- Updated dependencies
- cojson@0.7.33-hotfixes.3
## 0.7.33-hotfixes.2
### Patch Changes
- Handle ws closing while buffering
## 0.7.33-hotfixes.1
### Patch Changes
- Wait if WS buffer is full
## 0.7.33-hotfixes.0
### Patch Changes
- Updated dependencies
- cojson@0.7.33-hotfixes.0
## 0.7.31
### Patch Changes
- Properly close connecting websockets
- Updated dependencies
- cojson@0.7.31
## 0.7.30
### Patch Changes
- Immediately resolve an already-open websocket
## 0.7.29
### Patch Changes
- Updated dependencies
- cojson@0.7.29
## 0.7.28
### Patch Changes
- Updated dependencies
- cojson@0.7.28
## 0.7.27
### Patch Changes
- Option to not expect pings
## 0.7.26
### Patch Changes
- Remove Effect from jazz/cojson internals
- Updated dependencies
- cojson@0.7.26
## 0.7.23
### Patch Changes

View File

@@ -1,13 +1,12 @@
{
"name": "cojson-transport-ws",
"type": "module",
"version": "0.7.23",
"version": "0.7.34-neverthrow.8",
"main": "dist/index.js",
"types": "src/index.ts",
"license": "MIT",
"dependencies": {
"cojson": "workspace:*",
"effect": "^3.5.2",
"typescript": "^5.1.6"
},
"scripts": {

View File

@@ -1,5 +1,10 @@
import { DisconnectedError, Peer, PingTimeoutError, SyncMessage } from "cojson";
import { Stream, Queue, Effect, Console } from "effect";
import {
DisconnectedError,
Peer,
PingTimeoutError,
SyncMessage,
cojsonInternals,
} from "cojson";
interface WebsocketEvents {
close: { code: number; reason: string };
@@ -15,6 +20,7 @@ interface AnyWebSocket {
addEventListener<K extends keyof WebsocketEvents>(
type: K,
listener: (event: WebsocketEvents[K]) => void,
options?: { once: boolean },
): void;
removeEventListener<K extends keyof WebsocketEvents>(
type: K,
@@ -22,6 +28,8 @@ interface AnyWebSocket {
): void;
close(): void;
send(data: string): void;
readyState: number;
bufferedAmount: number;
}
const g: typeof globalThis & {
@@ -32,88 +40,99 @@ const g: typeof globalThis & {
}[];
} = globalThis;
export function createWebSocketPeer(options: {
export function createWebSocketPeer({
id,
websocket,
role,
expectPings = true,
}: {
id: string;
websocket: AnyWebSocket;
role: Peer["role"];
}): Effect.Effect<Peer> {
return Effect.gen(function* () {
const ws = options.websocket;
const ws_ = ws as unknown as Stream.EventListener<WebsocketEvents["message"]>;
expectPings?: boolean;
}): Peer {
const incoming = new cojsonInternals.Channel<
SyncMessage | DisconnectedError | PingTimeoutError
>();
const outgoing = yield* Queue.unbounded<SyncMessage>();
const closed = once(ws, "close").pipe(
Effect.flatMap(
(event) =>
new DisconnectedError({
message: `${event.code}: ${event.reason}`,
}),
),
Stream.fromEffect,
);
const isSyncMessage = (msg: unknown): msg is SyncMessage => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if ((msg as any)?.type === "ping") {
const ping = msg as PingMsg;
g.jazzPings ||= [];
g.jazzPings.push({
received: Date.now(),
sent: ping.time,
dc: ping.dc,
});
return false;
}
return true;
};
yield* Effect.forkDaemon(Effect.gen(function* () {
yield* once(ws, "open");
yield* Queue.take(outgoing).pipe(
Effect.andThen((message) => ws.send(JSON.stringify(message))),
Effect.forever,
websocket.addEventListener("close", function handleClose() {
incoming
.push("Disconnected")
.catch((e) =>
console.error("Error while pushing disconnect msg", e),
);
}));
type E = WebsocketEvents["message"];
const messages = Stream.fromEventListener<E>(ws_, "message").pipe(
Stream.timeoutFail(() => new PingTimeoutError(), "10 seconds"),
Stream.tapError((_e) =>
Console.warn("Ping timeout").pipe(
Effect.andThen(Effect.try(() => ws.close())),
Effect.catchAll((e) =>
Console.error(
"Error while trying to close ws on ping timeout",
e,
),
),
),
),
Stream.mergeLeft(closed),
Stream.map((_) => JSON.parse(_.data as string)),
Stream.filter(isSyncMessage),
Stream.buffer({ capacity: "unbounded" }),
Stream.onDone(() => Queue.shutdown(outgoing)),
);
return {
id: options.id,
incoming: messages,
outgoing,
role: options.role,
};
});
let pingTimeout: ReturnType<typeof setTimeout> | null = null;
websocket.addEventListener("message", function handleIncomingMsg(event) {
const msg = JSON.parse(event.data as string);
pingTimeout && clearTimeout(pingTimeout);
if (msg?.type === "ping") {
const ping = msg as PingMsg;
g.jazzPings ||= [];
g.jazzPings.push({
received: Date.now(),
sent: ping.time,
dc: ping.dc,
});
} else {
incoming
.push(msg)
.catch((e) =>
console.error("Error while pushing incoming msg", e),
);
}
if (expectPings) {
pingTimeout = setTimeout(() => {
incoming
.push("PingTimeout")
.catch((e) =>
console.error("Error while pushing ping timeout", e),
);
}, 10_000);
}
});
const websocketOpen = new Promise<void>((resolve) => {
if (websocket.readyState === 1) {
resolve();
} else {
websocket.addEventListener("open", resolve, { once: true });
}
});
return {
id,
incoming,
outgoing: {
async push(msg) {
await websocketOpen;
if (websocket.readyState === 1) {
while (websocket.bufferedAmount > 1_000_000) {
await new Promise((resolve) =>
setTimeout(resolve, 100),
);
if (websocket.readyState !== 1) {
console.log("WebSocket closed while buffering", id, websocket.bufferedAmount);
return;
}
}
websocket.send(JSON.stringify(msg));
}
},
close() {
console.log("Trying to close", id, websocket.readyState)
if (websocket.readyState === 0) {
websocket.addEventListener("open", function handleClose() {
websocket.close();
}, { once: true });
} else if (websocket.readyState == 1) {
websocket.close();
}
},
},
role,
crashOnClose: false,
};
}
const once = <Event extends keyof WebsocketEvents>(
ws: AnyWebSocket,
event: Event,
) =>
Effect.async<WebsocketEvents[Event]>((register) => {
const cb = (msg: WebsocketEvents[Event]) => {
ws.removeEventListener(event, cb);
register(Effect.succeed(msg));
};
ws.addEventListener(event, cb);
});

View File

@@ -3,10 +3,10 @@ module.exports = {
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:require-extensions/recommended",
"prettier"
"prettier",
],
parser: "@typescript-eslint/parser",
plugins: ["@typescript-eslint", "require-extensions"],
plugins: ["@typescript-eslint", "require-extensions", "neverthrow"],
parserOptions: {
project: "./tsconfig.json",
tsconfigRootDir: __dirname,
@@ -20,5 +20,6 @@ module.exports = {
{ argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
],
"@typescript-eslint/no-floating-promises": "error",
"neverthrow/must-use-result": "error",
},
}
};

View File

@@ -1,5 +1,97 @@
# cojson
## 0.7.34-neverthrow.8
### Patch Changes
- Use queueable fork
## 0.7.34-neverthrow.7
### Patch Changes
- Use fork of queueable
## 0.7.34-neverthrow.4
### Patch Changes
- Stop using tryAddTransactionsAsync
## 0.7.34-neverthrow.3
### Patch Changes
- Better log message for failed transactions
## 0.7.34-neverthrow.1
### Patch Changes
- Only one async transaction per CoValue at a time again
## 0.7.34-neverthrow.0
### Patch Changes
- Start introducing neverthrow, make tryAddNewTransactionsAsync and handleNewContent less throwy
## 0.7.33
### Patch Changes
- b297c93: Improve logging
- 3bf5127: Allow crashing whole node on peer errors
- a8b74ff: Throw properly on peer that should crash on close
## 0.7.33-hotfixes.5
### Patch Changes
- Make simulated errors even more likely
## 0.7.33-hotfixes.4
### Patch Changes
- Throw properly on peer that should crash on close
## 0.7.33-hotfixes.3
### Patch Changes
- Allow crashing whole node on peer errors
## 0.7.33-hotfixes.0
### Patch Changes
- Improve logging
## 0.7.31
### Patch Changes
- Close both ends of the peer on gracefulShutdown
## 0.7.29
### Patch Changes
- Remove noisy log
## 0.7.28
### Patch Changes
- Fix ignoring server peers
## 0.7.26
### Patch Changes
- Remove Effect from jazz/cojson internals
## 0.7.23
### Patch Changes

View File

@@ -5,25 +5,27 @@
"types": "src/index.ts",
"type": "module",
"license": "MIT",
"version": "0.7.23",
"version": "0.7.34-neverthrow.8",
"devDependencies": {
"@types/jest": "^29.5.3",
"@typescript-eslint/eslint-plugin": "^6.2.1",
"@typescript-eslint/parser": "^6.2.1",
"eslint": "^8.46.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-neverthrow": "^1.1.4",
"eslint-plugin-require-extensions": "^0.1.3",
"typescript": "5.0.2",
"vitest": "^0.34.6"
},
"dependencies": {
"@hazae41/berith": "^1.2.6",
"@noble/curves": "^1.3.0",
"@noble/ciphers": "^0.1.3",
"@noble/curves": "^1.3.0",
"@noble/hashes": "^1.4.0",
"@scure/base": "^1.1.1",
"effect": "^3.5.2",
"hash-wasm": "^4.9.0"
"hash-wasm": "^4.9.0",
"neverthrow": "^7.0.1",
"queueueue": "^4.1.2"
},
"scripts": {
"dev": "tsc --watch --sourceMap --outDir dist",

View File

@@ -7,6 +7,7 @@ import {
StreamingHash,
KeyID,
CryptoProvider,
SignerID,
} from "./crypto/crypto.js";
import { JsonObject, JsonValue } from "./jsonValue.js";
import { base58 } from "@scure/base";
@@ -16,7 +17,7 @@ import {
isKeyForKeyField,
} from "./permissions.js";
import { RawGroup } from "./coValues/group.js";
import { LocalNode } from "./localNode.js";
import { LocalNode, ResolveAccountAgentError } from "./localNode.js";
import { CoValueKnownState, NewContentMessage } from "./sync.js";
import { AgentID, RawCoID, SessionID, TransactionID } from "./ids.js";
import { AccountID, ControlledAccountOrAgent } from "./coValues/account.js";
@@ -25,7 +26,15 @@ import { coreToCoValue } from "./coreToCoValue.js";
import { expectGroup } from "./typeUtils/expectGroup.js";
import { isAccountID } from "./typeUtils/isAccountID.js";
import { accountOrAgentIDfromSessionID } from "./typeUtils/accountOrAgentIDfromSessionID.js";
import { err, ok, Result } from "neverthrow";
/**
In order to not block other concurrently syncing CoValues we introduce a maximum size of transactions,
since they are the smallest unit of progress that can be synced within a CoValue.
This is particularly important for storing binary data in CoValues, since they are likely to be at least on the order of megabytes.
This also means that we want to keep signatures roughly after each MAX_RECOMMENDED_TX size chunk,
to be able to verify partially loaded CoValues or CoValues that are still being created (like a video live stream).
**/
export const MAX_RECOMMENDED_TX_SIZE = 100 * 1024;
export type CoValueHeader = {
@@ -96,10 +105,10 @@ export class CoValueCore {
_decryptionCache: {
[key: Encrypted<JsonValue[], JsonValue>]: JsonValue[] | undefined;
} = {};
currentlyAsyncApplyingTxDone?: Promise<void>;
_cachedKnownState?: CoValueKnownState;
_cachedDependentOn?: RawCoID[];
_cachedNewContentSinceEmpty?: NewContentMessage[] | undefined;
_currentAsyncAddTransaction?: Promise<void>;
constructor(
header: CoValueHeader,
@@ -175,7 +184,9 @@ export class CoValueCore {
this.header.meta?.type === "account"
? (this.node.currentSessionID.replace(
this.node.account.id,
this.node.account.currentAgentID(),
this.node.account
.currentAgentID()
._unsafeUnwrap({ withStackTrace: true }),
) as SessionID)
: this.node.currentSessionID;
@@ -190,167 +201,198 @@ export class CoValueCore {
newTransactions: Transaction[],
givenExpectedNewHash: Hash | undefined,
newSignature: Signature,
): boolean {
const signerID = this.crypto.getAgentSignerID(
this.node.resolveAccountAgent(
): Result<true, TryAddTransactionsError> {
return this.node
.resolveAccountAgent(
accountOrAgentIDfromSessionID(sessionID),
"Expected to know signer of transaction",
),
);
)
.andThen((agent) => {
const signerID = this.crypto.getAgentSignerID(agent);
if (!signerID) {
console.warn(
"Unknown agent",
accountOrAgentIDfromSessionID(sessionID),
);
return false;
}
// const beforeHash = performance.now();
const { expectedNewHash, newStreamingHash } =
this.expectedNewHashAfter(sessionID, newTransactions);
// const afterHash = performance.now();
// console.log(
// "Hashing took",
// afterHash - beforeHash
// );
// const beforeHash = performance.now();
const { expectedNewHash, newStreamingHash } = this.expectedNewHashAfter(
sessionID,
newTransactions,
);
// const afterHash = performance.now();
// console.log(
// "Hashing took",
// afterHash - beforeHash
// );
if (
givenExpectedNewHash &&
givenExpectedNewHash !== expectedNewHash
) {
return err({
type: "InvalidHash",
id: this.id,
expectedNewHash,
givenExpectedNewHash,
} satisfies InvalidHashError);
}
if (givenExpectedNewHash && givenExpectedNewHash !== expectedNewHash) {
console.warn("Invalid hash", {
expectedNewHash,
givenExpectedNewHash,
// const beforeVerify = performance.now();
if (
!this.crypto.verify(newSignature, expectedNewHash, signerID)
) {
return err({
type: "InvalidSignature",
id: this.id,
newSignature,
sessionID,
signerID,
} satisfies InvalidSignatureError);
}
// const afterVerify = performance.now();
// console.log(
// "Verify took",
// afterVerify - beforeVerify
// );
this.doAddTransactions(
sessionID,
newTransactions,
newSignature,
expectedNewHash,
newStreamingHash,
"immediate",
);
return ok(true as const);
});
return false;
}
// const beforeVerify = performance.now();
if (!this.crypto.verify(newSignature, expectedNewHash, signerID)) {
console.warn(
"Invalid signature in",
this.id,
newSignature,
expectedNewHash,
signerID,
);
return false;
}
// const afterVerify = performance.now();
// console.log(
// "Verify took",
// afterVerify - beforeVerify
// );
this.doAddTransactions(
sessionID,
newTransactions,
newSignature,
expectedNewHash,
newStreamingHash,
"immediate",
);
return true;
}
async tryAddTransactionsAsync(
/*tryAddTransactionsAsync(
sessionID: SessionID,
newTransactions: Transaction[],
givenExpectedNewHash: Hash | undefined,
newSignature: Signature,
): Promise<boolean> {
if (this.currentlyAsyncApplyingTxDone) {
await this.currentlyAsyncApplyingTxDone;
}
let resolveDone!: () => void;
): ResultAsync<true, TryAddTransactionsError> {
const currentAsyncAddTransaction = this._currentAsyncAddTransaction;
let maybeAwaitPrevious:
| ResultAsync<void, TryAddTransactionsError>
| undefined;
let thisDone = () => {};
this.currentlyAsyncApplyingTxDone = new Promise((resolve) => {
resolveDone = resolve;
});
const signerID = this.crypto.getAgentSignerID(
await this.node.resolveAccountAgentAsync(
accountOrAgentIDfromSessionID(sessionID),
"Expected to know signer of transaction",
),
);
if (!signerID) {
console.warn(
"Unknown agent",
accountOrAgentIDfromSessionID(sessionID),
if (currentAsyncAddTransaction) {
// eslint-disable-next-line neverthrow/must-use-result
maybeAwaitPrevious = ResultAsync.fromSafePromise(
currentAsyncAddTransaction,
);
resolveDone();
return false;
}
const nTxBefore =
this.sessionLogs.get(sessionID)?.transactions.length ?? 0;
// const beforeHash = performance.now();
const { expectedNewHash, newStreamingHash } =
await this.expectedNewHashAfterAsync(sessionID, newTransactions);
// const afterHash = performance.now();
// console.log(
// "Hashing took",
// afterHash - beforeHash
// );
const nTxAfter =
this.sessionLogs.get(sessionID)?.transactions.length ?? 0;
if (nTxAfter !== nTxBefore) {
const newTransactionLengthBefore = newTransactions.length;
newTransactions = newTransactions.slice(nTxAfter - nTxBefore);
console.warn("Transactions changed while async hashing", {
nTxBefore,
nTxAfter,
newTransactionLengthBefore,
remainingNewTransactions: newTransactions.length,
} else {
// eslint-disable-next-line neverthrow/must-use-result
maybeAwaitPrevious = ResultAsync.fromSafePromise(Promise.resolve());
this._currentAsyncAddTransaction = new Promise((resolve) => {
thisDone = resolve;
});
}
if (givenExpectedNewHash && givenExpectedNewHash !== expectedNewHash) {
console.warn("Invalid hash", {
expectedNewHash,
givenExpectedNewHash,
return maybeAwaitPrevious
.andThen((_previousDone) =>
this.node
.resolveAccountAgentAsync(
accountOrAgentIDfromSessionID(sessionID),
"Expected to know signer of transaction",
)
.andThen((agent) => {
const signerID = this.crypto.getAgentSignerID(agent);
const nTxBefore =
this.sessionLogs.get(sessionID)?.transactions
.length ?? 0;
// const beforeHash = performance.now();
return ResultAsync.fromSafePromise(
this.expectedNewHashAfterAsync(
sessionID,
newTransactions,
),
).andThen(({ expectedNewHash, newStreamingHash }) => {
// const afterHash = performance.now();
// console.log(
// "Hashing took",
// afterHash - beforeHash
// );
const nTxAfter =
this.sessionLogs.get(sessionID)?.transactions
.length ?? 0;
if (nTxAfter !== nTxBefore) {
const newTransactionLengthBefore =
newTransactions.length;
newTransactions = newTransactions.slice(
nTxAfter - nTxBefore,
);
console.warn(
"Transactions changed while async hashing",
{
nTxBefore,
nTxAfter,
newTransactionLengthBefore,
remainingNewTransactions:
newTransactions.length,
},
);
}
if (
givenExpectedNewHash &&
givenExpectedNewHash !== expectedNewHash
) {
return err({
type: "InvalidHash",
id: this.id,
expectedNewHash,
givenExpectedNewHash,
} satisfies InvalidHashError);
}
performance.mark("verifyStart" + this.id);
if (
!this.crypto.verify(
newSignature,
expectedNewHash,
signerID,
)
) {
return err({
type: "InvalidSignature",
id: this.id,
newSignature,
sessionID,
signerID,
} satisfies InvalidSignatureError);
}
performance.mark("verifyEnd" + this.id);
performance.measure(
"verify" + this.id,
"verifyStart" + this.id,
"verifyEnd" + this.id,
);
this.doAddTransactions(
sessionID,
newTransactions,
newSignature,
expectedNewHash,
newStreamingHash,
"deferred",
);
return ok(true as const);
});
}),
)
.map((trueResult) => {
thisDone();
return trueResult;
})
.mapErr((err) => {
thisDone();
return err;
});
resolveDone();
return false;
}
performance.mark("verifyStart" + this.id);
if (!this.crypto.verify(newSignature, expectedNewHash, signerID)) {
console.warn(
"Invalid signature in",
this.id,
newSignature,
expectedNewHash,
signerID,
);
resolveDone();
return false;
}
performance.mark("verifyEnd" + this.id);
performance.measure(
"verify" + this.id,
"verifyStart" + this.id,
"verifyEnd" + this.id,
);
this.doAddTransactions(
sessionID,
newTransactions,
newSignature,
expectedNewHash,
newStreamingHash,
"deferred",
);
resolveDone();
return true;
}
}*/
private doAddTransactions(
sessionID: SessionID,
@@ -360,6 +402,9 @@ export class CoValueCore {
newStreamingHash: StreamingHash,
notifyMode: "immediate" | "deferred",
) {
if (this.node.crashed) {
throw new Error("Trying to add transactions after node is crashed");
}
const transactions =
this.sessionLogs.get(sessionID)?.transactions ?? [];
transactions.push(...newTransactions);
@@ -383,7 +428,7 @@ export class CoValueCore {
0,
);
if (sizeOfTxsSinceLastInbetweenSignature > 100 * 1024) {
if (sizeOfTxsSinceLastInbetweenSignature > MAX_RECOMMENDED_TX_SIZE) {
// console.log(
// "Saving inbetween signature for tx ",
// sessionID,
@@ -535,7 +580,9 @@ export class CoValueCore {
this.header.meta?.type === "account"
? (this.node.currentSessionID.replace(
this.node.account.id,
this.node.account.currentAgentID(),
this.node.account
.currentAgentID()
._unsafeUnwrap({ withStackTrace: true }),
) as SessionID)
: this.node.currentSessionID;
@@ -553,7 +600,7 @@ export class CoValueCore {
[transaction],
expectedNewHash,
signature,
);
)._unsafeUnwrap({ withStackTrace: true });
if (success) {
void this.node.syncManager.syncCoValue(this);
@@ -699,7 +746,9 @@ export class CoValueCore {
// Try to find key revelation for us
const lookupAccountOrAgentID =
this.header.meta?.type === "account"
? this.node.account.currentAgentID()
? this.node.account
.currentAgentID()
._unsafeUnwrap({ withStackTrace: true })
: this.node.account.id;
const lastReadyKeyEdit = content.lastEditAt(
@@ -708,10 +757,9 @@ export class CoValueCore {
if (lastReadyKeyEdit?.value) {
const revealer = lastReadyKeyEdit.by;
const revealerAgent = this.node.resolveAccountAgent(
revealer,
"Expected to know revealer",
);
const revealerAgent = this.node
.resolveAccountAgent(revealer, "Expected to know revealer")
._unsafeUnwrap({ withStackTrace: true });
const secret = this.crypto.unseal(
lastReadyKeyEdit.value,
@@ -976,3 +1024,23 @@ function getNextKnownSignatureIdx(
idx >= (sentStateForSessionID ?? knownStateForSessionID ?? -1),
);
}
export type InvalidHashError = {
type: "InvalidHash";
id: RawCoID;
expectedNewHash: Hash;
givenExpectedNewHash: Hash;
};
export type InvalidSignatureError = {
type: "InvalidSignature";
id: RawCoID;
newSignature: Signature;
sessionID: SessionID;
signerID: SignerID;
};
export type TryAddTransactionsError =
| ResolveAccountAgentError
| InvalidHashError
| InvalidSignatureError;

View File

@@ -13,6 +13,7 @@ import { RawCoMap } from "./coMap.js";
import { RawGroup, InviteSecret } from "./group.js";
import { LocalNode } from "../index.js";
import { JsonObject } from "../jsonValue.js";
import { err, ok, Result } from "neverthrow";
export function accountHeaderForInitialAgentSecret(
agentSecret: AgentSecret,
@@ -30,28 +31,34 @@ export function accountHeaderForInitialAgentSecret(
};
}
export type InvalidAccountAgentIDError = {
type: "InvalidAccountAgentID";
reason: string;
};
export class RawAccount<
Meta extends AccountMeta = AccountMeta,
> extends RawGroup<Meta> {
_cachedCurrentAgentID: AgentID | undefined;
currentAgentID(): AgentID {
currentAgentID(): Result<AgentID, InvalidAccountAgentIDError> {
if (this._cachedCurrentAgentID) {
return this._cachedCurrentAgentID;
return ok(this._cachedCurrentAgentID);
}
const agents = this.keys().filter((k): k is AgentID =>
k.startsWith("sealer_"),
);
if (agents.length !== 1) {
throw new Error(
"Expected exactly one agent in account, got " + agents.length,
);
return err({
type: "InvalidAccountAgentID",
reason: "Account has " + agents.length + " agents",
});
}
this._cachedCurrentAgentID = agents[0];
return agents[0]!;
return ok(agents[0]!);
}
}
@@ -59,10 +66,10 @@ export interface ControlledAccountOrAgent {
id: AccountID | AgentID;
agentSecret: AgentSecret;
currentAgentID: () => AgentID;
currentSignerID: () => SignerID;
currentAgentID: () => Result<AgentID, InvalidAccountAgentIDError>;
currentSignerID: () => Result<SignerID, InvalidAccountAgentIDError>;
currentSignerSecret: () => SignerSecret;
currentSealerID: () => SealerID;
currentSealerID: () => Result<SealerID, InvalidAccountAgentIDError>;
currentSealerSecret: () => SealerSecret;
}
@@ -96,25 +103,29 @@ export class RawControlledAccount<Meta extends AccountMeta = AccountMeta>
return this.core.node.acceptInvite(groupOrOwnedValueID, inviteSecret);
}
currentAgentID(): AgentID {
currentAgentID(): Result<AgentID, InvalidAccountAgentIDError> {
if (this._cachedCurrentAgentID) {
return this._cachedCurrentAgentID;
return ok(this._cachedCurrentAgentID);
}
const agentID = this.crypto.getAgentID(this.agentSecret);
this._cachedCurrentAgentID = agentID;
return agentID;
return ok(agentID);
}
currentSignerID(): SignerID {
return this.crypto.getAgentSignerID(this.currentAgentID());
currentSignerID() {
return this.currentAgentID().map((id) =>
this.crypto.getAgentSignerID(id),
);
}
currentSignerSecret(): SignerSecret {
return this.crypto.getAgentSignerSecret(this.agentSecret);
}
currentSealerID(): SealerID {
return this.crypto.getAgentSealerID(this.currentAgentID());
currentSealerID() {
return this.currentAgentID().map((id) =>
this.crypto.getAgentSealerID(id),
);
}
currentSealerSecret(): SealerSecret {
@@ -133,20 +144,24 @@ export class ControlledAgent implements ControlledAccountOrAgent {
return this.crypto.getAgentID(this.agentSecret);
}
currentAgentID(): AgentID {
return this.crypto.getAgentID(this.agentSecret);
currentAgentID() {
return ok(this.crypto.getAgentID(this.agentSecret));
}
currentSignerID(): SignerID {
return this.crypto.getAgentSignerID(this.currentAgentID());
currentSignerID() {
return this.currentAgentID().map((id) =>
this.crypto.getAgentSignerID(id),
);
}
currentSignerSecret(): SignerSecret {
return this.crypto.getAgentSignerSecret(this.agentSecret);
}
currentSealerID(): SealerID {
return this.crypto.getAgentSealerID(this.currentAgentID());
currentSealerID() {
return this.currentAgentID().map((id) =>
this.crypto.getAgentSealerID(id),
);
}
currentSealerSecret(): SealerSecret {

View File

@@ -120,7 +120,9 @@ export class RawGroup<
const agent =
typeof account === "string"
? account
: account.currentAgentID();
: account
.currentAgentID()
._unsafeUnwrap({ withStackTrace: true });
this.set(memberKey, role, "trusting");
if (this.get(memberKey) !== role) {
@@ -175,7 +177,7 @@ export class RawGroup<
const reader = this.core.node.resolveAccountAgent(
readerID,
"Expected to know currently permitted reader",
);
)._unsafeUnwrap({ withStackTrace: true });
this.set(
`${newReadKey.id}_for_${readerID}`,

View File

@@ -18,7 +18,7 @@ import {
} from "./crypto/crypto.js";
import { WasmCrypto } from "./crypto/WasmCrypto.js";
import { PureJSCrypto } from "./crypto/PureJSCrypto.js";
import { connectedPeers } from "./streamUtils.js";
import { connectedPeers, Channel } from "./streamUtils.js";
import { ControlledAgent, RawControlledAccount } from "./coValues/account.js";
import type { Role } from "./permissions.js";
import { rawCoIDtoBytes, rawCoIDfromBytes, isRawCoID } from "./ids.js";
@@ -59,12 +59,7 @@ import type * as Media from "./media.js";
type Value = JsonValue | AnyRawCoValue;
import {
LSMStorage,
FSErr,
BlockFilename,
WalFilename,
} from "./storage/index.js";
import { LSMStorage, BlockFilename, WalFilename } from "./storage/index.js";
import { FileSystem } from "./storage/FileSystem.js";
/** @hidden */
@@ -84,6 +79,7 @@ export const cojsonInternals = {
accountHeaderForInitialAgentSecret,
idforHeader,
StreamingHash,
Channel,
};
export {
@@ -123,18 +119,17 @@ export {
SyncMessage,
isRawCoID,
LSMStorage,
DisconnectedError,
PingTimeoutError,
};
export type {
Value,
FileSystem,
FSErr,
BlockFilename,
WalFilename,
IncomingSyncStream,
OutgoingSyncQueue,
DisconnectedError,
PingTimeoutError,
};
// eslint-disable-next-line @typescript-eslint/no-namespace

View File

@@ -22,9 +22,11 @@ import {
AccountID,
RawProfile,
RawAccountMigration,
InvalidAccountAgentIDError,
} from "./coValues/account.js";
import { Profile, RawCoValue } from "./index.js";
import { expectGroup } from "./typeUtils/expectGroup.js";
import { err, ok, okAsync, Result, ResultAsync } from "neverthrow";
/** A `LocalNode` represents a local view of a set of loaded `CoValue`s, from the perspective of a particular account (or primitive cryptographic agent).
@@ -49,6 +51,8 @@ export class LocalNode {
/** @category 3. Low-level */
syncManager = new SyncManager(this);
crashed: Error | undefined = undefined;
/** @category 3. Low-level */
constructor(
account: ControlledAccountOrAgent,
@@ -240,6 +244,12 @@ export class LocalNode {
/** @internal */
createCoValue(header: CoValueHeader): CoValueCore {
if (this.crashed) {
throw new Error("Trying to create CoValue after node has crashed", {
cause: this.crashed,
});
}
const coValue = new CoValueCore(header, this);
this.coValues[coValue.id] = { state: "loaded", coValue: coValue };
@@ -257,6 +267,12 @@ export class LocalNode {
onProgress?: (progress: number) => void;
} = {},
): Promise<CoValueCore | "unavailable"> {
if (this.crashed) {
throw new Error("Trying to load CoValue after node has crashed", {
cause: this.crashed,
});
}
let entry = this.coValues[id];
if (!entry) {
const peersToWaitFor = new Set(
@@ -519,9 +535,9 @@ export class LocalNode {
resolveAccountAgent(
id: AccountID | AgentID,
expectation?: string,
): AgentID {
): Result<AgentID, ResolveAccountAgentError> {
if (isAgentID(id)) {
return id;
return ok(id);
}
const coValue = this.expectCoValueLoaded(id, expectation);
@@ -533,49 +549,58 @@ export class LocalNode {
!("type" in coValue.header.meta) ||
coValue.header.meta.type !== "account"
) {
throw new Error(
`${
expectation ? expectation + ": " : ""
}CoValue ${id} is not an account`,
);
return err({
type: "UnexpectedlyNotAccount",
expectation,
id,
} satisfies UnexpectedlyNotAccountError);
}
return (coValue.getCurrentContent() as RawAccount).currentAgentID();
}
async resolveAccountAgentAsync(
resolveAccountAgentAsync(
id: AccountID | AgentID,
expectation?: string,
): Promise<AgentID> {
): ResultAsync<AgentID, ResolveAccountAgentError> {
if (isAgentID(id)) {
return id;
return okAsync(id);
}
const coValue = await this.loadCoValueCore(id);
return ResultAsync.fromPromise(
this.loadCoValueCore(id),
(e) =>
({
type: "ErrorLoadingCoValueCore",
expectation,
id,
error: e,
}) satisfies LoadCoValueCoreError,
).andThen((coValue) => {
if (coValue === "unavailable") {
return err({
type: "AccountUnavailableFromAllPeers" as const,
expectation,
id,
} satisfies AccountUnavailableFromAllPeersError);
}
if (coValue === "unavailable") {
throw new Error(
`${
expectation ? expectation + ": " : ""
}Account ${id} is unavailable from all peers`,
);
}
if (
coValue.header.type !== "comap" ||
coValue.header.ruleset.type !== "group" ||
!coValue.header.meta ||
!("type" in coValue.header.meta) ||
coValue.header.meta.type !== "account"
) {
return err({
type: "UnexpectedlyNotAccount" as const,
expectation,
id,
} satisfies UnexpectedlyNotAccountError);
}
if (
coValue.header.type !== "comap" ||
coValue.header.ruleset.type !== "group" ||
!coValue.header.meta ||
!("type" in coValue.header.meta) ||
coValue.header.meta.type !== "account"
) {
throw new Error(
`${
expectation ? expectation + ": " : ""
}CoValue ${id} is not an account`,
);
}
return (coValue.getCurrentContent() as RawAccount).currentAgentID();
return (coValue.getCurrentContent() as RawAccount).currentAgentID();
});
}
/**
@@ -600,7 +625,9 @@ export class LocalNode {
this.crypto.seal({
message: readKey.secret,
from: this.account.currentSealerSecret(),
to: this.account.currentSealerID(),
to: this.account
.currentSealerID()
._unsafeUnwrap({ withStackTrace: true }),
nOnceMaterial: {
in: groupCoValue.id,
tx: groupCoValue.nextTransactionID(),
@@ -670,6 +697,10 @@ export class LocalNode {
return newNode;
}
gracefulShutdown() {
this.syncManager.gracefulShutdown();
}
}
/** @internal */
@@ -696,6 +727,31 @@ type CoValueState =
onProgress?: (progress: number) => void;
};
export type LoadCoValueCoreError = {
type: "ErrorLoadingCoValueCore";
error: unknown;
expectation?: string;
id: AccountID;
};
export type AccountUnavailableFromAllPeersError = {
type: "AccountUnavailableFromAllPeers";
expectation?: string;
id: AccountID;
};
export type UnexpectedlyNotAccountError = {
type: "UnexpectedlyNotAccount";
expectation?: string;
id: AccountID;
};
export type ResolveAccountAgentError =
| InvalidAccountAgentIDError
| LoadCoValueCoreError
| AccountUnavailableFromAllPeersError
| UnexpectedlyNotAccountError;
/** @internal */
export function newLoadingState(
currentPeerIds: Set<PeerID>,

View File

@@ -249,8 +249,22 @@ export function determineValidTransactions(
const effectiveTransactor =
transactor === groupContent.id &&
groupAtTime instanceof RawAccount
? groupAtTime.currentAgentID()
? groupAtTime.currentAgentID().match(
(agentID) => agentID,
(e) => {
console.error(
"Error while determining current agent ID in valid transactions",
e,
);
return undefined;
},
)
: transactor;
if (!effectiveTransactor) {
return false;
}
const transactorRoleAtTxTime =
groupAtTime.get(effectiveTransactor) ||
groupAtTime.get(EVERYONE);

View File

@@ -1,4 +1,3 @@
import { Effect } from "effect";
import { CoValueChunk } from "./index.js";
import { RawCoID } from "../ids.js";
import { CryptoProvider, StreamingHash } from "../crypto/crypto.js";
@@ -11,150 +10,124 @@ export type WalEntry = { id: RawCoID } & CoValueChunk;
export type WalFilename = `wal-${number}.jsonl`;
export type FSErr = {
type: "fileSystemError";
error: Error;
};
export interface FileSystem<WriteHandle, ReadHandle> {
crypto: CryptoProvider;
createFile(filename: string): Effect.Effect<WriteHandle, FSErr>;
append(handle: WriteHandle, data: Uint8Array): Effect.Effect<void, FSErr>;
close(handle: ReadHandle | WriteHandle): Effect.Effect<void, FSErr>;
closeAndRename(
handle: WriteHandle,
filename: BlockFilename,
): Effect.Effect<void, FSErr>;
openToRead(
filename: string,
): Effect.Effect<{ handle: ReadHandle; size: number }, FSErr>;
createFile(filename: string): Promise<WriteHandle>;
append(handle: WriteHandle, data: Uint8Array): Promise<void>;
close(handle: ReadHandle | WriteHandle): Promise<void>;
closeAndRename(handle: WriteHandle, filename: BlockFilename): Promise<void>;
openToRead(filename: string): Promise<{ handle: ReadHandle; size: number }>;
read(
handle: ReadHandle,
offset: number,
length: number,
): Effect.Effect<Uint8Array, FSErr>;
listFiles(): Effect.Effect<string[], FSErr>;
removeFile(
filename: BlockFilename | WalFilename,
): Effect.Effect<void, FSErr>;
): Promise<Uint8Array>;
listFiles(): Promise<string[]>;
removeFile(filename: BlockFilename | WalFilename): Promise<void>;
}
export const textEncoder = new TextEncoder();
export const textDecoder = new TextDecoder();
export function readChunk<RH, FS extends FileSystem<unknown, RH>>(
export async function readChunk<RH, FS extends FileSystem<unknown, RH>>(
handle: RH,
header: { start: number; length: number },
fs: FS,
): Effect.Effect<CoValueChunk, FSErr> {
return Effect.gen(function* ($) {
const chunkBytes = yield* $(
fs.read(handle, header.start, header.length),
);
): Promise<CoValueChunk> {
const chunkBytes = await fs.read(handle, header.start, header.length);
const chunk = JSON.parse(textDecoder.decode(chunkBytes));
return chunk;
});
const chunk = JSON.parse(textDecoder.decode(chunkBytes));
return chunk;
}
export function readHeader<RH, FS extends FileSystem<unknown, RH>>(
export async function readHeader<RH, FS extends FileSystem<unknown, RH>>(
filename: string,
handle: RH,
size: number,
fs: FS,
): Effect.Effect<BlockHeader, FSErr> {
return Effect.gen(function* ($) {
const headerLength = Number(filename.match(/-H(\d+)\.jsonl$/)![1]!);
): Promise<BlockHeader> {
const headerLength = Number(filename.match(/-H(\d+)\.jsonl$/)![1]!);
const headerBytes = yield* $(
fs.read(handle, size - headerLength, headerLength),
);
const headerBytes = await fs.read(
handle,
size - headerLength,
headerLength,
);
const header = JSON.parse(textDecoder.decode(headerBytes));
return header;
});
const header = JSON.parse(textDecoder.decode(headerBytes));
return header;
}
export function writeBlock<WH, RH, FS extends FileSystem<WH, RH>>(
export async function writeBlock<WH, RH, FS extends FileSystem<WH, RH>>(
chunks: Map<RawCoID, CoValueChunk>,
level: number,
blockNumber: number,
fs: FS,
): Effect.Effect<BlockFilename, FSErr> {
): Promise<BlockFilename> {
if (chunks.size === 0) {
return Effect.die(new Error("No chunks to write"));
throw new Error("No chunks to write");
}
return Effect.gen(function* ($) {
const blockHeader: BlockHeader = [];
const blockHeader: BlockHeader = [];
let offset = 0;
let offset = 0;
const file = yield* $(
fs.createFile(
"wipBlock" +
Math.random().toString(36).substring(7) +
".tmp.jsonl",
),
);
const hash = new StreamingHash(fs.crypto);
const file = await fs.createFile(
"wipBlock" + Math.random().toString(36).substring(7) + ".tmp.jsonl",
);
const hash = new StreamingHash(fs.crypto);
const chunksSortedById = Array.from(chunks).sort(([id1], [id2]) =>
id1.localeCompare(id2),
);
const chunksSortedById = Array.from(chunks).sort(([id1], [id2]) =>
id1.localeCompare(id2),
);
for (const [id, chunk] of chunksSortedById) {
const encodedBytes = hash.update(chunk);
const encodedBytesWithNewline = new Uint8Array(
encodedBytes.length + 1,
);
encodedBytesWithNewline.set(encodedBytes);
encodedBytesWithNewline[encodedBytes.length] = 10;
yield* $(fs.append(file, encodedBytesWithNewline));
const length = encodedBytesWithNewline.length;
blockHeader.push({ id, start: offset, length });
offset += length;
}
for (const [id, chunk] of chunksSortedById) {
const encodedBytes = hash.update(chunk);
const encodedBytesWithNewline = new Uint8Array(encodedBytes.length + 1);
encodedBytesWithNewline.set(encodedBytes);
encodedBytesWithNewline[encodedBytes.length] = 10;
await fs.append(file, encodedBytesWithNewline);
const length = encodedBytesWithNewline.length;
blockHeader.push({ id, start: offset, length });
offset += length;
}
const headerBytes = textEncoder.encode(JSON.stringify(blockHeader));
yield* $(fs.append(file, headerBytes));
const headerBytes = textEncoder.encode(JSON.stringify(blockHeader));
await fs.append(file, headerBytes);
// console.log(
// "full file",
// yield* $(
// fs.read(file as unknown as RH, 0, offset + headerBytes.length),
// ),
// );
// console.log(
// "full file",
// yield* $(
// fs.read(file as unknown as RH, 0, offset + headerBytes.length),
// ),
// );
const filename: BlockFilename = `L${level}-${(
blockNumber + ""
).padStart(3, "0")}-${hash
.digest()
.replace("hash_", "")
.slice(0, 15)}-H${headerBytes.length}.jsonl`;
// console.log("renaming to" + filename);
yield* $(fs.closeAndRename(file, filename));
const filename: BlockFilename = `L${level}-${(blockNumber + "").padStart(
3,
"0",
)}-${hash.digest().replace("hash_", "").slice(0, 15)}-H${
headerBytes.length
}.jsonl`;
// console.log("renaming to" + filename);
await fs.closeAndRename(file, filename);
return filename;
return filename;
// console.log("Wrote block", filename, blockHeader);
// console.log("IDs in block", blockHeader.map(e => e.id));
});
// console.log("Wrote block", filename, blockHeader);
// console.log("IDs in block", blockHeader.map(e => e.id));
}
export function writeToWal<WH, RH, FS extends FileSystem<WH, RH>>(
export async function writeToWal<WH, RH, FS extends FileSystem<WH, RH>>(
handle: WH,
fs: FS,
id: RawCoID,
chunk: CoValueChunk,
): Effect.Effect<void, FSErr> {
return Effect.gen(function* ($) {
const walEntry: WalEntry = {
id,
...chunk,
};
const bytes = textEncoder.encode(JSON.stringify(walEntry) + "\n");
console.log("writing to WAL", handle, id, bytes.length);
yield* $(fs.append(handle, bytes));
});
) {
const walEntry: WalEntry = {
id,
...chunk,
};
const bytes = textEncoder.encode(JSON.stringify(walEntry) + "\n");
console.log("writing to WAL", handle, id, bytes.length);
return fs.append(handle, bytes);
}

View File

@@ -1,4 +1,3 @@
import { Either } from "effect";
import { RawCoID, SessionID } from "../ids.js";
import { MAX_RECOMMENDED_TX_SIZE } from "../index.js";
import { CoValueKnownState, NewContentMessage } from "../sync.js";
@@ -80,7 +79,7 @@ export function chunkToKnownState(id: RawCoID, chunk: CoValueChunk) {
export function mergeChunks(
chunkA: CoValueChunk,
chunkB: CoValueChunk,
): Either.Either<"nonContigous", CoValueChunk> {
): "nonContigous" | CoValueChunk {
const header = chunkA.header || chunkB.header;
const newSessions = { ...chunkA.sessionEntries };
@@ -133,9 +132,9 @@ export function mergeChunks(
}
newSessions[sessionID] = newEntries;
} else {
return Either.right("nonContigous" as const);
return "nonContigous" as const;
}
}
return Either.left({ header, sessionEntries: newSessions });
return { header, sessionEntries: newSessions };
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,6 @@
import { Console, Effect, Queue, Stream } from "effect";
import { Peer, PeerID, SyncMessage } from "./sync.js";
import { Channel } from "queueueue";
export { Channel } from "queueueue";
export function connectedPeers(
peer1id: PeerID,
@@ -8,65 +9,66 @@ export function connectedPeers(
trace = false,
peer1role = "peer",
peer2role = "peer",
crashOnClose = false,
}: {
trace?: boolean;
peer1role?: Peer["role"];
peer2role?: Peer["role"];
crashOnClose?: boolean;
} = {},
): Effect.Effect<[Peer, Peer]> {
return Effect.gen(function* () {
const [from1to2Rx, from1to2Tx] = yield* newQueuePair(
trace ? { traceAs: `${peer1id} -> ${peer2id}` } : undefined,
);
const [from2to1Rx, from2to1Tx] = yield* newQueuePair(
trace ? { traceAs: `${peer2id} -> ${peer1id}` } : undefined,
);
): [Peer, Peer] {
const [from1to2Rx, from1to2Tx] = newQueuePair(
trace ? { traceAs: `${peer1id} -> ${peer2id}` } : undefined,
);
const [from2to1Rx, from2to1Tx] = newQueuePair(
trace ? { traceAs: `${peer2id} -> ${peer1id}` } : undefined,
);
const peer2AsPeer: Peer = {
id: peer2id,
incoming: from2to1Rx,
outgoing: from1to2Tx,
role: peer2role,
};
const peer2AsPeer: Peer = {
id: peer2id,
incoming: from2to1Rx,
outgoing: from1to2Tx,
role: peer2role,
crashOnClose: crashOnClose,
};
const peer1AsPeer: Peer = {
id: peer1id,
incoming: from1to2Rx,
outgoing: from2to1Tx,
role: peer1role,
};
const peer1AsPeer: Peer = {
id: peer1id,
incoming: from1to2Rx,
outgoing: from2to1Tx,
role: peer1role,
crashOnClose: crashOnClose,
};
return [peer1AsPeer, peer2AsPeer];
});
return [peer1AsPeer, peer2AsPeer];
}
export function newQueuePair(
options: { traceAs?: string } = {},
): Effect.Effect<[Stream.Stream<SyncMessage>, Queue.Enqueue<SyncMessage>]> {
return Effect.gen(function* () {
const queue = yield* Queue.unbounded<SyncMessage>();
): [AsyncIterable<SyncMessage>, Channel<SyncMessage>] {
const channel = new Channel<SyncMessage>();
if (options.traceAs) {
return [
Stream.fromQueue(queue).pipe(
Stream.tap((msg) =>
Console.debug(
options.traceAs,
JSON.stringify(
msg,
(k, v) =>
k === "changes" || k === "encryptedChanges"
? v.slice(0, 20) + "..."
: v,
2,
),
if (options.traceAs) {
return [
(async function* () {
for await (const msg of channel) {
console.debug(
options.traceAs,
JSON.stringify(
msg,
(k, v) =>
k === "changes" || k === "encryptedChanges"
? v.slice(0, 20) + "..."
: v,
2,
),
),
),
queue,
];
} else {
return [Stream.fromQueue(queue), queue];
}
});
);
yield msg;
}
})(),
channel,
];
} else {
return [channel.wrap(), channel];
}
}

View File

@@ -3,7 +3,6 @@ import { CoValueHeader, Transaction } from "./coValueCore.js";
import { CoValueCore } from "./coValueCore.js";
import { LocalNode, newLoadingState } from "./localNode.js";
import { RawCoID, SessionID } from "./ids.js";
import { Data, Effect, Queue, Stream } from "effect";
export type CoValueKnownState = {
id: RawCoID;
@@ -56,27 +55,25 @@ export type DoneMessage = {
export type PeerID = string;
export class DisconnectedError extends Data.TaggedError("DisconnectedError")<{
message: string;
}> {}
export type DisconnectedError = "Disconnected";
export class PingTimeoutError extends Error {
readonly _tag = "PingTimeoutError";
}
export type PingTimeoutError = "PingTimeout";
export type IncomingSyncStream = Stream.Stream<
SyncMessage,
DisconnectedError | PingTimeoutError
export type IncomingSyncStream = AsyncIterable<
SyncMessage | DisconnectedError | PingTimeoutError
>;
export type OutgoingSyncQueue = Queue.Enqueue<SyncMessage>;
export type OutgoingSyncQueue = {
push: (msg: SyncMessage) => Promise<unknown>;
close: () => void;
};
export interface Peer {
id: PeerID;
incoming: IncomingSyncStream;
outgoing: OutgoingSyncQueue;
role: "peer" | "server" | "client";
delayOnError?: number;
priority?: number;
crashOnClose: boolean;
}
export interface PeerState {
@@ -86,8 +83,8 @@ export interface PeerState {
incoming: IncomingSyncStream;
outgoing: OutgoingSyncQueue;
role: "peer" | "server" | "client";
delayOnError?: number;
priority?: number;
crashOnClose: boolean;
}
export function combinedKnownStates(
@@ -144,15 +141,11 @@ export class SyncManager {
for (const peer of eligiblePeers) {
// console.log("loading", id, "from", peer.id);
Effect.runPromise(
Queue.offer(peer.outgoing, {
action: "load",
id: id,
header: false,
sessions: {},
}),
).catch((e) => {
console.error("Error writing to peer", e);
await peer.outgoing.push({
action: "load",
id: id,
header: false,
sessions: {},
});
const coValueEntry = this.local.coValues[id];
@@ -229,11 +222,13 @@ export class SyncManager {
}
if (entry.state === "loading") {
await this.trySendToPeer(peer, {
this.trySendToPeer(peer, {
action: "load",
id,
header: false,
sessions: {},
}).catch((e) => {
console.error("Error sending load", e);
});
return;
}
@@ -246,9 +241,11 @@ export class SyncManager {
if (!peer.toldKnownState.has(id)) {
peer.toldKnownState.add(id);
await this.trySendToPeer(peer, {
this.trySendToPeer(peer, {
action: "load",
...coValue.knownState(),
}).catch((e) => {
console.error("Error sending load", e);
});
}
}
@@ -273,10 +270,12 @@ export class SyncManager {
);
if (!peer.toldKnownState.has(id)) {
await this.trySendToPeer(peer, {
this.trySendToPeer(peer, {
action: "known",
asDependencyOf,
...coValue.knownState(),
}).catch((e) => {
console.error("Error sending known state", e);
});
peer.toldKnownState.add(id);
@@ -311,7 +310,9 @@ export class SyncManager {
// } header: ${!!piece.header}`,
// // Object.values(piece.new).map((s) => s.newTransactions)
// );
await this.trySendToPeer(peer, piece);
this.trySendToPeer(peer, piece).catch((e) => {
console.error("Error sending content piece", e);
});
if (performance.now() - lastYield > 10) {
await new Promise<void>((resolve) => {
setTimeout(resolve, 0);
@@ -342,8 +343,8 @@ export class SyncManager {
outgoing: peer.outgoing,
toldKnownState: new Set(),
role: peer.role,
delayOnError: peer.delayOnError,
priority: peer.priority,
crashOnClose: peer.crashOnClose,
};
this.peers[peer.id] = peerState;
@@ -365,55 +366,61 @@ export class SyncManager {
void initialSync();
}
void Effect.runPromise(
peerState.incoming.pipe(
Stream.ensuring(
Effect.sync(() => {
console.log("Peer disconnected:", peer.id);
delete this.peers[peer.id];
}),
),
Stream.runForEach((msg) =>
Effect.tryPromise({
try: () => this.handleSyncMessage(msg, peerState),
catch: (e) =>
new Error(
`Error reading from peer ${
peer.id
}, handling msg\n\n${JSON.stringify(
msg,
(k, v) =>
k === "changes" ||
k === "encryptedChanges"
? v.slice(0, 20) + "..."
: v,
)}`,
{ cause: e },
),
}).pipe(
Effect.timeoutFail({
duration: 10000,
onTimeout: () =>
new Error("Took >10s to process message"),
}),
),
),
Effect.catchAll((e) =>
Effect.logError(
"Error in peer",
peer.id,
e.message,
typeof e.cause === "object" &&
e.cause instanceof Error &&
e.cause.message,
),
),
),
);
const processMessages = async () => {
for await (const msg of peerState.incoming) {
if (msg === "Disconnected") {
return;
}
if (msg === "PingTimeout") {
console.error("Ping timeout from peer", peer.id);
return;
}
try {
await this.handleSyncMessage(msg, peerState);
} catch (e) {
throw new Error(
`Error reading from peer ${
peer.id
}, handling msg\n\n${JSON.stringify(msg, (k, v) =>
k === "changes" || k === "encryptedChanges"
? v.slice(0, 20) + "..."
: v,
)}`,
{ cause: e },
);
}
}
};
processMessages()
.then(() => {
if (peer.crashOnClose) {
console.error("Unexepcted close from peer", peer.id);
this.local.crashed = new Error(
"Unexpected close from peer",
);
throw new Error("Unexpected close from peer");
}
})
.catch((e) => {
console.error(
"Error processing messages from peer",
peer.id,
e,
);
if (peer.crashOnClose) {
this.local.crashed = e;
throw new Error(e);
}
})
.finally(() => {
peer.outgoing.close();
delete this.peers[peer.id];
});
}
trySendToPeer(peer: PeerState, msg: SyncMessage) {
return Effect.runPromise(Queue.offer(peer.outgoing, msg));
return peer.outgoing.push(msg);
}
async handleLoad(msg: LoadMessage, peer: PeerState) {
@@ -426,7 +433,7 @@ export class SyncManager {
// special case: we should be able to solve this much more neatly
// with an explicit state machine in the future
const eligiblePeers = this.peersInPriorityOrder().filter(
(other) => other.id !== peer.id && peer.role === "server",
(other) => other.id !== peer.id && other.role === "server",
);
if (eligiblePeers.length === 0) {
if (msg.header || Object.keys(msg.sessions).length > 0) {
@@ -439,7 +446,7 @@ export class SyncManager {
header: false,
sessions: {},
}).catch((e) => {
console.error("Error sending known state back", e);
console.error("Error sending known state", e);
});
}
return;
@@ -458,23 +465,25 @@ export class SyncManager {
}
if (entry.state === "loading") {
console.log(
"Waiting for loaded",
msg.id,
"after message from",
peer.id,
);
// console.debug(
// "Waiting for loaded",
// msg.id,
// "after message from",
// peer.id,
// );
const loaded = await entry.done;
console.log("Loaded", msg.id, loaded);
// console.log("Loaded", msg.id, loaded);
if (loaded === "unavailable") {
peer.optimisticKnownStates[msg.id] = knownStateIn(msg);
peer.toldKnownState.add(msg.id);
await this.trySendToPeer(peer, {
this.trySendToPeer(peer, {
action: "known",
id: msg.id,
header: false,
sessions: {},
}).catch((e) => {
console.error("Error sending known state back", e);
});
return;
@@ -552,9 +561,10 @@ export class SyncManager {
let entry = this.local.coValues[msg.id];
if (!entry) {
throw new Error(
console.error(
`Expected coValue entry for ${msg.id} to be created on new content, missing subscribe?`,
);
return;
}
let resolveAfterDone: ((coValue: CoValueCore) => void) | undefined;
@@ -562,14 +572,16 @@ export class SyncManager {
const peerOptimisticKnownState = peer.optimisticKnownStates[msg.id];
if (!peerOptimisticKnownState) {
throw new Error(
console.error(
"Expected optimisticKnownState to be set for coValue we receive new content for",
);
return;
}
if (entry.state === "loading") {
if (!msg.header) {
throw new Error("Expected header to be sent in first message");
console.error("Expected header to be sent in first message");
return;
}
const firstPeerStateEntry = entry.firstPeerState[peer.id];
@@ -621,7 +633,8 @@ export class SyncManager {
}
const before = performance.now();
const success = await coValue.tryAddTransactionsAsync(
// eslint-disable-next-line neverthrow/must-use-result
const result = coValue.tryAddTransactions(
sessionID,
newTransactions,
undefined,
@@ -657,15 +670,16 @@ export class SyncManager {
entry.onProgress?.(ourTotalnTxs / theirTotalnTxs);
if (!success) {
if (result.isErr()) {
console.error(
"Failed to add transactions",
"Failed to add transactions from",
peer.id,
result.error,
msg.id,
JSON.stringify(newTransactions, (k, v) =>
k === "changes" || k === "encryptedChanges"
? v.slice(0, 20) + "..."
: v,
),
newTransactions.length + " new transactions",
"after: " + newContentForSession.after,
"our last known tx idx initially: " + ourKnownTxIdx,
"our last known tx idx now: " + coValue.sessionLogs.get(sessionID)?.transactions.length,
);
continue;
}
@@ -684,10 +698,12 @@ export class SyncManager {
await this.syncCoValue(coValue);
if (invalidStateAssumed) {
await this.trySendToPeer(peer, {
this.trySendToPeer(peer, {
action: "known",
isCorrection: true,
...coValue.knownState(),
}).catch((e) => {
console.error("Error sending known state correction", e);
});
}
}
@@ -755,6 +771,16 @@ export class SyncManager {
}
}
}
gracefulShutdown() {
for (const peer of Object.values(this.peers)) {
console.debug("Gracefully closing", peer.id);
peer.outgoing.close();
peer.incoming = (async function* () {
yield "Disconnected" as const;
})();
}
}
}
function knownStateIn(msg: LoadMessage | KnownStateMessage) {

View File

@@ -3,7 +3,6 @@ import { newRandomSessionID } from "../coValueCore.js";
import { LocalNode } from "../localNode.js";
import { connectedPeers } from "../streamUtils.js";
import { WasmCrypto } from "../crypto/WasmCrypto.js";
import { Effect } from "effect";
const Crypto = await WasmCrypto.create();
@@ -53,13 +52,11 @@ test("Can create account with one node, and then load it on another", async () =
map.set("foo", "bar", "private");
expect(map.get("foo")).toEqual("bar");
const [node1asPeer, node2asPeer] = await Effect.runPromise(
connectedPeers("node1", "node2", {
trace: true,
peer1role: "server",
peer2role: "client",
}),
);
const [node1asPeer, node2asPeer] = connectedPeers("node1", "node2", {
trace: true,
peer1role: "server",
peer2role: "client",
})
console.log("After connected peers");

View File

@@ -36,12 +36,14 @@ test("Can create coValue with new agent credentials and add transaction to it",
);
expect(
coValue.tryAddTransactions(
node.currentSessionID,
[transaction],
expectedNewHash,
Crypto.sign(account.currentSignerSecret(), expectedNewHash),
),
coValue
.tryAddTransactions(
node.currentSessionID,
[transaction],
expectedNewHash,
Crypto.sign(account.currentSignerSecret(), expectedNewHash),
)
._unsafeUnwrap(),
).toBe(true);
});
@@ -72,8 +74,9 @@ test("transactions with wrong signature are rejected", () => {
[transaction],
);
expect(
coValue.tryAddTransactions(
// eslint-disable-next-line neverthrow/must-use-result
coValue
.tryAddTransactions(
node.currentSessionID,
[transaction],
expectedNewHash,
@@ -81,8 +84,8 @@ test("transactions with wrong signature are rejected", () => {
Crypto.getAgentSignerSecret(wrongAgent),
expectedNewHash,
),
),
).toBe(false);
)
._unsafeUnwrapErr({ withStackTrace: true });
});
test("transactions with correctly signed, but wrong hash are rejected", () => {
@@ -121,14 +124,15 @@ test("transactions with correctly signed, but wrong hash are rejected", () => {
],
);
expect(
coValue.tryAddTransactions(
// eslint-disable-next-line neverthrow/must-use-result
coValue
.tryAddTransactions(
node.currentSessionID,
[transaction],
expectedNewHash,
Crypto.sign(account.currentSignerSecret(), expectedNewHash),
),
).toBe(false);
)
._unsafeUnwrapErr({ withStackTrace: true });
});
test("New transactions in a group correctly update owned values, including subscriptions", async () => {
@@ -174,12 +178,14 @@ test("New transactions in a group correctly update owned values, including subsc
expect(map.core.getValidSortedTransactions().length).toBe(1);
const manuallyAdddedTxSuccess = group.core.tryAddTransactions(
node.currentSessionID,
[resignationThatWeJustLearnedAbout],
expectedNewHash,
signature,
);
const manuallyAdddedTxSuccess = group.core
.tryAddTransactions(
node.currentSessionID,
[resignationThatWeJustLearnedAbout],
expectedNewHash,
signature,
)
._unsafeUnwrap({ withStackTrace: true });
expect(manuallyAdddedTxSuccess).toBe(true);

View File

@@ -354,7 +354,7 @@ test("Admins can set group read key and then use it to create and read private t
const revelation = Crypto.seal({
message: readKey,
from: admin.currentSealerSecret(),
to: admin.currentSealerID(),
to: admin.currentSealerID()._unsafeUnwrap(),
nOnceMaterial: {
in: groupCore.id,
tx: groupCore.nextTransactionID(),
@@ -410,7 +410,7 @@ test("Admins can set group read key and then writers can use it to create and re
const revelation1 = Crypto.seal({
message: readKey,
from: admin.currentSealerSecret(),
to: admin.currentSealerID(),
to: admin.currentSealerID()._unsafeUnwrap(),
nOnceMaterial: {
in: groupCore.id,
tx: groupCore.nextTransactionID(),
@@ -422,7 +422,7 @@ test("Admins can set group read key and then writers can use it to create and re
const revelation2 = Crypto.seal({
message: readKey,
from: admin.currentSealerSecret(),
to: writer.currentSealerID(),
to: writer.currentSealerID()._unsafeUnwrap(),
nOnceMaterial: {
in: groupCore.id,
tx: groupCore.nextTransactionID(),
@@ -489,7 +489,7 @@ test("Admins can set group read key and then use it to create private transactio
const revelation1 = Crypto.seal({
message: readKey,
from: admin.currentSealerSecret(),
to: admin.currentSealerID(),
to: admin.currentSealerID()._unsafeUnwrap(),
nOnceMaterial: {
in: groupCore.id,
tx: groupCore.nextTransactionID(),
@@ -501,7 +501,7 @@ test("Admins can set group read key and then use it to create private transactio
const revelation2 = Crypto.seal({
message: readKey,
from: admin.currentSealerSecret(),
to: reader.currentSealerID(),
to: reader.currentSealerID()._unsafeUnwrap(),
nOnceMaterial: {
in: groupCore.id,
tx: groupCore.nextTransactionID(),
@@ -576,7 +576,7 @@ test("Admins can set group read key and then use it to create private transactio
const revelation1 = Crypto.seal({
message: readKey,
from: admin.currentSealerSecret(),
to: admin.currentSealerID(),
to: admin.currentSealerID()._unsafeUnwrap(),
nOnceMaterial: {
in: groupCore.id,
tx: groupCore.nextTransactionID(),
@@ -588,7 +588,7 @@ test("Admins can set group read key and then use it to create private transactio
const revelation2 = Crypto.seal({
message: readKey,
from: admin.currentSealerSecret(),
to: reader1.currentSealerID(),
to: reader1.currentSealerID()._unsafeUnwrap(),
nOnceMaterial: {
in: groupCore.id,
tx: groupCore.nextTransactionID(),
@@ -627,7 +627,7 @@ test("Admins can set group read key and then use it to create private transactio
const revelation3 = Crypto.seal({
message: readKey,
from: admin.currentSealerSecret(),
to: reader2.currentSealerID(),
to: reader2.currentSealerID()._unsafeUnwrap(),
nOnceMaterial: {
in: groupCore.id,
tx: groupCore.nextTransactionID(),
@@ -692,7 +692,7 @@ test("Admins can set group read key, make a private transaction in an owned obje
const revelation1 = Crypto.seal({
message: readKey,
from: admin.currentSealerSecret(),
to: admin.currentSealerID(),
to: admin.currentSealerID()._unsafeUnwrap(),
nOnceMaterial: {
in: groupCore.id,
tx: groupCore.nextTransactionID(),
@@ -722,7 +722,7 @@ test("Admins can set group read key, make a private transaction in an owned obje
const revelation2 = Crypto.seal({
message: readKey2,
from: admin.currentSealerSecret(),
to: admin.currentSealerID(),
to: admin.currentSealerID()._unsafeUnwrap(),
nOnceMaterial: {
in: groupCore.id,
tx: groupCore.nextTransactionID(),
@@ -775,7 +775,7 @@ test("Admins can set group read key, make a private transaction in an owned obje
const revelation = Crypto.seal({
message: readKey,
from: admin.currentSealerSecret(),
to: admin.currentSealerID(),
to: admin.currentSealerID()._unsafeUnwrap(),
nOnceMaterial: {
in: groupCore.id,
tx: groupCore.nextTransactionID(),
@@ -802,7 +802,7 @@ test("Admins can set group read key, make a private transaction in an owned obje
const revelation2 = Crypto.seal({
message: readKey2,
from: admin.currentSealerSecret(),
to: admin.currentSealerID(),
to: admin.currentSealerID()._unsafeUnwrap(),
nOnceMaterial: {
in: groupCore.id,
tx: groupCore.nextTransactionID(),
@@ -814,7 +814,7 @@ test("Admins can set group read key, make a private transaction in an owned obje
const revelation3 = Crypto.seal({
message: readKey2,
from: admin.currentSealerSecret(),
to: reader.currentSealerID(),
to: reader.currentSealerID()._unsafeUnwrap(),
nOnceMaterial: {
in: groupCore.id,
tx: groupCore.nextTransactionID(),
@@ -910,7 +910,7 @@ test("Admins can set group read rey, make a private transaction in an owned obje
const revelation1 = Crypto.seal({
message: readKey,
from: admin.currentSealerSecret(),
to: admin.currentSealerID(),
to: admin.currentSealerID()._unsafeUnwrap(),
nOnceMaterial: {
in: groupCore.id,
tx: groupCore.nextTransactionID(),
@@ -922,7 +922,7 @@ test("Admins can set group read rey, make a private transaction in an owned obje
const revelation2 = Crypto.seal({
message: readKey,
from: admin.currentSealerSecret(),
to: reader.currentSealerID(),
to: reader.currentSealerID()._unsafeUnwrap(),
nOnceMaterial: {
in: groupCore.id,
tx: groupCore.nextTransactionID(),
@@ -934,7 +934,7 @@ test("Admins can set group read rey, make a private transaction in an owned obje
const revelation3 = Crypto.seal({
message: readKey,
from: admin.currentSealerSecret(),
to: reader2.currentSealerID(),
to: reader2.currentSealerID()._unsafeUnwrap(),
nOnceMaterial: {
in: groupCore.id,
tx: groupCore.nextTransactionID(),
@@ -980,7 +980,7 @@ test("Admins can set group read rey, make a private transaction in an owned obje
const newRevelation1 = Crypto.seal({
message: readKey2,
from: admin.currentSealerSecret(),
to: admin.currentSealerID(),
to: admin.currentSealerID()._unsafeUnwrap(),
nOnceMaterial: {
in: groupCore.id,
tx: groupCore.nextTransactionID(),
@@ -996,7 +996,7 @@ test("Admins can set group read rey, make a private transaction in an owned obje
const newRevelation2 = Crypto.seal({
message: readKey2,
from: admin.currentSealerSecret(),
to: reader2.currentSealerID(),
to: reader2.currentSealerID()._unsafeUnwrap(),
nOnceMaterial: {
in: groupCore.id,
tx: groupCore.nextTransactionID(),
@@ -1121,7 +1121,7 @@ test("Admins can create an adminInvite, which can add an admin", () => {
const revelation = Crypto.seal({
message: readKey,
from: admin.currentSealerSecret(),
to: admin.currentSealerID(),
to: admin.currentSealerID()._unsafeUnwrap(),
nOnceMaterial: {
in: groupCore.id,
tx: groupCore.nextTransactionID(),
@@ -1231,7 +1231,7 @@ test("Admins can create a writerInvite, which can add a writer", () => {
const revelation = Crypto.seal({
message: readKey,
from: admin.currentSealerSecret(),
to: admin.currentSealerID(),
to: admin.currentSealerID()._unsafeUnwrap(),
nOnceMaterial: {
in: groupCore.id,
tx: groupCore.nextTransactionID(),
@@ -1334,7 +1334,7 @@ test("Admins can create a readerInvite, which can add a reader", () => {
const revelation = Crypto.seal({
message: readKey,
from: admin.currentSealerSecret(),
to: admin.currentSealerID(),
to: admin.currentSealerID()._unsafeUnwrap(),
nOnceMaterial: {
in: groupCore.id,
tx: groupCore.nextTransactionID(),
@@ -1427,7 +1427,7 @@ test("WriterInvites can not invite admins", () => {
const revelation = Crypto.seal({
message: readKey,
from: admin.currentSealerSecret(),
to: admin.currentSealerID(),
to: admin.currentSealerID()._unsafeUnwrap(),
nOnceMaterial: {
in: groupCore.id,
tx: groupCore.nextTransactionID(),
@@ -1481,7 +1481,7 @@ test("ReaderInvites can not invite admins", () => {
const revelation = Crypto.seal({
message: readKey,
from: admin.currentSealerSecret(),
to: admin.currentSealerID(),
to: admin.currentSealerID()._unsafeUnwrap(),
nOnceMaterial: {
in: groupCore.id,
tx: groupCore.nextTransactionID(),
@@ -1535,7 +1535,7 @@ test("ReaderInvites can not invite writers", () => {
const revelation = Crypto.seal({
message: readKey,
from: admin.currentSealerSecret(),
to: admin.currentSealerID(),
to: admin.currentSealerID()._unsafeUnwrap(),
nOnceMaterial: {
in: groupCore.id,
tx: groupCore.nextTransactionID(),
@@ -1610,7 +1610,7 @@ test("Can give read permission to 'everyone'", () => {
childObject
.testWithDifferentAccount(
newAccount,
newRandomSessionID(newAccount.currentAgentID()),
newRandomSessionID(newAccount.currentAgentID()._unsafeUnwrap()),
)
.getCurrentContent(),
);
@@ -1639,7 +1639,7 @@ test("Can give read permissions to 'everyone' (high-level)", async () => {
childObject.core
.testWithDifferentAccount(
new ControlledAgent(Crypto.newRandomAgentSecret(), Crypto),
newRandomSessionID(newAccount.currentAgentID()),
newRandomSessionID(newAccount.currentAgentID()._unsafeUnwrap()),
)
.getCurrentContent(),
);
@@ -1680,7 +1680,7 @@ test("Can give write permission to 'everyone'", async () => {
childObject
.testWithDifferentAccount(
newAccount,
newRandomSessionID(newAccount.currentAgentID()),
newRandomSessionID(newAccount.currentAgentID()._unsafeUnwrap()),
)
.getCurrentContent(),
);
@@ -1715,7 +1715,7 @@ test("Can give write permissions to 'everyone' (high-level)", async () => {
childObject.core
.testWithDifferentAccount(
newAccount,
newRandomSessionID(newAccount.currentAgentID()),
newRandomSessionID(newAccount.currentAgentID()._unsafeUnwrap()),
)
.getCurrentContent(),
);

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,157 @@
# jazz-browser-media-images
## 0.7.34-neverthrow.8
### Patch Changes
- jazz-browser@0.7.34-neverthrow.8
- jazz-tools@0.7.34-neverthrow.8
## 0.7.34-neverthrow.7
### Patch Changes
- jazz-browser@0.7.34-neverthrow.7
- jazz-tools@0.7.34-neverthrow.7
## 0.7.34-neverthrow.4
### Patch Changes
- jazz-browser@0.7.34-neverthrow.4
- jazz-tools@0.7.34-neverthrow.4
## 0.7.34-neverthrow.3
### Patch Changes
- jazz-browser@0.7.34-neverthrow.3
- jazz-tools@0.7.34-neverthrow.3
## 0.7.34-neverthrow.2
### Patch Changes
- jazz-browser@0.7.34-neverthrow.2
## 0.7.34-neverthrow.1
### Patch Changes
- jazz-browser@0.7.34-neverthrow.1
- jazz-tools@0.7.34-neverthrow.1
## 0.7.34-neverthrow.0
### Patch Changes
- jazz-browser@0.7.34-neverthrow.0
- jazz-tools@0.7.34-neverthrow.0
## 0.7.33
### Patch Changes
- jazz-browser@0.7.33
- jazz-tools@0.7.33
## 0.7.33-hotfixes.5
### Patch Changes
- jazz-browser@0.7.33-hotfixes.5
- jazz-tools@0.7.33-hotfixes.5
## 0.7.33-hotfixes.4
### Patch Changes
- jazz-browser@0.7.33-hotfixes.4
- jazz-tools@0.7.33-hotfixes.4
## 0.7.33-hotfixes.3
### Patch Changes
- jazz-browser@0.7.33-hotfixes.3
- jazz-tools@0.7.33-hotfixes.3
## 0.7.33-hotfixes.2
### Patch Changes
- jazz-browser@0.7.33-hotfixes.2
## 0.7.33-hotfixes.1
### Patch Changes
- jazz-browser@0.7.33-hotfixes.1
## 0.7.33-hotfixes.0
### Patch Changes
- jazz-browser@0.7.33-hotfixes.0
- jazz-tools@0.7.33-hotfixes.0
## 0.7.32
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.32
- jazz-browser@0.7.32
## 0.7.31
### Patch Changes
- jazz-browser@0.7.31
- jazz-tools@0.7.31
## 0.7.30
### Patch Changes
- jazz-browser@0.7.30
## 0.7.29
### Patch Changes
- jazz-browser@0.7.29
- jazz-tools@0.7.29
## 0.7.28
### Patch Changes
- jazz-browser@0.7.28
- jazz-tools@0.7.28
## 0.7.27
### Patch Changes
- jazz-browser@0.7.27
## 0.7.26
### Patch Changes
- Updated dependencies
- jazz-browser@0.7.26
- jazz-tools@0.7.26
## 0.7.25
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.25
- jazz-browser@0.7.25
## 0.7.24
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-browser-media-images",
"version": "0.7.24",
"version": "0.7.34-neverthrow.8",
"type": "module",
"main": "dist/index.js",
"types": "src/index.ts",

View File

@@ -1,5 +1,211 @@
# jazz-browser
## 0.7.34-neverthrow.8
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.8
- cojson-storage-indexeddb@0.7.34-neverthrow.8
- cojson-transport-ws@0.7.34-neverthrow.8
- jazz-tools@0.7.34-neverthrow.8
## 0.7.34-neverthrow.7
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.7
- cojson-storage-indexeddb@0.7.34-neverthrow.7
- cojson-transport-ws@0.7.34-neverthrow.7
- jazz-tools@0.7.34-neverthrow.7
## 0.7.34-neverthrow.4
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.4
- cojson-storage-indexeddb@0.7.34-neverthrow.4
- cojson-transport-ws@0.7.34-neverthrow.4
- jazz-tools@0.7.34-neverthrow.4
## 0.7.34-neverthrow.3
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.3
- cojson-storage-indexeddb@0.7.34-neverthrow.3
- cojson-transport-ws@0.7.34-neverthrow.3
- jazz-tools@0.7.34-neverthrow.3
## 0.7.34-neverthrow.2
### Patch Changes
- Updated dependencies
- cojson-transport-ws@0.7.34-neverthrow.2
## 0.7.34-neverthrow.1
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.1
- cojson-storage-indexeddb@0.7.34-neverthrow.1
- cojson-transport-ws@0.7.34-neverthrow.1
- jazz-tools@0.7.34-neverthrow.1
## 0.7.34-neverthrow.0
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.0
- cojson-storage-indexeddb@0.7.34-neverthrow.0
- cojson-transport-ws@0.7.34-neverthrow.0
- jazz-tools@0.7.34-neverthrow.0
## 0.7.33
### Patch Changes
- Updated dependencies [fdde8db]
- Updated dependencies [b297c93]
- Updated dependencies [07fe2b9]
- Updated dependencies [3bf5127]
- Updated dependencies [a8b74ff]
- Updated dependencies [db53161]
- cojson-transport-ws@0.7.33
- cojson@0.7.33
- cojson-storage-indexeddb@0.7.33
- jazz-tools@0.7.33
## 0.7.33-hotfixes.5
### Patch Changes
- Updated dependencies
- cojson@0.7.33-hotfixes.5
- cojson-storage-indexeddb@0.7.33-hotfixes.5
- cojson-transport-ws@0.7.33-hotfixes.5
- jazz-tools@0.7.33-hotfixes.5
## 0.7.33-hotfixes.4
### Patch Changes
- Updated dependencies
- cojson@0.7.33-hotfixes.4
- cojson-storage-indexeddb@0.7.33-hotfixes.4
- cojson-transport-ws@0.7.33-hotfixes.4
- jazz-tools@0.7.33-hotfixes.4
## 0.7.33-hotfixes.3
### Patch Changes
- Updated dependencies
- cojson-storage-indexeddb@0.7.33-hotfixes.3
- cojson-transport-ws@0.7.33-hotfixes.3
- cojson@0.7.33-hotfixes.3
- jazz-tools@0.7.33-hotfixes.3
## 0.7.33-hotfixes.2
### Patch Changes
- Updated dependencies
- cojson-transport-ws@0.7.33-hotfixes.2
## 0.7.33-hotfixes.1
### Patch Changes
- Updated dependencies
- cojson-transport-ws@0.7.33-hotfixes.1
## 0.7.33-hotfixes.0
### Patch Changes
- Updated dependencies
- cojson@0.7.33-hotfixes.0
- cojson-storage-indexeddb@0.7.33-hotfixes.0
- cojson-transport-ws@0.7.33-hotfixes.0
- jazz-tools@0.7.33-hotfixes.0
## 0.7.32
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.32
## 0.7.31
### Patch Changes
- Updated dependencies
- Updated dependencies
- cojson-transport-ws@0.7.31
- cojson@0.7.31
- cojson-storage-indexeddb@0.7.31
- jazz-tools@0.7.31
## 0.7.30
### Patch Changes
- Updated dependencies
- cojson-transport-ws@0.7.30
## 0.7.29
### Patch Changes
- Updated dependencies
- cojson@0.7.29
- cojson-storage-indexeddb@0.7.29
- cojson-transport-ws@0.7.29
- jazz-tools@0.7.29
## 0.7.28
### Patch Changes
- Updated dependencies
- cojson@0.7.28
- cojson-storage-indexeddb@0.7.28
- cojson-transport-ws@0.7.28
- jazz-tools@0.7.28
## 0.7.27
### Patch Changes
- Updated dependencies
- cojson-transport-ws@0.7.27
## 0.7.26
### Patch Changes
- Remove Effect from jazz/cojson internals
- Updated dependencies
- cojson@0.7.26
- cojson-storage-indexeddb@0.7.26
- cojson-transport-ws@0.7.26
- jazz-tools@0.7.26
## 0.7.25
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.25
## 0.7.24
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-browser",
"version": "0.7.24",
"version": "0.7.34-neverthrow.8",
"type": "module",
"main": "dist/index.js",
"types": "src/index.ts",
@@ -10,7 +10,6 @@
"cojson": "workspace:*",
"cojson-storage-indexeddb": "workspace:*",
"cojson-transport-ws": "workspace:*",
"effect": "^3.5.2",
"jazz-tools": "workspace:*",
"typescript": "^5.1.6"
},

View File

@@ -1,13 +1,12 @@
import {
BlockFilename,
FSErr,
FileSystem,
WalFilename,
CryptoProvider,
} from "cojson";
import { Effect } from "effect";
import { BlockFilename, FileSystem, WalFilename, CryptoProvider } from "cojson";
export class OPFSFilesystem implements FileSystem<{id: number, filename: string}, {id: number, filename: string}> {
export class OPFSFilesystem
implements
FileSystem<
{ id: number; filename: string },
{ id: number; filename: string }
>
{
opfsWorker: Worker;
callbacks: Map<number, (event: MessageEvent) => void> = new Map();
nextRequestId = 0;
@@ -28,8 +27,8 @@ export class OPFSFilesystem implements FileSystem<{id: number, filename: string}
};
}
listFiles(): Effect.Effect<string[], FSErr, never> {
return Effect.async((cb) => {
listFiles(): Promise<string[]> {
return new Promise((resolve) => {
const requestId = this.nextRequestId++;
performance.mark("listFiles" + requestId + "_listFiles");
this.callbacks.set(requestId, (event) => {
@@ -39,7 +38,7 @@ export class OPFSFilesystem implements FileSystem<{id: number, filename: string}
"listFiles" + requestId + "_listFiles",
"listFilesEnd" + requestId + "_listFiles",
);
cb(Effect.succeed(event.data.fileNames));
resolve(event.data.fileNames);
});
this.opfsWorker.postMessage({ type: "listFiles", requestId });
});
@@ -47,17 +46,15 @@ export class OPFSFilesystem implements FileSystem<{id: number, filename: string}
openToRead(
filename: string,
): Effect.Effect<{ handle: {id: number, filename: string}; size: number }, FSErr, never> {
return Effect.async((cb) => {
): Promise<{ handle: { id: number; filename: string }; size: number }> {
return new Promise((resolve) => {
const requestId = this.nextRequestId++;
performance.mark("openToRead" + "_" + filename);
this.callbacks.set(requestId, (event) => {
cb(
Effect.succeed({
handle: {id: event.data.handle, filename},
size: event.data.size,
}),
);
resolve({
handle: { id: event.data.handle, filename },
size: event.data.size,
});
performance.mark("openToReadEnd" + "_" + filename);
performance.measure(
"openToRead" + "_" + filename,
@@ -73,8 +70,8 @@ export class OPFSFilesystem implements FileSystem<{id: number, filename: string}
});
}
createFile(filename: string): Effect.Effect<{id: number, filename: string}, FSErr, never> {
return Effect.async((cb) => {
createFile(filename: string): Promise<{ id: number; filename: string }> {
return new Promise((resolve) => {
const requestId = this.nextRequestId++;
performance.mark("createFile" + "_" + filename);
this.callbacks.set(requestId, (event) => {
@@ -84,7 +81,7 @@ export class OPFSFilesystem implements FileSystem<{id: number, filename: string}
"createFile" + "_" + filename,
"createFileEnd" + "_" + filename,
);
cb(Effect.succeed({id: event.data.handle, filename}));
resolve({ id: event.data.handle, filename });
});
this.opfsWorker.postMessage({
type: "createFile",
@@ -94,10 +91,8 @@ export class OPFSFilesystem implements FileSystem<{id: number, filename: string}
});
}
openToWrite(
filename: string,
): Effect.Effect<{id: number, filename: string}, FSErr, never> {
return Effect.async((cb) => {
openToWrite(filename: string): Promise<{ id: number; filename: string }> {
return new Promise((resolve) => {
const requestId = this.nextRequestId++;
performance.mark("openToWrite" + "_" + filename);
this.callbacks.set(requestId, (event) => {
@@ -107,7 +102,7 @@ export class OPFSFilesystem implements FileSystem<{id: number, filename: string}
"openToWrite" + "_" + filename,
"openToWriteEnd" + "_" + filename,
);
cb(Effect.succeed({id: event.data.handle, filename}));
resolve({ id: event.data.handle, filename });
});
this.opfsWorker.postMessage({
type: "openToWrite",
@@ -118,10 +113,10 @@ export class OPFSFilesystem implements FileSystem<{id: number, filename: string}
}
append(
handle: {id: number, filename: string},
handle: { id: number; filename: string },
data: Uint8Array,
): Effect.Effect<void, FSErr, never> {
return Effect.async((cb) => {
): Promise<void> {
return new Promise((resolve) => {
const requestId = this.nextRequestId++;
performance.mark("append" + "_" + handle.filename);
this.callbacks.set(requestId, (_) => {
@@ -131,7 +126,7 @@ export class OPFSFilesystem implements FileSystem<{id: number, filename: string}
"append" + "_" + handle.filename,
"appendEnd" + "_" + handle.filename,
);
cb(Effect.succeed(undefined));
resolve(undefined);
});
this.opfsWorker.postMessage({
type: "append",
@@ -143,11 +138,11 @@ export class OPFSFilesystem implements FileSystem<{id: number, filename: string}
}
read(
handle: {id: number, filename: string},
handle: { id: number; filename: string },
offset: number,
length: number,
): Effect.Effect<Uint8Array, FSErr, never> {
return Effect.async((cb) => {
): Promise<Uint8Array> {
return new Promise((resolve) => {
const requestId = this.nextRequestId++;
performance.mark("read" + "_" + handle.filename);
this.callbacks.set(requestId, (event) => {
@@ -157,7 +152,7 @@ export class OPFSFilesystem implements FileSystem<{id: number, filename: string}
"read" + "_" + handle.filename,
"readEnd" + "_" + handle.filename,
);
cb(Effect.succeed(event.data.data));
resolve(event.data.data);
});
this.opfsWorker.postMessage({
type: "read",
@@ -169,8 +164,8 @@ export class OPFSFilesystem implements FileSystem<{id: number, filename: string}
});
}
close(handle: {id: number, filename: string}): Effect.Effect<void, FSErr, never> {
return Effect.async((cb) => {
close(handle: { id: number; filename: string }): Promise<void> {
return new Promise((resolve) => {
const requestId = this.nextRequestId++;
performance.mark("close" + "_" + handle.filename);
this.callbacks.set(requestId, (_) => {
@@ -180,7 +175,7 @@ export class OPFSFilesystem implements FileSystem<{id: number, filename: string}
"close" + "_" + handle.filename,
"closeEnd" + "_" + handle.filename,
);
cb(Effect.succeed(undefined));
resolve(undefined);
});
this.opfsWorker.postMessage({
type: "close",
@@ -191,22 +186,20 @@ export class OPFSFilesystem implements FileSystem<{id: number, filename: string}
}
closeAndRename(
handle: {id: number, filename: string},
handle: { id: number; filename: string },
filename: BlockFilename,
): Effect.Effect<void, FSErr, never> {
return Effect.async((cb) => {
): Promise<void> {
return new Promise((resolve) => {
const requestId = this.nextRequestId++;
performance.mark("closeAndRename" + "_" + handle.filename);
this.callbacks.set(requestId, () => {
performance.mark(
"closeAndRenameEnd" + "_" + handle.filename,
);
performance.mark("closeAndRenameEnd" + "_" + handle.filename);
performance.measure(
"closeAndRename" + "_" + handle.filename,
"closeAndRename" + "_" + handle.filename,
"closeAndRenameEnd" + "_" + handle.filename,
);
cb(Effect.succeed(undefined));
resolve(undefined);
});
this.opfsWorker.postMessage({
type: "closeAndRename",
@@ -217,10 +210,8 @@ export class OPFSFilesystem implements FileSystem<{id: number, filename: string}
});
}
removeFile(
filename: BlockFilename | WalFilename,
): Effect.Effect<void, FSErr, never> {
return Effect.async((cb) => {
removeFile(filename: BlockFilename | WalFilename): Promise<void> {
return new Promise((resolve) => {
const requestId = this.nextRequestId++;
performance.mark("removeFile" + "_" + filename);
this.callbacks.set(requestId, () => {
@@ -230,7 +221,7 @@ export class OPFSFilesystem implements FileSystem<{id: number, filename: string}
"removeFile" + "_" + filename,
"removeFileEnd" + "_" + filename,
);
cb(Effect.succeed(undefined));
resolve(undefined);
});
this.opfsWorker.postMessage({
type: "removeFile",

View File

@@ -14,7 +14,6 @@ import { AccountID, LSMStorage } from "cojson";
import { AuthProvider } from "./auth/auth.js";
import { OPFSFilesystem } from "./OPFSFilesystem.js";
import { IDBStorage } from "cojson-storage-indexeddb";
import { Effect, Queue } from "effect";
import { createWebSocketPeer } from "cojson-transport-ws";
export * from "./auth/auth.js";
@@ -42,13 +41,11 @@ export async function createJazzBrowserContext<Acc extends Account>({
const crypto = customCrypto || (await WasmCrypto.create());
let sessionDone: () => void;
const firstWsPeer = await Effect.runPromise(
createWebSocketPeer({
websocket: new WebSocket(peerAddr),
id: peerAddr + "@" + new Date().toISOString(),
role: "server",
}),
);
const firstWsPeer = createWebSocketPeer({
websocket: new WebSocket(peerAddr),
id: peerAddr + "@" + new Date().toISOString(),
role: "server",
});
let shouldTryToReconnect = true;
let currentReconnectionTimeout = initialReconnectionTimeout;
@@ -112,13 +109,11 @@ export async function createJazzBrowserContext<Acc extends Account>({
});
me._raw.core.node.syncManager.addPeer(
await Effect.runPromise(
createWebSocketPeer({
websocket: new WebSocket(peerAddr),
id: peerAddr + "@" + new Date().toISOString(),
role: "server",
}),
),
createWebSocketPeer({
websocket: new WebSocket(peerAddr),
id: peerAddr + "@" + new Date().toISOString(),
role: "server",
})
);
}
}
@@ -132,11 +127,7 @@ export async function createJazzBrowserContext<Acc extends Account>({
shouldTryToReconnect = false;
window.removeEventListener("online", onOnline);
console.log("Cleaning up node");
for (const peer of Object.values(
me._raw.core.node.syncManager.peers,
)) {
void Effect.runPromise(Queue.shutdown(peer.outgoing));
}
me._raw.core.node.gracefulShutdown();
sessionDone?.();
},
};

View File

@@ -1,5 +1,196 @@
# jazz-autosub
## 0.7.34-neverthrow.8
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.8
- cojson-transport-ws@0.7.34-neverthrow.8
- jazz-tools@0.7.34-neverthrow.8
## 0.7.34-neverthrow.7
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.7
- cojson-transport-ws@0.7.34-neverthrow.7
- jazz-tools@0.7.34-neverthrow.7
## 0.7.34-neverthrow.4
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.4
- cojson-transport-ws@0.7.34-neverthrow.4
- jazz-tools@0.7.34-neverthrow.4
## 0.7.34-neverthrow.3
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.3
- cojson-transport-ws@0.7.34-neverthrow.3
- jazz-tools@0.7.34-neverthrow.3
## 0.7.34-neverthrow.2
### Patch Changes
- Updated dependencies
- cojson-transport-ws@0.7.34-neverthrow.2
## 0.7.34-neverthrow.1
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.1
- cojson-transport-ws@0.7.34-neverthrow.1
- jazz-tools@0.7.34-neverthrow.1
## 0.7.34-neverthrow.0
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.0
- cojson-transport-ws@0.7.34-neverthrow.0
- jazz-tools@0.7.34-neverthrow.0
## 0.7.33
### Patch Changes
- Updated dependencies [fdde8db]
- Updated dependencies [b297c93]
- Updated dependencies [07fe2b9]
- Updated dependencies [3bf5127]
- Updated dependencies [a8b74ff]
- Updated dependencies [db53161]
- cojson-transport-ws@0.7.33
- cojson@0.7.33
- jazz-tools@0.7.33
## 0.7.33-hotfixes.5
### Patch Changes
- Updated dependencies
- cojson@0.7.33-hotfixes.5
- cojson-transport-ws@0.7.33-hotfixes.5
- jazz-tools@0.7.33-hotfixes.5
## 0.7.33-hotfixes.4
### Patch Changes
- Updated dependencies
- cojson@0.7.33-hotfixes.4
- cojson-transport-ws@0.7.33-hotfixes.4
- jazz-tools@0.7.33-hotfixes.4
## 0.7.33-hotfixes.3
### Patch Changes
- Updated dependencies
- cojson-transport-ws@0.7.33-hotfixes.3
- cojson@0.7.33-hotfixes.3
- jazz-tools@0.7.33-hotfixes.3
## 0.7.33-hotfixes.2
### Patch Changes
- Updated dependencies
- cojson-transport-ws@0.7.33-hotfixes.2
## 0.7.33-hotfixes.1
### Patch Changes
- Updated dependencies
- cojson-transport-ws@0.7.33-hotfixes.1
## 0.7.33-hotfixes.0
### Patch Changes
- Updated dependencies
- cojson@0.7.33-hotfixes.0
- cojson-transport-ws@0.7.33-hotfixes.0
- jazz-tools@0.7.33-hotfixes.0
## 0.7.32
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.32
## 0.7.31
### Patch Changes
- Updated dependencies
- Updated dependencies
- cojson-transport-ws@0.7.31
- cojson@0.7.31
- jazz-tools@0.7.31
## 0.7.30
### Patch Changes
- Updated dependencies
- cojson-transport-ws@0.7.30
## 0.7.29
### Patch Changes
- Updated dependencies
- cojson@0.7.29
- cojson-transport-ws@0.7.29
- jazz-tools@0.7.29
## 0.7.28
### Patch Changes
- Updated dependencies
- cojson@0.7.28
- cojson-transport-ws@0.7.28
- jazz-tools@0.7.28
## 0.7.27
### Patch Changes
- Updated dependencies
- cojson-transport-ws@0.7.27
## 0.7.26
### Patch Changes
- Remove Effect from jazz/cojson internals
- Updated dependencies
- cojson@0.7.26
- cojson-transport-ws@0.7.26
- jazz-tools@0.7.26
## 0.7.25
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.25
## 0.7.24
### Patch Changes

View File

@@ -5,11 +5,10 @@
"types": "src/index.ts",
"type": "module",
"license": "MIT",
"version": "0.7.24",
"version": "0.7.34-neverthrow.8",
"dependencies": {
"cojson": "workspace:*",
"cojson-transport-ws": "workspace:*",
"effect": "^3.5.2",
"jazz-tools": "workspace:*",
"ws": "^8.14.2"
},

View File

@@ -1,7 +1,6 @@
import { AgentSecret, Peer, SessionID, WasmCrypto } from "cojson";
import { createWebSocketPeer } from "cojson-transport-ws";
import { Account, CoValueClass, ID } from "jazz-tools";
import { Effect } from "effect";
import { WebSocket } from "ws";
/** @category Context Creation */
@@ -18,13 +17,11 @@ export async function startWorker<Acc extends Account>({
syncServer?: string;
accountSchema?: CoValueClass<Acc> & typeof Account;
}): Promise<{ worker: Acc }> {
const wsPeer: Peer = await Effect.runPromise(
createWebSocketPeer({
id: "upstream",
websocket: new WebSocket(peer),
role: "server",
}),
);
const wsPeer: Peer = createWebSocketPeer({
id: "upstream",
websocket: new WebSocket(peer),
role: "server",
});
if (!accountID) {
throw new Error("No accountID provided");
@@ -52,13 +49,11 @@ export async function startWorker<Acc extends Account>({
if (!worker._raw.core.node.syncManager.peers["upstream"]) {
console.log(new Date(), "Reconnecting to upstream " + peer);
const wsPeer: Peer = await Effect.runPromise(
createWebSocketPeer({
id: "upstream",
websocket: new WebSocket(peer),
role: "server",
}),
);
const wsPeer: Peer = createWebSocketPeer({
id: "upstream",
websocket: new WebSocket(peer),
role: "server",
});
worker._raw.core.node.syncManager.addPeer(wsPeer);
}

View File

@@ -1,5 +1,190 @@
# jazz-react
## 0.7.34-neverthrow.8
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.8
- jazz-browser@0.7.34-neverthrow.8
- jazz-tools@0.7.34-neverthrow.8
## 0.7.34-neverthrow.7
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.7
- jazz-browser@0.7.34-neverthrow.7
- jazz-tools@0.7.34-neverthrow.7
## 0.7.34-neverthrow.4
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.4
- jazz-browser@0.7.34-neverthrow.4
- jazz-tools@0.7.34-neverthrow.4
## 0.7.34-neverthrow.3
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.3
- jazz-browser@0.7.34-neverthrow.3
- jazz-tools@0.7.34-neverthrow.3
## 0.7.34-neverthrow.2
### Patch Changes
- jazz-browser@0.7.34-neverthrow.2
## 0.7.34-neverthrow.1
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.1
- jazz-browser@0.7.34-neverthrow.1
- jazz-tools@0.7.34-neverthrow.1
## 0.7.34-neverthrow.0
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.0
- jazz-browser@0.7.34-neverthrow.0
- jazz-tools@0.7.34-neverthrow.0
## 0.7.33
### Patch Changes
- Updated dependencies [b297c93]
- Updated dependencies [3bf5127]
- Updated dependencies [a8b74ff]
- Updated dependencies [db53161]
- cojson@0.7.33
- jazz-browser@0.7.33
- jazz-tools@0.7.33
## 0.7.33-hotfixes.5
### Patch Changes
- Updated dependencies
- cojson@0.7.33-hotfixes.5
- jazz-browser@0.7.33-hotfixes.5
- jazz-tools@0.7.33-hotfixes.5
## 0.7.33-hotfixes.4
### Patch Changes
- Updated dependencies
- cojson@0.7.33-hotfixes.4
- jazz-browser@0.7.33-hotfixes.4
- jazz-tools@0.7.33-hotfixes.4
## 0.7.33-hotfixes.3
### Patch Changes
- Updated dependencies
- cojson@0.7.33-hotfixes.3
- jazz-browser@0.7.33-hotfixes.3
- jazz-tools@0.7.33-hotfixes.3
## 0.7.33-hotfixes.2
### Patch Changes
- jazz-browser@0.7.33-hotfixes.2
## 0.7.33-hotfixes.1
### Patch Changes
- jazz-browser@0.7.33-hotfixes.1
## 0.7.33-hotfixes.0
### Patch Changes
- Updated dependencies
- cojson@0.7.33-hotfixes.0
- jazz-browser@0.7.33-hotfixes.0
- jazz-tools@0.7.33-hotfixes.0
## 0.7.32
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.32
- jazz-browser@0.7.32
## 0.7.31
### Patch Changes
- Updated dependencies
- cojson@0.7.31
- jazz-browser@0.7.31
- jazz-tools@0.7.31
## 0.7.30
### Patch Changes
- jazz-browser@0.7.30
## 0.7.29
### Patch Changes
- Updated dependencies
- cojson@0.7.29
- jazz-browser@0.7.29
- jazz-tools@0.7.29
## 0.7.28
### Patch Changes
- Updated dependencies
- cojson@0.7.28
- jazz-browser@0.7.28
- jazz-tools@0.7.28
## 0.7.27
### Patch Changes
- jazz-browser@0.7.27
## 0.7.26
### Patch Changes
- Remove Effect from jazz/cojson internals
- Updated dependencies
- cojson@0.7.26
- jazz-browser@0.7.26
- jazz-tools@0.7.26
## 0.7.25
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.25
- jazz-browser@0.7.25
## 0.7.24
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-react",
"version": "0.7.24",
"version": "0.7.34-neverthrow.8",
"type": "module",
"main": "dist/index.js",
"types": "src/index.ts",

View File

@@ -1,5 +1,215 @@
# jazz-autosub
## 0.7.34-neverthrow.8
### Patch Changes
- Use queueable fork
- Updated dependencies
- cojson@0.7.34-neverthrow.8
- cojson-transport-ws@0.7.34-neverthrow.8
- jazz-tools@0.7.34-neverthrow.8
## 0.7.34-neverthrow.7
### Patch Changes
- Use fork of queueable
- Updated dependencies
- cojson@0.7.34-neverthrow.7
- cojson-transport-ws@0.7.34-neverthrow.7
- jazz-tools@0.7.34-neverthrow.7
## 0.7.34-neverthrow.6
### Patch Changes
- Fix deps even more
## 0.7.34-neverthrow.6
### Patch Changes
- Fix peer deps
## 0.7.34-neverthrow.5
### Patch Changes
- Hack to make jazz-run account create work
## 0.7.34-neverthrow.4
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.4
- cojson-transport-ws@0.7.34-neverthrow.4
- jazz-tools@0.7.34-neverthrow.4
## 0.7.34-neverthrow.3
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.3
- cojson-transport-ws@0.7.34-neverthrow.3
- jazz-tools@0.7.34-neverthrow.3
## 0.7.34-neverthrow.2
### Patch Changes
- Updated dependencies
- cojson-transport-ws@0.7.34-neverthrow.2
## 0.7.34-neverthrow.1
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.1
- cojson-transport-ws@0.7.34-neverthrow.1
- jazz-tools@0.7.34-neverthrow.1
## 0.7.34-neverthrow.0
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.0
- cojson-transport-ws@0.7.34-neverthrow.0
- jazz-tools@0.7.34-neverthrow.0
## 0.7.33
### Patch Changes
- Updated dependencies [fdde8db]
- Updated dependencies [b297c93]
- Updated dependencies [07fe2b9]
- Updated dependencies [3bf5127]
- Updated dependencies [a8b74ff]
- Updated dependencies [db53161]
- cojson-transport-ws@0.7.33
- cojson@0.7.33
- jazz-tools@0.7.33
## 0.7.33-hotfixes.5
### Patch Changes
- Updated dependencies
- cojson@0.7.33-hotfixes.5
- cojson-transport-ws@0.7.33-hotfixes.5
- jazz-tools@0.7.33-hotfixes.5
## 0.7.33-hotfixes.4
### Patch Changes
- Updated dependencies
- cojson@0.7.33-hotfixes.4
- cojson-transport-ws@0.7.33-hotfixes.4
- jazz-tools@0.7.33-hotfixes.4
## 0.7.33-hotfixes.3
### Patch Changes
- Updated dependencies
- cojson-transport-ws@0.7.33-hotfixes.3
- cojson@0.7.33-hotfixes.3
- jazz-tools@0.7.33-hotfixes.3
## 0.7.33-hotfixes.2
### Patch Changes
- Updated dependencies
- cojson-transport-ws@0.7.33-hotfixes.2
## 0.7.33-hotfixes.1
### Patch Changes
- Updated dependencies
- cojson-transport-ws@0.7.33-hotfixes.1
## 0.7.33-hotfixes.0
### Patch Changes
- Updated dependencies
- cojson@0.7.33-hotfixes.0
- cojson-transport-ws@0.7.33-hotfixes.0
- jazz-tools@0.7.33-hotfixes.0
## 0.7.32
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.32
## 0.7.31
### Patch Changes
- Updated dependencies
- Updated dependencies
- cojson-transport-ws@0.7.31
- cojson@0.7.31
- jazz-tools@0.7.31
## 0.7.30
### Patch Changes
- Updated dependencies
- cojson-transport-ws@0.7.30
## 0.7.29
### Patch Changes
- Updated dependencies
- cojson@0.7.29
- cojson-transport-ws@0.7.29
- jazz-tools@0.7.29
## 0.7.28
### Patch Changes
- Updated dependencies
- cojson@0.7.28
- cojson-transport-ws@0.7.28
- jazz-tools@0.7.28
## 0.7.27
### Patch Changes
- Updated dependencies
- cojson-transport-ws@0.7.27
## 0.7.26
### Patch Changes
- Updated dependencies
- cojson@0.7.26
- cojson-transport-ws@0.7.26
- jazz-tools@0.7.26
## 0.7.25
### Patch Changes
- Updated dependencies
- jazz-tools@0.7.25
## 0.7.24
### Patch Changes

View File

@@ -3,7 +3,7 @@
"bin": "./dist/index.js",
"type": "module",
"license": "MIT",
"version": "0.7.24",
"version": "0.7.34-neverthrow.8",
"scripts": {
"lint": "eslint . --ext ts,tsx",
"format": "prettier --write './src/**/*.{ts,tsx}'",
@@ -11,20 +11,21 @@
"prepublishOnly": "npm run build"
},
"dependencies": {
"@effect/cli": "^0.36.21",
"@effect/platform-node": "^0.49.2",
"@effect/schema": "^0.66.16",
"effect": "^3.6.5",
"@effect/cli": "^0.41.2",
"@effect/schema": "^0.71.1",
"@effect/platform-node": "^0.57.2",
"@effect/printer": "^0.34.5",
"@effect/printer-ansi": "^0.34.5",
"@effect/typeclass": "^0.25.5",
"cojson": "workspace:*",
"cojson-transport-ws": "workspace:*",
"effect": "^3.5.2",
"fast-check": "^3.17.2",
"jazz-tools": "workspace:*",
"ws": "^8.14.2"
},
"devDependencies": {
"@types/ws": "^8.5.5",
"typescript": "^5.3.3",
"vitest": "^0.34.6"
"typescript": "^5.3.3"
},
"lint-staged": {
"*.{ts,tsx}": "eslint --fix",

View File

@@ -25,7 +25,7 @@ const accountCreate = Command.make(
return Effect.gen(function* () {
const crypto = yield* Effect.promise(() => WasmCrypto.create());
const peer = yield* createWebSocketPeer({
const peer = createWebSocketPeer({
id: "upstream",
websocket: new WebSocket(peerAddr),
role: "server",
@@ -53,7 +53,10 @@ const accountCreate = Command.make(
),
);
const peer2 = yield* createWebSocketPeer({
// TODO: remove this once we have a better way to wait for the sync
yield* Effect.sleep(500);
const peer2 = createWebSocketPeer({
id: "upstream2",
websocket: new WebSocket(peerAddr),
role: "server",

View File

@@ -1,5 +1,126 @@
# jazz-autosub
## 0.7.34-neverthrow.8
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.8
## 0.7.34-neverthrow.7
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.7
## 0.7.34-neverthrow.4
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.4
## 0.7.34-neverthrow.3
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.3
## 0.7.34-neverthrow.1
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.1
## 0.7.34-neverthrow.0
### Patch Changes
- Updated dependencies
- cojson@0.7.34-neverthrow.0
## 0.7.33
### Patch Changes
- Updated dependencies [b297c93]
- Updated dependencies [3bf5127]
- Updated dependencies [a8b74ff]
- Updated dependencies [db53161]
- cojson@0.7.33
## 0.7.33-hotfixes.5
### Patch Changes
- Updated dependencies
- cojson@0.7.33-hotfixes.5
## 0.7.33-hotfixes.4
### Patch Changes
- Updated dependencies
- cojson@0.7.33-hotfixes.4
## 0.7.33-hotfixes.3
### Patch Changes
- Updated dependencies
- cojson@0.7.33-hotfixes.3
## 0.7.33-hotfixes.0
### Patch Changes
- Updated dependencies
- cojson@0.7.33-hotfixes.0
## 0.7.32
### Patch Changes
- Adapt type of applyDiff to make CoMaps fully subclassable again
## 0.7.31
### Patch Changes
- Updated dependencies
- cojson@0.7.31
## 0.7.29
### Patch Changes
- Updated dependencies
- cojson@0.7.29
## 0.7.28
### Patch Changes
- Updated dependencies
- cojson@0.7.28
## 0.7.26
### Patch Changes
- Remove Effect from jazz/cojson internals
- Updated dependencies
- cojson@0.7.26
## 0.7.25
### Patch Changes
- Implement applyDiff on CoMap to only update changed fields
## 0.7.24
### Patch Changes

View File

@@ -5,11 +5,9 @@
"types": "./src/index.ts",
"type": "module",
"license": "MIT",
"version": "0.7.24",
"version": "0.7.34-neverthrow.8",
"dependencies": {
"@effect/schema": "^0.66.16",
"cojson": "workspace:*",
"effect": "^3.5.2",
"fast-check": "^3.17.2"
},
"scripts": {

View File

@@ -11,7 +11,6 @@ import type {
RawControlledAccount,
SessionID,
} from "cojson";
import { Context, Effect } from "effect";
import {
CoMap,
CoValue,
@@ -221,12 +220,10 @@ export class Account extends CoValueBase implements CoValue {
},
) {
// TODO: is there a cleaner way to do this?
const connectedPeers = await Effect.runPromise(
cojsonInternals.connectedPeers(
"creatingAccount",
"createdAccount",
{ peer1role: "server", peer2role: "client" },
),
const connectedPeers = cojsonInternals.connectedPeers(
"creatingAccount",
"createdAccount",
{ peer1role: "server", peer2role: "client" },
);
as._raw.core.node.syncManager.addPeer(connectedPeers[1]);
@@ -378,9 +375,6 @@ export const AccountAndGroupProxyHandler: ProxyHandler<Account | Group> = {
},
};
/** @category Identity & Permissions */
export class AccountCtx extends Context.Tag("Account")<AccountCtx, Account>() {}
/** @category Identity & Permissions */
export function isControlledAccount(account: Account): account is Account & {
isMe: true;

View File

@@ -1,4 +1,4 @@
import type { RawCoList } from "cojson";
import type { JsonValue, RawCoList } from "cojson";
import { RawAccount } from "cojson";
import type {
CoValue,
@@ -26,8 +26,8 @@ import {
makeRefs,
subscribeToCoValue,
subscribeToExistingCoValue,
subscriptionsScopes,
} from "../internal.js";
import { encodeSync, decodeSync } from "@effect/schema/Schema";
/**
* CoLists are collaborative versions of plain arrays.
@@ -303,7 +303,7 @@ export class CoList<Item = any> extends Array<Item> implements CoValue {
} else if ("encoded" in itemDescriptor) {
return this._raw
.asArray()
.map((e) => encodeSync(itemDescriptor.encoded)(e));
.map((e) => itemDescriptor.encoded.encode(e));
} else if (isRefEncoded(itemDescriptor)) {
return this.map((item, idx) =>
seenAbove?.includes((item as CoValue)?.id)
@@ -444,16 +444,21 @@ export class CoList<Item = any> extends Array<Item> implements CoValue {
castAs<Cl extends CoValueClass & CoValueFromRaw<CoValue>>(
cl: Cl,
): InstanceType<Cl> {
return cl.fromRaw(this._raw) as InstanceType<Cl>;
const casted = cl.fromRaw(this._raw) as InstanceType<Cl>;
const subscriptionScope = subscriptionsScopes.get(this);
if (subscriptionScope) {
subscriptionsScopes.set(casted, subscriptionScope);
}
return casted;
}
}
function toRawItems<Item>(items: Item[], itemDescriptor: Schema) {
const rawItems =
itemDescriptor === "json"
? items
? (items as JsonValue[])
: "encoded" in itemDescriptor
? items?.map((e) => encodeSync(itemDescriptor.encoded)(e))
? items?.map((e) => itemDescriptor.encoded.encode(e))
: isRefEncoded(itemDescriptor)
? items?.map((v) => (v as unknown as CoValue).id)
: (() => {
@@ -472,7 +477,7 @@ const CoListProxyHandler: ProxyHandler<CoList> = {
} else if ("encoded" in itemDescriptor) {
return rawValue === undefined
? undefined
: decodeSync(itemDescriptor.encoded)(rawValue);
: itemDescriptor.encoded.decode(rawValue);
} else if (isRefEncoded(itemDescriptor)) {
return rawValue === undefined
? undefined
@@ -505,7 +510,7 @@ const CoListProxyHandler: ProxyHandler<CoList> = {
if (itemDescriptor === "json") {
rawValue = value;
} else if ("encoded" in itemDescriptor) {
rawValue = encodeSync(itemDescriptor.encoded)(value);
rawValue = itemDescriptor.encoded.encode(value);
} else if (isRefEncoded(itemDescriptor)) {
rawValue = value.id;
}

View File

@@ -1,6 +1,4 @@
import type { JsonValue, RawCoMap } from "cojson";
import type { Simplify } from "effect/Types";
import { encodeSync, decodeSync } from "@effect/schema/Schema";
import type {
CoValue,
Schema,
@@ -36,6 +34,10 @@ type CoMapEdit<V> = {
madeAt: Date;
};
export type Simplify<A> = {
[K in keyof A]: A[K]
} extends infer B ? B : never
/**
* CoMaps are collaborative versions of plain objects, mapping string-like keys to values.
*
@@ -147,7 +149,7 @@ export class CoMap extends CoValueBase implements CoValue {
descriptor === "json"
? rawEdit.value
: "encoded" in descriptor
? decodeSync(descriptor.encoded)(rawEdit.value)
? descriptor.encoded.encode(rawEdit.value)
: new Ref(
rawEdit.value as ID<CoValue>,
target._loadedAs,
@@ -317,7 +319,7 @@ export class CoMap extends CoValueBase implements CoValue {
rawInit[key] = (initValue as unknown as CoValue).id;
}
} else if ("encoded" in descriptor) {
rawInit[key] = encodeSync(descriptor.encoded)(
rawInit[key] = descriptor.encoded.encode(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
initValue as any,
);
@@ -454,6 +456,37 @@ export class CoMap extends CoValueBase implements CoValue {
): () => void {
return subscribeToExistingCoValue(this, depth, listener);
}
applyDiff<N extends Partial<CoMapInit<this>>>(newValues: N) {
for (const key in newValues) {
if (Object.prototype.hasOwnProperty.call(newValues, key)) {
const tKey = key as keyof typeof newValues & keyof this;
const descriptor = (this._schema[tKey as string] ||
this._schema[ItemsSym]) as Schema;
if (tKey in this._schema) {
const newValue = newValues[tKey];
const currentValue = (this as unknown as N)[tKey];
if (descriptor === "json" || "encoded" in descriptor) {
if (currentValue !== newValue) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(this as any)[tKey] = newValue;
}
} else if (isRefEncoded(descriptor)) {
const currentId = (currentValue as CoValue | undefined)
?.id;
const newId = (newValue as CoValue | undefined)?.id;
if (currentId !== newId) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(this as any)[tKey] = newValue;
}
}
}
}
}
return this;
}
}
export type CoKeys<Map extends object> = Exclude<
@@ -485,7 +518,7 @@ const CoMapProxyHandler: ProxyHandler<CoMap> = {
} else if ("encoded" in descriptor) {
return raw === undefined
? undefined
: decodeSync(descriptor.encoded)(raw);
: descriptor.encoded.decode(raw);
} else if (isRefEncoded(descriptor)) {
return raw === undefined
? undefined
@@ -519,7 +552,7 @@ const CoMapProxyHandler: ProxyHandler<CoMap> = {
if (descriptor === "json") {
target._raw.set(key, value);
} else if ("encoded" in descriptor) {
target._raw.set(key, encodeSync(descriptor.encoded)(value));
target._raw.set(key, descriptor.encoded.encode(value));
} else if (isRefEncoded(descriptor)) {
target._raw.set(key, value.id);
subscriptionsScopes

View File

@@ -35,7 +35,6 @@ import {
ensureCoValueLoaded,
subscribeToExistingCoValue,
} from "../internal.js";
import { encodeSync, decodeSync } from "@effect/schema/Schema";
export type CoStreamEntry<Item> = SingleCoStreamEntry<Item> & {
all: IterableIterator<SingleCoStreamEntry<Item>>;
@@ -141,7 +140,7 @@ export class CoStream<Item = any> extends CoValueBase implements CoValue {
if (itemDescriptor === "json") {
this._raw.push(item as JsonValue);
} else if ("encoded" in itemDescriptor) {
this._raw.push(encodeSync(itemDescriptor.encoded)(item));
this._raw.push(itemDescriptor.encoded.encode(item));
} else if (isRefEncoded(itemDescriptor)) {
this._raw.push((item as unknown as CoValue).id);
}
@@ -153,7 +152,7 @@ export class CoStream<Item = any> extends CoValueBase implements CoValue {
itemDescriptor === "json"
? (v: unknown) => v
: "encoded" in itemDescriptor
? encodeSync(itemDescriptor.encoded)
? itemDescriptor.encoded.encode
: (v: unknown) => v && (v as CoValue).id;
return {
@@ -247,7 +246,7 @@ function entryFromRawEntry<Item>(
? (CoValue & Item) | null
: Item;
} else if ("encoded" in itemField) {
return decodeSync(itemField.encoded)(rawEntry.value);
return itemField.encoded.decode(rawEntry.value);
} else if (isRefEncoded(itemField)) {
return this.ref?.accessFrom(
accessFrom,

View File

@@ -122,7 +122,12 @@ export class CoValueBase implements CoValue {
castAs<Cl extends CoValueClass & CoValueFromRaw<CoValue>>(
cl: Cl,
): InstanceType<Cl> {
return cl.fromRaw(this._raw) as InstanceType<Cl>;
const casted = cl.fromRaw(this._raw) as InstanceType<Cl>;
const subscriptionScope = subscriptionsScopes.get(this);
if (subscriptionScope) {
subscriptionsScopes.set(casted, subscriptionScope);
}
return casted;
}
}

View File

@@ -5,7 +5,6 @@ import {
isCoValueClass,
CoValueFromRaw,
} from "../internal.js";
import type { Schema as EffectSchema, TypeId } from "@effect/schema/Schema";
export type CoMarker = { readonly __co: unique symbol };
/** @category Schema definition */
@@ -113,7 +112,7 @@ function ref<
}
export type JsonEncoded = "json";
export type EncodedAs<V> = { encoded: Encoder<V> };
export type EncodedAs<V> = { encoded: Encoder<V> | OptionalEncoder<V> };
export type RefEncoded<V extends CoValue> = {
ref: CoValueClass<V> | ((raw: RawCoValue) => CoValueClass<V>);
optional: boolean;
@@ -152,31 +151,23 @@ export type SchemaFor<Field> = NonNullable<Field> extends CoValue
? JsonEncoded
: EncodedAs<NonNullable<Field>>;
export type EffectSchemaWithInputAndOutput<A, I = A> = EffectSchema<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
any,
never
> & {
[TypeId]: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
_A: (_: any) => A;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
_I: (_: any) => I;
};
export type Encoder<V> = {
encode: (value: V) => JsonValue;
decode: (value: JsonValue) => V;
};
export type OptionalEncoder<V> =
| Encoder<V>
| {
encode: (value: V | undefined) => JsonValue;
decode: (value: JsonValue) => V | undefined;
};
export type Encoder<V> = EffectSchemaWithInputAndOutput<V, JsonValue>;
export type OptionalEncoder<V> = EffectSchemaWithInputAndOutput<
V,
JsonValue | undefined
>;
import { Date } from "@effect/schema/Schema";
import { SchemaInit, ItemsSym, MembersSym } from "./symbols.js";
/** @category Schema definition */
export const Encoders = {
Date,
Date: {
encode: (value: Date) => value.toISOString(),
decode: (value: JsonValue) => new Date(value as string),
},
};

View File

@@ -1,12 +1,12 @@
import { expect, describe, test } from "vitest";
import { connectedPeers } from "cojson/src/streamUtils.js";
import { newRandomSessionID } from "cojson/src/coValueCore.js";
import { Effect, Queue } from "effect";
import {
Account,
CoList,
WasmCrypto,
co,
cojsonInternals,
isControlledAccount,
} from "../index.js";
@@ -157,11 +157,13 @@ describe("CoList resolution", async () => {
test("Loading and availability", async () => {
const { me, list } = await initNodeAndList();
const [initialAsPeer, secondPeer] = await Effect.runPromise(
connectedPeers("initial", "second", {
const [initialAsPeer, secondPeer] = connectedPeers(
"initial",
"second",
{
peer1role: "server",
peer2role: "client",
}),
},
);
if (!isControlledAccount(me)) {
throw "me is not a controlled account";
@@ -217,11 +219,13 @@ describe("CoList resolution", async () => {
test("Subscription & auto-resolution", async () => {
const { me, list } = await initNodeAndList();
const [initialAsPeer, secondPeer] = await Effect.runPromise(
connectedPeers("initial", "second", {
const [initialAsPeer, secondPeer] = connectedPeers(
"initial",
"second",
{
peer1role: "server",
peer2role: "client",
}),
},
);
if (!isControlledAccount(me)) {
throw "me is not a controlled account";
@@ -236,63 +240,52 @@ describe("CoList resolution", async () => {
crypto: Crypto,
});
await Effect.runPromise(
Effect.gen(function* ($) {
const queue = yield* $(Queue.unbounded<TestList>());
const queue = new cojsonInternals.Channel();
TestList.subscribe(
list.id,
meOnSecondPeer,
[],
(subscribedList) => {
console.log(
"subscribedList?.[0]?.[0]?.[0]",
subscribedList?.[0]?.[0]?.[0],
);
void Effect.runPromise(
Queue.offer(queue, subscribedList),
);
},
);
TestList.subscribe(list.id, meOnSecondPeer, [], (subscribedList) => {
console.log(
"subscribedList?.[0]?.[0]?.[0]",
subscribedList?.[0]?.[0]?.[0],
);
void queue.push(subscribedList);
});
const update1 = yield* $(Queue.take(queue));
expect(update1?.[0]).toBe(null);
const update1 = (await queue.next()).value;
expect(update1?.[0]).toBe(null);
const update2 = yield* $(Queue.take(queue));
expect(update2?.[0]).toBeDefined();
expect(update2?.[0]?.[0]).toBe(null);
const update2 = (await queue.next()).value;
expect(update2?.[0]).toBeDefined();
expect(update2?.[0]?.[0]).toBe(null);
const update3 = yield* $(Queue.take(queue));
expect(update3?.[0]?.[0]).toBeDefined();
expect(update3?.[0]?.[0]?.[0]).toBe("a");
expect(update3?.[0]?.[0]?.joined()).toBe("a,b");
const update3 = (await queue.next()).value;
expect(update3?.[0]?.[0]).toBeDefined();
expect(update3?.[0]?.[0]?.[0]).toBe("a");
expect(update3?.[0]?.[0]?.joined()).toBe("a,b");
update3[0]![0]![0] = "x";
update3[0]![0]![0] = "x";
const update4 = yield* $(Queue.take(queue));
expect(update4?.[0]?.[0]?.[0]).toBe("x");
const update4 = (await queue.next()).value;
expect(update4?.[0]?.[0]?.[0]).toBe("x");
// When assigning a new nested value, we get an update
// When assigning a new nested value, we get an update
const newTwiceNestedList = TwiceNestedList.create(["y", "z"], {
owner: meOnSecondPeer,
});
const newTwiceNestedList = TwiceNestedList.create(["y", "z"], {
owner: meOnSecondPeer,
});
const newNestedList = NestedList.create([newTwiceNestedList], {
owner: meOnSecondPeer,
});
const newNestedList = NestedList.create([newTwiceNestedList], {
owner: meOnSecondPeer,
});
update4[0] = newNestedList;
update4[0] = newNestedList;
const update5 = yield* $(Queue.take(queue));
expect(update5?.[0]?.[0]?.[0]).toBe("y");
expect(update5?.[0]?.[0]?.joined()).toBe("y,z");
const update5 = (await queue.next()).value;
expect(update5?.[0]?.[0]?.[0]).toBe("y");
expect(update5?.[0]?.[0]?.joined()).toBe("y,z");
// we get updates when the new nested value changes
newTwiceNestedList[0] = "w";
const update6 = yield* $(Queue.take(queue));
expect(update6?.[0]?.[0]?.[0]).toBe("w");
}),
);
// we get updates when the new nested value changes
newTwiceNestedList[0] = "w";
const update6 = (await queue.next()).value;
expect(update6?.[0]?.[0]?.[0]).toBe("w");
});
});

View File

@@ -1,7 +1,6 @@
import { expect, describe, test } from "vitest";
import { connectedPeers } from "cojson/src/streamUtils.js";
import { newRandomSessionID } from "cojson/src/coValueCore.js";
import { Effect, Queue } from "effect";
import {
Account,
Encoders,
@@ -9,8 +8,8 @@ import {
co,
WasmCrypto,
isControlledAccount,
cojsonInternals,
} from "../index.js";
import { Schema } from "@effect/schema";
const Crypto = await WasmCrypto.create();
@@ -25,7 +24,10 @@ describe("Simple CoMap operations", async () => {
_height = co.number;
birthday = co.encoded(Encoders.Date);
name? = co.string;
nullable = co.optional.encoded(Schema.NullishOr(Schema.String));
nullable = co.optional.encoded<string | undefined>({
encode: (value: string | undefined) => value || null,
decode: (value: unknown) => (value as string) || undefined,
});
optionalDate = co.optional.encoded(Encoders.Date);
get roughColor() {
@@ -42,7 +44,7 @@ describe("Simple CoMap operations", async () => {
color: "red",
_height: 10,
birthday: birthday,
nullable: null,
nullable: undefined,
},
{ owner: me },
);
@@ -94,7 +96,7 @@ describe("Simple CoMap operations", async () => {
expect(map._raw.get("_height")).toEqual(20);
map.nullable = "not null";
map.nullable = null;
map.nullable = undefined;
delete map.nullable;
map.nullable = undefined;
@@ -282,12 +284,12 @@ describe("CoMap resolution", async () => {
test("Loading and availability", async () => {
const { me, map } = await initNodeAndMap();
const [initialAsPeer, secondPeer] = await Effect.runPromise(
const [initialAsPeer, secondPeer] =
connectedPeers("initial", "second", {
peer1role: "server",
peer2role: "client",
}),
);
});
if (!isControlledAccount(me)) {
throw "me is not a controlled account";
}
@@ -353,12 +355,12 @@ describe("CoMap resolution", async () => {
test("Subscription & auto-resolution", async () => {
const { me, map } = await initNodeAndMap();
const [initialAsPeer, secondAsPeer] = await Effect.runPromise(
const [initialAsPeer, secondAsPeer] =
connectedPeers("initial", "second", {
peer1role: "server",
peer2role: "client",
}),
);
});
if (!isControlledAccount(me)) {
throw "me is not a controlled account";
}
@@ -372,9 +374,8 @@ describe("CoMap resolution", async () => {
crypto: Crypto,
});
await Effect.runPromise(
Effect.gen(function* ($) {
const queue = yield* $(Queue.unbounded<TestMap>());
const queue = new cojsonInternals.Channel<TestMap>();
TestMap.subscribe(
map.id,
@@ -385,22 +386,20 @@ describe("CoMap resolution", async () => {
"subscribedMap.nested?.twiceNested?.taste",
subscribedMap.nested?.twiceNested?.taste,
);
void Effect.runPromise(
Queue.offer(queue, subscribedMap),
);
void queue.push(subscribedMap);
},
);
const update1 = yield* $(Queue.take(queue));
const update1 = (await queue.next()).value;
expect(update1.nested).toEqual(null);
const update2 = yield* $(Queue.take(queue));
const update2 = (await queue.next()).value;
expect(update2.nested?.name).toEqual("nested");
map.nested!.name = "nestedUpdated";
const _ = yield* $(Queue.take(queue));
const update3 = yield* $(Queue.take(queue));
const _ = (await queue.next()).value;
const update3 = (await queue.next()).value;
expect(update3.nested?.name).toEqual("nestedUpdated");
const oldTwiceNested = update3.nested!.twiceNested;
@@ -424,23 +423,21 @@ describe("CoMap resolution", async () => {
update3.nested = newNested;
yield* $(Queue.take(queue));
// const update4 = yield* $(Queue.take(queue));
const update4b = yield* $(Queue.take(queue));
(await queue.next()).value;
// const update4 = (await queue.next()).value;
const update4b = (await queue.next()).value;
expect(update4b.nested?.name).toEqual("newNested");
expect(update4b.nested?.twiceNested?.taste).toEqual("sweet");
// we get updates when the new nested value changes
newTwiceNested.taste = "salty";
const update5 = yield* $(Queue.take(queue));
const update5 = (await queue.next()).value;
expect(update5.nested?.twiceNested?.taste).toEqual("salty");
newTwiceNested.taste = "umami";
const update6 = yield* $(Queue.take(queue));
const update6 = (await queue.next()).value;
expect(update6.nested?.twiceNested?.taste).toEqual("umami");
}),
);
});
class TestMapWithOptionalRef extends CoMap {
@@ -577,3 +574,163 @@ describe("CoMap resolution", async () => {
]);
});
});
describe("CoMap applyDiff", async () => {
const me = await Account.create({
creationProps: { name: "Tester McTesterson" },
crypto: Crypto,
});
class TestMap extends CoMap {
name = co.string;
age = co.number;
isActive = co.boolean;
birthday = co.encoded(Encoders.Date);
nested = co.ref(NestedMap);
optionalField = co.optional.string;
}
class NestedMap extends CoMap {
value = co.string;
}
test("Basic applyDiff", () => {
const map = TestMap.create(
{
name: "Alice",
age: 30,
isActive: true,
birthday: new Date("1990-01-01"),
nested: NestedMap.create({ value: "original" }, { owner: me }),
},
{ owner: me },
);
const newValues = {
name: "Bob",
age: 35,
isActive: false,
};
map.applyDiff(newValues);
expect(map.name).toEqual("Bob");
expect(map.age).toEqual(35);
expect(map.isActive).toEqual(false);
expect(map.birthday).toEqual(new Date("1990-01-01"));
expect(map.nested?.value).toEqual("original");
});
test("applyDiff with nested changes", () => {
const map = TestMap.create(
{
name: "Charlie",
age: 25,
isActive: true,
birthday: new Date("1995-01-01"),
nested: NestedMap.create({ value: "original" }, { owner: me }),
},
{ owner: me },
);
const newValues = {
name: "David",
nested: NestedMap.create({ value: "updated" }, { owner: me }),
};
map.applyDiff(newValues);
expect(map.name).toEqual("David");
expect(map.age).toEqual(25);
expect(map.nested?.value).toEqual("updated");
});
test("applyDiff with encoded fields", () => {
const map = TestMap.create(
{
name: "Eve",
age: 28,
isActive: true,
birthday: new Date("1993-01-01"),
nested: NestedMap.create({ value: "original" }, { owner: me }),
},
{ owner: me },
);
const newValues = {
birthday: new Date("1993-06-15"),
};
map.applyDiff(newValues);
expect(map.birthday).toEqual(new Date("1993-06-15"));
});
test("applyDiff with optional fields", () => {
const map = TestMap.create(
{
name: "Frank",
age: 40,
isActive: true,
birthday: new Date("1980-01-01"),
nested: NestedMap.create({ value: "original" }, { owner: me }),
},
{ owner: me },
);
const newValues = {
optionalField: "New optional value",
};
map.applyDiff(newValues);
expect(map.optionalField).toEqual("New optional value");
map.applyDiff({ optionalField: undefined });
expect(map.optionalField).toBeUndefined();
});
test("applyDiff with no changes", () => {
const map = TestMap.create(
{
name: "Grace",
age: 35,
isActive: true,
birthday: new Date("1985-01-01"),
nested: NestedMap.create({ value: "original" }, { owner: me }),
},
{ owner: me },
);
const originalJSON = map.toJSON();
map.applyDiff({});
expect(map.toJSON()).toEqual(originalJSON);
});
test("applyDiff with invalid field", () => {
const map = TestMap.create(
{
name: "Henry",
age: 45,
isActive: false,
birthday: new Date("1975-01-01"),
nested: NestedMap.create({ value: "original" }, { owner: me }),
},
{ owner: me },
);
const newValues = {
name: "Ian",
invalidField: "This should be ignored",
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
map.applyDiff(newValues as any);
expect(map.name).toEqual("Ian");
// eslint-disable-next-line @typescript-eslint/no-explicit-any
expect((map as any).invalidField).toBeUndefined();
});
});

View File

@@ -1,7 +1,6 @@
import { expect, describe, test } from "vitest";
import { connectedPeers } from "cojson/src/streamUtils.js";
import { newRandomSessionID } from "cojson/src/coValueCore.js";
import { Effect, Queue } from "effect";
import {
BinaryCoStream,
ID,
@@ -10,6 +9,7 @@ import {
co,
WasmCrypto,
isControlledAccount,
cojsonInternals,
} from "../index.js";
const Crypto = await WasmCrypto.create();
@@ -83,11 +83,13 @@ describe("CoStream resolution", async () => {
test("Loading and availability", async () => {
const { me, stream } = await initNodeAndStream();
const [initialAsPeer, secondPeer] = await Effect.runPromise(
connectedPeers("initial", "second", {
const [initialAsPeer, secondPeer] = connectedPeers(
"initial",
"second",
{
peer1role: "server",
peer2role: "client",
}),
},
);
if (!isControlledAccount(me)) {
throw "me is not a controlled account";
@@ -176,12 +178,15 @@ describe("CoStream resolution", async () => {
test("Subscription & auto-resolution", async () => {
const { me, stream } = await initNodeAndStream();
const [initialAsPeer, secondAsPeer] = await Effect.runPromise(
connectedPeers("initial", "second", {
const [initialAsPeer, secondAsPeer] = connectedPeers(
"initial",
"second",
{
peer1role: "server",
peer2role: "client",
}),
},
);
me._raw.core.node.syncManager.addPeer(secondAsPeer);
if (!isControlledAccount(me)) {
throw "me is not a controlled account";
@@ -195,78 +200,68 @@ describe("CoStream resolution", async () => {
crypto: Crypto,
});
await Effect.runPromise(
Effect.gen(function* ($) {
const queue = yield* $(Queue.unbounded<TestStream>());
const queue = new cojsonInternals.Channel();
TestStream.subscribe(
stream.id,
meOnSecondPeer,
[],
(subscribedStream) => {
console.log(
"subscribedStream[me.id]",
subscribedStream[me.id],
);
console.log(
"subscribedStream[me.id]?.value?.[me.id]?.value",
subscribedStream[me.id]?.value?.[me.id]?.value,
);
console.log(
"subscribedStream[me.id]?.value?.[me.id]?.value?.[me.id]?.value",
subscribedStream[me.id]?.value?.[me.id]?.value?.[
me.id
]?.value,
);
void Effect.runPromise(
Queue.offer(queue, subscribedStream),
);
},
TestStream.subscribe(
stream.id,
meOnSecondPeer,
[],
(subscribedStream) => {
console.log("subscribedStream[me.id]", subscribedStream[me.id]);
console.log(
"subscribedStream[me.id]?.value?.[me.id]?.value",
subscribedStream[me.id]?.value?.[me.id]?.value,
);
console.log(
"subscribedStream[me.id]?.value?.[me.id]?.value?.[me.id]?.value",
subscribedStream[me.id]?.value?.[me.id]?.value?.[me.id]
?.value,
);
void queue.push(subscribedStream);
},
);
const update1 = yield* $(Queue.take(queue));
expect(update1[me.id]?.value).toEqual(null);
const update1 = (await queue.next()).value;
expect(update1[me.id]?.value).toEqual(null);
const update2 = yield* $(Queue.take(queue));
expect(update2[me.id]?.value).toBeDefined();
expect(update2[me.id]?.value?.[me.id]?.value).toBe(null);
const update2 = (await queue.next()).value;
expect(update2[me.id]?.value).toBeDefined();
expect(update2[me.id]?.value?.[me.id]?.value).toBe(null);
const update3 = yield* $(Queue.take(queue));
expect(update3[me.id]?.value?.[me.id]?.value).toBeDefined();
expect(
update3[me.id]?.value?.[me.id]?.value?.[me.id]?.value,
).toBe("milk");
const update3 = (await queue.next()).value;
expect(update3[me.id]?.value?.[me.id]?.value).toBeDefined();
expect(update3[me.id]?.value?.[me.id]?.value?.[me.id]?.value).toBe(
"milk",
);
update3[me.id]!.value![me.id]!.value!.push("bread");
update3[me.id]!.value![me.id]!.value!.push("bread");
const update4 = yield* $(Queue.take(queue));
expect(
update4[me.id]?.value?.[me.id]?.value?.[me.id]?.value,
).toBe("bread");
const update4 = (await queue.next()).value;
expect(update4[me.id]?.value?.[me.id]?.value?.[me.id]?.value).toBe(
"bread",
);
// When assigning a new nested stream, we get an update
const newTwiceNested = TwiceNestedStream.create(["butter"], {
owner: meOnSecondPeer,
});
// When assigning a new nested stream, we get an update
const newTwiceNested = TwiceNestedStream.create(["butter"], {
owner: meOnSecondPeer,
});
const newNested = NestedStream.create([newTwiceNested], {
owner: meOnSecondPeer,
});
const newNested = NestedStream.create([newTwiceNested], {
owner: meOnSecondPeer,
});
update4.push(newNested);
update4.push(newNested);
const update5 = yield* $(Queue.take(queue));
expect(
update5[me.id]?.value?.[me.id]?.value?.[me.id]?.value,
).toBe("butter");
const update5 = (await queue.next()).value;
expect(update5[me.id]?.value?.[me.id]?.value?.[me.id]?.value).toBe(
"butter",
);
// we get updates when the new nested stream changes
newTwiceNested.push("jam");
const update6 = yield* $(Queue.take(queue));
expect(
update6[me.id]?.value?.[me.id]?.value?.[me.id]?.value,
).toBe("jam");
}),
// we get updates when the new nested stream changes
newTwiceNested.push("jam");
const update6 = (await queue.next()).value;
expect(update6[me.id]?.value?.[me.id]?.value?.[me.id]?.value).toBe(
"jam",
);
});
});
@@ -327,11 +322,13 @@ describe("BinaryCoStream loading & Subscription", async () => {
test("Loading and availability", async () => {
const { me, stream } = await initNodeAndStream();
const [initialAsPeer, secondAsPeer] = await Effect.runPromise(
connectedPeers("initial", "second", {
const [initialAsPeer, secondAsPeer] = connectedPeers(
"initial",
"second",
{
peer1role: "server",
peer2role: "client",
}),
},
);
if (!isControlledAccount(me)) {
throw "me is not a controlled account";
@@ -360,98 +357,83 @@ describe("BinaryCoStream loading & Subscription", async () => {
});
test("Subscription", async () => {
await Effect.runPromise(
Effect.gen(function* ($) {
const { me } = yield* Effect.promise(() => initNodeAndStream());
const { me } = await initNodeAndStream();
const stream = BinaryCoStream.create({ owner: me });
const stream = BinaryCoStream.create({ owner: me });
const [initialAsPeer, secondAsPeer] = yield* connectedPeers(
"initial",
"second",
{ peer1role: "server", peer2role: "client" },
);
me._raw.core.node.syncManager.addPeer(secondAsPeer);
if (!isControlledAccount(me)) {
throw "me is not a controlled account";
}
const meOnSecondPeer = yield* Effect.promise(() =>
Account.become({
accountID: me.id,
accountSecret: me._raw.agentSecret,
peersToLoadFrom: [initialAsPeer],
// eslint-disable-next-line @typescript-eslint/no-explicit-any
sessionID: newRandomSessionID(me.id as any),
crypto: Crypto,
}),
);
const queue = yield* $(Queue.unbounded<BinaryCoStream>());
BinaryCoStream.subscribe(
stream.id,
meOnSecondPeer,
[],
(subscribedStream) => {
void Effect.runPromise(
Queue.offer(queue, subscribedStream),
);
},
);
const update1 = yield* $(Queue.take(queue));
expect(update1.getChunks()).toBe(undefined);
stream.start({ mimeType: "text/plain" });
const update2 = yield* $(Queue.take(queue));
expect(update2.getChunks({ allowUnfinished: true })).toEqual({
mimeType: "text/plain",
fileName: undefined,
chunks: [],
totalSizeBytes: undefined,
finished: false,
});
stream.push(new Uint8Array([1, 2, 3]));
const update3 = yield* $(Queue.take(queue));
expect(update3.getChunks({ allowUnfinished: true })).toEqual({
mimeType: "text/plain",
fileName: undefined,
chunks: [new Uint8Array([1, 2, 3])],
totalSizeBytes: undefined,
finished: false,
});
stream.push(new Uint8Array([4, 5, 6]));
const update4 = yield* $(Queue.take(queue));
expect(update4.getChunks({ allowUnfinished: true })).toEqual({
mimeType: "text/plain",
fileName: undefined,
chunks: [
new Uint8Array([1, 2, 3]),
new Uint8Array([4, 5, 6]),
],
totalSizeBytes: undefined,
finished: false,
});
stream.end();
const update5 = yield* $(Queue.take(queue));
expect(update5.getChunks()).toEqual({
mimeType: "text/plain",
fileName: undefined,
chunks: [
new Uint8Array([1, 2, 3]),
new Uint8Array([4, 5, 6]),
],
totalSizeBytes: undefined,
finished: true,
});
}),
const [initialAsPeer, secondAsPeer] = connectedPeers(
"initial",
"second",
{ peer1role: "server", peer2role: "client" },
);
me._raw.core.node.syncManager.addPeer(secondAsPeer);
if (!isControlledAccount(me)) {
throw "me is not a controlled account";
}
const meOnSecondPeer = await Account.become({
accountID: me.id,
accountSecret: me._raw.agentSecret,
peersToLoadFrom: [initialAsPeer],
// eslint-disable-next-line @typescript-eslint/no-explicit-any
sessionID: newRandomSessionID(me.id as any),
crypto: Crypto,
});
const queue = new cojsonInternals.Channel();
BinaryCoStream.subscribe(
stream.id,
meOnSecondPeer,
[],
(subscribedStream) => {
void queue.push(subscribedStream);
},
);
const update1 = (await queue.next()).value;
expect(update1.getChunks()).toBe(undefined);
stream.start({ mimeType: "text/plain" });
const update2 = (await queue.next()).value;
expect(update2.getChunks({ allowUnfinished: true })).toEqual({
mimeType: "text/plain",
fileName: undefined,
chunks: [],
totalSizeBytes: undefined,
finished: false,
});
stream.push(new Uint8Array([1, 2, 3]));
const update3 = (await queue.next()).value;
expect(update3.getChunks({ allowUnfinished: true })).toEqual({
mimeType: "text/plain",
fileName: undefined,
chunks: [new Uint8Array([1, 2, 3])],
totalSizeBytes: undefined,
finished: false,
});
stream.push(new Uint8Array([4, 5, 6]));
const update4 = (await queue.next()).value;
expect(update4.getChunks({ allowUnfinished: true })).toEqual({
mimeType: "text/plain",
fileName: undefined,
chunks: [new Uint8Array([1, 2, 3]), new Uint8Array([4, 5, 6])],
totalSizeBytes: undefined,
finished: false,
});
stream.end();
const update5 = (await queue.next()).value;
expect(update5.getChunks()).toEqual({
mimeType: "text/plain",
fileName: undefined,
chunks: [new Uint8Array([1, 2, 3]), new Uint8Array([4, 5, 6])],
totalSizeBytes: undefined,
finished: true,
});
});
});

View File

@@ -14,7 +14,6 @@ import {
ID,
} from "../index.js";
import { newRandomSessionID } from "cojson/src/coValueCore.js";
import { Effect } from "effect";
class TestMap extends CoMap {
list = co.ref(TestList);
@@ -39,12 +38,11 @@ describe("Deep loading with depth arg", async () => {
crypto: Crypto,
});
const [initialAsPeer, secondPeer] = await Effect.runPromise(
connectedPeers("initial", "second", {
peer1role: "server",
peer2role: "client",
}),
);
const [initialAsPeer, secondPeer] = connectedPeers("initial", "second", {
peer1role: "server",
peer2role: "client",
});
if (!isControlledAccount(me)) {
throw "me is not a controlled account";
}
@@ -140,8 +138,8 @@ describe("Deep loading with depth arg", async () => {
throw new Error("map4 is undefined");
}
expect(map4.list[0]?.stream).not.toBe(null);
expect(map4.list[0]?.stream?.[me.id]?.value).toBe(null);
expect(map4.list[0]?.stream?.byMe?.value).toBe(null);
expect(map4.list[0]?.stream?.[me.id]).toBe(undefined)
expect(map4.list[0]?.stream?.byMe?.value).toBe(undefined);
const map5 = await TestMap.load(map.id, meOnSecondPeer, {
list: [{ stream: [{}] }],
@@ -254,15 +252,15 @@ test("Deep loading a record-like coMap", async () => {
crypto: Crypto,
});
const [initialAsPeer, secondPeer] = await Effect.runPromise(
connectedPeers("initial", "second", {
peer1role: "server",
peer2role: "client",
}),
);
const [initialAsPeer, secondPeer] = connectedPeers("initial", "second", {
peer1role: "server",
peer2role: "client",
});
if (!isControlledAccount(me)) {
throw "me is not a controlled account";
}
me._raw.core.node.syncManager.addPeer(secondPeer);
const meOnSecondPeer = await Account.become({
accountID: me.id,

381
pnpm-lock.yaml generated
View File

@@ -156,9 +156,6 @@ importers:
cojson-transport-ws:
specifier: workspace:*
version: link:../../packages/cojson-transport-ws
effect:
specifier: ^3.5.2
version: 3.5.2
hash-slash:
specifier: workspace:*
version: link:../../packages/hash-slash
@@ -444,12 +441,15 @@ importers:
'@scure/base':
specifier: ^1.1.1
version: 1.1.5
effect:
specifier: ^3.5.2
version: 3.5.2
hash-wasm:
specifier: ^4.9.0
version: 4.11.0
neverthrow:
specifier: ^7.0.1
version: 7.0.1
queueueue:
specifier: ^4.1.2
version: 4.1.2
devDependencies:
'@types/jest':
specifier: ^29.5.3
@@ -466,6 +466,9 @@ importers:
eslint-config-prettier:
specifier: ^9.1.0
version: 9.1.0(eslint@8.56.0)
eslint-plugin-neverthrow:
specifier: ^1.1.4
version: 1.1.4(@typescript-eslint/parser@6.15.0(eslint@8.56.0)(typescript@5.0.2))(eslint@8.56.0)(typescript@5.0.2)
eslint-plugin-require-extensions:
specifier: ^0.1.3
version: 0.1.3(eslint@8.56.0)
@@ -481,9 +484,6 @@ importers:
cojson:
specifier: workspace:*
version: link:../cojson
effect:
specifier: ^3.5.2
version: 3.5.2
typescript:
specifier: ^5.1.6
version: 5.3.3
@@ -506,9 +506,6 @@ importers:
cojson:
specifier: workspace:*
version: link:../cojson
effect:
specifier: ^3.5.2
version: 3.5.2
typescript:
specifier: ^5.1.6
version: 5.3.3
@@ -522,9 +519,6 @@ importers:
cojson:
specifier: workspace:*
version: link:../cojson
effect:
specifier: ^3.5.2
version: 3.5.2
typescript:
specifier: ^5.1.6
version: 5.3.3
@@ -559,9 +553,6 @@ importers:
cojson-transport-ws:
specifier: workspace:*
version: link:../cojson-transport-ws
effect:
specifier: ^3.5.2
version: 3.5.2
jazz-tools:
specifier: workspace:*
version: link:../jazz-tools
@@ -602,9 +593,6 @@ importers:
cojson-transport-ws:
specifier: workspace:*
version: link:../cojson-transport-ws
effect:
specifier: ^3.5.2
version: 3.5.2
jazz-tools:
specifier: workspace:*
version: link:../jazz-tools
@@ -650,14 +638,23 @@ importers:
packages/jazz-run:
dependencies:
'@effect/cli':
specifier: ^0.36.21
version: 0.36.21(@effect/platform@0.53.2(@effect/schema@0.66.16(effect@3.5.2)(fast-check@3.17.2))(effect@3.5.2))(@effect/printer-ansi@0.33.12(@effect/typeclass@0.24.12(effect@3.5.2))(effect@3.5.2))(@effect/printer@0.33.12(@effect/typeclass@0.24.12(effect@3.5.2))(effect@3.5.2))(@effect/schema@0.66.16(effect@3.5.2)(fast-check@3.17.2))(effect@3.5.2)
specifier: ^0.41.2
version: 0.41.2(@effect/platform@0.62.2(@effect/schema@0.71.1(effect@3.6.5))(effect@3.6.5))(@effect/printer-ansi@0.34.5(@effect/typeclass@0.25.5(effect@3.6.5))(effect@3.6.5))(@effect/printer@0.34.5(@effect/typeclass@0.25.5(effect@3.6.5))(effect@3.6.5))(@effect/schema@0.71.1(effect@3.6.5))(effect@3.6.5)
'@effect/platform-node':
specifier: ^0.49.2
version: 0.49.2(@effect/platform@0.53.2(@effect/schema@0.66.16(effect@3.5.2)(fast-check@3.17.2))(effect@3.5.2))(effect@3.5.2)
specifier: ^0.57.2
version: 0.57.2(@effect/platform@0.62.2(@effect/schema@0.71.1(effect@3.6.5))(effect@3.6.5))(effect@3.6.5)
'@effect/printer':
specifier: ^0.34.5
version: 0.34.5(@effect/typeclass@0.25.5(effect@3.6.5))(effect@3.6.5)
'@effect/printer-ansi':
specifier: ^0.34.5
version: 0.34.5(@effect/typeclass@0.25.5(effect@3.6.5))(effect@3.6.5)
'@effect/schema':
specifier: ^0.66.16
version: 0.66.16(effect@3.5.2)(fast-check@3.17.2)
specifier: ^0.71.1
version: 0.71.1(effect@3.6.5)
'@effect/typeclass':
specifier: ^0.25.5
version: 0.25.5(effect@3.6.5)
cojson:
specifier: workspace:*
version: link:../cojson
@@ -665,11 +662,8 @@ importers:
specifier: workspace:*
version: link:../cojson-transport-ws
effect:
specifier: ^3.5.2
version: 3.5.2
fast-check:
specifier: ^3.17.2
version: 3.17.2
specifier: ^3.6.5
version: 3.6.5
jazz-tools:
specifier: workspace:*
version: link:../jazz-tools
@@ -683,21 +677,12 @@ importers:
typescript:
specifier: ^5.3.3
version: 5.3.3
vitest:
specifier: ^0.34.6
version: 0.34.6(@vitest/browser@0.34.6)(safaridriver@0.1.1)(webdriverio@8.26.3(typescript@5.3.3))
packages/jazz-tools:
dependencies:
'@effect/schema':
specifier: ^0.66.16
version: 0.66.16(effect@3.5.2)(fast-check@3.17.2)
cojson:
specifier: workspace:*
version: link:../cojson
effect:
specifier: ^3.5.2
version: 3.5.2
fast-check:
specifier: ^3.17.2
version: 3.17.2
@@ -791,55 +776,54 @@ packages:
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
engines: {node: '>=12'}
'@effect/cli@0.36.21':
resolution: {integrity: sha512-39ya5HjJxeFE84pv6D1JvZHHLwalB+yQWbNOWvu65Lf8FdngLptwDCNgOnrzvHvgOEguaPUaN7YHlHReUAOddA==}
'@effect/cli@0.41.2':
resolution: {integrity: sha512-RQmHfS8QNj+Xv59vvlb9jZL1B4h8hGXHtl6lURhCGt170R6Rztlv5FfrCBDNMPeHXDJnbEbQdIV5G9uVymc2dQ==}
peerDependencies:
'@effect/platform': ^0.53.2
'@effect/printer': ^0.33.12
'@effect/printer-ansi': ^0.33.12
'@effect/schema': ^0.66.16
effect: ^3.1.3
'@effect/platform': ^0.62.2
'@effect/printer': ^0.34.5
'@effect/printer-ansi': ^0.34.5
'@effect/schema': ^0.71.1
effect: ^3.6.5
'@effect/platform-node-shared@0.4.21':
resolution: {integrity: sha512-F0cEeLQ9Qi5hr/KYxgjL2+bJT1Ti4Sd24czyCE1ETEMxUid7zca3NCBbhjDKNMkHYroGhP2bG2lVzRx048cfzg==}
'@effect/platform-node-shared@0.12.2':
resolution: {integrity: sha512-t+gmgaC4G9scqDT9ByZWTHrfLiRcdJ+TrkzbinhjHuT3M2IHZHd57zUdn+J0xqlWNRZETSn+jLI1mKFx5OlVAg==}
peerDependencies:
'@effect/platform': ^0.53.2
effect: ^3.1.3
'@effect/platform': ^0.62.2
effect: ^3.6.5
'@effect/platform-node@0.49.2':
resolution: {integrity: sha512-OvixXYcQrRZAt0T/7GJ9Li2RRr6+3HDQ8kUiiBNo+OWyg25QVfQTsTU4OjT7MHnuR0mcNzbsu5VlDskci7/T/g==}
'@effect/platform-node@0.57.2':
resolution: {integrity: sha512-yB31Gf8rTOgvSbmHBwGPoC3QfF7LeSLuOtjpwhysdXN8ST1Pi/77zcAeUIA3kKytJcO8Abg8bsTRkq9WvvMG9g==}
peerDependencies:
'@effect/platform': ^0.53.2
effect: ^3.1.3
'@effect/platform': ^0.62.2
effect: ^3.6.5
'@effect/platform@0.53.2':
resolution: {integrity: sha512-+OeR9o7UOdWsl8xQow7FUIgoJaPjqZ5dPz2xxGv0VeNDkAJMxu7+FWDtB7tK+r4PzhA3X/x5YaGbH09mcOD+mg==}
'@effect/platform@0.62.2':
resolution: {integrity: sha512-pdC2gDaFB4aNJviumUGoArXiC44UdorkRjO2+LjhsK1ahfxYNnw+vl1xLg9igU2E1/qDonZVHyJ1WHpWcWF5Ow==}
peerDependencies:
'@effect/schema': ^0.66.16
effect: ^3.1.3
'@effect/schema': ^0.71.1
effect: ^3.6.5
'@effect/printer-ansi@0.33.12':
resolution: {integrity: sha512-MEGolsEBQlZNsgEoqq02erGgMmLH/6QAU3Bq9pQp2fkOR907LMvXTD++HiMRmEnV1mqDMxgQvICF6RA/voLnCA==}
'@effect/printer-ansi@0.34.5':
resolution: {integrity: sha512-XxpXDubkFRR8UvwI+TLI/q2IJ5LbZ4I+G7zn9FA/njZ0GFEETs1N+gcnHYamh0XzQe8LyzKvTAu+LAvD7lWs7Q==}
peerDependencies:
'@effect/typeclass': ^0.24.12
effect: ^3.1.3
'@effect/typeclass': ^0.25.5
effect: ^3.6.5
'@effect/printer@0.33.12':
resolution: {integrity: sha512-AY5uupL6yWm/fQsD+j9UHBKZcd6Ai4I6Z2PHec48KwgFQUiKCpn6sN69Scp8CINwKHrYsCtwwqyO+xt8yZOXDw==}
'@effect/printer@0.34.5':
resolution: {integrity: sha512-fzLOJL9chdU1kLL1iLWfxBlKAvkF9Vc+SKAlHlgoVCwumiodK5HLGExWjy5aclQwiaRZl/kz1G2U9G9zxwEDYw==}
peerDependencies:
'@effect/typeclass': ^0.24.12
effect: ^3.1.3
'@effect/typeclass': ^0.25.5
effect: ^3.6.5
'@effect/schema@0.66.16':
resolution: {integrity: sha512-sT/k5NOgKslGPzs3DUaCFuM6g2JQoIIT8jpwEorAZooplPIMK2xIspr7ECz6pp6Dc7Wz/ppXGk7HVyGZQsIYEQ==}
'@effect/schema@0.71.1':
resolution: {integrity: sha512-XvFttkuBUL3s4ofZ+OVE4Pagb4wsPG8laSS8iO5lVI9Yt1zIM49uxlYIA2BJ45jjS3MdplUepC0NilotKnjU2A==}
peerDependencies:
effect: ^3.1.3
fast-check: ^3.13.2
effect: ^3.6.5
'@effect/typeclass@0.24.12':
resolution: {integrity: sha512-s18bZwNKgVkp9kEK2ZkwnxOHRvn1UdpAnhVVHyUyYiUYJWGwlHUXTZWf0Y+kCaai+fLcLUGF63qu97JdNzdDyQ==}
'@effect/typeclass@0.25.5':
resolution: {integrity: sha512-TwharqoM3DiAVh1CkjHM0ofZSFYvp0KLCDGwds9L4mAMVn1/0GyLxPM8u25d/oNFgBxPFPU9DJtJB/Hj6yUHgg==}
peerDependencies:
effect: ^3.1.3
effect: ^3.6.5
'@esbuild/aix-ppc64@0.19.10':
resolution: {integrity: sha512-Q+mk96KJ+FZ30h9fsJl+67IjNJm3x2eX+GBWGmocAKgzp27cowCOOqSdscX80s0SpdFXZnIv/+1xD1EctFx96Q==}
@@ -1707,6 +1691,12 @@ packages:
'@types/chai@4.3.11':
resolution: {integrity: sha512-qQR1dr2rGIHYlJulmr8Ioq3De0Le9E4MJ5AiaeAETJJpndT1uUNHsGFK3L/UIu+rbkQSdj8J/w2bCsBZc/Y5fQ==}
'@types/eslint-utils@3.0.5':
resolution: {integrity: sha512-dGOLJqHXpjomkPgZiC7vnVSJtFIOM1Y6L01EyUhzPuD0y0wfIGiqxiGs3buUBfzxLIQHrCvZsIMDaCZ8R5IIoA==}
'@types/eslint@9.6.0':
resolution: {integrity: sha512-gi6WQJ7cHRgZxtkQEoyHMppPjq9Kxo5Tjn2prSKDSmZrCz8TZ3jSRCeTJm+WoM+oB0WG37bRqLzaaU3q7JypGg==}
'@types/estree@1.0.5':
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
@@ -2436,8 +2426,8 @@ packages:
resolution: {integrity: sha512-G0wNgFMFRDnFfKaXG2R6HiyVHqhKwdQ3EgoxW3wPlns2wKqem7F+HgkWBcevN7Vz0nN4AXtskID7/6jsYDXcKw==}
hasBin: true
effect@3.5.2:
resolution: {integrity: sha512-jZAZAgHHrnkqrRaaLBU/a6zEFm3+uOimr+ZJqvCqiHtw5k2AE6F0jiEPuM/MNJOe4r16JDo4SYa3IULIQuXvAw==}
effect@3.6.5:
resolution: {integrity: sha512-NhopZTAKljaAlR0CEroOAJJngdqg7bzlnWcDrCwh4d2WNVohVbBtUS4SGqLt8tUy7IFsTWATYiUtmhDG+YELjA==}
electron-to-chromium@1.4.615:
resolution: {integrity: sha512-/bKPPcgZVUziECqDc+0HkT87+0zhaWSZHNXqF8FLd2lQcptpmUFwoCSWjCdOng9Gdq+afKArPdEg/0ZW461Eng==}
@@ -2528,6 +2518,13 @@ packages:
peerDependencies:
eslint: '>=7.0.0'
eslint-plugin-neverthrow@1.1.4:
resolution: {integrity: sha512-+8zsE5rDqsDfKYAOq0Fr2jbuxHXTmntIWWJqJA3ms1GAKcVCjl0ycetzOu/hTxot9ctr+WYQpCBgB3F2HATR7A==}
engines: {node: '>=14.17'}
peerDependencies:
'@typescript-eslint/parser': '>=4.20.0'
eslint: '>=5.16.0'
eslint-plugin-react-hooks@4.6.0:
resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==}
engines: {node: '>=10'}
@@ -2549,6 +2546,16 @@ packages:
resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
eslint-utils@3.0.0:
resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==}
engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0}
peerDependencies:
eslint: '>=5'
eslint-visitor-keys@2.1.0:
resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==}
engines: {node: '>=10'}
eslint-visitor-keys@3.4.3:
resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -2617,8 +2624,9 @@ packages:
resolution: {integrity: sha512-+3DPTxtxABLgmmVpYxrash3DHoq0cMa1jjLYNp3qqokKKhqVEaS4lbnaDKqWU5Dd6C2pEudPPBAEEQ9nUou9OQ==}
engines: {node: '>=8.0.0'}
fast-decode-uri-component@1.0.1:
resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==}
fast-check@3.21.0:
resolution: {integrity: sha512-QpmbiqRFRZ+SIlBJh6xi5d/PgXciUc/xWKc4Vi2RWEHHIRx6oM3f0fWNna++zP9VB5HUBTObUK9gTKQP3vVcrQ==}
engines: {node: '>=8.0.0'}
fast-deep-equal@2.0.1:
resolution: {integrity: sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==}
@@ -2639,12 +2647,12 @@ packages:
fast-levenshtein@2.0.6:
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
fast-list@1.0.3:
resolution: {integrity: sha512-Lm56Ci3EqefHNdIneRFuzhpPcpVVBz9fgqVmG3UQIxAefJv1mEYsZ1WQLTWqmdqeGEwbI2t6fbZgp9TqTYARuA==}
fast-loops@1.1.3:
resolution: {integrity: sha512-8EZzEP0eKkEEVX+drtd9mtuQ+/QrlfW/5MlwcwK5Nds6EkZ/tRzEexkzUY2mIssnAyVLT+TKHuRXmFNNXYUd6g==}
fast-querystring@1.1.2:
resolution: {integrity: sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==}
fast-shallow-equal@1.0.0:
resolution: {integrity: sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw==}
@@ -2672,8 +2680,8 @@ packages:
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
engines: {node: '>=8'}
find-my-way-ts@0.1.2:
resolution: {integrity: sha512-AWsH49RGcyayzzxSAkwQKC1bToY5ForAfNreix88slo7wijg1wwTtCPvUUoVm/qhIY3UVSHBKApw8mOGPxsvSg==}
find-my-way-ts@0.1.5:
resolution: {integrity: sha512-4GOTMrpGQVzsCH2ruUn2vmwzV/02zF4q+ybhCIrw/Rkt3L8KWcycdC6aJMctJzwN4fXD4SD5F/4B9Sksh5rE0A==}
find-up@4.1.0:
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
@@ -2948,8 +2956,8 @@ packages:
ini@1.3.8:
resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
ini@4.1.2:
resolution: {integrity: sha512-AMB1mvwR1pyBFY/nSevUX6y8nJWS63/SzUKD3JyQn97s4xgIdgQPT75IRouIiBAN4yLQBUShNYVW0+UG25daCw==}
ini@4.1.3:
resolution: {integrity: sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
inline-style-prefixer@7.0.0:
@@ -3375,8 +3383,8 @@ packages:
multimath@2.0.0:
resolution: {integrity: sha512-toRx66cAMJ+Ccz7pMIg38xSIrtnbozk0dchXezwQDMgQmbGpfxjtv68H+L00iFL8hxDaVjrmwAFSb3I6bg8Q2g==}
multipasta@0.2.1:
resolution: {integrity: sha512-0NR15o5iewJFMsZ4jN6cztCd4izTLZPyHUfBrakbg/x70B8zplPE6qumbs+A7LmhGtQMzpN/K8MOOdifMgAKFA==}
multipasta@0.2.5:
resolution: {integrity: sha512-c8eMDb1WwZcE02WVjHoOmUVk7fnKU/RmUcosHACglrWAuPQsEJv+E8430sXj6jNc1jHw0zrS16aCjQh4BcEb4A==}
mz@2.7.0:
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
@@ -3405,13 +3413,16 @@ packages:
resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==}
engines: {node: '>= 0.4.0'}
neverthrow@7.0.1:
resolution: {integrity: sha512-NY1sD1wZn6dwwzV9/5KZ4wRIpH4xC7ARy3fGmnEi2Pi8CHDTWlp+WSF+if8cudrwuCWGrk2y2BE/dnGh070iuA==}
engines: {node: '>=18'}
node-abi@3.62.0:
resolution: {integrity: sha512-CPMcGa+y33xuL1E0TcNIu4YyaZCxnnvkVaEXrsosR3FxN+fV8xvb7Mzpb7IgKler10qeMkE6+Dp8qJhpzdq35g==}
engines: {node: '>=10'}
node-addon-api@7.1.0:
resolution: {integrity: sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g==}
engines: {node: ^16 || ^18 || >= 20}
node-addon-api@7.1.1:
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
node-domexception@1.0.0:
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
@@ -3545,9 +3556,6 @@ packages:
resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
engines: {node: '>=8'}
path-browserify@1.0.1:
resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
path-exists@4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
@@ -3743,6 +3751,10 @@ packages:
queue-tick@1.0.1:
resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==}
queueueue@4.1.2:
resolution: {integrity: sha512-YNOihQyk72FRrGQP+fVjUDVCSgxtHS5EdfgXjCYwPCTyhVWXriHfUt0XK0eT7vcBX0m91g0e0KVO8CxoNLh60g==}
engines: {node: '>=18'}
quick-lru@4.0.1:
resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==}
engines: {node: '>=8'}
@@ -4274,9 +4286,18 @@ packages:
'@swc/wasm':
optional: true
tslib@1.14.1:
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
tslib@2.6.2:
resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
tsutils@3.21.0:
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
engines: {node: '>= 6'}
peerDependencies:
typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta'
tty-table@4.2.3:
resolution: {integrity: sha512-Fs15mu0vGzCrj8fmJNP7Ynxt5J7praPXqFN0leZeZBXJwkMxv9cb2D454k1ltrtUSJbZ4yH4e0CynsHLxmUfFA==}
engines: {node: '>=8.0.0'}
@@ -4396,8 +4417,8 @@ packages:
undici-types@5.26.5:
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
undici@6.16.1:
resolution: {integrity: sha512-NeNiTT7ixpeiL1qOIU/xTVpHpVP0svmI6PwoCKaMGaI5AsHOaRdwqU/f7Fi9eyU4u03nd5U/BC8wmRMnS9nqoA==}
undici@6.19.8:
resolution: {integrity: sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g==}
engines: {node: '>=18.17'}
uniqolor@1.1.1:
@@ -4678,6 +4699,18 @@ packages:
utf-8-validate:
optional: true
ws@8.18.0:
resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: '>=5.0.2'
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
y18n@4.0.3:
resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
@@ -4691,15 +4724,16 @@ packages:
yallist@4.0.0:
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
yaml@2.3.4:
resolution: {integrity: sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==}
engines: {node: '>= 14'}
yaml@2.4.2:
resolution: {integrity: sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA==}
engines: {node: '>= 14'}
hasBin: true
yaml@2.5.0:
resolution: {integrity: sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==}
engines: {node: '>= 14'}
hasBin: true
yargs-parser@18.1.3:
resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
engines: {node: '>=6'}
@@ -4914,63 +4948,62 @@ snapshots:
dependencies:
'@jridgewell/trace-mapping': 0.3.9
'@effect/cli@0.36.21(@effect/platform@0.53.2(@effect/schema@0.66.16(effect@3.5.2)(fast-check@3.17.2))(effect@3.5.2))(@effect/printer-ansi@0.33.12(@effect/typeclass@0.24.12(effect@3.5.2))(effect@3.5.2))(@effect/printer@0.33.12(@effect/typeclass@0.24.12(effect@3.5.2))(effect@3.5.2))(@effect/schema@0.66.16(effect@3.5.2)(fast-check@3.17.2))(effect@3.5.2)':
'@effect/cli@0.41.2(@effect/platform@0.62.2(@effect/schema@0.71.1(effect@3.6.5))(effect@3.6.5))(@effect/printer-ansi@0.34.5(@effect/typeclass@0.25.5(effect@3.6.5))(effect@3.6.5))(@effect/printer@0.34.5(@effect/typeclass@0.25.5(effect@3.6.5))(effect@3.6.5))(@effect/schema@0.71.1(effect@3.6.5))(effect@3.6.5)':
dependencies:
'@effect/platform': 0.53.2(@effect/schema@0.66.16(effect@3.5.2)(fast-check@3.17.2))(effect@3.5.2)
'@effect/printer': 0.33.12(@effect/typeclass@0.24.12(effect@3.5.2))(effect@3.5.2)
'@effect/printer-ansi': 0.33.12(@effect/typeclass@0.24.12(effect@3.5.2))(effect@3.5.2)
'@effect/schema': 0.66.16(effect@3.5.2)(fast-check@3.17.2)
effect: 3.5.2
ini: 4.1.2
'@effect/platform': 0.62.2(@effect/schema@0.71.1(effect@3.6.5))(effect@3.6.5)
'@effect/printer': 0.34.5(@effect/typeclass@0.25.5(effect@3.6.5))(effect@3.6.5)
'@effect/printer-ansi': 0.34.5(@effect/typeclass@0.25.5(effect@3.6.5))(effect@3.6.5)
'@effect/schema': 0.71.1(effect@3.6.5)
effect: 3.6.5
ini: 4.1.3
toml: 3.0.0
yaml: 2.4.2
yaml: 2.5.0
'@effect/platform-node-shared@0.4.21(@effect/platform@0.53.2(@effect/schema@0.66.16(effect@3.5.2)(fast-check@3.17.2))(effect@3.5.2))(effect@3.5.2)':
'@effect/platform-node-shared@0.12.2(@effect/platform@0.62.2(@effect/schema@0.71.1(effect@3.6.5))(effect@3.6.5))(effect@3.6.5)':
dependencies:
'@effect/platform': 0.53.2(@effect/schema@0.66.16(effect@3.5.2)(fast-check@3.17.2))(effect@3.5.2)
'@effect/platform': 0.62.2(@effect/schema@0.71.1(effect@3.6.5))(effect@3.6.5)
'@parcel/watcher': 2.4.1
effect: 3.5.2
multipasta: 0.2.1
effect: 3.6.5
multipasta: 0.2.5
'@effect/platform-node@0.49.2(@effect/platform@0.53.2(@effect/schema@0.66.16(effect@3.5.2)(fast-check@3.17.2))(effect@3.5.2))(effect@3.5.2)':
'@effect/platform-node@0.57.2(@effect/platform@0.62.2(@effect/schema@0.71.1(effect@3.6.5))(effect@3.6.5))(effect@3.6.5)':
dependencies:
'@effect/platform': 0.53.2(@effect/schema@0.66.16(effect@3.5.2)(fast-check@3.17.2))(effect@3.5.2)
'@effect/platform-node-shared': 0.4.21(@effect/platform@0.53.2(@effect/schema@0.66.16(effect@3.5.2)(fast-check@3.17.2))(effect@3.5.2))(effect@3.5.2)
effect: 3.5.2
'@effect/platform': 0.62.2(@effect/schema@0.71.1(effect@3.6.5))(effect@3.6.5)
'@effect/platform-node-shared': 0.12.2(@effect/platform@0.62.2(@effect/schema@0.71.1(effect@3.6.5))(effect@3.6.5))(effect@3.6.5)
effect: 3.6.5
mime: 3.0.0
undici: 6.16.1
ws: 8.17.0
undici: 6.19.8
ws: 8.18.0
transitivePeerDependencies:
- bufferutil
- utf-8-validate
'@effect/platform@0.53.2(@effect/schema@0.66.16(effect@3.5.2)(fast-check@3.17.2))(effect@3.5.2)':
'@effect/platform@0.62.2(@effect/schema@0.71.1(effect@3.6.5))(effect@3.6.5)':
dependencies:
'@effect/schema': 0.66.16(effect@3.5.2)(fast-check@3.17.2)
effect: 3.5.2
find-my-way-ts: 0.1.2
multipasta: 0.2.1
path-browserify: 1.0.1
'@effect/schema': 0.71.1(effect@3.6.5)
effect: 3.6.5
find-my-way-ts: 0.1.5
multipasta: 0.2.5
'@effect/printer-ansi@0.33.12(@effect/typeclass@0.24.12(effect@3.5.2))(effect@3.5.2)':
'@effect/printer-ansi@0.34.5(@effect/typeclass@0.25.5(effect@3.6.5))(effect@3.6.5)':
dependencies:
'@effect/printer': 0.33.12(@effect/typeclass@0.24.12(effect@3.5.2))(effect@3.5.2)
'@effect/typeclass': 0.24.12(effect@3.5.2)
effect: 3.5.2
'@effect/printer': 0.34.5(@effect/typeclass@0.25.5(effect@3.6.5))(effect@3.6.5)
'@effect/typeclass': 0.25.5(effect@3.6.5)
effect: 3.6.5
'@effect/printer@0.33.12(@effect/typeclass@0.24.12(effect@3.5.2))(effect@3.5.2)':
'@effect/printer@0.34.5(@effect/typeclass@0.25.5(effect@3.6.5))(effect@3.6.5)':
dependencies:
'@effect/typeclass': 0.24.12(effect@3.5.2)
effect: 3.5.2
'@effect/typeclass': 0.25.5(effect@3.6.5)
effect: 3.6.5
'@effect/schema@0.66.16(effect@3.5.2)(fast-check@3.17.2)':
'@effect/schema@0.71.1(effect@3.6.5)':
dependencies:
effect: 3.5.2
fast-check: 3.17.2
effect: 3.6.5
fast-check: 3.21.0
'@effect/typeclass@0.24.12(effect@3.5.2)':
'@effect/typeclass@0.25.5(effect@3.6.5)':
dependencies:
effect: 3.5.2
effect: 3.6.5
'@esbuild/aix-ppc64@0.19.10':
optional: true
@@ -5287,7 +5320,7 @@ snapshots:
detect-libc: 1.0.3
is-glob: 4.0.3
micromatch: 4.0.5
node-addon-api: 7.1.0
node-addon-api: 7.1.1
optionalDependencies:
'@parcel/watcher-android-arm64': 2.4.1
'@parcel/watcher-darwin-arm64': 2.4.1
@@ -5661,6 +5694,16 @@ snapshots:
'@types/chai@4.3.11': {}
'@types/eslint-utils@3.0.5':
dependencies:
'@types/eslint': 9.6.0
'@types/estree': 1.0.5
'@types/eslint@9.6.0':
dependencies:
'@types/estree': 1.0.5
'@types/json-schema': 7.0.15
'@types/estree@1.0.5': {}
'@types/http-cache-semantics@4.0.4': {}
@@ -6568,7 +6611,7 @@ snapshots:
unzipper: 0.10.14
which: 4.0.0
effect@3.5.2: {}
effect@3.6.5: {}
electron-to-chromium@1.4.615: {}
@@ -6741,6 +6784,16 @@ snapshots:
dependencies:
eslint: 8.56.0
eslint-plugin-neverthrow@1.1.4(@typescript-eslint/parser@6.15.0(eslint@8.56.0)(typescript@5.0.2))(eslint@8.56.0)(typescript@5.0.2):
dependencies:
'@types/eslint-utils': 3.0.5
'@typescript-eslint/parser': 6.15.0(eslint@8.56.0)(typescript@5.0.2)
eslint: 8.56.0
eslint-utils: 3.0.0(eslint@8.56.0)
tsutils: 3.21.0(typescript@5.0.2)
transitivePeerDependencies:
- typescript
eslint-plugin-react-hooks@4.6.0(eslint@8.56.0):
dependencies:
eslint: 8.56.0
@@ -6758,6 +6811,13 @@ snapshots:
esrecurse: 4.3.0
estraverse: 5.3.0
eslint-utils@3.0.0(eslint@8.56.0):
dependencies:
eslint: 8.56.0
eslint-visitor-keys: 2.1.0
eslint-visitor-keys@2.1.0: {}
eslint-visitor-keys@3.4.3: {}
eslint@8.56.0:
@@ -6873,7 +6933,9 @@ snapshots:
dependencies:
pure-rand: 6.1.0
fast-decode-uri-component@1.0.1: {}
fast-check@3.21.0:
dependencies:
pure-rand: 6.1.0
fast-deep-equal@2.0.1: {}
@@ -6893,11 +6955,9 @@ snapshots:
fast-levenshtein@2.0.6: {}
fast-loops@1.1.3: {}
fast-list@1.0.3: {}
fast-querystring@1.1.2:
dependencies:
fast-decode-uri-component: 1.0.1
fast-loops@1.1.3: {}
fast-shallow-equal@1.0.0: {}
@@ -6926,9 +6986,7 @@ snapshots:
dependencies:
to-regex-range: 5.0.1
find-my-way-ts@0.1.2:
dependencies:
fast-querystring: 1.1.2
find-my-way-ts@0.1.5: {}
find-up@4.1.0:
dependencies:
@@ -7235,7 +7293,7 @@ snapshots:
ini@1.3.8: {}
ini@4.1.2: {}
ini@4.1.3: {}
inline-style-prefixer@7.0.0:
dependencies:
@@ -7625,7 +7683,7 @@ snapshots:
glur: 1.1.2
object-assign: 4.1.1
multipasta@0.2.1: {}
multipasta@0.2.5: {}
mz@2.7.0:
dependencies:
@@ -7656,11 +7714,13 @@ snapshots:
netmask@2.0.2: {}
neverthrow@7.0.1: {}
node-abi@3.62.0:
dependencies:
semver: 7.5.4
node-addon-api@7.1.0: {}
node-addon-api@7.1.1: {}
node-domexception@1.0.0: {}
@@ -7793,8 +7853,6 @@ snapshots:
json-parse-even-better-errors: 2.3.1
lines-and-columns: 1.2.4
path-browserify@1.0.1: {}
path-exists@4.0.0: {}
path-is-absolute@1.0.1: {}
@@ -7864,7 +7922,7 @@ snapshots:
postcss-load-config@4.0.2(postcss@8.4.32)(ts-node@10.9.2(@swc/core@1.3.101)(@types/node@20.10.5)(typescript@5.3.3)):
dependencies:
lilconfig: 3.0.0
yaml: 2.3.4
yaml: 2.4.2
optionalDependencies:
postcss: 8.4.32
ts-node: 10.9.2(@swc/core@1.3.101)(@types/node@20.10.5)(typescript@5.3.3)
@@ -8010,6 +8068,10 @@ snapshots:
queue-tick@1.0.1: {}
queueueue@4.1.2:
dependencies:
fast-list: 1.0.3
quick-lru@4.0.1: {}
quick-lru@5.1.1: {}
@@ -8617,8 +8679,15 @@ snapshots:
optionalDependencies:
'@swc/core': 1.3.101
tslib@1.14.1: {}
tslib@2.6.2: {}
tsutils@3.21.0(typescript@5.0.2):
dependencies:
tslib: 1.14.1
typescript: 5.0.2
tty-table@4.2.3:
dependencies:
chalk: 4.1.2
@@ -8738,7 +8807,7 @@ snapshots:
undici-types@5.26.5: {}
undici@6.16.1: {}
undici@6.19.8: {}
uniqolor@1.1.1: {}
@@ -9120,6 +9189,8 @@ snapshots:
ws@8.17.0: {}
ws@8.18.0: {}
y18n@4.0.3: {}
y18n@5.0.8: {}
@@ -9128,10 +9199,10 @@ snapshots:
yallist@4.0.0: {}
yaml@2.3.4: {}
yaml@2.4.2: {}
yaml@2.5.0: {}
yargs-parser@18.1.3:
dependencies:
camelcase: 5.3.1