Drizzle Relations

WARNING

이 페이지는 Drizzle 버전 1.0.0-beta.1 이상에서 사용 가능한 개념을 설명합니다.

npm
yarn
pnpm
bun
npm i drizzle-orm@beta
npm i drizzle-kit@beta -D

This guide assumes familiarity with:
  • 관계 기초 - 외래 키 제약조건, 소프트 관계, 데이터베이스 정규화 등의 개념 이해 - 여기에서 읽기
  • 스키마 선언 - Drizzle 스키마 정의 방법 이해 - 여기에서 읽기
  • 데이터베이스 연결 - Drizzle을 사용한 데이터베이스 연결 방법 이해 - 여기에서 읽기

Drizzle Relations의 유일한 목적은 관계형 데이터를 가장 간단하고 간결한 방식으로 쿼리할 수 있도록 하는 것입니다:

Relational queries
Select with joins
import { drizzle } from 'drizzle-orm/…';
import { defineRelations } from 'drizzle-orm';
import * as p from 'drizzle-orm/pg-core';

export const users = p.pgTable('users', {
	id: p.integer().primaryKey(),
	name: p.text().notNull()
});

export const posts = p.pgTable('posts', {
	id: p.integer().primaryKey(),
	content: p.text().notNull(),
	ownerId: p.integer('owner_id'),
});

const relations = defineRelations({ users, posts }, (r) => ({
	posts: {
		author: r.one.users({
			from: r.posts.ownerId,
			to: r.users.id,
		}),
	}
}))

const db = drizzle(client, { relations });

const result = db.query.posts.findMany({
  with: {
    author: true,
  },
});
[{
  id: 10,
  content: "My first post!",
  author: {
    id: 1,
    name: "Alex"
  }
}]

one()

다음은 Drizzle Relations에서 .one()에 사용 가능한 모든 필드 목록입니다

const relations = defineRelations({ users, posts }, (r) => ({
	posts: {
		author: r.one.users({
			from: r.posts.ownerId,
			to: r.users.id,
			optional: false,
      alias: 'custom_name',
			where: {
				verified: true,
			}
		}),
	}
}))

many()

다음은 Drizzle Relations에서 .many()에 사용 가능한 모든 필드 목록입니다

const relations = defineRelations({ users, posts }, (r) => ({
	users: {
		feed: r.many.posts({
			from: r.users.id,
			to: r.posts.ownerId,
			optional: false,
      alias: 'custom_name',
			where: {
				approved: true,
			}
		}),
	}
}))

---

일대일

Drizzle ORM은 defineRelations 함수를 사용하여 테이블 간의 일대일 관계를 정의하는 API를 제공합니다.

사용자가 다른 사용자를 초대할 수 있는 users와 users 간의 일대일 관계 예제입니다 (이 예제는 자기 참조를 사용합니다):

import { pgTable, serial, text, boolean } from 'drizzle-orm/pg-core';
import { defineRelations } from 'drizzle-orm';

export const users = pgTable('users', {
	id: integer().primaryKey(),
	name: text(),
	invitedBy: integer('invited_by'),
});

export const relations = defineRelations({ users }, (r) => ({
	users: {
		invitee: r.one.users({
			from: r.users.invitedBy,
			to: r.users.id,
		})
	}
}));

또 다른 예는 사용자가 별도의 테이블에 저장된 프로필 정보를 갖는 경우입니다. 이 경우 외래 키가 profile_info 테이블에 저장되어 있기 때문에 user 관계에는 필드나 참조가 없습니다. 이는 TypeScript에게 user.profileInfo가 nullable임을 알려줍니다:

import { pgTable, serial, text, integer, jsonb } from 'drizzle-orm/pg-core';
import { defineRelations } from 'drizzle-orm';

export const users = pgTable('users', {
	id: integer().primaryKey(),
	name: text(),
});

export const profileInfo = pgTable('profile_info', {
	id: serial().primaryKey(),
	userId: integer('user_id').references(() => users.id),
	metadata: jsonb(),
});

export const relations = defineRelations({ users, profileInfo }, (r) => ({
	users: {
		profileInfo: r.one.profileInfo({
			from: r.users.id,
			to: r.profileInfo.userId,
		})
	}
}));

const user = await db.query.posts.findFirst({ with: { profileInfo: true } });
//____^? type { id: number, profileInfo: { ... } | null  }

