Migrations
From 0.90.0
and above
Welcome to a leaner @snaplet/seed
Hello, @snaplet/seed
community! With version 0.90.0, we're excited to bring you a suite of enhancements aimed at refining and streamlining your experience.
This version introduces breaking changes, but fear not—we're here to guide you through them smoothly.
Why the Change?
Our goal is to sharpen @snaplet/seed
's focus, simplifying both maintenance and your development workflows.
These changes will differentiate @snaplet/seed
from our snapshot tool, making it more intuitive and powerful for your projects.
What's New?
- SQLite Support: We're rolling out alpha support for SQLite databases, expanding your options.
- Enhanced API Methods: The
$resetDatabase
method now supports selective table resets, offering more control. - PostgreSQL Improvements: Expect fewer bugs, especially around unique constraints during seeding.
Key Changes to Note
- Seed CLI: The
@snaplet/seed
now has its own CLI. - New Configuration File: Transition from
snaplet.config.ts
toseed.config.ts
for a@snaplet/seed
specific setup. - Simplified Syncing: A single
npx @snaplet/seed sync
command now replaces the previousgenerate
andintrospect
commands, streamlining your workflow.
@snaplet/seed
now has its own CLI
If you are not using our Snapshot tool, you can uninstall the snaplet
CLI.
Start your @snaplet/seed
journey using the new onboarding wizard command:
You can read the new CLI reference to learn about all the available commands.
Dive into seed.config.ts
Adapting with "adapter"
The introduction of a "database adapter" parameter in seed.config.ts
is a significant update. Here's how you might configure the Prisma adapter:
import { SeedPrisma } from "@snaplet/seed/adapter-prisma";import { defineConfig } from "@snaplet/seed/config";// You can import the prisma instance from your own code like you would// in your backend codeimport { db } from "./src/utils/db";export default defineConfig({ adapter: () => new SeedPrisma(db),});
We've expanded our adapter offerings, including support for Prisma, pg, postgres-js, and betterSqlite3, with more on the horizon. Custom adapter creation is also possible, enhancing flexibility (Documentation forthcoming).
The new adapter change also means we don't automatically close the database connection for you.
You might now want to call process.exit
to close the connection or your seed script may hang indefinitely.
// at the end of seed.tsprocess.exit(0);
You can read more about the new configuration by checking out the Configuration reference.
Inflection is now opt-in
In response to user feedback, we've transitioned inflection to an opt-in feature, acknowledging that its default behaviour didn't align with the expectations of many within our community. To learn more about what inflection is and what it's for you can read the documentation here.
export default defineConfig({ // Additional configuration options alias: { // Opt-in to enable inflection behavior inflection: true, }});
Selective Sync with "select"
The "select" option has been overhauled for greater flexibility, adopting a glob pattern matching (opens in a new tab) approach to include or exclude tables:
export default defineConfig({ select: [ '!some_schema_name*', // Exclude all tables under a schema '!public._*', // Exclude all tables prefixed by _ in the schema public 'public._specific_table', // include a specific table even with the prefix ]});
All tables are included by default, if you prefix a pattern with !
everything matching that pattern will be excluded.
The patterns apply in their declaration order.
Refined $resetDatabase
Usage
Fine-tune data resets with the $resetDatabase
method, allowing you to persist certain tables across seeding operations:
import { createSeedClient } from "@snaplet/seed";const seed = await createSeedClient();await seed.$resetDatabase(); // Resets, respecting exclusions from config// Example usage in teststest('your_test', () => {...});test('another_test', async () => { await seed.$resetDatabase([ '!public.users', // Keeps user data intact ]);});
$transaction
and $reset
methods have been removed
To get a new instance of the client, you can simply use createSeedClient
to get a new instance.
// beforeawait snaplet.$transaction(async (snaplet) => { await snaplet.users([{}]);});// afterconst snaplet = await createdSeedClient()await snaplet.users([{}])
// beforeawait snaplet.users([{}]);snaplet.$reset()// afterlet snaplet = await createdSeedClient()await snaplet.users([{}])snaplet = await createSeedClient()
Let's Grow Together
We're excited for you to experience the improvements in @snaplet/seed
v0.90.0.
Your feedback is invaluable to us, join the conversation on Discord (opens in a new tab) and let's make @snaplet/seed
even better together.
From 0.85.0
and above
🚨 Breaking changes ahead 🚨
We're refining the API and getting closer and closer to a stable v1 release. 🎉
Creating a client is now async
We've changed the way in which the client is instantiated.
Previously, you would instantiate a class like this:
// beforeimport { SnapletClient } from '@snaplet/seed'const snaplet = new SnapletClient({ /* options */ })
We now instead provide a promise-returning createSeedClient
function for instantiating the client:
// afterimport { createSeedClient } from '@snaplet/seed'const seed = await createSeedClient({ /* options */ })
You will also need to update your generated assets (opens in a new tab):
This change is due to the fact that we need to scan your database at instantiation time in order to fetch your sequences current values.
Also, you noticed that we changed our naming convention, from snaplet
to seed
. We think it's easier to understand this way.
Better compatibility with non-empty databases
@snaplet/seed
now works better with tables that already have existing data in them. For tables with sequences, if you have existing data in them when using @snaplet/seed
,
any new rows generated by @snaplet/seed
will start off the sequences where the existing data left off.
For example, if you have a table with a SERIAL PRIMARY KEY
, and there are 10 rows in a table already (so you already have primary keys from 1
to 10
),
@snaplet/seed
will start generating primary keys from 11
onwards.
Model callback API
We are introducing a new way of definining models when you need contextual data. It impacts both parents and children fields.
Children fields
You can now define children fields as a callback function that receives the model's seed and the index of the current iteration:
await seed.posts([ // model level seed ({ index, seed }) => ({ title: `Post #${index}`, content: `This post's seed is ${seed}` }), // static data is of course still supported { title: 'Static post', // field level seed content: ({ seed }) => copycat.paragraph(seed) },]);
This change is also reflected into the x
helper function which now supports static data as well:
await seed.posts(x => x(3, (ctx) => ({ title: `Post #${ctx.index}`, content: `This post's seed is ${ctx.seed}`})));await seed.posts(x => x(3, { title: 'Static post', content: (ctx) => copycat.paragraph(ctx.seed)}));
Parent fields
You can now define parent fields as a callback function that receives the model's seed and a connect
function. It finally makes connecting data more explicit:
await seed.posts([ { // the author will be connected to the first user in the store author: (ctx) => ctx.connect(({ store }) => store.users[0]) }, { // a new author will be generated author: (ctx) => { const createdAt = new Date(copycat.dateString(ctx.seed)) const day = 1000 * 60 * 60 * 24 const updatedAt = new Date(createdAt.getTime() + day) return { createdAt, updatedAt, } } },])
Fix the seed
value for parent fields
We were injecting the model's seed for all parent fields in a model. We're now injected a per parent field seed to have more variety in connected data.
As the seed value changed, it means that you might experience different models being picked up when using connect
.
Inflection is now turned on by default
You no longer need a seed.config.ts
file to enable inflection. It's now turned on by default.
We think inflection brings a better developer experience and we want to make it the default.
If you want to disable it, you can pass false
to the inflection
option in your seed.config.ts
file:
Don't forget to regenerate your types and assets after updating your config by running:
From 0.83.0
and above
We've removed the modelSeed
parameter that we pass to generate functions in favor of a new data
parameter.
Previously, if you had a field with a value that depended on another field, modelSeed
provided a way to accomplish this:
// **before**await snaplet.users([{ createdAt: ({ modelSeed }) => copycat.dateString(modelSeed), updateAt: ({ modelSeed }) => { const createdAt = copycat.dateString(modelSeed) const updatedAtMs = Number(new Date(createdAt)) + 60_000 return new Date(updatedAtMs).toISOString() }}]
Since we now have the data
parameter, we have a more natural way of obtaining the value generated for previous values:
// **after**await snaplet.users([{ createdAt: ({ seed }) => copycat.dateString(seed), updateAt: ({ data }) => { const updatedAtMs = Number(new Date(data.createdAt)) + 60_000 return new Date(updatedAtMs).toISOString() }}]
From 0.81.0
and above
Stateful data client
The SnapletClient
is now stateful.
It means that a global seed is now incremented every time you call a model function on the data client.
All the generated data is now centralized into a global store available as the $store
property on the data client.
You can reset the state of the data client by calling the $reset
function.
And you can get a new instance of the data client by calling the $transaction
function.
It receives a data client which state is reset.
It's particularly useful in tests when you want a new client for each test.
Store as a first-class citizen
Everytime you call a model function, it now returns the generated data as a store.
Fine-grained connections
autoConnect
is renamed to connect
and now accepts either true
or a store.
It allows for more flexibility when you want to connect generated data to existing data.
connect: true
is a shorthand for connect: snaplet.$store
.
And here is how you can pass a custom store.
Good bye operators!
We are removing the pipe
and merge
operators in light of the previous changes.
If previously you had something like this:
It is now equivalent to:
Or if you don't want to connect on the global store:
From 0.76.0
and above
In this version, we are removing the run
function from the generate
configuration.
Instead, we're introducing a new package called @snaplet/seed
.
We extracted the data client into its own package so you can use it everywhere in your codebase!
Seed scripts or tests, data are now one import away! Let's see how to migrate to it.
First, install the new package:
Like the Prisma Client (opens in a new tab), this package is in fact a proxy to the local data client you will generate with Snaplet CLI based on your database schema.
In order to do that, we repurposed the command snaplet generate
to generate the data client! You can now run it.
It will create a folder containing your data client source code under node_modules/.snaplet
. This package is then re-exported by @snaplet/seed
.
Now, let's edit your seed.config.ts
and port your run
function to a seed script.
Given this configuration.
- Copy the content of your
run
function and remove it. - Rename the
generate
key in your config toseed
:
Create a new seed.ts
file and import the Snaplet data client.
Create a new snaplet
client instance.
If you were relying on snaplet to clear any existing data from your database first, you now do this explicitly with snaplet.$resetDatabase()
.
Paste what you copied earlier from your run
function.
Given this configuration.
- Copy the content of your
run
function and remove it. - Rename the
generate
key in your config toseed
:
Create a new seed.ts
file and import the Snaplet data client.
Create a new snaplet
client instance.
If you were relying on snaplet to clear any existing data from your database first, you now do this explicitly with snaplet.$resetDatabase()
.
Paste what you copied earlier from your run
function.
The snaplet
data client is automatically connected to your target database defined in either your .snaplet/config.json
file or your SNAPLET_TARGET_DATABASE_URL
environment variable.
You can now run your seed script with any TypeScript runner like tsx
(opens in a new tab) or ts-node
(opens in a new tab).
If you were relying on the --sql
option to generate a SQL file instead of having the data client writing directly to your database, you can now use the dryRun
option in the data client.
To know more about the data client, check out the documentation.
From 0.66.0
and above
The generate
API is evolving! After a great number of feedbacks from the community during the alpha, we decided to make it more flexible and powerful.
The new API is still in beta, but we encourage you to try it out and give us feedbacks.
Here is a step-by-step guide to migrate your existing generate
plan to the new API.
You can start by regenerating your types by running:
Now, let's edit your seed.config.ts
file.
This is what we had before version 0.66.0
.
Rename plan
to run
, and make it async. You are no longer required to return a plan. Calling await
on a plan will automatically run it.
We inject directly the snaplet
data client in the run
function.
The utilities and operators are now available directly on the snaplet
object: snaplet.$pipe
, snaplet.$merge
and snaplet.$createStore
.
We no longer need count
and data
, you can pass the values directly.
To create lists of data, you can either pass an array or use our x
helper function passed in a callback.
Congrats, you are now using the new API!
This is what we had before version 0.66.0
.
Rename plan
to run
, and make it async. You are no longer required to return a plan. Calling await
on a plan will automatically run it.
We inject directly the snaplet
data client in the run
function.
The utilities and operators are now available directly on the snaplet
object: snaplet.$pipe
, snaplet.$merge
and snaplet.$createStore
.
We no longer need count
and data
, you can pass the values directly.
To create lists of data, you can either pass an array or use our x
helper function passed in a callback.
Congrats, you are now using the new API!
To learn more about the new API, check out the documentation.