keystone-seeder
Version:
A flexible data seeder for Keystone 6 projects
352 lines (351 loc) • 19.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateFieldValue = generateFieldValue;
exports.handleRelationField = handleRelationField;
exports.buildSeedData = buildSeedData;
const faker_1 = require("@faker-js/faker");
// Entity context for maintaining consistency between related fields
class EntityContext {
constructor() {
this.gender = faker_1.fakerES_MX.person.sex();
this.firstName = faker_1.fakerES_MX.person.firstName(this.gender.toLowerCase());
this.lastName = faker_1.fakerES_MX.person.lastName();
this.username = faker_1.fakerES_MX.internet.username({ firstName: this.firstName, lastName: this.lastName });
this.email = faker_1.fakerES_MX.internet.email({ firstName: this.firstName, lastName: this.lastName });
this.avatar = faker_1.fakerES_MX.image.avatar();
this.company = faker_1.fakerES_MX.company.name();
this.jobTitle = faker_1.fakerES_MX.person.jobTitle();
this.phoneNumber = faker_1.fakerES_MX.phone.number();
this.address = faker_1.fakerES_MX.location.streetAddress();
this.city = faker_1.fakerES_MX.location.city();
this.state = faker_1.fakerES_MX.location.state();
this.zipCode = faker_1.fakerES_MX.location.zipCode();
this.country = faker_1.fakerES_MX.location.countryCode({ variant: "alpha-3" }).toLowerCase();
this.birthDate = faker_1.fakerES_MX.date.birthdate({ min: 18, max: 65, mode: 'age' });
}
getFirstName() { return this.firstName; }
getLastName() { return this.lastName; }
getFullName() { return `${this.firstName} ${this.lastName}`; }
getUsername() { return this.username; }
getEmail() { return this.email; }
getGender() { return this.gender; }
getAvatar() { return this.avatar; }
getCompany() { return this.company; }
getJobTitle() { return this.jobTitle; }
getPhoneNumber() { return this.phoneNumber; }
getAddress() { return this.address; }
getCity() { return this.city; }
getState() { return this.state; }
getZipCode() { return this.zipCode; }
getCountry() { return this.country; }
getBirthDate() { return this.birthDate; }
getAge() { return new Date().getFullYear() - this.birthDate.getFullYear(); }
}
const stringFieldPatterns = {
fullname: (_, context) => context ? context.getFullName() : `${faker_1.fakerES_MX.person.firstName()} ${faker_1.fakerES_MX.person.lastName()}`,
firstname: (_, context) => context ? context.getFirstName() : faker_1.fakerES_MX.person.firstName(),
lastname: (_, context) => context ? context.getLastName() : faker_1.fakerES_MX.person.lastName(),
username: (_, context) => context ? context.getUsername() : faker_1.fakerES_MX.internet.username(),
name: (modelName, context) => {
const sanitizedModelName = modelName.toLowerCase();
if (sanitizedModelName.includes("product"))
return faker_1.fakerES_MX.commerce.productName();
if (sanitizedModelName.includes("event"))
return faker_1.fakerES_MX.lorem.words(3);
if (sanitizedModelName.includes("company"))
return faker_1.fakerES_MX.company.name();
if (sanitizedModelName.includes("job"))
return faker_1.fakerES_MX.person.jobTitle();
if (sanitizedModelName.includes("course"))
return faker_1.fakerES_MX.lorem.words(2) + " Course";
if (sanitizedModelName.includes("category"))
return faker_1.fakerES_MX.commerce.department();
if (sanitizedModelName.includes("blog") || sanitizedModelName.includes("post") || sanitizedModelName.includes("article"))
return faker_1.fakerES_MX.lorem.sentence(5);
return context ? context.getFirstName() : faker_1.fakerES_MX.person.firstName();
},
email: (_, context) => context ? context.getEmail() : faker_1.fakerES_MX.internet.email(),
gender: (_, context) => context ? context.getGender() : faker_1.fakerES_MX.person.sex(),
password: () => faker_1.fakerES_MX.internet.password({ length: 12, memorable: true }),
phone: (_, context) => context ? context.getPhoneNumber() : faker_1.fakerES_MX.phone.number(),
phonenumber: (_, context) => context ? context.getPhoneNumber() : faker_1.fakerES_MX.phone.number(),
mobile: (_, context) => context ? context.getPhoneNumber() : faker_1.fakerES_MX.phone.number(),
address: (_, context) => context ? context.getAddress() : faker_1.fakerES_MX.location.streetAddress(),
street: (_, context) => context ? context.getAddress() : faker_1.fakerES_MX.location.street(),
city: (_, context) => context ? context.getCity() : faker_1.fakerES_MX.location.city(),
state: (_, context) => context ? context.getState() : faker_1.fakerES_MX.location.state(),
zipcode: (_, context) => context ? context.getZipCode() : faker_1.fakerES_MX.location.zipCode(),
postalcode: (_, context) => context ? context.getZipCode() : faker_1.fakerES_MX.location.zipCode(),
country: (_, context) => context ? context.getCountry() : faker_1.fakerES_MX.location.countryCode({ variant: "alpha-3" }).toLowerCase(),
company: (_, context) => context ? context.getCompany() : faker_1.fakerES_MX.company.name(),
jobtitle: (_, context) => context ? context.getJobTitle() : faker_1.fakerES_MX.person.jobTitle(),
title: (modelName) => {
const sanitizedModelName = modelName.toLowerCase();
if (sanitizedModelName.includes("product"))
return faker_1.fakerES_MX.commerce.productName();
if (sanitizedModelName.includes("job"))
return faker_1.fakerES_MX.person.jobTitle();
if (sanitizedModelName.includes("post") || sanitizedModelName.includes("article") || sanitizedModelName.includes("blog"))
return faker_1.fakerES_MX.lorem.sentence();
return faker_1.fakerES_MX.lorem.words(3);
},
description: (modelName) => {
const sanitizedModelName = modelName.toLowerCase();
if (sanitizedModelName.includes("product"))
return faker_1.fakerES_MX.commerce.productDescription();
if (sanitizedModelName.includes("user") || sanitizedModelName.includes("person"))
return faker_1.fakerES_MX.lorem.sentence(8);
return faker_1.fakerES_MX.lorem.paragraph();
},
content: () => faker_1.fakerES_MX.lorem.paragraphs(3),
summary: () => faker_1.fakerES_MX.lorem.paragraph(),
bio: () => faker_1.fakerES_MX.lorem.paragraph(2),
avatar: (_, context) => context ? context.getAvatar() : faker_1.fakerES_MX.image.avatar(),
image: (modelName) => {
const sanitizedModelName = modelName.toLowerCase();
if (sanitizedModelName.includes("avatar") || sanitizedModelName.includes("profile"))
return faker_1.fakerES_MX.image.avatar();
return faker_1.fakerES_MX.image.url();
},
url: () => faker_1.fakerES_MX.internet.url(),
website: () => faker_1.fakerES_MX.internet.url(),
date: () => faker_1.fakerES_MX.date.recent().toISOString(),
birthdate: (_, context) => context ? context.getBirthDate().toISOString() : faker_1.fakerES_MX.date.birthdate({ min: 18, max: 65, mode: 'age' }).toISOString(),
birthday: (_, context) => context ? context.getBirthDate().toISOString() : faker_1.fakerES_MX.date.birthdate({ min: 18, max: 65, mode: 'age' }).toISOString(),
age: (_, context) => context ? context.getAge().toString() : faker_1.fakerES_MX.number.int({ min: 18, max: 65 }).toString(),
sku: () => faker_1.fakerES_MX.commerce.isbn({ separator: "" }),
isbn: () => faker_1.fakerES_MX.commerce.isbn(),
price: () => faker_1.fakerES_MX.commerce.price().toString(),
cost: () => faker_1.fakerES_MX.commerce.price().toString(),
currency: () => faker_1.fakerES_MX.finance.currencyCode(),
color: () => faker_1.fakerES_MX.color.human(),
hex: () => faker_1.fakerES_MX.color.rgb(),
rgb: () => faker_1.fakerES_MX.color.rgb(),
department: () => faker_1.fakerES_MX.commerce.department(),
category: () => faker_1.fakerES_MX.commerce.department(),
tag: () => faker_1.fakerES_MX.lorem.word(),
tags: () => faker_1.fakerES_MX.lorem.words(3),
status: (modelName) => {
const statuses = ["active", "inactive", "pending", "completed", "cancelled"];
if (modelName.toLowerCase().includes("order")) {
return faker_1.fakerES_MX.helpers.arrayElement(["pending", "processing", "shipped", "delivered", "cancelled"]);
}
return faker_1.fakerES_MX.helpers.arrayElement(statuses);
},
role: () => faker_1.fakerES_MX.helpers.arrayElement(["user", "admin", "editor", "viewer"]),
permission: () => faker_1.fakerES_MX.helpers.arrayElement(["read", "write", "admin"]),
language: () => faker_1.fakerES_MX.helpers.arrayElement(["en", "es", "fr", "de", "it"]),
locale: () => faker_1.fakerES_MX.helpers.arrayElement(["en-US", "es-ES", "fr-FR", "de-DE", "it-IT"]),
timezone: () => faker_1.fakerES_MX.location.timeZone(),
uuid: () => faker_1.fakerES_MX.string.uuid(),
ip: () => faker_1.fakerES_MX.internet.ip(),
mac: () => faker_1.fakerES_MX.internet.mac(),
domain: () => faker_1.fakerES_MX.internet.domainName(),
useragent: () => faker_1.fakerES_MX.internet.userAgent(),
token: () => faker_1.fakerES_MX.string.alphanumeric(32),
default: () => faker_1.fakerES_MX.lorem.words(3)
};
const typeHandlers = {
String: (fieldName, modelName, context) => {
const pattern = Object.keys(stringFieldPatterns).find(pattern => fieldName.includes(pattern));
return pattern ? stringFieldPatterns[pattern](modelName, context) : stringFieldPatterns.default(modelName);
},
Int: (fieldName, modelName) => {
const sanitizedFieldName = fieldName.toLowerCase();
const sanitizedModelName = modelName.toLowerCase();
if (sanitizedFieldName.includes("age"))
return faker_1.fakerES_MX.number.int({ min: 18, max: 65 });
if (sanitizedFieldName.includes("year"))
return faker_1.fakerES_MX.number.int({ min: 2000, max: 2023 });
if (sanitizedFieldName.includes("step"))
return faker_1.fakerES_MX.number.int({ min: 1, max: 20 });
if (sanitizedFieldName.includes("count") || sanitizedFieldName.includes("quantity"))
return faker_1.fakerES_MX.number.int({ min: 1, max: 100 });
if (sanitizedFieldName.includes("rating") || sanitizedFieldName.includes("score"))
return faker_1.fakerES_MX.number.int({ min: 1, max: 5 });
if (sanitizedFieldName.includes("price") || sanitizedFieldName.includes("cost"))
return faker_1.fakerES_MX.number.int({ min: 5, max: 500 });
if (sanitizedFieldName.includes("discount") || sanitizedFieldName.includes("percentage"))
return faker_1.fakerES_MX.number.int({ min: 5, max: 30 });
if (sanitizedModelName.includes("order") && sanitizedFieldName.includes("total"))
return faker_1.fakerES_MX.number.int({ min: 10, max: 1000 });
return faker_1.fakerES_MX.number.int({ min: 1, max: 1000 });
},
Float: (fieldName) => {
const sanitizedFieldName = fieldName.toLowerCase();
if (sanitizedFieldName.includes("price") || sanitizedFieldName.includes("cost"))
return faker_1.fakerES_MX.number.float({ min: 5, max: 500, fractionDigits: 2 });
if (sanitizedFieldName.includes("rating") || sanitizedFieldName.includes("score"))
return faker_1.fakerES_MX.number.float({ min: 1, max: 5, fractionDigits: 1 });
if (sanitizedFieldName.includes("discount") || sanitizedFieldName.includes("percentage"))
return faker_1.fakerES_MX.number.float({ min: 0.05, max: 0.3, fractionDigits: 2 });
if (sanitizedFieldName.includes("weight"))
return faker_1.fakerES_MX.number.float({ min: 0.1, max: 10, fractionDigits: 1 });
if (sanitizedFieldName.includes("height") || sanitizedFieldName.includes("width") || sanitizedFieldName.includes("length")) {
return faker_1.fakerES_MX.number.float({ min: 1, max: 100, fractionDigits: 1 });
}
return faker_1.fakerES_MX.number.float({ min: 1, max: 1000, fractionDigits: 2 });
},
Boolean: (fieldName) => {
const sanitizedFieldName = fieldName.toLowerCase();
if (sanitizedFieldName.includes("active") || sanitizedFieldName.includes("enabled") || sanitizedFieldName.includes("available")) {
return true;
}
if (sanitizedFieldName.includes("deleted") || sanitizedFieldName.includes("archived") || sanitizedFieldName.includes("disabled")) {
return false;
}
if (sanitizedFieldName.includes("featured") || sanitizedFieldName.includes("premium")) {
return faker_1.fakerES_MX.datatype.boolean({ probability: 0.3 }); // 30% chance of being true
}
if (sanitizedFieldName.includes("verified") || sanitizedFieldName.includes("confirmed")) {
return faker_1.fakerES_MX.datatype.boolean({ probability: 0.8 }); // 80% chance of being true
}
return faker_1.fakerES_MX.datatype.boolean();
},
DateTime: (fieldName) => {
const sanitizedFieldName = fieldName.toLowerCase();
if (sanitizedFieldName.includes("created") || sanitizedFieldName.includes("createdAt")) {
return faker_1.fakerES_MX.date.recent({ days: 30 });
}
if (sanitizedFieldName.includes("updated") || sanitizedFieldName.includes("updatedAt")) {
return faker_1.fakerES_MX.date.recent({ days: 7 });
}
if (sanitizedFieldName.includes("birth") || sanitizedFieldName.includes("dob")) {
return faker_1.fakerES_MX.date.birthdate({ min: 18, max: 65, mode: 'age' });
}
if (sanitizedFieldName.includes("expiry") || sanitizedFieldName.includes("expiration")) {
return faker_1.fakerES_MX.date.future({ years: 2 });
}
if (sanitizedFieldName.includes("start")) {
return faker_1.fakerES_MX.date.past({ years: 1 });
}
if (sanitizedFieldName.includes("end") || sanitizedFieldName.includes("deadline")) {
return faker_1.fakerES_MX.date.future({ years: 1 });
}
return faker_1.fakerES_MX.date.recent();
},
default: () => null
};
/**
* Generate a value for a field based on its type and name
*/
function generateFieldValue(field, modelName, context) {
const fieldName = field.name.toLowerCase();
const handler = typeHandlers[field.type] || typeHandlers.default;
return handler(fieldName, modelName, context);
}
/**
* Build seed data for a model based on its fields and metadata
*/
/**
* Get related entities for a relation field
*/
async function getRelatedEntities(context, relatedModel) {
try {
// Query the database to get all entities of the related model
const entities = await context.sudo().db[relatedModel].findMany({
take: 100, // Limit to 100 records for performance
});
return entities;
}
catch (error) {
console.error(`Error fetching related entities for ${relatedModel}:`, error);
return [];
}
}
/**
* Handle relation field by selecting a related entity
*/
const interactive_prompts_1 = require("./interactive-prompts");
async function handleRelationField(context, field, relationMode) {
try {
// Get the related model name from the field
const relatedModel = field.type;
const fieldName = field.name;
// Get all available entities of the related model
const relatedEntities = await getRelatedEntities(context, relatedModel);
if (relatedEntities.length === 0) {
console.warn(`No ${relatedModel} entities found for relation. Skipping relation field.`);
return null;
}
// If connect-one mode, always use the first entity
if (relationMode === 'connect-one' && relatedEntities.length > 0) {
return { connect: { id: relatedEntities[0].id } };
}
// If interactive mode, prompt user to select entities
if (relationMode === 'interactive') {
if (field.isList) {
const selectedEntities = await (0, interactive_prompts_1.selectMultipleEntities)(fieldName, relatedModel, relatedEntities);
return selectedEntities ? { connect: selectedEntities } : null;
}
else {
const selectedEntity = await (0, interactive_prompts_1.selectSingleEntity)(fieldName, relatedModel, relatedEntities);
return selectedEntity ? { connect: selectedEntity } : null;
}
}
// Otherwise, randomly select an entity
const randomEntity = faker_1.fakerES_MX.helpers.arrayElement(relatedEntities);
return { connect: { id: randomEntity.id } };
}
catch (error) {
console.error(`Error handling relation field:`, error);
return null;
}
}
function buildSeedData(model, fieldsMeta, context, relationMode) {
const data = {};
try {
// Get non-relation required fields
const regularFields = model.fields.filter((field) => field.isRequired &&
!field.isGenerated &&
!field.isId &&
!field.relationName);
// Get relation fields (if relation mode is specified)
const relationFields = relationMode ? model.fields.filter((field) => field.isRequired && field.relationName && !field.isId && !field.name.startsWith('from')) : [];
// Create entity context if the model appears to be person-related
const modelNameLower = model.name.toLowerCase();
const isPersonModel = modelNameLower.includes('user') ||
modelNameLower.includes('person') ||
modelNameLower.includes('employee') ||
modelNameLower.includes('customer');
const entityContext = isPersonModel ? new EntityContext() : undefined;
// Process regular fields
for (const field of regularFields) {
const fieldName = field.name;
const fieldMeta = fieldsMeta[fieldName];
if (fieldMeta?.options) {
// Handle select fields with options
const options = fieldMeta.options;
// Check if 'published' exists in options
const publishedOption = options.find((opt) => opt.value === "published");
if (publishedOption) {
data[field.name] = "published";
}
else if (fieldMeta?.defaultValue !== undefined) {
// Use defaultValue if available
data[field.name] = fieldMeta.defaultValue;
}
else {
data[field.name] = faker_1.fakerES_MX.helpers.arrayElement(options).value;
}
}
else {
data[field.name] = generateFieldValue(field, model.name, entityContext);
}
}
// Process relation fields if relation mode is specified
if (relationMode && context) {
// We'll handle relation fields asynchronously later in the seed function
data._relationFields = relationFields.map((field) => ({
fieldName: field.name,
relatedModel: field.type,
isList: field.isList
}));
}
return data;
}
catch (error) {
console.error("Error building seed data:", error);
throw error;
}
}