Generates objection.js
models in Typescript
from a YAML specification.
- Generate your initial set of
objection.js
models from a YAML file - Supports
$ref
for re-using common definitions - Can also generate a basic
knex
migration file based on the YAML file
$ npm i objection-generator -g
$ npm i knex objection --save
$ objection-generator generate <specFile> <outDir>
objection-generator generate <specFile> <outDir>
Generates objection.js models from a YAML file
Positionals:
specFile The YAML file to use to generate models. [string] [required]
outDir The directory to output the models. [string] [required] [string] [required]
Using the sample.yaml
spec in this project:
$ objection-generator generate sample.yaml /tmp/lib
Will generate the following folder structure:
/tmp/lib/
├── models/
│ ├── BaseModel.ts
│ ├── <model>.ts
Will generate models that look like this:
import { Model } from 'objection'
import path from 'path'
import { BaseModel } from './BaseModel'
export enum PersonGenderEnum {
MALE = 'Male',
FEMALE = 'Female',
OTHER = 'Other'
}
export enum PersonFavFoodEnum {
PINE_APPLE = 'pine-apple',
BLUE_BERRY = 'blueBerry',
CHEESE_PIZZA = 'cheese_pizza'
}
export class PersonModel extends BaseModel {
id: string
name: string
age: number | null
gender: PersonGenderEnum
favFood: PersonFavFoodEnum
username: string
created: string
static tableName = 'persons'
static get jsonSchema () {
return {
type: 'object',
required: ['name', 'username'],
properties: {
id: {
type: 'string'
},
name: {
type: 'string',
minLength: 1,
maxLength: 100
},
age: {
type: ['number', 'null']
},
gender: {
type: 'string',
enum: ['Male', 'Female', 'Other'],
default: 'Female'
},
favFood: {
type: 'string',
enum: ['pine-apple', 'blueBerry', 'cheese_pizza']
},
username: {
type: 'string',
minLength: 1,
maxLength: 25,
default: 'default-user'
},
created: {
type: 'string',
format: 'date-time'
}
}
}
}
static get relationMappings () {
return {
movies: {
relation: Model.ManyToManyRelation,
modelClass: path.join(__dirname, 'MovieModel'),
join: {
from: 'persons.id',
through: {
from: 'persons_movies.personId',
to: 'persons_movies.movieId'
},
to: 'movies.id'
}
},
reviews: {
relation: Model.HasManyRelation,
modelClass: path.join(__dirname, 'ReviewModel'),
join: {
from: 'persons.id',
to: 'review.authorId'
}
}
}
}
}
You must use knexSnakeCaseMappers
in your knex
configuration.
import { knexSnakeCaseMappers } from 'objection'
import knex from 'knex'
const db = knex({
client: 'postgres',
connection: {
host: '127.0.0.1',
user: 'objection',
database: 'objection_test'
},
// allows usage of camel cased names in the model
// and snake cased fields in the database
...knexSnakeCaseMappers()
})
The YAML can also be used to generate a basic migration file. This can be used as a good starting base for building a desired migration.
There are many limitations to the generation since there is not an exact mapping between JSON schema types / information in the objection models to an exact database specification.
Some limitations include:
- No foreign keys are generated (PRs welcomed - make use of the
relations
please) - No through tables are generated
PRs are welcomed for improvements!
$ objection-generator knex <specFile> <outDir>
objection-generator knex <specFile> <outDir>
Generates a basic knex migration from a YAML file
Positionals:
specFile The YAML file to use to generate models. [string] [required]
outDir The directory to output the models. [string] [required] [string] [required]
Using the sample.yaml
spec in this project:
$ objection-generator knex sample.yaml /tmp/lib
Will generate the following folder structure:
/tmp/lib/
├── migrations/
│ └── 000-init.js
└── migrate.js
Example migration output:
async function up (knex) {
await knex.schema.createTable('persons', table => {
table.string('id')
table.string('name', 100).notNullable()
table.integer('age')
table.enu('gender', ['Male', 'Female', 'Other']).defaultTo('Female')
table
.string('username', 25)
.notNullable()
.defaultTo('default-user')
table.datetime('created')
table.primary(['id'])
table.unique(['username'], 'uniq_username')
table.index(['age', 'name'], 'name_age_index')
})
await knex.schema.createTable('movies', table => {
table.string('id')
table.string('name', 255).notNullable()
table.primary(['id'])
})
await knex.schema.createTable('reviews', table => {
table.string('review_id')
table.string('author_id').notNullable()
table.string('movie_id').notNullable()
table.string('content')
table.primary(['review_id'])
})
}
async function down (knex) {
await knex.schema.dropTable('persons')
await knex.schema.dropTable('movies')
await knex.schema.dropTable('reviews')
}
module.exports = {
up,
down
}
The output includes a sample migration script that uses sqlite3
as the
database driver for quick prototyping.
$ npm i sqlite3 --save-dev
$ node <outputDir>/migrate.js
Modify this file to your liking to work with your own database.
See sample.yaml
for an example spec.
config:
model:
# Adds a prefix to the class names of the generated objection.js models
classNamePrefix:
# Adds a postfix to the class names of the generated objection.js models
classNamePostfix: Model
# Objection models to generate
models:
# Defines an objection model named Person (actually PersonModel with the postfix)
Person:
# database table name
tableName: persons
# maps to Model#jsonSchema()
# https://json-schema.org/understanding-json-schema/reference/type.html
# https://vincit.github.io/objection.js/guide/models.html#examples
jsonSchema:
required: ['name', 'username']
properties:
id:
type: string
name:
type: string
minLength: 1
maxLength: 100
age:
# You can define a re-usable set of properties and reference them via $ref
$ref: '#/components/fieldProperties/age'
gender:
type: string
enum: ['Male', 'Female', 'Other']
default: 'Female'
favFood:
type: string
enum: ['pine-apple', 'blueBerry', 'cheese_pizza']
childrenCount:
type: number
default: 0
username:
allOf:
# combine a ref and a non-ref, see json schema spec for more info
- $ref: '#/components/fieldProperties/username'
- default: 'default-user'
someOtherField:
type: string
created:
type: string
format: date-time
# Define relations - maps to Model#relationMappings()
# https://vincit.github.io/objection.js/guide/relations.html#examples
relations:
movies:
relation: Model.ManyToManyRelation
modelClass: Movie
join:
from: persons.id
through:
from: persons_movies.personId
to: persons_movies.movieId
to: movies.id
reviews:
relation: Model.HasManyRelation
modelClass: Review
join:
from: persons.id
to: review.authorId
# Section for knex-specific generation
database:
# define unique indices
unique:
# made-up name for the unique index
uniq_username:
# columns to add to unique index
# values will always be converted to snake case
columns: ['username']
# Define indices
index:
# made-up name for the index
name_age_index:
# columns to index
# values will always be converted to snake case
columns: ['age', 'name']
exclude:
# exclude these fields from being generated in the migration file
# this is if you want to have a field defined in the model
# but not in the database
columns: ['someOtherField']
Movie:
tableName: movies
jsonSchema:
required: ['name']
properties:
id:
type: string
name:
type: string
minLength: 1
maxLength: 255
relations:
reviews:
relation: Model.HasManyRelation
modelClass: Review
join:
from: movie.id
to: review.movieId
Review:
tableName: reviews
# If you want to use a primary key that's not called "id"
idColumn: reviewId
jsonSchema:
required: ['authorId', 'movieId']
properties:
reviewId:
type: string
authorId:
type: string
movieId:
type: string
content:
type: string
relations:
author:
relation: Model.HasOneRelation
modelClass: Person
join:
from: reviews.authorId
to: persons.id
# components are re-usable elements that can be
# referenced in the model via $ref
components:
# This is a made up section used for
# defining common field properties
fieldProperties:
age:
type: ['number', 'null']
username:
type: string
minLength: 1
maxLength: 25