npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

primed-model

v1.0.4

Published

TypeScript model management

Downloads

709

Readme

primed-model

npm NPM

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 classes
  • Primed <decorator> - To mark properties to be dynamically instantiated
  • Base <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: false is specified in the Primed options
  • Specify properties for classes defined after the point of reference passing a string to Primed
  • Pass a factory function to Primed for 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 clone method 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 Model decorator 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: false to the Primed options to prevent primed properties from being automatically instantiated

    @Model
    class Foo extends Base<Foo>{
        @Primed(Bar, { required: false })
        bar!: Bar
    }
  • Pass array: true to the Primed options 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 Base class when extending

    interface 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