Domain configuration
Entities
Seed configuration

Seed Configuration

When developing, testing or deploying an API having reliable test or initial data comes very handy. ActiveQL supports the generation of seed data with some nice convenient features.

ConfigTypeDescription
seedsSeedConfig | SeedConfig[]you can either add a Seed configuration or an array of Seed Configurations to an entity

SeedConfig

ConfigTypeDescription
environmentdevelopment | test | stage | productionif you provide one or more of RuntimeEnvironments those seeds will only be applied in this environment
itemsnumber |
SeedItemConfig[] |
map of SeedItemConfig |
SeedItemConfigsFn
configuring the seed items

Randomly generated items

You can just configure a number of items the Seeder should randomly create like so:

enum: 
  CarBrand: 
    - Mercedes
    - BMW
    - Porsche
entity:
  Driver: 
    assocFrom: Car
    attributes: 
      firstname: String
      lastname: String!
    seeds: 
      items: 30
  Car:
    assocTo: Driver
    attributes:
      brand: CarBrand!
      color:         
        - white
        - black
        - silver
      power: 
        type: Int!
        validation: 
          numericality: 
            greaterThanOrEqualTo: 20
            lessThanOrEqualTo: 200
    seeds:
      items: 100

This would result in 100 items with random values.

ActiveQL tries to guess meaningfull random values for seed items:

  • the type of value will be considerd
  • required attributes will always get a value, non-required attributes will get randomy assigned a value or left undefined
  • Associatons between entities will assigned
  • Int and Float attributes will get random numbers, if applicable within the bounds of their validation
  • For String values will be tried to find a suitable FakerJS (opens in a new tab) call

Take a look at the generated values from the example configuration above (excerpt). You see how the values were somehow meaningfull guessed, so can start testing your API with some near-real-life data.

const cars = [
  {
    brand: 'BMW',
    color: 'white',
    power: 36
    driverId: '0xBO7bUcejJpB5jq',
    id: 'EX0sPq7MHxhLhK1E'
  },
  {
    brand: 'Mercedes',
    power: 115,
    driverId: 'Umm0m7Ku1liqZLcg',
    id: 'czUXFrbPhOmtWQW5'
  },
  {
    brand: 'Porsche',
    color: 'white',
    power: 184,
    id: 'kiUMHfrfelJOgPdM'
  }
]
const drivers = [
  {
    firstname: 'Shayna',
    lastname: 'Gutkowski'
    id: 'MNkTAneqBG1h0mf6'
  },
  {
    lastname: 'Olson'
    id: 'xCd6LaGaNpus68nY'
  },
  {
    firstname: 'Kay',
    lastname: 'Dare'
    id: 'wnRwKxLg6inepS5u'
  }
]

List of seed configurations

You can provide a list of seed items which will be seeded with their values.

entity:
  Car:
    attributes:
      brand: String!
      color: String
    seeds:
      items:
        - brand: Mercedes
          color: silver
        - brand: BMW
          color: black

This would result in 2 items with exactly these values.

Map of seed configurations & referencing other seed items

When seeding more than one entity you might want to access the seeded item from another entity. Let's say you want to seed cars and drivers and decide which driver has which car. You can use the key from the map of the Car seed configuration as value for the assocTo association to Car in Driver.

In the seed configuration use the fieldName of the assocTo configuration. By default this is the typeQueryName of the associated entity, which by default is the lower letter singular of the entity name.

entity:
  Car:
    attributes:
      brand: String!
      color: String
    seeds:
      items:
        silverMercedes:
          brand: Mercedes
          color: silver
        blackBWM:
          brand: BMW
          color: black
 
  Driver:
    assocTo: Car
    attributes:
      firstname: String
      lastname: String!
    seeds:
      items:
        - firstname: Thomas
          lastname: Thompson
          car: silverMercedes

Number as key in seed configuration map

If you want to combine the generation of n items with the configuration of how these items look like, simply use a number as key for the map. The seed item configuration will create exactly this number of items.

entity:
  Car:
    attributes:
      brand: String!
      color: String
    seeds:
      items:
        10:
          brand: Mercedes

