Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,522 changes: 1,110 additions & 1,412 deletions package-lock.json

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"scripts": {
"dev": "tsx scripts/dev.ts",
"dev:db": "tsx scripts/dev-db.ts",
"dev:zero": "npx zero-cache",
"dev:zero": "npx zero-cache-dev",
"dev:yjs": "HOST=localhost PORT=1234 npx y-websocket",
"dev:ui": "tsx scripts/dev-ui.ts",
"dev:clean": "tsx scripts/clean.ts",
Expand All @@ -23,7 +23,7 @@
"@milkdown/plugin-collab": "^7.13.1",
"@milkdown/react": "^7.3.6",
"@milkdown/theme-nord": "^7.3.6",
"@rocicorp/zero": "file:../mono/packages/zero/rocicorp-zero-0.21.2025061100.tgz",
"@rocicorp/zero": "0.24.2025101500",
"@y/websocket-server": "^0.1.1",
"better-auth": "^1.2.9",
"chokidar-cli": "^3.0.0",
Expand All @@ -42,7 +42,8 @@
"react-dom": "^18.2.0",
"wait-on": "^8.0.3",
"y-websocket": "^3.0.0",
"yjs": "^13.6.27"
"yjs": "^13.6.27",
"zod": "^4.1.12"
},
"devDependencies": {
"@types/express": "^5.0.3",
Expand Down
8 changes: 1 addition & 7 deletions src/LoggedIn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,8 @@ import {id, UserId, WorkspaceId} from '../zero/schema';

const workspaceId: WorkspaceId = id('1');
export function LoggedIn({userId}: {userId: UserId}) {
const z = useSyncExternalStore(
zeroAtom.onChange,
useCallback(() => zeroAtom.value, []),
);
if (!z) return null;

return (
<ZeroProvider zero={z}>
<ZeroProvider mutators={createMutators()}>
<App workspaceId={workspaceId} userId={userId} />
</ZeroProvider>
);
Expand Down
8 changes: 4 additions & 4 deletions src/components/Editor.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React, {useRef} from 'react';
import {Menu, Share, MoreHorizontal, Star} from 'lucide-react';
import {MilkdownEditor} from './MilkdownEditor';
import {DocId, User} from '../../zero/schema';
import {DocId, Schema, User} from '../../zero/schema';
import {useQuery, useZero} from '@rocicorp/zero/react';
import {doc} from '../../zero/queries';
import {useCachedProp} from '../hooks/cachedProp';
import {updateDoc} from '../../zero/mutators';
import {Mutators} from 'zero/mutators';

interface EditorProps {
documentId: DocId;
Expand All @@ -22,7 +22,7 @@ const Editor: React.FC<EditorProps> = ({
sidebarOpen,
user,
}) => {
const zero = useZero();
const zero = useZero<Schema, Mutators>();
const [document] = useQuery(doc(documentId));

const [title, setTitle, titleDirty] = useCachedProp(
Expand All @@ -39,7 +39,7 @@ const Editor: React.FC<EditorProps> = ({
}

const handle = setTimeout(() => {
updateDoc(zero, {
zero.mutate.doc.update({
id: documentId,
title: newTitle,
});
Expand Down
14 changes: 10 additions & 4 deletions src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import React, {useState} from 'react';
import {FileText, Plus, Search, Settings, X} from 'lucide-react';
import {DocId, Document, User as UserType, Workspace} from '../../zero/schema';
import {createDoc} from '../../zero/mutators';
import {
DocId,
Document,
Schema,
User as UserType,
Workspace,
} from '../../zero/schema';
import {useZero} from '@rocicorp/zero/react';
import {nanoid} from 'nanoid';
import {Mutators} from 'zero/mutators';

interface SidebarProps {
documents: Document[];
Expand All @@ -24,7 +30,7 @@ const Sidebar: React.FC<SidebarProps> = ({
currentUser,
currentWorkspace,
}) => {
const zero = useZero();
const zero = useZero<Schema, Mutators>();
const [searchQuery, setSearchQuery] = useState('');
const [expandedSections, setExpandedSections] = useState<Set<string>>(
new Set(['documents']),
Expand All @@ -47,7 +53,7 @@ const Sidebar: React.FC<SidebarProps> = ({
// CUT: it would be ideal to have local ephemeral state to draft the document into before
// persisting.
function createDocument() {
createDoc(zero, {
zero.mutate.doc.create({
id: nanoid(),
ownerId: currentUser.id,
workspaceId: currentWorkspace.id,
Expand Down
4 changes: 2 additions & 2 deletions src/data/data.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Document, User } from '../types';
import {Document, User} from '../../zero/schema';

export const users: User[] = [
{
Expand Down Expand Up @@ -139,4 +139,4 @@ Initial budget estimate: $50,000 - $75,000
isPublic: false,
emoji: '📝',
},
];
];
4 changes: 2 additions & 2 deletions src/zero-setup.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {Zero} from '@rocicorp/zero';
import {Atom} from '../shared/atom';
import {schema, Schema, UserId} from '../zero/schema';
import * as mutators from '../zero/mutators';
import {createMutators} from '../zero/mutators';

import {getSession} from './auth/authClient';

Expand All @@ -28,6 +28,6 @@ authAtom.onChange(auth => {
server: window.location.protocol + '//' + window.location.host,
userID: auth?.id ?? 'anon',
schema,
mutators,
mutators: createMutators(),
});
});
147 changes: 70 additions & 77 deletions zero/mutators.ts
Original file line number Diff line number Diff line change
@@ -1,83 +1,76 @@
import {mutator, Transaction} from '@rocicorp/zero';
import {CustomMutatorDefs, Transaction} from '@rocicorp/zero';
import {Schema} from './schema.gen';
import {DocId, UserId, WorkspaceId} from './schema';

export const createDoc = mutator(
'createDoc',
async (
tx: Transaction<Schema>,
export function createMutators() {
return {
doc: {
id: DocId;
ownerId: UserId;
workspaceId: WorkspaceId;
title: string;
emoji?: string;
create: async (
tx: Transaction<Schema>,
doc: {
id: DocId;
ownerId: UserId;
workspaceId: WorkspaceId;
title: string;
emoji?: string;
},
) => {
await tx.mutate.document.insert({
id: doc.id,
ownerId: doc.ownerId,
workspaceId: doc.workspaceId,
title: doc.title,
created: Date.now(),
modified: Date.now(),
emoji: doc.emoji,
visibility: 'private',
});
await tx.mutate.documentBody.insert({
documentId: doc.id,
content: '',
});
},
createBody: async (
tx: Transaction<Schema>,
docBody: {
documentId: DocId;
content?: string;
},
) => {
await tx.mutate.documentBody.insert({
documentId: docBody.documentId,
content: docBody.content ?? '',
});
},
update: async (
tx: Transaction<Schema>,
doc: {
id: DocId;
title?: string;
emoji?: string;
visibility?: 'private' | 'public' | 'members';
},
) => {
await tx.mutate.document.update({
...doc,
modified: Date.now(),
});
},
// TODO: this will change to surgical updates in the future
// CUT: it would be ideal to be able to collect a bunch of mutations and apply them in a single
// transaction on the backend. But we do not have control over transaction
// boundaries in the current zero-pg sdk.
updateBody: async (
tx: Transaction<Schema>,
docBody: {
documentId: DocId;
content: string;
},
) => {
await tx.mutate.documentBody.update(docBody);
},
},
) => {
await tx.mutate.document.insert({
id: doc.id,
ownerId: doc.ownerId,
workspaceId: doc.workspaceId,
title: doc.title,
created: Date.now(),
modified: Date.now(),
emoji: doc.emoji,
visibility: 'private',
});
await tx.mutate.documentBody.insert({
documentId: doc.id,
content: '',
});
},
);
} as const satisfies CustomMutatorDefs;
}

export const createDocBody = mutator(
'createDocBody',
async (
tx: Transaction<Schema>,
docBody: {
documentId: DocId;
content?: string;
},
) => {
await tx.mutate.documentBody.insert({
documentId: docBody.documentId,
content: docBody.content ?? '',
});
},
);

export const updateDoc = mutator(
'updateDoc',
async (
tx: Transaction<Schema>,
doc: {
id: DocId;
title?: string;
emoji?: string;
visibility?: 'private' | 'public' | 'members';
},
) => {
await tx.mutate.document.update({
...doc,
modified: Date.now(),
});
},
);

// TODO: this will change to surgical updates in the future
// CUT: it would be ideal to be able to collect a bunch of mutations and apply them in a single
// transaction on the backend. But we do not have control over transaction
// boundaries in the current zero-pg sdk.
export const updateDocBody = mutator(
'updateDocBody',
async (
tx: Transaction<Schema>,
docBody: {
documentId: DocId;
content: string;
},
) => {
await tx.mutate.documentBody.update(docBody);
},
);
export type Mutators = ReturnType<typeof createMutators>;
37 changes: 25 additions & 12 deletions zero/queries.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,37 @@
import {namedQuery} from '@rocicorp/zero';
import {syncedQuery} from '@rocicorp/zero';
import {builder, DocId, UserId, WorkspaceId} from './schema';
import * as z from 'zod';

export const allDocTitles = namedQuery(
export const allDocTitles = syncedQuery(
'allDocTitles',
(workspaceId: WorkspaceId) =>
builder.document.where('workspaceId', workspaceId),
z.tuple([z.string()]),
(workspaceId: string) =>
builder.document.where('workspaceId', workspaceId as WorkspaceId),
);

export const doc = namedQuery('doc', (docId: DocId) =>
builder.document.where('id', docId).related('body').one(),
export const doc = syncedQuery('doc', z.tuple([z.string()]), (docId: string) =>
builder.document
.where('id', docId as DocId)
.related('body')
.one(),
);

export const user = namedQuery('user', (userId: UserId) =>
builder.user.where('id', userId).one(),
export const user = syncedQuery(
'user',
z.tuple([z.string()]),
(userId: string) => builder.user.where('id', userId as UserId).one(),
);

export const workspace = namedQuery('workspace', (workspaceId: WorkspaceId) =>
builder.workspace.where('id', workspaceId).one(),
export const workspace = syncedQuery(
'workspace',
z.tuple([z.string()]),
(workspaceId: string) =>
builder.workspace.where('id', workspaceId as WorkspaceId).one(),
);

export const docBody = namedQuery('docBody', (docId: DocId) =>
builder.documentBody.where('documentId', docId).one(),
export const docBody = syncedQuery(
'docBody',
z.tuple([z.string()]),
(docId: string) =>
builder.documentBody.where('documentId', docId as DocId).one(),
);
4 changes: 2 additions & 2 deletions zero/schema.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {definePermissions, querify, Row} from '@rocicorp/zero';
import {definePermissions, createBuilder, Row} from '@rocicorp/zero';
import {schema, type Schema} from './schema.gen';
export {schema, type Schema};

Expand All @@ -11,7 +11,7 @@ export const permissions = definePermissions<{}, Schema>(schema, () => {
return {};
});

export const builder = querify(schema);
export const builder = createBuilder(schema);

export type Opaque<BaseType, BrandType = unknown> = BaseType & {
readonly [Symbols.base]: BaseType;
Expand Down