Skip to content

Commit

Permalink
Merge pull request #1 from BriefHQ/relationships
Browse files Browse the repository at this point in the history
Relationships
  • Loading branch information
0xcadams authored Jan 4, 2025
2 parents f0c9f4d + 7cb0f2a commit 6e127b1
Show file tree
Hide file tree
Showing 29 changed files with 1,845 additions and 509 deletions.
100 changes: 75 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,45 +14,95 @@ pnpm add drizzle-zero

## Usage

Here's a simple example of how to convert a Drizzle schema to a Zero schema:
Here's an example of how to convert a Drizzle schema to a Zero schema, complete with bidirectional relationships:

```ts
import { pgTable, text } from "drizzle-orm/pg-core";
import { createSchema, createTableSchema } from "@rocicorp/zero";
import { createZeroSchema } from "drizzle-zero";

// Define your Drizzle table
const userTable = pgTable("user", {
id: text("id").primaryKey(),
name: text("name").notNull(),
import { relations } from "drizzle-orm";
import { integer, pgTable, serial, text } from "drizzle-orm/pg-core";

export const users = pgTable("user", {
id: serial("id").primaryKey(),
name: text("name"),
});

// Convert to Zero table schema and select columns
const userSchema = createTableSchema(
createZeroSchema(userTable, {
id: true,
name: true,
export const usersRelations = relations(users, ({ many }) => ({
posts: many(posts),
}));

export const posts = pgTable("post", {
id: serial("id").primaryKey(),
content: text("content"),
authorId: integer("author_id").references(() => users.id),
});

export const postsRelations = relations(posts, ({ one }) => ({
author: one(users, {
fields: [posts.authorId],
references: [users.id],
}),
);
}));

// Create your Zero schema
export const schema = createSchema({
version: 1,
tables: {
user: userSchema,
},
export const comments = pgTable("comment", {
id: serial("id").primaryKey(),
text: text("text"),
authorId: integer("author_id").references(() => users.id),
postId: integer("post_id").references(() => posts.id),
});

export const commentsRelations = relations(comments, ({ one }) => ({
post: one(posts, {
fields: [comments.postId],
references: [posts.id],
}),
author: one(users, {
fields: [comments.authorId],
references: [users.id],
}),
}));
```

You can then convert this Drizzle schema to a Zero schema:

```ts
import { createSchema } from "@rocicorp/zero";
import { createZeroTableSchema } from "drizzle-zero";
import * as drizzleSchema from "./drizzle-schema";

// Convert to Zero schema
export const schema = createSchema(
createZeroSchema(drizzleSchema, {
version: 1,
tables: {
user: {
id: true,
name: true,
},
post: {
id: true,
content: true,
author_id: true,
},
comment: {
id: true,
text: true,
post_id: true,
author_id: true,
},
},
}),
);
```

## Features

- Convert Drizzle ORM schemas to Zero schemas
- Handles all Drizzle column types that are supported by Zero
- Type-safe schema generation

## Limitations

- Relationships are not supported yet
- Supports relationships:
- One-to-one relationships
- One-to-many relationships
- Many-to-many relationships
- Self-referential relationships

## License

Expand Down
33 changes: 26 additions & 7 deletions integration/drizzle/schema.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,39 @@
import { pgTable, text } from "drizzle-orm/pg-core";
import { boolean } from "drizzle-orm/pg-core";
import { relations } from "drizzle-orm";
import { boolean, pgTable, text } from "drizzle-orm/pg-core";