This would create 10 car items with the brand attribute set to "Mercedes". We are using a static value "Mercedes" here, which is not always sufficient for creating real-world mimicking seed data. Those static values are in fact just a shortcut for a more complex seed item configuration.

SeedItemConfig

ConfigTypeDescription
attributeNameshortcut | SeedAttributeConfigthe value or configuration how to generate a value

Shortcut: If the item config is just a static value is resolved into a SeedAttributeValue.

OptionDescription
Static ValueSeedAttributeValuesame as value but with the possibility to add a share
Sample from ValuesSeedAttributeSamplevalue should be taken from a list of values, an Enum or reference to another entity
Faker ValueSeedAttributeFakervalue should be generated by the FakerJS (opens in a new tab) library
Random Number ValueSeedAttributeRandomvalue should be a random number
Hash ValueSeedAttributeHashvalue is the hashed value; e.g. for passwords
Formatted Random StringSeedAttributRfsvalue is Formatted Random String

Static Value

SeedAttributeValue

ConfigTypeDefaultDescription
valuestring | number | boolean | string[] | number[] | boolean[]the value of the seeded item
share0..11when generated n time, share % of n items will have the value the others will be undefined
entity:
  Car:
    attributes:
      brand: String
    seeds:
      items:
        100:
          brand:
            value: Mercedes
            share: 0.2

This would result in 100 car items with ca. 20% of items having the value "Mercedes" in the brand attribute, the rest will have no value there.

Sample from Values

SeedAttributeSample

ConfigTypeDefaultDescription
sample(string | number)[] | Enum | Entitythe source from the where a sample should be taken
share0..11when generated n time, share % of n items will have the value the others will be undefined
sizenumber | { min: number, max: number }when not list or assocToMany: 1; random number between 0.1 and 0.9 otherwisewhen the seeded attribute is of type list or the association is of type assocToMany this will determine the size of the sample
enum:
  Brand:
    - Mercedes
    - BMW
    - Porsche
 
entity:
  Car:
    attributes:
      brand: String
      color: String[]
    seeds:
      items:
        100:
          brand:
            sample: Brand
          color:
            sample:
              - green
              - blue
              - black
              - white
            size:
              min: 1
              max: 2
 
 
  Driver:
    assocTo: Car
    attributes:
      firstname: String
      lastname: String!
    seeds:
      items:
        - firstname: Max
          lastname: Maxwell
          Car:
            sample: Car

This would create 100 car items with random assigned values for brand from the Brand enum and for color from the given list of values. The color values will have either 1 or 2 entries. The driver item will get a random car from the just created car items as its assocTo.

Faker Value

SeedAttributeFaker

ConfigTypeDefaultDescription
fakerstringaccording the FakerJS (opens in a new tab) library
share0..11 when the attribute is required; otherwise a random number between 0.1 and 0.9when generated n time, share % of n items will have the value the others will be undefined

The FakerJS (opens in a new tab) library is a great way to generate seed data. You can simply state a seed configuration in the form ${moduleName}.${methodName} and it will be resolved. When using tab-completion (based on the ActiveQL DomainConfiguration JSON schema) it will show all possible values from the library.

entity:
  Car:
    attributes:
      brand: String!
      color: String
    seeds:
      items:
        100:
          brand:
            faker: vehicle.manufacturer
          color:
            faker: vehicle.color
  Driver:
    attributes:
      firstname: String
      lastname: String!
    seeds:
      items:
        100:
          firstname:
            faker: person.firstName
          lastname:
            faker: person.lastName

Random number value

Random values will only be generated for attributes of the type Float or Int.

valuedescription
randomif random is a number it is the upper border (incl.) or maximum for the generated number
random.minthe lower border (incl.) of the generated random number; default: 0
random.maxthe upper border (incl.) of the generated random number; default: 100000 for Int and 1 for Float
entity:
  Example:
    attributes:
      int1: Int
      int2: Int
      float1: Float
      float2: Float
    seeds:
      items:
        100:
          int1:           # any int number 0 ... 10
            random: 10
 
          int2:           # any int number 100 ... 300
            random:
              min: 100
              max: 300
 
          float1:         # any float number 0 ... 1
            random: 1
 
          float2:
            random:       # any float number 1 ... 3
              min: 1
              max: 3