일대다

Drizzle ORM은 defineRelations 함수를 사용하여 테이블 간의 일대다 관계를 정의하는 API를 제공합니다.

사용자와 그들이 작성한 게시물 간의 일대다 관계 예제입니다:

import { pgTable, serial, text, integer } from 'drizzle-orm/pg-core';
import { defineRelations } from 'drizzle-orm';

export const users = pgTable('users', {
	id: integer('id').primaryKey(),
	name: text('name'),
});

export const posts = pgTable('posts', {
	id: integer('id').primaryKey(),
	content: text('content'),
	authorId: integer('author_id'),
});

export const relations = defineRelations({ users, posts }, (r) => ({
  posts: {
    author: r.one.users({
      from: r.posts.authorId,
      to: r.users.id,
    }),
  },
  users: {
    posts: r.many.posts(),
  },
}));

이제 게시물에 댓글을 추가해 보겠습니다:

...

export const posts = pgTable('posts', {
	id: integer('id').primaryKey(),
	content: text('content'),
	authorId: integer('author_id'),
});

export const comments = pgTable("comments", {
  id: integer().primaryKey(),
  text: text(),
  authorId: integer("author_id"),
  postId: integer("post_id"),
});

export const relations = defineRelations({ users, posts, comments }, (r) => ({
  posts: {
    author: r.one.users({
      from: r.posts.authorId,
      to: r.users.id,
    }),
    comments: r.many.comments(),
  },
  users: {
    posts: r.many.posts(),
  },
  comments: {
    post: r.one.posts({
      from: r.comments.postId,
      to: r.posts.id,
    }),
  },
}));

다대다

Drizzle ORM은 소위 junction 또는 join 테이블을 통해 테이블 간의 다대다 관계를 정의하는 API를 제공합니다. 이러한 테이블은 명시적으로 정의되어야 하며 관련 테이블 간의 연관 관계를 저장합니다.

사용자와 그룹 간의 다대다 관계 예제입니다. through를 사용하여 중간 테이블 선택을 우회하고 각 user에 대해 여러 groups를 직접 조회합니다.

import { defineRelations } from 'drizzle-orm';
import { integer, pgTable, primaryKey, text } from 'drizzle-orm/pg-core';

export const users = pgTable('users', {
  id: integer().primaryKey(),
  name: text(),
});

export const groups = pgTable('groups', {
  id: integer().primaryKey(),
  name: text(),
});

export const usersToGroups = pgTable(
  'users_to_groups',
  {
    userId: integer('user_id')
      .notNull()
      .references(() => users.id),
    groupId: integer('group_id')
      .notNull()
      .references(() => groups.id),
  },
  (t) => [primaryKey({ columns: [t.userId, t.groupId] })],
);

export const relations = defineRelations({ users, groups, usersToGroups },
  (r) => ({
    users: {
      groups: r.many.groups({
        from: r.users.id.through(r.usersToGroups.userId),
        to: r.groups.id.through(r.usersToGroups.groupId),
      }),
    },
    groups: {
      participants: r.many.users(),
    },
  })
);

쿼리 예제:

const res = await db.query.users.findMany({
  with: {
    groups: true
  },
});

// response type
type Response = {
  id: number;
  name: string | null;
  groups: {
    id: number;
    name: string | null;
  }[];
}[];
Relational Queries v1

이전에는 junction 테이블을 통해 쿼리한 다음 모든 응답에 대해 매핑해야 했습니다

❌ 이제는 이렇게 할 필요가 없습니다!

const response = await db._query.users.findMany({
	with: {
		usersToGroups: {
			columns: {},
			with: {
				groups: true,
			},
		},
	},
});

// response type
type Response = {
  id: number;
  name: string | null;
  usersToGroups: {
    groups: {
       id: number;
       name: string | null;
    }
  }[];
}[];

미리 정의된 필터

Drizzle의 관계 정의에서 미리 정의된 where 문은 다형성 관계 구현의 한 유형이지만 완전하지는 않습니다. 기본적으로 특정 컬럼을 선택하는 것뿐만 아니라 사용자 정의 where 문을 통해 테이블을 연결할 수 있습니다. 몇 가지 예제를 살펴보겠습니다:

