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

graine

v1.0.1

Published

Efficiently populating databases with hierarchical and customizable test data

Downloads

63

Readme

Graine

Graine is a versatile Node.js tool that simplifies the process of populating databases with test data. It allows you to generate structured and realistic data, making it an essential tool for testing and development.

Features

  • Hierarchical Data Generation: Graine can create complex, nested data structures with ease, including one-to-one, one-to-many, many-to-many relationships, and even deep hierarchies.
  • Customizable Data Templates: You can tailor the generated data to match your database schema requirements, ensuring accurate testing scenarios.
  • Efficiency and Speed: Graine is designed for efficiency and speed, making it suitable for projects with large-scale data seeding needs.
  • Cross-Framework Compatibility: It can seamlessly integrate with various databases, ORMs, and frameworks, making it a versatile choice for a wide range of projects.

Getting Started

To get started with Graine, follow these simple steps:

Installation

You can install Graine using npm or yarn:

npm install -D graine

Usage

This example demonstrates seeding data for user(s) and channel(s), since having a user requires a channel, we use refs to define the relationship between the two and the seeder will automatically handle the relationship. You can configure factories according to your project's needs.

import Graine, { SeederFactory, ISeederWriter } from 'graine';
import { faker } from '@faker-js/faker';

class MyDatabaseWriter implements ISeederWriter {
  async insert(tableName: string, primaryKey: string, data: object): Promise<number> {
    // insert data into the database...
  }

  async cleanUp(tables?: string[]): Promise<void> {
    // clean up the database... should clean up all tables if no tables are specified
  }
}

Graine.setWriter(new MyDatabaseWriter());

class UserFactory extends SeederFactory {
  name = 'user';
  tableName = 'users';
  primaryKey = 'userID';
  
  get refs() {
    return [
      ref({ 
        factoryName: 'channel', // reference to the channel factory, which is defined below
        foreignKey: 'channelID'
      }) // one-to-many relationship with a foreign key
    ];
  }

  provider(args, context) {
    return {
      name: faker.person.firstName(),
      phone: faker.phone.imei(),
      age: faker.number.int({ min: 18, max: 60 }),
    };
  }
}

class ChannelFactory extends SeederFactory {
  name = 'channel';
  tableName = 'channels';
  primaryKey = 'channelID';

  provider(args, context) {
    return {
      name: faker.word.noun(),
    };
  }
}

Graine.register(new UserFactory());
Graine.register(new ChannelFactory());

// Seed multiple users, with the same channel
const channelID = await Graine.seed('channel',  { name: 'General Channel' });
await Graine.seedMany('user', { count: 2, args: { channelID } });

// Seed multiple users, with the different channels
await Graine.seedMany('user', { count: 2 });

// Seed multiple users, with the same channel
await Graine.seedMany('user', { count: 2, resuseRefs: false });

// Clean up
Graine.cleanUp('users', 'channels');

// Clean up all factories?
Graine.cleanUp();

Showcase (Messaging App)

This example demonstrates a messaging app scenario, where we have users, channels, channel users, and messages. We use refs to define the relationships between the factories, and the seeder will automatically handle the relationships.

class UserFactory extends SeederFactory {
  name = 'user';
  tableName = 'users';
  primaryKey = 'userID';

  provider(args, context) {
    return {
      name: args.name ?? faker.person.firstName(),
      phone: args.phone ?? faker.phone.imei(),
      age: args.age ?? faker.number.int({ min: 18, max: 60 }),
    };
  }
}

class ChannelFactory extends SeederFactory {
  name = 'channel';
  tableName = 'channels';
  primaryKey = 'channelID';

    get refs() {
    return [
      ref({ 
        factoryName: 'user',
        foreignKey: 'createdBy'
      })
    ];
  }

  provider(args, context) {
    return {
      name: args.name ?? faker.word.noun(),
    };
  }

  after(args, context, seeder) {
    // after creating a channel, we also want to add the creator as a channel user
    return seeder.seed('channel_user', { channelID: context.channel, userID: context.user || args.userID });
  }
}

class ChannelUserFactory extends SeederFactory {
  name = 'channel_user';
  tableName = 'channel_user';
  primaryKey = 'id';

  get refs() {
    return [
      ref({ 
        factoryName: 'channel',
        foreignKey: 'channelID'
      }),
      ref({ 
        factoryName: 'user',
        foreignKey: 'userID'
      })
    ];
  }

  provider(args, context) {
    return {
      joinedAt: args.joinedAt ?? faker.date.recent(),
    };
  }
}

class MessageFactory extends SeederFactory {
  name = 'message';
  tableName = 'messages';
  primaryKey = 'messageID';

  get refs() {
    return [
      ref({ 
        factoryName: 'user',
        foreignKey: 'sentBy'
      }),
      ref({ 
        factoryName: 'channel',
        foreignKey: 'channelID'
      })
    ];
  }

  provider(args, context) {
    return {
      content: args.content ?? faker.lorem.sentence(),
      sentAt: args.sentAt ?? faker.date.recent(),
    };
  }
}

Graine.register(new UserFactory());
Graine.register(new ChannelFactory());
Graine.register(new ChannelUserFactory());
Graine.register(new MessageFactory());

describe('Messaging', () => {
  afterEach(() => {
    Graine.cleanUp();
  });

  it('should allow channel owner to send messages', async () => {
    // This seeds a channel, and a user who is the owner of the channel.
    // The channel user is automatically created by the ChannelFactory's after hook
    const [,, context] = await Graine.seed('channel', { name: 'General' });

    const subject = MessagineService.sendMessage({
      content: 'Hello, World!',
      sentBy: context.user.userID,
      channelID: context.channel.channelID,
    });

    await expect(subject).resolves.toEqual(expect.objectContaining({ status: 'success' }));
  });

  it('should not allow non-channel users to send messages', async () => {
    const [,, context] = await Graine.seed('channel', { name: 'General' }); 
    const nonChannelUser = await Graine.seedObject('user');

    const subject = MessagineService.sendMessage({
      content: 'Hello, World!',
      sentBy: nonChannelUser.userID,
      channelID: context.channel.channelID,
    });

    await expect(subject).rejects.toThrowError('User is not a member of the channel');
  });
});

Tests

Graine is thoroughly tested to ensure its reliability and functionality. You can find various test cases in the test folder, covering scenarios like one-to-one, one-to-many, many-to-many relationships, and deep hierarchies.

Contributions

We welcome contributions from the community. If you have any improvements, bug fixes, or new features to add, please open a pull request on our GitHub repository.

License

Graine is licensed under the MIT License. You can find the full license details in the LICENSE file.

Happy seeding!