Hash

If you want to seed a hashed password field e.g. used for the JWTLogin included in the starter application you can use this method. Please be aware that this method is by factor 100 slower than any every other method.

entity:
  User:
    attributes:
      username: String
      roles: String[]
      password: String
    seeds:
      items:
      - username: User1
        roles: guest
        password:
          hash: passwordForUser1

The value for password in the DataStore will be something like $2a$10$lulvGsUIP7bZ3TlCw02Bw...

The JWTLoging (or any other implementation) can check if the provided password was correct like so

const login = async (runtime:Runtime, username:string, password:string) => {
  const user = await findUser( runtime, username );
  if( user && await bcrypt.compare( password, user.password ) ) return generateToken( user );
}

Random Format String (RFS)

Sometimes you need random values that follow a certain pattern or format. While you can always add a function callback to provide for that there is an easy way to do this in a more expressive way using the RandomFormatString or rfs syntax.

You build a formatted string by appending values, using the append method.

new RandomFormatString().append( values:string|string[], count?:number, randMax?:number, share?:number ):RandomFormatString 
ParameterTypedefault
valuesstring | string[]the value to append, if value is an array, count and randMax decide what to append
countnumber1how many items from the values array should at least be appended to the result
randMaxnumber1random number of items from the values (count ... randMax) array should be appended to the result
sharenumber1probability (0...1) of appending the values(s) at all

To add just random characters you can use the following constants from the class `RandomFormatString``

constantcontent
alphaLower['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o', ... ]
alphaUpper['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O', ... ];
numeric['0','1','2','3','4','5','7','8','9']
specialChars['^','!','§','$','%','&','/','(',')','?',',','.','-','_',':','<','>']

As shortcuts to add a number of alpha, numeric or alphanumeric characters you can use the following methods (with the same method signature - except the values - as the append method.)

methodgenerated values
alphaLowerlower alpha characters
alphaUpperupper alpha characters
alphaalpha characters (lower, upper mixed)
numericnumeric characters
specialCharsspecial characters

So alphaLower(10).numeric( 2, 3, .7 ).specialChars(1, 0, 1) which you can read as

  • 10 random lower alpha characters
  • followed by (with 70% probability) 2 to 3 random numeric characters
  • followed by 0 or 1 special characters

is just a shortcut for

const result = new RandomFormatString().
  append( RandomFormatString.alphaLower, 10).
  append( RandomFormatString.numeric, 2, 3, .7 ).
  append( RandomFormatString.specialChars, 1, 0, 1).toString();
 
//

The following example would give a random string like HH-TR 2021 (which is the german license plate format).

entity:
  Car:
    attributes:
      licence: String
    seeds:
      items:
        100:
          licence:
              rfs: alphaUpper(1, 3).append('-').alphaUpper( 2, 3 ).append(['-', ' ']).numeric( 3, 4 )

Dymaic seed generation

If you need full control about the generation of seed items you can always create the DomainConfiguration or its relevant parts dynamically.

In this example we use the FakerJS library and use a 3rd party API to guess the gender for a generated firstname. Since this is asynchronously, we make use of an async factory method for the DomainConfiguration.

const items = async () => {
  const items:any[] = [];
  for( let i = 0; i < 2; i++ ){
    const item:any = {
      firstname: faker.person.firstName(),
      lastname: faker.person.lastName()
    }
    const genderize = await (await fetch( `https://api.genderize.io/?name=${item.firstname}`)).json()
    item.gender = _.get( genderize, 'gender', 'unknown' );
    items.push( item );
  }
  return items;
}
 
const getDomainConfiguration = async () => ({
  entity: {
    Driver: {
      attributes: {
        firstname: 'String!',
        lastname: 'String!',
        gender: 'String!'
      },
      seeds: {
        items: await items()
      }
    }
  }
});