Relationships
@snaplet/seed
is a tool to help you write seed scripts more easily. It provides a type-safe, "ORM-ish" way to define your seeding logic. A significant part of using a relational database is managing relationships between data entities. In the context of a seed script, "connection" refers to references to existing data rather than creating new data. Here, we'll guide you through the various ways @snaplet/seed
allows you to define such connections. You might not use all of them at once, depending on your needs, but they should all make sense based on the use cases we describe.
Connections overview
@snaplet/seed
allows you to "connect" data in several ways:
- Path connect (or implicit connections): These connections are made when a parent declares children, automatically creating a connection between them.
- Plan connect: This defines a connection pool only for a specific plan, helping to break down your seeding logic into dedicated functions.
- Global connect: This sets the default connection behavior for all plans defined in the seed script, useful for connecting everything across plans or augmenting data for existing rows.
- Field connect: This is the most granular level of connection, allowing you to connect specific columns and rows.
While this may seem like a lot, all connections can be achieved via field connect; the other methods are just shortcuts for convenience.
Let's dive in with an example, using a database schema for a "Slack clone" app:
This schema includes tables and relationships typical of a Slack app:
- Users
- Private messages between users (via
user_message
) - Workspaces
- Workspaces can have members
- Workspace members have a "type" (admin / moderator / member)
- Workspaces can have channels
- Channels can have members
- Channels can have messages
- Channels can have threads
- Threads can have messages
Now, let's look at a seeding scenario:
Path connect
For starters, let's say we want a scenario where we can log in as a user and we want:
- One user
- Author of 2 direct messages
- Recipient of 2 direct messages
- Author of a conversation channel, with a thread, and 3 messages in it
Using path connections, the code looks like this:
Since all elements are children of our user, path connections will connect all references to the currently created user. So, our user will be the author of the channel and the channel thread messages.
However, there's an issue: our user will be both the author and the recipient of all the direct messages. We want different users for authors and recipients. We can enforce the creation of new users for these relations like this:
Plan connect
Sometimes, you need more control over generated data without defining each element. Returning to our example, we have multiple "pools of users" in our app via members.
- A workspace connects to a pool of users via
workspace_members
- A channel connects to a pool of users via
channel_members
We want to ensure:
- All authors and channel members in a workspace are a subset of the workspace members.
- All thread messages in a channel are authored by channel members.
- All direct messages between users are between users sharing at least one workspace.
We'll create pools of users and use plan connections to generate data with the correct relationships:
Now, we can generate more workspaces that match our constraints by adjusting one variable (workspacesNumber
).
Global connect
We have some issues with our data, such as workspace members
being connected to a workspace_user_type
(user roles). We likely want to use the same values: "admin" and "member". We can use plan connections for these, or pass them as global values for our whole client, ensuring every plan uses these values:
Adding this global connect at the beginning of our script ensures all our workspace_members have either the "admin" or "member" role.
Field connect
Sometimes, you need precise control over connections for each row. For example, we might want only one "admin" per workspace, with the rest being "members". We can implement this logic via field connections:
Conclusion
Whether you use path connections for simplicity, plan connections for specific scenarios, global connections for shared configurations, or field connections for fine-grained control, you can efficiently seed your database with meaningful and correctly related data. By understanding and applying these connection strategies, you can ensure your seed data accurately represents the relationships in your database.