Compare commits

..

130 Commits

Author SHA1 Message Date
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
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
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
Guido D'Orsi
12481e14c2 test: logout integration tests on browser 2025-07-31 18:56:37 +02:00
Anselm
0ab4d7a20d Update meta description 2025-07-30 11:34:54 -07: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
Guido D'Orsi
d1609cdd55 Merge pull request #2679 from garden-co/changeset-release/main
Version Packages
2025-07-28 18:12:46 +02:00
github-actions[bot]
b71ab3168a Version Packages 2025-07-28 15:15:40 +00:00
Nico Rainhart
0c8f6e5039 Merge pull request #2677 from garden-co/feat/add-nullable-support
feat: Add support for nullable non-collaborative fields
2025-07-28 12:12:12 -03:00
NicoR
6a93a1b8a3 chore: add comment on why nullable date cofields are not supported 2025-07-28 11:52:44 -03:00
NicoR
9f654a2603 test: loading a map with a nullable field 2025-07-28 11:46:50 -03:00
NicoR
dbf735d9e1 Fix rebase error 2025-07-28 10:27:44 -03:00
NicoR
c62abefb66 Add changeset 2025-07-28 10:19:55 -03:00
NicoR
1453869a46 Add support for nullable non-collaborative fields 2025-07-28 10:18:39 -03: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
NicoR
0c0178764e chore: fix rebase errors 2025-07-28 09:34:24 -03:00
NicoR
928350b821 refactor: rename OptionalizeUndefinedKeys to PartialOnUndefined 2025-07-28 09:34:23 -03:00
NicoR
be3fd9c696 test: create CoMap with shallowly resolved CoValue 2025-07-28 09:34:23 -03:00
NicoR
269c028df0 test: add test for CoMapSchema + catchall 2025-07-28 09:34:23 -03:00
NicoR
e4df837138 refactor: rename CoMapInitZod to CoMapSchemaInit 2025-07-28 09:34:23 -03:00
NicoR
54fe6d93ba refactor: extract CoMapSchema.create's return type 2025-07-28 09:34:23 -03:00
NicoR
979689c6d8 refactor: improve CatchAll type handling in CoMapSchemas 2025-07-28 09:34:23 -03:00
NicoR
859a37868f refactor: simplify CoMapSchema.create's return type 2025-07-28 09:34:23 -03:00
NicoR
57bd32d77e refactor: simplify the type of CoMapSchema.create's init parameter 2025-07-28 09:34:23 -03:00
Nico Rainhart
e21cbccd4b Merge pull request #2674 from garden-co/changeset-release/main
Version Packages
2025-07-25 11:07:46 -03:00
github-actions[bot]
a66ab7d174 Version Packages 2025-07-25 13:34:52 +00:00
Nico Rainhart
78e91f4030 Merge pull request #2667 from garden-co/0-16
Jazz 0.16 upgrade
2025-07-25 10:32:40 -03:00
Guido D'Orsi
7a915c198e Merge pull request #2657 from garden-co/fix/root-trusting
feat: store the root id unencrypted in account
2025-07-25 14:39:59 +02:00
Guido D'Orsi
c9b0420746 Merge pull request #2672 from garden-co/chore/fix-conflicts-between-http-api-and-schema-refactoring
chore: Fix conflicts between HTTP requests and CoValue schema refactor
2025-07-25 14:39:16 +02:00
NicoR
2303f3e70a fix: schema definition error in server-http-worker example 2025-07-25 09:31:47 -03:00
NicoR
a7bc9569a3 chore: Fix conflicts between HTTP requests and CoValue schema refactor 2025-07-25 09:19:15 -03:00
NicoR
f351ba0fcd Merge remote-tracking branch 'origin/main' into chore/fix-conflicts-between-http-api-and-schema-refactoring 2025-07-25 09:12:05 -03:00
Guido D'Orsi
d3e554f491 Merge pull request #2671 from garden-co/chore/queues
chore: move cojson queues in a dedicated directory
2025-07-25 13:03:33 +02:00
Guido D'Orsi
b5e31456ad chore: trigger deploy 2025-07-25 12:25:05 +02:00
Guido D'Orsi
42d07ba7b4 docs: upgrade docs for account root id 2025-07-25 11:13:40 +02:00
Guido D'Orsi
b81b6ba69b Merge remote-tracking branch 'origin/0-16' into fix/root-trusting 2025-07-25 10:54:21 +02:00
Guido D'Orsi
1bc1759bb4 Merge remote-tracking branch 'origin/main' into chore/queues 2025-07-25 10:51:43 +02:00
Nico Rainhart
512aacdbc2 Merge pull request #2669 from garden-co/fix/simplify-circular-constraint
fix: circular constraint type check error with `Simplify`
2025-07-24 17:07:53 -03:00
NicoR
7ad843aa3e fix: circular constraint type check error with Simplify 2025-07-24 16:18:21 -03:00
Guido D'Orsi
fc027a56db Merge pull request #2650 from garden-co/refactor/covalue-zod-schema-boundary
refactor: CoValue schemas are no longer Zod schemas
2025-07-24 17:41:09 +02:00
Guido D'Orsi
959a7a3927 Merge branch '0-16' into refactor/covalue-zod-schema-boundary 2025-07-24 17:40:51 +02:00
NicoR
2548085b59 Update upgrade guide and docs with improved support for recursive refs 2025-07-24 12:24:33 -03:00
Guido D'Orsi
b27bb3e65b test: remove .only 2025-07-24 17:21:04 +02:00
Guido D'Orsi
d1efde468f Merge remote-tracking branch 'origin/main' into fix/root-trusting 2025-07-24 16:55:31 +02:00
Guido D'Orsi
2b61e853a7 test: cover recursive references without explicit return type 2025-07-24 16:43:35 +02:00
Guido D'Orsi
6f79b45544 chore: move cojson queues in a dedicated directory 2025-07-24 16:23:58 +02:00
Guido D'Orsi
2e1ff99579 chore: use import content instead of copy on acceptInvite 2025-07-24 16:04:43 +02:00
NicoR
ac782674de One more Upgrade guide tweak 2025-07-23 11:24:14 -03:00
NicoR
5eb406d54d Upgrade guide adjustments 2025-07-23 11:03:47 -03:00
NicoR
a3be832414 Remove WithHelpers export 2025-07-23 10:55:35 -03:00
NicoR
7ca8dd960e Merge branch 'main' into refactor/covalue-zod-schema-boundary 2025-07-23 10:50:08 -03:00
NicoR
62c8aff73f Fix Upgrade guide navigation 2025-07-23 10:24:53 -03:00
NicoR
7731109a28 Address Upgrade guide comments 2025-07-23 10:08:14 -03:00
NicoR
dfc4286694 Return resolved resized images on createImage 2025-07-22 15:15:02 -03:00
NicoR
970ff0d813 Fix docs 2025-07-22 14:57:57 -03:00
NicoR
eee221f563 Merge branch 'main' into refactor/covalue-zod-schema-boundary 2025-07-22 13:38:47 -03:00
NicoR
2283d375ef Update docs 2025-07-22 12:17:49 -03:00
NicoR
202e763380 Merge branch 'main' into refactor/covalue-zod-schema-boundary 2025-07-22 12:07:57 -03:00
NicoR
52bbdb37a9 Add .optional() to all CoValue schemas 2025-07-22 12:06:21 -03:00
NicoR
f5c47feeb6 Upgrade Zod to 3.25.76 instead 2025-07-22 11:33:01 -03:00
NicoR
b8b0851433 Add upgrade guide 2025-07-22 10:42:46 -03:00
NicoR
2bbb07b0bf Add changeset 2025-07-22 10:01:00 -03:00
NicoR
d3053955d8 Update docs 2025-07-22 09:34:40 -03:00
NicoR
f40484eca9 Migrate plain text, rich text, file stream and optional schemas to classes 2025-07-21 17:00:57 -03:00
NicoR
d581a59aa1 Convert CoDiscriminatedUnionSchema into a class 2025-07-21 16:39:43 -03:00
NicoR
0ca09f75c1 Convert CoFeedSchema into a class 2025-07-21 16:30:04 -03:00
NicoR
e8fcd101f2 Convert CoListSchema into a class 2025-07-21 16:24:35 -03:00
NicoR
cf43fa7529 Remove usage of withHelpers 2025-07-21 15:23:36 -03:00
NicoR
df1cdda4e8 Update zod version in all examples 2025-07-21 15:15:51 -03:00
NicoR
7a60d7bb76 Avoid NotNull duplication 2025-07-21 14:33:22 -03:00
NicoR
f8263a8358 Stop extending Zod schemas when creating core CoValue schemas 2025-07-21 14:25:23 -03:00
NicoR
f6da966922 Explain "core" CoValue schema / actual CoValue schema distinction 2025-07-21 14:21:44 -03:00
NicoR
8a2ab51543 Expose internal schemas for co.map, co.list, co.optional and co.account 2025-07-21 14:09:48 -03:00
NicoR
466e6c44ee Remove unused imports 2025-07-21 11:43:11 -03:00
NicoR
5bd8277161 Remove withHelpers schema method 2025-07-21 11:41:28 -03:00
NicoR
0ec917e453 Remove non-namespaced CoValue schema exports 2025-07-21 10:42:23 -03:00
NicoR
6326d0fc45 Export co.Image type 2025-07-21 10:30:55 -03:00
NicoR
d746b1279a Remove deprecated createCoValueObservable function 2025-07-21 10:18:54 -03:00
Guido D'Orsi
c09dcdfc76 feat: make the root trusting 2025-07-21 12:06:28 +02:00
NicoR
4402c553b6 Fix z.object bug with cyclic references 2025-07-18 15:44:54 -03:00
NicoR
e76fe343da Fix co.discriminatedUnion with cyclic references 2025-07-18 15:10:53 -03:00
NicoR
a2626a0f38 Avoid rehydrating CoValue schemas 2025-07-18 14:21:27 -03:00
NicoR
ec579bcaf7 Go back to using tuple for discriminatedUnion's options type 2025-07-18 14:19:53 -03:00
Joe Innes
6b662b0efe Type fixes for twoslash 2025-07-18 15:06:03 +02:00
NicoR
e9af90c841 Fix Zod type messing up Zod's type inference 2025-07-17 17:06:37 -03:00
NicoR
2b7c6f5aa7 Rename anySchemaToCoSchema to coValueClassFromCoValueClassOrSchema 2025-07-17 16:55:43 -03:00
NicoR
d73a3d9d46 Rename files 2025-07-17 16:52:56 -03:00
NicoR
8af39077a3 Remove CoValueSchema.getZodSchema 2025-07-17 16:44:48 -03:00
NicoR
54bd487818 PlainText, RichText and FileStream schemas are no longer Zod schemas 2025-07-17 16:30:09 -03: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
NicoR
a420b43029 Add CoDiscriminatedUnionSchema.optional() 2025-07-17 14:29:52 -03:00
NicoR
a57268de32 Add runtime check to prevent using z.object with coValues as values 2025-07-17 14:12:41 -03:00
NicoR
6b2c4ed280 Remove no longer necessary Zod re-export wrappers 2025-07-17 14:12:41 -03:00
NicoR
8d4e0027be Upgrade Zod to 4.0.5 2025-07-17 14:12:41 -03:00
NicoR
a4141da1b7 Drop support for z.optional CoValue schemas 2025-07-17 14:12:41 -03:00
NicoR
c9ca5202f9 Fix browser integration tests 2025-07-17 14:12:41 -03:00
NicoR
7b50a2e06d Avoid using core.$ZodTypeDiscriminable for CoDiscriminatedUnions 2025-07-17 14:12:40 -03:00
NicoR
43dabccb57 [WIP] Discriminable CoValue schemas no longer extend $ZodDiscriminatedUnion 2025-07-17 14:12:40 -03:00
NicoR
b6d04f56ef Preserve catchall type info in CoMapSchema 2025-07-17 14:12:40 -03:00
NicoR
628195b678 Remove unused types from test 2025-07-17 14:12:40 -03:00
NicoR
9a5d769717 Use CoValue schema types (instead of Zod's) in circular references 2025-07-17 14:12:40 -03:00
NicoR
e30a3f66bf CoValue schemas are no longer Zod schemas 2025-07-17 14:12:40 -03:00
NicoR
6327fce933 Refactor CoValue instances & Zod primitives type inference 2025-07-17 14:12:40 -03:00
NicoR
a650da4184 Fix bug with deeply nested discriminated unions 2025-07-17 14:12:40 -03:00
NicoR
6e4a94f6ce Avoid accessing CoDiscriminatedUnion Zod internals directly 2025-07-17 14:12:40 -03:00
NicoR
b73bec64bc Rename AnyCoSchemas to CoreCoValueSchemas 2025-07-17 14:12:40 -03:00
NicoR
50ae2f47c2 Remove CoValue schema cache 2025-07-17 14:12:40 -03:00
NicoR
724d8e7f30 Drop support for z.discriminatedUnion of CoValue schemas 2025-07-17 14:12:40 -03:00
NicoR
7b285ab110 Modify CoMap schema to no longer extend ZodObject 2025-07-17 14:12:40 -03:00
NicoR
01ac9b8c4c Rewrite tests that access Zod internals 2025-07-17 14:12:40 -03:00
NicoR
4e2e1ac73e Convert CoValue schemas into interfaces 2025-07-17 14:12:40 -03:00
NicoR
94960c1f65 Convert CoValue schemas into a discriminated union 2025-07-17 14:12:40 -03:00
NicoR
b5af58347b Add getZodSchema method to CoValue schemas 2025-07-17 14:12:40 -03:00
NicoR
46a84558c5 Clean up InstanceOfSchema types 2025-07-17 14:12:40 -03:00
NicoR
f93566c045 Replace references to z.core.$ZodType with AnyZodSchema 2025-07-17 14:12:39 -03:00
NicoR
d97ed603a3 Tighten CoOptionalSchema inner type 2025-07-17 14:12:39 -03:00
NicoR
8d33103182 Rename zodSchemaToCoSchema to coreSchemaToCoSchema 2025-07-17 14:12:39 -03:00
NicoR
aaa1ff978b Organize Schema Union types 2025-07-17 14:12:39 -03:00
NicoR
82655ea7a7 Stop exporting zodSchemaToCoSchema 2025-07-17 14:12:39 -03:00
NicoR
8afe3a2e02 Remove unnecessary usages of zodSchemaToCoSchema 2025-07-17 14:12:39 -03:00
NicoR
ae2adcbd15 Extract functions to create CoreCoSchemas 2025-07-17 14:12:39 -03:00
NicoR
eb0460d330 Revert https://github.com/garden-co/jazz/pull/2651 2025-07-17 14:12:30 -03:00
166 changed files with 3549 additions and 2327 deletions

View File

@@ -1,5 +1,35 @@
# passkey-svelte
## 0.0.108
### Patch Changes
- jazz-tools@0.16.2
## 0.0.107
### Patch Changes
- Updated dependencies [c62abef]
- jazz-tools@0.16.1
## 0.0.106
### Patch Changes
- 2bbb07b: Introduce a cleaner separation between Zod and CoValue schemas:
- Zod schemas and CoValue schemas are fully separated. Zod schemas can only be composed with other Zod schemas. CoValue schemas can be composed with either Zod or other CoValue schemas.
- `z.optional()` and `z.discriminatedUnion()` no longer work with CoValue schemas. Use `co.optional()` and `co.discriminatedUnion()` instead.
- Internal schema access is now simpler. You no longer need to use Zods `.def` to access internals. Use properties like `CoMapSchema.shape`, `CoListSchema.element`, and `CoOptionalSchema.innerType` directly.
- CoValue schema types are now namespaced under `co.`. Non-namespaced exports have been removed
- CoMap schemas no longer incorrectly inherit from Zod. Previously, methods like `.extend()` and `.partial()` appeared available but could cause unexpected behavior. These methods are now disabled. In their place, `.optional()` has been added, and more Zod-like methods will be introduced in future releases.
- Upgraded Zod from `3.25.28` to `3.25.76`.
- Removed deprecated `withHelpers` method from CoValue schemas
- Removed deprecated `createCoValueObservable` function
- Updated dependencies [c09dcdf]
- Updated dependencies [2bbb07b]
- jazz-tools@0.16.0
## 0.0.105
### Patch Changes

View File

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

View File

@@ -1,8 +1,8 @@
import { co, z } from 'jazz-tools';
import { co } from 'jazz-tools';
export const Message = co.map({
text: co.plainText(),
image: z.optional(co.image())
image: co.optional(co.image())
});
export const Chat = co.list(Message);

View File

@@ -18,7 +18,7 @@
"lucide-react": "^0.274.0",
"react": "19.1.0",
"react-dom": "19.1.0",
"zod": "3.25.28"
"zod": "3.25.76"
},
"devDependencies": {
"@playwright/test": "^1.50.1",

View File

@@ -10,6 +10,7 @@ import {
DraftBubbleTeaOrder,
JazzAccount,
ListOfBubbleTeaAddOns,
validateDraftOrder,
} from "./schema.ts";
export function CreateOrder() {
@@ -22,8 +23,7 @@ export function CreateOrder() {
if (!me?.root) return;
const onSave = (draft: Loaded<typeof DraftBubbleTeaOrder>) => {
// validate if the draft is a valid order
const validation = DraftBubbleTeaOrder.validate(draft);
const validation = validateDraftOrder(draft);
setErrors(validation.errors);
if (validation.errors.length > 0) {
return;

View File

@@ -1,11 +1,11 @@
import { useAccount } from "jazz-tools/react";
import { DraftBubbleTeaOrder, JazzAccount } from "./schema";
import { JazzAccount, hasChanges } from "./schema";
export function DraftIndicator() {
const { me } = useAccount(JazzAccount, {
resolve: { root: { draft: true } },
});
if (DraftBubbleTeaOrder.hasChanges(me?.root.draft)) {
if (hasChanges(me?.root.draft)) {
return (
<div className="absolute -top-1 -right-1 bg-blue-500 border-2 border-white w-3 h-3 rounded-full dark:border-stone-925">
<span className="sr-only">You have a draft</span>

View File

@@ -15,13 +15,13 @@ export const BubbleTeaBaseTeaTypes = [
"Thai",
] as const;
export const ListOfBubbleTeaAddOns = co
.list(z.literal([...BubbleTeaAddOnTypes]))
.withHelpers((Self) => ({
hasChanges(list?: Loaded<typeof Self> | null) {
return list && Object.entries(list._raw.insertions).length > 0;
},
}));
export const ListOfBubbleTeaAddOns = co.list(
z.literal([...BubbleTeaAddOnTypes]),
);
function hasAddOnsChanges(list?: Loaded<typeof ListOfBubbleTeaAddOns> | null) {
return list && Object.entries(list._raw.insertions).length > 0;
}
export const BubbleTeaOrder = co.map({
baseTea: z.literal([...BubbleTeaBaseTeaTypes]),
@@ -31,36 +31,33 @@ export const BubbleTeaOrder = co.map({
instructions: co.optional(co.plainText()),
});
export const DraftBubbleTeaOrder = co
.map({
baseTea: z.optional(z.literal([...BubbleTeaBaseTeaTypes])),
addOns: co.optional(ListOfBubbleTeaAddOns),
deliveryDate: z.optional(z.date()),
withMilk: z.optional(z.boolean()),
instructions: co.optional(co.plainText()),
})
.withHelpers((Self) => ({
hasChanges(order: Loaded<typeof Self> | undefined) {
return (
!!order &&
(Object.keys(order._edits).length > 1 ||
ListOfBubbleTeaAddOns.hasChanges(order.addOns))
);
},
export const DraftBubbleTeaOrder = co.map({
baseTea: z.optional(z.literal([...BubbleTeaBaseTeaTypes])),
addOns: co.optional(ListOfBubbleTeaAddOns),
deliveryDate: z.optional(z.date()),
withMilk: z.optional(z.boolean()),
instructions: co.optional(co.plainText()),
});
validate(order: Loaded<typeof Self>) {
const errors: string[] = [];
export function validateDraftOrder(order: Loaded<typeof DraftBubbleTeaOrder>) {
const errors: string[] = [];
if (!order.baseTea) {
errors.push("Please select your preferred base tea.");
}
if (!order.deliveryDate) {
errors.push("Plese select a delivery date.");
}
if (!order.baseTea) {
errors.push("Please select your preferred base tea.");
}
if (!order.deliveryDate) {
errors.push("Plese select a delivery date.");
}
return { errors };
},
}));
return { errors };
}
export function hasChanges(order?: Loaded<typeof DraftBubbleTeaOrder> | null) {
return (
!!order &&
(Object.keys(order._edits).length > 1 || hasAddOnsChanges(order.addOns))
);
}
/** The root is an app-specific per-user private `CoMap`
* where you can store top-level objects for that user */

View File

@@ -11,9 +11,9 @@ export const Issue = co.map({
status: z.enum(["open", "closed"]),
labels: co.list(z.string()),
reactions: ReactionsList,
file: z.optional(co.fileStream()),
image: z.optional(co.image()),
lead: z.optional(co.account()),
file: co.optional(co.fileStream()),
image: co.optional(co.image()),
lead: co.optional(co.account()),
});
export const Project = co.map({

View File

@@ -15,7 +15,7 @@
"jazz-tools": "workspace:*",
"react": "19.1.0",
"react-dom": "19.1.0",
"zod": "3.25.28"
"zod": "3.25.76"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
@@ -31,4 +31,4 @@
"vite": "^6.3.5",
"vitest": "3.1.1"
}
}
}

View File

@@ -7,6 +7,7 @@ import {
JazzAccount,
Organization,
Project,
validateDraftOrganization,
} from "../schema.ts";
import { Errors } from "./Errors.tsx";
import { OrganizationForm } from "./OrganizationForm.tsx";
@@ -21,8 +22,7 @@ export function CreateOrganization() {
if (!me?.root?.organizations) return;
const onSave = (draft: Loaded<typeof DraftOrganization>) => {
// validate if the draft is a valid organization
const validation = DraftOrganization.validate(draft);
const validation = validateDraftOrganization(draft);
setErrors(validation.errors);
if (validation.errors.length > 0) {
return;

View File

@@ -10,24 +10,24 @@ export const Organization = co.map({
projects: co.list(Project),
});
export const DraftOrganization = co
.map({
name: z.optional(z.string()),
projects: co.list(Project),
})
.withHelpers((Self) => ({
validate(org: Loaded<typeof Self>) {
const errors: string[] = [];
export const DraftOrganization = co.map({
name: z.optional(z.string()),
projects: co.list(Project),
});
if (!org.name) {
errors.push("Please enter a name.");
}
export function validateDraftOrganization(
org: Loaded<typeof DraftOrganization>,
) {
const errors: string[] = [];
return {
errors,
};
},
}));
if (!org.name) {
errors.push("Please enter a name.");
}
return {
errors,
};
}
export const JazzAccountRoot = co.map({
organizations: co.list(Organization),

View File

@@ -1,7 +1,5 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
};
const nextConfig: NextConfig = {};
export default nextConfig;

View File

@@ -1,5 +1,5 @@
import { jazzServerAccount } from "@/jazzServerAccount";
import { Game, Player, PlayerState, createGameState } from "@/schema";
import { Game, createGameState } from "@/schema";
import { serverApi } from "@/serverApi";
import { Account, Group, JazzRequestError } from "jazz-tools";

View File

@@ -32,7 +32,7 @@ export type Game = co.loaded<typeof Game>;
export const WaitingRoom = co.map({
creator: co.account(),
game: z.optional(Game),
game: co.optional(Game),
});
export type WaitingRoom = co.loaded<typeof WaitingRoom>;

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

@@ -244,7 +244,7 @@ export function CreateOrder() {
In a `BubbleTeaOrder`, the `name` field is required, so it would be a good idea to validate this before turning the draft into a real order.
Update the schema to include a `validate` helper.
Update the schema to include a `validateDraftOrder` helper.
<CodeGroup>
```ts twoslash
@@ -253,17 +253,17 @@ import { co, z } from "jazz-tools";
// schema.ts
export const DraftBubbleTeaOrder = co.map({
name: z.optional(z.string()),
}).withHelpers((Self) => ({ // [!code ++:11]
validate(draft: co.loaded<typeof Self>) {
const errors: string[] = [];
});
if (!draft.name) {
errors.push("Please enter a name.");
}
export function validateDraftOrder(draft: co.loaded<typeof DraftBubbleTeaOrder>) { // [!code ++:9]
const errors: string[] = [];
return { errors };
},
}));
if (!draft.name) {
errors.push("Please enter a name.");
}
return { errors };
};
```
</CodeGroup>
@@ -282,17 +282,17 @@ export const BubbleTeaOrder = co.map({
export const DraftBubbleTeaOrder = co.map({
name: z.optional(z.string()),
}).withHelpers((Self) => ({
validate(draft: co.loaded<typeof Self>) {
const errors: string[] = [];
});
if (!draft.name) {
errors.push("Please enter a name.");
}
export function validateDraftOrder(draft: co.loaded<typeof DraftBubbleTeaOrder>) {
const errors: string[] = [];
return { errors };
},
}));
if (!draft.name) {
errors.push("Please enter a name.");
}
return { errors };
};
export const AccountRoot = co.map({
draft: DraftBubbleTeaOrder,
@@ -340,7 +340,7 @@ export function CreateOrder() {
e.preventDefault();
if (!draft) return;
const validation = DraftBubbleTeaOrder.validate(draft); // [!code ++:5]
const validation = validateDraftOrder(draft); // [!code ++:5]
if (validation.errors.length > 0) {
console.log(validation.errors);
return;
@@ -455,17 +455,17 @@ export const BubbleTeaOrder = co.map({
export const DraftBubbleTeaOrder = co.map({
name: z.optional(z.string()),
}).withHelpers((Self) => ({
validate(draft: co.loaded<typeof Self>) {
const errors: string[] = [];
});
if (!draft.name) {
errors.push("Please enter a name.");
}
export function validateDraftOrder(draft: co.loaded<typeof DraftBubbleTeaOrder>) {
const errors: string[] = [];
return { errors };
},
}));
if (!draft.name) {
errors.push("Please enter a name.");
}
return { errors };
};
export const AccountRoot = co.map({
draft: DraftBubbleTeaOrder,
@@ -485,7 +485,7 @@ export const JazzAccount = co.account({
// @filename: CreateOrder.tsx
import * as React from "react";
import { useCoState, useAccount } from "jazz-tools/react";
import { BubbleTeaOrder, DraftBubbleTeaOrder, JazzAccount } from "schema";
import { BubbleTeaOrder, DraftBubbleTeaOrder, JazzAccount, validateDraftOrder } from "schema";
import { co } from "jazz-tools";
export function OrderForm({
@@ -527,7 +527,7 @@ export function CreateOrder() {
const draft = me.root.draft; // [!code ++:2]
if (!draft) return;
const validation = DraftBubbleTeaOrder.validate(draft);
const validation = validateDraftOrder(draft);
if (validation.errors.length > 0) {
console.log(validation.errors);
return;
@@ -579,21 +579,21 @@ import { co, z } from "jazz-tools";
// schema.ts
export const DraftBubbleTeaOrder = co.map({
name: z.optional(z.string()),
}).withHelpers((Self) => ({
validate(draft: co.loaded<typeof Self>) {
const errors: string[] = [];
});
if (!draft.name) {
errors.push("Plese enter a name.");
}
export function validateDraftOrder(draft: co.loaded<typeof DraftBubbleTeaOrder>) {
const errors: string[] = [];
return { errors };
},
if (!draft.name) {
errors.push("Please enter a name.");
}
hasChanges(draft?: co.loaded<typeof Self>) { // [!code ++:3]
return draft ? Object.keys(draft._edits).length : false;
},
}));
return { errors };
};
export function hasChanges(draft?: co.loaded<typeof DraftBubbleTeaOrder>) { // [!code ++:3]
return draft ? Object.keys(draft._edits).length : false;
};
```
</CodeGroup>
@@ -611,21 +611,21 @@ export const BubbleTeaOrder = co.map({
export const DraftBubbleTeaOrder = co.map({
name: z.optional(z.string()),
}).withHelpers((Self) => ({
validate(draft: co.loaded<typeof Self>) {
const errors: string[] = [];
});
if (!draft.name) {
errors.push("Plese enter a name.");
}
export function validateDraftOrder(draft: co.loaded<typeof DraftBubbleTeaOrder>) {
const errors: string[] = [];
return { errors };
},
if (!draft.name) {
errors.push("Please enter a name.");
}
hasChanges(draft?: co.loaded<typeof Self>) { // [!code ++:3]
return draft ? Object.keys(draft._edits).length : false;
},
}));
return { errors };
};
export function hasChanges(draft?: co.loaded<typeof DraftBubbleTeaOrder>) {
return draft ? Object.keys(draft._edits).length : false;
};
export const AccountRoot = co.map({
draft: DraftBubbleTeaOrder,
@@ -649,7 +649,7 @@ export function DraftIndicator() {
resolve: { root: { draft: true } },
});
if (DraftBubbleTeaOrder.hasChanges(me?.root.draft)) {
if (hasChanges(me?.root.draft)) {
return (
<p>You have a draft</p>
);

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

@@ -109,6 +109,11 @@ export const docNavigationItems = [
// collapse: true,
prefix: "/docs/upgrade",
items: [
{
name: "0.16.0 - Cleaner separation between Zod and CoValue schemas",
href: "/docs/upgrade/0-16-0",
done: 100,
},
{
name: "0.15.0 - Everything inside `jazz-tools`",
href: "/docs/upgrade/0-15-0",

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

@@ -89,7 +89,7 @@ export const MyAppRoot = co.map({
export const MyAppProfile = co.profile({ // [!code ++:4]
name: z.string(), // compatible with default Profile schema
avatar: z.optional(co.image()),
avatar: co.optional(co.image()),
});
export const MyAppAccount = co.account({
@@ -241,7 +241,7 @@ const MyAppProfile = co.profile({
// ---cut---
const MyAppRoot = co.map({
myChats: co.list(Chat),
myBookmarks: z.optional(co.list(Bookmark)), // [!code ++:1]
myBookmarks: co.optional(co.list(Bookmark)), // [!code ++:1]
});

View File

@@ -320,6 +320,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 +335,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>
@@ -353,16 +358,16 @@ const Person = co.map({
```
</CodeGroup>
You can use the same technique for mutually recursive references, but you'll need to help TypeScript along:
You can use the same technique for mutually recursive references:
<CodeGroup>
```ts twoslash
// ---cut---
import { co, z, CoListSchema } from "jazz-tools";
import { co, z } from "jazz-tools";
const Person = co.map({
name: z.string(),
get friends(): CoListSchema<typeof Person> {
get friends() {
return ListOfPeople;
}
});
@@ -372,22 +377,6 @@ const ListOfPeople = co.list(Person);
</CodeGroup>
Note: similarly, if you use modifiers like `co.optional()` you'll need to help TypeScript along:
<CodeGroup>
```ts twoslash
import { co, z } from "jazz-tools";
// ---cut---
const Person = co.map({
name: z.string(),
get bestFriend(): z.ZodOptional<typeof Person> {
return co.optional(Person);
}
});
```
</CodeGroup>
### Helper methods
If you find yourself repeating the same logic to access computed CoValues properties,

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

@@ -0,0 +1,158 @@
import { CodeGroup } from '@/components/forMdx'
# Jazz 0.16.0 - Cleaner separation between Zod and CoValue schemas
This release introduces a cleaner separation between Zod and CoValue schemas, improves type inference with circular references, and simplifies how you access internal schemas.
While most applications won't require extensive refactors, some breaking changes will require action.
## Motivation
Before 0.16.0, CoValue schemas were a thin wrapper around Zod schemas. This made it easy to use Zod methods on CoValue schemas,
but it also prevented the type checker from detecting issues when combining Zod and CoValue schemas.
For example, the following code would previously compile without errors, but would have severe limitations:
<CodeGroup>
```tsx
import { co, z } from "jazz-tools";
const Dog = co.map({
breed: z.string(),
});
const Person = co.map({
pets: z.array(Dog),
});
// You can create a CoMap with a z.array field that contains another CoMap
const map = Person.create({
pets: [Dog.create({ breed: "Labrador" })],
});
// But then you cannot eagerly load the nested CoMap, because
// there's a plain JS object in between. So this would fail:
Person.load(map.id, { resolve: { pets: { $each: true } } });
```
</CodeGroup>
Schema composition rules are now stricter: Zod schemas can only be composed with other Zod schemas.
CoValue schemas can be composed with either Zod or other CoValue schemas. These rules are enforced at the type level, to make it easier
to spot errors in schema definitions and avoid possible footguns when mixing Zod and CoValue schemas.
Having a stricter separation between Zod and CoValue schemas also allowed us to improve type inference with circular references.
Previously, the type checker would not be able to infer types for even simple circular references, but now it can!
<CodeGroup>
```tsx
import { co, z } from "jazz-tools";
const Person = co.map({
name: z.string(),
get friends(): CoListSchema<typeof Person> { // [!code --]
get friends() { // [!code ++]
return co.list(Person);
},
});
```
</CodeGroup>
There are some scenarios where recursive type inference can still fail due to TypeScript limitations, but these should be rare.
## Breaking changes
### The Account root id is now discoverable
In prior Jazz releases, the Account root id was stored encrypted and accessible only by the account owner.
This made it impossible to load the account root this way:
<CodeGroup>
```tsx
const bob = MyAppAccount.load(bobId, { resolve: { root: true }, loadAs: me });
```
</CodeGroup>
So we changed Account root id to be discoverable by everyone.
**This doesn't affect the visibility of the account root**, which still follows the permissions defined in its group.
For existing accounts, the change is applied the next time the user loads their account.
No action is required on your side, but we preferred to mark this as a breaking change because it
minimally affects access to the account root. (e.g., if in your app the root is public, now users can access other users' root by knowing their account ID)
### `z.optional()` and `z.discriminatedUnion()` no longer work with CoValue schemas
You'll now need to use the `co.optional()` and `co.discriminatedUnion()` equivalents.
This change may require you to update any explicitly typed cyclic references.
<CodeGroup>
```tsx
import { co, z } from "jazz-tools";
const Person = co.map({
name: z.string(),
get bestFriend(): z.ZodOptional<typeof Person> { // [!code --]
return z.optional(Person); // [!code --]
get bestFriend(): co.Optional<typeof Person> { // [!code ++]
return co.optional(Person); // [!code ++]
}
});
```
</CodeGroup>
### CoValue schema types are now under the `co.` namespace
All CoValue schema types are now accessed via the `co.` namespace. If you're using explicit types (especially in recursive schemas), you'll need to update them accordingly.
<CodeGroup>
```tsx
import { co, z } from "jazz-tools";
const Person = co.map({
name: z.string(),
get friends(): CoListSchema<typeof Person> { // [!code --]
get friends(): co.List<typeof Person> { // [!code ++]
return co.list(Person);
}
});
```
</CodeGroup>
### Unsupported Zod methods have been removed from CoMap schemas
CoMap schemas no longer incorrectly inherit Zod methods like `.extend()` and `.partial()`. These methods previously appeared to work but could behave unpredictably. They have now been disabled.
We're keeping `.optional()` and plan to introduce more Zod-like methods in future releases.
### Internal schema access is now simpler
You no longer need to use Zod's `.def` to access schema internals. Instead, you can directly use methods like `CoMapSchema.shape`, `CoListSchema.element`, and `CoOptionalSchema.innerType`.
<CodeGroup>
```tsx
import { co, z } from "jazz-tools";
const Message = co.map({
content: co.richText(),
});
const Thread = co.map({
messages: co.list(Message),
});
const thread = Thread.create({
messages: Thread.def.shape.messages.create([ // [!code --]
messages: Thread.shape.messages.create([ // [!code ++]
Message.create({
content: co.richText().create("Hi!"),
}),
Message.create({
content: co.richText().create("What's up?"),
}),
]),
});
```
</CodeGroup>
### Removed the deprecated `withHelpers` method from CoValue schemas
The deprecated `withHelpers()` method has been removed from CoValue schemas. You can define helper functions manually to encapsulate CoValue-related logic.
[Learn how to define helper methods](https://jazz.tools/docs/vanilla/schemas/covalues#helper-methods).

View File

@@ -343,14 +343,14 @@ CoLists can be used to create one-to-many relationships:
<CodeGroup>
```ts twoslash
import { co, z, CoListSchema } from "jazz-tools";
import { co, z } from "jazz-tools";
const Task = co.map({
title: z.string(),
status: z.literal(["todo", "in-progress", "complete"]),
get project(): z.ZodOptional<typeof Project> {
return z.optional(Project);
get project(): co.Optional<typeof Project> {
return co.optional(Project);
}
});
@@ -359,7 +359,7 @@ const ListOfTasks = co.list(Task);
const Project = co.map({
name: z.string(),
get tasks(): CoListSchema<typeof Task> {
get tasks(): co.List<typeof Task> {
return ListOfTasks;
}
});

View File

@@ -213,14 +213,14 @@ const Member = co.map({
name: z.string(),
});
// ---cut---
import { co, z, CoListSchema } from "jazz-tools";
import { co, z } from "jazz-tools";
const Project = co.map({
name: z.string(),
startDate: z.date(),
status: z.literal(["planning", "active", "completed"]),
coordinator: co.optional(Member),
get subProjects(): z.ZodOptional<CoListSchema<typeof Project>> {
get subProjects(): co.Optional<co.List<typeof Project>> {
return co.optional(co.list(Project));
}
});

View File

@@ -50,51 +50,3 @@ export type User = co.loaded<typeof User>;
This direct linking approach offers a single source of truth. When you update a referenced CoValue, all other CoValues that point to it are automatically updated, ensuring data consistency across your application.
By connecting CoValues through these direct references, you can build robust and collaborative applications where data is consistent, efficient to manage, and relationships are clearly defined. The ability to link different CoValue types to the same underlying data is fundamental to building complex applications with Jazz.
## Recursive references with DiscriminatedUnion
In advanced schemas, you may want a CoValue that recursively references itself. For example, a `ReferenceItem` that contains a list of other items like `NoteItem` or `AttachmentItem`. This is common in tree-like structures such as threaded comments or nested project outlines.
You can model this with a Zod `z.discriminatedUnion`, but TypeScripts type inference doesn't handle recursive unions well without a workaround.
Heres how to structure your schema to avoid circular reference errors.
### Use this pattern for recursive discriminated unions
<CodeGroup>
```ts twoslash
import { CoListSchema, co, z } from "jazz-tools";
// Recursive item modeling pattern using discriminated unions
// First, define the non-recursive types
export const NoteItem = co.map({
type: z.literal("note"),
internal: z.boolean(),
content: co.plainText(),
});
export const AttachmentItem = co.map({
type: z.literal("attachment"),
internal: z.boolean(),
content: co.fileStream(),
});
export const ReferenceItem = co.map({
type: z.literal("reference"),
internal: z.boolean(),
content: z.string(),
// Workaround: declare the field type using CoListSchema and ZodDiscriminatedUnion so TS can safely recurse
get children(): CoListSchema<z.ZodDiscriminatedUnion<[typeof NoteItem, typeof AttachmentItem, typeof ReferenceItem]>> {
return ProjectContextItemList;
},
});
// Create the recursive union
export const ProjectContextItem = z.discriminatedUnion("type", [NoteItem, AttachmentItem, ReferenceItem]);
// Final list of recursive types
export const ProjectContextItemList = co.list(ProjectContextItem);
```
</CodeGroup>
Even though this seems like a shortcut, TypeScript and Zod can't resolve the circular reference this way. Always define the discriminated union before introducing recursive links.

View File

@@ -24,7 +24,7 @@ import { Group, co, z } from "jazz-tools";
const MyProfile = co.profile({
name: z.string(),
image: z.optional(co.image()),
image: co.optional(co.image()),
});
const MyAccount = co.account({

View File

@@ -25,7 +25,7 @@ import { Group, co, z } from "jazz-tools";
const MyProfile = co.profile({
name: z.string(),
image: z.optional(co.image()),
image: co.optional(co.image()),
});
const MyAccount = co.account({

View File

@@ -249,7 +249,7 @@ Resolve queries let you declare exactly which references to load and how deep to
<CodeGroup>
```ts twoslash
import { co, z, CoListSchema } from "jazz-tools";
import { co, z } from "jazz-tools";
const projectId = "co_123";
// ---cut-before---
@@ -259,8 +259,8 @@ const TeamMember = co.map({
const Task = co.map({
title: z.string(),
assignee: z.optional(TeamMember),
get subtasks(): CoListSchema<typeof Task> { return co.list(Task) },
assignee: co.optional(TeamMember),
get subtasks(): co.List<typeof Task> { return co.list(Task) },
});
const Project = co.map({
@@ -349,7 +349,7 @@ When a user tries to load a reference they don't have access to:
<CodeGroup>
```ts twoslash
import { co, z, CoListSchema } from "jazz-tools";
import { co, z } from "jazz-tools";
const TeamMember = co.map({
name: z.string(),
@@ -357,8 +357,8 @@ const TeamMember = co.map({
const Task = co.map({
title: z.string(),
assignee: z.optional(TeamMember),
get subtasks(): CoListSchema<typeof Task> { return co.list(Task) },
assignee: co.optional(TeamMember),
get subtasks(): co.List<typeof Task> { return co.list(Task) },
});
const Project = co.map({
@@ -388,7 +388,7 @@ When a list contains references to items the user can't access:
<CodeGroup>
```ts twoslash
import { co, z, CoListSchema } from "jazz-tools";
import { co, z } from "jazz-tools";
const TeamMember = co.map({
name: z.string(),
@@ -396,8 +396,8 @@ const TeamMember = co.map({
const Task = co.map({
title: z.string(),
assignee: z.optional(TeamMember),
get subtasks(): CoListSchema<typeof Task> { return co.list(Task) },
assignee: co.optional(TeamMember),
get subtasks(): co.List<typeof Task> { return co.list(Task) },
});
const Project = co.map({
@@ -424,7 +424,7 @@ When trying to load an object with an inaccessible reference without directly re
<CodeGroup>
```ts twoslash
import { co, z, CoListSchema } from "jazz-tools";
import { co, z } from "jazz-tools";
const TeamMember = co.map({
name: z.string(),
@@ -432,8 +432,8 @@ const TeamMember = co.map({
const Task = co.map({
title: z.string(),
assignee: z.optional(TeamMember),
get subtasks(): CoListSchema<typeof Task> { return co.list(Task) },
assignee: co.optional(TeamMember),
get subtasks(): co.List<typeof Task> { return co.list(Task) },
});
const Project = co.map({
@@ -468,7 +468,7 @@ This way the inaccessible items are replaced with `null` in the returned list.
<CodeGroup>
```ts twoslash
import { co, z, CoListSchema, Group } from "jazz-tools";
import { co, z, Group } from "jazz-tools";
import { createJazzTestAccount } from "jazz-tools/testing";
const me = await createJazzTestAccount();
@@ -653,7 +653,7 @@ The `co.loaded` type is especially useful when passing data between components,
<ContentByFramework framework="react">
<CodeGroup>
```tsx twoslash
import { CoListSchema, co, z } from "jazz-tools";
import { co, z } from "jazz-tools";
import React from "react";
const TeamMember = co.map({
@@ -662,8 +662,8 @@ const TeamMember = co.map({
const Task = co.map({
title: z.string(),
assignee: z.optional(TeamMember),
get subtasks(): CoListSchema<typeof Task> {
assignee: co.optional(TeamMember),
get subtasks(): co.List<typeof Task> {
return co.list(Task);
},
});
@@ -727,7 +727,7 @@ function processProject(project: FullyLoadedProject) {
<ContentByFramework framework="vanilla">
<CodeGroup>
```ts twoslash
import { CoListSchema, co, z } from "jazz-tools";
import { co, z } from "jazz-tools";
const TeamMember = co.map({
name: z.string(),
@@ -735,8 +735,8 @@ const TeamMember = co.map({
const Task = co.map({
title: z.string(),
assignee: z.optional(TeamMember),
get subtasks(): CoListSchema<typeof Task> {
assignee: co.optional(TeamMember),
get subtasks(): co.List<typeof Task> {
return co.list(Task);
},
});
@@ -799,7 +799,7 @@ Sometimes you need to make sure data is loaded before proceeding with an operati
<CodeGroup>
```ts twoslash
import { CoListSchema, co, z } from "jazz-tools";
import { co, z } from "jazz-tools";
const TeamMember = co.map({
name: z.string(),
@@ -809,7 +809,7 @@ const Task = co.map({
title: z.string(),
status: z.literal(["todo", "in-progress", "completed"]),
assignee: z.string().optional(),
get subtasks(): CoListSchema<typeof Task> {
get subtasks(): co.List<typeof Task> {
return co.list(Task);
},
});

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,24 @@
# cojson-storage-indexeddb
## 0.16.2
### Patch Changes
- cojson@0.16.2
## 0.16.1
### Patch Changes
- cojson@0.16.1
## 0.16.0
### Patch Changes
- Updated dependencies [c09dcdf]
- cojson@0.16.0
## 0.15.16
### Patch Changes

View File

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

View File

@@ -1,5 +1,24 @@
# cojson-storage-sqlite
## 0.16.2
### Patch Changes
- cojson@0.16.2
## 0.16.1
### Patch Changes
- cojson@0.16.1
## 0.16.0
### Patch Changes
- Updated dependencies [c09dcdf]
- cojson@0.16.0
## 0.15.16
### Patch Changes

View File

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

View File

@@ -1,5 +1,24 @@
# cojson-transport-nodejs-ws
## 0.16.2
### Patch Changes
- cojson@0.16.2
## 0.16.1
### Patch Changes
- cojson@0.16.1
## 0.16.0
### Patch Changes
- Updated dependencies [c09dcdf]
- cojson@0.16.0
## 0.15.16
### Patch Changes

View File

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

View File

@@ -1,5 +1,17 @@
# cojson
## 0.16.2
## 0.16.1
## 0.16.0
### Minor Changes
- c09dcdf: Change the root attribute to be public on Account. The root content will still follow the visiblity rules specified in their group.
Existing accounts will be gradually migrated as they are loaded.
## 0.15.16
## 0.15.15

View File

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

View File

@@ -50,6 +50,7 @@ export type DecryptedTransaction = {
txID: TransactionID;
changes: JsonValue[];
madeAt: number;
trusting?: boolean;
};
const readKeyCache = new WeakMap<CoValueCore, { [id: KeyID]: KeySecret }>();
@@ -657,6 +658,7 @@ export class CoValueCore {
txID,
madeAt: tx.madeAt,
changes: parseJSON(tx.changes),
trusting: true,
});
} catch (e) {
logger.error("Failed to parse trusting transaction on " + this.id, {

View File

@@ -16,6 +16,7 @@ type MapOp<K extends string, V extends JsonValue | undefined> = {
madeAt: number;
changeIdx: number;
change: MapOpPayload<K, V>;
trusting?: boolean;
};
// TODO: add after TransactionID[] for conflicts/ordering
@@ -112,7 +113,7 @@ export class RawCoMapView<
NonNullable<(typeof ops)[keyof typeof ops]>
>();
for (const { txID, changes, madeAt } of newValidTransactions) {
for (const { txID, changes, madeAt, trusting } of newValidTransactions) {
if (madeAt > this.latestTxMadeAt) {
this.latestTxMadeAt = madeAt;
}
@@ -127,6 +128,7 @@ export class RawCoMapView<
madeAt,
changeIdx,
change,
trusting,
};
const entries = ops[change.key];

View File

@@ -63,7 +63,7 @@ import { DisconnectedError, SyncManager, emptyKnownState } from "./sync.js";
type Value = JsonValue | AnyRawCoValue;
export { PriorityBasedMessageQueue } from "./PriorityBasedMessageQueue.js";
export { PriorityBasedMessageQueue } from "./queue/PriorityBasedMessageQueue.js";
import { getDependedOnCoValuesFromRawData } from "./coValueCore/utils.js";
import {
CO_VALUE_LOADING_CONFIG,

View File

@@ -313,6 +313,15 @@ export class LocalNode {
throw new Error("Account has no profile");
}
const rootID = account.get("root");
if (rootID) {
const rawEntry = account.getRaw("root");
if (!rawEntry?.trusting) {
account.set("root", rootID, "trusting");
}
}
// Preload the profile
await node.load(profileID);

View File

@@ -2,7 +2,7 @@ import { CoID } from "./coValue.js";
import { CoValueCore } from "./coValueCore/coValueCore.js";
import { Transaction } from "./coValueCore/verifiedState.js";
import { RawAccount, RawAccountID, RawProfile } from "./coValues/account.js";
import { MapOpPayload } from "./coValues/coMap.js";
import { MapOpPayload, RawCoMap } from "./coValues/coMap.js";
import {
EVERYONE,
Everyone,
@@ -270,6 +270,7 @@ function determineValidTransactionsForGroup(
| MapOpPayload<RawAccountID | AgentID | Everyone, Role>
| MapOpPayload<"readKey", JsonValue>
| MapOpPayload<"profile", CoID<RawProfile>>
| MapOpPayload<"root", CoID<RawCoMap>>
| MapOpPayload<`parent_${CoID<RawGroup>}`, CoID<RawGroup>>
| MapOpPayload<`child_${CoID<RawGroup>}`, CoID<RawGroup>>;
@@ -297,6 +298,14 @@ function determineValidTransactionsForGroup(
continue;
}
validTransactions.push({ txID: { sessionID, txIndex }, tx });
continue;
} else if (change.key === "root") {
if (memberState[transactor] !== "admin") {
logPermissionError("Only admins can set root");
continue;
}
validTransactions.push({ txID: { sessionID, txIndex }, tx });
continue;
} else if (

View File

@@ -1,9 +1,9 @@
import { Counter, ValueType, metrics } from "@opentelemetry/api";
import type { PeerState } from "./PeerState.js";
import { LinkedList } from "./PriorityBasedMessageQueue.js";
import { SYNC_SCHEDULER_CONFIG } from "./config.js";
import { logger } from "./logger.js";
import type { SyncMessage } from "./sync.js";
import type { PeerState } from "../PeerState.js";
import { SYNC_SCHEDULER_CONFIG } from "../config.js";
import { logger } from "../logger.js";
import type { SyncMessage } from "../sync.js";
import { LinkedList } from "./LinkedList.js";
/**
* A queue that schedules messages across different peers using a round-robin approach.

View File

@@ -1,6 +1,5 @@
import { Counter, ValueType, metrics } from "@opentelemetry/api";
import { CO_VALUE_PRIORITY, type CoValuePriority } from "./priority.js";
import type { SyncMessage } from "./sync.js";
import type { SyncMessage } from "../sync.js";
/**
* Since we have a fixed range of priority values (0-7) we can create a fixed array of queues.
@@ -10,18 +9,16 @@ type Tuple<T, N extends number, A extends unknown[] = []> = A extends {
}
? A
: Tuple<T, N, [...A, T]>;
type QueueTuple = Tuple<LinkedList<SyncMessage>, 3>;
export type QueueTuple = Tuple<LinkedList<SyncMessage>, 3>;
type LinkedListNode<T> = {
value: T;
next: LinkedListNode<T> | undefined;
};
/**
* Using a linked list to make the shift operation O(1) instead of O(n)
* as our queues can grow very large when the system is under pressure.
*/
export class LinkedList<T> {
constructor(private meter?: QueueMeter) {}
@@ -70,7 +67,6 @@ export class LinkedList<T> {
return this.head === undefined;
}
}
class QueueMeter {
private pullCounter: Counter;
private pushCounter: Counter;
@@ -111,52 +107,9 @@ class QueueMeter {
this.pushCounter.add(1, this.attrs);
}
}
function meteredList<T>(
export function meteredList<T>(
type: "incoming" | "outgoing",
attrs?: Record<string, string | number>,
) {
return new LinkedList<T>(new QueueMeter("jazz.messagequeue." + type, attrs));
}
const PRIORITY_TO_QUEUE_INDEX = {
[CO_VALUE_PRIORITY.HIGH]: 0,
[CO_VALUE_PRIORITY.MEDIUM]: 1,
[CO_VALUE_PRIORITY.LOW]: 2,
} as const;
export class PriorityBasedMessageQueue {
private queues: QueueTuple;
constructor(
private defaultPriority: CoValuePriority,
type: "incoming" | "outgoing",
/**
* Optional attributes to be added to the generated metrics.
* By default the metrics will have the priority as an attribute.
*/
attrs?: Record<string, string | number>,
) {
this.queues = [
meteredList(type, { priority: CO_VALUE_PRIORITY.HIGH, ...attrs }),
meteredList(type, { priority: CO_VALUE_PRIORITY.MEDIUM, ...attrs }),
meteredList(type, { priority: CO_VALUE_PRIORITY.LOW, ...attrs }),
];
}
private getQueue(priority: CoValuePriority) {
return this.queues[PRIORITY_TO_QUEUE_INDEX[priority]];
}
public push(msg: SyncMessage) {
const priority = "priority" in msg ? msg.priority : this.defaultPriority;
this.getQueue(priority).push(msg);
}
public pull() {
const priority = this.queues.findIndex((queue) => queue.length > 0);
return this.queues[priority]?.shift();
}
}

View File

@@ -0,0 +1,45 @@
import { CO_VALUE_PRIORITY, type CoValuePriority } from "../priority.js";
import type { SyncMessage } from "../sync.js";
import { QueueTuple, meteredList } from "./LinkedList.js";
const PRIORITY_TO_QUEUE_INDEX = {
[CO_VALUE_PRIORITY.HIGH]: 0,
[CO_VALUE_PRIORITY.MEDIUM]: 1,
[CO_VALUE_PRIORITY.LOW]: 2,
} as const;
export class PriorityBasedMessageQueue {
private queues: QueueTuple;
constructor(
private defaultPriority: CoValuePriority,
type: "incoming" | "outgoing",
/**
* Optional attributes to be added to the generated metrics.
* By default the metrics will have the priority as an attribute.
*/
attrs?: Record<string, string | number>,
) {
this.queues = [
meteredList(type, { priority: CO_VALUE_PRIORITY.HIGH, ...attrs }),
meteredList(type, { priority: CO_VALUE_PRIORITY.MEDIUM, ...attrs }),
meteredList(type, { priority: CO_VALUE_PRIORITY.LOW, ...attrs }),
];
}
private getQueue(priority: CoValuePriority) {
return this.queues[PRIORITY_TO_QUEUE_INDEX[priority]];
}
public push(msg: SyncMessage) {
const priority = "priority" in msg ? msg.priority : this.defaultPriority;
this.getQueue(priority).push(msg);
}
public pull() {
const priority = this.queues.findIndex((queue) => queue.length > 0);
return this.queues[priority]?.shift();
}
}

View File

@@ -1,6 +1,6 @@
import { LinkedList } from "../PriorityBasedMessageQueue.js";
import { logger } from "../logger.js";
import { CoValueKnownState, NewContentMessage } from "../sync.js";
import { LinkedList } from "./LinkedList.js";
type StoreQueueEntry = {
data: NewContentMessage[];

View File

@@ -1,4 +1,3 @@
import { LinkedList } from "../PriorityBasedMessageQueue.js";
import {
type CoValueCore,
MAX_RECOMMENDED_TX_SIZE,
@@ -7,12 +6,12 @@ import {
type StorageAPI,
} from "../exports.js";
import { getPriorityFromHeader } from "../priority.js";
import { StoreQueue } from "../queue/StoreQueue.js";
import {
CoValueKnownState,
NewContentMessage,
emptyKnownState,
} from "../sync.js";
import { StoreQueue } from "./StoreQueue.js";
import { StorageKnownState } from "./knownState.js";
import { collectNewTxs, getDependedOnCoValues } from "./syncUtils.js";
import type {

View File

@@ -1,5 +1,4 @@
import { Histogram, ValueType, metrics } from "@opentelemetry/api";
import { IncomingMessagesQueue } from "./IncomingMessagesQueue.js";
import { PeerState } from "./PeerState.js";
import { SyncStateManager } from "./SyncStateManager.js";
import { CoValueCore } from "./coValueCore/coValueCore.js";
@@ -10,6 +9,7 @@ import { RawCoID, SessionID } from "./ids.js";
import { LocalNode } from "./localNode.js";
import { logger } from "./logger.js";
import { CoValuePriority } from "./priority.js";
import { IncomingMessagesQueue } from "./queue/IncomingMessagesQueue.js";
import { accountOrAgentIDfromSessionID } from "./typeUtils/accountOrAgentIDfromSessionID.js";
import { isAccountID } from "./typeUtils/isAccountID.js";

View File

@@ -1,6 +1,6 @@
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import { IncomingMessagesQueue } from "../IncomingMessagesQueue.js";
import { PeerState } from "../PeerState.js";
import { IncomingMessagesQueue } from "../queue/IncomingMessagesQueue.js";
import { ConnectedPeerChannel } from "../streamUtils.js";
import { Peer, SyncMessage } from "../sync.js";
import {

View File

@@ -1,5 +1,5 @@
import { beforeEach, describe, expect, it } from "vitest";
import { LinkedList } from "../PriorityBasedMessageQueue";
import { LinkedList } from "../queue/LinkedList.js";
describe("LinkedList", () => {
let list: LinkedList<number>;

View File

@@ -1,6 +1,6 @@
import { afterEach, describe, expect, test } from "vitest";
import { PriorityBasedMessageQueue } from "../PriorityBasedMessageQueue.js";
import { CO_VALUE_PRIORITY } from "../priority.js";
import { PriorityBasedMessageQueue } from "../queue/PriorityBasedMessageQueue.js";
import type { SyncMessage } from "../sync.js";
import {
createTestMetricReader,

View File

@@ -1,5 +1,5 @@
import { beforeEach, describe, expect, test, vi } from "vitest";
import { StoreQueue } from "../storage/StoreQueue.js";
import { StoreQueue } from "../queue/StoreQueue.js";
import type { CoValueKnownState, NewContentMessage } from "../sync.js";
function createMockNewContentMessage(id: string): NewContentMessage[] {

View File

@@ -74,6 +74,69 @@ test("Can create account with one node, and then load it on another", async () =
expect(map2.get("foo")).toEqual("bar");
});
test("Should migrate the root from private to trusting", async () => {
const { node, accountID, accountSecret } =
await LocalNode.withNewlyCreatedAccount({
creationProps: { name: "Hermes Puggington" },
crypto: Crypto,
});
const group = await node.createGroup();
expect(group).not.toBeNull();
const map = group.createMap();
map.set("foo", "bar", "private");
expect(map.get("foo")).toEqual("bar");
const peers1 = connectedPeers("node1", "node2", {
peer1role: "server",
peer2role: "client",
});
const account = await node.load(accountID);
if (account === "unavailable") throw new Error("Account unavailable");
account.set("root", map.id, "private");
node.syncManager.addPeer(peers1[1]);
const node2 = await LocalNode.withLoadedAccount({
accountID,
accountSecret,
sessionID: Crypto.newRandomSessionID(accountID),
peersToLoadFrom: [peers1[0]],
crypto: Crypto,
});
const account2 = await node2.load(accountID);
if (account2 === "unavailable") throw new Error("Account unavailable");
expect(account2.getRaw("root")?.trusting).toEqual(true);
node2.gracefulShutdown(); // Stop getting updates from node1
const peers2 = connectedPeers("node2", "node3", {
peer1role: "server",
peer2role: "client",
});
node.syncManager.addPeer(peers2[1]);
const node3 = await LocalNode.withLoadedAccount({
accountID,
accountSecret,
sessionID: Crypto.newRandomSessionID(accountID),
peersToLoadFrom: [peers2[0]],
crypto: Crypto,
});
const account3 = await node3.load(accountID);
if (account3 === "unavailable") throw new Error("Account unavailable");
expect(account3.getRaw("root")?.trusting).toEqual(true);
expect(account3.ops).toEqual(account2.ops); // No new transactions were made
});
test("throws an error if the user tried to create an invite from an account", async () => {
const { node, accountID } = await LocalNode.withNewlyCreatedAccount({
creationProps: { name: "Hermes Puggington" },

View File

@@ -1,5 +1,32 @@
# jazz-auth-betterauth
## 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
- Updated dependencies [c62abef]
- jazz-tools@0.16.1
- jazz-betterauth-client-plugin@0.16.1
- cojson@0.16.1
## 0.16.0
### Patch Changes
- Updated dependencies [c09dcdf]
- Updated dependencies [2bbb07b]
- jazz-tools@0.16.0
- cojson@0.16.0
- jazz-betterauth-client-plugin@0.16.0
## 0.15.16
### Patch Changes

View File

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

View File

@@ -1,5 +1,24 @@
# jazz-betterauth-client-plugin
## 0.16.2
### Patch Changes
- jazz-betterauth-server-plugin@0.16.2
## 0.16.1
### Patch Changes
- jazz-betterauth-server-plugin@0.16.1
## 0.16.0
### Patch Changes
- Updated dependencies [2bbb07b]
- jazz-betterauth-server-plugin@0.16.0
## 0.15.16
### Patch Changes

View File

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

View File

@@ -1,5 +1,38 @@
# jazz-betterauth-server-plugin
## 0.16.2
### Patch Changes
- cojson@0.16.2
- jazz-tools@0.16.2
## 0.16.1
### Patch Changes
- Updated dependencies [c62abef]
- jazz-tools@0.16.1
- cojson@0.16.1
## 0.16.0
### Patch Changes
- 2bbb07b: Introduce a cleaner separation between Zod and CoValue schemas:
- Zod schemas and CoValue schemas are fully separated. Zod schemas can only be composed with other Zod schemas. CoValue schemas can be composed with either Zod or other CoValue schemas.
- `z.optional()` and `z.discriminatedUnion()` no longer work with CoValue schemas. Use `co.optional()` and `co.discriminatedUnion()` instead.
- Internal schema access is now simpler. You no longer need to use Zods `.def` to access internals. Use properties like `CoMapSchema.shape`, `CoListSchema.element`, and `CoOptionalSchema.innerType` directly.
- CoValue schema types are now namespaced under `co.`. Non-namespaced exports have been removed
- CoMap schemas no longer incorrectly inherit from Zod. Previously, methods like `.extend()` and `.partial()` appeared available but could cause unexpected behavior. These methods are now disabled. In their place, `.optional()` has been added, and more Zod-like methods will be introduced in future releases.
- Upgraded Zod from `3.25.28` to `3.25.76`.
- Removed deprecated `withHelpers` method from CoValue schemas
- Removed deprecated `createCoValueObservable` function
- Updated dependencies [c09dcdf]
- Updated dependencies [2bbb07b]
- jazz-tools@0.16.0
- cojson@0.16.0
## 0.15.16
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-betterauth-server-plugin",
"version": "0.15.16",
"version": "0.16.2",
"type": "module",
"main": "dist/index.js",
"types": "src/index.ts",
@@ -10,7 +10,7 @@
"jazz-tools": "workspace:*",
"better-auth": "^1.2.4",
"better-sqlite3": "^11.9.1",
"zod": "3.25.28"
"zod": "3.25.76"
},
"scripts": {
"format-and-lint": "biome check .",
@@ -24,4 +24,4 @@
"typescript": "~5.6.2",
"@types/better-sqlite3": "^7.6.12"
}
}
}

View File

@@ -1,5 +1,35 @@
# jazz-react-auth-betterauth
## 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
- Updated dependencies [c62abef]
- jazz-tools@0.16.1
- jazz-auth-betterauth@0.16.1
- jazz-betterauth-client-plugin@0.16.1
- cojson@0.16.1
## 0.16.0
### Patch Changes
- Updated dependencies [c09dcdf]
- Updated dependencies [2bbb07b]
- jazz-tools@0.16.0
- cojson@0.16.0
- jazz-auth-betterauth@0.16.0
- jazz-betterauth-client-plugin@0.16.0
## 0.15.16
### Patch Changes

View File

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

View File

@@ -1,5 +1,45 @@
# jazz-run
## 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
- Updated dependencies [c62abef]
- jazz-tools@0.16.1
- cojson@0.16.1
- cojson-storage-sqlite@0.16.1
- cojson-transport-ws@0.16.1
## 0.16.0
### Patch Changes
- 2bbb07b: Introduce a cleaner separation between Zod and CoValue schemas:
- Zod schemas and CoValue schemas are fully separated. Zod schemas can only be composed with other Zod schemas. CoValue schemas can be composed with either Zod or other CoValue schemas.
- `z.optional()` and `z.discriminatedUnion()` no longer work with CoValue schemas. Use `co.optional()` and `co.discriminatedUnion()` instead.
- Internal schema access is now simpler. You no longer need to use Zods `.def` to access internals. Use properties like `CoMapSchema.shape`, `CoListSchema.element`, and `CoOptionalSchema.innerType` directly.
- CoValue schema types are now namespaced under `co.`. Non-namespaced exports have been removed
- CoMap schemas no longer incorrectly inherit from Zod. Previously, methods like `.extend()` and `.partial()` appeared available but could cause unexpected behavior. These methods are now disabled. In their place, `.optional()` has been added, and more Zod-like methods will be introduced in future releases.
- Upgraded Zod from `3.25.28` to `3.25.76`.
- Removed deprecated `withHelpers` method from CoValue schemas
- Removed deprecated `createCoValueObservable` function
- Updated dependencies [c09dcdf]
- Updated dependencies [2bbb07b]
- jazz-tools@0.16.0
- cojson@0.16.0
- cojson-storage-sqlite@0.16.0
- cojson-transport-ws@0.16.0
## 0.15.16
### Patch Changes

View File

@@ -3,15 +3,15 @@
"bin": "./dist/index.js",
"type": "module",
"license": "MIT",
"version": "0.15.16",
"version": "0.16.2",
"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.15.16",
"cojson-storage-sqlite": "workspace:0.15.16",
"cojson-transport-ws": "workspace:0.15.16",
"cojson": "workspace:0.16.2",
"cojson-storage-sqlite": "workspace:0.16.2",
"cojson-transport-ws": "workspace:0.16.2",
"effect": "^3.6.5",
"jazz-tools": "workspace:0.15.16",
"jazz-tools": "workspace:0.16.2",
"ws": "^8.14.2"
},
"devDependencies": {

View File

@@ -5,15 +5,12 @@ import { join } from "node:path";
import {
Account,
AccountClass,
AccountSchema,
AnyAccountSchema,
CoMap,
CoValueFromRaw,
Group,
InboxSender,
Loaded,
co,
coField,
z,
} from "jazz-tools";
import { startWorker } from "jazz-tools/worker";

View File

@@ -1,5 +1,47 @@
# jazz-tools
## 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
- c62abef: Add support for nullable non-collaborative fields
- cojson@0.16.1
- cojson-storage-indexeddb@0.16.1
- cojson-transport-ws@0.16.1
## 0.16.0
### Minor Changes
- c09dcdf: Change the root attribute to be public on Account. The root content will still follow the visiblity rules specified in their group.
Existing accounts will be gradually migrated as they are loaded.
- 2bbb07b: Introduce a cleaner separation between Zod and CoValue schemas:
- Zod schemas and CoValue schemas are fully separated. Zod schemas can only be composed with other Zod schemas. CoValue schemas can be composed with either Zod or other CoValue schemas.
- `z.optional()` and `z.discriminatedUnion()` no longer work with CoValue schemas. Use `co.optional()` and `co.discriminatedUnion()` instead.
- Internal schema access is now simpler. You no longer need to use Zods `.def` to access internals. Use properties like `CoMapSchema.shape`, `CoListSchema.element`, and `CoOptionalSchema.innerType` directly.
- CoValue schema types are now namespaced under `co.`. Non-namespaced exports have been removed
- CoMap schemas no longer incorrectly inherit from Zod. Previously, methods like `.extend()` and `.partial()` appeared available but could cause unexpected behavior. These methods are now disabled. In their place, `.optional()` has been added, and more Zod-like methods will be introduced in future releases.
- Upgraded Zod from `3.25.28` to `3.25.76`.
- Removed deprecated `withHelpers` method from CoValue schemas
- Removed deprecated `createCoValueObservable` function
### Patch Changes
- Updated dependencies [c09dcdf]
- cojson@0.16.0
- cojson-storage-indexeddb@0.16.0
- cojson-transport-ws@0.16.0
## 0.15.16
### Patch Changes

View File

@@ -139,7 +139,7 @@
},
"type": "module",
"license": "MIT",
"version": "0.15.16",
"version": "0.16.2",
"dependencies": {
"@manuscripts/prosemirror-recreate-steps": "^0.1.4",
"@scure/base": "1.2.1",
@@ -160,7 +160,7 @@
"prosemirror-schema-basic": "^1.2.2",
"prosemirror-state": "^1.4.3",
"prosemirror-transform": "^1.9.0",
"zod": "3.25.28"
"zod": "3.25.76"
},
"scripts": {
"check": "tsc --noEmit",

View File

@@ -17,7 +17,7 @@ export async function createImage(
owner?: Group | Account;
maxSize?: 256 | 1024 | 2048;
},
): Promise<Loaded<typeof ImageDefinition>> {
): Promise<Loaded<typeof ImageDefinition, { $each: true }>> {
// Get the original size of the image
const { width: originalWidth, height: originalHeight } =
await getImageSize(imageBlobOrFile);

View File

@@ -1,9 +1,7 @@
import {
Account,
CoValue,
CoValueClass,
CoValueOrZodSchema,
ID,
CoValueClassOrSchema,
InviteSecret,
createInviteLink as baseCreateInviteLink,
consumeInviteLink,
@@ -36,7 +34,7 @@ export { parseInviteLink } from "jazz-tools";
/** @category Invite Links */
export async function consumeInviteLinkFromWindowLocation<
S extends CoValueOrZodSchema,
S extends CoValueClassOrSchema,
>({
as,
forValueHint,

View File

@@ -11,7 +11,7 @@ import {
AnonymousJazzAgent,
AnyAccountSchema,
CoValue,
CoValueOrZodSchema,
CoValueClassOrSchema,
InboxSender,
InstanceOfSchema,
JazzContextManager,
@@ -20,7 +20,7 @@ import {
ResolveQuery,
ResolveQueryStrict,
SubscriptionScope,
anySchemaToCoSchema,
coValueClassFromCoValueClassOrSchema,
} from "jazz-tools";
import { JazzContext, JazzContextManagerContext } from "./provider.js";
import { getCurrentAccountFromContextManager } from "./utils.js";
@@ -80,7 +80,7 @@ export function useIsAuthenticated() {
}
function useCoValueSubscription<
S extends CoValueOrZodSchema,
S extends CoValueClassOrSchema,
const R extends ResolveQuery<S>,
>(
Schema: S,
@@ -107,7 +107,7 @@ function useCoValueSubscription<
options?.resolve ?? true,
id,
{
ref: anySchemaToCoSchema(Schema),
ref: coValueClassFromCoValueClassOrSchema(Schema),
optional: true,
},
);
@@ -142,7 +142,7 @@ function useCoValueSubscription<
}
export function useCoState<
S extends CoValueOrZodSchema,
S extends CoValueClassOrSchema,
const R extends ResolveQuery<S> = true,
>(
Schema: S,
@@ -198,7 +198,7 @@ function useAccountSubscription<
const node = contextManager.getCurrentValue()!.node;
const subscription = new SubscriptionScope<any>(node, resolve, agent.id, {
ref: anySchemaToCoSchema(Schema),
ref: coValueClassFromCoValueClassOrSchema(Schema),
optional: true,
});

View File

@@ -1,6 +1,6 @@
// @vitest-environment happy-dom
import { RefsToResolve, co, z, zodSchemaToCoSchema } from "jazz-tools";
import { RefsToResolve, co, z } from "jazz-tools";
import { beforeEach, describe, expect, it } from "vitest";
import { useAccount, useJazzContextManager } from "../hooks.js";
import { useIsAuthenticated } from "../index.js";
@@ -46,7 +46,7 @@ describe("useAccount", () => {
});
const account = await createJazzTestAccount({
AccountSchema: zodSchemaToCoSchema(AccountSchema),
AccountSchema,
});
const { result } = renderHook(

View File

@@ -520,12 +520,13 @@ describe("useCoState", () => {
const Message = co.map({
content: CoRichText,
});
const Messages = co.list(Message);
const Thread = co.map({
messages: co.list(Message),
messages: Messages,
});
const thread = Thread.create({
messages: Thread.def.shape.messages.create([
messages: Messages.create([
Message.create({
content: CoRichText.create("Hello man!"),
}),

View File

@@ -1,6 +1,6 @@
import { useEffect } from "react";
import { CoValueOrZodSchema, parseInviteLink } from "jazz-tools";
import { CoValueClassOrSchema, parseInviteLink } from "jazz-tools";
import { useJazzContext } from "jazz-tools/react-core";
import { Linking } from "react-native";
@@ -15,7 +15,7 @@ export {
useAccount,
} from "jazz-tools/react-core";
export function useAcceptInviteNative<S extends CoValueOrZodSchema>({
export function useAcceptInviteNative<S extends CoValueClassOrSchema>({
invitedObjectSchema,
onAccept,
forValueHint,

View File

@@ -1,12 +1,12 @@
import { consumeInviteLinkFromWindowLocation } from "jazz-tools/browser";
import { useEffect } from "react";
import { CoValueOrZodSchema } from "jazz-tools";
import { CoValueClassOrSchema } from "jazz-tools";
import { useJazzContext } from "jazz-tools/react-core";
export { useCoState, useAuthSecretStorage } from "jazz-tools/react-core";
export function useAcceptInvite<S extends CoValueOrZodSchema>({
export function useAcceptInvite<S extends CoValueClassOrSchema>({
invitedObjectSchema,
onAccept,
forValueHint,

View File

@@ -2,21 +2,24 @@ import type {
Account,
AccountClass,
AnyAccountSchema,
CoValueClassOrSchema,
CoValueFromRaw,
CoValueOrZodSchema,
InstanceOfSchema,
Loaded,
ResolveQuery,
ResolveQueryStrict,
} from "jazz-tools";
import { anySchemaToCoSchema, subscribeToCoValue } from "jazz-tools";
import {
coValueClassFromCoValueClassOrSchema,
subscribeToCoValue,
} from "jazz-tools";
import { untrack } from "svelte";
import { createSubscriber } from "svelte/reactivity";
import { useIsAuthenticated } from "./auth/useIsAuthenticated.svelte.js";
import { getJazzContext } from "./jazz.svelte";
export class CoState<
V extends CoValueOrZodSchema,
V extends CoValueClassOrSchema,
R extends ResolveQuery<V> = true,
> {
#value: Loaded<V, R> | undefined | null = undefined;
@@ -47,10 +50,10 @@ export class CoState<
const agent = "me" in ctx ? ctx.me : ctx.guest;
const unsubscribe = subscribeToCoValue(
anySchemaToCoSchema(Schema),
coValueClassFromCoValueClassOrSchema(Schema),
id,
{
// @ts-expect-error The resolve query type isn't compatible with the anySchemaToCoSchema conversion
// @ts-expect-error The resolve query type isn't compatible with the coValueClassFromCoValueClassOrSchema conversion
resolve: options?.resolve,
loadAs: agent,
onUnavailable: () => {
@@ -112,10 +115,10 @@ export class AccountCoState<
const me = ctx.me;
const unsubscribe = subscribeToCoValue(
anySchemaToCoSchema(Schema),
coValueClassFromCoValueClassOrSchema(Schema),
me.id,
{
// @ts-expect-error The resolve query type isn't compatible with the anySchemaToCoSchema conversion
// @ts-expect-error The resolve query type isn't compatible with the coValueClassFromCoValueClassOrSchema conversion
resolve: options?.resolve,
loadAs: me,
onUnavailable: () => {

View File

@@ -1,7 +1,7 @@
import type {
AccountClass,
AuthSecretStorage,
CoValueOrZodSchema,
CoValueClassOrSchema,
ID,
InstanceOfSchema,
JazzContextType,
@@ -66,7 +66,7 @@ export function getAuthSecretStorage() {
* @param forValueHint - Hint for the value.
* @returns The accept invite hook.
*/
export class InviteListener<V extends CoValueOrZodSchema> {
export class InviteListener<V extends CoValueClassOrSchema> {
constructor({
invitedObjectSchema,
onAccept,

View File

@@ -1,5 +1,5 @@
import { ControlledAccount, RawAccount, type RawCoValue } from "cojson";
import { z } from "../implementation/zodSchema/zodReExport.js";
import { CoreCoValueSchema } from "../implementation/zodSchema/schemaTypes/CoValueSchema.js";
import {
AnonymousJazzAgent,
CoValue,
@@ -8,13 +8,14 @@ import {
ID,
RegisteredSchemas,
accessChildById,
anySchemaToCoSchema,
coValueClassFromCoValueClassOrSchema,
coValuesCache,
inspect,
isCoValueSchema,
} from "../internal.js";
import type {
Account,
CoValueClassOrSchema,
Group,
InstanceOfSchemaCoValuesNullable,
} from "../internal.js";
@@ -46,9 +47,9 @@ export class CoValueBase implements CoValue {
if (agent instanceof ControlledAccount) {
return coValuesCache.get(agent.account, () =>
anySchemaToCoSchema(RegisteredSchemas["Account"]).fromRaw(
agent.account,
),
coValueClassFromCoValueClassOrSchema(
RegisteredSchemas["Account"],
).fromRaw(agent.account),
);
}
@@ -81,24 +82,11 @@ export class CoValueBase implements CoValue {
}
/** @category Type Helpers */
castAs<
S extends
| CoValueClass
| z.core.$ZodType
| (z.core.$ZodObject<any, any> & {
builtin: "Account";
migration?: (account: any, creationProps?: { name: string }) => void;
})
| (z.core.$ZodCustom<any, any> & { builtin: "FileStream" })
| (z.core.$ZodCustom<any, any> & {
builtin: "CoFeed";
element: z.core.$ZodType;
}),
>(
castAs<S extends CoValueClassOrSchema>(
schema: S,
): S extends CoValueClass
? InstanceType<S>
: S extends z.core.$ZodType
: S extends CoreCoValueSchema
? NonNullable<InstanceOfSchemaCoValuesNullable<S>>
: never {
const cl = isCoValueSchema(schema) ? schema.getCoValueClass() : schema;

View File

@@ -16,12 +16,12 @@ import {
} from "cojson";
import {
AnonymousJazzAgent,
AnyAccountSchema,
type CoMap,
type CoValue,
CoValueBase,
CoValueClass,
CoValueOrZodSchema,
CoValueClassOrSchema,
CoreAccountSchema,
type Group,
ID,
InstanceOfSchema,
@@ -40,7 +40,7 @@ import {
SubscribeRestArgs,
accessChildByKey,
activeAccountContext,
anySchemaToCoSchema,
coValueClassFromCoValueClassOrSchema,
coValuesCache,
createInboxRoot,
ensureCoValueLoaded,
@@ -254,7 +254,7 @@ export class Account extends CoValueBase implements CoValue {
return value._owner.getRoleOf(this.id) === "admin";
}
async acceptInvite<S extends CoValueOrZodSchema>(
async acceptInvite<S extends CoValueClassOrSchema>(
valueID: string,
inviteSecret: InviteSecret,
coValueClass: S,
@@ -268,9 +268,13 @@ export class Account extends CoValueBase implements CoValue {
inviteSecret,
);
return loadCoValue(anySchemaToCoSchema(coValueClass), valueID, {
loadAs: this,
}) as Resolved<InstanceOrPrimitiveOfSchema<S>, true> | null;
return loadCoValue(
coValueClassFromCoValueClassOrSchema(coValueClass),
valueID,
{
loadAs: this,
},
) as Resolved<InstanceOrPrimitiveOfSchema<S>, true> | null;
}
/** @private */
@@ -447,9 +451,7 @@ export class Account extends CoValueBase implements CoValue {
*
* @category Subscription & Loading
*/
waitForSync(options?: {
timeout?: number;
}) {
waitForSync(options?: { timeout?: number }) {
return this._raw.core.waitForSync(options);
}
@@ -458,9 +460,7 @@ export class Account extends CoValueBase implements CoValue {
*
* @category Subscription & Loading
*/
waitForAllCoValuesSync(options?: {
timeout?: number;
}) {
waitForAllCoValuesSync(options?: { timeout?: number }) {
return this._raw.core.node.syncManager.waitForAllCoValuesSync(
options?.timeout,
);
@@ -502,7 +502,11 @@ export const AccountAndGroupProxyHandler: ProxyHandler<Account | Group> = {
return true;
} else if (key === "root") {
if (value) {
target._raw.set("root", value.id as unknown as CoID<RawCoMap>);
target._raw.set(
"root",
value.id as unknown as CoID<RawCoMap>,
"trusting",
);
}
return true;
} else {

View File

@@ -12,12 +12,10 @@ import type {
import { MAX_RECOMMENDED_TX_SIZE, cojsonInternals } from "cojson";
import type {
AnonymousJazzAgent,
AnyAccountSchema,
CoValue,
CoValueClass,
Group,
ID,
InstanceOfSchema,
RefsToResolve,
RefsToResolveStrict,
Resolved,
@@ -31,10 +29,8 @@ import {
CoValueBase,
ItemsSym,
Ref,
RegisteredSchemas,
SchemaInit,
accessChildById,
anySchemaToCoSchema,
coField,
ensureCoValueLoaded,
inspect,

View File

@@ -3,13 +3,11 @@ import { ControlledAccount, RawAccount } from "cojson";
import { calcPatch } from "fast-myers-diff";
import type {
Account,
AnyAccountSchema,
CoValue,
CoValueClass,
CoValueFromRaw,
Group,
ID,
InstanceOfSchema,
RefEncoded,
RefsToResolve,
RefsToResolveStrict,
@@ -26,8 +24,8 @@ import {
RegisteredSchemas,
SchemaInit,
accessChildByKey,
anySchemaToCoSchema,
coField,
coValueClassFromCoValueClassOrSchema,
coValuesCache,
ensureCoValueLoaded,
inspect,
@@ -117,9 +115,9 @@ export class CoList<out Item = any> extends Array<Item> implements CoValue {
/** @category Collaboration */
get _owner(): Account | Group {
return this._raw.group instanceof RawAccount
? anySchemaToCoSchema(RegisteredSchemas["Account"]).fromRaw(
this._raw.group,
)
? coValueClassFromCoValueClassOrSchema(
RegisteredSchemas["Account"],
).fromRaw(this._raw.group)
: RegisteredSchemas["Group"].fromRaw(this._raw.group);
}
@@ -175,9 +173,9 @@ export class CoList<out Item = any> extends Array<Item> implements CoValue {
if (agent instanceof ControlledAccount) {
return coValuesCache.get(agent.account, () =>
anySchemaToCoSchema(RegisteredSchemas["Account"]).fromRaw(
agent.account,
),
coValueClassFromCoValueClassOrSchema(
RegisteredSchemas["Account"],
).fromRaw(agent.account),
);
}

View File

@@ -14,12 +14,14 @@ import type {
CoValueClass,
Group,
ID,
PartialOnUndefined,
RefEncoded,
RefIfCoValue,
RefsToResolve,
RefsToResolveStrict,
Resolved,
Schema,
Simplify,
SubscribeListenerOptions,
SubscribeRestArgs,
} from "../internal.js";
@@ -54,12 +56,6 @@ type CoMapEdit<V> = {
type LastAndAllCoMapEdits<V> = CoMapEdit<V> & { all: CoMapEdit<V>[] };
export type Simplify<A> = {
[K in keyof A]: A[K];
} extends infer B
? B
: never;
/**
* CoMaps are collaborative versions of plain objects, mapping string-like keys to values.
*
@@ -783,13 +779,9 @@ type ForceRequiredRef<V> = V extends InstanceType<CoValueClass> | null
? V | null
: V;
export type CoMapInit<Map extends object> = {
[Key in CoKeys<Map> as undefined extends Map[Key]
? never
: Key]: ForceRequiredRef<Map[Key]>;
} & {
[Key in CoKeys<Map>]?: ForceRequiredRef<Map[Key]>;
};
export type CoMapInit<Map extends object> = PartialOnUndefined<{
[Key in CoKeys<Map>]: ForceRequiredRef<Map[Key]>;
}>;
// TODO: cache handlers per descriptor for performance?
const CoMapProxyHandler: ProxyHandler<CoMap> = {

View File

@@ -14,7 +14,7 @@ import {
Resolved,
SubscribeListenerOptions,
SubscribeRestArgs,
anySchemaToCoSchema,
coValueClassFromCoValueClassOrSchema,
parseCoValueCreateOptions,
} from "../internal.js";
import {
@@ -47,9 +47,9 @@ export class CoPlainText extends String implements CoValue {
if (agent instanceof ControlledAccount) {
return coValuesCache.get(agent.account, () =>
anySchemaToCoSchema(RegisteredSchemas["Account"]).fromRaw(
agent.account,
),
coValueClassFromCoValueClassOrSchema(
RegisteredSchemas["Account"],
).fromRaw(agent.account),
);
}

View File

@@ -5,7 +5,10 @@ import { CoFeedEntry } from "./coFeed.js";
import { type CoKeys } from "./coMap.js";
import { type CoValue, type ID } from "./interfaces.js";
type NotNull<T> = Exclude<T, null>;
/**
* Similar to {@link NonNullable}, but removes only `null` and preserves `undefined`.
*/
export type NotNull<T> = Exclude<T, null>;
/**
* Used to check if T is a union type.

View File

@@ -8,9 +8,9 @@ const ImageDefinitionBase = coMapDefiner({
}).catchall(coFileStreamDefiner());
/** @category Media */
export const ImageDefinition = ImageDefinitionBase.withHelpers((Self) => ({
export const ImageDefinition = Object.assign({}, ImageDefinitionBase, {
highestResAvailable(
imageDef: Loaded<typeof Self>,
imageDef: ImageDefinition,
options?: {
maxWidth?: number;
targetWidth?: number;
@@ -56,5 +56,5 @@ export const ImageDefinition = ImageDefinitionBase.withHelpers((Self) => ({
}
);
},
}));
});
export type ImageDefinition = Loaded<typeof ImageDefinition>;

View File

@@ -3,11 +3,11 @@ import { CoStreamItem, RawCoStream } from "cojson";
import {
type Account,
CoValue,
CoValueOrZodSchema,
CoValueClassOrSchema,
ID,
InstanceOfSchema,
activeAccountContext,
anySchemaToCoSchema,
coValueClassFromCoValueClassOrSchema,
loadCoValue,
} from "../internal.js";
import { isCoValueId } from "../lib/id.js";
@@ -109,7 +109,7 @@ export class Inbox {
this.failed = failed;
}
subscribe<M extends CoValueOrZodSchema, O extends CoValue | undefined>(
subscribe<M extends CoValueClassOrSchema, O extends CoValue | undefined>(
Schema: M,
callback: (
message: InstanceOfSchema<M>,
@@ -172,7 +172,7 @@ export class Inbox {
}
return loadCoValue(
anySchemaToCoSchema(Schema),
coValueClassFromCoValueClassOrSchema(Schema),
message.get("payload")!,
{
loadAs: account,

View File

@@ -1,5 +1,4 @@
import {
CoValueCore,
type CoValueUniqueness,
type CojsonInternalTypes,
type RawCoValue,
@@ -9,7 +8,7 @@ import { AvailableCoValueCore } from "cojson/dist/coValueCore/coValueCore.js";
import {
Account,
AnonymousJazzAgent,
CoValueOrZodSchema,
CoValueClassOrSchema,
type Group,
Loaded,
RefsToResolve,
@@ -21,8 +20,7 @@ import {
SubscriptionScope,
type SubscriptionValue,
activeAccountContext,
anySchemaToCoSchema,
getSubscriptionScope,
coValueClassFromCoValueClassOrSchema,
inspect,
} from "../internal.js";
@@ -312,71 +310,6 @@ export function subscribeToCoValue<
return unsubscribe;
}
/**
* @deprecated Used for the React integration in the past, but we moved to use SubscriptionScope directly.
*
* Going to be removed in the next minor version.
*/
export function createCoValueObservable<
S extends CoValueOrZodSchema,
const R extends ResolveQuery<S>,
>(initialValue: undefined | null = undefined) {
let currentValue: Loaded<S, R> | undefined | null = initialValue;
let subscriberCount = 0;
function subscribe(
cls: S,
id: string,
options: {
loadAs: Account | AnonymousJazzAgent;
resolve?: ResolveQueryStrict<S, R>;
onUnavailable?: () => void;
onUnauthorized?: () => void;
syncResolution?: boolean;
},
listener: () => void,
) {
subscriberCount++;
const unsubscribe = subscribeToCoValue(
anySchemaToCoSchema(cls),
id,
{
loadAs: options.loadAs,
resolve: options.resolve as any,
onUnavailable: () => {
currentValue = null;
options.onUnavailable?.();
},
onUnauthorized: () => {
currentValue = null;
options.onUnauthorized?.();
},
syncResolution: options.syncResolution,
},
(value) => {
currentValue = value as Loaded<S, R>;
listener();
},
);
return () => {
unsubscribe();
subscriberCount--;
if (subscriberCount === 0) {
currentValue = undefined;
}
};
}
const observable = {
getCurrentValue: () => currentValue,
subscribe,
};
return observable;
}
export function subscribeToExistingCoValue<
V extends CoValue,
const R extends RefsToResolve<V>,
@@ -515,7 +448,7 @@ export function parseGroupCreateOptions(
* ```
*/
export async function exportCoValue<
S extends CoValueOrZodSchema | CoValueClass<CoValue>,
S extends CoValueClassOrSchema,
const R extends ResolveQuery<S>,
>(
cls: S,
@@ -537,7 +470,7 @@ export async function exportCoValue<
resolve as any,
id,
{
ref: anySchemaToCoSchema(cls),
ref: coValueClassFromCoValueClassOrSchema(cls),
optional: false,
},
options.skipRetry,

View File

@@ -8,19 +8,19 @@ import {
} from "cojson";
import z from "zod/v4";
import {
AnyCoMapSchema,
AnyCoSchema,
AnyZodOrCoValueSchema,
CoMap,
CoMapInitZod,
CoMapSchema,
CoMapSchemaInit,
CoValueClass,
CoreCoMapSchema,
Group,
Loaded,
ResolveQuery,
ResolveQueryStrict,
Simplify,
anySchemaToCoSchema,
coMapDefiner,
coValueClassFromCoValueClassOrSchema,
exportCoValue,
importContentPieces,
loadCoValue,
@@ -28,7 +28,7 @@ import {
import { isCoValueId } from "../lib/id.js";
import { Account } from "./account.js";
type MessageShape = Record<string, z.core.$ZodType | AnyCoSchema>;
type MessageShape = Record<string, AnyZodOrCoValueSchema>;
type RequestSchemaDefinition<
S extends MessageShape,
@@ -93,11 +93,11 @@ type AsNullablePayload<T extends MessageShape> = T extends Record<string, never>
? undefined
: never;
type MessageValuePayload<T extends MessageShape> =
| Simplify<CoMapInitZod<T>>
| Simplify<CoMapSchemaInit<T>>
| AsNullablePayload<T>;
function createMessageEnvelope<S extends MessageShape>(
schema: AnyCoMapSchema,
schema: CoreCoMapSchema,
value: MessageValuePayload<S>,
owner: Account,
sharedWith: Account | Group,
@@ -111,7 +111,7 @@ function createMessageEnvelope<S extends MessageShape>(
group.addMember(sharedWith, "reader");
}
// @ts-expect-error - AnyCoMapSchema doesn't have static methods
// @ts-expect-error - CoreCoMapSchema doesn't have static methods
return schema.create(value ?? {}, group);
}
@@ -129,7 +129,7 @@ async function serializeMessagePayload({
}: {
// Skipping type validation here to avoid excessive type complexity that affects the typecheck performance
type: "request" | "response";
schema: AnyCoMapSchema;
schema: CoreCoMapSchema;
resolve: any;
value: any;
owner: Account;
@@ -198,7 +198,7 @@ async function handleMessagePayload({
}: {
type: "request" | "response";
// Skipping type validation here to avoid excessive type complexity that affects the typecheck performance
schema: AnyCoMapSchema;
schema: CoreCoMapSchema;
resolve: any;
request: unknown;
loadAs: Account;
@@ -290,7 +290,9 @@ async function handleMessagePayload({
throw new JazzRequestError("Creator account not found", 400);
}
const coSchema = anySchemaToCoSchema(schema) as CoValueClass<CoMap>;
const coSchema = coValueClassFromCoValueClassOrSchema(
schema,
) as CoValueClass<CoMap>;
const value = await loadCoValue<CoMap, true>(coSchema, requestData.id, {
resolve,
loadAs,
@@ -317,13 +319,13 @@ function parseSchemaAndResolve<
if ("schema" in options) {
return {
// Using a type cast to reduce the type complexity
schema: coMapDefiner(options.schema) as AnyCoMapSchema,
schema: coMapDefiner(options.schema) as CoreCoMapSchema,
resolve: options.resolve as any,
};
}
return {
schema: coMapDefiner(options) as AnyCoMapSchema,
schema: coMapDefiner(options) as CoreCoMapSchema,
resolve: true as any,
};
}
@@ -335,11 +337,11 @@ class HttpRoute<
ResponseResolve extends ResolveQuery<CoMapSchema<ResponseShape>> = any,
> {
private requestDefinition: {
schema: AnyCoMapSchema;
schema: CoreCoMapSchema;
resolve: any;
};
private responseDefinition: {
schema: AnyCoMapSchema;
schema: CoreCoMapSchema;
resolve: any;
};
private url: string;

View File

@@ -6,8 +6,6 @@ import {
CoValueClass,
CoValueFromRaw,
ID,
RefsToResolve,
RefsToResolveStrict,
Resolved,
SubscribeListenerOptions,
SubscribeRestArgs,
@@ -16,6 +14,12 @@ import {
subscribeToCoValueWithoutMe,
} from "../internal.js";
/**
* Extends `SchemaUnion` with a non-abstract constructor.
*/
export type SchemaUnionConcreteSubclass<V extends CoValue> =
typeof SchemaUnion & CoValueClass<V>;
/**
* SchemaUnion allows you to create union types of CoValues that can be discriminated at runtime.
*
@@ -86,7 +90,7 @@ export abstract class SchemaUnion extends CoValueBase implements CoValue {
**/
static Of<V extends CoValue>(
discriminator: (raw: V["_raw"]) => CoValueClass<V> & CoValueFromRaw<V>,
): CoValueClass<V> & typeof SchemaUnion {
): SchemaUnionConcreteSubclass<V> {
return class SchemaUnionClass extends SchemaUnion {
static override fromRaw<T extends CoValue>(
this: CoValueClass<T> & CoValueFromRaw<T>,
@@ -97,7 +101,7 @@ export abstract class SchemaUnion extends CoValueBase implements CoValue {
) as unknown as CoValueClass<T> & CoValueFromRaw<T>;
return ResolvedClass.fromRaw(raw);
}
} as unknown as CoValueClass<V> & typeof SchemaUnion;
} as unknown as SchemaUnionConcreteSubclass<V>;
}
/**

View File

@@ -35,7 +35,6 @@ export type {
TextPos,
AccountClass,
AccountCreationProps,
WithHelpers,
} from "./internal.js";
export {
@@ -49,7 +48,6 @@ export {
CoRichText,
Account,
isControlledAccount,
createCoValueObservable,
loadCoValue,
subscribeToCoValue,
ImageDefinition,
@@ -99,26 +97,17 @@ export {
export type * from "./types.js";
export {
zodSchemaToCoSchema,
anySchemaToCoSchema,
coValueClassFromCoValueClassOrSchema,
type InstanceOfSchema,
type InstanceOfSchemaCoValuesNullable,
type CoValueOrZodSchema,
type CoValueClassOrSchema,
type Loaded,
type BaseAccountShape,
type DefaultAccountShape,
type AccountSchema,
type AnyAccountSchema,
type CoListSchema,
type CoMapSchema,
type CoFeedSchema,
type PlainTextSchema,
type FileStreamSchema,
type CoreAccountSchema as AnyAccountSchema,
type ResolveQuery,
type ResolveQueryStrict,
type InitFor,
type CoRecordSchema,
type CoProfileSchema,
} from "./internal.js";
export {

View File

@@ -1,7 +1,6 @@
import {
AgentSecret,
CoID,
ControlledAgent,
CryptoProvider,
LocalNode,
Peer,
@@ -14,12 +13,11 @@ import { AuthSecretStorage } from "../auth/AuthSecretStorage.js";
import { type Account, type AccountClass } from "../coValues/account.js";
import { RegisteredSchemas } from "../coValues/registeredSchemas.js";
import {
type AccountSchema,
type AnyAccountSchema,
CoValueFromRaw,
type CoreAccountSchema,
type ID,
type InstanceOfSchema,
anySchemaToCoSchema,
coValueClassFromCoValueClassOrSchema,
} from "../internal.js";
import { AuthCredentials, NewAccountProps } from "../types.js";
import { activeAccountContext } from "./activeAccountContext.js";
@@ -89,7 +87,7 @@ export type JazzContext<Acc extends Account> =
export async function createJazzContextFromExistingCredentials<
S extends
| (AccountClass<Account> & CoValueFromRaw<Account>)
| AnyAccountSchema,
| CoreAccountSchema,
>({
credentials,
peersToLoadFrom,
@@ -115,7 +113,8 @@ export async function createJazzContextFromExistingCredentials<
const CurrentAccountSchema =
PropsAccountSchema ?? (RegisteredSchemas["Account"] as unknown as S);
const AccountClass = anySchemaToCoSchema(CurrentAccountSchema);
const AccountClass =
coValueClassFromCoValueClassOrSchema(CurrentAccountSchema);
const node = await LocalNode.withLoadedAccount({
accountID: credentials.accountID as unknown as CoID<RawAccount>,
@@ -153,7 +152,7 @@ export async function createJazzContextFromExistingCredentials<
export async function createJazzContextForNewAccount<
S extends
| (AccountClass<Account> & CoValueFromRaw<Account>)
| AnyAccountSchema,
| CoreAccountSchema,
>({
creationProps,
initialAgentSecret,
@@ -174,7 +173,8 @@ export async function createJazzContextForNewAccount<
const CurrentAccountSchema =
PropsAccountSchema ?? (RegisteredSchemas["Account"] as unknown as S);
const AccountClass = anySchemaToCoSchema(CurrentAccountSchema);
const AccountClass =
coValueClassFromCoValueClassOrSchema(CurrentAccountSchema);
const { node } = await LocalNode.withNewlyCreatedAccount({
creationProps,
@@ -209,7 +209,7 @@ export async function createJazzContextForNewAccount<
export async function createJazzContext<
S extends
| (AccountClass<Account> & CoValueFromRaw<Account>)
| AnyAccountSchema,
| CoreAccountSchema,
>(options: {
credentials?: AuthCredentials;
newAccountProps?: NewAccountProps;

View File

@@ -3,7 +3,7 @@ import { Account } from "../coValues/account.js";
import type {
CoValue,
CoValueClass,
CoValueOrZodSchema,
CoValueClassOrSchema,
ID,
} from "../internal.js";
@@ -68,7 +68,7 @@ export function parseInviteLink(inviteURL: string):
}
/** @category Invite Links */
export function consumeInviteLink<S extends CoValueOrZodSchema>({
export function consumeInviteLink<S extends CoValueClassOrSchema>({
inviteURL,
as = Account.getMe(),
forValueHint,

Some files were not shown because too many files have changed in this diff Show More