Compare commits

..

47 Commits

Author SHA1 Message Date
Nico Rainhart
0a98d6aaf2 Merge pull request #2691 from garden-co/changeset-release/main
Version Packages
2025-08-01 12:57:33 -03:00
github-actions[bot]
4ea1a63a0a Version Packages 2025-08-01 15:47:51 +00:00
Nico Rainhart
41a4c3bc95 Merge pull request #2683 from garden-co/feat/json-create-and-set
feat: create CoValues using plain JSON objects
2025-08-01 12:45:30 -03:00
Guido D'Orsi
60d0027f9d Merge pull request #2690 from garden-co/docs/useCoState-jsDoc
docs: adds jsDocs to useCoState and useAccount react hooks
2025-08-01 16:12:39 +02:00
Guido D'Orsi
748c2ff751 Merge pull request #2688 from garden-co/changeset-release/main
Version Packages
2025-08-01 15:46:41 +02:00
github-actions[bot]
70938b0ab3 Version Packages 2025-08-01 13:42:25 +00:00
Guido D'Orsi
f2f5b55dbf Update packages/jazz-tools/src/react-core/hooks.ts
Co-authored-by: Nico Rainhart <nmrainhart@gmail.com>
2025-08-01 15:42:24 +02:00
Guido D'Orsi
3c3acae803 Merge pull request #2689 from joeinnes/2687-fix-broken-link
Fix HTTP API link in inbox.mdx. Fixes #2687
2025-08-01 15:40:07 +02:00
Guido D'Orsi
896ee3460f docs: adds jsDocs to useCoState and useAccount react hooks 2025-08-01 15:37:02 +02:00
Joe Innes
9b9bf44e2b Fix HTTP API link in inbox.mdx. Fixes #2687 2025-08-01 15:13:33 +02:00
Guido D'Orsi
392aa88d95 Merge pull request #2655 from joeinnes/docs/optional-references
Docs/optional references
2025-08-01 13:15:20 +02:00
Joe Innes
7ce82cd934 Merge branch 'main' into docs/optional-references 2025-08-01 13:13:26 +02:00
Guido D'Orsi
0c8158b91c Merge pull request #2676 from Gabrola/fix/jazz-run-exports
fix: jazz-run package.json exports
2025-08-01 13:07:30 +02:00
Guido D'Orsi
25c56146f5 Merge pull request #2686 from garden-co/test/logout-state
test: logout integration tests on browser
2025-08-01 09:57:18 +02:00
NicoR
c564fbb02e test: add permission tests for creating nested CoValues from JSON 2025-07-31 15:03:25 -03:00
Guido D'Orsi
12481e14c2 test: logout integration tests on browser 2025-07-31 18:56:37 +02:00
NicoR
fd2d247ff5 docs: improve examples 2025-07-31 13:08:50 -03:00
NicoR
9e9ea029b2 fix: move alert out of CodeGroup 2025-07-30 17:32:56 -03:00
NicoR
a0da272dcd fix: add missing import in docs 2025-07-30 16:51:23 -03:00
NicoR
72fbcc3262 chore: remove unnecessary import from form example 2025-07-30 15:52:07 -03:00
NicoR
f4c8cc858b docs: add sections for creating CoValues from JSON and permissions 2025-07-30 15:48:55 -03:00
Anselm
0ab4d7a20d Update meta description 2025-07-30 11:34:54 -07:00
NicoR
4cbda689c4 refactor: update form example to use new API 2025-07-30 14:54:08 -03:00
NicoR
771b0ed914 fix: cannot create empty plain text when nested 2025-07-30 14:53:38 -03:00
NicoR
79913c3136 fix: simplify CoMapInit schema 2025-07-30 13:37:10 -03:00
NicoR
43d3511d15 Add changeset 2025-07-30 12:40:25 -03:00
NicoR
928ef14086 feat: support deeply nested optional primitive fields 2025-07-30 12:33:28 -03:00
NicoR
048dd7def0 feat: support deeply nested optional CoValue fields 2025-07-30 12:33:09 -03:00
NicoR
873b146d15 feat: create a child group for each created CoValue 2025-07-30 11:03:38 -03:00
NicoR
ab1798c7bd feat: make CoValue creation from JSON type safe 2025-07-29 16:41:48 -03:00
NicoR
26ae69a242 refactor: reuse TypeOfZodSchema 2025-07-29 12:44:42 -03:00
NicoR
21ad3767b9 refactor: avoid InstanceOrPrimitiveOfSchemaCoValuesNullable duplication 2025-07-29 11:48:46 -03:00
NicoR
a9383516c1 refactor: avoid InstanceOrPrimitiveOfSchema duplication 2025-07-29 11:38:09 -03:00
NicoR
bffc516c68 refactor: extract TypeOfZodSchema util 2025-07-29 11:16:14 -03:00
NicoR
9e7c0d9887 chore: clean up instantiateRefEncodedWithInit's implementation 2025-07-29 10:56:32 -03:00
NicoR
99b44d5780 feat: create CoMap with JSON discriminated union fields 2025-07-29 10:36:33 -03:00
NicoR
02db5f3b1d feat: create CoMap with JSON CoFeed fields 2025-07-29 09:13:01 -03:00
NicoR
1949a5fcd9 feat: create CoMap with JSON CoList fields 2025-07-28 17:15:43 -03:00
NicoR
dcd3b022cc feat: create CoMap with JSON plain and rich text fields 2025-07-28 16:56:43 -03:00
NicoR
a7b837c7e1 feat: create CoMap with JSON CoMap fields 2025-07-28 16:47:51 -03:00
Anselm Eickhoff
88ebcf58ab Merge pull request #2680 from garden-co/jazz-as-a-db
Jazz as a DB narrative MVP
2025-07-28 11:34:01 -07:00
Anselm
f379a168be Update garden co slogan 2025-07-28 10:30:18 -07:00
Anselm
bde6ac7d45 Jazz as a DB narrative MVP 2025-07-28 10:08:56 -07:00
Youssef Gaber
239da90c9f chore: changeset 2025-07-28 17:06:01 +04:00
Youssef Gaber
972791e7a8 fix: correct jazz-run package.json exports 2025-07-28 17:05:45 +04:00
Joe Innes
6b662b0efe Type fixes for twoslash 2025-07-18 15:06:03 +02:00
Joe Innes
a8b3ec7bb0 Add more detail regarding optional references
As the boundary becomes more defined between CoValue schemas and Zod schemas, we need to ensure folks pick the right `.optional()` between `co.optional()` for CoValues and `z.optional()` for primitives.
2025-07-17 20:24:03 +02:00
77 changed files with 1380 additions and 535 deletions

View File

@@ -1,5 +1,18 @@
# passkey-svelte
## 0.0.109
### Patch Changes
- Updated dependencies [43d3511]
- jazz-tools@0.16.3
## 0.0.108
### Patch Changes
- jazz-tools@0.16.2
## 0.0.107
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "chat-svelte",
"version": "0.0.107",
"version": "0.0.109",
"type": "module",
"private": true,
"scripts": {

View File

@@ -9,7 +9,6 @@ import {
BubbleTeaOrder,
DraftBubbleTeaOrder,
JazzAccount,
ListOfBubbleTeaAddOns,
validateDraftOrder,
} from "./schema.ts";
@@ -34,7 +33,7 @@ export function CreateOrder() {
// reset the draft
me.root.draft = DraftBubbleTeaOrder.create({
addOns: ListOfBubbleTeaAddOns.create([]),
addOns: [],
});
router.navigate("/");

View File

@@ -73,15 +73,9 @@ export const JazzAccount = co
})
.withMigration((account) => {
if (!account.root) {
const orders = co.list(BubbleTeaOrder).create([], account);
const draft = DraftBubbleTeaOrder.create(
{
addOns: ListOfBubbleTeaAddOns.create([], account),
instructions: co.plainText().create("", account),
},
account.root = AccountRoot.create(
{ draft: { addOns: [], instructions: "" }, orders: [] },
account,
);
account.root = AccountRoot.create({ draft, orders }, account);
}
});

View File

@@ -41,7 +41,9 @@ export function Footer({
</Link>
</div>
<p className="col-span-full sm:col-span-6 md:col-span-4 text-sm sm:text-base">
Playful software for serious problems.
Computers are magic.
<br />
Time to make them less complex.
</p>
</div>
<div className="grid gap-y-8 grid-cols-12">

View File

@@ -1,6 +1,5 @@
import { ChatDemoSection } from "@/components/home/ChatDemoSection";
import { CollaborationFeaturesSection } from "@/components/home/CollaborationFeaturesSection";
import { ComingSoonSection } from "@/components/home/ComingSoonSection";
import { EarlyAdopterSection } from "@/components/home/EarlyAdopterSection";
import { EncryptionSection } from "@/components/home/EncryptionSection";
import { FeaturesSection } from "@/components/home/FeaturesSection";
@@ -15,9 +14,9 @@ import { Testimonial } from "@garden-co/design-system/src/components/molecules/T
export default function Home() {
return (
<>
<HeroSection />
<HeroSection />
<div className="container flex flex-col gap-12 lg:gap-20">
<GetStartedSnippetSelect />
<SupportedEnvironmentsSection />
<HowJazzWorksSection />
@@ -54,8 +53,6 @@ export default function Home() {
<FeaturesSection />
<ComingSoonSection />
<EarlyAdopterSection />
</div>
</>

View File

@@ -1,54 +0,0 @@
import CoPlainTextDescription from "@/app/(others)/(home)/coValueDescriptions/coPlainTextDescription.mdx";
import CursorsAndCaretsDescription from "@/app/(others)/(home)/toolkit/cursorsAndCarets.mdx";
import TwoWaySyncDescription from "@/app/(others)/(home)/toolkit/twoWaySync.mdx";
import VideoPresenceCallsDescription from "@/app/(others)/(home)/toolkit/videoPresenceCalls.mdx";
import { CodeRef } from "@garden-co/design-system/src/components/atoms/CodeRef";
import { P } from "@garden-co/design-system/src/components/atoms/Paragraph";
import { FeatureCard } from "@garden-co/design-system/src/components/molecules/FeatureCard";
import { GappedGrid } from "@garden-co/design-system/src/components/molecules/GappedGrid";
import { Prose } from "@garden-co/design-system/src/components/molecules/Prose";
import { SectionHeader } from "@garden-co/design-system/src/components/molecules/SectionHeader";
export function ComingSoonSection() {
return (
<div>
<SectionHeader title="More features coming soon" />
<GappedGrid cols={4}>
<FeatureCard className="p-4" label={<h3>Cursors & carets</h3>}>
<P>Ready-made spatial presence.</P>
<Prose size="sm">
<CursorsAndCaretsDescription />
</Prose>
</FeatureCard>
<FeatureCard className="p-4" label={<h3>Two-way sync to your DB</h3>}>
<P>Add Jazz to an existing app.</P>
<Prose size="sm">
<TwoWaySyncDescription />
</Prose>
</FeatureCard>
<FeatureCard className="p-4" label={<h3>Video presence & calls</h3>}>
<P>Stream and record audio & video.</P>
<Prose size="sm">
<VideoPresenceCallsDescription />
</Prose>
</FeatureCard>
<FeatureCard
className="p-4"
label={
<h3>
<CodeRef>CoPlainText</CodeRef> & <CodeRef>CoRichText</CodeRef>
</h3>
}
>
<Prose size="sm">
<CoPlainTextDescription />
</Prose>
</FeatureCard>
</GappedGrid>
</div>
);
}

View File

@@ -16,81 +16,70 @@ const features: Array<{
title: string;
icon: IconName;
}> = [
{
title: "Instant updates",
icon: "instant",
},
{
title: "Real-time sync",
icon: "devices",
},
{
title: "Multiplayer",
icon: "spatialPresence",
},
{
title: "File uploads",
icon: "upload",
},
{
title: "Social features",
icon: "social",
},
{
title: "Permissions",
icon: "permissions",
},
{
title: "E2E encryption",
icon: "encryption",
},
{
title: "Authentication",
icon: "auth",
},
];
{
title: "Instant updates",
icon: "instant",
},
{
title: "Real-time sync",
icon: "devices",
},
{
title: "Multiplayer",
icon: "spatialPresence",
},
{
title: "File uploads",
icon: "upload",
},
{
title: "Social features",
icon: "social",
},
{
title: "Permissions",
icon: "permissions",
},
{
title: "E2E encryption",
icon: "encryption",
},
{
title: "Authentication",
icon: "auth",
},
];
export function HeroSection() {
return (
<div className="container grid items-center gap-x-8 gap-y-12 my-12 md:my-16 lg:my-24 lg:gap-x-10 lg:grid-cols-12">
<div className="container grid items-center gap-x-8 gap-y-12 mt-12 md:mt-16 lg:mt-24 mb-12 lg:gap-x-10 lg:grid-cols-12">
<div className="flex flex-col justify-center gap-5 lg:col-span-11 lg:gap-8">
<Kicker>Toolkit for backendless apps</Kicker>
<Kicker>Reactive, distributed, secure</Kicker>
<H1>
<span className="inline-block text-highlight">
{marketingCopy.headline}
</span>
</H1>
<Prose size="lg" className="text-pretty max-w-2xl dark:text-stone-200">
<Prose size="lg" className="text-pretty max-w-2xl dark:text-stone-200 prose-p:leading-normal">
<p>
Jazz gives you data without needing a database plus auth,
permissions, files and multiplayer without needing a backend.
Jazz is a new kind of database that's distributed across your frontend, containers, serverless functions and its own storage cloud.
</p>
<p>It syncs structured data, files and LLM streams instantly.<br/>It looks like local reactive JSON state.</p>
<p>And you get auth, orgs & teams, real-time multiplayer, edit histories, permissions, E2E encryption and offline-support out of the box.</p>
<p>
Do everything right from the frontend and ship better apps, faster.
This lets you get rid of 90% of the traditional backend, and most of your frontend state juggling.
You&apos;ll ship better apps, faster.
</p>
<p>
Open source. Self-host or use{" "}
<p className="text-base">
Self-host or use{" "}
<Link className="text-reset" href="/cloud">
Jazz Cloud
</Link>{" "}
for zero-config magic.
for a zero-deploy globally-scaled DB.
<br/>Open source (MIT)
</p>
</Prose>
<div className="grid grid-cols-2 gap-2 max-w-3xl sm:grid-cols-4 sm:gap-4">
{features.map(({ title, icon }) => (
<div
key={title}
className="flex text-xs sm:text-sm gap-2 items-center"
>
<span className="p-1.5 rounded-lg bg-primary-transparent">
<Icon size="xs" name={icon} intent="primary" />
</span>
<p>{title}</p>
</div>
))}
</div>
</div>
</div>
);

View File

@@ -83,7 +83,7 @@ export function HowJazzWorksSection() {
<div className="grid gap-3">
<Kicker>How it works</Kicker>
<H2>Build entire apps using only client-side code</H2>
<H2>Build entire apps with collaborative state</H2>
</div>
<GappedGrid>
<Step

View File

@@ -53,7 +53,7 @@ export function LocalFirstFeaturesSection() {
return (
<div>
<SectionHeader
title="The best of all worlds"
title="Local-first state with global sync"
slogan={
<>
<p>

View File

@@ -9,7 +9,7 @@ export default function ProblemStatementSection() {
<div className="grid gap-4 lg:gap-8">
<SectionHeader
className="sm:text-center sm:mx-auto"
title={"Powered by the first “flat stack”"}
title={"A database that does what's actually needed"}
slogan="A perspective shift worth 10,000 hours"
/>
@@ -41,8 +41,7 @@ export default function ProblemStatementSection() {
<Prose>
<p>
For each new app you tackle a{" "}
<strong>mess of moving parts and infra worries.</strong> Or, you
haven't even tried because "you're not full-stack".
<strong>mess of moving parts and infra worries.</strong> Your backend is responsible for shuffling data around in a myriad of ways.
</p>
<p>
Want to build a <strong>modern app</strong> with multiplayer or
@@ -68,7 +67,7 @@ export default function ProblemStatementSection() {
<strong>With users &amp; permissions built-in.</strong>
</p>
<p>
With completely <strong>app-independent infra,</strong> you get to
With a <strong>DB and infra made for modern apps</strong> you get to
focus on <strong>building the app your users want.</strong> You'll
notice that <strong>90% of the work is now the UI.</strong>
</p>

View File

@@ -1,5 +1,6 @@
import { BunLogo } from "@/components/icons/BunLogo";
import { CloudflareWorkerLogo } from "@/components/icons/CloudflareWorkerLogo";
import { VercelLogo } from "@/components/icons/VercelLogo";
import { ExpoLogo } from "@/components/icons/ExpoLogo";
import { JavascriptLogo } from "@/components/icons/JavascriptLogo";
import { NodejsLogo } from "@/components/icons/NodejsLogo";
@@ -44,14 +45,18 @@ const serverWorkers = [
icon: NodejsLogo,
href: "/docs/react/server-workers",
},
{
name: "Cloudflare Workers",
icon: CloudflareWorkerLogo,
},
{
name: "Bun",
icon: BunLogo,
},
{
name: "Vercel",
icon: VercelLogo,
},
{
name: "CF Workers",
icon: CloudflareWorkerLogo,
}
];
export function SupportedEnvironmentsSection() {

View File

@@ -0,0 +1,16 @@
import React from "react";
import type { SVGProps } from "react";
export function VercelLogo(props: SVGProps<SVGSVGElement>) {
return (
<svg
width="1.5em"
height="1.5em"
viewBox="0 0 76 65"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M37.5274 0L75.0548 65H0L37.5274 0Z" fill="currentColor" />
</svg>
);
}

View File

@@ -54,7 +54,7 @@ function AuthStateIndicator() {
const isGuest = agent._type !== "Account"
// Anonymous authentication: has an account but not fully authenticated
const isAnonymous = agent._type === "Account" && !isAuthenticated;
const isAnonymous = agent._type === "Account" && !isAuthenticated;
return (
<div>
{isGuest && <span>Guest Mode</span>}

View File

@@ -31,7 +31,7 @@ export const Organization = co.map({
name: z.string(),
// shared data between users of each organization
projects: co.list(Project),
projects: co.list(Project),
});
export const ListOfOrganizations = co.list(Organization);
@@ -115,7 +115,7 @@ import * as React from "react";
import { useAcceptInvite, useAccount } from "jazz-tools/react";
import { co, z } from "jazz-tools";
const Project = z.object({
const Project = co.map({
name: z.string(),
});

View File

@@ -205,6 +205,101 @@ console.log(containingGroup.getParentGroups()); // [addedGroup]
```
</CodeGroup>
## Group hierarchy on CoValue creation
When creating CoValues that contain other CoValues using plain JSON objects, Jazz not only creates
the necessary CoValues automatically but it will also manage their group ownership.
<CodeGroup>
```ts twoslash
import { co, z } from "jazz-tools";
// ---cut---
const Task = co.plainText();
const Column = co.list(Task);
const Board = co.map({
title: z.string(),
columns: co.list(Column),
});
const board = Board.create({
title: "My board",
columns: [
["Task 1.1", "Task 1.2"],
["Task 2.1", "Task 2.2"],
],
});
```
</CodeGroup>
For each created column and task CoValue, Jazz also creates a new group as its owner and
adds the referencing CoValue's owner as a member of that group. This means permissions for nested CoValues
are inherited from the CoValue that references them, but can also be modified independently for each CoValue
if needed.
<CodeGroup>
```ts twoslash
import { co, z, Group, Account } from "jazz-tools";
const alice = {} as unknown as Account;
const bob = {} as unknown as Account;
const Task = co.plainText();
const Column = co.list(Task);
const Board = co.map({
title: z.string(),
columns: co.list(Column),
});
// ---cut---
const writeAccess = Group.create();
writeAccess.addMember(bob, "writer");
// Give Bob write access to the board, columns and tasks
const board = Board.create({
title: "My board",
columns: [
["Task 1.1", "Task 1.2"],
["Task 2.1", "Task 2.2"],
],
}, writeAccess);
// Give Alice read access to one specific task
const task = board.columns[0][0];
const taskGroup = task._owner.castAs(Group);
taskGroup.addMember(alice, "reader");
```
</CodeGroup>
If you prefer to manage permissions differently, you can always create CoValues explicitly:
<CodeGroup>
```ts twoslash
import { co, Group, z, Account } from "jazz-tools";
const bob = {} as unknown as Account;
const Task = co.plainText();
const Column = co.list(Task);
const Board = co.map({
title: z.string(),
columns: co.list(Column),
});
// ---cut---
const writeAccess = Group.create();
writeAccess.addMember(bob, "writer");
const readAccess = Group.create();
readAccess.addMember(bob, "reader");
// Give Bob read access to the board and write access to the columns and tasks
const board = Board.create({
title: "My board",
columns: co.list(Column).create([
["Task 1.1", "Task 1.2"],
["Task 2.1", "Task 2.2"],
], writeAccess),
}, readAccess);
```
</CodeGroup>
## Example: Team Hierarchy
Here's a practical example of using group inheritance for team permissions:

View File

@@ -7,9 +7,11 @@ export const metadata = {
# Learn some <span className="sr-only">Jazz</span> <JazzLogo className="h-[41px] -ml-0.5 -mt-[3px] inline" />
**Jazz is a toolkit for building backendless apps**. You get data without needing a database — plus auth, permissions, files and multiplayer without needing a backend. Jazz lets you do everything right from the frontend and you'll ship better apps, faster.
**Jazz is a new kind of database** that's **distributed** across your frontend, containers, serverless functions and its own storage cloud.
Instead of wrestling with databases, APIs, and server infrastructure, you work with **CoValues** ("collaborative values") — your new cloud-synced building blocks that feel like local state but automatically sync across all devices and users in real-time.
It syncs structured data, files and LLM streams instantly, and looks like local reactive JSON state.
It also provides auth, orgs & teams, real-time multiplayer, edit histories, permissions, E2E encryption and offline-support out of the box.
---
@@ -19,7 +21,7 @@ You can use [`create-jazz-app`](/docs/tools/create-jazz-app) to create a new Jaz
<CodeGroup>
```sh
npx create-jazz-app@latest --api-key you@example.com
npx create-jazz-app@latest --api-key you@example.com
```
</CodeGroup>
@@ -30,21 +32,10 @@ Or you can follow this [React step-by-step guide](/docs/react/guide) where we wa
</ContentByFramework> */}
## Why Jazz is different
Most apps rebuild the same thing: shared state that syncs between users and devices. Jazz starts from that shared state, giving you:
- **No backend required** — Focus on building features, not infrastructure
- **Real-time sync** — Changes appear everywhere immediately
- **Multiplayer by default** — Collaboration just works
- **Local-first** — Your app works offline and feels instant
Think Figma, Notion, or Linear — but you don't need years to build a custom stack.
## How it works
1. **Define your data** with CoValues schemas
2. **Connect to sync infrastructure** (Jazz Cloud or self-hosted)
2. **Connect to storage infrastructure** (Jazz Cloud or self-hosted)
3. **Create and edit CoValues** like normal objects
4. **Get automatic sync and persistence** across all devices and users

View File

@@ -3,6 +3,7 @@ export const metadata = {
};
import { CodeGroup, ComingSoon } from "@/components/forMdx";
import { Alert } from "@garden-co/design-system/src/components/atoms/Alert";
# Defining schemas: CoValues
@@ -80,6 +81,40 @@ const project = TodoProject.create(
</CodeGroup>
When creating CoValues that contain other CoValues, you can pass in a plain JSON object.
Jazz will automatically create the CoValues for you.
<CodeGroup>
```ts twoslash
// @filename: schema.ts
import { co, z, CoMap, CoList } from "jazz-tools";
export const ListOfTasks = co.list(z.string());
export const TodoProject = co.map({
title: z.string(),
tasks: ListOfTasks,
});
// @filename: app.ts
// ---cut---
// app.ts
import { Group } from "jazz-tools";
import { TodoProject, ListOfTasks } from "./schema";
const group = Group.create().makePublic();
const project = TodoProject.create({
title: "New Project",
tasks: [], // Permissions are inherited, so the tasks list will also be public
}, group);
```
</CodeGroup>
<Alert variant="info" className="flex gap-2 items-center my-4">
To learn more about how permissions work when creating nested CoValues with plain JSON objects,
refer to [Group hierarchy on CoValue creation](/docs/groups/inheritance#group-hierarchy-on-covalue-creation).
</Alert>
## Types of CoValues
### `CoMap` (declaration)
@@ -320,6 +355,10 @@ const Company = co.map({
</CodeGroup>
#### Optional References
You can make schema fields optional using either `z.optional()` or `co.optional()`, depending on the type of value:
- Use `z.optional()` for primitive Zod values like `z.string()`, `z.number()`, or `z.boolean()`
- Use `co.optional()` for CoValues like `co.map()`, `co.list()`, or `co.record()`
You can make references optional with `co.optional()`:
@@ -331,7 +370,8 @@ const Pet = co.map({
});
// ---cut---
const Person = co.map({
pet: co.optional(Pet),
age: z.optional(z.number()), // primitive
pet: co.optional(Pet), // CoValue
});
```
</CodeGroup>

View File

@@ -7,7 +7,7 @@ import { Alert } from "@garden-co/design-system/src/components/atoms/Alert";
# Inbox API with Server Workers
The Inbox API provides a message-based communication system for Server Workers in Jazz.
The Inbox API provides a message-based communication system for Server Workers in Jazz.
It works on top of the Jazz APIs and uses sync to transfer messages between the client and the server.
@@ -154,8 +154,8 @@ function EventComponent({ event }: { event: Event }) {
```
</CodeGroup>
The `sendInboxMessage` API returns a Promise that waits for the message to be handled by a Worker.
A message is considered to be handled when the Promise returned by `inbox.subscribe` resolves.
The `sendInboxMessage` API returns a Promise that waits for the message to be handled by a Worker.
A message is considered to be handled when the Promise returned by `inbox.subscribe` resolves.
The value returned will be the id of the CoValue returned in the `inbox.subscribe` resolved promise.
@@ -163,4 +163,4 @@ The value returned will be the id of the CoValue returned in the `inbox.subscrib
Multi-region deployments are not supported when using the Inbox API.
If you need to split the workload across multiple regions, you can use the [HTTP API](./http-requests.mdx) instead.
If you need to split the workload across multiple regions, you can use the [HTTP API](./http-requests) instead.

View File

@@ -1,5 +1,5 @@
export const marketingCopy = {
headline: "Whip up an app",
headline: "Smooth database.",
description:
"Jazz gives you data without needing a database — plus auth, permissions, files and multiplayer without needing a backend. Do everything right from the frontend and ship better apps, faster.",
"Jazz is a database that's distributed across your frontend, containers and functions. It syncs structured data, files and LLM streams instantly and looks like local reactive JSON state.",
};

View File

@@ -1,5 +1,17 @@
# cojson-storage-indexeddb
## 0.16.3
### Patch Changes
- cojson@0.16.3
## 0.16.2
### Patch Changes
- cojson@0.16.2
## 0.16.1
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "cojson-storage-indexeddb",
"version": "0.16.1",
"version": "0.16.3",
"main": "dist/index.js",
"type": "module",
"types": "dist/index.d.ts",

View File

@@ -1,5 +1,17 @@
# cojson-storage-sqlite
## 0.16.3
### Patch Changes
- cojson@0.16.3
## 0.16.2
### Patch Changes
- cojson@0.16.2
## 0.16.1
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "cojson-storage-sqlite",
"type": "module",
"version": "0.16.1",
"version": "0.16.3",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"license": "MIT",

View File

@@ -1,5 +1,17 @@
# cojson-transport-nodejs-ws
## 0.16.3
### Patch Changes
- cojson@0.16.3
## 0.16.2
### Patch Changes
- cojson@0.16.2
## 0.16.1
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "cojson-transport-ws",
"type": "module",
"version": "0.16.1",
"version": "0.16.3",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"license": "MIT",

View File

@@ -1,5 +1,9 @@
# cojson
## 0.16.3
## 0.16.2
## 0.16.1
## 0.16.0

View File

@@ -25,7 +25,7 @@
},
"type": "module",
"license": "MIT",
"version": "0.16.1",
"version": "0.16.3",
"devDependencies": {
"@opentelemetry/sdk-metrics": "^2.0.0",
"libsql": "^0.5.13",

View File

@@ -1,5 +1,22 @@
# jazz-auth-betterauth
## 0.16.3
### Patch Changes
- Updated dependencies [43d3511]
- jazz-tools@0.16.3
- jazz-betterauth-client-plugin@0.16.3
- cojson@0.16.3
## 0.16.2
### Patch Changes
- cojson@0.16.2
- jazz-betterauth-client-plugin@0.16.2
- jazz-tools@0.16.2
## 0.16.1
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-auth-betterauth",
"version": "0.16.1",
"version": "0.16.3",
"type": "module",
"main": "dist/index.js",
"types": "src/index.ts",

View File

@@ -1,5 +1,17 @@
# jazz-betterauth-client-plugin
## 0.16.3
### Patch Changes
- jazz-betterauth-server-plugin@0.16.3
## 0.16.2
### Patch Changes
- jazz-betterauth-server-plugin@0.16.2
## 0.16.1
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-betterauth-client-plugin",
"version": "0.16.1",
"version": "0.16.3",
"type": "module",
"main": "dist/index.js",
"types": "src/index.ts",

View File

@@ -1,5 +1,20 @@
# jazz-betterauth-server-plugin
## 0.16.3
### Patch Changes
- Updated dependencies [43d3511]
- jazz-tools@0.16.3
- cojson@0.16.3
## 0.16.2
### Patch Changes
- cojson@0.16.2
- jazz-tools@0.16.2
## 0.16.1
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-betterauth-server-plugin",
"version": "0.16.1",
"version": "0.16.3",
"type": "module",
"main": "dist/index.js",
"types": "src/index.ts",

View File

@@ -1,5 +1,24 @@
# jazz-react-auth-betterauth
## 0.16.3
### Patch Changes
- Updated dependencies [43d3511]
- jazz-tools@0.16.3
- jazz-auth-betterauth@0.16.3
- jazz-betterauth-client-plugin@0.16.3
- cojson@0.16.3
## 0.16.2
### Patch Changes
- cojson@0.16.2
- jazz-auth-betterauth@0.16.2
- jazz-betterauth-client-plugin@0.16.2
- jazz-tools@0.16.2
## 0.16.1
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-react-auth-betterauth",
"version": "0.16.1",
"version": "0.16.3",
"type": "module",
"main": "dist/index.js",
"types": "src/index.tsx",

View File

@@ -1,5 +1,25 @@
# jazz-run
## 0.16.3
### Patch Changes
- Updated dependencies [43d3511]
- jazz-tools@0.16.3
- cojson@0.16.3
- cojson-storage-sqlite@0.16.3
- cojson-transport-ws@0.16.3
## 0.16.2
### Patch Changes
- 239da90: Fix jazz-run package.json exports
- cojson@0.16.2
- cojson-storage-sqlite@0.16.2
- cojson-transport-ws@0.16.2
- jazz-tools@0.16.2
## 0.16.1
### Patch Changes

View File

@@ -3,15 +3,15 @@
"bin": "./dist/index.js",
"type": "module",
"license": "MIT",
"version": "0.16.1",
"version": "0.16.3",
"exports": {
"./startSyncServer": {
"import": "./dist/startSyncServer.js",
"types": "./dist/startSyncServer.d.ts"
"types": "./dist/startSyncServer.d.ts",
"default": "./dist/startSyncServer.js"
},
"./createWorkerAccount": {
"import": "./dist/createWorkerAccount.js",
"types": "./dist/createWorkerAccount.d.ts"
"types": "./dist/createWorkerAccount.d.ts",
"default": "./dist/createWorkerAccount.js"
}
},
"scripts": {
@@ -28,11 +28,11 @@
"@effect/printer-ansi": "^0.34.5",
"@effect/schema": "^0.71.1",
"@effect/typeclass": "^0.25.5",
"cojson": "workspace:0.16.1",
"cojson-storage-sqlite": "workspace:0.16.1",
"cojson-transport-ws": "workspace:0.16.1",
"cojson": "workspace:0.16.3",
"cojson-storage-sqlite": "workspace:0.16.3",
"cojson-transport-ws": "workspace:0.16.3",
"effect": "^3.6.5",
"jazz-tools": "workspace:0.16.1",
"jazz-tools": "workspace:0.16.3",
"ws": "^8.14.2"
},
"devDependencies": {

View File

@@ -1,5 +1,24 @@
# jazz-tools
## 0.16.3
### Patch Changes
- 43d3511: Streamlined CoValue creation:
- CoValues can be created with plain JSON objects. Nested CoValues will be automatically created when necessary.
- Optional fields can be ommited (i.e. it's no longer necessary to provide an explicit `undefined` value).
- cojson@0.16.3
- cojson-storage-indexeddb@0.16.3
- cojson-transport-ws@0.16.3
## 0.16.2
### Patch Changes
- cojson@0.16.2
- cojson-storage-indexeddb@0.16.2
- cojson-transport-ws@0.16.2
## 0.16.1
### Patch Changes

View File

@@ -139,7 +139,7 @@
},
"type": "module",
"license": "MIT",
"version": "0.16.1",
"version": "0.16.3",
"dependencies": {
"@manuscripts/prosemirror-recreate-steps": "^0.1.4",
"@scure/base": "1.2.1",

View File

@@ -141,13 +141,102 @@ function useCoValueSubscription<
return subscription.subscription;
}
/**
* React hook for subscribing to CoValues and handling loading states.
*
* This hook provides a convenient way to subscribe to CoValues and automatically
* handles the subscription lifecycle (subscribe on mount, unsubscribe on unmount).
* It also supports deep loading of nested CoValues through resolve queries.
*
* @returns The loaded CoValue, or `undefined` if loading, or `null` if not found/not accessible
*
* @example
* ```tsx
* // Deep loading with resolve queries
* const Project = co.map({
* name: z.string(),
* tasks: co.list(Task),
* owner: TeamMember,
* });
*
* function ProjectView({ projectId }: { projectId: string }) {
* const project = useCoState(Project, projectId, {
* resolve: {
* tasks: { $each: true },
* owner: true,
* },
* });
*
* if (!project) {
* return project === null
* ? "Project not found or not accessible"
* : "Loading project...";
* }
*
* return (
* <div>
* <h1>{project.name}</h1>
* <p>Owner: {project.owner.name}</p>
* <ul>
* {project.tasks.map((task) => (
* <li key={task.id}>{task.title}</li>
* ))}
* </ul>
* </div>
* );
* }
* ```
*
* @example
* ```tsx
* // Using with optional references and error handling
* const Task = co.map({
* title: z.string(),
* assignee: co.optional(TeamMember),
* subtasks: co.list(Task),
* });
*
* function TaskDetail({ taskId }: { taskId: string }) {
* const task = useCoState(Task, taskId, {
* resolve: {
* assignee: true,
* subtasks: { $each: { $onError: null } },
* },
* });
*
* if (!task) {
* return task === null
* ? "Task not found or not accessible"
* : "Loading task...";
* }
*
* return (
* <div>
* <h2>{task.title}</h2>
* {task.assignee && <p>Assigned to: {task.assignee.name}</p>}
* <ul>
* {task.subtasks.map((subtask, index) => (
* subtask ? <li key={subtask.id}>{subtask.title}</li> : <li key={index}>Inaccessible subtask</li>
* ))}
* </ul>
* </div>
* );
* }
* ```
*
* For more examples, see the [subscription and deep loading](https://jazz.tools/docs/react/using-covalues/subscription-and-loading) documentation.
*/
export function useCoState<
S extends CoValueClassOrSchema,
const R extends ResolveQuery<S> = true,
>(
/** The CoValue schema or class constructor */
Schema: S,
/** The ID of the CoValue to subscribe to. If `undefined`, returns `null` */
id: string | undefined,
/** Optional configuration for the subscription */
options?: {
/** Resolve query to specify which nested CoValues to load */
resolve?: ResolveQueryStrict<S, R>;
},
): Loaded<S, R> | undefined | null {
@@ -229,12 +318,66 @@ function useAccountSubscription<
return subscription.subscription;
}
/**
* React hook for accessing the current user's account and authentication state.
*
* This hook provides access to the current user's account profile and root data,
* along with authentication utilities. It automatically handles subscription to
* the user's account data and provides a logout function.
*
* @returns An object containing:
* - `me`: The loaded account data, or `undefined` if loading, or `null` if not authenticated
* - `agent`: The current agent (anonymous or authenticated user). Can be used as `loadAs` parameter for load and subscribe methods.
* - `logOut`: Function to log out the current user
* @example
* ```tsx
* // Deep loading with resolve queries
* function ProjectListWithDetails() {
* const { me } = useAccount(MyAppAccount, {
* resolve: {
* profile: true,
* root: {
* myProjects: {
* $each: {
* tasks: true,
* },
* },
* },
* },
* });
*
* if (!me) {
* return me === null
* ? <div>Failed to load your projects</div>
* : <div>Loading...</div>;
* }
*
* return (
* <div>
* <h1>{me.profile.name}'s projects</h1>
* <ul>
* {me.root.myProjects.map((project) => (
* <li key={project.id}>
* {project.name} ({project.tasks.length} tasks)
* </li>
* ))}
* </ul>
* </div>
* );
* }
* ```
*
*/
export function useAccount<
A extends AccountClass<Account> | AnyAccountSchema,
R extends ResolveQuery<A> = true,
>(
/** The account schema to use. Defaults to the base Account schema */
AccountSchema: A = Account as unknown as A,
/** Optional configuration for the subscription */
options?: {
/** Resolve query to specify which nested CoValues to load from the account */
resolve?: ResolveQueryStrict<A, R>;
},
): {

View File

@@ -34,6 +34,7 @@ import {
coField,
ensureCoValueLoaded,
inspect,
instantiateRefEncodedWithInit,
isRefEncoded,
loadCoValueWithoutMe,
parseCoValueCreateOptions,
@@ -278,7 +279,16 @@ export class CoFeed<out Item = any> extends CoValueBase implements CoValue {
} else if ("encoded" in itemDescriptor) {
this._raw.push(itemDescriptor.encoded.encode(item));
} else if (isRefEncoded(itemDescriptor)) {
this._raw.push((item as unknown as CoValue).id);
let refId = (item as unknown as CoValue).id;
if (!refId) {
const coValue = instantiateRefEncodedWithInit(
itemDescriptor,
item,
this._owner,
);
refId = coValue.id;
}
this._raw.push(refId);
}
}
@@ -418,9 +428,7 @@ export class CoFeed<out Item = any> extends CoValueBase implements CoValue {
*
* @category Subscription & Loading
*/
waitForSync(options?: {
timeout?: number;
}) {
waitForSync(options?: { timeout?: number }) {
return this._raw.core.waitForSync(options);
}
}

View File

@@ -29,6 +29,7 @@ import {
coValuesCache,
ensureCoValueLoaded,
inspect,
instantiateRefEncodedWithInit,
isRefEncoded,
loadCoValueWithoutMe,
makeRefs,
@@ -240,7 +241,7 @@ export class CoList<out Item = any> extends Array<Item> implements CoValue {
const { owner } = parseCoValueCreateOptions(options);
const instance = new this({ init: items, owner });
const raw = owner._raw.createList(
toRawItems(items, instance._schema[ItemsSym]),
toRawItems(items, instance._schema[ItemsSym], owner),
);
Object.defineProperties(instance, {
@@ -256,7 +257,7 @@ export class CoList<out Item = any> extends Array<Item> implements CoValue {
push(...items: Item[]): number {
this._raw.appendItems(
toRawItems(items, this._schema[ItemsSym]),
toRawItems(items, this._schema[ItemsSym], this._owner),
undefined,
"private",
);
@@ -265,7 +266,11 @@ export class CoList<out Item = any> extends Array<Item> implements CoValue {
}
unshift(...items: Item[]): number {
for (const item of toRawItems(items as Item[], this._schema[ItemsSym])) {
for (const item of toRawItems(
items as Item[],
this._schema[ItemsSym],
this._owner,
)) {
this._raw.prepend(item);
}
@@ -306,7 +311,11 @@ export class CoList<out Item = any> extends Array<Item> implements CoValue {
this._raw.delete(idxToDelete);
}
const rawItems = toRawItems(items as Item[], this._schema[ItemsSym]);
const rawItems = toRawItems(
items as Item[],
this._schema[ItemsSym],
this._owner,
);
// If there are no items to insert, return the deleted items
if (rawItems.length === 0) {
@@ -551,23 +560,36 @@ export class CoList<out Item = any> extends Array<Item> implements CoValue {
* Convert an array of items to a raw array of items.
* @param items - The array of items to convert.
* @param itemDescriptor - The descriptor of the items.
* @param owner - The owner of the CoList.
* @returns The raw array of items.
*/
function toRawItems<Item>(items: Item[], itemDescriptor: Schema) {
const rawItems =
itemDescriptor === "json"
? (items as JsonValue[])
: "encoded" in itemDescriptor
? items?.map((e) => itemDescriptor.encoded.encode(e))
: isRefEncoded(itemDescriptor)
? items?.map((v) => {
if (!v) return null;
return (v as unknown as CoValue).id;
})
: (() => {
throw new Error("Invalid element descriptor");
})();
function toRawItems<Item>(
items: Item[],
itemDescriptor: Schema,
owner: Account | Group,
) {
let rawItems: JsonValue[] = [];
if (itemDescriptor === "json") {
rawItems = items as JsonValue[];
} else if ("encoded" in itemDescriptor) {
rawItems = items?.map((e) => itemDescriptor.encoded.encode(e));
} else if (isRefEncoded(itemDescriptor)) {
rawItems = items?.map((value) => {
if (value == null) return null;
let refId = (value as unknown as CoValue).id;
if (!refId) {
const coValue = instantiateRefEncodedWithInit(
itemDescriptor,
value,
owner,
);
refId = coValue.id;
}
return refId;
});
} else {
throw new Error("Invalid element descriptor");
}
return rawItems;
}

View File

@@ -37,6 +37,7 @@ import {
activeAccountContext,
ensureCoValueLoaded,
inspect,
instantiateRefEncodedWithInit,
isRefEncoded,
loadCoValueWithoutMe,
makeRefs,
@@ -424,8 +425,17 @@ export class CoMap extends CoValueBase implements CoValue {
if (descriptor === "json") {
rawInit[key] = initValue as JsonValue;
} else if (isRefEncoded(descriptor)) {
if (initValue) {
rawInit[key] = (initValue as unknown as CoValue).id;
if (initValue != null) {
let refId = (initValue as unknown as CoValue).id;
if (!refId) {
const coValue = instantiateRefEncodedWithInit(
descriptor,
initValue,
owner,
);
refId = coValue.id;
}
rawInit[key] = refId;
}
} else if ("encoded" in descriptor) {
rawInit[key] = descriptor.encoded.encode(

View File

@@ -145,7 +145,7 @@ export class Inbox {
for (const [sessionID, items] of Object.entries(stream.items) as [
SessionID,
CoStreamItem<CoID<InboxMessage<InstanceOfSchema<M>, O>>>[],
CoStreamItem<CoID<InboxMessage<NonNullable<InstanceOfSchema<M>>, O>>>[],
][]) {
const accountID = getAccountIDfromSessionID(sessionID);

View File

@@ -1,12 +1,16 @@
import { JsonValue, RawCoMap } from "cojson";
import {
Account,
AnonymousJazzAgent,
CoMapInit,
CoValue,
CoValueBase,
CoValueClass,
CoValueFromRaw,
Group,
ID,
Resolved,
Simplify,
SubscribeListenerOptions,
SubscribeRestArgs,
loadCoValueWithoutMe,
@@ -20,6 +24,10 @@ import {
export type SchemaUnionConcreteSubclass<V extends CoValue> =
typeof SchemaUnion & CoValueClass<V>;
export type SchemaUnionDiscriminator<V extends CoValue> = (discriminable: {
get(key: string): JsonValue | undefined;
}) => CoValueClass<V> & CoValueFromRaw<V>;
/**
* SchemaUnion allows you to create union types of CoValues that can be discriminated at runtime.
*
@@ -89,28 +97,45 @@ export abstract class SchemaUnion extends CoValueBase implements CoValue {
* @category Declaration
**/
static Of<V extends CoValue>(
discriminator: (raw: V["_raw"]) => CoValueClass<V> & CoValueFromRaw<V>,
discriminator: SchemaUnionDiscriminator<V>,
): SchemaUnionConcreteSubclass<V> {
return class SchemaUnionClass extends SchemaUnion {
static override create<V extends CoValue>(
this: CoValueClass<V>,
init: Simplify<CoMapInit<V>>,
owner: Account | Group,
): V {
const ResolvedClass = discriminator(new Map(Object.entries(init)));
// @ts-expect-error - create is a static method in the CoMap class
return ResolvedClass.create(init, owner);
}
static override fromRaw<T extends CoValue>(
this: CoValueClass<T> & CoValueFromRaw<T>,
raw: T["_raw"],
): T {
const ResolvedClass = discriminator(
raw as V["_raw"],
raw as RawCoMap,
) as unknown as CoValueClass<T> & CoValueFromRaw<T>;
return ResolvedClass.fromRaw(raw);
}
} as unknown as SchemaUnionConcreteSubclass<V>;
}
static create<V extends CoValue>(
this: CoValueClass<V>,
init: Simplify<CoMapInit<V>>,
owner: Account | Group,
): V {
throw new Error("Not implemented");
}
/**
* Create an instance from raw data. This is called internally and should not be used directly.
* Use {@link SchemaUnion.Of} to create a union type instead.
*
* @internal
*/
// @ts-ignore
static fromRaw<V extends CoValue>(this: CoValueClass<V>, raw: V["_raw"]): V {
throw new Error("Not implemented");
}

View File

@@ -107,7 +107,6 @@ export {
type CoreAccountSchema as AnyAccountSchema,
type ResolveQuery,
type ResolveQueryStrict,
type InitFor,
} from "./internal.js";
export {

View File

@@ -1,11 +1,12 @@
import type { JsonValue, RawCoValue } from "cojson";
import { CojsonInternalTypes } from "cojson";
import {
Account,
type CoValue,
type CoValueClass,
CoValueFromRaw,
Group,
ItemsSym,
JazzToolsSymbol,
SchemaInit,
isCoValueClass,
} from "../internal.js";
@@ -140,7 +141,7 @@ export function isRefEncoded<V extends CoValue>(
);
}
export function instantiateRefEncoded<V extends CoValue>(
export function instantiateRefEncodedFromRaw<V extends CoValue>(
schema: RefEncoded<V>,
raw: RawCoValue,
): V {
@@ -151,6 +152,31 @@ export function instantiateRefEncoded<V extends CoValue>(
).fromRaw(raw);
}
/**
* Creates a new CoValue of the given ref type, using the provided init values.
*
* @param schema - The schema of the CoValue to create.
* @param init - The init values to use to create the CoValue.
* @param parentOwner - The owner of the referencing CoValue. Will be used
* as the parent group of the created CoValue's group
* @returns The created CoValue.
*/
export function instantiateRefEncodedWithInit<V extends CoValue>(
schema: RefEncoded<V>,
init: any,
parentOwner: Account | Group,
): V {
if (!isCoValueClass<V>(schema.ref)) {
throw Error(
`Cannot automatically create CoValue from value: ${JSON.stringify(init)}. Use the CoValue schema's create() method instead.`,
);
}
const owner = Group.create();
owner.addMember(parentOwner.castAs(Group));
// @ts-expect-error - create is a static method in all CoValue classes
return schema.ref.create(init, owner);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type Schema = JsonEncoded | RefEncoded<CoValue> | EncodedAs<any>;

View File

@@ -6,21 +6,16 @@ import {
RefsToResolve,
RefsToResolveStrict,
Resolved,
Simplify,
SubscribeListenerOptions,
coOptionalDefiner,
} from "../../../internal.js";
import { AnonymousJazzAgent } from "../../anonymousJazzAgent.js";
import { CoFieldInit } from "../typeConverters/CoFieldInit.js";
import { CoFeedInit } from "../typeConverters/CoFieldInit.js";
import { InstanceOrPrimitiveOfSchema } from "../typeConverters/InstanceOrPrimitiveOfSchema.js";
import { InstanceOrPrimitiveOfSchemaCoValuesNullable } from "../typeConverters/InstanceOrPrimitiveOfSchemaCoValuesNullable.js";
import { CoOptionalSchema } from "./CoOptionalSchema.js";
import { CoreCoValueSchema } from "./CoValueSchema.js";
type CoFeedInit<T extends AnyZodOrCoValueSchema> = Simplify<
Array<CoFieldInit<T>>
>;
export class CoFeedSchema<T extends AnyZodOrCoValueSchema>
implements CoreCoFeedSchema<T>
{
@@ -36,7 +31,7 @@ export class CoFeedSchema<T extends AnyZodOrCoValueSchema>
init: CoFeedInit<T>,
options?: { owner: Account | Group } | Account | Group,
): CoFeedInstance<T> {
return this.coValueClass.create(init, options) as CoFeedInstance<T>;
return this.coValueClass.create(init as any, options) as CoFeedInstance<T>;
}
load<const R extends RefsToResolve<CoFeedInstanceCoValuesNullable<T>> = true>(

View File

@@ -5,22 +5,17 @@ import {
RefsToResolve,
RefsToResolveStrict,
Resolved,
Simplify,
SubscribeListenerOptions,
coOptionalDefiner,
} from "../../../internal.js";
import { AnonymousJazzAgent } from "../../anonymousJazzAgent.js";
import { CoFieldInit } from "../typeConverters/CoFieldInit.js";
import { CoListInit } from "../typeConverters/CoFieldInit.js";
import { InstanceOrPrimitiveOfSchema } from "../typeConverters/InstanceOrPrimitiveOfSchema.js";
import { InstanceOrPrimitiveOfSchemaCoValuesNullable } from "../typeConverters/InstanceOrPrimitiveOfSchemaCoValuesNullable.js";
import { AnyZodOrCoValueSchema } from "../zodSchema.js";
import { CoOptionalSchema } from "./CoOptionalSchema.js";
import { CoreCoValueSchema } from "./CoValueSchema.js";
type CoListInit<T extends AnyZodOrCoValueSchema> = Simplify<
Array<CoFieldInit<T>>
>;
export class CoListSchema<T extends AnyZodOrCoValueSchema>
implements CoreCoListSchema<T>
{
@@ -36,7 +31,7 @@ export class CoListSchema<T extends AnyZodOrCoValueSchema>
items: CoListInit<T>,
options?: { owner: Account | Group } | Account | Group,
): CoListInstance<T> {
return this.coValueClass.create(items, options) as CoListInstance<T>;
return this.coValueClass.create(items as any, options) as CoListInstance<T>;
}
load<const R extends RefsToResolve<CoListInstanceCoValuesNullable<T>> = true>(

View File

@@ -5,7 +5,6 @@ import {
DiscriminableCoValueSchemaDefinition,
DiscriminableCoreCoValueSchema,
Group,
PartialOnUndefined,
RefsToResolve,
RefsToResolveStrict,
Resolved,
@@ -17,7 +16,7 @@ import {
} from "../../../internal.js";
import { AnonymousJazzAgent } from "../../anonymousJazzAgent.js";
import { removeGetters } from "../../schemaUtils.js";
import { CoFieldInit } from "../typeConverters/CoFieldInit.js";
import { CoMapSchemaInit } from "../typeConverters/CoFieldInit.js";
import { InstanceOrPrimitiveOfSchema } from "../typeConverters/InstanceOrPrimitiveOfSchema.js";
import { InstanceOrPrimitiveOfSchemaCoValuesNullable } from "../typeConverters/InstanceOrPrimitiveOfSchemaCoValuesNullable.js";
import { z } from "../zodReExport.js";
@@ -30,7 +29,7 @@ export interface CoMapSchema<
Owner extends Account | Group = Account | Group,
> extends CoreCoMapSchema<Shape, CatchAll> {
create: (
init: Simplify<CoMapSchemaInit<Shape>>,
init: CoMapSchemaInit<Shape>,
options?:
| {
owner: Owner;
@@ -227,13 +226,6 @@ export function enrichCoMapSchema<
return coValueSchema;
}
// Due to a TS limitation with types that contain known properties and
// an index signature, we cannot accept catchall properties on creation
export type CoMapSchemaInit<Shape extends z.core.$ZodLooseShape> =
PartialOnUndefined<{
[key in keyof Shape]: CoFieldInit<Shape[key]>;
}>;
export interface CoMapSchemaDefinition<
Shape extends z.core.$ZodLooseShape = z.core.$ZodLooseShape,
CatchAll extends AnyZodOrCoValueSchema | unknown = unknown,

View File

@@ -1,13 +1,78 @@
import { NotNull } from "../../../internal.js";
import {
CoDiscriminatedUnionSchema,
CoValueClass,
CoreCoFeedSchema,
CoreCoListSchema,
CoreCoMapSchema,
CoreCoRecordSchema,
CorePlainTextSchema,
PartialOnUndefined,
Simplify,
} from "../../../internal.js";
import { CoreCoOptionalSchema } from "../schemaTypes/CoOptionalSchema.js";
import { CoreCoValueSchema } from "../schemaTypes/CoValueSchema.js";
import { CoreRichTextSchema } from "../schemaTypes/RichTextSchema.js";
import { z } from "../zodReExport.js";
import { AnyZodOrCoValueSchema } from "../zodSchema.js";
import { InstanceOrPrimitiveOfSchemaCoValuesNullable } from "./InstanceOrPrimitiveOfSchemaCoValuesNullable.js";
import { AnyZodOrCoValueSchema, Loaded } from "../zodSchema.js";
import { TypeOfZodSchema } from "./TypeOfZodSchema.js";
/**
* Returns the type of the value that should be used to initialize a coField
* of the given schema.
* The type of value that can be used to initialize a CoField of the given schema.
*
* For CoValue fields, this can be either a shallowly-loaded CoValue instance
* or a JSON object that will be used to create the CoValue.
*/
export type CoFieldInit<T extends AnyZodOrCoValueSchema> =
T extends z.core.$ZodNullable
? InstanceOrPrimitiveOfSchemaCoValuesNullable<T>
: NotNull<InstanceOrPrimitiveOfSchemaCoValuesNullable<T>>;
export type CoFieldInit<S extends CoValueClass | AnyZodOrCoValueSchema> =
S extends CoreCoValueSchema
?
| Loaded<S>
| (S extends CoreCoRecordSchema<infer K, infer V>
? CoMapSchemaInit<{ [key in z.output<K> & string]: V }>
: S extends CoreCoMapSchema<infer Shape>
? CoMapSchemaInit<Shape>
: S extends CoreCoListSchema<infer T>
? CoListInit<T>
: S extends CoreCoFeedSchema<infer T>
? CoFeedInit<T>
: S extends CorePlainTextSchema | CoreRichTextSchema
? string
: S extends CoreCoOptionalSchema<infer T>
? CoFieldInit<T> | undefined
: S extends CoDiscriminatedUnionSchema<infer Members>
? CoFieldInit<Members[number]>
: never)
: S extends z.core.$ZodType
? TypeOfZodSchema<S>
: S extends CoValueClass
? InstanceType<S>
: never;
// Due to a TS limitation with types that contain known properties and
// an index signature, we cannot accept catchall properties on creation
export type CoMapSchemaInit<Shape extends z.core.$ZodLooseShape> = Simplify<
{
/**
* Cannot use {@link PartialOnUndefined} because evaluating CoFieldInit<Shape[Key]>
* to know if the value can be undefined does not work with recursive types.
*/
[Key in keyof Shape as Shape[Key] extends
| CoreCoOptionalSchema
| z.core.$ZodOptional
? never
: Key]: CoFieldInit<Shape[Key]>;
} & {
[Key in keyof Shape as Shape[Key] extends
| CoreCoOptionalSchema
| z.core.$ZodOptional
? Key
: never]?: CoFieldInit<Shape[Key]>;
}
>;
export type CoListInit<T extends AnyZodOrCoValueSchema> = Simplify<
ReadonlyArray<CoFieldInit<T>>
>;
export type CoFeedInit<T extends AnyZodOrCoValueSchema> = Simplify<
ReadonlyArray<CoFieldInit<T>>
>;

View File

@@ -27,15 +27,20 @@ export type InstanceOfSchema<S extends CoValueClass | AnyZodOrCoValueSchema> =
S extends CoreCoValueSchema
? S extends CoreAccountSchema<infer Shape>
? {
[key in keyof Shape]: InstanceOrPrimitiveOfSchema<Shape[key]>;
-readonly [key in keyof Shape]: InstanceOrPrimitiveOfSchema<
Shape[key]
>;
} & Account
: S extends CoreCoRecordSchema<infer K, infer V>
? {
[key in z.output<K> & string]: InstanceOrPrimitiveOfSchema<V>;
-readonly [key in z.output<K> &
string]: InstanceOrPrimitiveOfSchema<V>;
} & CoMap
: S extends CoreCoMapSchema<infer Shape, infer CatchAll>
? {
[key in keyof Shape]: InstanceOrPrimitiveOfSchema<Shape[key]>;
-readonly [key in keyof Shape]: InstanceOrPrimitiveOfSchema<
Shape[key]
>;
} & (CatchAll extends AnyZodOrCoValueSchema
? {
[key: string]: InstanceOrPrimitiveOfSchema<CatchAll>;
@@ -53,7 +58,7 @@ export type InstanceOfSchema<S extends CoValueClass | AnyZodOrCoValueSchema> =
: S extends CoreFileStreamSchema
? FileStream
: S extends CoreCoOptionalSchema<infer T>
? InstanceOrPrimitiveOfSchema<T>
? InstanceOrPrimitiveOfSchema<T> | undefined
: S extends CoDiscriminatedUnionSchema<infer Members>
? InstanceOrPrimitiveOfSchema<Members[number]>
: never

View File

@@ -29,7 +29,7 @@ export type InstanceOfSchemaCoValuesNullable<
? S extends CoreAccountSchema<infer Shape>
?
| ({
[key in keyof Shape]: InstanceOrPrimitiveOfSchemaCoValuesNullable<
-readonly [key in keyof Shape]: InstanceOrPrimitiveOfSchemaCoValuesNullable<
Shape[key]
>;
} & Account)
@@ -37,14 +37,14 @@ export type InstanceOfSchemaCoValuesNullable<
: S extends CoreCoRecordSchema<infer K, infer V>
?
| ({
[key in z.output<K> &
-readonly [key in z.output<K> &
string]: InstanceOrPrimitiveOfSchemaCoValuesNullable<V>;
} & CoMap)
| null
: S extends CoreCoMapSchema<infer Shape, infer CatchAll>
?
| ({
[key in keyof Shape]: InstanceOrPrimitiveOfSchemaCoValuesNullable<
-readonly [key in keyof Shape]: InstanceOrPrimitiveOfSchemaCoValuesNullable<
Shape[key]
>;
} & (CatchAll extends AnyZodOrCoValueSchema

View File

@@ -1,122 +1,11 @@
import { JsonValue } from "cojson";
import {
Account,
AnyZodOrCoValueSchema,
CoDiscriminatedUnionSchema,
CoFeed,
CoList,
CoMap,
CoPlainText,
CoRichText,
CoValueClass,
CoreAccountSchema,
CoreCoRecordSchema,
FileStream,
Profile,
InstanceOfSchema,
} from "../../../internal.js";
import { CoreCoFeedSchema } from "../schemaTypes/CoFeedSchema.js";
import { CoreCoListSchema } from "../schemaTypes/CoListSchema.js";
import { CoreCoMapSchema } from "../schemaTypes/CoMapSchema.js";
import { CoreCoOptionalSchema } from "../schemaTypes/CoOptionalSchema.js";
import { CoreCoValueSchema } from "../schemaTypes/CoValueSchema.js";
import { CoreFileStreamSchema } from "../schemaTypes/FileStreamSchema.js";
import { CorePlainTextSchema } from "../schemaTypes/PlainTextSchema.js";
import { CoreRichTextSchema } from "../schemaTypes/RichTextSchema.js";
import { z } from "../zodReExport.js";
import { TypeOfZodSchema } from "./TypeOfZodSchema.js";
export type InstanceOrPrimitiveOfSchema<
S extends CoValueClass | AnyZodOrCoValueSchema,
> = S extends CoreCoValueSchema
? S extends CoreAccountSchema<infer Shape>
? {
-readonly [key in keyof Shape]: InstanceOrPrimitiveOfSchema<Shape[key]>;
} & { profile: Profile } & Account
: S extends CoreCoRecordSchema<infer K, infer V>
? {
-readonly [key in z.output<K> &
string]: InstanceOrPrimitiveOfSchema<V>;
} & CoMap
: S extends CoreCoMapSchema<infer Shape, infer CatchAll>
? {
-readonly [key in keyof Shape]: InstanceOrPrimitiveOfSchema<
Shape[key]
>;
} & (CatchAll extends AnyZodOrCoValueSchema
? {
[key: string]: InstanceOrPrimitiveOfSchema<CatchAll>;
}
: {}) &
CoMap
: S extends CoreCoListSchema<infer T>
? CoList<InstanceOrPrimitiveOfSchema<T>>
: S extends CoreCoFeedSchema<infer T>
? CoFeed<InstanceOrPrimitiveOfSchema<T>>
: S extends CorePlainTextSchema
? CoPlainText
: S extends CoreRichTextSchema
? CoRichText
: S extends CoreFileStreamSchema
? FileStream
: S extends CoreCoOptionalSchema<infer T>
? InstanceOrPrimitiveOfSchema<T> | undefined
: S extends CoDiscriminatedUnionSchema<infer Members>
? InstanceOrPrimitiveOfSchema<Members[number]>
: never
: S extends z.core.$ZodType
? S extends z.core.$ZodOptional<infer Inner extends z.core.$ZodType>
? InstanceOrPrimitiveOfSchema<Inner> | undefined
: S extends z.core.$ZodNullable<infer Inner extends z.core.$ZodType>
? InstanceOrPrimitiveOfSchema<Inner> | null
: S extends z.ZodJSONSchema
? JsonValue
: S extends z.core.$ZodUnion<infer Members extends z.core.$ZodType[]>
? InstanceOrPrimitiveOfSchema<Members[number]>
: // primitives below here - we manually traverse to ensure we only allow what we can handle
S extends z.core.$ZodObject<infer Shape>
? {
-readonly [key in keyof Shape]: InstanceOrPrimitiveOfSchema<
Shape[key]
>;
}
: S extends z.core.$ZodArray<infer Item extends z.core.$ZodType>
? InstanceOrPrimitiveOfSchema<Item>[]
: S extends z.core.$ZodTuple<
infer Items extends readonly z.core.$ZodType[]
>
? {
[key in keyof Items]: InstanceOrPrimitiveOfSchema<
Items[key]
>;
}
: S extends z.core.$ZodString
? string
: S extends z.core.$ZodNumber
? number
: S extends z.core.$ZodBoolean
? boolean
: S extends z.core.$ZodLiteral<infer Literal>
? Literal
: S extends z.core.$ZodDate
? Date
: S extends z.core.$ZodEnum<infer Enum>
? Enum[keyof Enum]
: S extends z.core.$ZodTemplateLiteral<
infer pattern
>
? pattern
: S extends z.core.$ZodReadonly<
infer Inner extends z.core.$ZodType
>
? InstanceOrPrimitiveOfSchema<Inner>
: S extends z.core.$ZodDefault<
infer Default extends z.core.$ZodType
>
? InstanceOrPrimitiveOfSchema<Default>
: S extends z.core.$ZodCatch<
infer Catch extends z.core.$ZodType
>
? InstanceOrPrimitiveOfSchema<Catch>
: never
: S extends CoValueClass
? InstanceType<S>
: never;
> = S extends z.core.$ZodType ? TypeOfZodSchema<S> : InstanceOfSchema<S>;

View File

@@ -1,137 +1,13 @@
import { JsonValue } from "cojson";
import {
Account,
AnyZodOrCoValueSchema,
CoDiscriminatedUnionSchema,
CoFeed,
CoList,
CoMap,
CoPlainText,
CoRichText,
CoValueClass,
CoreAccountSchema,
CoreCoRecordSchema,
FileStream,
InstanceOrPrimitiveOfSchema,
Profile,
InstanceOfSchemaCoValuesNullable,
} from "../../../internal.js";
import { CoreCoFeedSchema } from "../schemaTypes/CoFeedSchema.js";
import { CoreCoListSchema } from "../schemaTypes/CoListSchema.js";
import { CoreCoMapSchema } from "../schemaTypes/CoMapSchema.js";
import { CoreCoOptionalSchema } from "../schemaTypes/CoOptionalSchema.js";
import { CoreCoValueSchema } from "../schemaTypes/CoValueSchema.js";
import { CoreFileStreamSchema } from "../schemaTypes/FileStreamSchema.js";
import { CorePlainTextSchema } from "../schemaTypes/PlainTextSchema.js";
import { CoreRichTextSchema } from "../schemaTypes/RichTextSchema.js";
import { z } from "../zodReExport.js";
import { TypeOfZodSchema } from "./TypeOfZodSchema.js";
export type InstanceOrPrimitiveOfSchemaCoValuesNullable<
S extends CoValueClass | AnyZodOrCoValueSchema,
> = S extends CoreCoValueSchema
? S extends CoreAccountSchema<infer Shape>
?
| ({
-readonly [key in keyof Shape]: InstanceOrPrimitiveOfSchemaCoValuesNullable<
Shape[key]
>;
} & { profile: Profile | null } & Account)
| null
: S extends CoreCoRecordSchema<infer K, infer V>
?
| ({
-readonly [key in z.output<K> &
string]: InstanceOrPrimitiveOfSchemaCoValuesNullable<V>;
} & CoMap)
| null
: S extends CoreCoMapSchema<infer Shape, infer CatchAll>
?
| ({
-readonly [key in keyof Shape]: InstanceOrPrimitiveOfSchemaCoValuesNullable<
Shape[key]
>;
} & (CatchAll extends AnyZodOrCoValueSchema
? {
[
key: string
]: InstanceOrPrimitiveOfSchemaCoValuesNullable<CatchAll>;
}
: {}) &
CoMap)
| null
: S extends CoreCoListSchema<infer T>
? CoList<InstanceOrPrimitiveOfSchemaCoValuesNullable<T>> | null
: S extends CoreCoFeedSchema<infer T>
? CoFeed<InstanceOrPrimitiveOfSchemaCoValuesNullable<T>> | null
: S extends CorePlainTextSchema
? CoPlainText | null
: S extends CoreRichTextSchema
? CoRichText | null
: S extends CoreFileStreamSchema
? FileStream | null
: S extends CoreCoOptionalSchema<infer T>
? InstanceOrPrimitiveOfSchemaCoValuesNullable<T> | undefined
: S extends CoDiscriminatedUnionSchema<infer Members>
? InstanceOrPrimitiveOfSchemaCoValuesNullable<
Members[number]
>
: never
: S extends z.core.$ZodType
? S extends z.core.$ZodOptional<infer Inner extends z.core.$ZodType>
? InstanceOrPrimitiveOfSchemaCoValuesNullable<Inner> | undefined
: S extends z.core.$ZodNullable<infer Inner extends z.core.$ZodType>
? InstanceOrPrimitiveOfSchemaCoValuesNullable<Inner> | null
: S extends z.ZodJSONSchema
? JsonValue
: S extends z.core.$ZodUnion<
infer Members extends readonly z.core.$ZodType[]
>
? InstanceOrPrimitiveOfSchemaCoValuesNullable<Members[number]>
: // primitives below here - we manually traverse to ensure we only allow what we can handle
S extends z.core.$ZodObject<infer Shape>
? {
-readonly [key in keyof Shape]: InstanceOrPrimitiveOfSchema<
Shape[key]
>;
}
: S extends z.core.$ZodArray<infer Item extends z.core.$ZodType>
? InstanceOrPrimitiveOfSchema<Item>[]
: S extends z.core.$ZodTuple<
infer Items extends z.core.$ZodType[]
>
? {
[key in keyof Items]: InstanceOrPrimitiveOfSchema<
Items[key]
>;
}
: S extends z.core.$ZodString
? string
: S extends z.core.$ZodNumber
? number
: S extends z.core.$ZodBoolean
? boolean
: S extends z.core.$ZodLiteral<infer Literal>
? Literal
: S extends z.core.$ZodDate
? Date
: S extends z.core.$ZodEnum<infer Enum>
? Enum[keyof Enum]
: S extends z.core.$ZodTemplateLiteral<
infer pattern
>
? pattern
: S extends z.core.$ZodReadonly<
infer Inner extends z.core.$ZodType
>
? InstanceOrPrimitiveOfSchema<Inner>
: S extends z.core.$ZodDefault<
infer Default extends z.core.$ZodType
>
? InstanceOrPrimitiveOfSchema<Default>
: S extends z.core.$ZodCatch<
infer Catch extends z.core.$ZodType
>
? InstanceOrPrimitiveOfSchema<Catch>
: never
: S extends CoValueClass
? InstanceType<S> | null
: never;
> = S extends z.core.$ZodType
? TypeOfZodSchema<S>
: InstanceOfSchemaCoValuesNullable<S>;

View File

@@ -0,0 +1,79 @@
import { JsonValue } from "cojson";
import { PartialOnUndefined } from "../../../internal.js";
import { z } from "../zodReExport.js";
// Copied from https://github.com/colinhacks/zod/blob/7e7e3461aceecf3633e158df50d6bc852e7cdf45/packages/zod/src/v4/core/schemas.ts#L1591,
// since this type is not exported by Zod
type OptionalInSchema = {
_zod: {
optin: "optional";
};
};
/**
* Get type from Zod schema definition.
*
* Similar to `z.infer`, but we manually traverse Zod types to ensure we only allow what we can handle
*/
export type TypeOfZodSchema<S extends z.core.$ZodType> =
S extends z.core.$ZodOptional<infer Inner extends z.core.$ZodType>
? TypeOfZodSchema<Inner> | undefined
: S extends z.core.$ZodNullable<infer Inner extends z.core.$ZodType>
? TypeOfZodSchema<Inner> | null
: S extends z.ZodJSONSchema
? JsonValue
: S extends z.core.$ZodUnion<
infer Members extends readonly z.core.$ZodType[]
>
? TypeOfZodSchema<Members[number]>
: S extends z.core.$ZodObject<infer Shape>
? /**
* Cannot use {@link PartialOnUndefined} because evaluating TypeOfZodSchema<Shape[key]>
* to know if the value can be undefined does not work with recursive types.
*/
{
-readonly [key in keyof Shape as Shape[key] extends OptionalInSchema
? never
: key]: TypeOfZodSchema<Shape[key]>;
} & {
-readonly [key in keyof Shape as Shape[key] extends OptionalInSchema
? key
: never]?: TypeOfZodSchema<Shape[key]>;
}
: S extends z.core.$ZodArray<infer Item extends z.core.$ZodType>
? TypeOfZodSchema<Item>[]
: S extends z.core.$ZodTuple<
infer Items extends readonly z.core.$ZodType[]
>
? {
[key in keyof Items]: TypeOfZodSchema<Items[key]>;
}
: S extends z.core.$ZodString
? string
: S extends z.core.$ZodNumber
? number
: S extends z.core.$ZodBoolean
? boolean
: S extends z.core.$ZodLiteral<infer Literal>
? Literal
: S extends z.core.$ZodDate
? Date
: S extends z.core.$ZodEnum<infer Enum>
? Enum[keyof Enum]
: S extends z.core.$ZodTemplateLiteral<
infer pattern
>
? pattern
: S extends z.core.$ZodReadonly<
infer Inner extends z.core.$ZodType
>
? TypeOfZodSchema<Inner>
: S extends z.core.$ZodDefault<
infer Default extends z.core.$ZodType
>
? TypeOfZodSchema<Default>
: S extends z.core.$ZodCatch<
infer Catch extends z.core.$ZodType
>
? TypeOfZodSchema<Catch>
: never;

View File

@@ -7,6 +7,7 @@ import {
CoreCoMapSchema,
DiscriminableCoValueSchemas,
DiscriminableCoreCoValueSchema,
SchemaUnionDiscriminator,
} from "../../internal.js";
import {
hydrateCoreCoValueSchema,
@@ -56,21 +57,17 @@ export function schemaUnionDiscriminatorFor(
}
}
const determineSchema = (_raw: RawCoMap | RawAccount | RawCoList) => {
if (_raw instanceof RawCoList) {
throw new Error(
"co.discriminatedUnion() of collaborative types is not supported for CoLists",
);
}
const determineSchema: SchemaUnionDiscriminator<CoMap> = (
discriminable,
) => {
for (const option of availableOptions) {
let match = true;
for (const key of Object.keys(discriminatorMap)) {
const discriminatorDef = (option as CoreCoMapSchema).getDefinition()
.shape[key as string];
.shape[key];
const discriminatorValue = (_raw as RawCoMap).get(key as string);
const discriminatorValue = discriminable.get(key);
if (discriminatorValue && typeof discriminatorValue === "object") {
throw new Error("Discriminator must be a primitive value");

View File

@@ -23,11 +23,7 @@ import {
} from "./schemaTypes/CoDiscriminatedUnionSchema.js";
import { CoFeedSchema, CoreCoFeedSchema } from "./schemaTypes/CoFeedSchema.js";
import { CoListSchema, CoreCoListSchema } from "./schemaTypes/CoListSchema.js";
import {
CoMapSchema,
CoMapSchemaInit,
CoreCoMapSchema,
} from "./schemaTypes/CoMapSchema.js";
import { CoMapSchema, CoreCoMapSchema } from "./schemaTypes/CoMapSchema.js";
import {
CoOptionalSchema,
CoreCoOptionalSchema,
@@ -84,8 +80,8 @@ export type CoValueSchemaFromCoreSchema<S extends CoreCoValueSchema> =
export type CoValueClassFromAnySchema<S extends CoValueClassOrSchema> =
S extends CoValueClass<any>
? S
: CoValueClass<InstanceOfSchema<S>> &
CoValueFromRaw<InstanceOfSchema<S>> &
: CoValueClass<NonNullable<InstanceOfSchema<S>>> &
CoValueFromRaw<NonNullable<InstanceOfSchema<S>>> &
(S extends CoreAccountSchema ? AccountClassEssentials : {});
type AccountClassEssentials = {
@@ -122,9 +118,3 @@ export type ResolveQueryStrict<
T extends CoValueClassOrSchema,
R extends ResolveQuery<T>,
> = RefsToResolveStrict<NonNullable<InstanceOfSchemaCoValuesNullable<T>>, R>;
export type InitFor<T extends CoValueClassOrSchema> = T extends CoreCoMapSchema<
infer Shape
>
? Simplify<CoMapSchemaInit<Shape>>
: never;

View File

@@ -45,6 +45,7 @@ export * from "./implementation/zodSchema/typeConverters/InstanceOrPrimitiveOfSc
export * from "./implementation/zodSchema/typeConverters/InstanceOrPrimitiveOfSchemaCoValuesNullable.js";
export * from "./implementation/zodSchema/typeConverters/InstanceOfSchema.js";
export * from "./implementation/zodSchema/typeConverters/InstanceOfSchemaCoValuesNullable.js";
export * from "./implementation/zodSchema/typeConverters/CoFieldInit.js";
export * from "./implementation/zodSchema/runtimeConverters/coValueSchemaTransformation.js";
export * from "./implementation/zodSchema/runtimeConverters/schemaFieldToCoFieldDef.js";
export * from "./coValues/extensions/imageDef.js";

View File

@@ -7,7 +7,7 @@ import {
type ID,
type RefEncoded,
type RefsToResolve,
instantiateRefEncoded,
instantiateRefEncodedFromRaw,
isRefEncoded,
} from "../internal.js";
import { applyCoValueMigrations } from "../lib/migration.js";
@@ -75,7 +75,9 @@ export class SubscriptionScope<D extends CoValue> {
}
this.migrating = true;
applyCoValueMigrations(instantiateRefEncoded(this.schema, value));
applyCoValueMigrations(
instantiateRefEncodedFromRaw(this.schema, value),
);
this.migrated = true;
this.handleUpdate(lastUpdate);
return;

View File

@@ -4,7 +4,7 @@ import {
CoValue,
RefEncoded,
coValueClassFromCoValueClassOrSchema,
instantiateRefEncoded,
instantiateRefEncodedFromRaw,
} from "../internal.js";
import { coValuesCache } from "../lib/cache.js";
import { SubscriptionScope } from "./SubscriptionScope.js";
@@ -26,7 +26,7 @@ export function createCoValue<D extends CoValue>(
raw: RawCoValue,
subscriptionScope: SubscriptionScope<D>,
) {
const freshValueInstance = instantiateRefEncoded(ref, raw);
const freshValueInstance = instantiateRefEncodedFromRaw(ref, raw);
Object.defineProperty(freshValueInstance, "_subscriptionScope", {
value: subscriptionScope,

View File

@@ -52,6 +52,46 @@ describe("Simple CoFeed operations", async () => {
expect(stream.perSession[me.sessionID]?.value).toEqual("milk");
});
describe("Create CoFeed with a reference", () => {
let me: Account;
beforeEach(async () => {
await setupJazzTestSync();
me = await createJazzTestAccount({
isCurrentActiveAccount: true,
creationProps: { name: "Hermes Puggington" },
});
});
test("using a CoValue", () => {
const Text = co.plainText();
const TextStream = co.feed(Text);
const stream = TextStream.create([Text.create("milk")], { owner: me });
const coValue = stream.perAccount[me.id]?.value;
expect(coValue?.toString()).toEqual("milk");
});
describe("using JSON", () => {
test("automatically creates CoValues for nested objects", () => {
const Text = co.plainText();
const TextStream = co.feed(Text);
const stream = TextStream.create(["milk"], { owner: me });
const coValue = stream.perAccount[me.id]?.value;
expect(coValue?.toString()).toEqual("milk");
});
test("can create a coPlainText from an empty string", () => {
const Schema = co.feed(co.plainText());
const feed = Schema.create([""]);
expect(feed.perAccount[me.id]?.value?.toString()).toBe("");
});
});
});
test("Construction with nullable values", () => {
const NullableTestStream = co.feed(z.string().nullable());
const stream = NullableTestStream.create(["milk", null], { owner: me });

View File

@@ -52,6 +52,48 @@ describe("Simple CoList operations", async () => {
expect(list[2]).toBe("c");
});
test("create CoList with reference using CoValue", () => {
const Dog = co.map({
name: z.string(),
});
const Person = co.map({
pets: co.list(Dog),
});
const person = Person.create({
pets: [Dog.create({ name: "Rex" }), Dog.create({ name: "Fido" })],
});
expect(person.pets.length).toEqual(2);
expect(person.pets[0]?.name).toEqual("Rex");
expect(person.pets[1]?.name).toEqual("Fido");
});
describe("create CoList with reference using JSON", () => {
test("automatically creates CoValues for nested objects", () => {
const Dog = co.map({
name: z.string(),
});
const Person = co.map({
pets: co.list(Dog),
});
const person = Person.create({
pets: [{ name: "Rex" }, { name: "Fido" }],
});
expect(person.pets.length).toEqual(2);
expect(person.pets[0]?.name).toEqual("Rex");
expect(person.pets[1]?.name).toEqual("Fido");
});
test("can create a coPlainText from an empty string", () => {
const Schema = co.list(co.plainText());
const list = Schema.create([""]);
expect(list[0]?.toString()).toBe("");
});
});
test("list with nullable content", () => {
const List = co.list(z.string().nullable());
const list = List.create(["a", "b", "c", null]);

View File

@@ -54,7 +54,7 @@ describe("CoMap", async () => {
expectTypeOf(john._owner).toEqualTypeOf<Account | Group>();
});
test("CoMap with reference", () => {
test("create CoMap with reference using CoValue", () => {
const Dog = co.map({
name: z.string(),
breed: z.string(),
@@ -85,6 +85,46 @@ describe("CoMap", async () => {
matches(person);
});
test("create CoMap with reference using JSON", () => {
const Dog = co.map({
name: z.string(),
breed: z.string(),
});
const Person = co.map({
dog1: Dog,
dog2: Dog,
get friend() {
return Person.optional();
},
});
const person = Person.create({
// @ts-expect-error - breed is missing
dog1: { name: "Rex", items },
// @ts-expect-error - Object literal may only specify known properties
dog2: { name: "Fido", breed: "Labrador", extra: "extra" },
friend: {
dog1: {
name: "Rex",
breed: "Labrador",
},
dog2: { name: "Fido", breed: "Labrador" },
},
});
type ExpectedType = {
dog1: Loaded<typeof Dog>;
dog2: Loaded<typeof Dog>;
friend: Loaded<typeof Person> | undefined;
};
function matches(value: ExpectedType) {
return value;
}
matches(person);
});
test("CoMap with optional reference", () => {
const Dog = co.map({
name: z.string(),

View File

@@ -117,10 +117,9 @@ describe("CoMap", async () => {
expect(emptyMap.color).toEqual(undefined);
});
test("CoMap with reference", () => {
test("create CoMap with reference using CoValue", () => {
const Dog = co.map({
name: z.string(),
breed: z.string(),
});
const Person = co.map({
@@ -132,11 +131,89 @@ describe("CoMap", async () => {
const person = Person.create({
name: "John",
age: 20,
dog: Dog.create({ name: "Rex", breed: "Labrador" }),
dog: Dog.create({ name: "Rex" }),
});
expect(person.dog?.name).toEqual("Rex");
expect(person.dog?.breed).toEqual("Labrador");
});
describe("create CoMap with references using JSON", () => {
const Dog = co.map({
type: z.literal("dog"),
name: z.string(),
});
const Cat = co.map({
type: z.literal("cat"),
name: z.string(),
});
const Person = co.map({
name: co.plainText(),
bio: co.richText(),
dog: Dog,
get friends() {
return co.list(Person);
},
reactions: co.feed(co.plainText()),
pet: co.discriminatedUnion("type", [Dog, Cat]),
});
let person: ReturnType<typeof Person.create>;
beforeEach(() => {
person = Person.create({
name: "John",
bio: "I am a software engineer",
dog: { type: "dog", name: "Rex" },
friends: [
{
name: "Jane",
bio: "I am a mechanical engineer",
dog: { type: "dog", name: "Fido" },
friends: [],
reactions: [],
pet: { type: "dog", name: "Fido" },
},
],
reactions: ["👎", "👍"],
pet: { type: "cat", name: "Whiskers" },
});
});
it("automatically creates CoValues for each CoValue reference", () => {
expect(person.name.toString()).toEqual("John");
expect(person.bio.toString()).toEqual("I am a software engineer");
expect(person.dog?.name).toEqual("Rex");
expect(person.friends.length).toEqual(1);
expect(person.friends[0]?.name.toString()).toEqual("Jane");
expect(person.friends[0]?.bio.toString()).toEqual(
"I am a mechanical engineer",
);
expect(person.friends[0]?.dog.name).toEqual("Fido");
expect(person.friends[0]?.friends.length).toEqual(0);
expect(person.reactions.byMe?.value?.toString()).toEqual("👍");
expect(person.pet.name).toEqual("Whiskers");
});
it("creates a group for each new CoValue that is a child of the referencing CoValue's owner", () => {
for (const value of Object.values(person)) {
expect(
value._owner.getParentGroups().map((group: Group) => group.id),
).toContain(person._owner.id);
}
const friend = person.friends[0]!;
for (const value of Object.values(friend)) {
expect(
value._owner.getParentGroups().map((group: Group) => group.id),
).toContain(friend._owner.id);
}
});
it("can create a coPlainText from an empty string", () => {
const Schema = co.map({ text: co.plainText() });
const map = Schema.create({ text: "" });
expect(map.text.toString()).toBe("");
});
});
test("CoMap with self reference", () => {
@@ -209,7 +286,7 @@ describe("CoMap", async () => {
const Person = co.map({
name: z.string(),
age: z.number(),
get friend(): co.Optional<typeof Person> {
get friend() {
return co.optional(Person);
},
});

View File

@@ -316,6 +316,60 @@ describe("Group inheritance", () => {
expect(group.getRoleOf(bob.id)).toBe("reader");
expect(group.getRoleOf(alice.id)).toBe(undefined);
});
describe("when creating nested CoValues from a JSON object", () => {
const Task = co.plainText();
const Column = co.list(Task);
const Board = co.map({
title: z.string(),
columns: co.list(Column),
});
let board: ReturnType<typeof Board.create>;
beforeEach(async () => {
const me = co.account().getMe();
const writeAccess = Group.create();
writeAccess.addMember(me, "writer");
board = Board.create(
{
title: "My board",
columns: [
["Task 1.1", "Task 1.2"],
["Task 2.1", "Task 2.2"],
],
},
writeAccess,
);
});
test("nested CoValues inherit permissions from the referencing CoValue", async () => {
const me = co.account().getMe();
const task = board.columns[0]![0]!;
const boardAsWriter = await Board.load(board.id, { loadAs: me });
expect(boardAsWriter?.title).toEqual("My board");
const taskAsWriter = await Task.load(task.id, { loadAs: me });
expect(taskAsWriter?.toString()).toEqual("Task 1.1");
});
test("nested CoValues inherit permissions from the referencing CoValue", async () => {
const me = co.account().getMe();
const reader = await co.account().createAs(me, {
creationProps: { name: "Reader" },
});
const task = board.columns[0]![0]!;
const taskGroup = task._owner.castAs(Group);
taskGroup.addMember(reader, "reader");
const taskAsReader = await Task.load(task.id, { loadAs: reader });
expect(taskAsReader?.toString()).toEqual("Task 1.1");
const boardAsReader = await Board.load(board.id, { loadAs: reader });
expect(boardAsReader).toBeNull();
});
});
});
describe("Group.getRoleOf", () => {

View File

@@ -98,6 +98,22 @@ describe("co.map and Zod schema compatibility", () => {
expect(map.updatedAt).toEqual("Test");
});
it("should handle nested optional fields", async () => {
const RecursiveZodSchema = z.object({
get optionalField() {
return RecursiveZodSchema.optional();
},
});
const CoMapSchema = co.map({ field: RecursiveZodSchema });
const map = CoMapSchema.create({
field: { optionalField: { optionalField: {} } },
});
expect(
map.field.optionalField!.optionalField!.optionalField,
).toBeUndefined();
});
it("should handle literal fields", async () => {
const schema = co.map({
status: z.literal("active"),

24
pnpm-lock.yaml generated
View File

@@ -1842,19 +1842,19 @@ importers:
specifier: ^0.25.5
version: 0.25.8(effect@3.11.9)
cojson:
specifier: workspace:0.16.1
specifier: workspace:0.16.3
version: link:../cojson
cojson-storage-sqlite:
specifier: workspace:0.16.1
specifier: workspace:0.16.3
version: link:../cojson-storage-sqlite
cojson-transport-ws:
specifier: workspace:0.16.1
specifier: workspace:0.16.3
version: link:../cojson-transport-ws
effect:
specifier: ^3.6.5
version: 3.11.9
jazz-tools:
specifier: workspace:0.16.1
specifier: workspace:0.16.3
version: link:../jazz-tools
ws:
specifier: ^8.14.2
@@ -2182,6 +2182,15 @@ importers:
tests/browser-integration:
dependencies:
'@testing-library/jest-dom':
specifier: 6.6.3
version: 6.6.3
'@testing-library/react':
specifier: 16.2.0
version: 16.2.0(@testing-library/dom@10.4.0)(@types/react-dom@19.1.0(@types/react@19.1.0))(@types/react@19.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@vitejs/plugin-react-swc':
specifier: ^3.10.1
version: 3.10.1(@swc/helpers@0.5.17)(vite@6.3.5(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.20.3)(yaml@2.6.1))
cojson:
specifier: workspace:*
version: link:../../packages/cojson
@@ -2197,6 +2206,12 @@ importers:
jazz-tools:
specifier: workspace:*
version: link:../../packages/jazz-tools
react:
specifier: 19.1.0
version: 19.1.0
react-dom:
specifier: 19.1.0
version: 19.1.0(react@19.1.0)
devDependencies:
typescript:
specifier: 'catalog:'
@@ -12406,6 +12421,7 @@ packages:
source-map@0.8.0-beta.0:
resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==}
engines: {node: '>= 8'}
deprecated: The work that was done in this beta branch won't be included in future versions
sourcemap-codec@1.4.8:
resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}

View File

@@ -1,5 +1,18 @@
# jazz-react-tailwind-starter
## 0.0.140
### Patch Changes
- Updated dependencies [43d3511]
- jazz-tools@0.16.3
## 0.0.139
### Patch Changes
- jazz-tools@0.16.2
## 0.0.138
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-react-passkey-auth-starter",
"private": true,
"version": "0.0.138",
"version": "0.0.140",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,18 @@
# svelte-passkey-auth
## 0.0.114
### Patch Changes
- Updated dependencies [43d3511]
- jazz-tools@0.16.3
## 0.0.113
### Patch Changes
- jazz-tools@0.16.2
## 0.0.112
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "svelte-passkey-auth",
"version": "0.0.112",
"version": "0.0.114",
"type": "module",
"private": true,
"scripts": {

View File

@@ -15,6 +15,11 @@
"cojson-storage-indexeddb": "workspace:*",
"cojson-storage-sqlite": "workspace:*",
"cojson-transport-ws": "workspace:*",
"jazz-tools": "workspace:*"
"jazz-tools": "workspace:*",
"react": "19.1.0",
"react-dom": "19.1.0",
"@vitejs/plugin-react-swc": "^3.10.1",
"@testing-library/react": "16.2.0",
"@testing-library/jest-dom": "6.6.3"
}
}

View File

@@ -0,0 +1,179 @@
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { commands } from "@vitest/browser/context";
import { AuthSecretStorage, co, z } from "jazz-tools";
import { JazzReactProvider, useAccount, useCoState } from "jazz-tools/react";
import { afterAll, afterEach, describe, expect, test } from "vitest";
import { createAccountContext, startSyncServer } from "./testUtils";
// Define a simple account schema for testing
const TestMap = co.map({ count: co.map({ value: z.number() }) });
const TestAccount = co
.account({
profile: co.map({ name: z.string() }),
root: TestMap,
})
.withMigration((account) => {
if (!account.root) {
account.root = TestMap.create(
{ count: TestMap.shape.count.create({ value: 0 }) },
{ owner: account },
);
}
});
// React component that uses Jazz hooks for testing logout behavior
function TestLogoutComponent({
onLogout,
}: {
onLogout?: () => void;
}) {
const { logOut, me } = useAccount(TestAccount, {
resolve: {
profile: true,
},
});
const root = useCoState(TestAccount.shape.root, me?.root?.id, {
resolve: {
count: true,
},
});
const handleLogout = () => {
logOut();
onLogout?.();
};
if (me && root) {
return (
<div>
<p data-testid="user-name">Welcome, {me.profile.name}</p>
<p data-testid="root-value">{root.count.value}</p>
<button
data-testid="increment-button"
onClick={() => {
root.count.value++;
}}
>
Increment
</button>
<button data-testid="logout-button" onClick={handleLogout}>
Logout
</button>
</div>
);
}
return (
<div>
<p data-testid="not-authenticated">Not authenticated</p>
</div>
);
}
afterAll(async () => {
await commands.cleanup();
});
describe("Jazz logout behavior in React apps", () => {
afterEach(async () => {
await new AuthSecretStorage().clear();
});
test("should update the profile state on logout", async () => {
const syncServer = await startSyncServer();
let logoutCallbackCalled = false;
// Create an authenticated account context
await createAccountContext({
defaultProfileName: "John Doe",
sync: {
peer: syncServer.url,
},
storage: "indexedDB",
AccountSchema: TestAccount,
});
// Render the React component with Jazz provider
const { unmount } = render(
<JazzReactProvider
sync={{ peer: syncServer.url }}
storage="indexedDB"
AccountSchema={TestAccount}
defaultProfileName="Anonymous user"
>
<TestLogoutComponent
onLogout={() => {
logoutCallbackCalled = true;
}}
/>
</JazzReactProvider>,
);
await waitFor(() => {
expect(screen.getByTestId("user-name")).toBeInTheDocument();
});
expect(screen.getByTestId("user-name")).toHaveTextContent(
"Welcome, John Doe",
);
const logoutButton = screen.getByTestId("logout-button");
fireEvent.click(logoutButton);
expect(logoutCallbackCalled).toBe(true);
await waitFor(() => {
expect(screen.getByTestId("user-name")).toHaveTextContent(
"Welcome, Anonymous user",
);
});
unmount();
});
test("should reset nested co-state on logout", async () => {
const syncServer = await startSyncServer();
let logoutCallbackCalled = false;
render(
<JazzReactProvider
sync={{ peer: syncServer.url }}
storage="indexedDB"
AccountSchema={TestAccount}
defaultProfileName="Anonymous user"
>
<TestLogoutComponent
onLogout={() => {
logoutCallbackCalled = true;
}}
/>
</JazzReactProvider>,
);
await waitFor(() => {
expect(screen.getByTestId("root-value")).toBeInTheDocument();
});
expect(screen.getByTestId("root-value")).toHaveTextContent("0");
const incrementButton = screen.getByTestId("increment-button");
fireEvent.click(incrementButton);
expect(screen.getByTestId("root-value")).toHaveTextContent("1");
fireEvent.click(incrementButton);
expect(screen.getByTestId("root-value")).toHaveTextContent("2");
const logoutButton = screen.getByTestId("logout-button");
fireEvent.click(logoutButton);
expect(logoutCallbackCalled).toBe(true);
await waitFor(() => {
expect(screen.getByTestId("root-value")).toHaveTextContent("0");
});
});
});

View File

@@ -9,7 +9,8 @@
"sourceMap": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"outDir": "dist"
"outDir": "dist",
"jsx": "react-jsx"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]

View File

@@ -1,7 +1,9 @@
import react from "@vitejs/plugin-react-swc";
import { defineProject } from "vitest/config";
import { customCommands } from "./src/commands";
export default defineProject({
plugins: [react()],
test: {
name: "browser-integration-tests",
browser: {