export const user = pgTable("user", {
export const userTable = pgTable("user", {
id: text("id").primaryKey(),
name: text("name").notNull(),
partner: boolean("partner").notNull(),
});

export const medium = pgTable("medium", {
export const userRelations = relations(userTable, ({ many }) => ({
messages: many(messageTable),
}));

export const mediumTable = pgTable("medium", {
id: text("id").primaryKey(),
name: text("name").notNull(),
});

export const message = pgTable("message", {
export const mediumRelations = relations(mediumTable, ({ many }) => ({
messages: many(messageTable),
}));

export const messageTable = pgTable("message", {
id: text("id").primaryKey(),
senderId: text("senderId").references(() => user.id),
mediumId: text("mediumId").references(() => medium.id),
senderId: text("senderId").references(() => userTable.id),
mediumId: text("mediumId").references(() => mediumTable.id),
body: text("body").notNull(),
});

export const messageRelations = relations(messageTable, ({ one }) => ({
medium: one(mediumTable, {
fields: [messageTable.mediumId],
references: [mediumTable.id],
}),
sender: one(userTable, {
fields: [messageTable.senderId],
references: [userTable.id],
}),
}));
2 changes: 2 additions & 0 deletions integration/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
"test": "vitest run"
},
"dependencies": {
"@rocicorp/zero": "^0.10.2024122404",
"drizzle-orm": "^0.38.3",
"drizzle-zero": "workspace:*"
},
"devDependencies": {
Expand Down
104 changes: 34 additions & 70 deletions integration/schema.ts
Original file line number Diff line number Diff line change
@@ -1,66 +1,39 @@
import {
ANYONE_CAN,
createSchema,
createTableSchema,
definePermissions,
NOBODY_CAN,
type ExpressionBuilder,
type Row,
type TableSchema,
} from "@rocicorp/zero";
import { createZeroSchema } from "drizzle-zero";
import { user } from "./drizzle/schema";
import * as drizzleSchema from "./drizzle/schema";

const userSchema = createZeroSchema(user, {
id: true,
name: true,
partner: false,
});

const mediumSchema = createTableSchema({
tableName: "medium",
columns: {
id: "string",
name: "string",
},
primaryKey: "id",
});

const messageSchema = createTableSchema({
tableName: "message",
columns: {
id: "string",
senderId: "string",
mediumId: "string",
body: "string",
},
primaryKey: "id",
relationships: {
sender: {
sourceField: "senderId",
destSchema: userSchema,
destField: "id",
const zeroSchema = createZeroSchema(drizzleSchema, {
version: 1,
tables: {
user: {
id: true,
name: true,
partner: false,
},
medium: {
sourceField: "mediumId",
destSchema: mediumSchema,
destField: "id",
id: true,
name: true,
},
message: {
id: true,
senderId: true,
mediumId: true,
body: true,
},
},
});

export const schema = createSchema({
version: 1,
tables: {
user: userSchema,
medium: mediumSchema,
message: messageSchema,
},
});
export const schema = createSchema(zeroSchema);

export type Schema = typeof schema;
export type Message = Row<typeof messageSchema>;
export type Medium = Row<typeof mediumSchema>;
export type Message = Row<typeof schema.tables.message>;
export type Medium = Row<typeof schema.tables.medium>;
export type User = Row<typeof schema.tables.user>;

// The contents of your decoded JWT.
Expand All @@ -69,45 +42,36 @@ type AuthData = {
};

export const permissions = definePermissions<AuthData, Schema>(schema, () => {
const allowIfLoggedIn = (
authData: AuthData,
{ cmpLit }: ExpressionBuilder<TableSchema>,
) => cmpLit(authData.sub, "IS NOT", null);

const allowIfMessageSender = (
authData: AuthData,
{ cmp }: ExpressionBuilder<typeof messageSchema>,
) => cmp("senderId", "=", authData.sub ?? "");
const allowIfSender1 = (
_authData: AuthData,
{ cmp }: ExpressionBuilder<typeof schema.tables.message>,
) => cmp("senderId", "=", "1");

return {
medium: {
row: {
insert: NOBODY_CAN,
update: {
preMutation: NOBODY_CAN,
},
delete: NOBODY_CAN,
select: ANYONE_CAN,
insert: ANYONE_CAN,
update: ANYONE_CAN,
delete: ANYONE_CAN,
},
},
user: {
row: {
insert: NOBODY_CAN,
update: {
preMutation: NOBODY_CAN,
},
delete: NOBODY_CAN,
select: ANYONE_CAN,
insert: ANYONE_CAN,
update: ANYONE_CAN,
delete: ANYONE_CAN,
},
},
message: {
row: {
// anyone can insert
select: [allowIfSender1],
insert: ANYONE_CAN,
// only sender can edit their own messages
update: {
preMutation: [allowIfMessageSender],
preMutation: [allowIfSender1],
},
// must be logged in to delete
delete: [allowIfLoggedIn],
delete: [allowIfSender1],
},
},
};
Expand Down
Loading

0 comments on commit 6e127b1

Please sign in to comment.