그룹의 사용자를 쿼리할 때 verified 컬럼이 true로 설정된 사용자만 검색하도록 groupsusers 간의 관계를 정의할 수 있습니다.

Relations
Schema
import { defineRelations } from "drizzle-orm";
import * as p from "drizzle-orm/pg-core";
import * as schema from './schema';

export const relations = defineRelations(schema,(r) => ({
    groups: {
      verifiedUsers: r.many.users({
        from: r.groups.id.through(r.usersToGroups.groupId),
        to: r.users.id.through(r.usersToGroups.userId),
        where: {
          verified: true,
        },
      }),
    },
  })
);

...

await db.query.groups.findMany({
    with: {
      verifiedUsers: true,
    },
});
IMPORTANT

대상(to) 테이블에만 필터를 지정할 수 있습니다. 따라서 이 예제에서 where 절은 users TO 관계를 설정하므로 users 테이블의 컬럼만 포함합니다.

export const relations = defineRelations(schema,(r) => ({
    groups: {
      verifiedUsers: r.many.users({
        from: r.groups.id.through(r.usersToGroups.groupId),
        to: r.users.id.through(r.usersToGroups.userId),
        where: {
          verified: true,
        },
      }),
    },
  })
);

---

관계 파트

관계 설정을 여러 부분으로 분리해야 하는 경우 defineRelationsPart 헬퍼를 사용할 수 있습니다.

import { defineRelations, defineRelationsPart } from 'drizzle-orm';
import * as schema from "./schema";

export const relations = defineRelations(schema, (r) => ({
  users: {
    invitee: r.one.users({
      from: r.users.invitedBy,
      to: r.users.id,
    }),
    posts: r.many.posts(),
  }
}));

export const part = defineRelationsPart(schema, (r) => ({
  posts: {
    author: r.one.users({
      from: r.posts.authorId,
      to: r.users.id,
    }),
  }
}));

그런 다음 db 인스턴스에 제공할 수 있습니다:

const db = drizzle(process.env.DB_URL, { relations: { ...relations, ...part } })
IMPORTANT

defineRelationsParts가 예상대로 작동하도록 하려면 몇 가지 규칙을 따라야 합니다.

규칙 1: 파트와 함께 관계를 지정하는 경우, drizzle db 함수에 전달할 때 올바른 순서로 지정해야 합니다(메인 관계가 먼저).

// ✅
const db = drizzle(process.env.DB_URL, { relations: { ...relations, ...part } })

// ❌
const db = drizzle(process.env.DB_URL, { relations: { ...part, ...relations } })

왜 중요한가요?

타입이나 런타임 오류가 없더라도, 이것이 ”…”가 객체와 작동하는 방식입니다. 메인 관계가 모든 테이블 이름을 재귀적으로 추론하므로 자동 완성에서 사용할 수 있습니다. 다음은 예제입니다:

export const relations = defineRelations(schema, (r) => ({
  users: {
    invitee: r.one.users({
      from: r.users.invitedBy,
      to: r.users.id,
    }),
    posts: r.many.posts(),
  }
}));

export const part = defineRelationsPart(schema, (r) => ({
  posts: {
    author: r.one.users({
      from: r.posts.authorId,
      to: r.users.id,
    }),
  }
}));

여기서 relationspart는 다음 객체로 표현될 수 있습니다:

// relations
{
  "users": {"invitee": {...}, "posts": {...}},
  // 여기에 추가되어 스키마의 모든 테이블이 자동 완성에 존재합니다
  "posts": {}
}

// part
{
  "posts": {"author": {...}}
}

{ ...relations, ...part }는 다음과 같은 결과를 만듭니다

{
  "users": {"invitee": {...}, "posts": {...}},
  "posts": {"author": {...}}
}

그리고 { ...part, ...relations }는 다음과 같은 결과를 만듭니다

{
  "users": {"invitee": {...}, "posts": {...}},
  // 최종 객체에서 posts 관계 정보가 손실됩니다
  "posts": {}
}

규칙 2: Drizzle이 자동 완성을 위해 모든 테이블을 추론할 수 있도록 최소 관계가 있어야 합니다. 파트만 사용하려면 다음과 같이 파트 중 하나가 비어 있어야 합니다:

export const mainPart = defineRelationsPart(schema);

이 경우 모든 테이블이 올바르게 추론되며 스키마에 대한 완전한 정보를 갖게 됩니다.

---

성능

특히 상당한 데이터나 복잡한 쿼리가 있는 애플리케이션에서 Drizzle ORM의 관계를 사용할 때 데이터베이스 성능 최적화가 중요합니다. 인덱스는 데이터 검색 속도를 높이는 데 중요한 역할을 하며, 특히 관련 데이터를 쿼리할 때 그렇습니다. 이 섹션에서는 Drizzle ORM을 사용하여 정의된 각 관계 유형에 대한 권장 인덱싱 전략을 설명합니다.

일대일 관계

“user invites user” 예제나 “user has profile info” 예제와 같은 일대일 관계에서 핵심 성능 고려 사항은 관련 테이블의 효율적인 조인입니다.

일대일 관계에서 최적의 성능을 위해 참조되는 테이블 (타겟 테이블)의 외래 키 컬럼에 인덱스를 생성해야 합니다.

왜 중요한가요

관련된 일대일 정보로 데이터를 쿼리할 때 Drizzle은 JOIN 작업을 수행합니다. 외래 키 컬럼의 인덱스를 통해 데이터베이스가 타겟 테이블에서 관련 행을 빠르게 찾을 수 있어 조인 프로세스가 크게 빨라집니다.

예제:

import * as p from 'drizzle-orm/pg-core';
import { defineRelations } from 'drizzle-orm';

export const users = p.pgTable('users', {
	id: p.integer().primaryKey(),
	name: p.text(),
});

export const profileInfo = p.pgTable('profile_info', {
	id: p.integer().primaryKey(),
	userId: p.integer('user_id').references(() => users.id),
	metadata: p.jsonb(),
});

export const relations = defineRelations({ users, profileInfo }, (r) => ({
	users: {
		profileInfo: r.one.profileInfo({
			from: r.users.id,
			to: r.profileInfo.userId,
		})
	}
}));

프로필 정보와 함께 사용자 데이터를 가져오는 쿼리를 최적화하려면 profile_info 테이블의 userId 컬럼에 인덱스를 생성해야 합니다.

import * as p from 'drizzle-orm/pg-core';
import { defineRelations } from 'drizzle-orm';

export const users = p.pgTable('users', {
	id: p.integer().primaryKey(),
	name: p.text(),
});

export const profileInfo = pgTable('profile_info', {
	id: p.integer().primaryKey(),
	userId: p.integer('user_id').references(() => users.id),
	metadata: p.jsonb(),
}, (table) => [
  p.index('profile_info_user_id_idx').on(table.userId)
]);

export const relations = defineRelations({ users, profileInfo }, (r) => ({
	users: {
		profileInfo: r.one.profileInfo({
			from: r.users.id,
			to: r.profileInfo.userId,
		})
	}
}));
CREATE INDEX idx_profile_info_user_id ON profile_info (user_id);

일대다 관계

일대일 관계와 유사하게 일대다 관계는 조인 작업을 최적화하기 위한 인덱싱의 이점을 크게 받습니다. 한 명의 사용자가 여러 게시물을 가질 수 있는 “users and posts” 예제를 고려해 보세요.

일대다 관계의 경우, 관계의 “다” 쪽을 나타내는 테이블(즉, “일” 쪽을 참조하는 외래 키가 있는 테이블)의 외래 키 컬럼에 인덱스를 생성하세요.

왜 중요한가요

사용자와 그들의 게시물을 가져오거나 게시물과 작성자를 가져올 때 조인이 수행됩니다. 외래 키(posts 테이블의 authorId)를 인덱싱하면 데이터베이스가 주어진 사용자와 연결된 모든 게시물을 효율적으로 검색하거나 게시물의 작성자를 빠르게 찾을 수 있습니다.

예제:

import * as p from "drizzle-orm/pg-core";
import { defineRelations } from 'drizzle-orm';

export const users = p.pgTable('users', {
	id: p.integer().primaryKey(),
	name: p.text(),
});

export const posts = p.pgTable('posts', {
	id: p.integer().primaryKey(),
	content: p.text(),
	authorId: p.integer('author_id'),
});

export const relations = defineRelations({ users, posts }, (r) => ({
  posts: {
    author: r.one.users({
      from: r.posts.authorId,
      to: r.users.id,
    }),
  },
  users: {
    posts: r.many.posts(),
  },
}));

사용자와 그들의 게시물을 포함하는 쿼리를 최적화하려면 posts 테이블의 authorId 컬럼에 인덱스를 생성하세요.

import * as p from "drizzle-orm/pg-core";
import { defineRelations } from 'drizzle-orm';

export const users = p.pgTable('users', {
	id: p.integer().primaryKey(),
	name: p.text(),
});

export const posts = p.pgTable('posts', {
	id: p.integer().primaryKey(),
	content: p.text(),
	authorId: p.integer('author_id'),
}, (t) => [
  index('posts_author_id_idx').on(table.authorId)
]);

export const relations = defineRelations({ users, posts }, (r) => ({
  posts: {
    author: r.one.users({
      from: r.posts.authorId,
      to: r.users.id,
    }),
  },
  users: {
    posts: r.many.posts(),
  },
}));
CREATE INDEX idx_posts_author_id ON posts (author_id);

다대다 관계

중간 테이블을 사용하여 구현되는 다대다 관계는 최적의 쿼리 성능을 보장하기 위해 약간 더 세밀한 인덱싱 전략이 필요합니다. usersToGroups 중간 테이블을 사용한 “users and groups” 예제를 고려해 보세요.

다대다 관계의 경우 일반적으로 중간 테이블에 다음 인덱스를 생성하는 것이 권장됩니다:

  1. 각 외래 키 컬럼에 개별 인덱스: 이것은 관계의 한 쪽을 기반으로 필터링하거나 조인하는 쿼리를 최적화합니다 (예: 사용자의 모든 그룹 찾기 또는 그룹의 모든 사용자 찾기).
  2. 두 외래 키 컬럼의 복합 인덱스: 이것은 다대다 관계 자체를 효율적으로 해결하는 데 중요합니다. 두 엔티티 간의 연결을 찾아야 하는 쿼리의 속도를 높입니다.

왜 중요한가요

특히 Drizzle ORM에서 through를 사용할 때 다대다 관계를 쿼리할 때 데이터베이스는 중간 테이블을 효율적으로 탐색해야 합니다.

  • 개별 외래 키 컬럼(usersToGroupsuserId, groupId)의 인덱스는 한 쪽에서 다른 쪽을 찾는 쿼리를 할 때 도움이 됩니다 (예: “사용자의 그룹 찾기”).
  • usersToGroups(userId, groupId) 복합 인덱스는 중간 테이블에 정의된 모든 관계를 빠르게 찾는 데 특히 중요합니다. 이는 Drizzle ORM이 관련 엔티티를 가져오기 위해 다대다 관계를 해결할 때 사용됩니다.

예제:

“users and groups” 예제에서 usersToGroups 중간 테이블은 usersgroups를 연결합니다.

import { defineRelations } from 'drizzle-orm';
import * as p from 'drizzle-orm/pg-core';

export const users = p.pgTable('users', {
  id: p.integer().primaryKey(),
  name: p.text(),
});

export const groups = p.pgTable('groups', {
  id: p.integer().primaryKey(),
  name: p.text(),
});

export const usersToGroups = p.pgTable(
  'users_to_groups',
  {
    userId: p.integer('user_id')
      .notNull()
      .references(() => users.id),
    groupId: p.integer('group_id')
      .notNull()
      .references(() => groups.id),
  },
  (t) => [p.primaryKey({ columns: [t.userId, t.groupId] })],
);

export const relations = defineRelations({ users, groups, usersToGroups },
  (r) => ({
    users: {
      groups: r.many.groups({
        from: r.users.id.through(r.usersToGroups.userId),
        to: r.groups.id.through(r.usersToGroups.groupId),
      }),
    },
    groups: {
      participants: r.many.users(),
    },
  })
);

사용자와 그룹에 대한 쿼리를 최적화하려면 다음과 같이 usersToGroups 테이블에 인덱스를 생성하세요:

import { defineRelations } from 'drizzle-orm';
import * as p from 'drizzle-orm/pg-core';

export const users = p.pgTable('users', {
  id: p.integer().primaryKey(),
  name: p.text(),
});

export const groups = p.pgTable('groups', {
  id: p.integer().primaryKey(),
  name: p.text(),
});

