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

json-transqlify

v1.0.0-alpha.6

Published

Transforms and loads JSON to a Mysql db

Downloads

7

Readme

JSON TRANS SQLIFY

This library aims to abstract away some of the common functilnality involved when transforming and loading JSON into a Mysql database. By common functionality I'm specifically referring to (Validate, Transform, Insert/Update)

Contents

How does it works

npm install json-transqlify

You define how your entire TL(Transform Load) pipeline should look like by using a yaml definition file. Each definition file consistes of 2 main secions.

  • Validator: which uses json-schema to validate the entity you are trying to TL.
  • Loaders: which does the actually Insert, Update to the specific tables

Assuming you have a bunch of user objects that you would like to insert to your db. Each user has the following fields

{
  "name": "FIRST_NAME LAST_NAME",
  "age": "NUMBER",
  "address":{
    "country": "STRING",
    "city": "CITY"
  }
}

The user Table schema is

CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `fname` varchar(45) NOT NULL,
  `lname` varchar(45) NOT NULL,
  `age` int(11) NOT NULL,
  `country` varchar(45) NOT NULL,
  `city` varchar(45) NOT NULL,
  PRIMARY KEY (`id`)
)

A Simple definition file that TL(Transforms and Loads) user objects might look like this

version: 1.0
validator:
  schema:  # validate user object schema  
    default: user-schema.json
loaders: # notice loaders is an array
  - insert:
      label: InsertUser # name of this operation (can be anything)
      tableName: users # table to which the json will be inserted
      transform: 
        columns: # map each column to appropreiate field on json
          - column: fname # insert into a column name fname
            value: $entity.name.split(' ')[0] # $entity refers to the user object we are inserting.
          - column: lname
            value: $entity.name.split(' ')[1] # grap last name
          - column: country
            value: $entity.address.country
          - column: city
            value: $entity.address.city
          - column: age
            value: $entity.age
            

The user-schema.json uses json-schema rules to validate each user object you are trying to insert.

