npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

data-bakery

v1.1.1

Published

A tool to prepare test SQL data for integration tests

Downloads

25

Readme

:croissant: Data Bakery

A tool to help build SQL test data for integration tests.

Features

  • Automatically keeps track of important row IDs (Named IDs)
  • Exports named IDs for easy use in tests
  • Removes the need for manually keeping track of auto increment IDs

Getting started

# Install the package
npm i data-bakery

# Set up your config and recipe files

# Generate the SQL files
npx data-bakery generate

Example

Example config: data-bakery.config.js

module.exports = () => ({
  sqlDialect: 'mysql',
  recipesDir: 'recipes',
  outputDir: 'output',
})

Example recipe: recipes/user-vip.js

const { NamedId } = require('data-bakery')

module.exports = async () => ({
  user: [
    {
      id: new NamedId('importantUser'),
      email: '[email protected]',
      is_important: 'Y',
    },
  ],
})

Example output

Run the generate command to build the SQL files and meta.json:

npx data-bakery generate
Files
.
├── data-bakery.config.js
├── output
│   ├── 1-user-vip.sql
│   └── meta.json
└── recipes
    └── user-vip.js
output/meta.json
{
  "namedIds": {
    "user": {
      "importantUser": {
        "id": 1
      },
    }
  }
}
output/1-user-vip.sql
INSERT INTO `user` (id,email,is_important) VALUES (1, '[email protected]', 'Y');

You can now seed your test MySQL instance with the SQL files in the output directory.

Query for the rows using their named IDs in output/meta.json, like so:

import * as testIds from '../output/meta.json'
test('Check user is important', () => {
  const userId = testIds.user.importantUser.id

  // Load the user and perform checks
})

Recipes

A recipe is a collection of data for one or more tables.

For example, a userWithOrders recipe could contain data for the users, orders, and products tables.

Recipe files must be within in the recipesDir directory.

Recipes are maps, with table names as keys, and an array of rows as their value.

// This recipe exports two columns for the `user` table, and another two for the `orders` table
module.exports = {
  user: [
    { id: new AutoIncId(), email: '[email protected]' },
    { id: new NamedId('specialUser'), email: '[email protected]' },
  ],
  orders: [
    { id: new AutoIncId(), userId: getNamedId('user', 'specialUser'), amount: 5000 },
    { id: new AutoIncId(), userId: getNamedId('user', 'specialUser'), amount: 15000 },
  ],
}

You can also export a function or async function if you want to perform more complex logic:

module.exports = async () => ({
  user: [
    { id: new AutoIncId(), email: '[email protected]' },
    { id: new NamedId('specialUser'), email: '[email protected]' },
  ],
})

Function recipes are passed in a recipeContext object. You can add custom data to it with the extraRecipeContext config option.

This recipe, for example, would generate an SQL query like this:

INSERT INTO user (id, email)
VALUES
    (1, '[email protected]'),
    (2, '[email protected]');

Recipe context

By default, all function recipes are provided a recipe context like this:

{
  sqlDialect: 'mysql',
  manager: RecipeManager,
}

You can add your own custom data by adding a extraRecipeContext option to your config file.

module.exports = () => ({
  recipesDir: 'recipes',
  sqlDialect: 'mysql',
  outputDir: 'actual-output',
  emptyOutputDir: true,
  outputPrefixStart: 10,
  // These values will be passed to all recipe functions as the first argument
  extraRecipeContext: () => {
    return {
      hello: 'world',
    }
  },
})

Generated IDs

There are two types of IDs to use in your recipes:

  • AutoIncId: A simple AUTO INCREMENT value, tracked for each table
  • NamedId: A special ID that should be exported in the meta.json file

Both of these IDs work together, even in the same recipe. The key difference is a named ID gets exported so it can be easily used in tests.

The values are tracked across multiple recipes, so you can create any number of rows in any amount of recipe files.

Named IDs

One of the most powerful features is the ability to create named IDs, which work like bookmarks.

You can generate a row and give it a unique name to reference it later, for example:

