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

@icanbwell/fhirproof

v1.0.8

Published

FHIR Mongo object document mapper with built in validation

Downloads

22

Readme

FHIRProof

FHIRProof is an ODM for mongo that provides object document mapper to FHIR with all R4 resources and validation by default. It's meant to be heavily extensinble. It provides you the following mongo specific operations:

  • Model.create (validates by default)
  • Model.find
  • Model.findOne
  • Model.findById
  • Model.findByIdAndUpdate (validates by default)
  • Model.delete
  • Model.validate
  • Model.history
  • Model.historyByVersionId
  • instance.save (validates by default)
  • instance.toJSON (converts to fhir compliant JSON, removes invalid fhir expression)
  • instance.history
  • instance.historyByVersionId

It also exposes the collection on the constructor and object, so you can do anything via mongo you would normally do.

Installation

From your terminal, run

npm install --save @icanbwell/fhirproof

Examples

Connecting to MongoDB

Pass in your connection string and any options you would normally pass to Mongodb.

const { mongoconnect } = require('@icanbwell/fhirproof');
await mongoconnect(
  ('mongodb://localhost/my_database',
  {
    useNewUrlParser: true,
    useUnifiedTopology: true,
  })
);

Define a Model

You will want to inherit from the BaseFHIRModel for all FHIR Resources. Below is an example of creating an Organization resource. This format is required. You can do more, but this is the minimum.

// organization.model.js
const { BaseFHIRModel, CONSTANTS } = require('@icanbwell/fhirproof');

const resourceName = 'Organization';
const collectionName = CONSTANTS.COLLECTION.ORGANIZATION;
class Organization extends BaseFHIRModel {
  constructor(resource, _id) {
    super(collectionName, collectionName);
    this.resource = new this.Resource(resource);
    this._id = _id;
  }
}

Organization.resourceName = resourceName;
Organization.collectionName = collectionName;

module.exports = Organization;

And using it in a router.

//organization.route.js
const express = require('express');
const Organization = require('./organization.model');
const router = express.Router();

router
  .get('/Organization/:id', async (req, res, next) => {
    const organization = await Organization.initialize().findById(req.param.id);
    res.status(200).json(organization.toJSON());
  })
  .get('/Organization/:id/history', async (req, res, next) => {
    const organizationHistory = await Organization.initialize().history(
      req.param.id
    );
    res.status(200).json(organizationHistory);
  })
  .post('/Organization', async (req, res, next) => {
    try {
      const organization = await Organization.initialize().create(req.body);
      //try catch logic to handle 400s if desired.  Create validates by default
      res.status(201).json(organization.toJSON());
    } catch (error) {
      if (error.statusCode && error.operationOutcome) {
        // we handle status 400, 404 as extended error objects
        return res.status(error.statusCode).json(error.operationOutcome);
      }

      next(error);
    }
  });

module.exports = router;

Extending Models

Of course, everyone needs to add more capabilities to support their use case. Below are two examples. The first is adding a completely new method. The second is adding more functionality ontop of an existing method.

// organization.model.js from above
const { BaseFHIRModel, CONSTANTS } = require('@icanbwell/fhirproof');

const resourceName = 'Organization';
const collectionName = CONSTANTS.COLLECTION.ORGANIZATION;
class Organization extends BaseFHIRModel {
  constructor(resource, _id) {
    super(collectionName, collectionName);
    this.resource = new this.Resource(resource);
    this._id = _id;
  }

  //new search method
  static async search(queries, options) {
    // NOTICE: you have access to mongodb collection if you find yourself wanting to get closer to the metal
    // console.log(this.collection)
    // console.log(this.historyCollection)
    // console.log(this.db)
    // console.log(this.validator)
    const cursor = await this.collection.find(queries, options);
    return cursor.toArray();
  }

  //new instance method
  addExtension({ valueUrl, value }) {
    this.resource.extension.push({ valueUrl: value });
    return this.save();
  }

  //override create method to make sure organization.name is present
  static async create(payload) {
    if (!payload.name) {
      throw new Error('organization.name is required');
    }
    return await super.create(payload);
  }
}

Organization.resourceName = resourceName;
Organization.collectionName = collectionName;

Errors

We try to handle errors as http status code errors. When certain situations arise, we return these errors extending from the base Error object. See ./src/http-errors for more information. For bad request (status code 400), we have the BadRequestError with statusCode and operationOutcome property. For not found, (status code 404), we have the NotFoundError with statusCode and operationOutcome.