{
  "type": "object",
  "properties": {
    "name": {
      "type": "string"
    },
    "age": {
      "type": "number"
    },
    "address": {
      "type": "object",
      "properties": {
        "country": {
          "type": "string"
        },
        "city": {
          "type": "string"
        }
      },
      "required": [
        "country",
        "city"
      ]  
  },
  "required": [
    "name",
    "age"
  ]
}

All is left is to construct a json transqlifier object

const createFactory = require('json-transqlify').createFactory;

const db = {
  host: 'localhost',
  user: 'root',
  password: '',
  database: 'json_transqlify_demos',
  connectionLimit: 2
}

const factory = createFactory(db)
const transqlifier = factory.createTransqlifier('./insert-user.yaml');

const obj = { name: "Harry Potter", age: 10, address: { city: 'UK', country: 'Little Whinging' } };

transqlifier(obj);

Please refer to examples folder

API

The definition file consists of the following sections

Version

should be 1.0 for now

version: 1.0

Validator

The Validator filters out entities before they get handed to the Loaders. There are two kind of validators:

1. Schema

a schema validator can be defined using json files to describe how the entity schema should look like. Underneath the hood Json Transqlifier uses AJV implementation of Json Schema

The schema file for the entity should go under the default section (refer to the example below). While any $ref defniitions can used to load any additional definitions that the default schema might refer to. For example, to write a validator for the following user object

{
  "name": "User Name",
  "age": 28,
  "address": {
    "country": "some country",
    "city": "some city"
  } 
}
We might break the validator into two schema definitions into User and Address
1. user-definition.json
```json
{
  "type": "object",
  "properties": {
    "name": { "type": "string" },
    "age": { "type": "number" },
    "address": {
      "$ref": "Address"
    }
  },
  "required": ["name", "age", "address"]
}
2. address-definition.json
```json
{
  "type": "object",
  "properties": {
    "country": { "type": "string" },
    "city": { "type": "string" }
  },
  "required": ["country", "city"]
}

Then we can reference both schemas like this

version: 1.0
validator:
  schema:
    default: user-schema.json #a json file containing the JSON-SCHEMA definition for root entity
    refs:
      id: Address
      file: address-schema.json

2. Func Validator

when a schema validator is not enough you can have more control by providing a custom function validator. The function should be defined in a seperate file and exposed as a default export

// is-odd.js
const isOdd = num => num % 2
module.exports = isOdd

Then refeence the custom validator in the Transqlifier definition file

version: 1.0
validator:
  func: is-odd.js

Transformers

Transformers are defined as part lof loaders. They map the given $entity to table columns.

1. columns

The columns transformers allows you to map $entity to table columns by defining custom expressions.

For example, given the User object mentioned earlier and users table with (fname, lname, age, country, city) columns

transformer:
  columns:
    - column: fname
      value: _.head($entity.name.split(' ')) # $entity refers to the object we are trying to transform. You can define here any expression you like and it will be evaluated at run time. You have access to Lodash by using (_)
    - column: lname
      value: _.tail($entity.name.split(' ')).join(' ')
    - column: age
      value: $entity.age
    - column: country
      value: $entity.address.country
    - column: city
      value: $entity.address.city

$history: if the transformer was part of multiple loaders pipeline, the $history can be used to access values transformed via a previous loader (more on this later)

2. Func Transformer

A Custom function trasformer can be used by providing a file with a default exported function that should return a promise

//custom-transformer.js
const func = ({$entity, $history, $conn}) => {
   // the transformer will be provided the following
   // $entity: the entity we are currently processing
   // $history: in case the transformer is part of multiple pipeline loaders, $history will containg previously transformer values
   // $conn a connection to the db
  return Promise.resolve({
    col1: 'val1',
    col2: 'val2'
  })
module.exports = func
}
transformer:
  func: customer-transformer.js

Loaders

Loaders handle massaging the JSON (entity) and Inserting/Updating the DB.

The loaders section is an array, so you can insert the JSON into multiple tables by defining multiple loaders.

1.Insert

The insert loaders inserts entity to a given table. It requires a transformer to be defined.

loaders:
  - insert:
      tableName: users # table to insert entity into
      label: insertUser # a custom name to the loader.
      trasformer: # refer to transformers doc
        columns:
          - column: fname
            value: $entity.name 

2. Update

The Update loader is used to update an existing row in db. It requires a transformer and update condition

loaders:
  - update:
      tableName: users
      transformer:
        columns:
          - column: fname
            value: $entity.name
      where:
        query: id = ?
        params:
          - $entity.id

3. Upsert

The Upsert loader is used to insert or update (on duplicate key error) existing record. It requires a transformer, tableName, and label.

loaders:
  - upsert:
      tableName: courses
      primaryKey: id 
      transformer:
        columns:
          - column: title # when the title column has a unique index constraint, the existing record will get updated  
            value: $entity.title
          - column: difficulty
            value: $entity.difficulty

primaryKey is an optional field. It pointes to the auto incremented column (if any) in db. In case of update, it will be needed to retrieve the id of the affected row. checkout the examples/upsert-example for working demo

3. Batch Insert Loader

In cases where you want to insert a bulk of data in one go. Batch Insert Loader offers a great performance gain over multiple Insert Loader. It requires you to define transformer, tableName, label and source.

  - batchInsert:
      tableName: users # table to insert entity into
      source: $entity
      label: insertUser # a custom name to the loader.
      trasformer: # refer to transformers doc
        columns:
          - column: fname
            value: $entity.name 

Source is an expression that should return an array of items that will be inserted. For example if $entity is

{
  items: ['item1', 'item1']
}

Then source should be defined as

source: $entity.source

In cases were $entity is the array of items you wish to insert, then deine source as

source: $entity

3. Batch Upsert Loader

In cases where you want to insert a bulk of data in one go. Batch Upsert Loader will insert and updated existing record in one transaction. It requires you to define transformer, tableName, label and source.

  - batchUpsert:
      source: $entity
      tableName: courses
      primaryKey: id 
      transformer:
        columns:
          - column: title # when the title column has a unique index constraint, the existing record will get updated  
            value: $entity.title
          - column: difficulty
            value: $entity.difficulty

Preconditions

Preconditions validate $enitity before executing the loader, and if it returns false, the loader does not get executed

1. Expression Precondition (exp)

Evalutes a given expression at runtime that can access $entity and $history objects. It can also use _ lodash

loaders:
  - insert:
    transformer:
      columns:
        - column: name
          value: $entity.name
    on: # pre conditions are defined here 
      - exp: $entity.age < 30 # only insert uses who are below 30

2. Database Query (db)

Runs a query against the db and allows you to assert the returned result. Forexample, we want to insert a course but avoide duplicate titles

{
  "title": "Course Title"
}
loaders:
  - insert:
    transformer:
      tableName: courses
      columns:
        - column: title
          value: $entity.title
    on: # pre conditions are defined here 
      - db:
          query: SELECT 1 from courses WHERE title = ?
          params: 
            - $entity.title
          expect: $rows.length === 0 # $rows refers to the result of query 

3: Custom Precondition function (func)

Executed a custom precondition function. The function is expected to return a promise that resolves to true or `false

// custom-precondition.js
const func = ({$entity, $history, $conn}) => {
  // the precondition function will be provided the following
   // $entity: the entity we are currently processing
   // $history: in case the transformer is part of multiple pipeline loaders, $history will containg previously transformer values
   // $conn a connection to the db
  Promise.resolve(true)
}
module.exports = func
loaders:
  - insert:
    transformer:
      tableName: courses
      columns:
        - column: title
          value: $entity.title
    on: # pre conditions are defined here 
      - func: custom-precondition.js

Note: preconditions are defined inside an array object. Meaning, you can provide multiple preconditions that should all resolve to true for the loader to execute.

$history

The $history object can be accessed inside transformers and preconditions. It contains the result of previous loaders. For example

loaders:
  - insert:
      tableName: table1
      label: InsertTable1
      transformer:
        columns:
          - column: title
            value: $entity.title
  - insert:
      tableName: table2
      label: InsertTable2
      transformer:
        columns:
          - column: table1_id
            value: $history.InsertTable1.$insertedId # the tranformed value of each previous loader is added to $history inside <LABEL> of the loader. For Insert loaders the inserted Id for AutoIncreament columns is added to $insertedId.
          - column: cap_title
            value: $history.InsertTable.title.toUperCase() # $history has access to the transformed $entity fields 

note: In case there is a precondition defined in the First Loader, and that precondition happened to evalute to false, the loader result won't be added to $history object

tableName

the table name field defined in loader can either be a string refering to the table name. or it can be an expression evaulated at run time. The expression has access to the following variables _, R (ramda), $entity, $source (in case of batchInsert and batchUpsert loaders)

loaders:
  - insert:
      tableName:
        exp: $entity.tableName