{
  'customer': [
    { id: new NamedId('customerWithNoOrders'), name: 'John'},
  ]
}

To reference a named ID in another recipe, you can use: getNamedId(tableName, idName):

module.exports = {
  user: [
    {
      id: new NamedId('specialUser'),
      email: '[email protected]'
    },
  ],
  orders: [
    {
      id: new AutoIncId(),
      // This will get the same ID value generated in the `user` table data
      userId: getNamedId('user', 'specialUser'),
      amount: 5000
    },
  ],
}

If you want to access the named ID outside of a simple column value use case, you should export your recipe as a function and use manager.getNamedId() instead.

module.exports = async ({ manager }) => ({
  user: [
    { id: new NamedId('firstUser'), email: '[email protected]' },
  ],
  user_extra: [
    {
      json: JSON.stringify({
        // This will evaluate to `1-static-prefix` for example
        generatedId: `${manager.getNamedId('user', 'firstUser')}-static-prefix`,
      }),
    },
  ],
})

After running the generate command, a meta.json file will be created.

You can import this JSON file in your tests to reference the generated ID by its name.

import * as testIds from 'output/meta.json'

test('Check customer with no orders returns empty array', () => {
    const customerId = testIds.customer.customerWithNoOrders.id

    // Try and fetch the customer's orders
})

You don't need to keep track of specific IDs, or worry about the ordering of INSERT statements.

Auto increment IDs

An AutoIncId is similar to a NamedId, except that they aren't exported into meta.json.

They merely serve as a way to avoid tracking IDs manually.

{
  'user': [
    // Alice will be ID: 1
    { id: new AutoIncId(), name: 'Alice'},
    // Bob will be ID: 2
    { id: new NamedId('specialUser'), name: 'Bob'},
  ]
}

Config

data-bakery.config.js options

  • recipesDir: string: The directory that contains your recipe files
    • e.g. recipes
  • outputDir: string: The directory to save the generated SQL and meta.json files
    • e.g. sql or output
  • emptyOutputDir: boolean: Whether the outputDir should be emptied before generating new files
    • This prevents stale/unexpected output by starting from a clean slate each run
  • outputPrefixStart: string: The start prefix to use for generated SQL file names
    • e.g. 10 to output 10-myrecipe.sql as the first file
  • sqlDialect: string: Which SQL dialect to generate
    • Currently only mysql is supported
  • extraRecipeContext: () => object: A custom function to allow adding your own data into the recipe context passed to all exported recipe functions
  • tableDefaults: Record<tableName, () => object>: A function to generate default data for all rows in specific tables
  • tableStartIds: Record<tableName, number>: A map, keyed by table name, of values to start incrementing IDs from
    • e.g. { 'user': 100 } would use 100 for the first AutoIncId value

Table defaults

In your data-bakery.config.js, you can define defaults like this:

  tableDefaults: {
    // All `user` rows will have these values added automatically, and can be overridden in individual recipes
    user: () => ({
      id: new AutoIncId()
      extra: 'column-value-here',
    }),
  },

CLI options

  • --debug: Enable debug logging
  • -s, --sql-dialect <dialect>: Override the dialect in the config file
  • -d, --output-dir <directory>: Override the outputDir in the config file
  • -r, --recipes-dir <directory>: Override the recipesDir in the config file

.databakeryignore

You can optionally include a .databakeryignore file inside your outputDir directory to retain some files, even if the emptyOutputDir option is enabled.

This works similarly to .gitignore, each line is a glob pattern of files to keep.

See tests/snapshot/tests/databakeryignore/expected-output/.databakeryignore for an example.


Build steps

To build as a local NPM package:

# Install dependencies
npm ci
# Compile TS and generate .d.ts types
rm -rf dist
npx tsc
# Copy the package.json into dist to avoid needing `dist` in imports
cp package.json ./dist/
cp .npmignore ./dist/
cp README.md ./dist/
cp LICENSE.md ./dist/

# Build a local package in the root directory
(cd dist && npm pack --pack-destination ../)

You can then install the package in another local project like this:

npm i /path/to/data-bakery-0.0.1.tgz