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

@poppinss/data-models

v2.0.1

Published

Abstract data models with first class support for relationships

Downloads

9

Readme

Data models

Data models to manage data in OOP style with first class support for relationships

circleci-image npm-image license-image

This library is abstracted from AdonisJs Lucid ORM to work as a standalone implementation of Active model heavily inspired by Rails.

The consumer of this library will be a developer creating an ORM for backend or frontend and implementing adapter for fetching and persisting data.

Table of contents

How it works

Let's say you are working with relational data in your app and want clean abstractions to fetch and save that data. For example:

import { BaseModel, column } from '@poppinss/data-models'

class User extends BaseModel {
  @primaryColumn()
  public id: number

  @column()
  public username: string
}

class Post extends BaseModel {
  @primaryColumn()
  public id: number

  @column()
  public title: string
}

We start by defining 2 models that extends the BaseModel class and define columns on them. The model itself doesn't know how to fetch or persist the data and instead relies on an Adapter.

Useless without Adapter

The Adapter can run SQL queries to fetch the data or making an HTTP call to fetch data from some REST API endpoint. Which inturn means, the models are useless without adapters. That's why, this library is meant for developers creating ORM's and not users using ORM's.

Standardized API

However, the BaseModel class exposes a standardized API and does all the heavy lifting of normalizing the adapter results to model instances. For example:

import { BaseModel, column } from '@poppinss/data-models'

class User extends BaseModel {
  @primaryColumn()
  public id: number

  @column()
  public username: string
}

const user = new User()
user.username = 'virk'

await user.save()

The user.save will ask the adapter to perform insert for the given model. A SQL adapter will perform an insert query, whereas a REST based adapter will perform a POST request to some endpoint.

Once the API call is over, the user.$persisted will return true.

Relationship example

Let's also see how to work with relationships.

import { BaseModel, column, belongsTo } from '@poppinss/data-models'

class User extends BaseModel {
  @primaryColumn()
  public id: number

  @column()
  public username: string
}

class Post extends BaseModel {
  @primaryColumn()
  public id: number

  @column()
  public title: string

  @belongsTo({ relatedModel: () => User })
  public author: User
}
  • First we define a belongsTo relationship on the post model.
  • Next we ask the adapter (depends on your implementation) to make an API call to some endpoint that returns an array of posts along with their authors.

Fetched JSON

[
  {
    "id": "1",
    "title": "AdonisJs 101",
    "author": {
      "id": 1,
      "username": "virk",
    }
  }
]

We can pass this data to the post model to create an array of model instances.

const posts = Post.$createMultipleFromAdapterResult(result)
posts.length  // 1
posts[0].title = 'Adonis 101'

// Mutate and update
posts[0].title = 'Adonis 102'
await posts[0].save()

Once the model is persisted, the save method will fire the update method on the adapter vs the insert.

Creating adapter

We make sure to keep the surface area small for the adapter by making it define handful of methods. However, you can extend the BaseModel to add additional methods that inturns invokes different methods on the adapter.

const restAdapter = {
  async insert (instance, attributes) {
    const response = await axios.post({
      url: instance.$getConstructor().url,
      body: attributes
    })
    instance.$consumeAdapterResult(response.data)
  },

  async delete (instance) {
    await axios.delete({
      url: `${instance.$getConstructor().url}/${instance.$getAttribute('id')}`,
    })
  },

  update (instance, dirtyAttributes) {
    const response = await axios.patch({
      url: `${instance.$getConstructor().url}/${instance.$getAttribute('id')}`,
      body: dirtyAttributes,
    })
    instance.$consumeAdapterResult(response.data)
  },

  find (model, key, value) {
    const result = await axios.get({
      url: instance.$getConstructor().url,
      params: {
        [key]: value,
      }
    })

    return model.$createFromAdapterResult(result.body[0])
  },

  findAll (model) {
    const result = await axios.get({
      url: instance.$getConstructor().url,
    })

    return model.$createMultipleFromAdapterResult(result.body)
  }
}

The find and findAll method gets the model constructor and not the instance, but they must return one or more instances of the model using $createFromAdapterResult and $createMultipleFromAdapterResult methods.

API

Please check the API docs generated via Typedoc

Maintainers

Harminder virk