Migrations
Seed

Seed migration

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:


// before
import { SnapletClient } from '@snaplet/seed'
const snaplet = new SnapletClient({ /* options */ })

We now instead provide a promise-returning createSeedClient function for instantiating the client:


// after
import { createSeedClient } from '@snaplet/seed'
const seed = await createSeedClient({ /* options */ })

You will also need to update your generated assets (opens in a new tab):

>_ terminal

npx snaplet generate

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 snaplet.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 snaplet.config.ts file:

snaplet.config.ts

/// <reference path=".snaplet/snaplet.d.ts" />
import { defineConfig } from 'snaplet';
export default defineConfig({
seed: {
alias: {
inflection: false,
},
},
});

Don't forget to regenerate your types and assets after updating your config by running:

>_ terminal

npx snaplet generate

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.

seed.mts

// calling a model multiple times works and will continue the id sequences
await snaplet.users([{}]);
await snaplet.users([{}]);

All the generated data is now centralized into a global store available as the $store property on the data client.

seed.mts

await snaplet.users([{}]);
// { users: [ { id: 1, email: 'john.doe@acme.com' } ] }
console.log(snaplet.$store);

You can reset the state of the data client by calling the $reset function.

seed.mts

await snaplet.users([{}]);
snaplet.$reset();

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.

seed.mts

await snaplet.$transaction(async (snaplet) => {
await snaplet.users([{}]);
});

Store as a first-class citizen

Everytime you call a model function, it now returns the generated data as a store.

seed.mts

const singleUserStore = await snaplet.users([{}]);
const usersStore = await snaplet.users(x => x(3));
// { users: [ { id: 1 } ] }
console.log(singleUserStore);
// { users: [ { id: 2 }, { id: 3 }, { id: 4 } ] }
console.log(usersStore);
// { users: [ { id: 1 }, { id: 2 }, { id: 3 }, { id: 4 } ] }
console.log(snaplet.$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.

seed.mts

await snaplet.users(x => x(3));
// it will connect the authors of the posts to the previously generated users
await snaplet.posts(x => x(3), { connect: true });

connect: true is a shorthand for connect: snaplet.$store.

And here is how you can pass a custom store.

seed.mts

// it will connect the authors of the posts to one of these users
const externalStore = { users: [{ id: 42 }, { id: 5432 }]} };
await snaplet.posts(x => x(3), { connect: externalStore });

Good bye operators!

We are removing the pipe and merge operators in light of the previous changes.

If previously you had something like this:

seed.mts

await snaplet.$pipe([
snaplet.users(x => x(3)),
snaplet.posts(x => x(3), { autoConnect: true }),
]);

It is now equivalent to:

seed.mts

await snaplet.users(x => x(3));
await snaplet.posts(x => x(3), { connect: true });

Or if you don't want to connect on the global store:

seed.mts

const usersStore = await snaplet.users(x => x(3));
await snaplet.posts(x => x(3), { connect: usersStore });

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:

>_ terminal

npm install -D @snaplet/seed

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.

>_ terminal

npx snaplet generate

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 snaplet.config.ts and port your run function to a seed script.

Given this configuration.

snaplet.config.ts

/// <reference path=".snaplet/snaplet.d.ts" />
import { defineConfig } from 'snaplet';
export default defineConfig({
generate: {
alias: {
inflection: true,
},
async run(snaplet) {
await snaplet.users((x) => x(3, () => ({
posts: (x) => x(10),
}))),
},
},
});

  • Copy the content of your run function and remove it.
  • Rename the generate key in your config to seed:
snaplet.config.ts

/// <reference path=".snaplet/snaplet.d.ts" />
import { defineConfig } from 'snaplet';
export default defineConfig({
seed: {
alias: {
inflection: true,
},
},
});

Create a new seed.mts file and import the Snaplet data client.

snaplet.config.ts
seed.mts

import { SnapletClient } from '@snaplet/seed';

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().

snaplet.config.ts
seed.mts

