# drizzle-zero

Generate [Zero](https://zero.rocicorp.dev/) schemas from [Drizzle ORM](https://orm.drizzle.team) schemas.

## Installation

```bash
npm install drizzle-zero
# or
bun add drizzle-zero
# or
yarn add drizzle-zero
# or
pnpm add drizzle-zero
```

## Usage

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

### Define Drizzle schema

You should have an existing Drizzle schema, e.g.:

```ts
import {relations} from 'drizzle-orm';
import {pgTable, text, jsonb} from 'drizzle-orm/pg-core';

export const users = pgTable('user', {
  id: text('id').primaryKey(),
  name: text('name'),
  // custom types are supported for any column type!
  email: text('email').$type<`${string}@${string}`>().notNull(),
});

export const usersRelations = relations(users, ({many}) => ({
  posts: many(posts),
}));

export const posts = pgTable('post', {
  id: text('id').primaryKey(),
  // this JSON type will be passed to Zero
  content: jsonb('content').$type<{textValue: string}>().notNull(),
  authorId: text('author_id').references(() => users.id),
});

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

See the [integration test's `schema.ts`](db/drizzle/schema.ts)
for more examples of how to define Drizzle schemas with custom types.

### Add schema generation script

You can then add the schema generation script to your `package.json`:

```json
{
  "scripts": {
    "generate": "drizzle-zero generate --format",
    "postinstall": "npm generate"
  }
}
```

This command will look for a Drizzle Kit config at `drizzle.config.ts` in the current directory
and use the Drizzle schema defined in it. _This must be a single TS file and
not a folder/glob for type resolution to work_. It will also use the
casing defined in your drizzle config.

You can change this behavior with `-s, --schema <input-file>`
as the path to your Drizzle schema file, or
`-k, --drizzle-kit-config <input-file>` with the path to your `drizzle.config.ts` file.

By default, it will output your schema to `zero-schema.gen.ts`.
You can customize the generated file path with `-o, --output <output-file>`.

If you have Prettier installed, you can use it to format the generated output
with `-f, --format`.

To specify a custom tsconfig file, use `-t, --tsconfig <tsconfig-file>`.
It will, by default, look for one in the current directory.

The CLI automatically detects whether `.js` file extensions are needed in import statements based on your tsconfig's `moduleResolution` setting. If you're using `"moduleResolution": "node16"` or `"nodenext"`, the generator will automatically add `.js` extensions to imports. You can override this behavior with `-j, --js-file-extension` if needed.

You can also control optional outputs from the generator:

- **--skip-types**: Skip generating table `Row[]` type exports.
- **--skip-builder**: Skip generating the query `createBuilder` export.
- **--skip-declare**: Skip generating the module augmentation for default types in Zero.
- **--enable-legacy-mutators**: Enable legacy CRUD mutators (sets `enableLegacyMutators` to `true` in the generated schema).
- **--enable-legacy-queries**: Enable legacy CRUD queries (sets `enableLegacyQueries` to `true` in the generated schema).
- **--suppress-defaults-warning**: Hide warnings for columns with database default values. By default, drizzle-zero warns when columns use database defaults (`.default()` or `.defaultFn()`) since these won't be available on the Zero client.

For more information on disabling legacy mutators and queries, see the [Zero documentation](https://zero.rocicorp.dev/docs/custom-mutators#disabling-crud-mutators).

**Important:** the Drizzle schema **must be included in the tsconfig** for
type resolution to work. If they are not included, there will be an error similar to
`Failed to find type definitions`.

Please reference the Zero docs for how to use your new Zero schema: [https://zero.rocicorp.dev/docs/reading-data](https://zero.rocicorp.dev/docs/reading-data).

### Customize with `drizzle-zero.config.ts`

If you want to customize the tables/columns that are synced by Zero, you can optionally
create a new config file at `drizzle-zero.config.ts` specifying the tables and/or columns you want to
include in the CLI output:

> **Important:** The config file currently struggles with types for large schemas. In those cases,
> stick with the default CLI behavior.

```ts
import {drizzleZeroConfig} from 'drizzle-zero';
// directly glob import your original Drizzle schema w/ tables/relations
import * as drizzleSchema from './drizzle-schema';

// Define your configuration file for the CLI
export default drizzleZeroConfig(drizzleSchema, {
  // Specify which tables and columns to include in the Zero schema.
  // This allows for the "expand/migrate/contract" pattern recommended in the Zero docs.

  // All tables/columns must be defined, but can be omitted or set to false to exclude them from the Zero schema.
  // Column names match your Drizzle schema definitions
  tables: {
    // this can be set to false
    // e.g. users: false,
    users: {
      id: true,
      name: true,
      // omit columns to exclude them
      email: true,
    },
    posts: {
      // or this can be set to false
      // e.g. id: false,
      id: true,
      content: true,
      // Use the JavaScript field name (authorId), not the DB column name (author_id)
      authorId: true,
    },
  },

  // Specify the casing style to use for the schema.
  // This is useful for when you want to use a different casing style than the default.
  // This works in the same way as the `casing` option in the Drizzle ORM.
  //
  // @example
  // casing: "snake_case",
});
```

You can customize this config file path with `-c, --config <input-file>`.

**Important:** the `drizzle-zero.config.ts` file **must be included in the tsconfig**
for the type resolution to work. If they are not included, there will be an error similar to
`Failed to find type definitions`.

## Many-to-Many Relationships

drizzle-zero supports many-to-many relationships with a junction table. You can configure them in two ways:

### Simple Configuration

```ts
export default drizzleZeroConfig(drizzleSchema, {
  tables: {
    user: {
      id: true,
      name: true,
    },
    usersToGroup: {
      userId: true,
      groupId: true,
    },
    group: {
      id: true,
      name: true,
    },
  },
  manyToMany: {
    user: {
      // Simple format: [junction table, target table]
      groups: ['usersToGroup', 'group'],
    },
  },
});
```

Then query as usual, skipping the junction table:

```tsx
const userQuery = syncedQuery(
  z.query.user.where('id', '=', '1').related('groups').one(),
);

const [user] = useQuery(userQuery());

console.log(user);
// {
//   id: "user_1",
//   name: "User 1",
//   groups: [
//     { id: "group_1", name: "Group 1" },
//     { id: "group_2", name: "Group 2" },
//   ],
// }
```

### Extended Configuration

For more complex scenarios like self-referential relationships:

```ts
export default drizzleZeroConfig(drizzleSchema, {
  tables: {
    user: {
      id: true,
      name: true,
    },
    friendship: {
      requestingId: true,
      acceptingId: true,
    },
  },
  manyToMany: {
    user: {
      // Extended format with explicit field mappings
      friends: [
        {
          sourceField: ['id'],
          destTable: 'friendship',
          destField: ['requestingId'],
        },
        {
          sourceField: ['acceptingId'],
          destTable: 'user',
          destField: ['id'],
        },
      ],
    },
  },
});
```

## Features

- Output static schemas from the CLI
- Convert Drizzle ORM schemas to Zero schemas
  - Sync a subset of tables and columns
- Handles all Drizzle column types that are supported by Zero
- Type-safe schema generation with inferred types from Drizzle
- Custom ZQL database adapter for using Drizzle in the same `tx` as Zero mutators
- Supports relationships:
  - One-to-one relationships
  - One-to-many relationships
  - Many-to-many relationships with simple or extended configuration
  - Self-referential relationships
- Handles custom schemas and column mappings
