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

vuex-orm-json-api

v1.0.0

Published

A JSON:API adapter for Vuex ORM.

Downloads

735

Readme

Vuex ORM JSON:API - A JSON:API adapter for Vuex ORM

Vuex ORM JSON:API is a Vuex ORM library plugin for reading the JSON:API format. It contains mechanisms for transforming the JSON:API specification's flexible, self-describing documents, resources, attributes, and relationships into their Vuex ORM analogues of records, attributes, and relations. Also included are RESTful actions that extend the Vuex ORM Axios plugin's HTTP actions.

Installation

  1. Installing via NPM:

    npm install --save vuex-orm-json-api

    Installing via Yarn:

    yarn add vuex-orm-json-api
  2. Importing the plugin:

    import axios from 'axios';
    import VuexORM from '@vuex-orm/core';
    import VuexOrmJsonApi, {RestfulActionsMixin} from 'vuex-orm-json-api';
        
    VuexORM.use(VuexOrmJsonApi, {axios, mixins: [RestfulActionsMixin]});

Usage

Assuming that you've included the RestfulActionsMixin, the adapter supports five, Ruby on Rails-y RESTful actions:

User.jsonApi().index();                             // Get all users.
User.jsonApi().show(1);                             // Get user 1.
User.jsonApi().create({name: 'Harry Bovik'});       // Create a user.
User.jsonApi().update(1, {name: 'Harry Q. Bovik'}); // Update user 1.
User.jsonApi().destroy(1);                          // Destroy user 1.

Some considerations:

  • Because JSON:API documents are divided into primary data (data: {} or data: []) and related resources (included: []), all actions come with the side effect of upserting received resources, via Model.insertOrUpdate, into the Vuex ORM database (or deleting in case of destroy).

  • The return value of each action is the upserted record(s) corresponding to the primary data section, with proper multiplicity: Array for index; Object for show, create, and update; and null for destroy.

  • index, show, create, update, and destroy respectively use the GET, GET, POST, PATCH, and DELETE HTTP methods. The JSON:API specification seems to prefer PATCH over PUT.

  • You may pass in a scope function to qualify returned records further. For example:

    await User.jsonApi().show(1, {scope: (query) => query.with('users.group')});
  • Additional options beyond the required action parameters will be pass through to the underlying axios request. Potential configurations include url and method.

  • If you want to dig into JSON:API top-level properties like meta and links (see https://jsonapi.org/format/#document-top-level), use the rawRequest API like so:

    const vuexOrmJsonApiResponse = await User.jsonApi().rawRequest({method: 'get', url: '/api/users/1'});
    const theMeta = vuexOrmJsonApiResponse.meta;
    // Commit to the database.
    await vuexOrmJsonApiResponse.commit();

The adapter may be configured at installation time

VuexORM.use(VuexOrmJsonApi, {axios, mixins: [RestfulActionsMixin], apiRoot: '/'});

or at the model level:

class User extends Model {
  static jsonApiConfig = {
    apiRoot: '/'
  }
}

Configuration options include:

  • apiRoot - The path prefix to your API routes. The default is /api.
  • resourceToEntityCase - The converter from JSON:API resource casing to Vuex ORM model casing. The default is a simple kebab-to-snake case function, because the JSON:API specification prefers names like users-groups and Vuex ORM prefers names like users_groups.
  • entityToResourceRouteCase - The converter from Vue ORM model casing to resource route casing. The default is a no-op because this author writes Ruby on Rails API servers, which happen to also use plural snake case.

Examples

At a fundamental level, the adapter comprehends Vuex ORM relations and transforms the relationships found in JSON:API documents into insertion-ready data. Consider the following models:

class User extends Model {
  static entity = 'users'

  static fields() {
    return {
      id: this.attr(null),
      name: this.attr(null),

      groups: this.belongsToMany(Group, UsersGroup, 'user_id', 'group_id'),
    };
  }
}

class Group extends Model {
  static entity = 'groups'

  static fields() {
    return {
      id: this.attr(null),
      name: this.attr(null),

      users: this.belongsToMany(User, UsersGroup, 'group_id', 'user_id'),
    };
  }
}

class UsersGroup extends Model {
  static entity = 'users_groups'

  static primaryKey = ['user_id', 'group_id']

  static fields() {
    return {
      user_id: this.attr(null),
      group_id: this.attr(null),
    };
  }
}

The somewhat complex setup above involves two models with bidirectional many-to-many relations and an intermediate model with composite primary key. Upon making the request

await User.jsonApi().show(1);

and receiving the response

{
  data: {
    id: 1,
    type: 'users',
    attributes: {
      name: 'Harry Bovik',
    },
    relationships: {
      groups: {
        data: [
          {id: 1, type: 'groups'},
        ],
      },
    },
  },
  included: [
    {id: 1, type: 'groups', attributes: {name: 'CMU'}},
  ],
}

the state of the Vuex store is what we would expect, composite primary keys and all:

{
  users: {
    1: {$id: '1', id: 1, name: 'Harry Bovik', users_groups: [], groups: []},
  },
  groups: {
    1: {$id: '1', id: 1, name: 'CMU', users_groups: [], users: []},
  },
  users_groups: {
    '[1,1]': {$id: '[1,1]', id: null, user_id: 1, user: null, group_id: 1, group: null},
  },
}

The JSON:API specification allows for self-describing, intrinsically polymorphic relations. How about those?

class Monster extends Model {
  static entity = 'monsters'

  static fields() {
    return {
      id: this.attr(null),
      name: this.attr(null),

      scaree_id: this.attr(null),
      scaree_type: this.attr(null),
      scaree: this.morphTo('scaree_id', 'scaree_type'),
    };
  }
}

class Child extends Model {
  static entity = 'children'

  static fields() {
    return {
      id: this.attr(null),
      name: this.attr(null),

      monster_in_the_closet: this.morphOne(Monster, 'scaree_id', 'scaree_type'),
    };
  }
}

Upon making the request

await Child.jsonApi().show(1);

and receiving the response

{
  data: {
    id: 1,
    type: 'children',
    attributes: {
      name: 'Boo',
    },
    relationships: {
      'monster-in-the-closet': {
        data: {id: 1, type: 'monsters'},
      },
    },
  },
  included: [
    {id: 1, type: 'monsters', attributes: {name: 'Sully'}},
  ],
}

the query

Child.query().whereId(1).with('monster_in_the_closet').first();

will return Boo along with her monster friend Sully.

The adapter transforms all types of Vuex ORM relations except for HasManyThrough. In the many-to-many example above, it takes extra care to ensure that records with composite primary keys have those component *_id attributes assigned before insertion. As a result, Vuex ORM can maintain its own concept of primary key in the internal $id attribute independently of the API server's usage of the id attribute in JSON:API resources.

License

Copyright 2020 Roy Liu

Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance with the License. You may obtain a copy
of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.