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.
| Config | Type | Description |
|---|---|---|
| seeds | SeedConfig | SeedConfig[] | you can either add a Seed configuration or an array of Seed Configurations to an entity |
SeedConfig
| Config | Type | Description |
|---|---|---|
| environment | development | test | stage | production | if you provide one or more of RuntimeEnvironments those seeds will only be applied in this environment |
| items | number | 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: 100This 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
requiredattributes will always get a value, non-required attributes will get randomy assigned a value or leftundefined- Associatons between entities will assigned
IntandFloatattributes will get random numbers, if applicable within the bounds of their validation- For
Stringvalues 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: blackThis 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: silverMercedesNumber 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: MercedesThis 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
| Config | Type | Description |
|---|---|---|
| attributeName | shortcut | SeedAttributeConfig | the value or configuration how to generate a value |
Shortcut: If the item config is just a static value is resolved into a SeedAttributeValue.
| Option | Description | |
|---|---|---|
| Static Value | SeedAttributeValue | same as value but with the possibility to add a share |
| Sample from Values | SeedAttributeSample | value should be taken from a list of values, an Enum or reference to another entity |
| Faker Value | SeedAttributeFaker | value should be generated by the FakerJS (opens in a new tab) library |
| Random Number Value | SeedAttributeRandom | value should be a random number |
| Hash Value | SeedAttributeHash | value is the hashed value; e.g. for passwords |
| Formatted Random String | SeedAttributRfs | value is Formatted Random String |
Static Value
SeedAttributeValue
| Config | Type | Default | Description |
|---|---|---|---|
| value | string | number | boolean | string[] | number[] | boolean[] | the value of the seeded item | |
| share | 0..1 | 1 | when 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.2This 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
| Config | Type | Default | Description |
|---|---|---|---|
| sample | (string | number)[] | Enum | Entity | the source from the where a sample should be taken | |
| share | 0..1 | 1 | when generated n time, share % of n items will have the value the others will be undefined |
| size | number | { min: number, max: number } | when not list or assocToMany: 1; random number between 0.1 and 0.9 otherwise | when 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: CarThis 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
| Config | Type | Default | Description |
|---|---|---|---|
| faker | string | according the FakerJS (opens in a new tab) library | |
| share | 0..1 | 1 when the attribute is required; otherwise a random number between 0.1 and 0.9 | when 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.lastNameRandom number value
Random values will only be generated for attributes of the type Float or Int.
| value | description |
|---|---|
| random | if random is a number it is the upper border (incl.) or maximum for the generated number |
| random.min | the lower border (incl.) of the generated random number; default: 0 |
| random.max | the 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: 3Hash
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: passwordForUser1The 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 | Parameter | Type | default | |
|---|---|---|---|
| values | string | string[] | the value to append, if value is an array, count and randMax decide what to append | |
| count | number | 1 | how many items from the values array should at least be appended to the result |
| randMax | number | 1 | random number of items from the values (count ... randMax) array should be appended to the result |
| share | number | 1 | probability (0...1) of appending the values(s) at all |
To add just random characters you can use the following constants from the class `RandomFormatString``
| constant | content |
|---|---|
| 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.)
| method | generated values |
|---|---|
| alphaLower | lower alpha characters |
| alphaUpper | upper alpha characters |
| alpha | alpha characters (lower, upper mixed) |
| numeric | numeric characters |
| specialChars | special 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()
}
}
}
});