primed-model v1.0.4
primed-model
Hassle free TypeScript model management
Dynamically create your instances from JSON objects to take full advantage of OOP. This approach is to be contrasted with factory functions for managing highly structured data
Note: This project uses reflect-metadata for storing metadata in classes and properties
Model definition
Model<decorator> - To register the classesPrimed<decorator> - To mark properties to be dynamically instantiatedBase<class> - To extend the models, configure their constructor's typing, and give internal functionality
@Model
class Person extends Base<Person> {
name: string = ''
middleName: string = ''
lastName: string = ''
get fullName(){
return [this.name, this.middleName, this.lastName].join(' ').trim() || 'Empty Name'
}
@Primed(Person)
parent!: Person
@Primed('Cat', { array: true })
cats!: Cat[]
}
@Model
class Cat extends Base<Cat>{
name: string | null = null
breed: string | null = null
}Note: Cat is being passed to the Primed decorator as a string in the Person class because TS/JS does not allow classes which haven't been defined to be referenced by value
Example usage
Initializing with empty constructor
new Person()Output
{
name: "",
middleName: "",
lastName: "",
fullName: "Empty Name",
parent: {
name: "",
middleName: "",
lastName: "",
fullName: "Empty Name",
cats: [
{
name: null,
breed: null
}
]
},
cats:[
{
name: null,
breed: null
}
]
}Passing JSON to constructor
new Person({
name: "Alice",
lastName: "Liddell",
cats: [
{
name: "garfield"
}
],
parent: {
name: "Bob",
cats: [
{
name: "Tom"
}
]
}
})Output
{
name: "Alice",
middleName: "",
lastName: "Liddell",
fullName: "Alice Liddell",
parent: {
name: "Bob",
middleName: "",
lastName: "",
fullName: "Bob",
cats: [
{
name: "Tom",
breed: null
}
]
},
cats: [
{
name: "Garfield",
breed: null
}
]
}Features
- Recursively and dynamically create model instances, automatically instantiated by default, unless
required: falseis specified in thePrimedoptions - Specify properties for classes defined after the point of reference passing a string to
Primed - Pass a factory function to
Primedfor custom data types - Getters are enumerable by default so that they show when iterating over the keys or in the string representation of your class (using
JSON.stringify) - Provided
clonemethod for copying whole instances
Custom properties examples
You can define your own functions that can be passed into the Prime decorator for custom behavior
Luxon's DateTime
import { DateTime } from 'luxon'
function PrimedDateTime(value?: string | DateTime): DateTime {
if(value instanceof DateTime){
return DateTime.fromJSDate(value.toJSDate()) // Copy the value
} else if(typeof value === 'string'){
return DateTime.fromISO(value) // Build from string
} else {
return DateTime.local() // Create default value
}
}
@Model
class Foo extends Base<Foo>{
@Primed(PrimedDateTime)
someDateTime!: DateTime
}import { Decimal } from 'decimal.js'
function PrimedDecimal(value: number | string | Decimal = 0): Decimal {
return new Decimal(value)
}function PrimedDate(value?: string | Date): Date {
if(typeof value === 'undefined'){
return new Date() // Create default value
} else {
return new Date(value) // Build from string or copy existing
}
}function PrimedId(value?: string): string {
return value ? value : "-1"
}Noteworthy
- If you're minifying/compressing/uglyfing your JS, you must pass a string to the
Modeldecorator with the name of the class. The function name is being relied uppon for initializing properties that depend on it at runtime@Model('Foo') class Foo extends Base<Foo>{ @Primed(Bar) bar!: Bar } - Pass
required: falseto thePrimedoptions to prevent primed properties from being automatically instantiated@Model class Foo extends Base<Foo>{ @Primed(Bar, { required: false }) bar!: Bar } - Pass
array: trueto thePrimedoptions to automatically instantiate arrays@Model class Foo extends Base<Foo>{ @Primed(Bar, { array: true }) bar!: Bar[] } If the payload type differs from the class's type and you want those typings, you can pass an second interface (which can be a partial of the class) to the
Baseclass when extendinginterface FooInput{ someNumber: number, someDate: string, } @Model class Foo extends Base<Foo, FooInput>{ someString: string = '' @Primed(PrimedDecimal) someNumber!: Decimal @Primed(PrimedDateTime) someDate!: DateTime }Auto initialization will stop after detecting a circular reference
@Model class Alpha extends Base<Alpha> { @Primed('Bravo') bravo!: Bravo } @Model class Bravo extends Base<Bravo> { @Primed('Charlie') charlie!: Charlie } @Model class Charlie extends Base<Charlie> { @Primed(Alpha) alpha!: Alpha } new Alpha()Output
{ bravo: { charlie: { alpha: { bravo: undefined } } } }
To do
- Add tests
- Implement change detection mechanism