Playing in node console

# load configuration files to connect to mongo
node -r dotenv/config
> const { mongoconnect, BaseFHIRModel } = require('@icanbwell/fhirproof')
undefined
> mongoconnect('mongodb://localhost:27017')
Promise { <pending> }
> const Organization = require('./example/organization/organization.model')
undefined
> Organization
[class Organization extends BaseFHIRModel] {
  resourceName: 'Organization',
  collectionName: 'Organization'
}
Organization.initialize().create({resourceType: 'Organizatin', name: "This is a test"}).then(data => org = data).catch(err => error = err)
Promise { <pending> }
> org
Uncaught ReferenceError: org is not defined
> error
BadRequestError: Invalid resourceType 'Organizatin'
    at /Users/nathanhall/bwell/marketplace-vendor/marketplace-vendor-onboarding/src/server/fhir/base-fhir.model.js:54:23
    at new Promise (<anonymous>)
    at Function.create (/Users/nathanhall/bwell/marketplace-vendor/marketplace-vendor-onboarding/src/server/fhir/base-fhir.model.js:51:12)
    at REPL48:1:27
    at Script.runInThisContext (vm.js:133:18)
    at REPLServer.defaultEval (repl.js:484:29)
    at bound (domain.js:413:15)
    at REPLServer.runBound [as eval] (domain.js:424:12)
    at REPLServer.onLine (repl.js:817:10)
    at REPLServer.emit (events.js:327:22) {
  operationOutcome: { resourceType: 'OperationOutcome', issue: [ [Object] ] },
  statusCode: 400
}
> Organization.initialize().create({resourceType: 'Organization', name: "This is a test"}).then(data => org = data).catch(err => error = err)
Promise { <pending> }
org.toJSON()
{
  resourceType: 'Organization',
  id: '606c91947c00311223dfb08b',
  meta: { versionId: '1', lastUpdated: '2021-04-06T16:51:32+00:00' },
  name: 'This is a test'
}
> org.resource.name
'This is a test'
> org.resource.name = 'Update me'
'Update me'
> org.save()
Promise { <pending> }
> org.name
undefined
> org.resource.name
'Update me'
> org._id
606c91947c00311223dfb08b
> org.history().then(data => history = data)
Promise { <pending> }
> history
[
  {
    _id: 606c91947c00311223dfb08c,
    resourceType: 'Organization',
    meta: { versionId: '1', lastUpdated: '2021-04-06T16:51:32+00:00' },
    name: 'This is a test',
    id: 606c91947c00311223dfb08b
  },
  {
    _id: 606c91fe7c00311223dfb08d,
    resourceType: 'Organization',
    id: 606c91947c00311223dfb08b,
    meta: { versionId: '2', lastUpdated: '2021-04-06T16:53:18+00:00' },
    name: 'Update me'
  }
]

Adding Indexing

You'll probably want to add indexes to things you search for frequently. We tried to make it as easy as possible. Using mongoconnect, when successful, fires an event call 'mongoconnect'. Listening to this event with fhirProofEvent you can use our class method to create indexes, or operate on the collection directly.

//organization.model.js
const { fhirProofEvent } = require('@icanbwell/fhirproof');

// ...Other lines from  above
fhirProofEvent.on('mongoconnect', async () => {
  try {
    await Organization.initialize().createIndex(
      { name: 1 },
      {
        unique: true,
        sparse: true,
      }
    );

    // collection directly
    // await Organization.collection.createIndex(...args)
  } catch (error) {
    console.error(`[OrganizationModel] - Failed to create index ${error}`);
    throw error;
  }
});

Roadmap

  1. Support more FHIR Versions. Currently the default only supports R4 FHIR schemas
  2. Support more node versions. Will want to add in some transpiler to support back to node 8
  3. more!?

Contributing

The primary purpose of FHIRProof is to build a great data model for FHIR, mongo, and nodejs... making it faster and easier to use. To do this, we have FHIRProof on github for everyone to make it better.

Shout out to @asymmetrik/node-fhir-server-core and @asymmetrik/fhir-json-schema-validator for building the pieces to put this together.

Why Not Mongoose?

Because I couldn't find all the generated mongoose schemas in FHIR, I like JSON Schemas, and using ObjectRef is tricky with how FHIR does relations.

License

FHIRProof is MIT licensed.