Compare commits
17 Commits
fix/existi
...
benjamin-j
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
327a7315b7 | ||
|
|
2bf28170f7 | ||
|
|
e472d2c513 | ||
|
|
64e0aaba88 | ||
|
|
27b0d75338 | ||
|
|
d6175fb936 | ||
|
|
8311a61f3f | ||
|
|
2e3b10c5f6 | ||
|
|
2dfe2158fd | ||
|
|
2a4f9da62a | ||
|
|
67b1a6ba66 | ||
|
|
0609388899 | ||
|
|
b12f8e3f26 | ||
|
|
892ca34443 | ||
|
|
3533707794 | ||
|
|
2f848a8d83 | ||
|
|
eae493b9d3 |
5
.changeset/tidy-foxes-carry.md
Normal file
5
.changeset/tidy-foxes-carry.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"jazz-browser": patch
|
||||
---
|
||||
|
||||
Persist PasskeyAuth credentials on reload
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -15,4 +15,6 @@ coverage
|
||||
# Playwright
|
||||
test-results
|
||||
|
||||
.husky
|
||||
.husky
|
||||
|
||||
.vscode/settings.json
|
||||
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
}
|
||||
@@ -20,7 +20,7 @@ export default defineConfig({
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
baseURL: "http://localhost:5173/?peer=ws://localhost:1234",
|
||||
baseURL: "http://localhost:5173/",
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: "on-first-retry",
|
||||
@@ -42,10 +42,5 @@ export default defineConfig({
|
||||
url: "http://localhost:5173/",
|
||||
reuseExistingServer: !isCI,
|
||||
},
|
||||
{
|
||||
command: "pnpm sync --in-memory --port 1234",
|
||||
url: "http://localhost:1234/health",
|
||||
reuseExistingServer: !isCI,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Button } from "@/components/Button.tsx";
|
||||
import { useAccount, useCoState } from "@/main.tsx";
|
||||
import { useAcceptInvite, useAccount, useCoState } from "@/main.tsx";
|
||||
import { EmployeeList } from "@/pages/EmployeeList.tsx";
|
||||
import { EmployeeOnboading } from "@/pages/EmployeeOnboarding.tsx";
|
||||
import { NewEmployee } from "@/pages/NewEmployee.tsx";
|
||||
@@ -8,7 +8,7 @@ import { ID } from "jazz-tools";
|
||||
import { useEffect } from "react";
|
||||
import {
|
||||
RouterProvider,
|
||||
createBrowserRouter,
|
||||
createHashRouter,
|
||||
useNavigate,
|
||||
useParams,
|
||||
} from "react-router-dom";
|
||||
@@ -36,11 +36,24 @@ function ImportEmployee({
|
||||
return <div>Importing Employee ${employeeCoId} ...</div>;
|
||||
}
|
||||
|
||||
function AcceptInvite() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
useAcceptInvite({
|
||||
invitedObjectSchema: CoEmployee,
|
||||
onAccept: (employeeCoId) => {
|
||||
navigate(`/import/${employeeCoId}`);
|
||||
},
|
||||
});
|
||||
|
||||
return <p>Accepting invite...</p>;
|
||||
}
|
||||
|
||||
function App() {
|
||||
const { me, logOut } = useAccount();
|
||||
const employeeCoListId = me.profile?._refs.employees.id;
|
||||
|
||||
const router = createBrowserRouter([
|
||||
const router = createHashRouter([
|
||||
{
|
||||
path: "/",
|
||||
element: <EmployeeList employeeListCoId={employeeCoListId} />,
|
||||
@@ -57,6 +70,10 @@ function App() {
|
||||
path: "/import/:employeeCoId",
|
||||
element: <ImportEmployee employeeListCoId={employeeCoListId} />,
|
||||
},
|
||||
{
|
||||
path: "/invite/*",
|
||||
element: <AcceptInvite />,
|
||||
},
|
||||
]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -10,15 +10,17 @@ const Jazz = createJazzReactApp({
|
||||
});
|
||||
export const { useAccount, useCoState, useAcceptInvite } = Jazz;
|
||||
|
||||
const peer =
|
||||
(new URL(window.location.href).searchParams.get(
|
||||
"peer",
|
||||
) as `ws://${string}`) ??
|
||||
"wss://cloud.jazz.tools/?key=onboarding-example-jazz@gcmp.io";
|
||||
|
||||
function JazzAndAuth({ children }: { children: React.ReactNode }) {
|
||||
const [auth, authState] = useDemoAuth();
|
||||
return (
|
||||
<>
|
||||
<Jazz.Provider
|
||||
auth={auth}
|
||||
// replace `you@example.com` with your email as a temporary API key
|
||||
peer="wss://cloud.jazz.tools/?key=you@example.com"
|
||||
>
|
||||
<Jazz.Provider auth={auth} peer={peer}>
|
||||
{children}
|
||||
</Jazz.Provider>
|
||||
{authState.state !== "signedIn" && (
|
||||
|
||||
@@ -2,13 +2,12 @@ import { Button } from "@/components/Button.tsx";
|
||||
import { NavigateBack } from "@/components/NavigateBack.tsx";
|
||||
import { Stack } from "@/components/Stack.tsx";
|
||||
import { TextInput } from "@/components/TextInput.tsx";
|
||||
import { useAcceptInvite, useCoState } from "@/main.tsx";
|
||||
import { useCoState } from "@/main.tsx";
|
||||
import { createImage } from "jazz-browser-media-images";
|
||||
import { ProgressiveImg, createInviteLink } from "jazz-react";
|
||||
import { CoMap, ID } from "jazz-tools";
|
||||
import { ChangeEvent, ReactNode, useCallback } from "react";
|
||||
import { useParams } from "react-router";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import {
|
||||
CoDocUploadStep,
|
||||
CoEmployee,
|
||||
@@ -173,17 +172,9 @@ const ConfirmationCard = ({
|
||||
|
||||
export function EmployeeOnboading() {
|
||||
const { employeeCoId } = useParams();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const employee = useCoState(CoEmployee, employeeCoId as ID<CoEmployee>, {});
|
||||
|
||||
useAcceptInvite({
|
||||
invitedObjectSchema: CoEmployee,
|
||||
onAccept: (employeeCoId) => {
|
||||
navigate(`/import/${employeeCoId}`);
|
||||
},
|
||||
});
|
||||
|
||||
const handleInviteLinkCreation = useCallback(
|
||||
(role: "reader" | "writer") => {
|
||||
if (!employee) return;
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
import { Page, expect, test } from "@playwright/test";
|
||||
import {
|
||||
Browser,
|
||||
BrowserContext,
|
||||
Page,
|
||||
chromium,
|
||||
expect,
|
||||
test,
|
||||
} from "@playwright/test";
|
||||
import { EmployeeOnboardingPage } from "./pages/EmployeeOnboardingPage";
|
||||
import { HomePage } from "./pages/HomePage";
|
||||
import { LoginPage } from "./pages/LoginPage";
|
||||
@@ -29,60 +36,82 @@ const login = async ({
|
||||
};
|
||||
|
||||
test.describe("Admin onboarding flow", () => {
|
||||
test("Create and delete flow", async ({ page }) => {
|
||||
await login({ page, userName: "HR specialist" });
|
||||
let browser: Browser;
|
||||
let adminContext: BrowserContext;
|
||||
let writerContext: BrowserContext;
|
||||
|
||||
const homePage = new HomePage(page);
|
||||
await homePage.createEmployee("Paul");
|
||||
await homePage.createEmployee("Sean");
|
||||
await homePage.expectEmployee(["Sean", "admin"]);
|
||||
await homePage.expectEmployee(["Paul", "admin"]);
|
||||
await homePage.deleteEmployee("Sean");
|
||||
await homePage.expectEmployeeDeleted("Sean");
|
||||
test.beforeAll(async () => {
|
||||
browser = await chromium.launch();
|
||||
adminContext = await browser.newContext();
|
||||
writerContext = await browser.newContext();
|
||||
});
|
||||
|
||||
test("Onboard flow", async ({ page }) => {
|
||||
test.afterAll(async () => {
|
||||
await adminContext.close();
|
||||
await writerContext.close();
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
test("Create and delete flow", async () => {
|
||||
const adminPage = await adminContext.newPage();
|
||||
await login({ page: adminPage, userName: "HR specialist" });
|
||||
const adminHomePage = new HomePage(adminPage);
|
||||
await adminHomePage.createEmployee("Paul");
|
||||
await adminHomePage.createEmployee("Sean");
|
||||
await adminHomePage.expectEmployee(["Sean", "admin"]);
|
||||
await adminHomePage.expectEmployee(["Paul", "admin"]);
|
||||
await adminHomePage.deleteEmployee("Sean");
|
||||
await adminHomePage.expectEmployeeDeleted("Sean");
|
||||
|
||||
await adminPage.close();
|
||||
});
|
||||
|
||||
test("Onboard flow", async () => {
|
||||
const adminPage = await adminContext.newPage();
|
||||
const writerPage = await writerContext.newPage();
|
||||
|
||||
const adminUser = "HR specialist";
|
||||
const writerUser = "Invitee";
|
||||
await login({ page, userName: adminUser });
|
||||
await login({ page: adminPage, userName: adminUser });
|
||||
await login({ page: writerPage, userName: writerUser });
|
||||
|
||||
const homePage = new HomePage(page);
|
||||
await homePage.createEmployee("Paul");
|
||||
await homePage.expectEmployee(["Paul", "admin"]);
|
||||
await homePage.navigateToEmployeeOnboardingPage("Paul");
|
||||
const onboardingPage = new EmployeeOnboardingPage(page);
|
||||
const adminHomePage = new HomePage(adminPage);
|
||||
await adminHomePage.createEmployee("Paul");
|
||||
await adminHomePage.expectEmployee(["Paul", "admin"]);
|
||||
await adminHomePage.navigateToEmployeeOnboardingPage("Paul");
|
||||
const adminOnboardingPage = new EmployeeOnboardingPage(adminPage);
|
||||
|
||||
// create invitation
|
||||
const invitation = await onboardingPage.getShareLink();
|
||||
await onboardingPage.logout();
|
||||
const invitation = await adminOnboardingPage.getShareLink();
|
||||
|
||||
// Wait for the invitation to be synced
|
||||
await writerPage.waitForTimeout(3000);
|
||||
|
||||
//fill out by invitee (writer)
|
||||
await login({ page, userName: writerUser });
|
||||
await page.goto(invitation);
|
||||
await page.waitForTimeout(1000);
|
||||
await homePage.expectEmployee(["Paul", "write"]);
|
||||
await homePage.navigateToEmployeeOnboardingPage("Paul");
|
||||
await onboardingPage.expectEmployeeName("Paul");
|
||||
await onboardingPage.fillPersonalDetailsCardAndSave(
|
||||
await writerPage.goto(invitation);
|
||||
|
||||
const writerHomePage = new HomePage(writerPage);
|
||||
await writerHomePage.expectEmployee(["Paul", "write"]);
|
||||
await writerHomePage.navigateToEmployeeOnboardingPage("Paul");
|
||||
const writerOnboardingPage = new EmployeeOnboardingPage(writerPage);
|
||||
await writerOnboardingPage.expectEmployeeName("Paul");
|
||||
await writerOnboardingPage.fillPersonalDetailsCardAndSave(
|
||||
"123-45-6789",
|
||||
"123 Elm Street",
|
||||
);
|
||||
await onboardingPage.fillUploadCardAndSave(
|
||||
await writerOnboardingPage.fillUploadCardAndSave(
|
||||
"./public/jazz-logo-low-res.jpg",
|
||||
);
|
||||
|
||||
// invitee cannot confirm the onboarding completion
|
||||
expect(onboardingPage.finalConfirmationButton.isDisabled()).toBeTruthy();
|
||||
expect(
|
||||
writerOnboardingPage.finalConfirmationButton.isDisabled(),
|
||||
).toBeTruthy();
|
||||
|
||||
// final confirmation step by admin
|
||||
await onboardingPage.logout();
|
||||
await login({ page, userName: adminUser, loginAs: true });
|
||||
|
||||
await homePage.expectEmployee(["Paul", "admin"]);
|
||||
await homePage.navigateToEmployeeOnboardingPage("Paul");
|
||||
await scrollToBottom(page);
|
||||
await onboardingPage.finalConfirmationButton.click();
|
||||
await onboardingPage.backButton.click();
|
||||
await homePage.expectOnboardingCompleteForEmployee("Paul");
|
||||
await scrollToBottom(adminPage);
|
||||
await adminOnboardingPage.finalConfirmationButton.click();
|
||||
await adminOnboardingPage.backButton.click();
|
||||
await adminHomePage.expectOnboardingCompleteForEmployee("Paul");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import {
|
||||
Account,
|
||||
CoFeed,
|
||||
CoList,
|
||||
CoMap,
|
||||
CoStream,
|
||||
ImageDefinition,
|
||||
Profile,
|
||||
co,
|
||||
@@ -25,7 +25,7 @@ export const ReactionTypes = [
|
||||
] as const;
|
||||
export type ReactionType = (typeof ReactionTypes)[number];
|
||||
|
||||
export class PetReactions extends CoStream.Of(co.json<ReactionType>()) {}
|
||||
export class PetReactions extends CoFeed.Of(co.json<ReactionType>()) {}
|
||||
|
||||
export class PetPost extends CoMap {
|
||||
name = co.string;
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
# The documentation for this feature is not yet available
|
||||
# Documentation coming soon
|
||||
|
||||
Grayed out pages on the sidebar indicate that the documentation is not yet available.
|
||||
Grayed out pages on our sidebar indicate that documentation for this feature is still in progress. We're excited to bring you comprehensive guides and tutorials as soon as possible.
|
||||
|
||||
This feature has already been released, but the documentation is still a work in progress.
|
||||
|
||||
If you don't find what you're looking for, [please ask for help in the Discord](https://discord.gg/utDMjHYg42).
|
||||
We have a whole team of developers who are ready to help you get started.
|
||||
This feature has already been released, and we're working hard to provide top-notch support. In the meantime, if you have any questions or need assistance, please don't hesitate to reach out to us on [Discord](https://discord.gg/utDMjHYg42).
|
||||
We would love to help you get started.
|
||||
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import { Prose } from "gcmp-design-system/src/app/components/molecules/Prose";
|
||||
|
||||
export const metadata = {
|
||||
title: "Docs",
|
||||
description: "Jazz Guide & Docs.",
|
||||
};
|
||||
|
||||
export default function DocsLayout({
|
||||
children,
|
||||
}: {
|
||||
|
||||
44
homepage/homepage/app/docs/(others)/page.mdx
Normal file
44
homepage/homepage/app/docs/(others)/page.mdx
Normal file
@@ -0,0 +1,44 @@
|
||||
import { HeroHeader } from "gcmp-design-system/src/app/components/molecules/HeroHeader";
|
||||
import { JazzLogo } from 'gcmp-design-system/src/app/components/atoms/logos/JazzLogo';
|
||||
|
||||
<div className="not-prose">
|
||||
<h1 className="sr-only">Getting started</h1>
|
||||
<HeroHeader
|
||||
title={<>Learn some <JazzLogo className="h-[1.3em] relative -top-0.5 inline-block -ml-[0.1em] -mr-[0.1em]"/></>}
|
||||
slogan=""
|
||||
pt={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Welcome to the Jazz documentation!
|
||||
|
||||
The Jazz docs are currently heavily work in progress, sorry about that!
|
||||
|
||||
## Quickstart
|
||||
|
||||
To get started, set up Jazz in your project. Here's how you can do that on your framework of choice:
|
||||
|
||||
- [React](/docs/project-setup/react)
|
||||
- [Next.js](/docs/project-setup/next)
|
||||
- [React Native](/docs/project-setup/react-native)
|
||||
- [Vue](/docs/project-setup/vue)
|
||||
|
||||
Or you can follow this [React step-by-step guide](/docs/guide) where we walk you through building an issue tracker app.
|
||||
|
||||
## Example apps
|
||||
|
||||
You can also find [example apps](/examples) with code most similar to what you want to build. These apps
|
||||
make use of different features such as auth, file upload, and more.
|
||||
|
||||
## Sync and storage
|
||||
|
||||
Sync and persist your data by setting up a [sync and storage infrastructure](/docs/sync-and-storage) using Jazz Cloud, or do it yourself.
|
||||
|
||||
## Collaborative values
|
||||
|
||||
Learn how to structure your data using [collaborative values](/docs/schemas/covalues).
|
||||
|
||||
## Get support
|
||||
|
||||
If you have any questions or need assistance, please don't hesitate to reach out to us on [Discord](https://discord.gg/utDMjHYg42).
|
||||
We would love to help you get started.
|
||||
@@ -1,55 +0,0 @@
|
||||
import { Prose } from "gcmp-design-system/src/app/components/molecules/Prose";
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<Prose>
|
||||
<h1>Welcome to the Jazz documentation.</h1>
|
||||
<p>
|
||||
The Jazz docs are currently heavily work in progress, sorry about that!
|
||||
</p>
|
||||
<p>The best ways to get started are:</p>
|
||||
<ul>
|
||||
<li>
|
||||
Quickstart (work in progress)
|
||||
<ol>
|
||||
<li>
|
||||
<a href="/docs/sync-and-storage">Sync & Storage Setup</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/docs/project-setup/react">React Project Setup</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/docs/schemas/covalues">
|
||||
CoValue Basics & Schema Definition
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<span className="opacity-50">Creating Covalues</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="opacity-50">Using Covalues</span>
|
||||
</li>
|
||||
</ol>
|
||||
</li>
|
||||
<li>
|
||||
The step-by-step <a href="/docs/guide">Guide</a> (work in progress)
|
||||
</li>
|
||||
</ul>
|
||||
<p>Also make sure to:</p>
|
||||
<ul>
|
||||
<li>
|
||||
Find an <a href="/examples">example app with code</a> most similar to
|
||||
what you want to build
|
||||
</li>
|
||||
<li>
|
||||
Check out the <a href="/docs/api-reference">API Reference</a> (work in
|
||||
progress)
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
And the best way to get help is to join the{" "}
|
||||
<a href="https://discord.gg/utDMjHYg42">Discord</a>!
|
||||
</p>
|
||||
</Prose>
|
||||
);
|
||||
}
|
||||
@@ -1,26 +1,11 @@
|
||||
import { HeroHeader } from "gcmp-design-system/src/app/components/molecules/HeroHeader";
|
||||
import { CodeGroup } from "@/components/forMdx";
|
||||
import { JazzLogo } from 'gcmp-design-system/src/app/components/atoms/logos/JazzLogo'
|
||||
|
||||
<div className="not-prose">
|
||||
<HeroHeader
|
||||
title={<>Learn some <JazzLogo className="h-[1.3em] relative -top-0.5 inline-block -ml-[0.1em] -mr-[0.1em]"/></>}
|
||||
slogan="Build an issue tracker with distributed state."
|
||||
pt={false}
|
||||
/>
|
||||
</div>
|
||||
# React Guide
|
||||
|
||||
## About this guide
|
||||
This is a step-by-step tutorial where we'll build an issue tracker app using React.
|
||||
|
||||
You might notice that right now, this guide is the only form of documentation there is for Jazz.
|
||||
Over time, we're hoping to introduce independent doc sections for every concept in Jazz, but right now this works as:
|
||||
|
||||
- a quickstart guide
|
||||
- a reference for the concepts in Jazz (ordered from simple & most important to more advanced)
|
||||
- a tutorial that makes you build a full app (that you can use as a base)
|
||||
|
||||
Plus, if you get stuck or you have questions, [ask us on Discord](https://discord.gg/utDMjHYg42)
|
||||
and we'll know exactly where you're at.
|
||||
You'll learn how to set up a Jazz app, use Jazz Cloud for sync and storage, create and manipulate data using
|
||||
Collaborative Values (CoValues), build a UI and subscribe to changes, set permissions, and send invites.
|
||||
|
||||
## Project Setup
|
||||
|
||||
|
||||
87
homepage/homepage/app/docs/[...slug]/schemas/accounts.mdx
Normal file
87
homepage/homepage/app/docs/[...slug]/schemas/accounts.mdx
Normal file
@@ -0,0 +1,87 @@
|
||||
import { CodeGroup } from "@/components/forMdx";
|
||||
|
||||
# Accounts & migrations
|
||||
|
||||
Accounts are a fundamental building block for user identity and data organization in Jazz applications, providing a structured way to manage both public and private user data while maintaining type-safety and access control.
|
||||
|
||||
Account data and metadata are publicly visible to other users.
|
||||
|
||||
For private data, we suggest nesting privately owned CoValues within the account root.
|
||||
|
||||
## Accounts
|
||||
|
||||
To create a custom account type, extend the `Account` class and register it with the Jazz runtime.
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
import { Account, CoMap } from "jazz-tools";
|
||||
|
||||
class MyAccountRoot extends CoMap {
|
||||
privateString = co.string; // Readable by the account owner
|
||||
}
|
||||
|
||||
class MyProfile extends Profile {
|
||||
publicString = co.string; // Readable by everyone
|
||||
}
|
||||
|
||||
class MyAccount extends Account {
|
||||
root = co.ref(MyAccountRoot);
|
||||
profile = co.ref(MyProfile);
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
### Schema
|
||||
|
||||
When passing the account class to the Jazz runtime, the schema is used to type account data.
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
// React
|
||||
const Jazz = createJazzReactApp({
|
||||
AccountSchema: MyAccount,
|
||||
});
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
// React Native
|
||||
const Jazz = createJazzReactNativeApp({
|
||||
AccountSchema: MyAccount,
|
||||
});
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
// Vue
|
||||
const Jazz = createJazzVueApp({
|
||||
AccountSchema: MyAccount,
|
||||
});
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Migrations
|
||||
|
||||
Accounts can be migrated to add new data or update schemas. The `migrate()` method runs automatically when an account is created and each time a user logs in.
|
||||
|
||||
You can use migrations to initialize the account root and set up any other required CoValues.
|
||||
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
class MyAccount extends Account {
|
||||
async migrate() {
|
||||
super.migrate(creationProps);
|
||||
|
||||
if (!this._refs.root) {
|
||||
this.root = MyAccountRoot.create({
|
||||
privateString: "Keep it hidden, keep it safe.",
|
||||
},
|
||||
{ owner: this });
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
@@ -13,7 +13,7 @@ Jazz Cloud will
|
||||
|
||||
- Jazz Cloud is free during the public alpha, with no strict usage limits
|
||||
- We plan to keep a free tier, so you'll always be able to get started with zero setup
|
||||
- See [Jazz Cloud Pricing](https://jazz.tools/cloud#pricing) for more details
|
||||
- See [Jazz Cloud pricing](/cloud#pricing) for more details
|
||||
- ⚠️ Please use a valid email address as your API key.
|
||||
|
||||
Your full sync server URL should look something like
|
||||
|
||||
@@ -2,8 +2,8 @@ import { DocNav } from "@/components/docs/nav";
|
||||
import { clsx } from "clsx";
|
||||
|
||||
export const metadata = {
|
||||
title: "Docs",
|
||||
description: "Jazz Guide & Docs.",
|
||||
title: "Documentation",
|
||||
description: "Jazz guide and documentation.",
|
||||
};
|
||||
|
||||
export default function DocsLayout({
|
||||
|
||||
@@ -8,7 +8,6 @@ import { JazzFooter } from "@/components/footer";
|
||||
import { JazzNav } from "@/components/nav";
|
||||
import { Analytics } from "@vercel/analytics/react";
|
||||
import { SpeedInsights } from "@vercel/speed-insights/next";
|
||||
import { clsx } from "clsx";
|
||||
import { ThemeProvider } from "gcmp-design-system/src/app/components/molecules/ThemeProvider";
|
||||
|
||||
// If loading a variable font, you don't need to specify the font weight
|
||||
|
||||
@@ -39,13 +39,13 @@ export const docNavigationItems = [
|
||||
href: "/docs/project-setup/next",
|
||||
},
|
||||
{
|
||||
name: "Node.JS / Server Workers",
|
||||
href: "/docs/project-setup/server-side",
|
||||
name: "VueJS",
|
||||
href: "/docs/project-setup/vue",
|
||||
done: 80,
|
||||
},
|
||||
{
|
||||
name: "VueJS",
|
||||
href: "/docs/project-setup/vue",
|
||||
name: "Node.JS / Server Workers",
|
||||
href: "/docs/project-setup/server-side",
|
||||
done: 80,
|
||||
},
|
||||
],
|
||||
@@ -56,12 +56,12 @@ export const docNavigationItems = [
|
||||
{
|
||||
name: "CoValues",
|
||||
href: "/docs/schemas/covalues",
|
||||
done: 20,
|
||||
done: 50,
|
||||
},
|
||||
{
|
||||
name: "Accounts & Migrations",
|
||||
href: "/docs/schemas/accounts",
|
||||
done: 0,
|
||||
done: 20,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import type { MDXComponents } from "mdx/types";
|
||||
import Link from "next/link";
|
||||
|
||||
export function useMDXComponents(components: MDXComponents): MDXComponents {
|
||||
return {
|
||||
a: (props) =>
|
||||
props.href ? (
|
||||
<Link href={props.href}>{props.children}</Link>
|
||||
) : (
|
||||
props.children
|
||||
),
|
||||
...components,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,41 @@
|
||||
# `jazz-browser-media-images`
|
||||
# `jazz-browser-auth-clerk`
|
||||
|
||||
This is an optional add-on for `jazz-browser` or `jazz-react` that provides support for creating `ImageDefinition`-compatible image sets from images provided as `File` or `Blob` objects.
|
||||
This package provides a [Clerk-based](https://clerk.com/) authentication strategy for Jazz.
|
||||
|
||||
In particular, it implements multi-resolution resizing based on `pica`.
|
||||
## Usage
|
||||
|
||||
`useJazzClerkAuth` is a hook that returns a `JazzAuth` object and a `JazzAuthState` object. Provide a Clerk instance to `useJazzClerkAuth`, and it will return the appropriate `JazzAuth` object. Once authenticated, authentication will persist across page reloads, even if the device is offline.
|
||||
|
||||
|
||||
From [the example chat app](https://github.com/gardencmp/jazz/tree/main/examples/chat-clerk):
|
||||
|
||||
```typescript
|
||||
import { ClerkProvider, SignInButton, useClerk } from "@clerk/clerk-react";
|
||||
import { useJazzClerkAuth } from "jazz-react-auth-clerk";
|
||||
|
||||
const Jazz = createJazzReactApp();
|
||||
export const { useAccount, useCoState } = Jazz;
|
||||
|
||||
function JazzAndAuth({ children }: { children: React.ReactNode }) {
|
||||
const clerk = useClerk();
|
||||
const [auth, state] = useJazzClerkAuth(clerk);
|
||||
|
||||
return (
|
||||
<>
|
||||
{state.errors.map((error) => (
|
||||
<div key={error}>{error}</div>
|
||||
))}
|
||||
{auth ? (
|
||||
<Jazz.Provider
|
||||
auth={auth}
|
||||
peer="wss://cloud.jazz.tools/?key=chat-example-jazz-clerk@gcmp.io"
|
||||
>
|
||||
{children}
|
||||
</Jazz.Provider>
|
||||
) : (
|
||||
<SignInButton />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -148,6 +148,12 @@ export class BrowserPasskeyAuth implements AuthMethod {
|
||||
resolve({
|
||||
type: "existing",
|
||||
credentials: { accountID, secret },
|
||||
saveCredentials: async ({ accountID, secret }) => {
|
||||
localStorage[localStorageKey] = JSON.stringify({
|
||||
accountID,
|
||||
accountSecret: secret,
|
||||
} satisfies LocalStorageData);
|
||||
},
|
||||
onSuccess: () => {
|
||||
this.driver.onSignedIn({ logOut });
|
||||
},
|
||||
|
||||
@@ -98,11 +98,13 @@ export class BrowserPassphraseAuth implements AuthMethod {
|
||||
resolve({
|
||||
type: "existing",
|
||||
credentials: { accountID, secret: accountSecret },
|
||||
onSuccess: () => {
|
||||
saveCredentials: async ({ accountID, secret }) => {
|
||||
localStorage[localStorageKey] = JSON.stringify({
|
||||
accountID,
|
||||
accountSecret,
|
||||
accountSecret: secret,
|
||||
} satisfies LocalStorageData);
|
||||
},
|
||||
onSuccess: () => {
|
||||
this.driver.onSignedIn({ logOut });
|
||||
},
|
||||
onError: (error: string | Error) => {
|
||||
|
||||
@@ -38,11 +38,17 @@ import {
|
||||
subscribeToExistingCoValue,
|
||||
} from "../internal.js";
|
||||
|
||||
export type CoStreamEntry<Item> = SingleCoStreamEntry<Item> & {
|
||||
all: IterableIterator<SingleCoStreamEntry<Item>>;
|
||||
/** @deprecated Use CoFeedEntry instead */
|
||||
export type CoStreamEntry<Item> = CoFeedEntry<Item>;
|
||||
|
||||
export type CoFeedEntry<Item> = SingleCoFeedEntry<Item> & {
|
||||
all: IterableIterator<SingleCoFeedEntry<Item>>;
|
||||
};
|
||||
|
||||
export type SingleCoStreamEntry<Item> = {
|
||||
/** @deprecated Use SingleCoFeedEntry instead */
|
||||
export type SingleCoStreamEntry<Item> = SingleCoFeedEntry<Item>;
|
||||
|
||||
export type SingleCoFeedEntry<Item> = {
|
||||
value: NonNullable<Item> extends CoValue ? NonNullable<Item> | null : Item;
|
||||
ref: NonNullable<Item> extends CoValue ? Ref<NonNullable<Item>> : never;
|
||||
by?: Account | null;
|
||||
@@ -50,11 +56,14 @@ export type SingleCoStreamEntry<Item> = {
|
||||
tx: CojsonInternalTypes.TransactionID;
|
||||
};
|
||||
|
||||
/** @deprecated Use CoFeed instead */
|
||||
export { CoFeed as CoStream };
|
||||
|
||||
/** @category CoValues */
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export class CoStream<Item = any> extends CoValueBase implements CoValue {
|
||||
static Of<Item>(item: IfCo<Item, Item>): typeof CoStream<Item> {
|
||||
return class CoStreamOf extends CoStream<Item> {
|
||||
export class CoFeed<Item = any> extends CoValueBase implements CoValue {
|
||||
static Of<Item>(item: IfCo<Item, Item>): typeof CoFeed<Item> {
|
||||
return class CoFeedOf extends CoFeed<Item> {
|
||||
[co.items] = item;
|
||||
};
|
||||
}
|
||||
@@ -73,12 +82,12 @@ export class CoStream<Item = any> extends CoValueBase implements CoValue {
|
||||
get _schema(): {
|
||||
[ItemsSym]: SchemaFor<Item>;
|
||||
} {
|
||||
return (this.constructor as typeof CoStream)._schema;
|
||||
return (this.constructor as typeof CoFeed)._schema;
|
||||
}
|
||||
|
||||
[key: ID<Account>]: CoStreamEntry<Item>;
|
||||
[key: ID<Account>]: CoFeedEntry<Item>;
|
||||
|
||||
get byMe(): CoStreamEntry<Item> | undefined {
|
||||
get byMe(): CoFeedEntry<Item> | undefined {
|
||||
if (this._loadedAs._type === "Account") {
|
||||
return this[this._loadedAs.id];
|
||||
} else {
|
||||
@@ -86,9 +95,9 @@ export class CoStream<Item = any> extends CoValueBase implements CoValue {
|
||||
}
|
||||
}
|
||||
perSession!: {
|
||||
[key: SessionID]: CoStreamEntry<Item>;
|
||||
[key: SessionID]: CoFeedEntry<Item>;
|
||||
};
|
||||
get inCurrentSession(): CoStreamEntry<Item> | undefined {
|
||||
get inCurrentSession(): CoFeedEntry<Item> | undefined {
|
||||
if (this._loadedAs._type === "Account") {
|
||||
return this.perSession[this._loadedAs.sessionID!];
|
||||
} else {
|
||||
@@ -116,9 +125,9 @@ export class CoStream<Item = any> extends CoValueBase implements CoValue {
|
||||
return new Proxy(this, CoStreamProxyHandler as ProxyHandler<this>);
|
||||
}
|
||||
|
||||
static create<S extends CoStream>(
|
||||
static create<S extends CoFeed>(
|
||||
this: CoValueClass<S>,
|
||||
init: S extends CoStream<infer Item> ? UnCo<Item>[] : never,
|
||||
init: S extends CoFeed<infer Item> ? UnCo<Item>[] : never,
|
||||
options: { owner: Account | Group },
|
||||
) {
|
||||
const instance = new this({ init, owner: options.owner });
|
||||
@@ -197,9 +206,9 @@ export class CoStream<Item = any> extends CoValueBase implements CoValue {
|
||||
return this.toJSON();
|
||||
}
|
||||
|
||||
static schema<V extends CoStream>(
|
||||
static schema<V extends CoFeed>(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
this: { new (...args: any): V } & typeof CoStream,
|
||||
this: { new (...args: any): V } & typeof CoFeed,
|
||||
def: { [ItemsSym]: V["_schema"][ItemsSym] },
|
||||
) {
|
||||
this._schema ||= {};
|
||||
@@ -207,7 +216,7 @@ export class CoStream<Item = any> extends CoValueBase implements CoValue {
|
||||
}
|
||||
|
||||
/** @category Subscription & Loading */
|
||||
static load<S extends CoStream, Depth>(
|
||||
static load<S extends CoFeed, Depth>(
|
||||
this: CoValueClass<S>,
|
||||
id: ID<S>,
|
||||
as: Account,
|
||||
@@ -217,7 +226,7 @@ export class CoStream<Item = any> extends CoValueBase implements CoValue {
|
||||
}
|
||||
|
||||
/** @category Subscription & Loading */
|
||||
static subscribe<S extends CoStream, Depth>(
|
||||
static subscribe<S extends CoFeed, Depth>(
|
||||
this: CoValueClass<S>,
|
||||
id: ID<S>,
|
||||
as: Account,
|
||||
@@ -228,7 +237,7 @@ export class CoStream<Item = any> extends CoValueBase implements CoValue {
|
||||
}
|
||||
|
||||
/** @category Subscription & Loading */
|
||||
ensureLoaded<S extends CoStream, Depth>(
|
||||
ensureLoaded<S extends CoFeed, Depth>(
|
||||
this: S,
|
||||
depth: Depth & DepthsIn<S>,
|
||||
): Promise<DeeplyLoaded<S, Depth> | undefined> {
|
||||
@@ -236,7 +245,7 @@ export class CoStream<Item = any> extends CoValueBase implements CoValue {
|
||||
}
|
||||
|
||||
/** @category Subscription & Loading */
|
||||
subscribe<S extends CoStream, Depth>(
|
||||
subscribe<S extends CoFeed, Depth>(
|
||||
this: S,
|
||||
depth: Depth & DepthsIn<S>,
|
||||
listener: (value: DeeplyLoaded<S, Depth>) => void,
|
||||
@@ -256,7 +265,7 @@ function entryFromRawEntry<Item>(
|
||||
loadedAs: Account | AnonymousJazzAgent,
|
||||
accountID: ID<Account> | undefined,
|
||||
itemField: Schema,
|
||||
): Omit<CoStreamEntry<Item>, "all"> {
|
||||
): Omit<CoFeedEntry<Item>, "all"> {
|
||||
return {
|
||||
get value(): NonNullable<Item> extends CoValue
|
||||
? (CoValue & Item) | null
|
||||
@@ -307,7 +316,7 @@ function entryFromRawEntry<Item>(
|
||||
};
|
||||
}
|
||||
|
||||
export const CoStreamProxyHandler: ProxyHandler<CoStream> = {
|
||||
export const CoStreamProxyHandler: ProxyHandler<CoFeed> = {
|
||||
get(target, key, receiver) {
|
||||
if (typeof key === "string" && key.startsWith("co_")) {
|
||||
const rawEntry = target._raw.lastItemBy(key as RawAccountID);
|
||||
@@ -337,7 +346,7 @@ export const CoStreamProxyHandler: ProxyHandler<CoStream> = {
|
||||
);
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
})() satisfies IterableIterator<SingleCoStreamEntry<any>>;
|
||||
})() satisfies IterableIterator<SingleCoFeedEntry<any>>;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -350,8 +359,8 @@ export const CoStreamProxyHandler: ProxyHandler<CoStream> = {
|
||||
},
|
||||
set(target, key, value, receiver) {
|
||||
if (key === ItemsSym && typeof value === "object" && SchemaInit in value) {
|
||||
(target.constructor as typeof CoStream)._schema ||= {};
|
||||
(target.constructor as typeof CoStream)._schema[ItemsSym] =
|
||||
(target.constructor as typeof CoFeed)._schema ||= {};
|
||||
(target.constructor as typeof CoFeed)._schema[ItemsSym] =
|
||||
value[SchemaInit];
|
||||
return true;
|
||||
} else {
|
||||
@@ -365,8 +374,8 @@ export const CoStreamProxyHandler: ProxyHandler<CoStream> = {
|
||||
typeof descriptor.value === "object" &&
|
||||
SchemaInit in descriptor.value
|
||||
) {
|
||||
(target.constructor as typeof CoStream)._schema ||= {};
|
||||
(target.constructor as typeof CoStream)._schema[ItemsSym] =
|
||||
(target.constructor as typeof CoFeed)._schema ||= {};
|
||||
(target.constructor as typeof CoFeed)._schema[ItemsSym] =
|
||||
descriptor.value[SchemaInit];
|
||||
return true;
|
||||
} else {
|
||||
@@ -396,8 +405,8 @@ export const CoStreamProxyHandler: ProxyHandler<CoStream> = {
|
||||
};
|
||||
|
||||
const CoStreamPerSessionProxyHandler = (
|
||||
innerTarget: CoStream,
|
||||
accessFrom: CoStream,
|
||||
innerTarget: CoFeed,
|
||||
accessFrom: CoFeed,
|
||||
): ProxyHandler<Record<string, never>> => ({
|
||||
get(_target, key, receiver) {
|
||||
if (typeof key === "string" && key.includes("session")) {
|
||||
@@ -435,7 +444,7 @@ const CoStreamPerSessionProxyHandler = (
|
||||
);
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
})() satisfies IterableIterator<SingleCoStreamEntry<any>>;
|
||||
})() satisfies IterableIterator<SingleCoFeedEntry<any>>;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -339,7 +339,7 @@ export class CoList<Item = any> extends Array<Item> implements CoValue {
|
||||
*
|
||||
* You can pass `[]` or for shallowly loading only this CoList, or `[itemDepth]` for recursively loading referenced CoValues.
|
||||
*
|
||||
* Check out the `load` methods on `CoMap`/`CoList`/`CoStream`/`Group`/`Account` to see which depth structures are valid to nest.
|
||||
* Check out the `load` methods on `CoMap`/`CoList`/`CoFeed`/`Group`/`Account` to see which depth structures are valid to nest.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
@@ -372,7 +372,7 @@ export class CoList<Item = any> extends Array<Item> implements CoValue {
|
||||
*
|
||||
* You can pass `[]` or for shallowly loading only this CoList, or `[itemDepth]` for recursively loading referenced CoValues.
|
||||
*
|
||||
* Check out the `load` methods on `CoMap`/`CoList`/`CoStream`/`Group`/`Account` to see which depth structures are valid to nest.
|
||||
* Check out the `load` methods on `CoMap`/`CoList`/`CoFeed`/`Group`/`Account` to see which depth structures are valid to nest.
|
||||
*
|
||||
* Returns an unsubscribe function that you should call when you no longer need updates.
|
||||
*
|
||||
|
||||
@@ -366,7 +366,7 @@ export class CoMap extends CoValueBase implements CoValue {
|
||||
*
|
||||
* You can pass `[]` or `{}` for shallowly loading only this CoMap, or `{ fieldA: depthA, fieldB: depthB }` for recursively loading referenced CoValues.
|
||||
*
|
||||
* Check out the `load` methods on `CoMap`/`CoList`/`CoStream`/`Group`/`Account` to see which depth structures are valid to nest.
|
||||
* Check out the `load` methods on `CoMap`/`CoList`/`CoFeed`/`Group`/`Account` to see which depth structures are valid to nest.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
@@ -398,7 +398,7 @@ export class CoMap extends CoValueBase implements CoValue {
|
||||
*
|
||||
* You can pass `[]` or `{}` for shallowly loading only this CoMap, or `{ fieldA: depthA, fieldB: depthB }` for recursively loading referenced CoValues.
|
||||
*
|
||||
* Check out the `load` methods on `CoMap`/`CoList`/`CoStream`/`Group`/`Account` to see which depth structures are valid to nest.
|
||||
* Check out the `load` methods on `CoMap`/`CoList`/`CoFeed`/`Group`/`Account` to see which depth structures are valid to nest.
|
||||
*
|
||||
* Returns an unsubscribe function that you should call when you no longer need updates.
|
||||
*
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { SessionID } from "cojson";
|
||||
import {
|
||||
Account,
|
||||
CoFeed,
|
||||
CoFeedEntry,
|
||||
CoList,
|
||||
CoStream,
|
||||
CoStreamEntry,
|
||||
ItemsSym,
|
||||
Ref,
|
||||
RefEncoded,
|
||||
@@ -66,10 +66,10 @@ export function fulfillsDepth(depth: any, value: CoValue): boolean {
|
||||
return true;
|
||||
} else {
|
||||
const itemDepth = depth[0];
|
||||
return Object.values((value as CoStream).perSession).every((entry) =>
|
||||
return Object.values((value as CoFeed).perSession).every((entry) =>
|
||||
entry.ref
|
||||
? entry.value && fulfillsDepth(itemDepth, entry.value)
|
||||
: ((value as CoStream)._schema[ItemsSym] as RefEncoded<CoValue>)
|
||||
: ((value as CoFeed)._schema[ItemsSym] as RefEncoded<CoValue>)
|
||||
.optional,
|
||||
);
|
||||
}
|
||||
@@ -121,7 +121,7 @@ export type DepthsIn<
|
||||
| never[]
|
||||
: V extends {
|
||||
_type: "CoStream";
|
||||
byMe: CoStreamEntry<infer Item> | undefined;
|
||||
byMe: CoFeedEntry<infer Item> | undefined;
|
||||
}
|
||||
?
|
||||
| [
|
||||
@@ -192,7 +192,7 @@ export type DeeplyLoaded<
|
||||
: [V] extends [
|
||||
{
|
||||
_type: "CoStream";
|
||||
byMe: CoStreamEntry<infer Item> | undefined;
|
||||
byMe: CoFeedEntry<infer Item> | undefined;
|
||||
},
|
||||
]
|
||||
? Depth extends never[]
|
||||
|
||||
@@ -17,6 +17,7 @@ export {
|
||||
BinaryCoStream,
|
||||
CoList,
|
||||
CoMap,
|
||||
CoFeed,
|
||||
CoStream,
|
||||
CoValueBase,
|
||||
Group,
|
||||
|
||||
@@ -5,7 +5,7 @@ export * from "./coValues/interfaces.js";
|
||||
export * from "./coValues/coMap.js";
|
||||
export * from "./coValues/account.js";
|
||||
export * from "./coValues/coList.js";
|
||||
export * from "./coValues/coStream.js";
|
||||
export * from "./coValues/coFeed.js";
|
||||
export * from "./coValues/group.js";
|
||||
|
||||
export * from "./implementation/errors.js";
|
||||
|
||||
@@ -3,7 +3,7 @@ import { describe, expect, test } from "vitest";
|
||||
import {
|
||||
Account,
|
||||
BinaryCoStream,
|
||||
CoStream,
|
||||
CoFeed,
|
||||
ID,
|
||||
WasmCrypto,
|
||||
co,
|
||||
@@ -16,7 +16,7 @@ import { randomSessionProvider } from "../internal.js";
|
||||
|
||||
const Crypto = await WasmCrypto.create();
|
||||
|
||||
describe("Simple CoStream operations", async () => {
|
||||
describe("Simple CoFeed operations", async () => {
|
||||
const me = await Account.create({
|
||||
creationProps: { name: "Hermes Puggington" },
|
||||
crypto: Crypto,
|
||||
@@ -24,7 +24,7 @@ describe("Simple CoStream operations", async () => {
|
||||
if (!isControlledAccount(me)) {
|
||||
throw "me is not a controlled account";
|
||||
}
|
||||
class TestStream extends CoStream.Of(co.string) {}
|
||||
class TestStream extends CoFeed.Of(co.string) {}
|
||||
|
||||
const stream = TestStream.create(["milk"], { owner: me });
|
||||
|
||||
@@ -46,16 +46,16 @@ describe("Simple CoStream operations", async () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("CoStream resolution", async () => {
|
||||
class TwiceNestedStream extends CoStream.Of(co.string) {
|
||||
describe("CoFeed resolution", async () => {
|
||||
class TwiceNestedStream extends CoFeed.Of(co.string) {
|
||||
fancyValueOf(account: ID<Account>) {
|
||||
return "Sir " + this[account]?.value;
|
||||
}
|
||||
}
|
||||
|
||||
class NestedStream extends CoStream.Of(co.ref(TwiceNestedStream)) {}
|
||||
class NestedStream extends CoFeed.Of(co.ref(TwiceNestedStream)) {}
|
||||
|
||||
class TestStream extends CoStream.Of(co.ref(NestedStream)) {}
|
||||
class TestStream extends CoFeed.Of(co.ref(NestedStream)) {}
|
||||
|
||||
const initNodeAndStream = async () => {
|
||||
const me = await Account.create({
|
||||
@@ -3,9 +3,9 @@ import { connectedPeers } from "cojson/src/streamUtils.ts";
|
||||
import { describe, expect, expectTypeOf, test } from "vitest";
|
||||
import {
|
||||
Account,
|
||||
CoFeed,
|
||||
CoList,
|
||||
CoMap,
|
||||
CoStream,
|
||||
ID,
|
||||
Profile,
|
||||
SessionID,
|
||||
@@ -28,7 +28,7 @@ class InnerMap extends CoMap {
|
||||
stream = co.ref(TestStream);
|
||||
}
|
||||
|
||||
class TestStream extends CoStream.Of(co.ref(() => InnermostMap)) {}
|
||||
class TestStream extends CoFeed.Of(co.ref(() => InnermostMap)) {}
|
||||
|
||||
class InnermostMap extends CoMap {
|
||||
value = co.string;
|
||||
|
||||
@@ -3,9 +3,9 @@ import { connectedPeers } from "cojson/src/streamUtils.js";
|
||||
import { describe, expect, it, onTestFinished, vi } from "vitest";
|
||||
import {
|
||||
Account,
|
||||
CoFeed,
|
||||
CoList,
|
||||
CoMap,
|
||||
CoStream,
|
||||
WasmCrypto,
|
||||
co,
|
||||
createJazzContext,
|
||||
@@ -31,7 +31,7 @@ class Message extends CoMap {
|
||||
}
|
||||
|
||||
class MessagesList extends CoList.Of(co.ref(Message)) {}
|
||||
class ReactionsStream extends CoStream.Of(co.string) {}
|
||||
class ReactionsStream extends CoFeed.Of(co.string) {}
|
||||
|
||||
async function setupAccount() {
|
||||
const me = await Account.create({
|
||||
|
||||
Reference in New Issue
Block a user