export const usersToGroups = p.pgTable(
  'users_to_groups',
  {
    userId: p.integer('user_id')
      .notNull()
      .references(() => users.id),
    groupId: p.integer('group_id')
      .notNull()
      .references(() => groups.id),
  },
  (t) => [
    p.primaryKey({ columns: [t.userId, t.groupId] }),
    p.index('users_to_groups_user_id_idx').on(table.userId),
    p.index('users_to_groups_group_id_idx').on(table.groupId),
    p.index('users_to_groups_composite_idx').on(table.userId, table.groupId),
  ],
);

export const relations = defineRelations({ users, groups, usersToGroups },
  (r) => ({
    users: {
      groups: r.many.groups({
        from: r.users.id.through(r.usersToGroups.userId),
        to: r.groups.id.through(r.usersToGroups.groupId),
      }),
    },
    groups: {
      participants: r.many.users(),
    },
  })
);
CREATE INDEX idx_users_to_groups_user_id ON users_to_groups (user_id);
CREATE INDEX idx_users_to_groups_group_id ON users_to_groups (group_id);
CREATE INDEX idx_users_to_groups_composite ON users_to_groups (userId, groupId);

이러한 인덱싱 전략을 적용하면 특히 데이터 볼륨이 증가하고 쿼리가 복잡해질수록 관계형 데이터로 작업할 때 Drizzle ORM 애플리케이션의 성능을 크게 향상시킬 수 있습니다. 특정 쿼리 패턴과 애플리케이션 요구 사항에 가장 적합한 인덱스를 선택하는 것을 기억하세요.

---

외래 키

relations가 외래 키와 유사해 보인다는 것을 눈치채셨을 것입니다. 심지어 references 속성도 있습니다. 그렇다면 차이점은 무엇일까요?

외래 키는 테이블 간의 관계를 정의하는 유사한 목적을 수행하지만 relations와 비교하여 다른 수준에서 작동합니다.

외래 키는 데이터베이스 수준 제약조건으로, 모든 insert/update/delete 작업에서 확인되며 제약조건이 위반되면 오류가 발생합니다. 반면에 relations는 상위 수준 추상화로, 애플리케이션 수준에서만 테이블 간의 관계를 정의하는 데 사용됩니다. 데이터베이스 스키마에 어떤 방식으로도 영향을 주지 않으며 암시적으로 외래 키를 생성하지 않습니다.

이것이 의미하는 바는 relations와 외래 키를 함께 사용할 수 있지만 서로 의존하지 않는다는 것입니다. 외래 키를 사용하지 않고 relations를 정의할 수 있으며 (그 반대도 가능), 외래 키를 지원하지 않는 데이터베이스에서도 사용할 수 있습니다.

다음 두 예제는 Drizzle 관계형 쿼리를 사용하여 데이터를 쿼리하는 측면에서 정확히 동일하게 작동합니다.

schema1.ts
schema2.ts
export const users = p.pgTable("users", {
  id: p.integer().primaryKey(),
  name: p.text(),
});

export const profileInfo = p.pgTable("profile_info", {
  id: p.integer().primaryKey(),
  userId: p.integer("user_id"),
  metadata: p.jsonb(),
});

export const relations = defineRelations({ users, profileInfo }, (r) => ({
  users: {
    profileInfo: r.one.profileInfo({
      from: r.users.id,
      to: r.profileInfo.userId,
    }),
  },
}));

관계 명확화

Drizzle은 또한 동일한 두 테이블 간에 여러 관계를 정의할 때 관계를 명확하게 구분하는 방법으로 alias 옵션을 제공합니다. 예를 들어 authorreviewer 관계가 있는 posts 테이블을 정의하는 경우입니다.

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

export const users = pgTable('users', {
	id: integer('id').primaryKey(),
	name: text('name'),
});

export const posts = pgTable('posts', {
	id: integer('id').primaryKey(),
	content: text('content'),
	authorId: integer('author_id'),
	reviewerId: integer('reviewer_id'),
});

export const relations = defineRelations({ users, posts }, (r) => ({
  users: {
    posts: r.many.posts({
      alias: "author",
    }),
    reviewedPosts: r.many.posts({
      alias: "reviewer",
    }),
  },
  posts: {
    author: r.one.users({
      from: r.posts.authorId,
      to: r.users.id,
      alias: "author",
    }),
    reviewer: r.one.users({
      from: r.posts.authorId,
      to: r.users.id,
      alias: "reviewer",
    }),
  },
}));

문제 해결