Compare commits
97 Commits
cursor-doc
...
example-co
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c563c3675f | ||
|
|
05372df4be | ||
|
|
c32c405bfd | ||
|
|
3c0b2cb689 | ||
|
|
6a2a176361 | ||
|
|
824bbc8bab | ||
|
|
e7ae6d95ba | ||
|
|
8bdf6908b8 | ||
|
|
12bc4fb2d3 | ||
|
|
75211e3c82 | ||
|
|
3185a20777 | ||
|
|
0c3905f93f | ||
|
|
8a31f56770 | ||
|
|
4373e290fe | ||
|
|
037ed4d59d | ||
|
|
c030128b28 | ||
|
|
34946c18bc | ||
|
|
fcca08655a | ||
|
|
adcd08c95a | ||
|
|
937e20b248 | ||
|
|
a759d9a7aa | ||
|
|
38af5b8dc8 | ||
|
|
b9473da159 | ||
|
|
cb1126ac15 | ||
|
|
989211f02f | ||
|
|
49a1a9d6f0 | ||
|
|
2f32599987 | ||
|
|
9ede39c229 | ||
|
|
ca3eb9dbd8 | ||
|
|
12273cf35b | ||
|
|
b3d4944608 | ||
|
|
2068aaff13 | ||
|
|
9dfad43433 | ||
|
|
db4986059e | ||
|
|
ed5369e99b | ||
|
|
2671fca1dc | ||
|
|
c81a2dcd0b | ||
|
|
b7a19c0693 | ||
|
|
2678bdbcca | ||
|
|
ee7823b33e | ||
|
|
46683235ba | ||
|
|
8ce7bb808c | ||
|
|
3a69928ebc | ||
|
|
00790a1535 | ||
|
|
56ccf5ea65 | ||
|
|
4814595724 | ||
|
|
706357ac4f | ||
|
|
4fcc8edc70 | ||
|
|
0f8ba9966b | ||
|
|
91fa2e092a | ||
|
|
ff52d6df3e | ||
|
|
a539b9e26b | ||
|
|
f3129a7914 | ||
|
|
d349b794e1 | ||
|
|
fad14dcff6 | ||
|
|
b99f13c948 | ||
|
|
e7cb337a24 | ||
|
|
85c9a432c3 | ||
|
|
c28d1c331c | ||
|
|
474ea89b81 | ||
|
|
4772309fb6 | ||
|
|
1501510cfc | ||
|
|
eda1588907 | ||
|
|
b14e0bfe24 | ||
|
|
87aa43b46b | ||
|
|
b93ce9fb7e | ||
|
|
a7d83e1c10 | ||
|
|
76a693da15 | ||
|
|
df2f021cfd | ||
|
|
50b15d2d1d | ||
|
|
48bf7cb188 | ||
|
|
e2e0af34b5 | ||
|
|
6a5bcd3063 | ||
|
|
bf7e62ec76 | ||
|
|
71dda6b10b | ||
|
|
4612e0545e | ||
|
|
07feedd641 | ||
|
|
edbd567f11 | ||
|
|
4d8bb9cdb8 | ||
|
|
1971448f5d | ||
|
|
0e861e7df8 | ||
|
|
6e3f1efcd0 | ||
|
|
eb87d10783 | ||
|
|
5a54e4aa50 | ||
|
|
16b0a22ded | ||
|
|
6a8ce1e32d | ||
|
|
10a4b0e888 | ||
|
|
3c973c84ce | ||
|
|
d469d68771 | ||
|
|
c068d7a369 | ||
|
|
25088ed5db | ||
|
|
21e74998e8 | ||
|
|
a2e9ae4731 | ||
|
|
0514a7e64b | ||
|
|
b063cccdfc | ||
|
|
d89d2978ff | ||
|
|
221ca30790 |
4
.github/workflows/pre-release.yml
vendored
4
.github/workflows/pre-release.yml
vendored
@@ -1,7 +1,7 @@
|
||||
name: Pre-Publish tagged Pull Requests
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
types: [opened, synchronize, reopened, labeled]
|
||||
|
||||
jobs:
|
||||
pre-release:
|
||||
@@ -99,4 +99,4 @@ jobs:
|
||||
);
|
||||
await logPublishInfo();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ We welcome all ideas! If you have suggestions, feel free to open an issue marked
|
||||
|
||||
### 5. Local Setup
|
||||
|
||||
You'll need Node.js 20.x or 22.x installed (we're working on support for 23.x), and pnpm 9.x installed. If you're using nix, run `nix develop` to get a shell with the correct versions of everything installed.
|
||||
You'll need Node.js 22.x installed (we're working on support for 23.x), and pnpm 9.x installed. If you're using nix, run `nix develop` to get a shell with the correct versions of everything installed.
|
||||
|
||||
1. **Clone the repository**:
|
||||
```bash
|
||||
@@ -54,6 +54,12 @@ You'll need Node.js 20.x or 22.x installed (we're working on support for 23.x),
|
||||
cd homepage && pnpm install
|
||||
```
|
||||
|
||||
4. **Go back to the project root**:
|
||||
|
||||
```bash
|
||||
cd ..
|
||||
```
|
||||
|
||||
4. **Build the packages**:
|
||||
|
||||
```bash
|
||||
|
||||
30
biome.json
30
biome.json
@@ -12,7 +12,9 @@
|
||||
"**/ios/**",
|
||||
"**/android/**",
|
||||
"packages/jazz-svelte/**",
|
||||
"examples/*svelte*/**"
|
||||
"examples/*svelte*/**",
|
||||
"homepage/homepage/**",
|
||||
"**/package.json"
|
||||
]
|
||||
},
|
||||
"formatter": {
|
||||
@@ -42,15 +44,6 @@
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"include": ["**/package.json"],
|
||||
"linter": {
|
||||
"enabled": false
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"include": ["packages/**/src/**"],
|
||||
"linter": {
|
||||
@@ -61,21 +54,24 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"include": ["packages/**/src/tests/**", "packages/**/src/test/**"],
|
||||
"include": ["packages/cojson-storage*/**", "cojson-transport-ws/**"],
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"correctness": {
|
||||
"useImportExtensions": "off"
|
||||
}
|
||||
"recommended": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"include": ["packages/cojson-storage-indexeddb/**"],
|
||||
"include": ["packages/**/src/tests/**"],
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": true,
|
||||
"correctness": {
|
||||
"useImportExtensions": "off"
|
||||
},
|
||||
"style": {
|
||||
"noNonNullAssertion": "off"
|
||||
},
|
||||
"suspicious": {
|
||||
"noExplicitAny": "info"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,43 @@
|
||||
# chat-rn-clerk
|
||||
|
||||
## 1.0.79
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [75211e3]
|
||||
- jazz-tools@0.10.14
|
||||
- jazz-react-native@0.10.14
|
||||
- jazz-react-native-auth-clerk@0.10.14
|
||||
- jazz-react-native-media-images@0.10.14
|
||||
|
||||
## 1.0.78
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [07feedd]
|
||||
- jazz-tools@0.10.13
|
||||
- jazz-react-native@0.10.13
|
||||
- jazz-react-native-auth-clerk@0.10.13
|
||||
- jazz-react-native-media-images@0.10.13
|
||||
|
||||
## 1.0.77
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [4612e05]
|
||||
- jazz-tools@0.10.12
|
||||
- jazz-react-native@0.10.12
|
||||
- jazz-react-native-auth-clerk@0.10.12
|
||||
- jazz-react-native-media-images@0.10.12
|
||||
|
||||
## 1.0.76
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [5a54e4a]
|
||||
- jazz-react-native@0.10.11
|
||||
- jazz-react-native-auth-clerk@0.10.11
|
||||
|
||||
## 1.0.75
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import "../global.css";
|
||||
import { ClerkLoaded, ClerkProvider } from "@clerk/clerk-expo";
|
||||
import { secureStore } from "@clerk/clerk-expo/secure-store";
|
||||
import { useFonts } from "expo-font";
|
||||
import { Slot } from "expo-router";
|
||||
import * as SplashScreen from "expo-splash-screen";
|
||||
@@ -33,7 +34,11 @@ export default function RootLayout() {
|
||||
}
|
||||
|
||||
return (
|
||||
<ClerkProvider tokenCache={tokenCache} publishableKey={publishableKey}>
|
||||
<ClerkProvider
|
||||
tokenCache={tokenCache}
|
||||
publishableKey={publishableKey}
|
||||
__experimental_resourceCache={secureStore}
|
||||
>
|
||||
<ClerkLoaded>
|
||||
<JazzAndAuth>
|
||||
<Slot />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "chat-rn-clerk",
|
||||
"main": "index.js",
|
||||
"version": "1.0.75",
|
||||
"version": "1.0.79",
|
||||
"scripts": {
|
||||
"build": "expo export -p ios",
|
||||
"start": "expo start",
|
||||
|
||||
@@ -1,5 +1,36 @@
|
||||
# chat-rn
|
||||
|
||||
## 1.0.75
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [75211e3]
|
||||
- jazz-tools@0.10.14
|
||||
- jazz-react-native@0.10.14
|
||||
|
||||
## 1.0.74
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [07feedd]
|
||||
- jazz-tools@0.10.13
|
||||
- jazz-react-native@0.10.13
|
||||
|
||||
## 1.0.73
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [4612e05]
|
||||
- jazz-tools@0.10.12
|
||||
- jazz-react-native@0.10.12
|
||||
|
||||
## 1.0.72
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [5a54e4a]
|
||||
- jazz-react-native@0.10.11
|
||||
|
||||
## 1.0.71
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "chat-rn",
|
||||
"version": "1.0.71",
|
||||
"version": "1.0.75",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "expo export -p ios",
|
||||
@@ -35,8 +35,6 @@
|
||||
"react": "^18.3.1",
|
||||
"react-native": "~0.76.3",
|
||||
"react-native-get-random-values": "^1.11.0",
|
||||
"react-native-nitro-modules": "0.21.0",
|
||||
"react-native-quick-crypto": "1.0.0-beta.12",
|
||||
"react-native-safe-area-context": "4.12.0",
|
||||
"react-native-screens": "4.1.0",
|
||||
"react-native-url-polyfill": "^2.0.0",
|
||||
|
||||
@@ -9,7 +9,7 @@ import * as Linking from "expo-linking";
|
||||
import React, { StrictMode, useEffect, useState } from "react";
|
||||
import HandleInviteScreen from "./invite";
|
||||
|
||||
import { JazzProvider, RNQuickCrypto } from "jazz-react-native";
|
||||
import { JazzProvider } from "jazz-react-native";
|
||||
import { apiKey } from "./apiKey";
|
||||
import ChatScreen from "./chat";
|
||||
|
||||
@@ -50,7 +50,6 @@ function App() {
|
||||
sync={{
|
||||
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
|
||||
}}
|
||||
CryptoProvider={RNQuickCrypto}
|
||||
>
|
||||
<NavigationContainer linking={linking} ref={navigationRef}>
|
||||
<Stack.Navigator initialRouteName={initialRoute}>
|
||||
|
||||
@@ -1,5 +1,32 @@
|
||||
# chat-vue
|
||||
|
||||
## 0.0.61
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [75211e3]
|
||||
- jazz-tools@0.10.14
|
||||
- jazz-browser@0.10.14
|
||||
- jazz-vue@0.10.14
|
||||
|
||||
## 0.0.60
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [07feedd]
|
||||
- jazz-tools@0.10.13
|
||||
- jazz-browser@0.10.13
|
||||
- jazz-vue@0.10.13
|
||||
|
||||
## 0.0.59
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [4612e05]
|
||||
- jazz-tools@0.10.12
|
||||
- jazz-vue@0.10.12
|
||||
- jazz-browser@0.10.12
|
||||
|
||||
## 0.0.58
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "chat-vue",
|
||||
"version": "0.0.58",
|
||||
"version": "0.0.61",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,32 @@
|
||||
# jazz-example-chat
|
||||
|
||||
## 0.0.157
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [75211e3]
|
||||
- jazz-tools@0.10.14
|
||||
- jazz-react@0.10.14
|
||||
- jazz-browser-media-images@0.10.14
|
||||
|
||||
## 0.0.156
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [07feedd]
|
||||
- jazz-tools@0.10.13
|
||||
- jazz-browser-media-images@0.10.13
|
||||
- jazz-react@0.10.13
|
||||
|
||||
## 0.0.155
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [4612e05]
|
||||
- jazz-tools@0.10.12
|
||||
- jazz-react@0.10.12
|
||||
- jazz-browser-media-images@0.10.12
|
||||
|
||||
## 0.0.154
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-chat",
|
||||
"private": true,
|
||||
"version": "0.0.154",
|
||||
"version": "0.0.157",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,32 @@
|
||||
# minimal-auth-clerk
|
||||
|
||||
## 0.0.56
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [75211e3]
|
||||
- jazz-tools@0.10.14
|
||||
- jazz-react@0.10.14
|
||||
- jazz-react-auth-clerk@0.10.14
|
||||
|
||||
## 0.0.55
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [07feedd]
|
||||
- jazz-tools@0.10.13
|
||||
- jazz-react@0.10.13
|
||||
- jazz-react-auth-clerk@0.10.13
|
||||
|
||||
## 0.0.54
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [4612e05]
|
||||
- jazz-tools@0.10.12
|
||||
- jazz-react@0.10.12
|
||||
- jazz-react-auth-clerk@0.10.12
|
||||
|
||||
## 0.0.53
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "clerk",
|
||||
"private": true,
|
||||
"version": "0.0.53",
|
||||
"version": "0.0.56",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -13,7 +13,7 @@
|
||||
"dependencies": {
|
||||
"@clerk/clerk-react": "^5.4.1",
|
||||
"jazz-react": "workspace:*",
|
||||
"jazz-react-auth-clerk": "workspace:0.10.9",
|
||||
"jazz-react-auth-clerk": "workspace:0.10.14",
|
||||
"jazz-tools": "workspace:*",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
# file-share-svelte
|
||||
|
||||
## 0.0.41
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [75211e3]
|
||||
- jazz-tools@0.10.14
|
||||
- jazz-svelte@0.10.14
|
||||
|
||||
## 0.0.40
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [07feedd]
|
||||
- jazz-tools@0.10.13
|
||||
- jazz-svelte@0.10.13
|
||||
|
||||
## 0.0.39
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [4612e05]
|
||||
- jazz-svelte@0.10.12
|
||||
- jazz-tools@0.10.12
|
||||
|
||||
## 0.0.38
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "file-share-svelte",
|
||||
"version": "0.0.38",
|
||||
"version": "0.0.41",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,32 @@
|
||||
# form
|
||||
|
||||
## 0.0.52
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [75211e3]
|
||||
- jazz-tools@0.10.14
|
||||
- jazz-react@0.10.14
|
||||
- jazz-browser-media-images@0.10.14
|
||||
|
||||
## 0.0.51
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [07feedd]
|
||||
- jazz-tools@0.10.13
|
||||
- jazz-browser-media-images@0.10.13
|
||||
- jazz-react@0.10.13
|
||||
|
||||
## 0.0.50
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [4612e05]
|
||||
- jazz-tools@0.10.12
|
||||
- jazz-react@0.10.12
|
||||
- jazz-browser-media-images@0.10.12
|
||||
|
||||
## 0.0.49
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "form",
|
||||
"private": true,
|
||||
"version": "0.0.49",
|
||||
"version": "0.0.52",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,32 @@
|
||||
# image-upload
|
||||
|
||||
## 0.0.54
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [75211e3]
|
||||
- jazz-tools@0.10.14
|
||||
- jazz-react@0.10.14
|
||||
- jazz-browser-media-images@0.10.14
|
||||
|
||||
## 0.0.53
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [07feedd]
|
||||
- jazz-tools@0.10.13
|
||||
- jazz-browser-media-images@0.10.13
|
||||
- jazz-react@0.10.13
|
||||
|
||||
## 0.0.52
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [4612e05]
|
||||
- jazz-tools@0.10.12
|
||||
- jazz-react@0.10.12
|
||||
- jazz-browser-media-images@0.10.12
|
||||
|
||||
## 0.0.51
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "image-upload",
|
||||
"private": true,
|
||||
"version": "0.0.51",
|
||||
"version": "0.0.54",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -83,7 +83,10 @@ export default function CoJsonViewerApp() {
|
||||
|
||||
const addAccount = (id: RawAccountID, secret: AgentSecret) => {
|
||||
const newAccount = { id, secret };
|
||||
setAccounts([...accounts, newAccount]);
|
||||
const accountExists = accounts.some((account) => account.id === id);
|
||||
if (!accountExists) {
|
||||
setAccounts([...accounts, newAccount]);
|
||||
}
|
||||
setCurrentAccount(newAccount);
|
||||
};
|
||||
|
||||
|
||||
1
examples/multiauth/.env
Normal file
1
examples/multiauth/.env
Normal file
@@ -0,0 +1 @@
|
||||
VITE_CLERK_PUBLISHABLE_KEY=pk_test_ZXZpZGVudC1kYW5lLTg5LmNsZXJrLmFjY291bnRzLmRldiQ
|
||||
24
examples/multiauth/.gitignore
vendored
Normal file
24
examples/multiauth/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
43
examples/multiauth/README.md
Normal file
43
examples/multiauth/README.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Multi-auth example with Jazz and React
|
||||
|
||||
This example demonstrates using Jazz with multiple authentication methods; in this case, Clerk and passphrases are able to be used.
|
||||
|
||||
## Getting started
|
||||
|
||||
To run this example, you may either:
|
||||
* Clone the Jazz monorepo and run this example from within.
|
||||
* Create a new Jazz project using this example as a template, and run that new project.
|
||||
|
||||
|
||||
### Using this example as a template
|
||||
|
||||
1. Create a new Jazz project, and use this example as a template.
|
||||
```bash
|
||||
npx create-jazz-app@latest --example counter --project-name counter
|
||||
```
|
||||
2. Navigate to the new project and start the development server.
|
||||
```bash
|
||||
cd counter
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Using the monorepo
|
||||
|
||||
This requires `pnpm` to be installed; see [https://pnpm.io/installation](https://pnpm.io/installation).
|
||||
|
||||
1. Clone the `jazz` repository.
|
||||
```bash
|
||||
git clone https://github.com/garden-co/jazz.git
|
||||
```
|
||||
2. Install dependencies.
|
||||
```bash
|
||||
cd jazz
|
||||
pnpm install
|
||||
```
|
||||
3. Navigate to the example and start the development server.
|
||||
```bash
|
||||
cd examples/counter
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
The example should be running at [http://localhost:5173](http://localhost:5173) by default.
|
||||
28
examples/multiauth/eslint.config.js
Normal file
28
examples/multiauth/eslint.config.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import js from "@eslint/js";
|
||||
import reactHooks from "eslint-plugin-react-hooks";
|
||||
import reactRefresh from "eslint-plugin-react-refresh";
|
||||
import globals from "globals";
|
||||
import tseslint from "typescript-eslint";
|
||||
|
||||
export default tseslint.config(
|
||||
{ ignores: ["dist"] },
|
||||
{
|
||||
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
||||
files: ["**/*.{ts,tsx}"],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
},
|
||||
plugins: {
|
||||
"react-hooks": reactHooks,
|
||||
"react-refresh": reactRefresh,
|
||||
},
|
||||
rules: {
|
||||
...reactHooks.configs.recommended.rules,
|
||||
"react-refresh/only-export-components": [
|
||||
"warn",
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
},
|
||||
},
|
||||
);
|
||||
12
examples/multiauth/index.html
Normal file
12
examples/multiauth/index.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Jazz Multi-auth (React)</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
30
examples/multiauth/package.json
Normal file
30
examples/multiauth/package.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "multiauth",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"preview": "vite preview",
|
||||
"format-and-lint": "biome check .",
|
||||
"format-and-lint:fix": "biome check . --write"
|
||||
},
|
||||
"dependencies": {
|
||||
"@clerk/clerk-react": "^5.4.1",
|
||||
"jazz-react": "workspace:*",
|
||||
"jazz-react-auth-clerk": "workspace:*",
|
||||
"jazz-tools": "workspace:*",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "1.9.4",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@vitejs/plugin-react": "^4.3.3",
|
||||
"globals": "^15.11.0",
|
||||
"typescript": "~5.6.2",
|
||||
"vite": "^6.0.11"
|
||||
}
|
||||
}
|
||||
12
examples/multiauth/src/App.css
Normal file
12
examples/multiauth/src/App.css
Normal file
@@ -0,0 +1,12 @@
|
||||
#root {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 2em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
12
examples/multiauth/src/App.tsx
Normal file
12
examples/multiauth/src/App.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Home } from "./components/Home.tsx";
|
||||
import "./App.css";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<div className="card">
|
||||
<h1>Jazz Multi-auth (React)</h1>
|
||||
|
||||
<Home />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
1
examples/multiauth/src/apiKey.ts
Normal file
1
examples/multiauth/src/apiKey.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const apiKey = "counter-example@garden.co";
|
||||
26
examples/multiauth/src/components/Home.tsx
Normal file
26
examples/multiauth/src/components/Home.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { useAccount, useIsAuthenticated } from "jazz-react";
|
||||
|
||||
export function Home() {
|
||||
const { me, logOut } = useAccount({ root: {} });
|
||||
const isAuthenticated = useIsAuthenticated();
|
||||
|
||||
if (!me) return;
|
||||
if (!isAuthenticated) return;
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
gap: "1rem",
|
||||
}}
|
||||
>
|
||||
<div className="container">
|
||||
<h1>You're logged in</h1>
|
||||
<p>Welcome back, {me?.profile?.name}</p>
|
||||
<button onClick={() => logOut()}>Logout</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
184
examples/multiauth/src/components/OmniAuth.tsx
Normal file
184
examples/multiauth/src/components/OmniAuth.tsx
Normal file
@@ -0,0 +1,184 @@
|
||||
import { SignInButton } from "@clerk/clerk-react";
|
||||
import { usePassphraseAuth } from "jazz-react";
|
||||
import { JazzProviderProps, useIsAuthenticated } from "jazz-react";
|
||||
import { useState } from "react";
|
||||
import "../index.css";
|
||||
import { useClerk } from "@clerk/clerk-react";
|
||||
import { JazzProviderWithClerk } from "jazz-react-auth-clerk";
|
||||
import { wordlist } from "../wordlist.ts";
|
||||
|
||||
export function OmniAuthContainer(props: {
|
||||
appName: string;
|
||||
wordlist: string[];
|
||||
children?: React.ReactNode;
|
||||
}) {
|
||||
const isAuthenticated = useIsAuthenticated();
|
||||
|
||||
const passphraseAuth = usePassphraseAuth({
|
||||
wordlist: props.wordlist,
|
||||
});
|
||||
|
||||
const [step, setStep] = useState<
|
||||
"initial" | "create" | "loginWithPassphrase" | "loginWithClerk"
|
||||
>("initial");
|
||||
const [loginPassphrase, setLoginPassphrase] = useState("");
|
||||
const [isCopied, setIsCopied] = useState(false);
|
||||
const [currentPassphrase, setCurrentPassphrase] = useState(() =>
|
||||
passphraseAuth.generateRandomPassphrase(),
|
||||
);
|
||||
|
||||
if (passphraseAuth.state === "signedIn" || isAuthenticated) {
|
||||
return props.children ?? null;
|
||||
}
|
||||
|
||||
const handleCreateAccount = async () => {
|
||||
setStep("create");
|
||||
};
|
||||
|
||||
const handleLoginWithPassphrase = () => {
|
||||
setStep("loginWithPassphrase");
|
||||
};
|
||||
|
||||
const handleLoginWithClerk = () => {
|
||||
setStep("loginWithClerk");
|
||||
};
|
||||
|
||||
const handleReroll = () => {
|
||||
const newPassphrase = passphraseAuth.generateRandomPassphrase();
|
||||
setCurrentPassphrase(newPassphrase);
|
||||
setIsCopied(false);
|
||||
};
|
||||
|
||||
const handleBack = () => {
|
||||
setStep("initial");
|
||||
setLoginPassphrase("");
|
||||
};
|
||||
|
||||
const handleCopy = async () => {
|
||||
await navigator.clipboard.writeText(passphraseAuth.passphrase);
|
||||
setIsCopied(true);
|
||||
};
|
||||
|
||||
const handleLoginSubmit = async () => {
|
||||
await passphraseAuth.logIn(loginPassphrase);
|
||||
setStep("initial");
|
||||
setLoginPassphrase("");
|
||||
};
|
||||
|
||||
const handleNext = async () => {
|
||||
await passphraseAuth.registerNewAccount(currentPassphrase, "My Account");
|
||||
setStep("initial");
|
||||
setLoginPassphrase("");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="auth-container">
|
||||
<div className="auth-card">
|
||||
{step === "initial" && (
|
||||
<div>
|
||||
<h1 className="auth-heading">{props.appName}</h1>
|
||||
<button
|
||||
onClick={handleCreateAccount}
|
||||
className="auth-button-primary"
|
||||
>
|
||||
Signup with passphrase
|
||||
</button>
|
||||
<button
|
||||
onClick={handleLoginWithPassphrase}
|
||||
className="auth-button-secondary"
|
||||
>
|
||||
Login with passphrase
|
||||
</button>
|
||||
<button
|
||||
onClick={handleLoginWithClerk}
|
||||
className="auth-button-secondary"
|
||||
>
|
||||
Login with Clerk
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{step === "create" && (
|
||||
<>
|
||||
<h1 className="auth-heading">Your Passphrase</h1>
|
||||
<p className="auth-description">
|
||||
Please copy and store this passphrase somewhere safe. You'll need
|
||||
it to log in.
|
||||
</p>
|
||||
<textarea
|
||||
readOnly
|
||||
value={currentPassphrase}
|
||||
className="auth-textarea"
|
||||
rows={5}
|
||||
/>
|
||||
<button onClick={handleCopy} className="auth-button-primary">
|
||||
{isCopied ? "Copied!" : "Copy"}
|
||||
</button>
|
||||
<div className="auth-button-group">
|
||||
<button onClick={handleBack} className="auth-button-secondary">
|
||||
Back
|
||||
</button>
|
||||
<button onClick={handleReroll} className="auth-button-secondary">
|
||||
Generate New Passphrase
|
||||
</button>
|
||||
<button onClick={handleNext} className="auth-button-primary">
|
||||
Register
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{step === "loginWithPassphrase" && (
|
||||
<div>
|
||||
<h1 className="auth-heading">Log In</h1>
|
||||
<textarea
|
||||
value={loginPassphrase}
|
||||
onChange={(e) => setLoginPassphrase(e.target.value)}
|
||||
placeholder="Enter your passphrase"
|
||||
className="auth-textarea"
|
||||
rows={5}
|
||||
/>
|
||||
<div className="auth-button-group">
|
||||
<button onClick={handleBack} className="auth-button-secondary">
|
||||
Back
|
||||
</button>
|
||||
<button
|
||||
onClick={handleLoginSubmit}
|
||||
className="auth-button-primary"
|
||||
>
|
||||
Log In
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{step === "loginWithClerk" && <SignInButton />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function OmniAuth({
|
||||
children,
|
||||
AccountSchema,
|
||||
sync,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
} & JazzProviderProps) {
|
||||
const clerk = useClerk();
|
||||
|
||||
return (
|
||||
<JazzProviderWithClerk
|
||||
clerk={clerk}
|
||||
sync={sync}
|
||||
AccountSchema={AccountSchema}
|
||||
>
|
||||
<OmniAuthContainer
|
||||
appName="Jazz Multi-Authentication Example"
|
||||
wordlist={wordlist}
|
||||
>
|
||||
{children}
|
||||
</OmniAuthContainer>
|
||||
</JazzProviderWithClerk>
|
||||
);
|
||||
}
|
||||
151
examples/multiauth/src/index.css
Normal file
151
examples/multiauth/src/index.css
Normal file
@@ -0,0 +1,151 @@
|
||||
:root {
|
||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: auto 0;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.2em;
|
||||
line-height: 1.1;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.6px;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 6px;
|
||||
border: 1px solid white;
|
||||
color: inherit;
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
background-color: transparent;
|
||||
cursor: pointer;
|
||||
transition: all 0.05s ease, border-color 0.1s ease;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
button:hover {
|
||||
background-color: #313131;
|
||||
}
|
||||
|
||||
button:active {
|
||||
border-color: #3313f7;
|
||||
transform: translateY(2px);
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: rgb(21, 20, 20);
|
||||
background-color: #ffffff;
|
||||
}
|
||||
a:hover {
|
||||
color: #747bff;
|
||||
}
|
||||
button {
|
||||
border-color: #e5e4e2;
|
||||
}
|
||||
button:hover {
|
||||
border-color: rgb(47, 46, 46);
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
button:active {
|
||||
border-color: #3313f7;
|
||||
}
|
||||
}
|
||||
|
||||
.auth-container {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #f3f4f6;
|
||||
}
|
||||
|
||||
.auth-card {
|
||||
background-color: white;
|
||||
padding: 2rem;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px
|
||||
rgba(0, 0, 0, 0.06);
|
||||
width: 28rem;
|
||||
}
|
||||
|
||||
.auth-button-primary,
|
||||
.auth-button-secondary {
|
||||
width: 100%;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.25rem;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.auth-button-primary {
|
||||
background-color: black;
|
||||
color: white;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.auth-button-secondary {
|
||||
background-color: white;
|
||||
color: black;
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
.auth-heading {
|
||||
color: black;
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.auth-textarea {
|
||||
width: 100%;
|
||||
padding: 0.5rem;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 0.25rem;
|
||||
margin-bottom: 1rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.auth-description {
|
||||
font-size: 0.875rem;
|
||||
color: #4b5563;
|
||||
text-align: center;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.auth-button-group {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
}
|
||||
29
examples/multiauth/src/main.tsx
Normal file
29
examples/multiauth/src/main.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { ClerkProvider } from "@clerk/clerk-react";
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import App from "./App";
|
||||
import "./index.css";
|
||||
import { apiKey } from "./apiKey.ts";
|
||||
import { OmniAuth } from "./components/OmniAuth.tsx";
|
||||
|
||||
// Import your publishable key
|
||||
const PUBLISHABLE_KEY = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY;
|
||||
|
||||
if (!PUBLISHABLE_KEY) {
|
||||
throw new Error("Add your Clerk publishable key to the .env.local file");
|
||||
}
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<StrictMode>
|
||||
<ClerkProvider publishableKey={PUBLISHABLE_KEY} afterSignOutUrl="/">
|
||||
<OmniAuth
|
||||
sync={{
|
||||
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
|
||||
when: "signedUp", // This makes the app work in local mode when the user is not authenticated
|
||||
}}
|
||||
>
|
||||
<App />
|
||||
</OmniAuth>
|
||||
</ClerkProvider>
|
||||
</StrictMode>,
|
||||
);
|
||||
1
examples/multiauth/src/vite-env.d.ts
vendored
Normal file
1
examples/multiauth/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
2050
examples/multiauth/src/wordlist.ts
Normal file
2050
examples/multiauth/src/wordlist.ts
Normal file
File diff suppressed because it is too large
Load Diff
24
examples/multiauth/tsconfig.app.json
Normal file
24
examples/multiauth/tsconfig.app.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2023", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
"moduleResolution": "Bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
7
examples/multiauth/tsconfig.json
Normal file
7
examples/multiauth/tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
}
|
||||
22
examples/multiauth/tsconfig.node.json
Normal file
22
examples/multiauth/tsconfig.node.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
"moduleResolution": "Bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
7
examples/multiauth/vite.config.ts
Normal file
7
examples/multiauth/vite.config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import react from "@vitejs/plugin-react";
|
||||
import { defineConfig } from "vite";
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
});
|
||||
@@ -1,5 +1,32 @@
|
||||
# jazz-example-musicplayer
|
||||
|
||||
## 0.0.78
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [75211e3]
|
||||
- jazz-tools@0.10.14
|
||||
- jazz-inspector@0.10.11
|
||||
- jazz-react@0.10.14
|
||||
|
||||
## 0.0.77
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [07feedd]
|
||||
- jazz-tools@0.10.13
|
||||
- jazz-inspector@0.10.10
|
||||
- jazz-react@0.10.13
|
||||
|
||||
## 0.0.76
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [4612e05]
|
||||
- jazz-tools@0.10.12
|
||||
- jazz-inspector@0.10.9
|
||||
- jazz-react@0.10.12
|
||||
|
||||
## 0.0.75
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-music-player",
|
||||
"private": true,
|
||||
"version": "0.0.75",
|
||||
"version": "0.0.78",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -22,8 +22,8 @@
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"jazz-inspector": "workspace:*",
|
||||
"jazz-react": "workspace:0.10.9",
|
||||
"jazz-tools": "workspace:0.10.8",
|
||||
"jazz-react": "workspace:0.10.14",
|
||||
"jazz-tools": "workspace:0.10.14",
|
||||
"lucide-react": "^0.274.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
# organization
|
||||
|
||||
## 0.0.50
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [75211e3]
|
||||
- jazz-tools@0.10.14
|
||||
- jazz-react@0.10.14
|
||||
|
||||
## 0.0.49
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [07feedd]
|
||||
- jazz-tools@0.10.13
|
||||
- jazz-react@0.10.13
|
||||
|
||||
## 0.0.48
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [4612e05]
|
||||
- jazz-tools@0.10.12
|
||||
- jazz-react@0.10.12
|
||||
|
||||
## 0.0.47
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "organization",
|
||||
"private": true,
|
||||
"version": "0.0.47",
|
||||
"version": "0.0.50",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,24 @@
|
||||
# passkey-svelte
|
||||
|
||||
## 0.0.45
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-svelte@0.10.14
|
||||
|
||||
## 0.0.44
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-svelte@0.10.13
|
||||
|
||||
## 0.0.43
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [4612e05]
|
||||
- jazz-svelte@0.10.12
|
||||
|
||||
## 0.0.42
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "passkey-svelte",
|
||||
"version": "0.0.42",
|
||||
"version": "0.0.45",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
# minimal-auth-passkey
|
||||
|
||||
## 0.0.55
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [75211e3]
|
||||
- jazz-tools@0.10.14
|
||||
- jazz-react@0.10.14
|
||||
|
||||
## 0.0.54
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [07feedd]
|
||||
- jazz-tools@0.10.13
|
||||
- jazz-react@0.10.13
|
||||
|
||||
## 0.0.53
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [4612e05]
|
||||
- jazz-tools@0.10.12
|
||||
- jazz-react@0.10.12
|
||||
|
||||
## 0.0.52
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "passkey",
|
||||
"private": true,
|
||||
"version": "0.0.52",
|
||||
"version": "0.0.55",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
# passphrase
|
||||
|
||||
## 0.0.52
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [75211e3]
|
||||
- jazz-tools@0.10.14
|
||||
- jazz-react@0.10.14
|
||||
|
||||
## 0.0.51
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [07feedd]
|
||||
- jazz-tools@0.10.13
|
||||
- jazz-react@0.10.13
|
||||
|
||||
## 0.0.50
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [4612e05]
|
||||
- jazz-tools@0.10.12
|
||||
- jazz-react@0.10.12
|
||||
|
||||
## 0.0.49
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "passphrase",
|
||||
"private": true,
|
||||
"version": "0.0.49",
|
||||
"version": "0.0.52",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -66,3 +66,72 @@ main {
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.auth-container {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #f3f4f6;
|
||||
}
|
||||
|
||||
.auth-card {
|
||||
background-color: white;
|
||||
padding: 2rem;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px
|
||||
rgba(0, 0, 0, 0.06);
|
||||
width: 28rem;
|
||||
}
|
||||
|
||||
.auth-button-primary,
|
||||
.auth-button-secondary {
|
||||
width: 100%;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.25rem;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.auth-button-primary {
|
||||
background-color: black;
|
||||
color: white;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.auth-button-secondary {
|
||||
background-color: white;
|
||||
color: black;
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
.auth-heading {
|
||||
color: black;
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.auth-textarea {
|
||||
width: 100%;
|
||||
padding: 0.5rem;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 0.25rem;
|
||||
margin-bottom: 1rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.auth-description {
|
||||
font-size: 0.875rem;
|
||||
color: #4b5563;
|
||||
text-align: center;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.auth-button-group {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,142 @@
|
||||
import { JazzProvider, PassphraseAuthBasicUI } from "jazz-react";
|
||||
import { StrictMode } from "react";
|
||||
import { JazzProvider, usePassphraseAuth } from "jazz-react";
|
||||
import { StrictMode, useState } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import App from "./App.tsx";
|
||||
import "./index.css";
|
||||
import { wordlist } from "./wordlist.ts";
|
||||
|
||||
function PassphraseAuthBasicUI(props: {
|
||||
appName: string;
|
||||
wordlist: string[];
|
||||
children?: React.ReactNode;
|
||||
}) {
|
||||
const auth = usePassphraseAuth({
|
||||
wordlist: props.wordlist,
|
||||
});
|
||||
|
||||
const [step, setStep] = useState<"initial" | "create" | "login">("initial");
|
||||
const [loginPassphrase, setLoginPassphrase] = useState("");
|
||||
const [isCopied, setIsCopied] = useState(false);
|
||||
const [currentPassphrase, setCurrentPassphrase] = useState(() =>
|
||||
auth.generateRandomPassphrase(),
|
||||
);
|
||||
|
||||
if (auth.state === "signedIn") {
|
||||
return props.children ?? null;
|
||||
}
|
||||
|
||||
const handleCreateAccount = async () => {
|
||||
setStep("create");
|
||||
};
|
||||
|
||||
const handleLogin = () => {
|
||||
setStep("login");
|
||||
};
|
||||
|
||||
const handleReroll = () => {
|
||||
const newPassphrase = auth.generateRandomPassphrase();
|
||||
setCurrentPassphrase(newPassphrase);
|
||||
setIsCopied(false);
|
||||
};
|
||||
|
||||
const handleBack = () => {
|
||||
setStep("initial");
|
||||
setLoginPassphrase("");
|
||||
};
|
||||
|
||||
const handleCopy = async () => {
|
||||
await navigator.clipboard.writeText(auth.passphrase);
|
||||
setIsCopied(true);
|
||||
};
|
||||
|
||||
const handleLoginSubmit = async () => {
|
||||
await auth.logIn(loginPassphrase);
|
||||
setStep("initial");
|
||||
setLoginPassphrase("");
|
||||
};
|
||||
|
||||
const handleNext = async () => {
|
||||
await auth.registerNewAccount(currentPassphrase, "My Account");
|
||||
setStep("initial");
|
||||
setLoginPassphrase("");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="auth-container">
|
||||
<div className="auth-card">
|
||||
{step === "initial" && (
|
||||
<div>
|
||||
<h1 className="auth-heading">{props.appName}</h1>
|
||||
<button
|
||||
onClick={handleCreateAccount}
|
||||
className="auth-button-primary"
|
||||
>
|
||||
Create new account
|
||||
</button>
|
||||
<button onClick={handleLogin} className="auth-button-secondary">
|
||||
Log in
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{step === "create" && (
|
||||
<>
|
||||
<h1 className="auth-heading">Your Passphrase</h1>
|
||||
<p className="auth-description">
|
||||
Please copy and store this passphrase somewhere safe. You'll need
|
||||
it to log in.
|
||||
</p>
|
||||
<textarea
|
||||
readOnly
|
||||
value={currentPassphrase}
|
||||
className="auth-textarea"
|
||||
rows={5}
|
||||
/>
|
||||
<button onClick={handleCopy} className="auth-button-primary">
|
||||
{isCopied ? "Copied!" : "Copy"}
|
||||
</button>
|
||||
<div className="auth-button-group">
|
||||
<button onClick={handleBack} className="auth-button-secondary">
|
||||
Back
|
||||
</button>
|
||||
<button onClick={handleReroll} className="auth-button-secondary">
|
||||
Generate New Passphrase
|
||||
</button>
|
||||
<button onClick={handleNext} className="auth-button-primary">
|
||||
Register
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{step === "login" && (
|
||||
<div>
|
||||
<h1 className="auth-heading">Log In</h1>
|
||||
<textarea
|
||||
value={loginPassphrase}
|
||||
onChange={(e) => setLoginPassphrase(e.target.value)}
|
||||
placeholder="Enter your passphrase"
|
||||
className="auth-textarea"
|
||||
rows={5}
|
||||
/>
|
||||
<div className="auth-button-group">
|
||||
<button onClick={handleBack} className="auth-button-secondary">
|
||||
Back
|
||||
</button>
|
||||
<button
|
||||
onClick={handleLoginSubmit}
|
||||
className="auth-button-primary"
|
||||
>
|
||||
Log In
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function JazzAndAuth({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<JazzProvider
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
# jazz-password-manager
|
||||
|
||||
## 0.0.76
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [75211e3]
|
||||
- jazz-tools@0.10.14
|
||||
- jazz-react@0.10.14
|
||||
|
||||
## 0.0.75
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [07feedd]
|
||||
- jazz-tools@0.10.13
|
||||
- jazz-react@0.10.13
|
||||
|
||||
## 0.0.74
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [4612e05]
|
||||
- jazz-tools@0.10.12
|
||||
- jazz-react@0.10.12
|
||||
|
||||
## 0.0.73
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-password-manager",
|
||||
"private": true,
|
||||
"version": "0.0.73",
|
||||
"version": "0.0.76",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -12,8 +12,8 @@
|
||||
"clean-install": "rm -rf node_modules pnpm-lock.yaml && pnpm install"
|
||||
},
|
||||
"dependencies": {
|
||||
"jazz-react": "workspace:0.10.9",
|
||||
"jazz-tools": "workspace:0.10.8",
|
||||
"jazz-react": "workspace:0.10.14",
|
||||
"jazz-tools": "workspace:0.10.14",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-hook-form": "^7.41.5",
|
||||
|
||||
@@ -1,5 +1,32 @@
|
||||
# jazz-example-pets
|
||||
|
||||
## 0.0.174
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [75211e3]
|
||||
- jazz-tools@0.10.14
|
||||
- jazz-react@0.10.14
|
||||
- jazz-browser-media-images@0.10.14
|
||||
|
||||
## 0.0.173
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [07feedd]
|
||||
- jazz-tools@0.10.13
|
||||
- jazz-browser-media-images@0.10.13
|
||||
- jazz-react@0.10.13
|
||||
|
||||
## 0.0.172
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [4612e05]
|
||||
- jazz-tools@0.10.12
|
||||
- jazz-react@0.10.12
|
||||
- jazz-browser-media-images@0.10.12
|
||||
|
||||
## 0.0.171
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-pets",
|
||||
"private": true,
|
||||
"version": "0.0.171",
|
||||
"version": "0.0.174",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -19,9 +19,9 @@
|
||||
"@radix-ui/react-toast": "^1.1.4",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"jazz-browser-media-images": "workspace:0.10.9",
|
||||
"jazz-react": "workspace:0.10.9",
|
||||
"jazz-tools": "workspace:0.10.8",
|
||||
"jazz-browser-media-images": "workspace:0.10.14",
|
||||
"jazz-react": "workspace:0.10.14",
|
||||
"jazz-tools": "workspace:0.10.14",
|
||||
"lucide-react": "^0.274.0",
|
||||
"qrcode": "^1.5.3",
|
||||
"react": "^18.3.1",
|
||||
@@ -41,7 +41,7 @@
|
||||
"@vitejs/plugin-react-swc": "^3.3.2",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"is-ci": "^3.0.1",
|
||||
"jazz-run": "workspace:0.10.8",
|
||||
"jazz-run": "workspace:0.10.14",
|
||||
"postcss": "^8.4.27",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "~5.6.2",
|
||||
|
||||
@@ -1,5 +1,32 @@
|
||||
# reactions
|
||||
|
||||
## 0.0.54
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [75211e3]
|
||||
- jazz-tools@0.10.14
|
||||
- jazz-react@0.10.14
|
||||
- jazz-browser-media-images@0.10.14
|
||||
|
||||
## 0.0.53
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [07feedd]
|
||||
- jazz-tools@0.10.13
|
||||
- jazz-browser-media-images@0.10.13
|
||||
- jazz-react@0.10.13
|
||||
|
||||
## 0.0.52
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [4612e05]
|
||||
- jazz-tools@0.10.12
|
||||
- jazz-react@0.10.12
|
||||
- jazz-browser-media-images@0.10.12
|
||||
|
||||
## 0.0.51
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "reactions",
|
||||
"private": true,
|
||||
"version": "0.0.51",
|
||||
"version": "0.0.54",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,32 @@
|
||||
# todo-vue
|
||||
|
||||
## 0.0.59
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [75211e3]
|
||||
- jazz-tools@0.10.14
|
||||
- jazz-browser@0.10.14
|
||||
- jazz-vue@0.10.14
|
||||
|
||||
## 0.0.58
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [07feedd]
|
||||
- jazz-tools@0.10.13
|
||||
- jazz-browser@0.10.13
|
||||
- jazz-vue@0.10.13
|
||||
|
||||
## 0.0.57
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [4612e05]
|
||||
- jazz-tools@0.10.12
|
||||
- jazz-vue@0.10.12
|
||||
- jazz-browser@0.10.12
|
||||
|
||||
## 0.0.56
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "todo-vue",
|
||||
"version": "0.0.56",
|
||||
"version": "0.0.59",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
# jazz-example-todo
|
||||
|
||||
## 0.0.173
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [75211e3]
|
||||
- jazz-tools@0.10.14
|
||||
- jazz-react@0.10.14
|
||||
|
||||
## 0.0.172
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [07feedd]
|
||||
- jazz-tools@0.10.13
|
||||
- jazz-react@0.10.13
|
||||
|
||||
## 0.0.171
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [4612e05]
|
||||
- jazz-tools@0.10.12
|
||||
- jazz-react@0.10.12
|
||||
|
||||
## 0.0.170
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-todo",
|
||||
"private": true,
|
||||
"version": "0.0.170",
|
||||
"version": "0.0.173",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -16,8 +16,8 @@
|
||||
"@radix-ui/react-toast": "^1.1.4",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"jazz-react": "workspace:0.10.9",
|
||||
"jazz-tools": "workspace:0.10.8",
|
||||
"jazz-react": "workspace:0.10.14",
|
||||
"jazz-tools": "workspace:0.10.14",
|
||||
"lucide-react": "^0.274.0",
|
||||
"qrcode": "^1.5.3",
|
||||
"react": "^18.3.1",
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
# version-history
|
||||
|
||||
## 0.0.51
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [75211e3]
|
||||
- jazz-tools@0.10.14
|
||||
- jazz-react@0.10.14
|
||||
|
||||
## 0.0.50
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [07feedd]
|
||||
- jazz-tools@0.10.13
|
||||
- jazz-react@0.10.13
|
||||
|
||||
## 0.0.49
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [4612e05]
|
||||
- jazz-tools@0.10.12
|
||||
- jazz-react@0.10.12
|
||||
|
||||
## 0.0.48
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "version-history",
|
||||
"private": true,
|
||||
"version": "0.0.48",
|
||||
"version": "0.0.51",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
import { packages } from "@/lib/packages";
|
||||
import { clsx } from "clsx";
|
||||
import { Icon } from "gcmp-design-system/src/app/components/atoms/Icon";
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "API reference",
|
||||
openGraph: {
|
||||
title: "API reference",
|
||||
},
|
||||
};
|
||||
|
||||
const CardHeading = ({
|
||||
children,
|
||||
className,
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import { CodeGroup, ContentByFramework, JazzLogo } from '@/components/forMdx'
|
||||
|
||||
export const metadata = {
|
||||
title: "Learn some Jazz",
|
||||
openGraph: {
|
||||
title: "Learn some Jazz",
|
||||
},
|
||||
};
|
||||
|
||||
# Learn some <span className="sr-only">Jazz</span> <JazzLogo className="h-[41px] -ml-0.5 -mt-[3px] inline" />
|
||||
|
||||
Welcome to the Jazz documentation!
|
||||
|
||||
@@ -211,7 +211,7 @@ The clerk provider is not built into `jazz-react` and needs the `jazz-react-auth
|
||||
</ContentByFramework>
|
||||
|
||||
<ContentByFramework framework="react-native">
|
||||
The clerk provider is not built into `jazz-react-native` and needs the `jazz-react-native-auth-clerk` package to be installed.
|
||||
The clerk provider is not built into `jazz-react-native` and needs the `jazz-react-native-auth-clerk` package to be installed. Note the `__experimental_resourceCache` option. This helps render Clerk components when offline.
|
||||
</ContentByFramework>
|
||||
|
||||
After installing the package you can use the `JazzProviderWithClerk` component to wrap your app:
|
||||
@@ -249,6 +249,7 @@ createRoot(document.getElementById("root")!).render(
|
||||
<CodeGroup>
|
||||
```tsx
|
||||
import { JazzProviderWithClerk } from "jazz-react-native-auth-clerk";
|
||||
import { secureStore } from "@clerk/clerk-expo/secure-store";
|
||||
|
||||
function JazzAndAuth({ children }: { children: React.ReactNode }) {
|
||||
const clerk = useClerk();
|
||||
@@ -275,7 +276,11 @@ export default function RootLayout() {
|
||||
}
|
||||
|
||||
return (
|
||||
<ClerkProvider tokenCache={tokenCache} publishableKey={publishableKey}>
|
||||
<ClerkProvider
|
||||
tokenCache={tokenCache}
|
||||
publishableKey={publishableKey}
|
||||
__experimental_resourceCache={secureStore}
|
||||
>
|
||||
<ClerkLoaded>
|
||||
<JazzAndAuth>
|
||||
<Slot />
|
||||
@@ -380,7 +385,7 @@ export async function onAnonymousAccountDiscarded(
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
To see how this works in reality we suggest you to try
|
||||
To see how this works in reality we suggest you to try
|
||||
to upload a song in the [music player demo](https://music-demo.jazz.tools/) and then
|
||||
try to log in with an existing account.
|
||||
|
||||
|
||||
@@ -442,11 +442,11 @@ All we have to do is create a new group to own each new issue and add "everyone"
|
||||
import { useState } from "react"; // old
|
||||
import { Issue } from "./schema"; // old
|
||||
import { IssueComponent } from "./components/Issue.tsx"; // old
|
||||
import { useCoState } from "jazz-react"; // old
|
||||
import { useAccount, useCoState } from "jazz-react";
|
||||
import { ID, Group } from "jazz-tools"
|
||||
// old
|
||||
function App() { // old
|
||||
const { me } = useAccount(); // old
|
||||
const { me } = useAccount();
|
||||
const [issueID, setIssueID] = useState<ID<Issue> | undefined>(// old
|
||||
(window.location.search?.replace("?issue=", "") || undefined) as ID<Issue> | undefined,// old
|
||||
); // old
|
||||
@@ -665,7 +665,7 @@ export function ProjectComponent({ projectID }: { projectID: ID<Project> }) {//
|
||||
|
||||
The loading-depth spec `{ issues: [{}] }` means "in `Project`, load `issues` and load each item in `issues` shallowly". (Since an `Issue` doesn't have any further references, "shallowly" actually means all its properties will be available).
|
||||
|
||||
- Now, we can get rid of a lot of coniditional accesses because we know that once `project` is loaded, `project.issues` and each `Issue` in it will be loaded as well.
|
||||
- Now, we can get rid of a lot of conditional accesses because we know that once `project` is loaded, `project.issues` and each `Issue` in it will be loaded as well.
|
||||
- This also results in only one rerender and visual update when everything is loaded, which is faster (especially for long lists) and gives you more control over the loading UX.
|
||||
|
||||
{/* TODO: explain about not loaded vs not set/defined and `_refs` basics */}
|
||||
|
||||
@@ -6,6 +6,38 @@ import { Framework, frameworks } from "@/lib/framework";
|
||||
import type { Toc } from "@stefanprobst/rehype-extract-toc";
|
||||
import { Prose } from "gcmp-design-system/src/app/components/molecules/Prose";
|
||||
|
||||
async function getMdxSource(slugPath: string, framework: string) {
|
||||
try {
|
||||
return await import(`./${slugPath}.mdx`);
|
||||
} catch (error) {
|
||||
return await import(`./${slugPath}/${framework}.mdx`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function generateMetadata({
|
||||
params: { slug, framework },
|
||||
}: { params: { slug: string[]; framework: string } }) {
|
||||
const slugPath = slug.join("/");
|
||||
try {
|
||||
const mdxSource = await getMdxSource(slugPath, framework);
|
||||
const title = mdxSource.tableOfContents?.[0].value || "Documentation";
|
||||
|
||||
return {
|
||||
title,
|
||||
openGraph: {
|
||||
title,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
title: "Documentation",
|
||||
openGraph: {
|
||||
title: "Documentation",
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default async function Page({
|
||||
params: { slug, framework },
|
||||
}: { params: { slug: string[]; framework: string } }) {
|
||||
@@ -13,13 +45,7 @@ export default async function Page({
|
||||
const bodyClassName = "overflow-x-hidden lg:flex-1 py-10 max-w-3xl mx-auto";
|
||||
|
||||
try {
|
||||
let mdxSource;
|
||||
try {
|
||||
mdxSource = await import(`./${slugPath}.mdx`);
|
||||
} catch (error) {
|
||||
mdxSource = await import(`./${slugPath}/${framework}.mdx`);
|
||||
}
|
||||
|
||||
const mdxSource = await getMdxSource(slugPath, framework);
|
||||
const { default: Content, tableOfContents } = mdxSource;
|
||||
|
||||
// Exclude h1 from table of contents
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
export const metadata = { title: "CoFeeds" };
|
||||
|
||||
import { CodeGroup, ComingSoon, ContentByFramework } from "@/components/forMdx";
|
||||
|
||||
# CoFeeds
|
||||
|
||||
CoFeeds are append-only data structures that track entries from different user sessions and accounts. Unlike other CoValues where everyone edits the same data, CoFeeds maintain separate streams for each session.
|
||||
|
||||
Each account can have multiple sessions (different browser tabs, devices, or app instances), making CoFeeds ideal for building features like activity logs, presence indicators, and notification systems.
|
||||
|
||||
The [Reactions example](https://github.com/garden-co/jazz/tree/main/examples/reactions) demonstrates a practical use of CoFeeds.
|
||||
|
||||
## Creating CoFeeds
|
||||
|
||||
CoFeeds are defined by specifying the type of items they'll contain, similar to how you define CoLists:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
// Define a schema for feed items
|
||||
class Activity extends CoMap {
|
||||
timestamp = co.Date;
|
||||
action = co.literal("watering", "planting", "harvesting", "maintenance");
|
||||
notes = co.optional.string;
|
||||
}
|
||||
|
||||
// Define a feed of garden activities
|
||||
class ActivityFeed extends CoFeed.Of(co.ref(Activity)) {}
|
||||
|
||||
// Create a feed instance
|
||||
const activityFeed = ActivityFeed.create([]);
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
Like other CoValues, you can specify [ownership](/docs/using-covalues/ownership) when creating CoFeeds.
|
||||
|
||||
## Reading from CoFeeds
|
||||
|
||||
Since CoFeeds are made of entries from users over multiple sessions, you can access entries in different ways - from a specific user's session or from their account as a whole.
|
||||
|
||||
### Per-Session Access
|
||||
|
||||
To retrieve entries from a session:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
// Get the feed for a specific session
|
||||
const sessionFeed = activityFeed.perSession[sessionId];
|
||||
|
||||
// Latest entry from a session
|
||||
console.log(sessionFeed.value.action); // "watering"
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
For convenience, you can also access the latest entry from the current session with `inCurrentSession`:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
// Get the feed for the current session
|
||||
const currentSessionFeed = activityFeed.inCurrentSession;
|
||||
|
||||
// Latest entry from the current session
|
||||
console.log(currentSessionFeed.value.action); // "harvesting"
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
### Per-Account Access
|
||||
|
||||
To retrieve entries from a specific account you can use bracket notation with the account ID:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
// Get the feed for a specific account
|
||||
const accountFeed = activityFeed[accountId];
|
||||
|
||||
// Latest entry from the account
|
||||
console.log(accountFeed.value.action); // "watering"
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
For convenience, you can also access the latest entry from the current account with `byMe`:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
// Get the feed for the current account
|
||||
const myLatestEntry = activityFeed.byMe;
|
||||
|
||||
// Latest entry from the current account
|
||||
console.log(myLatestEntry.value.action); // "harvesting"
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
### Feed Entries
|
||||
|
||||
#### All Entries
|
||||
|
||||
To retrieve all entries from a CoFeed:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
// Get the feeds for a specific account and session
|
||||
const accountFeed = activityFeed[accountId];
|
||||
const sessionFeed = activityFeed.perSession[sessionId];
|
||||
|
||||
// Iterate over all entries from the account
|
||||
for (const entry of accountFeed.all) {
|
||||
console.log(entry.value);
|
||||
}
|
||||
|
||||
// Iterate over all entries from the session
|
||||
for (const entry of sessionFeed.all) {
|
||||
console.log(entry.value);
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
#### Latest Entry
|
||||
|
||||
To retrieve the latest entry from a CoFeed, ie. the last update:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
// Get the latest entry from the current account
|
||||
const latestEntry = activityFeed.byMe;
|
||||
|
||||
console.log(`My last action was ${latestEntry.value.action}`);
|
||||
// "My last action was harvesting"
|
||||
|
||||
// Get the latest entry from each account
|
||||
const latestEntriesByAccount = Object.values(activityFeed).map(entry => ({
|
||||
accountName: entry.by?.profile?.name,
|
||||
value: entry.value,
|
||||
}));
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Writing to CoFeeds
|
||||
|
||||
CoFeeds are append-only; you can add new items, but not modify existing ones. This creates a chronological record of events or activities.
|
||||
|
||||
### Adding Items
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
// Log a new activity
|
||||
activityFeed.push(Activity.create({
|
||||
timestamp: new Date(),
|
||||
action: "watering",
|
||||
notes: "Extra water for new seedlings"
|
||||
}));
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
Each item is automatically associated with the current user's session. You don't need to specify which session the item belongs to - Jazz handles this automatically.
|
||||
|
||||
### Understanding Session Context
|
||||
|
||||
Each entry is automatically added to the current session's feed. When a user has multiple open sessions (like both a mobile app and web browser), each session creates its own separate entries:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
// On mobile device:
|
||||
fromMobileFeed.push(Activity.create({
|
||||
timestamp: new Date(),
|
||||
action: "harvesting",
|
||||
location: "Vegetable patch"
|
||||
}));
|
||||
|
||||
// On web browser (same user):
|
||||
fromBrowserFeed.push(Activity.create({
|
||||
timestamp: new Date(),
|
||||
action: "planting",
|
||||
location: "Flower bed"
|
||||
}));
|
||||
|
||||
// These are separate entries in the same feed, from the same account
|
||||
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Metadata
|
||||
|
||||
CoFeeds support metadata, which is useful for tracking information about the feed itself.
|
||||
|
||||
### By
|
||||
|
||||
The `by` property is the account that made the entry.
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
const accountFeed = activityFeed[accountId];
|
||||
|
||||
// Get the account that made the last entry
|
||||
console.log(accountFeed?.by);
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
### MadeAt
|
||||
|
||||
The `madeAt` property is a timestamp of when the entry was added to the feed.
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
const accountFeed = activityFeed[accountId];
|
||||
|
||||
// Get the timestamp of the last update
|
||||
console.log(accountFeed?.madeAt);
|
||||
|
||||
// Get the timestamp of each entry
|
||||
for (const entry of accountFeed.all) {
|
||||
console.log(entry.madeAt);
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Best Practices
|
||||
|
||||
### When to Use CoFeeds
|
||||
|
||||
- **Use CoFeeds when**:
|
||||
- You need to track per-user/per-session data
|
||||
- Time-based information matters (activity logs, presence)
|
||||
|
||||
- **Consider alternatives when**:
|
||||
- Data needs to be collaboratively edited (use CoMaps or CoLists)
|
||||
- You need structured relationships (use CoMaps/CoLists with references)
|
||||
@@ -0,0 +1,192 @@
|
||||
import { CodeGroup, ContentByFramework } from "@/components/forMdx";
|
||||
|
||||
export const metadata = { title: "CoLists" };
|
||||
|
||||
# CoLists
|
||||
|
||||
CoLists are ordered collections that work like JavaScript arrays. They provide indexed access, iteration methods, and length properties, making them perfect for managing sequences of items.
|
||||
|
||||
## Creating CoLists
|
||||
|
||||
CoLists are defined by specifying the type of items they contain:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
class ListOfResources extends CoList.Of(co.string) {}
|
||||
|
||||
class ListOfTasks extends CoList.Of(co.ref(Task)) {}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
To instantiate a CoList:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
// Create an empty list
|
||||
const resources = ListOfResources.create([]);
|
||||
|
||||
// Create a list with initial items
|
||||
const tasks = ListOfTasks.create([
|
||||
Task.create({ title: "Prepare soil beds", status: "in-progress" }),
|
||||
Task.create({ title: "Order compost", status: "todo" })
|
||||
]);
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
Like other CoValues, you can specify [ownership](/docs/using-covalues/ownership) when creating CoLists.
|
||||
|
||||
## Reading from CoLists
|
||||
|
||||
CoLists support standard array access patterns:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
// Access by index
|
||||
const firstTask = tasks[0];
|
||||
console.log(firstTask.title); // "Prepare soil beds"
|
||||
|
||||
// Get list length
|
||||
console.log(tasks.length); // 2
|
||||
|
||||
// Iteration
|
||||
tasks.forEach(task => {
|
||||
console.log(task.title);
|
||||
// "Prepare soil beds"
|
||||
// "Order compost"
|
||||
});
|
||||
|
||||
// Array methods
|
||||
const todoTasks = tasks.filter(task => task.status === "todo");
|
||||
console.log(todoTasks.length); // 1
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Writing to CoLists
|
||||
|
||||
CoLists support all the standard JavaScript array mutation methods:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
// Add items
|
||||
resources.push("Courgette"); // Add to end
|
||||
resources.unshift("Lettuce"); // Add to beginning
|
||||
|
||||
console.log(resources);
|
||||
// ["Lettuce", "Tomatoes", "Basil", "Peppers", "Courgette"]
|
||||
|
||||
// Add complex items to lists of references
|
||||
tasks.push(Task.create({
|
||||
title: "Install irrigation system",
|
||||
status: "todo"
|
||||
}));
|
||||
|
||||
// Remove items
|
||||
resources.pop(); // Remove last item
|
||||
resources.shift(); // Remove first item
|
||||
resources.splice(1, 2); // Remove items at index 1 and 2
|
||||
|
||||
// Replace items
|
||||
resources[0] = "Cucumber"; // Replace first item
|
||||
|
||||
console.log(resources); // ["Cucumber"]
|
||||
|
||||
// Modify items in a list of references
|
||||
tasks[0].status = "in-progress"; // Modify a property of an item
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
### Type Safety
|
||||
|
||||
CoLists maintain type safety for their items:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
// TypeScript knows each item's type
|
||||
resources.push("Carrots"); // ✓ Valid string
|
||||
resources.push(42); // ✗ Type error: expected string
|
||||
|
||||
// For lists of references, TypeScript knows the referenced type
|
||||
tasks.forEach(task => {
|
||||
console.log(task.title); // TypeScript knows 'task' has a 'title'
|
||||
});
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
### Array Methods
|
||||
|
||||
CoLists support many standard JavaScript array methods:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
resources.push("Tomatoes", "Basil", "Peppers");
|
||||
|
||||
// Filter
|
||||
const springResources = resources.filter(resource => resource.includes("Tomato"));
|
||||
console.log(springResources); // ["Tomatoes"]
|
||||
|
||||
// Map (creates a regular array, not a CoList)
|
||||
const resourceNames = resources.map(resource => resource.toUpperCase());
|
||||
console.log(resourceNames); // ["TOMATOES", "BASIL", "PEPPERS"]
|
||||
|
||||
// Find
|
||||
const basil = resources.find(resource => resource === "Basil");
|
||||
|
||||
// Sort (modifies the CoList in-place)
|
||||
resources.sort();
|
||||
console.log(resources); // ["Basil", "Cucumber", "Peppers", "Tomatoes"]
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Common Patterns
|
||||
|
||||
#### List Rendering
|
||||
|
||||
CoLists work well with UI rendering libraries:
|
||||
|
||||
<CodeGroup>
|
||||
```tsx
|
||||
// React example
|
||||
function TaskList({ tasks }) {
|
||||
return (
|
||||
<ul>
|
||||
{tasks.map(task => (
|
||||
<li key={task.id}>
|
||||
{task.title} - {task.status}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
#### Managing Relations
|
||||
|
||||
CoLists can be used to create one-to-many relationships:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
class Project extends CoMap {
|
||||
name = co.string;
|
||||
tasks = co.ref(ListOfTasks);
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
const task = Task.create({
|
||||
title: "Plant seedlings",
|
||||
status: "todo",
|
||||
project: project, // Add a reference to the project
|
||||
});
|
||||
|
||||
// Add a task to a garden project
|
||||
project.tasks.push(task);
|
||||
|
||||
// Access the project from the task
|
||||
console.log(task.project); // { name: "Garden Project", tasks: [task] }
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
import { CodeGroup, ContentByFramework } from "@/components/forMdx";
|
||||
|
||||
export const metadata = { title: "CoMaps" };
|
||||
|
||||
# CoMaps
|
||||
|
||||
CoMaps are key-value objects that work like JavaScript objects. You can access properties with dot notation and define typed fields that provide TypeScript safety. They're ideal for structured data that needs type validation.
|
||||
|
||||
## Creating CoMaps
|
||||
|
||||
CoMaps are typically defined by extending the `CoMap` class and specifying primitive fields using the `co` declarer (see [Defining schemas: CoValues](/docs/schemas/covalues) for more details on primitive fields):
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
class Project extends CoMap {
|
||||
name = co.string;
|
||||
startDate = co.Date;
|
||||
status = co.literal("planning", "active", "completed");
|
||||
coordinator = co.optional.ref(Member);
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
You can create either struct-like CoMaps with fixed fields (as above) or record-like CoMaps for key-value pairs:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
class Inventory extends CoMap.Record(co.number) {}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
To instantiate a CoMap:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
const project = Project.create({
|
||||
name: "Spring Planting",
|
||||
startDate: new Date("2025-03-15"),
|
||||
status: "planning",
|
||||
});
|
||||
|
||||
const inventory = Inventory.create({
|
||||
tomatoes: 48,
|
||||
basil: 12,
|
||||
});
|
||||
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
### Ownership
|
||||
|
||||
When creating CoMaps, you can specify ownership to control access:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
// Create with default owner (current user)
|
||||
const privateProject = Project.create({
|
||||
name: "My Herb Garden",
|
||||
startDate: new Date("2025-04-01"),
|
||||
status: "planning",
|
||||
});
|
||||
|
||||
// Create with shared ownership
|
||||
const gardenGroup = Group.create();
|
||||
gardenGroup.addMember(memberAccount, "writer");
|
||||
|
||||
const communityProject = Project.create(
|
||||
{
|
||||
name: "Community Vegetable Plot",
|
||||
startDate: new Date("2025-03-20"),
|
||||
status: "planning",
|
||||
},
|
||||
{ owner: gardenGroup },
|
||||
);
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Reading from CoMaps
|
||||
|
||||
CoMaps can be accessed using familiar JavaScript object notation:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
console.log(project.name); // "Spring Planting"
|
||||
console.log(project.status); // "planning"
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
### Handling Optional Fields
|
||||
|
||||
Optional fields require checks before access:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
if (project.coordinator) {
|
||||
console.log(project.coordinator.name); // Safe access
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
### Working with Record CoMaps
|
||||
|
||||
For record-type CoMaps, you can access values using bracket notation:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
const inventory = Inventory.create({
|
||||
tomatoes: 48,
|
||||
peppers: 24,
|
||||
basil: 12
|
||||
});
|
||||
|
||||
console.log(inventory["tomatoes"]); // 48
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Updating CoMaps
|
||||
|
||||
Updating CoMap properties uses standard JavaScript assignment:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
project.name = "Spring Vegetable Garden"; // Update name
|
||||
project.startDate = new Date("2025-03-20"); // Update date
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
### Type Safety
|
||||
|
||||
CoMaps are fully typed in TypeScript, giving you autocomplete and error checking:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
project.name = "Spring Vegetable Planting"; // ✓ Valid string
|
||||
project.startDate = "2025-03-15"; // ✗ Type error: expected Date
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
### Deleting Properties
|
||||
|
||||
You can delete properties from CoMaps:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
delete inventory["basil"]; // Remove a key-value pair
|
||||
|
||||
// For optional fields in struct-like CoMaps
|
||||
project.coordinator = null; // Remove the reference
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Structuring Data
|
||||
|
||||
- Use struct-like CoMaps for entities with fixed, known properties
|
||||
- Use record-like CoMaps for dynamic key-value collections
|
||||
- Group related properties into nested CoMaps for better organization
|
||||
|
||||
### Common Patterns
|
||||
|
||||
#### Using Computed Properties
|
||||
|
||||
CoMaps support computed properties and methods:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
class ComputedProject extends CoMap {
|
||||
name = co.string;
|
||||
startDate = co.Date;
|
||||
endDate = co.optional.Date;
|
||||
|
||||
get isActive() {
|
||||
const now = new Date();
|
||||
return now >= this.startDate && (!this.endDate || now <= this.endDate);
|
||||
}
|
||||
|
||||
formatDuration(format: "short" | "full") {
|
||||
const start = this.startDate.toLocaleDateString();
|
||||
if (!this.endDate) {
|
||||
return format === "full"
|
||||
? `Started on ${start}, ongoing`
|
||||
: `From ${start}`;
|
||||
}
|
||||
|
||||
const end = this.endDate.toLocaleDateString();
|
||||
return format === "full"
|
||||
? `From ${start} to ${end}`
|
||||
: `${(this.endDate.getTime() - this.startDate.getTime()) / 86400000} days`;
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
console.log(computedProject.isActive); // false
|
||||
console.log(computedProject.formatDuration("short")); // "3 days"
|
||||
```
|
||||
</CodeGroup>
|
||||
@@ -0,0 +1,107 @@
|
||||
import { CodeGroup, ComingSoon, ContentByFramework } from "@/components/forMdx";
|
||||
|
||||
export const metadata = { title: "Creation & ownership" };
|
||||
|
||||
# Creation & Ownership
|
||||
|
||||
CoValues are collaborative by nature - anything you create can be shared and synced with others. Who gets to read or change each CoValue is controlled by its owner - either an individual `Account` or a shared `Group`. This foundation of ownership is what enables Jazz applications to support real-time collaboration while maintaining proper access control. Understanding how to create and manage ownership of CoValues is essential for building effectively with Jazz.
|
||||
|
||||
## Creating CoValues
|
||||
|
||||
Every CoValue starts with a [schema](/docs/schemas/covalues#start-your-app-with-a-schema). From there you can create CoValues with the `create` method. Creating CoValues is straightforward - you define the structure in your schema and then instantiate instances with initial values. These newly created CoValues automatically sync across devices and users who have access to them.
|
||||
|
||||
Here's a simple example:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
class Task extends CoMap {
|
||||
title = co.string;
|
||||
description = co.string;
|
||||
status = co.literal("todo", "in-progress", "completed");
|
||||
}
|
||||
|
||||
// Create a new task
|
||||
const task = Task.create({
|
||||
title: "Plant spring vegetables",
|
||||
description: "Plant peas, carrots, and lettuce",
|
||||
status: "todo",
|
||||
});
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
When you create a CoValue, you provide its initial data and optionally specify who owns it. The data must match the schema you defined - TypeScript will help ensure you get this right.
|
||||
|
||||
For more examples of creating different types of CoValues:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
// Creating a CoFeed for activity notifications
|
||||
class ActivityNotification extends CoMap {
|
||||
message = co.string;
|
||||
type = co.literal("info", "warning", "success");
|
||||
timestamp = co.Date;
|
||||
}
|
||||
|
||||
class ActivityFeed extends CoFeed.Of(co.ref(ActivityNotification)) {}
|
||||
|
||||
const feed = ActivityFeed.create();
|
||||
|
||||
// Adding an item to the feed
|
||||
feed.addItem(ActivityNotification.create({
|
||||
message: "New task created",
|
||||
type: "info",
|
||||
timestamp: new Date()
|
||||
}));
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Ownership & Access Control
|
||||
|
||||
Every CoValue needs an owner to control who can access it. An owner can be an individual `Account`, but it's usually a `Group` since that lets you share with multiple people. The ownership model in Jazz provides fine-grained control over who can read, write, or administer your collaborative data. This system makes it easy to implement common patterns like shared workspaces, personal data, or public resources.
|
||||
|
||||
### Groups & Roles
|
||||
|
||||
Groups have members with different roles that control what they can do. These roles provide a permission system that's both simple to understand and powerful enough for complex collaboration scenarios. By assigning appropriate roles, you can control exactly who can view, edit, or manage access to your data.
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
// Create a group
|
||||
const gardenTeam = Group.create();
|
||||
|
||||
// Add garden members with different roles
|
||||
gardenTeam.addMember(coordinator, "admin"); // Garden coordinator manages everything
|
||||
gardenTeam.addMember(gardener, "writer"); // Gardeners can update tasks
|
||||
gardenTeam.addMember(visitor, "reader"); // Visitors can view progress
|
||||
|
||||
// Create a list of tasks with the same owner
|
||||
const taskList = ListOfTasks.create([]);
|
||||
|
||||
// Create a garden project with nested tasks, all with the same ownership
|
||||
const springProject = Project.create({
|
||||
name: "Spring Planting",
|
||||
description: "Preparing the community garden for spring vegetables",
|
||||
tasks: taskList
|
||||
});
|
||||
|
||||
// Add tasks to the list
|
||||
taskList.push(Task.create({
|
||||
title: "Start tomato seedlings",
|
||||
description: "Plant Roma and Cherry varieties in seed trays",
|
||||
status: "todo"
|
||||
});
|
||||
|
||||
taskList.push(Task.create({
|
||||
title: "Prepare herb garden",
|
||||
description: "Clear old growth and add fresh compost",
|
||||
status: "todo"
|
||||
});
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
Each role has specific permissions:
|
||||
- `admin`: Full control including managing members
|
||||
- `writer`: Can modify content
|
||||
- `reader`: Can only read content
|
||||
- `writerOnly`: Can only write to the CoValue, not read it
|
||||
|
||||
For more information on groups and roles, see the [Groups](/docs/groups/intro) documentation.
|
||||
@@ -0,0 +1,269 @@
|
||||
import { CodeGroup } from "@/components/forMdx";
|
||||
|
||||
export const metadata = { title: "Metadata & time-travel" };
|
||||
|
||||
# Metadata & time-travel
|
||||
|
||||
One of Jazz's most powerful features is that every CoValue automatically tracks its complete edit history. This means you can see who changed what and when, examine the state of your data at any point in time, and build features like audit logs, activity feeds, and undo/redo functionality. This page explores how to access and work with the rich metadata that comes with every CoValue.
|
||||
|
||||
## Understanding Edit History
|
||||
|
||||
Every CoValue in Jazz maintains a full history of all changes made to it. This edit history is accessible through two main APIs:
|
||||
|
||||
`CoValue._edits` provides a structured, field-by-field view of a CoValue's edit history. It organizes edits by property name and makes them easily accessible. For each field:
|
||||
- `_edits.fieldName` gives you the most recent edit
|
||||
- `_edits.fieldName.all` provides all historical edits as an array
|
||||
- `_edits.fieldName.madeAt` gives you the timestamp of the last edit
|
||||
- Each edit contains the value, who made the change, and when it happened
|
||||
|
||||
`CoValue._raw` gives you access to the internal state and lower-level operations on a CoValue. As this is an internal API, it should be used with caution. If you find yourself using `_raw`, consider letting us know so we can consider adding a public API for your use case.
|
||||
|
||||
## Working with Edit History Metadata
|
||||
|
||||
CoValues track who made each change and when. Every edit has metadata attached to it, including the author, timestamp, value, and transaction ID. This metadata enables you to build powerful audit and history features without having to implement your own tracking system.
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
class Task extends CoMap {
|
||||
title = co.string;
|
||||
description = co.string;
|
||||
status = co.literal("todo", "in-progress", "completed");
|
||||
priority = co.literal("low", "medium", "high");
|
||||
subtasks = co.optional.ref(ListOfTasks);
|
||||
}
|
||||
|
||||
class ListOfTasks extends CoList.Of(co.ref(Task)) {}
|
||||
|
||||
const task = Task.create({
|
||||
title: "Plant spring vegetables",
|
||||
description: "Plant peas, carrots, and lettuce in the south garden bed",
|
||||
status: "todo",
|
||||
priority: "medium",
|
||||
});
|
||||
|
||||
// Change the status
|
||||
task.status = "in-progress";
|
||||
|
||||
// Get the latest edit for a field
|
||||
console.log("Latest edit:", task._edits.status);
|
||||
// { value: "in-progress", by: Account, madeAt: Date, ... }
|
||||
|
||||
// Get when a field was last edited (timestamp)
|
||||
const lastEditTime = task._edits.status.madeAt;
|
||||
console.log(`Status was last changed at: ${lastEditTime?.toLocaleString()}`);
|
||||
|
||||
// Get the full edit history for a field
|
||||
for (const edit of task._edits.status.all) {
|
||||
console.log({
|
||||
author: edit.by, // Account that made the change
|
||||
timestamp: edit.madeAt, // When the change happened
|
||||
value: edit.value, // Value of the change
|
||||
});
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
### Common Patterns
|
||||
|
||||
With knowledge of the edit history, you can build all sorts of useful features that enhance your application's user experience and administrative capabilities. Here are some common patterns that leverage CoValue metadata.
|
||||
|
||||
#### Audit Log
|
||||
|
||||
Getting all the changes to a CoValue in order allows you to build an audit log. This is especially useful for tracking important changes in collaborative environments or for compliance purposes:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
|
||||
function getAuditLog(task: Task) {
|
||||
const changes = [];
|
||||
|
||||
for (const field of Object.keys(task)) {
|
||||
// Check if the field has edits to avoid accessing non-existent properties
|
||||
if (task._edits[field as keyof typeof task._edits]) {
|
||||
for (const edit of task._edits[field as keyof typeof task._edits].all) {
|
||||
changes.push({
|
||||
field,
|
||||
...edit,
|
||||
timestamp: edit.madeAt,
|
||||
at: edit.madeAt,
|
||||
by: edit.by,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by timestamp
|
||||
return changes.sort((a, b) => b.at.getTime() - a.at.getTime());
|
||||
}
|
||||
|
||||
// Example usage
|
||||
const auditLog = getAuditLog(task);
|
||||
auditLog.forEach((entry) => {
|
||||
console.log(
|
||||
`${entry.timestamp} - ${entry.field} changed to "${entry.value}" by ${entry.by?.id}`,
|
||||
);
|
||||
});
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
#### Activity Feeds
|
||||
|
||||
Activity feeds are a great way to see recent changes to a CoValue, helping users understand what's happening in a collaborative workspace. They can show who did what and when, creating transparency in team environments:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
function getRecentActivity(project: Project) {
|
||||
const activity = [];
|
||||
const hourAgo = new Date(Date.now() - 3600000);
|
||||
|
||||
for (const field of Object.keys(project)) {
|
||||
// Skip if the field doesn't have edits
|
||||
if (!project._edits[field as keyof typeof project._edits]) continue;
|
||||
|
||||
for (const edit of project._edits[field as keyof typeof project._edits].all) {
|
||||
if (edit.madeAt > hourAgo) {
|
||||
activity.push({
|
||||
field,
|
||||
value: edit.value,
|
||||
by: edit.by,
|
||||
at: edit.madeAt
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return activity.sort((a, b) => b.at.getTime() - a.at.getTime());
|
||||
}
|
||||
|
||||
// Example usage
|
||||
const recentActivity = getRecentActivity(gardenProject);
|
||||
console.log("Recent Garden Activity:");
|
||||
recentActivity.forEach(activity => {
|
||||
console.log(`${activity.at.toLocaleString()} - ${activity.field} updated by ${activity.by?.id}`);
|
||||
});
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Edit History & Time Travel
|
||||
|
||||
CoValues track their entire history of changes, creating a timeline you can explore. You can see who changed what and when, or even view past states of the data. This capability enables powerful debugging tools and user-facing features like history browsing and restoration of previous versions:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
class Task extends CoMap {
|
||||
title = co.string;
|
||||
description = co.string;
|
||||
status = co.literal("todo", "in-progress", "completed");
|
||||
priority = co.literal("low", "medium", "high");
|
||||
}
|
||||
|
||||
// Create a new task
|
||||
const task = Task.create({
|
||||
title: "Plant spring vegetables",
|
||||
description: "Plant peas, carrots, and lettuce in the south garden bed",
|
||||
status: "todo",
|
||||
priority: "medium",
|
||||
});
|
||||
|
||||
// Make some changes
|
||||
task.status = "in-progress";
|
||||
task.priority = "high";
|
||||
|
||||
// See all edits for a field
|
||||
for (const edit of task._edits.status.all) {
|
||||
console.log(
|
||||
`${edit.madeAt.toISOString()}: Status changed to "${edit.value}" by ${edit.by?.id}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Get the initial value
|
||||
const initialStatus = task._edits.status.all[0]?.value;
|
||||
console.log(`Original status: ${initialStatus}`);
|
||||
|
||||
// Get a specific edit by index
|
||||
const previousEdit = task._edits.status.all[1]; // Second edit
|
||||
console.log(`Previous status: ${previousEdit?.value}`);
|
||||
|
||||
// Check who made the most recent change
|
||||
const latestEdit = task._edits.status;
|
||||
console.log(`Latest change made by: ${latestEdit?.by?.id}`);
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Time Travel
|
||||
|
||||
The ability to view a CoValue as it existed at any point in time is one of Jazz's most powerful features. Looking into the past can help you understand how things changed - perfect for audit logs, debugging, or showing user activity. You can reconstruct the exact state of any CoValue at any moment in its history:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
class Project extends CoMap {
|
||||
name = co.string;
|
||||
status = co.literal("planning", "active", "completed");
|
||||
lastUpdate = co.Date;
|
||||
}
|
||||
|
||||
// See when a project was started
|
||||
function findStatusChange(project: Project, targetStatus: string) {
|
||||
// Get all the edits for the status field
|
||||
const statusEdits = project._edits.status.all;
|
||||
|
||||
for (const edit of statusEdits) {
|
||||
if (edit.value === targetStatus) {
|
||||
console.log({
|
||||
changeTime: edit.madeAt,
|
||||
lastUpdate: project.lastUpdate,
|
||||
changedBy: edit.by,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Example usage
|
||||
findStatusChange(gardenProject, "active");
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
### Common Use Cases
|
||||
|
||||
The time travel capabilities of CoValues enable several practical use cases that would otherwise require complex custom implementations. Here are some examples of how you can use time travel in your applications:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
// Track task progress over time
|
||||
function getTaskStatusHistory(task: Task, days: number = 7) {
|
||||
const statusHistory = [];
|
||||
const dayInMs = 86400000;
|
||||
|
||||
// Check every day for the past week
|
||||
for (let day = 0; day < days; day++) {
|
||||
const timePoint = new Date(Date.now() - day * dayInMs);
|
||||
// Using the internal _raw API to get state at a specific point in time
|
||||
const state = task._raw.atTime(timePoint);
|
||||
statusHistory.push({
|
||||
date: timePoint.toLocaleDateString(),
|
||||
status: state.status,
|
||||
priority: state.priority
|
||||
});
|
||||
}
|
||||
|
||||
return statusHistory;
|
||||
}
|
||||
|
||||
// Example usage
|
||||
const history = getTaskStatusHistory(plantingTask);
|
||||
history.forEach(entry => {
|
||||
console.log(`${entry.date}: Status was "${entry.status}" with ${entry.priority} priority`);
|
||||
});
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
### Best Practices
|
||||
|
||||
- Check field existence before accessing edits (`if (task._edits.fieldName)`)
|
||||
- Access the most recent edit directly with `_edits.fieldName` instead of using any `.latest` property
|
||||
- Cache historical queries if you're displaying them in UI
|
||||
- Be specific about time ranges you care about
|
||||
- Remember that accessing history requires loading the CoValue
|
||||
- Consider using timestamps from your data rather than scanning all edits
|
||||
|
||||
Time travel is great for understanding how you got here, but keep queries focused on the range of time that matters to your use case.
|
||||
@@ -0,0 +1,272 @@
|
||||
import { CodeGroup, ComingSoon, ContentByFramework } from "@/components/forMdx";
|
||||
|
||||
export const metadata = { title: "Reading from CoValues" };
|
||||
|
||||
# Reading from CoValues
|
||||
|
||||
Jazz lets you access your collaborative data with familiar JavaScript patterns while providing TypeScript type safety. Once you have a CoValue, you can read its values, traverse references, and iterate through collections using the same syntax you'd use with regular objects and arrays. This page covers how to read from different types of CoValues and handle loading states effectively.
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
class Project extends CoMap {
|
||||
name = co.string;
|
||||
tasks = co.ref(ListOfTasks);
|
||||
lead = co.optional.ref(TeamMember);
|
||||
status = co.literal("planning", "active", "completed");
|
||||
}
|
||||
|
||||
// Reading basic fields
|
||||
console.log(project.name); // "Spring Garden Planning"
|
||||
console.log(project.status); // "active"
|
||||
|
||||
// Reading from lists
|
||||
for (const task of project.tasks) {
|
||||
console.log(task.title); // "Plant tomato seedlings"
|
||||
}
|
||||
|
||||
// Checking if an optional field exists
|
||||
if (project.lead) {
|
||||
console.log(project.lead.name); // "Maria Chen"
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Different Types of CoValues
|
||||
|
||||
Jazz provides several CoValue types to represent different kinds of data. Each type has its own access patterns, but they all maintain the familiar JavaScript syntax you already know.
|
||||
|
||||
### CoMaps
|
||||
`CoMap`s work like JavaScript objects, providing named properties you can access with dot notation. These are the most common CoValue type and form the foundation of most Jazz data models:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
class TeamMember extends CoMap {
|
||||
name = co.string;
|
||||
role = co.string;
|
||||
active = co.boolean;
|
||||
}
|
||||
|
||||
console.log(member.name); // "Maria Chen"
|
||||
console.log(member.role); // "Garden Coordinator"
|
||||
console.log(member.active); // true
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
### CoLists
|
||||
`CoList`s work like JavaScript arrays, supporting indexed access, iteration methods, and length properties. They're perfect for ordered collections of items where the order matters:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
class ListOfTasks extends CoList.Of(co.ref(Task)) {}
|
||||
|
||||
// Access items by index
|
||||
console.log(tasks[0].title); // "Plant tomato seedlings"
|
||||
|
||||
// Use array methods
|
||||
tasks.forEach(task => {
|
||||
console.log(task.title); // "Plant tomato seedlings"
|
||||
});
|
||||
|
||||
// Get list length
|
||||
console.log(tasks.length); // 3
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
### CoFeeds
|
||||
`CoFeed`s provide a specialized way to track data from different sessions (tabs, devices, app instances). They're ideal for activity logs, presence indicators, or other session-specific streams of information. Each account can have multiple sessions, and each session maintains its own append-only log.
|
||||
|
||||
## Type Safety with CoValues
|
||||
|
||||
CoValues are fully typed in TypeScript, giving you the same autocomplete and error checking you'd expect from regular objects. This type safety helps catch errors at compile time rather than runtime, making your application more robust. Here's how the type system works with CoValues:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
class Project extends CoMap {
|
||||
name = co.string;
|
||||
memberCount = co.number;
|
||||
priority = co.literal("low", "medium", "high");
|
||||
lead = co.optional.ref(TeamMember);
|
||||
tasks = co.ref(ListOfTasks);
|
||||
}
|
||||
|
||||
// TypeScript knows exactly what fields exist
|
||||
const project = await Project.load(gardenProjectId);
|
||||
|
||||
project.name = "Community Garden"; // ✓ string
|
||||
project.memberCount = "few"; // ✗ Type error: expected number
|
||||
project.priority = "urgent"; // ✗ Type error: must be low/medium/high
|
||||
|
||||
// Optional fields are handled safely
|
||||
if (project.lead) {
|
||||
console.log(project.lead.name); // Type safe
|
||||
}
|
||||
|
||||
// Lists with specific item types
|
||||
project.tasks.forEach(task => {
|
||||
// TypeScript knows each task's structure
|
||||
console.log(`${task.title}: ${task.status}`); // "Plant herbs: in-progress"
|
||||
});
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Loading States
|
||||
|
||||
When you load a CoValue, it might not be immediately available due to network latency or data size. Jazz provides patterns to handle these loading states gracefully, and TypeScript helps ensure you check for availability before accessing properties:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
const project = await Project.load(gardenProjectId);
|
||||
|
||||
if (!project) {
|
||||
return "Data still loading";
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
<ContentByFramework framework="react">
|
||||
And in React, `useCoState` provides a similar pattern to allow you to wait for a CoValue to be loaded before accessing it:
|
||||
<CodeGroup>
|
||||
```tsx
|
||||
// Type shows this might be `undefined` while loading
|
||||
const project = useCoState(Project, gardenProjectId, {
|
||||
tasks: [{}]
|
||||
});
|
||||
|
||||
if (!project) {
|
||||
return <div>Loading project data...</div>;
|
||||
}
|
||||
|
||||
// TypeScript now knows project exists and has tasks loaded
|
||||
return <div>{project.tasks.length}</div>;
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
### Accessing Nested CoValues
|
||||
|
||||
Nested CoValues need special handling for loading and access. Since each reference might need to be loaded separately, you need patterns to manage these dependencies and handle loading states appropriately throughout your object graph.
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
class Project extends CoMap {
|
||||
name = co.string;
|
||||
tasks = co.ref(ListOfTasks);
|
||||
resources = co.optional.ref(ResourceList);
|
||||
}
|
||||
|
||||
class Task extends CoMap {
|
||||
title = co.string;
|
||||
status = co.literal("todo", "in-progress", "completed");
|
||||
subtasks = co.ref(ListOfSubtasks);
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
### Loading
|
||||
|
||||
Loading nested data efficiently is important for performance. Jazz provides depth specifications to control exactly how much of your object graph is loaded, from shallow loading of just the top-level object to deep loading of complex nested structures:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
// Basic load - tasks won't be loaded yet
|
||||
const project = await Project.load(gardenProjectId);
|
||||
|
||||
// Load with nested data
|
||||
const projectWithTasks = await Project.load(gardenProjectId, { tasks: {} });
|
||||
|
||||
// Deep load pattern
|
||||
const fullyLoaded = await Project.load(gardenProjectId, {
|
||||
tasks: {
|
||||
subtasks: {}
|
||||
}
|
||||
});
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
More details on loading and subscribing to CoValues can be found in [Subscribing](/docs/using-covalues/subscribing-and-deep-loading).
|
||||
|
||||
### Handling Loading States
|
||||
|
||||
Unloaded references return `undefined`. This means you need to check for undefined values before trying to access properties of nested CoValues.
|
||||
|
||||
For general JavaScript/TypeScript usage, here's a pattern that works across any context:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
// Generic pattern for handling nested data
|
||||
function processTaskData(project) {
|
||||
// Check if project and its tasks are loaded
|
||||
if (!project || !project.tasks) {
|
||||
return "Data still loading";
|
||||
}
|
||||
|
||||
// Safe to process tasks
|
||||
const completedTasks = project.tasks.filter(task =>
|
||||
task && task.status === "completed"
|
||||
);
|
||||
|
||||
// Check for subtasks before accessing them
|
||||
const subtaskCount = completedTasks.reduce((count, task) => {
|
||||
if (!(task && task.subtasks)) return count
|
||||
return count + task.subtasks.length;
|
||||
}, 0);
|
||||
|
||||
return {
|
||||
completedCount: completedTasks.length,
|
||||
subtaskCount: subtaskCount
|
||||
};
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
<ContentByFramework framework="react">
|
||||
Handle these loading states in your components:
|
||||
|
||||
<CodeGroup>
|
||||
```tsx
|
||||
// React pattern for handling nested data
|
||||
function TaskList({ project }: { project: Project }) {
|
||||
if (!project.tasks) {
|
||||
return <div>Loading tasks...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{project.tasks.map(task => {
|
||||
// Handle potentially missing nested data
|
||||
if (!task.subtasks) {
|
||||
return <div key={task.id}>Loading subtasks...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={task.id}>
|
||||
{task.title}: {task.subtasks.length} subtasks
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
Note: We're working on making these patterns more explicit and robust. We'll provide clearer loading states and better error handling patterns. For now, be defensive with your checks for `undefined`.
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
// Current safest pattern for deep access
|
||||
function getSubtasks(project: Project, taskTitle: string) {
|
||||
const task = project.tasks?.find(t => t.title === taskTitle);
|
||||
const subtasks = task?.subtasks;
|
||||
|
||||
if (!subtasks) {
|
||||
return null; // Could mean loading or error
|
||||
}
|
||||
|
||||
return subtasks.map(st => st.title);
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
Stay tuned for updates to this API - we're working on making these patterns more robust and explicit.
|
||||
@@ -0,0 +1,340 @@
|
||||
import { CodeGroup, ContentByFramework } from "@/components/forMdx";
|
||||
|
||||
export const metadata = { title: "Subscriptions & Deep Loading" };
|
||||
|
||||
# Subscriptions & Deep Loading
|
||||
|
||||
When working with collaborative applications, you need to know when data changes and ensure you have all the necessary related data. Jazz provides powerful subscription and deep loading capabilities that make it easy to keep your UI in sync with the underlying data and efficiently load complex object graphs.
|
||||
|
||||
## Understanding Subscriptions
|
||||
|
||||
Subscriptions in Jazz allow you to react to changes in CoValues. When a CoValue changes, all subscribers are notified with the updated value. This is essential for building reactive UIs that stay in sync with collaborative data.
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
class Task extends CoMap {
|
||||
title = co.string;
|
||||
description = co.string;
|
||||
status = co.literal("todo", "in-progress", "completed");
|
||||
assignedTo = co.optional.string;
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
// Subscribe to a Task by ID
|
||||
const unsubscribe = Task.subscribe(taskId, { /* loading depth */ }, (updatedTask) => {
|
||||
console.log("Task updated:", updatedTask.title);
|
||||
console.log("New status:", updatedTask.status);
|
||||
});
|
||||
|
||||
// Later, when you're done:
|
||||
unsubscribe();
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
### Static vs. Instance Subscriptions
|
||||
|
||||
There are two main ways to subscribe to CoValues:
|
||||
|
||||
1. **Static Subscription** - When you have an ID but don't have the CoValue loaded yet:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
// Subscribe by ID (static method)
|
||||
const unsubscribe = Task.subscribe(taskId, { /* loading depth */ }, (task) => {
|
||||
if (task) {
|
||||
console.log("Task loaded/updated:", task.title);
|
||||
}
|
||||
});
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
2. **Instance Subscription** - When you already have a CoValue instance:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
// Subscribe to an instance (instance method)
|
||||
const task = Task.create({
|
||||
status: "todo",
|
||||
title: "Cut the grass",
|
||||
});
|
||||
if (task) {
|
||||
const unsubscribe = task.subscribe({ /* loading depth */ }, (updatedTask) => {
|
||||
console.log("Task updated:", updatedTask.title);
|
||||
});
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Deep Loading
|
||||
|
||||
When working with related CoValues (like tasks in a project), you often need to load not just the top-level object but also its nested references. Jazz provides a flexible mechanism for specifying exactly how much of the object graph to load.
|
||||
|
||||
### Loading Depth Specifications
|
||||
|
||||
Loading depth specifications let you declare exactly which references to load and how deep to go:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
class Project extends CoMap {
|
||||
name = co.string;
|
||||
tasks = co.ref(ListOfTasks);
|
||||
owner = co.ref(TeamMember);
|
||||
}
|
||||
|
||||
class Task extends CoMap {
|
||||
title = co.string;
|
||||
subtasks = co.ref(ListOfSubtasks);
|
||||
assignee = co.optional.ref(TeamMember);
|
||||
}
|
||||
|
||||
// Load just the project, not its tasks
|
||||
const project = await Project.load(projectId, {});
|
||||
|
||||
// Load the project and its tasks (but not subtasks)
|
||||
const projectWithTasks = await Project.load(projectId, {
|
||||
tasks: {}
|
||||
});
|
||||
|
||||
// Load the project, its tasks, and their subtasks
|
||||
const projectDeep = await Project.load(projectId, {
|
||||
tasks: {
|
||||
subtasks: {}
|
||||
}
|
||||
});
|
||||
|
||||
// Load the project, its tasks, and task assignees
|
||||
const projectWithAssignees = await Project.load(projectId, {
|
||||
tasks: {
|
||||
assignee: {}
|
||||
}
|
||||
});
|
||||
|
||||
// Complex loading pattern: load project, tasks with their subtasks, and the project owner
|
||||
const fullyLoaded = await Project.load(projectId, {
|
||||
tasks: {
|
||||
subtasks: {}
|
||||
},
|
||||
owner: {}
|
||||
});
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
The depth specification object mirrors the structure of your data model, making it intuitive to express which parts of the graph you want to load.
|
||||
|
||||
### Array Notation for Lists
|
||||
|
||||
For lists, you can use array notation to specify how to load the items:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
// Load project with all tasks but load each task shallowly
|
||||
const project = await Project.load(projectId, {
|
||||
tasks: [{}]
|
||||
});
|
||||
|
||||
// Load project with tasks and load subtasks for each task
|
||||
const project = await Project.load(projectId, {
|
||||
tasks: [{
|
||||
subtasks: [{}]
|
||||
}]
|
||||
});
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Framework Integration
|
||||
|
||||
<ContentByFramework framework="react">
|
||||
### React Integration with useCoState
|
||||
|
||||
In React applications, the `useCoState` hook provides a convenient way to subscribe to CoValues and handle loading states:
|
||||
|
||||
<CodeGroup>
|
||||
```tsx
|
||||
function GardenPlanner({ projectId }: { projectId: ID<Project> }) {
|
||||
// Subscribe to a project and its tasks
|
||||
const project = useCoState(Project, projectId, {
|
||||
tasks: [{}]
|
||||
});
|
||||
|
||||
// Handle loading state
|
||||
if (!project) {
|
||||
return <div>Loading garden project...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>{project.name}</h1>
|
||||
<TaskList tasks={project.tasks} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TaskList({ tasks }: { tasks: Task[] }) {
|
||||
return (
|
||||
<ul>
|
||||
{tasks.map(task => (
|
||||
<li key={task.id}>
|
||||
<span>{task.title}</span>
|
||||
<span>{task.status}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
The `useCoState` hook handles subscribing when the component mounts and unsubscribing when it unmounts, making it easy to keep your UI in sync with the underlying data.
|
||||
</ContentByFramework>
|
||||
|
||||
<ContentByFramework framework="vue">
|
||||
### Vue Integration
|
||||
|
||||
In Vue applications, you can use the `useCoState` composable to subscribe to CoValues:
|
||||
|
||||
<CodeGroup>
|
||||
```vue
|
||||
<script setup>
|
||||
import { useCoState } from 'jazz-vue';
|
||||
|
||||
const props = defineProps({
|
||||
projectId: String
|
||||
});
|
||||
|
||||
// Subscribe to a project and its tasks
|
||||
const project = useCoState(Project, props.projectId, {
|
||||
tasks: [{}]
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="project">
|
||||
<h1>{{ project.name }}</h1>
|
||||
<ul>
|
||||
<li v-for="task in project.tasks" :key="task.id">
|
||||
{{ task.title }} - {{ task.status }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-else>
|
||||
Loading garden project...
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
<ContentByFramework framework="svelte">
|
||||
### Svelte Integration
|
||||
|
||||
In Svelte applications, you can use the `useCoState` function to subscribe to CoValues:
|
||||
|
||||
<CodeGroup>
|
||||
```svelte
|
||||
<script>
|
||||
import { useCoState } from 'jazz-svelte';
|
||||
|
||||
export let projectId;
|
||||
|
||||
// Subscribe to a project and its tasks
|
||||
const project = useCoState(Project, projectId, {
|
||||
tasks: [{}]
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if $project}
|
||||
<h1>{$project.name}</h1>
|
||||
<ul>
|
||||
{#each $project.tasks as task (task.id)}
|
||||
<li>{task.title} - {task.status}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{:else}
|
||||
<div>Loading garden project...</div>
|
||||
{/if}
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
## Ensuring Data is Loaded
|
||||
|
||||
Sometimes you need to make sure data is loaded before proceeding with an operation. The `ensureLoaded` method lets you guarantee that a CoValue and its referenced data are loaded to a specific depth:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
async function completeAllTasks(projectId: ID<Project>) {
|
||||
// Ensure the project and its tasks are loaded
|
||||
const project = await Project.load(projectId, {});
|
||||
if (!project) return;
|
||||
|
||||
const loadedProject = await project.ensureLoaded({
|
||||
tasks: [{}]
|
||||
});
|
||||
|
||||
// Now we can safely access and modify tasks
|
||||
loadedProject.tasks.forEach(task => {
|
||||
task.status = "completed";
|
||||
});
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
Loading depth is directly related to performance. Loading too much data can slow down your application, while loading too little can lead to "undefined" references. Here are some guidelines:
|
||||
|
||||
- **Load only what you need** for the current view or operation
|
||||
- **Preload data** that will be needed soon to improve perceived performance
|
||||
- Use **caching** to avoid reloading data that hasn't changed
|
||||
|
||||
{/* TODO: Add a note about supporting pagination */}
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
// Bad: Loading everything deeply
|
||||
const project = await Project.load(projectId, {
|
||||
tasks: [{
|
||||
subtasks: [{
|
||||
comments: [{}]
|
||||
}]
|
||||
}],
|
||||
members: [{}],
|
||||
resources: [{}]
|
||||
});
|
||||
|
||||
// Better: Loading only what's needed for the current view
|
||||
const project = await Project.load(projectId, {
|
||||
tasks: [{}] // Just load the tasks shallowly
|
||||
});
|
||||
|
||||
// Later, when a task is selected:
|
||||
const task = await Task.load(selectedTaskId, {
|
||||
subtasks: [{}] // Now load its subtasks
|
||||
});
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Using a Loading Cache
|
||||
|
||||
By default, Jazz maintains a cache of loaded CoValues to avoid unnecessary network requests. This means that if you've already loaded a CoValue, subsequent load requests will use the cached version unless you explicitly request a refresh.
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
// First load: fetches from network or local storage
|
||||
const project = await Project.load(projectId, {});
|
||||
|
||||
// Later loads: uses cached version if available
|
||||
const sameProject = await Project.load(projectId, {});
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Be explicit about loading depths**: Always specify exactly what you need
|
||||
2. **Clean up subscriptions**: Always store and call the unsubscribe function when you're done
|
||||
3. **Handle loading states**: Check for undefined/null before accessing properties
|
||||
4. **Use framework integrations**: They handle subscription lifecycle automatically
|
||||
5. **Balance depth and performance**: Load only what you need for the current view
|
||||
|
||||
By effectively using subscriptions and deep loading, you can build responsive, collaborative applications that handle complex data relationships while maintaining good performance.
|
||||
@@ -0,0 +1,175 @@
|
||||
export const metadata = { title: "Writing & deleting CoValues" };
|
||||
import { CodeGroup } from "@/components/forMdx";
|
||||
|
||||
# Writing & deleting CoValues
|
||||
|
||||
Collaborative applications need ways to update and remove data. Jazz makes this simple by treating CoValues like regular JavaScript objects while handling all the complexity of syncing changes in the background. This page covers how to modify CoValues, work with collections, handle concurrent edits, and properly remove data when needed.
|
||||
|
||||
## Writing to CoValues
|
||||
|
||||
Once you have a CoValue, modifying it is straightforward. You can update fields like regular JavaScript properties. Changes are applied locally first for immediate feedback, then synchronized to other users with access to the same CoValues. This approach provides a natural programming model while handling all the distributed systems complexity behind the scenes.
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
class Task extends CoMap {
|
||||
title = co.string;
|
||||
status = co.literal("todo", "in-progress", "completed");
|
||||
assignee = co.optional.string;
|
||||
}
|
||||
|
||||
//...
|
||||
|
||||
// Update fields
|
||||
task.status = "in-progress"; // Direct assignment
|
||||
task.assignee = "Alex"; // Optional field
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
### Working with Lists
|
||||
|
||||
CoLists support familiar array operations, making it easy to work with collections of data. You can add, remove, and modify items using the standard JavaScript array methods, while Jazz handles the collaborative aspects automatically. These operations work correctly even when multiple users are making changes simultaneously.
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
class Project extends CoMap {
|
||||
name = co.string;
|
||||
tasks = co.ref(ListOfTasks);
|
||||
}
|
||||
|
||||
//...
|
||||
|
||||
// Add items
|
||||
project.tasks.push(Task.create({
|
||||
title: "Build raised beds",
|
||||
status: "todo"
|
||||
}));
|
||||
|
||||
// Remove items
|
||||
project.tasks.splice(2, 1); // Remove third task
|
||||
|
||||
// Update items
|
||||
project.tasks[0].status = "in-progress";
|
||||
|
||||
// Bulk updates
|
||||
project.tasks.forEach(task => {
|
||||
if (task.status === "todo") {
|
||||
task.status = "in-progress";
|
||||
}
|
||||
});
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
Changes sync automatically to everyone with access. Any edits you make are immediately visible in your local view and propagate to other users as they sync.
|
||||
|
||||
## Concurrent Edits
|
||||
|
||||
CoValues use [CRDTs](/docs/schemas/covalues#defining-schemas-covalues) to handle concurrent edits smoothly. In most cases, you don't need to think about conflicts - Jazz handles them automatically. This conflict resolution happens transparently, allowing multiple users to make changes simultaneously without disruption or data loss.
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
class Dashboard extends CoMap {
|
||||
activeProjects = co.number;
|
||||
status = co.literal("active", "maintenance");
|
||||
notifications = co.ref(ListOfNotifications);
|
||||
}
|
||||
|
||||
//...
|
||||
|
||||
// Multiple users can edit simultaneously
|
||||
// Last-write-wins for simple fields
|
||||
dashboard.status = "maintenance"; // Local change is immediate
|
||||
dashboard.activeProjects = 5; // Syncs automatically
|
||||
|
||||
// Lists handle concurrent edits too
|
||||
dashboard.notifications.push(Notification.create({
|
||||
timestamp: new Date(),
|
||||
message: "System update scheduled"
|
||||
}));
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Deleting CoValues
|
||||
|
||||
There are a few ways to delete CoValues, from simple field removal to full cleanup. Jazz provides flexible options for removing data depending on your needs. You can remove references while keeping the underlying data, remove items from lists, or completely delete CoValues when they're no longer needed.
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
class Project extends CoMap {
|
||||
tasks = co.ref(ListOfTasks);
|
||||
resources = co.optional.ref(ListOfResources);
|
||||
}
|
||||
|
||||
//...
|
||||
|
||||
// Remove a reference
|
||||
project.resources = null; // Removes the reference but resources still exist
|
||||
|
||||
// Remove from a list
|
||||
project.tasks.splice(2, 1); // Removes third team member from list
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
### Best Practices
|
||||
|
||||
- Load everything you plan to delete
|
||||
- Check permissions before attempting deletes
|
||||
- Consider soft deletes for recoverable data
|
||||
|
||||
## Removing Data in CoValues
|
||||
|
||||
You can delete fields from any `CoMap` to remove specific properties while keeping the CoValue itself. This is useful when you need to clear certain data without affecting the rest of your object structure. The deletion operations are also synchronized to all users with access.
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
class Project extends CoMap {
|
||||
name = co.string;
|
||||
team = co.ref(ListOfMembers);
|
||||
budget = co.optional.ref(Budget);
|
||||
}
|
||||
|
||||
//...
|
||||
|
||||
// Delete fields from a regular CoMap
|
||||
delete project.budget; // Removes the budget reference
|
||||
|
||||
// Delete from a record-type CoMap
|
||||
class ProjectTags extends CoMap.Record(co.string) {}
|
||||
|
||||
const projectTags = ProjectTags.create({
|
||||
"priority-high": "High priority tasks",
|
||||
});
|
||||
|
||||
delete projectTags["priority-high"]; // Removes specific tag
|
||||
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
For `CoList`s, use array methods:
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
// Remove from lists using splice
|
||||
project.team.splice(2, 1); // Removes third team member
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
### Restoring Data
|
||||
|
||||
For data you might want to restore later, consider using status fields instead of permanent deletion. This "soft delete" pattern is common in applications where users might need to recover previously removed items. By using a boolean field to mark items as archived or deleted, you maintain the ability to restore them later.
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
class Task extends CoMap {
|
||||
title = co.string;
|
||||
archived = co.boolean;
|
||||
}
|
||||
|
||||
// Mark as archived
|
||||
task.archived = true;
|
||||
|
||||
// Restore later
|
||||
task.archived = false; // Task is back in the active list!
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
Removed data remains in the edit history. If you need to handle sensitive information, plan your data model accordingly.
|
||||
@@ -1,25 +1,27 @@
|
||||
import { Pricing } from "@/components/Pricing";
|
||||
import { LatencyMap } from "@/components/cloud/latencyMap";
|
||||
import { GridCard } from "gcmp-design-system/src/app/components/atoms/GridCard";
|
||||
import {
|
||||
H2,
|
||||
H3,
|
||||
H4,
|
||||
} from "gcmp-design-system/src/app/components/atoms/Headings";
|
||||
import { LI } from "gcmp-design-system/src/app/components/atoms/ListItem";
|
||||
import { H2, H3 } from "gcmp-design-system/src/app/components/atoms/Headings";
|
||||
import { P } from "gcmp-design-system/src/app/components/atoms/Paragraph";
|
||||
import { GappedGrid } from "gcmp-design-system/src/app/components/molecules/GappedGrid";
|
||||
import { HeroHeader } from "gcmp-design-system/src/app/components/molecules/HeroHeader";
|
||||
import { UL } from "gcmp-design-system/src/app/components/molecules/List";
|
||||
import { Prose } from "gcmp-design-system/src/app/components/molecules/Prose";
|
||||
import { SectionHeader } from "gcmp-design-system/src/app/components/molecules/SectionHeader";
|
||||
import type { Metadata } from "next";
|
||||
import CloudPlusBackup from "./cloudPlusBackup.mdx";
|
||||
import CloudPlusDIY from "./cloudPlusDIY.mdx";
|
||||
import CompletelyDIY from "./completelyDIY.mdx";
|
||||
|
||||
export const metadata = {
|
||||
title: "Jazz Cloud",
|
||||
description: "Serverless sync & storage for Jazz apps.",
|
||||
const title = "Jazz Cloud";
|
||||
const description = "Serverless sync & storage for Jazz apps.";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title,
|
||||
description,
|
||||
openGraph: {
|
||||
title,
|
||||
description,
|
||||
},
|
||||
};
|
||||
|
||||
export default function Cloud() {
|
||||
|
||||
@@ -11,6 +11,20 @@ import { H2 } from "gcmp-design-system/src/app/components/atoms/Headings";
|
||||
import { Icon } from "gcmp-design-system/src/app/components/atoms/Icon";
|
||||
import { GappedGrid } from "gcmp-design-system/src/app/components/molecules/GappedGrid";
|
||||
import { HeroHeader } from "gcmp-design-system/src/app/components/molecules/HeroHeader";
|
||||
import type { Metadata } from "next";
|
||||
|
||||
const title = "Examples";
|
||||
const description =
|
||||
"Find an example app with code most similar to what you want to build.";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title,
|
||||
description,
|
||||
openGraph: {
|
||||
title,
|
||||
description,
|
||||
},
|
||||
};
|
||||
|
||||
const MockButton = ({ children }: { children: React.ReactNode }) => (
|
||||
<p className="bg-blue-100 text-blue-800 py-1 px-3 rounded-full font-medium text-xs inline-flex items-center justify-center">
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
import { products } from "@/lib/showcase";
|
||||
import { HeroHeader } from "gcmp-design-system/src/app/components/molecules/HeroHeader";
|
||||
import type { Metadata } from "next";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
|
||||
export const metadata = {
|
||||
title: "Built with Jazz",
|
||||
description: "Great apps by smart people.",
|
||||
const title = "Built with Jazz";
|
||||
const description = "Great apps by smart people.";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title,
|
||||
description,
|
||||
openGraph: {
|
||||
title,
|
||||
description,
|
||||
},
|
||||
};
|
||||
|
||||
export default function Page() {
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
import { clsx } from "clsx";
|
||||
import { HeroHeader } from "gcmp-design-system/src/app/components/molecules/HeroHeader";
|
||||
import type { Metadata } from "next";
|
||||
import dynamic from "next/dynamic";
|
||||
import { Fragment } from "react";
|
||||
|
||||
const title = "Status";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title,
|
||||
openGraph: {
|
||||
title,
|
||||
},
|
||||
};
|
||||
|
||||
const LatencyChart = dynamic(() => import("@/components/LatencyChart"), {
|
||||
ssr: false,
|
||||
});
|
||||
@@ -133,11 +143,6 @@ const query = async () => {
|
||||
return byRegion;
|
||||
};
|
||||
|
||||
export const metadata = {
|
||||
title: "Status",
|
||||
description: "Great apps by smart people.",
|
||||
};
|
||||
|
||||
export default async function Page() {
|
||||
const byRegion = await query();
|
||||
|
||||
|
||||
@@ -18,6 +18,20 @@ import {
|
||||
PropDecl,
|
||||
} from "./tags";
|
||||
|
||||
function isDeclarationReflection(child: any): child is DeclarationReflection {
|
||||
return (
|
||||
'kind' in child &&
|
||||
'name' in child &&
|
||||
'flags' in child &&
|
||||
(child.kind === ReflectionKind.Class ||
|
||||
child.kind === ReflectionKind.Interface ||
|
||||
child.kind === ReflectionKind.TypeAlias ||
|
||||
child.kind === ReflectionKind.Function ||
|
||||
child.kind === ReflectionKind.Property ||
|
||||
child.kind === ReflectionKind.Method)
|
||||
);
|
||||
}
|
||||
|
||||
export async function PackageDocs({
|
||||
package: packageName,
|
||||
}: {
|
||||
@@ -36,7 +50,7 @@ export async function PackageDocs({
|
||||
return (
|
||||
<section key={category.title}>
|
||||
<h2>{category.title}</h2>
|
||||
{category.children.map((child) => (
|
||||
{category.children.filter(isDeclarationReflection).map((child) => (
|
||||
<RenderPackageChild
|
||||
child={child}
|
||||
key={child.id}
|
||||
@@ -189,7 +203,7 @@ function RenderClassOrInterface({
|
||||
),
|
||||
)}
|
||||
/>
|
||||
{category.children.map((prop) => (
|
||||
{category.children.filter(isDeclarationReflection).map((prop) => (
|
||||
<RenderProp prop={prop} klass={classOrInterface} key={prop.id} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Deserializer, JSONOutput, ProjectReflection } from "typedoc";
|
||||
import { Deserializer, FileRegistry, JSONOutput, ProjectReflection } from "typedoc";
|
||||
|
||||
import JazzBrowserMediaImagesDocs from "../../typedoc/jazz-browser-media-images.json";
|
||||
import JazzBrowserDocs from "../../typedoc/jazz-browser.json";
|
||||
@@ -7,17 +7,19 @@ import JazzReactDocs from "../../typedoc/jazz-react.json";
|
||||
import JazzToolsDocs from "../../typedoc/jazz-tools.json";
|
||||
|
||||
const docs = {
|
||||
"jazz-tools": JazzToolsDocs as JSONOutput.ProjectReflection,
|
||||
"jazz-react": JazzReactDocs as JSONOutput.ProjectReflection,
|
||||
"jazz-browser": JazzBrowserDocs as JSONOutput.ProjectReflection,
|
||||
"jazz-browser-media-images":
|
||||
JazzBrowserMediaImagesDocs as JSONOutput.ProjectReflection,
|
||||
"jazz-nodejs": JazzNodejsDocs as JSONOutput.ProjectReflection,
|
||||
};
|
||||
"jazz-tools": JazzToolsDocs,
|
||||
"jazz-react": JazzReactDocs,
|
||||
"jazz-browser": JazzBrowserDocs,
|
||||
"jazz-browser-media-images": JazzBrowserMediaImagesDocs,
|
||||
"jazz-nodejs": JazzNodejsDocs,
|
||||
} as const;
|
||||
|
||||
export async function requestProject(
|
||||
packageName: keyof typeof docs,
|
||||
): Promise<ProjectReflection> {
|
||||
const deserializer = new Deserializer({} as any);
|
||||
return deserializer.reviveProject(docs[packageName], packageName);
|
||||
return deserializer.reviveProject(packageName, docs[packageName] as unknown as JSONOutput.ProjectReflection, {
|
||||
projectRoot: ".",
|
||||
registry: new FileRegistry(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -111,28 +111,43 @@ export const docNavigationItems = [
|
||||
name: "Using CoValues",
|
||||
items: [
|
||||
{
|
||||
name: "Creation & ownership",
|
||||
href: "/docs/using-covalues/creation",
|
||||
name: "CoMaps",
|
||||
href: "/docs/using-covalues/comaps",
|
||||
done: 80,
|
||||
},
|
||||
{
|
||||
name: "CoLists",
|
||||
href: "/docs/using-covalues/colists",
|
||||
done: 80,
|
||||
},
|
||||
{
|
||||
name: "CoFeeds",
|
||||
href: "/docs/using-covalues/cofeeds",
|
||||
done: 80,
|
||||
},
|
||||
{
|
||||
name: "FileStreams",
|
||||
href: "/docs/using-covalues/filestreams",
|
||||
done: 0,
|
||||
},
|
||||
{
|
||||
name: "Reading",
|
||||
href: "/docs/using-covalues/reading",
|
||||
name: "SchemaUnions",
|
||||
href: "/docs/using-covalues/schemaunions",
|
||||
done: 0,
|
||||
},
|
||||
{
|
||||
name: "Subscribing & deep loading",
|
||||
href: "/docs/using-covalues/subscription-and-loading",
|
||||
name: "Loading & subscribing",
|
||||
href: "/docs/using-covalues/loading-and-subscribing",
|
||||
done: 0,
|
||||
},
|
||||
{
|
||||
name: "Writing & deleting",
|
||||
href: "/docs/using-covalues/writing",
|
||||
name: "History & time travel",
|
||||
href: "/docs/using-covalues/history-and-time-travel",
|
||||
done: 0,
|
||||
},
|
||||
{
|
||||
name: "Metadata & time-travel",
|
||||
href: "/docs/using-covalues/metadata",
|
||||
name: "Access control",
|
||||
href: "/docs/using-covalues/access-control",
|
||||
done: 0,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -37,7 +37,7 @@ const config = {
|
||||
function highlightPlugin() {
|
||||
return async function transformer(tree) {
|
||||
const highlighter = await getHighlighter({
|
||||
langs: ["typescript", "bash", "tsx", "json", "svelte"],
|
||||
langs: ["typescript", "bash", "tsx", "json", "svelte", "vue"],
|
||||
theme: "css-variables", // use css variables in shiki.css
|
||||
});
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
"autoprefixer": "^10",
|
||||
"postcss": "^8",
|
||||
"tailwindcss": "^3",
|
||||
"typedoc": "^0.25.13",
|
||||
"typescript": "^5.3.3"
|
||||
"typedoc": "^0.27.9",
|
||||
"typescript": "~5.6.2"
|
||||
}
|
||||
}
|
||||
|
||||
288
homepage/pnpm-lock.yaml
generated
288
homepage/pnpm-lock.yaml
generated
@@ -267,7 +267,7 @@ importers:
|
||||
version: 0.14.7
|
||||
shiki-twoslash:
|
||||
specifier: ^3.1.2
|
||||
version: 3.1.2(typescript@5.4.5)
|
||||
version: 3.1.2(typescript@5.6.3)
|
||||
tailwind-merge:
|
||||
specifier: ^1.14.0
|
||||
version: 1.14.0
|
||||
@@ -303,11 +303,11 @@ importers:
|
||||
specifier: ^3
|
||||
version: 3.4.3
|
||||
typedoc:
|
||||
specifier: ^0.25.13
|
||||
version: 0.25.13(typescript@5.4.5)
|
||||
specifier: ^0.27.9
|
||||
version: 0.27.9(typescript@5.6.3)
|
||||
typescript:
|
||||
specifier: ^5.3.3
|
||||
version: 5.4.5
|
||||
specifier: ~5.6.2
|
||||
version: 5.6.3
|
||||
|
||||
packages:
|
||||
|
||||
@@ -438,6 +438,9 @@ packages:
|
||||
'@floating-ui/utils@0.2.8':
|
||||
resolution: {integrity: sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==}
|
||||
|
||||
'@gerrit0/mini-shiki@1.27.2':
|
||||
resolution: {integrity: sha512-GeWyHz8ao2gBiUW4OJnQDxXQnFgZQwwQk05t/CVVgNBN7/rK8XZ7xY6YhLVv9tH3VppWWmr9DCl3MwemB/i+Og==}
|
||||
|
||||
'@headlessui/react@2.2.0':
|
||||
resolution: {integrity: sha512-RzCEg+LXsuI7mHiSomsu/gBJSjpupm6A1qIZ5sWjd7JhARNlMiSA4kKfJpCKwU9tE+zMRterhhrP74PvfJrpXQ==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -458,6 +461,10 @@ packages:
|
||||
resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
'@jridgewell/gen-mapping@0.3.8':
|
||||
resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
'@jridgewell/resolve-uri@3.1.2':
|
||||
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
@@ -472,6 +479,9 @@ packages:
|
||||
'@jridgewell/sourcemap-codec@1.4.15':
|
||||
resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.0':
|
||||
resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
|
||||
|
||||
'@jridgewell/trace-mapping@0.3.25':
|
||||
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
|
||||
|
||||
@@ -873,6 +883,15 @@ packages:
|
||||
'@selderee/plugin-htmlparser2@0.11.0':
|
||||
resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==}
|
||||
|
||||
'@shikijs/engine-oniguruma@1.29.2':
|
||||
resolution: {integrity: sha512-7iiOx3SG8+g1MnlzZVDYiaeHe7Ez2Kf2HrJzdmGwkRisT7r4rak0e655AcM/tF9JG/kg5fMNYlLLKglbN7gBqA==}
|
||||
|
||||
'@shikijs/types@1.29.2':
|
||||
resolution: {integrity: sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw==}
|
||||
|
||||
'@shikijs/vscode-textmate@10.0.2':
|
||||
resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==}
|
||||
|
||||
'@stefanprobst/rehype-extract-toc@2.2.0':
|
||||
resolution: {integrity: sha512-/4UjstX8ploZklY8MmlOQoXB1jWIo3Go4MP0R39sbkoWuN6rJ7Zt6l4bjkDPM4hKsjoODh9SSKn2otlp3XL3/A==}
|
||||
engines: {node: '>=14.17'}
|
||||
@@ -1290,8 +1309,8 @@ packages:
|
||||
'@types/node@20.12.11':
|
||||
resolution: {integrity: sha512-vDg9PZ/zi+Nqp6boSOT7plNuthRugEKixDv5sFTIpkE89MmNtEArAShI4mxuX2+UrLEe9pxC1vm2cjm9YlWbJw==}
|
||||
|
||||
'@types/node@20.17.6':
|
||||
resolution: {integrity: sha512-VEI7OdvK2wP7XHnsuXbAJnEpEkF6NjSN45QJlL4VGqZSXsnicpesdTWsg9RISeSdYd3yeRj/y3k5KGjUXYnFwQ==}
|
||||
'@types/node@20.17.22':
|
||||
resolution: {integrity: sha512-9RV2zST+0s3EhfrMZIhrz2bhuhBwxgkbHEwP2gtGWPjBzVQjifMzJ9exw7aDZhR1wbpj8zBrfp3bo8oJcGiUUw==}
|
||||
|
||||
'@types/prop-types@15.7.12':
|
||||
resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==}
|
||||
@@ -1433,14 +1452,30 @@ packages:
|
||||
engines: {node: '>=0.4.0'}
|
||||
hasBin: true
|
||||
|
||||
ajv-formats@2.1.1:
|
||||
resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
|
||||
peerDependencies:
|
||||
ajv: ^8.0.0
|
||||
peerDependenciesMeta:
|
||||
ajv:
|
||||
optional: true
|
||||
|
||||
ajv-keywords@3.5.2:
|
||||
resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==}
|
||||
peerDependencies:
|
||||
ajv: ^6.9.1
|
||||
|
||||
ajv-keywords@5.1.0:
|
||||
resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==}
|
||||
peerDependencies:
|
||||
ajv: ^8.8.2
|
||||
|
||||
ajv@6.12.6:
|
||||
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
|
||||
|
||||
ajv@8.17.1:
|
||||
resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==}
|
||||
|
||||
ansi-regex@5.0.1:
|
||||
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -1470,6 +1505,9 @@ packages:
|
||||
arg@5.0.2:
|
||||
resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
|
||||
|
||||
argparse@2.0.1:
|
||||
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
||||
|
||||
astring@1.8.6:
|
||||
resolution: {integrity: sha512-ISvCdHdlTDlH5IpxQJIex7BWBywFWgjJSVdwst+/iQCoEYnyOaQ95+X1JGshuBjGp6nxKUy1jMgE3zPqN7fQdg==}
|
||||
hasBin: true
|
||||
@@ -1503,8 +1541,8 @@ packages:
|
||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||
hasBin: true
|
||||
|
||||
browserslist@4.24.2:
|
||||
resolution: {integrity: sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==}
|
||||
browserslist@4.24.4:
|
||||
resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==}
|
||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||
hasBin: true
|
||||
|
||||
@@ -1529,6 +1567,9 @@ packages:
|
||||
caniuse-lite@1.0.30001683:
|
||||
resolution: {integrity: sha512-iqmNnThZ0n70mNwvxpEC2nBJ037ZHZUoBI5Gorh1Mw6IlEAZujEoU1tXA628iZfzm7R9FvFzxbfdgml82a3k8Q==}
|
||||
|
||||
caniuse-lite@1.0.30001701:
|
||||
resolution: {integrity: sha512-faRs/AW3jA9nTwmJBSO1PQ6L/EOgsB5HMQQq4iCu5zhPgVVgO/pZRHlmatwijZKetFw8/Pr4q6dEN8sJuq8qTw==}
|
||||
|
||||
ccount@2.0.1:
|
||||
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
|
||||
|
||||
@@ -1684,8 +1725,8 @@ packages:
|
||||
electron-to-chromium@1.4.761:
|
||||
resolution: {integrity: sha512-PIbxpiJGx6Bb8dQaonNc6CGTRlVntdLg/2nMa1YhnrwYOORY9a3ZgGN0UQYE6lAcj/lkyduJN7BPt/JiY+jAQQ==}
|
||||
|
||||
electron-to-chromium@1.5.64:
|
||||
resolution: {integrity: sha512-IXEuxU+5ClW2IGEYFC2T7szbyVgehupCWQe5GNh+H065CD6U6IFN0s4KeAMFGNmQolRU4IV7zGBWSYMmZ8uuqQ==}
|
||||
electron-to-chromium@1.5.109:
|
||||
resolution: {integrity: sha512-AidaH9JETVRr9DIPGfp1kAarm/W6hRJTPuCnkF+2MqhF4KaAgRIcBc8nvjk+YMXZhwfISof/7WG29eS4iGxQLQ==}
|
||||
|
||||
emoji-regex@8.0.0:
|
||||
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
||||
@@ -1693,16 +1734,16 @@ packages:
|
||||
emoji-regex@9.2.2:
|
||||
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
|
||||
|
||||
enhanced-resolve@5.17.1:
|
||||
resolution: {integrity: sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==}
|
||||
enhanced-resolve@5.18.1:
|
||||
resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
|
||||
entities@4.5.0:
|
||||
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
|
||||
engines: {node: '>=0.12'}
|
||||
|
||||
es-module-lexer@1.5.4:
|
||||
resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==}
|
||||
es-module-lexer@1.6.0:
|
||||
resolution: {integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==}
|
||||
|
||||
escalade@3.1.2:
|
||||
resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==}
|
||||
@@ -1776,6 +1817,9 @@ packages:
|
||||
fast-json-stable-stringify@2.1.0:
|
||||
resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
|
||||
|
||||
fast-uri@3.0.6:
|
||||
resolution: {integrity: sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==}
|
||||
|
||||
fastq@1.17.1:
|
||||
resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==}
|
||||
|
||||
@@ -1960,6 +2004,9 @@ packages:
|
||||
json-schema-traverse@0.4.1:
|
||||
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
|
||||
|
||||
json-schema-traverse@1.0.0:
|
||||
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
|
||||
|
||||
jsonc-parser@3.2.1:
|
||||
resolution: {integrity: sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==}
|
||||
|
||||
@@ -1985,6 +2032,9 @@ packages:
|
||||
lines-and-columns@1.2.4:
|
||||
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
|
||||
|
||||
linkify-it@5.0.0:
|
||||
resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==}
|
||||
|
||||
loader-runner@4.3.0:
|
||||
resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==}
|
||||
engines: {node: '>=6.11.5'}
|
||||
@@ -2032,6 +2082,10 @@ packages:
|
||||
resolution: {integrity: sha512-WWC0ZuMzCyDHYCasEGs4IPvLyTGftYwh6wIEOULOF0HXcqZlhwRzrK0w2VUlxWA98xnvb/jszw4ZSkJ6ADpM6Q==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
markdown-it@14.1.0:
|
||||
resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==}
|
||||
hasBin: true
|
||||
|
||||
marked@4.3.0:
|
||||
resolution: {integrity: sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==}
|
||||
engines: {node: '>= 12'}
|
||||
@@ -2091,6 +2145,9 @@ packages:
|
||||
mdast-util-to-string@4.0.0:
|
||||
resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==}
|
||||
|
||||
mdurl@2.0.0:
|
||||
resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==}
|
||||
|
||||
merge-stream@2.0.0:
|
||||
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
|
||||
|
||||
@@ -2286,6 +2343,10 @@ packages:
|
||||
resolution: {integrity: sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
|
||||
minimatch@9.0.5:
|
||||
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
|
||||
minipass@7.1.0:
|
||||
resolution: {integrity: sha512-oGZRv2OT1lO2UF1zUcwdTb3wqUwI0kBGTgt/T7OdSj6M6N5m3o5uPf0AIW6lVxGGoiWUR7e2AwTE+xiwK8WQig==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
@@ -2354,8 +2415,8 @@ packages:
|
||||
node-releases@2.0.14:
|
||||
resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==}
|
||||
|
||||
node-releases@2.0.18:
|
||||
resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==}
|
||||
node-releases@2.0.19:
|
||||
resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
|
||||
|
||||
nopt@7.2.1:
|
||||
resolution: {integrity: sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==}
|
||||
@@ -2503,6 +2564,10 @@ packages:
|
||||
proto-list@1.2.4:
|
||||
resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==}
|
||||
|
||||
punycode.js@2.3.1:
|
||||
resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
punycode@2.3.1:
|
||||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -2565,6 +2630,10 @@ packages:
|
||||
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
require-from-string@2.0.2:
|
||||
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
require-main-filename@2.0.0:
|
||||
resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==}
|
||||
|
||||
@@ -2603,6 +2672,10 @@ packages:
|
||||
resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==}
|
||||
engines: {node: '>= 10.13.0'}
|
||||
|
||||
schema-utils@4.3.0:
|
||||
resolution: {integrity: sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==}
|
||||
engines: {node: '>= 10.13.0'}
|
||||
|
||||
selderee@0.11.0:
|
||||
resolution: {integrity: sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==}
|
||||
|
||||
@@ -2746,8 +2819,8 @@ packages:
|
||||
resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
terser-webpack-plugin@5.3.10:
|
||||
resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==}
|
||||
terser-webpack-plugin@5.3.12:
|
||||
resolution: {integrity: sha512-jDLYqo7oF8tJIttjXO6jBY5Hk8p3A8W4ttih7cCEq64fQFWmgJ4VqAQjKr7WwIDlmXKEc6QeoRb5ecjZ+2afcg==}
|
||||
engines: {node: '>= 10.13.0'}
|
||||
peerDependencies:
|
||||
'@swc/core': '*'
|
||||
@@ -2762,8 +2835,8 @@ packages:
|
||||
uglify-js:
|
||||
optional: true
|
||||
|
||||
terser@5.36.0:
|
||||
resolution: {integrity: sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==}
|
||||
terser@5.39.0:
|
||||
resolution: {integrity: sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
@@ -2808,11 +2881,26 @@ packages:
|
||||
peerDependencies:
|
||||
typescript: 4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x
|
||||
|
||||
typedoc@0.27.9:
|
||||
resolution: {integrity: sha512-/z585740YHURLl9DN2jCWe6OW7zKYm6VoQ93H0sxZ1cwHQEQrUn5BJrEnkWhfzUdyO+BLGjnKUZ9iz9hKloFDw==}
|
||||
engines: {node: '>= 18'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
typescript: 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x
|
||||
|
||||
typescript@5.4.5:
|
||||
resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==}
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
typescript@5.6.3:
|
||||
resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==}
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
uc.micro@2.1.0:
|
||||
resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
|
||||
|
||||
undici-types@5.26.5:
|
||||
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
|
||||
|
||||
@@ -2870,8 +2958,8 @@ packages:
|
||||
peerDependencies:
|
||||
browserslist: '>= 4.21.0'
|
||||
|
||||
update-browserslist-db@1.1.1:
|
||||
resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==}
|
||||
update-browserslist-db@1.1.3:
|
||||
resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
browserslist: '>= 4.21.0'
|
||||
@@ -2951,6 +3039,11 @@ packages:
|
||||
engines: {node: '>= 14'}
|
||||
hasBin: true
|
||||
|
||||
yaml@2.7.0:
|
||||
resolution: {integrity: sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==}
|
||||
engines: {node: '>= 14'}
|
||||
hasBin: true
|
||||
|
||||
yargs-parser@18.1.3:
|
||||
resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -3066,6 +3159,12 @@ snapshots:
|
||||
|
||||
'@floating-ui/utils@0.2.8': {}
|
||||
|
||||
'@gerrit0/mini-shiki@1.27.2':
|
||||
dependencies:
|
||||
'@shikijs/engine-oniguruma': 1.29.2
|
||||
'@shikijs/types': 1.29.2
|
||||
'@shikijs/vscode-textmate': 10.0.2
|
||||
|
||||
'@headlessui/react@2.2.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@floating-ui/react': 0.26.27(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
@@ -3094,17 +3193,25 @@ snapshots:
|
||||
'@jridgewell/sourcemap-codec': 1.4.15
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
|
||||
'@jridgewell/gen-mapping@0.3.8':
|
||||
dependencies:
|
||||
'@jridgewell/set-array': 1.2.1
|
||||
'@jridgewell/sourcemap-codec': 1.5.0
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
|
||||
'@jridgewell/resolve-uri@3.1.2': {}
|
||||
|
||||
'@jridgewell/set-array@1.2.1': {}
|
||||
|
||||
'@jridgewell/source-map@0.3.6':
|
||||
dependencies:
|
||||
'@jridgewell/gen-mapping': 0.3.5
|
||||
'@jridgewell/gen-mapping': 0.3.8
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.4.15': {}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.0': {}
|
||||
|
||||
'@jridgewell/trace-mapping@0.3.25':
|
||||
dependencies:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
@@ -3449,6 +3556,18 @@ snapshots:
|
||||
domhandler: 5.0.3
|
||||
selderee: 0.11.0
|
||||
|
||||
'@shikijs/engine-oniguruma@1.29.2':
|
||||
dependencies:
|
||||
'@shikijs/types': 1.29.2
|
||||
'@shikijs/vscode-textmate': 10.0.2
|
||||
|
||||
'@shikijs/types@1.29.2':
|
||||
dependencies:
|
||||
'@shikijs/vscode-textmate': 10.0.2
|
||||
'@types/hast': 3.0.4
|
||||
|
||||
'@shikijs/vscode-textmate@10.0.2': {}
|
||||
|
||||
'@stefanprobst/rehype-extract-toc@2.2.0':
|
||||
dependencies:
|
||||
estree-util-is-identifier-name: 2.1.0
|
||||
@@ -4636,7 +4755,7 @@ snapshots:
|
||||
dependencies:
|
||||
undici-types: 5.26.5
|
||||
|
||||
'@types/node@20.17.6':
|
||||
'@types/node@20.17.22':
|
||||
dependencies:
|
||||
undici-types: 6.19.8
|
||||
|
||||
@@ -4790,10 +4909,19 @@ snapshots:
|
||||
|
||||
acorn@8.14.0: {}
|
||||
|
||||
ajv-formats@2.1.1(ajv@8.17.1):
|
||||
optionalDependencies:
|
||||
ajv: 8.17.1
|
||||
|
||||
ajv-keywords@3.5.2(ajv@6.12.6):
|
||||
dependencies:
|
||||
ajv: 6.12.6
|
||||
|
||||
ajv-keywords@5.1.0(ajv@8.17.1):
|
||||
dependencies:
|
||||
ajv: 8.17.1
|
||||
fast-deep-equal: 3.1.3
|
||||
|
||||
ajv@6.12.6:
|
||||
dependencies:
|
||||
fast-deep-equal: 3.1.3
|
||||
@@ -4801,6 +4929,13 @@ snapshots:
|
||||
json-schema-traverse: 0.4.1
|
||||
uri-js: 4.4.1
|
||||
|
||||
ajv@8.17.1:
|
||||
dependencies:
|
||||
fast-deep-equal: 3.1.3
|
||||
fast-uri: 3.0.6
|
||||
json-schema-traverse: 1.0.0
|
||||
require-from-string: 2.0.2
|
||||
|
||||
ansi-regex@5.0.1: {}
|
||||
|
||||
ansi-regex@6.0.1: {}
|
||||
@@ -4822,6 +4957,8 @@ snapshots:
|
||||
|
||||
arg@5.0.2: {}
|
||||
|
||||
argparse@2.0.1: {}
|
||||
|
||||
astring@1.8.6: {}
|
||||
|
||||
autoprefixer@10.4.19(postcss@8.4.38):
|
||||
@@ -4855,12 +4992,12 @@ snapshots:
|
||||
node-releases: 2.0.14
|
||||
update-browserslist-db: 1.0.15(browserslist@4.23.0)
|
||||
|
||||
browserslist@4.24.2:
|
||||
browserslist@4.24.4:
|
||||
dependencies:
|
||||
caniuse-lite: 1.0.30001683
|
||||
electron-to-chromium: 1.5.64
|
||||
node-releases: 2.0.18
|
||||
update-browserslist-db: 1.1.1(browserslist@4.24.2)
|
||||
caniuse-lite: 1.0.30001701
|
||||
electron-to-chromium: 1.5.109
|
||||
node-releases: 2.0.19
|
||||
update-browserslist-db: 1.1.3(browserslist@4.24.4)
|
||||
|
||||
buffer-from@1.1.2: {}
|
||||
|
||||
@@ -4876,6 +5013,8 @@ snapshots:
|
||||
|
||||
caniuse-lite@1.0.30001683: {}
|
||||
|
||||
caniuse-lite@1.0.30001701: {}
|
||||
|
||||
ccount@2.0.1: {}
|
||||
|
||||
character-entities-html4@2.1.0: {}
|
||||
@@ -5017,20 +5156,20 @@ snapshots:
|
||||
|
||||
electron-to-chromium@1.4.761: {}
|
||||
|
||||
electron-to-chromium@1.5.64: {}
|
||||
electron-to-chromium@1.5.109: {}
|
||||
|
||||
emoji-regex@8.0.0: {}
|
||||
|
||||
emoji-regex@9.2.2: {}
|
||||
|
||||
enhanced-resolve@5.17.1:
|
||||
enhanced-resolve@5.18.1:
|
||||
dependencies:
|
||||
graceful-fs: 4.2.11
|
||||
tapable: 2.2.1
|
||||
|
||||
entities@4.5.0: {}
|
||||
|
||||
es-module-lexer@1.5.4: {}
|
||||
es-module-lexer@1.6.0: {}
|
||||
|
||||
escalade@3.1.2: {}
|
||||
|
||||
@@ -5105,6 +5244,8 @@ snapshots:
|
||||
|
||||
fast-json-stable-stringify@2.1.0: {}
|
||||
|
||||
fast-uri@3.0.6: {}
|
||||
|
||||
fastq@1.17.1:
|
||||
dependencies:
|
||||
reusify: 1.0.4
|
||||
@@ -5276,7 +5417,7 @@ snapshots:
|
||||
|
||||
jest-worker@27.5.1:
|
||||
dependencies:
|
||||
'@types/node': 20.17.6
|
||||
'@types/node': 20.17.22
|
||||
merge-stream: 2.0.0
|
||||
supports-color: 8.1.1
|
||||
|
||||
@@ -5298,6 +5439,8 @@ snapshots:
|
||||
|
||||
json-schema-traverse@0.4.1: {}
|
||||
|
||||
json-schema-traverse@1.0.0: {}
|
||||
|
||||
jsonc-parser@3.2.1: {}
|
||||
|
||||
jsts@2.7.1: {}
|
||||
@@ -5312,6 +5455,10 @@ snapshots:
|
||||
|
||||
lines-and-columns@1.2.4: {}
|
||||
|
||||
linkify-it@5.0.0:
|
||||
dependencies:
|
||||
uc.micro: 2.1.0
|
||||
|
||||
loader-runner@4.3.0: {}
|
||||
|
||||
locate-path@5.0.0:
|
||||
@@ -5344,6 +5491,15 @@ snapshots:
|
||||
|
||||
markdown-extensions@1.1.1: {}
|
||||
|
||||
markdown-it@14.1.0:
|
||||
dependencies:
|
||||
argparse: 2.0.1
|
||||
entities: 4.5.0
|
||||
linkify-it: 5.0.0
|
||||
mdurl: 2.0.0
|
||||
punycode.js: 2.3.1
|
||||
uc.micro: 2.1.0
|
||||
|
||||
marked@4.3.0: {}
|
||||
|
||||
mdast-util-definitions@5.1.2:
|
||||
@@ -5534,6 +5690,8 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/mdast': 4.0.3
|
||||
|
||||
mdurl@2.0.0: {}
|
||||
|
||||
merge-stream@2.0.0: {}
|
||||
|
||||
merge2@1.4.1: {}
|
||||
@@ -5969,6 +6127,10 @@ snapshots:
|
||||
dependencies:
|
||||
brace-expansion: 2.0.1
|
||||
|
||||
minimatch@9.0.5:
|
||||
dependencies:
|
||||
brace-expansion: 2.0.1
|
||||
|
||||
minipass@7.1.0: {}
|
||||
|
||||
mri@1.2.0: {}
|
||||
@@ -6049,7 +6211,7 @@ snapshots:
|
||||
|
||||
node-releases@2.0.14: {}
|
||||
|
||||
node-releases@2.0.18: {}
|
||||
node-releases@2.0.19: {}
|
||||
|
||||
nopt@7.2.1:
|
||||
dependencies:
|
||||
@@ -6183,6 +6345,8 @@ snapshots:
|
||||
|
||||
proto-list@1.2.4: {}
|
||||
|
||||
punycode.js@2.3.1: {}
|
||||
|
||||
punycode@2.3.1: {}
|
||||
|
||||
qrcode@1.5.4:
|
||||
@@ -6263,6 +6427,8 @@ snapshots:
|
||||
|
||||
require-directory@2.1.1: {}
|
||||
|
||||
require-from-string@2.0.2: {}
|
||||
|
||||
require-main-filename@2.0.0: {}
|
||||
|
||||
resend@4.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
@@ -6304,6 +6470,13 @@ snapshots:
|
||||
ajv: 6.12.6
|
||||
ajv-keywords: 3.5.2(ajv@6.12.6)
|
||||
|
||||
schema-utils@4.3.0:
|
||||
dependencies:
|
||||
'@types/json-schema': 7.0.15
|
||||
ajv: 8.17.1
|
||||
ajv-formats: 2.1.1(ajv@8.17.1)
|
||||
ajv-keywords: 5.1.0(ajv@8.17.1)
|
||||
|
||||
selderee@0.11.0:
|
||||
dependencies:
|
||||
parseley: 0.12.1
|
||||
@@ -6334,6 +6507,16 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
shiki-twoslash@3.1.2(typescript@5.6.3):
|
||||
dependencies:
|
||||
'@typescript/twoslash': 3.1.0
|
||||
'@typescript/vfs': 1.3.4
|
||||
fenceparser: 1.1.1
|
||||
shiki: 0.10.1
|
||||
typescript: 5.6.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
shiki@0.10.1:
|
||||
dependencies:
|
||||
jsonc-parser: 3.2.1
|
||||
@@ -6461,16 +6644,16 @@ snapshots:
|
||||
|
||||
tapable@2.2.1: {}
|
||||
|
||||
terser-webpack-plugin@5.3.10(webpack@5.91.0):
|
||||
terser-webpack-plugin@5.3.12(webpack@5.91.0):
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
jest-worker: 27.5.1
|
||||
schema-utils: 3.3.0
|
||||
schema-utils: 4.3.0
|
||||
serialize-javascript: 6.0.2
|
||||
terser: 5.36.0
|
||||
terser: 5.39.0
|
||||
webpack: 5.91.0
|
||||
|
||||
terser@5.36.0:
|
||||
terser@5.39.0:
|
||||
dependencies:
|
||||
'@jridgewell/source-map': 0.3.6
|
||||
acorn: 8.14.0
|
||||
@@ -6515,8 +6698,21 @@ snapshots:
|
||||
shiki: 0.14.7
|
||||
typescript: 5.4.5
|
||||
|
||||
typedoc@0.27.9(typescript@5.6.3):
|
||||
dependencies:
|
||||
'@gerrit0/mini-shiki': 1.27.2
|
||||
lunr: 2.3.9
|
||||
markdown-it: 14.1.0
|
||||
minimatch: 9.0.5
|
||||
typescript: 5.6.3
|
||||
yaml: 2.7.0
|
||||
|
||||
typescript@5.4.5: {}
|
||||
|
||||
typescript@5.6.3: {}
|
||||
|
||||
uc.micro@2.1.0: {}
|
||||
|
||||
undici-types@5.26.5: {}
|
||||
|
||||
undici-types@6.19.8: {}
|
||||
@@ -6599,9 +6795,9 @@ snapshots:
|
||||
escalade: 3.1.2
|
||||
picocolors: 1.0.0
|
||||
|
||||
update-browserslist-db@1.1.1(browserslist@4.24.2):
|
||||
update-browserslist-db@1.1.3(browserslist@4.24.4):
|
||||
dependencies:
|
||||
browserslist: 4.24.2
|
||||
browserslist: 4.24.4
|
||||
escalade: 3.2.0
|
||||
picocolors: 1.1.1
|
||||
|
||||
@@ -6657,10 +6853,10 @@ snapshots:
|
||||
'@webassemblyjs/wasm-parser': 1.14.1
|
||||
acorn: 8.14.0
|
||||
acorn-import-assertions: 1.9.0(acorn@8.14.0)
|
||||
browserslist: 4.24.2
|
||||
browserslist: 4.24.4
|
||||
chrome-trace-event: 1.0.4
|
||||
enhanced-resolve: 5.17.1
|
||||
es-module-lexer: 1.5.4
|
||||
enhanced-resolve: 5.18.1
|
||||
es-module-lexer: 1.6.0
|
||||
eslint-scope: 5.1.1
|
||||
events: 3.3.0
|
||||
glob-to-regexp: 0.4.1
|
||||
@@ -6671,7 +6867,7 @@ snapshots:
|
||||
neo-async: 2.6.2
|
||||
schema-utils: 3.3.0
|
||||
tapable: 2.2.1
|
||||
terser-webpack-plugin: 5.3.10(webpack@5.91.0)
|
||||
terser-webpack-plugin: 5.3.12(webpack@5.91.0)
|
||||
watchpack: 2.4.2
|
||||
webpack-sources: 3.2.3
|
||||
transitivePeerDependencies:
|
||||
@@ -6707,6 +6903,8 @@ snapshots:
|
||||
|
||||
yaml@2.4.2: {}
|
||||
|
||||
yaml@2.7.0: {}
|
||||
|
||||
yargs-parser@18.1.3:
|
||||
dependencies:
|
||||
camelcase: 5.3.1
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { type DB as DatabaseT } from "@op-engineering/op-sqlite";
|
||||
import {
|
||||
import type { DB as DatabaseT } from "@op-engineering/op-sqlite";
|
||||
import type {
|
||||
CojsonInternalTypes,
|
||||
type OutgoingSyncQueue,
|
||||
OutgoingSyncQueue,
|
||||
RawCoID,
|
||||
SessionID,
|
||||
} from "cojson";
|
||||
@@ -29,14 +29,25 @@ export class SQLiteClient implements DBClientInterface {
|
||||
|
||||
if (!rows || rows.length === 0) return;
|
||||
|
||||
const coValueRow = rows[0] as any & { rowID: number };
|
||||
type DbCoValueRow = {
|
||||
id: string;
|
||||
header: string;
|
||||
rowID: number;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
const coValueRow = rows[0] as DbCoValueRow;
|
||||
try {
|
||||
const parsedHeader =
|
||||
coValueRow?.header &&
|
||||
(JSON.parse(coValueRow.header) as CojsonInternalTypes.CoValueHeader);
|
||||
coValueRow?.header && coValueRow.header.trim() !== ""
|
||||
? (JSON.parse(coValueRow.header) as CojsonInternalTypes.CoValueHeader)
|
||||
: undefined;
|
||||
|
||||
if (!parsedHeader) return undefined;
|
||||
|
||||
return {
|
||||
...coValueRow,
|
||||
id: coValueId,
|
||||
header: parsedHeader,
|
||||
};
|
||||
} catch (e) {
|
||||
@@ -76,10 +87,13 @@ export class SQLiteClient implements DBClientInterface {
|
||||
if (!rows || rows.length === 0) return [];
|
||||
|
||||
try {
|
||||
return rows.map((row: any) => ({
|
||||
...row,
|
||||
tx: JSON.parse(row.tx) as CojsonInternalTypes.Transaction,
|
||||
}));
|
||||
return rows.map((row) => {
|
||||
const rowData = row as { ses: number; idx: number; tx: string };
|
||||
return {
|
||||
...rowData,
|
||||
tx: JSON.parse(rowData.tx) as CojsonInternalTypes.Transaction,
|
||||
};
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn("Invalid JSON in transaction", e);
|
||||
return [];
|
||||
@@ -126,7 +140,7 @@ export class SQLiteClient implements DBClientInterface {
|
||||
sessionUpdate.sessionID,
|
||||
sessionUpdate.lastIdx,
|
||||
sessionUpdate.lastSignature,
|
||||
sessionUpdate.bytesSinceLastSignature!,
|
||||
sessionUpdate.bytesSinceLastSignature ?? 0,
|
||||
],
|
||||
);
|
||||
return rows[0]?.rowID as number;
|
||||
|
||||
@@ -113,7 +113,7 @@ export class SQLiteReactNative {
|
||||
);
|
||||
|
||||
await db.execute(
|
||||
`CREATE INDEX IF NOT EXISTS sessionsByCoValue ON sessions (coValue);`,
|
||||
"CREATE INDEX IF NOT EXISTS sessionsByCoValue ON sessions (coValue);",
|
||||
);
|
||||
|
||||
await db.execute(
|
||||
@@ -125,7 +125,7 @@ export class SQLiteReactNative {
|
||||
);
|
||||
|
||||
await db.execute(
|
||||
`CREATE INDEX IF NOT EXISTS coValuesByID ON coValues (id);`,
|
||||
"CREATE INDEX IF NOT EXISTS coValuesByID ON coValues (id);",
|
||||
);
|
||||
|
||||
await db.execute("PRAGMA user_version = 1");
|
||||
@@ -142,7 +142,7 @@ export class SQLiteReactNative {
|
||||
);
|
||||
|
||||
await db.execute(
|
||||
`ALTER TABLE sessions ADD COLUMN bytesSinceLastSignature INTEGER;`,
|
||||
"ALTER TABLE sessions ADD COLUMN bytesSinceLastSignature INTEGER;",
|
||||
);
|
||||
|
||||
await db.execute("PRAGMA user_version = 3");
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import { Database as DatabaseT } from "better-sqlite3";
|
||||
import type { Database as DatabaseT } from "better-sqlite3";
|
||||
import {
|
||||
CojsonInternalTypes,
|
||||
OutgoingSyncQueue,
|
||||
SessionID,
|
||||
type CojsonInternalTypes,
|
||||
type OutgoingSyncQueue,
|
||||
type SessionID,
|
||||
logger,
|
||||
} from "cojson";
|
||||
import RawCoID = CojsonInternalTypes.RawCoID;
|
||||
import Signature = CojsonInternalTypes.Signature;
|
||||
import Transaction = CojsonInternalTypes.Transaction;
|
||||
import {
|
||||
import type {
|
||||
DBClientInterface,
|
||||
SessionRow,
|
||||
SignatureAfterRow,
|
||||
@@ -17,6 +14,10 @@ import {
|
||||
TransactionRow,
|
||||
} from "cojson-storage";
|
||||
|
||||
type RawCoID = CojsonInternalTypes.RawCoID;
|
||||
type Signature = CojsonInternalTypes.Signature;
|
||||
type Transaction = CojsonInternalTypes.Transaction;
|
||||
|
||||
export type RawCoValueRow = {
|
||||
id: CojsonInternalTypes.RawCoID;
|
||||
header: string;
|
||||
@@ -43,7 +44,7 @@ export class SQLiteClient implements DBClientInterface {
|
||||
|
||||
getCoValue(coValueId: RawCoID): StoredCoValueRow | undefined {
|
||||
const coValueRow = this.db
|
||||
.prepare(`SELECT * FROM coValues WHERE id = ?`)
|
||||
.prepare("SELECT * FROM coValues WHERE id = ?")
|
||||
.get(coValueId) as RawCoValueRow & { rowID: number };
|
||||
|
||||
if (!coValueRow) return;
|
||||
@@ -58,7 +59,7 @@ export class SQLiteClient implements DBClientInterface {
|
||||
};
|
||||
} catch (e) {
|
||||
const headerValue = coValueRow?.header ?? "";
|
||||
logger.warn("Invalid JSON in header: " + headerValue, {
|
||||
logger.warn(`Invalid JSON in header: ${headerValue}`, {
|
||||
id: coValueId,
|
||||
});
|
||||
return;
|
||||
@@ -67,7 +68,7 @@ export class SQLiteClient implements DBClientInterface {
|
||||
|
||||
getCoValueSessions(coValueRowId: number): StoredSessionRow[] {
|
||||
return this.db
|
||||
.prepare<number>(`SELECT * FROM sessions WHERE coValue = ?`)
|
||||
.prepare<number>("SELECT * FROM sessions WHERE coValue = ?")
|
||||
.all(coValueRowId) as StoredSessionRow[];
|
||||
}
|
||||
|
||||
@@ -77,7 +78,7 @@ export class SQLiteClient implements DBClientInterface {
|
||||
): StoredSessionRow | undefined {
|
||||
return this.db
|
||||
.prepare<[number, string]>(
|
||||
`SELECT * FROM sessions WHERE coValue = ? AND sessionID = ?`,
|
||||
"SELECT * FROM sessions WHERE coValue = ? AND sessionID = ?",
|
||||
)
|
||||
.get(coValueRowId, sessionID) as StoredSessionRow | undefined;
|
||||
}
|
||||
@@ -88,7 +89,7 @@ export class SQLiteClient implements DBClientInterface {
|
||||
): TransactionRow[] {
|
||||
const txs = this.db
|
||||
.prepare<[number, number]>(
|
||||
`SELECT * FROM transactions WHERE ses = ? AND idx >= ?`,
|
||||
"SELECT * FROM transactions WHERE ses = ? AND idx >= ?",
|
||||
)
|
||||
.all(sessionRowId, firstNewTxIdx) as RawTransactionRow[];
|
||||
|
||||
@@ -109,7 +110,7 @@ export class SQLiteClient implements DBClientInterface {
|
||||
): SignatureAfterRow[] {
|
||||
return this.db
|
||||
.prepare<[number, number]>(
|
||||
`SELECT * FROM signatureAfter WHERE ses = ? AND idx >= ?`,
|
||||
"SELECT * FROM signatureAfter WHERE ses = ? AND idx >= ?",
|
||||
)
|
||||
.all(sessionRowId, firstNewTxIdx) as SignatureAfterRow[];
|
||||
}
|
||||
@@ -117,7 +118,7 @@ export class SQLiteClient implements DBClientInterface {
|
||||
addCoValue(msg: CojsonInternalTypes.NewContentMessage): number {
|
||||
return this.db
|
||||
.prepare<[CojsonInternalTypes.RawCoID, string]>(
|
||||
`INSERT INTO coValues (id, header) VALUES (?, ?)`,
|
||||
"INSERT INTO coValues (id, header) VALUES (?, ?)",
|
||||
)
|
||||
.run(msg.id, JSON.stringify(msg.header)).lastInsertRowid as number;
|
||||
}
|
||||
@@ -153,7 +154,7 @@ export class SQLiteClient implements DBClientInterface {
|
||||
) {
|
||||
this.db
|
||||
.prepare<[number, number, string]>(
|
||||
`INSERT INTO transactions (ses, idx, tx) VALUES (?, ?, ?)`,
|
||||
"INSERT INTO transactions (ses, idx, tx) VALUES (?, ?, ?)",
|
||||
)
|
||||
.run(sessionRowID, nextIdx, JSON.stringify(newTransaction));
|
||||
}
|
||||
@@ -165,12 +166,13 @@ export class SQLiteClient implements DBClientInterface {
|
||||
}: { sessionRowID: number; idx: number; signature: Signature }) {
|
||||
this.db
|
||||
.prepare<[number, number, string]>(
|
||||
`INSERT INTO signatureAfter (ses, idx, signature) VALUES (?, ?, ?)`,
|
||||
"INSERT INTO signatureAfter (ses, idx, signature) VALUES (?, ?, ?)",
|
||||
)
|
||||
.run(sessionRowID, idx, signature);
|
||||
}
|
||||
|
||||
transaction(operationsCallback: () => unknown) {
|
||||
this.db.transaction(operationsCallback)();
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import Database, { Database as DatabaseT } from "better-sqlite3";
|
||||
import Database, { type Database as DatabaseT } from "better-sqlite3";
|
||||
import {
|
||||
IncomingSyncStream,
|
||||
OutgoingSyncQueue,
|
||||
Peer,
|
||||
type IncomingSyncStream,
|
||||
type OutgoingSyncQueue,
|
||||
type Peer,
|
||||
cojsonInternals,
|
||||
logger,
|
||||
} from "cojson";
|
||||
import { SyncManager, TransactionRow } from "cojson-storage";
|
||||
import { SyncManager, type TransactionRow } from "cojson-storage";
|
||||
import { SQLiteClient } from "./sqliteClient.js";
|
||||
|
||||
export class SQLiteNode {
|
||||
@@ -46,7 +46,7 @@ export class SQLiteNode {
|
||||
msg,
|
||||
(k, v) =>
|
||||
k === "changes" || k === "encryptedChanges"
|
||||
? v.slice(0, 20) + "..."
|
||||
? `${v.slice(0, 20)}...`
|
||||
: v,
|
||||
)}`,
|
||||
);
|
||||
@@ -117,7 +117,7 @@ export class SQLiteNode {
|
||||
).run();
|
||||
|
||||
db.prepare(
|
||||
`CREATE INDEX IF NOT EXISTS sessionsByCoValue ON sessions (coValue);`,
|
||||
"CREATE INDEX IF NOT EXISTS sessionsByCoValue ON sessions (coValue);",
|
||||
).run();
|
||||
|
||||
db.prepare(
|
||||
@@ -129,7 +129,7 @@ export class SQLiteNode {
|
||||
).run();
|
||||
|
||||
db.prepare(
|
||||
`CREATE INDEX IF NOT EXISTS coValuesByID ON coValues (id);`,
|
||||
"CREATE INDEX IF NOT EXISTS coValuesByID ON coValues (id);",
|
||||
).run();
|
||||
|
||||
db.pragma("user_version = 1");
|
||||
@@ -138,17 +138,17 @@ export class SQLiteNode {
|
||||
if (oldVersion <= 1) {
|
||||
// fix embarrassing off-by-one error for transaction indices
|
||||
const txs = db
|
||||
.prepare(`SELECT * FROM transactions`)
|
||||
.prepare("SELECT * FROM transactions")
|
||||
.all() as TransactionRow[];
|
||||
|
||||
for (const tx of txs) {
|
||||
db.prepare(`DELETE FROM transactions WHERE ses = ? AND idx = ?`).run(
|
||||
db.prepare("DELETE FROM transactions WHERE ses = ? AND idx = ?").run(
|
||||
tx.ses,
|
||||
tx.idx,
|
||||
);
|
||||
tx.idx -= 1;
|
||||
db.prepare(
|
||||
`INSERT INTO transactions (ses, idx, tx) VALUES (?, ?, ?)`,
|
||||
"INSERT INTO transactions (ses, idx, tx) VALUES (?, ?, ?)",
|
||||
).run(tx.ses, tx.idx, tx.tx);
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ export class SQLiteNode {
|
||||
).run();
|
||||
|
||||
db.prepare(
|
||||
`ALTER TABLE sessions ADD COLUMN bytesSinceLastSignature INTEGER;`,
|
||||
"ALTER TABLE sessions ADD COLUMN bytesSinceLastSignature INTEGER;",
|
||||
).run();
|
||||
|
||||
db.pragma("user_version = 3");
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import {
|
||||
CojsonInternalTypes,
|
||||
MAX_RECOMMENDED_TX_SIZE,
|
||||
OutgoingSyncQueue,
|
||||
SessionID,
|
||||
SyncMessage,
|
||||
type OutgoingSyncQueue,
|
||||
type SessionID,
|
||||
type SyncMessage,
|
||||
cojsonInternals,
|
||||
emptyKnownState,
|
||||
logger,
|
||||
} from "cojson";
|
||||
import { collectNewTxs, getDependedOnCoValues } from "./syncUtils.js";
|
||||
import { DBClientInterface, StoredSessionRow } from "./types.js";
|
||||
import type { DBClientInterface, StoredSessionRow } from "./types.js";
|
||||
import NewContentMessage = CojsonInternalTypes.NewContentMessage;
|
||||
import KnownStateMessage = CojsonInternalTypes.KnownStateMessage;
|
||||
import RawCoID = CojsonInternalTypes.RawCoID;
|
||||
@@ -82,12 +82,15 @@ export class SyncManager {
|
||||
|
||||
// reverse it to send the top level id the last in the order
|
||||
const collectedMessages = Object.values(outputMessages).reverse();
|
||||
collectedMessages.forEach(({ knownMessage, contentMessages }) => {
|
||||
for (const { knownMessage, contentMessages } of collectedMessages) {
|
||||
this.sendStateMessage(knownMessage);
|
||||
|
||||
contentMessages?.length &&
|
||||
contentMessages.forEach((msg) => this.sendStateMessage(msg));
|
||||
});
|
||||
if (contentMessages?.length) {
|
||||
for (const msg of contentMessages) {
|
||||
this.sendStateMessage(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async collectCoValueData(
|
||||
@@ -106,7 +109,9 @@ export class SyncManager {
|
||||
action: "known",
|
||||
...emptyKnownState(peerKnownState.id),
|
||||
};
|
||||
asDependencyOf && (emptyKnownMessage.asDependencyOf = asDependencyOf);
|
||||
if (asDependencyOf) {
|
||||
emptyKnownMessage.asDependencyOf = asDependencyOf;
|
||||
}
|
||||
messageMap[peerKnownState.id] = { knownMessage: emptyKnownMessage };
|
||||
return messageMap;
|
||||
}
|
||||
@@ -153,7 +158,9 @@ export class SyncManager {
|
||||
action: "known",
|
||||
...newCoValueKnownState,
|
||||
};
|
||||
asDependencyOf && (knownMessage.asDependencyOf = asDependencyOf);
|
||||
if (asDependencyOf) {
|
||||
knownMessage.asDependencyOf = asDependencyOf;
|
||||
}
|
||||
messageMap[newCoValueKnownState.id] = {
|
||||
knownMessage: knownMessage,
|
||||
contentMessages: newContentMessages,
|
||||
@@ -272,11 +279,13 @@ export class SyncManager {
|
||||
|
||||
const nextIdx = sessionRow?.lastIdx || 0;
|
||||
|
||||
if (!msg.new[sessionID]) throw new Error("Session ID not found");
|
||||
|
||||
const sessionUpdate = {
|
||||
coValue: storedCoValueRowID,
|
||||
sessionID,
|
||||
lastIdx: newLastIdx,
|
||||
lastSignature: msg.new[sessionID]!.lastSignature,
|
||||
lastSignature: msg.new[sessionID].lastSignature,
|
||||
bytesSinceLastSignature: newBytesSinceLastSignature,
|
||||
};
|
||||
|
||||
@@ -289,7 +298,7 @@ export class SyncManager {
|
||||
await this.dbClient.addSignatureAfter({
|
||||
sessionRowID,
|
||||
idx: newLastIdx - 1,
|
||||
signature: msg.new[sessionID]!.lastSignature,
|
||||
signature: msg.new[sessionID].lastSignature,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -306,7 +315,11 @@ export class SyncManager {
|
||||
|
||||
handleDone(_msg: CojsonInternalTypes.DoneMessage) {}
|
||||
|
||||
async sendStateMessage(msg: any): Promise<unknown> {
|
||||
async sendStateMessage(
|
||||
msg:
|
||||
| CojsonInternalTypes.KnownStateMessage
|
||||
| CojsonInternalTypes.NewContentMessage,
|
||||
): Promise<unknown> {
|
||||
return this.toLocalNode
|
||||
.push(msg)
|
||||
.catch((e) =>
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import {
|
||||
CojsonInternalTypes,
|
||||
JsonValue,
|
||||
SessionID,
|
||||
Stringified,
|
||||
type CojsonInternalTypes,
|
||||
type JsonValue,
|
||||
type SessionID,
|
||||
type Stringified,
|
||||
cojsonInternals,
|
||||
} from "cojson";
|
||||
import { StoredCoValueRow, StoredSessionRow, TransactionRow } from "./types.js";
|
||||
import type {
|
||||
StoredCoValueRow,
|
||||
StoredSessionRow,
|
||||
TransactionRow,
|
||||
} from "./types.js";
|
||||
|
||||
export function collectNewTxs({
|
||||
newTxsInSession,
|
||||
@@ -19,19 +23,17 @@ export function collectNewTxs({
|
||||
firstNewTxIdx: number;
|
||||
}) {
|
||||
for (const tx of newTxsInSession) {
|
||||
let sessionEntry =
|
||||
newContentMessages[newContentMessages.length - 1]!.new[
|
||||
sessionRow.sessionID
|
||||
];
|
||||
const lastMessage = newContentMessages[newContentMessages.length - 1];
|
||||
if (!lastMessage) return;
|
||||
|
||||
let sessionEntry = lastMessage.new[sessionRow.sessionID];
|
||||
if (!sessionEntry) {
|
||||
sessionEntry = {
|
||||
after: firstNewTxIdx,
|
||||
lastSignature: "WILL_BE_REPLACED" as CojsonInternalTypes.Signature,
|
||||
newTransactions: [],
|
||||
};
|
||||
newContentMessages[newContentMessages.length - 1]!.new[
|
||||
sessionRow.sessionID
|
||||
] = sessionEntry;
|
||||
lastMessage.new[sessionRow.sessionID] = sessionEntry;
|
||||
}
|
||||
|
||||
sessionEntry.newTransactions.push(tx.tx);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
Mocked,
|
||||
type Mocked,
|
||||
afterEach,
|
||||
beforeEach,
|
||||
describe,
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
vi,
|
||||
} from "vitest";
|
||||
|
||||
import {
|
||||
import type {
|
||||
CojsonInternalTypes,
|
||||
OutgoingSyncQueue,
|
||||
SessionID,
|
||||
@@ -16,11 +16,12 @@ import {
|
||||
} from "cojson";
|
||||
import { SyncManager } from "../syncManager.js";
|
||||
import { getDependedOnCoValues } from "../syncUtils.js";
|
||||
import { DBClientInterface } from "../types.js";
|
||||
import type { DBClientInterface } from "../types.js";
|
||||
import { fixtures } from "./fixtureMessages.js";
|
||||
import RawCoID = CojsonInternalTypes.RawCoID;
|
||||
import NewContentMessage = CojsonInternalTypes.NewContentMessage;
|
||||
|
||||
type RawCoID = CojsonInternalTypes.RawCoID;
|
||||
type NewContentMessage = CojsonInternalTypes.NewContentMessage;
|
||||
type Transaction = CojsonInternalTypes.Transaction;
|
||||
vi.mock("../syncUtils");
|
||||
|
||||
const coValueIdToLoad = "co_zKwG8NyfZ8GXqcjDHY4NS3SbU2m";
|
||||
@@ -40,7 +41,7 @@ const incomingContentMessage = fixtures[coValueIdToLoad].getContent({
|
||||
|
||||
describe("DB sync manager", () => {
|
||||
let syncManager: SyncManager;
|
||||
let queue: OutgoingSyncQueue = {} as unknown as OutgoingSyncQueue;
|
||||
const queue: OutgoingSyncQueue = {} as unknown as OutgoingSyncQueue;
|
||||
|
||||
const DBClient = vi.fn();
|
||||
DBClient.prototype.getCoValue = vi.fn();
|
||||
@@ -154,11 +155,11 @@ describe("DB sync manager", () => {
|
||||
header: true,
|
||||
id: coValueIdToLoad,
|
||||
sessions: sessionsData.reduce(
|
||||
(acc, sessionRow) => ({
|
||||
...acc,
|
||||
[sessionRow.sessionID]: sessionRow.lastIdx,
|
||||
}),
|
||||
{},
|
||||
(acc, sessionRow) => {
|
||||
acc[sessionRow.sessionID] = sessionRow.lastIdx;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, number>,
|
||||
),
|
||||
});
|
||||
|
||||
@@ -167,15 +168,22 @@ describe("DB sync manager", () => {
|
||||
header: coValueHeader,
|
||||
id: coValueIdToLoad,
|
||||
new: sessionsData.reduce(
|
||||
(acc, sessionRow) => ({
|
||||
...acc,
|
||||
[sessionRow.sessionID]: {
|
||||
(acc, sessionRow) => {
|
||||
acc[sessionRow.sessionID] = {
|
||||
after: expect.any(Number),
|
||||
lastSignature: expect.any(String),
|
||||
newTransactions: expect.any(Array),
|
||||
},
|
||||
}),
|
||||
{},
|
||||
};
|
||||
return acc;
|
||||
},
|
||||
{} as Record<
|
||||
string,
|
||||
{
|
||||
after: number;
|
||||
lastSignature: string;
|
||||
newTransactions: Transaction[];
|
||||
}
|
||||
>,
|
||||
),
|
||||
priority: 0,
|
||||
});
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { CojsonInternalTypes, SessionID } from "cojson";
|
||||
import RawCoID = CojsonInternalTypes.RawCoID;
|
||||
import Transaction = CojsonInternalTypes.Transaction;
|
||||
import Signature = CojsonInternalTypes.Signature;
|
||||
import type { CojsonInternalTypes, SessionID } from "cojson";
|
||||
|
||||
type RawCoID = CojsonInternalTypes.RawCoID;
|
||||
type Transaction = CojsonInternalTypes.Transaction;
|
||||
type Signature = CojsonInternalTypes.Signature;
|
||||
|
||||
export type CoValueRow = {
|
||||
id: CojsonInternalTypes.RawCoID;
|
||||
@@ -72,7 +73,7 @@ export interface DBClientInterface {
|
||||
sessionRowID: number,
|
||||
idx: number,
|
||||
newTransaction: Transaction,
|
||||
): Promise<number> | void | unknown;
|
||||
): Promise<number> | undefined | unknown;
|
||||
|
||||
addSignatureAfter({
|
||||
sessionRowID,
|
||||
@@ -82,7 +83,7 @@ export interface DBClientInterface {
|
||||
sessionRowID: number;
|
||||
idx: number;
|
||||
signature: Signature;
|
||||
}): Promise<number> | void | unknown;
|
||||
}): Promise<number> | undefined | unknown;
|
||||
|
||||
transaction(callback: () => unknown): Promise<unknown> | void;
|
||||
transaction(callback: () => unknown): Promise<unknown> | undefined;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { SyncMessage } from "cojson";
|
||||
import type { SyncMessage } from "cojson";
|
||||
import { addMessageToBacklog } from "./serialization.js";
|
||||
|
||||
export const MAX_OUTGOING_MESSAGES_CHUNK_BYTES = 25_000;
|
||||
|
||||
export class BatchedOutgoingMessages {
|
||||
private backlog: string = "";
|
||||
private backlog = "";
|
||||
private timeout: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
constructor(private send: (messages: string) => void) {}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Peer, logger } from "cojson";
|
||||
import { type Peer, logger } from "cojson";
|
||||
import { createWebSocketPeer } from "./createWebSocketPeer.js";
|
||||
|
||||
export class WebSocketPeerWithReconnection {
|
||||
@@ -61,7 +61,7 @@ export class WebSocketPeerWithReconnection {
|
||||
const timeout = this.reconnectionTimeout * this.reconnectionAttempts;
|
||||
|
||||
logger.debug(
|
||||
"Websocket disconnected, trying to reconnect in " + timeout + "ms",
|
||||
`Websocket disconnected, trying to reconnect in ${timeout}ms`,
|
||||
);
|
||||
|
||||
await this.waitForOnline(timeout);
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import {
|
||||
DisconnectedError,
|
||||
Peer,
|
||||
PingTimeoutError,
|
||||
SyncMessage,
|
||||
type DisconnectedError,
|
||||
type Peer,
|
||||
type PingTimeoutError,
|
||||
type SyncMessage,
|
||||
cojsonInternals,
|
||||
logger,
|
||||
} from "cojson";
|
||||
import { BatchedOutgoingMessages } from "./BatchedOutgoingMessages.js";
|
||||
import { deserializeMessages, getErrorMessage } from "./serialization.js";
|
||||
import { AnyWebSocket } from "./types.js";
|
||||
import type { AnyWebSocket } from "./types.js";
|
||||
|
||||
export const BUFFER_LIMIT = 100_000;
|
||||
export const BUFFER_LIMIT_POLLING_INTERVAL = 10;
|
||||
@@ -52,7 +52,7 @@ function waitForWebSocketOpen(websocket: AnyWebSocket) {
|
||||
if (websocket.readyState === 1) {
|
||||
resolve();
|
||||
} else {
|
||||
websocket.addEventListener("open", resolve, { once: true });
|
||||
websocket.addEventListener("open", () => resolve(), { once: true });
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -144,6 +144,8 @@ export function createWebSocketPeer({
|
||||
}
|
||||
|
||||
websocket.addEventListener("close", handleClose);
|
||||
// TODO (#1537): Remove this any once the WebSocket error event type is fixed
|
||||
// biome-ignore lint/suspicious/noExplicitAny: WebSocket error event type
|
||||
websocket.addEventListener("error" as any, (err) => {
|
||||
if (err.message) {
|
||||
logger.warn(err.message);
|
||||
@@ -174,7 +176,7 @@ export function createWebSocketPeer({
|
||||
|
||||
if (!result.ok) {
|
||||
logger.warn(
|
||||
"Error while deserializing messages: " + getErrorMessage(result.error),
|
||||
`Error while deserializing messages: ${getErrorMessage(result.error)}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -227,7 +229,7 @@ export function createWebSocketPeer({
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
} else if (websocket.readyState == 1) {
|
||||
} else if (websocket.readyState === 1) {
|
||||
websocket.close();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { SyncMessage, logger } from "cojson";
|
||||
import { PingMsg } from "./types.js";
|
||||
import { type SyncMessage, logger } from "cojson";
|
||||
import type { PingMsg } from "./types.js";
|
||||
|
||||
export function getErrorMessage(error: unknown) {
|
||||
return error instanceof Error ? error.message : "Unknown error";
|
||||
@@ -28,7 +28,7 @@ export function deserializeMessages(messages: unknown) {
|
||||
| PingMsg[],
|
||||
} as const;
|
||||
} catch (e) {
|
||||
logger.error("Error while deserializing messages: " + getErrorMessage(e));
|
||||
logger.error(`Error while deserializing messages: ${getErrorMessage(e)}`);
|
||||
return {
|
||||
ok: false,
|
||||
error: e,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user