import { SnapletClient } from '@snaplet/seed';
const snaplet = new SnapletClient();
snaplet.$resetDatabase();

Paste what you copied earlier from your run function.

snaplet.config.ts
seed.mts

import { SnapletClient } from '@snaplet/seed';
const snaplet = new SnapletClient();
snaplet.$resetDatabase();
await snaplet.users((x) => x(3, () => ({
posts: (x) => x(10),
})));

Given this configuration.

  • Copy the content of your run function and remove it.
  • Rename the generate key in your config to seed:

Create a new seed.mts 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.

snaplet.config.ts

/// <reference path=".snaplet/snaplet.d.ts" />
import { defineConfig } from 'snaplet';
export default defineConfig({
generate: {
alias: {
inflection: true,
},
async run(snaplet) {
await snaplet.users((x) => x(3, () => ({
posts: (x) => x(10),
}))),
},
},
});

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).

>_ terminal

npx tsx seed.mts

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.

seed.mts

import { SnapletClient } from '@snaplet/seed';
const snaplet = new SnapletClient({ dryRun: true });
// With the dryRun option, the data client will output all the SQL queries to stdout.
await snaplet.users((x) => x(3, () => ({
posts: (x) => x(10),
})));

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:

>_ terminal

snaplet config generate

Now, let's edit your snaplet.config.ts file.

This is what we had before version 0.66.0.

snaplet.config.ts

/// <reference path=".snaplet/snaplet.d.ts" />
import { defineConfig } from 'snaplet';
export default defineConfig({
generate: {
plan({ snaplet }) {
return snaplet.User({
count: 3,
data: {
email: ({ seed }) =>
copycat.email(seed),
Post: {
count: 10,
}
}
}),
},
},
});

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.

snaplet.config.ts

/// <reference path=".snaplet/snaplet.d.ts" />
import { defineConfig } from 'snaplet';
export default defineConfig({
generate: {
async run({ snaplet }) {
await snaplet.User({
count: 3,
data: {
email: ({ seed }) =>
copycat.email(seed),
Post: {
count: 10,
}
}
}),
},
},
});

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.

snaplet.config.ts

/// <reference path=".snaplet/snaplet.d.ts" />
import { defineConfig } from 'snaplet';
export default defineConfig({
generate: {
async run(snaplet) {
await snaplet.User({
count: 3,
data: {
email: ({ seed }) =>
copycat.email(seed),
Post: {
count: 10,
}
}
}),
},
},
});

We no longer need count and data, you can pass the values directly.

snaplet.config.ts

/// <reference path=".snaplet/snaplet.d.ts" />
import { defineConfig } from 'snaplet';
export default defineConfig({
generate: {
async run(snaplet) {
await snaplet.User({
email: ({ seed }) =>
copycat.email(seed),
Post: {}
}),
},
},
});

To create lists of data, you can either pass an array or use our x helper function passed in a callback.

snaplet.config.ts

/// <reference path=".snaplet/snaplet.d.ts" />
import { defineConfig } from 'snaplet';
export default defineConfig({
generate: {
async run(snaplet) {
await snaplet.User((x) => x(3, () => ({
email: ({ seed }) =>
copycat.email(seed),
Post: (x) => x(10),
}))),
},
},
});

Congrats, you are now using the new API!

snaplet.config.ts

/// <reference path=".snaplet/snaplet.d.ts" />
import { defineConfig } from 'snaplet';
export default defineConfig({
generate: {
async run(snaplet) {
await snaplet.User((x) => x(3, () => ({
email: ({ seed }) =>
copycat.email(seed),
Post: (x) => x(10),
}))),
},
},
});

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!

snaplet.config.ts

/// <reference path=".snaplet/snaplet.d.ts" />
import { defineConfig } from 'snaplet';
export default defineConfig({
generate: {
plan({ snaplet }) {
return snaplet.User({
count: 3,
data: {
email: ({ seed }) =>
copycat.email(seed),
Post: {
count: 10,
}
}
}),
},
},
});

To learn more about the new API, check out the documentation.