Skip to content

Instantly share code, notes, and snippets.

@mickmister
Created February 13, 2025 19:19
Show Gist options
  • Save mickmister/cceb4db79b5bddb55d5e99ff596a83fc to your computer and use it in GitHub Desktop.
Save mickmister/cceb4db79b5bddb55d5e99ff596a83fc to your computer and use it in GitHub Desktop.
Zero Sync schema example
import {
createSchema,
definePermissions,
relationships,
string,
table,
type ExpressionBuilder,
} from "@rocicorp/zero";
const User = table("User")
.columns({
id: string(),
login: string(),
name: string().optional(),
avatar: string(),
role: string(),
})
.primaryKey("id");
const WorkspaceMembership = table("WorkspaceMembership")
.columns({
id: string(),
user_id: string(),
workspace_id: string(),
})
.primaryKey("id");
const Workspace = table("Workspace")
.columns({
id: string(),
})
.primaryKey("id");
const Issue = table("Issue")
.columns({
id: string(),
workspace_id: string(),
title: string(),
creator_id: string(),
})
.primaryKey("id");
const workspaceRelationships = relationships(Workspace, ({ many }) => ({
workspaceMemberships: many({
destField: ["workspace_id"],
destSchema: WorkspaceMembership,
sourceField: ["id"],
}),
issues: many({
sourceField: ["id"],
destField: ["workspace_id"],
destSchema: Issue,
}),
}));
const workspaceMembershipRelationships = relationships(
WorkspaceMembership,
({ one }) => ({
user: one({
sourceField: ["user_id"],
destField: ["id"],
destSchema: User,
}),
workspace: one({
sourceField: ["workspace_id"],
destField: ["id"],
destSchema: Workspace,
}),
}),
);
const issueRelationships = relationships(Issue, ({ many, one }) => ({
workspace: one({
sourceField: ["workspace_id"],
destField: ["id"],
destSchema: Workspace,
}),
creator: one({
sourceField: ["creator_id"],
destField: ["id"],
destSchema: User,
}),
}));
const userRelationships = relationships(User, ({ many }) => ({
createdIssues: many({
sourceField: ["id"],
destField: ["creator_id"],
destSchema: Issue,
}),
workspaceMemberships: many({
sourceField: ["id"],
destField: ["user_id"],
destSchema: WorkspaceMembership,
}),
}));
type AuthData = {
sub: string;
role: "crew" | "user";
};
export const schema = createSchema(5, {
tables: [User, Issue, WorkspaceMembership, Workspace],
relationships: [
userRelationships,
issueRelationships,
workspaceMembershipRelationships,
workspaceRelationships,
],
});
export type Schema = typeof schema;
export const permissions: ReturnType<typeof definePermissions> =
definePermissions<AuthData, Schema>(schema, () => {
const isWorkspaceMember = (
authData: AuthData,
eb: ExpressionBuilder<
Schema,
"Issue"
// | "Comment"
// any other things that have a workspace_id pointer
>,
) =>
eb.exists("workspace", (q) => {
return q.where((ebWorkspace) =>
ebWorkspace.exists("workspaceMemberships", (q2) =>
q2.where("user_id", "=", authData.sub),
),
);
});
const isWorkspaceFriendInAnyWorkspace = (
authData: AuthData,
eb: ExpressionBuilder<Schema, "User">,
) =>
eb.exists("workspaceMemberships", (q) => {
return q.where((eb_WM) =>
eb_WM.exists("workspace", (q2) =>
q2.whereExists("workspaceMemberships", (q3) =>
q3.where("user_id", "=", authData.sub),
),
),
);
});
return {
User: {
row: {
select: [isWorkspaceFriendInAnyWorkspace],
},
},
Issue: {
row: {
select: [isWorkspaceMember],
insert: [isWorkspaceMember],
update: [isWorkspaceMember],
delete: [isWorkspaceMember],
},
},
};
});
@mickmister
Copy link
Author

This example is mainly meant to outline how workspace access can be enforced across different objects belonging to a given workspace, in this case it's being done through the isWorkspaceMember permission helper. The example is bit incomplete, as we need to also provide the rest of the User permissions, and the permissions for Workspace and WorkspaceMembership.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment